Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f175bf6
docs: design spec — agent wallet + multi-token settlement standard
Pattermesh Jul 1, 2026
d5004d5
docs: expand spec — MCP/CLI/tools, frontend onboarding, fairness+acce…
Pattermesh Jul 1, 2026
bf43f19
docs: implementation plan — 20 units across 3 dependency-ordered wave…
Pattermesh Jul 1, 2026
2cd93dc
feat(contracts): IAgentEscrow shared interface (unit ⑤)
Pattermesh Jul 2, 2026
c3c89fe
feat(⑥): token negotiation + settlement_token in PaymentRequest (v1.2)
Pattermesh Jul 2, 2026
5579128
docs: bump agent-payment-protocol.md to v1.2
Pattermesh Jul 2, 2026
2be92f6
feat(agent-wallet): add Treasury + AgentWallet with EscrowClient seam…
Pattermesh Jul 2, 2026
ced9ff3
feat(delegation): session keys + SpendPolicy enforcement (Unit ⑨)
Pattermesh Jul 2, 2026
d71ac62
feat(metrics): unit ⑳ — escrow-fulfilment metrics + polling dashboard
Pattermesh Jul 2, 2026
ae64991
feat(contracts): MultiTokenAgentEscrow — approach A sibling (unit ①)
Pattermesh Jul 2, 2026
24d7b3f
feat(⑦): x402 accepts[] multi-token envelope
Pattermesh Jul 2, 2026
b1b17b8
feat(web): unit ⑱ — agent onboarding frontend flow
Pattermesh Jul 2, 2026
4c91ca9
integrate(contracts): units 5,1
Pattermesh Jul 2, 2026
a9cbd3e
integrate(protocol): units 6,7
Pattermesh Jul 2, 2026
9f07017
integrate(wallet): units 8,9
Pattermesh Jul 2, 2026
e8569e1
integrate(metrics): units 20
Pattermesh Jul 2, 2026
a1e6332
integrate(frontend): units 18
Pattermesh Jul 2, 2026
de996fe
feat(⑲): fairness + agent access policy engine — AccessPolicy, tiers,…
Pattermesh Jul 2, 2026
877b36c
eips: draft Multi-Token A2A Escrow ERC + ethereum-magicians post
Pattermesh Jul 2, 2026
23e1714
feat(router/⑩): TokenSelector — pick source token by balance/fee/slip…
Pattermesh Jul 2, 2026
f739a19
feat(router/⑪): RailSelector — cheapest rail by amount (x402/escrow/mpp)
Pattermesh Jul 2, 2026
637c34f
feat(router/⑫): FleetBalancer — spread nonces across N wallets via No…
Pattermesh Jul 2, 2026
059f82d
feat(router/⑬): Rebalancer — emit SwapIntents to approach target allo…
Pattermesh Jul 2, 2026
68b0481
feat(router): Router.route() composes ⑩⑪⑫⑬ strategies + emits WalletO…
Pattermesh Jul 2, 2026
526238e
chore(contracts): import batch-1 multi-token escrow base (units ①⑤)
Pattermesh Jul 2, 2026
4fc7a3d
feat(contracts): SwapSettlementAdapter — opt-in swap-at-release (unit ②)
Pattermesh Jul 2, 2026
80c1ecd
ci(foundry): run + assert multi-token & swap suites; pin forge-std (u…
Pattermesh Jul 2, 2026
afbb0db
feat(⑰): tool registry — DRY source of truth for MCP + CLI tools
Pattermesh Jul 2, 2026
b3e3181
feat(⑮): MCP server over stdio exposing 7 wallet+escrow tools
Pattermesh Jul 2, 2026
951d8db
feat(⑯): CLI — switchboard wallet/escrow/metrics commands
Pattermesh Jul 2, 2026
4aaf42e
integrate(erc): units 4
Pattermesh Jul 2, 2026
56b1001
integrate(fairness): units 19
Pattermesh Jul 2, 2026
c5e153b
integrate(router): units 10,11,12,13
Pattermesh Jul 2, 2026
095e0d7
integrate(swap): units 2,3
Pattermesh Jul 2, 2026
2489fe0
integrate(mcp): units 15,16,17
Pattermesh Jul 2, 2026
99d0438
wire(seam 1): AgentWallet uses canonical PaymentRequest
Pattermesh Jul 2, 2026
50d2ba9
wire(seam 2): access_policy emits canonical metrics.WalletOpEvent
Pattermesh Jul 2, 2026
fac2ad9
wire(seam 3): AccessPolicy satisfies MCP Protocol; real engine gates MCP
Pattermesh Jul 2, 2026
460c3d5
wire(seam 4): Router + access-policy in the AgentWallet.pay path
Pattermesh Jul 2, 2026
f2eaa65
wire(seam 5): ERC-165 supportsInterface on MultiTokenAgentEscrow
Pattermesh Jul 2, 2026
5d26996
docs: thinking chains & intelligent financial systems on switchboard …
Pattermesh Jul 2, 2026
c2cae00
feat(hanzo): Hanzo.ai MCP compatibility adapter + HanzoAgentWallet
Pattermesh Jul 2, 2026
d5264c4
integrate(hanzo): Hanzo.ai compatibility adapter + HanzoAgentWallet
Pattermesh Jul 2, 2026
0385a4d
integrate(thinking-chain): ThinkingChain + HanzoEscrowThinkingChain +…
Pattermesh Jul 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,28 @@ jobs:

- name: Install OZ + forge-std
run: |
forge install foundry-rs/forge-std --shallow
# Pin forge-std to the version the suite is developed against so CI
# can't drift onto a newer forge-std that changes cheatcode/std behavior.
forge install foundry-rs/forge-std@v1.9.4 --shallow
# OZ v5.0.2 is not reachable via foundry's tag-API window (OZ is at v5.6.x).
# Pin by SHA; --shallow excluded because shallow clones can't checkout arbitrary commits.
# Pin by SHA (== the v5.0.2 release tag); --shallow excluded because
# shallow clones can't checkout arbitrary commits.
forge install OpenZeppelin/openzeppelin-contracts@dbb6104ce834628e473d2173bbc9d47f81a9eec3

# `forge build --sizes` / `forge test` auto-discover every suite under
# contracts/test + test/, so the multi-token escrow (unit ①) and swap
# adapter (unit ②) suites run here without an explicit test list.
- run: forge build --sizes
- run: forge test -vv

# Belt-and-suspenders: fail loudly if the new multi-token / swap suites
# ever stop being discovered (e.g. a bad path move), rather than silently
# shrinking coverage.
- name: Assert multi-token + swap suites are present
run: |
forge test --match-contract MultiTokenAgentEscrowTest -vv
forge test --match-contract SwapSettlementAdapterTest -vv

pytest:
name: pytest
runs-on: ubuntu-latest
Expand Down
110 changes: 110 additions & 0 deletions contracts/IAgentEscrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
* @title IAgentEscrow
* @notice Shared lifecycle interface for agent-to-agent escrow, generalized
* over the settlement asset. This is the "Multi-Token A2A Escrow"
* surface referenced by the design spec §3.1 / §5 unit ⑤.
*
* @dev The native-ETH escrow (`AgentEscrow.sol`) is the **ETH profile** of this
* standard: the case where `token == address(0)`. `MultiTokenAgentEscrow`
* implements this interface for both native ETH (`address(0)`, via
* `msg.value`) and ERC-20 tokens (via `transferFrom` / `transfer`).
*
* Lifecycle (unchanged from the native EIP, now parameterized by `token`):
* create → (Locked) → confirm | releaseByAttestation → Released
* → requestRefund (after timeout+challenge) → Refunded
* → cancelPayment → Cancelled
*
* Design notes:
* - `Payment` carries an `address token` field. `address(0)` denotes the
* native-ETH profile; any other address is the ERC-20 being escrowed.
* - `amount` in `createPayment` is the *declared* amount. For the ETH
* profile it must equal `msg.value`. For ERC-20s it is the amount the
* escrow will attempt to pull; the *credited* amount is the measured
* balance delta (fee-on-transfer safe) — see `MultiTokenAgentEscrow`.
* - Every escrow event carries `token` so indexers can attribute flows
* per asset.
*/
interface IAgentEscrow {
enum State {
Created,
Locked,
Confirmed,
Released,
Refunded,
Cancelled
}

struct Payment {
address payer;
address payee;
address token; // address(0) = native ETH profile; else the ERC-20 escrowed
uint256 amount; // credited amount held in escrow (balance-delta for ERC-20)
uint256 timeoutBlocks; // blocks until auto-expire
uint256 challengePeriod; // blocks payer must wait to reclaim after timeout
State state;
string requestId; // off-chain payment request ID
uint256 createdAt; // block number at creation
bytes32 policyHash; // 0x00 = payer-only release; non-zero enables oracle release
}

// ─── Events (all carry `token`) ──────────────────────────────────────────

event PaymentCreated(
string indexed requestId,
address indexed payer,
address indexed payee,
address token,
uint256 amount
);
event PaymentLocked(string indexed requestId, address token);
event PaymentConfirmed(string indexed requestId, address indexed payer, address token);
event PaymentReleased(string indexed requestId, address indexed payee, address token, uint256 amount);
event PaymentReleasedByOracle(string indexed requestId, bytes32 policyHash, bytes32 attestationHash);
event PaymentRefunded(string indexed requestId, address indexed payer, address token, uint256 amount);
event PaymentCancelled(string indexed requestId, address indexed payer, address token, uint256 amount);

// ─── Lifecycle ────────────────────────────────────────────────────────────

/**
* @notice Create a payment request and lock funds in escrow.
* @param requestId Off-chain payment request ID (unique).
* @param payee Recipient on release.
* @param token Settlement asset. `address(0)` = native ETH (send via `msg.value`);
* otherwise an ERC-20 the payer has approved to this contract.
* @param amount Declared amount. Native profile requires `amount == msg.value`;
* ERC-20 profile pulls up to `amount` via `transferFrom` and credits
* the measured balance delta.
* @param timeoutBlocks Blocks until the payment auto-expires.
* @param challengePeriod Blocks the payer must additionally wait after timeout to reclaim.
*/
function createPayment(
string calldata requestId,
address payee,
address token,
uint256 amount,
uint256 timeoutBlocks,
uint256 challengePeriod
) external payable returns (bool);

/// @notice Payer confirms work is done -> release funds to payee.
function confirmPayment(string calldata requestId) external returns (bool);

/// @notice Oracle-mediated release, gated by the payment's `policyHash`.
function releaseByAttestation(
string calldata requestId,
bytes32 attestationHash,
bytes[] calldata signatures
) external returns (bool);

/// @notice Payer reclaims funds after timeout + challenge period.
function requestRefund(string calldata requestId) external returns (bool);

/// @notice Cancel a still-locked payment (mutual agreement / pre-timeout).
function cancelPayment(string calldata requestId) external returns (bool);

/// @notice Read a payment record.
function getPayment(string calldata requestId) external view returns (Payment memory);
}
32 changes: 32 additions & 0 deletions contracts/IPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
* @title IPriceOracle
* @notice Price-quote surface the `SwapSettlementAdapter` uses to compute the
* *expected* output of a swap and to bound realized slippage
* (design spec §3.5; plan unit ⑤'s `quote(tokenIn, tokenOut, amountIn)`).
*
* @dev DISTINCT FROM `IOracleAggregator`. `IOracleAggregator` answers a boolean
* "is this release attested?" for the escrow core. This interface answers a
* *pricing* question — "how much `tokenOut` is `amountIn` of `tokenIn`
* worth right now, and how fresh is that price?" — for the OPT-IN swap
* layer only. Keeping them separate honors spec §8: "Oracle used only for
* slippage bounds, never as the settlement authority" — the price oracle
* never moves funds and never authorizes a release; it only sets the
* expected-out reference the adapter measures realized slippage against.
*
* `updatedAt` is the unix timestamp of the underlying price observation.
* The adapter rejects the swap if `block.timestamp - updatedAt` exceeds its
* configured `maxPriceStaleness`, so a frozen/stale oracle can never be
* used to justify an off-market swap.
*/
interface IPriceOracle {
/// @notice Expected output of swapping `amountIn` of `tokenIn` into `tokenOut`.
/// @return amountOut Fair-value output at the oracle's current price.
/// @return updatedAt Unix timestamp of the price observation (for staleness).
function quote(address tokenIn, address tokenOut, uint256 amountIn)
external
view
returns (uint256 amountOut, uint256 updatedAt);
}
35 changes: 35 additions & 0 deletions contracts/ISwapRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
* @title ISwapRouter
* @notice Minimal DEX-router surface the `SwapSettlementAdapter` swaps through
* at release (design spec §3.5, plan unit ②).
*
* @dev This is a deliberately tiny, exact-input swap interface — the smallest
* thing the adapter needs to convert the escrowed `tokenIn` into the
* payee's `tokenOut`. The real wiring to `switchboard/adapters/lucidly.py`
* (the production DEX/liquidity engine) lands in a later unit; for now the
* adapter depends only on this interface so it can be swapped for lucidly,
* a Uniswap-style router, or a mock without any change to the adapter.
*
* Semantics (Uniswap-v2 `swapExactTokensForTokens` shaped, single hop):
* - The caller (the adapter) must have `approve`d `amountIn` of `tokenIn`
* to this router before calling.
* - The router pulls exactly `amountIn` of `tokenIn` from the caller,
* performs the swap, and sends the resulting `tokenOut` to `recipient`.
* - `minAmountOut` is the router's OWN floor (it MUST revert if it cannot
* deliver at least this much). The adapter passes its slippage-derived
* floor here as defense-in-depth; the adapter ALSO re-checks the
* realized output itself after the call (never trusts the router alone).
* - Returns the actual `amountOut` delivered to `recipient`.
*/
interface ISwapRouter {
function swapExactInput(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut,
address recipient
) external returns (uint256 amountOut);
}
Loading
Loading