Cycle

The Scriptorium

Smart Assembly code templates and tools for on-chain development in Eve Frontier.

Tribe-Gated Storage
IntermediateChapter 3 of 520 min read

Share and Withdraw Functions

The tribe_storage.move module implements the core player-facing operations: moving items into and out of the shared tribe pool. There are three functions that handle different scenarios based on whether the caller is the SSU owner or a visiting player.

The Import Block

move
module tribe_storage_access::tribe_storage;

use tribe_storage_access::config::{Self, AdminCap, XAuth, ExtensionConfig};
use world::access::OwnerCap;
use world::character::Character;
use world::storage_unit::{Self, StorageUnit};

The module imports from two sources:

  • tribe_storage_access::config -- our own config module for ExtensionConfig, AdminCap, and the XAuth witness.
  • world::* -- EVE Frontier's on-chain framework providing OwnerCap, Character, and StorageUnit types.

Error Constants

move
#[error(code = 0)]
const ENotTribeMember: vector<u8> = b"Character is not in the configured tribe";

#[error(code = 1)]
const ENoTribeConfig: vector<u8> = b"Tribe configuration not set on ExtensionConfig";

SUI Move errors use the #[error] attribute. When an assert! fails with one of these constants, the error message is included in the transaction failure response, making debugging much easier.

share() -- Non-Owner Sharing

The share() function is for non-owner players -- players who have docked at the SSU and have items in their personal per-player slot.

move
public fun share<T: key>(
    extension_config: &ExtensionConfig,
    storage_unit: &mut StorageUnit,
    character: &Character,
    owner_cap: &OwnerCap<T>,
    type_id: u64,
    quantity: u32,
    ctx: &mut TxContext,
) {
    verify_tribe(extension_config, character);

    let item = storage_unit::withdraw_by_owner(
        storage_unit,
        character,
        owner_cap,
        type_id,
        quantity,
        ctx,
    );

    storage_unit::deposit_to_open_inventory<XAuth>(
        storage_unit,
        character,
        item,
        config::x_auth(),
        ctx,
    );
}

Step-by-step breakdown:

  • verify_tribe(extension_config, character) -- Checks that the character belongs to the configured tribe. Aborts if not.
  • storage_unit::withdraw_by_owner(...) -- Withdraws items from the player's personal slot. This requires the player's OwnerCap as proof of identity. The function returns an Item value representing the withdrawn goods.
  • storage_unit::deposit_to_open_inventory(...) -- Deposits the item into the SSU's open inventory (the shared tribe pool). This requires the XAuth witness, produced by config::x_auth().
  • The Type Parameter

    The share function is generic over T: key. This allows the OwnerCap to be parameterized by any object type (e.g., OwnerCap). In practice, non-owners always pass OwnerCap.

    share_from_main() -- Owner Sharing

    The SSU owner's items are in the main inventory rather than a per-player slot. This requires different withdrawal logic:

    move
    public fun share_from_main(
        extension_config: &ExtensionConfig,
        storage_unit: &mut StorageUnit,
        character: &Character,
        type_id: u64,
        quantity: u32,
        ctx: &mut TxContext,
    ) {
        verify_tribe(extension_config, character);
    
        let item = storage_unit::withdraw_item<XAuth>(
            storage_unit,
            character,
            config::x_auth(),
            type_id,
            quantity,
            ctx,
        );
    
        storage_unit::deposit_to_open_inventory<XAuth>(
            storage_unit,
            character,
            item,
            config::x_auth(),
            ctx,
        );
    }

    Key Differences from share()

    Aspectshare() (non-owner)share_from_main() (owner)
    Withdrawal functionwithdraw_by_ownerwithdraw_item
    Auth for withdrawalOwnerCapXAuth witness
    Source inventoryPer-player slotMain inventory
    Needs OwnerCap paramYesNo

    The owner does not need to pass an OwnerCap because withdraw_item uses the XAuth witness for authorization instead. The SSU framework recognizes the character as the owner and grants access to the main inventory.

    withdraw() -- Taking from the Shared Pool

    The withdraw() function moves items from the open inventory back to a player's personal slot:

    move
    public fun withdraw(
        extension_config: &ExtensionConfig,
        storage_unit: &mut StorageUnit,
        character: &Character,
        type_id: u64,
        quantity: u32,
        to_ssu_owner: bool,
        ctx: &mut TxContext,
    ) {
        verify_tribe(extension_config, character);
    
        // Withdraw from the shared tribe pool (open inventory)
        let item = storage_unit::withdraw_from_open_inventory<XAuth>(
            storage_unit,
            character,
            config::x_auth(),
            type_id,
            quantity,
            ctx,
        );
    
        // Deposit to the caller's personal slot
        if (to_ssu_owner) {
            storage_unit::deposit_item<XAuth>(
                storage_unit, character, item, config::x_auth(), ctx,
            );
        } else {
            storage_unit::deposit_to_owned<XAuth>(
                storage_unit, character, item, config::x_auth(), ctx,
            );
        };
    }

    The to_ssu_owner Flag

    The withdraw() function uses a boolean flag to determine the destination:

    • to_ssu_owner = true: Calls deposit_item, which puts items into the SSU owner's main inventory. Used when the caller IS the SSU owner.
    • to_ssu_owner = false: Calls deposit_to_owned, which puts items into the caller's per-player slot. Used when the caller is a visiting player.

    This flag is determined client-side by checking whether the connected wallet owns the SSU.

    Deposit Function Summary

    FunctionDestinationWho uses it
    deposit_to_open_inventoryOpen inventory (shared pool)share() and share_from_main()
    deposit_itemMain inventory (owner's slot)withdraw() when to_ssu_owner = true
    deposit_to_ownedPer-player slotwithdraw() when to_ssu_owner = false

    Calling from TypeScript

    Withdraw Call

    typescript
    const tx = new Transaction();
    
    tx.moveCall({
      target: ${TRIBE_STORAGE_PACKAGE_ID}::tribe_storage::withdraw,
      arguments: [
        tx.object(TRIBE_STORAGE_CONFIG_ID),   // ExtensionConfig (shared)
        tx.object(ssuId),                      // StorageUnit (shared)
        tx.object(characterObjectId),          // Character (owned)
        tx.pure.u64(typeId),                   // item type ID
        tx.pure.u32(quantity),                 // how many
        tx.pure.bool(isOwner),                 // to_ssu_owner flag
      ],
    });

    Share Call (Non-Owner)

    The non-owner share is more complex because it requires borrowing OwnerCap:

    typescript
    const characterType = ${EVE_FRONTIER_PACKAGE_ID}::character::Character;
    
    // Step 1: Borrow the OwnerCap
    const [ownerCap, receipt] = tx.moveCall({
      target: ${EVE_FRONTIER_PACKAGE_ID}::character::borrow_owner_cap,
      typeArguments: [characterType],
      arguments: [
        tx.object(characterObjectId),
        tx.object(Inputs.ReceivingRef({
          objectId: characterOwnerCapId,
          version: capRef.version,
          digest: capRef.digest,
        })),
      ],
    });
    
    // Step 2: Call share with the borrowed cap
    tx.moveCall({
      target: ${TRIBE_STORAGE_PACKAGE_ID}::tribe_storage::share,
      typeArguments: [characterType],
      arguments: [
        tx.object(TRIBE_STORAGE_CONFIG_ID),
        tx.object(ssuId),
        tx.object(characterObjectId),
        ownerCap,
        tx.pure.u64(typeId),
        tx.pure.u32(quantity),
      ],
    });
    
    // Step 3: Return the borrowed cap
    tx.moveCall({
      target: ${EVE_FRONTIER_PACKAGE_ID}::character::return_owner_cap,
      typeArguments: [characterType],
      arguments: [tx.object(characterObjectId), ownerCap, receipt],
    });

    Share Call (Owner)

    The owner path is simpler -- no cap borrowing needed:

    typescript
    tx.moveCall({
      target: ${TRIBE_STORAGE_PACKAGE_ID}::tribe_storage::share_from_main,
      arguments: [
        tx.object(TRIBE_STORAGE_CONFIG_ID),
        tx.object(ssuId),
        tx.object(characterObjectId),
        tx.pure.u64(typeId),
        tx.pure.u32(quantity),
      ],
    });

    Key Takeaways

    • There are three entry functions: share() for non-owners, share_from_main() for the SSU owner, and withdraw() for both.
    • The SSU framework uses different functions for different inventory slots: withdraw_by_owner vs withdraw_item, deposit_to_owned vs deposit_item vs deposit_to_open_inventory.
    • All gated SSU operations require the XAuth witness, produced by config::x_auth().
    • The to_ssu_owner boolean in withdraw() routes items to the correct destination inventory.
    • Non-owner share operations require borrowing OwnerCap via the borrow-use-return pattern in the PTB.

    Sign in to track your progress.