otto-intel-mcp 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -18
- package/dist/account-userop-policy-definition.d.ts +14 -0
- package/dist/account-userop-policy-definition.js +12 -0
- package/dist/account-userop-policy-relation.d.ts +371 -0
- package/dist/account-userop-policy-relation.js +87 -0
- package/dist/adapter/cdp-signer.d.ts +76 -9
- package/dist/adapter/cdp-signer.js +274 -33
- package/dist/adapter/evm-call-failure.d.ts +29 -0
- package/dist/adapter/evm-call-failure.js +109 -0
- package/dist/adapter/refusal.d.ts +1 -1
- package/dist/adapter/refusal.js +13 -0
- package/dist/adapter/route-simulator.d.ts +91 -0
- package/dist/adapter/route-simulator.js +143 -0
- package/dist/adapter-cdp-index.d.ts +1 -1
- package/dist/executable-equities.d.ts +55 -0
- package/dist/executable-equities.js +116 -0
- package/dist/executable-yield-markets.d.ts +261 -0
- package/dist/executable-yield-markets.js +154 -0
- package/dist/execution-account-gas-quote.d.ts +141 -0
- package/dist/execution-account-gas-quote.js +443 -0
- package/dist/execution-account-gas-review.d.ts +123 -0
- package/dist/execution-account-gas-review.js +104 -0
- package/dist/execution-account-gas-runtime.d.ts +33 -0
- package/dist/execution-account-gas-runtime.js +131 -0
- package/dist/execution-account-gas-view.d.ts +936 -0
- package/dist/execution-account-gas-view.js +56 -0
- package/dist/execution-config.d.ts +3 -0
- package/dist/execution-config.js +4 -0
- package/dist/execution-delegated-definition.d.ts +7 -6
- package/dist/execution-delegated-definition.js +13 -2
- package/dist/execution-delegation-admin-definition.d.ts +127 -5
- package/dist/execution-delegation-admin-definition.js +155 -5
- package/dist/execution-delegation-admin.d.ts +48 -2
- package/dist/execution-delegation-admin.js +422 -12
- package/dist/execution-delegation-cap-store.d.ts +386 -0
- package/dist/execution-delegation-cap-store.js +1150 -0
- package/dist/execution-delegation-exit-definition.d.ts +309 -0
- package/dist/execution-delegation-exit-definition.js +204 -0
- package/dist/execution-delegation-exit-store.d.ts +114 -0
- package/dist/execution-delegation-exit-store.js +671 -0
- package/dist/execution-delegation-exit.d.ts +4 -0
- package/dist/execution-delegation-exit.js +614 -0
- package/dist/execution-delegation-finality.d.ts +22 -0
- package/dist/execution-delegation-finality.js +90 -0
- package/dist/execution-delegation-operation-store.d.ts +129 -0
- package/dist/execution-delegation-operation-store.js +1107 -0
- package/dist/execution-delegation-operations-definition.d.ts +282 -0
- package/dist/execution-delegation-operations-definition.js +117 -0
- package/dist/execution-delegation-operations.d.ts +6 -0
- package/dist/execution-delegation-operations.js +282 -0
- package/dist/execution-delegation-policy.d.ts +154 -8
- package/dist/execution-delegation-policy.js +273 -40
- package/dist/execution-delegation.d.ts +51 -0
- package/dist/execution-delegation.js +441 -31
- package/dist/execution-errors.d.ts +1 -1
- package/dist/execution-errors.js +122 -0
- package/dist/execution-index.d.ts +7 -2
- package/dist/execution-index.js +7 -2
- package/dist/execution-journal-schema.d.ts +96 -0
- package/dist/execution-journal-schema.js +368 -0
- package/dist/execution-journal-tables.d.ts +14 -0
- package/dist/execution-journal-tables.js +227 -0
- package/dist/execution-policy-compatibility.d.ts +34 -0
- package/dist/execution-policy-compatibility.js +76 -0
- package/dist/execution-registration.d.ts +54 -2
- package/dist/execution-registration.js +151 -5
- package/dist/execution-sponsored-account-profile.d.ts +32 -0
- package/dist/execution-sponsored-account-profile.js +59 -0
- package/dist/execution-sponsored-account.d.ts +1519 -0
- package/dist/execution-sponsored-account.js +222 -0
- package/dist/execution-sponsored-definition.d.ts +1713 -0
- package/dist/execution-sponsored-definition.js +185 -0
- package/dist/execution-sponsored-gas-policy.d.ts +27 -0
- package/dist/execution-sponsored-gas-policy.js +110 -0
- package/dist/execution-sponsored-hosted.d.ts +3 -0
- package/dist/execution-sponsored-hosted.js +71 -0
- package/dist/execution-sponsored-index.d.ts +1322 -0
- package/dist/execution-sponsored-index.js +102 -0
- package/dist/execution-sponsored-mcp-definition.d.ts +1662 -0
- package/dist/execution-sponsored-mcp-definition.js +94 -0
- package/dist/execution-sponsored-receipt.d.ts +47 -0
- package/dist/execution-sponsored-receipt.js +74 -0
- package/dist/execution-sponsored-registration.d.ts +14 -0
- package/dist/execution-sponsored-registration.js +77 -0
- package/dist/execution-sponsored-runtime.d.ts +84 -0
- package/dist/execution-sponsored-runtime.js +149 -0
- package/dist/execution-sponsored-settlement.d.ts +15 -0
- package/dist/execution-sponsored-settlement.js +88 -0
- package/dist/execution-sponsored-store.d.ts +67 -0
- package/dist/execution-sponsored-store.js +517 -0
- package/dist/execution-sponsored-transport.d.ts +90 -0
- package/dist/execution-sponsored-transport.js +180 -0
- package/dist/execution-tool-definitions.d.ts +21 -21
- package/dist/execution-tool-definitions.js +0 -7
- package/dist/execution-tools.d.ts +53 -1
- package/dist/execution-tools.js +171 -13
- package/dist/execution-worker-guard.d.ts +28 -0
- package/dist/execution-worker-guard.js +124 -0
- package/dist/hybrid-yield-index.d.ts +10 -0
- package/dist/hybrid-yield-index.js +11 -0
- package/dist/hybrid-yield-markets.d.ts +96 -0
- package/dist/hybrid-yield-markets.js +124 -0
- package/dist/hybrid-yield.d.ts +411 -0
- package/dist/hybrid-yield.js +155 -0
- package/dist/hyperliquid-order-builder.js +4 -0
- package/dist/lifi-execution-client.d.ts +6 -0
- package/dist/lifi-execution-client.js +3 -0
- package/dist/lp-userop-account.d.ts +461 -0
- package/dist/lp-userop-account.js +417 -0
- package/dist/lp-userop-authority.d.ts +49 -0
- package/dist/lp-userop-authority.js +211 -0
- package/dist/lp-userop-definition.d.ts +233 -0
- package/dist/lp-userop-definition.js +85 -0
- package/dist/lp-userop-hosted.d.ts +3 -0
- package/dist/lp-userop-hosted.js +73 -0
- package/dist/lp-userop-index.d.ts +1569 -0
- package/dist/lp-userop-index.js +112 -0
- package/dist/lp-userop-mcp-definition.d.ts +1220 -0
- package/dist/lp-userop-mcp-definition.js +46 -0
- package/dist/lp-userop-operation.d.ts +1230 -0
- package/dist/lp-userop-operation.js +140 -0
- package/dist/lp-userop-registration.d.ts +14 -0
- package/dist/lp-userop-registration.js +70 -0
- package/dist/lp-userop-settlement.d.ts +89 -0
- package/dist/lp-userop-settlement.js +184 -0
- package/dist/lp-userop.d.ts +643 -0
- package/dist/lp-userop.js +343 -0
- package/dist/native-yield-account-definition.d.ts +1192 -0
- package/dist/native-yield-account-definition.js +232 -0
- package/dist/native-yield-account-gas.d.ts +58 -0
- package/dist/native-yield-account-gas.js +228 -0
- package/dist/native-yield-account-index.d.ts +23 -0
- package/dist/native-yield-account-index.js +28 -0
- package/dist/native-yield-account-policy.d.ts +168 -0
- package/dist/native-yield-account-policy.js +525 -0
- package/dist/native-yield-account-rpc.d.ts +8 -0
- package/dist/native-yield-account-rpc.js +128 -0
- package/dist/native-yield-account-runtime.d.ts +71 -0
- package/dist/native-yield-account-runtime.js +125 -0
- package/dist/native-yield-account-store.d.ts +40 -0
- package/dist/native-yield-account-store.js +307 -0
- package/dist/native-yield-account.d.ts +662 -0
- package/dist/native-yield-account.js +371 -0
- package/dist/native-yield-consent-store.d.ts +20 -0
- package/dist/native-yield-consent-store.js +317 -0
- package/dist/native-yield-definition.d.ts +288 -0
- package/dist/native-yield-definition.js +105 -0
- package/dist/native-yield-reader.d.ts +4 -0
- package/dist/native-yield-reader.js +137 -0
- package/dist/native-yield-settlement.d.ts +48 -0
- package/dist/native-yield-settlement.js +167 -0
- package/dist/native-yield-simulation.d.ts +18 -0
- package/dist/native-yield-simulation.js +45 -0
- package/dist/native-yield-userop-account.d.ts +675 -0
- package/dist/native-yield-userop-account.js +357 -0
- package/dist/native-yield-userop-authority.d.ts +214 -0
- package/dist/native-yield-userop-authority.js +296 -0
- package/dist/native-yield-userop-definition.d.ts +331 -0
- package/dist/native-yield-userop-definition.js +105 -0
- package/dist/native-yield-userop-hosted.d.ts +3 -0
- package/dist/native-yield-userop-hosted.js +71 -0
- package/dist/native-yield-userop-index.d.ts +1522 -0
- package/dist/native-yield-userop-index.js +142 -0
- package/dist/native-yield-userop-mcp-definition.d.ts +1356 -0
- package/dist/native-yield-userop-mcp-definition.js +50 -0
- package/dist/native-yield-userop-operation.d.ts +845 -0
- package/dist/native-yield-userop-operation.js +127 -0
- package/dist/native-yield-userop-policy.d.ts +23 -0
- package/dist/native-yield-userop-policy.js +25 -0
- package/dist/native-yield-userop-registration.d.ts +17 -0
- package/dist/native-yield-userop-registration.js +76 -0
- package/dist/native-yield-userop-settlement.d.ts +8 -0
- package/dist/native-yield-userop-settlement.js +79 -0
- package/dist/native-yield-userop-simulation.d.ts +18 -0
- package/dist/native-yield-userop-simulation.js +34 -0
- package/dist/native-yield-userop.d.ts +35 -0
- package/dist/native-yield-userop.js +156 -0
- package/dist/native-yield-verify.d.ts +30 -0
- package/dist/native-yield-verify.js +141 -0
- package/dist/native-yield.d.ts +17 -0
- package/dist/native-yield.js +119 -0
- package/dist/stdio-main.js +0 -0
- package/dist/stock-buy-definition.d.ts +338 -0
- package/dist/stock-buy-definition.js +127 -0
- package/dist/stock-buy-oracle.d.ts +8 -0
- package/dist/stock-buy-oracle.js +22 -0
- package/dist/stock-buy.d.ts +17 -0
- package/dist/stock-buy.js +164 -0
- package/dist/tool-definitions.d.ts +3 -1
- package/dist/tool-definitions.js +55 -2
- package/dist/vendor/lp-v3/lp-v3-atomic-contract.d.ts +445 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-contract.js +90 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-evidence.d.ts +14 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-evidence.js +82 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-index.d.ts +80 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-index.js +19 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-plan.d.ts +43 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-plan.js +112 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-contract.d.ts +312 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-contract.js +41 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-index.d.ts +3 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-index.js +3 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-rpc.d.ts +49 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read-rpc.js +88 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read.d.ts +17 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-read.js +232 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-settlement.d.ts +105 -0
- package/dist/vendor/lp-v3/lp-v3-atomic-settlement.js +162 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-contract.d.ts +606 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-contract.js +59 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-index.d.ts +168 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-index.js +23 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-observation.d.ts +32 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-observation.js +228 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-plan.d.ts +11 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-plan.js +91 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-reconcile.d.ts +9 -0
- package/dist/vendor/lp-v3/lp-v3-collect-protocol-reconcile.js +124 -0
- package/dist/vendor/lp-v3/lp-v3-contract.d.ts +538 -0
- package/dist/vendor/lp-v3/lp-v3-contract.js +137 -0
- package/dist/vendor/lp-v3/lp-v3-evidence.d.ts +25 -0
- package/dist/vendor/lp-v3/lp-v3-evidence.js +168 -0
- package/dist/vendor/lp-v3/lp-v3-plan.d.ts +22 -0
- package/dist/vendor/lp-v3/lp-v3-plan.js +135 -0
- package/dist/vendor/lp-v3/types.d.ts +131 -0
- package/dist/vendor/lp-v3/types.js +6 -0
- package/dist/vendor/lp-v3/v4-math.d.ts +18 -0
- package/dist/vendor/lp-v3/v4-math.js +90 -0
- package/dist/yield-deposit-definition.d.ts +374 -0
- package/dist/yield-deposit-definition.js +123 -0
- package/dist/yield-deposit.d.ts +16 -0
- package/dist/yield-deposit.js +149 -0
- package/dist/yield-route-semantics.d.ts +27 -0
- package/dist/yield-route-semantics.js +71 -0
- package/dist/yield-withdraw-definition.d.ts +357 -0
- package/dist/yield-withdraw-definition.js +90 -0
- package/dist/yield-withdraw.d.ts +11 -0
- package/dist/yield-withdraw.js +122 -0
- package/package.json +239 -7
package/dist/execution-tools.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { createHostedLpUserOpBinding } from './lp-userop-hosted.js';
|
|
2
|
+
import { createHostedNativeYieldUserOpBinding } from './native-yield-userop-hosted.js';
|
|
3
|
+
import { createHostedSponsoredAccountBinding } from './execution-sponsored-hosted.js';
|
|
1
4
|
import { randomBytes } from 'node:crypto';
|
|
2
5
|
import { encodeFunctionData, getAddress, parseAbi } from 'viem';
|
|
3
6
|
import { attributedFeeAmount, HL_BUILDER_ATTRIBUTION_STATUS, HYPERLIQUID_L1_SIGNATURE_CHAIN_ID, isEnumeratedCctpBridgeRoute, reviewedLiFiFeeForwarders, loadExecutionServiceConfig, POLYMARKET_CHAIN_ID, } from './execution-config.js';
|
|
4
7
|
import { refuse } from './execution-errors.js';
|
|
5
8
|
import { createDelegationServicesFromEnv, } from './execution-delegation.js';
|
|
6
9
|
import { recordPreparedArtifact } from './prepared-artifacts.js';
|
|
10
|
+
import { createRouteSimulator } from './adapter/route-simulator.js';
|
|
7
11
|
import { swapIntentDigestInput } from './execution-intent.js';
|
|
8
12
|
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
13
|
import { accountBinding, artifactCommitmentDigest, bridgeAccountBinding, createArtifactId, executionDigest, hyperliquidAccountBinding, polymarketAccountBinding, } from './execution-types.js';
|
|
@@ -11,8 +15,12 @@ import { RestHyperliquidInfoClient, } from './hyperliquid-info-client.js';
|
|
|
11
15
|
import { assertHyperliquidBracketCoherent, assertHyperliquidPriceOnGrid, assertHyperliquidSizeOnGrid, decodeAndAssertHyperliquidAction, hyperliquidSigningDeclaration, } from './hyperliquid-order-assertions.js';
|
|
12
16
|
import { buildUnsignedHyperliquidOrderAction, } from './hyperliquid-order-builder.js';
|
|
13
17
|
import { decodeAndAssertLiFiBridge } from './lifi-bridge-assertions.js';
|
|
14
|
-
import { RestLiFiQuoteClient } from './lifi-execution-client.js';
|
|
18
|
+
import { RestLiFiQuoteClient, } from './lifi-execution-client.js';
|
|
15
19
|
import { decodeAndAssertLiFiSwap } from './lifi-swap-assertions.js';
|
|
20
|
+
import { modelBWithdrawToken } from './execution-delegation-policy.js';
|
|
21
|
+
import { viemYieldVaultReader } from './yield-withdraw.js';
|
|
22
|
+
import { viemEquityOracleReader } from './stock-buy-oracle.js';
|
|
23
|
+
import { EXECUTABLE_EQUITY_TOKEN_SET } from './executable-equities.js';
|
|
16
24
|
import { RestPolymarketClobClient } from './polymarket-clob-client.js';
|
|
17
25
|
import { assertPolymarketBuilderAttributionRuled, assertPolymarketPriceOnTick, decodeAndAssertPolymarketOrder, polymarketAtomicUnits, } from './polymarket-order-assertions.js';
|
|
18
26
|
import { buildUnsignedPolymarketOrder, } from './polymarket-order-builder.js';
|
|
@@ -31,6 +39,12 @@ export function createExecutionToolRuntime(config, overrides = {}) {
|
|
|
31
39
|
entropy: overrides.entropy ?? (() => randomBytes(10)),
|
|
32
40
|
clientOrderIdEntropy: overrides.clientOrderIdEntropy ?? (() => randomBytes(16)),
|
|
33
41
|
...(overrides.delegation ? { delegation: overrides.delegation } : {}),
|
|
42
|
+
...(overrides.sponsoredAccount ? { sponsoredAccount: overrides.sponsoredAccount } : {}),
|
|
43
|
+
...(overrides.nativeYieldUserOp ? { nativeYieldUserOp: overrides.nativeYieldUserOp } : {}),
|
|
44
|
+
...(overrides.lpUserOp ? { lpUserOp: overrides.lpUserOp } : {}),
|
|
45
|
+
...(overrides.routeSimulator ? { routeSimulator: overrides.routeSimulator } : {}),
|
|
46
|
+
...(overrides.yieldVaultReader ? { yieldVaultReader: overrides.yieldVaultReader } : {}),
|
|
47
|
+
...(overrides.equityOracleReader ? { equityOracleReader: overrides.equityOracleReader } : {}),
|
|
34
48
|
});
|
|
35
49
|
}
|
|
36
50
|
export function createExecutionToolRuntimeFromEnv(env = process.env) {
|
|
@@ -52,8 +66,24 @@ export async function createExecutionToolRuntimeWithDelegationFromEnv(env = proc
|
|
|
52
66
|
}
|
|
53
67
|
return undefined;
|
|
54
68
|
}
|
|
55
|
-
|
|
69
|
+
// The delegated rail's Base client doubles as the route simulator's view of the chain (#2125) and as
|
|
70
|
+
// the yield constructor's vault read and the stock constructor's Chainlink read (#2129).
|
|
71
|
+
return createExecutionToolRuntime(config, {
|
|
72
|
+
...(delegation
|
|
73
|
+
? {
|
|
74
|
+
delegation,
|
|
75
|
+
sponsoredAccount: await createHostedSponsoredAccountBinding(env, delegation),
|
|
76
|
+
nativeYieldUserOp: await createHostedNativeYieldUserOpBinding(env, delegation),
|
|
77
|
+
lpUserOp: await createHostedLpUserOpBinding(env, delegation),
|
|
78
|
+
yieldVaultReader: viemYieldVaultReader(delegation.rig.rig.pub),
|
|
79
|
+
equityOracleReader: viemEquityOracleReader(delegation.rig.rig.pub),
|
|
80
|
+
}
|
|
81
|
+
: {}),
|
|
82
|
+
...(delegation?.pub ? { routeSimulator: createRouteSimulator(delegation.pub) } : {}),
|
|
83
|
+
});
|
|
56
84
|
}
|
|
85
|
+
/** How many LI.FI tools the constructor will try before it refuses: the first quote plus two re-quotes. */
|
|
86
|
+
export const MAX_ROUTE_ATTEMPTS = 3;
|
|
57
87
|
function normalizedAddress(value) {
|
|
58
88
|
return getAddress(value.toLowerCase());
|
|
59
89
|
}
|
|
@@ -135,6 +165,29 @@ function capabilityFor(profile) {
|
|
|
135
165
|
}
|
|
136
166
|
export async function prepareSwap(runtime, rawInput, signal) {
|
|
137
167
|
const input = PREPARE_SWAP_INPUT_SCHEMA.parse(rawInput);
|
|
168
|
+
/**
|
|
169
|
+
* 🔴 REGISTRY VAULT SHARES ARE EXCLUSIVE TO THE YIELD CONSTRUCTORS (codex gate r1 HIGH 2). A generic swap
|
|
170
|
+
* whose input or output is a Yield Copilot vault share would reach the delegated gate as an
|
|
171
|
+
* indistinguishable `operation: 'swap'` artifact and be admitted as a deposit/withdraw WITHOUT the registry
|
|
172
|
+
* commitment and the vault-parity guard the yield constructors run. So this public constructor refuses that
|
|
173
|
+
* token class by name; `otto_prepare_yield_deposit` / `otto_prepare_yield_withdraw` are the only paths.
|
|
174
|
+
*/
|
|
175
|
+
for (const token of [input.fromToken, input.toToken]) {
|
|
176
|
+
if (modelBWithdrawToken(normalizedAddress(token))) {
|
|
177
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', `${token} is a Yield Copilot vault share token; use otto_prepare_yield_deposit / otto_prepare_yield_withdraw, which pin the registry row and check vault parity`);
|
|
178
|
+
}
|
|
179
|
+
// Same discipline for the executable equities (2026-09-03): a generic swap into a B20 token would
|
|
180
|
+
// reach the delegated gate as an indistinguishable swap artifact WITHOUT the registry commitment,
|
|
181
|
+
// the Chainlink price-parity guard and the freshness gate. The stock constructor is the only path.
|
|
182
|
+
if (EXECUTABLE_EQUITY_TOKEN_SET.has(token.toLowerCase())) {
|
|
183
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', `${token} is an executable tokenized equity; use otto_prepare_stock_buy, which pins the registry row and checks price parity against the equity's own Chainlink mark`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const quote = await quoteSwap(runtime, input, signal);
|
|
187
|
+
return prepareSwapFromQuote(runtime, input, quote);
|
|
188
|
+
}
|
|
189
|
+
/** The swap's account profile, normalized; refuses a profile whose chain is not the swap's chain. */
|
|
190
|
+
function swapProfile(input) {
|
|
138
191
|
const profile = {
|
|
139
192
|
kind: input.accountProfile.kind,
|
|
140
193
|
address: normalizedAddress(input.accountProfile.address),
|
|
@@ -143,24 +196,114 @@ export async function prepareSwap(runtime, rawInput, signal) {
|
|
|
143
196
|
if (profile.chainId !== input.chainId) {
|
|
144
197
|
return refuse('ACCOUNT_BINDING_MISMATCH', 'account profile chain does not match swap chain');
|
|
145
198
|
}
|
|
199
|
+
return profile;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The route-set and fee pre-checks, then ONE LI.FI quote for the parsed swap input. Split from
|
|
203
|
+
* `prepareSwapFromQuote` so a constructor that must read the quote itself (the Yield Copilot deposit:
|
|
204
|
+
* expected shares, the router's floor, the fee lines) quotes exactly once and builds the envelope from
|
|
205
|
+
* that same quote — a second quote could route through a different tool and carry a different floor.
|
|
206
|
+
*/
|
|
207
|
+
export async function quoteSwap(runtime, input, signal) {
|
|
208
|
+
const profile = swapProfile(input);
|
|
146
209
|
if (!reviewedLiFiFeeForwarders(input.chainId)) {
|
|
147
210
|
return refuse('UNSUPPORTED_LIFI_ROUTE', 'chain is outside the reviewed LI.FI v1 route set');
|
|
148
211
|
}
|
|
149
|
-
const fromToken = normalizedAddress(input.fromToken);
|
|
150
|
-
const toToken = normalizedAddress(input.toToken);
|
|
151
212
|
const fromAmount = BigInt(input.fromAmount);
|
|
152
213
|
if (attributedFeeAmount(fromAmount, runtime.config.lifiFee) <= 0n) {
|
|
153
214
|
return refuse('UNSUPPORTED_LIFI_ROUTE', 'input amount is too small for the configured Otto fee');
|
|
154
215
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
216
|
+
/**
|
|
217
|
+
* ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
218
|
+
* 🔴 A ROUTE IS QUOTED ONLY ONCE IT HAS BEEN SEEN TO EXECUTE (2026-09-03, the first live delegated swap)
|
|
219
|
+
* ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
220
|
+
* LI.FI picks the exchange tool per quote. That day it picked KyberSwap for a $1 USDC→WETH swap, and
|
|
221
|
+
* the diamond call reverted deterministically — at any gas, on a fresh quote too — while the same
|
|
222
|
+
* request through SushiSwap executed. Every assertion the constructor makes is about SHAPE (selector,
|
|
223
|
+
* receiver, fee, minimum output); none of them can see that a tool's calldata fails inside the diamond.
|
|
224
|
+
*
|
|
225
|
+
* So, on a server that has a chain view (the delegated rail's Base client), the quoted swap step is
|
|
226
|
+
* SIMULATED from the executing account with its preconditions overridden (balance and allowance to the
|
|
227
|
+
* spender — neither exists at prepare time). A route that reverts is re-quoted with that tool DENIED,
|
|
228
|
+
* bounded to `MAX_ROUTE_ATTEMPTS` tools; if none executes the quote refuses and names them. A
|
|
229
|
+
* simulation that cannot be run (no layout for the token, a node that ignores overrides) refuses too —
|
|
230
|
+
* an unproven route is not prepared on a server that was supposed to prove it.
|
|
231
|
+
*
|
|
232
|
+
* It lives HERE, in the one quote every swap-shaped constructor takes — the public swap and the Yield
|
|
233
|
+
* Copilot deposit/withdraw alike — so the quote a caller reads (expected shares, the floor, the fee
|
|
234
|
+
* lines) IS the quote that was proven, and `prepareSwapFromQuote` builds the envelope from exactly it.
|
|
235
|
+
* The proven tool and whether it was simulated ride on the returned quote (`route`), are recorded in
|
|
236
|
+
* the envelope's assertions, and the delegated receipt repeats them, so the receipt says which venue
|
|
237
|
+
* executed. A server without a chain view (the prepare-only x402 constructor) quotes as before and
|
|
238
|
+
* records `route_simulated:false`.
|
|
239
|
+
*/
|
|
240
|
+
const fromToken = normalizedAddress(input.fromToken);
|
|
241
|
+
const toToken = normalizedAddress(input.toToken);
|
|
242
|
+
const denied = [];
|
|
243
|
+
for (let attempt = 1; attempt <= MAX_ROUTE_ATTEMPTS; attempt += 1) {
|
|
244
|
+
const quote = await runtime.quoteClient.getSameChainSwapQuote({
|
|
245
|
+
chainId: input.chainId,
|
|
246
|
+
fromToken,
|
|
247
|
+
toToken,
|
|
248
|
+
fromAmount: input.fromAmount,
|
|
249
|
+
sender: profile.address,
|
|
250
|
+
receiver: profile.address,
|
|
251
|
+
maxSlippageBps: input.slippage.maxBps,
|
|
252
|
+
// A COPY: the quote client must never see this list grow after it answered.
|
|
253
|
+
...(denied.length > 0 ? { denyExchanges: Object.freeze([...denied]) } : {}),
|
|
254
|
+
}, signal);
|
|
255
|
+
const routeTool = typeof quote.tool === 'string' && quote.tool.trim() ? quote.tool.trim().slice(0, 48) : 'unknown';
|
|
256
|
+
if (!runtime.routeSimulator) {
|
|
257
|
+
return Object.freeze({ ...quote, route: Object.freeze({ tool: routeTool, simulated: false }) });
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 🔴 SHAPE BEFORE SIMULATION (merge gate 2026-09-04). The quote is decoded and asserted HERE, on the
|
|
261
|
+
* quote-stage input, before the node sees it: a quote whose spender or target is not the diamond, whose
|
|
262
|
+
* selector, receiver, fee or floor is wrong, is refused by the assertion — it is never simulated, and a
|
|
263
|
+
* simulation that failed only because the quote's own `approvalAddress` was wrong can never deny a
|
|
264
|
+
* healthy tool. What the node simulates is the ASSERTED transaction and spender, the same bytes
|
|
265
|
+
* `prepareSwapFromQuote` asserts again (with the caller's floor) when it builds the envelope.
|
|
266
|
+
*/
|
|
267
|
+
const asserted = decodeAndAssertLiFiSwap({
|
|
268
|
+
quote,
|
|
269
|
+
profile,
|
|
270
|
+
chainId: input.chainId,
|
|
271
|
+
fromToken,
|
|
272
|
+
toToken,
|
|
273
|
+
fromAmount,
|
|
274
|
+
maxSlippageBps: input.slippage.maxBps,
|
|
275
|
+
minAmountOut: input.slippage.minAmountOut ? BigInt(input.slippage.minAmountOut) : undefined,
|
|
276
|
+
config: runtime.config,
|
|
277
|
+
});
|
|
278
|
+
const simulation = await runtime.routeSimulator.simulate({
|
|
279
|
+
from: profile.address,
|
|
280
|
+
to: asserted.transaction.to,
|
|
281
|
+
data: asserted.transaction.data,
|
|
282
|
+
value: BigInt(asserted.transaction.value),
|
|
283
|
+
token: fromToken,
|
|
284
|
+
spender: asserted.approvalSpender,
|
|
285
|
+
amount: fromAmount,
|
|
286
|
+
});
|
|
287
|
+
if (simulation.outcome === 'ok') {
|
|
288
|
+
return Object.freeze({ ...quote, route: Object.freeze({ tool: routeTool, simulated: true }) });
|
|
289
|
+
}
|
|
290
|
+
if (simulation.outcome === 'unavailable') {
|
|
291
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', `[ROUTE_SIMULATION_UNAVAILABLE] the quoted route could not be simulated from ${profile.address}, so it is not prepared: ${simulation.detail}`);
|
|
292
|
+
}
|
|
293
|
+
denied.push(routeTool);
|
|
294
|
+
// A tool LI.FI did not name cannot be denied on the next quote; re-asking would only repeat it.
|
|
295
|
+
if (attempt === MAX_ROUTE_ATTEMPTS || routeTool === 'unknown') {
|
|
296
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', `[ROUTE_SIMULATION_REVERTED] no LI.FI route executes from ${profile.address} for this swap: tried ${denied.join(', ')} (last: ${simulation.detail})`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return refuse('UNSUPPORTED_LIFI_ROUTE', 'no LI.FI quote was obtained');
|
|
300
|
+
}
|
|
301
|
+
/** Decode, assert and commit the envelope for a parsed swap input over the quote `quoteSwap` returned for it. */
|
|
302
|
+
export function prepareSwapFromQuote(runtime, input, quote) {
|
|
303
|
+
const profile = swapProfile(input);
|
|
304
|
+
const fromToken = normalizedAddress(input.fromToken);
|
|
305
|
+
const toToken = normalizedAddress(input.toToken);
|
|
306
|
+
const fromAmount = BigInt(input.fromAmount);
|
|
164
307
|
const asserted = decodeAndAssertLiFiSwap({
|
|
165
308
|
quote,
|
|
166
309
|
profile,
|
|
@@ -172,6 +315,13 @@ export async function prepareSwap(runtime, rawInput, signal) {
|
|
|
172
315
|
minAmountOut: input.slippage.minAmountOut ? BigInt(input.slippage.minAmountOut) : undefined,
|
|
173
316
|
config: runtime.config,
|
|
174
317
|
});
|
|
318
|
+
// The route facts `quoteSwap` proved; a bare quote (a caller that quoted elsewhere) was not simulated.
|
|
319
|
+
const route = quote.route ?? {
|
|
320
|
+
tool: typeof quote.tool === 'string' && quote.tool.trim() ? quote.tool.trim().slice(0, 48) : 'unknown',
|
|
321
|
+
simulated: false,
|
|
322
|
+
};
|
|
323
|
+
const routeTool = route.tool;
|
|
324
|
+
const routeSimulated = route.simulated;
|
|
175
325
|
const capability = capabilityFor(profile);
|
|
176
326
|
const approvalCalldata = (amount) => encodeFunctionData({
|
|
177
327
|
abi: ERC20_APPROVE_ABI,
|
|
@@ -230,6 +380,9 @@ export async function prepareSwap(runtime, rawInput, signal) {
|
|
|
230
380
|
},
|
|
231
381
|
assertions: [
|
|
232
382
|
...asserted.assertions,
|
|
383
|
+
// The route that was prepared, and whether it was proven to execute — repeated on the delegated receipt.
|
|
384
|
+
{ id: `route_tool:${routeTool}`, result: 'passed' },
|
|
385
|
+
{ id: `route_simulated:${routeSimulated}`, result: 'passed' },
|
|
233
386
|
{ id: 'payload_digest_committed', result: 'passed' },
|
|
234
387
|
{ id: 'erc20_allowance_reset_ordered', result: 'passed' },
|
|
235
388
|
{ id: 'artifact_expiry_committed', result: 'passed' },
|
|
@@ -528,6 +681,11 @@ export async function preparePolymarketOrder(runtime, rawInput, signal) {
|
|
|
528
681
|
*/
|
|
529
682
|
export async function preparePerpOrder(runtime, rawInput, signal) {
|
|
530
683
|
const input = PREPARE_PERP_ORDER_INPUT_SCHEMA.parse(rawInput);
|
|
684
|
+
// Kept for existing account recovery only, and deliberately absent from public discovery.
|
|
685
|
+
// Refuse before market reads, entropy, or an injected builder can construct new exposure.
|
|
686
|
+
if (input.reduceOnly !== true) {
|
|
687
|
+
return refuse('HYPERLIQUID_RETIRED', 'New Hyperliquid trading is retired; only explicit reduce-only recovery is supported');
|
|
688
|
+
}
|
|
531
689
|
const profile = {
|
|
532
690
|
kind: input.accountProfile.kind,
|
|
533
691
|
address: normalizedAddress(input.accountProfile.address),
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare const KINDS: readonly ["delegated", "exit", "native", "sponsored"];
|
|
2
|
+
export type ExecutionWorkerKind = (typeof KINDS)[number];
|
|
3
|
+
export interface ExecutionWorkerGuard {
|
|
4
|
+
readonly kind: ExecutionWorkerKind;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
release(): void;
|
|
7
|
+
}
|
|
8
|
+
export interface ExecutionWorkerGuardStore {
|
|
9
|
+
readonly available: boolean;
|
|
10
|
+
readonly unavailableReason?: string;
|
|
11
|
+
/** Create/validate the operation's permanent guard file and hold its writer lock until release. */
|
|
12
|
+
acquire(kind: ExecutionWorkerKind, id: string, create?: boolean): Promise<ExecutionWorkerGuard>;
|
|
13
|
+
}
|
|
14
|
+
export declare class ExecutionWorkerBusyError extends Error {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
export declare class ExecutionWorkerGuardInvalidError extends Error {
|
|
18
|
+
constructor();
|
|
19
|
+
}
|
|
20
|
+
export declare function unavailableExecutionWorkerGuardStore(reason: string): ExecutionWorkerGuardStore;
|
|
21
|
+
/**
|
|
22
|
+
* The lock lives in a different SQLite file from operations.db. Holding BEGIN IMMEDIATE here therefore
|
|
23
|
+
* proves the prior handler is gone without blocking that same handler's durable journal transactions.
|
|
24
|
+
* The file and its exact metadata are permanent. Missing/corrupt metadata is a refusal, never evidence
|
|
25
|
+
* that an old owner died.
|
|
26
|
+
*/
|
|
27
|
+
export declare function openExecutionWorkerGuardStore(root: string): Promise<ExecutionWorkerGuardStore>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { access, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const GUARD_ID = /^[a-zA-Z0-9_-]{8,128}$/;
|
|
4
|
+
const KINDS = ['delegated', 'exit', 'native', 'sponsored'];
|
|
5
|
+
export class ExecutionWorkerBusyError extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super('another worker still owns this operation');
|
|
8
|
+
this.name = 'ExecutionWorkerBusyError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class ExecutionWorkerGuardInvalidError extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super('the operation worker guard is missing, corrupt, or belongs to another operation');
|
|
14
|
+
this.name = 'ExecutionWorkerGuardInvalidError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function unavailableExecutionWorkerGuardStore(reason) {
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
available: false,
|
|
20
|
+
unavailableReason: reason,
|
|
21
|
+
acquire: () => Promise.reject(new ExecutionWorkerGuardInvalidError()),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The lock lives in a different SQLite file from operations.db. Holding BEGIN IMMEDIATE here therefore
|
|
26
|
+
* proves the prior handler is gone without blocking that same handler's durable journal transactions.
|
|
27
|
+
* The file and its exact metadata are permanent. Missing/corrupt metadata is a refusal, never evidence
|
|
28
|
+
* that an old owner died.
|
|
29
|
+
*/
|
|
30
|
+
export async function openExecutionWorkerGuardStore(root) {
|
|
31
|
+
let DatabaseSync;
|
|
32
|
+
try {
|
|
33
|
+
const specifier = 'node:sqlite';
|
|
34
|
+
({ DatabaseSync } = (await import(specifier)));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return unavailableExecutionWorkerGuardStore('this runtime has no node:sqlite worker locks');
|
|
38
|
+
}
|
|
39
|
+
const guardRoot = join(root, 'operation-guards');
|
|
40
|
+
try {
|
|
41
|
+
await mkdir(guardRoot, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return unavailableExecutionWorkerGuardStore('the operation worker guard directory is unavailable');
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze({
|
|
47
|
+
available: true,
|
|
48
|
+
async acquire(kind, id, create = false) {
|
|
49
|
+
if (!KINDS.includes(kind) || !GUARD_ID.test(id))
|
|
50
|
+
throw new ExecutionWorkerGuardInvalidError();
|
|
51
|
+
const path = join(guardRoot, `${kind}-${id}.db`);
|
|
52
|
+
let db;
|
|
53
|
+
try {
|
|
54
|
+
if (!create) {
|
|
55
|
+
try {
|
|
56
|
+
await access(path);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new ExecutionWorkerGuardInvalidError();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
db = new DatabaseSync(path);
|
|
63
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
64
|
+
db.exec('PRAGMA synchronous = FULL');
|
|
65
|
+
db.exec('PRAGMA busy_timeout = 0');
|
|
66
|
+
if (create) {
|
|
67
|
+
db.exec(`CREATE TABLE IF NOT EXISTS guard_meta (
|
|
68
|
+
singleton INTEGER NOT NULL PRIMARY KEY CHECK (singleton = 1),
|
|
69
|
+
kind TEXT NOT NULL CHECK (kind IN ('delegated','exit','native','sponsored')),
|
|
70
|
+
guard_id TEXT NOT NULL
|
|
71
|
+
)`);
|
|
72
|
+
db.prepare('INSERT OR IGNORE INTO guard_meta (singleton, kind, guard_id) VALUES (1, ?, ?)')
|
|
73
|
+
.run(kind, id);
|
|
74
|
+
}
|
|
75
|
+
const meta = db.prepare('SELECT kind, guard_id FROM guard_meta WHERE singleton = 1').get();
|
|
76
|
+
if (meta?.kind !== kind || meta?.guard_id !== id)
|
|
77
|
+
throw new ExecutionWorkerGuardInvalidError();
|
|
78
|
+
try {
|
|
79
|
+
db.exec('BEGIN IMMEDIATE');
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new ExecutionWorkerBusyError();
|
|
83
|
+
}
|
|
84
|
+
// Re-read under the held lock. Replacing the file or metadata can never manufacture liveness.
|
|
85
|
+
const held = db.prepare('SELECT kind, guard_id FROM guard_meta WHERE singleton = 1').get();
|
|
86
|
+
if (held?.kind !== kind || held?.guard_id !== id)
|
|
87
|
+
throw new ExecutionWorkerGuardInvalidError();
|
|
88
|
+
let released = false;
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
kind,
|
|
91
|
+
id,
|
|
92
|
+
release() {
|
|
93
|
+
if (released)
|
|
94
|
+
return;
|
|
95
|
+
released = true;
|
|
96
|
+
try {
|
|
97
|
+
db.exec('ROLLBACK');
|
|
98
|
+
}
|
|
99
|
+
catch { /* close still releases the OS lock */ }
|
|
100
|
+
try {
|
|
101
|
+
db.close();
|
|
102
|
+
}
|
|
103
|
+
catch { /* best effort */ }
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
try {
|
|
109
|
+
db?.close();
|
|
110
|
+
}
|
|
111
|
+
catch { /* the original refusal stands */ }
|
|
112
|
+
if (error instanceof ExecutionWorkerBusyError || error instanceof ExecutionWorkerGuardInvalidError)
|
|
113
|
+
throw error;
|
|
114
|
+
// node:sqlite currently reports both SQLITE_BUSY and SQLITE_LOCKED through ERR_SQLITE_ERROR;
|
|
115
|
+
// the stable distinction available here is its fixed engine message. Nothing upstream is relayed.
|
|
116
|
+
const detail = error instanceof Error ? error.message.toLowerCase() : '';
|
|
117
|
+
if (detail.includes('database is locked') || detail.includes('database is busy'))
|
|
118
|
+
throw new ExecutionWorkerBusyError();
|
|
119
|
+
throw new ExecutionWorkerGuardInvalidError();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=execution-worker-guard.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Gated hybrid-yield library. Importing this module registers no MCP tool or signer authority. */
|
|
2
|
+
export * from './hybrid-yield-markets.js';
|
|
3
|
+
export * from './hybrid-yield.js';
|
|
4
|
+
export * from './native-yield-definition.js';
|
|
5
|
+
export * from './native-yield.js';
|
|
6
|
+
export * from './native-yield-reader.js';
|
|
7
|
+
export * from './native-yield-verify.js';
|
|
8
|
+
export * from './native-yield-settlement.js';
|
|
9
|
+
export * from './yield-route-semantics.js';
|
|
10
|
+
export * from './native-yield-simulation.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Gated hybrid-yield library. Importing this module registers no MCP tool or signer authority. */
|
|
2
|
+
export * from './hybrid-yield-markets.js';
|
|
3
|
+
export * from './hybrid-yield.js';
|
|
4
|
+
export * from './native-yield-definition.js';
|
|
5
|
+
export * from './native-yield.js';
|
|
6
|
+
export * from './native-yield-reader.js';
|
|
7
|
+
export * from './native-yield-verify.js';
|
|
8
|
+
export * from './native-yield-settlement.js';
|
|
9
|
+
export * from './yield-route-semantics.js';
|
|
10
|
+
export * from './native-yield-simulation.js';
|
|
11
|
+
//# sourceMappingURL=hybrid-yield-index.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type Address } from 'viem';
|
|
2
|
+
export type HybridYieldChainId = 8453 | 42161;
|
|
3
|
+
export type HybridYieldAction = 'deposit' | 'withdraw';
|
|
4
|
+
export interface HybridYieldMarket {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly discoveryMarketId: string;
|
|
7
|
+
readonly chainId: HybridYieldChainId;
|
|
8
|
+
readonly chainSlug: 'base' | 'arbitrum';
|
|
9
|
+
readonly protocol: 'AAVE' | 'MORPHO';
|
|
10
|
+
readonly venue: string;
|
|
11
|
+
readonly asset: {
|
|
12
|
+
readonly address: Address;
|
|
13
|
+
readonly symbol: 'USDC' | 'WETH';
|
|
14
|
+
readonly decimals: 6 | 18;
|
|
15
|
+
readonly group: 'USDC' | 'ETH';
|
|
16
|
+
readonly assetClass: 'stable' | 'eth';
|
|
17
|
+
};
|
|
18
|
+
readonly position: {
|
|
19
|
+
readonly tokenAddress: Address;
|
|
20
|
+
readonly symbol: string;
|
|
21
|
+
readonly decimals: 6 | 18;
|
|
22
|
+
readonly kind: 'erc4626-shares' | 'rebasing-atoken';
|
|
23
|
+
};
|
|
24
|
+
readonly protocolTarget: Address;
|
|
25
|
+
readonly curation: {
|
|
26
|
+
readonly status: 'reviewed-candidate' | 'legacy-exit-only' | 'existing-supported';
|
|
27
|
+
readonly reviewedAt: '2026-09-10';
|
|
28
|
+
readonly rationale: string;
|
|
29
|
+
readonly risks: readonly string[];
|
|
30
|
+
readonly sources: readonly string[];
|
|
31
|
+
};
|
|
32
|
+
readonly capabilities: {
|
|
33
|
+
readonly native: {
|
|
34
|
+
readonly deposit: 'gated' | 'unavailable';
|
|
35
|
+
readonly withdraw: 'gated';
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
};
|
|
38
|
+
readonly lifi: {
|
|
39
|
+
readonly deposit: 'existing-route' | 'unavailable';
|
|
40
|
+
readonly withdraw: 'existing-route' | 'unavailable';
|
|
41
|
+
readonly crossChain: 'gated';
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/** Old pair used ONLY for the already-reviewed LI.FI constructor. */
|
|
45
|
+
readonly legacyRegistryCommitment: `sha256:${string}` | null;
|
|
46
|
+
}
|
|
47
|
+
export declare function freezeHybridValue<T>(value: T): T;
|
|
48
|
+
export declare const HYBRID_YIELD_MARKETS: readonly HybridYieldMarket[];
|
|
49
|
+
export declare function hybridYieldCommitment(value: unknown): `sha256:${string}`;
|
|
50
|
+
export declare function getHybridYieldMarket(id: string): HybridYieldMarket | null;
|
|
51
|
+
export declare function getHybridYieldCapabilities(): {
|
|
52
|
+
schemaVersion: "1";
|
|
53
|
+
markets: {
|
|
54
|
+
registryCommitment: `sha256:${string}`;
|
|
55
|
+
id: string;
|
|
56
|
+
discoveryMarketId: string;
|
|
57
|
+
chainId: HybridYieldChainId;
|
|
58
|
+
chainSlug: "base" | "arbitrum";
|
|
59
|
+
protocol: "AAVE" | "MORPHO";
|
|
60
|
+
venue: string;
|
|
61
|
+
asset: {
|
|
62
|
+
readonly address: Address;
|
|
63
|
+
readonly symbol: "USDC" | "WETH";
|
|
64
|
+
readonly decimals: 6 | 18;
|
|
65
|
+
readonly group: "USDC" | "ETH";
|
|
66
|
+
readonly assetClass: "stable" | "eth";
|
|
67
|
+
};
|
|
68
|
+
position: {
|
|
69
|
+
readonly tokenAddress: Address;
|
|
70
|
+
readonly symbol: string;
|
|
71
|
+
readonly decimals: 6 | 18;
|
|
72
|
+
readonly kind: "erc4626-shares" | "rebasing-atoken";
|
|
73
|
+
};
|
|
74
|
+
protocolTarget: Address;
|
|
75
|
+
curation: {
|
|
76
|
+
readonly status: "reviewed-candidate" | "legacy-exit-only" | "existing-supported";
|
|
77
|
+
readonly reviewedAt: "2026-09-10";
|
|
78
|
+
readonly rationale: string;
|
|
79
|
+
readonly risks: readonly string[];
|
|
80
|
+
readonly sources: readonly string[];
|
|
81
|
+
};
|
|
82
|
+
capabilities: {
|
|
83
|
+
readonly native: {
|
|
84
|
+
readonly deposit: "gated" | "unavailable";
|
|
85
|
+
readonly withdraw: "gated";
|
|
86
|
+
readonly reason: string;
|
|
87
|
+
};
|
|
88
|
+
readonly lifi: {
|
|
89
|
+
readonly deposit: "existing-route" | "unavailable";
|
|
90
|
+
readonly withdraw: "existing-route" | "unavailable";
|
|
91
|
+
readonly crossChain: "gated";
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
legacyRegistryCommitment: `sha256:${string}` | null;
|
|
95
|
+
}[];
|
|
96
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain-general capability registry for hybrid Yield. These records DO NOT extend
|
|
3
|
+
* the legacy TEA/Safe or CDP allowlist in executable-yield-markets.ts.
|
|
4
|
+
* Curation evidence: docs/0910_hybrid_yield_curation.md (10 September 2026).
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { getAddress } from 'viem';
|
|
8
|
+
import { BASE_USDC_MORPHO_GAUNTLET_PRIME_MARKET, BASE_USDC_MORPHO_STEAKHOUSE_PRIME_MARKET, getExecutableYieldMarketCommitment, } from './executable-yield-markets.js';
|
|
9
|
+
export function freezeHybridValue(value) {
|
|
10
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
11
|
+
for (const child of Object.values(value))
|
|
12
|
+
freezeHybridValue(child);
|
|
13
|
+
Object.freeze(value);
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
const NATIVE_GATE = 'Requires reviewed native executor deployment, chain/account policy, limits and funded acceptance; registry membership grants no signing authority.';
|
|
18
|
+
const AAVE_RISKS = [
|
|
19
|
+
'Aave Pool and reserve configuration can be upgraded by governance.',
|
|
20
|
+
'Supply yield and available withdrawal liquidity vary with utilization; instant liquidity is not guaranteed.',
|
|
21
|
+
'Underlying token, oracle, bad-debt and chain risks remain; a protocol review is not a safety guarantee.',
|
|
22
|
+
];
|
|
23
|
+
const MORPHO_RISKS = [
|
|
24
|
+
'The vault curator chooses collateral markets, oracles and caps; review the exact vault, not only Morpho.',
|
|
25
|
+
'Borrower liquidation, bad debt, curator changes and depleted liquidity can impair value or delay exits.',
|
|
26
|
+
'USDC issuer/peg and Base network risks remain; APY is variable and not a guarantee.',
|
|
27
|
+
];
|
|
28
|
+
function aave(chainId, symbol, assetAddress, pool, receiptAddress, receiptSymbol) {
|
|
29
|
+
const chainSlug = chainId === 8453 ? 'base' : 'arbitrum';
|
|
30
|
+
return {
|
|
31
|
+
id: `${chainSlug}-${symbol.toLowerCase()}-aave-v3`,
|
|
32
|
+
discoveryMarketId: `aave:${chainId}:${pool.toLowerCase()}:${assetAddress.toLowerCase()}`,
|
|
33
|
+
chainId, chainSlug, protocol: 'AAVE', venue: `Aave V3 · ${symbol}`,
|
|
34
|
+
asset: {
|
|
35
|
+
address: getAddress(assetAddress), symbol, decimals: symbol === 'USDC' ? 6 : 18,
|
|
36
|
+
group: symbol === 'USDC' ? 'USDC' : 'ETH', assetClass: symbol === 'USDC' ? 'stable' : 'eth',
|
|
37
|
+
},
|
|
38
|
+
position: {
|
|
39
|
+
tokenAddress: getAddress(receiptAddress), symbol: receiptSymbol,
|
|
40
|
+
decimals: symbol === 'USDC' ? 6 : 18, kind: 'rebasing-atoken',
|
|
41
|
+
},
|
|
42
|
+
protocolTarget: getAddress(pool),
|
|
43
|
+
curation: {
|
|
44
|
+
status: 'reviewed-candidate', reviewedAt: '2026-09-10',
|
|
45
|
+
rationale: symbol === 'USDC'
|
|
46
|
+
? 'Established already-integrated lending protocol; exact native USDC reserve and receipt token verified. Candidate by integration/exit fit, not highest APY.'
|
|
47
|
+
: 'Separate ETH-denominated lending exposure using plain WETH. This is lending, not staking, and accepts neither native ETH nor a liquid-staking token.',
|
|
48
|
+
risks: AAVE_RISKS,
|
|
49
|
+
sources: [
|
|
50
|
+
`https://github.com/aave-dao/aave-address-book/blob/main/src/AaveV3${chainId === 8453 ? 'Base' : 'Arbitrum'}.sol`,
|
|
51
|
+
'https://aave.com/docs/aave-v3/smart-contracts/pool',
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
capabilities: {
|
|
55
|
+
native: { deposit: 'gated', withdraw: 'gated', reason: NATIVE_GATE },
|
|
56
|
+
lifi: { deposit: 'unavailable', withdraw: 'unavailable', crossChain: 'gated' },
|
|
57
|
+
},
|
|
58
|
+
legacyRegistryCommitment: null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function morpho(old, legacy) {
|
|
62
|
+
return {
|
|
63
|
+
id: old.id, discoveryMarketId: old.discoveryMarketId, chainId: 8453, chainSlug: 'base',
|
|
64
|
+
protocol: 'MORPHO', venue: old.venue,
|
|
65
|
+
asset: { ...old.asset, address: getAddress(old.asset.address), assetClass: 'stable' },
|
|
66
|
+
position: {
|
|
67
|
+
tokenAddress: getAddress(old.vaultAddress), symbol: legacy ? 'steakUSDC' : 'gtUSDCp',
|
|
68
|
+
decimals: old.vaultShareDecimals, kind: 'erc4626-shares',
|
|
69
|
+
},
|
|
70
|
+
protocolTarget: getAddress(old.protocolTarget),
|
|
71
|
+
curation: {
|
|
72
|
+
status: legacy ? 'legacy-exit-only' : 'existing-supported', reviewedAt: '2026-09-10',
|
|
73
|
+
rationale: legacy
|
|
74
|
+
? 'Steakhouse lists this exact vault as operational but deprioritized/superseded. Retain the position and exit; do not promote new allocations.'
|
|
75
|
+
: 'Retained exact previously supported vault identity. A technical execution pin is not a best-yield ranking or blanket curator approval.',
|
|
76
|
+
risks: MORPHO_RISKS,
|
|
77
|
+
sources: [
|
|
78
|
+
`https://app.morpho.org/base/vault/${old.vaultAddress}`,
|
|
79
|
+
...(legacy ? ['https://www.steakhouse.financial/docs/products/vault-products/legacy-vaults'] : []),
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
capabilities: {
|
|
83
|
+
native: { deposit: legacy ? 'unavailable' : 'gated', withdraw: 'gated', reason: NATIVE_GATE },
|
|
84
|
+
// Legacy positions remain routable for exit; new preparation is disabled.
|
|
85
|
+
lifi: { deposit: legacy ? 'unavailable' : 'existing-route', withdraw: 'existing-route', crossChain: 'gated' },
|
|
86
|
+
},
|
|
87
|
+
legacyRegistryCommitment: getExecutableYieldMarketCommitment(old),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export const HYBRID_YIELD_MARKETS = freezeHybridValue([
|
|
91
|
+
aave(8453, 'USDC', '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5', '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB', 'aBasUSDC'),
|
|
92
|
+
aave(8453, 'WETH', '0x4200000000000000000000000000000000000006', '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5', '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7', 'aBasWETH'),
|
|
93
|
+
aave(42161, 'USDC', '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', '0x794a61358D6845594F94dc1DB02A252b5b4814aD', '0x724dc807b04555b71ed48a6896b6F41593b8C637', 'aArbUSDCn'),
|
|
94
|
+
aave(42161, 'WETH', '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', '0x794a61358D6845594F94dc1DB02A252b5b4814aD', '0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8', 'aArbWETH'),
|
|
95
|
+
morpho(BASE_USDC_MORPHO_GAUNTLET_PRIME_MARKET, false),
|
|
96
|
+
morpho(BASE_USDC_MORPHO_STEAKHOUSE_PRIME_MARKET, true),
|
|
97
|
+
]);
|
|
98
|
+
function canonical(value) {
|
|
99
|
+
if (value === null || ['string', 'boolean'].includes(typeof value))
|
|
100
|
+
return JSON.stringify(value);
|
|
101
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
if (Array.isArray(value))
|
|
104
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
105
|
+
if (typeof value === 'object')
|
|
106
|
+
return `{${Object.entries(value)
|
|
107
|
+
.sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(',')}}`;
|
|
108
|
+
throw new Error('Non-canonical hybrid yield value');
|
|
109
|
+
}
|
|
110
|
+
export function hybridYieldCommitment(value) {
|
|
111
|
+
return `sha256:${createHash('sha256').update('otto:hybrid-yield:v1:' + canonical(value)).digest('hex')}`;
|
|
112
|
+
}
|
|
113
|
+
export function getHybridYieldMarket(id) {
|
|
114
|
+
return HYBRID_YIELD_MARKETS.find((m) => m.id === id) ?? null;
|
|
115
|
+
}
|
|
116
|
+
export function getHybridYieldCapabilities() {
|
|
117
|
+
return freezeHybridValue({
|
|
118
|
+
schemaVersion: '1',
|
|
119
|
+
markets: HYBRID_YIELD_MARKETS.map((market) => ({
|
|
120
|
+
...market, registryCommitment: hybridYieldCommitment(market),
|
|
121
|
+
})),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=hybrid-yield-markets.js.map
|