Skip to main content
What you’ll be able to do
  • Read an account’s MIP20 token balance.
  • Send a direct payment to a recipient.
A token contract is a program on Metal that records how much of a particular token each account holds. For example, the *USDZ contract keeps track of accounts’ *USDZ balances. Call this contract to check balances and transfer tokens. For a single demonstration transfer, start with Make Your First Transfer. To deploy a new MIP20 token, use the MIP20 Factory.

Choose the token and contract call

Obtain the token’s network and contract address from its issuer and save both in the token configuration. Reads and transfers use that contract address. Different tokens can share a name or symbol, so those labels alone cannot identify the token. Before using the contract, call isMIP20(tokenAddress) on MIP20Factory to check that Metal recognises it as a MIP20 token.
MIP20 status does not establish who issued the token. Verify the issuer’s identity separately.
On testnet, *USDZ’s contract is at 0x000000000000000000000000000000000000b5d5 and has 18 decimals. Read decimals() for each configured token instead of assuming all tokens use the same precision. Read requests consume endpoint RPC credit. They do not submit transactions or incur transaction fees. balanceOf excludes tokens reserved in MIP20 holds. See the MIP20 reference for hold behavior. Install the public packages used below:

Read metadata

Check that Metal recognises the token as MIP20 and read its metadata. The code uses testnet *USDZ. For another token, substitute its verified address. These reads require no private key.
Save the chain ID, address, and decimals with the token configuration. Use the name and symbol for display after checking the issuer independently.

Read an account balance

Supply accountAddress from the application’s account record as a full EVM address. The account can be read without access to its signing key.
The result is a bigint in base units. Convert it to a display string with viem’s formatUnits(balance, decimals). Refresh the balance before spending because other transactions may have changed it.

Convert a payment amount

Supply amountText as a decimal string from the payment instruction. Reject excess precision before conversion so it cannot be silently rounded.
Keep amounts as integers during calculation and serialize them as decimal strings. This transfer requires a positive amount.

Prepare a transfer

Confirm that wallet’s signing account matches holder. Set recipientAddress from the validated payment instruction. Check the deployment and screen this exact token, sender, recipient and amount using Receive policy. Require the documented ReceivePolicy contract and compatible token deployment before relying on these rules. Screening is a preflight check, as policy can change before execution. Follow Estimate and record fees with this token, recipient, and amount. Use holder as sender and the quoted price as maxFeePerGas. Before signing, check the sender’s balance in the token being transferred and the signing account’s balance in the gas token. This transaction uses *USDZ as its gas token.
The transaction calls transfer on the token contract with the recipient and amount as arguments. Sending tokens from the signing account requires no spending approval. Save signed.txHash and signed.raw before calling await signed.send(). Verify the successful transaction receipt’s Transfer event against the token, holder, recipient, and amount. Confirm finality before crediting the payment.

Verify results and handle failures

Use Verify and reconcile payments to check execution, finality, and the expected transfer before crediting another system. The guide also covers preventing duplicate credit. A recipient’s receive policy can refuse a credit. In the default REVERT mode, the transfer reverts. With QUARANTINE enabled, execution can succeed while the tokens move to ReceivePolicy quarantine instead of the recipient. Require the exact recipient Transfer before marking the payment paid. If the transaction receipt contains a matching quarantine receipt, save the full tuple and recovery record, leave the payment unpaid, and reconcile the claim before authorizing another payment. A balance difference or successful transaction receipt alone cannot identify the intended payment. Check that an amount larger than the available balance is rejected before submission. For applications with interactive approval, use a controlled signer that rejects a request to test refusal. The signer configured with the test account’s private key signs automatically. Serialize transaction preparation and submission for each signing account to prevent concurrent transactions from using the same nonce.