Smart contracts¶
The shop's limits live on chain, in a contract the agent cannot change. These are the contracts
in contracts/ (Solidity 0.8.37, Arc Foundry).
Deployed on Arc Testnet¶
The server installed with deploy/install.sh automatically discovers ShopPayables and
ShopEarn contracts on Arc, and ShopFunding on Base Sepolia and Ethereum Sepolia. It submits
their sources to Sourcify and requires an exact creation and runtime
match. A contract counts as verified once Sourcify confirms that match. The worker then asks each
chain's Blockscout explorer once to import it; an explorer error never keeps a contract pending.
No extra signature, fee or action is required from the owner.
Verification happens in the background after 12 blocks. The first run also scans older creations of the configured factories in bounded ranges; catching up can take several rounds. The worker uses the original verified factory compilation, not today's source files, and keeps its compilation, discovery cursor and pending jobs across restarts. Errors are retried with backoff. Verification delays do not stop the dashboard or the agent.
Old event ranges can use the explorer's paginated index when public nodes have pruned history; the last 1,000 blocks use the node to avoid advancing past events not indexed yet. If neither source can serve a range, its cursor stays pending. Provider range limits are reduced rather than skipping blocks.
The explorers asked to import are Arc's explorer, Base Sepolia Blockscout and Ethereum Sepolia Blockscout. Sourcify may also forward results to other explorers; an explorer, Etherscan or Basescan listing is not guaranteed by this worker. Third-party vaults, USDC and contracts from previous factories are outside its discovery scope. See server operations for status and troubleshooting.
| Contract | Address |
|---|---|
ShopPayablesFactory |
0x5D455dF984C85abe160E4585E0c1139ded5d2c3b (source verified on Sourcify) |
ShopEarnFactory |
0xEDF589982F10CCC3c8489726FaB39124b1F683Aa |
JournalAnchor |
0x4385a2F8acda166F3FE3B3825F6950BA7dAc2582 |
DecisionVerifier |
0xE9ca18fa99251555DAbed0cE1414b7157B9aD516 (source verified on Sourcify) |
| Morpho USDC Vault (ERC-4626, not embco's) | 0x8f2d33b5d4b9b5f02df635ae308f7b4c9da8d2dc |
| EarnKit USDC Vault (ERC-4626, not embco's; deposits closed) | 0xaabbef1d3971c710276ed41ec791bbe14cdb8e88 |
| USDC (ERC-20 interface) | 0x3600000000000000000000000000000000000000 |
| Pilot shop's agent wallet (Circle EOA, from before shops got their own gasless agent) | 0x2309ed4a43e3b1ab4222f6938538a6c88bd27dd8 |
Who can do what¶
The owner can also call pay, and the limits do not apply to the owner. Every other caller is
refused with NotAuthorized.
How a payment is accepted¶
Each invoiceRef is a unique reference for the invoice (the contract documents a hash of its ERP
name as one example), so it can be paid once and matched to its invoice. The transfer uses transferFrom: the money stays in the owner's wallet until the call
succeeds, and revoking the USDC allowance stops every payment.
Limits¶
maxPerPaymentis greater than zero and no more thanweeklyCap.- The weekly window starts when the shop is created and runs in seven-day blocks.
- Only the owner changes limits. Every change emits
LimitsChanged.
Events¶
| Event | Emitted when |
|---|---|
Paid |
An invoice is paid, with its reference, payee, amount and caller |
LimitsChanged |
The owner changes the per-payment or weekly limit |
AgentChanged |
The owner sets the agent's address |
PayeeSet |
The owner approves or removes a supplier wallet |
Paused / Unpaused |
The agent or owner pauses; only the owner resumes |
The dashboard reads these events from the chain to show suppliers and payments.
ShopEarn: idle USDC in vaults¶
Each owner creates one ShopEarn from ShopEarnFactory, approves the vaults the agent may use and
sets a per-park limit and a weekly cap.
| Who | Can do | Cannot do |
|---|---|---|
| Agent | park: move the owner's USDC into an approved vault, within the per-park limit and the weekly cap; unpark / unparkAll: bring it back to the owner's wallet at any time; pause |
Use a vault the owner did not approve, change a limit, send USDC or shares anywhere but the owner |
| Owner | Everything the agent can, with no limit; approve vaults, set the limits and the agent; resume | Transfer ownership |
Neither USDC nor vault shares stay in ShopEarn: park deposits with the owner as the receiver, so
the shares go to the owner's wallet, and unpark redeems them back to the owner. Taking money back
does not use the weekly cap, so the agent can always recall funds to pay a bill. Every move emits
Parked or Unparked, which the dashboard reads for its Earn card.
JournalAnchor: the journal, checkable by anyone¶
After money moves, and at least once a day, the agent writes the Merkle root (RFC 9162) of its whole journal and how many entries it covers.
| Who | Can do | Cannot do |
|---|---|---|
| The agent the shop contract names | anchor(shop, root, size), with a size larger than the last one |
Change or remove an anchor, anchor a smaller journal |
| Anyone | Read latest, count and anchorAt for any shop |
Anchor for a shop that does not name them as its agent |
The contract has no owner. embco verify recomputes the root from a copy of the journal and
compares it with every anchor: a changed, removed or reordered entry gives a different root.
DecisionVerifier: one decision, checked by Arc¶
| Who | Can do | Cannot do |
|---|---|---|
| Anyone | verify(proof): a decision's MANIFEST entry, its audit path and its anchor, as one value |
Change anything: it has no owner, no state and no write function |
verify hashes the entry as the journal does, follows the RFC 9162 audit path and compares the
root with the anchor the shop's agent wrote in JournalAnchor. Only if it matches does it read the
decision out of the entry and return it, with the shop's owner(). The entry is read at fixed
places, never as general JSON, and free text arrives as hex, so a record of any other shape is
refused. See Audit the agent.
ShopFunding: the owner's USDC on other chains¶
| Chain | ShopFundingFactory |
|---|---|
| Base Sepolia | 0x69e31Af302Ee222204AA78099D39a2956F5C95d6 |
| Ethereum Sepolia | 0x69e31Af302Ee222204AA78099D39a2956F5C95d6 |
Both factories are verified on Sourcify for each chain (Base Sepolia, Ethereum Sepolia).
ShopFunding is the same idea on a source chain of the owner's Unified Balance (Circle
Gateway). Each owner creates one from ShopFundingFactory on that chain and approves it, the way
they approve the shop contract on Arc.
| Who | Can do | Cannot do |
|---|---|---|
| Agent | fund(amount): take that much of the owner's USDC and deposit it into the owner's own Gateway balance, within a per-move and a weekly cap; pause |
Send it anywhere else, change a limit, resume after a pause |
| Owner | Everything the agent can, with no limit; set the limits and the agent; resume | Transfer ownership |
The money never stays in the contract: fund takes it from the owner's wallet and calls
Gateway's depositFor(usdc, owner, amount) in the same transaction, so the deposit always belongs
to the owner. From there the agent, which the owner authorizes as a delegate in Gateway, brings it
to the owner's wallet on Arc. Unit and fuzz tests, plus a fork test against Base Sepolia's real
GatewayWallet and USDC.