> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MIP20 Contract API

> MIP20 metadata, transfers, allowances, authorizations, holds, receive policy, and token ownership

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](https://docs.metalntwx.com/reference/compliance/receive-policy.md) to check the
recipient's stored rules. Applications can use a send policy contract to check
outgoing rules before requesting a token transfer.

## Constructor

```solidity theme={null}
constructor(
    string memory name_,
    string memory symbol_,
    uint8 decimals_,
    uint256 initialSupply,
    address initialHolder
)
```

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

| Getter | Return | Meaning |
| - | - | - |
| `name()` | `string` | Display name |
| `symbol()` | `string` | Display symbol |
| `decimals()` | `uint8` | Amount precision |
| `totalSupply()` | `uint256` | Total token supply |
| `balanceOf(account)` | `uint256` | Spendable balance, excluding holds |
| `allowance(owner, spender)` | `uint256` | Remaining delegated transfer amount |
| `heldBalanceOf(owner, spender)` | `uint256` | Funds held for this spender |
| `heldTotalOf(owner)` | `uint256` | Owner's funds held across all spenders |
| `authorizationState(authorizer, nonce)` | `bool` | Whether an authorization was used or canceled |
| `DOMAIN_SEPARATOR()` | `bytes32` | Current EIP-712 domain separator |
| `eip712Domain()` | EIP-5267 tuple | Domain fields, name, version, chain ID, verifying contract, salt and extensions |

`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

| Function | Caller | Return or result |
| - | - | - |
| `transfer(to, amount)` | Token holder | `bool` |
| `approve(spender, amount)` | Allowance owner | `bool`, replacing the remaining allowance |
| `transferFrom(from, to, amount)` | Approved spender | `bool`, consuming allowance |

`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](#receive-policy-and-quarantine). 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](https://docs.metalntwx.com/build/prepare/connect-to-metal.md)
and a wallet client named `wallet`, configured with the holder's signing account
as described in
[Configure the test signer](https://docs.metalntwx.com/build/prepare/estimate-and-record-fees.md#configure-the-test-signer).
Both clients must use the same verified network. Install the public packages:

```bash theme={null}
pnpm add @metalntwx/metal-ts@0.3.2 viem@2.57.7
```

Verify the `token` address using
[Read metadata](https://docs.metalntwx.com/build/payments/direct-payment.md#read-metadata). For a new
allowance, validate and convert the spending limit to the positive base-unit
`amount` using
[Convert a payment amount](https://docs.metalntwx.com/build/payments/direct-payment.md#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:

```typescript theme={null}
import { erc20Abi, getAddress } from "viem"
import { signMip20Contract } from "@metalntwx/metal-ts"

const holder = wallet.account?.address
if (!holder) throw new Error("Configure the wallet client with the holder's signing account")
const spender = getAddress(spenderAddress)
const allowance = await client.readContract({
  address: token, abi: erc20Abi, functionName: "allowance", args: [holder, spender],
})
```

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](https://docs.metalntwx.com/build/prepare/estimate-and-record-fees.md), 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:

```typescript theme={null}
const approval = await signMip20Contract({
  publicClient: client, walletClient: wallet,
  address: token, abi: erc20Abi, functionName: "approve",
  args: [spender, approvalAmount],
  gasToken: "0x000000000000000000000000000000000000b5d5",
  gasLimit, gasFallback: gasLimit, maxFeePerGas,
})
```

Keep each reset, grant, or revocation in its own
[saved transaction attempt](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md#save-outgoing-attempts-before-submission).
Save `approval.txHash` and `approval.raw` before calling
`await approval.send()`. Require a successful receipt,
[block finality](https://docs.metalntwx.com/reference/network-fundamentals/settlement.md), 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.

| Function | Caller | Result |
| - | - | - |
| `holdFrom(owner, amount)` | Approved spender | Consumes allowance and moves funds into this spender's hold |
| `releaseHold(owner, amount)` | Holding spender | Returns held funds to the owner's spendable balance |
| `captureHold(owner, to, amount)` | Holding spender | Reduces the hold and transfers from the owner to the recipient or quarantine |

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](https://docs.metalntwx.com/build/payments/payment-from-reserved-funds.md).

## ERC-3009 authorizations

The bytes-signature forms are:

```solidity theme={null}
function transferWithAuthorization(
    address from, address to, uint256 value,
    uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes calldata signature
) external;
function receiveWithAuthorization(
    address from, address to, uint256 value,
    uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes calldata signature
) external;
function cancelAuthorization(address authorizer, bytes32 nonce, bytes calldata signature) external;
```

`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](https://docs.metalntwx.com/reference/network-fundamentals/signing.md#mip20-erc-3009) 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](https://docs.metalntwx.com/reference/compliance/receive-policy.md). 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](https://docs.metalntwx.com/reference/compliance/receive-policy.md) 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](https://docs.metalntwx.com/build/controls/restrict-token-transfers.md) and
[Route incoming payments](https://docs.metalntwx.com/build/payments/route-incoming-payments.md).

## Factory-deployed `MetalToken`

`MIP20Factory.deployMIP20` deploys a `MetalToken` and adds its address to the
MIP20 allowlist. The caller becomes its owner.

```solidity theme={null}
function deployMIP20(
    string calldata name_, string calldata symbol_, uint8 decimals_,
    uint256 initialSupply, address initialHolder
) external returns (address token);
```

| Function or getter | Authorization | Result |
| - | - | - |
| `owner()` | Anyone | Current token owner |
| `mint(to, amount)` | Token owner | Increases supply and credits the destination or quarantine |
| `burn(amount)` | Token owner | Burns tokens from the owner's balance |
| `transferOwnership(newOwner)` | Token owner | Changes the owner to a nonzero address |
| `renounceOwnership()` | Token owner | Sets owner to zero, disabling owner mint and burn |

The testnet default `*USDZ` gas token is a fixed-supply base MIP20. It does not
expose these ownership, mint or burn functions.

| Factory getter | Return |
| - | - |
| `isMIP20(token)` | Whether the address is allowlisted |
| `deployedTokens(index)` | Token address at a deployment index |
| `deployedTokensLength()` | Number of factory deployments |
| `deployCounter()` | Salt counter used for the next deployment |
| `yieldCodeHash()` | Approved creation-code hash for `deployYieldMIP20` |

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](https://docs.metalntwx.com/reference/network-fundamentals/events.md#mip20-and-factory) for emitted
fields and [Error Codes](https://docs.metalntwx.com/reference/error-codes.md#mip20) for revert reasons.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.