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
98 changes: 98 additions & 0 deletions .agent/context/20260907T130109Z-b01-sdk-network-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Session Context: B01 SDK and Arc network compatibility

## Date/time

- UTC: 2026-09-07T13:01:09Z

## User goal

Begin Coder B implementation. Deliver B01 from `milestones/coder-b`: pin a
compatible Privy/Ethereum/TypeScript toolchain, encode Arc deployment profiles,
probe Memo/policy constraint feasibility, define the configuration schema, build
a fail-closed readiness probe, and establish the sanitized fixture boundary.

## Original prompt/request

Start coding as Coder B, following the repository instructions, `plan.md`, and
the task order in `milestones/coder-b/`. CI/CD instructions may be ignored for
now because no site or deployment target exists yet; the user will say when that
changes.

## Assumptions

- CI/CD is out of scope this session by explicit user instruction. Local
package-scoped checks still run, and no CI configuration is added or changed.
- Package manager is npm with package-local installs. Coder A owns root
workspace composition after scaffold freeze, so B01 adds no root manifest,
lockfile, or workspace configuration.
- `settlement-config-v1` is a published contract artifact, not a new package.
It is documented under `docs/settlement/` and implemented inside B-owned
packages.
- Arc Mainnet parameters are unpublished. The Mainnet profile therefore carries
no chain ID, RPC, explorer, or token value at all, per `plan.md` section 5b.

## Plan

1. Record this context and branch from current `develop`.
2. Build `packages/arc-adapter`: deployment profiles, money, configuration
schema, readiness probe, redaction.
3. Build `packages/privy-adapter`: wallet/policy identity validation and the
Memo/policy compatibility spike result.
4. Build `packages/testkit-settlement`: fixtures, readiness simulator, and
redaction tests.
5. Run package-local install, type, lint, unit, and build checks.
6. Publish the `settlement-config-v1` handoff artifact and dependency rationale.

## Key decisions

- Branch from `develop` at `9dc541d08daf4e9a9c338c562fb1fbe6ac6be04a`, which
already contains the merged plan clarification, so B01 encodes the corrected
Arc constants rather than the superseded ones.
- Arc Testnet is the only enabled profile: chain ID `5042002`, CAIP-2
`eip155:5042002`, USDC interface `0x3600000000000000000000000000000000000000`,
six-decimal atomic units.
- The Arc Mainnet profile is structurally present but holds no guessed values.
Commit `d6758dd` on `develop` deliberately removed the previously asserted
mainnet chain `5042` and its launch date as unverified guesses.

## Files/components touched

- `packages/arc-adapter`, `packages/privy-adapter`,
`packages/testkit-settlement`: new B-owned packages.
- `docs/settlement/`: `settlement-config-v1` handoff and provider setup notes.

## Commands/checks

- `npm view` for candidate dependency versions - viem `2.56.3`,
`@privy-io/node` `0.34.0`, `@privy-io/server-auth` `1.32.5`, TypeScript
`7.0.2`, Vitest `5.0.0`.
- Local Node is `v22.16.0` and npm is `10.9.2`; Vitest 5 declares
`node ^22.12.0 || ^24.0.0 || >=26.0.0`, which the local runtime satisfies.

## External-doc findings

- Pending. B01.2 and B01.3 must verify Arc chain, RPC, explorer, USDC, and Memo
identities against official Arc documentation before any value is pinned.

## Unresolved questions

- Whether Privy policy decoding can constrain a nested Arc Memo call. B01.3
decides this; direct transfer remains the fallback.

## Git and PR state

- Branch: `milestone/b01-sdk-network-compatibility`
- Base: `develop` at `9dc541d08daf4e9a9c338c562fb1fbe6ac6be04a`
- Commit: uncommitted
- PR: not created
- CI: out of scope this session by user instruction

## Review gates

- Gate A: NOT RUN
- Gate B: NOT RUN

## Handoff/next steps

1. Scaffold and implement the three B-owned packages.
2. Run package-local checks and record results here.
115 changes: 115 additions & 0 deletions .agent/research/20260907-b01-arc-privy-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# B01 primary-source verification: Arc network values and Privy policy reach

Date: 2026-09-07
Packet: `milestones/coder-b/B01-sdk-network-compatibility.md`
Purpose: satisfy B01.2 (pin Arc values only after checking official documentation)
and decide B01.3 (whether Privy policy can constrain a nested Arc Memo call).

## Sources

- Arc, "Connect to Arc", <https://docs.arc.io/arc/references/connect-to-arc> — accessed 2026-09-07.
- Arc, "Contract addresses", <https://docs.arc.io/arc/references/contract-addresses> — accessed 2026-09-07.
- Privy, "Policies & controls overview", <https://docs.privy.io/controls/policies/overview> — accessed 2026-09-07.

## 1. Arc Testnet values (B01.2)

| Value | Official source | Matches `milestones/CONTRACTS.md`? |
| --- | --- | --- |
| Chain ID `5042002` | Connect to Arc | Yes |
| CAIP-2 `eip155:5042002` | Derived from chain ID | Yes |
| USDC ERC-20 interface `0x3600000000000000000000000000000000000000` | Contract addresses | Yes |
| ERC-20 interface decimals `6` | Contract addresses | Yes |
| Block explorer `https://testnet.arcscan.app` | Connect to Arc | Not previously recorded |
| Primary RPC `https://rpc.testnet.arc.io` | Connect to Arc | Not previously recorded |

The frozen contract values are confirmed correct. Nothing had to change.

### Finding 1: native gas precision differs from settlement precision

Arc's native gas asset is also called USDC, but it uses **18 decimals**, while
the **USDC ERC-20 interface uses 6**. Same name, same chain, a factor of 10^12
apart.

This is the exact hazard B01.2 names when it requires settlement amounts to be
separated from native USDC gas accounting. A single `decimals` field on a
deployment profile would invite code to price a settlement in gas units and
overpay or underpay by twelve orders of magnitude.

Encoded as two distinct fields, `tokenDecimals` (6, settlement) and
`nativeDecimals` (18, gas). The readiness probe reports `MISMATCH` if a profile
ever declares them equal or declares native decimals as anything but 18.

### Finding 2: RPC endpoints are operator configuration, not constants

Arc publishes four testnet RPC endpoints (a primary plus Blockdaemon, dRPC, and
QuickNode). There is no single canonical endpoint to pin, which confirms the
decision to keep RPC and explorer URLs out of the profile table and in
validated configuration.

A first draft of `profiles.ts` had guessed `https://rpc.testnet.arc.network`.
The real host is `arc.io`, not `arc.network`, so the guess was wrong as well as
against policy. A test now asserts that no profile contains any `http(s)://`
string.

## 2. Privy policy reach and the Arc Memo path (B01.3)

### Arc Memo contract

Address `0x5294E9927c3306DcBaDb03fe70b92e01cCede505`. It attaches memo metadata
to contract calls and emits `Memo` events carrying a sequential index.

Using it for settlement means the wallet calls the Memo contract, which forwards
the USDC transfer. The recipient and amount then live inside the forwarded inner
call rather than in the transaction the wallet signs directly.

### What a Privy policy can constrain

Privy policies are built from rules and conditions over these field sources:

- `ethereum_transaction` — `to`, `value`, `chain_id`.
- `ethereum_calldata` — the called function by name, and its decoded arguments
as `function_name.param_name`, supplied with the contract's JSON ABI.
- `ethereum_typed_data_domain` / `ethereum_typed_data_message` — EIP-712 data.

This is sufficient to fully constrain a **direct** ERC-20 transfer: the policy
can pin the destination contract, the chain, a zero native value, the method,
and the decoded recipient and amount arguments.

### Verdict: `NOT SUPPORTED` for the nested Memo path

`ethereum_calldata` decodes the arguments of the function the wallet calls. For
a Memo-forwarded settlement, that is the Memo function; the USDC recipient and
amount sit inside an inner call that Privy's documented conditions do not
decode. Privy's documentation does not describe constraining a nested or
forwarded inner call.

B01.3 permits recording `SUPPORTED` only with deny fixtures proving every wrong
dimension is rejected. The recipient and amount dimensions cannot be denied
through documented policy conditions on the nested path, so the honest result is
`NOT SUPPORTED`.

**Consequence.** v1 settlement uses the **direct USDC ERC-20 transfer**, which
is fully policy-constrainable. The Memo path is not used for settlement. This
matches `plan.md` section 27, which already lists "Arc Memo correlation if Privy
cannot constrain the forwarded call" as the cuttable option, and
`milestones/CONTRACTS.md` section 2, which admits `memo_id` only when the Memo
path passes B01 policy validation. It has not passed, so `memo_id` stays unused.

Correlation for hashless recovery therefore relies on the tuple/window discovery
and Subgraph MCP path owned by Coder C, not on a memo identifier.

### Not ruled out, but out of B01 scope

A narrow purpose-built settlement contract with recipient and amount as
top-level arguments would be policy-constrainable and could carry a memo. That
is a new contract to write, audit, and deploy. It is recorded here as a
possibility, not adopted.

## 3. Residual verification gaps

- The Memo contract ABI is not published on the pages read. Not needed, since
the Memo path is not adopted for settlement.
- Privy wallet and policy identifier formats are not documented on the page
read. `packages/arc-adapter` validates a conservative shape only; B02 should
replace it with the documented format.
- Arc Mainnet parameters remain unpublished. The mainnet profile stays empty.
165 changes: 165 additions & 0 deletions docs/settlement/SETTLEMENT_CONFIG_V1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# settlement-config-v1

B01 handoff artifact for the Coder B lane. Consumers: Coder A composition,
Coder C reconciliation, and human operators performing provider setup.

Implemented by `packages/arc-adapter` and `packages/privy-adapter`. Both install,
lint, typecheck, test, and build independently with no root workspace
composition and no credential.

## 1. Arc deployment profiles

Verified 2026-09-07 against official Arc documentation. Evidence and source URLs
are in `.agent/research/20260907-b01-arc-privy-verification.md`.

| Field | Arc Testnet | Arc Mainnet |
| --- | --- | --- |
| `id` | `arc-testnet` | `arc-mainnet` |
| `verification` | `PINNED` | `UNPUBLISHED` |
| `enabled` | `true` | `false` |
| `chainId` | `5042002` | absent |
| `caip2` | `eip155:5042002` | absent |
| `tokenContract` | `0x3600000000000000000000000000000000000000` | absent |
| `tokenDecimals` (settlement) | `6` | absent |
| `nativeDecimals` (gas) | `18` | absent |

### Two precisions, one name

Arc's native gas asset and its USDC ERC-20 interface are both called USDC and
use **different precision**: 18 decimals for gas, 6 for the ERC-20 interface.
They are 10^12 apart.

Settlement amounts are always atomic units of `tokenDecimals`. Gas accounting
uses `nativeDecimals`. The readiness probe reports `MISMATCH` if a profile
declares them equal, or declares native decimals as anything but 18.

### Why the mainnet profile is empty

Arc has not published mainnet network parameters. The profile carries no chain
ID, RPC, explorer, or token value, because a plausible-looking default is more
dangerous than an absent one. Enabling a mainnet profile requires three
independent conditions: pinned verified values, an enabled flag, and
`ONESHOT_ALLOW_MAINNET_ACTIVATION=true` set by a human. Authorization alone is
refused.

### Why profiles carry no RPC or explorer URL

Arc publishes four testnet RPC endpoints, so there is no single canonical value
to pin. Endpoints are operator configuration, validated at load and re-verified
against the profile chain ID by the readiness probe.

## 2. Configuration variables

Classification: `public` safe to log; `secret` never logged, committed, or sent
to a reviewer; `optional` public with a documented default; `human-only` a human
must supply and approve it.

| Variable | Class | Required | Notes |
| --- | --- | --- | --- |
| `ONESHOT_ARC_PROFILE` | public | yes | Unknown id is refused, never defaulted |
| `ONESHOT_ARC_RPC_URL` | public | yes | https, or http on loopback only |
| `ONESHOT_ARC_EXPLORER_URL` | optional | no | Operator evidence links |
| `ONESHOT_PRIVY_APP_ID` | public | yes | Not a credential |
| `ONESHOT_PRIVY_APP_SECRET` | secret | yes at runtime | Read by no code in these packages |
| `ONESHOT_PRIVY_WALLET_ID` | public | yes | Execution wallet |
| `ONESHOT_PRIVY_POLICY_ID` | public | yes | Must be attached to the wallet |
| `ONESHOT_RECIPIENT_ALLOWLIST` | human-only | yes | Empty list settles nothing |
| `ONESHOT_SETTLEMENT_CAP_ATOMIC` | human-only | yes | Atomic units, compared as `bigint` |
| `ONESHOT_RPC_TIMEOUT_MS` | optional | no | Default 10000, max 120000 |
| `ONESHOT_ALLOW_MAINNET_ACTIVATION` | human-only | no | Default false |

`packages/arc-adapter/.env.example` is generated from this schema and contains
placeholders only.

## 3. Readiness probe

`probeReadiness(config, probe)` returns `{ ready, hasMismatch, checks }` and is
ready only when every check passes. There is no partial-ready state.

| Check | Asserts |
| --- | --- |
| `profile.consistency` | CAIP-2 matches chain ID; settlement precision is 6; gas precision is 18 and differs from settlement |
| `privy.identityFormat` | Wallet and policy identifier shape, printing neither value |
| `rpc.chainId` | Live `eth_chainId` equals the profile chain ID |
| `token.bytecode` | The configured USDC address holds contract bytecode |

### `UNAVAILABLE` versus `MISMATCH`

- `UNAVAILABLE` — the answer could not be learned. Configuration may be fine.
Retrying later is reasonable.
- `MISMATCH` — the answer was learned and is wrong. A human must fix it. It
must never be retried into working.

Both block readiness. Only `MISMATCH` is permanent. The probe runs against the
`RpcProbe` interface, so it works fully offline with no credential.

## 4. Selected settlement path

The B01.3 spike result: **direct USDC ERC-20 `transfer(address,uint256)`**.

The Arc Memo path (`0x5294E9927c3306DcBaDb03fe70b92e01cCede505`) is recorded
`NOT_SUPPORTED` for settlement. Privy policy conditions decode the arguments of
the function the wallet calls; on the Memo path that is the Memo function, so
the forwarded transfer's recipient and amount cannot be constrained or denied.
B01.3 permits `SUPPORTED` only with deny fixtures for every wrong dimension, and
two dimensions have none available.

Consequence: `memo_id` in `milestones/CONTRACTS.md` section 2 stays unused.
Hashless correlation relies on the tuple/window and Subgraph MCP discovery owned
by Coder C.

### Constrained dimensions

`evaluateScope` refuses anything outside the expected scope, with an independent
deny reason per dimension: `WRONG_CHAIN`, `WRONG_DESTINATION_CONTRACT`,
`NON_ZERO_NATIVE_VALUE`, `WRONG_METHOD`, `WRONG_RECIPIENT`, `WRONG_AMOUNT`,
`MALFORMED_CALLDATA`.

This duplicates the remote Privy policy on purpose. A policy lives in Privy
configuration and can drift, and the local check can only refuse, never grant.

## 5. Pinned dependencies and rationale

| Dependency | Version | Rationale |
| --- | --- | --- |
| Node | `>=22.12.0` | Vitest 5 requires `^22.12.0 \|\| ^24 \|\| >=26`; local runtime is 22.16.0 |
| TypeScript | `5.9.3` | See rejection below |
| viem | `2.56.3` | Typed ABI encoding and address handling; peer `typescript >=5.0.4` |
| Vitest | `5.0.0` | Test runner; peer `@types/node ^22 \|\| >=24` |
| ESLint | `9.39.1` | With `typescript-eslint` `8.69.0` `strictTypeChecked` |

### Rejected: TypeScript 7.0.2

TypeScript `7.0.2` is published, but `typescript-eslint` constrains `typescript`
to `>=4.8.4 <6.1.0` at every published version including the latest `8.69.0`.
Adopting TS 7 would mean dropping type-aware linting on the packages that
validate chain identity and money. Rejected; pinned `5.9.3`.

### Upgrade risks

- `eslint@9.39.1` already reports as outside its supported version window and
needs a scheduled bump.
- TypeScript 7 becomes adoptable only once `typescript-eslint` widens its peer
range. Re-evaluate then.
- Versions are pinned exact, so upgrades are deliberate rather than incidental.

## 6. Package-local commands

```bash
cd packages/arc-adapter # or packages/privy-adapter
npm install
npm run lint
npm run typecheck
npm run test
npm run build
npm run check # all of the above in order
```

## 7. Known gaps for B02

- Privy wallet and policy identifier formats are not documented on the pages
read. `IDENTIFIER_SHAPE` is a conservative guess and should be replaced with
the documented format.
- No Privy SDK call is made yet. B01 models the policy; B02 exercises it.
- The Arc Memo ABI was not published on the pages read. Not needed while the
Memo path stays unadopted.
Loading
Loading