A stolen agent key can move up to that agent's auto limit per UTC day with no approval, and double that across UTC midnight, until you freeze it.
Fund the Work. Control the Spend.
Your Agents.
Your Spending Rules.
A budget desk for AI agents on Robinhood Chain. Each agent gets a daily budget, a list of providers, a threshold above which you decide and a limit on what moves without you in a day. Your funds stay in your wallet until a payment passes. The desk is tested but not deployed, so nothing on this site can move real funds today. A stolen agent key can still spend up to its limits, and an allowance is separate from them.
Keep scrolling, go through it
Agents spend all day.
The bill comes later.
In pieces.
Each key pays for its own jobs from its own place, so nobody sees the day until it is over. Here is a sample day read through the desk's real rules, line by line.
About 0 USDG moved, simulated Sample day
Morning, UTC21.50 USDG
Afternoon, UTC12.75 USDG
08:05 UTC. support pays inference-host 0.80 for ticket-4101. Paid.
A threshold limits one payment, not the day. The auto limit caps what moves without approval in a UTC day, so payments split below the threshold wait for you once they reach it. A stolen key can still move up to that per day, and double across UTC midnight.
Which day, simulated in USDGOpen this day in the simulatorEvery payment stops at nine checks.
In this order.
The agent key calls the desk. The first failed check ends the call. A refused payment reverts, so it costs the agent gas and moves nothing.
001 / Is the caller this agent's key?
Only the key you registered can ask. Any other wallet is turned away.
No: refused You, at registration
002 / Is the fleet or the agent frozen?
Until you restart it.
Yes: refused You, any time
003 / Does the agent have a budget in this token?
Other tokens in your wallet stay out of reach.
No: refused You, per token
004 / Is this provider on the agent's list?
An agent cannot pay a new address on its own.
No: refused You, per agent
005 / Is the amount within the cap for one payment?
Outright, never queued.
No: refused You, per token
006 / Does it fit in today's budget?
Until the next UTC day.
No: refused You, per token
007 / Is this one payment above your approval threshold?
This payment stops here. A request waits for your transaction, for up to three days.
Yes: it waits You, per token
008 / Would it pass what can move without approval today?
The auto limit, per UTC day. Past it the payment waits in the same way. It bounds a loss, it does not stop one.
Yes: it waits You, per token
009 / All of it passed
Tokens move from your wallet to the provider in the same call. The desk keeps nothing.
Paid The desk
BurzoSpendDesk.sol
// SPDX-License-Identifier: MITpragma solidity 0.8.28;import {BurzoTokenCalls as TokenCalls} from "./BurzoTokenCalls.sol";/// @title BurzoSpendDesk/// @notice Spending rules for many agent keys, checked at the moment of payment./// The owner keeps funds in their own wallet and gives this desk an ERC-20 allowance. An agent key asks for a/// payment; the desk checks that agent's rules and moves tokens straight from the owner to the provider in the/// same call, or records a request the owner has to approve./// @dev No admin, no fee, no upgrade, no pause path other than each owner's own freeze. The desk never holds a balance/// and accepts no native coin. Ownership of an agent cannot move: a transfer would let anyone point their agent/// at a victim who has an allowance here.contract BurzoSpendDesk {// ------------------------------------------------------------------ limitsuint256 public constant MAX_FLEET = 64;uint256 public constant MAX_TOKENS = 8;uint256 public constant MAX_PROVIDERS = 64;uint256 public constant MAX_OPEN_REQUESTS = 32;uint256 public constant MAX_BATCH = 32;uint256 public constant MAX_NAME = 32;uint256 public constant REQUEST_TTL = 3 days;// ------------------------------------------------------------------ outcome codes (see src/lib/rules.ts)uint8 public constant PASS = 0;uint8 public constant NEEDS_APPROVAL = 1;uint8 public constant UNKNOWN_AGENT = 2;uint8 public constant RETIRED = 3;uint8 public constant FLEET_FROZEN = 4;uint8 public constant AGENT_FROZEN = 5;uint8 public constant TOKEN_NOT_BUDGETED = 6;uint8 public constant PROVIDER_NOT_ALLOWED = 7;uint8 public constant ZERO_AMOUNT = 8;uint8 public constant OVER_PAYMENT_CAP = 9;uint8 public constant OVER_DAILY_WINDOW = 10;uint8 public constant TOO_MANY_OPEN = 11;uint8 public constant OVER_AUTO_LIMIT = 12;// ------------------------------------------------------------------ typesenum RequestStatus {None,Pending,Paid,Rejected,Cancelled,Expired}struct Agent {address owner;address key;uint64 registeredAt;bool frozen;bool retired;string name;}struct Budget {uint128 dailyLimit;uint128 paymentCap;uint128 approvalThreshold;uint128 autoDailyLimit;uint128 spent;uint128 autoSpent;uint40 day;bool enabled;}struct Request {uint256 agentId;address provider;address token;uint256 amount;bytes32 jobId;uint64 createdAt;uint64 expiresAt;RequestStatus status;}struct AgentView {uint256 id;address owner;address key;string name;uint64 registeredAt;bool frozen;bool fleetFrozen;bool retired;uint256 openRequests;address[] tokens;address[] providers;}struct BudgetView {bool enabled;uint128 dailyLimit;uint128 paymentCap;uint128 approvalThreshold;uint128 autoDailyLimit;uint256 spentToday;uint256 leftToday;uint256 autoSpentToday;uint256 autoLeftToday;uint256 resetsAt;}struct OpenRequest {uint256 id;Request request;}struct Check {uint8 code;bool allowanceRead;uint256 allowance;bool balanceRead;uint256 balance;}// ------------------------------------------------------------------ stateuint256 public agentCount;uint256 public requestCount;mapping(address owner => bool) public fleetFrozen;mapping(uint256 agentId => Agent) private _agents;mapping(address owner => uint256[]) private _fleet;mapping(uint256 agentId => uint256) private _fleetPos;mapping(uint256 agentId => mapping(address token => Budget)) private _budgets;mapping(uint256 agentId => address[]) private _tokens;mapping(uint256 agentId => mapping(address token => uint256)) private _tokenPos;mapping(uint256 agentId => address[]) private _providers;mapping(uint256 agentId => mapping(address provider => uint256)) private _providerPos;mapping(uint256 requestId => Request) private _requests;mapping(uint256 agentId => uint256[]) private _open;mapping(uint256 requestId => uint256) private _openPos;uint256 private _lock = 1;// ------------------------------------------------------------------ eventsevent AgentRegistered(uint256 indexed agentId, address indexed owner, address indexed key, string name);event KeyRotated(uint256 indexed agentId, address indexed owner, address indexed key);event AgentFrozen(uint256 indexed agentId, address indexed owner, bool frozen);event FleetFrozen(address indexed owner, bool frozen);event AgentRetired(uint256 indexed agentId, address indexed owner);event BudgetSet(uint256 indexed agentId, address indexed owner, address indexed token, uint128 dailyLimit, uint128 paymentCap, uint128 approvalThreshold, uint128 autoDailyLimit);event BudgetRemoved(uint256 indexed agentId, address indexed owner, address indexed token);event ProviderSet(uint256 indexed agentId, address indexed owner, address indexed provider, bool allowed);event Paid(uint256 indexed agentId, address indexed owner, address indexed provider, bytes32 jobId, address token, uint256 amount, uint256 requestId);event RequestOpened(uint256 indexed requestId, address indexed owner, uint256 indexed agentId, bytes32 jobId, address provider, address token, uint256 amount, uint64 expiresAt);event RequestClosed(uint256 indexed requestId, uint256 indexed agentId, address indexed by, RequestStatus status);// ------------------------------------------------------------------ errorserror NotOwner(uint256 agentId);error NotAgentKey(uint256 agentId);error Refused(uint8 code);error BadName();error BadKey(address key);error BadProvider(address provider);error BadLimits();error UnknownAgent(uint256 agentId);error AgentIsRetired(uint256 agentId);error FleetFull();error TooManyTokens();error TooManyProviders();error BatchTooLarge();error TokenHasNoBudget(address token);error RequestNotPending(uint256 requestId);error RequestHasExpired(uint256 requestId);error RequestStillOpen(uint256 requestId);error TransferMismatch(uint256 expected, uint256 left, uint256 arrived);error Reentered();modifier nonReentrant() {if (_lock != 1) revert Reentered();_lock = 2;_;_lock = 1;}modifier onlyOwnerOf(uint256 agentId) {Agent storage a = _agents[agentId];if (a.owner == address(0)) revert UnknownAgent(agentId);if (a.owner != msg.sender) revert NotOwner(agentId);_;}modifier active(uint256 agentId) {if (_agents[agentId].retired) revert AgentIsRetired(agentId);_;}// ------------------------------------------------------------------ owner: agents/// @notice Adds an agent to the caller's fleet. The caller is its owner for good.function registerAgent(string calldata name, address key) external nonReentrant returns (uint256 agentId) {_checkName(name);_checkKey(key);if (_fleet[msg.sender].length >= MAX_FLEET) revert FleetFull();agentId = ++agentCount;Agent storage a = _agents[agentId];a.owner = msg.sender;a.key = key;a.registeredAt = uint64(block.timestamp);a.name = name;_fleet[msg.sender].push(agentId);_fleetPos[agentId] = _fleet[msg.sender].length;emit AgentRegistered(agentId, msg.sender, key, name);}/// @notice Replaces the agent's key. The old key stops working in this transaction, and every open request of this/// agent is closed as rejected, because the key that asked for it may be the reason for the rotation.function rotateKey(uint256 agentId, address key) external nonReentrant onlyOwnerOf(agentId) active(agentId) {_checkKey(key);if (_providerPos[agentId][key] != 0) revert BadKey(key);_agents[agentId].key = key;_rejectAllOpen(agentId);emit KeyRotated(agentId, msg.sender, key);}/// @notice Stops or restarts one agent. Never blocked by a payment in progress.function freezeAgent(uint256 agentId, bool frozen) external onlyOwnerOf(agentId) active(agentId) {_agents[agentId].frozen = frozen;emit AgentFrozen(agentId, msg.sender, frozen);}/// @notice Stops or restarts every agent of the caller in one transaction, whatever the size of the fleet.function freezeFleet(bool frozen) external {fleetFrozen[msg.sender] = frozen;emit FleetFrozen(msg.sender, frozen);}/// @notice Ends an agent for good: it leaves the fleet and its open requests are closed as rejected.function retireAgent(uint256 agentId) external nonReentrant onlyOwnerOf(agentId) active(agentId) {Agent storage a = _agents[agentId];a.retired = true;uint256[] storage list = _fleet[msg.sender];uint256 pos = _fleetPos[agentId];uint256 last = list[list.length - 1];list[pos - 1] = last;_fleetPos[last] = pos;list.pop();delete _fleetPos[agentId];_rejectAllOpen(agentId);emit AgentRetired(agentId, msg.sender);}// ------------------------------------------------------------------ owner: rules/// @notice Sets the limits of one agent for one token, in the token's base units. Today's spending is kept./// @param paymentCap largest single payment; anything above is refused, never queued./// @param approvalThreshold payments above this wait for the owner. Equal to the cap means no single size waits; zero means all do./// @param autoDailyLimit the most that can leave without the owner in one UTC day. Payments under the threshold that/// would pass it wait as well, so splitting a large sum into small payments stops here. Zero means all wait; equal/// to the daily limit means no separate limit. Approved payments count toward the daily limit, not this one.function setBudget(uint256 agentId, address token, uint128 dailyLimit, uint128 paymentCap, uint128 approvalThreshold, uint128 autoDailyLimit) external nonReentrant onlyOwnerOf(agentId) active(agentId) {if (token.code.length == 0) revert TokenCalls.NotAToken(token);if (dailyLimit == 0 || paymentCap == 0 || paymentCap > dailyLimit || approvalThreshold > paymentCap || autoDailyLimit > dailyLimit) revert BadLimits();if (_tokenPos[agentId][token] == 0) {if (_tokens[agentId].length >= MAX_TOKENS) revert TooManyTokens();_tokens[agentId].push(token);_tokenPos[agentId][token] = _tokens[agentId].length;}Budget storage b = _budgets[agentId][token];b.dailyLimit = dailyLimit;b.paymentCap = paymentCap;b.approvalThreshold = approvalThreshold;b.autoDailyLimit = autoDailyLimit;b.enabled = true;emit BudgetSet(agentId, msg.sender, token, dailyLimit, paymentCap, approvalThreshold, autoDailyLimit);}/// @notice Stops an agent from spending one token. What it already spent today is remembered if the budget returns.function removeBudget(uint256 agentId, address token) external nonReentrant onlyOwnerOf(agentId) {uint256 pos = _tokenPos[agentId][token];if (pos == 0) revert TokenHasNoBudget(token);address[] storage list = _tokens[agentId];address last = list[list.length - 1];list[pos - 1] = last;_tokenPos[agentId][last] = pos;list.pop();delete _tokenPos[agentId][token];_budgets[agentId][token].enabled = false;emit BudgetRemoved(agentId, msg.sender, token);}/// @notice Adds to or removes from the agent's approved providers.function setProviders(uint256 agentId, address[] calldata providers, bool allowed) external nonReentrant onlyOwnerOf(agentId) active(agentId) {if (providers.length > MAX_BATCH) revert BatchTooLarge();Agent storage a = _agents[agentId];address[] storage list = _providers[agentId];for (uint256 i; i < providers.length; ++i) {address p = providers[i];uint256 pos = _providerPos[agentId][p];if (allowed) {if (p == address(0) || p == address(this) || p == a.owner || p == a.key) revert BadProvider(p);if (pos != 0) continue;if (list.length >= MAX_PROVIDERS) revert TooManyProviders();list.push(p);_providerPos[agentId][p] = list.length;} else {if (pos == 0) continue;address last = list[list.length - 1];list[pos - 1] = last;_providerPos[agentId][last] = pos;list.pop();delete _providerPos[agentId][p];}emit ProviderSet(agentId, msg.sender, p, allowed);}}// ------------------------------------------------------------------ agent key/// @notice Pays a provider from the agent owner's wallet, or opens a request when the amount is above the threshold or/// would pass what this agent may spend without approval today./// @return paid true when the tokens moved in this call./// @return requestId the new request when they did not; zero otherwise.function pay(uint256 agentId, address provider, address token, uint256 amount, bytes32 jobId) external nonReentrant returns (bool paid, uint256 requestId) {Agent storage a = _agents[agentId];if (a.owner == address(0)) revert Refused(UNKNOWN_AGENT);if (msg.sender != a.key) revert NotAgentKey(agentId);uint8 code = _evaluate(agentId, provider, token, amount);if (code != PASS && code != NEEDS_APPROVAL && code != OVER_AUTO_LIMIT) revert Refused(code);if (code != PASS) {requestId = _openRequest(agentId, a.owner, provider, token, amount, jobId);return (false, requestId);}_record(agentId, token, amount, true);emit Paid(agentId, a.owner, provider, jobId, token, amount, 0);_settle(a.owner, provider, token, amount);return (true, 0);}/// @notice The agent key withdraws its own open request.function cancelRequest(uint256 requestId) external nonReentrant {Request storage r = _requests[requestId];if (r.status != RequestStatus.Pending) revert RequestNotPending(requestId);if (_agents[r.agentId].key != msg.sender) revert NotAgentKey(r.agentId);_close(requestId, RequestStatus.Cancelled);}// ------------------------------------------------------------------ owner: requests/// @notice Pays an open request. All of the rules are read again, except the threshold and the auto limit the owner is/// answering. The payment counts toward the daily limit and leaves the auto allowance as it is.function approveRequest(uint256 requestId) external nonReentrant {Request storage r = _requests[requestId];if (r.status != RequestStatus.Pending) revert RequestNotPending(requestId);uint256 agentId = r.agentId;Agent storage a = _agents[agentId];if (a.owner != msg.sender) revert NotOwner(agentId);if (block.timestamp >= r.expiresAt) revert RequestHasExpired(requestId);address provider = r.provider;address token = r.token;uint256 amount = r.amount;uint8 code = _evaluate(agentId, provider, token, amount);// The threshold and the auto limit are what this approval answers; the open-request ceiling does not apply to a closing request.if (code != PASS && code != NEEDS_APPROVAL && code != OVER_AUTO_LIMIT && code != TOO_MANY_OPEN) revert Refused(code);_close(requestId, RequestStatus.Paid);_record(agentId, token, amount, false);emit Paid(agentId, msg.sender, provider, r.jobId, token, amount, requestId);_settle(msg.sender, provider, token, amount);}/// @notice Refuses several open requests at once, for example everything a stolen key has queued. Ids that are no longer/// open are skipped, so nobody can undo the batch by closing one of its requests first.function rejectRequests(uint256[] calldata requestIds) external nonReentrant {if (requestIds.length > MAX_BATCH) revert BatchTooLarge();for (uint256 i; i < requestIds.length; ++i) {Request storage r = _requests[requestIds[i]];if (r.status != RequestStatus.Pending) continue;if (_agents[r.agentId].owner != msg.sender) revert NotOwner(r.agentId);_close(requestIds[i], RequestStatus.Rejected);}}/// @notice Anyone can close a request past its expiry, which frees its place in the queue.function closeExpired(uint256 requestId) external nonReentrant {Request storage r = _requests[requestId];if (r.status != RequestStatus.Pending) revert RequestNotPending(requestId);if (block.timestamp < r.expiresAt) revert RequestStillOpen(requestId);_close(requestId, RequestStatus.Expired);}// ------------------------------------------------------------------ viewsfunction today() public view returns (uint40) {return uint40(block.timestamp / 1 days);}function agent(uint256 agentId) public view returns (AgentView memory v) {Agent storage a = _agents[agentId];v = AgentView(agentId, a.owner, a.key, a.name, a.registeredAt, a.frozen, fleetFrozen[a.owner], a.retired, _open[agentId].length, _tokens[agentId], _providers[agentId]);}/// @notice Every live agent of an owner in one read, for the dashboard.function fleetOf(address owner) external view returns (AgentView[] memory list) {uint256[] storage ids = _fleet[owner];list = new AgentView[](ids.length);for (uint256 i; i < ids.length; ++i) list[i] = agent(ids[i]);}function fleetIdsOf(address owner) external view returns (uint256[] memory) {return _fleet[owner];}function budget(uint256 agentId, address token) external view returns (BudgetView memory v) {Budget storage b = _budgets[agentId][token];uint256 spent = _spentToday(b);uint256 autoSpent = _autoSpentToday(b);v.enabled = b.enabled;v.dailyLimit = b.dailyLimit;v.paymentCap = b.paymentCap;v.approvalThreshold = b.approvalThreshold;v.autoDailyLimit = b.autoDailyLimit;v.spentToday = spent;v.leftToday = spent >= b.dailyLimit ? 0 : b.dailyLimit - spent;v.autoSpentToday = autoSpent;// What can still leave without the owner: the auto allowance, as far as the daily limit leaves room for it.uint256 autoLeft = autoSpent >= b.autoDailyLimit ? 0 : b.autoDailyLimit - autoSpent;v.autoLeftToday = autoLeft < v.leftToday ? autoLeft : v.leftToday;v.resetsAt = (uint256(today()) + 1) * 1 days;}function isProvider(uint256 agentId, address provider) external view returns (bool) {return _providerPos[agentId][provider] != 0;}function request(uint256 requestId) external view returns (Request memory) {return _requests[requestId];}function openRequestsOf(uint256 agentId) external view returns (OpenRequest[] memory list) {uint256[] storage ids = _open[agentId];list = new OpenRequest[](ids.length);for (uint256 i; i < ids.length; ++i) list[i] = OpenRequest(ids[i], _requests[ids[i]]);}/// @notice What pay would do right now, with the owner's allowance and balance read next to it./// A low allowance or balance is shown, not coded: the desk has no rule about them, the token refuses.function check(uint256 agentId, address provider, address token, uint256 amount) external view returns (Check memory c) {c.code = _evaluate(agentId, provider, token, amount);address owner = _agents[agentId].owner;if (owner == address(0)) return c;(c.allowanceRead, c.allowance) = TokenCalls.tryAllowance(token, owner, address(this));(c.balanceRead, c.balance) = TokenCalls.tryBalance(token, owner);}// ------------------------------------------------------------------ rules/// @dev The one place the rules live. Same order as evaluate() in src/lib/rules.ts; shared vectors keep the two equal.function _evaluate(uint256 agentId, address provider, address token, uint256 amount) private view returns (uint8) {Agent storage a = _agents[agentId];if (a.owner == address(0)) return UNKNOWN_AGENT;if (a.retired) return RETIRED;if (fleetFrozen[a.owner]) return FLEET_FROZEN;if (a.frozen) return AGENT_FROZEN;Budget storage b = _budgets[agentId][token];if (!b.enabled) return TOKEN_NOT_BUDGETED;if (_providerPos[agentId][provider] == 0 || provider == token) return PROVIDER_NOT_ALLOWED;if (amount == 0) return ZERO_AMOUNT;if (amount > b.paymentCap) return OVER_PAYMENT_CAP;if (_spentToday(b) + amount > b.dailyLimit) return OVER_DAILY_WINDOW;bool overThreshold = amount > b.approvalThreshold;if (!overThreshold && _autoSpentToday(b) + amount <= b.autoDailyLimit) return PASS;if (_open[agentId].length >= MAX_OPEN_REQUESTS) return TOO_MANY_OPEN;return overThreshold ? NEEDS_APPROVAL : OVER_AUTO_LIMIT;}function _spentToday(Budget storage b) private view returns (uint256) {return b.day == today() ? b.spent : 0;}function _autoSpentToday(Budget storage b) private view returns (uint256) {return b.day == today() ? b.autoSpent : 0;}/// @dev Called only after _evaluate passed, so the new totals fit their limits and therefore uint128. Both counters are/// rewritten before the day moves: an approval on a new day must not carry yesterday's auto count into it.function _record(uint256 agentId, address token, uint256 amount, bool withoutApproval) private {Budget storage b = _budgets[agentId][token];uint256 autoSpent = _autoSpentToday(b);b.spent = uint128(_spentToday(b) + amount);b.autoSpent = uint128(withoutApproval ? autoSpent + amount : autoSpent);b.day = today();}function _openRequest(uint256 agentId, address owner, address provider, address token, uint256 amount, bytes32 jobId) private returns (uint256 requestId) {requestId = ++requestCount;uint64 expiresAt = uint64(block.timestamp + REQUEST_TTL);_requests[requestId] = Request(agentId, provider, token, amount, jobId, uint64(block.timestamp), expiresAt, RequestStatus.Pending);_open[agentId].push(requestId);_openPos[requestId] = _open[agentId].length;emit RequestOpened(requestId, owner, agentId, jobId, provider, token, amount, expiresAt);}/// @dev Bounded by MAX_OPEN_REQUESTS.function _rejectAllOpen(uint256 agentId) private {uint256[] storage open = _open[agentId];while (open.length != 0) _close(open[open.length - 1], RequestStatus.Rejected);}function _close(uint256 requestId, RequestStatus status) private {Request storage r = _requests[requestId];r.status = status;uint256 agentId = r.agentId;uint256[] storage list = _open[agentId];uint256 pos = _openPos[requestId];uint256 last = list[list.length - 1];list[pos - 1] = last;_openPos[last] = pos;list.pop();delete _openPos[requestId];emit RequestClosed(requestId, agentId, msg.sender, status);}/// @dev Last step of a payment. Refuses any token that does not move exactly the amount, out of the owner and into the/// provider: a fee, a tax or a rebase would otherwise break what the limits mean.function _settle(address owner, address provider, address token, uint256 amount) private {uint256 ownerBefore = TokenCalls.balanceOf(token, owner);uint256 providerBefore = TokenCalls.balanceOf(token, provider);TokenCalls.pull(token, owner, provider, amount);uint256 ownerAfter = TokenCalls.balanceOf(token, owner);uint256 providerAfter = TokenCalls.balanceOf(token, provider);uint256 left = ownerBefore > ownerAfter ? ownerBefore - ownerAfter : 0;uint256 arrived = providerAfter > providerBefore ? providerAfter - providerBefore : 0;if (left != amount || arrived != amount) revert TransferMismatch(amount, left, arrived);}function _checkName(string calldata name) private pure {bytes memory b = bytes(name);if (b.length == 0 || b.length > MAX_NAME) revert BadName();for (uint256 i; i < b.length; ++i) if (b[i] < 0x20 || b[i] > 0x7e) revert BadName();}function _checkKey(address key) private view {if (key == address(0) || key == address(this)) revert BadKey(key);}}
Terminal
~/burzo > open-simulator # no wallet, no transaction
try:
All of your agents, on one board.
This is the difference we are testing, and it is untested with real teams. The Fleet screen reads budget left today, cost per job and pending requests for every agent you own, from the chain. Below: the same sample day, then each screen, set in letters. Hover to see it.
| Agent | Budget | Paid | Left | Per job | Waiting | Refused |
|---|---|---|---|---|---|---|
| research | 40.00 | 22.75 | 17.25 | 7.58 | 9.00 | 0 |
| support | 25.00 | 2.00 | 23.00 | 1.00 | 0.00 | 2 |
| localizer | 18.00 | 9.50 | 8.50 | 4.75 | 0.00 | 0 |
| All agents | 83.00 | 34.25 | 48.75 | 9.00 | 2 |
Simulated USDG from the sample day above. Nothing here is a measured result.
SimulatorOpen now, no wallet. Replays a day against your rules.
FleetEvery agent in one view. Pictured on a local test chain; waits for the desk to be deployed.
RequestsApprove or reject what waits. Pictured on a local test chain; waits for the desk to be deployed.
ReportsCost per job, agent and provider, with CSV. Pictured on a local test chain; waits for the desk to be deployed.
How it decidesEach limit, what it does and where it stops.
RisksThe ways money can be lost or stuck, as far as we know them.
IntegrateA small client and a sample agent, runnable on a local chain.
Build orderWhat works today and what is only planned.One desk.
No fee.
No balance held.
Tested, not deployed yet
Fee the contract takes
0%
- 01No admin, fee or upgrade
- 02Holds no balance between calls
- 03Pulls only from the owner of the agent asking
- 04Pays only budgeted tokens to listed providers
- 05Tested, not deployed yet
Steps open today
1/3
- 01Budget simulation: open now
- 02Limited payments on mainnet: contract tested, not deployed
- 03Trials with users: not started
- 04Advanced reports and premium: planned
- 05Each step shows whether the last one was worth it
Contract address
TBA
- 01A memecoin that carries the project's name
- 02Launches on Pons, no promised value
- 03The product works without it
- 04Premium payment with it: planned, unproven
- 05One address only: the one shown on the token page
The contract takes nothing. Gas goes to the network, paid by whoever sends the call. The desk is new code and can have faults.