00 / READ THIS FIRST
A governed wallet is a boundary, not a bank account.
The agent proposes a transaction. ARCANUM evaluates the proposal against a signed policy doctrine. Only the governed wallet can settle it. Successful contract events and escalations are indexed in the ledger; a reverted policy denial does not emit a successful DENY event. Signed decision receipts are separate preflight evidence. The model never receives the operator key.
Agent proposes
A typed spend intent enters the policy surface.
Policy decides
Caps, destinations, and velocity are checked.
Human intervenes
Exceptions pause until quorum is satisfied.
01 / DEPLOY A GOVERNED WALLET
Start with a wallet that cannot improvise.
Create the wallet from the operator console, then bind the first doctrine before funding it. Rehearse on Arc Testnet first; this console is running on Arc Mainnet with real USDC.
- 1
Connect the operator
Open the console with your EOA and complete Sign-In with Ethereum (SIWE). The message includes your workspace domain, Arc Mainnet, a nonce, and an expiry. Never paste a private key into an agent runtime.
- 2
Name the boundary
Register the wallet as procurement-bot and record 0x3f…9a2c in the inventory. The wallet address is the stable identity used in every ledger decision.
- 3
Fund the rehearsal
Send a small real USDC balance on Arc Mainnet. Confirm the chain ID and token contract in the wallet drawer before your first proposal.
arcana wallet inspect \ --wallet 0x3f...9a2c \ --network arc-mainnet
02 / AUTHOR A POLICY DOCTRINE
Write the exception before the request.
A doctrine is executable policy with an accountable author. Keep it narrow: name approved vendors, set a transaction cap, and describe what happens when the facts fall outside the line.
vendor in [ AWS, OpenAI ]
and amount ≤ $500
then
ALLOW · record to ledger
otherwise
ESCALATE · require 2 operators
Publish only after a second operator reviews the rendered rule. A doctrine change is a new signed instrument; it does not rewrite previous ledger decisions.
03 / HANDLE YOUR FIRST RESTRAINT
A pause is a successful control.
When growth-bot asks Anthropic for $2,100.00, the request should stop. Review the reason, compare it to the doctrine, then make a recorded decision. Speed is not the objective; legibility is.
Open restraint queue ↗04 / OPERATOR RUNBOOK
Keep the record useful.
05 / SDK QUICKSTART
Deploy your first GuardedWallet.
Stand up a governed agent wallet on Arc Mainnet in five steps. Every proposed payment through the governed wallet is evaluated against a Doctrine before it can settle onchain. Successful movement and escalation events are indexed; a reverted policy denial leaves no successful DENY event. Signed payment decision receipts attest the separate preflight evaluation.
- 1
Install the SDK
Add the Arcanum SDK to your project. It ships with the ArcanumClient, typed contract ABIs, and every verdict type.
- 2
Configure the signer
Point the client at Arc Mainnet and supply an admin signer that will own the Doctrine.
- 3
Deploy the wallet with a Doctrine
Create a GuardedWallet through WalletFactory and attach spend limits, category caps, and an escalation quorum.
- 4
Fund the wallet
Transfer real USDC to the deployed address; it appears in the AGENT REGISTER immediately.
- 5
Watch the Event Stream
Watch indexed movement and escalation events after they settle. Reverted DENY calls do not leave a successful contract event; signed decision receipts are a separate preflight record.
npm install arcanum-sdk viem
Snippets are files, not terminal commands. Save the block below as test.mjs, then run node test.mjs. It reads a live GuardedWallet on Arc Mainnet: real policy, real verdicts, no keys required.
import { ArcanumClient } from "arcanum-sdk";
import { ARC_MAINNET_RPC_URL, arcMainnet, usdcErc20 } from "arcanum-sdk/chains";
import { formatUnits } from "viem";
const walletAddress = process.env.GUARDED_WALLET;
const vendorAddress = process.env.VENDOR_ADDRESS;
if (!walletAddress || !vendorAddress) {
throw new Error("Set GUARDED_WALLET and VENDOR_ADDRESS first.");
}
// The SDK chain definition is sourced from Arc's current Mainnet config.
// Arc's native USDC gas balance uses 18 decimals.
const client = new ArcanumClient({
walletAddress,
chain: arcMainnet,
rpcUrl: process.env.ARC_MAINNET_RPC ?? ARC_MAINNET_RPC_URL,
});
const policy = await client.getPolicy();
// GuardedWallet policy and ERC20 USDC amounts use six-decimal token units.
console.log("Per-tx cap:", formatUnits(policy.perTxCap, 6), "USDC");
const allowed = await client.simulate({
to: vendorAddress,
amount: usdcErc20(1),
});
console.log("Vendor verdict:", allowed.verdict, allowed.reason);
const denied = await client.simulate({
to: vendorAddress,
amount: usdcErc20(1000000),
});
console.log("Large payment verdict:", denied.verdict, denied.reason);Ready to deploy your own? Save this as deploy.mjs, set OPERATOR_KEY to a fundedArc Mainnet key, and run it.
import { WalletFactoryAbi } from "arcanum-sdk";
import { ARC_MAINNET_RPC_URL, arcMainnet } from "arcanum-sdk/chains";
import { createWalletClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const operatorKey = process.env.OPERATOR_KEY;
const agentSigner = process.env.AGENT_SIGNER_ADDRESS;
if (!operatorKey || !agentSigner) {
throw new Error("Set OPERATOR_KEY and AGENT_SIGNER_ADDRESS first.");
}
// The operator account owns the Doctrine. Arc native USDC gas uses 18 decimals;
// GuardedWallet policy values below are ERC20 USDC base units (6 decimals).
const account = privateKeyToAccount(operatorKey);
const walletClient = createWalletClient({
account,
chain: arcMainnet,
transport: http(process.env.ARC_MAINNET_RPC ?? ARC_MAINNET_RPC_URL),
});
// Current Arc Mainnet deployment manifest:
// packages/contracts/deployments/arc-mainnet.json
const WALLET_FACTORY = "0x7077A28C003D9274d45263b04Ac9cB9a58Ab5342";
const policy = {
perTxCap: parseUnits("50", 6),
daily24hCap: parseUnits("500", 6),
monthlyCap: parseUnits("5000", 6), // wallet-wide monthly cap
allowedCategories: 0b11111n,
escalationThreshold: parseUnits("25", 6),
requireAllowlist: true,
freezeOnBlockedVendor: true,
};
const council = [account.address]; // use additional approvers for a real quorum
const txHash = await walletClient.writeContract({
address: WALLET_FACTORY,
abi: WalletFactoryAbi,
functionName: "createWallet",
args: [account.address, "ResearchAgent", policy, [agentSigner], council, 1, 3600],
});
console.log("Deployed:", txHash);Onchain policy changes affect real onchain state. Test with small limits first.
A quorum of 1 disables multi-party approval. Use at least 2 for treasury wallets.
GuardedWallet policy and deployed ERC20 USDC amounts are expressed in 6-decimal base units. Arc native USDC gas uses 18 decimals; source-chain ERC20/CCTP amounts also use 6. One GuardedWallet dollar is parseUnits("1", 6).