@agentlayer.tech/wallet 0.1.77 → 0.1.88
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/.openclaw/extensions/agent-wallet/dist/index.js +2 -2
- package/.openclaw/extensions/agent-wallet/index.ts +2 -2
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +15 -0
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +6 -4
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +31 -16
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/skills/wallet-operator/SKILL.md +1 -1
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/package.json +1 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/.env.example +5 -0
- package/wdk-evm-wallet/README.md +1 -0
- package/wdk-evm-wallet/package.json +1 -1
- package/wdk-evm-wallet/src/config.js +38 -0
- package/wdk-evm-wallet/src/server.js +18 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +870 -87
|
@@ -23,12 +23,68 @@ const DEFAULT_LIFI_SLIPPAGE = 0.005;
|
|
|
23
23
|
const ALWAYS_DENIED_LIFI_BRIDGES = ["mayan"];
|
|
24
24
|
const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
25
25
|
const UNISWAP_SUPPORTED_CHAIN_IDS = { ethereum: 1, base: 8453, robinhood: 4663 };
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// Every executable path is declared here rather than inferred from a Trading
|
|
27
|
+
// API response. A new chain/router version therefore requires an explicit,
|
|
28
|
+
// reviewed allow-list entry before it can receive a signed transaction.
|
|
29
|
+
const UNISWAP_EXECUTION_PROFILES = {
|
|
30
|
+
ethereum: {
|
|
31
|
+
chainId: 1,
|
|
32
|
+
universalRouters: { "2.0": "0x66a9893cc07d91d95644aedd05d03f95e1dba8af" },
|
|
33
|
+
wrappedNative: "0xc02aaA39b223fe8d0a0e5c4f27ead9083c756cc2".toLowerCase(),
|
|
34
|
+
ammProtocols: ["V2", "V3", "V4"],
|
|
35
|
+
},
|
|
36
|
+
base: {
|
|
37
|
+
chainId: 8453,
|
|
38
|
+
universalRouters: { "2.0": "0x6ff5693b99212da76ad316178a184ab56d299b43" },
|
|
39
|
+
wrappedNative: "0x4200000000000000000000000000000000000006",
|
|
40
|
+
ammProtocols: ["V2", "V3", "V4"],
|
|
41
|
+
},
|
|
42
|
+
robinhood: {
|
|
43
|
+
chainId: 4663,
|
|
44
|
+
// Robinhood Chain has no Universal Router 2.0 deployment - Uniswap
|
|
45
|
+
// defaults it (like Ink) to 2.1.1, and requesting "2.0" makes the Trading
|
|
46
|
+
// API return a blanket "No quotes available" for every pair on this
|
|
47
|
+
// chain. See https://blog.uniswap.org/robinhood-chain-is-live. Same
|
|
48
|
+
// contract address as before; only the version label changes.
|
|
49
|
+
universalRouters: { "2.1.1": "0x8876789976decbfcbbbe364623c63652db8c0904" },
|
|
50
|
+
wrappedNative: "0x0bd7d308f8e1639fab988df18a8011f41eacad73",
|
|
51
|
+
// Full protocol stack like ethereum/base - the earlier V3-only filter
|
|
52
|
+
// wasn't the actual blocker (see router version note above) but there's
|
|
53
|
+
// no reason to hide V2/V4 routes from the Trading API either.
|
|
54
|
+
ammProtocols: ["V2", "V3", "V4"],
|
|
55
|
+
v3DirectFallback: {
|
|
56
|
+
quoterV2: "0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7",
|
|
57
|
+
swapRouter02: "0xcaf681a66d020601342297493863e78c959e5cb2",
|
|
58
|
+
feeTiers: [100, 500, 3000, 10000],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
31
61
|
};
|
|
62
|
+
// Compatibility export for callers/tests. New execution code resolves a router
|
|
63
|
+
// from UNISWAP_EXECUTION_PROFILES and its explicitly selected version.
|
|
64
|
+
const UNISWAP_UNIVERSAL_ROUTER_BY_NETWORK = Object.fromEntries(
|
|
65
|
+
Object.entries(UNISWAP_EXECUTION_PROFILES).map(([network, profile]) => [
|
|
66
|
+
network,
|
|
67
|
+
profile.universalRouters["2.0"],
|
|
68
|
+
])
|
|
69
|
+
);
|
|
70
|
+
const UNISWAP_WRAPPED_NATIVE_BY_NETWORK = Object.fromEntries(
|
|
71
|
+
Object.entries(UNISWAP_EXECUTION_PROFILES).map(([network, profile]) => [
|
|
72
|
+
network,
|
|
73
|
+
profile.wrappedNative,
|
|
74
|
+
])
|
|
75
|
+
);
|
|
76
|
+
const WETH_DEPOSIT_SELECTOR = "0xd0e30db0";
|
|
77
|
+
const WETH_WITHDRAW_SELECTOR = "0x2e1a7d4d";
|
|
78
|
+
const UNISWAP_V3_QUOTER_INTERFACE = new Interface([
|
|
79
|
+
"function quoteExactInputSingle((address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96) params) external returns (uint256 amountOut,uint160 sqrtPriceX96After,uint32 initializedTicksCrossed,uint256 gasEstimate)",
|
|
80
|
+
]);
|
|
81
|
+
const UNISWAP_V3_SWAP_ROUTER_INTERFACE = new Interface([
|
|
82
|
+
"function exactInputSingle((address tokenIn,address tokenOut,uint24 fee,address recipient,uint256 amountIn,uint256 amountOutMinimum,uint160 sqrtPriceLimitX96) params) payable returns (uint256 amountOut)",
|
|
83
|
+
"function unwrapWETH9(uint256 amountMinimum,address recipient) payable",
|
|
84
|
+
"function multicall(bytes[] data) payable returns (bytes[] results)",
|
|
85
|
+
]);
|
|
86
|
+
const UNISWAP_CLASSIC_ROUTINGS = new Set(["CLASSIC"]);
|
|
87
|
+
const UNISWAPX_ROUTINGS = new Set(["DUTCH_V2", "DUTCH_V3", "LIMIT_ORDER", "PRIORITY"]);
|
|
32
88
|
const AAVE_RAY = 10n ** 27n;
|
|
33
89
|
const LIDO_STETH_DECIMALS = 18;
|
|
34
90
|
const LIDO_MIN_STETH_WITHDRAWAL_AMOUNT = 100n;
|
|
@@ -104,8 +160,20 @@ const MORPHO_MAX_LIST_LIMIT = 500;
|
|
|
104
160
|
// two minutes is below the indexer's own aggregation noise.
|
|
105
161
|
const MORPHO_DISCOVERY_CACHE_TTL_MS = 120_000;
|
|
106
162
|
const MORPHO_DISCOVERY_CACHE_MAX_ENTRIES = 64;
|
|
163
|
+
// DeFi routes can take a different on-chain branch between estimate time and
|
|
164
|
+
// inclusion. Estimate exact final calldata immediately before signing and use
|
|
165
|
+
// an explicit headroom rather than relying on the provider/WDK default.
|
|
166
|
+
const DEFI_GAS_BUFFER_BPS = 13_000n;
|
|
167
|
+
const MORPHO_GAS_BUFFER_BPS = DEFI_GAS_BUFFER_BPS;
|
|
168
|
+
const BPS_DENOMINATOR = 10_000n;
|
|
107
169
|
const morphoDiscoveryCache = new Map();
|
|
108
170
|
|
|
171
|
+
function applyGasBuffer(value, bufferBps = MORPHO_GAS_BUFFER_BPS) {
|
|
172
|
+
const normalizedValue = BigInt(value);
|
|
173
|
+
const normalizedBufferBps = BigInt(bufferBps);
|
|
174
|
+
return (normalizedValue * normalizedBufferBps + BPS_DENOMINATOR - 1n) / BPS_DENOMINATOR;
|
|
175
|
+
}
|
|
176
|
+
|
|
109
177
|
function morphoDiscoveryCacheGet(key) {
|
|
110
178
|
const entry = morphoDiscoveryCache.get(key);
|
|
111
179
|
if (!entry) {
|
|
@@ -857,6 +925,115 @@ function assertUniswapSupportedNetwork(network) {
|
|
|
857
925
|
return chainId;
|
|
858
926
|
}
|
|
859
927
|
|
|
928
|
+
function getUniswapNetworkExecutionProfile(network) {
|
|
929
|
+
const profile = UNISWAP_EXECUTION_PROFILES[network];
|
|
930
|
+
if (!profile) {
|
|
931
|
+
assertUniswapSupportedNetwork(network);
|
|
932
|
+
}
|
|
933
|
+
return profile;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function resolveUniswapRouterExecutionProfile(network, routerVersion) {
|
|
937
|
+
const networkProfile = getUniswapNetworkExecutionProfile(network);
|
|
938
|
+
const version = String(routerVersion || "2.0").trim() || "2.0";
|
|
939
|
+
const router = networkProfile.universalRouters[version];
|
|
940
|
+
if (!router) {
|
|
941
|
+
throw createTaggedError(
|
|
942
|
+
`Uniswap Universal Router version ${version} is not approved for ${network}.`,
|
|
943
|
+
"uniswap_router_version_unsupported",
|
|
944
|
+
{
|
|
945
|
+
network,
|
|
946
|
+
chainId: networkProfile.chainId,
|
|
947
|
+
requestedVersion: version,
|
|
948
|
+
supportedVersions: Object.keys(networkProfile.universalRouters),
|
|
949
|
+
}
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
return { ...networkProfile, routerVersion: version, router };
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function encodeUint256(value) {
|
|
956
|
+
return BigInt(value).toString(16).padStart(64, "0");
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function buildDirectWrappedNativeOperation(networkProfile, swapRequest) {
|
|
960
|
+
const wrappedNative = networkProfile.wrappedNative;
|
|
961
|
+
if (isZeroAddress(swapRequest.tokenIn) && swapRequest.tokenOut === wrappedNative) {
|
|
962
|
+
return {
|
|
963
|
+
executionKind: "wrap",
|
|
964
|
+
routing: "WRAP",
|
|
965
|
+
transaction: { to: wrappedNative, data: WETH_DEPOSIT_SELECTOR, value: swapRequest.tokenInAmount },
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
if (swapRequest.tokenIn === wrappedNative && isZeroAddress(swapRequest.tokenOut)) {
|
|
969
|
+
return {
|
|
970
|
+
executionKind: "unwrap",
|
|
971
|
+
routing: "UNWRAP",
|
|
972
|
+
transaction: {
|
|
973
|
+
to: wrappedNative,
|
|
974
|
+
data: `${WETH_WITHDRAW_SELECTOR}${encodeUint256(swapRequest.tokenInAmount)}`,
|
|
975
|
+
value: 0n,
|
|
976
|
+
},
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function buildUniswapV3DirectSwapTransaction({
|
|
983
|
+
networkProfile,
|
|
984
|
+
recipient,
|
|
985
|
+
swapRequest,
|
|
986
|
+
feeTier,
|
|
987
|
+
minimumTokenOutAmount,
|
|
988
|
+
}) {
|
|
989
|
+
const fallback = networkProfile.v3DirectFallback;
|
|
990
|
+
if (!fallback) {
|
|
991
|
+
throw new Error("Uniswap V3 direct fallback is not configured for this network.");
|
|
992
|
+
}
|
|
993
|
+
const nativeTokenIn = isZeroAddress(swapRequest.tokenIn);
|
|
994
|
+
const nativeTokenOut = isZeroAddress(swapRequest.tokenOut);
|
|
995
|
+
const effectiveTokenIn = nativeTokenIn ? networkProfile.wrappedNative : swapRequest.tokenIn;
|
|
996
|
+
const effectiveTokenOut = nativeTokenOut ? networkProfile.wrappedNative : swapRequest.tokenOut;
|
|
997
|
+
const exactInput = UNISWAP_V3_SWAP_ROUTER_INTERFACE.encodeFunctionData("exactInputSingle", [
|
|
998
|
+
{
|
|
999
|
+
tokenIn: effectiveTokenIn,
|
|
1000
|
+
tokenOut: effectiveTokenOut,
|
|
1001
|
+
fee: feeTier,
|
|
1002
|
+
// For an ETH output the router must receive WETH first, then unwrap it in
|
|
1003
|
+
// the same atomic multicall. All other paths transfer directly to the user.
|
|
1004
|
+
recipient: nativeTokenOut ? fallback.swapRouter02 : recipient,
|
|
1005
|
+
amountIn: swapRequest.tokenInAmount,
|
|
1006
|
+
amountOutMinimum: minimumTokenOutAmount,
|
|
1007
|
+
sqrtPriceLimitX96: 0,
|
|
1008
|
+
},
|
|
1009
|
+
]);
|
|
1010
|
+
const data = nativeTokenOut
|
|
1011
|
+
? UNISWAP_V3_SWAP_ROUTER_INTERFACE.encodeFunctionData("multicall(bytes[])", [
|
|
1012
|
+
[
|
|
1013
|
+
exactInput,
|
|
1014
|
+
UNISWAP_V3_SWAP_ROUTER_INTERFACE.encodeFunctionData("unwrapWETH9", [
|
|
1015
|
+
minimumTokenOutAmount,
|
|
1016
|
+
recipient,
|
|
1017
|
+
]),
|
|
1018
|
+
],
|
|
1019
|
+
])
|
|
1020
|
+
: exactInput;
|
|
1021
|
+
return {
|
|
1022
|
+
to: fallback.swapRouter02,
|
|
1023
|
+
data,
|
|
1024
|
+
value: nativeTokenIn ? swapRequest.tokenInAmount : 0n,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function isUniswapNoQuotesAvailableError(error) {
|
|
1029
|
+
return (
|
|
1030
|
+
error &&
|
|
1031
|
+
typeof error === "object" &&
|
|
1032
|
+
error.errorCode === "network_unavailable" &&
|
|
1033
|
+
/no quotes available/i.test(String(error.message || ""))
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
860
1037
|
function uniswapSlippagePercentFromBps(bps) {
|
|
861
1038
|
const parsed = Number(bps);
|
|
862
1039
|
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 5000) {
|
|
@@ -2352,12 +2529,20 @@ export class WdkEvmWalletService {
|
|
|
2352
2529
|
const protocol = this.#createMorphoProtocol(account, runtimeConfig, request);
|
|
2353
2530
|
let result;
|
|
2354
2531
|
try {
|
|
2355
|
-
result = await
|
|
2356
|
-
|
|
2357
|
-
|
|
2532
|
+
result = await this.#sendMorphoProtocolOperation({
|
|
2533
|
+
account,
|
|
2534
|
+
runtimeConfig,
|
|
2535
|
+
protocol,
|
|
2536
|
+
request,
|
|
2537
|
+
});
|
|
2358
2538
|
} finally {
|
|
2359
2539
|
await maybeDispose(protocol);
|
|
2360
2540
|
}
|
|
2541
|
+
await this.#waitForTransactionReceipt(runtimeConfig, result.hash, {
|
|
2542
|
+
operationLabel: "Morpho operation",
|
|
2543
|
+
failureCode: "morpho_operation_reverted",
|
|
2544
|
+
timeoutCode: "morpho_operation_confirmation_timeout",
|
|
2545
|
+
});
|
|
2361
2546
|
const resultFee = BigInt(result?.fee || finalPlan.operationFee || 0);
|
|
2362
2547
|
const totalFee = requirementExecution.totalFee + resultFee;
|
|
2363
2548
|
return {
|
|
@@ -2383,6 +2568,7 @@ export class WdkEvmWalletService {
|
|
|
2383
2568
|
requirementsFee: requirementExecution.totalFee.toString(),
|
|
2384
2569
|
requirements: requirementExecution.transactions,
|
|
2385
2570
|
},
|
|
2571
|
+
confirmed: true,
|
|
2386
2572
|
};
|
|
2387
2573
|
} catch (error) {
|
|
2388
2574
|
const cleanup = await this.#restoreMorphoRequirementsAfterFailedOperation({
|
|
@@ -2529,12 +2715,20 @@ export class WdkEvmWalletService {
|
|
|
2529
2715
|
const protocol = this.#createMorphoProtocol(account, runtimeConfig, request);
|
|
2530
2716
|
let result;
|
|
2531
2717
|
try {
|
|
2532
|
-
result = await
|
|
2533
|
-
|
|
2534
|
-
|
|
2718
|
+
result = await this.#sendMorphoProtocolOperation({
|
|
2719
|
+
account,
|
|
2720
|
+
runtimeConfig,
|
|
2721
|
+
protocol,
|
|
2722
|
+
request,
|
|
2723
|
+
});
|
|
2535
2724
|
} finally {
|
|
2536
2725
|
await maybeDispose(protocol);
|
|
2537
2726
|
}
|
|
2727
|
+
await this.#waitForTransactionReceipt(runtimeConfig, result.hash, {
|
|
2728
|
+
operationLabel: "Morpho operation",
|
|
2729
|
+
failureCode: "morpho_operation_reverted",
|
|
2730
|
+
timeoutCode: "morpho_operation_confirmation_timeout",
|
|
2731
|
+
});
|
|
2538
2732
|
const resultFee = BigInt(result?.fee || finalPlan.operationFee || 0);
|
|
2539
2733
|
const totalFee = requirementExecution.totalFee + resultFee;
|
|
2540
2734
|
return {
|
|
@@ -2560,6 +2754,7 @@ export class WdkEvmWalletService {
|
|
|
2560
2754
|
requirementsFee: requirementExecution.totalFee.toString(),
|
|
2561
2755
|
requirements: requirementExecution.transactions,
|
|
2562
2756
|
},
|
|
2757
|
+
confirmed: true,
|
|
2563
2758
|
};
|
|
2564
2759
|
} catch (error) {
|
|
2565
2760
|
const cleanup = await this.#restoreMorphoRequirementsAfterFailedOperation({
|
|
@@ -2696,13 +2891,25 @@ export class WdkEvmWalletService {
|
|
|
2696
2891
|
const protocol = new AaveProtocolEvm(account);
|
|
2697
2892
|
let result;
|
|
2698
2893
|
try {
|
|
2699
|
-
result = await
|
|
2700
|
-
|
|
2701
|
-
|
|
2894
|
+
result = await this.#sendBufferedDefiProtocolOperation({
|
|
2895
|
+
account,
|
|
2896
|
+
runtimeConfig,
|
|
2897
|
+
operationLabel: `Aave ${request.operation}`,
|
|
2898
|
+
operation: request.operation,
|
|
2899
|
+
invoke: () =>
|
|
2900
|
+
protocol[request.operation]({
|
|
2901
|
+
token: request.token,
|
|
2902
|
+
amount: request.amount,
|
|
2903
|
+
}),
|
|
2702
2904
|
});
|
|
2703
2905
|
} finally {
|
|
2704
2906
|
await maybeDispose(protocol);
|
|
2705
2907
|
}
|
|
2908
|
+
await this.#waitForTransactionReceipt(runtimeConfig, result.hash, {
|
|
2909
|
+
operationLabel: "Aave operation",
|
|
2910
|
+
failureCode: "aave_operation_reverted",
|
|
2911
|
+
timeoutCode: "aave_operation_confirmation_timeout",
|
|
2912
|
+
});
|
|
2706
2913
|
const resultFee = BigInt(result?.fee || 0);
|
|
2707
2914
|
const totalFee = approvalExecution.totalFee + resultFee;
|
|
2708
2915
|
return {
|
|
@@ -2731,6 +2938,7 @@ export class WdkEvmWalletService {
|
|
|
2731
2938
|
? { resetAllowanceHash: approvalExecution.resetAllowanceHash }
|
|
2732
2939
|
: {}),
|
|
2733
2940
|
},
|
|
2941
|
+
confirmed: true,
|
|
2734
2942
|
};
|
|
2735
2943
|
} catch (error) {
|
|
2736
2944
|
const cleanup = await this.#restoreAllowanceAfterFailedAaveOperation({
|
|
@@ -3012,7 +3220,19 @@ export class WdkEvmWalletService {
|
|
|
3012
3220
|
);
|
|
3013
3221
|
}
|
|
3014
3222
|
|
|
3015
|
-
const result = await
|
|
3223
|
+
const result = await this.#sendBufferedDefiTransaction({
|
|
3224
|
+
account,
|
|
3225
|
+
runtimeConfig,
|
|
3226
|
+
from: address,
|
|
3227
|
+
tx: finalPlan.operationTx,
|
|
3228
|
+
operationLabel: `Lido ${request.operation}`,
|
|
3229
|
+
operation: request.operation,
|
|
3230
|
+
});
|
|
3231
|
+
await this.#waitForTransactionReceipt(runtimeConfig, result.hash, {
|
|
3232
|
+
operationLabel: "Lido operation",
|
|
3233
|
+
failureCode: "lido_operation_reverted",
|
|
3234
|
+
timeoutCode: "lido_operation_confirmation_timeout",
|
|
3235
|
+
});
|
|
3016
3236
|
const resultFee = BigInt(result?.fee || finalPlan.operationFee || 0);
|
|
3017
3237
|
const totalFee = approvalExecution.totalFee + resultFee;
|
|
3018
3238
|
return {
|
|
@@ -3041,6 +3261,7 @@ export class WdkEvmWalletService {
|
|
|
3041
3261
|
? { resetAllowanceHash: approvalExecution.resetAllowanceHash }
|
|
3042
3262
|
: {}),
|
|
3043
3263
|
},
|
|
3264
|
+
confirmed: true,
|
|
3044
3265
|
};
|
|
3045
3266
|
} catch (error) {
|
|
3046
3267
|
const cleanup = await this.#restoreAllowanceAfterFailedLidoOperation({
|
|
@@ -3163,7 +3384,19 @@ export class WdkEvmWalletService {
|
|
|
3163
3384
|
);
|
|
3164
3385
|
}
|
|
3165
3386
|
|
|
3166
|
-
const result = await
|
|
3387
|
+
const result = await this.#sendBufferedDefiTransaction({
|
|
3388
|
+
account,
|
|
3389
|
+
runtimeConfig,
|
|
3390
|
+
from: address,
|
|
3391
|
+
tx: finalPlan.operationTx,
|
|
3392
|
+
operationLabel: `Lido ${request.operation}`,
|
|
3393
|
+
operation: request.operation,
|
|
3394
|
+
});
|
|
3395
|
+
await this.#waitForTransactionReceipt(runtimeConfig, result.hash, {
|
|
3396
|
+
operationLabel: "Lido withdrawal",
|
|
3397
|
+
failureCode: "lido_withdrawal_reverted",
|
|
3398
|
+
timeoutCode: "lido_withdrawal_confirmation_timeout",
|
|
3399
|
+
});
|
|
3167
3400
|
const resultFee = BigInt(result?.fee || finalPlan.operationFee || 0);
|
|
3168
3401
|
const totalFee = approvalExecution.totalFee + resultFee;
|
|
3169
3402
|
return {
|
|
@@ -3192,6 +3425,7 @@ export class WdkEvmWalletService {
|
|
|
3192
3425
|
? { resetAllowanceHash: approvalExecution.resetAllowanceHash }
|
|
3193
3426
|
: {}),
|
|
3194
3427
|
},
|
|
3428
|
+
confirmed: true,
|
|
3195
3429
|
};
|
|
3196
3430
|
} catch (error) {
|
|
3197
3431
|
const cleanup = await this.#restoreAllowanceAfterFailedLidoWithdrawal({
|
|
@@ -3424,12 +3658,23 @@ export class WdkEvmWalletService {
|
|
|
3424
3658
|
})
|
|
3425
3659
|
: finalPlan.simulation;
|
|
3426
3660
|
this.#assertSimulationSucceeded(effectiveSimulation);
|
|
3427
|
-
const
|
|
3428
|
-
|
|
3661
|
+
const swapResult = await this.#sendBufferedDefiTransaction({
|
|
3662
|
+
account,
|
|
3663
|
+
runtimeConfig,
|
|
3664
|
+
from: address,
|
|
3665
|
+
tx: finalPlan.swapTx,
|
|
3666
|
+
operationLabel: "Velora swap",
|
|
3667
|
+
});
|
|
3668
|
+
await this.#waitForTransactionReceipt(runtimeConfig, swapResult.hash, {
|
|
3669
|
+
operationLabel: "Velora swap",
|
|
3670
|
+
failureCode: "swap_reverted",
|
|
3671
|
+
timeoutCode: "swap_confirmation_timeout",
|
|
3672
|
+
});
|
|
3673
|
+
const totalFee = approvalExecution.totalFee + BigInt(swapResult.fee);
|
|
3429
3674
|
const result = {
|
|
3430
|
-
|
|
3675
|
+
...swapResult,
|
|
3431
3676
|
fee: totalFee.toString(),
|
|
3432
|
-
swapFee:
|
|
3677
|
+
swapFee: swapResult.fee,
|
|
3433
3678
|
approvalFee: approvalExecution.totalFee.toString(),
|
|
3434
3679
|
tokenInAmount: finalPlan.tokenInAmount.toString(),
|
|
3435
3680
|
tokenOutAmount: finalPlan.tokenOutAmount.toString(),
|
|
@@ -3470,6 +3715,7 @@ export class WdkEvmWalletService {
|
|
|
3470
3715
|
simulation: effectiveSimulation,
|
|
3471
3716
|
swapTransaction: finalPlan.swapTransaction,
|
|
3472
3717
|
result,
|
|
3718
|
+
confirmed: true,
|
|
3473
3719
|
source: "wdk-protocol-swap-velora-evm",
|
|
3474
3720
|
};
|
|
3475
3721
|
} catch (error) {
|
|
@@ -3580,12 +3826,23 @@ export class WdkEvmWalletService {
|
|
|
3580
3826
|
: finalPlan.simulation;
|
|
3581
3827
|
this.#assertSimulationSucceeded(effectiveSimulation);
|
|
3582
3828
|
|
|
3583
|
-
const
|
|
3584
|
-
|
|
3829
|
+
const swapResult = await this.#sendBufferedDefiTransaction({
|
|
3830
|
+
account,
|
|
3831
|
+
runtimeConfig,
|
|
3832
|
+
from: sourceAddress,
|
|
3833
|
+
tx: finalPlan.swapTx,
|
|
3834
|
+
operationLabel: "LI.FI swap",
|
|
3835
|
+
});
|
|
3836
|
+
await this.#waitForTransactionReceipt(runtimeConfig, swapResult.hash, {
|
|
3837
|
+
operationLabel: "LI.FI swap",
|
|
3838
|
+
failureCode: "swap_reverted",
|
|
3839
|
+
timeoutCode: "swap_confirmation_timeout",
|
|
3840
|
+
});
|
|
3841
|
+
const totalFee = approvalExecution.totalFee + BigInt(swapResult.fee);
|
|
3585
3842
|
const result = {
|
|
3586
|
-
|
|
3843
|
+
...swapResult,
|
|
3587
3844
|
fee: totalFee.toString(),
|
|
3588
|
-
swapFee:
|
|
3845
|
+
swapFee: swapResult.fee,
|
|
3589
3846
|
approvalFee: approvalExecution.totalFee.toString(),
|
|
3590
3847
|
tokenInAmount: finalPlan.tokenInAmount.toString(),
|
|
3591
3848
|
tokenOutAmount: finalPlan.tokenOutAmount.toString(),
|
|
@@ -3612,6 +3869,7 @@ export class WdkEvmWalletService {
|
|
|
3612
3869
|
},
|
|
3613
3870
|
}),
|
|
3614
3871
|
result,
|
|
3872
|
+
confirmed: true,
|
|
3615
3873
|
};
|
|
3616
3874
|
} catch (error) {
|
|
3617
3875
|
const cleanup = await this.#restoreAllowanceAfterFailedSwap({
|
|
@@ -3757,36 +4015,84 @@ export class WdkEvmWalletService {
|
|
|
3757
4015
|
);
|
|
3758
4016
|
}
|
|
3759
4017
|
|
|
3760
|
-
const signature =
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
: await this.#signUniswapPermit(account, finalPlan.permitData, runtimeConfig);
|
|
3764
|
-
|
|
3765
|
-
const swapTx = await this.#fetchUniswapSwapCalldata({
|
|
3766
|
-
runtimeConfig,
|
|
3767
|
-
quoteResponse: finalPlan.quoteResponse,
|
|
3768
|
-
permitData: finalPlan.permitData,
|
|
3769
|
-
signature,
|
|
3770
|
-
});
|
|
4018
|
+
const signature = finalPlan.permitData
|
|
4019
|
+
? await this.#signUniswapPermit(account, finalPlan.permitData, runtimeConfig)
|
|
4020
|
+
: null;
|
|
3771
4021
|
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
4022
|
+
let simulation;
|
|
4023
|
+
let swapTransaction = null;
|
|
4024
|
+
let result;
|
|
4025
|
+
if (finalPlan.isUniswapX) {
|
|
4026
|
+
const order = await this.#submitUniswapOrder({
|
|
4027
|
+
quoteResponse: finalPlan.quoteResponse,
|
|
4028
|
+
signature,
|
|
4029
|
+
nativeTokenIn: finalPlan.isNativeTokenIn,
|
|
4030
|
+
routing: finalPlan.routing,
|
|
4031
|
+
routerProfile: finalPlan.routerProfile,
|
|
4032
|
+
});
|
|
4033
|
+
simulation = {
|
|
4034
|
+
ok: true,
|
|
4035
|
+
skipped: true,
|
|
4036
|
+
reason: "uniswapx-order",
|
|
4037
|
+
message: "UniswapX validates and submits the signed order to its filler network.",
|
|
4038
|
+
details: null,
|
|
4039
|
+
};
|
|
4040
|
+
result = {
|
|
4041
|
+
orderId: order.orderId,
|
|
4042
|
+
orderStatus: order.orderStatus,
|
|
4043
|
+
approvalFee: approvalExecution.totalFee.toString(),
|
|
4044
|
+
tokenInAmount: finalPlan.tokenInAmount.toString(),
|
|
4045
|
+
tokenOutAmount: finalPlan.tokenOutAmount.toString(),
|
|
4046
|
+
...(approvalExecution.approveHash ? { approveHash: approvalExecution.approveHash } : {}),
|
|
4047
|
+
...(approvalExecution.resetAllowanceHash
|
|
4048
|
+
? { resetAllowanceHash: approvalExecution.resetAllowanceHash }
|
|
4049
|
+
: {}),
|
|
4050
|
+
};
|
|
4051
|
+
} else {
|
|
4052
|
+
const swapTx =
|
|
4053
|
+
finalPlan.preparedTransaction ||
|
|
4054
|
+
(await this.#fetchUniswapSwapCalldata({
|
|
4055
|
+
runtimeConfig,
|
|
4056
|
+
routerProfile: finalPlan.routerProfile,
|
|
4057
|
+
quoteResponse: finalPlan.quoteResponse,
|
|
4058
|
+
permitData: finalPlan.permitData,
|
|
4059
|
+
signature,
|
|
4060
|
+
}));
|
|
4061
|
+
simulation = await this.#simulatePreparedTransaction({
|
|
4062
|
+
runtimeConfig,
|
|
4063
|
+
from: address,
|
|
4064
|
+
tx: swapTx,
|
|
4065
|
+
});
|
|
4066
|
+
this.#assertSimulationSucceeded(simulation);
|
|
3778
4067
|
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
:
|
|
3789
|
-
|
|
4068
|
+
const swapResult = await this.#sendBufferedDefiTransaction({
|
|
4069
|
+
account,
|
|
4070
|
+
runtimeConfig,
|
|
4071
|
+
from: address,
|
|
4072
|
+
tx: swapTx,
|
|
4073
|
+
operationLabel: "Uniswap swap",
|
|
4074
|
+
});
|
|
4075
|
+
await this.#waitForTransactionReceipt(runtimeConfig, swapResult.hash, {
|
|
4076
|
+
operationLabel: "Uniswap swap",
|
|
4077
|
+
failureCode: "swap_reverted",
|
|
4078
|
+
timeoutCode: "swap_confirmation_timeout",
|
|
4079
|
+
});
|
|
4080
|
+
swapTransaction = {
|
|
4081
|
+
to: swapTx.to,
|
|
4082
|
+
value: swapTx.value.toString(),
|
|
4083
|
+
dataHash: sha256Hex(swapTx.data),
|
|
4084
|
+
};
|
|
4085
|
+
result = {
|
|
4086
|
+
...swapResult,
|
|
4087
|
+
approvalFee: approvalExecution.totalFee.toString(),
|
|
4088
|
+
tokenInAmount: finalPlan.tokenInAmount.toString(),
|
|
4089
|
+
tokenOutAmount: finalPlan.tokenOutAmount.toString(),
|
|
4090
|
+
...(approvalExecution.approveHash ? { approveHash: approvalExecution.approveHash } : {}),
|
|
4091
|
+
...(approvalExecution.resetAllowanceHash
|
|
4092
|
+
? { resetAllowanceHash: approvalExecution.resetAllowanceHash }
|
|
4093
|
+
: {}),
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
3790
4096
|
return {
|
|
3791
4097
|
...(await this.#formatUniswapSwapResponse({
|
|
3792
4098
|
runtimeConfig,
|
|
@@ -3802,8 +4108,9 @@ export class WdkEvmWalletService {
|
|
|
3802
4108
|
},
|
|
3803
4109
|
})),
|
|
3804
4110
|
simulation,
|
|
3805
|
-
swapTransaction
|
|
4111
|
+
swapTransaction,
|
|
3806
4112
|
result,
|
|
4113
|
+
confirmed: Boolean(result.hash),
|
|
3807
4114
|
};
|
|
3808
4115
|
} catch (error) {
|
|
3809
4116
|
const cleanup = await this.#restoreAllowanceAfterFailedSwap({
|
|
@@ -3845,7 +4152,8 @@ export class WdkEvmWalletService {
|
|
|
3845
4152
|
address,
|
|
3846
4153
|
protocol: "uniswap",
|
|
3847
4154
|
executionSupported: true,
|
|
3848
|
-
|
|
4155
|
+
executionKind: plan.executionKind,
|
|
4156
|
+
routing: plan.routing,
|
|
3849
4157
|
swapRequest: {
|
|
3850
4158
|
tokenIn: swapRequest.tokenIn,
|
|
3851
4159
|
tokenOut: swapRequest.tokenOut,
|
|
@@ -3854,6 +4162,7 @@ export class WdkEvmWalletService {
|
|
|
3854
4162
|
tokenInMetadata,
|
|
3855
4163
|
tokenOutMetadata,
|
|
3856
4164
|
inputAmountFormatted: formatUnits(swapRequest.tokenInAmount, tokenInMetadata.decimals),
|
|
4165
|
+
outputAmount: plan.tokenOutAmount.toString(),
|
|
3857
4166
|
outputAmountFormatted: formatUnits(plan.tokenOutAmount, tokenOutMetadata.decimals),
|
|
3858
4167
|
quoteFingerprint: plan.quoteFingerprint,
|
|
3859
4168
|
slippageBps: plan.slippageBps,
|
|
@@ -3871,7 +4180,19 @@ export class WdkEvmWalletService {
|
|
|
3871
4180
|
readError: plan.allowanceReadError,
|
|
3872
4181
|
},
|
|
3873
4182
|
router: plan.router,
|
|
3874
|
-
|
|
4183
|
+
swapTransaction: plan.preparedTransaction
|
|
4184
|
+
? {
|
|
4185
|
+
to: plan.preparedTransaction.to,
|
|
4186
|
+
value: plan.preparedTransaction.value.toString(),
|
|
4187
|
+
dataHash: sha256Hex(plan.preparedTransaction.data),
|
|
4188
|
+
}
|
|
4189
|
+
: null,
|
|
4190
|
+
source:
|
|
4191
|
+
plan.executionKind === "wrap" || plan.executionKind === "unwrap"
|
|
4192
|
+
? "canonical-wrapped-native"
|
|
4193
|
+
: plan.executionKind === "v3-direct"
|
|
4194
|
+
? "uniswap-v3-direct-fallback"
|
|
4195
|
+
: "uniswap-trading-api",
|
|
3875
4196
|
};
|
|
3876
4197
|
}
|
|
3877
4198
|
|
|
@@ -4325,6 +4646,150 @@ export class WdkEvmWalletService {
|
|
|
4325
4646
|
return options;
|
|
4326
4647
|
}
|
|
4327
4648
|
|
|
4649
|
+
async #sendMorphoProtocolOperation({ account, runtimeConfig, protocol, request }) {
|
|
4650
|
+
const methods = this.#getMorphoOperationMethods(request);
|
|
4651
|
+
const originalSendTransaction = account.sendTransaction;
|
|
4652
|
+
if (typeof originalSendTransaction !== "function") {
|
|
4653
|
+
throw new Error("Morpho operation requires an EVM account with sendTransaction().");
|
|
4654
|
+
}
|
|
4655
|
+
const hadOwnSendTransaction = Object.prototype.hasOwnProperty.call(account, "sendTransaction");
|
|
4656
|
+
|
|
4657
|
+
// The protocol constructs the final calldata immediately before sending it.
|
|
4658
|
+
// Intercept that one protocol-scoped send so the estimate and padded limit
|
|
4659
|
+
// are based on the exact transaction, rather than a stale preview.
|
|
4660
|
+
account.sendTransaction = async (tx) => {
|
|
4661
|
+
const gas = await this.#prepareMorphoGasLimit({
|
|
4662
|
+
runtimeConfig,
|
|
4663
|
+
from: await account.getAddress(),
|
|
4664
|
+
tx,
|
|
4665
|
+
operation: request.operation,
|
|
4666
|
+
});
|
|
4667
|
+
const result = await originalSendTransaction.call(account, {
|
|
4668
|
+
...tx,
|
|
4669
|
+
gasLimit: gas.gasLimit,
|
|
4670
|
+
});
|
|
4671
|
+
return {
|
|
4672
|
+
...result,
|
|
4673
|
+
fee: gas.maximumFee,
|
|
4674
|
+
gasEstimate: gas.gasEstimate.toString(),
|
|
4675
|
+
gasLimit: gas.gasLimit.toString(),
|
|
4676
|
+
gasBufferBps: MORPHO_GAS_BUFFER_BPS.toString(),
|
|
4677
|
+
};
|
|
4678
|
+
};
|
|
4679
|
+
|
|
4680
|
+
try {
|
|
4681
|
+
return await protocol[methods.sendMethod](this.#buildMorphoOperationOptions(request));
|
|
4682
|
+
} finally {
|
|
4683
|
+
if (hadOwnSendTransaction) {
|
|
4684
|
+
account.sendTransaction = originalSendTransaction;
|
|
4685
|
+
} else {
|
|
4686
|
+
delete account.sendTransaction;
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
async #prepareMorphoGasLimit({ runtimeConfig, from, tx, operation }) {
|
|
4692
|
+
return this.#prepareBufferedDefiGasLimit({
|
|
4693
|
+
runtimeConfig,
|
|
4694
|
+
from,
|
|
4695
|
+
tx,
|
|
4696
|
+
operationLabel: `morpho ${operation}`,
|
|
4697
|
+
unavailableCode: "morpho_gas_estimate_unavailable",
|
|
4698
|
+
unavailableMessage: "Morpho gas estimate was empty. Generate a new quote before sending.",
|
|
4699
|
+
});
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4702
|
+
async #prepareBufferedDefiGasLimit({
|
|
4703
|
+
runtimeConfig,
|
|
4704
|
+
from,
|
|
4705
|
+
tx,
|
|
4706
|
+
operationLabel,
|
|
4707
|
+
unavailableCode = "defi_gas_estimate_unavailable",
|
|
4708
|
+
unavailableMessage = "DeFi gas estimate was empty. Generate a new quote before sending.",
|
|
4709
|
+
}) {
|
|
4710
|
+
const gasEstimateHex = await rpcRequest(runtimeConfig.providerUrl, "eth_estimateGas", [
|
|
4711
|
+
{
|
|
4712
|
+
from: normalizeAddress(from, "from"),
|
|
4713
|
+
to: normalizeAddress(String(tx.to || ""), "to"),
|
|
4714
|
+
data: assertNonEmptyString(String(tx.data || ""), "data"),
|
|
4715
|
+
value: toRpcHex(tx.value || 0),
|
|
4716
|
+
},
|
|
4717
|
+
]);
|
|
4718
|
+
const gasEstimate = BigInt(gasEstimateHex || "0x0");
|
|
4719
|
+
if (gasEstimate <= 0n) {
|
|
4720
|
+
throw createTaggedError(unavailableMessage, unavailableCode, { operation: operationLabel });
|
|
4721
|
+
}
|
|
4722
|
+
const gasLimit = applyGasBuffer(gasEstimate, DEFI_GAS_BUFFER_BPS);
|
|
4723
|
+
const effectiveFeePerGas = await this.#getEffectiveGasPrice(runtimeConfig);
|
|
4724
|
+
const maximumFee = gasLimit * effectiveFeePerGas;
|
|
4725
|
+
this.#assertMaxFee(runtimeConfig, maximumFee, operationLabel);
|
|
4726
|
+
return { gasEstimate, gasLimit, maximumFee };
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
async #sendBufferedDefiTransaction({ account, runtimeConfig, from, tx, operationLabel }) {
|
|
4730
|
+
return this.#sendBufferedDefiTransactionWithSender({
|
|
4731
|
+
sendTransaction: (preparedTx) => account.sendTransaction(preparedTx),
|
|
4732
|
+
runtimeConfig,
|
|
4733
|
+
from,
|
|
4734
|
+
tx,
|
|
4735
|
+
operationLabel,
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
async #sendBufferedDefiTransactionWithSender({
|
|
4740
|
+
sendTransaction,
|
|
4741
|
+
runtimeConfig,
|
|
4742
|
+
from,
|
|
4743
|
+
tx,
|
|
4744
|
+
operationLabel,
|
|
4745
|
+
}) {
|
|
4746
|
+
const gas = await this.#prepareBufferedDefiGasLimit({
|
|
4747
|
+
runtimeConfig,
|
|
4748
|
+
from,
|
|
4749
|
+
tx,
|
|
4750
|
+
operationLabel,
|
|
4751
|
+
});
|
|
4752
|
+
const result = await sendTransaction({ ...tx, gasLimit: gas.gasLimit });
|
|
4753
|
+
return {
|
|
4754
|
+
...result,
|
|
4755
|
+
fee: gas.maximumFee.toString(),
|
|
4756
|
+
gasEstimate: gas.gasEstimate.toString(),
|
|
4757
|
+
gasLimit: gas.gasLimit.toString(),
|
|
4758
|
+
gasBufferBps: DEFI_GAS_BUFFER_BPS.toString(),
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
async #sendBufferedDefiProtocolOperation({
|
|
4763
|
+
account,
|
|
4764
|
+
runtimeConfig,
|
|
4765
|
+
operationLabel,
|
|
4766
|
+
operation,
|
|
4767
|
+
invoke,
|
|
4768
|
+
}) {
|
|
4769
|
+
const originalSendTransaction = account.sendTransaction;
|
|
4770
|
+
if (typeof originalSendTransaction !== "function") {
|
|
4771
|
+
throw new Error(`${operationLabel} requires an EVM account with sendTransaction().`);
|
|
4772
|
+
}
|
|
4773
|
+
const hadOwnSendTransaction = Object.prototype.hasOwnProperty.call(account, "sendTransaction");
|
|
4774
|
+
account.sendTransaction = async (tx) =>
|
|
4775
|
+
this.#sendBufferedDefiTransactionWithSender({
|
|
4776
|
+
sendTransaction: (preparedTx) => originalSendTransaction.call(account, preparedTx),
|
|
4777
|
+
runtimeConfig,
|
|
4778
|
+
from: await account.getAddress(),
|
|
4779
|
+
tx,
|
|
4780
|
+
operationLabel: `${operationLabel} ${operation}`,
|
|
4781
|
+
});
|
|
4782
|
+
try {
|
|
4783
|
+
return await invoke();
|
|
4784
|
+
} finally {
|
|
4785
|
+
if (hadOwnSendTransaction) {
|
|
4786
|
+
account.sendTransaction = originalSendTransaction;
|
|
4787
|
+
} else {
|
|
4788
|
+
delete account.sendTransaction;
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
|
|
4328
4793
|
async #buildMorphoOperationPlan({
|
|
4329
4794
|
account,
|
|
4330
4795
|
runtimeConfig,
|
|
@@ -4525,7 +4990,7 @@ export class WdkEvmWalletService {
|
|
|
4525
4990
|
try {
|
|
4526
4991
|
const quote = await protocol[methods.quoteMethod](operationOptions);
|
|
4527
4992
|
return {
|
|
4528
|
-
fee: BigInt(quote?.fee || 0),
|
|
4993
|
+
fee: applyGasBuffer(BigInt(quote?.fee || 0), MORPHO_GAS_BUFFER_BPS),
|
|
4529
4994
|
error: null,
|
|
4530
4995
|
};
|
|
4531
4996
|
} catch (error) {
|
|
@@ -4622,11 +5087,23 @@ export class WdkEvmWalletService {
|
|
|
4622
5087
|
for (let index = 0; index < plan.requirements.transactions.length; index += 1) {
|
|
4623
5088
|
const requirementTx = plan.requirements.transactions[index];
|
|
4624
5089
|
const step = plan.requirements.steps[index];
|
|
4625
|
-
const
|
|
5090
|
+
const tx = {
|
|
4626
5091
|
to: requirementTx.to,
|
|
4627
5092
|
value: requirementTx.value ?? 0n,
|
|
4628
5093
|
data: requirementTx.data,
|
|
4629
|
-
}
|
|
5094
|
+
};
|
|
5095
|
+
// ERC-20 approval writes intentionally retain the narrow WDK path. A
|
|
5096
|
+
// Morpho authorization is a protocol write, so give it the same buffer
|
|
5097
|
+
// as the potentially stateful final operation.
|
|
5098
|
+
const result = step?.type === "authorization"
|
|
5099
|
+
? await this.#sendBufferedDefiTransaction({
|
|
5100
|
+
account,
|
|
5101
|
+
runtimeConfig,
|
|
5102
|
+
from: await account.getAddress(),
|
|
5103
|
+
tx,
|
|
5104
|
+
operationLabel: "Morpho authorization",
|
|
5105
|
+
})
|
|
5106
|
+
: await account.sendTransaction(tx);
|
|
4630
5107
|
const fee = BigInt(result?.fee || 0);
|
|
4631
5108
|
totalFee += fee;
|
|
4632
5109
|
transactions.push({
|
|
@@ -4702,13 +5179,19 @@ export class WdkEvmWalletService {
|
|
|
4702
5179
|
}
|
|
4703
5180
|
|
|
4704
5181
|
for (const context of requirementExecution.authorizationContexts || []) {
|
|
4705
|
-
const result = await
|
|
5182
|
+
const result = await this.#sendBufferedDefiTransaction({
|
|
5183
|
+
account,
|
|
5184
|
+
runtimeConfig,
|
|
5185
|
+
from: await account.getAddress(),
|
|
5186
|
+
operationLabel: "Morpho authorization cleanup",
|
|
5187
|
+
tx: {
|
|
4706
5188
|
to: context.contractAddress,
|
|
4707
5189
|
value: 0n,
|
|
4708
5190
|
data: MORPHO_AUTHORIZATION_INTERFACE.encodeFunctionData("setAuthorization", [
|
|
4709
5191
|
context.authorized,
|
|
4710
5192
|
false,
|
|
4711
5193
|
]),
|
|
5194
|
+
},
|
|
4712
5195
|
});
|
|
4713
5196
|
cleanup.authorizations.push({
|
|
4714
5197
|
contractAddress: context.contractAddress,
|
|
@@ -5179,12 +5662,19 @@ export class WdkEvmWalletService {
|
|
|
5179
5662
|
};
|
|
5180
5663
|
}
|
|
5181
5664
|
|
|
5182
|
-
async #uniswapTradingApiRequest(
|
|
5665
|
+
async #uniswapTradingApiRequest(
|
|
5666
|
+
pathname,
|
|
5667
|
+
body,
|
|
5668
|
+
{ erc20EthEnabled = false, routerVersion = this.config.uniswapRouterVersion, chainId = null } = {}
|
|
5669
|
+
) {
|
|
5183
5670
|
const headers = {
|
|
5184
5671
|
"Content-Type": "application/json",
|
|
5185
5672
|
Accept: "application/json",
|
|
5186
|
-
"x-universal-router-version":
|
|
5673
|
+
"x-universal-router-version": routerVersion,
|
|
5187
5674
|
};
|
|
5675
|
+
if (erc20EthEnabled) {
|
|
5676
|
+
headers["x-erc20eth-enabled"] = "true";
|
|
5677
|
+
}
|
|
5188
5678
|
if (this.config.uniswapViaGateway) {
|
|
5189
5679
|
// The provider gateway holds the Uniswap key and injects x-api-key upstream;
|
|
5190
5680
|
// we authenticate to it with the shared gateway bearer (same token used for
|
|
@@ -5193,6 +5683,13 @@ export class WdkEvmWalletService {
|
|
|
5193
5683
|
if (token) {
|
|
5194
5684
|
headers.Authorization = `Bearer ${token}`;
|
|
5195
5685
|
}
|
|
5686
|
+
// The gateway selects its own configured version and checks this value for
|
|
5687
|
+
// equality. The chain id is an allow-listed routing hint, never an upstream
|
|
5688
|
+
// header passthrough.
|
|
5689
|
+
if (chainId !== null) {
|
|
5690
|
+
headers["x-agentlayer-chain-id"] = String(chainId);
|
|
5691
|
+
headers["x-agentlayer-uniswap-router-version"] = routerVersion;
|
|
5692
|
+
}
|
|
5196
5693
|
} else {
|
|
5197
5694
|
if (!this.config.uniswapApiKey) {
|
|
5198
5695
|
throw createTaggedError(
|
|
@@ -5246,9 +5743,9 @@ export class WdkEvmWalletService {
|
|
|
5246
5743
|
return payload;
|
|
5247
5744
|
}
|
|
5248
5745
|
|
|
5249
|
-
async #fetchUniswapQuote({ runtimeConfig, address, swapRequest }) {
|
|
5746
|
+
async #fetchUniswapQuote({ runtimeConfig, routerProfile, address, swapRequest }) {
|
|
5250
5747
|
const chainId = UNISWAP_SUPPORTED_CHAIN_IDS[runtimeConfig.network];
|
|
5251
|
-
const
|
|
5748
|
+
const quoteRequest = {
|
|
5252
5749
|
swapper: address,
|
|
5253
5750
|
tokenIn: swapRequest.tokenIn,
|
|
5254
5751
|
tokenOut: swapRequest.tokenOut,
|
|
@@ -5257,21 +5754,50 @@ export class WdkEvmWalletService {
|
|
|
5257
5754
|
amount: swapRequest.tokenInAmount.toString(),
|
|
5258
5755
|
type: "EXACT_INPUT",
|
|
5259
5756
|
slippageTolerance: swapRequest.slippagePercent,
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5757
|
+
};
|
|
5758
|
+
const requestOptions = {
|
|
5759
|
+
erc20EthEnabled: isZeroAddress(swapRequest.tokenIn),
|
|
5760
|
+
routerVersion: routerProfile.routerVersion,
|
|
5761
|
+
chainId,
|
|
5762
|
+
};
|
|
5763
|
+
const ammProtocols = routerProfile.ammProtocols;
|
|
5764
|
+
let payload;
|
|
5765
|
+
try {
|
|
5766
|
+
// Prefer the full route set. On a sub-minimum UniswapX request the API can
|
|
5767
|
+
// return No quotes available even when an AMM pool is liquid, so retry the
|
|
5768
|
+
// same exact swap through AMMs only before reporting a quote failure.
|
|
5769
|
+
payload = await this.#uniswapTradingApiRequest(
|
|
5770
|
+
"/quote",
|
|
5771
|
+
{
|
|
5772
|
+
...quoteRequest,
|
|
5773
|
+
protocols: [
|
|
5774
|
+
...ammProtocols,
|
|
5775
|
+
runtimeConfig.network === "ethereum" ? "UNISWAPX_V2" : "UNISWAPX_V3",
|
|
5776
|
+
],
|
|
5777
|
+
},
|
|
5778
|
+
requestOptions
|
|
5779
|
+
);
|
|
5780
|
+
} catch (error) {
|
|
5781
|
+
if (!isUniswapNoQuotesAvailableError(error)) {
|
|
5782
|
+
throw error;
|
|
5783
|
+
}
|
|
5784
|
+
payload = await this.#uniswapTradingApiRequest(
|
|
5785
|
+
"/quote",
|
|
5786
|
+
{ ...quoteRequest, protocols: ammProtocols },
|
|
5787
|
+
requestOptions
|
|
5788
|
+
);
|
|
5789
|
+
}
|
|
5264
5790
|
const routing = String(payload.routing || "").toUpperCase();
|
|
5265
|
-
if (routing
|
|
5791
|
+
if (!UNISWAP_CLASSIC_ROUTINGS.has(routing) && !UNISWAPX_ROUTINGS.has(routing)) {
|
|
5266
5792
|
throw createTaggedError(
|
|
5267
|
-
`Uniswap returned unsupported routing '${routing}'
|
|
5793
|
+
`Uniswap returned unsupported routing '${routing}'.`,
|
|
5268
5794
|
"uniswap_unsupported_route",
|
|
5269
5795
|
{ provider: "uniswap", routing }
|
|
5270
5796
|
);
|
|
5271
5797
|
}
|
|
5272
5798
|
if (!payload.quote || typeof payload.quote !== "object" || !payload.quote.output) {
|
|
5273
5799
|
throw createTaggedError(
|
|
5274
|
-
"Uniswap quote response is missing
|
|
5800
|
+
"Uniswap quote response is missing quote/output fields.",
|
|
5275
5801
|
"network_unavailable",
|
|
5276
5802
|
{ provider: "uniswap" }
|
|
5277
5803
|
);
|
|
@@ -5279,8 +5805,195 @@ export class WdkEvmWalletService {
|
|
|
5279
5805
|
return payload;
|
|
5280
5806
|
}
|
|
5281
5807
|
|
|
5808
|
+
async #quoteRobinhoodV3Direct({ runtimeConfig, networkProfile, swapRequest }) {
|
|
5809
|
+
const fallback = networkProfile.v3DirectFallback;
|
|
5810
|
+
if (!fallback) {
|
|
5811
|
+
return null;
|
|
5812
|
+
}
|
|
5813
|
+
const tokenIn = isZeroAddress(swapRequest.tokenIn)
|
|
5814
|
+
? networkProfile.wrappedNative
|
|
5815
|
+
: swapRequest.tokenIn;
|
|
5816
|
+
const tokenOut = isZeroAddress(swapRequest.tokenOut)
|
|
5817
|
+
? networkProfile.wrappedNative
|
|
5818
|
+
: swapRequest.tokenOut;
|
|
5819
|
+
let best = null;
|
|
5820
|
+
for (const feeTier of fallback.feeTiers) {
|
|
5821
|
+
const data = UNISWAP_V3_QUOTER_INTERFACE.encodeFunctionData("quoteExactInputSingle", [
|
|
5822
|
+
{
|
|
5823
|
+
tokenIn,
|
|
5824
|
+
tokenOut,
|
|
5825
|
+
amountIn: swapRequest.tokenInAmount,
|
|
5826
|
+
fee: feeTier,
|
|
5827
|
+
sqrtPriceLimitX96: 0,
|
|
5828
|
+
},
|
|
5829
|
+
]);
|
|
5830
|
+
try {
|
|
5831
|
+
const raw = await rpcRequest(runtimeConfig.providerUrl, "eth_call", [
|
|
5832
|
+
{ to: fallback.quoterV2, data },
|
|
5833
|
+
"latest",
|
|
5834
|
+
]);
|
|
5835
|
+
const decoded = UNISWAP_V3_QUOTER_INTERFACE.decodeFunctionResult(
|
|
5836
|
+
"quoteExactInputSingle",
|
|
5837
|
+
raw
|
|
5838
|
+
);
|
|
5839
|
+
const amountOut = BigInt(decoded[0]);
|
|
5840
|
+
if (amountOut > 0n && (!best || amountOut > best.amountOut)) {
|
|
5841
|
+
best = { feeTier, amountOut, gasEstimate: BigInt(decoded[3]) };
|
|
5842
|
+
}
|
|
5843
|
+
} catch {
|
|
5844
|
+
// A missing pool and a failed Quoter call are equivalent for this
|
|
5845
|
+
// constrained direct route. Try every reviewed fee tier before failing.
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
return best;
|
|
5849
|
+
}
|
|
5850
|
+
|
|
5851
|
+
async #buildRobinhoodV3DirectFallbackPlan({ account, runtimeConfig, address, swapRequest }) {
|
|
5852
|
+
const networkProfile = getUniswapNetworkExecutionProfile(runtimeConfig.network);
|
|
5853
|
+
const quoted = await this.#quoteRobinhoodV3Direct({
|
|
5854
|
+
runtimeConfig,
|
|
5855
|
+
networkProfile,
|
|
5856
|
+
swapRequest,
|
|
5857
|
+
});
|
|
5858
|
+
if (!quoted) {
|
|
5859
|
+
return null;
|
|
5860
|
+
}
|
|
5861
|
+
const slippageBps = Math.round(swapRequest.slippagePercent * 100);
|
|
5862
|
+
const minimumTokenOutAmount =
|
|
5863
|
+
quoted.amountOut - (quoted.amountOut * BigInt(slippageBps)) / 10000n;
|
|
5864
|
+
const nativeTokenIn = isZeroAddress(swapRequest.tokenIn);
|
|
5865
|
+
const spender = nativeTokenIn ? null : networkProfile.v3DirectFallback.swapRouter02;
|
|
5866
|
+
const allowanceState = nativeTokenIn
|
|
5867
|
+
? { currentAllowance: swapRequest.tokenInAmount, error: null }
|
|
5868
|
+
: await this.#getSwapAllowanceState({
|
|
5869
|
+
account,
|
|
5870
|
+
tokenAddress: swapRequest.tokenIn,
|
|
5871
|
+
spender,
|
|
5872
|
+
});
|
|
5873
|
+
const approval = nativeTokenIn
|
|
5874
|
+
? { required: false, estimatedFee: 0n, steps: [] }
|
|
5875
|
+
: await this.#buildSwapApprovalPlan({
|
|
5876
|
+
account,
|
|
5877
|
+
runtimeConfig,
|
|
5878
|
+
tokenAddress: swapRequest.tokenIn,
|
|
5879
|
+
spender,
|
|
5880
|
+
requiredAmount: swapRequest.tokenInAmount,
|
|
5881
|
+
currentAllowance: allowanceState.currentAllowance,
|
|
5882
|
+
});
|
|
5883
|
+
const preparedTransaction = buildUniswapV3DirectSwapTransaction({
|
|
5884
|
+
networkProfile,
|
|
5885
|
+
recipient: address,
|
|
5886
|
+
swapRequest,
|
|
5887
|
+
feeTier: quoted.feeTier,
|
|
5888
|
+
minimumTokenOutAmount,
|
|
5889
|
+
});
|
|
5890
|
+
const quoteFingerprint = sha256Hex(
|
|
5891
|
+
JSON.stringify({
|
|
5892
|
+
chainId: runtimeConfig.chainId,
|
|
5893
|
+
network: runtimeConfig.network,
|
|
5894
|
+
from: address.toLowerCase(),
|
|
5895
|
+
router: networkProfile.v3DirectFallback.swapRouter02,
|
|
5896
|
+
spender: spender ? spender.toLowerCase() : null,
|
|
5897
|
+
tokenIn: swapRequest.tokenIn.toLowerCase(),
|
|
5898
|
+
tokenOut: swapRequest.tokenOut.toLowerCase(),
|
|
5899
|
+
tokenInAmount: swapRequest.tokenInAmount.toString(),
|
|
5900
|
+
feeTier: quoted.feeTier,
|
|
5901
|
+
slippageBps,
|
|
5902
|
+
executionKind: "v3-direct",
|
|
5903
|
+
})
|
|
5904
|
+
);
|
|
5905
|
+
return {
|
|
5906
|
+
quoteResponse: null,
|
|
5907
|
+
permitData: null,
|
|
5908
|
+
isNativeTokenIn: nativeTokenIn,
|
|
5909
|
+
spender,
|
|
5910
|
+
currentAllowance: allowanceState.currentAllowance,
|
|
5911
|
+
allowanceReadError: allowanceState.error,
|
|
5912
|
+
approval,
|
|
5913
|
+
tokenInAmount: swapRequest.tokenInAmount,
|
|
5914
|
+
tokenOutAmount: quoted.amountOut,
|
|
5915
|
+
minimumTokenOutAmount,
|
|
5916
|
+
slippageBps,
|
|
5917
|
+
routing: "CLASSIC",
|
|
5918
|
+
executionKind: "v3-direct",
|
|
5919
|
+
isUniswapX: false,
|
|
5920
|
+
quoteFingerprint,
|
|
5921
|
+
// QuoterV2 returns a gas-unit estimate, not a wei-denominated fee.
|
|
5922
|
+
// Keep the fee field empty rather than mislabeling units in the preview.
|
|
5923
|
+
gasFee: null,
|
|
5924
|
+
gasFeeUSD: null,
|
|
5925
|
+
router: networkProfile.v3DirectFallback.swapRouter02,
|
|
5926
|
+
routerProfile: null,
|
|
5927
|
+
preparedTransaction,
|
|
5928
|
+
v3FeeTier: quoted.feeTier,
|
|
5929
|
+
};
|
|
5930
|
+
}
|
|
5931
|
+
|
|
5282
5932
|
async #buildUniswapSwapPlan({ account, runtimeConfig, address, swapRequest }) {
|
|
5283
|
-
const
|
|
5933
|
+
const networkProfile = getUniswapNetworkExecutionProfile(runtimeConfig.network);
|
|
5934
|
+
const directOperation = buildDirectWrappedNativeOperation(networkProfile, swapRequest);
|
|
5935
|
+
const slippageBps = Math.round(swapRequest.slippagePercent * 100);
|
|
5936
|
+
if (directOperation) {
|
|
5937
|
+
const quoteFingerprint = sha256Hex(
|
|
5938
|
+
JSON.stringify({
|
|
5939
|
+
chainId: runtimeConfig.chainId,
|
|
5940
|
+
network: runtimeConfig.network,
|
|
5941
|
+
from: address.toLowerCase(),
|
|
5942
|
+
executionKind: directOperation.executionKind,
|
|
5943
|
+
wrappedNative: networkProfile.wrappedNative,
|
|
5944
|
+
tokenIn: swapRequest.tokenIn,
|
|
5945
|
+
tokenOut: swapRequest.tokenOut,
|
|
5946
|
+
tokenInAmount: swapRequest.tokenInAmount.toString(),
|
|
5947
|
+
})
|
|
5948
|
+
);
|
|
5949
|
+
return {
|
|
5950
|
+
quoteResponse: null,
|
|
5951
|
+
permitData: null,
|
|
5952
|
+
isNativeTokenIn: isZeroAddress(swapRequest.tokenIn),
|
|
5953
|
+
spender: null,
|
|
5954
|
+
currentAllowance: 0n,
|
|
5955
|
+
allowanceReadError: null,
|
|
5956
|
+
approval: { required: false, estimatedFee: 0n, steps: [] },
|
|
5957
|
+
tokenInAmount: swapRequest.tokenInAmount,
|
|
5958
|
+
tokenOutAmount: swapRequest.tokenInAmount,
|
|
5959
|
+
minimumTokenOutAmount: swapRequest.tokenInAmount,
|
|
5960
|
+
slippageBps,
|
|
5961
|
+
routing: directOperation.routing,
|
|
5962
|
+
executionKind: directOperation.executionKind,
|
|
5963
|
+
isUniswapX: false,
|
|
5964
|
+
quoteFingerprint,
|
|
5965
|
+
gasFee: null,
|
|
5966
|
+
gasFeeUSD: null,
|
|
5967
|
+
router: networkProfile.wrappedNative,
|
|
5968
|
+
routerProfile: null,
|
|
5969
|
+
preparedTransaction: directOperation.transaction,
|
|
5970
|
+
};
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5973
|
+
const routerProfile = this.#resolveUniswapRouterProfile(runtimeConfig);
|
|
5974
|
+
let quoteResponse;
|
|
5975
|
+
try {
|
|
5976
|
+
quoteResponse = await this.#fetchUniswapQuote({
|
|
5977
|
+
runtimeConfig,
|
|
5978
|
+
routerProfile,
|
|
5979
|
+
address,
|
|
5980
|
+
swapRequest,
|
|
5981
|
+
});
|
|
5982
|
+
} catch (error) {
|
|
5983
|
+
if (runtimeConfig.network !== "robinhood" || !isUniswapNoQuotesAvailableError(error)) {
|
|
5984
|
+
throw error;
|
|
5985
|
+
}
|
|
5986
|
+
const fallbackPlan = await this.#buildRobinhoodV3DirectFallbackPlan({
|
|
5987
|
+
account,
|
|
5988
|
+
runtimeConfig,
|
|
5989
|
+
address,
|
|
5990
|
+
swapRequest,
|
|
5991
|
+
});
|
|
5992
|
+
if (fallbackPlan) {
|
|
5993
|
+
return fallbackPlan;
|
|
5994
|
+
}
|
|
5995
|
+
throw error;
|
|
5996
|
+
}
|
|
5284
5997
|
const permitData = quoteResponse.permitData ?? null;
|
|
5285
5998
|
const isNativeTokenIn = isZeroAddress(swapRequest.tokenIn);
|
|
5286
5999
|
const spender = isNativeTokenIn ? null : PERMIT2_ADDRESS;
|
|
@@ -5302,16 +6015,17 @@ export class WdkEvmWalletService {
|
|
|
5302
6015
|
requiredAmount: swapRequest.tokenInAmount,
|
|
5303
6016
|
currentAllowance,
|
|
5304
6017
|
});
|
|
5305
|
-
const
|
|
5306
|
-
const
|
|
6018
|
+
const quoteOutput = quoteResponse.quote.output;
|
|
6019
|
+
const tokenOutAmount = BigInt(String(quoteOutput.amount || "0"));
|
|
5307
6020
|
// The Trading API returns the post-slippage floor directly; fall back to a
|
|
5308
6021
|
// local computation only if the field is absent.
|
|
5309
6022
|
const minimumTokenOutAmount =
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
? BigInt(String(quoteResponse.quote.output.minimumAmount))
|
|
6023
|
+
quoteOutput.minimumAmount !== undefined && quoteOutput.minimumAmount !== null
|
|
6024
|
+
? BigInt(String(quoteOutput.minimumAmount))
|
|
5313
6025
|
: tokenOutAmount - (tokenOutAmount * BigInt(slippageBps)) / 10000n;
|
|
5314
|
-
const
|
|
6026
|
+
const routing = String(quoteResponse.routing || "").toUpperCase();
|
|
6027
|
+
const isUniswapX = UNISWAPX_ROUTINGS.has(routing);
|
|
6028
|
+
const router = isUniswapX ? null : routerProfile.router;
|
|
5315
6029
|
// Bind only the stable swap *intent* (who/what/how-much/slippage/route), never
|
|
5316
6030
|
// the live quoted output: the Trading API re-prices every block, so including
|
|
5317
6031
|
// tokenOutAmount here made execute's re-quote fingerprint differ from preview's
|
|
@@ -5323,13 +6037,15 @@ export class WdkEvmWalletService {
|
|
|
5323
6037
|
chainId: runtimeConfig.chainId,
|
|
5324
6038
|
network: runtimeConfig.network,
|
|
5325
6039
|
from: address.toLowerCase(),
|
|
5326
|
-
router: router.toLowerCase(),
|
|
6040
|
+
router: router ? router.toLowerCase() : null,
|
|
5327
6041
|
spender: spender ? spender.toLowerCase() : null,
|
|
5328
6042
|
tokenIn: swapRequest.tokenIn.toLowerCase(),
|
|
5329
6043
|
tokenOut: swapRequest.tokenOut.toLowerCase(),
|
|
5330
6044
|
tokenInAmount: swapRequest.tokenInAmount.toString(),
|
|
5331
6045
|
slippageBps,
|
|
5332
|
-
routing
|
|
6046
|
+
routing,
|
|
6047
|
+
executionKind: isUniswapX ? "uniswapx" : "classic",
|
|
6048
|
+
routerVersion: routerProfile.routerVersion,
|
|
5333
6049
|
})
|
|
5334
6050
|
);
|
|
5335
6051
|
return {
|
|
@@ -5344,13 +6060,25 @@ export class WdkEvmWalletService {
|
|
|
5344
6060
|
tokenOutAmount,
|
|
5345
6061
|
minimumTokenOutAmount,
|
|
5346
6062
|
slippageBps,
|
|
6063
|
+
routing,
|
|
6064
|
+
executionKind: isUniswapX ? "uniswapx" : "classic",
|
|
6065
|
+
isUniswapX,
|
|
5347
6066
|
quoteFingerprint,
|
|
5348
6067
|
gasFee: quoteResponse.quote.gasFee ?? null,
|
|
5349
6068
|
gasFeeUSD: quoteResponse.quote.gasFeeUSD ?? null,
|
|
5350
6069
|
router,
|
|
6070
|
+
routerProfile,
|
|
6071
|
+
preparedTransaction: null,
|
|
5351
6072
|
};
|
|
5352
6073
|
}
|
|
5353
6074
|
|
|
6075
|
+
#resolveUniswapRouterProfile(runtimeConfig) {
|
|
6076
|
+
const configuredVersion =
|
|
6077
|
+
this.config.uniswapRouterVersionsByNetwork?.[runtimeConfig.network] ||
|
|
6078
|
+
this.config.uniswapRouterVersion;
|
|
6079
|
+
return resolveUniswapRouterExecutionProfile(runtimeConfig.network, configuredVersion);
|
|
6080
|
+
}
|
|
6081
|
+
|
|
5354
6082
|
async #signUniswapPermit(account, permitData, runtimeConfig) {
|
|
5355
6083
|
const typed = normalizeUniswapPermitData(permitData, runtimeConfig);
|
|
5356
6084
|
return account.signTypedData({
|
|
@@ -5360,7 +6088,13 @@ export class WdkEvmWalletService {
|
|
|
5360
6088
|
});
|
|
5361
6089
|
}
|
|
5362
6090
|
|
|
5363
|
-
async #fetchUniswapSwapCalldata({
|
|
6091
|
+
async #fetchUniswapSwapCalldata({
|
|
6092
|
+
runtimeConfig,
|
|
6093
|
+
routerProfile,
|
|
6094
|
+
quoteResponse,
|
|
6095
|
+
permitData,
|
|
6096
|
+
signature,
|
|
6097
|
+
}) {
|
|
5364
6098
|
const { permitData: _permitData, permitTransaction: _permitTransaction, ...cleanQuote } =
|
|
5365
6099
|
quoteResponse;
|
|
5366
6100
|
const body = { ...cleanQuote };
|
|
@@ -5370,10 +6104,13 @@ export class WdkEvmWalletService {
|
|
|
5370
6104
|
body.signature = signature;
|
|
5371
6105
|
body.permitData = permitData;
|
|
5372
6106
|
}
|
|
5373
|
-
const payload = await this.#uniswapTradingApiRequest("/swap", body
|
|
6107
|
+
const payload = await this.#uniswapTradingApiRequest("/swap", body, {
|
|
6108
|
+
routerVersion: routerProfile.routerVersion,
|
|
6109
|
+
chainId: runtimeConfig.chainId,
|
|
6110
|
+
});
|
|
5374
6111
|
const swap = payload.swap || {};
|
|
5375
6112
|
const to = normalizeAddress(String(swap.to || ""), "swap.to");
|
|
5376
|
-
const expectedRouter =
|
|
6113
|
+
const expectedRouter = routerProfile.router;
|
|
5377
6114
|
if (to.toLowerCase() !== expectedRouter) {
|
|
5378
6115
|
throw createTaggedError(
|
|
5379
6116
|
"Uniswap /swap returned an unexpected target contract.",
|
|
@@ -5382,6 +6119,7 @@ export class WdkEvmWalletService {
|
|
|
5382
6119
|
);
|
|
5383
6120
|
}
|
|
5384
6121
|
const data = assertNonEmptyString(String(swap.data || ""), "swap.data");
|
|
6122
|
+
const value = parseHexOrDecimalBigInt(swap.value || "0", "swap.value");
|
|
5385
6123
|
if (data === "0x") {
|
|
5386
6124
|
throw createTaggedError(
|
|
5387
6125
|
"Uniswap /swap returned empty calldata. The quote likely expired; generate a new preview.",
|
|
@@ -5392,7 +6130,38 @@ export class WdkEvmWalletService {
|
|
|
5392
6130
|
return {
|
|
5393
6131
|
to,
|
|
5394
6132
|
data,
|
|
5395
|
-
value
|
|
6133
|
+
value,
|
|
6134
|
+
};
|
|
6135
|
+
}
|
|
6136
|
+
|
|
6137
|
+
async #submitUniswapOrder({ quoteResponse, signature, nativeTokenIn, routing, routerProfile }) {
|
|
6138
|
+
if (!signature) {
|
|
6139
|
+
throw createTaggedError(
|
|
6140
|
+
"UniswapX order quote is missing required signature data.",
|
|
6141
|
+
"uniswap_order_signature_missing",
|
|
6142
|
+
{ provider: "uniswap" }
|
|
6143
|
+
);
|
|
6144
|
+
}
|
|
6145
|
+
const payload = await this.#uniswapTradingApiRequest(
|
|
6146
|
+
"/order",
|
|
6147
|
+
{ quote: quoteResponse.quote, routing, signature },
|
|
6148
|
+
{
|
|
6149
|
+
erc20EthEnabled: nativeTokenIn,
|
|
6150
|
+
routerVersion: routerProfile.routerVersion,
|
|
6151
|
+
chainId: routerProfile.chainId,
|
|
6152
|
+
}
|
|
6153
|
+
);
|
|
6154
|
+
const orderId = String(payload.orderId || payload.order?.orderId || "").trim();
|
|
6155
|
+
if (!orderId) {
|
|
6156
|
+
throw createTaggedError(
|
|
6157
|
+
"Uniswap /order response is missing orderId.",
|
|
6158
|
+
"network_unavailable",
|
|
6159
|
+
{ provider: "uniswap" }
|
|
6160
|
+
);
|
|
6161
|
+
}
|
|
6162
|
+
return {
|
|
6163
|
+
orderId,
|
|
6164
|
+
orderStatus: String(payload.orderStatus || payload.order?.orderStatus || "open"),
|
|
5396
6165
|
};
|
|
5397
6166
|
}
|
|
5398
6167
|
|
|
@@ -7533,13 +8302,21 @@ export class WdkEvmWalletService {
|
|
|
7533
8302
|
}
|
|
7534
8303
|
}
|
|
7535
8304
|
|
|
7536
|
-
async #waitForTransactionReceipt(
|
|
8305
|
+
async #waitForTransactionReceipt(
|
|
8306
|
+
runtimeConfig,
|
|
8307
|
+
txHash,
|
|
8308
|
+
{
|
|
8309
|
+
operationLabel = "Approval transaction",
|
|
8310
|
+
failureCode = "swap_approval_failed",
|
|
8311
|
+
timeoutCode = "swap_approval_timeout",
|
|
8312
|
+
} = {}
|
|
8313
|
+
) {
|
|
7537
8314
|
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
7538
8315
|
const receipt = await rpcRequest(runtimeConfig.providerUrl, "eth_getTransactionReceipt", [txHash]);
|
|
7539
8316
|
if (receipt) {
|
|
7540
8317
|
const status = String(receipt.status || "").toLowerCase();
|
|
7541
8318
|
if (status === "0x0") {
|
|
7542
|
-
throw createTaggedError(
|
|
8319
|
+
throw createTaggedError(`${operationLabel} reverted onchain.`, failureCode, {
|
|
7543
8320
|
txHash,
|
|
7544
8321
|
network: runtimeConfig.network,
|
|
7545
8322
|
});
|
|
@@ -7549,8 +8326,8 @@ export class WdkEvmWalletService {
|
|
|
7549
8326
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
7550
8327
|
}
|
|
7551
8328
|
throw createTaggedError(
|
|
7552
|
-
|
|
7553
|
-
|
|
8329
|
+
`Timed out waiting for ${operationLabel.toLowerCase()} confirmation.`,
|
|
8330
|
+
timeoutCode,
|
|
7554
8331
|
{
|
|
7555
8332
|
txHash,
|
|
7556
8333
|
network: runtimeConfig.network,
|
|
@@ -7560,11 +8337,17 @@ export class WdkEvmWalletService {
|
|
|
7560
8337
|
}
|
|
7561
8338
|
|
|
7562
8339
|
export const __testables = {
|
|
8340
|
+
applyGasBuffer,
|
|
8341
|
+
MORPHO_GAS_BUFFER_BPS,
|
|
7563
8342
|
PERMIT2_ADDRESS,
|
|
7564
8343
|
UNISWAP_SUPPORTED_CHAIN_IDS,
|
|
8344
|
+
UNISWAP_EXECUTION_PROFILES,
|
|
7565
8345
|
UNISWAP_UNIVERSAL_ROUTER_BY_NETWORK,
|
|
8346
|
+
UNISWAP_WRAPPED_NATIVE_BY_NETWORK,
|
|
7566
8347
|
normalizeUniswapTokenAddress,
|
|
7567
8348
|
assertUniswapSupportedNetwork,
|
|
8349
|
+
resolveUniswapRouterExecutionProfile,
|
|
8350
|
+
buildDirectWrappedNativeOperation,
|
|
7568
8351
|
assertValidNetwork,
|
|
7569
8352
|
uniswapSlippagePercentFromBps,
|
|
7570
8353
|
normalizeUniswapPermitData,
|