Skip to main content
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.
MetalAssertions is not on Metal Testnet yet. 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 for transaction signing. Use the token metadata and spending approval procedure 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, using test funding where supported. Each approval, assertion, challenge, settlement, and withdrawal is a separate transaction.

Load the interface and validate the domain

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.
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.
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 to verify successful execution, the canonical block hash, and the finalized block height. Filter logs by the assertions address before decoding:
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 for each transaction. If the bond token also pays gas, account for transaction fees separately from the withdrawal amount. ReceivePolicy 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:
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 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.