MandateWallet wraps MandateClient with a viem signing layer. You call a single method like transfer() and it handles the entire flow: estimate gas, compute intentHash, validate against your policies, sign locally, broadcast, post the event, and wait for on-chain confirmation. Your private key never leaves your machine.
Synchronous getter. Returns the wallet’s 0x${string} address. If you use an external signer whose getAddress() returns a Promise, this throws until the address is resolved. Use getAddress() instead.
Send an ERC20 token transfer with full policy enforcement. The SDK encodes the ERC20 transfer(address,uint256) calldata internally using the built-in ABI.
Copy
Ask AI
const { txHash, intentId, status } = await wallet.transfer( '0xRecipientAddress', '5000000', // 5 USDC (6 decimals, raw amount) '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC on Base Sepolia { reason: 'Vendor payment for March services' },);
Parameters:
Name
Type
Description
to
`0x${string}`
Recipient address
rawAmount
string
Token amount in smallest unit (e.g. '5000000' for 5 USDC)
tokenAddress
`0x${string}`
ERC20 contract address
opts.reason
string
Human-readable reason for audit log
opts.waitForConfirmation
boolean
Wait for on-chain confirmation. Default: true
Returns:TransferResult
Copy
Ask AI
interface TransferResult { txHash: Hash; // On-chain transaction hash intentId: string; // Mandate intent ID for status tracking status: IntentStatus; // Current intent status object}
Same as sendTransaction, but catches ApprovalRequiredError and polls for human approval before proceeding. Use this when your policy includes approval rules and the agent should wait rather than fail.
Flow: If rawValidate throws ApprovalRequiredError, the method calls onApprovalPending, then polls waitForApproval until the human approves or rejects. On approval, it signs, broadcasts, and confirms as usual. On rejection or timeout, it throws.
ERC20 transfer with built-in approval wait support. Combines the ERC20 encoding of transfer() with the approval polling of sendTransactionWithApproval().
Execute an x402 payment flow. The method probes the URL, detects a 402 Payment Required response, parses the payment details from the X-Payment-Required header, transfers the requested amount, and retries the original request with the transaction hash.
Copy
Ask AI
const response = await wallet.x402Pay( 'https://api.example.com/premium-endpoint', { headers: { 'Authorization': 'Bearer my-api-key' }, reason: 'x402 payment for premium API access', },);const data = await response.json();
Parameters:
Name
Type
Description
url
string
The URL to request
opts.headers
Record<string, string>
Additional request headers
opts.reason
string
Reason for audit log
Returns: The final Response object from the retry request. If the initial request does not return 402, it returns that response directly (no payment attempted).
Lightweight policy check without signing or broadcasting. Uses the action-based validate() endpoint under the hood. Use this to check if a transfer would pass your policies before committing to the full flow.
Copy
Ask AI
const check = await wallet.preflightTransfer({ to: '0xRecipientAddress', amount: '50', token: 'USDC', reason: 'Check if vendor payment is allowed',});if (check.allowed) { // Proceed with actual transfer await wallet.transfer(/* ... */);}
Parameters:
Name
Type
Required
Description
params.to
`0x${string}`
Yes
Recipient address
params.amount
string
Yes
Human-readable amount (e.g. '50')
params.token
string
No
Token symbol (e.g. 'USDC')
params.reason
string
Yes
Reason for the transfer
Returns:PreflightResult with allowed, intentId, requiresApproval, and blockReason fields.
preflightTransfer uses human-readable amounts (e.g. '50' for 50 USDC), unlike transfer() which requires raw amounts in the token’s smallest unit.
sendTransaction receives a fully prepared transaction object. Sign it, broadcast it, and return the transaction hash. getAddress can be sync or async depending on your wallet provider.Example: wrapping ethers.js
Copy
Ask AI
import { Wallet } from 'ethers';import { MandateWallet, type ExternalSigner } from '@mandate.md/sdk';class EthersSigner implements ExternalSigner { constructor(private wallet: Wallet) {} async sendTransaction(tx: Parameters<ExternalSigner['sendTransaction']>[0]) { const response = await this.wallet.sendTransaction({ to: tx.to, data: tx.data, value: tx.value, gasLimit: tx.gas, maxFeePerGas: tx.maxFeePerGas, maxPriorityFeePerGas: tx.maxPriorityFeePerGas, nonce: tx.nonce, }); return response.hash as `0x${string}`; } getAddress() { return this.wallet.address as `0x${string}`; }}const mandateWallet = new MandateWallet({ runtimeKey: process.env.MANDATE_RUNTIME_KEY!, signer: new EthersSigner(ethersWallet), chainId: 84532,});