@agentlayer.tech/wallet 0.1.76 → 0.1.84
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/README.md +4 -4
- package/.openclaw/extensions/agent-wallet/dist/index.js +57 -19
- package/.openclaw/extensions/agent-wallet/index.ts +57 -19
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +19 -0
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/autonomous_permissions.py +2 -2
- package/agent-wallet/agent_wallet/autonomous_policy.py +1 -1
- package/agent-wallet/agent_wallet/config.py +3 -3
- package/agent-wallet/agent_wallet/openclaw_adapter.py +28 -26
- package/agent-wallet/agent_wallet/providers/evm_portfolio.py +2 -2
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +25 -10
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/skills/wallet-operator/SKILL.md +4 -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/codex/plugins/agent-wallet/server.py +13 -10
- package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +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 +6 -2
- package/wdk-evm-wallet/package.json +5 -2
- package/wdk-evm-wallet/src/config.js +48 -2
- package/wdk-evm-wallet/src/network_state.js +5 -2
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +570 -63
|
@@ -22,12 +22,64 @@ const DEFAULT_SWAP_SLIPPAGE_BPS = 100;
|
|
|
22
22
|
const DEFAULT_LIFI_SLIPPAGE = 0.005;
|
|
23
23
|
const ALWAYS_DENIED_LIFI_BRIDGES = ["mayan"];
|
|
24
24
|
const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
25
|
-
const UNISWAP_SUPPORTED_CHAIN_IDS = { ethereum: 1, base: 8453 };
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const UNISWAP_SUPPORTED_CHAIN_IDS = { ethereum: 1, base: 8453, robinhood: 4663 };
|
|
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
|
+
universalRouters: { "2.0": "0x8876789976decbfcbbbe364623c63652db8c0904" },
|
|
45
|
+
wrappedNative: "0x0bd7d308f8e1639fab988df18a8011f41eacad73",
|
|
46
|
+
// Robinhood's official deployment exposes the V3 stack. Asking the Trading
|
|
47
|
+
// API for undeployed V2/V4 routes can make an otherwise valid V3-only pair
|
|
48
|
+
// appear unavailable, so keep the protocol set chain-specific.
|
|
49
|
+
ammProtocols: ["V3"],
|
|
50
|
+
v3DirectFallback: {
|
|
51
|
+
quoterV2: "0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7",
|
|
52
|
+
swapRouter02: "0xcaf681a66d020601342297493863e78c959e5cb2",
|
|
53
|
+
feeTiers: [100, 500, 3000, 10000],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
30
56
|
};
|
|
57
|
+
// Compatibility export for callers/tests. New execution code resolves a router
|
|
58
|
+
// from UNISWAP_EXECUTION_PROFILES and its explicitly selected version.
|
|
59
|
+
const UNISWAP_UNIVERSAL_ROUTER_BY_NETWORK = Object.fromEntries(
|
|
60
|
+
Object.entries(UNISWAP_EXECUTION_PROFILES).map(([network, profile]) => [
|
|
61
|
+
network,
|
|
62
|
+
profile.universalRouters["2.0"],
|
|
63
|
+
])
|
|
64
|
+
);
|
|
65
|
+
const UNISWAP_WRAPPED_NATIVE_BY_NETWORK = Object.fromEntries(
|
|
66
|
+
Object.entries(UNISWAP_EXECUTION_PROFILES).map(([network, profile]) => [
|
|
67
|
+
network,
|
|
68
|
+
profile.wrappedNative,
|
|
69
|
+
])
|
|
70
|
+
);
|
|
71
|
+
const WETH_DEPOSIT_SELECTOR = "0xd0e30db0";
|
|
72
|
+
const WETH_WITHDRAW_SELECTOR = "0x2e1a7d4d";
|
|
73
|
+
const UNISWAP_V3_QUOTER_INTERFACE = new Interface([
|
|
74
|
+
"function quoteExactInputSingle((address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96) params) external returns (uint256 amountOut,uint160 sqrtPriceX96After,uint32 initializedTicksCrossed,uint256 gasEstimate)",
|
|
75
|
+
]);
|
|
76
|
+
const UNISWAP_V3_SWAP_ROUTER_INTERFACE = new Interface([
|
|
77
|
+
"function exactInputSingle((address tokenIn,address tokenOut,uint24 fee,address recipient,uint256 amountIn,uint256 amountOutMinimum,uint160 sqrtPriceLimitX96) params) payable returns (uint256 amountOut)",
|
|
78
|
+
"function unwrapWETH9(uint256 amountMinimum,address recipient) payable",
|
|
79
|
+
"function multicall(bytes[] data) payable returns (bytes[] results)",
|
|
80
|
+
]);
|
|
81
|
+
const UNISWAP_CLASSIC_ROUTINGS = new Set(["CLASSIC"]);
|
|
82
|
+
const UNISWAPX_ROUTINGS = new Set(["DUTCH_V2", "DUTCH_V3", "LIMIT_ORDER", "PRIORITY"]);
|
|
31
83
|
const AAVE_RAY = 10n ** 27n;
|
|
32
84
|
const LIDO_STETH_DECIMALS = 18;
|
|
33
85
|
const LIDO_MIN_STETH_WITHDRAWAL_AMOUNT = 100n;
|
|
@@ -502,10 +554,13 @@ function assertValidNetwork(network, fieldName = "network") {
|
|
|
502
554
|
eth: "ethereum",
|
|
503
555
|
"base-mainnet": "base",
|
|
504
556
|
base_sepolia: "base-sepolia",
|
|
557
|
+
"robinhood-mainnet": "robinhood",
|
|
505
558
|
};
|
|
506
559
|
const effective = aliases[normalized] || normalized;
|
|
507
|
-
if (!["ethereum", "sepolia", "base", "base-sepolia"].includes(effective)) {
|
|
508
|
-
throw new Error(
|
|
560
|
+
if (!["ethereum", "sepolia", "base", "base-sepolia", "robinhood"].includes(effective)) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`${fieldName} must be one of: ethereum, sepolia, base, base-sepolia, robinhood.`
|
|
563
|
+
);
|
|
509
564
|
}
|
|
510
565
|
return effective;
|
|
511
566
|
}
|
|
@@ -847,12 +902,121 @@ function assertUniswapSupportedNetwork(network) {
|
|
|
847
902
|
const chainId = UNISWAP_SUPPORTED_CHAIN_IDS[network];
|
|
848
903
|
if (!chainId) {
|
|
849
904
|
throw new Error(
|
|
850
|
-
"Uniswap Trading API swaps are currently supported only on ethereum and
|
|
905
|
+
"Uniswap Trading API swaps are currently supported only on ethereum, base, and robinhood mainnet."
|
|
851
906
|
);
|
|
852
907
|
}
|
|
853
908
|
return chainId;
|
|
854
909
|
}
|
|
855
910
|
|
|
911
|
+
function getUniswapNetworkExecutionProfile(network) {
|
|
912
|
+
const profile = UNISWAP_EXECUTION_PROFILES[network];
|
|
913
|
+
if (!profile) {
|
|
914
|
+
assertUniswapSupportedNetwork(network);
|
|
915
|
+
}
|
|
916
|
+
return profile;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function resolveUniswapRouterExecutionProfile(network, routerVersion) {
|
|
920
|
+
const networkProfile = getUniswapNetworkExecutionProfile(network);
|
|
921
|
+
const version = String(routerVersion || "2.0").trim() || "2.0";
|
|
922
|
+
const router = networkProfile.universalRouters[version];
|
|
923
|
+
if (!router) {
|
|
924
|
+
throw createTaggedError(
|
|
925
|
+
`Uniswap Universal Router version ${version} is not approved for ${network}.`,
|
|
926
|
+
"uniswap_router_version_unsupported",
|
|
927
|
+
{
|
|
928
|
+
network,
|
|
929
|
+
chainId: networkProfile.chainId,
|
|
930
|
+
requestedVersion: version,
|
|
931
|
+
supportedVersions: Object.keys(networkProfile.universalRouters),
|
|
932
|
+
}
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
return { ...networkProfile, routerVersion: version, router };
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function encodeUint256(value) {
|
|
939
|
+
return BigInt(value).toString(16).padStart(64, "0");
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function buildDirectWrappedNativeOperation(networkProfile, swapRequest) {
|
|
943
|
+
const wrappedNative = networkProfile.wrappedNative;
|
|
944
|
+
if (isZeroAddress(swapRequest.tokenIn) && swapRequest.tokenOut === wrappedNative) {
|
|
945
|
+
return {
|
|
946
|
+
executionKind: "wrap",
|
|
947
|
+
routing: "WRAP",
|
|
948
|
+
transaction: { to: wrappedNative, data: WETH_DEPOSIT_SELECTOR, value: swapRequest.tokenInAmount },
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
if (swapRequest.tokenIn === wrappedNative && isZeroAddress(swapRequest.tokenOut)) {
|
|
952
|
+
return {
|
|
953
|
+
executionKind: "unwrap",
|
|
954
|
+
routing: "UNWRAP",
|
|
955
|
+
transaction: {
|
|
956
|
+
to: wrappedNative,
|
|
957
|
+
data: `${WETH_WITHDRAW_SELECTOR}${encodeUint256(swapRequest.tokenInAmount)}`,
|
|
958
|
+
value: 0n,
|
|
959
|
+
},
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function buildUniswapV3DirectSwapTransaction({
|
|
966
|
+
networkProfile,
|
|
967
|
+
recipient,
|
|
968
|
+
swapRequest,
|
|
969
|
+
feeTier,
|
|
970
|
+
minimumTokenOutAmount,
|
|
971
|
+
}) {
|
|
972
|
+
const fallback = networkProfile.v3DirectFallback;
|
|
973
|
+
if (!fallback) {
|
|
974
|
+
throw new Error("Uniswap V3 direct fallback is not configured for this network.");
|
|
975
|
+
}
|
|
976
|
+
const nativeTokenIn = isZeroAddress(swapRequest.tokenIn);
|
|
977
|
+
const nativeTokenOut = isZeroAddress(swapRequest.tokenOut);
|
|
978
|
+
const effectiveTokenIn = nativeTokenIn ? networkProfile.wrappedNative : swapRequest.tokenIn;
|
|
979
|
+
const effectiveTokenOut = nativeTokenOut ? networkProfile.wrappedNative : swapRequest.tokenOut;
|
|
980
|
+
const exactInput = UNISWAP_V3_SWAP_ROUTER_INTERFACE.encodeFunctionData("exactInputSingle", [
|
|
981
|
+
{
|
|
982
|
+
tokenIn: effectiveTokenIn,
|
|
983
|
+
tokenOut: effectiveTokenOut,
|
|
984
|
+
fee: feeTier,
|
|
985
|
+
// For an ETH output the router must receive WETH first, then unwrap it in
|
|
986
|
+
// the same atomic multicall. All other paths transfer directly to the user.
|
|
987
|
+
recipient: nativeTokenOut ? fallback.swapRouter02 : recipient,
|
|
988
|
+
amountIn: swapRequest.tokenInAmount,
|
|
989
|
+
amountOutMinimum: minimumTokenOutAmount,
|
|
990
|
+
sqrtPriceLimitX96: 0,
|
|
991
|
+
},
|
|
992
|
+
]);
|
|
993
|
+
const data = nativeTokenOut
|
|
994
|
+
? UNISWAP_V3_SWAP_ROUTER_INTERFACE.encodeFunctionData("multicall(bytes[])", [
|
|
995
|
+
[
|
|
996
|
+
exactInput,
|
|
997
|
+
UNISWAP_V3_SWAP_ROUTER_INTERFACE.encodeFunctionData("unwrapWETH9", [
|
|
998
|
+
minimumTokenOutAmount,
|
|
999
|
+
recipient,
|
|
1000
|
+
]),
|
|
1001
|
+
],
|
|
1002
|
+
])
|
|
1003
|
+
: exactInput;
|
|
1004
|
+
return {
|
|
1005
|
+
to: fallback.swapRouter02,
|
|
1006
|
+
data,
|
|
1007
|
+
value: nativeTokenIn ? swapRequest.tokenInAmount : 0n,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function isUniswapNoQuotesAvailableError(error) {
|
|
1012
|
+
return (
|
|
1013
|
+
error &&
|
|
1014
|
+
typeof error === "object" &&
|
|
1015
|
+
error.errorCode === "network_unavailable" &&
|
|
1016
|
+
/no quotes available/i.test(String(error.message || ""))
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
856
1020
|
function uniswapSlippagePercentFromBps(bps) {
|
|
857
1021
|
const parsed = Number(bps);
|
|
858
1022
|
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 5000) {
|
|
@@ -3753,36 +3917,73 @@ export class WdkEvmWalletService {
|
|
|
3753
3917
|
);
|
|
3754
3918
|
}
|
|
3755
3919
|
|
|
3756
|
-
const signature =
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
: await this.#signUniswapPermit(account, finalPlan.permitData, runtimeConfig);
|
|
3760
|
-
|
|
3761
|
-
const swapTx = await this.#fetchUniswapSwapCalldata({
|
|
3762
|
-
runtimeConfig,
|
|
3763
|
-
quoteResponse: finalPlan.quoteResponse,
|
|
3764
|
-
permitData: finalPlan.permitData,
|
|
3765
|
-
signature,
|
|
3766
|
-
});
|
|
3920
|
+
const signature = finalPlan.permitData
|
|
3921
|
+
? await this.#signUniswapPermit(account, finalPlan.permitData, runtimeConfig)
|
|
3922
|
+
: null;
|
|
3767
3923
|
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3924
|
+
let simulation;
|
|
3925
|
+
let swapTransaction = null;
|
|
3926
|
+
let result;
|
|
3927
|
+
if (finalPlan.isUniswapX) {
|
|
3928
|
+
const order = await this.#submitUniswapOrder({
|
|
3929
|
+
quoteResponse: finalPlan.quoteResponse,
|
|
3930
|
+
signature,
|
|
3931
|
+
nativeTokenIn: finalPlan.isNativeTokenIn,
|
|
3932
|
+
routing: finalPlan.routing,
|
|
3933
|
+
routerProfile: finalPlan.routerProfile,
|
|
3934
|
+
});
|
|
3935
|
+
simulation = {
|
|
3936
|
+
ok: true,
|
|
3937
|
+
skipped: true,
|
|
3938
|
+
reason: "uniswapx-order",
|
|
3939
|
+
message: "UniswapX validates and submits the signed order to its filler network.",
|
|
3940
|
+
details: null,
|
|
3941
|
+
};
|
|
3942
|
+
result = {
|
|
3943
|
+
orderId: order.orderId,
|
|
3944
|
+
orderStatus: order.orderStatus,
|
|
3945
|
+
approvalFee: approvalExecution.totalFee.toString(),
|
|
3946
|
+
tokenInAmount: finalPlan.tokenInAmount.toString(),
|
|
3947
|
+
tokenOutAmount: finalPlan.tokenOutAmount.toString(),
|
|
3948
|
+
...(approvalExecution.approveHash ? { approveHash: approvalExecution.approveHash } : {}),
|
|
3949
|
+
...(approvalExecution.resetAllowanceHash
|
|
3950
|
+
? { resetAllowanceHash: approvalExecution.resetAllowanceHash }
|
|
3951
|
+
: {}),
|
|
3952
|
+
};
|
|
3953
|
+
} else {
|
|
3954
|
+
const swapTx =
|
|
3955
|
+
finalPlan.preparedTransaction ||
|
|
3956
|
+
(await this.#fetchUniswapSwapCalldata({
|
|
3957
|
+
runtimeConfig,
|
|
3958
|
+
routerProfile: finalPlan.routerProfile,
|
|
3959
|
+
quoteResponse: finalPlan.quoteResponse,
|
|
3960
|
+
permitData: finalPlan.permitData,
|
|
3961
|
+
signature,
|
|
3962
|
+
}));
|
|
3963
|
+
simulation = await this.#simulatePreparedTransaction({
|
|
3964
|
+
runtimeConfig,
|
|
3965
|
+
from: address,
|
|
3966
|
+
tx: swapTx,
|
|
3967
|
+
});
|
|
3968
|
+
this.#assertSimulationSucceeded(simulation);
|
|
3774
3969
|
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
:
|
|
3785
|
-
|
|
3970
|
+
const { hash } = await account.sendTransaction(swapTx);
|
|
3971
|
+
swapTransaction = {
|
|
3972
|
+
to: swapTx.to,
|
|
3973
|
+
value: swapTx.value.toString(),
|
|
3974
|
+
dataHash: sha256Hex(swapTx.data),
|
|
3975
|
+
};
|
|
3976
|
+
result = {
|
|
3977
|
+
hash,
|
|
3978
|
+
approvalFee: approvalExecution.totalFee.toString(),
|
|
3979
|
+
tokenInAmount: finalPlan.tokenInAmount.toString(),
|
|
3980
|
+
tokenOutAmount: finalPlan.tokenOutAmount.toString(),
|
|
3981
|
+
...(approvalExecution.approveHash ? { approveHash: approvalExecution.approveHash } : {}),
|
|
3982
|
+
...(approvalExecution.resetAllowanceHash
|
|
3983
|
+
? { resetAllowanceHash: approvalExecution.resetAllowanceHash }
|
|
3984
|
+
: {}),
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3786
3987
|
return {
|
|
3787
3988
|
...(await this.#formatUniswapSwapResponse({
|
|
3788
3989
|
runtimeConfig,
|
|
@@ -3798,7 +3999,7 @@ export class WdkEvmWalletService {
|
|
|
3798
3999
|
},
|
|
3799
4000
|
})),
|
|
3800
4001
|
simulation,
|
|
3801
|
-
swapTransaction
|
|
4002
|
+
swapTransaction,
|
|
3802
4003
|
result,
|
|
3803
4004
|
};
|
|
3804
4005
|
} catch (error) {
|
|
@@ -3841,7 +4042,8 @@ export class WdkEvmWalletService {
|
|
|
3841
4042
|
address,
|
|
3842
4043
|
protocol: "uniswap",
|
|
3843
4044
|
executionSupported: true,
|
|
3844
|
-
|
|
4045
|
+
executionKind: plan.executionKind,
|
|
4046
|
+
routing: plan.routing,
|
|
3845
4047
|
swapRequest: {
|
|
3846
4048
|
tokenIn: swapRequest.tokenIn,
|
|
3847
4049
|
tokenOut: swapRequest.tokenOut,
|
|
@@ -3850,6 +4052,7 @@ export class WdkEvmWalletService {
|
|
|
3850
4052
|
tokenInMetadata,
|
|
3851
4053
|
tokenOutMetadata,
|
|
3852
4054
|
inputAmountFormatted: formatUnits(swapRequest.tokenInAmount, tokenInMetadata.decimals),
|
|
4055
|
+
outputAmount: plan.tokenOutAmount.toString(),
|
|
3853
4056
|
outputAmountFormatted: formatUnits(plan.tokenOutAmount, tokenOutMetadata.decimals),
|
|
3854
4057
|
quoteFingerprint: plan.quoteFingerprint,
|
|
3855
4058
|
slippageBps: plan.slippageBps,
|
|
@@ -3867,7 +4070,19 @@ export class WdkEvmWalletService {
|
|
|
3867
4070
|
readError: plan.allowanceReadError,
|
|
3868
4071
|
},
|
|
3869
4072
|
router: plan.router,
|
|
3870
|
-
|
|
4073
|
+
swapTransaction: plan.preparedTransaction
|
|
4074
|
+
? {
|
|
4075
|
+
to: plan.preparedTransaction.to,
|
|
4076
|
+
value: plan.preparedTransaction.value.toString(),
|
|
4077
|
+
dataHash: sha256Hex(plan.preparedTransaction.data),
|
|
4078
|
+
}
|
|
4079
|
+
: null,
|
|
4080
|
+
source:
|
|
4081
|
+
plan.executionKind === "wrap" || plan.executionKind === "unwrap"
|
|
4082
|
+
? "canonical-wrapped-native"
|
|
4083
|
+
: plan.executionKind === "v3-direct"
|
|
4084
|
+
? "uniswap-v3-direct-fallback"
|
|
4085
|
+
: "uniswap-trading-api",
|
|
3871
4086
|
};
|
|
3872
4087
|
}
|
|
3873
4088
|
|
|
@@ -5175,12 +5390,19 @@ export class WdkEvmWalletService {
|
|
|
5175
5390
|
};
|
|
5176
5391
|
}
|
|
5177
5392
|
|
|
5178
|
-
async #uniswapTradingApiRequest(
|
|
5393
|
+
async #uniswapTradingApiRequest(
|
|
5394
|
+
pathname,
|
|
5395
|
+
body,
|
|
5396
|
+
{ erc20EthEnabled = false, routerVersion = this.config.uniswapRouterVersion, chainId = null } = {}
|
|
5397
|
+
) {
|
|
5179
5398
|
const headers = {
|
|
5180
5399
|
"Content-Type": "application/json",
|
|
5181
5400
|
Accept: "application/json",
|
|
5182
|
-
"x-universal-router-version":
|
|
5401
|
+
"x-universal-router-version": routerVersion,
|
|
5183
5402
|
};
|
|
5403
|
+
if (erc20EthEnabled) {
|
|
5404
|
+
headers["x-erc20eth-enabled"] = "true";
|
|
5405
|
+
}
|
|
5184
5406
|
if (this.config.uniswapViaGateway) {
|
|
5185
5407
|
// The provider gateway holds the Uniswap key and injects x-api-key upstream;
|
|
5186
5408
|
// we authenticate to it with the shared gateway bearer (same token used for
|
|
@@ -5189,6 +5411,13 @@ export class WdkEvmWalletService {
|
|
|
5189
5411
|
if (token) {
|
|
5190
5412
|
headers.Authorization = `Bearer ${token}`;
|
|
5191
5413
|
}
|
|
5414
|
+
// The gateway selects its own configured version and checks this value for
|
|
5415
|
+
// equality. The chain id is an allow-listed routing hint, never an upstream
|
|
5416
|
+
// header passthrough.
|
|
5417
|
+
if (chainId !== null) {
|
|
5418
|
+
headers["x-agentlayer-chain-id"] = String(chainId);
|
|
5419
|
+
headers["x-agentlayer-uniswap-router-version"] = routerVersion;
|
|
5420
|
+
}
|
|
5192
5421
|
} else {
|
|
5193
5422
|
if (!this.config.uniswapApiKey) {
|
|
5194
5423
|
throw createTaggedError(
|
|
@@ -5242,9 +5471,9 @@ export class WdkEvmWalletService {
|
|
|
5242
5471
|
return payload;
|
|
5243
5472
|
}
|
|
5244
5473
|
|
|
5245
|
-
async #fetchUniswapQuote({ runtimeConfig, address, swapRequest }) {
|
|
5474
|
+
async #fetchUniswapQuote({ runtimeConfig, routerProfile, address, swapRequest }) {
|
|
5246
5475
|
const chainId = UNISWAP_SUPPORTED_CHAIN_IDS[runtimeConfig.network];
|
|
5247
|
-
const
|
|
5476
|
+
const quoteRequest = {
|
|
5248
5477
|
swapper: address,
|
|
5249
5478
|
tokenIn: swapRequest.tokenIn,
|
|
5250
5479
|
tokenOut: swapRequest.tokenOut,
|
|
@@ -5253,21 +5482,50 @@ export class WdkEvmWalletService {
|
|
|
5253
5482
|
amount: swapRequest.tokenInAmount.toString(),
|
|
5254
5483
|
type: "EXACT_INPUT",
|
|
5255
5484
|
slippageTolerance: swapRequest.slippagePercent,
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5485
|
+
};
|
|
5486
|
+
const requestOptions = {
|
|
5487
|
+
erc20EthEnabled: isZeroAddress(swapRequest.tokenIn),
|
|
5488
|
+
routerVersion: routerProfile.routerVersion,
|
|
5489
|
+
chainId,
|
|
5490
|
+
};
|
|
5491
|
+
const ammProtocols = routerProfile.ammProtocols;
|
|
5492
|
+
let payload;
|
|
5493
|
+
try {
|
|
5494
|
+
// Prefer the full route set. On a sub-minimum UniswapX request the API can
|
|
5495
|
+
// return No quotes available even when an AMM pool is liquid, so retry the
|
|
5496
|
+
// same exact swap through AMMs only before reporting a quote failure.
|
|
5497
|
+
payload = await this.#uniswapTradingApiRequest(
|
|
5498
|
+
"/quote",
|
|
5499
|
+
{
|
|
5500
|
+
...quoteRequest,
|
|
5501
|
+
protocols: [
|
|
5502
|
+
...ammProtocols,
|
|
5503
|
+
runtimeConfig.network === "ethereum" ? "UNISWAPX_V2" : "UNISWAPX_V3",
|
|
5504
|
+
],
|
|
5505
|
+
},
|
|
5506
|
+
requestOptions
|
|
5507
|
+
);
|
|
5508
|
+
} catch (error) {
|
|
5509
|
+
if (!isUniswapNoQuotesAvailableError(error)) {
|
|
5510
|
+
throw error;
|
|
5511
|
+
}
|
|
5512
|
+
payload = await this.#uniswapTradingApiRequest(
|
|
5513
|
+
"/quote",
|
|
5514
|
+
{ ...quoteRequest, protocols: ammProtocols },
|
|
5515
|
+
requestOptions
|
|
5516
|
+
);
|
|
5517
|
+
}
|
|
5260
5518
|
const routing = String(payload.routing || "").toUpperCase();
|
|
5261
|
-
if (routing
|
|
5519
|
+
if (!UNISWAP_CLASSIC_ROUTINGS.has(routing) && !UNISWAPX_ROUTINGS.has(routing)) {
|
|
5262
5520
|
throw createTaggedError(
|
|
5263
|
-
`Uniswap returned unsupported routing '${routing}'
|
|
5521
|
+
`Uniswap returned unsupported routing '${routing}'.`,
|
|
5264
5522
|
"uniswap_unsupported_route",
|
|
5265
5523
|
{ provider: "uniswap", routing }
|
|
5266
5524
|
);
|
|
5267
5525
|
}
|
|
5268
5526
|
if (!payload.quote || typeof payload.quote !== "object" || !payload.quote.output) {
|
|
5269
5527
|
throw createTaggedError(
|
|
5270
|
-
"Uniswap quote response is missing
|
|
5528
|
+
"Uniswap quote response is missing quote/output fields.",
|
|
5271
5529
|
"network_unavailable",
|
|
5272
5530
|
{ provider: "uniswap" }
|
|
5273
5531
|
);
|
|
@@ -5275,8 +5533,195 @@ export class WdkEvmWalletService {
|
|
|
5275
5533
|
return payload;
|
|
5276
5534
|
}
|
|
5277
5535
|
|
|
5536
|
+
async #quoteRobinhoodV3Direct({ runtimeConfig, networkProfile, swapRequest }) {
|
|
5537
|
+
const fallback = networkProfile.v3DirectFallback;
|
|
5538
|
+
if (!fallback) {
|
|
5539
|
+
return null;
|
|
5540
|
+
}
|
|
5541
|
+
const tokenIn = isZeroAddress(swapRequest.tokenIn)
|
|
5542
|
+
? networkProfile.wrappedNative
|
|
5543
|
+
: swapRequest.tokenIn;
|
|
5544
|
+
const tokenOut = isZeroAddress(swapRequest.tokenOut)
|
|
5545
|
+
? networkProfile.wrappedNative
|
|
5546
|
+
: swapRequest.tokenOut;
|
|
5547
|
+
let best = null;
|
|
5548
|
+
for (const feeTier of fallback.feeTiers) {
|
|
5549
|
+
const data = UNISWAP_V3_QUOTER_INTERFACE.encodeFunctionData("quoteExactInputSingle", [
|
|
5550
|
+
{
|
|
5551
|
+
tokenIn,
|
|
5552
|
+
tokenOut,
|
|
5553
|
+
amountIn: swapRequest.tokenInAmount,
|
|
5554
|
+
fee: feeTier,
|
|
5555
|
+
sqrtPriceLimitX96: 0,
|
|
5556
|
+
},
|
|
5557
|
+
]);
|
|
5558
|
+
try {
|
|
5559
|
+
const raw = await rpcRequest(runtimeConfig.providerUrl, "eth_call", [
|
|
5560
|
+
{ to: fallback.quoterV2, data },
|
|
5561
|
+
"latest",
|
|
5562
|
+
]);
|
|
5563
|
+
const decoded = UNISWAP_V3_QUOTER_INTERFACE.decodeFunctionResult(
|
|
5564
|
+
"quoteExactInputSingle",
|
|
5565
|
+
raw
|
|
5566
|
+
);
|
|
5567
|
+
const amountOut = BigInt(decoded[0]);
|
|
5568
|
+
if (amountOut > 0n && (!best || amountOut > best.amountOut)) {
|
|
5569
|
+
best = { feeTier, amountOut, gasEstimate: BigInt(decoded[3]) };
|
|
5570
|
+
}
|
|
5571
|
+
} catch {
|
|
5572
|
+
// A missing pool and a failed Quoter call are equivalent for this
|
|
5573
|
+
// constrained direct route. Try every reviewed fee tier before failing.
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
return best;
|
|
5577
|
+
}
|
|
5578
|
+
|
|
5579
|
+
async #buildRobinhoodV3DirectFallbackPlan({ account, runtimeConfig, address, swapRequest }) {
|
|
5580
|
+
const networkProfile = getUniswapNetworkExecutionProfile(runtimeConfig.network);
|
|
5581
|
+
const quoted = await this.#quoteRobinhoodV3Direct({
|
|
5582
|
+
runtimeConfig,
|
|
5583
|
+
networkProfile,
|
|
5584
|
+
swapRequest,
|
|
5585
|
+
});
|
|
5586
|
+
if (!quoted) {
|
|
5587
|
+
return null;
|
|
5588
|
+
}
|
|
5589
|
+
const slippageBps = Math.round(swapRequest.slippagePercent * 100);
|
|
5590
|
+
const minimumTokenOutAmount =
|
|
5591
|
+
quoted.amountOut - (quoted.amountOut * BigInt(slippageBps)) / 10000n;
|
|
5592
|
+
const nativeTokenIn = isZeroAddress(swapRequest.tokenIn);
|
|
5593
|
+
const spender = nativeTokenIn ? null : networkProfile.v3DirectFallback.swapRouter02;
|
|
5594
|
+
const allowanceState = nativeTokenIn
|
|
5595
|
+
? { currentAllowance: swapRequest.tokenInAmount, error: null }
|
|
5596
|
+
: await this.#getSwapAllowanceState({
|
|
5597
|
+
account,
|
|
5598
|
+
tokenAddress: swapRequest.tokenIn,
|
|
5599
|
+
spender,
|
|
5600
|
+
});
|
|
5601
|
+
const approval = nativeTokenIn
|
|
5602
|
+
? { required: false, estimatedFee: 0n, steps: [] }
|
|
5603
|
+
: await this.#buildSwapApprovalPlan({
|
|
5604
|
+
account,
|
|
5605
|
+
runtimeConfig,
|
|
5606
|
+
tokenAddress: swapRequest.tokenIn,
|
|
5607
|
+
spender,
|
|
5608
|
+
requiredAmount: swapRequest.tokenInAmount,
|
|
5609
|
+
currentAllowance: allowanceState.currentAllowance,
|
|
5610
|
+
});
|
|
5611
|
+
const preparedTransaction = buildUniswapV3DirectSwapTransaction({
|
|
5612
|
+
networkProfile,
|
|
5613
|
+
recipient: address,
|
|
5614
|
+
swapRequest,
|
|
5615
|
+
feeTier: quoted.feeTier,
|
|
5616
|
+
minimumTokenOutAmount,
|
|
5617
|
+
});
|
|
5618
|
+
const quoteFingerprint = sha256Hex(
|
|
5619
|
+
JSON.stringify({
|
|
5620
|
+
chainId: runtimeConfig.chainId,
|
|
5621
|
+
network: runtimeConfig.network,
|
|
5622
|
+
from: address.toLowerCase(),
|
|
5623
|
+
router: networkProfile.v3DirectFallback.swapRouter02,
|
|
5624
|
+
spender: spender ? spender.toLowerCase() : null,
|
|
5625
|
+
tokenIn: swapRequest.tokenIn.toLowerCase(),
|
|
5626
|
+
tokenOut: swapRequest.tokenOut.toLowerCase(),
|
|
5627
|
+
tokenInAmount: swapRequest.tokenInAmount.toString(),
|
|
5628
|
+
feeTier: quoted.feeTier,
|
|
5629
|
+
slippageBps,
|
|
5630
|
+
executionKind: "v3-direct",
|
|
5631
|
+
})
|
|
5632
|
+
);
|
|
5633
|
+
return {
|
|
5634
|
+
quoteResponse: null,
|
|
5635
|
+
permitData: null,
|
|
5636
|
+
isNativeTokenIn: nativeTokenIn,
|
|
5637
|
+
spender,
|
|
5638
|
+
currentAllowance: allowanceState.currentAllowance,
|
|
5639
|
+
allowanceReadError: allowanceState.error,
|
|
5640
|
+
approval,
|
|
5641
|
+
tokenInAmount: swapRequest.tokenInAmount,
|
|
5642
|
+
tokenOutAmount: quoted.amountOut,
|
|
5643
|
+
minimumTokenOutAmount,
|
|
5644
|
+
slippageBps,
|
|
5645
|
+
routing: "CLASSIC",
|
|
5646
|
+
executionKind: "v3-direct",
|
|
5647
|
+
isUniswapX: false,
|
|
5648
|
+
quoteFingerprint,
|
|
5649
|
+
// QuoterV2 returns a gas-unit estimate, not a wei-denominated fee.
|
|
5650
|
+
// Keep the fee field empty rather than mislabeling units in the preview.
|
|
5651
|
+
gasFee: null,
|
|
5652
|
+
gasFeeUSD: null,
|
|
5653
|
+
router: networkProfile.v3DirectFallback.swapRouter02,
|
|
5654
|
+
routerProfile: null,
|
|
5655
|
+
preparedTransaction,
|
|
5656
|
+
v3FeeTier: quoted.feeTier,
|
|
5657
|
+
};
|
|
5658
|
+
}
|
|
5659
|
+
|
|
5278
5660
|
async #buildUniswapSwapPlan({ account, runtimeConfig, address, swapRequest }) {
|
|
5279
|
-
const
|
|
5661
|
+
const networkProfile = getUniswapNetworkExecutionProfile(runtimeConfig.network);
|
|
5662
|
+
const directOperation = buildDirectWrappedNativeOperation(networkProfile, swapRequest);
|
|
5663
|
+
const slippageBps = Math.round(swapRequest.slippagePercent * 100);
|
|
5664
|
+
if (directOperation) {
|
|
5665
|
+
const quoteFingerprint = sha256Hex(
|
|
5666
|
+
JSON.stringify({
|
|
5667
|
+
chainId: runtimeConfig.chainId,
|
|
5668
|
+
network: runtimeConfig.network,
|
|
5669
|
+
from: address.toLowerCase(),
|
|
5670
|
+
executionKind: directOperation.executionKind,
|
|
5671
|
+
wrappedNative: networkProfile.wrappedNative,
|
|
5672
|
+
tokenIn: swapRequest.tokenIn,
|
|
5673
|
+
tokenOut: swapRequest.tokenOut,
|
|
5674
|
+
tokenInAmount: swapRequest.tokenInAmount.toString(),
|
|
5675
|
+
})
|
|
5676
|
+
);
|
|
5677
|
+
return {
|
|
5678
|
+
quoteResponse: null,
|
|
5679
|
+
permitData: null,
|
|
5680
|
+
isNativeTokenIn: isZeroAddress(swapRequest.tokenIn),
|
|
5681
|
+
spender: null,
|
|
5682
|
+
currentAllowance: 0n,
|
|
5683
|
+
allowanceReadError: null,
|
|
5684
|
+
approval: { required: false, estimatedFee: 0n, steps: [] },
|
|
5685
|
+
tokenInAmount: swapRequest.tokenInAmount,
|
|
5686
|
+
tokenOutAmount: swapRequest.tokenInAmount,
|
|
5687
|
+
minimumTokenOutAmount: swapRequest.tokenInAmount,
|
|
5688
|
+
slippageBps,
|
|
5689
|
+
routing: directOperation.routing,
|
|
5690
|
+
executionKind: directOperation.executionKind,
|
|
5691
|
+
isUniswapX: false,
|
|
5692
|
+
quoteFingerprint,
|
|
5693
|
+
gasFee: null,
|
|
5694
|
+
gasFeeUSD: null,
|
|
5695
|
+
router: networkProfile.wrappedNative,
|
|
5696
|
+
routerProfile: null,
|
|
5697
|
+
preparedTransaction: directOperation.transaction,
|
|
5698
|
+
};
|
|
5699
|
+
}
|
|
5700
|
+
|
|
5701
|
+
const routerProfile = this.#resolveUniswapRouterProfile(runtimeConfig);
|
|
5702
|
+
let quoteResponse;
|
|
5703
|
+
try {
|
|
5704
|
+
quoteResponse = await this.#fetchUniswapQuote({
|
|
5705
|
+
runtimeConfig,
|
|
5706
|
+
routerProfile,
|
|
5707
|
+
address,
|
|
5708
|
+
swapRequest,
|
|
5709
|
+
});
|
|
5710
|
+
} catch (error) {
|
|
5711
|
+
if (runtimeConfig.network !== "robinhood" || !isUniswapNoQuotesAvailableError(error)) {
|
|
5712
|
+
throw error;
|
|
5713
|
+
}
|
|
5714
|
+
const fallbackPlan = await this.#buildRobinhoodV3DirectFallbackPlan({
|
|
5715
|
+
account,
|
|
5716
|
+
runtimeConfig,
|
|
5717
|
+
address,
|
|
5718
|
+
swapRequest,
|
|
5719
|
+
});
|
|
5720
|
+
if (fallbackPlan) {
|
|
5721
|
+
return fallbackPlan;
|
|
5722
|
+
}
|
|
5723
|
+
throw error;
|
|
5724
|
+
}
|
|
5280
5725
|
const permitData = quoteResponse.permitData ?? null;
|
|
5281
5726
|
const isNativeTokenIn = isZeroAddress(swapRequest.tokenIn);
|
|
5282
5727
|
const spender = isNativeTokenIn ? null : PERMIT2_ADDRESS;
|
|
@@ -5298,16 +5743,17 @@ export class WdkEvmWalletService {
|
|
|
5298
5743
|
requiredAmount: swapRequest.tokenInAmount,
|
|
5299
5744
|
currentAllowance,
|
|
5300
5745
|
});
|
|
5301
|
-
const
|
|
5302
|
-
const
|
|
5746
|
+
const quoteOutput = quoteResponse.quote.output;
|
|
5747
|
+
const tokenOutAmount = BigInt(String(quoteOutput.amount || "0"));
|
|
5303
5748
|
// The Trading API returns the post-slippage floor directly; fall back to a
|
|
5304
5749
|
// local computation only if the field is absent.
|
|
5305
5750
|
const minimumTokenOutAmount =
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
? BigInt(String(quoteResponse.quote.output.minimumAmount))
|
|
5751
|
+
quoteOutput.minimumAmount !== undefined && quoteOutput.minimumAmount !== null
|
|
5752
|
+
? BigInt(String(quoteOutput.minimumAmount))
|
|
5309
5753
|
: tokenOutAmount - (tokenOutAmount * BigInt(slippageBps)) / 10000n;
|
|
5310
|
-
const
|
|
5754
|
+
const routing = String(quoteResponse.routing || "").toUpperCase();
|
|
5755
|
+
const isUniswapX = UNISWAPX_ROUTINGS.has(routing);
|
|
5756
|
+
const router = isUniswapX ? null : routerProfile.router;
|
|
5311
5757
|
// Bind only the stable swap *intent* (who/what/how-much/slippage/route), never
|
|
5312
5758
|
// the live quoted output: the Trading API re-prices every block, so including
|
|
5313
5759
|
// tokenOutAmount here made execute's re-quote fingerprint differ from preview's
|
|
@@ -5319,13 +5765,15 @@ export class WdkEvmWalletService {
|
|
|
5319
5765
|
chainId: runtimeConfig.chainId,
|
|
5320
5766
|
network: runtimeConfig.network,
|
|
5321
5767
|
from: address.toLowerCase(),
|
|
5322
|
-
router: router.toLowerCase(),
|
|
5768
|
+
router: router ? router.toLowerCase() : null,
|
|
5323
5769
|
spender: spender ? spender.toLowerCase() : null,
|
|
5324
5770
|
tokenIn: swapRequest.tokenIn.toLowerCase(),
|
|
5325
5771
|
tokenOut: swapRequest.tokenOut.toLowerCase(),
|
|
5326
5772
|
tokenInAmount: swapRequest.tokenInAmount.toString(),
|
|
5327
5773
|
slippageBps,
|
|
5328
|
-
routing
|
|
5774
|
+
routing,
|
|
5775
|
+
executionKind: isUniswapX ? "uniswapx" : "classic",
|
|
5776
|
+
routerVersion: routerProfile.routerVersion,
|
|
5329
5777
|
})
|
|
5330
5778
|
);
|
|
5331
5779
|
return {
|
|
@@ -5340,13 +5788,25 @@ export class WdkEvmWalletService {
|
|
|
5340
5788
|
tokenOutAmount,
|
|
5341
5789
|
minimumTokenOutAmount,
|
|
5342
5790
|
slippageBps,
|
|
5791
|
+
routing,
|
|
5792
|
+
executionKind: isUniswapX ? "uniswapx" : "classic",
|
|
5793
|
+
isUniswapX,
|
|
5343
5794
|
quoteFingerprint,
|
|
5344
5795
|
gasFee: quoteResponse.quote.gasFee ?? null,
|
|
5345
5796
|
gasFeeUSD: quoteResponse.quote.gasFeeUSD ?? null,
|
|
5346
5797
|
router,
|
|
5798
|
+
routerProfile,
|
|
5799
|
+
preparedTransaction: null,
|
|
5347
5800
|
};
|
|
5348
5801
|
}
|
|
5349
5802
|
|
|
5803
|
+
#resolveUniswapRouterProfile(runtimeConfig) {
|
|
5804
|
+
const configuredVersion =
|
|
5805
|
+
this.config.uniswapRouterVersionsByNetwork?.[runtimeConfig.network] ||
|
|
5806
|
+
this.config.uniswapRouterVersion;
|
|
5807
|
+
return resolveUniswapRouterExecutionProfile(runtimeConfig.network, configuredVersion);
|
|
5808
|
+
}
|
|
5809
|
+
|
|
5350
5810
|
async #signUniswapPermit(account, permitData, runtimeConfig) {
|
|
5351
5811
|
const typed = normalizeUniswapPermitData(permitData, runtimeConfig);
|
|
5352
5812
|
return account.signTypedData({
|
|
@@ -5356,7 +5816,13 @@ export class WdkEvmWalletService {
|
|
|
5356
5816
|
});
|
|
5357
5817
|
}
|
|
5358
5818
|
|
|
5359
|
-
async #fetchUniswapSwapCalldata({
|
|
5819
|
+
async #fetchUniswapSwapCalldata({
|
|
5820
|
+
runtimeConfig,
|
|
5821
|
+
routerProfile,
|
|
5822
|
+
quoteResponse,
|
|
5823
|
+
permitData,
|
|
5824
|
+
signature,
|
|
5825
|
+
}) {
|
|
5360
5826
|
const { permitData: _permitData, permitTransaction: _permitTransaction, ...cleanQuote } =
|
|
5361
5827
|
quoteResponse;
|
|
5362
5828
|
const body = { ...cleanQuote };
|
|
@@ -5366,10 +5832,13 @@ export class WdkEvmWalletService {
|
|
|
5366
5832
|
body.signature = signature;
|
|
5367
5833
|
body.permitData = permitData;
|
|
5368
5834
|
}
|
|
5369
|
-
const payload = await this.#uniswapTradingApiRequest("/swap", body
|
|
5835
|
+
const payload = await this.#uniswapTradingApiRequest("/swap", body, {
|
|
5836
|
+
routerVersion: routerProfile.routerVersion,
|
|
5837
|
+
chainId: runtimeConfig.chainId,
|
|
5838
|
+
});
|
|
5370
5839
|
const swap = payload.swap || {};
|
|
5371
5840
|
const to = normalizeAddress(String(swap.to || ""), "swap.to");
|
|
5372
|
-
const expectedRouter =
|
|
5841
|
+
const expectedRouter = routerProfile.router;
|
|
5373
5842
|
if (to.toLowerCase() !== expectedRouter) {
|
|
5374
5843
|
throw createTaggedError(
|
|
5375
5844
|
"Uniswap /swap returned an unexpected target contract.",
|
|
@@ -5378,6 +5847,7 @@ export class WdkEvmWalletService {
|
|
|
5378
5847
|
);
|
|
5379
5848
|
}
|
|
5380
5849
|
const data = assertNonEmptyString(String(swap.data || ""), "swap.data");
|
|
5850
|
+
const value = parseHexOrDecimalBigInt(swap.value || "0", "swap.value");
|
|
5381
5851
|
if (data === "0x") {
|
|
5382
5852
|
throw createTaggedError(
|
|
5383
5853
|
"Uniswap /swap returned empty calldata. The quote likely expired; generate a new preview.",
|
|
@@ -5388,7 +5858,38 @@ export class WdkEvmWalletService {
|
|
|
5388
5858
|
return {
|
|
5389
5859
|
to,
|
|
5390
5860
|
data,
|
|
5391
|
-
value
|
|
5861
|
+
value,
|
|
5862
|
+
};
|
|
5863
|
+
}
|
|
5864
|
+
|
|
5865
|
+
async #submitUniswapOrder({ quoteResponse, signature, nativeTokenIn, routing, routerProfile }) {
|
|
5866
|
+
if (!signature) {
|
|
5867
|
+
throw createTaggedError(
|
|
5868
|
+
"UniswapX order quote is missing required signature data.",
|
|
5869
|
+
"uniswap_order_signature_missing",
|
|
5870
|
+
{ provider: "uniswap" }
|
|
5871
|
+
);
|
|
5872
|
+
}
|
|
5873
|
+
const payload = await this.#uniswapTradingApiRequest(
|
|
5874
|
+
"/order",
|
|
5875
|
+
{ quote: quoteResponse.quote, routing, signature },
|
|
5876
|
+
{
|
|
5877
|
+
erc20EthEnabled: nativeTokenIn,
|
|
5878
|
+
routerVersion: routerProfile.routerVersion,
|
|
5879
|
+
chainId: routerProfile.chainId,
|
|
5880
|
+
}
|
|
5881
|
+
);
|
|
5882
|
+
const orderId = String(payload.orderId || payload.order?.orderId || "").trim();
|
|
5883
|
+
if (!orderId) {
|
|
5884
|
+
throw createTaggedError(
|
|
5885
|
+
"Uniswap /order response is missing orderId.",
|
|
5886
|
+
"network_unavailable",
|
|
5887
|
+
{ provider: "uniswap" }
|
|
5888
|
+
);
|
|
5889
|
+
}
|
|
5890
|
+
return {
|
|
5891
|
+
orderId,
|
|
5892
|
+
orderStatus: String(payload.orderStatus || payload.order?.orderStatus || "open"),
|
|
5392
5893
|
};
|
|
5393
5894
|
}
|
|
5394
5895
|
|
|
@@ -7558,8 +8059,14 @@ export class WdkEvmWalletService {
|
|
|
7558
8059
|
export const __testables = {
|
|
7559
8060
|
PERMIT2_ADDRESS,
|
|
7560
8061
|
UNISWAP_SUPPORTED_CHAIN_IDS,
|
|
8062
|
+
UNISWAP_EXECUTION_PROFILES,
|
|
8063
|
+
UNISWAP_UNIVERSAL_ROUTER_BY_NETWORK,
|
|
8064
|
+
UNISWAP_WRAPPED_NATIVE_BY_NETWORK,
|
|
7561
8065
|
normalizeUniswapTokenAddress,
|
|
7562
8066
|
assertUniswapSupportedNetwork,
|
|
8067
|
+
resolveUniswapRouterExecutionProfile,
|
|
8068
|
+
buildDirectWrappedNativeOperation,
|
|
8069
|
+
assertValidNetwork,
|
|
7563
8070
|
uniswapSlippagePercentFromBps,
|
|
7564
8071
|
normalizeUniswapPermitData,
|
|
7565
8072
|
};
|