@recoillink/cli 0.0.0-stage → 45.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +193 -0
- package/README.md +149 -2
- package/bundle/154.index.js +462 -0
- package/bundle/230.index.js +569 -0
- package/bundle/25.index.js +9569 -0
- package/bundle/267.index.js +1386 -0
- package/bundle/308.index.js +5121 -0
- package/bundle/367.index.js +4 -0
- package/bundle/369.index.js +3443 -0
- package/bundle/529.index.js +3596 -0
- package/bundle/576.index.js +36 -0
- package/bundle/610.index.js +86 -0
- package/bundle/617.index.js +1612 -0
- package/bundle/628.index.js +152 -0
- package/bundle/633.index.js +86 -0
- package/bundle/697.index.js +410 -0
- package/bundle/7183ea1c1bf0a5da980d.js +214 -0
- package/bundle/748.index.js +672 -0
- package/bundle/78.index.js +372 -0
- package/bundle/841.index.js +2093 -0
- package/bundle/868.index.js +290 -0
- package/bundle/902.index.js +173 -0
- package/bundle/956.index.js +9901 -0
- package/bundle/aleo_wasm.wasm +0 -0
- package/bundle/aleo_wasm1.wasm +0 -0
- package/bundle/build/Release/bigint_buffer.node +0 -0
- package/bundle/file.js +12 -0
- package/bundle/index.js +4 -0
- package/bundle/package.json +3 -0
- package/bundle/worker-pipeline.js +38 -0
- package/bundle/worker.js +54 -0
- package/bundle/worker1.js +171 -0
- package/bundle/worker2.js +19834 -0
- package/bundle/worker3.js +19834 -0
- package/examples/aleo/core-config.yaml +19 -0
- package/examples/ccip/ccip-warp-route.yaml +26 -0
- package/examples/ccip/multi-chain-ccip-warp-route.yaml +81 -0
- package/examples/chain-config.yaml +42 -0
- package/examples/contract-artifacts.yaml +20 -0
- package/examples/core-config.yaml +19 -0
- package/examples/cosmosnative/core-config.yaml +20 -0
- package/examples/cosmosnative/warp-route-deployment.yaml +21 -0
- package/examples/crosschain/README.md +31 -0
- package/examples/crosschain/cosmosnative/core-config.yaml +69 -0
- package/examples/crosschain/ethereum/core-config.yaml +70 -0
- package/examples/crosschain/radix/core-config.yaml +96 -0
- package/examples/fork/file-transactions.yaml +5 -0
- package/examples/fork/timelock-testing.yaml +36 -0
- package/examples/fork/warp-route-update.yaml +100 -0
- package/examples/hooks.yaml +70 -0
- package/examples/ism-advanced.yaml +33 -0
- package/examples/ism.yaml +13 -0
- package/examples/radix/core-config.yaml +29 -0
- package/examples/sealevel/core-config-apply.yaml +7 -0
- package/examples/sealevel/core-config-composite-ism.yaml +21 -0
- package/examples/sealevel/core-config.yaml +12 -0
- package/examples/sealevel/igp-hook-config.yaml +13 -0
- package/examples/sealevel/warp-config-routing-message-id-multisig-ism.yaml +21 -0
- package/examples/starknet/core-config.yaml +20 -0
- package/examples/submit/strategy/file-strategy.yaml +8 -0
- package/examples/submit/strategy/gnosis-chain-strategy.yaml +10 -0
- package/examples/submit/strategy/gnosis-chain-tx-builder-strategy.yaml +6 -0
- package/examples/submit/strategy/gnosis-strategy-avalanche.yaml +4 -0
- package/examples/submit/strategy/gnosis-tx-builder-ica-strategy.yaml +20 -0
- package/examples/submit/strategy/ica-timelock-strategy.yaml +15 -0
- package/examples/submit/strategy/impersonated-account-strategy.yaml +4 -0
- package/examples/submit/strategy/json-rpc-chain-strategy.yaml +8 -0
- package/examples/submit/strategy/json-rpc-ica-strategy.yaml +13 -0
- package/examples/submit/strategy/json-rpc-strategy.yaml +3 -0
- package/examples/submit/strategy/json-rpc-timelock-strategy.yaml +17 -0
- package/examples/submit/strategy/starknet-json-rpc-chain-strategy.yaml +11 -0
- package/examples/submit/transactions/anvil-transactions.yaml +10 -0
- package/examples/submit/transactions/avalanche-transactions.json +8 -0
- package/examples/submit/transactions/sepolia-transactions.json +8 -0
- package/examples/tron-core-config.yaml +18 -0
- package/examples/warp-base.yaml +24 -0
- package/examples/warp-config-predicate-mainnet.yaml +14 -0
- package/examples/warp-deployed-predicate-mainnet.yaml +19 -0
- package/examples/warp-ethereum.yaml +19 -0
- package/examples/warp-route-deployment.yaml +21 -0
- package/package.json +117 -3
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
export const id = 230;
|
|
2
|
+
export const ids = [230];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 65230:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ LiFiBridge: () => (/* binding */ LiFiBridge)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(38972);
|
|
12
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(88943);
|
|
13
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(91170);
|
|
14
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(16950);
|
|
15
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(89302);
|
|
16
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(68648);
|
|
17
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(83793);
|
|
18
|
+
/* harmony import */ var _lifi_sdk__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(38706);
|
|
19
|
+
/* harmony import */ var bs58__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(64537);
|
|
20
|
+
/* harmony import */ var _recoillink_utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(16639);
|
|
21
|
+
/* harmony import */ var _recoillink_utils__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(93142);
|
|
22
|
+
/* harmony import */ var _recoillink_utils__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(21387);
|
|
23
|
+
/* harmony import */ var viem__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(27503);
|
|
24
|
+
/* harmony import */ var viem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(20458);
|
|
25
|
+
/* harmony import */ var viem_accounts__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(67227);
|
|
26
|
+
/* harmony import */ var viem_chains__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55516);
|
|
27
|
+
/* harmony import */ var viem_chains__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(86296);
|
|
28
|
+
/* harmony import */ var viem_chains__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(70763);
|
|
29
|
+
/* harmony import */ var viem_chains__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(35540);
|
|
30
|
+
/* harmony import */ var _utils_solanaKeyParser_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(97781);
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* LiFi API base URL for REST endpoints.
|
|
40
|
+
* The SDK doesn't support toAmount quotes, so we use REST API directly.
|
|
41
|
+
*/
|
|
42
|
+
const LIFI_API_BASE = 'https://li.quest/v1';
|
|
43
|
+
/**
|
|
44
|
+
* Known chains for viem - add more as needed.
|
|
45
|
+
* TODO: can we think of a cleaner way to do this?
|
|
46
|
+
*/
|
|
47
|
+
const VIEM_CHAINS = {
|
|
48
|
+
[viem_chains__WEBPACK_IMPORTED_MODULE_2__/* .mainnet */ .r.id]: viem_chains__WEBPACK_IMPORTED_MODULE_2__/* .mainnet */ .r,
|
|
49
|
+
[viem_chains__WEBPACK_IMPORTED_MODULE_3__/* .arbitrum */ .D.id]: viem_chains__WEBPACK_IMPORTED_MODULE_3__/* .arbitrum */ .D,
|
|
50
|
+
[viem_chains__WEBPACK_IMPORTED_MODULE_4__/* .base */ .E.id]: viem_chains__WEBPACK_IMPORTED_MODULE_4__/* .base */ .E,
|
|
51
|
+
[viem_chains__WEBPACK_IMPORTED_MODULE_5__/* .optimism */ .R.id]: viem_chains__WEBPACK_IMPORTED_MODULE_5__/* .optimism */ .R,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Mapping from Hyperlane domain IDs to LiFi chain IDs for non-EVM chains.
|
|
55
|
+
* EVM chains use their native chain ID which is the same in both systems.
|
|
56
|
+
* Non-EVM chains (e.g., Solana) have different identifiers in each system.
|
|
57
|
+
*
|
|
58
|
+
* @see https://docs.li.fi/introduction/lifi-architecture/solana-overview
|
|
59
|
+
*/
|
|
60
|
+
const HYPERLANE_TO_LIFI_CHAIN_IDS = {
|
|
61
|
+
1399811149: 1151111081099710, // Solana: Hyperlane domain → LiFi chain ID
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Get viem chain config by chain ID.
|
|
65
|
+
* Falls back to a minimal chain config if not found.
|
|
66
|
+
*/
|
|
67
|
+
function getViemChain(chainId, rpcUrl) {
|
|
68
|
+
const chain = VIEM_CHAINS[chainId];
|
|
69
|
+
if (chain) {
|
|
70
|
+
if (rpcUrl) {
|
|
71
|
+
return { ...chain, rpcUrls: { default: { http: [rpcUrl] } } };
|
|
72
|
+
}
|
|
73
|
+
return chain;
|
|
74
|
+
}
|
|
75
|
+
// Fallback for chains not in our registry
|
|
76
|
+
return {
|
|
77
|
+
id: chainId,
|
|
78
|
+
name: `Chain ${chainId}`,
|
|
79
|
+
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
|
|
80
|
+
rpcUrls: {
|
|
81
|
+
default: { http: rpcUrl ? [rpcUrl] : [] },
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function toBase58SolanaKey(rawKey) {
|
|
86
|
+
const trimmedKey = rawKey.trim();
|
|
87
|
+
if (!trimmedKey.startsWith('[') && !trimmedKey.includes(',')) {
|
|
88
|
+
try {
|
|
89
|
+
const decoded = bs58__WEBPACK_IMPORTED_MODULE_0__["default"].decode(trimmedKey);
|
|
90
|
+
if (decoded.length === 64) {
|
|
91
|
+
return trimmedKey;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Not valid base58, continue to parse as byte array
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const bytes = (0,_utils_solanaKeyParser_js__WEBPACK_IMPORTED_MODULE_1__/* .parseSolanaPrivateKey */ .E)(trimmedKey);
|
|
99
|
+
return bs58__WEBPACK_IMPORTED_MODULE_0__["default"].encode(bytes);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* LiFi implementation of IExternalBridge using the official @lifi/sdk.
|
|
103
|
+
*
|
|
104
|
+
* The SDK provides:
|
|
105
|
+
* - Automatic token approvals via executeRoute()
|
|
106
|
+
* - Multi-step route handling (swap → bridge → swap)
|
|
107
|
+
* - Built-in status tracking via getStatus()
|
|
108
|
+
* - Native support for EVM, Solana, and other chains
|
|
109
|
+
*
|
|
110
|
+
* @see https://docs.li.fi/integrate-li.fi-sdk
|
|
111
|
+
*/
|
|
112
|
+
class LiFiBridge {
|
|
113
|
+
static NATIVE_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
114
|
+
externalBridgeId = 'lifi';
|
|
115
|
+
/**
|
|
116
|
+
* Convert a Hyperlane domain ID to a LiFi-compatible chain ID.
|
|
117
|
+
* For EVM chains, these are identical (e.g., Arbitrum = 42161 in both).
|
|
118
|
+
* For non-EVM chains like Solana, LiFi uses different chain identifiers.
|
|
119
|
+
*/
|
|
120
|
+
static toLiFiChainId(chainId) {
|
|
121
|
+
return HYPERLANE_TO_LIFI_CHAIN_IDS[chainId] ?? chainId;
|
|
122
|
+
}
|
|
123
|
+
logger;
|
|
124
|
+
initialized = false;
|
|
125
|
+
_executeLock = Promise.resolve();
|
|
126
|
+
config;
|
|
127
|
+
chainMetadataByChainId;
|
|
128
|
+
constructor(config, logger) {
|
|
129
|
+
this.config = config;
|
|
130
|
+
this.logger = logger;
|
|
131
|
+
// Build LiFi chainId -> metadata map for O(1) lookups.
|
|
132
|
+
// Numeric chainIds are only unique for EVM chains; non-EVM chains can
|
|
133
|
+
// legitimately collide (e.g. radix and ethereum both use 1), so index
|
|
134
|
+
// non-EVM chains only through explicit Hyperlane-domain -> LiFi mappings.
|
|
135
|
+
this.chainMetadataByChainId = new Map();
|
|
136
|
+
if (config.chainMetadata) {
|
|
137
|
+
const metadataByDomainId = new Map();
|
|
138
|
+
for (const metadata of Object.values(config.chainMetadata)) {
|
|
139
|
+
metadataByDomainId.set(metadata.domainId, metadata);
|
|
140
|
+
if (metadata.chainId !== undefined && (0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_6__/* .isEVMLike */ .Us)(metadata.protocol)) {
|
|
141
|
+
this.chainMetadataByChainId.set(Number(metadata.chainId), metadata);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Also key by LiFi chain IDs so both Hyperlane domains and LiFi IDs
|
|
145
|
+
// resolve to the same metadata for non-EVM chains like Solana.
|
|
146
|
+
for (const [hyperlaneDomainId, lifiChainId] of Object.entries(HYPERLANE_TO_LIFI_CHAIN_IDS)) {
|
|
147
|
+
const metadata = metadataByDomainId.get(Number(hyperlaneDomainId));
|
|
148
|
+
if (metadata !== undefined) {
|
|
149
|
+
this.chainMetadataByChainId.set(Number(lifiChainId), metadata);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
getNativeTokenAddress() {
|
|
155
|
+
return LiFiBridge.NATIVE_TOKEN_ADDRESS;
|
|
156
|
+
}
|
|
157
|
+
initialize() {
|
|
158
|
+
if (this.initialized)
|
|
159
|
+
return;
|
|
160
|
+
(0,_lifi_sdk__WEBPACK_IMPORTED_MODULE_7__/* .createConfig */ .Z)({
|
|
161
|
+
integrator: this.config.integrator,
|
|
162
|
+
apiKey: this.config.apiKey,
|
|
163
|
+
});
|
|
164
|
+
this.initialized = true;
|
|
165
|
+
this.logger.info({ integrator: this.config.integrator }, 'LiFi SDK initialized');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Resolve RPC URL for a given EVM chainId from chain metadata.
|
|
169
|
+
* Iterates metadata to find matching chainId and returns first HTTP RPC URL.
|
|
170
|
+
*/
|
|
171
|
+
getRpcUrlForChainId(chainId) {
|
|
172
|
+
return this.getMetadataForChainId(chainId)?.rpcUrls?.[0]?.http;
|
|
173
|
+
}
|
|
174
|
+
getProtocolTypeForChainId(chainId) {
|
|
175
|
+
return this.getMetadataForChainId(chainId)?.protocol;
|
|
176
|
+
}
|
|
177
|
+
getMetadataForChainId(chainId) {
|
|
178
|
+
const directMetadata = this.chainMetadataByChainId.get(chainId);
|
|
179
|
+
if (directMetadata) {
|
|
180
|
+
return directMetadata;
|
|
181
|
+
}
|
|
182
|
+
const matches = Object.values(this.config.chainMetadata ?? {}).filter((metadata) => metadata.chainId !== undefined && Number(metadata.chainId) === chainId);
|
|
183
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
184
|
+
}
|
|
185
|
+
addressesEqual(a, b, chainId) {
|
|
186
|
+
const protocol = this.getProtocolTypeForChainId(chainId);
|
|
187
|
+
// Sealevel uses base58 addresses where case is significant.
|
|
188
|
+
if (protocol === _recoillink_utils__WEBPACK_IMPORTED_MODULE_6__/* .ProtocolType */ .Hb.Sealevel) {
|
|
189
|
+
return a === b;
|
|
190
|
+
}
|
|
191
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Configure the LiFi SDK provider for the route source protocol.
|
|
195
|
+
*/
|
|
196
|
+
configureLiFiProvider(protocol, key, fromChain, fromRpcUrl) {
|
|
197
|
+
const providers = [];
|
|
198
|
+
switch (protocol) {
|
|
199
|
+
case _recoillink_utils__WEBPACK_IMPORTED_MODULE_6__/* .ProtocolType */ .Hb.Ethereum: {
|
|
200
|
+
const account = (0,viem_accounts__WEBPACK_IMPORTED_MODULE_8__/* .privateKeyToAccount */ .R)((0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_9__/* .ensure0x */ .Ho)(key));
|
|
201
|
+
const chain = getViemChain(fromChain, fromRpcUrl);
|
|
202
|
+
const walletClient = (0,viem__WEBPACK_IMPORTED_MODULE_10__/* .createWalletClient */ .F)({
|
|
203
|
+
account,
|
|
204
|
+
chain,
|
|
205
|
+
transport: (0,viem__WEBPACK_IMPORTED_MODULE_11__/* .http */ .L)(fromRpcUrl),
|
|
206
|
+
});
|
|
207
|
+
providers.push((0,_lifi_sdk__WEBPACK_IMPORTED_MODULE_12__/* .EVM */ .Z)({
|
|
208
|
+
getWalletClient: async () => walletClient,
|
|
209
|
+
switchChain: async (requiredChainId) => {
|
|
210
|
+
const switchRpcUrl = this.getRpcUrlForChainId(requiredChainId);
|
|
211
|
+
const requiredChain = getViemChain(requiredChainId, switchRpcUrl);
|
|
212
|
+
return (0,viem__WEBPACK_IMPORTED_MODULE_10__/* .createWalletClient */ .F)({
|
|
213
|
+
account,
|
|
214
|
+
chain: requiredChain,
|
|
215
|
+
transport: (0,viem__WEBPACK_IMPORTED_MODULE_11__/* .http */ .L)(switchRpcUrl),
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
}));
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case _recoillink_utils__WEBPACK_IMPORTED_MODULE_6__/* .ProtocolType */ .Hb.Sealevel: {
|
|
222
|
+
const base58Key = toBase58SolanaKey(key);
|
|
223
|
+
providers.push((0,_lifi_sdk__WEBPACK_IMPORTED_MODULE_13__/* .Solana */ .j)({
|
|
224
|
+
getWalletAdapter: async () => new _lifi_sdk__WEBPACK_IMPORTED_MODULE_14__/* .KeypairWalletAdapter */ .E(base58Key),
|
|
225
|
+
}));
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
default:
|
|
229
|
+
throw new Error(`Unsupported protocol type '${protocol}' for LiFi provider`);
|
|
230
|
+
}
|
|
231
|
+
_lifi_sdk__WEBPACK_IMPORTED_MODULE_15__/* .config */ .$.setProviders(providers);
|
|
232
|
+
this.logger.debug({
|
|
233
|
+
fromChain,
|
|
234
|
+
protocol,
|
|
235
|
+
}, 'Configured LiFi provider for route execution');
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get a quote for bridging tokens.
|
|
239
|
+
* Supports two modes:
|
|
240
|
+
* - fromAmount: "I'm sending X, what do I get?" (uses SDK)
|
|
241
|
+
* - toAmount: "I want X, how much do I send?" (uses REST API)
|
|
242
|
+
*
|
|
243
|
+
* Returns route data ready for execution.
|
|
244
|
+
*/
|
|
245
|
+
async quote(params) {
|
|
246
|
+
this.initialize();
|
|
247
|
+
// Validate that exactly one of fromAmount or toAmount is provided
|
|
248
|
+
if (params.fromAmount !== undefined && params.toAmount !== undefined) {
|
|
249
|
+
throw new Error('Cannot specify both fromAmount and toAmount - provide exactly one');
|
|
250
|
+
}
|
|
251
|
+
if (params.fromAmount === undefined && params.toAmount === undefined) {
|
|
252
|
+
throw new Error('Must specify either fromAmount or toAmount');
|
|
253
|
+
}
|
|
254
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(params.fromAmount === undefined || params.fromAmount > 0n, 'fromAmount must be positive');
|
|
255
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(params.toAmount === undefined || params.toAmount > 0n, 'toAmount must be positive');
|
|
256
|
+
// Dispatch to appropriate quote method
|
|
257
|
+
if (params.toAmount !== undefined) {
|
|
258
|
+
return this.quoteByReceivingAmount(params);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
return this.quoteBySpendingAmount(params);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get a quote by specifying the amount to send (standard quote).
|
|
266
|
+
* Uses the LiFi SDK.
|
|
267
|
+
*/
|
|
268
|
+
async quoteBySpendingAmount(params) {
|
|
269
|
+
this.logger.debug({ params }, 'Requesting LiFi quote by spending amount');
|
|
270
|
+
const lifiFromChain = LiFiBridge.toLiFiChainId(params.fromChain);
|
|
271
|
+
const lifiToChain = LiFiBridge.toLiFiChainId(params.toChain);
|
|
272
|
+
const quote = await (0,_lifi_sdk__WEBPACK_IMPORTED_MODULE_17__/* .getQuote */ .fn)({
|
|
273
|
+
fromChain: lifiFromChain,
|
|
274
|
+
toChain: lifiToChain,
|
|
275
|
+
fromToken: params.fromToken,
|
|
276
|
+
toToken: params.toToken,
|
|
277
|
+
fromAmount: params.fromAmount.toString(),
|
|
278
|
+
fromAddress: params.fromAddress,
|
|
279
|
+
toAddress: params.toAddress ?? params.fromAddress,
|
|
280
|
+
slippage: params.slippage ?? this.config.defaultSlippage ?? 0.005,
|
|
281
|
+
// Prefer faster routes for rebalancing
|
|
282
|
+
order: 'RECOMMENDED',
|
|
283
|
+
});
|
|
284
|
+
const { gasCosts, feeCosts } = this.extractCosts(quote);
|
|
285
|
+
this.logger.info({
|
|
286
|
+
quoteId: quote.id,
|
|
287
|
+
tool: quote.tool,
|
|
288
|
+
fromAmount: quote.action.fromAmount,
|
|
289
|
+
toAmount: quote.estimate.toAmount,
|
|
290
|
+
toAmountMin: quote.estimate.toAmountMin,
|
|
291
|
+
executionDuration: quote.estimate.executionDuration,
|
|
292
|
+
gasCosts: gasCosts.toString(),
|
|
293
|
+
feeCosts: feeCosts.toString(),
|
|
294
|
+
}, 'LiFi quote received (fromAmount)');
|
|
295
|
+
return {
|
|
296
|
+
id: quote.id,
|
|
297
|
+
tool: quote.tool,
|
|
298
|
+
fromAmount: BigInt(quote.action.fromAmount),
|
|
299
|
+
toAmount: BigInt(quote.estimate.toAmount),
|
|
300
|
+
toAmountMin: BigInt(quote.estimate.toAmountMin),
|
|
301
|
+
executionDuration: quote.estimate.executionDuration,
|
|
302
|
+
gasCosts,
|
|
303
|
+
feeCosts,
|
|
304
|
+
route: quote, // Store full quote for conversion to route
|
|
305
|
+
requestParams: {
|
|
306
|
+
...params,
|
|
307
|
+
fromChain: lifiFromChain,
|
|
308
|
+
toChain: lifiToChain,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get a quote by specifying the amount to receive (reverse quote).
|
|
314
|
+
* Uses the LiFi REST API directly since the SDK doesn't support toAmount.
|
|
315
|
+
*/
|
|
316
|
+
async quoteByReceivingAmount(params) {
|
|
317
|
+
this.logger.debug({ params }, 'Requesting LiFi quote by receiving amount');
|
|
318
|
+
const lifiFromChain = LiFiBridge.toLiFiChainId(params.fromChain);
|
|
319
|
+
const lifiToChain = LiFiBridge.toLiFiChainId(params.toChain);
|
|
320
|
+
const queryParams = new URLSearchParams({
|
|
321
|
+
fromChain: lifiFromChain.toString(),
|
|
322
|
+
toChain: lifiToChain.toString(),
|
|
323
|
+
fromToken: params.fromToken,
|
|
324
|
+
toToken: params.toToken,
|
|
325
|
+
toAmount: params.toAmount.toString(),
|
|
326
|
+
fromAddress: params.fromAddress,
|
|
327
|
+
toAddress: params.toAddress ?? params.fromAddress,
|
|
328
|
+
slippage: (params.slippage ?? this.config.defaultSlippage ?? 0.005)
|
|
329
|
+
.toFixed(4)
|
|
330
|
+
.replace(/\.?0+$/, ''),
|
|
331
|
+
order: 'RECOMMENDED',
|
|
332
|
+
integrator: this.config.integrator,
|
|
333
|
+
});
|
|
334
|
+
if (this.config.apiKey) {
|
|
335
|
+
queryParams.set('apiKey', this.config.apiKey);
|
|
336
|
+
}
|
|
337
|
+
const url = `${LIFI_API_BASE}/quote/toAmount?${queryParams.toString()}`;
|
|
338
|
+
this.logger.debug({ url: url.replace(/apiKey=[^&]+/, 'apiKey=***') }, 'Fetching LiFi toAmount quote');
|
|
339
|
+
const response = await fetch(url);
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
const errorBody = await response.text();
|
|
342
|
+
throw new Error(`LiFi toAmount quote failed: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
343
|
+
}
|
|
344
|
+
const quote = await response.json();
|
|
345
|
+
const { gasCosts, feeCosts } = this.extractCosts(quote);
|
|
346
|
+
this.logger.info({
|
|
347
|
+
quoteId: quote.id,
|
|
348
|
+
tool: quote.tool,
|
|
349
|
+
fromAmount: quote.action.fromAmount,
|
|
350
|
+
toAmount: quote.estimate.toAmount,
|
|
351
|
+
toAmountMin: quote.estimate.toAmountMin,
|
|
352
|
+
executionDuration: quote.estimate.executionDuration,
|
|
353
|
+
gasCosts: gasCosts.toString(),
|
|
354
|
+
feeCosts: feeCosts.toString(),
|
|
355
|
+
}, 'LiFi quote received (toAmount)');
|
|
356
|
+
return {
|
|
357
|
+
id: quote.id,
|
|
358
|
+
tool: quote.tool,
|
|
359
|
+
fromAmount: BigInt(quote.action.fromAmount),
|
|
360
|
+
toAmount: BigInt(quote.estimate.toAmount),
|
|
361
|
+
toAmountMin: BigInt(quote.estimate.toAmountMin),
|
|
362
|
+
executionDuration: quote.estimate.executionDuration,
|
|
363
|
+
gasCosts,
|
|
364
|
+
feeCosts,
|
|
365
|
+
route: quote, // Store full quote for conversion to route
|
|
366
|
+
requestParams: {
|
|
367
|
+
...params,
|
|
368
|
+
fromChain: lifiFromChain,
|
|
369
|
+
toChain: lifiToChain,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Extract gas and fee costs from a LiFi quote response.
|
|
375
|
+
* - gasCosts: Sum of all gas costs (transaction fees)
|
|
376
|
+
* - feeCosts: Sum of non-included fee costs (protocol fees not deducted from amount)
|
|
377
|
+
*/
|
|
378
|
+
extractCosts(quote) {
|
|
379
|
+
let gasCosts = 0n;
|
|
380
|
+
let feeCosts = 0n;
|
|
381
|
+
// Sum up gas costs
|
|
382
|
+
if (quote.estimate.gasCosts) {
|
|
383
|
+
for (const cost of quote.estimate.gasCosts) {
|
|
384
|
+
gasCosts += BigInt(cost.amount);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// Sum up non-included fee costs
|
|
388
|
+
// (included fees are already deducted from toAmount, so we only count non-included)
|
|
389
|
+
if (quote.estimate.feeCosts) {
|
|
390
|
+
for (const cost of quote.estimate.feeCosts) {
|
|
391
|
+
if (!cost.included) {
|
|
392
|
+
feeCosts += BigInt(cost.amount);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return { gasCosts, feeCosts };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Execute a bridge transfer using the SDK.
|
|
400
|
+
* Handles approvals, transaction signing, and execution automatically.
|
|
401
|
+
*
|
|
402
|
+
* @param quote - Quote obtained from quote()
|
|
403
|
+
* @param privateKeys - Private keys keyed by protocol type for signing transactions
|
|
404
|
+
*/
|
|
405
|
+
async execute(quote, privateKeys) {
|
|
406
|
+
this.initialize();
|
|
407
|
+
// Convert quote to route for execution
|
|
408
|
+
const route = (0,_lifi_sdk__WEBPACK_IMPORTED_MODULE_18__/* .convertQuoteToRoute */ ._)(quote.route);
|
|
409
|
+
this.validateRouteAgainstRequest(route, quote.requestParams);
|
|
410
|
+
const fromChain = route.fromChainId;
|
|
411
|
+
const toChain = route.toChainId;
|
|
412
|
+
const fromProtocol = this.getProtocolTypeForChainId(fromChain);
|
|
413
|
+
const sourceProtocol = fromProtocol ?? _recoillink_utils__WEBPACK_IMPORTED_MODULE_6__/* .ProtocolType */ .Hb.Ethereum;
|
|
414
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(privateKeys[sourceProtocol], `Missing private key for source chain protocol ${sourceProtocol}`);
|
|
415
|
+
this.logger.info({
|
|
416
|
+
quoteId: quote.id,
|
|
417
|
+
tool: quote.tool,
|
|
418
|
+
fromChain,
|
|
419
|
+
toChain,
|
|
420
|
+
fromAmount: quote.fromAmount.toString(),
|
|
421
|
+
}, 'Executing LiFi bridge transfer');
|
|
422
|
+
const fromRpcUrl = this.getRpcUrlForChainId(fromChain);
|
|
423
|
+
let release;
|
|
424
|
+
const acquired = new Promise((resolve) => {
|
|
425
|
+
release = resolve;
|
|
426
|
+
});
|
|
427
|
+
const prev = this._executeLock;
|
|
428
|
+
this._executeLock = acquired;
|
|
429
|
+
await prev;
|
|
430
|
+
let txHash;
|
|
431
|
+
let executedRoute;
|
|
432
|
+
try {
|
|
433
|
+
this.configureLiFiProvider(sourceProtocol, privateKeys[sourceProtocol], fromChain, fromRpcUrl);
|
|
434
|
+
// Execute route with update callbacks
|
|
435
|
+
executedRoute = await (0,_lifi_sdk__WEBPACK_IMPORTED_MODULE_19__/* .executeRoute */ .Zk)(route, {
|
|
436
|
+
// Update callback for route progress
|
|
437
|
+
updateRouteHook: (updatedRoute) => {
|
|
438
|
+
this.logger.debug({ step: updatedRoute.steps[0]?.id }, 'Route step updated');
|
|
439
|
+
// Extract txHash from execution if available (RouteExtended has LiFiStepExtended with execution)
|
|
440
|
+
const execution = updatedRoute.steps[0]?.execution;
|
|
441
|
+
if (execution?.process) {
|
|
442
|
+
for (const process of execution.process) {
|
|
443
|
+
if (process.txHash) {
|
|
444
|
+
txHash = process.txHash;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
// Auto-accept rate updates for rebalancing
|
|
450
|
+
acceptExchangeRateUpdateHook: async () => true,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
finally {
|
|
454
|
+
release();
|
|
455
|
+
}
|
|
456
|
+
// Extract txHash from executed route if not captured in callbacks
|
|
457
|
+
if (!txHash) {
|
|
458
|
+
const execution = executedRoute.steps[0]?.execution;
|
|
459
|
+
if (execution?.process) {
|
|
460
|
+
for (const process of execution.process) {
|
|
461
|
+
if (process.txHash) {
|
|
462
|
+
txHash = process.txHash;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!txHash) {
|
|
469
|
+
throw new Error('No transaction hash found in executed route');
|
|
470
|
+
}
|
|
471
|
+
this.logger.info({ txHash, quoteId: quote.id }, 'LiFi bridge transaction executed');
|
|
472
|
+
// Extract transfer ID if available (some bridges provide this)
|
|
473
|
+
let transferId;
|
|
474
|
+
const processes = executedRoute.steps[0]?.execution?.process;
|
|
475
|
+
const txInfo = processes?.find((p) => p.txHash === txHash);
|
|
476
|
+
if (txInfo && 'lifiExplorerLink' in txInfo) {
|
|
477
|
+
// Extract transfer ID from explorer link if available
|
|
478
|
+
const link = txInfo.lifiExplorerLink;
|
|
479
|
+
const match = link?.match(/\/tx\/([^/]+)/);
|
|
480
|
+
if (match) {
|
|
481
|
+
transferId = match[1];
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
txHash,
|
|
486
|
+
fromChain,
|
|
487
|
+
toChain,
|
|
488
|
+
transferId,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Validate that the route returned by LiFi matches the original request parameters.
|
|
493
|
+
* Prevents execution against wrong chains, tokens, or recipients if the bridge API
|
|
494
|
+
* returns a route that diverges from what was originally requested.
|
|
495
|
+
*
|
|
496
|
+
* TODO: Layer 2 validation — validate transaction calldata in route.steps[].transactionRequest
|
|
497
|
+
* and route.steps[0].estimate.approvalAddress against a known whitelist.
|
|
498
|
+
*/
|
|
499
|
+
validateRouteAgainstRequest(route, requestParams) {
|
|
500
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(route.fromChainId === requestParams.fromChain, `Route fromChainId ${route.fromChainId} does not match requested ${requestParams.fromChain}`);
|
|
501
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(route.toChainId === requestParams.toChain, `Route toChainId ${route.toChainId} does not match requested ${requestParams.toChain}`);
|
|
502
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(this.addressesEqual(route.fromToken.address, requestParams.fromToken, route.fromChainId), `Route fromToken ${route.fromToken.address} does not match requested ${requestParams.fromToken}`);
|
|
503
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(this.addressesEqual(route.toToken.address, requestParams.toToken, route.toChainId), `Route toToken ${route.toToken.address} does not match requested ${requestParams.toToken}`);
|
|
504
|
+
const expectedToAddress = requestParams.toAddress ?? requestParams.fromAddress;
|
|
505
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(this.addressesEqual(route.toAddress ?? '', expectedToAddress, route.toChainId), `Route toAddress ${route.toAddress} does not match requested ${expectedToAddress}`);
|
|
506
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(this.addressesEqual(route.fromAddress ?? '', requestParams.fromAddress, route.fromChainId), `Route fromAddress ${route.fromAddress} does not match requested ${requestParams.fromAddress}`);
|
|
507
|
+
const routeFromAmount = BigInt(route.fromAmount);
|
|
508
|
+
if (requestParams.fromAmount !== undefined) {
|
|
509
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(routeFromAmount === requestParams.fromAmount, `Route fromAmount ${route.fromAmount} does not match requested ${requestParams.fromAmount}`);
|
|
510
|
+
}
|
|
511
|
+
if (requestParams.toAmount !== undefined) {
|
|
512
|
+
const routeToAmount = BigInt(route.toAmount);
|
|
513
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(routeToAmount >= requestParams.toAmount, `Route toAmount ${route.toAmount} is less than requested ${requestParams.toAmount}`);
|
|
514
|
+
}
|
|
515
|
+
(0,_recoillink_utils__WEBPACK_IMPORTED_MODULE_16__/* .assert */ .v)(routeFromAmount > 0n, 'Route fromAmount must be positive');
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Get the status of a bridge transfer.
|
|
519
|
+
* Uses SDK's built-in status tracking.
|
|
520
|
+
*/
|
|
521
|
+
async getStatus(txHash, fromChain, toChain) {
|
|
522
|
+
this.initialize();
|
|
523
|
+
try {
|
|
524
|
+
const status = await (0,_lifi_sdk__WEBPACK_IMPORTED_MODULE_17__/* .getStatus */ .BS)({
|
|
525
|
+
txHash,
|
|
526
|
+
fromChain: LiFiBridge.toLiFiChainId(fromChain),
|
|
527
|
+
toChain: LiFiBridge.toLiFiChainId(toChain),
|
|
528
|
+
});
|
|
529
|
+
switch (status.status) {
|
|
530
|
+
case 'DONE': {
|
|
531
|
+
// receiving can be PendingReceivingInfo (only chainId) or ExtendedTransactionInfo (has txHash, amount)
|
|
532
|
+
const receiving = status.receiving;
|
|
533
|
+
const receivingTxHash = receiving && 'txHash' in receiving ? (receiving.txHash ?? '') : '';
|
|
534
|
+
const receivedAmount = receiving && 'amount' in receiving
|
|
535
|
+
? BigInt(receiving.amount ?? '0')
|
|
536
|
+
: BigInt(0);
|
|
537
|
+
return {
|
|
538
|
+
status: 'complete',
|
|
539
|
+
receivingTxHash,
|
|
540
|
+
receivedAmount,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
case 'FAILED':
|
|
544
|
+
return {
|
|
545
|
+
status: 'failed',
|
|
546
|
+
error: status.substatus,
|
|
547
|
+
};
|
|
548
|
+
case 'NOT_FOUND':
|
|
549
|
+
case 'INVALID':
|
|
550
|
+
return { status: 'not_found' };
|
|
551
|
+
case 'PENDING':
|
|
552
|
+
default:
|
|
553
|
+
return {
|
|
554
|
+
status: 'pending',
|
|
555
|
+
substatus: status.substatus,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch (error) {
|
|
560
|
+
this.logger.warn({ txHash, error }, 'Failed to get LiFi status');
|
|
561
|
+
return { status: 'not_found' };
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
//# sourceMappingURL=LiFiBridge.js.map
|
|
566
|
+
|
|
567
|
+
/***/ })
|
|
568
|
+
|
|
569
|
+
};
|