cordon-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/SKILL.md +98 -0
- package/dist/cordon-mcp.js +3651 -0
- package/package.json +42 -0
|
@@ -0,0 +1,3651 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/main.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// ../daemon/src/config.ts
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
// ../fixtures/src/gates.gen.ts
|
|
11
|
+
var GATE_RUNS = {
|
|
12
|
+
"G1": { "id": "G1", "status": "green", "tests": 66, "passed": 66, "recordedAt": "2026-09-11" },
|
|
13
|
+
"G2": { "id": "G2", "status": "green", "tests": 8, "passed": 8, "recordedAt": "2026-09-11" },
|
|
14
|
+
"G3": { "id": "G3", "status": "green", "tests": 5, "passed": 5, "recordedAt": "2026-09-10" },
|
|
15
|
+
"G4": { "id": "G4", "status": "green", "tests": 1, "passed": 1, "recordedAt": "2026-09-08" },
|
|
16
|
+
"G5": { "id": "G5", "status": "green", "tests": 12, "passed": 12, "recordedAt": "2026-09-08" },
|
|
17
|
+
"G6": { "id": "G6", "status": "green", "tests": 14, "passed": 14, "recordedAt": "2026-09-08" },
|
|
18
|
+
"G7": { "id": "G7", "status": "green", "tests": 10, "passed": 10, "recordedAt": "2026-09-09" }
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ../fixtures/src/search.gen.ts
|
|
22
|
+
var SEARCH = {
|
|
23
|
+
strategies: 1200,
|
|
24
|
+
draws: 45360,
|
|
25
|
+
released: 33327,
|
|
26
|
+
refused: 12033,
|
|
27
|
+
passedABound: 0,
|
|
28
|
+
closestToBudget6: 100000000n,
|
|
29
|
+
budget6: 100000000n,
|
|
30
|
+
releasedAmount6: 67783468046n,
|
|
31
|
+
recordedAt: "2026-09-08"
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ../fixtures/src/deployment.gen.ts
|
|
35
|
+
var DEPLOYMENT = {
|
|
36
|
+
chainId: 5042002,
|
|
37
|
+
registry: "0xf86de085e63b00c9fba300b19807c883deb961e9",
|
|
38
|
+
vault: "0x00ab57acd260c594a661b6101bdf7e92267af135",
|
|
39
|
+
record: "0x2a8361ac23f5ffcfde9f0d7bc7618178770332d0",
|
|
40
|
+
fromBlock: "61265318",
|
|
41
|
+
commit: "ee3955b82fb0b6f7786bbfb5b2f5ebc2b174f8cc",
|
|
42
|
+
deployedAt: "2026-09-09T17:24:22.882Z"
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ../fixtures/src/drill.gen.ts
|
|
46
|
+
var DRILL_RUN = {
|
|
47
|
+
recordedAt: "2026-09-10T20:56:30.337Z",
|
|
48
|
+
node: "0x92d7ae3dbcc740cdcdc5fb8cd911fc9bebcbd35b742e5d517941f8660d51d6de",
|
|
49
|
+
authorised6: 20000n,
|
|
50
|
+
lifetimeCap6: 20000n,
|
|
51
|
+
trancheCap6: 1000n,
|
|
52
|
+
spent6: 7000n,
|
|
53
|
+
requests: 9,
|
|
54
|
+
nodesInSubtree: 1,
|
|
55
|
+
refusals: [
|
|
56
|
+
{
|
|
57
|
+
"id": "6",
|
|
58
|
+
"reason": "concentration",
|
|
59
|
+
"node": "0x92d7ae3dbcc740cdcdc5fb8cd911fc9bebcbd35b742e5d517941f8660d51d6de",
|
|
60
|
+
"breachedAt": "0x92d7ae3dbcc740cdcdc5fb8cd911fc9bebcbd35b742e5d517941f8660d51d6de",
|
|
61
|
+
"tx": "0x425071b1d6af400d5b2efb8d228a60dea1b3608655212872ef27c7bc5ec4e822"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "7",
|
|
65
|
+
"reason": "concentration",
|
|
66
|
+
"node": "0x92d7ae3dbcc740cdcdc5fb8cd911fc9bebcbd35b742e5d517941f8660d51d6de",
|
|
67
|
+
"breachedAt": "0x92d7ae3dbcc740cdcdc5fb8cd911fc9bebcbd35b742e5d517941f8660d51d6de",
|
|
68
|
+
"tx": "0x81b956ff8769fe3b045edf0f3bf07ddc4893975af356abdfff7da3da17b4ecfb"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ../fixtures/src/index.ts
|
|
74
|
+
var ARC = {
|
|
75
|
+
chainId: 5042002,
|
|
76
|
+
name: "Arc testnet",
|
|
77
|
+
rpc: "https://rpc.testnet.arc.io",
|
|
78
|
+
explorer: "https://testnet.arcscan.app",
|
|
79
|
+
faucet: "https://faucet.circle.com",
|
|
80
|
+
/** Gas token is USDC, native, 18 decimals. */
|
|
81
|
+
nativeDecimals: 18,
|
|
82
|
+
/**
|
|
83
|
+
* The ERC-20 view of the same balance. balanceOf truncates below 1e-6.
|
|
84
|
+
*
|
|
85
|
+
* This predeploy is a real FiatTokenV2, confirmed against the live chain on
|
|
86
|
+
* 8 Sep 2026: it answers `DOMAIN_SEPARATOR()`, `authorizationState` and
|
|
87
|
+
* `nonces`, and `transferWithAuthorization` reverts with its own
|
|
88
|
+
* "FiatTokenV2: authorization is expired". An unknown selector reverts, so
|
|
89
|
+
* none of that is a fallback answering yes to everything. Its separator is
|
|
90
|
+
* what name "USDC" at version "2" on this chain hashes to, which is why
|
|
91
|
+
* x402 `exact` can settle here at all. No figure from that run is written
|
|
92
|
+
* down: `attest/src/collect.ts` reads the domain off the token at startup
|
|
93
|
+
* and is the only authority on it, because a domain one character out fails
|
|
94
|
+
* at settlement and looks like the payer's fault.
|
|
95
|
+
*/
|
|
96
|
+
erc20: "0x3600000000000000000000000000000000000000",
|
|
97
|
+
erc20Decimals: 6,
|
|
98
|
+
/**
|
|
99
|
+
* Multicall3, at the address it has on every chain that has one.
|
|
100
|
+
*
|
|
101
|
+
* Not ours and not deployed by us, so it belongs here beside the USDC
|
|
102
|
+
* predeploy rather than in `deployments/`. Confirmed on Arc testnet on
|
|
103
|
+
* 12 Sep 2026: the address carries code and `getBlockNumber()` answers
|
|
104
|
+
* 61598855.
|
|
105
|
+
*
|
|
106
|
+
* The console reads a tree of mandates node by node, four views each. Packed
|
|
107
|
+
* through this, a whole level of the descent is one request; without it, a
|
|
108
|
+
* thirteen-node tree was a hundred and twenty calls to the public RPC and a
|
|
109
|
+
* third of them came back 429.
|
|
110
|
+
*/
|
|
111
|
+
multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11",
|
|
112
|
+
mainnetLaunched: false
|
|
113
|
+
};
|
|
114
|
+
var ERC8004 = {
|
|
115
|
+
identity: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
|
|
116
|
+
reputation: "0x8004B663056A597Dffe9eCcC1965A193B7388713",
|
|
117
|
+
/** eth_getCode against Arc testnet returned 130 bytes for both. */
|
|
118
|
+
bytesOnArc: 130,
|
|
119
|
+
liveSince: "2026-01-29",
|
|
120
|
+
chains: 40,
|
|
121
|
+
registeredAgents: 173441,
|
|
122
|
+
/** The identity metadata key a spawned agent's stated purpose is written
|
|
123
|
+
* under. The spawner's description of the agent, never a bound: the
|
|
124
|
+
* contract reads nothing from it. */
|
|
125
|
+
purposeKey: "cordon.purpose",
|
|
126
|
+
/** One line, long enough to say what an agent is for and short enough to show. */
|
|
127
|
+
purposeMaxLength: 140
|
|
128
|
+
};
|
|
129
|
+
var GATEWAY = {
|
|
130
|
+
verifiedOn: "2026-09-08",
|
|
131
|
+
/** GatewayWallet on Arc testnet. An ERC-1967 proxy; the implementation is
|
|
132
|
+
* 22,818 bytes and carries the deposit selectors below. */
|
|
133
|
+
wallet: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
|
|
134
|
+
implementation: "0xa33d52b46964495ea6e2bb09ce85faed05776e28",
|
|
135
|
+
/**
|
|
136
|
+
* RISK 4, ANSWERED — and favourably.
|
|
137
|
+
*
|
|
138
|
+
* `depositFor(address token, address depositor, uint256 value)` credits the
|
|
139
|
+
* `depositor` parameter, not `msg.sender`. Verified on Arc: the call reaches
|
|
140
|
+
* the allowance check rather than failing dispatch, so the selector is live.
|
|
141
|
+
* The vault can therefore fund a beneficiary's Gateway balance directly, and
|
|
142
|
+
* the plan's fallback — routing through the daemon's own address — is not
|
|
143
|
+
* needed.
|
|
144
|
+
*/
|
|
145
|
+
depositForCreditsBeneficiary: true,
|
|
146
|
+
/**
|
|
147
|
+
* RISK 4b, THE ONE NOBODY ASKED.
|
|
148
|
+
*
|
|
149
|
+
* The seller is named in `destinationRecipient`, a field of the TransferSpec
|
|
150
|
+
* inside a burn intent that is signed OFF CHAIN by the depositor and handed
|
|
151
|
+
* to Circle's API. GatewayWallet never sees it, and no contract can.
|
|
152
|
+
*
|
|
153
|
+
* So a Gateway balance is spendable to anyone its depositor chooses, with
|
|
154
|
+
* one off-chain signature and no on-chain step for Cordon to sit in. Any
|
|
155
|
+
* bound that names a counterparty — concentration, above all — cannot be
|
|
156
|
+
* enforced against money that already sits in a Gateway balance. It can only
|
|
157
|
+
* be enforced at the moment the balance is topped up, or measured afterwards
|
|
158
|
+
* from settlement records.
|
|
159
|
+
*/
|
|
160
|
+
sellerIsOffChainOnly: true,
|
|
161
|
+
sellerField: "destinationRecipient",
|
|
162
|
+
/**
|
|
163
|
+
* RISK 5, STILL OPEN.
|
|
164
|
+
*
|
|
165
|
+
* The plan lists `GET /search-x402transfers` as the way the meter reads a
|
|
166
|
+
* buyer's own settled transfers. It could not be confirmed: Circle's public
|
|
167
|
+
* Gateway technical guide documents no x402 endpoints at all, and unauthed
|
|
168
|
+
* probes of the obvious hosts answered 404, which proves nothing either way.
|
|
169
|
+
* Until a key confirms it, the meter has no verified buyer-side source.
|
|
170
|
+
*/
|
|
171
|
+
searchX402Transfers: "unverified",
|
|
172
|
+
/* ---------------------------------------------------------------- */
|
|
173
|
+
/* Settlement, answered on 2026-09-11 by doing it */
|
|
174
|
+
/* ---------------------------------------------------------------- */
|
|
175
|
+
/**
|
|
176
|
+
* The two facts that were missing, and are not missing any more.
|
|
177
|
+
*
|
|
178
|
+
* `settle.ts` used to refuse on the grounds that the burn-intent EIP-712
|
|
179
|
+
* definition was unpublished and that submission needed a Circle key.
|
|
180
|
+
* Neither is true: the domain is `{ name: "GatewayWallet", version: "1" }`
|
|
181
|
+
* with the types the daemon now carries, and
|
|
182
|
+
* `https://gateway-api-testnet.circle.com` answers `/v1/info`,
|
|
183
|
+
* `/v1/balances` and `/v1/transfer` with no credential at all.
|
|
184
|
+
*/
|
|
185
|
+
api: "https://gateway-api-testnet.circle.com",
|
|
186
|
+
/** Gateway's own id for Arc testnet, from `/v1/info`. */
|
|
187
|
+
domain: 26,
|
|
188
|
+
/** GatewayMinter on Arc. The mint is the call that lands the money. */
|
|
189
|
+
minter: "0x0022222ABE238Cc2C7Bb1f21003F0a260052475B",
|
|
190
|
+
/**
|
|
191
|
+
* RISK 6, AND THE ONE THAT DECIDES THE RAIL.
|
|
192
|
+
*
|
|
193
|
+
* Circle's fee for a same-chain Gateway transfer on Arc, in base units:
|
|
194
|
+
* **$0.0035**, quoted by the API itself and charged **on top of** the value
|
|
195
|
+
* — a burn of $0.0065 against a $0.0100 balance is accepted, a burn of
|
|
196
|
+
* $0.0100 against it is refused for `required 0.0135`. So a tranche can
|
|
197
|
+
* only pay for its own settlement when it is larger than this.
|
|
198
|
+
*
|
|
199
|
+
* The alternative is `withdrawalDelay` on GatewayWallet, which reads
|
|
200
|
+
* 1,209,600 — fourteen days. Neither is compatible with a payment of a
|
|
201
|
+
* tenth of a cent, and that is a fact about the rail rather than about us.
|
|
202
|
+
*/
|
|
203
|
+
baseFee6: 3500n,
|
|
204
|
+
withdrawalDelaySeconds: 1209600,
|
|
205
|
+
/** Gas the mint itself cost on Arc, measured: 0.002975 of one balance. */
|
|
206
|
+
mintGas6: 2975n,
|
|
207
|
+
settlementVerifiedOn: "2026-09-11"
|
|
208
|
+
};
|
|
209
|
+
var PENDING_ADDRESS = "pending \u2014 written by the deploy script";
|
|
210
|
+
var PENDING_MANDATE = "pending \u2014 no mandate signed yet";
|
|
211
|
+
var MANDATE = {
|
|
212
|
+
/** No mandate is open yet. The owner signs the first one from their own
|
|
213
|
+
* wallet, and nothing here may stand in for a signature nobody gave. */
|
|
214
|
+
id: PENDING_MANDATE,
|
|
215
|
+
/** TreeVault.windowBudget(root) — base units, 6 dp. What may be drawn in
|
|
216
|
+
* any one window. It resets, which is why the cap below exists. */
|
|
217
|
+
budget6: 20000000n,
|
|
218
|
+
/** TreeVault.lifetimeSpent(node) — base units, 6 dp, and it never resets.
|
|
219
|
+
*
|
|
220
|
+
* A window budget alone is a rate, not a total: a mandate left running for
|
|
221
|
+
* a week authorises seven windows. This is the total the owner signed for,
|
|
222
|
+
* and the binding limit is whichever of the two is reached first. Larger
|
|
223
|
+
* than one window on purpose, so both bounds are reachable and a reader can
|
|
224
|
+
* see they are different things. */
|
|
225
|
+
lifetimeCap6: 50000000n,
|
|
226
|
+
/** Equal at every depth by construction. A shorter child window resets faster. */
|
|
227
|
+
windowSeconds: 86400,
|
|
228
|
+
/** MandateRegistry.maxDepth() */
|
|
229
|
+
maxDepth: 3,
|
|
230
|
+
/** TreeVault.trancheCap() */
|
|
231
|
+
tranche6: 1000000n,
|
|
232
|
+
/** TreeVault.concentrationBound() — share of the window to one counterparty. */
|
|
233
|
+
concentrationBoundPct: 35,
|
|
234
|
+
/** The one on chain, never a literal. */
|
|
235
|
+
vault: DEPLOYMENT?.vault ?? PENDING_ADDRESS,
|
|
236
|
+
/** The owner is whoever opens the mandate, and nobody has. */
|
|
237
|
+
owner: PENDING_MANDATE
|
|
238
|
+
};
|
|
239
|
+
var STRENGTH = {
|
|
240
|
+
budget: "enforced",
|
|
241
|
+
headroom: "enforced",
|
|
242
|
+
treeBar: "enforced",
|
|
243
|
+
refusal: "enforced",
|
|
244
|
+
depth: "enforced",
|
|
245
|
+
tranche: "enforced",
|
|
246
|
+
lifetime: "enforced",
|
|
247
|
+
release: "enforced",
|
|
248
|
+
revoke: "enforced",
|
|
249
|
+
record: "enforced",
|
|
250
|
+
/* `depositFor` credits the depositor parameter, checked on chain, so the
|
|
251
|
+
top-up itself is enforced. What is declared is who the operator then pays
|
|
252
|
+
out of that balance, and that is the `concentration` row below. */
|
|
253
|
+
gatewayTopUp: "enforced",
|
|
254
|
+
concentration: "declared"
|
|
255
|
+
};
|
|
256
|
+
var ENFORCED_BY = {
|
|
257
|
+
budget: "TreeVault.windowSpent(node)",
|
|
258
|
+
headroom: "TreeVault.headroom(node)",
|
|
259
|
+
treeBar: "TreeVault.ancestorDebit(node, amount)",
|
|
260
|
+
refusal: "TreeVault.evaluate(node, amount) -> Refused",
|
|
261
|
+
gatewayTopUp: "GatewayWallet.depositFor(usdc, operator, amount)",
|
|
262
|
+
/** Bounds the counterparty the daemon declared, not the one it paid. */
|
|
263
|
+
concentration: "TreeVault.concentrationBound(node, counterparty)",
|
|
264
|
+
depth: "MandateRegistry.maxDepth()",
|
|
265
|
+
tranche: "TreeVault.trancheCap()",
|
|
266
|
+
lifetime: "TreeVault.lifetimeSpent(node)",
|
|
267
|
+
release: "TreeVault.release(refusalId), owner signature",
|
|
268
|
+
revoke: "MandateRegistry.revoke(node), subtree",
|
|
269
|
+
record: "ReputationRegistry.giveFeedback(agentId, score, tags, uri)"
|
|
270
|
+
};
|
|
271
|
+
var STRENGTH_BY_FN = Object.fromEntries(
|
|
272
|
+
Object.entries(ENFORCED_BY).map(([key, fn]) => [fn, STRENGTH[key] ?? "declared"])
|
|
273
|
+
);
|
|
274
|
+
var GATE_DEFINITIONS = [
|
|
275
|
+
{ id: "G1", name: "tree arithmetic", ends: "every draw debits every ancestor, exactly" },
|
|
276
|
+
{ id: "G2", name: "live on Arc", ends: "four-agent tree, no agent holds a key, refusal on arcscan" },
|
|
277
|
+
{ id: "G3", name: "the hostile drill", ends: "an unrestricted agent is told to spend, and the number is published" },
|
|
278
|
+
{ id: "G4", name: "bounded search", ends: "thousands of strategies scored by the contract, none passes a bound" },
|
|
279
|
+
{ id: "G5", name: "the refusal survives us", ends: "no admin key, no proxy, reversal fails as the deployer" },
|
|
280
|
+
{ id: "G6", name: "the record cannot be forged", ends: "every record names its draw transaction; nobody else can write one" },
|
|
281
|
+
{ id: "G7", name: "the work still gets done", ends: "one task, two conditions, three runs each, and the brief completes under the fence" }
|
|
282
|
+
];
|
|
283
|
+
var GATES = GATE_DEFINITIONS.map((g) => {
|
|
284
|
+
const run = GATE_RUNS[g.id];
|
|
285
|
+
return {
|
|
286
|
+
...g,
|
|
287
|
+
status: run ? run.status : "pending",
|
|
288
|
+
tests: run ? run.tests : null,
|
|
289
|
+
recordedAt: run ? run.recordedAt : null
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
var DRILL = {
|
|
293
|
+
status: "run",
|
|
294
|
+
/** What the drill was measured against: the window its own mandate signed. */
|
|
295
|
+
ceiling6: DRILL_RUN.authorised6,
|
|
296
|
+
reached6: DRILL_RUN.spent6,
|
|
297
|
+
/** Of the ceiling, in basis points. 10,000 would be the treasury reached. */
|
|
298
|
+
reachedBps: Number(DRILL_RUN.spent6 * 10000n / DRILL_RUN.authorised6),
|
|
299
|
+
/** Every reason that stopped it, in the order the chain recorded them. */
|
|
300
|
+
stoppedBy: [...new Set(DRILL_RUN.refusals.map((refusal) => refusal.reason))],
|
|
301
|
+
run: DRILL_RUN
|
|
302
|
+
};
|
|
303
|
+
var REASONS = [
|
|
304
|
+
"none",
|
|
305
|
+
"revoked",
|
|
306
|
+
"tranche-cap",
|
|
307
|
+
"window-budget",
|
|
308
|
+
"concentration",
|
|
309
|
+
"vault-balance",
|
|
310
|
+
"lifetime-cap"
|
|
311
|
+
];
|
|
312
|
+
var UNRECOGNISED = "unrecognised-reason";
|
|
313
|
+
var REASON_MEANING = {
|
|
314
|
+
revoked: "the mandate for this branch was cut",
|
|
315
|
+
"tranche-cap": "the purchase is larger than one draw may be",
|
|
316
|
+
"window-budget": "the window is spent, on this node or an ancestor",
|
|
317
|
+
"lifetime-cap": "the total this mandate was signed for is spent, and it does not come back",
|
|
318
|
+
concentration: "this recipient has taken its share of the window",
|
|
319
|
+
"vault-balance": "the bounds passed and the treasury is empty"
|
|
320
|
+
};
|
|
321
|
+
var SEARCH_CLEAN = SEARCH.passedABound === 0;
|
|
322
|
+
var share = (bps) => MANDATE.budget6 * BigInt(bps) / 10000n;
|
|
323
|
+
var COUNTERFACTUAL = {
|
|
324
|
+
kind: "illustration",
|
|
325
|
+
steps: [
|
|
326
|
+
{ draw: 1, without6: share(12700), with6: share(4700), refused: false },
|
|
327
|
+
{ draw: 2, without6: share(28400), with6: share(8300), refused: false },
|
|
328
|
+
{ draw: 3, without6: share(45100), with6: MANDATE.budget6, refused: true },
|
|
329
|
+
{ draw: 4, without6: share(61200), with6: MANDATE.budget6, refused: true },
|
|
330
|
+
{ draw: 5, without6: share(84700), with6: MANDATE.budget6, refused: true }
|
|
331
|
+
]
|
|
332
|
+
};
|
|
333
|
+
var STRUCTURING_CALLS = 2e3;
|
|
334
|
+
var STRUCTURING_UNIT = 8e-3;
|
|
335
|
+
var STRUCTURING = {
|
|
336
|
+
calls: STRUCTURING_CALLS,
|
|
337
|
+
unitPrice: STRUCTURING_UNIT,
|
|
338
|
+
total: STRUCTURING_CALLS * STRUCTURING_UNIT,
|
|
339
|
+
refusedBy: ENFORCED_BY.concentration
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// ../daemon/src/config.ts
|
|
343
|
+
var ConfigError = class extends Error {
|
|
344
|
+
};
|
|
345
|
+
var ENV = {
|
|
346
|
+
required: {
|
|
347
|
+
CORDON_VAULT: "TreeVault address, from deployments/<chainId>.json",
|
|
348
|
+
CORDON_REGISTRY: "MandateRegistry address, from the same file",
|
|
349
|
+
"CORDON_NODE_<label>": "the mandate node this daemon acts for",
|
|
350
|
+
"CORDON_KEY_<label>": "the operator key for that node. The agent never sees it"
|
|
351
|
+
},
|
|
352
|
+
optional: {
|
|
353
|
+
CORDON_RECORD: "ConductRecord, from deployments/<chainId>.json. Without it, refusals are enforced but never published",
|
|
354
|
+
CORDON_IDENTITY: `ERC-8004 Identity; defaults to ${ERC8004.identity}`,
|
|
355
|
+
CORDON_RPC: `defaults to ${ARC.rpc}`,
|
|
356
|
+
CORDON_CHAIN_ID: `defaults to ${ARC.chainId}`,
|
|
357
|
+
CORDON_USDC: "the 6-decimal ERC-20 view; defaults to Arc's",
|
|
358
|
+
CORDON_NETWORKS: "CAIP-2 ids this daemon will settle on",
|
|
359
|
+
CORDON_ASSETS: "assets it will pay in",
|
|
360
|
+
CORDON_PORT: "defaults to 8402",
|
|
361
|
+
CORDON_POLL_MS: "receipt polling interval; defaults to 250, matched to Arc's finality rather than to viem's 4,000",
|
|
362
|
+
CORDON_KEY_FILE: "where a spawned child's key is written, before the spawn is sent; defaults to ~/.cordon/cordon.env"
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
function required(env, name) {
|
|
366
|
+
const v = env[name];
|
|
367
|
+
if (!v) throw new ConfigError(`${name} is not set`);
|
|
368
|
+
return v;
|
|
369
|
+
}
|
|
370
|
+
function address(env, name) {
|
|
371
|
+
const v = required(env, name);
|
|
372
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(v)) throw new ConfigError(`${name} is not an address: ${v}`);
|
|
373
|
+
return v;
|
|
374
|
+
}
|
|
375
|
+
function load(env = process.env) {
|
|
376
|
+
const keys = [];
|
|
377
|
+
for (const [name, value] of Object.entries(env)) {
|
|
378
|
+
if (!name.startsWith("CORDON_NODE_") || !value) continue;
|
|
379
|
+
const label = name.slice("CORDON_NODE_".length);
|
|
380
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {
|
|
381
|
+
throw new ConfigError(`${name} is not a node id: ${value}`);
|
|
382
|
+
}
|
|
383
|
+
const keyEnv = `CORDON_KEY_${label}`;
|
|
384
|
+
if (!env[keyEnv]) throw new ConfigError(`${name} is set but ${keyEnv} is not`);
|
|
385
|
+
keys.push({ node: value, keyEnv });
|
|
386
|
+
}
|
|
387
|
+
if (keys.length === 0) throw new ConfigError("no CORDON_NODE_<label> is set");
|
|
388
|
+
return {
|
|
389
|
+
rpcUrl: env.CORDON_RPC ?? ARC.rpc,
|
|
390
|
+
chainId: Number(env.CORDON_CHAIN_ID ?? ARC.chainId),
|
|
391
|
+
vault: address(env, "CORDON_VAULT"),
|
|
392
|
+
registry: address(env, "CORDON_REGISTRY"),
|
|
393
|
+
usdc: env.CORDON_USDC ?? ARC.erc20,
|
|
394
|
+
record: env.CORDON_RECORD ? address(env, "CORDON_RECORD") : void 0,
|
|
395
|
+
identity: env.CORDON_IDENTITY ?? ERC8004.identity,
|
|
396
|
+
networks: (env.CORDON_NETWORKS ?? `eip155:${ARC.chainId}`).split(",").map((s) => s.trim()),
|
|
397
|
+
assets: (env.CORDON_ASSETS ?? ARC.erc20).split(",").map((s) => s.trim()),
|
|
398
|
+
port: Number(env.CORDON_PORT ?? 8402),
|
|
399
|
+
pollMs: Number(env.CORDON_POLL_MS ?? 250),
|
|
400
|
+
keys,
|
|
401
|
+
keyFile: env.CORDON_KEY_FILE ?? join(homedir(), ".cordon", "cordon.env")
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ../daemon/src/gate.ts
|
|
406
|
+
import {
|
|
407
|
+
createPublicClient,
|
|
408
|
+
createWalletClient,
|
|
409
|
+
http,
|
|
410
|
+
defineChain,
|
|
411
|
+
decodeEventLog
|
|
412
|
+
} from "viem";
|
|
413
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
414
|
+
|
|
415
|
+
// ../daemon/src/abi.gen.ts
|
|
416
|
+
var TreeVaultAbi = [
|
|
417
|
+
{
|
|
418
|
+
"type": "constructor",
|
|
419
|
+
"inputs": [
|
|
420
|
+
{
|
|
421
|
+
"name": "usdc_",
|
|
422
|
+
"type": "address",
|
|
423
|
+
"internalType": "contract IERC20"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"name": "registry_",
|
|
427
|
+
"type": "address",
|
|
428
|
+
"internalType": "contract MandateRegistry"
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"name": "gateway_",
|
|
432
|
+
"type": "address",
|
|
433
|
+
"internalType": "contract IGatewayWallet"
|
|
434
|
+
}
|
|
435
|
+
],
|
|
436
|
+
"stateMutability": "nonpayable"
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"type": "function",
|
|
440
|
+
"name": "ancestorDebit",
|
|
441
|
+
"inputs": [
|
|
442
|
+
{
|
|
443
|
+
"name": "node",
|
|
444
|
+
"type": "bytes32",
|
|
445
|
+
"internalType": "bytes32"
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"name": "amount6",
|
|
449
|
+
"type": "uint128",
|
|
450
|
+
"internalType": "uint128"
|
|
451
|
+
}
|
|
452
|
+
],
|
|
453
|
+
"outputs": [
|
|
454
|
+
{
|
|
455
|
+
"name": "nodes",
|
|
456
|
+
"type": "bytes32[]",
|
|
457
|
+
"internalType": "bytes32[]"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"name": "spentAfter6",
|
|
461
|
+
"type": "uint128[]",
|
|
462
|
+
"internalType": "uint128[]"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"name": "budget6",
|
|
466
|
+
"type": "uint128[]",
|
|
467
|
+
"internalType": "uint128[]"
|
|
468
|
+
}
|
|
469
|
+
],
|
|
470
|
+
"stateMutability": "view"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
"type": "function",
|
|
474
|
+
"name": "concentrationBound",
|
|
475
|
+
"inputs": [
|
|
476
|
+
{
|
|
477
|
+
"name": "node",
|
|
478
|
+
"type": "bytes32",
|
|
479
|
+
"internalType": "bytes32"
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"name": "counterparty",
|
|
483
|
+
"type": "address",
|
|
484
|
+
"internalType": "address"
|
|
485
|
+
}
|
|
486
|
+
],
|
|
487
|
+
"outputs": [
|
|
488
|
+
{
|
|
489
|
+
"name": "spent6",
|
|
490
|
+
"type": "uint128",
|
|
491
|
+
"internalType": "uint128"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"name": "limit6",
|
|
495
|
+
"type": "uint128",
|
|
496
|
+
"internalType": "uint128"
|
|
497
|
+
}
|
|
498
|
+
],
|
|
499
|
+
"stateMutability": "view"
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
"type": "function",
|
|
503
|
+
"name": "draw",
|
|
504
|
+
"inputs": [
|
|
505
|
+
{
|
|
506
|
+
"name": "node",
|
|
507
|
+
"type": "bytes32",
|
|
508
|
+
"internalType": "bytes32"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
"name": "counterparty",
|
|
512
|
+
"type": "address",
|
|
513
|
+
"internalType": "address"
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
"name": "amount6",
|
|
517
|
+
"type": "uint128",
|
|
518
|
+
"internalType": "uint128"
|
|
519
|
+
}
|
|
520
|
+
],
|
|
521
|
+
"outputs": [
|
|
522
|
+
{
|
|
523
|
+
"name": "released",
|
|
524
|
+
"type": "bool",
|
|
525
|
+
"internalType": "bool"
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
"name": "refusalId",
|
|
529
|
+
"type": "uint256",
|
|
530
|
+
"internalType": "uint256"
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
"name": "reason",
|
|
534
|
+
"type": "uint8",
|
|
535
|
+
"internalType": "enum TreeVault.Reason"
|
|
536
|
+
}
|
|
537
|
+
],
|
|
538
|
+
"stateMutability": "nonpayable"
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
"type": "function",
|
|
542
|
+
"name": "evaluate",
|
|
543
|
+
"inputs": [
|
|
544
|
+
{
|
|
545
|
+
"name": "node",
|
|
546
|
+
"type": "bytes32",
|
|
547
|
+
"internalType": "bytes32"
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"name": "counterparty",
|
|
551
|
+
"type": "address",
|
|
552
|
+
"internalType": "address"
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
"name": "amount6",
|
|
556
|
+
"type": "uint128",
|
|
557
|
+
"internalType": "uint128"
|
|
558
|
+
}
|
|
559
|
+
],
|
|
560
|
+
"outputs": [
|
|
561
|
+
{
|
|
562
|
+
"name": "reason",
|
|
563
|
+
"type": "uint8",
|
|
564
|
+
"internalType": "enum TreeVault.Reason"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
"name": "breachedAt",
|
|
568
|
+
"type": "bytes32",
|
|
569
|
+
"internalType": "bytes32"
|
|
570
|
+
}
|
|
571
|
+
],
|
|
572
|
+
"stateMutability": "view"
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"type": "function",
|
|
576
|
+
"name": "fund",
|
|
577
|
+
"inputs": [
|
|
578
|
+
{
|
|
579
|
+
"name": "root",
|
|
580
|
+
"type": "bytes32",
|
|
581
|
+
"internalType": "bytes32"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"name": "amount6",
|
|
585
|
+
"type": "uint128",
|
|
586
|
+
"internalType": "uint128"
|
|
587
|
+
}
|
|
588
|
+
],
|
|
589
|
+
"outputs": [],
|
|
590
|
+
"stateMutability": "nonpayable"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
"type": "function",
|
|
594
|
+
"name": "gateway",
|
|
595
|
+
"inputs": [],
|
|
596
|
+
"outputs": [
|
|
597
|
+
{
|
|
598
|
+
"name": "",
|
|
599
|
+
"type": "address",
|
|
600
|
+
"internalType": "contract IGatewayWallet"
|
|
601
|
+
}
|
|
602
|
+
],
|
|
603
|
+
"stateMutability": "view"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
"type": "function",
|
|
607
|
+
"name": "headroom",
|
|
608
|
+
"inputs": [
|
|
609
|
+
{
|
|
610
|
+
"name": "node",
|
|
611
|
+
"type": "bytes32",
|
|
612
|
+
"internalType": "bytes32"
|
|
613
|
+
}
|
|
614
|
+
],
|
|
615
|
+
"outputs": [
|
|
616
|
+
{
|
|
617
|
+
"name": "available6",
|
|
618
|
+
"type": "uint128",
|
|
619
|
+
"internalType": "uint128"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
"name": "boundBy",
|
|
623
|
+
"type": "bytes32",
|
|
624
|
+
"internalType": "bytes32"
|
|
625
|
+
}
|
|
626
|
+
],
|
|
627
|
+
"stateMutability": "view"
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
"type": "function",
|
|
631
|
+
"name": "lifetimeSpent",
|
|
632
|
+
"inputs": [
|
|
633
|
+
{
|
|
634
|
+
"name": "node",
|
|
635
|
+
"type": "bytes32",
|
|
636
|
+
"internalType": "bytes32"
|
|
637
|
+
}
|
|
638
|
+
],
|
|
639
|
+
"outputs": [
|
|
640
|
+
{
|
|
641
|
+
"name": "",
|
|
642
|
+
"type": "uint128",
|
|
643
|
+
"internalType": "uint128"
|
|
644
|
+
}
|
|
645
|
+
],
|
|
646
|
+
"stateMutability": "view"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"type": "function",
|
|
650
|
+
"name": "refusal",
|
|
651
|
+
"inputs": [
|
|
652
|
+
{
|
|
653
|
+
"name": "id",
|
|
654
|
+
"type": "uint256",
|
|
655
|
+
"internalType": "uint256"
|
|
656
|
+
}
|
|
657
|
+
],
|
|
658
|
+
"outputs": [
|
|
659
|
+
{
|
|
660
|
+
"name": "",
|
|
661
|
+
"type": "tuple",
|
|
662
|
+
"internalType": "struct TreeVault.Refusal",
|
|
663
|
+
"components": [
|
|
664
|
+
{
|
|
665
|
+
"name": "node",
|
|
666
|
+
"type": "bytes32",
|
|
667
|
+
"internalType": "bytes32"
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
"name": "breachedAt",
|
|
671
|
+
"type": "bytes32",
|
|
672
|
+
"internalType": "bytes32"
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
"name": "counterparty",
|
|
676
|
+
"type": "address",
|
|
677
|
+
"internalType": "address"
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"name": "amount6",
|
|
681
|
+
"type": "uint128",
|
|
682
|
+
"internalType": "uint128"
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"name": "reason",
|
|
686
|
+
"type": "uint8",
|
|
687
|
+
"internalType": "enum TreeVault.Reason"
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
"name": "at",
|
|
691
|
+
"type": "uint64",
|
|
692
|
+
"internalType": "uint64"
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
"name": "released",
|
|
696
|
+
"type": "bool",
|
|
697
|
+
"internalType": "bool"
|
|
698
|
+
}
|
|
699
|
+
]
|
|
700
|
+
}
|
|
701
|
+
],
|
|
702
|
+
"stateMutability": "view"
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
"type": "function",
|
|
706
|
+
"name": "refusalCount",
|
|
707
|
+
"inputs": [],
|
|
708
|
+
"outputs": [
|
|
709
|
+
{
|
|
710
|
+
"name": "",
|
|
711
|
+
"type": "uint256",
|
|
712
|
+
"internalType": "uint256"
|
|
713
|
+
}
|
|
714
|
+
],
|
|
715
|
+
"stateMutability": "view"
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
"type": "function",
|
|
719
|
+
"name": "registry",
|
|
720
|
+
"inputs": [],
|
|
721
|
+
"outputs": [
|
|
722
|
+
{
|
|
723
|
+
"name": "",
|
|
724
|
+
"type": "address",
|
|
725
|
+
"internalType": "contract MandateRegistry"
|
|
726
|
+
}
|
|
727
|
+
],
|
|
728
|
+
"stateMutability": "view"
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
"type": "function",
|
|
732
|
+
"name": "release",
|
|
733
|
+
"inputs": [
|
|
734
|
+
{
|
|
735
|
+
"name": "refusalId",
|
|
736
|
+
"type": "uint256",
|
|
737
|
+
"internalType": "uint256"
|
|
738
|
+
}
|
|
739
|
+
],
|
|
740
|
+
"outputs": [],
|
|
741
|
+
"stateMutability": "nonpayable"
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
"type": "function",
|
|
745
|
+
"name": "treasury6",
|
|
746
|
+
"inputs": [
|
|
747
|
+
{
|
|
748
|
+
"name": "",
|
|
749
|
+
"type": "bytes32",
|
|
750
|
+
"internalType": "bytes32"
|
|
751
|
+
}
|
|
752
|
+
],
|
|
753
|
+
"outputs": [
|
|
754
|
+
{
|
|
755
|
+
"name": "",
|
|
756
|
+
"type": "uint128",
|
|
757
|
+
"internalType": "uint128"
|
|
758
|
+
}
|
|
759
|
+
],
|
|
760
|
+
"stateMutability": "view"
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
"type": "function",
|
|
764
|
+
"name": "usdc",
|
|
765
|
+
"inputs": [],
|
|
766
|
+
"outputs": [
|
|
767
|
+
{
|
|
768
|
+
"name": "",
|
|
769
|
+
"type": "address",
|
|
770
|
+
"internalType": "contract IERC20"
|
|
771
|
+
}
|
|
772
|
+
],
|
|
773
|
+
"stateMutability": "view"
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
"type": "function",
|
|
777
|
+
"name": "windowSpent",
|
|
778
|
+
"inputs": [
|
|
779
|
+
{
|
|
780
|
+
"name": "node",
|
|
781
|
+
"type": "bytes32",
|
|
782
|
+
"internalType": "bytes32"
|
|
783
|
+
}
|
|
784
|
+
],
|
|
785
|
+
"outputs": [
|
|
786
|
+
{
|
|
787
|
+
"name": "",
|
|
788
|
+
"type": "uint128",
|
|
789
|
+
"internalType": "uint128"
|
|
790
|
+
}
|
|
791
|
+
],
|
|
792
|
+
"stateMutability": "view"
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
"type": "function",
|
|
796
|
+
"name": "withdraw",
|
|
797
|
+
"inputs": [
|
|
798
|
+
{
|
|
799
|
+
"name": "root",
|
|
800
|
+
"type": "bytes32",
|
|
801
|
+
"internalType": "bytes32"
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
"name": "to",
|
|
805
|
+
"type": "address",
|
|
806
|
+
"internalType": "address"
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
"name": "amount6",
|
|
810
|
+
"type": "uint128",
|
|
811
|
+
"internalType": "uint128"
|
|
812
|
+
}
|
|
813
|
+
],
|
|
814
|
+
"outputs": [],
|
|
815
|
+
"stateMutability": "nonpayable"
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
"type": "event",
|
|
819
|
+
"name": "AncestorDebited",
|
|
820
|
+
"inputs": [
|
|
821
|
+
{
|
|
822
|
+
"name": "node",
|
|
823
|
+
"type": "bytes32",
|
|
824
|
+
"indexed": true,
|
|
825
|
+
"internalType": "bytes32"
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"name": "ancestor",
|
|
829
|
+
"type": "bytes32",
|
|
830
|
+
"indexed": true,
|
|
831
|
+
"internalType": "bytes32"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
"name": "amount6",
|
|
835
|
+
"type": "uint128",
|
|
836
|
+
"indexed": false,
|
|
837
|
+
"internalType": "uint128"
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
"name": "spent6",
|
|
841
|
+
"type": "uint128",
|
|
842
|
+
"indexed": false,
|
|
843
|
+
"internalType": "uint128"
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
"name": "budget6",
|
|
847
|
+
"type": "uint128",
|
|
848
|
+
"indexed": false,
|
|
849
|
+
"internalType": "uint128"
|
|
850
|
+
}
|
|
851
|
+
],
|
|
852
|
+
"anonymous": false
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
"type": "event",
|
|
856
|
+
"name": "Drawn",
|
|
857
|
+
"inputs": [
|
|
858
|
+
{
|
|
859
|
+
"name": "node",
|
|
860
|
+
"type": "bytes32",
|
|
861
|
+
"indexed": true,
|
|
862
|
+
"internalType": "bytes32"
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
"name": "counterparty",
|
|
866
|
+
"type": "address",
|
|
867
|
+
"indexed": true,
|
|
868
|
+
"internalType": "address"
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
"name": "beneficiary",
|
|
872
|
+
"type": "address",
|
|
873
|
+
"indexed": false,
|
|
874
|
+
"internalType": "address"
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
"name": "amount6",
|
|
878
|
+
"type": "uint128",
|
|
879
|
+
"indexed": false,
|
|
880
|
+
"internalType": "uint128"
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
"name": "root",
|
|
884
|
+
"type": "bytes32",
|
|
885
|
+
"indexed": true,
|
|
886
|
+
"internalType": "bytes32"
|
|
887
|
+
}
|
|
888
|
+
],
|
|
889
|
+
"anonymous": false
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
"type": "event",
|
|
893
|
+
"name": "Funded",
|
|
894
|
+
"inputs": [
|
|
895
|
+
{
|
|
896
|
+
"name": "root",
|
|
897
|
+
"type": "bytes32",
|
|
898
|
+
"indexed": true,
|
|
899
|
+
"internalType": "bytes32"
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
"name": "from",
|
|
903
|
+
"type": "address",
|
|
904
|
+
"indexed": true,
|
|
905
|
+
"internalType": "address"
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
"name": "amount6",
|
|
909
|
+
"type": "uint128",
|
|
910
|
+
"indexed": false,
|
|
911
|
+
"internalType": "uint128"
|
|
912
|
+
}
|
|
913
|
+
],
|
|
914
|
+
"anonymous": false
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
"type": "event",
|
|
918
|
+
"name": "Refused",
|
|
919
|
+
"inputs": [
|
|
920
|
+
{
|
|
921
|
+
"name": "refusalId",
|
|
922
|
+
"type": "uint256",
|
|
923
|
+
"indexed": true,
|
|
924
|
+
"internalType": "uint256"
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
"name": "node",
|
|
928
|
+
"type": "bytes32",
|
|
929
|
+
"indexed": true,
|
|
930
|
+
"internalType": "bytes32"
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
"name": "breachedAt",
|
|
934
|
+
"type": "bytes32",
|
|
935
|
+
"indexed": true,
|
|
936
|
+
"internalType": "bytes32"
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
"name": "counterparty",
|
|
940
|
+
"type": "address",
|
|
941
|
+
"indexed": false,
|
|
942
|
+
"internalType": "address"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"name": "amount6",
|
|
946
|
+
"type": "uint128",
|
|
947
|
+
"indexed": false,
|
|
948
|
+
"internalType": "uint128"
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
"name": "reason",
|
|
952
|
+
"type": "uint8",
|
|
953
|
+
"indexed": false,
|
|
954
|
+
"internalType": "enum TreeVault.Reason"
|
|
955
|
+
}
|
|
956
|
+
],
|
|
957
|
+
"anonymous": false
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
"type": "event",
|
|
961
|
+
"name": "Released",
|
|
962
|
+
"inputs": [
|
|
963
|
+
{
|
|
964
|
+
"name": "refusalId",
|
|
965
|
+
"type": "uint256",
|
|
966
|
+
"indexed": true,
|
|
967
|
+
"internalType": "uint256"
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
"name": "by",
|
|
971
|
+
"type": "address",
|
|
972
|
+
"indexed": true,
|
|
973
|
+
"internalType": "address"
|
|
974
|
+
},
|
|
975
|
+
{
|
|
976
|
+
"name": "counterparty",
|
|
977
|
+
"type": "address",
|
|
978
|
+
"indexed": true,
|
|
979
|
+
"internalType": "address"
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
"name": "amount6",
|
|
983
|
+
"type": "uint128",
|
|
984
|
+
"indexed": false,
|
|
985
|
+
"internalType": "uint128"
|
|
986
|
+
}
|
|
987
|
+
],
|
|
988
|
+
"anonymous": false
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
"type": "event",
|
|
992
|
+
"name": "Withdrawn",
|
|
993
|
+
"inputs": [
|
|
994
|
+
{
|
|
995
|
+
"name": "root",
|
|
996
|
+
"type": "bytes32",
|
|
997
|
+
"indexed": true,
|
|
998
|
+
"internalType": "bytes32"
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
"name": "to",
|
|
1002
|
+
"type": "address",
|
|
1003
|
+
"indexed": true,
|
|
1004
|
+
"internalType": "address"
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
"name": "amount6",
|
|
1008
|
+
"type": "uint128",
|
|
1009
|
+
"indexed": false,
|
|
1010
|
+
"internalType": "uint128"
|
|
1011
|
+
}
|
|
1012
|
+
],
|
|
1013
|
+
"anonymous": false
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
"type": "error",
|
|
1017
|
+
"name": "AlreadyReleased",
|
|
1018
|
+
"inputs": [
|
|
1019
|
+
{
|
|
1020
|
+
"name": "id",
|
|
1021
|
+
"type": "uint256",
|
|
1022
|
+
"internalType": "uint256"
|
|
1023
|
+
}
|
|
1024
|
+
]
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
"type": "error",
|
|
1028
|
+
"name": "InsufficientTreasury",
|
|
1029
|
+
"inputs": [
|
|
1030
|
+
{
|
|
1031
|
+
"name": "root",
|
|
1032
|
+
"type": "bytes32",
|
|
1033
|
+
"internalType": "bytes32"
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
"name": "have",
|
|
1037
|
+
"type": "uint128",
|
|
1038
|
+
"internalType": "uint128"
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
"name": "want",
|
|
1042
|
+
"type": "uint128",
|
|
1043
|
+
"internalType": "uint128"
|
|
1044
|
+
}
|
|
1045
|
+
]
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
"type": "error",
|
|
1049
|
+
"name": "NotARoot",
|
|
1050
|
+
"inputs": [
|
|
1051
|
+
{
|
|
1052
|
+
"name": "node",
|
|
1053
|
+
"type": "bytes32",
|
|
1054
|
+
"internalType": "bytes32"
|
|
1055
|
+
}
|
|
1056
|
+
]
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
"type": "error",
|
|
1060
|
+
"name": "NotOperator",
|
|
1061
|
+
"inputs": [
|
|
1062
|
+
{
|
|
1063
|
+
"name": "node",
|
|
1064
|
+
"type": "bytes32",
|
|
1065
|
+
"internalType": "bytes32"
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
"name": "caller",
|
|
1069
|
+
"type": "address",
|
|
1070
|
+
"internalType": "address"
|
|
1071
|
+
}
|
|
1072
|
+
]
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
"type": "error",
|
|
1076
|
+
"name": "NotOwner",
|
|
1077
|
+
"inputs": [
|
|
1078
|
+
{
|
|
1079
|
+
"name": "root",
|
|
1080
|
+
"type": "bytes32",
|
|
1081
|
+
"internalType": "bytes32"
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
"name": "caller",
|
|
1085
|
+
"type": "address",
|
|
1086
|
+
"internalType": "address"
|
|
1087
|
+
}
|
|
1088
|
+
]
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
"type": "error",
|
|
1092
|
+
"name": "TransferFailed",
|
|
1093
|
+
"inputs": []
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
"type": "error",
|
|
1097
|
+
"name": "UnknownRefusal",
|
|
1098
|
+
"inputs": [
|
|
1099
|
+
{
|
|
1100
|
+
"name": "id",
|
|
1101
|
+
"type": "uint256",
|
|
1102
|
+
"internalType": "uint256"
|
|
1103
|
+
}
|
|
1104
|
+
]
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
"type": "error",
|
|
1108
|
+
"name": "ZeroAmount",
|
|
1109
|
+
"inputs": []
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
"type": "error",
|
|
1113
|
+
"name": "ZeroCounterparty",
|
|
1114
|
+
"inputs": []
|
|
1115
|
+
}
|
|
1116
|
+
];
|
|
1117
|
+
var MandateRegistryAbi = [
|
|
1118
|
+
{
|
|
1119
|
+
"type": "function",
|
|
1120
|
+
"name": "BPS",
|
|
1121
|
+
"inputs": [],
|
|
1122
|
+
"outputs": [
|
|
1123
|
+
{
|
|
1124
|
+
"name": "",
|
|
1125
|
+
"type": "uint16",
|
|
1126
|
+
"internalType": "uint16"
|
|
1127
|
+
}
|
|
1128
|
+
],
|
|
1129
|
+
"stateMutability": "view"
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
"type": "function",
|
|
1133
|
+
"name": "exists",
|
|
1134
|
+
"inputs": [
|
|
1135
|
+
{
|
|
1136
|
+
"name": "node",
|
|
1137
|
+
"type": "bytes32",
|
|
1138
|
+
"internalType": "bytes32"
|
|
1139
|
+
}
|
|
1140
|
+
],
|
|
1141
|
+
"outputs": [
|
|
1142
|
+
{
|
|
1143
|
+
"name": "",
|
|
1144
|
+
"type": "bool",
|
|
1145
|
+
"internalType": "bool"
|
|
1146
|
+
}
|
|
1147
|
+
],
|
|
1148
|
+
"stateMutability": "view"
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
"type": "function",
|
|
1152
|
+
"name": "isLive",
|
|
1153
|
+
"inputs": [
|
|
1154
|
+
{
|
|
1155
|
+
"name": "node",
|
|
1156
|
+
"type": "bytes32",
|
|
1157
|
+
"internalType": "bytes32"
|
|
1158
|
+
}
|
|
1159
|
+
],
|
|
1160
|
+
"outputs": [
|
|
1161
|
+
{
|
|
1162
|
+
"name": "",
|
|
1163
|
+
"type": "bool",
|
|
1164
|
+
"internalType": "bool"
|
|
1165
|
+
}
|
|
1166
|
+
],
|
|
1167
|
+
"stateMutability": "view"
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
"type": "function",
|
|
1171
|
+
"name": "mandate",
|
|
1172
|
+
"inputs": [
|
|
1173
|
+
{
|
|
1174
|
+
"name": "node",
|
|
1175
|
+
"type": "bytes32",
|
|
1176
|
+
"internalType": "bytes32"
|
|
1177
|
+
}
|
|
1178
|
+
],
|
|
1179
|
+
"outputs": [
|
|
1180
|
+
{
|
|
1181
|
+
"name": "",
|
|
1182
|
+
"type": "tuple",
|
|
1183
|
+
"internalType": "struct MandateRegistry.Mandate",
|
|
1184
|
+
"components": [
|
|
1185
|
+
{
|
|
1186
|
+
"name": "parent",
|
|
1187
|
+
"type": "bytes32",
|
|
1188
|
+
"internalType": "bytes32"
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
"name": "root",
|
|
1192
|
+
"type": "bytes32",
|
|
1193
|
+
"internalType": "bytes32"
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
"name": "owner",
|
|
1197
|
+
"type": "address",
|
|
1198
|
+
"internalType": "address"
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
"name": "operator",
|
|
1202
|
+
"type": "address",
|
|
1203
|
+
"internalType": "address"
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
"name": "budget6",
|
|
1207
|
+
"type": "uint128",
|
|
1208
|
+
"internalType": "uint128"
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
"name": "lifetimeCap6",
|
|
1212
|
+
"type": "uint128",
|
|
1213
|
+
"internalType": "uint128"
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
"name": "windowSeconds",
|
|
1217
|
+
"type": "uint64",
|
|
1218
|
+
"internalType": "uint64"
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
"name": "trancheCap6",
|
|
1222
|
+
"type": "uint128",
|
|
1223
|
+
"internalType": "uint128"
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
"name": "concentrationBps",
|
|
1227
|
+
"type": "uint16",
|
|
1228
|
+
"internalType": "uint16"
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
"name": "depth",
|
|
1232
|
+
"type": "uint8",
|
|
1233
|
+
"internalType": "uint8"
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
"name": "maxDepth",
|
|
1237
|
+
"type": "uint8",
|
|
1238
|
+
"internalType": "uint8"
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
"name": "revoked",
|
|
1242
|
+
"type": "bool",
|
|
1243
|
+
"internalType": "bool"
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
"name": "exists",
|
|
1247
|
+
"type": "bool",
|
|
1248
|
+
"internalType": "bool"
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
"name": "createdAt",
|
|
1252
|
+
"type": "uint64",
|
|
1253
|
+
"internalType": "uint64"
|
|
1254
|
+
}
|
|
1255
|
+
]
|
|
1256
|
+
}
|
|
1257
|
+
],
|
|
1258
|
+
"stateMutability": "view"
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
"type": "function",
|
|
1262
|
+
"name": "open",
|
|
1263
|
+
"inputs": [
|
|
1264
|
+
{
|
|
1265
|
+
"name": "p",
|
|
1266
|
+
"type": "tuple",
|
|
1267
|
+
"internalType": "struct MandateRegistry.Params",
|
|
1268
|
+
"components": [
|
|
1269
|
+
{
|
|
1270
|
+
"name": "operator",
|
|
1271
|
+
"type": "address",
|
|
1272
|
+
"internalType": "address"
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
"name": "budget6",
|
|
1276
|
+
"type": "uint128",
|
|
1277
|
+
"internalType": "uint128"
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
"name": "lifetimeCap6",
|
|
1281
|
+
"type": "uint128",
|
|
1282
|
+
"internalType": "uint128"
|
|
1283
|
+
},
|
|
1284
|
+
{
|
|
1285
|
+
"name": "windowSeconds",
|
|
1286
|
+
"type": "uint64",
|
|
1287
|
+
"internalType": "uint64"
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
"name": "trancheCap6",
|
|
1291
|
+
"type": "uint128",
|
|
1292
|
+
"internalType": "uint128"
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
"name": "concentrationBps",
|
|
1296
|
+
"type": "uint16",
|
|
1297
|
+
"internalType": "uint16"
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
"name": "maxDepth",
|
|
1301
|
+
"type": "uint8",
|
|
1302
|
+
"internalType": "uint8"
|
|
1303
|
+
}
|
|
1304
|
+
]
|
|
1305
|
+
}
|
|
1306
|
+
],
|
|
1307
|
+
"outputs": [
|
|
1308
|
+
{
|
|
1309
|
+
"name": "node",
|
|
1310
|
+
"type": "bytes32",
|
|
1311
|
+
"internalType": "bytes32"
|
|
1312
|
+
}
|
|
1313
|
+
],
|
|
1314
|
+
"stateMutability": "nonpayable"
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
"type": "function",
|
|
1318
|
+
"name": "path",
|
|
1319
|
+
"inputs": [
|
|
1320
|
+
{
|
|
1321
|
+
"name": "node",
|
|
1322
|
+
"type": "bytes32",
|
|
1323
|
+
"internalType": "bytes32"
|
|
1324
|
+
}
|
|
1325
|
+
],
|
|
1326
|
+
"outputs": [
|
|
1327
|
+
{
|
|
1328
|
+
"name": "out",
|
|
1329
|
+
"type": "bytes32[]",
|
|
1330
|
+
"internalType": "bytes32[]"
|
|
1331
|
+
}
|
|
1332
|
+
],
|
|
1333
|
+
"stateMutability": "view"
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
"type": "function",
|
|
1337
|
+
"name": "revoke",
|
|
1338
|
+
"inputs": [
|
|
1339
|
+
{
|
|
1340
|
+
"name": "node",
|
|
1341
|
+
"type": "bytes32",
|
|
1342
|
+
"internalType": "bytes32"
|
|
1343
|
+
}
|
|
1344
|
+
],
|
|
1345
|
+
"outputs": [],
|
|
1346
|
+
"stateMutability": "nonpayable"
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
"type": "function",
|
|
1350
|
+
"name": "revokedAt",
|
|
1351
|
+
"inputs": [
|
|
1352
|
+
{
|
|
1353
|
+
"name": "node",
|
|
1354
|
+
"type": "bytes32",
|
|
1355
|
+
"internalType": "bytes32"
|
|
1356
|
+
}
|
|
1357
|
+
],
|
|
1358
|
+
"outputs": [
|
|
1359
|
+
{
|
|
1360
|
+
"name": "",
|
|
1361
|
+
"type": "bytes32",
|
|
1362
|
+
"internalType": "bytes32"
|
|
1363
|
+
}
|
|
1364
|
+
],
|
|
1365
|
+
"stateMutability": "view"
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
"type": "function",
|
|
1369
|
+
"name": "spawn",
|
|
1370
|
+
"inputs": [
|
|
1371
|
+
{
|
|
1372
|
+
"name": "parent",
|
|
1373
|
+
"type": "bytes32",
|
|
1374
|
+
"internalType": "bytes32"
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
"name": "p",
|
|
1378
|
+
"type": "tuple",
|
|
1379
|
+
"internalType": "struct MandateRegistry.Params",
|
|
1380
|
+
"components": [
|
|
1381
|
+
{
|
|
1382
|
+
"name": "operator",
|
|
1383
|
+
"type": "address",
|
|
1384
|
+
"internalType": "address"
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
"name": "budget6",
|
|
1388
|
+
"type": "uint128",
|
|
1389
|
+
"internalType": "uint128"
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
"name": "lifetimeCap6",
|
|
1393
|
+
"type": "uint128",
|
|
1394
|
+
"internalType": "uint128"
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
"name": "windowSeconds",
|
|
1398
|
+
"type": "uint64",
|
|
1399
|
+
"internalType": "uint64"
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
"name": "trancheCap6",
|
|
1403
|
+
"type": "uint128",
|
|
1404
|
+
"internalType": "uint128"
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
"name": "concentrationBps",
|
|
1408
|
+
"type": "uint16",
|
|
1409
|
+
"internalType": "uint16"
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
"name": "maxDepth",
|
|
1413
|
+
"type": "uint8",
|
|
1414
|
+
"internalType": "uint8"
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
}
|
|
1418
|
+
],
|
|
1419
|
+
"outputs": [
|
|
1420
|
+
{
|
|
1421
|
+
"name": "node",
|
|
1422
|
+
"type": "bytes32",
|
|
1423
|
+
"internalType": "bytes32"
|
|
1424
|
+
}
|
|
1425
|
+
],
|
|
1426
|
+
"stateMutability": "nonpayable"
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
"type": "event",
|
|
1430
|
+
"name": "MandateOpened",
|
|
1431
|
+
"inputs": [
|
|
1432
|
+
{
|
|
1433
|
+
"name": "node",
|
|
1434
|
+
"type": "bytes32",
|
|
1435
|
+
"indexed": true,
|
|
1436
|
+
"internalType": "bytes32"
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
"name": "owner",
|
|
1440
|
+
"type": "address",
|
|
1441
|
+
"indexed": true,
|
|
1442
|
+
"internalType": "address"
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
"name": "operator",
|
|
1446
|
+
"type": "address",
|
|
1447
|
+
"indexed": true,
|
|
1448
|
+
"internalType": "address"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
"name": "budget6",
|
|
1452
|
+
"type": "uint128",
|
|
1453
|
+
"indexed": false,
|
|
1454
|
+
"internalType": "uint128"
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
"name": "lifetimeCap6",
|
|
1458
|
+
"type": "uint128",
|
|
1459
|
+
"indexed": false,
|
|
1460
|
+
"internalType": "uint128"
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
"name": "windowSeconds",
|
|
1464
|
+
"type": "uint64",
|
|
1465
|
+
"indexed": false,
|
|
1466
|
+
"internalType": "uint64"
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
"name": "maxDepth",
|
|
1470
|
+
"type": "uint8",
|
|
1471
|
+
"indexed": false,
|
|
1472
|
+
"internalType": "uint8"
|
|
1473
|
+
}
|
|
1474
|
+
],
|
|
1475
|
+
"anonymous": false
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
"type": "event",
|
|
1479
|
+
"name": "MandateRevoked",
|
|
1480
|
+
"inputs": [
|
|
1481
|
+
{
|
|
1482
|
+
"name": "node",
|
|
1483
|
+
"type": "bytes32",
|
|
1484
|
+
"indexed": true,
|
|
1485
|
+
"internalType": "bytes32"
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
"name": "by",
|
|
1489
|
+
"type": "address",
|
|
1490
|
+
"indexed": true,
|
|
1491
|
+
"internalType": "address"
|
|
1492
|
+
}
|
|
1493
|
+
],
|
|
1494
|
+
"anonymous": false
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
"type": "event",
|
|
1498
|
+
"name": "MandateSpawned",
|
|
1499
|
+
"inputs": [
|
|
1500
|
+
{
|
|
1501
|
+
"name": "node",
|
|
1502
|
+
"type": "bytes32",
|
|
1503
|
+
"indexed": true,
|
|
1504
|
+
"internalType": "bytes32"
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
"name": "parent",
|
|
1508
|
+
"type": "bytes32",
|
|
1509
|
+
"indexed": true,
|
|
1510
|
+
"internalType": "bytes32"
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
"name": "operator",
|
|
1514
|
+
"type": "address",
|
|
1515
|
+
"indexed": true,
|
|
1516
|
+
"internalType": "address"
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
"name": "budget6",
|
|
1520
|
+
"type": "uint128",
|
|
1521
|
+
"indexed": false,
|
|
1522
|
+
"internalType": "uint128"
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
"name": "lifetimeCap6",
|
|
1526
|
+
"type": "uint128",
|
|
1527
|
+
"indexed": false,
|
|
1528
|
+
"internalType": "uint128"
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
"name": "depth",
|
|
1532
|
+
"type": "uint8",
|
|
1533
|
+
"indexed": false,
|
|
1534
|
+
"internalType": "uint8"
|
|
1535
|
+
}
|
|
1536
|
+
],
|
|
1537
|
+
"anonymous": false
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
"type": "error",
|
|
1541
|
+
"name": "ConcentrationOutOfRange",
|
|
1542
|
+
"inputs": [
|
|
1543
|
+
{
|
|
1544
|
+
"name": "bps",
|
|
1545
|
+
"type": "uint16",
|
|
1546
|
+
"internalType": "uint16"
|
|
1547
|
+
}
|
|
1548
|
+
]
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
"type": "error",
|
|
1552
|
+
"name": "DepthExceeded",
|
|
1553
|
+
"inputs": [
|
|
1554
|
+
{
|
|
1555
|
+
"name": "attempted",
|
|
1556
|
+
"type": "uint8",
|
|
1557
|
+
"internalType": "uint8"
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
"name": "max",
|
|
1561
|
+
"type": "uint8",
|
|
1562
|
+
"internalType": "uint8"
|
|
1563
|
+
}
|
|
1564
|
+
]
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
"type": "error",
|
|
1568
|
+
"name": "NotNarrowing",
|
|
1569
|
+
"inputs": [
|
|
1570
|
+
{
|
|
1571
|
+
"name": "field",
|
|
1572
|
+
"type": "string",
|
|
1573
|
+
"internalType": "string"
|
|
1574
|
+
}
|
|
1575
|
+
]
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
"type": "error",
|
|
1579
|
+
"name": "NotOwner",
|
|
1580
|
+
"inputs": []
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
"type": "error",
|
|
1584
|
+
"name": "NotParentOperator",
|
|
1585
|
+
"inputs": []
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
"type": "error",
|
|
1589
|
+
"name": "ParentRevoked",
|
|
1590
|
+
"inputs": [
|
|
1591
|
+
{
|
|
1592
|
+
"name": "node",
|
|
1593
|
+
"type": "bytes32",
|
|
1594
|
+
"internalType": "bytes32"
|
|
1595
|
+
}
|
|
1596
|
+
]
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
"type": "error",
|
|
1600
|
+
"name": "UnknownMandate",
|
|
1601
|
+
"inputs": [
|
|
1602
|
+
{
|
|
1603
|
+
"name": "node",
|
|
1604
|
+
"type": "bytes32",
|
|
1605
|
+
"internalType": "bytes32"
|
|
1606
|
+
}
|
|
1607
|
+
]
|
|
1608
|
+
},
|
|
1609
|
+
{
|
|
1610
|
+
"type": "error",
|
|
1611
|
+
"name": "WindowMustEqualParent",
|
|
1612
|
+
"inputs": [
|
|
1613
|
+
{
|
|
1614
|
+
"name": "child",
|
|
1615
|
+
"type": "uint64",
|
|
1616
|
+
"internalType": "uint64"
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
"name": "parent",
|
|
1620
|
+
"type": "uint64",
|
|
1621
|
+
"internalType": "uint64"
|
|
1622
|
+
}
|
|
1623
|
+
]
|
|
1624
|
+
},
|
|
1625
|
+
{
|
|
1626
|
+
"type": "error",
|
|
1627
|
+
"name": "ZeroBudget",
|
|
1628
|
+
"inputs": []
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
"type": "error",
|
|
1632
|
+
"name": "ZeroLifetimeCap",
|
|
1633
|
+
"inputs": []
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
"type": "error",
|
|
1637
|
+
"name": "ZeroOperator",
|
|
1638
|
+
"inputs": []
|
|
1639
|
+
},
|
|
1640
|
+
{
|
|
1641
|
+
"type": "error",
|
|
1642
|
+
"name": "ZeroTrancheCap",
|
|
1643
|
+
"inputs": []
|
|
1644
|
+
}
|
|
1645
|
+
];
|
|
1646
|
+
var ConductRecordAbi = [
|
|
1647
|
+
{
|
|
1648
|
+
"type": "constructor",
|
|
1649
|
+
"inputs": [
|
|
1650
|
+
{
|
|
1651
|
+
"name": "vault_",
|
|
1652
|
+
"type": "address",
|
|
1653
|
+
"internalType": "contract TreeVault"
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
"name": "identity_",
|
|
1657
|
+
"type": "address",
|
|
1658
|
+
"internalType": "contract IIdentityRegistry"
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
"name": "reputation_",
|
|
1662
|
+
"type": "address",
|
|
1663
|
+
"internalType": "contract IReputationRegistry"
|
|
1664
|
+
}
|
|
1665
|
+
],
|
|
1666
|
+
"stateMutability": "nonpayable"
|
|
1667
|
+
},
|
|
1668
|
+
{
|
|
1669
|
+
"type": "function",
|
|
1670
|
+
"name": "RECORD_BASE",
|
|
1671
|
+
"inputs": [],
|
|
1672
|
+
"outputs": [
|
|
1673
|
+
{
|
|
1674
|
+
"name": "",
|
|
1675
|
+
"type": "string",
|
|
1676
|
+
"internalType": "string"
|
|
1677
|
+
}
|
|
1678
|
+
],
|
|
1679
|
+
"stateMutability": "view"
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
"type": "function",
|
|
1683
|
+
"name": "agentIdOf",
|
|
1684
|
+
"inputs": [
|
|
1685
|
+
{
|
|
1686
|
+
"name": "",
|
|
1687
|
+
"type": "bytes32",
|
|
1688
|
+
"internalType": "bytes32"
|
|
1689
|
+
}
|
|
1690
|
+
],
|
|
1691
|
+
"outputs": [
|
|
1692
|
+
{
|
|
1693
|
+
"name": "",
|
|
1694
|
+
"type": "uint256",
|
|
1695
|
+
"internalType": "uint256"
|
|
1696
|
+
}
|
|
1697
|
+
],
|
|
1698
|
+
"stateMutability": "view"
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
"type": "function",
|
|
1702
|
+
"name": "attest",
|
|
1703
|
+
"inputs": [
|
|
1704
|
+
{
|
|
1705
|
+
"name": "refusalId",
|
|
1706
|
+
"type": "uint256",
|
|
1707
|
+
"internalType": "uint256"
|
|
1708
|
+
}
|
|
1709
|
+
],
|
|
1710
|
+
"outputs": [
|
|
1711
|
+
{
|
|
1712
|
+
"name": "feedbackIndex",
|
|
1713
|
+
"type": "uint64",
|
|
1714
|
+
"internalType": "uint64"
|
|
1715
|
+
}
|
|
1716
|
+
],
|
|
1717
|
+
"stateMutability": "nonpayable"
|
|
1718
|
+
},
|
|
1719
|
+
{
|
|
1720
|
+
"type": "function",
|
|
1721
|
+
"name": "attestRelease",
|
|
1722
|
+
"inputs": [
|
|
1723
|
+
{
|
|
1724
|
+
"name": "refusalId",
|
|
1725
|
+
"type": "uint256",
|
|
1726
|
+
"internalType": "uint256"
|
|
1727
|
+
}
|
|
1728
|
+
],
|
|
1729
|
+
"outputs": [],
|
|
1730
|
+
"stateMutability": "nonpayable"
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
"type": "function",
|
|
1734
|
+
"name": "attested",
|
|
1735
|
+
"inputs": [
|
|
1736
|
+
{
|
|
1737
|
+
"name": "",
|
|
1738
|
+
"type": "uint256",
|
|
1739
|
+
"internalType": "uint256"
|
|
1740
|
+
}
|
|
1741
|
+
],
|
|
1742
|
+
"outputs": [
|
|
1743
|
+
{
|
|
1744
|
+
"name": "",
|
|
1745
|
+
"type": "bool",
|
|
1746
|
+
"internalType": "bool"
|
|
1747
|
+
}
|
|
1748
|
+
],
|
|
1749
|
+
"stateMutability": "view"
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
"type": "function",
|
|
1753
|
+
"name": "bind",
|
|
1754
|
+
"inputs": [
|
|
1755
|
+
{
|
|
1756
|
+
"name": "node",
|
|
1757
|
+
"type": "bytes32",
|
|
1758
|
+
"internalType": "bytes32"
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
"name": "agentId",
|
|
1762
|
+
"type": "uint256",
|
|
1763
|
+
"internalType": "uint256"
|
|
1764
|
+
}
|
|
1765
|
+
],
|
|
1766
|
+
"outputs": [],
|
|
1767
|
+
"stateMutability": "nonpayable"
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
"type": "function",
|
|
1771
|
+
"name": "feedbackIndexOf",
|
|
1772
|
+
"inputs": [
|
|
1773
|
+
{
|
|
1774
|
+
"name": "",
|
|
1775
|
+
"type": "uint256",
|
|
1776
|
+
"internalType": "uint256"
|
|
1777
|
+
}
|
|
1778
|
+
],
|
|
1779
|
+
"outputs": [
|
|
1780
|
+
{
|
|
1781
|
+
"name": "",
|
|
1782
|
+
"type": "uint64",
|
|
1783
|
+
"internalType": "uint64"
|
|
1784
|
+
}
|
|
1785
|
+
],
|
|
1786
|
+
"stateMutability": "view"
|
|
1787
|
+
},
|
|
1788
|
+
{
|
|
1789
|
+
"type": "function",
|
|
1790
|
+
"name": "identity",
|
|
1791
|
+
"inputs": [],
|
|
1792
|
+
"outputs": [
|
|
1793
|
+
{
|
|
1794
|
+
"name": "",
|
|
1795
|
+
"type": "address",
|
|
1796
|
+
"internalType": "contract IIdentityRegistry"
|
|
1797
|
+
}
|
|
1798
|
+
],
|
|
1799
|
+
"stateMutability": "view"
|
|
1800
|
+
},
|
|
1801
|
+
{
|
|
1802
|
+
"type": "function",
|
|
1803
|
+
"name": "nodeOfAgent",
|
|
1804
|
+
"inputs": [
|
|
1805
|
+
{
|
|
1806
|
+
"name": "",
|
|
1807
|
+
"type": "uint256",
|
|
1808
|
+
"internalType": "uint256"
|
|
1809
|
+
}
|
|
1810
|
+
],
|
|
1811
|
+
"outputs": [
|
|
1812
|
+
{
|
|
1813
|
+
"name": "",
|
|
1814
|
+
"type": "bytes32",
|
|
1815
|
+
"internalType": "bytes32"
|
|
1816
|
+
}
|
|
1817
|
+
],
|
|
1818
|
+
"stateMutability": "view"
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
"type": "function",
|
|
1822
|
+
"name": "reasonTag",
|
|
1823
|
+
"inputs": [
|
|
1824
|
+
{
|
|
1825
|
+
"name": "reason",
|
|
1826
|
+
"type": "uint8",
|
|
1827
|
+
"internalType": "enum TreeVault.Reason"
|
|
1828
|
+
}
|
|
1829
|
+
],
|
|
1830
|
+
"outputs": [
|
|
1831
|
+
{
|
|
1832
|
+
"name": "",
|
|
1833
|
+
"type": "string",
|
|
1834
|
+
"internalType": "string"
|
|
1835
|
+
}
|
|
1836
|
+
],
|
|
1837
|
+
"stateMutability": "pure"
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
"type": "function",
|
|
1841
|
+
"name": "recordUri",
|
|
1842
|
+
"inputs": [
|
|
1843
|
+
{
|
|
1844
|
+
"name": "refusalId",
|
|
1845
|
+
"type": "uint256",
|
|
1846
|
+
"internalType": "uint256"
|
|
1847
|
+
}
|
|
1848
|
+
],
|
|
1849
|
+
"outputs": [
|
|
1850
|
+
{
|
|
1851
|
+
"name": "",
|
|
1852
|
+
"type": "string",
|
|
1853
|
+
"internalType": "string"
|
|
1854
|
+
}
|
|
1855
|
+
],
|
|
1856
|
+
"stateMutability": "pure"
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
"type": "function",
|
|
1860
|
+
"name": "refusalHash",
|
|
1861
|
+
"inputs": [
|
|
1862
|
+
{
|
|
1863
|
+
"name": "refusalId",
|
|
1864
|
+
"type": "uint256",
|
|
1865
|
+
"internalType": "uint256"
|
|
1866
|
+
}
|
|
1867
|
+
],
|
|
1868
|
+
"outputs": [
|
|
1869
|
+
{
|
|
1870
|
+
"name": "",
|
|
1871
|
+
"type": "bytes32",
|
|
1872
|
+
"internalType": "bytes32"
|
|
1873
|
+
}
|
|
1874
|
+
],
|
|
1875
|
+
"stateMutability": "view"
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
"type": "function",
|
|
1879
|
+
"name": "refusalHash",
|
|
1880
|
+
"inputs": [
|
|
1881
|
+
{
|
|
1882
|
+
"name": "refusalId",
|
|
1883
|
+
"type": "uint256",
|
|
1884
|
+
"internalType": "uint256"
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
"name": "r",
|
|
1888
|
+
"type": "tuple",
|
|
1889
|
+
"internalType": "struct TreeVault.Refusal",
|
|
1890
|
+
"components": [
|
|
1891
|
+
{
|
|
1892
|
+
"name": "node",
|
|
1893
|
+
"type": "bytes32",
|
|
1894
|
+
"internalType": "bytes32"
|
|
1895
|
+
},
|
|
1896
|
+
{
|
|
1897
|
+
"name": "breachedAt",
|
|
1898
|
+
"type": "bytes32",
|
|
1899
|
+
"internalType": "bytes32"
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
"name": "counterparty",
|
|
1903
|
+
"type": "address",
|
|
1904
|
+
"internalType": "address"
|
|
1905
|
+
},
|
|
1906
|
+
{
|
|
1907
|
+
"name": "amount6",
|
|
1908
|
+
"type": "uint128",
|
|
1909
|
+
"internalType": "uint128"
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
"name": "reason",
|
|
1913
|
+
"type": "uint8",
|
|
1914
|
+
"internalType": "enum TreeVault.Reason"
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
"name": "at",
|
|
1918
|
+
"type": "uint64",
|
|
1919
|
+
"internalType": "uint64"
|
|
1920
|
+
},
|
|
1921
|
+
{
|
|
1922
|
+
"name": "released",
|
|
1923
|
+
"type": "bool",
|
|
1924
|
+
"internalType": "bool"
|
|
1925
|
+
}
|
|
1926
|
+
]
|
|
1927
|
+
}
|
|
1928
|
+
],
|
|
1929
|
+
"outputs": [
|
|
1930
|
+
{
|
|
1931
|
+
"name": "",
|
|
1932
|
+
"type": "bytes32",
|
|
1933
|
+
"internalType": "bytes32"
|
|
1934
|
+
}
|
|
1935
|
+
],
|
|
1936
|
+
"stateMutability": "view"
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
"type": "function",
|
|
1940
|
+
"name": "registry",
|
|
1941
|
+
"inputs": [],
|
|
1942
|
+
"outputs": [
|
|
1943
|
+
{
|
|
1944
|
+
"name": "",
|
|
1945
|
+
"type": "address",
|
|
1946
|
+
"internalType": "contract MandateRegistry"
|
|
1947
|
+
}
|
|
1948
|
+
],
|
|
1949
|
+
"stateMutability": "view"
|
|
1950
|
+
},
|
|
1951
|
+
{
|
|
1952
|
+
"type": "function",
|
|
1953
|
+
"name": "releaseAttested",
|
|
1954
|
+
"inputs": [
|
|
1955
|
+
{
|
|
1956
|
+
"name": "",
|
|
1957
|
+
"type": "uint256",
|
|
1958
|
+
"internalType": "uint256"
|
|
1959
|
+
}
|
|
1960
|
+
],
|
|
1961
|
+
"outputs": [
|
|
1962
|
+
{
|
|
1963
|
+
"name": "",
|
|
1964
|
+
"type": "bool",
|
|
1965
|
+
"internalType": "bool"
|
|
1966
|
+
}
|
|
1967
|
+
],
|
|
1968
|
+
"stateMutability": "view"
|
|
1969
|
+
},
|
|
1970
|
+
{
|
|
1971
|
+
"type": "function",
|
|
1972
|
+
"name": "reputation",
|
|
1973
|
+
"inputs": [],
|
|
1974
|
+
"outputs": [
|
|
1975
|
+
{
|
|
1976
|
+
"name": "",
|
|
1977
|
+
"type": "address",
|
|
1978
|
+
"internalType": "contract IReputationRegistry"
|
|
1979
|
+
}
|
|
1980
|
+
],
|
|
1981
|
+
"stateMutability": "view"
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
"type": "function",
|
|
1985
|
+
"name": "vault",
|
|
1986
|
+
"inputs": [],
|
|
1987
|
+
"outputs": [
|
|
1988
|
+
{
|
|
1989
|
+
"name": "",
|
|
1990
|
+
"type": "address",
|
|
1991
|
+
"internalType": "contract TreeVault"
|
|
1992
|
+
}
|
|
1993
|
+
],
|
|
1994
|
+
"stateMutability": "view"
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
"type": "event",
|
|
1998
|
+
"name": "Attested",
|
|
1999
|
+
"inputs": [
|
|
2000
|
+
{
|
|
2001
|
+
"name": "refusalId",
|
|
2002
|
+
"type": "uint256",
|
|
2003
|
+
"indexed": true,
|
|
2004
|
+
"internalType": "uint256"
|
|
2005
|
+
},
|
|
2006
|
+
{
|
|
2007
|
+
"name": "node",
|
|
2008
|
+
"type": "bytes32",
|
|
2009
|
+
"indexed": true,
|
|
2010
|
+
"internalType": "bytes32"
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
"name": "agentId",
|
|
2014
|
+
"type": "uint256",
|
|
2015
|
+
"indexed": true,
|
|
2016
|
+
"internalType": "uint256"
|
|
2017
|
+
},
|
|
2018
|
+
{
|
|
2019
|
+
"name": "recordHash",
|
|
2020
|
+
"type": "bytes32",
|
|
2021
|
+
"indexed": false,
|
|
2022
|
+
"internalType": "bytes32"
|
|
2023
|
+
}
|
|
2024
|
+
],
|
|
2025
|
+
"anonymous": false
|
|
2026
|
+
},
|
|
2027
|
+
{
|
|
2028
|
+
"type": "event",
|
|
2029
|
+
"name": "Bound",
|
|
2030
|
+
"inputs": [
|
|
2031
|
+
{
|
|
2032
|
+
"name": "node",
|
|
2033
|
+
"type": "bytes32",
|
|
2034
|
+
"indexed": true,
|
|
2035
|
+
"internalType": "bytes32"
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
"name": "agentId",
|
|
2039
|
+
"type": "uint256",
|
|
2040
|
+
"indexed": true,
|
|
2041
|
+
"internalType": "uint256"
|
|
2042
|
+
},
|
|
2043
|
+
{
|
|
2044
|
+
"name": "operator",
|
|
2045
|
+
"type": "address",
|
|
2046
|
+
"indexed": true,
|
|
2047
|
+
"internalType": "address"
|
|
2048
|
+
}
|
|
2049
|
+
],
|
|
2050
|
+
"anonymous": false
|
|
2051
|
+
},
|
|
2052
|
+
{
|
|
2053
|
+
"type": "event",
|
|
2054
|
+
"name": "ReleaseAttested",
|
|
2055
|
+
"inputs": [
|
|
2056
|
+
{
|
|
2057
|
+
"name": "refusalId",
|
|
2058
|
+
"type": "uint256",
|
|
2059
|
+
"indexed": true,
|
|
2060
|
+
"internalType": "uint256"
|
|
2061
|
+
},
|
|
2062
|
+
{
|
|
2063
|
+
"name": "agentId",
|
|
2064
|
+
"type": "uint256",
|
|
2065
|
+
"indexed": true,
|
|
2066
|
+
"internalType": "uint256"
|
|
2067
|
+
},
|
|
2068
|
+
{
|
|
2069
|
+
"name": "recordHash",
|
|
2070
|
+
"type": "bytes32",
|
|
2071
|
+
"indexed": false,
|
|
2072
|
+
"internalType": "bytes32"
|
|
2073
|
+
}
|
|
2074
|
+
],
|
|
2075
|
+
"anonymous": false
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
"type": "error",
|
|
2079
|
+
"name": "AgentAlreadyBound",
|
|
2080
|
+
"inputs": [
|
|
2081
|
+
{
|
|
2082
|
+
"name": "agentId",
|
|
2083
|
+
"type": "uint256",
|
|
2084
|
+
"internalType": "uint256"
|
|
2085
|
+
},
|
|
2086
|
+
{
|
|
2087
|
+
"name": "node",
|
|
2088
|
+
"type": "bytes32",
|
|
2089
|
+
"internalType": "bytes32"
|
|
2090
|
+
}
|
|
2091
|
+
]
|
|
2092
|
+
},
|
|
2093
|
+
{
|
|
2094
|
+
"type": "error",
|
|
2095
|
+
"name": "AlreadyAttested",
|
|
2096
|
+
"inputs": [
|
|
2097
|
+
{
|
|
2098
|
+
"name": "refusalId",
|
|
2099
|
+
"type": "uint256",
|
|
2100
|
+
"internalType": "uint256"
|
|
2101
|
+
}
|
|
2102
|
+
]
|
|
2103
|
+
},
|
|
2104
|
+
{
|
|
2105
|
+
"type": "error",
|
|
2106
|
+
"name": "IdentityNotHeldByOperator",
|
|
2107
|
+
"inputs": [
|
|
2108
|
+
{
|
|
2109
|
+
"name": "agentId",
|
|
2110
|
+
"type": "uint256",
|
|
2111
|
+
"internalType": "uint256"
|
|
2112
|
+
},
|
|
2113
|
+
{
|
|
2114
|
+
"name": "holder",
|
|
2115
|
+
"type": "address",
|
|
2116
|
+
"internalType": "address"
|
|
2117
|
+
},
|
|
2118
|
+
{
|
|
2119
|
+
"name": "operator",
|
|
2120
|
+
"type": "address",
|
|
2121
|
+
"internalType": "address"
|
|
2122
|
+
}
|
|
2123
|
+
]
|
|
2124
|
+
},
|
|
2125
|
+
{
|
|
2126
|
+
"type": "error",
|
|
2127
|
+
"name": "NodeAlreadyBound",
|
|
2128
|
+
"inputs": [
|
|
2129
|
+
{
|
|
2130
|
+
"name": "node",
|
|
2131
|
+
"type": "bytes32",
|
|
2132
|
+
"internalType": "bytes32"
|
|
2133
|
+
},
|
|
2134
|
+
{
|
|
2135
|
+
"name": "agentId",
|
|
2136
|
+
"type": "uint256",
|
|
2137
|
+
"internalType": "uint256"
|
|
2138
|
+
}
|
|
2139
|
+
]
|
|
2140
|
+
},
|
|
2141
|
+
{
|
|
2142
|
+
"type": "error",
|
|
2143
|
+
"name": "NodeNotBound",
|
|
2144
|
+
"inputs": [
|
|
2145
|
+
{
|
|
2146
|
+
"name": "node",
|
|
2147
|
+
"type": "bytes32",
|
|
2148
|
+
"internalType": "bytes32"
|
|
2149
|
+
}
|
|
2150
|
+
]
|
|
2151
|
+
},
|
|
2152
|
+
{
|
|
2153
|
+
"type": "error",
|
|
2154
|
+
"name": "NotAttested",
|
|
2155
|
+
"inputs": [
|
|
2156
|
+
{
|
|
2157
|
+
"name": "refusalId",
|
|
2158
|
+
"type": "uint256",
|
|
2159
|
+
"internalType": "uint256"
|
|
2160
|
+
}
|
|
2161
|
+
]
|
|
2162
|
+
},
|
|
2163
|
+
{
|
|
2164
|
+
"type": "error",
|
|
2165
|
+
"name": "NotReleased",
|
|
2166
|
+
"inputs": [
|
|
2167
|
+
{
|
|
2168
|
+
"name": "refusalId",
|
|
2169
|
+
"type": "uint256",
|
|
2170
|
+
"internalType": "uint256"
|
|
2171
|
+
}
|
|
2172
|
+
]
|
|
2173
|
+
}
|
|
2174
|
+
];
|
|
2175
|
+
|
|
2176
|
+
// ../daemon/src/record.ts
|
|
2177
|
+
import { hexToString, stringToHex } from "viem";
|
|
2178
|
+
var IDENTITY_ABI = [
|
|
2179
|
+
{
|
|
2180
|
+
type: "function",
|
|
2181
|
+
name: "register",
|
|
2182
|
+
stateMutability: "nonpayable",
|
|
2183
|
+
inputs: [{ name: "agentURI", type: "string" }],
|
|
2184
|
+
outputs: [{ name: "agentId", type: "uint256" }]
|
|
2185
|
+
},
|
|
2186
|
+
{
|
|
2187
|
+
type: "function",
|
|
2188
|
+
name: "getMetadata",
|
|
2189
|
+
stateMutability: "view",
|
|
2190
|
+
inputs: [
|
|
2191
|
+
{ name: "agentId", type: "uint256" },
|
|
2192
|
+
{ name: "metadataKey", type: "string" }
|
|
2193
|
+
],
|
|
2194
|
+
outputs: [{ name: "", type: "bytes" }]
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
type: "function",
|
|
2198
|
+
name: "setMetadata",
|
|
2199
|
+
stateMutability: "nonpayable",
|
|
2200
|
+
inputs: [
|
|
2201
|
+
{ name: "agentId", type: "uint256" },
|
|
2202
|
+
{ name: "metadataKey", type: "string" },
|
|
2203
|
+
{ name: "metadataValue", type: "bytes" }
|
|
2204
|
+
],
|
|
2205
|
+
outputs: []
|
|
2206
|
+
}
|
|
2207
|
+
];
|
|
2208
|
+
var Recorder = class {
|
|
2209
|
+
deps;
|
|
2210
|
+
/**
|
|
2211
|
+
* How to sign as a node. The gate owns the keys and keeps owning them — this
|
|
2212
|
+
* is a function it lends, not a copy of its map, so there is still exactly
|
|
2213
|
+
* one place in the process where an operator key lives.
|
|
2214
|
+
*/
|
|
2215
|
+
walletFor;
|
|
2216
|
+
constructor(deps, walletFor) {
|
|
2217
|
+
this.deps = deps;
|
|
2218
|
+
this.walletFor = walletFor;
|
|
2219
|
+
}
|
|
2220
|
+
get enabled() {
|
|
2221
|
+
return Boolean(this.deps.record);
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Give a node an ERC-8004 identity and bind it to the seat.
|
|
2225
|
+
*
|
|
2226
|
+
* Registration is done with the node's operator key, so the token belongs to
|
|
2227
|
+
* the agent. `bind` then checks — on chain, in the contract — that the
|
|
2228
|
+
* identity's holder really is that node's operator. Nobody has to be trusted
|
|
2229
|
+
* for the link to be true.
|
|
2230
|
+
*
|
|
2231
|
+
* Returns the agent id, or null when there is nothing to write into.
|
|
2232
|
+
*/
|
|
2233
|
+
async enrol(node2) {
|
|
2234
|
+
const record = this.deps.record;
|
|
2235
|
+
if (!record) return null;
|
|
2236
|
+
const existing = await this.deps.publicClient.readContract({
|
|
2237
|
+
address: record,
|
|
2238
|
+
abi: ConductRecordAbi,
|
|
2239
|
+
functionName: "agentIdOf",
|
|
2240
|
+
args: [node2]
|
|
2241
|
+
});
|
|
2242
|
+
if (existing !== 0n) return existing;
|
|
2243
|
+
const wallet = this.walletFor(node2);
|
|
2244
|
+
const base = this.deps.agentUriBase ?? "https://getcordon.xyz/agent/";
|
|
2245
|
+
const registered = await wallet.writeContract({
|
|
2246
|
+
address: this.deps.identity,
|
|
2247
|
+
abi: IDENTITY_ABI,
|
|
2248
|
+
functionName: "register",
|
|
2249
|
+
args: [`${base}${node2}`],
|
|
2250
|
+
chain: this.deps.chain,
|
|
2251
|
+
account: wallet.account
|
|
2252
|
+
});
|
|
2253
|
+
const receipt = await this.deps.publicClient.waitForTransactionReceipt({ hash: registered });
|
|
2254
|
+
const agentId = await this.lastRegistered(wallet.account.address, receipt.blockNumber);
|
|
2255
|
+
if (agentId === null) return null;
|
|
2256
|
+
const bound = await wallet.writeContract({
|
|
2257
|
+
address: record,
|
|
2258
|
+
abi: ConductRecordAbi,
|
|
2259
|
+
functionName: "bind",
|
|
2260
|
+
args: [node2, agentId],
|
|
2261
|
+
chain: this.deps.chain,
|
|
2262
|
+
account: wallet.account
|
|
2263
|
+
});
|
|
2264
|
+
await this.deps.publicClient.waitForTransactionReceipt({ hash: bound });
|
|
2265
|
+
return agentId;
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Write what a node is for onto its own identity.
|
|
2269
|
+
*
|
|
2270
|
+
* Signed with the node's operator key, because the registry lets only an
|
|
2271
|
+
* identity's holder set its metadata. It is a statement by whoever spawned
|
|
2272
|
+
* the node and nothing reads it back as a bound: the contract has never seen
|
|
2273
|
+
* it, and a surface that shows it says so.
|
|
2274
|
+
*
|
|
2275
|
+
* Returns the transaction, or null when the node has no identity to write to.
|
|
2276
|
+
*/
|
|
2277
|
+
async describe(node2, purpose) {
|
|
2278
|
+
const record = this.deps.record;
|
|
2279
|
+
if (!record) return null;
|
|
2280
|
+
const agentId = await this.deps.publicClient.readContract({
|
|
2281
|
+
address: record,
|
|
2282
|
+
abi: ConductRecordAbi,
|
|
2283
|
+
functionName: "agentIdOf",
|
|
2284
|
+
args: [node2]
|
|
2285
|
+
});
|
|
2286
|
+
if (agentId === 0n) return null;
|
|
2287
|
+
const wallet = this.walletFor(node2);
|
|
2288
|
+
const hash = await wallet.writeContract({
|
|
2289
|
+
address: this.deps.identity,
|
|
2290
|
+
abi: IDENTITY_ABI,
|
|
2291
|
+
functionName: "setMetadata",
|
|
2292
|
+
args: [agentId, ERC8004.purposeKey, stringToHex(purpose)],
|
|
2293
|
+
chain: this.deps.chain,
|
|
2294
|
+
account: wallet.account
|
|
2295
|
+
});
|
|
2296
|
+
await this.deps.publicClient.waitForTransactionReceipt({ hash });
|
|
2297
|
+
return hash;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* What this node's identity already says it is for, if anything.
|
|
2301
|
+
*
|
|
2302
|
+
* Read before writing, so a daemon that republishes on every start sends a
|
|
2303
|
+
* transaction only for the nodes that are actually missing one. `null` means
|
|
2304
|
+
* the node has no identity at all, which is a different thing from an
|
|
2305
|
+
* identity that says nothing.
|
|
2306
|
+
*/
|
|
2307
|
+
async statedPurpose(node2) {
|
|
2308
|
+
const record = this.deps.record;
|
|
2309
|
+
if (!record) return null;
|
|
2310
|
+
const agentId = await this.deps.publicClient.readContract({
|
|
2311
|
+
address: record,
|
|
2312
|
+
abi: ConductRecordAbi,
|
|
2313
|
+
functionName: "agentIdOf",
|
|
2314
|
+
args: [node2]
|
|
2315
|
+
});
|
|
2316
|
+
if (agentId === 0n) return null;
|
|
2317
|
+
const raw = await this.deps.publicClient.readContract({
|
|
2318
|
+
address: this.deps.identity,
|
|
2319
|
+
abi: IDENTITY_ABI,
|
|
2320
|
+
functionName: "getMetadata",
|
|
2321
|
+
args: [agentId, ERC8004.purposeKey]
|
|
2322
|
+
});
|
|
2323
|
+
return raw === "0x" ? "" : hexToString(raw).trim();
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Publish one refusal.
|
|
2327
|
+
*
|
|
2328
|
+
* `attest` takes an id and nothing else: the amount, the node, the reason and
|
|
2329
|
+
* the bound that stopped it are all read out of the vault inside the call, so
|
|
2330
|
+
* this cannot report something that did not happen even if it wanted to.
|
|
2331
|
+
*
|
|
2332
|
+
* Anyone may call it, so the daemon uses whichever key it has for the node —
|
|
2333
|
+
* the record does not depend on who paid the gas.
|
|
2334
|
+
*/
|
|
2335
|
+
async attest(node2, refusalId) {
|
|
2336
|
+
const record = this.deps.record;
|
|
2337
|
+
if (!record || refusalId === 0n) return null;
|
|
2338
|
+
const wallet = this.walletFor(node2);
|
|
2339
|
+
const hash = await wallet.writeContract({
|
|
2340
|
+
address: record,
|
|
2341
|
+
abi: ConductRecordAbi,
|
|
2342
|
+
functionName: "attest",
|
|
2343
|
+
args: [refusalId],
|
|
2344
|
+
chain: this.deps.chain,
|
|
2345
|
+
account: wallet.account
|
|
2346
|
+
});
|
|
2347
|
+
await this.deps.publicClient.waitForTransactionReceipt({ hash });
|
|
2348
|
+
return hash;
|
|
2349
|
+
}
|
|
2350
|
+
/** The most recent identity this address registered, from the registry's own
|
|
2351
|
+
* event. Reading it back is the only way to learn an id the registry chose.
|
|
2352
|
+
* `fromBlock` is the block the registration landed in, not a guess. */
|
|
2353
|
+
async lastRegistered(owner, fromBlock) {
|
|
2354
|
+
const logs = await this.deps.publicClient.getLogs({
|
|
2355
|
+
address: this.deps.identity,
|
|
2356
|
+
event: {
|
|
2357
|
+
type: "event",
|
|
2358
|
+
name: "Registered",
|
|
2359
|
+
inputs: [
|
|
2360
|
+
{ name: "agentId", type: "uint256", indexed: true },
|
|
2361
|
+
{ name: "agentURI", type: "string", indexed: false },
|
|
2362
|
+
{ name: "owner", type: "address", indexed: true }
|
|
2363
|
+
]
|
|
2364
|
+
},
|
|
2365
|
+
args: { owner },
|
|
2366
|
+
fromBlock,
|
|
2367
|
+
toBlock: "latest"
|
|
2368
|
+
});
|
|
2369
|
+
const last = logs[logs.length - 1];
|
|
2370
|
+
return last ? last.args.agentId ?? null : null;
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2373
|
+
|
|
2374
|
+
// ../fixtures/src/serial.ts
|
|
2375
|
+
function serialiseByKey() {
|
|
2376
|
+
const tails = /* @__PURE__ */ new Map();
|
|
2377
|
+
return (key, work) => {
|
|
2378
|
+
const id = key.toLowerCase();
|
|
2379
|
+
const previous = tails.get(id) ?? Promise.resolve();
|
|
2380
|
+
const next = previous.then(work, work);
|
|
2381
|
+
tails.set(
|
|
2382
|
+
id,
|
|
2383
|
+
next.then(
|
|
2384
|
+
() => void 0,
|
|
2385
|
+
() => void 0
|
|
2386
|
+
)
|
|
2387
|
+
);
|
|
2388
|
+
return next;
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// ../daemon/src/gate.ts
|
|
2393
|
+
var SpawnRefused = class extends Error {
|
|
2394
|
+
broadcast = false;
|
|
2395
|
+
constructor(message, options) {
|
|
2396
|
+
super(message, options);
|
|
2397
|
+
this.name = "SpawnRefused";
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
var Gate = class {
|
|
2401
|
+
publicClient;
|
|
2402
|
+
wallets = /* @__PURE__ */ new Map();
|
|
2403
|
+
pending = /* @__PURE__ */ new Map();
|
|
2404
|
+
config;
|
|
2405
|
+
chain;
|
|
2406
|
+
/**
|
|
2407
|
+
* One send at a time per operator key.
|
|
2408
|
+
*
|
|
2409
|
+
* An agent's purchases are concurrent by design — that is what a tree of
|
|
2410
|
+
* them is for — and every draw for one node signs with one EOA. Two that
|
|
2411
|
+
* overlap between reading the pending nonce and sending ask for the same
|
|
2412
|
+
* one, and the second is refused for `nonce too low` after its simulation
|
|
2413
|
+
* said it was in bounds. Keyed by node, so two nodes still draw at once.
|
|
2414
|
+
*/
|
|
2415
|
+
inTurn = serialiseByKey();
|
|
2416
|
+
/**
|
|
2417
|
+
* The record. It borrows this gate's signer rather than holding a copy of a
|
|
2418
|
+
* key, so there is still exactly one place in the process where an operator
|
|
2419
|
+
* key lives.
|
|
2420
|
+
*/
|
|
2421
|
+
recorder;
|
|
2422
|
+
constructor(config2) {
|
|
2423
|
+
this.config = config2;
|
|
2424
|
+
const chain = defineChain({
|
|
2425
|
+
id: config2.chainId,
|
|
2426
|
+
name: `chain-${config2.chainId}`,
|
|
2427
|
+
nativeCurrency: { name: "USDC", symbol: "USDC", decimals: 18 },
|
|
2428
|
+
rpcUrls: { default: { http: [config2.rpcUrl] } }
|
|
2429
|
+
});
|
|
2430
|
+
this.chain = chain;
|
|
2431
|
+
this.publicClient = createPublicClient({
|
|
2432
|
+
chain,
|
|
2433
|
+
transport: http(config2.rpcUrl),
|
|
2434
|
+
pollingInterval: config2.pollMs
|
|
2435
|
+
});
|
|
2436
|
+
this.recorder = new Recorder(
|
|
2437
|
+
{ publicClient: this.publicClient, chain, record: config2.record, identity: config2.identity },
|
|
2438
|
+
(node2) => this.walletFor(node2)
|
|
2439
|
+
);
|
|
2440
|
+
for (const { node: node2, keyEnv } of config2.keys) {
|
|
2441
|
+
const secret = process.env[keyEnv];
|
|
2442
|
+
if (!secret) throw new Error(`${keyEnv} is not set`);
|
|
2443
|
+
this.wallets.set(
|
|
2444
|
+
node2.toLowerCase(),
|
|
2445
|
+
createWalletClient({
|
|
2446
|
+
account: privateKeyToAccount(secret),
|
|
2447
|
+
chain,
|
|
2448
|
+
transport: http(config2.rpcUrl),
|
|
2449
|
+
pollingInterval: config2.pollMs
|
|
2450
|
+
})
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
/** The reader the settler shares, so one process holds one connection to
|
|
2455
|
+
* the chain rather than two that can disagree about its head. */
|
|
2456
|
+
get publicClientForSettlement() {
|
|
2457
|
+
return this.publicClient;
|
|
2458
|
+
}
|
|
2459
|
+
/** The nodes this daemon can act for. It holds no other key. */
|
|
2460
|
+
nodes() {
|
|
2461
|
+
return [...this.wallets.keys()];
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Take custody of a key for a node spawned at run time.
|
|
2465
|
+
*
|
|
2466
|
+
* A sub-agent created mid-run needs an operator, and the claim is that the
|
|
2467
|
+
* daemon holds it and the agent holds none. The key stays in this process's
|
|
2468
|
+
* memory: it is never returned to a caller, never logged, and never written
|
|
2469
|
+
* to disk. A key an agent could read is a key it holds.
|
|
2470
|
+
*
|
|
2471
|
+
* The node is bound after the fact, once the registry has assigned it, which
|
|
2472
|
+
* is why this takes the key and `bindOperator` takes the node.
|
|
2473
|
+
*/
|
|
2474
|
+
addOperator(secret) {
|
|
2475
|
+
const account = privateKeyToAccount(secret);
|
|
2476
|
+
this.pending.set(account.address.toLowerCase(), secret);
|
|
2477
|
+
return account.address;
|
|
2478
|
+
}
|
|
2479
|
+
/** Attach a previously added key to the node the registry gave it. */
|
|
2480
|
+
bindOperator(node2, operator) {
|
|
2481
|
+
const secret = this.pending.get(operator.toLowerCase());
|
|
2482
|
+
if (!secret) throw new Error(`no key held for operator ${operator}`);
|
|
2483
|
+
this.wallets.set(
|
|
2484
|
+
node2.toLowerCase(),
|
|
2485
|
+
createWalletClient({
|
|
2486
|
+
account: privateKeyToAccount(secret),
|
|
2487
|
+
chain: this.chain,
|
|
2488
|
+
transport: http(this.config.rpcUrl)
|
|
2489
|
+
})
|
|
2490
|
+
);
|
|
2491
|
+
this.pending.delete(operator.toLowerCase());
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Lend this node's signer.
|
|
2495
|
+
*
|
|
2496
|
+
* The settler needs to sign a burn intent and an EIP-3009 authorisation as
|
|
2497
|
+
* the operator, and borrowing the signer keeps the key in one place rather
|
|
2498
|
+
* than handing a copy of it out — the same arrangement the recorder has.
|
|
2499
|
+
*/
|
|
2500
|
+
signerFor(node2) {
|
|
2501
|
+
return this.walletFor(node2);
|
|
2502
|
+
}
|
|
2503
|
+
walletFor(node2) {
|
|
2504
|
+
const wallet = this.wallets.get(node2.toLowerCase());
|
|
2505
|
+
if (!wallet) throw new Error(`this daemon holds no key for ${node2}`);
|
|
2506
|
+
return wallet;
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* What a draw would do, without doing it. Free, and the honest thing to show
|
|
2510
|
+
* an operator before they ask for money.
|
|
2511
|
+
*/
|
|
2512
|
+
async evaluate(node2, counterparty, amount) {
|
|
2513
|
+
const [reason, breachedAt] = await this.publicClient.readContract({
|
|
2514
|
+
address: this.config.vault,
|
|
2515
|
+
abi: TreeVaultAbi,
|
|
2516
|
+
functionName: "evaluate",
|
|
2517
|
+
args: [node2, counterparty, amount]
|
|
2518
|
+
});
|
|
2519
|
+
return {
|
|
2520
|
+
released: reason === 0,
|
|
2521
|
+
reason: REASONS[reason] ?? UNRECOGNISED,
|
|
2522
|
+
breachedAt: reason === 0 ? void 0 : breachedAt
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
/** What this node may still draw in total, and which node is the reason. */
|
|
2526
|
+
async headroom(node2) {
|
|
2527
|
+
const [available, boundBy] = await this.publicClient.readContract({
|
|
2528
|
+
address: this.config.vault,
|
|
2529
|
+
abi: TreeVaultAbi,
|
|
2530
|
+
functionName: "headroom",
|
|
2531
|
+
args: [node2]
|
|
2532
|
+
});
|
|
2533
|
+
return { available, boundBy };
|
|
2534
|
+
}
|
|
2535
|
+
async refusalCount() {
|
|
2536
|
+
return await this.publicClient.readContract({
|
|
2537
|
+
address: this.config.vault,
|
|
2538
|
+
abi: TreeVaultAbi,
|
|
2539
|
+
functionName: "refusalCount"
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
/** One refusal as the vault holds it, including whether the owner released it. */
|
|
2543
|
+
async refusal(id) {
|
|
2544
|
+
return await this.publicClient.readContract({
|
|
2545
|
+
address: this.config.vault,
|
|
2546
|
+
abi: TreeVaultAbi,
|
|
2547
|
+
functionName: "refusal",
|
|
2548
|
+
args: [id]
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
/** False when this node or any ancestor has been revoked. */
|
|
2552
|
+
async isLive(node2) {
|
|
2553
|
+
return await this.publicClient.readContract({
|
|
2554
|
+
address: this.config.registry,
|
|
2555
|
+
abi: MandateRegistryAbi,
|
|
2556
|
+
functionName: "isLive",
|
|
2557
|
+
args: [node2]
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
async mandate(node2) {
|
|
2561
|
+
return this.publicClient.readContract({
|
|
2562
|
+
address: this.config.registry,
|
|
2563
|
+
abi: MandateRegistryAbi,
|
|
2564
|
+
functionName: "mandate",
|
|
2565
|
+
args: [node2]
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Register a child mandate under a node this daemon holds the key for.
|
|
2570
|
+
*
|
|
2571
|
+
* No owner signature: spawns happen in seconds while the owner sleeps. The
|
|
2572
|
+
* contract refuses a child wider than its parent no matter who asks, so the
|
|
2573
|
+
* safety is structural rather than procedural. The narrowing is not checked
|
|
2574
|
+
* here on purpose — a second copy of that rule is a second place for it to
|
|
2575
|
+
* be wrong.
|
|
2576
|
+
*/
|
|
2577
|
+
async spawn(parent, params) {
|
|
2578
|
+
return this.inTurn(parent, () => this.spawnInTurn(parent, params));
|
|
2579
|
+
}
|
|
2580
|
+
async spawnInTurn(parent, params) {
|
|
2581
|
+
const wallet = this.walletFor(parent);
|
|
2582
|
+
const inherited = await this.mandate(parent);
|
|
2583
|
+
let request;
|
|
2584
|
+
try {
|
|
2585
|
+
({ request } = await this.publicClient.simulateContract({
|
|
2586
|
+
address: this.config.registry,
|
|
2587
|
+
abi: MandateRegistryAbi,
|
|
2588
|
+
functionName: "spawn",
|
|
2589
|
+
args: [
|
|
2590
|
+
parent,
|
|
2591
|
+
{
|
|
2592
|
+
operator: params.operator,
|
|
2593
|
+
budget6: params.budget6,
|
|
2594
|
+
lifetimeCap6: params.lifetimeCap6 ?? inherited.lifetimeCap6,
|
|
2595
|
+
windowSeconds: inherited.windowSeconds,
|
|
2596
|
+
trancheCap6: params.trancheCap6,
|
|
2597
|
+
concentrationBps: params.concentrationBps,
|
|
2598
|
+
maxDepth: inherited.maxDepth
|
|
2599
|
+
}
|
|
2600
|
+
],
|
|
2601
|
+
account: wallet.account
|
|
2602
|
+
}));
|
|
2603
|
+
} catch (error) {
|
|
2604
|
+
throw new SpawnRefused(error.message, { cause: error });
|
|
2605
|
+
}
|
|
2606
|
+
const txHash = await wallet.writeContract(request);
|
|
2607
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
2608
|
+
for (const log of receipt.logs) {
|
|
2609
|
+
if (log.address.toLowerCase() !== this.config.registry.toLowerCase()) continue;
|
|
2610
|
+
try {
|
|
2611
|
+
const event = decodeEventLog({ abi: MandateRegistryAbi, data: log.data, topics: log.topics });
|
|
2612
|
+
if (event.eventName === "MandateSpawned") {
|
|
2613
|
+
const node2 = event.args.node;
|
|
2614
|
+
if (this.pending.has(params.operator.toLowerCase())) {
|
|
2615
|
+
this.bindOperator(node2, params.operator);
|
|
2616
|
+
const enrolled = await this.enrol(node2) !== null;
|
|
2617
|
+
if (params.purpose) {
|
|
2618
|
+
return {
|
|
2619
|
+
node: node2,
|
|
2620
|
+
txHash,
|
|
2621
|
+
enrolled,
|
|
2622
|
+
purposePublished: enrolled ? await this.describe(node2, params.purpose) : false
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
return { node: node2, txHash, enrolled };
|
|
2626
|
+
}
|
|
2627
|
+
return { node: node2, txHash, ...params.purpose ? { purposePublished: false } : {} };
|
|
2628
|
+
}
|
|
2629
|
+
} catch {
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
throw new Error(`spawn ${txHash} emitted no MandateSpawned`);
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Ask for a tranche, sized to the purchase, declaring who it is for.
|
|
2637
|
+
*
|
|
2638
|
+
* A refusal is a returned value rather than a revert, so this resolves in
|
|
2639
|
+
* both cases and the caller reads `released`. It never throws to mean "no".
|
|
2640
|
+
*/
|
|
2641
|
+
async draw(node2, counterparty, amount) {
|
|
2642
|
+
return this.inTurn(node2, () => this.drawInTurn(node2, counterparty, amount));
|
|
2643
|
+
}
|
|
2644
|
+
async drawInTurn(node2, counterparty, amount) {
|
|
2645
|
+
const wallet = this.walletFor(node2);
|
|
2646
|
+
const { request, result } = await this.publicClient.simulateContract({
|
|
2647
|
+
address: this.config.vault,
|
|
2648
|
+
abi: TreeVaultAbi,
|
|
2649
|
+
functionName: "draw",
|
|
2650
|
+
args: [node2, counterparty, amount],
|
|
2651
|
+
account: wallet.account
|
|
2652
|
+
});
|
|
2653
|
+
const [wouldRelease] = result;
|
|
2654
|
+
const txHash = await wallet.writeContract(request);
|
|
2655
|
+
const outcome = { ...await this.outcomeFrom(txHash), txHash };
|
|
2656
|
+
if (!wouldRelease) {
|
|
2657
|
+
await this.publish(node2, outcome);
|
|
2658
|
+
}
|
|
2659
|
+
return outcome;
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Write a node's stated purpose onto its identity. Best-effort, like `enrol`:
|
|
2663
|
+
* a purpose that fails to publish costs a description and no control.
|
|
2664
|
+
*/
|
|
2665
|
+
/**
|
|
2666
|
+
* Publish the purposes this key file remembers and the chain has not.
|
|
2667
|
+
*
|
|
2668
|
+
* A child spawned by an operator with no gas is enrolled by nobody, so its
|
|
2669
|
+
* purpose is written to the key file and never to its identity — and
|
|
2670
|
+
* `describe` only ever ran at spawn, so funding that operator afterwards
|
|
2671
|
+
* healed nothing. The name existed in a file on one laptop and the agent
|
|
2672
|
+
* stayed anonymous everywhere else.
|
|
2673
|
+
*
|
|
2674
|
+
* Called at start, after enrolment, so a restart is the whole recovery
|
|
2675
|
+
* rather than half of it. It reads before it writes: a node whose identity
|
|
2676
|
+
* already carries its purpose costs one call and no transaction.
|
|
2677
|
+
*/
|
|
2678
|
+
async describeRemembered(remembered) {
|
|
2679
|
+
if (!this.recorder.enabled || remembered.size === 0) return 0;
|
|
2680
|
+
let published = 0;
|
|
2681
|
+
for (const node2 of this.nodes()) {
|
|
2682
|
+
const purpose = remembered.get(node2.toLowerCase());
|
|
2683
|
+
if (!purpose) continue;
|
|
2684
|
+
try {
|
|
2685
|
+
const already = await this.recorder.statedPurpose(node2);
|
|
2686
|
+
if (already === null || already === purpose) continue;
|
|
2687
|
+
if (await this.describe(node2, purpose)) published += 1;
|
|
2688
|
+
} catch (error) {
|
|
2689
|
+
console.error(`${node2}'s purpose stays unpublished: ${error.message}`);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
return published;
|
|
2693
|
+
}
|
|
2694
|
+
async describe(node2, purpose) {
|
|
2695
|
+
if (!this.recorder.enabled) return false;
|
|
2696
|
+
try {
|
|
2697
|
+
return await this.recorder.describe(node2, purpose) !== null;
|
|
2698
|
+
} catch (error) {
|
|
2699
|
+
console.error(`${node2}'s purpose stays unpublished: ${error.message}`);
|
|
2700
|
+
return false;
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Give a node an ERC-8004 identity, if there is a seat to bind it to.
|
|
2705
|
+
*
|
|
2706
|
+
* Idempotent and best-effort. A node that fails to enrol still draws and is
|
|
2707
|
+
* still refused; what it loses is the ability to have those refusals
|
|
2708
|
+
* published, which is a loss of publicity and never a loss of control.
|
|
2709
|
+
*/
|
|
2710
|
+
async enrol(node2) {
|
|
2711
|
+
if (!this.recorder.enabled) return null;
|
|
2712
|
+
try {
|
|
2713
|
+
return await this.recorder.enrol(node2);
|
|
2714
|
+
} catch (error) {
|
|
2715
|
+
console.error(`${node2} has no identity, so its refusals stay unpublished: ${error.message}`);
|
|
2716
|
+
return null;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
/**
|
|
2720
|
+
* Put the refusal on the public record.
|
|
2721
|
+
*
|
|
2722
|
+
* Deliberately after the fact and deliberately unable to fail loudly: the
|
|
2723
|
+
* bound has already been enforced and the refusal is already on chain by the
|
|
2724
|
+
* time this runs, so a seat that is unreachable, unfunded or absent costs the
|
|
2725
|
+
* record its completeness and costs the enforcement nothing. There is no
|
|
2726
|
+
* filter — every refusal is published, because a daemon that chose which
|
|
2727
|
+
* ones to report would be writing an opinion.
|
|
2728
|
+
*/
|
|
2729
|
+
async publish(node2, outcome) {
|
|
2730
|
+
if (outcome.released || !outcome.refusalId || !this.recorder.enabled) return;
|
|
2731
|
+
try {
|
|
2732
|
+
await this.recorder.attest(node2, outcome.refusalId);
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
console.error(
|
|
2735
|
+
`refusal ${outcome.refusalId} is on chain but was not published: ${error.message}`
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
/** Read what actually happened out of the log, not out of the simulation. */
|
|
2740
|
+
async outcomeFrom(txHash) {
|
|
2741
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
2742
|
+
for (const log of receipt.logs) {
|
|
2743
|
+
if (log.address.toLowerCase() !== this.config.vault.toLowerCase()) continue;
|
|
2744
|
+
let event;
|
|
2745
|
+
try {
|
|
2746
|
+
event = decodeEventLog({ abi: TreeVaultAbi, data: log.data, topics: log.topics });
|
|
2747
|
+
} catch {
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
if (event.eventName === "Drawn") {
|
|
2751
|
+
const args = event.args;
|
|
2752
|
+
return { released: true, reason: "none", beneficiary: args.beneficiary, txHash };
|
|
2753
|
+
}
|
|
2754
|
+
if (event.eventName === "Refused") {
|
|
2755
|
+
const args = event.args;
|
|
2756
|
+
return {
|
|
2757
|
+
released: false,
|
|
2758
|
+
reason: REASONS[args.reason] ?? UNRECOGNISED,
|
|
2759
|
+
breachedAt: args.breachedAt,
|
|
2760
|
+
refusalId: args.refusalId,
|
|
2761
|
+
txHash
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
throw new Error(`draw ${txHash} emitted neither Drawn nor Refused`);
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
|
|
2769
|
+
// ../daemon/src/settle.ts
|
|
2770
|
+
import { parseAbi as parseAbi2 } from "viem";
|
|
2771
|
+
|
|
2772
|
+
// ../daemon/src/gateway.ts
|
|
2773
|
+
import { pad, toHex, parseAbi } from "viem";
|
|
2774
|
+
var GATEWAY_EIP712_DOMAIN = { name: "GatewayWallet", version: "1" };
|
|
2775
|
+
var BURN_INTENT_TYPES = {
|
|
2776
|
+
TransferSpec: [
|
|
2777
|
+
{ name: "version", type: "uint32" },
|
|
2778
|
+
{ name: "sourceDomain", type: "uint32" },
|
|
2779
|
+
{ name: "destinationDomain", type: "uint32" },
|
|
2780
|
+
{ name: "sourceContract", type: "bytes32" },
|
|
2781
|
+
{ name: "destinationContract", type: "bytes32" },
|
|
2782
|
+
{ name: "sourceToken", type: "bytes32" },
|
|
2783
|
+
{ name: "destinationToken", type: "bytes32" },
|
|
2784
|
+
{ name: "sourceDepositor", type: "bytes32" },
|
|
2785
|
+
{ name: "destinationRecipient", type: "bytes32" },
|
|
2786
|
+
{ name: "sourceSigner", type: "bytes32" },
|
|
2787
|
+
{ name: "destinationCaller", type: "bytes32" },
|
|
2788
|
+
{ name: "value", type: "uint256" },
|
|
2789
|
+
{ name: "salt", type: "bytes32" },
|
|
2790
|
+
{ name: "hookData", type: "bytes" }
|
|
2791
|
+
],
|
|
2792
|
+
BurnIntent: [
|
|
2793
|
+
{ name: "maxBlockHeight", type: "uint256" },
|
|
2794
|
+
{ name: "maxFee", type: "uint256" },
|
|
2795
|
+
{ name: "spec", type: "TransferSpec" }
|
|
2796
|
+
]
|
|
2797
|
+
};
|
|
2798
|
+
var GATEWAY_MINTER_ABI = parseAbi([
|
|
2799
|
+
"function gatewayMint(bytes attestationPayload, bytes signature)"
|
|
2800
|
+
]);
|
|
2801
|
+
var b32 = (address2) => pad(address2.toLowerCase(), { size: 32 });
|
|
2802
|
+
var ZERO = "0x0000000000000000000000000000000000000000";
|
|
2803
|
+
function buildBurnIntent(terms) {
|
|
2804
|
+
return {
|
|
2805
|
+
maxBlockHeight: terms.maxBlockHeight,
|
|
2806
|
+
maxFee: terms.maxFee,
|
|
2807
|
+
spec: {
|
|
2808
|
+
version: 1,
|
|
2809
|
+
sourceDomain: terms.domain,
|
|
2810
|
+
destinationDomain: terms.domain,
|
|
2811
|
+
sourceContract: b32(terms.wallet),
|
|
2812
|
+
destinationContract: b32(terms.minter),
|
|
2813
|
+
sourceToken: b32(terms.token),
|
|
2814
|
+
destinationToken: b32(terms.token),
|
|
2815
|
+
sourceDepositor: b32(terms.depositor),
|
|
2816
|
+
/* The recipient is the depositor, and that is the whole shape of this
|
|
2817
|
+
step: the tranche is brought back out of Gateway into the operator's
|
|
2818
|
+
own balance so an x402 `exact` payment — an EIP-3009 authorisation
|
|
2819
|
+
over that balance — can be signed against it. Naming the seller here
|
|
2820
|
+
would pay them Gateway-side and leave the seller's own 402 unanswered. */
|
|
2821
|
+
destinationRecipient: b32(terms.depositor),
|
|
2822
|
+
sourceSigner: b32(terms.depositor),
|
|
2823
|
+
/* Anyone may submit the mint. We do, immediately. */
|
|
2824
|
+
destinationCaller: b32(ZERO),
|
|
2825
|
+
value: terms.value,
|
|
2826
|
+
salt: terms.salt ?? toHex(crypto.getRandomValues(new Uint8Array(32))),
|
|
2827
|
+
hookData: "0x"
|
|
2828
|
+
}
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
var GatewayError = class extends Error {
|
|
2832
|
+
};
|
|
2833
|
+
var GatewayApi = class {
|
|
2834
|
+
base;
|
|
2835
|
+
fetchImpl;
|
|
2836
|
+
constructor(options = {}) {
|
|
2837
|
+
this.base = options.base ?? GATEWAY.api;
|
|
2838
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
2839
|
+
}
|
|
2840
|
+
/** The window a burn intent must expire inside, read per call because it
|
|
2841
|
+
* moves with the chain: an intent built against a stale height is refused
|
|
2842
|
+
* with `maxBlockHeight is too low`. */
|
|
2843
|
+
async expirationHeight(domain) {
|
|
2844
|
+
const res = await this.fetchImpl(`${this.base}/v1/info`);
|
|
2845
|
+
if (!res.ok) throw new GatewayError(`gateway /v1/info answered ${res.status}`);
|
|
2846
|
+
const body = await res.json();
|
|
2847
|
+
const found = body.domains.find((d) => d.domain === domain);
|
|
2848
|
+
if (!found) throw new GatewayError(`gateway does not serve domain ${domain}`);
|
|
2849
|
+
return BigInt(found.burnIntentExpirationHeight);
|
|
2850
|
+
}
|
|
2851
|
+
async balance(domain, depositor) {
|
|
2852
|
+
const res = await this.fetchImpl(`${this.base}/v1/balances`, {
|
|
2853
|
+
method: "POST",
|
|
2854
|
+
headers: { "content-type": "application/json" },
|
|
2855
|
+
body: JSON.stringify({ token: "USDC", sources: [{ domain, depositor }] })
|
|
2856
|
+
});
|
|
2857
|
+
if (!res.ok) throw new GatewayError(`gateway /v1/balances answered ${res.status}`);
|
|
2858
|
+
const body = await res.json();
|
|
2859
|
+
return body.balances?.[0]?.balance ?? "0";
|
|
2860
|
+
}
|
|
2861
|
+
async transfer(intent, signature) {
|
|
2862
|
+
const body = JSON.stringify(
|
|
2863
|
+
[{ burnIntent: intent, signature }],
|
|
2864
|
+
(_k, v) => typeof v === "bigint" ? v.toString() : v
|
|
2865
|
+
);
|
|
2866
|
+
const res = await this.fetchImpl(`${this.base}/v1/transfer`, {
|
|
2867
|
+
method: "POST",
|
|
2868
|
+
headers: { "content-type": "application/json" },
|
|
2869
|
+
body
|
|
2870
|
+
});
|
|
2871
|
+
const text2 = await res.text();
|
|
2872
|
+
if (!res.ok) {
|
|
2873
|
+
let message = text2;
|
|
2874
|
+
try {
|
|
2875
|
+
message = JSON.parse(text2).message ?? text2;
|
|
2876
|
+
} catch {
|
|
2877
|
+
}
|
|
2878
|
+
throw new GatewayError(`Circle refused the burn intent: ${message}`);
|
|
2879
|
+
}
|
|
2880
|
+
const parsed = JSON.parse(text2);
|
|
2881
|
+
return {
|
|
2882
|
+
attestation: parsed.attestation,
|
|
2883
|
+
signature: parsed.signature,
|
|
2884
|
+
transferId: parsed.transferId,
|
|
2885
|
+
feeTotal: parsed.fees?.total ?? "unstated"
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
async function mint(clients, minter, accepted) {
|
|
2890
|
+
const { request } = await clients.publicClient.simulateContract({
|
|
2891
|
+
address: minter,
|
|
2892
|
+
abi: GATEWAY_MINTER_ABI,
|
|
2893
|
+
functionName: "gatewayMint",
|
|
2894
|
+
args: [accepted.attestation, accepted.signature],
|
|
2895
|
+
account: clients.wallet.account
|
|
2896
|
+
});
|
|
2897
|
+
const hash = await clients.wallet.writeContract(request);
|
|
2898
|
+
await clients.publicClient.waitForTransactionReceipt({ hash });
|
|
2899
|
+
return hash;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
// ../daemon/src/settle.ts
|
|
2903
|
+
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
2904
|
+
TransferWithAuthorization: [
|
|
2905
|
+
{ name: "from", type: "address" },
|
|
2906
|
+
{ name: "to", type: "address" },
|
|
2907
|
+
{ name: "value", type: "uint256" },
|
|
2908
|
+
{ name: "validAfter", type: "uint256" },
|
|
2909
|
+
{ name: "validBefore", type: "uint256" },
|
|
2910
|
+
{ name: "nonce", type: "bytes32" }
|
|
2911
|
+
]
|
|
2912
|
+
};
|
|
2913
|
+
var SettlementError = class extends Error {
|
|
2914
|
+
};
|
|
2915
|
+
var ERC20 = parseAbi2(["function balanceOf(address account) view returns (uint256)"]);
|
|
2916
|
+
var CircleSettler = class {
|
|
2917
|
+
options;
|
|
2918
|
+
api;
|
|
2919
|
+
constructor(options) {
|
|
2920
|
+
this.options = options;
|
|
2921
|
+
this.api = options.api ?? new GatewayApi();
|
|
2922
|
+
}
|
|
2923
|
+
async settle(node2, payment) {
|
|
2924
|
+
const wallet = this.options.walletFor(node2);
|
|
2925
|
+
const operator = wallet.account.address;
|
|
2926
|
+
const token = tokenDomain(payment, this.options.chainId);
|
|
2927
|
+
if (payment.value <= GATEWAY.baseFee6) {
|
|
2928
|
+
throw new SettlementError(
|
|
2929
|
+
`this purchase is ${payment.value} base units and Circle's Gateway fee on Arc is ${GATEWAY.baseFee6} \u2014 a tranche at or below the fee cannot pay for its own release. The alternative rail, GatewayWallet.withdraw, has a ${GATEWAY.withdrawalDelaySeconds}-second delay. Neither settles a payment this small, and that is a fact about the rail.`
|
|
2930
|
+
);
|
|
2931
|
+
}
|
|
2932
|
+
const released = payment.value - GATEWAY.baseFee6;
|
|
2933
|
+
const debited = released + GATEWAY.baseFee6;
|
|
2934
|
+
await this.awaitBalance(operator, debited);
|
|
2935
|
+
const intent = buildBurnIntent({
|
|
2936
|
+
depositor: operator,
|
|
2937
|
+
wallet: GATEWAY.wallet,
|
|
2938
|
+
minter: GATEWAY.minter,
|
|
2939
|
+
token: payment.asset,
|
|
2940
|
+
domain: GATEWAY.domain,
|
|
2941
|
+
value: released,
|
|
2942
|
+
maxFee: GATEWAY.baseFee6,
|
|
2943
|
+
maxBlockHeight: await this.api.expirationHeight(GATEWAY.domain) + (this.options.heightMargin ?? 5000n)
|
|
2944
|
+
});
|
|
2945
|
+
const signature = await wallet.signTypedData({
|
|
2946
|
+
account: wallet.account,
|
|
2947
|
+
domain: GATEWAY_EIP712_DOMAIN,
|
|
2948
|
+
types: BURN_INTENT_TYPES,
|
|
2949
|
+
primaryType: "BurnIntent",
|
|
2950
|
+
message: intent
|
|
2951
|
+
});
|
|
2952
|
+
const accepted = await this.api.transfer(intent, signature);
|
|
2953
|
+
const submit = this.options.mint ?? ((w, minter, a) => mint({ publicClient: this.options.publicClient, wallet: w }, minter, a));
|
|
2954
|
+
const txHash = await submit(wallet, GATEWAY.minter, accepted);
|
|
2955
|
+
await this.assertCanPay(payment, operator);
|
|
2956
|
+
const proof = await this.authorise(wallet, operator, payment, token);
|
|
2957
|
+
return { proof, txHash };
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Wait until Circle's own view of the balance covers what the intent will
|
|
2961
|
+
* debit — its value and the fee together, which is the tranche.
|
|
2962
|
+
*
|
|
2963
|
+
* `needed` is passed in rather than derived here. Deriving it twice is how
|
|
2964
|
+
* this came to wait for a balance a draw does not produce.
|
|
2965
|
+
*/
|
|
2966
|
+
async awaitBalance(operator, needed) {
|
|
2967
|
+
const deadline = (this.options.now?.() ?? Date.now()) + (this.options.balanceWaitMs ?? 6e4);
|
|
2968
|
+
let seen = "0";
|
|
2969
|
+
for (; ; ) {
|
|
2970
|
+
seen = await this.api.balance(GATEWAY.domain, operator);
|
|
2971
|
+
if (baseUnits(seen) >= needed) return;
|
|
2972
|
+
if ((this.options.now?.() ?? Date.now()) >= deadline) {
|
|
2973
|
+
throw new SettlementError(
|
|
2974
|
+
`Circle still reports ${seen} for ${operator} after waiting, and this intent debits ${needed} base units \u2014 its value and the fee charged on top of it. The draw has already happened, so the window is debited and the money is in the Gateway balance.`
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
await new Promise((done) => setTimeout(done, this.options.balancePollMs ?? 3e3));
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Does the operator hold what it is about to authorise?
|
|
2982
|
+
*
|
|
2983
|
+
* An EIP-3009 authorisation is checked against the balance at the moment the
|
|
2984
|
+
* seller submits it, not the moment it is signed, so this cannot be a
|
|
2985
|
+
* guarantee. It is the difference between a failure the daemon can name and
|
|
2986
|
+
* one that surfaces as a seller's revert against a signature that verified.
|
|
2987
|
+
*/
|
|
2988
|
+
async assertCanPay(payment, operator) {
|
|
2989
|
+
const read = this.options.balanceOf ?? ((token, holder) => this.options.publicClient.readContract({
|
|
2990
|
+
address: token,
|
|
2991
|
+
abi: ERC20,
|
|
2992
|
+
functionName: "balanceOf",
|
|
2993
|
+
args: [holder]
|
|
2994
|
+
}));
|
|
2995
|
+
const held = await read(payment.asset, operator);
|
|
2996
|
+
if (held >= payment.value) return;
|
|
2997
|
+
throw new SettlementError(
|
|
2998
|
+
`${operator} holds ${held} base units and this purchase authorises ${payment.value}. The burn returned the price less Circle's ${GATEWAY.baseFee6} fee, so an operator needs that difference in its own float \u2014 the same place its gas comes from, and outside the mandate either way. The draw has already happened, so the window is debited.`
|
|
2999
|
+
);
|
|
3000
|
+
}
|
|
3001
|
+
/** The x402 `exact` payload: an EIP-3009 authorisation the seller submits. */
|
|
3002
|
+
async authorise(wallet, operator, payment, token) {
|
|
3003
|
+
const now = Math.floor((this.options.now?.() ?? Date.now()) / 1e3);
|
|
3004
|
+
const authorization = {
|
|
3005
|
+
from: operator,
|
|
3006
|
+
to: payment.to,
|
|
3007
|
+
value: payment.value,
|
|
3008
|
+
validAfter: 0n,
|
|
3009
|
+
validBefore: BigInt(now + (payment.maxTimeoutSeconds ?? 300)),
|
|
3010
|
+
nonce: this.options.nonce?.() ?? randomNonce()
|
|
3011
|
+
};
|
|
3012
|
+
const signature = await wallet.signTypedData({
|
|
3013
|
+
account: wallet.account,
|
|
3014
|
+
domain: token,
|
|
3015
|
+
types: TRANSFER_WITH_AUTHORIZATION_TYPES,
|
|
3016
|
+
primaryType: "TransferWithAuthorization",
|
|
3017
|
+
message: authorization
|
|
3018
|
+
});
|
|
3019
|
+
return Buffer.from(
|
|
3020
|
+
JSON.stringify({
|
|
3021
|
+
x402Version: 2,
|
|
3022
|
+
scheme: "exact",
|
|
3023
|
+
network: payment.network,
|
|
3024
|
+
asset: payment.asset,
|
|
3025
|
+
payload: {
|
|
3026
|
+
signature,
|
|
3027
|
+
authorization: {
|
|
3028
|
+
...authorization,
|
|
3029
|
+
value: authorization.value.toString(),
|
|
3030
|
+
validAfter: authorization.validAfter.toString(),
|
|
3031
|
+
validBefore: authorization.validBefore.toString()
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}),
|
|
3035
|
+
"utf8"
|
|
3036
|
+
).toString("base64");
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
function tokenDomain(payment, chainId) {
|
|
3040
|
+
const extra = payment.extra ?? {};
|
|
3041
|
+
const name = typeof extra.name === "string" ? extra.name : void 0;
|
|
3042
|
+
const version = typeof extra.version === "string" ? extra.version : void 0;
|
|
3043
|
+
if (!name || !version) {
|
|
3044
|
+
throw new SettlementError(
|
|
3045
|
+
"the seller's offer carries no EIP-712 name and version in `extra`, and a payer that guesses them signs an authorisation the token will not verify"
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
return { name, version, chainId, verifyingContract: payment.asset };
|
|
3049
|
+
}
|
|
3050
|
+
function baseUnits(decimal) {
|
|
3051
|
+
const [whole = "0", fraction = ""] = decimal.trim().split(".");
|
|
3052
|
+
const padded = (fraction + "000000").slice(0, 6);
|
|
3053
|
+
const sign = whole.startsWith("-") ? -1n : 1n;
|
|
3054
|
+
return sign * (BigInt(whole.replace("-", "") || "0") * 1000000n + BigInt(padded || "0"));
|
|
3055
|
+
}
|
|
3056
|
+
function randomNonce() {
|
|
3057
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
3058
|
+
return `0x${Buffer.from(bytes).toString("hex")}`;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
// ../daemon/src/keyfile.ts
|
|
3062
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3063
|
+
import { dirname } from "node:path";
|
|
3064
|
+
function labelForOperator(operator) {
|
|
3065
|
+
return `CHILD_${operator.slice(2, 10).toUpperCase()}`;
|
|
3066
|
+
}
|
|
3067
|
+
function cleanPurpose(raw) {
|
|
3068
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
3069
|
+
if (typeof raw !== "string") throw new Error("purpose must be a string");
|
|
3070
|
+
const purpose = raw.trim();
|
|
3071
|
+
if (purpose === "") return void 0;
|
|
3072
|
+
if (/[\u0000-\u001f\u007f]/.test(purpose)) throw new Error("purpose must be a single line");
|
|
3073
|
+
if (purpose.length > ERC8004.purposeMaxLength) {
|
|
3074
|
+
throw new Error(`purpose is ${purpose.length} characters; the most is ${ERC8004.purposeMaxLength}`);
|
|
3075
|
+
}
|
|
3076
|
+
return purpose;
|
|
3077
|
+
}
|
|
3078
|
+
function keyFileAt(path) {
|
|
3079
|
+
return {
|
|
3080
|
+
path,
|
|
3081
|
+
remember(secret, operator, purpose) {
|
|
3082
|
+
const label = labelForOperator(operator);
|
|
3083
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
3084
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
3085
|
+
if (new RegExp(`^CORDON_KEY_${label}=`, "m").test(existing)) {
|
|
3086
|
+
throw new Error(`${path} already holds a key labelled ${label}`);
|
|
3087
|
+
}
|
|
3088
|
+
const lead = existing !== "" && !existing.endsWith("\n") ? "\n" : "";
|
|
3089
|
+
appendFileSync(
|
|
3090
|
+
path,
|
|
3091
|
+
`${lead}
|
|
3092
|
+
# Spawned ${(/* @__PURE__ */ new Date()).toISOString()} for operator ${operator}.
|
|
3093
|
+
` + (purpose ? `# Purpose: ${cleanPurpose(purpose)}
|
|
3094
|
+
` : "") + `# Private key: never commit this file, never paste its contents.
|
|
3095
|
+
CORDON_KEY_${label}=${secret}
|
|
3096
|
+
CORDON_NODE_${label}=
|
|
3097
|
+
`,
|
|
3098
|
+
{ mode: 384 }
|
|
3099
|
+
);
|
|
3100
|
+
chmodSync(path, 384);
|
|
3101
|
+
return label;
|
|
3102
|
+
},
|
|
3103
|
+
forget(label) {
|
|
3104
|
+
const current = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
3105
|
+
const block = new RegExp(
|
|
3106
|
+
`
|
|
3107
|
+
*^# Spawned [^
|
|
3108
|
+
]*for operator [^
|
|
3109
|
+
]*
|
|
3110
|
+
(?:^#[^
|
|
3111
|
+
]*
|
|
3112
|
+
)*^CORDON_KEY_${label}=[^
|
|
3113
|
+
]*
|
|
3114
|
+
^CORDON_NODE_${label}=$
|
|
3115
|
+
?`,
|
|
3116
|
+
"m"
|
|
3117
|
+
);
|
|
3118
|
+
if (!block.test(current)) return;
|
|
3119
|
+
const staging = `${path}.tmp-${process.pid}`;
|
|
3120
|
+
writeFileSync(staging, current.replace(block, "\n"), { mode: 384 });
|
|
3121
|
+
chmodSync(staging, 384);
|
|
3122
|
+
renameSync(staging, path);
|
|
3123
|
+
},
|
|
3124
|
+
purposes() {
|
|
3125
|
+
const found = /* @__PURE__ */ new Map();
|
|
3126
|
+
if (!existsSync(path)) return found;
|
|
3127
|
+
const lines = readFileSync(path, "utf8").split("\n");
|
|
3128
|
+
let said;
|
|
3129
|
+
for (const line of lines) {
|
|
3130
|
+
const purpose = /^# Purpose: (.+)$/.exec(line);
|
|
3131
|
+
if (purpose) {
|
|
3132
|
+
said = purpose[1].trim();
|
|
3133
|
+
continue;
|
|
3134
|
+
}
|
|
3135
|
+
const node2 = /^CORDON_NODE_[A-Za-z0-9_]+=(0x[0-9a-fA-F]{64})$/.exec(line);
|
|
3136
|
+
if (node2) {
|
|
3137
|
+
if (said) found.set(node2[1].toLowerCase(), said);
|
|
3138
|
+
said = void 0;
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
if (!/^CORDON_KEY_/.test(line) && line.trim() !== "" && !line.startsWith("#")) said = void 0;
|
|
3142
|
+
}
|
|
3143
|
+
return found;
|
|
3144
|
+
},
|
|
3145
|
+
bind(label, node2) {
|
|
3146
|
+
const current = readFileSync(path, "utf8");
|
|
3147
|
+
const empty = new RegExp(`^CORDON_NODE_${label}=$`, "m");
|
|
3148
|
+
if (!empty.test(current)) {
|
|
3149
|
+
throw new Error(`${path} has no empty CORDON_NODE_${label} to fill`);
|
|
3150
|
+
}
|
|
3151
|
+
const next = current.replace(empty, `CORDON_NODE_${label}=${node2}`);
|
|
3152
|
+
const staging = `${path}.tmp-${process.pid}`;
|
|
3153
|
+
writeFileSync(staging, next, { mode: 384 });
|
|
3154
|
+
chmodSync(staging, 384);
|
|
3155
|
+
renameSync(staging, path);
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
// ../daemon/src/released.ts
|
|
3161
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3162
|
+
import { dirname as dirname2 } from "node:path";
|
|
3163
|
+
var ChainReleases = class {
|
|
3164
|
+
options;
|
|
3165
|
+
spent;
|
|
3166
|
+
constructor(options) {
|
|
3167
|
+
this.options = options;
|
|
3168
|
+
this.spent = existsSync2(options.file) ? new Set(JSON.parse(readFileSync2(options.file, "utf8")).map(String)) : /* @__PURE__ */ new Set();
|
|
3169
|
+
}
|
|
3170
|
+
async claim(node2, payTo, amount6) {
|
|
3171
|
+
const count = await this.options.count();
|
|
3172
|
+
const scan = BigInt(this.options.scan ?? 256);
|
|
3173
|
+
const floor = count > scan ? count - scan : 0n;
|
|
3174
|
+
for (let id = count; id > floor; id--) {
|
|
3175
|
+
const key = id.toString();
|
|
3176
|
+
if (this.spent.has(key)) continue;
|
|
3177
|
+
const row = await this.options.read(id);
|
|
3178
|
+
if (!row.released) continue;
|
|
3179
|
+
if (row.node.toLowerCase() !== node2.toLowerCase()) continue;
|
|
3180
|
+
if (row.counterparty.toLowerCase() !== payTo.toLowerCase()) continue;
|
|
3181
|
+
if (row.amount6 !== amount6) continue;
|
|
3182
|
+
if (!await this.options.live(node2)) return null;
|
|
3183
|
+
this.spent.add(key);
|
|
3184
|
+
this.save();
|
|
3185
|
+
return id;
|
|
3186
|
+
}
|
|
3187
|
+
return null;
|
|
3188
|
+
}
|
|
3189
|
+
unclaim(refusalId) {
|
|
3190
|
+
if (this.spent.delete(refusalId.toString())) this.save();
|
|
3191
|
+
}
|
|
3192
|
+
save() {
|
|
3193
|
+
mkdirSync2(dirname2(this.options.file), { recursive: true });
|
|
3194
|
+
writeFileSync2(this.options.file, JSON.stringify([...this.spent], null, 2) + "\n");
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
|
|
3198
|
+
// src/main.ts
|
|
3199
|
+
import { dirname as dirname3, join as join2 } from "node:path";
|
|
3200
|
+
|
|
3201
|
+
// src/server.ts
|
|
3202
|
+
import { z } from "zod";
|
|
3203
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3204
|
+
import { generatePrivateKey } from "viem/accounts";
|
|
3205
|
+
|
|
3206
|
+
// ../daemon/src/challenge.ts
|
|
3207
|
+
var ChallengeError = class extends Error {
|
|
3208
|
+
};
|
|
3209
|
+
var ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
3210
|
+
var CAIP2 = /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/;
|
|
3211
|
+
function parseChallenge(status, body) {
|
|
3212
|
+
if (status !== 402) return null;
|
|
3213
|
+
if (typeof body !== "object" || body === null) {
|
|
3214
|
+
throw new ChallengeError("402 with no JSON body");
|
|
3215
|
+
}
|
|
3216
|
+
const raw = body;
|
|
3217
|
+
const version = raw.x402Version;
|
|
3218
|
+
if (typeof version !== "number") {
|
|
3219
|
+
throw new ChallengeError("402 body has no x402Version");
|
|
3220
|
+
}
|
|
3221
|
+
const accepts = raw.accepts;
|
|
3222
|
+
if (!Array.isArray(accepts) || accepts.length === 0) {
|
|
3223
|
+
throw new ChallengeError("402 body offers no way to pay");
|
|
3224
|
+
}
|
|
3225
|
+
return { x402Version: version, offers: accepts.map(parseOffer) };
|
|
3226
|
+
}
|
|
3227
|
+
function parseOffer(input, index) {
|
|
3228
|
+
if (typeof input !== "object" || input === null) {
|
|
3229
|
+
throw new ChallengeError(`accepts[${index}] is not an object`);
|
|
3230
|
+
}
|
|
3231
|
+
const o = input;
|
|
3232
|
+
const scheme = str(o.scheme, `accepts[${index}].scheme`);
|
|
3233
|
+
const network = str(o.network, `accepts[${index}].network`);
|
|
3234
|
+
if (!CAIP2.test(network)) {
|
|
3235
|
+
throw new ChallengeError(`accepts[${index}].network is not a CAIP-2 id: ${network}`);
|
|
3236
|
+
}
|
|
3237
|
+
const payTo = str(o.payTo, `accepts[${index}].payTo`);
|
|
3238
|
+
const asset = str(o.asset, `accepts[${index}].asset`);
|
|
3239
|
+
const amountRaw = o.amount;
|
|
3240
|
+
if (typeof amountRaw !== "string" || !/^\d+$/.test(amountRaw)) {
|
|
3241
|
+
throw new ChallengeError(`accepts[${index}].amount is not a base-unit string`);
|
|
3242
|
+
}
|
|
3243
|
+
const amount = BigInt(amountRaw);
|
|
3244
|
+
if (amount <= 0n) {
|
|
3245
|
+
throw new ChallengeError(`accepts[${index}].amount is not positive`);
|
|
3246
|
+
}
|
|
3247
|
+
return {
|
|
3248
|
+
scheme,
|
|
3249
|
+
network,
|
|
3250
|
+
asset,
|
|
3251
|
+
payTo,
|
|
3252
|
+
amount,
|
|
3253
|
+
maxTimeoutSeconds: typeof o.maxTimeoutSeconds === "number" ? o.maxTimeoutSeconds : void 0,
|
|
3254
|
+
extra: typeof o.extra === "object" && o.extra !== null ? o.extra : void 0
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
function str(v, where) {
|
|
3258
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
3259
|
+
throw new ChallengeError(`${where} is missing`);
|
|
3260
|
+
}
|
|
3261
|
+
return v;
|
|
3262
|
+
}
|
|
3263
|
+
function selectOffer(challenge, ok) {
|
|
3264
|
+
const scheme = ok.scheme ?? "exact";
|
|
3265
|
+
const networks = new Set(ok.networks);
|
|
3266
|
+
const assets = new Set(ok.assets.map((a) => a.toLowerCase()));
|
|
3267
|
+
const match = challenge.offers.find(
|
|
3268
|
+
(o) => o.scheme === scheme && networks.has(o.network) && assets.has(o.asset.toLowerCase())
|
|
3269
|
+
);
|
|
3270
|
+
if (!match) {
|
|
3271
|
+
throw new ChallengeError(
|
|
3272
|
+
`no offer this daemon can settle: seller accepts ${challenge.offers.map((o) => `${o.scheme}/${o.network}`).join(", ")}`
|
|
3273
|
+
);
|
|
3274
|
+
}
|
|
3275
|
+
if (match.network.startsWith("eip155:") && !ADDRESS.test(match.payTo)) {
|
|
3276
|
+
throw new ChallengeError(`payTo is not an address: ${match.payTo}`);
|
|
3277
|
+
}
|
|
3278
|
+
return match;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
// ../daemon/src/egress.ts
|
|
3282
|
+
import { lookup } from "node:dns/promises";
|
|
3283
|
+
import { isIP } from "node:net";
|
|
3284
|
+
var EgressError = class extends Error {
|
|
3285
|
+
};
|
|
3286
|
+
function isPrivateV4(address2) {
|
|
3287
|
+
const [a = 0, b = 0] = address2.split(".").map(Number);
|
|
3288
|
+
if (a === 10) return true;
|
|
3289
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
3290
|
+
if (a === 192 && b === 168) return true;
|
|
3291
|
+
if (a === 127) return true;
|
|
3292
|
+
if (a === 0) return true;
|
|
3293
|
+
if (a === 169 && b === 254) return true;
|
|
3294
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
3295
|
+
if (a >= 224) return true;
|
|
3296
|
+
return false;
|
|
3297
|
+
}
|
|
3298
|
+
function isPrivateV6(address2) {
|
|
3299
|
+
const plain = address2.toLowerCase().split("%")[0];
|
|
3300
|
+
if (plain === "::1" || plain === "::") return true;
|
|
3301
|
+
if (plain.startsWith("fc") || plain.startsWith("fd")) return true;
|
|
3302
|
+
if (plain.startsWith("fe8") || plain.startsWith("fe9")) return true;
|
|
3303
|
+
if (plain.startsWith("fea") || plain.startsWith("feb")) return true;
|
|
3304
|
+
if (plain.startsWith("ff")) return true;
|
|
3305
|
+
const mapped = plain.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
3306
|
+
if (mapped) return isPrivateV4(mapped[1]);
|
|
3307
|
+
return false;
|
|
3308
|
+
}
|
|
3309
|
+
function isPrivateAddress(address2) {
|
|
3310
|
+
const family = isIP(address2);
|
|
3311
|
+
if (family === 4) return isPrivateV4(address2);
|
|
3312
|
+
if (family === 6) return isPrivateV6(address2);
|
|
3313
|
+
return true;
|
|
3314
|
+
}
|
|
3315
|
+
var resolveAll = async (hostname) => {
|
|
3316
|
+
const answers = await lookup(hostname, { all: true, verbatim: true });
|
|
3317
|
+
return answers.map((answer) => answer.address);
|
|
3318
|
+
};
|
|
3319
|
+
async function assertPublicUrl(raw, options = {}) {
|
|
3320
|
+
let url;
|
|
3321
|
+
try {
|
|
3322
|
+
url = new URL(raw);
|
|
3323
|
+
} catch {
|
|
3324
|
+
throw new EgressError(`${raw} is not a URL`);
|
|
3325
|
+
}
|
|
3326
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
3327
|
+
throw new EgressError(
|
|
3328
|
+
`${url.protocol} is not a scheme this daemon fetches. A purchase is an HTTP request answered with 402, and nothing else here needs a network.`
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
3332
|
+
const addresses = isIP(host) ? [host] : await (options.resolve ?? resolveAll)(host).catch(() => []);
|
|
3333
|
+
if (addresses.length === 0) {
|
|
3334
|
+
throw new EgressError(`${url.hostname} does not resolve, so there is nothing to ask`);
|
|
3335
|
+
}
|
|
3336
|
+
for (const address2 of addresses) {
|
|
3337
|
+
if (isPrivateAddress(address2)) {
|
|
3338
|
+
throw new EgressError(
|
|
3339
|
+
`${url.hostname} resolves to ${address2}, which is on this machine or its private network. The daemon runs beside the meter and the attest endpoint, both of which listen on loopback because they trust it, and a URL is the only thing an agent can hand this tool.`
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
return url;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
// ../daemon/src/fetch.ts
|
|
3347
|
+
async function cordonFetch(request, deps) {
|
|
3348
|
+
const { gate: gate2, settler, acceptable, transport } = deps;
|
|
3349
|
+
const method = request.method ?? "GET";
|
|
3350
|
+
const headers = { ...request.headers };
|
|
3351
|
+
const first = await transport(request.url, { method, headers, body: request.body });
|
|
3352
|
+
const challenge = parseChallenge(first.status, first.body);
|
|
3353
|
+
if (!challenge) {
|
|
3354
|
+
return { paid: false, free: true, status: first.status, headers: first.headers, body: first.body };
|
|
3355
|
+
}
|
|
3356
|
+
const offer = selectOffer(challenge, acceptable);
|
|
3357
|
+
const releasedFrom = deps.released ? await deps.released.claim(request.node, offer.payTo, offer.amount) : null;
|
|
3358
|
+
const draw = releasedFrom !== null ? { released: true, reason: "none", refusalId: releasedFrom } : await gate2.draw(request.node, offer.payTo, offer.amount);
|
|
3359
|
+
if (!draw.released) {
|
|
3360
|
+
return { paid: false, free: false, refusal: draw, offer };
|
|
3361
|
+
}
|
|
3362
|
+
let settlement;
|
|
3363
|
+
try {
|
|
3364
|
+
settlement = await settler.settle(request.node, {
|
|
3365
|
+
to: offer.payTo,
|
|
3366
|
+
value: offer.amount,
|
|
3367
|
+
asset: offer.asset,
|
|
3368
|
+
network: offer.network,
|
|
3369
|
+
/* The seller's own terms for the signature it will submit: the token's
|
|
3370
|
+
EIP-712 name and version, and how long the authorisation must stay
|
|
3371
|
+
valid. Both come from the challenge, neither from us. */
|
|
3372
|
+
extra: offer.extra,
|
|
3373
|
+
maxTimeoutSeconds: offer.maxTimeoutSeconds
|
|
3374
|
+
});
|
|
3375
|
+
} catch (error) {
|
|
3376
|
+
if (releasedFrom !== null) deps.released.unclaim(releasedFrom);
|
|
3377
|
+
throw error;
|
|
3378
|
+
}
|
|
3379
|
+
const second = await transport(request.url, {
|
|
3380
|
+
method,
|
|
3381
|
+
headers: { ...headers, "PAYMENT-SIGNATURE": settlement.proof },
|
|
3382
|
+
body: request.body
|
|
3383
|
+
});
|
|
3384
|
+
return {
|
|
3385
|
+
paid: true,
|
|
3386
|
+
status: second.status,
|
|
3387
|
+
headers: second.headers,
|
|
3388
|
+
body: second.body,
|
|
3389
|
+
draw,
|
|
3390
|
+
offer,
|
|
3391
|
+
settlementTx: settlement.txHash,
|
|
3392
|
+
...releasedFrom !== null ? { releasedRefusal: releasedFrom } : {}
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
var MAX_REDIRECTS = 3;
|
|
3396
|
+
var httpTransport = (url, init) => httpTransportWith(assertPublicUrl)(url, init);
|
|
3397
|
+
var httpTransportWith = (assert) => async (url, init) => {
|
|
3398
|
+
let target = await assert(url);
|
|
3399
|
+
const origin = target.origin;
|
|
3400
|
+
const carriesPayment = Object.keys(init.headers).some(
|
|
3401
|
+
(name) => name.toLowerCase() === "payment-signature" || name.toLowerCase() === "x-payment"
|
|
3402
|
+
);
|
|
3403
|
+
for (let hop = 0; ; hop++) {
|
|
3404
|
+
const response = await fetch(target, {
|
|
3405
|
+
method: init.method,
|
|
3406
|
+
headers: init.headers,
|
|
3407
|
+
body: init.body,
|
|
3408
|
+
/* Followed below instead, so each hop is checked. */
|
|
3409
|
+
redirect: "manual",
|
|
3410
|
+
signal: AbortSignal.timeout(3e4)
|
|
3411
|
+
});
|
|
3412
|
+
const location = response.headers.get("location");
|
|
3413
|
+
if (response.status < 300 || response.status >= 400 || !location) {
|
|
3414
|
+
const text2 = await response.text();
|
|
3415
|
+
let body = text2;
|
|
3416
|
+
try {
|
|
3417
|
+
body = JSON.parse(text2);
|
|
3418
|
+
} catch {
|
|
3419
|
+
}
|
|
3420
|
+
return {
|
|
3421
|
+
status: response.status,
|
|
3422
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
3423
|
+
body
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
if (hop >= MAX_REDIRECTS) {
|
|
3427
|
+
throw new EgressError(`${url} redirected more than ${MAX_REDIRECTS} times`);
|
|
3428
|
+
}
|
|
3429
|
+
const next = await assert(new URL(location, target).toString());
|
|
3430
|
+
if (carriesPayment && next.origin !== origin) {
|
|
3431
|
+
throw new EgressError(
|
|
3432
|
+
`${target.origin} redirected a paid request to ${next.origin}. The authorisation in it is signed and bearer, and it was priced against the first of those two.`
|
|
3433
|
+
);
|
|
3434
|
+
}
|
|
3435
|
+
target = next;
|
|
3436
|
+
}
|
|
3437
|
+
};
|
|
3438
|
+
|
|
3439
|
+
// src/render.ts
|
|
3440
|
+
var usdc = (base6) => {
|
|
3441
|
+
const whole = base6 / 1000000n;
|
|
3442
|
+
const frac = (base6 % 1000000n).toString().padStart(6, "0").replace(/0+$/, "") || "0";
|
|
3443
|
+
return `$${whole}.${frac}`;
|
|
3444
|
+
};
|
|
3445
|
+
var short = (id) => `${id.slice(0, 10)}\u2026${id.slice(-6)}`;
|
|
3446
|
+
var WHY = REASON_MEANING;
|
|
3447
|
+
function renderRefusal(outcome, offer, explorer) {
|
|
3448
|
+
const lines = [
|
|
3449
|
+
`REFUSED \u2014 ${WHY[outcome.reason] ?? outcome.reason}.`,
|
|
3450
|
+
"",
|
|
3451
|
+
`The purchase ${usdc(offer.amount)} to ${offer.payTo}`,
|
|
3452
|
+
`The bound that hit ${outcome.reason}`
|
|
3453
|
+
];
|
|
3454
|
+
if (outcome.breachedAt) {
|
|
3455
|
+
lines.push(`Enforced at ${short(outcome.breachedAt)}`);
|
|
3456
|
+
}
|
|
3457
|
+
if (outcome.refusalId !== void 0) {
|
|
3458
|
+
lines.push(`On the record as refusal #${outcome.refusalId}`);
|
|
3459
|
+
}
|
|
3460
|
+
if (outcome.txHash) {
|
|
3461
|
+
lines.push(`Transaction ${outcome.txHash}`);
|
|
3462
|
+
if (explorer) lines.push(` ${explorer}/tx/${outcome.txHash}`);
|
|
3463
|
+
}
|
|
3464
|
+
lines.push(
|
|
3465
|
+
"",
|
|
3466
|
+
"Nothing was paid and no budget was consumed. This is a decision the",
|
|
3467
|
+
"contract made; it cannot be retried past the bound that produced it."
|
|
3468
|
+
);
|
|
3469
|
+
return lines.join("\n");
|
|
3470
|
+
}
|
|
3471
|
+
function renderPaid(outcome, offer, body) {
|
|
3472
|
+
const text2 = typeof body === "string" ? body : JSON.stringify(body, null, 2);
|
|
3473
|
+
return [
|
|
3474
|
+
`Paid ${usdc(offer.amount)} to ${offer.payTo} on ${offer.network}.`,
|
|
3475
|
+
!outcome.txHash && outcome.refusalId !== void 0 ? `Paid out of the owner's release of refusal ${outcome.refusalId}. No draw was sent; the bound did not move.` : "",
|
|
3476
|
+
outcome.beneficiary ? `Tranche credited to ${outcome.beneficiary}.` : "",
|
|
3477
|
+
outcome.txHash ? `Draw ${outcome.txHash}` : "",
|
|
3478
|
+
"",
|
|
3479
|
+
text2
|
|
3480
|
+
].filter(Boolean).join("\n");
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
// src/server.ts
|
|
3484
|
+
var text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
3485
|
+
function createMcpServer(deps) {
|
|
3486
|
+
const server2 = new McpServer(
|
|
3487
|
+
{ name: "cordon", version: "0.1.0" },
|
|
3488
|
+
{
|
|
3489
|
+
instructions: "Cordon bounds what this agent can spend. Use cordon_fetch for any URL that may charge; it pays through a contract that can refuse. There is no tool here that sends money to an address, and a refusal is a final answer rather than an error to retry."
|
|
3490
|
+
}
|
|
3491
|
+
);
|
|
3492
|
+
const transport = deps.transport ?? httpTransport;
|
|
3493
|
+
const newKey = deps.newOperatorKey ?? generatePrivateKey;
|
|
3494
|
+
server2.registerTool(
|
|
3495
|
+
"cordon_fetch",
|
|
3496
|
+
{
|
|
3497
|
+
description: "Fetch a URL. If it answers 402, pay for it through Cordon and return the body. The recipient and the price come from the seller's own challenge, not from you. May return a refusal, which is final.",
|
|
3498
|
+
inputSchema: {
|
|
3499
|
+
url: z.string().url().describe("The URL to fetch."),
|
|
3500
|
+
method: z.string().optional().describe("HTTP method. Defaults to GET."),
|
|
3501
|
+
body: z.string().optional().describe("Request body, for POST and friends.")
|
|
3502
|
+
}
|
|
3503
|
+
},
|
|
3504
|
+
async ({ url, method, body }) => {
|
|
3505
|
+
const result = await cordonFetch(
|
|
3506
|
+
{ node: deps.node, url, method, body },
|
|
3507
|
+
{ gate: deps.gate, settler: deps.settler, acceptable: deps.acceptable, transport, released: deps.released }
|
|
3508
|
+
);
|
|
3509
|
+
if ("free" in result && result.free) {
|
|
3510
|
+
const shown = typeof result.body === "string" ? result.body : JSON.stringify(result.body, null, 2);
|
|
3511
|
+
return text(`${url} charged nothing.
|
|
3512
|
+
|
|
3513
|
+
${shown}`);
|
|
3514
|
+
}
|
|
3515
|
+
if (!result.paid) {
|
|
3516
|
+
const refused = result;
|
|
3517
|
+
return text(renderRefusal(refused.refusal, refused.offer, deps.explorer));
|
|
3518
|
+
}
|
|
3519
|
+
const paid = result;
|
|
3520
|
+
return text(renderPaid(paid.draw, paid.offer, paid.body));
|
|
3521
|
+
}
|
|
3522
|
+
);
|
|
3523
|
+
server2.registerTool(
|
|
3524
|
+
"cordon_spawn",
|
|
3525
|
+
{
|
|
3526
|
+
description: "Register a child mandate under this one for a sub-agent. The child can only ever be narrower than its parent; the contract refuses a wider one whoever asks. The key for the child is held here, not by any agent.",
|
|
3527
|
+
inputSchema: {
|
|
3528
|
+
label: z.string().describe(
|
|
3529
|
+
"What this sub-agent is for, in one line. Written beside its key and onto its ERC-8004 identity as a stated purpose. A description, not a bound."
|
|
3530
|
+
),
|
|
3531
|
+
budgetUsdc: z.string().describe('Window budget in USDC, e.g. "25.00".'),
|
|
3532
|
+
trancheUsdc: z.string().optional().describe("Per-draw cap. Defaults to the parent's."),
|
|
3533
|
+
concentrationPct: z.number().optional().describe("Percent of the window one counterparty may take.")
|
|
3534
|
+
}
|
|
3535
|
+
},
|
|
3536
|
+
async ({ label, budgetUsdc, trancheUsdc, concentrationPct }) => {
|
|
3537
|
+
const parent = await deps.gate.mandate(deps.node);
|
|
3538
|
+
const purpose = cleanPurpose(label);
|
|
3539
|
+
const key = newKey();
|
|
3540
|
+
const operator = deps.gate.addOperator(key);
|
|
3541
|
+
const keyLabel = deps.keyFile?.remember(key, operator, purpose);
|
|
3542
|
+
const { node: node2, txHash, purposePublished } = await deps.gate.spawn(deps.node, {
|
|
3543
|
+
operator,
|
|
3544
|
+
budget6: toBase6(budgetUsdc),
|
|
3545
|
+
trancheCap6: trancheUsdc ? toBase6(trancheUsdc) : parent.trancheCap6,
|
|
3546
|
+
concentrationBps: concentrationPct !== void 0 ? Math.round(concentrationPct * 100) : parent.concentrationBps,
|
|
3547
|
+
purpose
|
|
3548
|
+
});
|
|
3549
|
+
let nodeIdSaved = false;
|
|
3550
|
+
if (deps.keyFile && keyLabel) {
|
|
3551
|
+
try {
|
|
3552
|
+
deps.keyFile.bind(keyLabel, node2);
|
|
3553
|
+
nodeIdSaved = true;
|
|
3554
|
+
} catch {
|
|
3555
|
+
nodeIdSaved = false;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
return text(
|
|
3559
|
+
[
|
|
3560
|
+
`Spawned "${label}".`,
|
|
3561
|
+
` node ${node2}`,
|
|
3562
|
+
` operator ${operator}`,
|
|
3563
|
+
` budget ${budgetUsdc} USDC`,
|
|
3564
|
+
purpose ? purposePublished ? " purpose on its ERC-8004 identity \u2014 stated, not enforced" : " purpose kept beside its key; not published to its identity" : "",
|
|
3565
|
+
keyLabel ? nodeIdSaved ? ` key saved under ${keyLabel} in the operator's key file` : ` key saved under ${keyLabel}; its node id could not be written beside it` : " key held in memory only \u2014 lost if this server restarts",
|
|
3566
|
+
txHash ? ` tx ${txHash}` : "",
|
|
3567
|
+
"",
|
|
3568
|
+
"Every draw this child makes also debits this mandate and every one",
|
|
3569
|
+
"above it. It cannot be widened later."
|
|
3570
|
+
].filter(Boolean).join("\n")
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3573
|
+
);
|
|
3574
|
+
server2.registerTool(
|
|
3575
|
+
"cordon_status",
|
|
3576
|
+
{
|
|
3577
|
+
description: "What this mandate may still spend, and which node in the tree is the limit. The answer is often an ancestor rather than this node.",
|
|
3578
|
+
inputSchema: {}
|
|
3579
|
+
},
|
|
3580
|
+
async () => {
|
|
3581
|
+
const { available, boundBy } = await deps.gate.headroom(deps.node);
|
|
3582
|
+
const own = boundBy.toLowerCase() === deps.node.toLowerCase();
|
|
3583
|
+
return text(
|
|
3584
|
+
[
|
|
3585
|
+
`Can still draw ${fromBase6(available)} USDC`,
|
|
3586
|
+
`Limited by ${own ? "this mandate" : boundBy}`,
|
|
3587
|
+
own ? "" : "An ancestor is the binding constraint, not this node's own budget."
|
|
3588
|
+
].filter(Boolean).join("\n")
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
);
|
|
3592
|
+
return server2;
|
|
3593
|
+
}
|
|
3594
|
+
function toBase6(usdc2) {
|
|
3595
|
+
const match = /^(\d+)(?:\.(\d{1,6}))?$/.exec(usdc2.trim());
|
|
3596
|
+
if (!match) throw new Error(`not a USDC amount: ${usdc2}`);
|
|
3597
|
+
return BigInt(match[1]) * 1000000n + BigInt((match[2] ?? "").padEnd(6, "0"));
|
|
3598
|
+
}
|
|
3599
|
+
function fromBase6(base6) {
|
|
3600
|
+
return `${base6 / 1000000n}.${(base6 % 1000000n).toString().padStart(6, "0").slice(0, 2)}`;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
// src/main.ts
|
|
3604
|
+
var envFiles = (process.env.CORDON_ENV_FILE ?? "").split(",").map((file) => file.trim()).filter(Boolean);
|
|
3605
|
+
for (const file of envFiles) {
|
|
3606
|
+
try {
|
|
3607
|
+
process.loadEnvFile(file);
|
|
3608
|
+
} catch (error) {
|
|
3609
|
+
console.error(`cordon: CORDON_ENV_FILE names ${file}, which could not be read: ${error.message}`);
|
|
3610
|
+
process.exit(1);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
if (envFiles.length > 0) process.env.CORDON_KEY_FILE ??= envFiles[envFiles.length - 1];
|
|
3614
|
+
if (DEPLOYMENT) {
|
|
3615
|
+
process.env.CORDON_VAULT ??= DEPLOYMENT.vault;
|
|
3616
|
+
process.env.CORDON_REGISTRY ??= DEPLOYMENT.registry;
|
|
3617
|
+
process.env.CORDON_RECORD ??= DEPLOYMENT.record;
|
|
3618
|
+
}
|
|
3619
|
+
var config = load(process.env);
|
|
3620
|
+
var gate = new Gate(config);
|
|
3621
|
+
var configured = gate.nodes();
|
|
3622
|
+
var asked = process.env.CORDON_MCP_NODE?.toLowerCase();
|
|
3623
|
+
var node = asked ? configured.find((n) => n.toLowerCase() === asked) : configured[0];
|
|
3624
|
+
if (!node) {
|
|
3625
|
+
console.error(
|
|
3626
|
+
asked ? `cordon: CORDON_MCP_NODE is ${asked} and this process holds no key for it. It holds: ${configured.join(", ") || "none"}` : "cordon: no node configured; set CORDON_NODE_<label> and CORDON_KEY_<label>"
|
|
3627
|
+
);
|
|
3628
|
+
process.exit(1);
|
|
3629
|
+
}
|
|
3630
|
+
var server = createMcpServer({
|
|
3631
|
+
gate,
|
|
3632
|
+
keyFile: keyFileAt(config.keyFile),
|
|
3633
|
+
/* The same file the daemon keeps, so a release spent through one surface is
|
|
3634
|
+
not spent again through the other. */
|
|
3635
|
+
released: new ChainReleases({
|
|
3636
|
+
count: () => gate.refusalCount(),
|
|
3637
|
+
read: (id) => gate.refusal(id),
|
|
3638
|
+
live: (node2) => gate.isLive(node2),
|
|
3639
|
+
file: join2(dirname3(config.keyFile), "released-spent.json")
|
|
3640
|
+
}),
|
|
3641
|
+
settler: new CircleSettler({
|
|
3642
|
+
publicClient: gate.publicClientForSettlement,
|
|
3643
|
+
walletFor: (node2) => gate.signerFor(node2),
|
|
3644
|
+
chainId: config.chainId
|
|
3645
|
+
}),
|
|
3646
|
+
acceptable: { networks: config.networks, assets: config.assets },
|
|
3647
|
+
node,
|
|
3648
|
+
explorer: config.chainId === ARC.chainId ? ARC.explorer : void 0
|
|
3649
|
+
});
|
|
3650
|
+
console.error(`cordon mcp: node ${node} on chain ${config.chainId}`);
|
|
3651
|
+
await server.connect(new StdioServerTransport());
|