This Move library provides a comprehensive framework for managing Aptos objects and fungible assets within a protocol context. It consists of two main modules:
protocol_object: Defines a generic framework for protocol-managed objects that wraps underlying Aptos Framework objectsprotocol_fungible_asset: Provides specialized functionality for managing fungible assets within the protocol framework
The library enables protocols to maintain secure, controlled access to object capabilities and fungible asset operations through a standardized capability delegation system.
A protocol-managed object is an object whose lifecycle and core functionalities (like extending its data, deriving new objects from it, transferring it, or deleting it) are explicitly controlled by a designated "protocol" module, rather than being freely accessible to any module or the object's owner.
This pattern solves several key problems compared to managing raw objects:
- Standardized Capability Handling: It provides a consistent mechanism for protocols to manage an object's fundamental capabilities (
ExtendRef,DeriveRef,DeleteRef,TransferRef). Instead of each protocol reinventing how to store or gate access to these,protocol_objectoffers a ready-to-use framework. - Protocol-Specific Logic Encapsulation: By controlling how and when capabilities are used, protocols can enforce their own rules and logic around object interactions. For example, a protocol might only allow an object to be extended if certain conditions are met, or it might manage transfers through a specific approval process.
- Enhanced Security and Control: It prevents unauthorized modification or misuse of objects by ensuring that critical operations can only be performed through the protocol's explicit authorization. This is achieved by storing capabilities within the object itself, but making them accessible only to the designated protocol.
The protocol_fungible_asset module extends the protocol framework to support Aptos fungible assets. It provides:
- Delegated Fungible Asset Capabilities: Mint, burn, transfer, and metadata mutation capabilities can be delegated to protocol control
- Protocol-Authorized Operations: All fungible asset operations (mint, burn, withdraw) require protocol authorization
- Standardized Asset Management: Consistent patterns for managing fungible asset lifecycles within protocol contexts
Both modules rely on two primary structs to manage capabilities in a controlled manner:
- Role: This struct acts as a persistent store for capabilities. When protocol-managed objects or fungible assets are created, instances of
Capabilityare stored directly on the object for each relevant capability (e.g.,ExtendRef,DeriveRef,DeleteRef,TransferRef,MintRef,BurnRef). - Protocol Control: The
Pis a phantom type parameter representing the specificProtocolthat manages this capability. This ensures that only the designated protocol can extract and use the capability. - Resource Type: The
Rparameter represents the actual type of the capability being stored (e.g.,object::ExtendRef,fungible_asset::MintRef).
- Role: This struct represents a temporary, "borrowed" reference to a capability
R, under the control ofProtocolP. When a protocol needs to use a capability, it callsextract_refto move theCapability<P, R>from storage and returns aRef<P, R>. - Safe Borrowing Mechanism: The protocol can use the
innerfield of theRefto perform operations. Once done, the protocol must callreturn_refto move the capability back to storage. - Ensuring Controlled Access: This extract/return mechanism ensures capabilities are not duplicated or lost, and are always returned to object control under protocol supervision.
- Phantom Types for Safety: The phantom type
Pensures that aRefextracted by one protocol cannot be accidentally used by another. TheRensures type safety for the specific capability.
To use this module in your Aptos Move project:
- Add
protocol-objectas a dependency in yourMove.toml:Replace[dependencies] protocol_object = { git = "https://github.com/yeap-finance/protocol-object.git", rev = "<specific-commit-hash-or-tag>" } # e.g., rev = "main" or a specific commit SHA
<specific-commit-hash-or-tag>with the desired version (e.g., a commit SHA, tag, or branch likemain). - Import the module in your Move code:
use protocol_object::protocol_object; // If your module also needs to define its own named address for protocol_object, // ensure it matches the one used by the protocol_object module itself (typically defined in its own Move.toml).
- Compile your project:
When you compile your project, the Aptos CLI will fetch and build the dependency. You'll typically specify the named address for your own module(s).
aptos move compile --named-addresses your_module_name=<your-account-address>
If you've cloned this protocol-object repository directly and want to build or test it as a standalone project:
- Navigate to the cloned directory.
- Compile the module:
aptos move compile --named-addresses protocol_object=<your-account-address>
- Run tests (if available):
Replace
aptos move test --named-addresses protocol_object=<your-account-address>
<your-account-address>with an Aptos account address for local testing and deployment (e.g., a profile fromaptos init).
This module is intended to be used by other Move modules that act as "protocols" to manage objects.
-
extract_ref<Protocol, T: store>(_protocol: &Protocol, object_address: address): Ref<Protocol, T>Extracts a capabilityTfrom the object atobject_addressand wraps it in aRef<Protocol, T>. This implements the "borrow" mechanism: it removes theCapability<Protocol, T>from storage and gives the protocol temporary exclusive access. -
return_ref<Protocol, T: store>(ref: Ref<Protocol, T>)Returns a previously extractedRef<Protocol, T>back to the object's storage. This is the "return" part of the mechanism that ensures capabilities are safely returned to object control. -
ref<P, R>(self: &Ref<P, R>): &RReturns an immutable reference to the inner capability from aRef.
-
delegate_all<Protocol>(constructor_ref: &ConstructorRef)Delegates all available capabilities of an object to a specific protocol during initialization. Automatically delegatesExtendRef,DeriveRef, and conditionally delegatesDeleteRefandTransferRef. -
delegate_extend_ref<Protocol>(object_ref: &ConstructorRef)Delegates anExtendRefcapability to the protocol. -
delegate_derive_ref<Protocol>(object_ref: &ConstructorRef)Delegates aDeriveRefcapability to the protocol. -
delegate_delete_ref<Protocol>(object_ref: &ConstructorRef)Delegates aDeleteRefcapability to the protocol (if the object can be deleted). -
delegate_transfer_ref<Protocol>(object_ref: &ConstructorRef)Delegates aTransferRefcapability to the protocol (if the object is transferable). -
object_signer<Protocol>(_protocol: &Protocol, object_address: address): signerGets the signer for the object using itsExtendRefcapability.
-
is_delegated<Protocol, R: store>(object: address): bool[view function] Checks if a specific capability is delegated to the protocol on the given object. -
revoke<Protocol, R: store>(_p: &Protocol, object_address: address): RRevokes a specific capability from a protocol-managed object and returns it. -
revoke_all<Protocol>(_protocol: &Protocol, object_address: address)Revokes all capabilities from a protocol-managed object. Useful for cleanup.
-
disable_ungated_transfer<Protocol>(_protocol: &Protocol, object_address: address)Disables direct transfers by the object owner; transfers must be mediated by the protocol. -
enable_ungated_transfer<Protocol>(_protocol: &Protocol, object_address: address)Enables direct transfers by the object owner. -
generate_linear_transfer_ref<Protocol>(_protocol: &Protocol, object_address: address): LinearTransferRefGenerates aLinearTransferReffor one-time transfer scenarios. -
grant_permission<Protocol, T: key>(_protocol: &Protocol, permissioned_signer: &signer, object: Object<T>)Grants transfer permission to another signer.
-
extract_ref<Protocol, T: store>(_protocol: &Protocol, object_address: address): Ref<Protocol, T>Extracts a fungible asset capability from the asset object. -
return_ref<Protocol, T: store>(ref: Ref<Protocol, T>)Returns a previously extracted fungible asset capability. -
ref<P, R>(self: &Ref<P, R>): &RReturns an immutable reference to the inner capability.
-
delegate_all(asset_object_ref: &ConstructorRef)Delegates all standard fungible asset capabilities in a single call, using capability types as protocol parameters. -
delegate_mint_ref<P>(asset_object_ref: &ConstructorRef)Delegates aMintRefcapability to the protocol for the fungible asset. -
delegate_burn_ref<P>(asset_object_ref: &ConstructorRef)Delegates aBurnRefcapability to the protocol. -
delegate_transfer_ref<P>(asset_object_ref: &ConstructorRef)Delegates aTransferRefcapability to the protocol. -
delegate_mutate_metadata_ref<P>(asset_object_ref: &ConstructorRef)Delegates aMutateMetadataRefcapability to the protocol. -
delegate_raw_supply_ref<P>(asset_object_ref: &ConstructorRef)Delegates aRawSupplyRefcapability to the protocol for direct supply management. -
delegate_raw_balance_ref<P>(asset_object_ref: &ConstructorRef)Delegates aRawBalanceRefcapability to the protocol for direct balance management.
-
revoke<Protocol, R: store>(_p: &Protocol, object_address: address): RRevokes a specific capability from a protocol-managed fungible asset and returns it. -
revoke_all<Protocol>(_p: &Protocol, object_address: address)Revokes all standard fungible asset capabilities from a protocol-managed asset. -
is_delegated<Protocol, R: store>(object: address): bool[view function] Checks if a specific capability is delegated to the protocol on the given fungible asset.
-
mint<P>(_p: &P, asset_metadata: &Object<Metadata>, amount: u64): FungibleAssetMints a specified amount of a fungible asset, authorized by protocolP. -
burn<P>(_p: &P, asset_metadata: &Object<Metadata>, asset: FungibleAsset)Burns aFungibleAsset, authorized by protocolP. -
withdraw<P>(_p: &P, store: Object<FungibleStore>, amount: u64): FungibleAssetWithdraws a specified amount from aFungibleStore, authorized by protocolP.
-
mutate_metadata<P>(_p: &P, metadata: Object<Metadata>, name: Option<String>, symbol: Option<String>, decimals: Option<u8>, icon_uri: Option<String>, project_uri: Option<String>)Mutates the metadata of a fungible asset, authorized by protocolP. -
raw_supply<P>(_p: &P, metadata: Object<Metadata>): Option<u128>Gets the raw supply of a fungible asset using protocol authorization. -
raw_balance<P>(_p: &P, store: Object<FungibleStore>): u64Gets the raw balance of a fungible store using protocol authorization.
Here's how to create a protocol that manages objects:
module your_addr::my_protocol_module {
use std::signer;
use aptos_framework::object::{Self, Object, ExtendRef, ConstructorRef};
use protocol_object::protocol_object;
/// Define a struct representing our protocol
struct MyProtocol has key {
admin: address,
}
/// Initialize your protocol
public entry fun initialize_protocol(sender: &signer) {
move_to(sender, MyProtocol { admin: signer::address_of(sender) });
}
/// Create a new object managed by MyProtocol
public entry fun create_my_object(sender: &signer, owner: address) {
let protocol_instance = borrow_global<MyProtocol>(signer::address_of(sender));
// Create a named object
let constructor_ref = object::create_named_object(sender, b"my_object");
// Delegate capabilities to the protocol
protocol_object::delegate_all<MyProtocol>(&constructor_ref);
// Transfer to desired owner
let object = object::object_from_constructor_ref<object::ObjectCore>(&constructor_ref);
object::transfer(sender, object, owner);
}
/// Example of using extracted capabilities
public fun extend_my_object_data(
protocol_signer: &signer,
object_address: address,
) acquires MyProtocol {
let protocol_instance = borrow_global<MyProtocol>(signer::address_of(protocol_signer));
// Extract the ExtendRef capability
let extend_capability_ref = protocol_object::extract_ref<MyProtocol, ExtendRef>(
protocol_instance,
object_address
);
// Get a reference to the actual ExtendRef
let extend_ref = protocol_object::ref(&extend_capability_ref);
// Use the ExtendRef to add resources to the object
let object_signer = protocol_object::object_signer(protocol_instance, object_address);
// move_to(&object_signer, YourDataStruct { /* ... */ });
// Return the capability
protocol_object::return_ref<MyProtocol, ExtendRef>(extend_capability_ref);
}
}Here's how to create a protocol that manages fungible assets:
module your_addr::my_token_protocol {
use std::signer;
use std::string;
use aptos_framework::object::{Self, Object};
use aptos_framework::fungible_asset::{Self, Metadata, FungibleAsset, MintRef, BurnRef};
use protocol_object::protocol_fungible_asset;
struct MyTokenProtocol has key {
admin: address,
}
/// Initialize the token protocol
public entry fun initialize_protocol(sender: &signer) {
move_to(sender, MyTokenProtocol { admin: signer::address_of(sender) });
}
/// Create a new fungible asset managed by the protocol
public entry fun create_token(
sender: &signer,
name: string::String,
symbol: string::String,
decimals: u8,
icon_uri: string::String,
project_uri: string::String,
): Object<Metadata> {
// Create the fungible asset
let constructor_ref = &object::create_named_object(sender, *string::bytes(&name));
fungible_asset::create_metadata(
constructor_ref,
100, // max_supply (optional)
name,
symbol,
decimals,
icon_uri,
project_uri,
);
// Delegate capabilities to our protocol
protocol_fungible_asset::delegate_mint_ref<MyTokenProtocol>(constructor_ref);
protocol_fungible_asset::delegate_burn_ref<MyTokenProtocol>(constructor_ref);
protocol_fungible_asset::delegate_transfer_ref<MyTokenProtocol>(constructor_ref);
protocol_fungible_asset::delegate_mutate_metadata_ref<MyTokenProtocol>(constructor_ref);
object::object_from_constructor_ref<Metadata>(constructor_ref)
}
/// Mint tokens using protocol authorization
public fun mint_tokens(
protocol_signer: &signer,
asset_metadata: &Object<Metadata>,
amount: u64,
): FungibleAsset acquires MyTokenProtocol {
let protocol = borrow_global<MyTokenProtocol>(signer::address_of(protocol_signer));
protocol_fungible_asset::mint<MyTokenProtocol>(protocol, asset_metadata, amount)
}
/// Burn tokens using protocol authorization
public fun burn_tokens(
protocol_signer: &signer,
asset_metadata: &Object<Metadata>,
asset: FungibleAsset,
) acquires MyTokenProtocol {
let protocol = borrow_global<MyTokenProtocol>(signer::address_of(protocol_signer));
protocol_fungible_asset::burn<MyTokenProtocol>(protocol, asset_metadata, asset);
}
/// Withdraw from a store using protocol authorization
public fun protocol_withdraw(
protocol_signer: &signer,
store: Object<fungible_asset::FungibleStore>,
amount: u64,
): FungibleAsset acquires MyTokenProtocol {
let protocol = borrow_global<MyTokenProtocol>(signer::address_of(protocol_signer));
protocol_fungible_asset::withdraw<MyTokenProtocol>(protocol, store, amount)
}
}Functions within both modules use assert! to enforce preconditions and maintain invariants. If an assertion fails, the transaction will abort.
E_NOT_OBJECT(Error Code1): Occurs when an operation expects an object but it's not found or is not a valid object. This can happen if:return_refis called but the object has been deleted- Required
Capabilityresources are missing from the object - Invalid object addresses are provided to functions
- Always ensure object addresses are valid before calling functions
- Verify that the protocol has been correctly initialized for objects/assets
- Confirm necessary capabilities exist on objects before extraction
- Always pair
extract_refcalls with correspondingreturn_refcalls - Handle capability extraction/return in the same function when possible to prevent capability leaks
For operations requiring multiple capabilities, you can extract multiple capabilities:
public fun complex_operation(protocol: &MyProtocol, object_addr: address) {
let extend_ref = protocol_object::extract_ref<MyProtocol, ExtendRef>(protocol, object_addr);
let derive_ref = protocol_object::extract_ref<MyProtocol, DeriveRef>(protocol, object_addr);
// Perform operations with both capabilities
// ...
// Return capabilities
protocol_object::return_ref(derive_ref);
protocol_object::return_ref(extend_ref);
}You can check if capabilities are delegated and manage them:
public fun manage_capabilities(protocol: &MyProtocol, object_addr: address) {
// Check if capabilities are delegated
let has_extend = protocol_object::is_delegated<MyProtocol, ExtendRef>(object_addr);
let has_delete = protocol_object::is_delegated<MyProtocol, DeleteRef>(object_addr);
if (has_delete) {
// Revoke a specific capability
let delete_ref = protocol_object::revoke<MyProtocol, DeleteRef>(protocol, object_addr);
// Use the delete_ref or store it elsewhere
object::delete(delete_ref);
};
// Or revoke all capabilities at once for cleanup
// protocol_object::revoke_all<MyProtocol>(protocol, object_addr);
}public fun manage_asset_capabilities(protocol: &MyTokenProtocol, asset_addr: address) {
// Check and revoke specific fungible asset capabilities
let mint_ref = protocol_fungible_asset::revoke<MyTokenProtocol, fungible_asset::MintRef>(
protocol,
asset_addr
);
// Use the mint_ref directly or delegate to another protocol
let assets = fungible_asset::mint(&mint_ref, 1000);
// Query asset information using protocol authorization
let metadata = object::address_to_object<Metadata>(asset_addr);
let supply = protocol_fungible_asset::raw_supply<MyTokenProtocol>(protocol, metadata);
// Clean up all capabilities when done
// protocol_fungible_asset::revoke_all<MyTokenProtocol>(protocol, asset_addr);
}Protocols can compose with each other by delegating sub-capabilities:
public fun delegate_to_subprotocol<MainProtocol, SubProtocol>(
main_protocol: &MainProtocol,
object_addr: address,
constructor_ref: &ConstructorRef
) {
// Main protocol can delegate specific capabilities to sub-protocols
protocol_object::delegate_mint_ref<SubProtocol>(constructor_ref);
// while retaining others
protocol_object::delegate_burn_ref<MainProtocol>(constructor_ref);
}This project is licensed under the LICENSE file.