otto-execute 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/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/cdp-signer.d.ts +2 -0
- package/dist/cdp-signer.js +3 -0
- package/dist/cdp.d.ts +7 -0
- package/dist/cdp.js +8 -0
- package/dist/chain.d.ts +2 -0
- package/dist/chain.js +3 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +400 -0
- package/dist/delegate.d.ts +117 -0
- package/dist/delegate.js +394 -0
- package/dist/eoa-signer.d.ts +94 -0
- package/dist/eoa-signer.js +289 -0
- package/dist/erc20.d.ts +2 -0
- package/dist/erc20.js +3 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +19 -0
- package/dist/lifi-decode.d.ts +2 -0
- package/dist/lifi-decode.js +3 -0
- package/dist/mutations.d.ts +55 -0
- package/dist/mutations.js +363 -0
- package/dist/refusal.d.ts +2 -0
- package/dist/refusal.js +3 -0
- package/dist/vendor/otto-intel-mcp/VENDORED.json +52 -0
- package/dist/vendor/otto-intel-mcp/adapter/cdp-signer.d.ts +133 -0
- package/dist/vendor/otto-intel-mcp/adapter/cdp-signer.js +356 -0
- package/dist/vendor/otto-intel-mcp/adapter/chain.d.ts +36 -0
- package/dist/vendor/otto-intel-mcp/adapter/chain.js +65 -0
- package/dist/vendor/otto-intel-mcp/adapter/erc20.d.ts +39 -0
- package/dist/vendor/otto-intel-mcp/adapter/erc20.js +17 -0
- package/dist/vendor/otto-intel-mcp/adapter/lifi-decode.d.ts +52 -0
- package/dist/vendor/otto-intel-mcp/adapter/lifi-decode.js +149 -0
- package/dist/vendor/otto-intel-mcp/adapter/refusal.d.ts +21 -0
- package/dist/vendor/otto-intel-mcp/adapter/refusal.js +55 -0
- package/dist/vendor/otto-intel-mcp/adapter/sent-step.d.ts +15 -0
- package/dist/vendor/otto-intel-mcp/adapter/sent-step.js +6 -0
- package/dist/vendor/otto-intel-mcp/adapter/verify.d.ts +149 -0
- package/dist/vendor/otto-intel-mcp/adapter/verify.js +432 -0
- package/dist/vendor/otto-intel-mcp/adapter-cdp-index.d.ts +6 -0
- package/dist/vendor/otto-intel-mcp/adapter-cdp-index.js +7 -0
- package/dist/vendor/otto-intel-mcp/adapter-index.d.ts +15 -0
- package/dist/vendor/otto-intel-mcp/adapter-index.js +15 -0
- package/dist/vendor/otto-intel-mcp/artifact-id.d.ts +16 -0
- package/dist/vendor/otto-intel-mcp/artifact-id.js +60 -0
- package/dist/vendor/otto-intel-mcp/execution-config.d.ts +232 -0
- package/dist/vendor/otto-intel-mcp/execution-config.js +443 -0
- package/dist/vendor/otto-intel-mcp/execution-delegated-definition.d.ts +165 -0
- package/dist/vendor/otto-intel-mcp/execution-delegated-definition.js +116 -0
- package/dist/vendor/otto-intel-mcp/execution-delegation-admin-definition.d.ts +208 -0
- package/dist/vendor/otto-intel-mcp/execution-delegation-admin-definition.js +170 -0
- package/dist/vendor/otto-intel-mcp/execution-delegation-policy.d.ts +257 -0
- package/dist/vendor/otto-intel-mcp/execution-delegation-policy.js +279 -0
- package/dist/vendor/otto-intel-mcp/execution-errors.d.ts +9 -0
- package/dist/vendor/otto-intel-mcp/execution-errors.js +134 -0
- package/dist/vendor/otto-intel-mcp/execution-index.d.ts +17 -0
- package/dist/vendor/otto-intel-mcp/execution-index.js +16 -0
- package/dist/vendor/otto-intel-mcp/execution-intent.d.ts +14 -0
- package/dist/vendor/otto-intel-mcp/execution-intent.js +36 -0
- package/dist/vendor/otto-intel-mcp/execution-tool-definitions.d.ts +1103 -0
- package/dist/vendor/otto-intel-mcp/execution-tool-definitions.js +1051 -0
- package/dist/vendor/otto-intel-mcp/execution-types.d.ts +274 -0
- package/dist/vendor/otto-intel-mcp/execution-types.js +157 -0
- package/dist/verify.d.ts +2 -0
- package/dist/verify.js +3 -0
- package/dist/x402-table.d.ts +99 -0
- package/dist/x402-table.js +221 -0
- package/package.json +97 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mutations.ts — the consistency matrix (mutation cases the adapter MUST refuse).
|
|
3
|
+
*
|
|
4
|
+
* Each entry derives an INCONSISTENT variant of a VALID artifact and names the exact refusal the
|
|
5
|
+
* adapter must produce. Used twice: by the test suite (every refusal must redden — delete a check
|
|
6
|
+
* in verify.ts and its row fails) and by the sitting's fence section (the BYO "policy engine" is the
|
|
7
|
+
* adapter itself; these rows are its out-of-policy inputs, graded BLOCKED / LEAKED by the ladder).
|
|
8
|
+
* Same vocabulary as the constructor's own mutation proofs (`test/*-mutation-proof.ts`).
|
|
9
|
+
*
|
|
10
|
+
* Two mutation models, deliberately both:
|
|
11
|
+
* - `recommit: false` — bytes change, digests are left stale. The integrity layer catches it.
|
|
12
|
+
* - `recommit: true` — payload + commitment digests are recomputed (they are unkeyed keccak over
|
|
13
|
+
* public content, so any party holding the artifact can). Only the adapter's INTENT and POLICY
|
|
14
|
+
* layers can catch these. A verifier that stopped at digests would grade every one of them LEAKED.
|
|
15
|
+
*
|
|
16
|
+
* Every row names the layer expected to catch it. Rows caught by the shared envelope schema are kept
|
|
17
|
+
* (they document the contract) but are NOT evidence for a verifier-local check; the verifier-local
|
|
18
|
+
* checks each have their own row(s) below.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NOT a row: an internal DEX leg re-pointed at a foreign contract with the receiver and
|
|
21
|
+
* minimum output intact. The adapter does not hold an allowlist of every DEX; that leg is bounded
|
|
22
|
+
* ON-CHAIN by the router's `_receiver` + `_minAmount` enforcement (GenericSwapV3 reverts unless the
|
|
23
|
+
* receiver gets at least the minimum), which is why the intent floor must be positive and the
|
|
24
|
+
* caller's own (verify.ts layer 2).
|
|
25
|
+
*/
|
|
26
|
+
import { decodeFunctionData, encodeFunctionData, getAddress, parseAbi } from 'viem';
|
|
27
|
+
import { decodeCollectTokenFees, decodeGenericSwap, encodeCollectTokenFees, encodeGenericSwap, } from './lifi-decode.js';
|
|
28
|
+
import { artifactCommitmentDigest, executionDigest } from './vendor/otto-intel-mcp/execution-index.js';
|
|
29
|
+
/** A foreign address no reviewed table names; the mutation target for every substitution row. */
|
|
30
|
+
export const FOREIGN_ADDRESS = getAddress('0x000000000000000000000000000000000000dEaD');
|
|
31
|
+
/** A foreign ERC20 no intent names (Base USDbC), for token substitutions. */
|
|
32
|
+
export const FOREIGN_TOKEN = getAddress('0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA');
|
|
33
|
+
const ERC20_APPROVE_ABI = parseAbi([
|
|
34
|
+
'function approve(address spender, uint256 amount) returns (bool)',
|
|
35
|
+
]);
|
|
36
|
+
function clone(artifact) {
|
|
37
|
+
return structuredClone(artifact);
|
|
38
|
+
}
|
|
39
|
+
/** Recompute both digests with the constructor's public canonicalization, as any holder of the artifact could. */
|
|
40
|
+
export function recommit(artifact) {
|
|
41
|
+
const next = clone(artifact);
|
|
42
|
+
const { artifact_digest: _dropped, ...submission } = next.submission;
|
|
43
|
+
const uncommitted = { ...next, submission: { ...submission, payload_digest: executionDigest(next.payload) } };
|
|
44
|
+
return {
|
|
45
|
+
...uncommitted,
|
|
46
|
+
submission: { ...uncommitted.submission, artifact_digest: artifactCommitmentDigest(uncommitted) },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function withSteps(artifact, edit) {
|
|
50
|
+
const next = clone(artifact);
|
|
51
|
+
next.payload = { ...next.payload, steps: edit(next.payload.steps) };
|
|
52
|
+
return next;
|
|
53
|
+
}
|
|
54
|
+
function withCallData(artifact, data) {
|
|
55
|
+
return withSteps(artifact, (steps) => steps.map((step, index) => (index === 2 ? { ...step, data } : step)));
|
|
56
|
+
}
|
|
57
|
+
function callData(artifact) {
|
|
58
|
+
return artifact.payload.steps[2].data;
|
|
59
|
+
}
|
|
60
|
+
function rewriteSwap(artifact, edit) {
|
|
61
|
+
return withCallData(artifact, encodeGenericSwap(edit(decodeGenericSwap(callData(artifact)))));
|
|
62
|
+
}
|
|
63
|
+
function isForwarder(forwarders, address) {
|
|
64
|
+
return forwarders.some((forwarder) => getAddress(forwarder) === getAddress(address));
|
|
65
|
+
}
|
|
66
|
+
function rewriteFeeLeg(artifact, forwarders, edit) {
|
|
67
|
+
return rewriteSwap(artifact, (swap) => ({
|
|
68
|
+
...swap,
|
|
69
|
+
legs: swap.legs.map((leg) => isForwarder(forwarders, leg.callTo)
|
|
70
|
+
? { ...leg, callData: encodeCollectTokenFees(edit(decodeCollectTokenFees(leg.callData))) }
|
|
71
|
+
: leg),
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
function rewriteApproval(artifact, index, edit) {
|
|
75
|
+
return withSteps(artifact, (steps) => steps.map((step, i) => {
|
|
76
|
+
if (i !== index)
|
|
77
|
+
return step;
|
|
78
|
+
const decoded = decodeFunctionData({ abi: ERC20_APPROVE_ABI, data: step.data });
|
|
79
|
+
const [spender, amount] = edit(getAddress(decoded.args[0]), decoded.args[1]);
|
|
80
|
+
return { ...step, data: encodeFunctionData({ abi: ERC20_APPROVE_ABI, functionName: 'approve', args: [spender, amount] }) };
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
export const MUTATION_CASES = Object.freeze([
|
|
84
|
+
{
|
|
85
|
+
id: 'F1-bytes-no-recommit',
|
|
86
|
+
what: 'flip one calldata byte, leave the digests alone',
|
|
87
|
+
recommit: false,
|
|
88
|
+
expect: 'DIGEST_MISMATCH',
|
|
89
|
+
layer: 'integrity',
|
|
90
|
+
mutate: (artifact) => {
|
|
91
|
+
const data = callData(artifact);
|
|
92
|
+
const flipped = `${data.slice(0, -2)}${data.endsWith('00') ? '01' : '00'}`;
|
|
93
|
+
return withCallData(artifact, flipped);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'F2-expired',
|
|
98
|
+
what: 'back-date valid_until before its artifact_id construction time (digests recomputed) — the envelope schema itself refuses',
|
|
99
|
+
recommit: true,
|
|
100
|
+
expect: 'SCHEMA_INVALID',
|
|
101
|
+
layer: 'schema',
|
|
102
|
+
mutate: (artifact, context) => {
|
|
103
|
+
const next = clone(artifact);
|
|
104
|
+
next.valid_until = new Date(context.nowMs - 60_000).toISOString();
|
|
105
|
+
return next;
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'F3-other-account',
|
|
110
|
+
what: 'artifact bound to a different account than the signer (digests recomputed)',
|
|
111
|
+
recommit: true,
|
|
112
|
+
expect: 'ACCOUNT_BINDING_MISMATCH',
|
|
113
|
+
layer: 'integrity',
|
|
114
|
+
mutate: (artifact) => {
|
|
115
|
+
const next = clone(artifact);
|
|
116
|
+
next.account_binding = {
|
|
117
|
+
...next.account_binding,
|
|
118
|
+
address: FOREIGN_ADDRESS,
|
|
119
|
+
protocol_roles: { lifi: { sender: FOREIGN_ADDRESS, receiver: FOREIGN_ADDRESS } },
|
|
120
|
+
};
|
|
121
|
+
return next;
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'F4-capability',
|
|
126
|
+
what: 'an eoa-bound artifact demanding submit_evm_batch (digests recomputed) — the envelope schema itself refuses',
|
|
127
|
+
recommit: true,
|
|
128
|
+
expect: 'SCHEMA_INVALID',
|
|
129
|
+
layer: 'schema',
|
|
130
|
+
mutate: (artifact) => {
|
|
131
|
+
const next = clone(artifact);
|
|
132
|
+
next.required_capabilities = ['submit_evm_batch'];
|
|
133
|
+
return next;
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'F5-native-value',
|
|
138
|
+
what: 'call step carries 1 wei of native value (digests recomputed) — the envelope schema itself refuses',
|
|
139
|
+
recommit: true,
|
|
140
|
+
expect: 'SCHEMA_INVALID',
|
|
141
|
+
layer: 'schema',
|
|
142
|
+
mutate: (artifact) => withSteps(artifact, (steps) => steps.map((step, index) => (index === 2 ? { ...step, value: '1' } : step))),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'F6-call-target',
|
|
146
|
+
what: 'call step re-pointed at a foreign contract while the approvals still name the router (digests recomputed)',
|
|
147
|
+
recommit: true,
|
|
148
|
+
expect: 'APPROVAL_INVALID',
|
|
149
|
+
layer: 'policy',
|
|
150
|
+
mutate: (artifact) => withSteps(artifact, (steps) => steps.map((step, index) => (index === 2 ? { ...step, to: FOREIGN_ADDRESS } : step))),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'F7-approve-spender',
|
|
154
|
+
what: 'both approvals re-pointed at a foreign spender, call re-pointed to match (digests recomputed)',
|
|
155
|
+
recommit: true,
|
|
156
|
+
expect: 'TARGET_NOT_ALLOWED',
|
|
157
|
+
layer: 'policy',
|
|
158
|
+
mutate: (artifact) => {
|
|
159
|
+
const repointed = rewriteApproval(rewriteApproval(artifact, 0, (_s, a) => [FOREIGN_ADDRESS, a]), 1, (_s, a) => [FOREIGN_ADDRESS, a]);
|
|
160
|
+
return withSteps(repointed, (steps) => steps.map((step, index) => (index === 2 ? { ...step, to: FOREIGN_ADDRESS } : step)));
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'F8-receiver',
|
|
165
|
+
what: 'swap _receiver redirected to a foreign address (digests recomputed)',
|
|
166
|
+
recommit: true,
|
|
167
|
+
expect: 'RECEIVER_MISMATCH',
|
|
168
|
+
layer: 'policy',
|
|
169
|
+
mutate: (artifact) => rewriteSwap(artifact, (swap) => ({ ...swap, receiver: FOREIGN_ADDRESS })),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'F9-integrator',
|
|
173
|
+
what: "swap _integrator changed from 'ottowallet' (digests recomputed)",
|
|
174
|
+
recommit: true,
|
|
175
|
+
expect: 'ATTRIBUTION_MISSING',
|
|
176
|
+
layer: 'policy',
|
|
177
|
+
mutate: (artifact) => rewriteSwap(artifact, (swap) => ({ ...swap, integrator: 'mallory' })),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'F10-fee-recipient',
|
|
181
|
+
what: "Otto's fee distribution redirected to a foreign address (digests recomputed)",
|
|
182
|
+
recommit: true,
|
|
183
|
+
expect: 'ATTRIBUTION_MISSING',
|
|
184
|
+
layer: 'policy',
|
|
185
|
+
mutate: (artifact, context) => rewriteFeeLeg(artifact, context.forwarders, (fees) => ({
|
|
186
|
+
...fees,
|
|
187
|
+
distributions: fees.distributions.map((d) => d.recipient === getAddress(context.ottoRecipient) ? { ...d, recipient: FOREIGN_ADDRESS } : d),
|
|
188
|
+
})),
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'F11-fee-amount',
|
|
192
|
+
what: "Otto's fee amount halved (digests recomputed)",
|
|
193
|
+
recommit: true,
|
|
194
|
+
expect: 'ATTRIBUTION_MISMATCH',
|
|
195
|
+
layer: 'policy',
|
|
196
|
+
mutate: (artifact, context) => rewriteFeeLeg(artifact, context.forwarders, (fees) => ({
|
|
197
|
+
...fees,
|
|
198
|
+
distributions: fees.distributions.map((d) => d.recipient === getAddress(context.ottoRecipient) ? { ...d, amount: d.amount / 2n } : d),
|
|
199
|
+
})),
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: 'F12-fee-stranger',
|
|
203
|
+
what: 'an extra fee distribution to a stranger appended (digests recomputed)',
|
|
204
|
+
recommit: true,
|
|
205
|
+
expect: 'ATTRIBUTION_MISMATCH',
|
|
206
|
+
layer: 'policy',
|
|
207
|
+
mutate: (artifact, context) => rewriteFeeLeg(artifact, context.forwarders, (fees) => ({
|
|
208
|
+
...fees,
|
|
209
|
+
distributions: [...fees.distributions, { recipient: FOREIGN_ADDRESS, amount: 1n }],
|
|
210
|
+
})),
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'F13-config-version',
|
|
214
|
+
what: 'fee_attribution.config_version rewritten to an unruled version (digests recomputed)',
|
|
215
|
+
recommit: true,
|
|
216
|
+
expect: 'ATTRIBUTION_MISMATCH',
|
|
217
|
+
layer: 'policy',
|
|
218
|
+
mutate: (artifact) => {
|
|
219
|
+
const next = clone(artifact);
|
|
220
|
+
next.fee_attribution = { ...next.fee_attribution, config_version: 'lifi-2026-01-01-5bps-v0' };
|
|
221
|
+
return next;
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 'F14-fee-leg-removed',
|
|
226
|
+
what: 'the FeeForwarder leg deleted, the DEX leg re-flagged as the deposit for the full amount (digests recomputed)',
|
|
227
|
+
recommit: true,
|
|
228
|
+
expect: 'ATTRIBUTION_MISSING',
|
|
229
|
+
layer: 'policy',
|
|
230
|
+
mutate: (artifact, context) => rewriteSwap(artifact, (swap) => {
|
|
231
|
+
const legs = swap.legs.filter((leg) => !isForwarder(context.forwarders, leg.callTo));
|
|
232
|
+
if (legs.length === swap.legs.length || legs.length === 0) {
|
|
233
|
+
throw new Error('fixture has no separable fee leg; F14 needs a multi-leg swap');
|
|
234
|
+
}
|
|
235
|
+
const gross = swap.legs.filter((leg) => leg.requiresDeposit).reduce((sum, leg) => sum + leg.fromAmount, 0n);
|
|
236
|
+
const [first, ...rest] = legs;
|
|
237
|
+
return { ...swap, legs: [{ ...first, requiresDeposit: true, fromAmount: gross }, ...rest] };
|
|
238
|
+
}),
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'F15-fee-collector-unreviewed',
|
|
242
|
+
what: 'the fee leg callTo + approveTo moved to an unreviewed collector, everything else intact (digests recomputed) — the user-side drift alarm',
|
|
243
|
+
recommit: true,
|
|
244
|
+
expect: 'ATTRIBUTION_MISSING',
|
|
245
|
+
layer: 'policy',
|
|
246
|
+
mutate: (artifact, context) => rewriteSwap(artifact, (swap) => ({
|
|
247
|
+
...swap,
|
|
248
|
+
legs: swap.legs.map((leg) => isForwarder(context.forwarders, leg.callTo) ? { ...leg, callTo: FOREIGN_ADDRESS, approveTo: FOREIGN_ADDRESS } : leg),
|
|
249
|
+
})),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'F16-min-output-zero',
|
|
253
|
+
what: 'swap _minAmount set to 0 (digests recomputed) — a swap that may deliver nothing',
|
|
254
|
+
recommit: true,
|
|
255
|
+
expect: 'MIN_OUTPUT_TOO_LOW',
|
|
256
|
+
layer: 'intent',
|
|
257
|
+
mutate: (artifact) => rewriteSwap(artifact, (swap) => ({ ...swap, minAmount: 0n })),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: 'F17-duplicate-deposit',
|
|
261
|
+
what: 'the deposit leg duplicated so the router pulls twice the intent amount (digests recomputed)',
|
|
262
|
+
recommit: true,
|
|
263
|
+
expect: 'SWAP_CALLDATA_INVALID',
|
|
264
|
+
layer: 'intent',
|
|
265
|
+
mutate: (artifact) => rewriteSwap(artifact, (swap) => {
|
|
266
|
+
const deposit = swap.legs.find((leg) => leg.requiresDeposit);
|
|
267
|
+
if (!deposit)
|
|
268
|
+
throw new Error('fixture has no deposit leg');
|
|
269
|
+
return { ...swap, legs: [deposit, ...swap.legs] };
|
|
270
|
+
}),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'F18-approve-oversized',
|
|
274
|
+
what: 'the approval doubled while the deposit legs still pull the intent amount (digests recomputed) — residual allowance',
|
|
275
|
+
recommit: true,
|
|
276
|
+
expect: 'INTENT_MISMATCH',
|
|
277
|
+
layer: 'intent',
|
|
278
|
+
mutate: (artifact) => rewriteApproval(artifact, 1, (spender, amount) => [spender, amount * 2n]),
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: 'F19-output-token',
|
|
282
|
+
what: 'the last leg delivers a foreign token instead of the intent output token (digests recomputed)',
|
|
283
|
+
recommit: true,
|
|
284
|
+
expect: 'INTENT_MISMATCH',
|
|
285
|
+
layer: 'intent',
|
|
286
|
+
mutate: (artifact) => rewriteSwap(artifact, (swap) => ({
|
|
287
|
+
...swap,
|
|
288
|
+
legs: swap.legs.map((leg, index) => (index === swap.legs.length - 1 ? { ...leg, receivingAssetId: FOREIGN_TOKEN } : leg)),
|
|
289
|
+
})),
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: 'F20-deposit-token',
|
|
293
|
+
what: 'the deposit leg pulls a foreign token while the approval still names the intent token (digests recomputed)',
|
|
294
|
+
recommit: true,
|
|
295
|
+
expect: 'SWAP_CALLDATA_INVALID',
|
|
296
|
+
layer: 'intent',
|
|
297
|
+
mutate: (artifact) => rewriteSwap(artifact, (swap) => ({
|
|
298
|
+
...swap,
|
|
299
|
+
legs: swap.legs.map((leg) => (leg.requiresDeposit ? { ...leg, sendingAssetId: FOREIGN_TOKEN } : leg)),
|
|
300
|
+
})),
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: 'F22-bytes-payload-digest-only',
|
|
304
|
+
what: 'one calldata byte flipped and ONLY the payload digest recomputed (commitment stale) — isolates the commitment check',
|
|
305
|
+
recommit: false,
|
|
306
|
+
expect: 'DIGEST_MISMATCH',
|
|
307
|
+
layer: 'integrity',
|
|
308
|
+
mutate: (artifact) => {
|
|
309
|
+
const data = callData(artifact);
|
|
310
|
+
const flipped = `${data.slice(0, -2)}${data.endsWith('00') ? '01' : '00'}`;
|
|
311
|
+
return recommitPayloadOnly(withCallData(artifact, flipped));
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: 'F23-bytes-commitment-only',
|
|
316
|
+
what: 'one calldata byte flipped and ONLY the commitment recomputed (payload digest stale) — isolates the payload-digest check',
|
|
317
|
+
recommit: false,
|
|
318
|
+
expect: 'DIGEST_MISMATCH',
|
|
319
|
+
layer: 'integrity',
|
|
320
|
+
mutate: (artifact) => {
|
|
321
|
+
const data = callData(artifact);
|
|
322
|
+
const flipped = `${data.slice(0, -2)}${data.endsWith('00') ? '01' : '00'}`;
|
|
323
|
+
return recommitCommitmentOnly(withCallData(artifact, flipped));
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: 'F24-fee-leg-receiving-token',
|
|
328
|
+
what: 'the fee leg declared to receive a foreign token while forwarding the intent token (digests recomputed)',
|
|
329
|
+
recommit: true,
|
|
330
|
+
expect: 'ATTRIBUTION_MISMATCH',
|
|
331
|
+
layer: 'policy',
|
|
332
|
+
mutate: (artifact, context) => rewriteSwap(artifact, (swap) => ({
|
|
333
|
+
...swap,
|
|
334
|
+
legs: swap.legs.map((leg) => (isForwarder(context.forwarders, leg.callTo) ? { ...leg, receivingAssetId: FOREIGN_TOKEN } : leg)),
|
|
335
|
+
})),
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'F21-approve-token',
|
|
339
|
+
what: 'both approvals moved to a foreign token contract (digests recomputed)',
|
|
340
|
+
recommit: true,
|
|
341
|
+
expect: 'INTENT_MISMATCH',
|
|
342
|
+
layer: 'intent',
|
|
343
|
+
mutate: (artifact) => withSteps(artifact, (steps) => steps.map((step, index) => (index < 2 ? { ...step, to: FOREIGN_TOKEN } : step))),
|
|
344
|
+
},
|
|
345
|
+
]);
|
|
346
|
+
/** Recompute ONLY the payload digest (commitment left stale) — isolates the commitment check. */
|
|
347
|
+
export function recommitPayloadOnly(artifact) {
|
|
348
|
+
const next = clone(artifact);
|
|
349
|
+
next.submission = { ...next.submission, payload_digest: executionDigest(next.payload) };
|
|
350
|
+
return next;
|
|
351
|
+
}
|
|
352
|
+
/** Recompute ONLY the whole-envelope commitment (payload digest left stale) — isolates the payload-digest check. */
|
|
353
|
+
export function recommitCommitmentOnly(artifact) {
|
|
354
|
+
const next = clone(artifact);
|
|
355
|
+
const { artifact_digest: _dropped, ...submission } = next.submission;
|
|
356
|
+
const uncommitted = { ...next, submission };
|
|
357
|
+
return { ...uncommitted, submission: { ...submission, artifact_digest: artifactCommitmentDigest(uncommitted) } };
|
|
358
|
+
}
|
|
359
|
+
export function applyMutation(mutation, artifact, context) {
|
|
360
|
+
const mutated = mutation.mutate(artifact, context);
|
|
361
|
+
return mutation.recommit ? recommit(mutated) : mutated;
|
|
362
|
+
}
|
|
363
|
+
//# sourceMappingURL=mutations.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** refusal.ts — re-exported from `./vendor/otto-intel-mcp/adapter-index.js` (moved 2026-08-29; see that entry's header). */
|
|
2
|
+
export { ADAPTER_REFUSAL_CODES, AdapterRefusal, isAdapterRefusal, isAdapterRefusalCode, refuse, type AdapterRefusalCode, } from './vendor/otto-intel-mcp/adapter-index.js';
|
package/dist/refusal.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** refusal.ts — re-exported from `./vendor/otto-intel-mcp/adapter-index.js` (moved 2026-08-29; see that entry's header). */
|
|
2
|
+
export { ADAPTER_REFUSAL_CODES, AdapterRefusal, isAdapterRefusal, isAdapterRefusalCode, refuse, } from './vendor/otto-intel-mcp/adapter-index.js';
|
|
3
|
+
//# sourceMappingURL=refusal.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"note": "Generated by scripts/vendor-seam.mjs — file-for-file copies of the published otto-intel-mcp dist. Do not edit.",
|
|
3
|
+
"package": "otto-intel-mcp",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"resolved": "https://registry.npmjs.org/otto-intel-mcp/-/otto-intel-mcp-0.1.1.tgz",
|
|
6
|
+
"integrity": "sha512-cpLVyjhW0iU4dKVaEPeTgEwswEX0RwO8KfeWcajH5qCR/IoAA/83RQWyREhhHjy0yLAOB/q2fxwfvpIt1Xv7Aw==",
|
|
7
|
+
"entries": {
|
|
8
|
+
"otto-intel-mcp/execution": "execution-index.js",
|
|
9
|
+
"otto-intel-mcp/adapter": "adapter-index.js",
|
|
10
|
+
"otto-intel-mcp/adapter-cdp": "adapter-cdp-index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": {
|
|
13
|
+
"adapter-cdp-index.js": "6b83915a79abbccb942aaa49692d71df2bb933cf4933935cab60fb9c32ba8f49",
|
|
14
|
+
"adapter-cdp-index.d.ts": "4435c07c18064fdad282106a6e40a600780db32b306f523a65dcabb10ae38ba9",
|
|
15
|
+
"adapter-index.js": "778732e9dcf8be8519e3862751ccdd5fb3aa5d2f6a474ef2f85df1ad81ceee05",
|
|
16
|
+
"adapter-index.d.ts": "90ede35e090903ddda98ac3c4a609073140cabc9868b10c25e25d93fc6bf5b1a",
|
|
17
|
+
"adapter/cdp-signer.js": "bd889add6a3f574dab8a30be6a5fdfcd815328cbdae14089af768e779e08cee6",
|
|
18
|
+
"adapter/cdp-signer.d.ts": "eb59580813aacfb4e34097d4cc2d9b3ddea27f0c04eb88b7dcc0b88625bef63e",
|
|
19
|
+
"adapter/chain.js": "b95f2d390275736c63426bbbacb2c958fb42d23bbcfb76b6dbe9c3fe6236e727",
|
|
20
|
+
"adapter/chain.d.ts": "1902f4876dfac155a35550c6ea2d6ebdf2d6f52e320a9d8ef6253bf97e1bd9e4",
|
|
21
|
+
"adapter/erc20.js": "3d24577b13ce21bcff657a8e90b425bfd77a44d7764d8955b6533c48c77354e1",
|
|
22
|
+
"adapter/erc20.d.ts": "ac5e6c0c759fc775e7227c2af148ba5532f3775eed251b89fa12dab5d198a3ec",
|
|
23
|
+
"adapter/lifi-decode.js": "e703367f243b592f46d5d160a2352b38a37ed5e91563e9e6968d4a409a2c463b",
|
|
24
|
+
"adapter/lifi-decode.d.ts": "962b7f6cc6d7beded0b43fed55620524931695ac1e683f9b6a006ec8f2525349",
|
|
25
|
+
"adapter/refusal.js": "1179cf581744070c4922154a9da3f06026ba37c7195bfb7cd2e66bdc4ddab67b",
|
|
26
|
+
"adapter/refusal.d.ts": "c75c66a5cf2fd718efdae60a5acae126888cc597a95207626c6d8a383d3dafe4",
|
|
27
|
+
"adapter/sent-step.js": "d4d0cfda10c49e63e179433335e00323f064fe4b22d60aec64f36dd2292dfb28",
|
|
28
|
+
"adapter/sent-step.d.ts": "35365162eb1023d124fdebcf9688ef2b986ff0dd71b092390e923aef9abdeb1a",
|
|
29
|
+
"adapter/verify.js": "fa10c8501c1fa626adf1992b62caa60abb3aaa268d620e1333d104016cea3b1d",
|
|
30
|
+
"adapter/verify.d.ts": "714128e2734b1b8b0ab24686b0efdeb63a67ce79c2bd4a4649306d56d05a8e7c",
|
|
31
|
+
"artifact-id.js": "896898a48d89c6d05b983c76d50c947462807691bfc5e00cfeb9cec07286e274",
|
|
32
|
+
"artifact-id.d.ts": "abc817b5bdb48608a6c2b6f83d8d06406c9d38fc53d1339851858e5feced395a",
|
|
33
|
+
"execution-config.js": "8704b3a5d8b7ff5d84c5bb7d7ac41c4b7b6124824d21e22b8a9f345aac63075b",
|
|
34
|
+
"execution-config.d.ts": "ec15569b3d962d2bfd7c297236362873795c0db2021464d15ad5bcb3b0190fcd",
|
|
35
|
+
"execution-delegated-definition.js": "6d4d558bb00b54f4ed320f966f2311239a263ac991e3922ddc0b722d845046b3",
|
|
36
|
+
"execution-delegated-definition.d.ts": "0e6febe4772188b4be0eb6e1a4f2416873c1da44ed8f35dc88896af1c049456d",
|
|
37
|
+
"execution-delegation-admin-definition.js": "9000403e3d9a67547ac4ca5eeb74fb8f938b2ac3b016eacee41ff2ce76f97239",
|
|
38
|
+
"execution-delegation-admin-definition.d.ts": "832c15a02a623be13bd7c3e4d43052be86c758ec7b4a222d4b29265fb9119c10",
|
|
39
|
+
"execution-delegation-policy.js": "a86ddfb60f8078f35c59cb7bf49be8ef77c12deae609e317e173996de0b3ef49",
|
|
40
|
+
"execution-delegation-policy.d.ts": "c6474159265af06c4e6418b45dbb0731c5a670932158b22e08e80aebbe64291c",
|
|
41
|
+
"execution-errors.js": "b550422d30149763d12eb6cfb42133e95ce820d6fc9885b4dae8b4180e10dbca",
|
|
42
|
+
"execution-errors.d.ts": "60d719e6225531d12c069fa69de9f6cc9bae5c6828f7b18a15c66dc4181f2286",
|
|
43
|
+
"execution-index.js": "291d83798a65cac2f5998492b1e273a893f21cf00a39c120ba86e9e775256473",
|
|
44
|
+
"execution-index.d.ts": "742d0af2f37747da56f54c8e0484e9d0c6e0847e79c41cc8c35b3872cfdc06a9",
|
|
45
|
+
"execution-intent.js": "11a3f0012679d3a91e57f0b980656897f322f89ff369d7ef3da8f5f91bc2b780",
|
|
46
|
+
"execution-intent.d.ts": "648d0f7b06a7ec544b040f2ee3e99b55bb328602ff4a7a0cb6761e5c46778613",
|
|
47
|
+
"execution-tool-definitions.js": "27a55d41e5b44f3eca9c36403c096cff2fcc87345140f5696415df7765656382",
|
|
48
|
+
"execution-tool-definitions.d.ts": "67013d0f42fdd888e28413601a7824b5d01d39416f3991b2c02a6df8e7d2e5b6",
|
|
49
|
+
"execution-types.js": "744f399fd92178d67e65041494d552bcff115eca48c0ee948acb5752a34e662b",
|
|
50
|
+
"execution-types.d.ts": "53b4823bfffdada869b9111cf49635436ca78971c3df412727f1d7a8d8afac49"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdp-signer.ts — the CDP-delegated capability provider, product-shaped.
|
|
3
|
+
*
|
|
4
|
+
* The end user has ALREADY signed in and minted a time-boxed delegation somewhere else (a dApp, the
|
|
5
|
+
* proof kit's OTP flow, …). This adapter holds Otto's developer-side CDP access and submits the
|
|
6
|
+
* user's OWN transactions under that delegation: user-authenticated, Otto-governed (the project policy
|
|
7
|
+
* is the fence), Otto-revocable. It never holds a user key, never mints a grant, and submits ONLY a
|
|
8
|
+
* plan that `assertPlanSignableBy` accepts for THIS user.
|
|
9
|
+
*
|
|
10
|
+
* RUNTIME privacy, not TypeScript privacy: the raw "send this unsigned transaction under the user's
|
|
11
|
+
* delegation" capability lives in a module-private WeakMap keyed by the rig object. A `CdpRig` value
|
|
12
|
+
* exposes only chain reads, delegation read-back and revocation; the signer reaches the raw send
|
|
13
|
+
* through the WeakMap, and its own submission path is a `#private` method. Nothing reachable from a
|
|
14
|
+
* rig or a signer can submit an unverified transaction.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle serialization lives on the RIG, not on the signer: `sendPlan`, its allowance clear, the
|
|
17
|
+
* signer's `revokeDeveloperSide` AND the rig's public `revoke` all run through one module-private lock
|
|
18
|
+
* per rig, so a revocation issued by anyone holding the rig can never race an in-flight plan or its
|
|
19
|
+
* cleanup. Revocation is a FAIL-CLOSED per-user transition: the lock is held through the raw revoke AND
|
|
20
|
+
* its read-back, and once revocation has been REQUESTED for a user on a rig, no plan for that user is
|
|
21
|
+
* ever submitted on that rig again — confirmed, unconfirmed or failed. A halted plan's clear is
|
|
22
|
+
* confirmed by an `allowance` read-back, never inferred from a receipt.
|
|
23
|
+
*
|
|
24
|
+
* Nonce, gas and serialization are adapter-owned (spec §6): the unsigned EIP-1559 transaction is
|
|
25
|
+
* assembled here from chain reads and handed to CDP to sign and broadcast.
|
|
26
|
+
*/
|
|
27
|
+
import { type Address, type Hex, type PublicClient } from 'viem';
|
|
28
|
+
import { type SealedWaitOptions } from './chain.js';
|
|
29
|
+
import type { SentStep } from './sent-step.js';
|
|
30
|
+
import { type SigningCapability, type VerifiedSwap } from './verify.js';
|
|
31
|
+
export declare const CDP_CAPABILITIES: readonly SigningCapability[];
|
|
32
|
+
export declare const CHAIN_ID: 8453;
|
|
33
|
+
export declare const NETWORK: "base";
|
|
34
|
+
export interface CdpDeveloperCredentials {
|
|
35
|
+
readonly apiKeyId: string;
|
|
36
|
+
readonly apiKeySecret: string;
|
|
37
|
+
readonly walletSecret: string;
|
|
38
|
+
}
|
|
39
|
+
export interface CdpEndUser {
|
|
40
|
+
readonly userId: string;
|
|
41
|
+
readonly address: Address;
|
|
42
|
+
}
|
|
43
|
+
/** The PUBLIC face of a rig: chain reads, delegation read-back, revocation. No submission surface. */
|
|
44
|
+
export interface CdpRig {
|
|
45
|
+
readonly pub: PublicClient;
|
|
46
|
+
readDelegation(userId: string): Promise<{
|
|
47
|
+
expiresAt: string;
|
|
48
|
+
} | undefined>;
|
|
49
|
+
/**
|
|
50
|
+
* Request revocation and confirm it by read-back, serialized with every plan on this rig. Resolves
|
|
51
|
+
* only once the grant no longer reads back; rejects if it still does. Either way the user is marked
|
|
52
|
+
* revocation-requested on this rig and no later plan for them is submitted (fail-closed).
|
|
53
|
+
*/
|
|
54
|
+
revoke(userId: string): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/** The raw capabilities a rig is built from. Held privately by this module once the rig exists. */
|
|
57
|
+
export interface CdpRawOperations {
|
|
58
|
+
readonly pub: PublicClient;
|
|
59
|
+
/** Sign + broadcast an unsigned serialized EIP-1559 transaction under the user's delegation. Returns the tx hash. */
|
|
60
|
+
submit(user: CdpEndUser, unsignedTransaction: Hex): Promise<Hex>;
|
|
61
|
+
revoke(userId: string): Promise<void>;
|
|
62
|
+
readDelegation(userId: string): Promise<{
|
|
63
|
+
expiresAt: string;
|
|
64
|
+
} | undefined>;
|
|
65
|
+
}
|
|
66
|
+
/** True once `rig.revoke(userId)` has been called on this rig, whatever it returned. */
|
|
67
|
+
export declare function revocationRequested(rig: CdpRig, userId: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Why a delegated plan halted — a closed vocabulary the signer sets from STRUCTURED facts only (the CDP
|
|
70
|
+
* `errorType`, an error's class, the receipt status, its own checks). Message text never selects it, so
|
|
71
|
+
* upstream text cannot steer the class a consumer reports.
|
|
72
|
+
*/
|
|
73
|
+
export type DelegatedHaltCause = 'policy_violation' | 'delegation_not_active' | 'artifact_no_longer_signable' | 'reverted' | 'receipt_not_sealed' | 'upstream_failure';
|
|
74
|
+
/**
|
|
75
|
+
* A delegated plan halted; `cause` is the structured class above, `allowanceCleared` says whether the
|
|
76
|
+
* router allowance is known to be zero again. The message is for the operator; a consumer that must not
|
|
77
|
+
* relay upstream text reads `cause` and the two fields only.
|
|
78
|
+
*/
|
|
79
|
+
export declare class DelegatedPlanHaltedError extends Error {
|
|
80
|
+
readonly failedStep: number;
|
|
81
|
+
readonly allowanceCleared: boolean;
|
|
82
|
+
readonly cause: DelegatedHaltCause;
|
|
83
|
+
readonly clearTransactionHash?: Hex | undefined;
|
|
84
|
+
constructor(message: string, failedStep: number, allowanceCleared: boolean, cause: DelegatedHaltCause, clearTransactionHash?: Hex | undefined);
|
|
85
|
+
}
|
|
86
|
+
/** Thrown by the signer itself once revocation was requested for the user on this rig (fail-closed). */
|
|
87
|
+
export declare class RevocationRequestedError extends Error {
|
|
88
|
+
constructor(userId: string);
|
|
89
|
+
}
|
|
90
|
+
/** Classify a failure from its STRUCTURE (class, `errorType`) — never from its message text. */
|
|
91
|
+
export declare function haltCauseOf(error: unknown): DelegatedHaltCause;
|
|
92
|
+
/**
|
|
93
|
+
* Build a rig from raw operations. The raw operations are captured into the module-private registry;
|
|
94
|
+
* the returned object exposes only reads and revocation. (Callers who hold a CDP client already have
|
|
95
|
+
* the raw capability — this function does not create it, it fences it.)
|
|
96
|
+
*/
|
|
97
|
+
export declare function createCdpRig(raw: CdpRawOperations): CdpRig;
|
|
98
|
+
/** Open a rig with Otto's developer credentials against Base mainnet. */
|
|
99
|
+
export declare function openCdpRig(credentials: CdpDeveloperCredentials, rpcUrl?: string): Promise<CdpRig>;
|
|
100
|
+
/**
|
|
101
|
+
* Revoke by `userId` alone, with read-back — usable even when the delegation request's response was
|
|
102
|
+
* lost (the grant may have committed remotely). Resolves only once the grant no longer reads back.
|
|
103
|
+
* This IS `rig.revoke`: serialized on the rig, lock held through the read-back, fail-closed afterwards.
|
|
104
|
+
*/
|
|
105
|
+
export declare function revokeDelegationByUserId(rig: CdpRig, userId: string): Promise<void>;
|
|
106
|
+
export declare class CdpDelegatedSigner {
|
|
107
|
+
#private;
|
|
108
|
+
readonly capabilities: readonly SigningCapability[];
|
|
109
|
+
readonly chainId: 8453;
|
|
110
|
+
constructor(rig: CdpRig, user: CdpEndUser, options?: {
|
|
111
|
+
readonly sealedWait?: SealedWaitOptions;
|
|
112
|
+
});
|
|
113
|
+
get address(): Address;
|
|
114
|
+
get user(): CdpEndUser;
|
|
115
|
+
/**
|
|
116
|
+
* `send_evm_transaction`, under delegation: only a plan `assertPlanSignableBy` accepts for THIS user,
|
|
117
|
+
* re-checked before every step AND again immediately before each hand-off to CDP (after the nonce /
|
|
118
|
+
* fee / gas round trips, so an artifact that expires during preparation is never submitted). The plan
|
|
119
|
+
* halts at the first step that does not succeed; after ANY failure a bounded `approve(spender, 0)` is
|
|
120
|
+
* attempted under the same policy and its real outcome — confirmed by an allowance read-back — is
|
|
121
|
+
* reported. A failed reset may leave a pre-existing allowance, so step 0 is not exempt.
|
|
122
|
+
*/
|
|
123
|
+
sendPlan(plan: VerifiedSwap): Promise<SentStep[]>;
|
|
124
|
+
/**
|
|
125
|
+
* The Otto-side kill switch, with read-back. Serialized on the rig (waits for any in-flight plan and
|
|
126
|
+
* its cleanup, holds the lock through the read-back) and fail-closed: no later plan for this user
|
|
127
|
+
* is submitted on this rig, whatever the read-back said.
|
|
128
|
+
*/
|
|
129
|
+
revokeDeveloperSide(): Promise<void>;
|
|
130
|
+
delegation(): Promise<{
|
|
131
|
+
expiresAt: string;
|
|
132
|
+
} | undefined>;
|
|
133
|
+
}
|