otto-intel-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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/adapter/cdp-signer.d.ts +133 -0
- package/dist/adapter/cdp-signer.js +356 -0
- package/dist/adapter/cdp-signer.js.map +1 -0
- package/dist/adapter/chain.d.ts +36 -0
- package/dist/adapter/chain.js +65 -0
- package/dist/adapter/chain.js.map +1 -0
- package/dist/adapter/erc20.d.ts +39 -0
- package/dist/adapter/erc20.js +17 -0
- package/dist/adapter/erc20.js.map +1 -0
- package/dist/adapter/lifi-decode.d.ts +52 -0
- package/dist/adapter/lifi-decode.js +149 -0
- package/dist/adapter/lifi-decode.js.map +1 -0
- package/dist/adapter/refusal.d.ts +21 -0
- package/dist/adapter/refusal.js +55 -0
- package/dist/adapter/refusal.js.map +1 -0
- package/dist/adapter/sent-step.d.ts +15 -0
- package/dist/adapter/sent-step.js +6 -0
- package/dist/adapter/sent-step.js.map +1 -0
- package/dist/adapter/verify.d.ts +149 -0
- package/dist/adapter/verify.js +432 -0
- package/dist/adapter/verify.js.map +1 -0
- package/dist/adapter-cdp-index.d.ts +6 -0
- package/dist/adapter-cdp-index.js +7 -0
- package/dist/adapter-index.d.ts +15 -0
- package/dist/adapter-index.js +15 -0
- package/dist/artifact-id.d.ts +16 -0
- package/dist/artifact-id.js +60 -0
- package/dist/boot-redaction.d.ts +16 -0
- package/dist/boot-redaction.js +54 -0
- package/dist/data-source.d.ts +7 -0
- package/dist/data-source.js +2 -0
- package/dist/errors.d.ts +70 -0
- package/dist/errors.js +221 -0
- package/dist/execution-config.d.ts +232 -0
- package/dist/execution-config.js +443 -0
- package/dist/execution-delegated-definition.d.ts +165 -0
- package/dist/execution-delegated-definition.js +116 -0
- package/dist/execution-delegation-admin-definition.d.ts +208 -0
- package/dist/execution-delegation-admin-definition.js +170 -0
- package/dist/execution-delegation-admin.d.ts +74 -0
- package/dist/execution-delegation-admin.js +290 -0
- package/dist/execution-delegation-policy.d.ts +257 -0
- package/dist/execution-delegation-policy.js +279 -0
- package/dist/execution-delegation.d.ts +190 -0
- package/dist/execution-delegation.js +545 -0
- package/dist/execution-errors.d.ts +9 -0
- package/dist/execution-errors.js +128 -0
- package/dist/execution-index.d.ts +17 -0
- package/dist/execution-index.js +16 -0
- package/dist/execution-intent.d.ts +14 -0
- package/dist/execution-intent.js +36 -0
- package/dist/execution-registration.d.ts +113 -0
- package/dist/execution-registration.js +161 -0
- package/dist/execution-tool-definitions.d.ts +1103 -0
- package/dist/execution-tool-definitions.js +1051 -0
- package/dist/execution-tools.d.ts +85 -0
- package/dist/execution-tools.js +690 -0
- package/dist/execution-types.d.ts +274 -0
- package/dist/execution-types.js +157 -0
- package/dist/hyperliquid-info-client.d.ts +31 -0
- package/dist/hyperliquid-info-client.js +121 -0
- package/dist/hyperliquid-order-assertions.d.ts +89 -0
- package/dist/hyperliquid-order-assertions.js +325 -0
- package/dist/hyperliquid-order-builder.d.ts +47 -0
- package/dist/hyperliquid-order-builder.js +55 -0
- package/dist/lifi-bridge-assertions.d.ts +49 -0
- package/dist/lifi-bridge-assertions.js +303 -0
- package/dist/lifi-execution-client.d.ts +74 -0
- package/dist/lifi-execution-client.js +165 -0
- package/dist/lifi-fee-assertions.d.ts +32 -0
- package/dist/lifi-fee-assertions.js +103 -0
- package/dist/lifi-swap-assertions.d.ts +32 -0
- package/dist/lifi-swap-assertions.js +214 -0
- package/dist/local-config.d.ts +9 -0
- package/dist/local-config.js +45 -0
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +44 -0
- package/dist/polymarket-clob-client.d.ts +73 -0
- package/dist/polymarket-clob-client.js +186 -0
- package/dist/polymarket-order-assertions.d.ts +60 -0
- package/dist/polymarket-order-assertions.js +273 -0
- package/dist/polymarket-order-builder.d.ts +43 -0
- package/dist/polymarket-order-builder.js +74 -0
- package/dist/prepared-artifacts.d.ts +45 -0
- package/dist/prepared-artifacts.js +92 -0
- package/dist/stdio-main.d.ts +2 -0
- package/dist/stdio-main.js +20 -0
- package/dist/tool-definitions.d.ts +20 -0
- package/dist/tool-definitions.js +162 -0
- package/dist/x402-read-source.d.ts +11 -0
- package/dist/x402-read-source.js +77 -0
- package/package.json +162 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { encodeFunctionData, getAddress, parseAbi } from 'viem';
|
|
3
|
+
import { attributedFeeAmount, HL_BUILDER_ATTRIBUTION_STATUS, HYPERLIQUID_L1_SIGNATURE_CHAIN_ID, isEnumeratedCctpBridgeRoute, reviewedLiFiFeeForwarders, loadExecutionServiceConfig, POLYMARKET_CHAIN_ID, } from './execution-config.js';
|
|
4
|
+
import { refuse } from './execution-errors.js';
|
|
5
|
+
import { createDelegationServicesFromEnv, } from './execution-delegation.js';
|
|
6
|
+
import { recordPreparedArtifact } from './prepared-artifacts.js';
|
|
7
|
+
import { swapIntentDigestInput } from './execution-intent.js';
|
|
8
|
+
import { BRIDGE_EXECUTION_ENVELOPE_SCHEMA, EXECUTION_ENVELOPE_SCHEMA, HYPERLIQUID_EXECUTION_ENVELOPE_SCHEMA, POLYMARKET_EXECUTION_ENVELOPE_SCHEMA, PREPARE_BRIDGE_INPUT_SCHEMA, PREPARE_PERP_ORDER_INPUT_SCHEMA, PREPARE_POLYMARKET_ORDER_INPUT_SCHEMA, PREPARE_SWAP_INPUT_SCHEMA, } from './execution-tool-definitions.js';
|
|
9
|
+
import { accountBinding, artifactCommitmentDigest, bridgeAccountBinding, createArtifactId, executionDigest, hyperliquidAccountBinding, polymarketAccountBinding, } from './execution-types.js';
|
|
10
|
+
import { RestHyperliquidInfoClient, } from './hyperliquid-info-client.js';
|
|
11
|
+
import { assertHyperliquidBracketCoherent, assertHyperliquidPriceOnGrid, assertHyperliquidSizeOnGrid, decodeAndAssertHyperliquidAction, hyperliquidSigningDeclaration, } from './hyperliquid-order-assertions.js';
|
|
12
|
+
import { buildUnsignedHyperliquidOrderAction, } from './hyperliquid-order-builder.js';
|
|
13
|
+
import { decodeAndAssertLiFiBridge } from './lifi-bridge-assertions.js';
|
|
14
|
+
import { RestLiFiQuoteClient } from './lifi-execution-client.js';
|
|
15
|
+
import { decodeAndAssertLiFiSwap } from './lifi-swap-assertions.js';
|
|
16
|
+
import { RestPolymarketClobClient } from './polymarket-clob-client.js';
|
|
17
|
+
import { assertPolymarketBuilderAttributionRuled, assertPolymarketPriceOnTick, decodeAndAssertPolymarketOrder, polymarketAtomicUnits, } from './polymarket-order-assertions.js';
|
|
18
|
+
import { buildUnsignedPolymarketOrder, } from './polymarket-order-builder.js';
|
|
19
|
+
const ERC20_APPROVE_ABI = parseAbi([
|
|
20
|
+
'function approve(address spender, uint256 amount) returns (bool)',
|
|
21
|
+
]);
|
|
22
|
+
export function createExecutionToolRuntime(config, overrides = {}) {
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
config,
|
|
25
|
+
quoteClient: overrides.quoteClient ?? new RestLiFiQuoteClient(config),
|
|
26
|
+
clobClient: overrides.clobClient ?? new RestPolymarketClobClient(config),
|
|
27
|
+
polymarketOrderBuilder: overrides.polymarketOrderBuilder ?? buildUnsignedPolymarketOrder,
|
|
28
|
+
hyperliquidInfoClient: overrides.hyperliquidInfoClient ?? new RestHyperliquidInfoClient(config),
|
|
29
|
+
hyperliquidActionBuilder: overrides.hyperliquidActionBuilder ?? buildUnsignedHyperliquidOrderAction,
|
|
30
|
+
nowMs: overrides.nowMs ?? Date.now,
|
|
31
|
+
entropy: overrides.entropy ?? (() => randomBytes(10)),
|
|
32
|
+
clientOrderIdEntropy: overrides.clientOrderIdEntropy ?? (() => randomBytes(16)),
|
|
33
|
+
...(overrides.delegation ? { delegation: overrides.delegation } : {}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function createExecutionToolRuntimeFromEnv(env = process.env) {
|
|
37
|
+
const config = loadExecutionServiceConfig(env);
|
|
38
|
+
return config ? createExecutionToolRuntime(config) : undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The hosted boot: the prepare runtime plus, when the five delegation variables are set, the delegated
|
|
42
|
+
* services (the optional SDK loaded, the Base RPC chain id checked). Delegation configured while
|
|
43
|
+
* execution is switched off is a misconfiguration and refuses to boot rather than silently dropping
|
|
44
|
+
* the tool.
|
|
45
|
+
*/
|
|
46
|
+
export async function createExecutionToolRuntimeWithDelegationFromEnv(env = process.env) {
|
|
47
|
+
const config = loadExecutionServiceConfig(env);
|
|
48
|
+
const delegation = await createDelegationServicesFromEnv(env);
|
|
49
|
+
if (!config) {
|
|
50
|
+
if (delegation) {
|
|
51
|
+
throw new Error('delegated send is configured but OTTO_EXECUTION_MCP_ENABLED is not true; enable the execution tools or unset the delegation variables');
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return createExecutionToolRuntime(config, delegation ? { delegation } : {});
|
|
56
|
+
}
|
|
57
|
+
function normalizedAddress(value) {
|
|
58
|
+
return getAddress(value.toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
function normalizedIntent(input, profile) {
|
|
61
|
+
// One implementation, shared with consumers through `otto-intel-mcp/execution`.
|
|
62
|
+
return swapIntentDigestInput({
|
|
63
|
+
...input,
|
|
64
|
+
accountProfile: { ...input.accountProfile, address: profile.address },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function normalizedBridgeIntent(input, profile, destinationRecipient) {
|
|
68
|
+
return {
|
|
69
|
+
accountProfile: {
|
|
70
|
+
kind: profile.kind,
|
|
71
|
+
address: profile.address,
|
|
72
|
+
chainId: profile.chainId,
|
|
73
|
+
},
|
|
74
|
+
fromChainId: input.fromChainId,
|
|
75
|
+
toChainId: input.toChainId,
|
|
76
|
+
fromToken: normalizedAddress(input.fromToken),
|
|
77
|
+
toToken: normalizedAddress(input.toToken),
|
|
78
|
+
fromAmount: input.fromAmount,
|
|
79
|
+
destinationRecipient,
|
|
80
|
+
slippage: {
|
|
81
|
+
maxBps: input.slippage.maxBps,
|
|
82
|
+
...(input.slippage.minAmountOut ? { minAmountOut: input.slippage.minAmountOut } : {}),
|
|
83
|
+
},
|
|
84
|
+
...(input.client_ref ? { client_ref: input.client_ref } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function normalizedPolymarketIntent(input, profile) {
|
|
88
|
+
return {
|
|
89
|
+
accountProfile: {
|
|
90
|
+
kind: profile.kind,
|
|
91
|
+
address: profile.address,
|
|
92
|
+
chainId: profile.chainId,
|
|
93
|
+
},
|
|
94
|
+
tokenId: input.tokenId,
|
|
95
|
+
side: input.side,
|
|
96
|
+
price: input.price,
|
|
97
|
+
size: input.size,
|
|
98
|
+
...(input.client_ref ? { client_ref: input.client_ref } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function normalizedPerpIntent(input, profile, tradingSigner, vaultAddress) {
|
|
102
|
+
return {
|
|
103
|
+
accountProfile: {
|
|
104
|
+
kind: profile.kind,
|
|
105
|
+
address: profile.address,
|
|
106
|
+
chainId: profile.chainId,
|
|
107
|
+
},
|
|
108
|
+
asset: input.asset,
|
|
109
|
+
side: input.side,
|
|
110
|
+
size: input.size,
|
|
111
|
+
limitPrice: input.limitPrice,
|
|
112
|
+
timeInForce: input.timeInForce,
|
|
113
|
+
reduceOnly: input.reduceOnly,
|
|
114
|
+
tradingSigner,
|
|
115
|
+
vaultAddress: vaultAddress ?? null,
|
|
116
|
+
...(input.takeProfitPrice ? { takeProfitPrice: input.takeProfitPrice } : {}),
|
|
117
|
+
...(input.stopLossPrice ? { stopLossPrice: input.stopLossPrice } : {}),
|
|
118
|
+
...(input.client_ref ? { client_ref: input.client_ref } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** 0x + 32 lowercase hex, the venue's client-order-id shape (`hl-write-request-schema.ts:549`). */
|
|
122
|
+
function mintClientOrderId(entropy) {
|
|
123
|
+
const bytes = entropy();
|
|
124
|
+
if (bytes.length !== 16) {
|
|
125
|
+
return refuse('HYPERLIQUID_ORDER_INVALID', 'client order id entropy source did not produce 16 bytes');
|
|
126
|
+
}
|
|
127
|
+
return `0x${Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')}`;
|
|
128
|
+
}
|
|
129
|
+
function capabilityFor(profile) {
|
|
130
|
+
if (profile.kind === 'eoa')
|
|
131
|
+
return 'send_evm_transaction';
|
|
132
|
+
if (profile.kind === 'safe')
|
|
133
|
+
return 'submit_evm_batch';
|
|
134
|
+
return refuse('UNSUPPORTED_ACCOUNT_PROFILE', 'only EOA and Safe profiles are supported');
|
|
135
|
+
}
|
|
136
|
+
export async function prepareSwap(runtime, rawInput, signal) {
|
|
137
|
+
const input = PREPARE_SWAP_INPUT_SCHEMA.parse(rawInput);
|
|
138
|
+
const profile = {
|
|
139
|
+
kind: input.accountProfile.kind,
|
|
140
|
+
address: normalizedAddress(input.accountProfile.address),
|
|
141
|
+
chainId: input.accountProfile.chainId,
|
|
142
|
+
};
|
|
143
|
+
if (profile.chainId !== input.chainId) {
|
|
144
|
+
return refuse('ACCOUNT_BINDING_MISMATCH', 'account profile chain does not match swap chain');
|
|
145
|
+
}
|
|
146
|
+
if (!reviewedLiFiFeeForwarders(input.chainId)) {
|
|
147
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', 'chain is outside the reviewed LI.FI v1 route set');
|
|
148
|
+
}
|
|
149
|
+
const fromToken = normalizedAddress(input.fromToken);
|
|
150
|
+
const toToken = normalizedAddress(input.toToken);
|
|
151
|
+
const fromAmount = BigInt(input.fromAmount);
|
|
152
|
+
if (attributedFeeAmount(fromAmount, runtime.config.lifiFee) <= 0n) {
|
|
153
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', 'input amount is too small for the configured Otto fee');
|
|
154
|
+
}
|
|
155
|
+
const quote = await runtime.quoteClient.getSameChainSwapQuote({
|
|
156
|
+
chainId: input.chainId,
|
|
157
|
+
fromToken,
|
|
158
|
+
toToken,
|
|
159
|
+
fromAmount: input.fromAmount,
|
|
160
|
+
sender: profile.address,
|
|
161
|
+
receiver: profile.address,
|
|
162
|
+
maxSlippageBps: input.slippage.maxBps,
|
|
163
|
+
}, signal);
|
|
164
|
+
const asserted = decodeAndAssertLiFiSwap({
|
|
165
|
+
quote,
|
|
166
|
+
profile,
|
|
167
|
+
chainId: input.chainId,
|
|
168
|
+
fromToken,
|
|
169
|
+
toToken,
|
|
170
|
+
fromAmount,
|
|
171
|
+
maxSlippageBps: input.slippage.maxBps,
|
|
172
|
+
minAmountOut: input.slippage.minAmountOut ? BigInt(input.slippage.minAmountOut) : undefined,
|
|
173
|
+
config: runtime.config,
|
|
174
|
+
});
|
|
175
|
+
const capability = capabilityFor(profile);
|
|
176
|
+
const approvalCalldata = (amount) => encodeFunctionData({
|
|
177
|
+
abi: ERC20_APPROVE_ABI,
|
|
178
|
+
functionName: 'approve',
|
|
179
|
+
args: [asserted.approvalSpender, amount],
|
|
180
|
+
});
|
|
181
|
+
const payload = Object.freeze({
|
|
182
|
+
kind: 'evm_execution_plan',
|
|
183
|
+
chain_id: input.chainId,
|
|
184
|
+
steps: Object.freeze([
|
|
185
|
+
// Approval-race-guarded ERC20s (USDT and friends) revert on a non-zero to non-zero
|
|
186
|
+
// approve, so a plan that only raises the allowance is not executable from every
|
|
187
|
+
// reachable prior allowance state. Resetting to zero first is executable from all of
|
|
188
|
+
// them, needs no allowance read, and leaves the swap step unchanged.
|
|
189
|
+
Object.freeze({
|
|
190
|
+
to: fromToken,
|
|
191
|
+
value: '0',
|
|
192
|
+
data: approvalCalldata(0n),
|
|
193
|
+
kind: 'approve_reset',
|
|
194
|
+
}),
|
|
195
|
+
Object.freeze({
|
|
196
|
+
to: fromToken,
|
|
197
|
+
value: '0',
|
|
198
|
+
data: approvalCalldata(fromAmount),
|
|
199
|
+
kind: 'approve',
|
|
200
|
+
}),
|
|
201
|
+
Object.freeze({
|
|
202
|
+
to: asserted.transaction.to,
|
|
203
|
+
value: asserted.transaction.value,
|
|
204
|
+
data: asserted.transaction.data,
|
|
205
|
+
kind: 'call',
|
|
206
|
+
}),
|
|
207
|
+
]),
|
|
208
|
+
});
|
|
209
|
+
const nowMs = runtime.nowMs();
|
|
210
|
+
const artifactId = createArtifactId(nowMs, runtime.entropy());
|
|
211
|
+
const payloadDigest = executionDigest(payload);
|
|
212
|
+
const uncommitted = {
|
|
213
|
+
schema_version: '1',
|
|
214
|
+
artifact_id: artifactId,
|
|
215
|
+
rail: 'lifi',
|
|
216
|
+
operation: 'swap',
|
|
217
|
+
intent_digest: executionDigest(normalizedIntent(input, profile)),
|
|
218
|
+
account_binding: accountBinding(profile),
|
|
219
|
+
required_capabilities: [capability],
|
|
220
|
+
payload,
|
|
221
|
+
fee_attribution: {
|
|
222
|
+
mechanism: 'lifi_integrator_fee',
|
|
223
|
+
config_version: runtime.config.lifiFeeConfigVersion,
|
|
224
|
+
},
|
|
225
|
+
replay_disclosure: {
|
|
226
|
+
artifact_role: 'construction_aid',
|
|
227
|
+
freshness_boundary: 'valid_until',
|
|
228
|
+
onchain_replay_boundary: 'signer_account_nonce',
|
|
229
|
+
tool_guarantees_single_execution: false,
|
|
230
|
+
},
|
|
231
|
+
assertions: [
|
|
232
|
+
...asserted.assertions,
|
|
233
|
+
{ id: 'payload_digest_committed', result: 'passed' },
|
|
234
|
+
{ id: 'erc20_allowance_reset_ordered', result: 'passed' },
|
|
235
|
+
{ id: 'artifact_expiry_committed', result: 'passed' },
|
|
236
|
+
],
|
|
237
|
+
valid_until: new Date(nowMs + runtime.config.artifactTtlMs).toISOString(),
|
|
238
|
+
submission: {
|
|
239
|
+
method: capability,
|
|
240
|
+
chain_id: input.chainId,
|
|
241
|
+
artifact_id: artifactId,
|
|
242
|
+
payload_digest: payloadDigest,
|
|
243
|
+
adapter_owned_fields: ['nonce', 'gas', 'serialization'],
|
|
244
|
+
...(input.client_ref ? { client_ref: input.client_ref } : {}),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
const envelope = {
|
|
248
|
+
...uncommitted,
|
|
249
|
+
submission: {
|
|
250
|
+
...uncommitted.submission,
|
|
251
|
+
artifact_digest: artifactCommitmentDigest(uncommitted),
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
const parsed = EXECUTION_ENVELOPE_SCHEMA.parse(envelope);
|
|
255
|
+
// Bind the artifact — id AND whole-envelope commitment — to THIS process for the delegated tool: it submits
|
|
256
|
+
// only what this process prepared. At capacity the prepare is refused; nothing issued is ever dropped.
|
|
257
|
+
if (runtime.delegation) {
|
|
258
|
+
recordPreparedArtifact(runtime.delegation, artifactId, parsed.submission.artifact_digest, nowMs + runtime.config.artifactTtlMs, nowMs);
|
|
259
|
+
}
|
|
260
|
+
return parsed;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Build the ordered source-chain plan for a cross-chain native-USDC bridge over the enumerated v1
|
|
264
|
+
* Circle-CCTP route set. Prepare-only: the plan is handed to the caller's own custody to sign.
|
|
265
|
+
*/
|
|
266
|
+
export async function prepareBridge(runtime, rawInput, signal) {
|
|
267
|
+
const input = PREPARE_BRIDGE_INPUT_SCHEMA.parse(rawInput);
|
|
268
|
+
const profile = {
|
|
269
|
+
kind: input.accountProfile.kind,
|
|
270
|
+
address: normalizedAddress(input.accountProfile.address),
|
|
271
|
+
chainId: input.accountProfile.chainId,
|
|
272
|
+
};
|
|
273
|
+
if (profile.chainId !== input.fromChainId) {
|
|
274
|
+
return refuse('ACCOUNT_BINDING_MISMATCH', 'account profile chain does not match the bridge source chain');
|
|
275
|
+
}
|
|
276
|
+
// An EOA is the same account on every EVM chain, so its own address is a safe default
|
|
277
|
+
// destination. A Safe is not, which is why the schema requires the caller to declare one.
|
|
278
|
+
const destinationRecipient = input.destinationRecipient
|
|
279
|
+
? normalizedAddress(input.destinationRecipient)
|
|
280
|
+
: profile.address;
|
|
281
|
+
const fromToken = normalizedAddress(input.fromToken);
|
|
282
|
+
const toToken = normalizedAddress(input.toToken);
|
|
283
|
+
const fromAmount = BigInt(input.fromAmount);
|
|
284
|
+
if (!isEnumeratedCctpBridgeRoute(input.fromChainId, input.toChainId, fromToken, toToken)) {
|
|
285
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', `route ${input.fromChainId}:${fromToken} -> ${input.toChainId}:${toToken} is outside the enumerated v1 PolymerCCTP USDC route set`);
|
|
286
|
+
}
|
|
287
|
+
if (attributedFeeAmount(fromAmount, runtime.config.lifiFee) <= 0n) {
|
|
288
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', 'input amount is too small for the configured Otto fee');
|
|
289
|
+
}
|
|
290
|
+
const quote = await runtime.quoteClient.getCrossChainBridgeQuote({
|
|
291
|
+
fromChainId: input.fromChainId,
|
|
292
|
+
toChainId: input.toChainId,
|
|
293
|
+
fromToken,
|
|
294
|
+
toToken,
|
|
295
|
+
fromAmount: input.fromAmount,
|
|
296
|
+
sender: profile.address,
|
|
297
|
+
receiver: destinationRecipient,
|
|
298
|
+
maxSlippageBps: input.slippage.maxBps,
|
|
299
|
+
}, signal);
|
|
300
|
+
const asserted = decodeAndAssertLiFiBridge({
|
|
301
|
+
quote,
|
|
302
|
+
profile,
|
|
303
|
+
fromChainId: input.fromChainId,
|
|
304
|
+
toChainId: input.toChainId,
|
|
305
|
+
fromToken,
|
|
306
|
+
toToken,
|
|
307
|
+
fromAmount,
|
|
308
|
+
destinationRecipient,
|
|
309
|
+
maxSlippageBps: input.slippage.maxBps,
|
|
310
|
+
minAmountOut: input.slippage.minAmountOut ? BigInt(input.slippage.minAmountOut) : undefined,
|
|
311
|
+
config: runtime.config,
|
|
312
|
+
});
|
|
313
|
+
const capability = capabilityFor(profile);
|
|
314
|
+
const approvalCalldata = (amount) => encodeFunctionData({
|
|
315
|
+
abi: ERC20_APPROVE_ABI,
|
|
316
|
+
functionName: 'approve',
|
|
317
|
+
args: [asserted.approvalSpender, amount],
|
|
318
|
+
});
|
|
319
|
+
const payload = Object.freeze({
|
|
320
|
+
kind: 'evm_execution_plan',
|
|
321
|
+
chain_id: input.fromChainId,
|
|
322
|
+
steps: Object.freeze([
|
|
323
|
+
// Same allowance-reset ordering as the swap plan: it is what keeps the plan executable from
|
|
324
|
+
// every reachable prior allowance state, including approval-race-guarded ERC20s.
|
|
325
|
+
Object.freeze({
|
|
326
|
+
to: fromToken,
|
|
327
|
+
value: '0',
|
|
328
|
+
data: approvalCalldata(0n),
|
|
329
|
+
kind: 'approve_reset',
|
|
330
|
+
}),
|
|
331
|
+
Object.freeze({
|
|
332
|
+
to: fromToken,
|
|
333
|
+
value: '0',
|
|
334
|
+
data: approvalCalldata(fromAmount),
|
|
335
|
+
kind: 'approve',
|
|
336
|
+
}),
|
|
337
|
+
Object.freeze({
|
|
338
|
+
to: asserted.transaction.to,
|
|
339
|
+
value: asserted.transaction.value,
|
|
340
|
+
data: asserted.transaction.data,
|
|
341
|
+
kind: 'call',
|
|
342
|
+
}),
|
|
343
|
+
]),
|
|
344
|
+
});
|
|
345
|
+
const nowMs = runtime.nowMs();
|
|
346
|
+
const artifactId = createArtifactId(nowMs, runtime.entropy());
|
|
347
|
+
const payloadDigest = executionDigest(payload);
|
|
348
|
+
const uncommitted = {
|
|
349
|
+
schema_version: '1',
|
|
350
|
+
artifact_id: artifactId,
|
|
351
|
+
rail: 'lifi',
|
|
352
|
+
operation: 'bridge',
|
|
353
|
+
intent_digest: executionDigest(normalizedBridgeIntent(input, profile, destinationRecipient)),
|
|
354
|
+
account_binding: bridgeAccountBinding(profile, {
|
|
355
|
+
destinationRecipient,
|
|
356
|
+
destinationChainId: input.toChainId,
|
|
357
|
+
sendingAsset: fromToken,
|
|
358
|
+
receivingAsset: toToken,
|
|
359
|
+
}),
|
|
360
|
+
required_capabilities: [capability],
|
|
361
|
+
payload,
|
|
362
|
+
fee_attribution: {
|
|
363
|
+
mechanism: 'lifi_integrator_fee',
|
|
364
|
+
config_version: runtime.config.lifiFeeConfigVersion,
|
|
365
|
+
},
|
|
366
|
+
replay_disclosure: {
|
|
367
|
+
artifact_role: 'construction_aid',
|
|
368
|
+
freshness_boundary: 'valid_until',
|
|
369
|
+
onchain_replay_boundary: 'signer_account_nonce',
|
|
370
|
+
tool_guarantees_single_execution: false,
|
|
371
|
+
},
|
|
372
|
+
assertions: [
|
|
373
|
+
...asserted.assertions,
|
|
374
|
+
{ id: 'payload_digest_committed', result: 'passed' },
|
|
375
|
+
{ id: 'erc20_allowance_reset_ordered', result: 'passed' },
|
|
376
|
+
{ id: 'artifact_expiry_committed', result: 'passed' },
|
|
377
|
+
],
|
|
378
|
+
valid_until: new Date(nowMs + runtime.config.artifactTtlMs).toISOString(),
|
|
379
|
+
submission: {
|
|
380
|
+
// The source chain: the only chain any step in this plan is submitted to.
|
|
381
|
+
method: capability,
|
|
382
|
+
chain_id: input.fromChainId,
|
|
383
|
+
artifact_id: artifactId,
|
|
384
|
+
payload_digest: payloadDigest,
|
|
385
|
+
adapter_owned_fields: ['nonce', 'gas', 'serialization'],
|
|
386
|
+
...(input.client_ref ? { client_ref: input.client_ref } : {}),
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
const envelope = {
|
|
390
|
+
...uncommitted,
|
|
391
|
+
submission: {
|
|
392
|
+
...uncommitted.submission,
|
|
393
|
+
artifact_digest: artifactCommitmentDigest(uncommitted),
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
return BRIDGE_EXECUTION_ENVELOPE_SCHEMA.parse(envelope);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Build an unsigned Polymarket CTF Exchange V2 limit order for the caller's own Polygon EOA, with
|
|
400
|
+
* Otto's builder code inside the signed struct (spec §7.3). Prepare-only: the artifact carries order
|
|
401
|
+
* args and the EIP-712 payload; the caller's own signer signs it and the caller's own credentials
|
|
402
|
+
* post it. Nothing here holds a key, a CLOB credential, or the caller's collateral.
|
|
403
|
+
*/
|
|
404
|
+
export async function preparePolymarketOrder(runtime, rawInput, signal) {
|
|
405
|
+
const input = PREPARE_POLYMARKET_ORDER_INPUT_SCHEMA.parse(rawInput);
|
|
406
|
+
const profile = {
|
|
407
|
+
kind: input.accountProfile.kind,
|
|
408
|
+
address: normalizedAddress(input.accountProfile.address),
|
|
409
|
+
chainId: input.accountProfile.chainId,
|
|
410
|
+
};
|
|
411
|
+
if (profile.kind !== 'eoa') {
|
|
412
|
+
// A Safe maker needs a POLY_GNOSIS_SAFE-vs-POLY_1271 signature-type ruling that spec §13 still
|
|
413
|
+
// lists as open. Refused by name rather than guessed: the signature type is inside the signed
|
|
414
|
+
// order, so the wrong guess produces orders the exchange rejects or validates against the wrong
|
|
415
|
+
// owner.
|
|
416
|
+
return refuse('UNSUPPORTED_ACCOUNT_PROFILE', 'v1 supports the EOA profile only; the Safe signature type for Polymarket is an open ruling');
|
|
417
|
+
}
|
|
418
|
+
if (profile.chainId !== POLYMARKET_CHAIN_ID) {
|
|
419
|
+
return refuse('ACCOUNT_BINDING_MISMATCH', `Polymarket orders are Polygon-only; the account profile declares chain ${profile.chainId}`);
|
|
420
|
+
}
|
|
421
|
+
const metadata = await runtime.clobClient.getMarketMetadata(input.tokenId, signal);
|
|
422
|
+
if (!metadata.acceptingOrders ||
|
|
423
|
+
!metadata.active ||
|
|
424
|
+
metadata.closed ||
|
|
425
|
+
!metadata.orderBookEnabled) {
|
|
426
|
+
return refuse('UNSUPPORTED_POLYMARKET_MARKET', `market ${metadata.conditionId} is not accepting orders (accepting_orders=${metadata.acceptingOrders}, active=${metadata.active}, closed=${metadata.closed}, order_book=${metadata.orderBookEnabled})`);
|
|
427
|
+
}
|
|
428
|
+
const { priceUnits } = assertPolymarketPriceOnTick(input.price, metadata.tickSize);
|
|
429
|
+
const sizeAtomic = polymarketAtomicUnits(input.size);
|
|
430
|
+
const minimumOrderSizeAtomic = polymarketAtomicUnits(String(metadata.minimumOrderSize));
|
|
431
|
+
const builderRates = await runtime.clobClient.getBuilderFeeRates(runtime.config.polymarketBuilderCode, signal);
|
|
432
|
+
const attributionAssertions = assertPolymarketBuilderAttributionRuled(builderRates, runtime.config);
|
|
433
|
+
const nowMs = runtime.nowMs();
|
|
434
|
+
const built = await runtime.polymarketOrderBuilder({
|
|
435
|
+
maker: profile.address,
|
|
436
|
+
tokenId: input.tokenId,
|
|
437
|
+
side: input.side,
|
|
438
|
+
price: Number(input.price),
|
|
439
|
+
size: Number(input.size),
|
|
440
|
+
tickSize: metadata.tickSize,
|
|
441
|
+
negRisk: metadata.negRisk,
|
|
442
|
+
builderCode: runtime.config.polymarketBuilderCode,
|
|
443
|
+
});
|
|
444
|
+
const asserted = decodeAndAssertPolymarketOrder({
|
|
445
|
+
built,
|
|
446
|
+
maker: profile.address,
|
|
447
|
+
tokenId: input.tokenId,
|
|
448
|
+
side: input.side,
|
|
449
|
+
priceUnits,
|
|
450
|
+
sizeAtomic,
|
|
451
|
+
tickSize: metadata.tickSize,
|
|
452
|
+
negRisk: metadata.negRisk,
|
|
453
|
+
minimumOrderSizeAtomic,
|
|
454
|
+
config: runtime.config,
|
|
455
|
+
nowMs,
|
|
456
|
+
});
|
|
457
|
+
const payload = Object.freeze({
|
|
458
|
+
kind: 'polymarket_order_v2',
|
|
459
|
+
chain_id: POLYMARKET_CHAIN_ID,
|
|
460
|
+
exchange: asserted.exchange,
|
|
461
|
+
neg_risk: metadata.negRisk,
|
|
462
|
+
tick_size: metadata.tickSize,
|
|
463
|
+
order_type: 'GTC',
|
|
464
|
+
order: Object.freeze(asserted.order),
|
|
465
|
+
eip712: asserted.typedData,
|
|
466
|
+
order_hash: asserted.orderHash,
|
|
467
|
+
});
|
|
468
|
+
const artifactId = createArtifactId(nowMs, runtime.entropy());
|
|
469
|
+
const payloadDigest = executionDigest(payload);
|
|
470
|
+
const assertions = [
|
|
471
|
+
...attributionAssertions,
|
|
472
|
+
...asserted.assertions,
|
|
473
|
+
{ id: 'payload_digest_committed', result: 'passed' },
|
|
474
|
+
{ id: 'artifact_expiry_committed', result: 'passed' },
|
|
475
|
+
];
|
|
476
|
+
const uncommitted = {
|
|
477
|
+
schema_version: '1',
|
|
478
|
+
artifact_id: artifactId,
|
|
479
|
+
rail: 'polymarket',
|
|
480
|
+
operation: 'pm_order',
|
|
481
|
+
intent_digest: executionDigest(normalizedPolymarketIntent(input, profile)),
|
|
482
|
+
account_binding: polymarketAccountBinding(profile, {
|
|
483
|
+
exchange: asserted.exchange,
|
|
484
|
+
tokenId: input.tokenId,
|
|
485
|
+
}),
|
|
486
|
+
required_capabilities: ['sign_eip712'],
|
|
487
|
+
payload,
|
|
488
|
+
fee_attribution: {
|
|
489
|
+
mechanism: 'polymarket_builder_code',
|
|
490
|
+
config_version: runtime.config.polymarketBuilderFeeConfigVersion,
|
|
491
|
+
},
|
|
492
|
+
replay_disclosure: {
|
|
493
|
+
artifact_role: 'construction_aid',
|
|
494
|
+
freshness_boundary: 'valid_until',
|
|
495
|
+
onchain_replay_boundary: 'exchange_order_hash',
|
|
496
|
+
tool_guarantees_single_execution: false,
|
|
497
|
+
},
|
|
498
|
+
assertions,
|
|
499
|
+
valid_until: new Date(nowMs + runtime.config.artifactTtlMs).toISOString(),
|
|
500
|
+
submission: {
|
|
501
|
+
method: 'sign_eip712',
|
|
502
|
+
chain_id: POLYMARKET_CHAIN_ID,
|
|
503
|
+
artifact_id: artifactId,
|
|
504
|
+
payload_digest: payloadDigest,
|
|
505
|
+
adapter_owned_fields: ['signature', 'clob_post'],
|
|
506
|
+
...(input.client_ref ? { client_ref: input.client_ref } : {}),
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
const envelope = {
|
|
510
|
+
...uncommitted,
|
|
511
|
+
submission: {
|
|
512
|
+
...uncommitted.submission,
|
|
513
|
+
artifact_digest: artifactCommitmentDigest(uncommitted),
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
return POLYMARKET_EXECUTION_ENVELOPE_SCHEMA.parse(envelope);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Build an unsigned Hyperliquid perp order L1 action for the caller's own Hyperliquid account, with
|
|
520
|
+
* optional reduce-only take-profit and stop-loss triggers bundled under `normalTpsl` (spec §7.2).
|
|
521
|
+
*
|
|
522
|
+
* Prepare-only, and structurally so: nothing on this path can sign or submit. The artifact carries the
|
|
523
|
+
* canonical action plus the declared EIP-712 Agent domain, struct, and `connectionId` recipe; the
|
|
524
|
+
* caller's own signer picks the nonce, computes the digest, and signs. The root-secret-derived agent
|
|
525
|
+
* wallet the trade-execution agent uses (`hyperliquid-client.ts:558`) is fenced out entirely (§11), and
|
|
526
|
+
* the builder attribution field is spec'd and VALUELESS until the Step-0 `approveBuilderFee` reversal
|
|
527
|
+
* lands (§7.2), so the action carries no builder key and the envelope declares that pending state.
|
|
528
|
+
*/
|
|
529
|
+
export async function preparePerpOrder(runtime, rawInput, signal) {
|
|
530
|
+
const input = PREPARE_PERP_ORDER_INPUT_SCHEMA.parse(rawInput);
|
|
531
|
+
const profile = {
|
|
532
|
+
kind: input.accountProfile.kind,
|
|
533
|
+
address: normalizedAddress(input.accountProfile.address),
|
|
534
|
+
chainId: input.accountProfile.chainId,
|
|
535
|
+
};
|
|
536
|
+
if (profile.kind !== 'eoa') {
|
|
537
|
+
// Hyperliquid recovers an L1 action's signer with ecrecover and does not accept ERC-1271, so a
|
|
538
|
+
// Safe cannot sign one at all — and a Safe master cannot even approve an API wallet to sign for
|
|
539
|
+
// it. Refused by name rather than constructed into an artifact no signer could ever satisfy.
|
|
540
|
+
return refuse('UNSUPPORTED_ACCOUNT_PROFILE', 'Hyperliquid recovers the L1 action signer with ecrecover; a Safe cannot produce an accepted signature, so v1 supports the EOA profile only');
|
|
541
|
+
}
|
|
542
|
+
const tradingSigner = input.tradingSigner
|
|
543
|
+
? normalizedAddress(input.tradingSigner)
|
|
544
|
+
: profile.address;
|
|
545
|
+
const vaultAddress = input.vaultAddress ? normalizedAddress(input.vaultAddress) : undefined;
|
|
546
|
+
const asset = await runtime.hyperliquidInfoClient.getPerpAsset(input.asset, signal);
|
|
547
|
+
if (asset.isDelisted) {
|
|
548
|
+
return refuse('UNSUPPORTED_HYPERLIQUID_MARKET', `perp ${asset.name} is delisted on Hyperliquid; it still occupies its universe index but no longer trades`);
|
|
549
|
+
}
|
|
550
|
+
// Grid checks, not rounding: an off-grid price or size is refused by name so the caller never signs
|
|
551
|
+
// an order at a price they did not ask for.
|
|
552
|
+
assertHyperliquidPriceOnGrid(input.limitPrice, asset.szDecimals);
|
|
553
|
+
assertHyperliquidSizeOnGrid(input.size, asset.szDecimals);
|
|
554
|
+
if (input.takeProfitPrice)
|
|
555
|
+
assertHyperliquidPriceOnGrid(input.takeProfitPrice, asset.szDecimals);
|
|
556
|
+
if (input.stopLossPrice)
|
|
557
|
+
assertHyperliquidPriceOnGrid(input.stopLossPrice, asset.szDecimals);
|
|
558
|
+
assertHyperliquidBracketCoherent({
|
|
559
|
+
isBuy: input.side === 'BUY',
|
|
560
|
+
price: input.limitPrice,
|
|
561
|
+
takeProfitPrice: input.takeProfitPrice,
|
|
562
|
+
stopLossPrice: input.stopLossPrice,
|
|
563
|
+
szDecimals: asset.szDecimals,
|
|
564
|
+
});
|
|
565
|
+
const isBuy = input.side === 'BUY';
|
|
566
|
+
// ONE DISTINCT CLIENT ID PER LEG, minted before construction. This is what lets the caller read the
|
|
567
|
+
// venue's per-leg verdict by identity instead of by position in the response array — the correlation
|
|
568
|
+
// failure `hyperliquid-client.ts:1801` documents.
|
|
569
|
+
const legs = [
|
|
570
|
+
{
|
|
571
|
+
role: 'entry',
|
|
572
|
+
isBuy,
|
|
573
|
+
price: input.limitPrice,
|
|
574
|
+
size: input.size,
|
|
575
|
+
reduceOnly: input.reduceOnly,
|
|
576
|
+
tif: input.timeInForce,
|
|
577
|
+
clientOrderId: mintClientOrderId(runtime.clientOrderIdEntropy),
|
|
578
|
+
},
|
|
579
|
+
];
|
|
580
|
+
// Triggers close the entry, so they sit on the opposite side, reduce-only, at their own trigger
|
|
581
|
+
// price, sized to the entry (`hyperliquid-client.ts:1825-1870`).
|
|
582
|
+
if (input.takeProfitPrice) {
|
|
583
|
+
legs.push({
|
|
584
|
+
role: 'take_profit',
|
|
585
|
+
isBuy: !isBuy,
|
|
586
|
+
price: input.takeProfitPrice,
|
|
587
|
+
size: input.size,
|
|
588
|
+
reduceOnly: true,
|
|
589
|
+
trigger: { triggerPx: input.takeProfitPrice, tpsl: 'tp' },
|
|
590
|
+
clientOrderId: mintClientOrderId(runtime.clientOrderIdEntropy),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
if (input.stopLossPrice) {
|
|
594
|
+
legs.push({
|
|
595
|
+
role: 'stop_loss',
|
|
596
|
+
isBuy: !isBuy,
|
|
597
|
+
price: input.stopLossPrice,
|
|
598
|
+
size: input.size,
|
|
599
|
+
reduceOnly: true,
|
|
600
|
+
trigger: { triggerPx: input.stopLossPrice, tpsl: 'sl' },
|
|
601
|
+
clientOrderId: mintClientOrderId(runtime.clientOrderIdEntropy),
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const grouping = legs.length > 1 ? 'normalTpsl' : 'na';
|
|
605
|
+
const built = runtime.hyperliquidActionBuilder({
|
|
606
|
+
grouping,
|
|
607
|
+
legs: legs.map((leg) => ({
|
|
608
|
+
assetId: asset.assetId,
|
|
609
|
+
isBuy: leg.isBuy,
|
|
610
|
+
price: leg.price,
|
|
611
|
+
size: leg.size,
|
|
612
|
+
reduceOnly: leg.reduceOnly,
|
|
613
|
+
orderType: leg.trigger
|
|
614
|
+
? { trigger: { isMarket: true, triggerPx: leg.trigger.triggerPx, tpsl: leg.trigger.tpsl } }
|
|
615
|
+
: { limit: { tif: leg.tif ?? input.timeInForce } },
|
|
616
|
+
clientOrderId: leg.clientOrderId,
|
|
617
|
+
})),
|
|
618
|
+
});
|
|
619
|
+
const asserted = decodeAndAssertHyperliquidAction({ built, asset, grouping, legs });
|
|
620
|
+
const nowMs = runtime.nowMs();
|
|
621
|
+
const validUntilMs = nowMs + runtime.config.artifactTtlMs;
|
|
622
|
+
const payload = Object.freeze({
|
|
623
|
+
kind: 'hyperliquid_l1_action',
|
|
624
|
+
chain_id: HYPERLIQUID_L1_SIGNATURE_CHAIN_ID,
|
|
625
|
+
action: asserted.action,
|
|
626
|
+
signing: hyperliquidSigningDeclaration(),
|
|
627
|
+
vault_address: vaultAddress ?? null,
|
|
628
|
+
// The one freshness control the constructor owns (§10), placed where the venue enforces it: inside
|
|
629
|
+
// the signed digest. The venue rejects the action after this instant, which makes `valid_until` a
|
|
630
|
+
// venue-side bound on this rail rather than an advisory one.
|
|
631
|
+
expires_after: validUntilMs,
|
|
632
|
+
});
|
|
633
|
+
const artifactId = createArtifactId(nowMs, runtime.entropy());
|
|
634
|
+
const payloadDigest = executionDigest(payload);
|
|
635
|
+
const assertions = [
|
|
636
|
+
...asserted.assertions,
|
|
637
|
+
{ id: 'hyperliquid_bracket_directionally_coherent', result: 'passed' },
|
|
638
|
+
{ id: 'hyperliquid_signing_domain_pinned', result: 'passed' },
|
|
639
|
+
{ id: 'hyperliquid_agent_struct_pinned', result: 'passed' },
|
|
640
|
+
{ id: 'hyperliquid_expires_after_matches_valid_until', result: 'passed' },
|
|
641
|
+
{ id: 'payload_digest_committed', result: 'passed' },
|
|
642
|
+
{ id: 'artifact_expiry_committed', result: 'passed' },
|
|
643
|
+
];
|
|
644
|
+
const uncommitted = {
|
|
645
|
+
schema_version: '1',
|
|
646
|
+
artifact_id: artifactId,
|
|
647
|
+
rail: 'hyperliquid',
|
|
648
|
+
operation: 'perp_order',
|
|
649
|
+
intent_digest: executionDigest(normalizedPerpIntent(input, profile, tradingSigner, vaultAddress)),
|
|
650
|
+
account_binding: hyperliquidAccountBinding(profile, {
|
|
651
|
+
tradingSigner,
|
|
652
|
+
vaultAddress,
|
|
653
|
+
asset: asset.name,
|
|
654
|
+
assetId: asset.assetId,
|
|
655
|
+
szDecimals: asset.szDecimals,
|
|
656
|
+
}),
|
|
657
|
+
required_capabilities: ['sign_hyperliquid_l1'],
|
|
658
|
+
payload,
|
|
659
|
+
fee_attribution: {
|
|
660
|
+
mechanism: 'hyperliquid_builder_fee',
|
|
661
|
+
config_version: runtime.config.hyperliquidBuilderFeeConfigVersion,
|
|
662
|
+
status: HL_BUILDER_ATTRIBUTION_STATUS,
|
|
663
|
+
},
|
|
664
|
+
replay_disclosure: {
|
|
665
|
+
artifact_role: 'construction_aid',
|
|
666
|
+
freshness_boundary: 'valid_until',
|
|
667
|
+
onchain_replay_boundary: 'signer_nonce_and_expires_after',
|
|
668
|
+
tool_guarantees_single_execution: false,
|
|
669
|
+
},
|
|
670
|
+
assertions,
|
|
671
|
+
valid_until: new Date(validUntilMs).toISOString(),
|
|
672
|
+
submission: {
|
|
673
|
+
method: 'sign_hyperliquid_l1',
|
|
674
|
+
chain_id: HYPERLIQUID_L1_SIGNATURE_CHAIN_ID,
|
|
675
|
+
artifact_id: artifactId,
|
|
676
|
+
payload_digest: payloadDigest,
|
|
677
|
+
adapter_owned_fields: ['nonce', 'signature', 'exchange_post'],
|
|
678
|
+
...(input.client_ref ? { client_ref: input.client_ref } : {}),
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
const envelope = {
|
|
682
|
+
...uncommitted,
|
|
683
|
+
submission: {
|
|
684
|
+
...uncommitted.submission,
|
|
685
|
+
artifact_digest: artifactCommitmentDigest(uncommitted),
|
|
686
|
+
},
|
|
687
|
+
};
|
|
688
|
+
return HYPERLIQUID_EXECUTION_ENVELOPE_SCHEMA.parse(envelope);
|
|
689
|
+
}
|
|
690
|
+
//# sourceMappingURL=execution-tools.js.map
|