Skip to main content
What you’ll be able to do
  • Connect a user’s browser wallet to Metal Testnet.
  • Request signatures for transactions or typed data.
  • Submit an approved transaction.
A browser wallet holds the user’s signing authority. The application prepares the network and transaction details, then requests wallet approval. Keep connection and payment actions behind explicit user controls, and invalidate pending actions whenever the selected account or network changes.

Prepare the application

Use an existing browser application with TypeScript and an EIP-1193 provider. Let the user select a provider when several wallets are installed. Install the public dependencies for the flows below:
Obtain an endpoint through Connect to Metal. A browser endpoint URL is visible to the browser’s user. Configure it for that application’s allowed origins and usage limits, and keep administrative credentials on the server. For testing, use a dedicated test account funded through Fund a test account. Define the network with the full configured endpoint URL:
Wallet schemas require nativeCurrency display metadata. Metal charges the default gas token for standard Ethereum transactions and exposes its balance through eth_getBalance. Set transaction value to 0n. Token payments call the MIP20 contract’s transfer function.

Connect from a user action

Call connectWallet(provider, rpcUrl) from a Connect button. Show failures in the application without automatically opening another wallet prompt. Only the wallet’s unknown-chain error 4902 triggers the add-network request.
Disable payment controls when session.valid becomes false and reconnect through another explicit user action. Call dispose() when replacing the connection or unmounting the UI. Check assertCurrent() before requesting a signature and again before broadcasting bytes held by the application.

Prepare the payment for approval

Resolve token, recipient, and amount using Direct payment. Reject invalid addresses, nonpositive amounts, excess decimal precision, and amounts beyond uint256. Read token metadata and balances through the public client. Use Estimate and record fees to obtain a successful estimate, apply headroom, and verify affordability. The signing account needs the transfer amount in the token being sent. The signing account’s balance in the gas token must cover the maximum gas cost. If those are the same token, account for both debits together. Show the user the network, token address and symbol, recipient, amount, gas token, and maximum gas cost before opening the approval request. Keep the application operation ID and expected payment fields in durable application storage. Disable repeat submission while the request is awaiting approval or reconciliation.

Submit a standard wallet transaction

A standard wallet transaction uses the network’s default gas token. Supply gasLimit, maxFeePerGas, and maxPriorityFeePerGas from the fee procedure. Estimate without fee fields first, because wallet-added fee fields can trigger a zero-allowance estimate failure on this network.
Here connection is the result of connectWallet. The nonpayable token call omits value, so the transaction carries zero native value. The wallet handles signing and broadcast, and returns a transaction hash. Save that hash against the existing operation immediately, even if a subsequent wallet event invalidates the connection. Verify the result through the public client independently of the wallet’s current network. eth_sendTransaction does not expose signed bytes before broadcast. If the wallet submits but its response is lost, the application may have no transaction hash. Mark the operation uncertain, inspect the wallet’s activity and the signing account’s transaction history, and identify the exact call before retrying.

Sign typed data before broadcasting

For a selected MIP20 gas token, signMip20Contract asks the wallet to sign Metal’s EIP-712 transaction data and returns the encoded 0x7f envelope without sending it. The signature authorizes that exact transaction, including chain, nonce, call, and fee fields. Create a durable browser store with idb, which wraps IndexedDB:
Before requesting the signature, add an attempt containing a fresh application ID and the immutable expected payment. expectedPayment contains the chain, token, sender, recipient, amount, gas token, and fee bounds. Encode bigint fields as decimal strings when sending this record to the server.
The helper uses maxFeePerGas for both its maximum and priority fee. Passing the successful estimate as gasLimit avoids silently proceeding after an estimation failure. See TypeScript SDK for the exact transaction fields. Serialize signing for each account and reconcile an unresolved attempt before creating another. Browser storage is local to the profile and can be cleared. For recovery across devices, save the attempt to the service’s durable store and wait for its acknowledgement before broadcasting. After a reload, read the saved attempt. Verify its hash first. If submission is still uncertain, use the identical raw bytes as described in Recover an uncertain submission.

Verify and display the result

A returned hash means submission, not payment completion. Wait for the receipt, check execution, and match the exact token, sender, recipient, and amount in its Transfer event. Verify the canonical containing block and finality through Best practices in processing payments. Update the order once from that verified result. Test refusal, unknown chain, account changes during a signature, disconnect, and an uncertain broadcast with a controlled provider fixture. Then exercise consent and one finalized payment with a clean real-wallet profile. A provider fixture establishes application behavior but does not establish wallet support.