Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ optimizer settings.

## Components

Seven top-level contracts in [`src/`](src/), grouped by role:
Six top-level contracts in [`src/`](src/), grouped by role:

### Asset custody (permanent, audited, not versioned)

Expand All @@ -50,8 +50,11 @@ Seven top-level contracts in [`src/`](src/), grouped by role:
conditional-payment results, indexed by `payId = keccak256(payHash, setterAddress)`.
- **[VirtContractResolver](src/VirtContractResolver.sol)** — On-demand deployer for
virtual (off-chain) contracts when disputes need them on-chain.
- **[EthPool](src/EthPool.sol)** — ERC-20-shaped wrapper for native ETH; enables
single-tx channel opening via a uniform `transferFrom` flow.

`CelerLedger` additionally depends on the chain's canonical wrapped-native
(WETH9-style) contract for the multi-party-funding path on native channels —
wired at deploy time via the `_nativeWrap` constructor argument. Users still
deposit and receive native; wrapped-native is internal plumbing only.

### Channel & payment logic (versioned, peer-controlled migration)

Expand All @@ -78,7 +81,6 @@ src/
├── CelerLedger.sol # channel state machine; primary entry point (versioned)
├── CelerLedgerMock.sol # test-only ledger variant
├── CelerWallet.sol # multi-owner asset custodian (permanent)
├── EthPool.sol # ERC20-like wrapper for native ETH
├── PayRegistry.sol # global resolved-payment registry (permanent)
├── PayResolver.sol # conditional-pay resolution (versioned)
├── RouterRegistry.sol # optional relay-router registry
Expand Down Expand Up @@ -110,11 +112,12 @@ gitignored.

## Supported tokens

ETH and **plain ERC-20** only. Tokens whose `transferFrom` delivers anything
other than the requested amount — fee-on-transfer, deflationary, rebasing,
ERC-777 with hooks, etc. — are **not supported**: channel accounting credits
the requested amount, so any actual-vs-requested mismatch will desync the
wallet's internal balance from its real token holdings.
Native (e.g. ETH) and **plain ERC-20** only. Tokens whose `transferFrom`
delivers anything other than the requested amount — fee-on-transfer,
deflationary, rebasing, ERC-777 with hooks, etc. — are **not supported**:
channel accounting credits the requested amount, so any actual-vs-requested
mismatch will desync the wallet's internal balance from its real token
holdings.

---

Expand Down
9 changes: 6 additions & 3 deletions docs/architecture-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ activity happens off-chain: peers exchange co-signed simplex states and forward
conditional payments through routed paths. The blockchain is only touched for deposits,
withdrawals, settlement, dispute resolution, and (rarely) deploying virtual contracts.

The seven contracts split cleanly into two roles. **Asset custody** lives in
The six contracts split cleanly into two roles. **Asset custody** lives in
permanent, audited contracts that change rarely or never (`CelerWallet`, `PayRegistry`,
`VirtContractResolver`, `EthPool`). **Channel and payment logic** lives in *versioned*
`VirtContractResolver`). **Channel and payment logic** lives in *versioned*
contracts (`CelerLedger`, `PayResolver`) that peers can cooperatively migrate between
without disturbing the assets — see [Decentralized Versioning][versioning] in the full
docs. `RouterRegistry` is an optional advertisement registry for relay nodes.

`CelerLedger` additionally depends on the chain's canonical wrapped-native
(wrapped-native) contract for the multi-party-funding path on native channels —
wired at deploy time, never user-visible. Users still deposit and receive native.

[versioning]: https://agentpay-docs.celer.network/agentpay-architecture/on-chain-contracts/decentralized-versioning

---
Expand Down Expand Up @@ -100,7 +104,6 @@ For the full state-transition rules, see
| [`PayResolver`](../src/PayResolver.sol) | On-chain conditional-pay resolution; writes results to `PayRegistry`. | **Yes** (chosen per-payment) |
| [`PayRegistry`](../src/PayRegistry.sol) | Global `payId → (amount, deadline)` map; immutable, public reference. | No (permanent) |
| [`VirtContractResolver`](../src/VirtContractResolver.sol) | On-demand deployment of virtual contracts during disputes. | No (permanent) |
| [`EthPool`](../src/EthPool.sol) | ERC20-like wrapper for native ETH; enables single-tx channel opening. | No |
| [`RouterRegistry`](../src/RouterRegistry.sol) | Optional registry for relay-router self-advertisement. | No |

For per-contract APIs (constructor args, external functions, events, storage), see
Expand Down
48 changes: 11 additions & 37 deletions docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ contract file is authoritative.
- [PayResolver](#payresolver)
- [PayRegistry](#payregistry)
- [VirtContractResolver](#virtcontractresolver)
- [EthPool](#ethpool)
- [RouterRegistry](#routerregistry)
- [Ledger libraries](#ledger-libraries) (where the channel logic actually lives)
- [Helpers and mocks](#helpers-and-mocks)
Expand All @@ -29,8 +28,8 @@ future versions) is an *operator* over individual wallets within it. The contrac
deliberately minimal logic — it only knows how to deposit, withdraw, and transfer
operatorship — to keep the audit surface small.

> **Supported tokens:** ETH and plain ERC-20 only. `depositERC20` credits the
> requested `_amount`, so tokens that deliver less than requested
> **Supported tokens:** native (e.g. ETH) and plain ERC-20 only. `depositERC20`
> credits the requested `_amount`, so tokens that deliver less than requested
> (fee-on-transfer, deflationary, rebasing, ERC-777 hooks) will desync this
> wallet's accounting from its real token balance. Use only standard ERC-20s.

Expand All @@ -55,7 +54,7 @@ can `pause` / `unpause` and (when paused) `drainToken` to recover stuck funds.
| Function | Caller | Purpose |
|---|---|---|
| [`create`](../src/CelerWallet.sol#L66) | anyone (typically a `CelerLedger`) | Create a new wallet for a peer-pair, returning its `walletId`. |
| [`depositETH`](../src/CelerWallet.sol#L89) | anyone (payable) | Deposit native ETH into a wallet. |
| [`depositNative`](../src/CelerWallet.sol#L89) | anyone (payable) | Deposit native (e.g., ETH) into a wallet. |
| [`depositERC20`](../src/CelerWallet.sol#L101) | anyone | Deposit ERC-20 tokens (requires prior `approve`). |
| [`withdraw`](../src/CelerWallet.sol#L119) | operator only | Withdraw funds to a receiver. |
| [`transferToWallet`](../src/CelerWallet.sol#L141) | operator only | Move funds between two wallets sharing the same operator (channel rebalancing). |
Expand Down Expand Up @@ -107,20 +106,20 @@ wrapper — the actual logic is split across five libraries under
[`src/lib/ledgerlib/`](../src/lib/ledgerlib/) and attached via `using ... for ...`. See
[Ledger libraries](#ledger-libraries) below.

> **Supported tokens:** ETH and plain ERC-20 only — see the same note under
> [CelerWallet](#celerwallet). Non-standard ERC-20s
> **Supported tokens:** native (e.g. ETH) and plain ERC-20 only — see the same
> note under [CelerWallet](#celerwallet). Non-standard ERC-20s
> (fee-on-transfer / rebasing / ERC-777 hooks) will desync channel accounting
> from the wallet's real token balance.

### Constructor

```solidity
constructor(address _ethPool, address _payRegistry, address _celerWallet) Ownable(msg.sender)
constructor(address _nativeWrap, address _payRegistry, address _celerWallet) Ownable(msg.sender)
```

| Param | Purpose |
|---|---|
| `_ethPool` | Address of the deployed [`EthPool`](#ethpool). |
| `_nativeWrap` | Chain's canonical wrapped-native (wrapped-native) address. Used internally as a funding-flow primitive for native channels; never user-visible. Constructor-set; no setter. |
| `_payRegistry` | Address of the deployed [`PayRegistry`](#payregistry). |
| `_celerWallet` | Address of the deployed [`CelerWallet`](#celerwallet). |

Expand All @@ -132,8 +131,8 @@ Balance limits are **enabled by default** post-deployment. Configure them via
| Function | Purpose |
|---|---|
| [`openChannel`](../src/CelerLedger.sol#L66) | Open a fully-funded channel from a co-signed `PaymentChannelInitializer` (single tx). |
| [`deposit`](../src/CelerLedger.sol#L78) | Deposit ETH (msg.value) and/or pull from `EthPool`/ERC20 into a channel. |
| [`depositInBatch`](../src/CelerLedger.sol#L91) | Batch deposit across multiple channels in one tx. |
| [`deposit`](../src/CelerLedger.sol#L78) | Deposit native (msg.value) and/or pull from pre-approved wrapped-native or ERC-20 into a channel. |
| [`depositInBatch`](../src/CelerLedger.sol#L91) | Batch deposit across multiple channels in one tx. Not payable — native entries fund only via pre-approved wrapped-native (no `msg.value` path). |
| [`snapshotStates`](../src/CelerLedger.sol#L114) | Persist a co-signed simplex state on-chain (lightweight checkpoint). |

### External functions — withdrawals
Expand Down Expand Up @@ -175,7 +174,7 @@ A wide set of getters: `getChannelStatus`, `getTokenContract`, `getTokenType`,
`getNextPayIdListHashMap`, `getPayClearDeadlineMap`, `getPendingPayOutMap`,
`getWithdrawIntent`, `getCooperativeWithdrawSeqNum`, `getSettleFinalizedTime`,
`getDisputeTimeout`, `getMigratedTo`, `getChannelMigrationArgs`,
`getPeersMigrationInfo`, `getChannelStatusNum`, `getEthPool`, `getPayRegistry`,
`getPeersMigrationInfo`, `getChannelStatusNum`, `getNativeWrap`, `getPayRegistry`,
`getCelerWallet`, `getBalanceLimit`, `getBalanceLimitsEnabled`. See
[`ICelerLedger.sol`](../src/lib/interface/ICelerLedger.sol).

Expand All @@ -190,7 +189,7 @@ Settle: `IntendSettle`, `ClearOnePay`, `ConfirmSettle`, `ConfirmSettleFail`,

```solidity
LedgerStruct.Ledger private ledger;
// → channelStatusNums, ethPool, payRegistry, celerWallet,
// → channelStatusNums, nativeWrap, payRegistry, celerWallet,
// balanceLimits, balanceLimitsEnabled, channelMap (bytes32 → Channel)
```

Expand Down Expand Up @@ -306,31 +305,6 @@ virtual address (used in `Condition.virtual_contract_address`) is

---

## EthPool

[Source](../src/EthPool.sol) · [Interface](../src/lib/interface/IEthPool.sol) · **Permanent**

ERC-20-shaped wrapper for native ETH. Used so that `CelerLedger.openChannel` and
`deposit` can pull funds via a uniform `transferFrom` flow regardless of token type.
Etherscan-friendly metadata: `name = "EthInPool"`, `symbol = "EthIP"`, `decimals = 18`.

### External / public functions

| Function | Purpose |
|---|---|
| [`deposit`](../src/EthPool.sol#L24) (payable) | Deposit `msg.value` ETH for a receiver. |
| [`withdraw`](../src/EthPool.sol#L35) | Withdraw ETH back to `msg.sender`. |
| [`approve`](../src/EthPool.sol#L44) / [`increaseAllowance`](../src/EthPool.sol#L93) / [`decreaseAllowance`](../src/EthPool.sol#L106) | ERC-20-style allowance management. |
| [`transferFrom`](../src/EthPool.sol#L59) | Pull-based ETH transfer to a payable address. |
| [`transferToCelerWallet`](../src/EthPool.sol#L73) | Specialized transfer that funds a `CelerWallet` wallet directly. |
| [`balanceOf`](../src/EthPool.sol#L119) / [`allowance`](../src/EthPool.sol#L129) | Standard ERC-20 views. |

### Events

`Deposit`, `Transfer`, `Approval` (ERC-20 shape).

---

## RouterRegistry

[Source](../src/RouterRegistry.sol) · [Interface](../src/lib/interface/IRouterRegistry.sol) · **Permanent**
Expand Down
20 changes: 9 additions & 11 deletions script/DeployCore.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
pragma solidity ^0.8.20;

import {Script, console} from "forge-std/Script.sol";
import {EthPool} from "../src/EthPool.sol";
import {PayRegistry} from "../src/PayRegistry.sol";
import {VirtContractResolver} from "../src/VirtContractResolver.sol";
import {CelerWallet} from "../src/CelerWallet.sol";

/**
* @title DeployCore
* @notice Deploys the four permanent (non-versioned) AgentPay contracts on a fresh
* network: `EthPool`, `PayRegistry`, `VirtContractResolver`, `CelerWallet`. These
* are deployed once per network and shared by every `CelerLedger` / `PayResolver`
* version that follows.
* @notice Deploys the three permanent (non-versioned) AgentPay contracts on a
* fresh network: `PayRegistry`, `VirtContractResolver`, `CelerWallet`. These
* are deployed once per network and shared by every `CelerLedger` /
* `PayResolver` version that follows.
*
* @dev Usage:
* forge script script/DeployCore.s.sol --rpc-url $RPC_URL --broadcast --verify -vv
*
* After deploy, paste the four addresses into `config.json`'s `core` block (see
* [`example_config.json`](example_config.json)) before running `DeployLedger` or
* `DeployPayResolver`.
* After deploy, paste the three addresses into `config.json`'s `core` block
* (see [`example_config.json`](example_config.json)) along with the chain's
* canonical `nativeWrap` (wrapped-native) address, before running
* `DeployLedger` or `DeployPayResolver`.
*
* Environment variables:
* PRIVATE_KEY — Deployer private key (required). Deployer becomes the
Expand All @@ -28,18 +28,16 @@ import {CelerWallet} from "../src/CelerWallet.sol";
contract DeployCore is Script {
function run()
external
returns (EthPool ethPool, PayRegistry payRegistry, VirtContractResolver virtResolver, CelerWallet celerWallet)
returns (PayRegistry payRegistry, VirtContractResolver virtResolver, CelerWallet celerWallet)
{
uint256 deployerKey = vm.envUint("PRIVATE_KEY");

vm.startBroadcast(deployerKey);
ethPool = new EthPool();
payRegistry = new PayRegistry();
virtResolver = new VirtContractResolver();
celerWallet = new CelerWallet();
vm.stopBroadcast();

console.log("EthPool: ", address(ethPool));
console.log("PayRegistry: ", address(payRegistry));
console.log("VirtContractResolver:", address(virtResolver));
console.log("CelerWallet: ", address(celerWallet));
Expand Down
16 changes: 9 additions & 7 deletions script/DeployLedger.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import {CelerLedger} from "../src/CelerLedger.sol";
/**
* @title DeployLedger
* @notice Deploys a `CelerLedger` instance wired against the existing core
* contracts. Run once per ledger version — peers cooperatively migrate channels
* between versions; the wallet / registry / pool stay shared.
* contracts and the chain's canonical wrapped-native (wrapped-native)
* contract. Run once per ledger version — peers cooperatively migrate
* channels between versions; the wallet / registry / nativeWrap stay shared.
*
* @dev Usage:
* forge script script/DeployLedger.s.sol --rpc-url $RPC_URL --broadcast --verify -vv
*
* Reads the core addresses from `config.json` (or the path in `DEPLOY_CONFIG`).
* See [`example_config.json`](example_config.json) for the schema.
* Reads the core addresses (including `nativeWrap`) from `config.json` (or
* the path in `DEPLOY_CONFIG`). See [`example_config.json`](example_config.json)
* for the schema.
*
* Environment variables:
* PRIVATE_KEY — Deployer private key (required). Deployer becomes the
Expand All @@ -28,16 +30,16 @@ contract DeployLedger is Script {
string memory configPath = vm.envOr("DEPLOY_CONFIG", string("config.json"));
string memory config = vm.readFile(configPath);

address ethPool = abi.decode(vm.parseJson(config, ".core.ethPool"), (address));
address nativeWrap = abi.decode(vm.parseJson(config, ".core.nativeWrap"), (address));
address payRegistry = abi.decode(vm.parseJson(config, ".core.payRegistry"), (address));
address celerWallet = abi.decode(vm.parseJson(config, ".core.celerWallet"), (address));

require(ethPool != address(0), "ethPool address required");
require(nativeWrap != address(0), "nativeWrap address required");
require(payRegistry != address(0), "payRegistry address required");
require(celerWallet != address(0), "celerWallet address required");

vm.startBroadcast(deployerKey);
ledger = new CelerLedger(ethPool, payRegistry, celerWallet);
ledger = new CelerLedger(nativeWrap, payRegistry, celerWallet);
vm.stopBroadcast();

console.log("CelerLedger: ", address(ledger));
Expand Down
8 changes: 5 additions & 3 deletions script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ existing core without touching the asset-custody contracts.

| Script | Deploys | Lifecycle |
|---|---|---|
| [`DeployCore.s.sol`](DeployCore.s.sol) | `EthPool`, `PayRegistry`, `VirtContractResolver`, `CelerWallet` | Once per network. Permanent — never redeployed. |
| [`DeployCore.s.sol`](DeployCore.s.sol) | `PayRegistry`, `VirtContractResolver`, `CelerWallet` | Once per network. Permanent — never redeployed. |
| [`DeployLedger.s.sol`](DeployLedger.s.sol) | `CelerLedger` | Versioned. Run again for each new ledger version; peers cooperatively migrate. |
| [`DeployPayResolver.s.sol`](DeployPayResolver.s.sol) | `PayResolver` | Versioned per-payment. Run when adding a new resolver. |
| [`DeployRouterRegistry.s.sol`](DeployRouterRegistry.s.sol) | `RouterRegistry` | Optional. Independent of the channel graph. |
Expand All @@ -33,7 +33,9 @@ set -a; source script/.env; set +a
# 3. Deploy the permanent core contracts
forge script script/DeployCore.s.sol --rpc-url $RPC_URL --broadcast --verify -vv

# 4. Paste the four addresses from step 3 into config.json's `core` block
# 4. Paste the three addresses from step 3 into config.json's `core` block,
# plus the chain's canonical `nativeWrap` (wrapped-native) address — see
# `example_config.json` for canonical values per chain.

# 5. Deploy the first ledger and resolver
forge script script/DeployLedger.s.sol --rpc-url $RPC_URL --broadcast --verify -vv
Expand Down Expand Up @@ -79,7 +81,7 @@ After **`DeployLedger`**:
balance limits **enabled by default** but no limits set — every deposit will
revert until you do one of:
```bash
# Option A: set caps for the tokens you'll use (address(0) = ETH)
# Option A: set caps for the tokens you'll use (address(0) = native)
cast send <CELER_LEDGER> "setBalanceLimits(address[],uint256[])" "[0x0000000000000000000000000000000000000000]" "[1000000000000000000000]" --rpc-url $RPC_URL --private-key $PRIVATE_KEY

# Option B: disable limits entirely
Expand Down
6 changes: 3 additions & 3 deletions script/example_config.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"_comment": "Copy this file to config.json and fill in the values for your target network.",
"core": {
"_comment": "Addresses of the four permanent core contracts. Set after running DeployCore.s.sol; reused by every CelerLedger / PayResolver version.",
"ethPool": "0x0000000000000000000000000000000000000000",
"_comment": "Addresses of the three permanent core contracts (set after running DeployCore.s.sol) plus the chain's canonical wrapped-native (wrapped-native) contract. Reused by every CelerLedger / PayResolver version.",
"nativeWrap": "0x0000000000000000000000000000000000000000",
"payRegistry": "0x0000000000000000000000000000000000000000",
"virtResolver": "0x0000000000000000000000000000000000000000",
"celerWallet": "0x0000000000000000000000000000000000000000"
}
}
}
Loading
Loading