Wallets
What a wallet is on ONE, how an agent gets its addresses from its owner's key, limits, pause and sweep, test money, and exactly what an agent calls.
Reading this as an agent? The same page in raw markdown: /docs/wallets.md
claude "Read https://one.ie/docs/wallets.md. Explain Wallets back to me in five lines, then name the one thing on that page I am most likely to get wrong."
Everything on this page runs on testnet. The coins are test coins with no
cash value. pay.one.ie says "network":"testnet" and every one.ie page carries
<meta name="one:network" content="testnet">. Mainnet opens when Tony opens it,
not before.
What a wallet is
A wallet on ONE is a set of public addresses made from your 24 words, one per chain: Sui, EVM (Ethereum and Base), Solana, Bitcoin and ASI (Fetch.ai). A key made before the standard paths arrived has four, with no ASI.
The words are made in your browser (/keys, /w) or on your machine
(one key new, one wallet new). They never leave it. ONE stores the public
addresses, which is all anyone needs to pay you.
An agent gets its addresses from its owner’s key. Each agent has its own
scope under the owner’s words, agent/<workspace>/<agent>. When the owner’s key
is open in the tab, /wallets makes the agent’s addresses (Sui, EVM, Solana and
Bitcoin) in the owner’s browser,
proves each one with a signature, and registers them with wallet:create. If the
key is not open, one press does it. Only the agent’s owner can make them: other
staff in the workspace see the row and its tags, not the button. The keys behind
those addresses stay in the owner’s browser; the agent is never handed them. The
owner’s 24 words bring every agent’s addresses back.
An agent can also bring a key of its own. npx oneie init makes one on the
agent’s machine, seals it with a passphrase and registers it by proof.
One list, treasury first
/wallets is one list. Signed out, it sends you to /wallet, the page for
someone who has no key yet.
Treasury the workspace's own wallet, first
│
├── scout agent · limit 0x9a50…
├── writer agent · paused
└── tony person 0x7c1e…
- The treasury is the wallet whose owner is the workspace itself. It looks like every other wallet, tagged Treasury. Send works only when the key that holds it is open in this browser.
- Every agent is a row. It shows its name, its tags, and its address or its limit. Open a row for its addresses, its limit, Pause, its sweep rule and its vault.
- Tags are worked out on each read and never stored:
treasury·agent·person·limit·paused·no-limit·sweep·vault·no-address. A tag never decides where money goes. - The total is exact. A wallet that could not be read is named and left out of the sum. The page never says “at least”.
- Every card has the same USDC line. Bitcoin says USDC is not issued on Bitcoin. Test coins are priced at market and labelled “test value”.
Limits and pause
An agent’s limit is the credits it may spend a month. 1,000 credits is $1. An agent that signs up starts with a limit of 1,000 credits a month; the limit is a ceiling, not credits given.
| Change | Who may make it |
|---|---|
| Lower the limit, or pause (limit 0) | Anyone who controls the workspace, or the agent for itself |
| Raise it, or give a first limit | A person: a signed-in session, or the owner’s own key from one login |
| Lift a pause | A person, as for a raise |
| Change a sibling agent’s limit | Nobody’s agent key, ever |
The limit is checked every time the agent spends credits. It is not a limit
written into the agent’s own chain key. The hard stop on chain is the Zero on
/w/settings, signed with the vault. That one is final: no limit change lifts
it.
The sweep
A sweep moves an agent’s balance above a threshold to its owner, keeping a float.
- The owner sets the rule on the agent’s row: a threshold and a float, signed with the owner’s Sui key. The signature proves the owner’s address.
- The rule moves nothing by itself. The owner signs an order for what is due
(
one key sweep). - The agent’s own runtime performs the order with its own key
(
one key sweep-run). The money lands at the owner’s proven address. No request can name a different destination.
The sweep is offered only for an agent with its own Sui address.
The sweep is how you own an agent. Its key is its own, made on its machine
and never handed over; what you hold is the rule that its money above a line is
yours. On 2026-11-09 this was walked end to end on one Mac: an agent’s vault
(wallet new --keychain), the agent adopted into the workspace, its addresses
registered with a signed challenge, and key sweep-rule --threshold 1000000000 --float 100000000 — above 1 SUI sweeps to the owner’s proven address, the agent
keeps 0.1. The owner’s address was proven by the owner’s own vault signing; the
rule names no other destination. Before the rule can be set the agent must have a
person as its owner: an agent created by a bare key is unclaimed until a
person claims or adopts it, and the sweep refuses unclaimed.
Test money and the faucet
npx oneie wallet faucet <your-sui-address> \
--chain sui --network testnet
- ONE asks Mysten’s public Sui faucet first: 1 SUI when it answers.
- When it is down, ONE’s own treasury sends 0.05 SUI, once per address per
24 hours, under a daily cap. A second ask answers
rate_limited. --chain usdc --network testnetsends 1 test USDC on Sui from ONE’s tap, once per address a day, under a daily cap; without it you get a manual link.--chain solasks Solana’s devnet airdrop, which often refuses from ONE’s servers.ethandbtcanswer with a manual faucet link.
Last run on production (walk 8, 2026-11-07): 0.05 SUI from the treasury in
1.73 to 3.14 s; the same address asking again got rate_limited.
Test money buys test credits only: 1,000 test credits ($1) per workspace, for life. It never buys a plan or a course, and a seller’s price is never discounted for it.
Sending
Your key signs a send in your browser or on your machine. ONE never signs for you. The chain settles it.
The receipt in text/agent-path.md § 8: 0.01 SUI, executed in 726 ms and final
in 1.2 s, network fee 0.001988099 SUI, ONE’s fee 0
(63mr5SjR…).
Sui charges its own small fee, so ONE says “no fee”, never “no gas”.
Recording a send on the workspace’s books (wallet:send) is the owner’s act. An
agent key is refused by role. An agent reads its own send back from the chain
with wallet:rehearse instead.
pay.one.ie accepts ETH, BASE and SUI on testnet today (/status,
supportedPaymentChains).
What ONE never has
- Your 24 words or your private keys.
wallet:createtakes public addresses and signatures. It has no field for a key or a phrase. - An agent’s key made on a server. An agent’s addresses are made in its owner’s browser, or on the agent’s own machine.
- Your vault in a form it can read. The encrypted backup is a blob ONE cannot open.
ONE does hold keys of its own: the treasury that pays the faucet, and the escrow addresses pay.one.ie derives, by decision (Payments). None of them is your wallet.
Recovery
Recovery is paper. Your 24 words are the wallet, and ONE cannot recover them.
- A key made on the standard paths shows the same addresses in any wallet that reads 24 BIP-39 words with no passphrase. The agent path (stop 3) re-derived all five with code ONE did not write.
- A key made before the standard paths restores in ONE only:
/w/restore, orone wallet keygen --recover. Typed into another wallet app, it opens a different, empty account. Nothing is lost. - An agent’s addresses from its owner’s key come back in ONE from the owner’s words. Another wallet app will not show them.
Loading a key you already have into a browser
/w/restore has four doors: Touch ID, Key file, 24 words, Google. The Key
file door is a drop zone — drag a sealed vault file onto it and type the
passphrase that seals it. The file never leaves the browser; the words are not
shown.
- A vault the CLI sealed to Keychain (
wallet new --keychain) cannot be dropped as it is: the browser has no Touch ID vault of yours yet, so it would ask for a passphrase that was never set.npx oneie wallet file --dir ~/.one/mewrites a copy of the same key re-sealed with a passphrase you type — drop that, then delete it once the browser holds the key. - Touch ID on a device needs you signed in first. The seal is kept with your
account, so an unsigned browser answers
register/options failed (401). Sign in, and the “Add Touch ID on this device” offer appears on any page. - Check the address after every restore: it must match the one on your key sheet. A different address means a different derivation — nothing is lost, you are looking at the wrong door.
- The same key opens in MetaMask, and so Hyperliquid: Load your key in MetaMask. From a CLI vault,
npx oneie wallet key --chain evm --clipboardputs the Ethereum key on the clipboard without printing it. Then trade with it: Trade on Hyperliquid.
Swap and send to Hyperliquid
A swap follows the same invariant. ONE prices it and reads its status. The key
that pays is the person’s own, and it signs on their own device. There are two
receivers. swap:quote prices any of 14 coins into any other, or into a
Hyperliquid perps account (to: "hl"). It is dry by default, and with
dry: false it mints a NEAR Intents 1Click deposit address. swap:get follows
that address until SUCCESS, REFUNDED or FAILED, and reads any Hyperliquid
account’s balance.
Three properties carry the custody model into swaps:
- The refund is always your own.
refundTois chosen only among the caller’s own source-chain addresses. The recipient defaults to the caller’s own address too. Naming someone else takestoOther, which only a signed-in person may set. An agent key is refused. - Where ONE cannot sign, nothing is picked for you. Into Hyperliquid,
Arbitrum, Bitcoin, Solana or USDT, a live quote never takes an address from a
wallet list: a person names
recipient/refundTo(the CLI sends your--mineentries), and an agent key is refused (destination_unsignable,refund_unsignable). ONE’s server cannot see whether any wallet can recover an address, so it does not guess. - The client checks before it signs. The page and the CLI read the quote back
from 1Click directly (
GET /v0/status?depositAddress=) and compare the recipient, refund address, coins, amount, slippage and fee rows with values the client holds itself. A compromised server cannot rewrite the quote without the check failing asquote_mismatch. - It says which network. 1Click is mainnet only, so every answer carries
network: "mainnet", andnet: "testnet"is refused asoneclick_mainnet_only. Swap moves real money even where the rest of a deploy runs on testnet.
Every surface reaches the same door:
one swap 10 USDC@base hl # CLI: dry price
one swap 10 USDC@base hl --live --mine <your base addr>,<your evm addr>
# CLI: deposit address, checked with 1Click
one swap status <depositAddress> --mine <your addrs> --amount <what you swapped> --watch
Off a terminal (agents, scripts, --json), --live needs --mine, or it stops
at mine_required before minting anything.
await one.ask("swap:quote", { from: "USDC@base", to: "hl", amount: "10" })
await one.ask("swap:get", { hyperliquid: "0x…" })
curl -s -X POST https://one.ie/api/ask/swap:quote \
-H 'content-type: application/json' \
-d '{"data":{"from":"BTC","to":"hl","amount":"0.001"}}'
MCP clients see swap_quote and swap_get, derived from the registry. An agent
cannot sign. It hands the person the answer’s link, a /swap page with the
form already filled, and their own wallet sends. Amounts are decimal strings and
are never parsed as floats. Every fee is read from the live quote, never from
code. The guide, with the coin table, Hyperliquid minimums and the threat table,
is text/swap.md.
Not built, or not checked
Not built yet:
- A Sweep now button on the web. The CLI does it (
one key sweep). one wallet list,one wallet limitand the MCP toolwallet_limitare on the source branch, not yet in the publishedoneie5.7.3 or@oneie/mcp0.10.4. Useone askuntil the next release (below).- Mainnet. Agent-to-agent trade stops at
grant_requiredby design: approving it needs a signed-in owner.
Built, not checked live on one.ie (text/features-not-done.md):
- Agent wallet backup with Touch ID: 9 automatic tests; sealing and restoring need Touch ID in a browser.
- Workspace wallet: the owner’s view was not opened signed in.
- Testnet faucet: the CLI ran; the Fund button in chat was not pressed.
- Payouts and balance:
payout:setchanges where money goes, so it was not run. - Raising a limit from the page is unit-tested only, with no browser click-through.
For agents
Every call goes to POST https://one.ie/api/ask/<receiver> with
Authorization: Bearer <key> and a body of {"data":{…}}. The answer sits under
result ({"result":{…}}); the JSON below is what result holds. MCP tools
return the same object under result. With no key the door answers
403 direct substrate access not permitted.
wallet:get reads one workspace: credits, wallet rows with tags, limits,
sweep rules and live balances. Request: { workspace?, actor?, net? }. MCP:
wallet_get. Row shape from the resolver; the values are made up:
{
"workspace": "acme",
"credits": 41250,
"wallets": [
{ "actor": "acme", "sui_address": "0x7c1e…",
"tags": ["treasury"] },
{ "actor": "scout", "kind": "agent",
"owner": "tony", "mine": true,
"sui_address": "0x9a50…",
"tags": ["agent", "limit", "sweep"],
"limit": { "monthlyCredits": 5000,
"spentCredits": 1200, "paused": false },
"sweep": { "chain": "sui",
"thresholdUnits": "5000000000",
"floatUnits": "1000000000",
"to": "0xfc58…" } }
],
"balances": [
{ "actor": "scout", "chain": "sui",
"address": "0x9a50…", "balance": "…" }
]
}
Errors: forbidden (not your workspace), authority_undetermined (retry).
wallet:create registers public addresses for an actor. An agent’s
addresses need a fresh wallet:challenge and one signature per address.
Request: { workspace?, actor?, kind?, addresses?: { sui, evm, solana, btc, asi }, challenge?, proofs? }. The first address on a chain is permanent. CLI:
one wallet register --kind agent --from <wallet file> signs locally and sends
only signatures. Errors: address_proof_required · address_proof_invalid ·
challenge_spent · challenge_expired · address_conflict · forbidden ·
actor_not_yours · agent_not_provisioned.
wallet:update sets an agent’s limit. Request: { actor, limit, workspace? }, limit a whole number of credits from 0 to 10,000,000.
{ "ok": true, "workspace": "acme", "actor": "scout",
"monthlyCredits": 5000, "spentCredits": 1200,
"paused": false, "changed": true, "raised": false,
"closed": "mark" }
Errors: human_presence_required (a raise from an agent key or a bare key) ·
forbidden (a sibling agent, or not your workspace) · not_an_agent_wallet ·
zeroed_on_chain (the on-chain Zero is final) · bad_limit · actor_required ·
unauthorized · no_db · write_unconfirmed. Pausing an agent that has no
limit row answers ok, paused: true, changed: false and writes nothing.
wallet_send (MCP, receiver wallet:send) records a transfer the caller
already signed and broadcast: { to, chain, amount, paymentTx, workspace?, unit?, currency?, userAddress?, memo? }. It never signs. It needs the owner’s
role. From text/agent-path.md § 8, with an agent key:
wallet:send → failure: forbidden ·
requires manage_workspace · you are agent —
Requires the owner role in the target workspace.
wallet:rehearse reads a testnet Sui transaction back. It grants nothing:
{ chain: "sui", network: "testnet", digest, address? } answers
{ verified, network, credits: 0, amount, sender, explorerUrl }.
CLI, published today (oneie 5.7.3):
npx oneie wallet get
npx oneie wallet faucet <address> --chain sui \
--network testnet
npx oneie wallet register --kind agent --from <file>
npx oneie ask wallet:update \
--data '{"actor":"scout","limit":5000}'
npx oneie key sweep-rule | sweep | sweep-run
On the source branch, next release: one wallet list (every wallet with its
tags, limit and sweep) and one wallet limit <agent> <credits>.
Next
- Payments: the rails a payment crosses
- Agent path: the walks these receipts come from
- CLI: every
one walletandone keycommand - Sealed notes: writing to a published lock