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

# Submit and challenge claims

> Submit objective bonded claims, prove contradictions, and reconcile each participant's funds

> [!NOTE]
>
> **What you'll be able to do**
>
> * Submit a claim, backing it by a bond.
> * Challenge an incorrect claim with proof.
> * Settle a claim and withdraw available funds.

> [!WARNING]
>
> `MetalAssertions` is [not on Metal Testnet yet](https://docs.metalntwx.com/reference/contract-addresses.md#contracts-not-yet-on-metal-testnet).
> Follow this guide on a network that has code at its address.

`MetalAssertions` at `0x000000000000000000000000000000000000018c` records claims
backed by a token deposit called a bond. Each claim belongs to a domain,
which sets who can participate, the required bond, and how challenges are
decided. This guide uses an objective domain, where the contract decides
challenges by checking proofs. Here, you'll use a Merkle proof to check a value
against a Merkle root.

Use a test account for the false claim and obtain the domain operator's
permission. A successful challenge permanently prevents that account from
submitting further claims in the domain.

## Obtain a domain and two permitted accounts

Get the domain name, its published `rulesHash`, and permission to use the
two-leaf test tree from the operator. The network configures domains at genesis.
Use separate signing accounts for the asserter and challenger, and reserve them
for this exercise so you can track changes to their balances.

Check for runtime bytecode at the assertions address.

Install the [TypeScript SDK](https://docs.metalntwx.com/reference/typescript-sdk.md) for transaction signing.
Use the
[token metadata and spending approval procedure](https://docs.metalntwx.com/reference/assets-and-payments/mip20.md#approve-a-spender)
to verify the bond token, read balances, and grant exact allowances to the
assertions contract. Fund each signing account with the bond token for bonds and
the gas token for [transaction fees](https://docs.metalntwx.com/build/prepare/estimate-and-record-fees.md), using
[test funding](https://docs.metalntwx.com/build/prepare/fund-a-test-account.md) where supported. Each
approval, assertion, challenge, settlement, and withdrawal is a separate
transaction.

## Load the interface and validate the domain

```typescript theme={null}
import { concatHex, encodeAbiParameters, keccak256, parseAbi, parseAbiParameters,
  parseEventLogs, toHex, zeroAddress, type Hex } from 'viem'

const assertions = '0x000000000000000000000000000000000000018c' as const
const assertionsAbi = parseAbi([
  'struct Domain { address asserter; address challenger; address bondToken; uint256 bondFloor; uint256 bondBps; uint256 windowSeconds; bool binding; bool evidence; address arbiter; }',
  'struct Assertion { bytes32 domainId; address asserter; bytes32 claimHash; bytes32 rulesHash; uint256 declaredValue; uint256 bond; uint64 expiresAt; uint8 status; address challenger; uint64 challengedAt; uint8 tooEarly; bool verified; }',
  'function domainIdOf(string name) pure returns (bytes32)',
  'function domain(bytes32 id) view returns (Domain)',
  'function assertion(bytes32 id) view returns (Assertion)',
  'function suspended(bytes32 id, address account) view returns (bool)',
  'function requiredBond(bytes32 id, uint256 declaredValue) view returns (uint256)',
  'function claimable(address token, address account) view returns (uint256)',
  'function burned(address token) view returns (uint256)',
  'function assertClaim(bytes32 domainId, bytes32 claimHash, bytes32 rulesHash, uint256 declaredValue, uint256 bond) returns (bytes32)',
  'function challenge(bytes32 id, bytes proof)',
  'function settle(bytes32 id)',
  'function withdraw(address token)',
  'event Asserted(bytes32 indexed id, bytes32 indexed domainId, address indexed asserter, bytes32 claimHash, bytes32 rulesHash, uint256 declaredValue, uint256 bond, uint64 expiresAt)',
  'event Settled(bytes32 indexed id)',
  'event Voided(bytes32 indexed id, address indexed challenger)',
  'event ChallengeFailed(bytes32 indexed id, address indexed challenger)',
  'event Withdrawn(address indexed token, address indexed account, uint256 amount)',
])

const domainId = await client.readContract({
  address: assertions, abi: assertionsAbi, functionName: 'domainIdOf', args: [domainName],
})
const block = await client.getBlock({ blockTag: 'finalized' })
const config = await client.readContract({
  address: assertions, abi: assertionsAbi, functionName: 'domain',
  args: [domainId], blockNumber: block.number,
})
if (config.windowSeconds === 0n) throw new Error('Unknown domain')
if (config.evidence || config.arbiter !== zeroAddress) throw new Error('Not objective')
if (config.asserter !== zeroAddress && config.asserter.toLowerCase() !== asserter.toLowerCase()) {
  throw new Error('Asserter not permitted')
}
if (config.challenger !== zeroAddress && config.challenger.toLowerCase() !== challenger.toLowerCase()) {
  throw new Error('Challenger not permitted')
}
```

Supply `domainName` and `rulesHash` from the operator, and `asserter` and
`challenger` from the two signing accounts' addresses. Read
`suspended(domainId, asserter)` at the same block and stop if true. Confirm a
nonzero bond floor and a window long enough to submit a challenge. Save
`claimable` for both accounts and `burned` so you can measure their changes
during the exercise.

For a declared value in the domain bond token's smallest units, the minimum bond
is `max(bondFloor, floor(declaredValue * bondBps / 10000))`. Read
`requiredBond(domainId, declaredValue)` and use that exact amount `B`. A
challenge posts the assertion's recorded bond, even if the asserter chose more
than the minimum. Account for transaction fees separately from bonds.

## Construct a claim and its Merkle proof

Construct the agreed test tree with two `bytes32` leaf values. Prefix leaves
with `0x00` before hashing and internal nodes with `0x01`. Order siblings
according to the leaf index bits, starting with the least significant bit.

```typescript theme={null}
const left = toHex(11n, { size: 32 })
const right = toHex(22n, { size: 32 })
const leafHash = (leaf: Hex) => keccak256(concatHex(['0x00', leaf]))
const root = keccak256(concatHex(['0x01', leafHash(left), leafHash(right)]))
const index = 0n
const siblings = [leafHash(right)]
const claimFields = parseAbiParameters('uint8, bytes32, uint256, bytes32')
const correctHash = keccak256(encodeAbiParameters(claimFields, [2, root, index, left]))
```

Store the root, index, claimed value, rules hash, and sibling path with the
claim. Compute its hash with
`keccak256(abi.encode(kind, subjectA, subjectB, value))`, using kind `2` for a
Merkle leaf.

## Submit and identify the claim

Approve `B` of the bond token to the assertions contract and verify the
resulting allowance. If replacing an existing allowance, first reconcile any
outstanding uses. Simulate
`assertClaim(domainId, correctHash, rulesHash, declaredValue, B)` from the
asserter, estimate its fee, and prepare it with the SDK. Use the asserter's
wallet client as `wallet`, the verified gas token address as `gasToken`, and
`gasLimit` and `maxFeePerGas` from the fee estimate.

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

const attempt = await signMip20Contract({
  publicClient: client, walletClient: wallet,
  address: assertions, abi: assertionsAbi, functionName: 'assertClaim',
  args: [domainId, correctHash, rulesHash, declaredValue, B],
  gasToken, gasLimit, gasFallback: gasLimit, maxFeePerGas,
})
```

Save `attempt.txHash`, `attempt.raw`, the signing account's address, nonce, and
all claim inputs in protected application storage before calling
`attempt.send()`. If submission times out, query the saved transaction hash to
recover its receipt.

Use
[Verify and reconcile payments](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md)
to verify successful execution, the canonical block hash, and the finalized
block height. Filter logs by the assertions address before decoding:

```typescript theme={null}
const events = parseEventLogs({
  abi: assertionsAbi, eventName: 'Asserted',
  logs: receipt.logs.filter(log => log.address.toLowerCase() === assertions),
})
if (events.length !== 1) throw new Error('Expected one Asserted event')
const id = events[0]!.args.id
const record = await client.readContract({
  address: assertions, abi: assertionsAbi, functionName: 'assertion',
  args: [id], blockNumber: receipt.blockNumber,
})
```

Verify every field against the submitted inputs, the asserter, bond `B`, status
`1` (`Asserted`), and `verified == false`. `expiresAt` must equal the receipt
block's timestamp plus `windowSeconds`. Independently compute the ID as
`keccak256(abi.encode(domainId, asserter, claimHash, rulesHash, receipt.blockNumber))`,
using types `bytes32,address,bytes32,bytes32,uint256`. Save the ID with the
signed attempt.

## Settle and withdraw the correct claim

Once the chain timestamp reaches `expiresAt`, submit `settle(id)` from either
signing account using a new saved signed transaction. An earlier call reverts
with `WindowActive`. Verify the finalized `Settled(id)` event, status `2`, and
an increase of exactly `B` in `claimable(bondToken, asserter)`.

Submit `withdraw(bondToken)` from the asserter to withdraw its full claimable
balance for that token. Verify the `Withdrawn(bondToken, asserter, amount)`
event, a matching bond-token `Transfer` from the assertions address to the
asserter, and zero remaining claimable balance. Follow
[settlement and finality](https://docs.metalntwx.com/reference/network-fundamentals/settlement.md) for each transaction. If the
bond token also pays gas, account for transaction fees separately from the
withdrawal amount.

[ReceivePolicy](https://docs.metalntwx.com/reference/compliance/receive-policy.md) checks the withdrawal's recipient. A
refusal in `REVERT` mode reverts the withdrawal and preserves the claimable
balance. In `QUARANTINE` mode, it sends the tokens to quarantine while the
withdrawal emits `Withdrawn` and clears the claimable balance. Save the
finalized quarantine receipt and matching token transfer to ReceivePolicy, then
mark the withdrawal as awaiting delivery. Recover the tokens through that
receipt.

## Challenge a false claim

After the first withdrawal completes, approve another bond and submit a second
claim using the same root and index with a false leaf value of
`toHex(99n, { size: 32 })`. Save the signed transaction and claim inputs. The
challenge encodes those inputs followed by the true leaf and its sibling path:

```typescript theme={null}
const falseLeaf = toHex(99n, { size: 32 })
const falseHash = keccak256(encodeAbiParameters(claimFields, [2, root, index, falseLeaf]))
const proof = encodeAbiParameters(
  parseAbiParameters('uint8, bytes32, uint256, bytes32, bytes32, bytes32[]'),
  [2, root, index, falseLeaf, left, siblings],
)
```

Read the second assertion and its deadline. The challenger approves its recorded
bond `B`, then submits `challenge(secondId, proof)` through its wallet client.
Leave enough time for the transaction to execute while
`block.timestamp < expiresAt`, and stop if the window has elapsed.

Verify a finalized `Voided(secondId, challenger)` event, status `3`, and
`suspended(domainId, asserter) == true`. With `H = floor(B / 2)`, the challenger
gains `B + H` claimable units and `burned(bondToken)` increases by `B - H`. This
returns the challenger's bond and awards it half of the asserter's forfeited
bond, rounded down. Withdraw the challenger's funds and verify delivery as
above.

Check the event to determine the challenge's outcome. `ChallengeFailed` means
the challenger forfeited its bond and the assertion remains `Asserted`, even
though the transaction executed successfully. This occurs when the proof fails
to reach the root or proves the claimed leaf correct. Claim inputs that fail to
reproduce the claim hash cause `BadClaim` and revert the transaction.

## Verify recovery and other claim classes

After a restart, use the saved transaction hashes and assertion IDs to recover
receipts and state. Check finality, the assertion's status, suspension,
claimable balances, and the change in burned funds before deciding whether
another transaction is needed.

See the [Assertions reference](https://docs.metalntwx.com/reference/data-and-signatures/assertions.md) for other claim
classes. Use `assertOracleClaim` to verify a retained oracle write immediately.
Evidence domains resolve challenges through validator verdicts, while subjective
domains use a named arbiter.

Test mismatched claim inputs, invalid paths, unknown or non-objective domains,
early settlement, and challenges at or after expiry before submitting
transactions. Include an odd bond amount to check rounding, and test withdrawal
refusal, quarantine, and recovery after a restart.


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