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,274 @@
|
|
|
1
|
+
import { type Address, type Hex } from 'viem';
|
|
2
|
+
import { type HyperliquidOrderAction, type HyperliquidSigningDeclaration, type PolymarketOrderArgs, type PolymarketOrderTypedData } from './execution-tool-definitions.js';
|
|
3
|
+
export { MAX_ARTIFACT_TTL_MS, artifactIdTimestampMs, createArtifactId } from './artifact-id.js';
|
|
4
|
+
export type AccountKind = 'eoa' | 'safe';
|
|
5
|
+
export type SigningCapability = 'send_evm_transaction' | 'submit_evm_batch' | 'sign_eip712' | 'sign_hyperliquid_l1';
|
|
6
|
+
export interface AccountProfile {
|
|
7
|
+
readonly kind: AccountKind;
|
|
8
|
+
readonly address: Address;
|
|
9
|
+
readonly chainId: number;
|
|
10
|
+
}
|
|
11
|
+
export interface AccountBinding extends AccountProfile {
|
|
12
|
+
readonly protocol_roles: {
|
|
13
|
+
readonly lifi: {
|
|
14
|
+
readonly sender: Address;
|
|
15
|
+
readonly receiver: Address;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface EvmExecutionStep {
|
|
20
|
+
readonly to: Address;
|
|
21
|
+
readonly value: string;
|
|
22
|
+
readonly data: Hex;
|
|
23
|
+
readonly kind: 'approve_reset' | 'approve' | 'call';
|
|
24
|
+
}
|
|
25
|
+
export interface EvmExecutionPlan {
|
|
26
|
+
readonly kind: 'evm_execution_plan';
|
|
27
|
+
readonly chain_id: number;
|
|
28
|
+
readonly steps: readonly EvmExecutionStep[];
|
|
29
|
+
}
|
|
30
|
+
/** The EVM-plan payload variant, shared by the swap and bridge rails. */
|
|
31
|
+
export type ExecutionPayload = EvmExecutionPlan;
|
|
32
|
+
/**
|
|
33
|
+
* The Polymarket payload variant: order args pre-signing plus the exact EIP-712 payload and digest
|
|
34
|
+
* the caller's own signer will sign. No signature, no CLOB credential, no collateral movement.
|
|
35
|
+
*/
|
|
36
|
+
export interface PolymarketOrderPayload {
|
|
37
|
+
readonly kind: 'polymarket_order_v2';
|
|
38
|
+
readonly chain_id: number;
|
|
39
|
+
readonly exchange: Address;
|
|
40
|
+
readonly neg_risk: boolean;
|
|
41
|
+
readonly tick_size: string;
|
|
42
|
+
readonly order_type: 'GTC';
|
|
43
|
+
readonly order: PolymarketOrderArgs;
|
|
44
|
+
readonly eip712: PolymarketOrderTypedData;
|
|
45
|
+
readonly order_hash: Hex;
|
|
46
|
+
}
|
|
47
|
+
export interface ExecutionAssertion {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly result: 'passed';
|
|
50
|
+
}
|
|
51
|
+
export interface ExecutionSubmission {
|
|
52
|
+
readonly method: SigningCapability;
|
|
53
|
+
readonly chain_id: number;
|
|
54
|
+
readonly artifact_id: string;
|
|
55
|
+
readonly payload_digest: Hex;
|
|
56
|
+
/** Commitment over every envelope field except itself, including valid_until. */
|
|
57
|
+
readonly artifact_digest: Hex;
|
|
58
|
+
readonly adapter_owned_fields: readonly ['nonce', 'gas', 'serialization'];
|
|
59
|
+
readonly client_ref?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface ExecutionEnvelope {
|
|
62
|
+
readonly schema_version: '1';
|
|
63
|
+
readonly artifact_id: string;
|
|
64
|
+
readonly rail: 'lifi';
|
|
65
|
+
readonly operation: 'swap';
|
|
66
|
+
readonly intent_digest: Hex;
|
|
67
|
+
readonly account_binding: AccountBinding;
|
|
68
|
+
readonly required_capabilities: readonly SigningCapability[];
|
|
69
|
+
readonly payload: ExecutionPayload;
|
|
70
|
+
readonly fee_attribution: {
|
|
71
|
+
readonly mechanism: 'lifi_integrator_fee';
|
|
72
|
+
readonly config_version: string;
|
|
73
|
+
};
|
|
74
|
+
readonly replay_disclosure: {
|
|
75
|
+
readonly artifact_role: 'construction_aid';
|
|
76
|
+
readonly freshness_boundary: 'valid_until';
|
|
77
|
+
readonly onchain_replay_boundary: 'signer_account_nonce';
|
|
78
|
+
readonly tool_guarantees_single_execution: false;
|
|
79
|
+
};
|
|
80
|
+
readonly assertions: readonly ExecutionAssertion[];
|
|
81
|
+
readonly valid_until: string;
|
|
82
|
+
readonly submission: ExecutionSubmission;
|
|
83
|
+
}
|
|
84
|
+
export interface BridgeAccountBinding extends AccountProfile {
|
|
85
|
+
readonly protocol_roles: {
|
|
86
|
+
readonly lifi: {
|
|
87
|
+
readonly sender: Address;
|
|
88
|
+
/** Where the destination chain mints. For a Safe this is caller-declared, never derived. */
|
|
89
|
+
readonly receiver: Address;
|
|
90
|
+
readonly source_chain_id: number;
|
|
91
|
+
readonly destination_chain_id: number;
|
|
92
|
+
readonly sending_asset: Address;
|
|
93
|
+
readonly receiving_asset: Address;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export interface BridgeExecutionEnvelope extends Omit<ExecutionEnvelope, 'operation' | 'account_binding'> {
|
|
98
|
+
readonly operation: 'bridge';
|
|
99
|
+
readonly account_binding: BridgeAccountBinding;
|
|
100
|
+
}
|
|
101
|
+
export interface PolymarketAccountBinding extends AccountProfile {
|
|
102
|
+
readonly protocol_roles: {
|
|
103
|
+
readonly polymarket: {
|
|
104
|
+
/** Source of funds for the order; inside the signed struct (spec §4). */
|
|
105
|
+
readonly maker: Address;
|
|
106
|
+
/** Who signs the EIP-712 order; inside the signed struct. */
|
|
107
|
+
readonly signer: Address;
|
|
108
|
+
/** Who holds the collateral. For the v1 EOA profile all three are the same account. */
|
|
109
|
+
readonly funder: Address;
|
|
110
|
+
readonly exchange: Address;
|
|
111
|
+
readonly token_id: string;
|
|
112
|
+
readonly signature_type: 0;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export interface PolymarketExecutionSubmission {
|
|
117
|
+
readonly method: 'sign_eip712';
|
|
118
|
+
readonly chain_id: number;
|
|
119
|
+
readonly artifact_id: string;
|
|
120
|
+
readonly payload_digest: Hex;
|
|
121
|
+
readonly artifact_digest: Hex;
|
|
122
|
+
readonly adapter_owned_fields: readonly ['signature', 'clob_post'];
|
|
123
|
+
readonly client_ref?: string;
|
|
124
|
+
}
|
|
125
|
+
export interface PolymarketExecutionEnvelope extends Omit<ExecutionEnvelope, 'rail' | 'operation' | 'account_binding' | 'payload' | 'fee_attribution' | 'replay_disclosure' | 'required_capabilities' | 'submission'> {
|
|
126
|
+
readonly rail: 'polymarket';
|
|
127
|
+
readonly operation: 'pm_order';
|
|
128
|
+
readonly account_binding: PolymarketAccountBinding;
|
|
129
|
+
readonly required_capabilities: readonly ['sign_eip712'];
|
|
130
|
+
readonly payload: PolymarketOrderPayload;
|
|
131
|
+
readonly fee_attribution: {
|
|
132
|
+
readonly mechanism: 'polymarket_builder_code';
|
|
133
|
+
readonly config_version: string;
|
|
134
|
+
};
|
|
135
|
+
readonly replay_disclosure: {
|
|
136
|
+
readonly artifact_role: 'construction_aid';
|
|
137
|
+
readonly freshness_boundary: 'valid_until';
|
|
138
|
+
/** An order struct carries its own salt, so the exchange's order hash is the replay boundary. */
|
|
139
|
+
readonly onchain_replay_boundary: 'exchange_order_hash';
|
|
140
|
+
readonly tool_guarantees_single_execution: false;
|
|
141
|
+
};
|
|
142
|
+
readonly submission: PolymarketExecutionSubmission;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The Hyperliquid payload variant: the canonical L1 order action plus the declared EIP-712 signing
|
|
146
|
+
* fields the caller's own signer needs. No nonce, no signature, no derived agent wallet.
|
|
147
|
+
*/
|
|
148
|
+
export interface HyperliquidL1ActionPayload {
|
|
149
|
+
readonly kind: 'hyperliquid_l1_action';
|
|
150
|
+
readonly chain_id: 1337;
|
|
151
|
+
readonly action: HyperliquidOrderAction;
|
|
152
|
+
readonly signing: HyperliquidSigningDeclaration;
|
|
153
|
+
readonly vault_address: Address | null;
|
|
154
|
+
/** Constructor-owned expiry in unix ms, equal to `valid_until` and inside the signed digest. */
|
|
155
|
+
readonly expires_after: number;
|
|
156
|
+
}
|
|
157
|
+
export interface HyperliquidAccountBinding extends AccountProfile {
|
|
158
|
+
readonly protocol_roles: {
|
|
159
|
+
readonly hyperliquid: {
|
|
160
|
+
readonly master: Address;
|
|
161
|
+
readonly trading_signer: Address;
|
|
162
|
+
readonly vault_address: Address | null;
|
|
163
|
+
readonly asset: string;
|
|
164
|
+
readonly asset_id: number;
|
|
165
|
+
readonly sz_decimals: number;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export interface HyperliquidExecutionSubmission {
|
|
170
|
+
readonly method: 'sign_hyperliquid_l1';
|
|
171
|
+
readonly chain_id: 1337;
|
|
172
|
+
readonly artifact_id: string;
|
|
173
|
+
readonly payload_digest: Hex;
|
|
174
|
+
readonly artifact_digest: Hex;
|
|
175
|
+
readonly adapter_owned_fields: readonly ['nonce', 'signature', 'exchange_post'];
|
|
176
|
+
readonly client_ref?: string;
|
|
177
|
+
}
|
|
178
|
+
export interface HyperliquidExecutionEnvelope extends Omit<ExecutionEnvelope, 'rail' | 'operation' | 'account_binding' | 'payload' | 'fee_attribution' | 'replay_disclosure' | 'required_capabilities' | 'submission'> {
|
|
179
|
+
readonly rail: 'hyperliquid';
|
|
180
|
+
readonly operation: 'perp_order';
|
|
181
|
+
readonly account_binding: HyperliquidAccountBinding;
|
|
182
|
+
readonly required_capabilities: readonly ['sign_hyperliquid_l1'];
|
|
183
|
+
readonly payload: HyperliquidL1ActionPayload;
|
|
184
|
+
readonly fee_attribution: {
|
|
185
|
+
readonly mechanism: 'hyperliquid_builder_fee';
|
|
186
|
+
readonly config_version: string;
|
|
187
|
+
/** Ruled pending state; the `{b, f}` assert lands with the Step-0 reversal (spec §7.2). */
|
|
188
|
+
readonly status: 'pending_step0_builder_fee_approval';
|
|
189
|
+
};
|
|
190
|
+
readonly replay_disclosure: {
|
|
191
|
+
readonly artifact_role: 'construction_aid';
|
|
192
|
+
readonly freshness_boundary: 'valid_until';
|
|
193
|
+
/** HL's signed digest covers an adapter-chosen nonce AND the constructor's expiresAfter. */
|
|
194
|
+
readonly onchain_replay_boundary: 'signer_nonce_and_expires_after';
|
|
195
|
+
readonly tool_guarantees_single_execution: false;
|
|
196
|
+
};
|
|
197
|
+
readonly submission: HyperliquidExecutionSubmission;
|
|
198
|
+
}
|
|
199
|
+
/** Any artifact this MCP produces. Discriminated by `operation`. */
|
|
200
|
+
export type ExecutionArtifact = ExecutionEnvelope | BridgeExecutionEnvelope | PolymarketExecutionEnvelope | HyperliquidExecutionEnvelope;
|
|
201
|
+
/** Envelope shape before the self-referential artifact digest is stamped into submission. */
|
|
202
|
+
export type UncommittedExecutionEnvelope = Omit<ExecutionEnvelope, 'submission'> & {
|
|
203
|
+
readonly submission: Omit<ExecutionSubmission, 'artifact_digest'> & {
|
|
204
|
+
readonly artifact_digest?: Hex;
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
/** Bridge envelope before the self-referential artifact digest is stamped into submission. */
|
|
208
|
+
export type UncommittedBridgeExecutionEnvelope = Omit<BridgeExecutionEnvelope, 'submission'> & {
|
|
209
|
+
readonly submission: Omit<ExecutionSubmission, 'artifact_digest'> & {
|
|
210
|
+
readonly artifact_digest?: Hex;
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
/** Polymarket envelope before the self-referential artifact digest is stamped into submission. */
|
|
214
|
+
export type UncommittedPolymarketExecutionEnvelope = Omit<PolymarketExecutionEnvelope, 'submission'> & {
|
|
215
|
+
readonly submission: Omit<PolymarketExecutionSubmission, 'artifact_digest'> & {
|
|
216
|
+
readonly artifact_digest?: Hex;
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
/** Hyperliquid envelope before the self-referential artifact digest is stamped into submission. */
|
|
220
|
+
export type UncommittedHyperliquidExecutionEnvelope = Omit<HyperliquidExecutionEnvelope, 'submission'> & {
|
|
221
|
+
readonly submission: Omit<HyperliquidExecutionSubmission, 'artifact_digest'> & {
|
|
222
|
+
readonly artifact_digest?: Hex;
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
/** Any envelope shape awaiting its whole-envelope commitment. */
|
|
226
|
+
export type UncommittedExecutionArtifact = UncommittedExecutionEnvelope | UncommittedBridgeExecutionEnvelope | UncommittedPolymarketExecutionEnvelope | UncommittedHyperliquidExecutionEnvelope;
|
|
227
|
+
export declare function canonicalJson(value: unknown): string;
|
|
228
|
+
export declare function executionDigest(value: unknown): Hex;
|
|
229
|
+
export declare function accountBinding(profile: AccountProfile): AccountBinding;
|
|
230
|
+
export declare function bridgeAccountBinding(profile: AccountProfile, route: {
|
|
231
|
+
readonly destinationRecipient: Address;
|
|
232
|
+
readonly destinationChainId: number;
|
|
233
|
+
readonly sendingAsset: Address;
|
|
234
|
+
readonly receivingAsset: Address;
|
|
235
|
+
}): BridgeAccountBinding;
|
|
236
|
+
/**
|
|
237
|
+
* The v1 Polymarket profile is a single EOA in all three roles: it signs the order, it is the maker
|
|
238
|
+
* of record inside the signed struct, and it funds the collateral. A Safe profile would need a
|
|
239
|
+
* signature type (POLY_GNOSIS_SAFE vs POLY_1271) that spec §13 leaves open, so it is refused rather
|
|
240
|
+
* than guessed — which is why this factory takes no separate signer or funder.
|
|
241
|
+
*/
|
|
242
|
+
export declare function polymarketAccountBinding(profile: AccountProfile, order: {
|
|
243
|
+
readonly exchange: Address;
|
|
244
|
+
readonly tokenId: string;
|
|
245
|
+
}): PolymarketAccountBinding;
|
|
246
|
+
/**
|
|
247
|
+
* The v1 Hyperliquid profile binds the master account to the signing EOA. `tradingSigner` may be a
|
|
248
|
+
* DIFFERENT address — the user's own approved API wallet — and is taken as a declared fact: Otto never
|
|
249
|
+
* derives an agent key, which is the custody line spec §11 draws around
|
|
250
|
+
* `trade-execution-agent/src/lib/hyperliquid-client.ts:558`. The vault address is likewise declared,
|
|
251
|
+
* never inferred from the account.
|
|
252
|
+
*/
|
|
253
|
+
export declare function hyperliquidAccountBinding(profile: AccountProfile, market: {
|
|
254
|
+
readonly tradingSigner?: Address;
|
|
255
|
+
readonly vaultAddress?: Address;
|
|
256
|
+
readonly asset: string;
|
|
257
|
+
readonly assetId: number;
|
|
258
|
+
readonly szDecimals: number;
|
|
259
|
+
}): HyperliquidAccountBinding;
|
|
260
|
+
export declare function assertArtifactAccountBinding(artifact: ExecutionArtifact, expected: AccountProfile): void;
|
|
261
|
+
export declare function assertArtifactPayloadDigest(artifact: ExecutionArtifact): void;
|
|
262
|
+
/**
|
|
263
|
+
* Commitment over the whole envelope except the commitment field itself. `valid_until` sits
|
|
264
|
+
* inside it, so freshness is bound to the artifact rather than being an editable side field.
|
|
265
|
+
*/
|
|
266
|
+
export declare function artifactCommitmentDigest(artifact: UncommittedExecutionArtifact): Hex;
|
|
267
|
+
/**
|
|
268
|
+
* Fail-closed commitment check: payload digest, whole-envelope commitment, and the
|
|
269
|
+
* constructor-owned freshness bound derived from the artifact id.
|
|
270
|
+
*/
|
|
271
|
+
export declare function assertArtifactCommitment(artifact: ExecutionArtifact): void;
|
|
272
|
+
/** Strict adapter-side validation, including every envelope cross-field relationship. */
|
|
273
|
+
export declare function assertArtifactEnvelope(artifact: unknown): ExecutionArtifact;
|
|
274
|
+
export declare function assertArtifactFresh(artifact: ExecutionArtifact, nowMs?: number): void;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { getAddress, keccak256, stringToHex } from 'viem';
|
|
2
|
+
import { isArtifactFreshnessWellFormed } from './artifact-id.js';
|
|
3
|
+
import { refuse } from './execution-errors.js';
|
|
4
|
+
import { EXECUTION_ARTIFACT_SCHEMA, } from './execution-tool-definitions.js';
|
|
5
|
+
export { MAX_ARTIFACT_TTL_MS, artifactIdTimestampMs, createArtifactId } from './artifact-id.js';
|
|
6
|
+
function canonicalize(value) {
|
|
7
|
+
if (value === null || typeof value !== 'object')
|
|
8
|
+
return value;
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value.map((item) => canonicalize(item));
|
|
11
|
+
const source = value;
|
|
12
|
+
const sorted = {};
|
|
13
|
+
for (const key of Object.keys(source).sort()) {
|
|
14
|
+
if (source[key] !== undefined)
|
|
15
|
+
sorted[key] = canonicalize(source[key]);
|
|
16
|
+
}
|
|
17
|
+
return sorted;
|
|
18
|
+
}
|
|
19
|
+
export function canonicalJson(value) {
|
|
20
|
+
return JSON.stringify(canonicalize(value));
|
|
21
|
+
}
|
|
22
|
+
export function executionDigest(value) {
|
|
23
|
+
return keccak256(stringToHex(canonicalJson(value)));
|
|
24
|
+
}
|
|
25
|
+
export function accountBinding(profile) {
|
|
26
|
+
const address = getAddress(profile.address);
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
kind: profile.kind,
|
|
29
|
+
address,
|
|
30
|
+
chainId: profile.chainId,
|
|
31
|
+
protocol_roles: Object.freeze({
|
|
32
|
+
lifi: Object.freeze({ sender: address, receiver: address }),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function bridgeAccountBinding(profile, route) {
|
|
37
|
+
const address = getAddress(profile.address);
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
kind: profile.kind,
|
|
40
|
+
address,
|
|
41
|
+
chainId: profile.chainId,
|
|
42
|
+
protocol_roles: Object.freeze({
|
|
43
|
+
lifi: Object.freeze({
|
|
44
|
+
sender: address,
|
|
45
|
+
receiver: getAddress(route.destinationRecipient),
|
|
46
|
+
source_chain_id: profile.chainId,
|
|
47
|
+
destination_chain_id: route.destinationChainId,
|
|
48
|
+
sending_asset: getAddress(route.sendingAsset),
|
|
49
|
+
receiving_asset: getAddress(route.receivingAsset),
|
|
50
|
+
}),
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The v1 Polymarket profile is a single EOA in all three roles: it signs the order, it is the maker
|
|
56
|
+
* of record inside the signed struct, and it funds the collateral. A Safe profile would need a
|
|
57
|
+
* signature type (POLY_GNOSIS_SAFE vs POLY_1271) that spec §13 leaves open, so it is refused rather
|
|
58
|
+
* than guessed — which is why this factory takes no separate signer or funder.
|
|
59
|
+
*/
|
|
60
|
+
export function polymarketAccountBinding(profile, order) {
|
|
61
|
+
const address = getAddress(profile.address);
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
kind: profile.kind,
|
|
64
|
+
address,
|
|
65
|
+
chainId: profile.chainId,
|
|
66
|
+
protocol_roles: Object.freeze({
|
|
67
|
+
polymarket: Object.freeze({
|
|
68
|
+
maker: address,
|
|
69
|
+
signer: address,
|
|
70
|
+
funder: address,
|
|
71
|
+
exchange: getAddress(order.exchange),
|
|
72
|
+
token_id: order.tokenId,
|
|
73
|
+
signature_type: 0,
|
|
74
|
+
}),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The v1 Hyperliquid profile binds the master account to the signing EOA. `tradingSigner` may be a
|
|
80
|
+
* DIFFERENT address — the user's own approved API wallet — and is taken as a declared fact: Otto never
|
|
81
|
+
* derives an agent key, which is the custody line spec §11 draws around
|
|
82
|
+
* `trade-execution-agent/src/lib/hyperliquid-client.ts:558`. The vault address is likewise declared,
|
|
83
|
+
* never inferred from the account.
|
|
84
|
+
*/
|
|
85
|
+
export function hyperliquidAccountBinding(profile, market) {
|
|
86
|
+
const address = getAddress(profile.address);
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
kind: profile.kind,
|
|
89
|
+
address,
|
|
90
|
+
chainId: profile.chainId,
|
|
91
|
+
protocol_roles: Object.freeze({
|
|
92
|
+
hyperliquid: Object.freeze({
|
|
93
|
+
master: address,
|
|
94
|
+
trading_signer: market.tradingSigner ? getAddress(market.tradingSigner) : address,
|
|
95
|
+
vault_address: market.vaultAddress ? getAddress(market.vaultAddress) : null,
|
|
96
|
+
asset: market.asset,
|
|
97
|
+
asset_id: market.assetId,
|
|
98
|
+
sz_decimals: market.szDecimals,
|
|
99
|
+
}),
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function assertArtifactAccountBinding(artifact, expected) {
|
|
104
|
+
const actual = artifact.account_binding;
|
|
105
|
+
if (actual.kind !== expected.kind ||
|
|
106
|
+
actual.chainId !== expected.chainId ||
|
|
107
|
+
getAddress(actual.address) !== getAddress(expected.address)) {
|
|
108
|
+
refuse('ACCOUNT_BINDING_MISMATCH', 'execution artifact is bound to a different account');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function assertArtifactPayloadDigest(artifact) {
|
|
112
|
+
if (artifact.submission.artifact_id !== artifact.artifact_id ||
|
|
113
|
+
executionDigest(artifact.payload) !== artifact.submission.payload_digest) {
|
|
114
|
+
refuse('DIGEST_MISMATCH', 'execution artifact payload digest does not match its payload');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Commitment over the whole envelope except the commitment field itself. `valid_until` sits
|
|
119
|
+
* inside it, so freshness is bound to the artifact rather than being an editable side field.
|
|
120
|
+
*/
|
|
121
|
+
export function artifactCommitmentDigest(artifact) {
|
|
122
|
+
const { submission, ...envelope } = artifact;
|
|
123
|
+
const { artifact_digest: _committed, ...committedSubmission } = submission;
|
|
124
|
+
return executionDigest({ ...envelope, submission: committedSubmission });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Fail-closed commitment check: payload digest, whole-envelope commitment, and the
|
|
128
|
+
* constructor-owned freshness bound derived from the artifact id.
|
|
129
|
+
*/
|
|
130
|
+
export function assertArtifactCommitment(artifact) {
|
|
131
|
+
assertArtifactPayloadDigest(artifact);
|
|
132
|
+
if (artifactCommitmentDigest(artifact) !== artifact.submission.artifact_digest) {
|
|
133
|
+
refuse('DIGEST_MISMATCH', 'execution artifact digest does not commit to this envelope');
|
|
134
|
+
}
|
|
135
|
+
if (!isArtifactFreshnessWellFormed(artifact.artifact_id, artifact.valid_until)) {
|
|
136
|
+
refuse('ARTIFACT_INVALID', 'execution artifact expiry is outside the constructor bound');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Strict adapter-side validation, including every envelope cross-field relationship. */
|
|
140
|
+
export function assertArtifactEnvelope(artifact) {
|
|
141
|
+
const parsed = EXECUTION_ARTIFACT_SCHEMA.safeParse(artifact);
|
|
142
|
+
if (!parsed.success) {
|
|
143
|
+
return refuse('ARTIFACT_INVALID', 'execution artifact failed strict envelope validation');
|
|
144
|
+
}
|
|
145
|
+
const envelope = parsed.data;
|
|
146
|
+
assertArtifactCommitment(envelope);
|
|
147
|
+
return envelope;
|
|
148
|
+
}
|
|
149
|
+
export function assertArtifactFresh(artifact, nowMs = Date.now()) {
|
|
150
|
+
// Fail-closed first: the commitment check binds valid_until to the artifact and rejects any
|
|
151
|
+
// expiry outside the construction-anchored ceiling, so the deadline below is constructor-owned.
|
|
152
|
+
assertArtifactCommitment(artifact);
|
|
153
|
+
if (Date.parse(artifact.valid_until) <= nowMs) {
|
|
154
|
+
refuse('STALE_ARTIFACT', 'execution artifact has expired');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=execution-types.js.map
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** verify.ts — re-exported from `./vendor/otto-intel-mcp/adapter-index.js` (moved 2026-08-29; see that entry's header). */
|
|
2
|
+
export { VERIFY_CHECK_IDS, assertPlanSignableBy, assertVerifiedPlanIntact, defaultAdapterPolicy, isDeepFrozen, isVerifierOutput, verifiedPlanIdentity, verifySwapEnvelope, type AdapterPolicy, type AttributionPolicy, type PlanIdentity, type SignerIdentity, type SigningCapability, type SwapIntent, type VerifiedCheck, type VerifiedStep, type VerifiedSwap, type VerifyExpectations, } from './vendor/otto-intel-mcp/adapter-index.js';
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** verify.ts — re-exported from `./vendor/otto-intel-mcp/adapter-index.js` (moved 2026-08-29; see that entry's header). */
|
|
2
|
+
export { VERIFY_CHECK_IDS, assertPlanSignableBy, assertVerifiedPlanIntact, defaultAdapterPolicy, isDeepFrozen, isVerifierOutput, verifiedPlanIdentity, verifySwapEnvelope, } from './vendor/otto-intel-mcp/adapter-index.js';
|
|
3
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402-table.ts — the expected-requirements table for x402 challenges (docs/0817_x402_leg_expected_requirements_table.md).
|
|
3
|
+
*
|
|
4
|
+
* In Model B the user's own adapter signs x402 payments. Before a challenge reaches that signer, every
|
|
5
|
+
* field the user is about to authorize payment against is checked against THIS static table — never
|
|
6
|
+
* derived from the incoming challenge. The failure this prevents: an altered or substituted 402 redirecting
|
|
7
|
+
* the payment to a substituted recipient, an inflated amount, or a long-lived authorization.
|
|
8
|
+
*
|
|
9
|
+
* Rules (0817 §2):
|
|
10
|
+
* - exact match, one canonical URL normalization (scheme+host lowercase, no trailing slash);
|
|
11
|
+
* addresses compared checksummed; amounts as BigInt atomic units, never floats.
|
|
12
|
+
* - unknown resource ⇒ refuse. The table is an allowlist.
|
|
13
|
+
* - the table is data, the check is code: adding a row never touches validation.
|
|
14
|
+
* - P3: rows are exact values — the type admits no pattern, prefix or regex field.
|
|
15
|
+
* - P4: fail-closed — a validation failure throws; nothing reaches the signer.
|
|
16
|
+
* - the validated requirement is returned as an IMMUTABLE snapshot, and the signer revalidates the
|
|
17
|
+
* document against the table again immediately before it signs (see eoa-signer.ts): validation
|
|
18
|
+
* and hand-off are two moments, and nothing that happens in between is trusted.
|
|
19
|
+
*/
|
|
20
|
+
import { type Address } from 'viem';
|
|
21
|
+
export declare const X402_TABLE_VERSION = "x402-req-2026-08-28-v4";
|
|
22
|
+
export interface X402ExpectedRow {
|
|
23
|
+
/** Exact resource URL after canonical normalization. No patterns. */
|
|
24
|
+
readonly resource: string;
|
|
25
|
+
readonly scheme: 'exact';
|
|
26
|
+
readonly network: 'eip155:8453';
|
|
27
|
+
/** Exact token contract (USDC on Base), never a symbol. */
|
|
28
|
+
readonly asset: Address;
|
|
29
|
+
/** Exact recipient, checksummed. */
|
|
30
|
+
readonly pay_to: Address;
|
|
31
|
+
/** The listed price in atomic units; the challenge amount must EQUAL it (fixed-price rows). */
|
|
32
|
+
readonly amount_atomic: string;
|
|
33
|
+
/** The EXACT authorization window the challenge must ask for (EIP-3009 validBefore − now). Exact, like every other field. */
|
|
34
|
+
readonly max_timeout_seconds: number;
|
|
35
|
+
/** The EIP-712 domain the signer will sign under; pinned so a challenge cannot move the domain. */
|
|
36
|
+
readonly extra: {
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly version: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface X402Table {
|
|
42
|
+
readonly table_version: string;
|
|
43
|
+
readonly rows: readonly X402ExpectedRow[];
|
|
44
|
+
}
|
|
45
|
+
/** Otto's published x402 revenue recipient of record (captured live 2026-08-25/26 from all four challenges). */
|
|
46
|
+
export declare const OTTO_X402_PAY_TO: `0x${string}`;
|
|
47
|
+
export declare const BASE_USDC: `0x${string}`;
|
|
48
|
+
/**
|
|
49
|
+
* Rows whose SERVED price is changing on the storefront (catalog.ts already carries the new price)
|
|
50
|
+
* but whose live capture in test/fixtures has not been re-captured yet. This table is an EXACT
|
|
51
|
+
* match against a signed live 402, so it can only move AFTER the backend deploys the new price and
|
|
52
|
+
* the fixture is re-captured — between those two moments the adapter REFUSES the row (fail-closed,
|
|
53
|
+
* never an overpay). `scripts/audit/client-price-caps.mjs` downgrades these rows to a warning until
|
|
54
|
+
* the re-capture PR clears them; it hard-fails any other table/catalog mismatch, and it fails a
|
|
55
|
+
* declaration that lingers after the table already matches (so this list cannot go stale).
|
|
56
|
+
* EMPTY = every row's table amount equals its live capture and the catalog price.
|
|
57
|
+
*/
|
|
58
|
+
export declare const PENDING_RECAPTURE: readonly string[];
|
|
59
|
+
/** The no-argument intelligence resources the proof may pay for ($0.001 reads; /base-season $0.002). Every row has a live capture in test/fixtures. */
|
|
60
|
+
export declare const X402_TABLE: X402Table;
|
|
61
|
+
export declare const X402_REFUSAL_CODES: readonly ["X402_MALFORMED", "X402_UNKNOWN_RESOURCE", "X402_SCHEME_MISMATCH", "X402_NETWORK_MISMATCH", "X402_ASSET_MISMATCH", "X402_PAYTO_MISMATCH", "X402_AMOUNT_EXCEEDED", "X402_TIMEOUT_EXCEEDED", "X402_DOMAIN_MISMATCH", "X402_SPEND_LIMIT_EXCEEDED"];
|
|
62
|
+
export type X402RefusalCode = (typeof X402_REFUSAL_CODES)[number];
|
|
63
|
+
export declare class X402Refusal extends Error {
|
|
64
|
+
readonly code: X402RefusalCode;
|
|
65
|
+
readonly field: string;
|
|
66
|
+
readonly expected: string;
|
|
67
|
+
readonly actual: string;
|
|
68
|
+
readonly tableVersion: string;
|
|
69
|
+
readonly errorType: X402RefusalCode;
|
|
70
|
+
constructor(code: X402RefusalCode, field: string, expected: string, actual: string, tableVersion: string);
|
|
71
|
+
}
|
|
72
|
+
export declare function canonicalResourceUrl(raw: string): string;
|
|
73
|
+
export interface PaymentRequirementLike {
|
|
74
|
+
readonly scheme: string;
|
|
75
|
+
readonly network: string;
|
|
76
|
+
readonly amount: string;
|
|
77
|
+
readonly asset: string;
|
|
78
|
+
readonly payTo: string;
|
|
79
|
+
readonly maxTimeoutSeconds: number;
|
|
80
|
+
readonly extra: {
|
|
81
|
+
readonly name: string;
|
|
82
|
+
readonly version: string;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export interface ValidatedX402Challenge {
|
|
86
|
+
readonly row: X402ExpectedRow;
|
|
87
|
+
/** An IMMUTABLE canonical snapshot of the accepted requirement's bound fields, taken at validation time. */
|
|
88
|
+
readonly requirement: PaymentRequirementLike;
|
|
89
|
+
readonly requirementIndex: number;
|
|
90
|
+
readonly amount: bigint;
|
|
91
|
+
}
|
|
92
|
+
/** True iff `requirement` carries exactly the row's bound fields (checksummed addresses, exact amount, window ≤ ceiling, pinned domain). */
|
|
93
|
+
export declare function requirementMatchesRow(requirement: unknown, row: X402ExpectedRow): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Validate an x402 v2 PaymentRequired document against the table. Returns the single accepted
|
|
96
|
+
* requirement that matches its row exactly (as an immutable snapshot), or throws the refusal naming
|
|
97
|
+
* the first mismatched field.
|
|
98
|
+
*/
|
|
99
|
+
export declare function validateX402Challenge(paymentRequired: unknown, table?: X402Table): ValidatedX402Challenge;
|