Skip to main content

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.

Coco assets overview: logic → asset logic → MOI asset engine

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.

MethodArgsReturnsDescription
asset.Transfertoken_id U64, beneficiary Identifier, amount U256Transfer an approved amount to beneficiary.
asset.TransferFromtoken_id U64, benefactor Identifier, beneficiary Identifier, amount U256Transfer an approved amount from benefactor to beneficiary. Anyone may call if approved.
asset.Minttoken_id U64, beneficiary Identifier, amount U256Mint amount and credit beneficiary.
asset.Burntoken_id U64, beneficiary Identifier, amount U256Burn amount from beneficiary.
asset.Approvetoken_id U64, beneficiary Identifier, amount U256, expires_at U64Approve up to amount for transfer to beneficiary until expires_at. Does not move funds.
asset.Revoketoken_id U64, beneficiary IdentifierRevoke any existing approval for beneficiary.
asset.Lockuptoken_id U64, beneficiary Identifier, amount U256Lock amount for beneficiary (deducts immediately; cannot be revoked).
asset.Releasetoken_id U64, benefactor Identifier, beneficiary Identifier, amount U256Release amount previously locked by benefactor to beneficiary. Anyone may call.
asset.Symbolsymbol StringAsset symbol.
asset.Decimalsdecimals U64Number of decimals.
asset.MaxSupplymax_supply U256Maximum supply.
asset.CirculatingSupplycirculating_supply U256Total minted minus total burned.
asset.BalanceOftoken_id U64, address Identifierbalance U256Balance of token_id at address.
asset.Creatorcreator IdentifierCreator address.
asset.Managermanager IdentifierManager address.
asset.EnableEventsenable_events BoolWhether asset emits events.
asset.SetMetadatakey String, value BytesSet asset metadata entry.
asset.GetMetadatakey Stringvalue BytesGet asset metadata entry.
asset.SetTokenMetadatatoken_id U64, key String, value BytesSet token-scoped metadata for sender.
asset.GetTokenMetadatatoken_id U64, key Stringvalue BytesGet 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)