Skip to content

Commit c82770e

Browse files
committed
docs(example): DEPLOY.md RWA runbook + Orbit gas gotcha, fix stale RPC/test-count/scenario-C status
Scenario C is live (was 'not implemented'). Added 3d (MockRwaRouter via forge create --gas-limit) + RWA smoke (dry-run + cast send on Robinhood). Documented the Orbit gas gotcha (forge script ignores gas flags). Fixed Robinhood RPC host (rpc.testnet, not testnet.rpc), Arbitrum RPC to publicnode, test count 20->25, deployed.json template + decoder note for both routers on both chains.
1 parent ffa974b commit c82770e

1 file changed

Lines changed: 59 additions & 17 deletions

File tree

examples/arbitrum-london/DEPLOY.md

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
# Deploy runbook - txKit Arbitrum London Buildathon demo
22

3-
Copy-paste deploy of the demo's on-chain pieces to Arbitrum Sepolia (scenario A, Pendle yield swap) and Robinhood Chain testnet (scenario C, gate only for now). Every command below is meant to be pasted as-is after you fill the env vars and the captured addresses.
3+
Copy-paste deploy of the demo's on-chain pieces to Arbitrum Sepolia (scenario A, Pendle yield swap) and Robinhood Chain testnet (scenario C, x402-paid RWA buy). Both scenarios are live on both chains. Every command below is meant to be pasted as-is after you fill the env vars and the captured addresses.
44

5-
Verified state at time of writing (2026-05-29): `forge build` + `forge test` green (20 tests: 15 AgentPolicyGate + 5 MockPendleRouter incl. an end-to-end gate->router integration test), app `tsc --noEmit` clean. The only thing standing between the repo and a live demo is this deploy, which needs a funded key you hold.
5+
Verified state (2026-06-08): `forge build` + `forge test` green (25 tests: 15 AgentPolicyGate + 5 MockPendleRouter + 5 MockRwaRouter incl. end-to-end gate->router integration tests), app `tsc --noEmit` + `next build` + 10 vitest clean. The deployed addresses + real tx hashes are in the README "Live on-chain" tables and `contracts/deployed.json`; this runbook reproduces them from a funded key you hold.
66

77
## What gets deployed
88

99
| Contract | Chain | Why | Verify on explorer |
1010
|---|---|---|---|
11-
| `AgentPolicyGate` | Arbitrum Sepolia (421614) | scenario A policy enforcement point | yes (Arbiscan) |
11+
| `AgentPolicyGate` | Arbitrum Sepolia (421614) | scenario A + C policy enforcement point | yes (Arbiscan) |
1212
| `MockPendleRouter` | Arbitrum Sepolia (421614) | scenario A inner swap target | yes (Arbiscan) |
13-
| `AgentPolicyGate` | Robinhood Chain testnet (46630) | multi-chain story; scenario C gate | no (explorer verifier API not published) |
13+
| `MockRwaRouter` | Arbitrum Sepolia (421614) | scenario C inner buy target (bonus proof) | yes (Arbiscan) |
14+
| `AgentPolicyGate` | Robinhood Chain testnet (46630) | scenario A + C gate (sponsor chain) | no (explorer verifier API not published) |
15+
| `MockPendleRouter` | Robinhood Chain testnet (46630) | scenario A inner target (proof) | no |
16+
| `MockRwaRouter` | Robinhood Chain testnet (46630) | scenario C inner buy target (live /flow-c) | no |
1417

15-
Scenario C (RWA on Robinhood) does NOT need a router yet: `buildRwaEnvelope` still throws "not implemented" (lands Phase 2 Day 10), and `buildPendleEnvelope` hardcodes the router on Arbitrum Sepolia only. Deploy the Robinhood gate now purely for the "deployed on both chains" claim.
18+
Both scenarios are live: `buildPendleEnvelope` targets Arbitrum Sepolia (`/flow-a`), `buildRwaEnvelope` targets Robinhood Chain (`/flow-c`, x402-gated). The RWA router was also deployed on Arbitrum as a bonus proof. See section 3d for the Robinhood RWA deploy and the Orbit gas gotcha.
1619

1720
## 0. Prerequisites
1821

@@ -39,8 +42,8 @@ export AGENT_SIGNER_ADDRESS=0x... # address the gate will trust
3942
export AGENT_SIGNER_PRIVATE_KEY=0x... # its private key (used by /api/agent)
4043

4144
# RPC + verify
42-
export ARB_SEPOLIA_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
43-
export ROBINHOOD_TESTNET_RPC_URL=https://testnet.rpc.chain.robinhood.com
45+
export ARB_SEPOLIA_RPC_URL=https://arbitrum-sepolia-rpc.publicnode.com # official sepolia-rollup.arbitrum.io can give tls-eof under load
46+
export ROBINHOOD_TESTNET_RPC_URL=https://rpc.testnet.chain.robinhood.com # NOTE host order: rpc.testnet, not testnet.rpc
4447
export ARBISCAN_API_KEY=... # only needed for --verify
4548
```
4649

@@ -63,7 +66,7 @@ Confirm tests green and balances funded:
6366
```bash
6467
cd examples/arbitrum-london/contracts
6568
forge build
66-
forge test # expect: 20 passed
69+
forge test # expect: 25 passed
6770

6871
DEPLOYER=$(cast wallet address --private-key $DEPLOYER_PRIVATE_KEY)
6972
cast balance $DEPLOYER --rpc-url arbitrum_sepolia
@@ -122,7 +125,9 @@ cast send $GATE_ARB "setSpendLimit(uint256)" 1000000000000000000 \
122125

123126
## 3. Deploy to Robinhood Chain testnet (sponsor bonus - real tx on a second chain)
124127

125-
Same shape as step 2 but on chainId 46630, no `--verify` (explorer verifier API not published as of 2026-05-26). Deploying the router here too lets `SmokeExecuteEnvelope` (step 7) land a real `executeEnvelope` tx on Robinhood - the only way to get a Robinhood tx hash while scenario C has no UI flow.
128+
Same shape as step 2 but on chainId 46630, no `--verify` (explorer verifier API not published).
129+
130+
**IMPORTANT - Orbit gas gotcha.** Robinhood's `eth_estimateGas` under-reports CREATE + L1-data cost, and `forge script` does NOT honour `--gas-estimate-multiplier` or `--gas-limit` here, so CREATE txs OOG at the raw estimate (observed gasUsed == gasLimit, status 0x0). Use `forge create --gas-limit` for contract deploys and `cast send --gas-limit` for state-changing calls; both honour the explicit limit (you only pay gasUsed). If a script-based deploy in 3a/3b OOGs, redo that contract with `forge create ... --gas-limit 3000000`.
126131

127132
### 3a. AgentPolicyGate
128133

@@ -156,6 +161,24 @@ cast send $GATE_ROBINHOOD "setAllowedRecipient(address,bool)" $ROUTER_ROBINHOOD
156161
--private-key $DEPLOYER_PRIVATE_KEY
157162
```
158163

164+
### 3d. MockRwaRouter (scenario C, live /flow-c)
165+
166+
Deploy via `forge create` (not `forge script`) because of the Orbit gas gotcha above:
167+
168+
```bash
169+
forge create src/MockRwaRouter.sol:MockRwaRouter \
170+
--rpc-url robinhood_testnet \
171+
--private-key $DEPLOYER_PRIVATE_KEY \
172+
--broadcast --gas-limit 3000000
173+
export RWA_ROUTER_ROBINHOOD=0x... # paste "Deployed to:"
174+
175+
# allow-list it on the gate (explicit gas)
176+
cast send $GATE_ROBINHOOD "setAllowedRecipient(address,bool)" $RWA_ROUTER_ROBINHOOD true \
177+
--rpc-url robinhood_testnet --private-key $DEPLOYER_PRIVATE_KEY --gas-limit 300000
178+
```
179+
180+
Bonus: the same `forge create` against `--rpc-url arbitrum_sepolia --verify --etherscan-api-key $ARBISCAN_API_KEY` deploys + verifies the RWA router on Arbitrum too (Arbitrum honours normal gas estimation; the explicit `--gas-limit` is harmless there). Capture it as `RWA_ROUTER_ARB` and allow-list on `GATE_ARB`.
181+
159182
## 4. Wire the addresses into the app
160183

161184
Three files carry `0x__PENDING__` / `0x000...0` placeholders. The app throws a clear "not deployed yet" error until they hold real addresses (the regex gate is `^0x[a-fA-F0-9]{40}$` with no "PENDING" substring).
@@ -179,13 +202,16 @@ Replace all three entries:
179202
}
180203
},
181204
"MockPendleRouter": {
182-
"421614": {
183-
"address": "<ROUTER_ARB>",
184-
"deployedAt": "2026-05-29T00:00:00Z",
185-
"blockExplorer": "https://sepolia.arbiscan.io/address/<ROUTER_ARB>"
186-
}
205+
"421614": { "address": "<ROUTER_ARB>", "deployedAt": "...", "blockExplorer": "https://sepolia.arbiscan.io/address/<ROUTER_ARB>" },
206+
"46630": { "address": "<ROUTER_ROBINHOOD>", "deployedAt": "...", "blockExplorer": "https://explorer.testnet.chain.robinhood.com/address/<ROUTER_ROBINHOOD>" }
207+
},
208+
"MockRwaRouter": {
209+
"421614": { "address": "<RWA_ROUTER_ARB>", "deployedAt": "...", "blockExplorer": "https://sepolia.arbiscan.io/address/<RWA_ROUTER_ARB>" },
210+
"46630": { "address": "<RWA_ROUTER_ROBINHOOD>", "deployedAt": "...", "blockExplorer": "https://explorer.testnet.chain.robinhood.com/address/<RWA_ROUTER_ROBINHOOD>" }
187211
}
188212
}
213+
214+
Also create `decoder-data/mock-rwa-router.json` (mirror `mock-pendle-router.json`: a `buy(address,bytes32,uint256)` descriptor with a `RwaPurchased` event + clear-signing labels) for both chains so the RWA inner call decodes as "Buy 5 TSLA" in the preview.
189215
```
190216

191217
### 4b. `decoder-data/agent-policy-gate.json`
@@ -203,8 +229,8 @@ Ensure `examples/arbitrum-london/.env.local` has:
203229
```bash
204230
ANTHROPIC_API_KEY=sk-ant-...
205231
AGENT_SIGNER_PRIVATE_KEY=0x... # SAME pair as the gate's agentSigner
206-
ARB_SEPOLIA_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
207-
ROBINHOOD_TESTNET_RPC_URL=https://testnet.rpc.chain.robinhood.com
232+
ARB_SEPOLIA_RPC_URL=https://arbitrum-sepolia-rpc.publicnode.com
233+
ROBINHOOD_TESTNET_RPC_URL=https://rpc.testnet.chain.robinhood.com
208234
```
209235

210236
## 5. Post-deploy verification
@@ -257,7 +283,21 @@ GATE_ADDRESS=$GATE_ROBINHOOD ROUTER_ADDRESS=$ROUTER_ROBINHOOD \
257283
forge script script/SmokeExecuteEnvelope.s.sol --rpc-url robinhood_testnet --broadcast
258284
```
259285

260-
The tx hash prints in the broadcast output and is saved to `broadcast/SmokeExecuteEnvelope.s.sol/<chainId>/run-latest.json` under `transactions[0].hash`. Paste both contract addresses and the tx hashes into the "Live on-chain" tables in `examples/arbitrum-london/README.md` - that is the verifiable proof judges check.
286+
RWA buy (scenario C). On Robinhood, `forge script --broadcast` OOGs (Orbit gas gotcha), so dry-run to compute the signed calldata, then send it with `cast` (which honours `--gas-limit`). On Arbitrum the script broadcast is fine.
287+
288+
```bash
289+
# Robinhood RWA buy (dry-run then cast send)
290+
GATE_ADDRESS=$GATE_ROBINHOOD ROUTER_ADDRESS=$RWA_ROUTER_ROBINHOOD AGENT_SIGNER_PRIVATE_KEY=$AGENT_SIGNER_PRIVATE_KEY \
291+
forge script script/SmokeRwaBuy.s.sol --rpc-url robinhood_testnet # NO --broadcast (dry-run)
292+
RWA_CALLDATA=$(python3 -c "import json;t=json.load(open('broadcast/SmokeRwaBuy.s.sol/46630/dry-run/run-latest.json'))['transactions'][0]['transaction'];print(t.get('input') or t.get('data'))")
293+
cast send $GATE_ROBINHOOD "$RWA_CALLDATA" --rpc-url robinhood_testnet --private-key $DEPLOYER_PRIVATE_KEY --gas-limit 1500000
294+
295+
# Arbitrum RWA buy (script broadcast works)
296+
GATE_ADDRESS=$GATE_ARB ROUTER_ADDRESS=$RWA_ROUTER_ARB AGENT_SIGNER_PRIVATE_KEY=$AGENT_SIGNER_PRIVATE_KEY \
297+
forge script script/SmokeRwaBuy.s.sol --rpc-url arbitrum_sepolia --broadcast
298+
```
299+
300+
The tx hash prints in the broadcast output and is saved to `broadcast/<Script>.s.sol/<chainId>/run-latest.json` under `transactions[0].hash` (for the cast-sent Robinhood RWA buy, the hash is in the `cast send` output). Paste both contract addresses and the tx hashes into the "Live on-chain" tables in `examples/arbitrum-london/README.md` - that is the verifiable proof judges check.
261301

262302
The gate enforces the prerequisites on-chain: a signer-pair mismatch reverts `InvalidSignature`, a missing allow-list reverts `RecipientNotAllowed`. If the script succeeds, the demo path is sound.
263303

@@ -267,3 +307,5 @@ The gate enforces the prerequisites on-chain: a signer-pair mismatch reverts `In
267307
- The allow-list step (2c) is the single easiest thing to forget - the gate compiles and deploys fine without it, then reverts only at execute time.
268308
- Signer-pair mismatch (step 1) fails the same way: silent until execute. Check it before you spend gas.
269309
- Robinhood verify is intentionally skipped; do not pass `--verify` there or the broadcast errors out on a missing verifier.
310+
- Orbit gas gotcha (Robinhood): `forge script` ignores `--gas-estimate-multiplier` and `--gas-limit` in Foundry 1.7.1, and `eth_estimateGas` under-reports CREATE/L1-data, so script-broadcast deploys OOG at the raw estimate. Fix: `forge create --gas-limit 3000000` for deploys, and for executeEnvelope-style calls run the smoke script as a dry-run (no `--broadcast`) then `cast send <gate> <calldata> --gas-limit 1500000`. `cast` honours the explicit limit.
311+
- Robinhood RPC host is `rpc.testnet.chain.robinhood.com` (not `testnet.rpc...`). The public RPC is rate-limited; an Alchemy Robinhood key is more reliable for repeated calls.

0 commit comments

Comments
 (0)