Wallet adapters
Adapters execute already-authorised intents. They contain no policy logic; authority lives entirely in the gateway, and every adapter refuses to execute an intent whose receipt does not show an APPROVED decision.
Interface
interface WalletAdapter {
providerName: string;
validateConnection(config: unknown): Promise<ValidationResult>;
simulate(intent: AuthorisedIntent): Promise<WalletSimulationResult>;
execute(intent: AuthorisedIntent, authorisationReceipt: EvidenceReceiptRef): Promise<WalletExecutionResult>;
getStatus(externalReference: string): Promise<WalletExecutionStatus>;
}
execute returns PENDING | CONFIRMED | FAILED with a provider transaction
reference. Callers poll getStatus for pending transactions, then report the
outcome so the budget reservation is resolved — adapter errors therefore never
corrupt budget state.
Reporting the outcome
There is exactly one flow, and which call ends it depends on what the provider did — not on how long ago you claimed the artifact:
| Outcome | Call | Effect on the reservation |
|---|---|---|
| The money moved | POST /v1/decision-intents/{id}/confirm |
reserved → consumed |
| The provider rejected or the transaction failed | POST /v1/authorisation-artifacts/{id}/fail |
released, including a hold that has already gone UNRECONCILED |
| You are abandoning the action entirely, before any artifact was claimed | POST /v1/decision-intents/{id}/cancel |
released, and the decision is terminal |
/fail is the failure path, not /cancel. Once an executor has claimed
an artifact, mnd8t can no longer assume the money stayed put, so /cancel
returns 409 for as long as that claim is unaccounted for — cancelling
releases budget, and releasing budget for a payment that may have gone out is
the one thing the reservation model must not do. /fail is the call that says
"the money definitely did not move", and it is the only way an UNRECONCILED
hold comes back.
After a /fail you can retry the same decision with a fresh artifact
(POST /v1/decision-intents/{id}/reissue-artifact), because failing an
artifact leaves an active reservation alone. Call /cancel only when you
are giving up on the action; it is terminal, and a later reissue returns 409.
See the policy model for the full state table.
Simulated adapter (SIMULATED)
Deterministic and replayable for demos/tests, driven by the destination string:
contains fail → fails; contains delay → PENDING for two status polls, then
CONFIRMED; otherwise immediate success.
EVM testnet adapter (EVM_TESTNET)
Environment configuration (see .env.example):
| Variable | Meaning |
|---|---|
WALLET_TESTNET_ENABLED |
feature flag, default false |
WALLET_TESTNET_RPC_URL |
e.g. https://sepolia.base.org |
WALLET_TESTNET_CHAIN_ID |
84532 (Base Sepolia) or 11155111 (Sepolia) |
WALLET_TESTNET_PRIVATE_KEY |
signer key — testnet funds only |
WALLET_TESTNET_TOKEN_ADDRESS |
optional ERC-20 (testnet USDC); empty = native |
Safety rails, enforced in code and covered by tests:
- disabled unless the flag is on;
- refuses any chain ID that is not a recognised testnet (mainnet is rejected even if configured);
- refuses execution without a valid authorisation artifact;
- the signing key stays inside your executor's environment — never logged, never transmitted, and never visible to mnd8t.
End-to-end example
A reference executor runs the full loop — decision → verification → claim →
adapter execution → status polling → confirmation or /fail. Without testnet
configuration it uses the simulated adapter, so you can watch the whole flow
with no credentials at all.
Download the annotated source — no account contact needed:
Download reference-executor.ts
npm install @mnd8t/sdk
MANDATE_API_KEY=mdt_test_... MANDATE_API_URL=https://api.your-workspace.example \
npx tsx reference-executor.ts
Standalone, swap its @mandate/wallet-adapters import for your own provider
call — that is the one line every real integration replaces anyway. Inside a
clone of the repository the adapters are already there: the same file is
apps/reference-executor, run with
pnpm --filter @mandate/reference-executor start.
AC2 adapter (AC2) — scaffold
AC2 is the Algorand Foundation's open, blockchain-agnostic standard for policy-controlled signing with hardware-bound human approvals and credential isolation. In mnd8t's model it is an enforcement transport: mnd8t decides whether an action is within the delegated authority — against versioned policy and accumulated spend — and AC2 is one way your executor then produces the signature, without the agent (or mnd8t) ever holding a key.
The adapter ships as a scaffold: the safety rails are real and tested
(disabled by default, refuses unapproved intents, https-only signer URLs,
fails closed), while the wire calls to an AC2 signer live behind the
Ac2Transport seam and are unimplemented until a released AC2 specification
version is pinned. Every submission carries the mnd8t evidence reference
(receiptId, receiptHash) so the signer — or the human approving on their
device — can independently verify what was authorised.
Adding a provider
Implement WalletAdapter for your provider, keep its configuration in
environment variables, and cover the safety cases (unapproved artifact,
disabled flag) with tests. Nothing on the mnd8t side changes — that
provider-neutrality is the point, and it is why one mandate can govern
several accounts at once.