Docs

Haltercard

A card for your AI agent. You set the limits, it spends inside them.

What it is

Haltercard is one contract on Robinhood Chain (chain id 4663). Anyone can issue a card: send ETH into it and name an agent address. The ETH stays in the contract, counted for that card only. The agent can pay from the card with pay, and only inside the rules you set. You, the holder, keep every other control.

There is no owner, no admin key, no upgrade path and no fee in the contract.

Contract

Reading contracts.json…

Demo card and team wallets

Card #1 is the live demo on the home page. It is a normal card, with no special rights.

Wire an agent

Give the agent its own private key (a fresh wallet, holding only a little ETH for gas). Issue a card with that address as the agent. Then the agent pays like this, using viem:

Simulate first, as above: if a rule would fail, simulateContract throws with the custom error name and nothing is sent. The memo is a bytes32: up to 32 bytes of text, stored in the statement. Do not put secrets in it.

Limits

Freeze first

When you change the agent or lower the limits, freeze the card first, make the change, then unfreeze. If the old agent key has leaked, whoever holds it could still spend inside the old limits until your change lands on chain. A frozen card cannot pay at all, so nothing slips through in between. The issue page offers this as one step (three transactions: freeze, change, unfreeze).

Order of checks in pay

Not closed → not frozen → not expired → recipient allowed → amount above zero → amount within per-payment cap → amount fits the daily window → amount within balance. Then the payment is written to the statement and the ETH is sent. If the recipient refuses the ETH, the whole call reverts.

Errors

ErrorMeaning

Statement

Every payment is stored on chain as { at, to, amount, memo } and also emitted as a Paid event. Read it with spendCount(id) and statement(id, offset, limit): newest first (offset 0 is the latest payment), at most 100 per call. cardsOf(holder) lists a holder's cards. cardsForAgent(agent) lists the cards an address was made agent of; after setAgent the old agent's list still holds the id, so always check card(id).agent.

Functions

FunctionWho
issue(agent, perTxLimit, dailyLimit, expiresAt, anyRecipient, allow[], label)Anyone; becomes holder. Payable: msg.value funds the card.
topUp(id)Anyone. Payable.
withdraw(id, amount, to)Holder
freeze(id) · unfreeze(id)Holder
setLimits(id, perTx, daily)Holder
setExpiry(id, t)Holder. 0 = no expiry. Unix seconds, UTC.
setAllowed(id, addr, bool)Holder
setAnyRecipient(id, bool)Holder
setAgent(id, agent)Holder
close(id)Holder. Refunds the balance, permanent.
pay(id, to, amount, memo)Agent
card(id) · allowed(id, addr) · spendCount(id) · statement(id, offset, limit) · cardsOf(a) · cardsForAgent(a) · nextId()Views. A card id that does not exist returns holder 0x0.

Plain ETH sent straight to the contract is rejected; use topUp(id). So every wei the contract holds belongs to a card.

What it does not do

Times

All times on this site are UTC.