Extension Authorization Flow
EVE Frontier Smart Storage Units (SSUs) support third-party extensions -- external Move packages that can read and write inventory data. But an SSU will not let just any code touch its inventories. Extensions must be explicitly authorized by the SSU owner.
This chapter walks through the full authorization lifecycle from deployment to first use.
The Big Picture
``
└─ init() creates AdminCap + ExtensionConfig
└─ Package defines XAuth witness type
└─ Calls storage_unit::authorize_extension
└─ SSU now trusts code that can produce XAuth values
└─ Calls set_tribe_config(config, admin_cap, tribe_id)
└─ Extension knows which tribe to gate for
└─ Call share() or withdraw()
└─ Extension passes XAuth{} to gated SSU functions
`
Step 1: Deploy the Package
When you publish the
tribe_storage_access package with sui client publish, the init function runs automatically:
movefun init(ctx: &mut TxContext) {
let admin_cap = AdminCap { id: object::new(ctx) };
transfer::transfer(admin_cap, ctx.sender());
let config = ExtensionConfig { id: object::new(ctx) };
transfer::share_object(config);
}
After publishing, you have:
- An
AdminCap object owned by your wallet address
An ExtensionConfig shared object accessible to everyone
A package containing the XAuth type at
Step 2: SSU Owner Authorizes the Extension
The SSU owner must explicitly grant your extension access. This is done by calling
storage_unit::authorize_extension with your XAuth type as the type argument.
This is a multi-step Programmable Transaction Block (PTB) because the owner's capability token (
OwnerCap) is stored inside their Character object and must be borrowed:
typescriptconst { Transaction, Inputs } = await import('@mysten/sui/transactions');
const tx = new Transaction();
const xAuthType = ${TRIBE_STORAGE_PACKAGE_ID}::config::XAuth;
const ssuType = ${EVE_FRONTIER_PACKAGE_ID}::storage_unit::StorageUnit;
// Step 1: Borrow the OwnerCap<StorageUnit> from the Character
const [borrowedCap, receipt] = tx.moveCall({
target: ${EVE_FRONTIER_PACKAGE_ID}::character::borrow_owner_cap,
typeArguments: [ssuType],
arguments: [
tx.object(characterObjectId),
tx.object(Inputs.ReceivingRef({
objectId: ownerCapId,
version: capRef.version,
digest: capRef.digest,
})),
],
});
// Step 2: Authorize the extension on the SSU
tx.moveCall({
target: ${EVE_FRONTIER_PACKAGE_ID}::storage_unit::authorize_extension,
typeArguments: [xAuthType],
arguments: [tx.object(ssuId), borrowedCap],
});
// Step 3: Return the borrowed OwnerCap
tx.moveCall({
target: ${EVE_FRONTIER_PACKAGE_ID}::character::return_owner_cap,
typeArguments: [ssuType],
arguments: [tx.object(characterObjectId), borrowedCap, receipt],
});
The Borrow-Use-Return Pattern
EVE Frontier's
OwnerCap objects live inside Character objects as received objects. You cannot use them directly -- you must:
Borrow the cap with borrow_owner_cap, which returns (OwnerCap, BorrowReceipt)
Use the cap for your operation (in this case, authorize_extension)
Return the cap with return_owner_cap, passing back the receipt
The receipt ensures the cap is always returned. If you try to drop it, compilation fails (it lacks
drop ability). This is the "hot potato" pattern -- you are forced to deal with it.
Step 3: Configure the Extension
After the SSU is authorized, the admin sets the tribe restriction:
movepublic fun set_tribe_config(
extension_config: &mut ExtensionConfig,
admin_cap: &AdminCap,
tribe_id: u32,
) {
config::set_rule<TribeConfigKey, TribeConfig>(
extension_config,
admin_cap,
TribeConfigKey {},
TribeConfig { tribe: tribe_id },
);
}
This stores the tribe ID as a dynamic field on
ExtensionConfig. Only the AdminCap holder can call this.
From TypeScript:
typescriptconst tx = new Transaction();
tx.moveCall({
target: ${TRIBE_STORAGE_PACKAGE_ID}::tribe_storage::set_tribe_config,
arguments: [
tx.object(TRIBE_STORAGE_CONFIG_ID), // shared ExtensionConfig
tx.object(adminCapId), // owned AdminCap
tx.pure.u32(tribeId), // tribe ID to gate for
],
});
Step 4: Players Use the Extension
Now tribe members can share and withdraw items. Here is the Move-side
share() function:
movepublic 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(), // <-- XAuth witness proves authorization
ctx,
);
}
The key line is
config::x_auth(). This produces an XAuth {} value that the SSU's deposit_to_open_inventory function checks against its authorized extensions list. Since the SSU owner authorized XAuth in step 2, this call succeeds.
Authorization Security Model
The security of this system relies on three properties:
Type uniqueness: XAuth is defined in your package. No other package can define the same fully-qualified type .
Visibility restriction: x_auth() is public(package), so only your modules can produce XAuth values. Even if someone knows the type, they cannot instantiate it from outside your package.
Explicit opt-in: The SSU owner must call authorize_extension to allow your type. Your code cannot authorize itself.
`
┌─────────────────────────────────────────────────┐
│ Can produce XAuth{}? │
│ │
│ tribe_storage_access::tribe_storage ✓ YES │
│ tribe_storage_access::config ✓ YES │
│ some_other_package::evil_module ✗ NO │
│ direct PTB transaction ✗ NO │
└─────────────────────────────────────────────────┘
`
Revoking Authorization
The SSU owner can revoke an extension's authorization at any time:
movestorage_unit::revoke_extension<XAuth>(ssu, owner_cap);
After revocation, any calls using
XAuth will fail, even though the extension's code is still published on-chain.
Key Takeaways
- Extensions are authorized per-SSU by the SSU owner calling
authorize_extension.
The XAuth witness type is the trust anchor -- only the defining package can produce values of this type.
The borrow-use-return pattern with OwnerCap is mandatory for SSU operations, enforced by the hot potato BorrowReceipt`.
Sign in to track your progress.