Skip to main content
MIP20 is Metal’s balance-based fungible token interface. It supports ERC-20 transfers and ERC-3009 authorizations. MIP20 calls the shared receive policy contract to check the recipient’s stored rules. Applications can use a send policy contract to check outgoing rules before requesting a token transfer.

Constructor

A nonzero initialSupply increases total supply during construction. If the initial holder refuses the credit, the mint goes to ReceivePolicy quarantine. Application-created tokens use the owner-managed MetalToken extension deployed by MIP20Factory. Verify deployment and recipient credit separately.

Metadata and state getters

authorizationState == true means the nonce was consumed by a transfer or cancellation. The transfer may have credited quarantine, so inspect the matching events to determine the payment outcome.

Storage layout

Protocol gas accounting reads MIP20 balances at storage slot 0. Applications read balances, allowances, holds and authorizations through the public getters above. Verify MIP20 status through the configured factory’s isMIP20.

Transfers and allowances

transferFrom does not reduce an allowance equal to type(uint256).max. Finite allowance consumption does not emit an additional Approval event. Read allowance to establish the remaining permission. A transfer rejects zero sender or recipient addresses and direct transfers to ReceivePolicy. An accepted credit emits Transfer(from, to, amount). A refused credit either reverts or emits a transfer to the ReceivePolicy escrow, according to the refusal rules. Match the destination in the emitted event to determine where the funds went.

Approve a spender

An allowance lets a specified account or contract spend up to a chosen amount of a holder’s tokens. The holder grants that permission by calling approve on the token contract. The approved spender can then call transferFrom to send tokens or holdFrom to set them aside for a payment. Use the public client from Connect to Metal and a wallet client named wallet, configured with the holder’s signing account as described in Configure the test signer. Both clients must use the same verified network. Install the public packages:
Verify the token address using Read metadata. For a new allowance, validate and convert the spending limit to the positive base-unit amount using Convert a payment amount. Obtain spenderAddress from the account configuration or contract deployment record. For testing, use a spender you control. Read its current allowance:
Set approvalAmount to the validated amount to grant permission, or 0n to revoke it. Grant the amount required for the operation. A new approval replaces the remaining allowance, including a nonzero allowance. If your application requires revocation before replacement, submit and verify approve(spender, 0) first. Estimate each approval using Estimate and record fees, with holder as the sender, approve as the function, and [spender, approvalAmount] as its arguments. Omit fee fields during estimation. Check that the signing account’s balance in the gas token covers the maximum gas cost. The following call pays gas in testnet *USDZ. Use the gasLimit and maxFeePerGas from that exact call’s estimate:
Keep each reset, grant, or revocation in its own saved transaction attempt. Save approval.txHash and approval.raw before calling await approval.send(). Require a successful receipt, block finality, and an Approval event from the token with the expected holder, spender, and amount. Read allowance(holder, spender) again using the read above. Verify zero after a reset or revocation before proceeding. Process allowance changes sequentially. A spender can act until revocation executes and may consume a new allowance immediately, so investigate an unexpected lower value against its spending activity. If submission or receipt polling fails, recover the saved hash before preparing another transaction. A reverted approval leaves the allowance unchanged but can still incur a fee. With a controlled test spender, verify that a grant sets the expected allowance, a second nonzero approval replaces it, and revocation restores zero.

Holds

A MIP20 hold moves an owner’s funds out of balanceOf into escrow at the token contract. The funds remain the owner’s, tracked by heldTotalOf(owner), while only the holding spender can capture or release them. Placement emits Transfer(owner, token, amount) followed by HoldPlaced. Release emits Transfer(token, owner, amount) followed by HoldReleased. These transfers bypass receive policy screening because they preserve ownership. Capture first emits the release events, then screens the owner-to-recipient credit. A refusal always quarantines, even when the recipient chose REVERT mode. The hold decreases as the funds enter quarantine. Retain the quarantine receipt and recover through its recovery authority. A different execution failure, such as an insufficient hold, reverts all changes. See Payment from reserved funds.

ERC-3009 authorizations

The bytes-signature forms are:
transferWithAuthorization permits any submitter. receiveWithAuthorization requires the submitter to be to. Cancellation can be relayed by anyone holding the valid cancellation signature. All three also have an overload replacing bytes signature with uint8 v, bytes32 r, bytes32 s for an ECDSA signature. For Unix-second validity bounds, an authorization is usable only while validAfter < block.timestamp < validBefore. Use ordinary timestamps for the payment procedures. The inherited interface also supports block-number bounds when both bounds set bit 47, which must not be mixed into timestamp inputs. The bytes form accepts the owner’s own ECDSA signature first. If that fails and the owner has code, it checks ERC-1271. The (v,r,s) form checks ECDSA only. A delegated EOA’s own key remains valid regardless of its delegate’s rules. Mandate payments must use the account’s pull operation to apply its accounting. Successful authorization emits AuthorizationUsed(authorizer, nonce) from the token. Cancellation emits AuthorizationCanceled(authorizer, nonce) instead. A revert rolls back nonce consumption. Quarantine succeeds and consumes the nonce, so retrying with a fresh authorization would create another payment. Use Signing and Typed Data for the exact domain and type strings. Keep application correlation and authenticated evidence in your payment records or pass them to the send policy contract that checks them.

Receive policy and quarantine

The receive policy contract, ReceivePolicy, is at 0x0000000000000000000000000000000000000f4d. It screens sender blocklists and allowlists, accepted tokens, minimum and maximum amounts, and per-sender totals and arrival counts. Maximum amounts and per-sender limits are not on Metal Testnet yet. The screened sender is the balance owner, even when a spender or relayer submits. An unconfigured account accepts all credits. A self-transfer is accepted. A refused ordinary transfer reverts by default with CreditRefused(reason). A recipient can select QUARANTINE mode instead. Refused mints, hold captures and payouts from a token’s own balance always quarantine. The token emits Transfer(originator, ReceivePolicy, amount), and ReceivePolicy emits Quarantined(receiptId, receiver, originator, receipt, reason). Store the full quarantine receipt, its ID, token, amount and transaction hash. The recovery authority recorded in the receipt can submit claim(receipt, destination). Verify Claimed and the exact token transfer before recording recipient payment. The receipt retains its original recovery authority after policy changes. The Receive Policy reference defines enum values, configuration, receipt fields, authority selection and claim screening. Use a send policy contract to enforce outgoing budgets. To forward funds, call a routing contract. See Build a restricted account and Route incoming payments.

Factory-deployed MetalToken

MIP20Factory.deployMIP20 deploys a MetalToken and adds its address to the MIP20 allowlist. The caller becomes its owner.
The testnet default *USDZ gas token is a fixed-supply base MIP20. It does not expose these ownership, mint or burn functions. The factory also supports yield-share tokens through deployYieldMIP20(bytes creationCode, string calldata name_, string calldata symbol_, uint8 decimals_, uint256 initialShares, address initialHolder, uint256 ratePerSecond). Obtain the exact approved creation artifact and yield-product configuration from the issuer. Its rebasing view is a separate contract and is not a gas token.

Events and errors

Use the Event Catalog for emitted fields and Error Codes for revert reasons.