Skip to main content
What you’ll be able to do
  • Estimate a transfer’s transaction fee.
  • Check that the signing account can cover the transfer and maximum gas cost.
  • Verify the final fee from the transaction receipt.
Metal charges a transaction fee based on the gas consumed and the effective gas price. On testnet, the default gas token is *USDZ. The bank or PSP sets its customer-facing service charge separately. It can use the transaction fee as an input to that pricing.

Choose the gas token

Transaction fees are deducted directly from the signing account’s balance in the gas token. No separate token-spending approval is required. Keep the transaction’s native value at zero. On testnet, the default gas token is *USDZ at 0x000000000000000000000000000000000000B5D5, with 18 decimals. The Metal SDK supports explicit selection through its gasToken argument. Only tokens recognised by the MIP20 allowlist can pay gas.
RPC credit in the console pays for endpoint requests. Transaction fees are paid from the signing account’s balance in the gas token.
Gas prices are integer amounts in the gas token’s smallest units. There is no automatic currency conversion. For example, a fee of 120000 base units is 0.12 of a six-decimal token but 0.00000000000012 of an 18-decimal token. Read the gas token’s decimals() when displaying costs.

Calculate the quote and balance requirement

The gas limit caps the work a transaction may consume. The gas price is the token amount paid per unit of work. Metal’s execution base fee is zero. For a transaction with fee-cap fields:
Read the gas price using eth_gasPrice (client.getGasPrice() in viem). Metal requires a positive effective price, a priority fee no greater than the fee cap, and a fee cap no greater than 1_000_000_000_000 base units per gas. The SDK sets both fields from its maxFeePerGas argument. For a direct transfer paid in the same token, require:
If the token being transferred differs from the gas token, check each balance separately. Also account for any other token spending performed by the contract call. A higher gas limit increases the balance required upfront. Unused execution gas is refunded, so the final charge depends on the gas actually consumed. These calculations cover transactions without blobs. Blob transactions have a separate blob fee.

Add quoting and fee accounting to an application

Use a dedicated test account, following the quickstart account instructions if you need to create one. Fund it with *USDZ through the faucet and supply its key as PRIVATE_KEY through secret configuration. For an application with a custody or signing service, provide that signer through the SDK’s wallet client. Set RECIPIENT_ADDRESS to a different EVM address you control. The test transfer sends 0.01 test tokens and compares the resulting account balances, so both accounts must have no other activity during the test. From the application directory, install the public packages:

Configure the test signer

Create a wallet client for the signer using the application’s rpcUrl. The transaction transfers *USDZ and pays gas in *USDZ. Reading balances and estimating fees require only the sender’s public address. The private key is needed to sign the transaction.
For application-supplied amounts, follow the precision checks in Direct payment.

Estimate the transfer

Omit fee fields from the estimate. A nonzero effective gas price can make the inherited Ethereum estimator return gas required exceeds allowance (0), even when the signing account’s balance in the gas token is sufficient. If estimation fails, stop before signing and investigate the cause. Add 20% plus 10,000 gas to the estimate as headroom, as shown above.

Check affordability

Before preparing the transaction, read the sender’s spendable balance and check that it covers the transfer and maximum gas cost. Record the recipient’s balance at the same block to reconcile both changes later:
When using different transfer and gas tokens, require the transfer amount in the first and maximumFee in the second. Keep these calculations in integer base units. Read the gas token’s decimals and use formatUnits for display.

Submit with the quoted gas budget

The wallet client must use sender as its signing account. Pass the same token, amount, price, and limit used in the quote:
Supplying gasLimit makes the SDK use the quoted limit. The SDK still requires gasFallback, but does not use it on this path. Save signed.txHash and signed.raw in the transaction record before calling await signed.send(). Submit transactions sequentially for each signing account.

Record the actual fee

After submission, look up the saved transaction hash:
Record the transaction hash, gas-token address, receipt status, and actualFee in base units. Calculate and record the fee even when execution reverted. Treat a successful transaction as a completed payment only after payment verification and finality checks pass. If receipt polling times out, keep looking up the saved hash until the outcome is known. For this direct transfer between otherwise inactive accounts, verify the fee against balances at the receipt block:
For these inactive accounts, the balance changes should show that the sender paid the amount plus the transaction fee and the recipient received the amount. Concurrent activity, yield accounting or quarantine can change those differences. Use transaction events to identify individual payments. If a successful transfer credits ReceivePolicy quarantine, record its fee and quarantine receipt separately. Do not count the intended recipient as paid or submit a replacement payment automatically.

Handle failures and uncertain submissions

Exercise the application’s checks with missing configuration, the wrong network, and an amount larger than the funded balance. Each should stop before submission. Keep receipt status separate from fee calculation as a failure can still incur a cost.