Technical documentation

TabWrit protocol

A contract-enforced spending-allowance layer for subscriptions and AI agents on NEAR. A Tab is the spending primitive; a Writ is the rule attached to it.

Open the app
Testnet deployment. Vault tab-vault.testnet · token tab-usdc.testnet (tUSDC). The test token has no monetary value. Cross-chain settlement is quoted live through 1Click; the testnet demo does not execute a mainnet settlement.

1. What TabWrit is

TabWrit separates custody from spending authorization. A user deposits NEP-141 tokens into a vault contract and keeps the right to withdraw. For each merchant or AI agent, the user opens a Tab: a rule with a token, per-charge cap, period budget, lifetime cap, expiry and receiver policy. The spender can pull payments inside that rule; it cannot withdraw, pause, revoke or change the rule.

The protocol is intentionally small. It does not try to be a wallet, a subscription manager or a solver. It is the missing on-chain primitive that lets a payee pull a bounded amount without an unlimited token approval. The primary design goal is containment: a compromised merchant or agent key can only spend inside its Tab and cannot withdraw, pause, revoke or change the rule. The final testnet vault has 0 access keys, so there is no deployer backdoor.

2. Quickstart

01

Connect a testnet wallet

Open /app and connect a NEAR testnet wallet through the HOT Labs connector. The owner key never leaves the wallet.

02

Get test tUSDC

If your wallet balance is zero, the app calls the public testnet faucet. It mints 100 tUSDC to your account. This is a real NEP-141 transfer, not a UI number.

03

Deposit and open a Tab

Deposit with ft_transfer_call, then create a Tab with the merchant account tab-merchant.testnet as the spender. The app attaches the 0.01 NEAR storage deposit.

Then open /merchant. The merchant's scoped charge key pulls the invoice. The receipt links to NearBlocks. The Attack Lab shows the full adversarial test matrix with real transaction hashes.

3. Contract API

MethodWho can callWhat it does
ft_on_transferAllowlisted NEP-141 tokenCredits the sender's vault balance; returns 0 to keep the tokens. Unknown tokens are returned in full.
create_tabAny account (owner)Stores a Tab. Requires 0.01 NEAR attached storage, caps ordered per_charge ≤ period ≤ lifetime, an expiry in the future, and a label ≤ 64 bytes.
chargeThe Tab's spenderValidates status, expiry, caps, receiver policy and balance. Consumes the exact nonce, reserves the amount, then calls NEP-141 ft_transfer and settles in a private callback.
pause_tab / resume_tabTab ownerTemporarily blocks or re-enables charges. The period clock continues.
revoke_tabTab ownerPermanently blocks charges. Revocation is terminal.
withdrawBalance ownerTransfers the owner's unspent ledger balance back to their account.
get_tab / get_remaining / get_statsAnyoneRead-only views used by the app, keeper and MCP server.

4. Security model

On-chain guarantees

  • No charge can exceed per_charge_cap, the current period budget or total_cap.
  • No charge can succeed after expiry or while paused/revoked.
  • Only the designated spender can call charge.
  • A fixed allowlist rejects any other receiver.
  • Every charge consumes a monotonic nonce; a replay fails.
  • If the token transfer fails, the callback restores the ledger without erasing later in-flight charges.

Key separation

  • The owner uses wallet-signed transactions; the server never holds the owner key.
  • The merchant / agent uses a NEAR Function-Call Access Key scoped to charge only.
  • Function-Call keys cannot attach a deposit, so a leaked spender key cannot move NEAR or call another contract.
  • The Tab caps limit the damage of a fully compromised spender key.
  • The owner can revoke without rotating the spender's key.

5. Cross-chain settlement

The merchant backend requests an EXACT_OUTPUT quote from the live 1Click API. For a 10 USDC invoice on Base, the response fixes 10 USDC output and returns the required input, a unique NEAR deposit address, a quote deadline and a signed quote payload. The vault sends the quoted input to that deposit address with a normal NEP-141 transfer; a 1Click solver delivers the output on the destination chain.

The quote is real. The testnet demo stops at the quote because 1Click operates on NEAR mainnet. A production deployment needs a mainnet vault, mainnet USDC, storage registration for each fresh deposit address, and a small gas/storage budget. The merchant checkout page labels the quote as a preview and never claims a settlement.

On-chain trust boundary. The contract enforces the token, amount, caps, expiry and nonce. It cannot verify that a 1Click deposit address belongs to a particular merchant. With ReceiverPolicy::Any, the spender chooses the address within the caps; with Allowlist, the owner fixes the receiver but rotating 1Click deposit addresses cannot be used. This is documented rather than hidden.

6. AI agents and MCP

The @tab-protocol/mcp package exposes read tools for Tab details, balances and remaining allowance, plus a scoped charge tool. The agent process receives only the function-call key; the owner key stays in the wallet. An MCP client can inspect the budget before it spends, and the contract still rejects over-budget calls.

The agent cannot create or revoke Tabs through the MCP server. Those are owner actions and require a wallet signature. This is a deliberate separation: a compromised agent can spend inside its Tab, not change the rule.

Gasless NEP-366: a merchant or agent can sign acharge delegate off-chain with its scoped key and let a relayer pay NEAR gas. The relayer only accepts one chargecall to the vault, signed by a Function-Call key scoped tocharge; a FullAccess-key delegate is refused. The merchant's NEAR balance stays unchanged. See the testnet evidence on /lab.

Prompt-injection containment: an agent Tab can allowlist its merchant receiver. A redirected charge to an attacker is rejected on-chain with RECEIVER_NOT_ALLOWED; no funds move and the Tab nonce is not consumed. The legitimate invoice can still settle.

x402-style paid API: the tab_pay_and_fetch MCP tool reads an HTTP 402 challenge, binds the payment to its invoice, preflights the Tab and charges with the scoped key. The API returns data only after it verifies the vault's charge_executed event, including Tab, payer, receiver, asset, amount and invoice reference. Replay, wrong payer, wrong amount, over-cap, redirected receiver and a revoked Tab are all rejected. This is an x402-style flow with a TabWrit payment scheme, not a full x402 protocol implementation. The real testnet evidence is pnpm test:x402 (13/13).

Sustainability: no token, no custody and no fee inside the vault. Revenue is merchant SaaS plus prepaid relayer gas credits; the keeper meters actual NEAR gas per sender at/relay/usage. A Function-Call NEP-366 delegate can contain only one charge action, so the relayer cannot deduct from the payment. See docs/SUSTAINABILITY.md.

7. Testnet addresses

ItemValue
Vaulttab-vault.testnet
Test tokentab-usdc.testnet
Merchant / spendertab-merchant.testnet
Agent / spendertab-agent.testnet
Gas-only relayertab-relayer.testnet
RPChttps://test.rpc.fastnear.com
Explorerhttps://testnet.nearblocks.io

8. Honest limitations

  • The demo uses a test token, not USDC. It has no value.
  • Cross-chain settlement is not executed on testnet; only the live quote is verified.
  • With Any receiver policy, the spender is trusted to choose the receiver within the caps. The owner should use a fixed allowlist when the receiver is known.
  • Each Tab requires a 0.01 NEAR storage deposit. Revoking does not refund the storage.
  • The contract is not upgradeable. The deployer's full-access key was removed after the final deployment, so the code cannot be changed. A bug would require a new deployment.
  • The merchant webhook SDK is a demo integration; production merchants should persist invoice IDs and reconcile 1Click status in their own database.
  • The keeper meters relayer gas, but prepaid-credit enforcement and mainnet pricing are not implemented. The relayer is testnet-only and sponsor-funded.
  • No third-party security audit has been performed. The Rust tests and testnet Attack Lab are the current evidence.

9. Reproduce

# 1. Rust contract tests (16 tests, including fuzz + rollback)
cd contracts/tab-vault && cargo test

# 2. Build the optimized WASM
pnpm build:contract

# 3. Deploy the testnet stack (creates testnet accounts + test token + vault)
pnpm tsx scripts/create-testnet-accounts.ts
pnpm tsx scripts/deploy-testnet.ts
pnpm tsx scripts/setup-spender-key.ts merchant
pnpm tsx scripts/setup-spender-key.ts agent

# 4. Run the real Attack Lab and the agent/gasless labs
pnpm testnet:e2e
pnpm test:meta
pnpm test:relayer-usage
pnpm test:agent-attack
pnpm test:x402

The runner writes every result and transaction hash to docs/evidence/testnet-e2e.json and to the lab page. It exits non-zero on the first mismatch.

Open your vault Attack Lab evidence View contract

TabWrit is MIT licensed. Built for NEAR Hackathon 2026. This page describes the implementation in this repository, not a promise of future performance.