> ## 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.

# Payment from reserved funds

> Reserve an owner's tokens for a spender, do a payment, and release the remainder

> [!NOTE]
>
> **What you'll be able to do**
>
> * Create a MIP20 hold.
> * Make a payment from held funds.
> * Release any unused funds.

A MIP20 hold sets funds aside until an authorized spender pays a recipient or
releases them. The holder - named `owner` in the hold API - approves that spender
first. Held funds leave the holder's spendable balance and cannot pay gas, while
the spender controls capture and release. `heldTotalOf(owner)` reports the funds
still attributed to that holder across all of its holds.

The token records one aggregate hold for each holder and spender. The
application records which orders share that hold and how much remains assigned
to each. MIP20 supplies no order ID, automatic expiry or release initiated by the holder.

## Configure the holder and spender

Use `ownerWallet` for the holder's signing account and `spenderWallet` for the
spender's. Each pays gas for its own transactions.
For a contract spender, its own application entry point must make the token
calls below with appropriate access controls.

Install `@metalntwx/metal-ts@0.3.2` and `viem@2.57.7` with pnpm. Resolve `token`
and its decimals using [Read metadata](https://docs.metalntwx.com/build/payments/direct-payment.md#read-metadata).
Validate the order's amount and convert it to base-unit `reserved` using
[Convert a payment amount](https://docs.metalntwx.com/build/payments/direct-payment.md#convert-a-payment-amount).
Read the holder's spendable balance and retain enough outside the hold for gas costs.

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

const owner = ownerWallet.account
const spender = spenderWallet.account
if (!owner || !spender) throw new Error("Configure each wallet client with its signing account")
const holdAbi = parseAbi([
  "function holdFrom(address owner, uint256 amount)",
  "function captureHold(address owner, address to, uint256 amount)",
  "function releaseHold(address owner, uint256 amount)",
  "function heldBalanceOf(address owner, address spender) view returns (uint256)",
  "function heldTotalOf(address owner) view returns (uint256)",
  "event HoldPlaced(address indexed owner, address indexed spender, uint256 amount)",
  "event HoldReleased(address indexed owner, address indexed spender, uint256 amount)",
])
const heldBefore = await client.readContract({
  address: token, abi: holdAbi, functionName: "heldBalanceOf",
  args: [owner.address, spender.address],
})
```

## Approve and place the hold

Use the holder's wallet client, `ownerWallet`, to grant the spender an
allowance of `reserved` using [Approve a
spender](https://docs.metalntwx.com/reference/assets-and-payments/mip20.md#approve-a-spender). If an allowance already
exists, reconcile its intended use first. `approve` replaces the allowance,
including a nonzero allowance. Coordinate with the spender so outstanding
transactions cannot spend an old allowance while it is being replaced.

The spender places the hold. Estimate and simulate this exact call using
[Estimate and record fees](https://docs.metalntwx.com/build/prepare/estimate-and-record-fees.md), with the
spender's signing account. Its balance in the gas token pays the transaction
fee. Set `gasLimit` from that estimate and use the quoted `price` as
`maxFeePerGas`.

```typescript theme={null}
const placement = {
  address: token, abi: holdAbi, functionName: "holdFrom",
  args: [owner.address, reserved],
} as const
await client.simulateContract({ ...placement, account: spender })
const signed = await signMip20Contract({
  publicClient: client, walletClient: spenderWallet, ...placement,
  gasToken: "0x000000000000000000000000000000000000b5d5",
  gasLimit, gasFallback: gasLimit, maxFeePerGas,
})
```

[Persist the signed transaction hash and
bytes](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md#save-outgoing-attempts-before-submission)
with the order's hold placement record before `await signed.send()`. Use the
same sign, save, submit and transaction receipt procedure for each capture and
release, with a new transaction attempt and an estimate for its actual
arguments.

Require successful execution and canonical finality. The transaction receipt must contain
`HoldPlaced(owner, spender, reserved)` and the token's `Transfer(owner, token,
reserved)`. Read `heldBalanceOf(owner, spender)` and require
`heldBefore + reserved` when no other transaction changed the hold concurrently. Verify
that the finite allowance decreased by `reserved` and the holder's spendable
balance decreased accordingly. Account separately for gas if the holder also
submits a transaction.

## Allocate the held amount to orders

Store the chain ID, token, holder and spender with each allocation. Track reserved,
captured and released amounts as base-unit integers, and retain each
transaction attempt and verified event identity. The remaining allocation is
`reserved - captured - released`.

A capture that enters quarantine still consumes its allocation. Record delivery
separately as paid or quarantined, with the quarantine receipt for quarantined
funds. Leaving those funds available for another capture would spend the order's
allocation twice.

Serialize changes to a shared aggregate hold and update allocations atomically
with their verified results. The sum of outstanding allocations must never
exceed the onchain hold. Refuse a capture larger than the order's remaining
allocation even when the aggregate hold has enough funds from other orders.
Use the storage and concurrency procedure in
[Verify and reconcile payments](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md).

## Capture part of the hold

Set `recipient` from the immutable order and choose `captured` such that
`0 < captured < reserved` for a partial payment. The spender assigned to the hold calls
`captureHold`, which releases that amount from escrow internally and then
transfers it from the holder to the recipient.

```typescript theme={null}
const capture = {
  address: token, abi: holdAbi, functionName: "captureHold",
  args: [owner.address, recipient, captured],
} as const
await client.simulateContract({ ...capture, account: spender })
```

Sign `capture` with `spenderWallet`, save its separate attempt, and submit it
using the placement procedure. Verify these token events in the same successful,
finalized transaction, in this order:

1. `Transfer(token, owner, captured)` returns escrowed funds internally.
2. `HoldReleased(owner, spender, captured)` records the hold reduction.
3. `Transfer(owner, recipient, captured)` records the payment.

The hold decreases by `captured`. The holder's spendable balance has no net
increase from capture. Credit the recipient only for the transfer from the
holder to the recipient, using the [finalized transfer
checks](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md#verify-a-finalized-transfer).

If receive policy refuses the recipient credit, capture forces quarantine even
when the recipient uses the default `REVERT` mode. The first two events still
occur, followed by a ReceivePolicy `Quarantined` event and the token's
`Transfer(owner, ReceivePolicy, captured)`. The hold and `heldTotalOf(owner)`
decrease, but the intended recipient receives no tokens. Verify and save the
[quarantine receipt](https://docs.metalntwx.com/reference/compliance/receive-policy.md), including its token, originator,
intended recipient (`receiver`), amount and recovery authority. Mark the allocation captured
and quarantined, leave the order unpaid, and recover through that receipt.
Do not repeat the capture to try to pay the same order.

## Release the remainder

Only the spender assigned to the hold can release it. The holder sends a cancellation
request to the application, which checks the order and instructs that spender.
For this partial-payment flow, release `reserved - captured`.

```typescript theme={null}
const remainder = reserved - captured
const release = {
  address: token, abi: holdAbi, functionName: "releaseHold",
  args: [owner.address, remainder],
} as const
await client.simulateContract({ ...release, account: spender })
```

Sign and persist a separate release attempt. Verify `Transfer(token, owner,
remainder)`, `HoldReleased(owner, spender, remainder)` and finality before
closing the allocation. With no other holds, `heldBalanceOf` is now zero. For
an existing aggregate hold, it returns to `heldBefore`. Revoke any unused
allowance separately.

Placement and release bypass receive screening. Release remains available even if
the holder's receive policy would refuse an ordinary credit. They emit
transfers into and out of the token contract, which must not be credited as customer payments.

## Check rejection and interruption

Before relying on holds, test excess capture and a different spender's capture
with simulation. Both must fail. A reverted capture preserves the
hold, token balances and application allocations, including the internal
release. With a controlled refusing receive policy, verify that capture instead
succeeds into quarantine, reduces the hold and allocation, saves its quarantine
receipt and leaves the order unpaid. These are different outcomes. Use fixtures
for these checks when the live deployment lacks the required policy code, and
report the live behavior as unverified.

A lost response leaves the allocation pending. Recover using the [saved
transaction
hash](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md#recover-an-uncertain-submission)
and inspect its transaction receipt before signing another placement, capture or
release. A hold remains until the spender acts, so retain the spender's signing
and recovery access for the complete order lifecycle.


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