Assets
Native programmable assets are a core concept in MOI. While MOI achieves high performance via parallelism, asset logic enables developers to define safe, native asset behavior at the protocol level. An asset’s rules—minting, burning, transfers, approvals, lockups, metadata—are enforced by the MOI asset engine and can only be accessed by an asset logic (written in Coco), attached to the asset, which defines the behaviour of the asset. This design gives you protocol-level safety (no userland balance bugs), flexible asset rules in asset logic and high performance through MOI’s parallel execution.
An asset is created by interaction on MOI, Coco can't directly create assets. When asset is created, its properties are defined (symbol, decimals, manager, limits, events), but an essential part of asset creation is also deployment of asset logic that can be written in Coco. Regular logics namely cannot talk to the asset engine directly; they must call the asset logic’s endpoints via an interface. This clean separation lets you expose a minimal, reviewed API for other logics while retaining privileged operations (e.g., mint/burn) under controlled endpoints.
Asset logic
Declare an asset logic by adding the asset
keyword after the coco
module declaration. Asset logics have access to the asset engine,
which provides the methods below to manage balances, allowances, and metadata.
Method | Args | Returns | Description |
---|---|---|---|
asset.Transfer | token_id U64, beneficiary Identifier, amount U256 | – | Transfer an approved amount to beneficiary . |
asset.TransferFrom | token_id U64, benefactor Identifier, beneficiary Identifier, amount U256 | – | Transfer an approved amount from benefactor to beneficiary . Anyone may call if approved. |
asset.Mint | token_id U64, beneficiary Identifier, amount U256 | – | Mint amount and credit beneficiary . |
asset.Burn | token_id U64, beneficiary Identifier, amount U256 | – | Burn amount from beneficiary . |
asset.Approve | token_id U64, beneficiary Identifier, amount U256, expires_at U64 | – | Approve up to amount for transfer to beneficiary until expires_at . Does not move funds. |
asset.Revoke | token_id U64, beneficiary Identifier | – | Revoke any existing approval for beneficiary . |
asset.Lockup | token_id U64, beneficiary Identifier, amount U256 | – | Lock amount for beneficiary (deducts immediately; cannot be revoked). |
asset.Release | token_id U64, benefactor Identifier, beneficiary Identifier, amount U256 | – | Release amount previously locked by benefactor to beneficiary . Anyone may call. |
asset.Symbol | – | symbol String | Asset symbol. |
asset.Decimals | – | decimals U64 | Number of decimals. |
asset.MaxSupply | – | max_supply U256 | Maximum supply. |
asset.CirculatingSupply | – | circulating_supply U256 | Total minted minus total burned. |
asset.BalanceOf | token_id U64, address Identifier | balance U256 | Balance of token_id at address . |
asset.Creator | – | creator Identifier | Creator address. |
asset.Manager | – | manager Identifier | Manager address. |
asset.EnableEvents | – | enable_events Bool | Whether asset emits events. |
asset.SetMetadata | key String, value Bytes | – | Set asset metadata entry. |
asset.GetMetadata | key String | value Bytes | Get asset metadata entry. |
asset.SetTokenMetadata | token_id U64, key String, value Bytes | – | Set token-scoped metadata for sender. |
asset.GetTokenMetadata | token_id U64, key String | value Bytes | Get token-scoped metadata for sender. |
coco asset MyAsset
// DefineAsset has to be customized for the asset
endpoint deploy DefineAsset():
asset.Define(
symbol: "MYASSET",
decimals: 2,
manager: Sender,
max_supply: U256(10000),
enable_events: true,
)
event AssetEvent:
topic operation String
field operator Identifier
field benefactor Identifier
field beneficiary Identifier
field amount U256
field expires_at U64
endpoint MyTransfer(beneficiary Identifier, amount U256):
if asset.EnableEvents():
emit AssetEvent{operation: "Transfer", operator: Sender, benefactor: Sender,
beneficiary: beneficiary, amount: amount}
// here the asset method is called, always for token 0 in this example
asset.Transfer(token_id: 0, beneficiary, amount)
Asset is created by passing the asset logic with asset properties in the interaction to create assets. In Cocolab it's simulated by issuing a command like
compile MyAsset from manifest(myasset.yaml)
create MyAsset(symbol: "MYASSET", decimals: 2, manager: default_user, max_supply: 10000, enable_events: true)
Once the asset is created, other logics can use the logic using an interface like in the example below.
coco RegularLogic
interface SomeAsset:
asset:
MyTransfer(receiver Identifier, amount U256)
// endpoint requires "asset" qualifier
endpoint Transfer(assetId Identifier, receiver Identifier, amount U256):
// we bind the interface to the concrete assetId
memory assetIface = SomeAsset(assetId)
// this is a call to asset logic, regular logics can't access "asset" object as the asset logic can
assetIface.MyTransfer(beneficiary: receiver, amount: amount)
// sends an asset with well known identifier to some constant receiver
const MYASSET Identifier = 0xd3b83c890d6ef90185c894e65052e2f18aefed8a171152d301bd20b9ae5ab9ed
const RECEIVER Identifier = 0xcadffe5d6654f1a6d2cc766d7ddaf8485307b2ebb351551b1a57bf1fcec54be5
endpoint MyPrecious(amount U256):
memory assetIface = SomeAsset(MYASSET)
assetIface.MyTransfer(beneficiary: RECEIVER, amount: amount)