@stablechain/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +893 -0
- package/dist/index.d.cts +169 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.js +893 -0
- package/package.json +42 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/stable.ts
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
var _viem = require('viem');
|
|
10
|
+
var _chains = require('viem/chains');
|
|
11
|
+
|
|
12
|
+
// src/errors.ts
|
|
13
|
+
|
|
14
|
+
var StableError = class extends _viem.BaseError {
|
|
15
|
+
};
|
|
16
|
+
var StableValidationError = class extends StableError {
|
|
17
|
+
constructor(field, value, reason, opts) {
|
|
18
|
+
super(reason, {
|
|
19
|
+
name: "StableValidationError",
|
|
20
|
+
cause: _optionalChain([opts, 'optionalAccess', _ => _.cause]),
|
|
21
|
+
metaMessages: [`Field: ${field}`, `Value: ${formatValue(value)}`]
|
|
22
|
+
});
|
|
23
|
+
this.field = field;
|
|
24
|
+
this.value = value;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var StableQuoteError = class extends StableError {
|
|
28
|
+
constructor(args) {
|
|
29
|
+
const meta = [`Provider: ${args.provider}`];
|
|
30
|
+
if (args.httpStatus !== void 0) meta.push(`HTTP status: ${args.httpStatus}`);
|
|
31
|
+
if (args.providerCode) meta.push(`Provider code: ${args.providerCode}`);
|
|
32
|
+
super(args.shortMessage, {
|
|
33
|
+
name: "StableQuoteError",
|
|
34
|
+
cause: args.cause,
|
|
35
|
+
metaMessages: meta
|
|
36
|
+
});
|
|
37
|
+
this.provider = args.provider;
|
|
38
|
+
this.httpStatus = args.httpStatus;
|
|
39
|
+
this.providerCode = args.providerCode;
|
|
40
|
+
this.body = args.body;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var StableTransactionError = class extends StableError {
|
|
44
|
+
constructor(args) {
|
|
45
|
+
const meta = [`Phase: ${args.phase}`];
|
|
46
|
+
if (args.chainId !== void 0) meta.push(`Chain ID: ${args.chainId}`);
|
|
47
|
+
if (args.txHash) meta.push(`Tx hash: ${args.txHash}`);
|
|
48
|
+
if (args.revertReason) meta.push(`Revert reason: ${args.revertReason}`);
|
|
49
|
+
super(args.shortMessage, {
|
|
50
|
+
name: "StableTransactionError",
|
|
51
|
+
cause: args.cause,
|
|
52
|
+
metaMessages: meta
|
|
53
|
+
});
|
|
54
|
+
this.phase = args.phase;
|
|
55
|
+
this.txHash = args.txHash;
|
|
56
|
+
this.chainId = args.chainId;
|
|
57
|
+
this.revertReason = args.revertReason;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var StableNetworkError = class extends StableError {
|
|
61
|
+
constructor(args) {
|
|
62
|
+
super(args.shortMessage, {
|
|
63
|
+
name: "StableNetworkError",
|
|
64
|
+
cause: args.cause,
|
|
65
|
+
metaMessages: args.url ? [`URL: ${args.url}`] : void 0
|
|
66
|
+
});
|
|
67
|
+
this.url = args.url;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function formatValue(v) {
|
|
71
|
+
if (typeof v === "bigint") return `${v}n`;
|
|
72
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
73
|
+
if (v === null) return "null";
|
|
74
|
+
if (v === void 0) return "undefined";
|
|
75
|
+
try {
|
|
76
|
+
return JSON.stringify(v);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return String(v);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/services/layerzero.ts
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// src/helpers/chain.ts
|
|
96
|
+
|
|
97
|
+
function makeChain(id, rpc) {
|
|
98
|
+
return _viem.defineChain.call(void 0, {
|
|
99
|
+
id,
|
|
100
|
+
name: "chain",
|
|
101
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
102
|
+
rpcUrls: { default: { http: [rpc] } }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/types.ts
|
|
107
|
+
var Chain = /* @__PURE__ */ ((Chain2) => {
|
|
108
|
+
Chain2["Sepolia"] = "sepolia";
|
|
109
|
+
Chain2["StableTestnet"] = "stable-testnet";
|
|
110
|
+
Chain2["Stable"] = "stable";
|
|
111
|
+
Chain2["Ethereum"] = "ethereum";
|
|
112
|
+
Chain2["Arbitrum"] = "arbitrum";
|
|
113
|
+
Chain2["Ink"] = "ink";
|
|
114
|
+
Chain2["Bera"] = "bera";
|
|
115
|
+
Chain2["MegaETH"] = "megaeth";
|
|
116
|
+
Chain2["Base"] = "base";
|
|
117
|
+
Chain2["BSC"] = "bsc";
|
|
118
|
+
Chain2["HyperEVM"] = "hyperevm";
|
|
119
|
+
return Chain2;
|
|
120
|
+
})(Chain || {});
|
|
121
|
+
var Network = /* @__PURE__ */ ((Network2) => {
|
|
122
|
+
Network2["Mainnet"] = "mainnet";
|
|
123
|
+
Network2["Testnet"] = "testnet";
|
|
124
|
+
return Network2;
|
|
125
|
+
})(Network || {});
|
|
126
|
+
|
|
127
|
+
// src/chains.ts
|
|
128
|
+
var CHAIN_CONFIGS = {
|
|
129
|
+
// ─── Testnet ──────────────────────────────────────────────────────────────
|
|
130
|
+
["sepolia" /* Sepolia */]: {
|
|
131
|
+
id: 11155111,
|
|
132
|
+
rpc: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
133
|
+
usdt: "0xc4DCC311c028e341fd8602D8eB89c5de94625927",
|
|
134
|
+
decimals: 18
|
|
135
|
+
},
|
|
136
|
+
["stable-testnet" /* StableTestnet */]: {
|
|
137
|
+
id: 2201,
|
|
138
|
+
rpc: "https://rpc.testnet.stable.xyz",
|
|
139
|
+
usdt: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
|
|
140
|
+
decimals: 6
|
|
141
|
+
},
|
|
142
|
+
// ─── Mainnet ──────────────────────────────────────────────────────────────
|
|
143
|
+
["stable" /* Stable */]: {
|
|
144
|
+
id: 988,
|
|
145
|
+
rpc: "https://rpc.stable.xyz",
|
|
146
|
+
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
147
|
+
decimals: 6
|
|
148
|
+
},
|
|
149
|
+
["ethereum" /* Ethereum */]: {
|
|
150
|
+
id: 1,
|
|
151
|
+
rpc: "https://ethereum-rpc.publicnode.com",
|
|
152
|
+
usdt: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
153
|
+
decimals: 6
|
|
154
|
+
},
|
|
155
|
+
["arbitrum" /* Arbitrum */]: {
|
|
156
|
+
id: 42161,
|
|
157
|
+
rpc: "https://arbitrum-one-rpc.publicnode.com",
|
|
158
|
+
usdt: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92",
|
|
159
|
+
decimals: 6
|
|
160
|
+
},
|
|
161
|
+
["ink" /* Ink */]: {
|
|
162
|
+
id: 57073,
|
|
163
|
+
rpc: "https://rpc-gel.inkonchain.com",
|
|
164
|
+
usdt: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
165
|
+
decimals: 6
|
|
166
|
+
},
|
|
167
|
+
["bera" /* Bera */]: {
|
|
168
|
+
id: 80094,
|
|
169
|
+
rpc: "https://rpc.berachain.com",
|
|
170
|
+
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
171
|
+
decimals: 6
|
|
172
|
+
},
|
|
173
|
+
["megaeth" /* MegaETH */]: {
|
|
174
|
+
id: 4326,
|
|
175
|
+
rpc: "https://carrot.megaeth.com",
|
|
176
|
+
usdt: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
|
|
177
|
+
decimals: 6
|
|
178
|
+
},
|
|
179
|
+
["base" /* Base */]: {
|
|
180
|
+
id: 8453,
|
|
181
|
+
rpc: "https://mainnet.base.org",
|
|
182
|
+
usdt: "0x102d758f688a4C1C5a80b116bD945d4455460282",
|
|
183
|
+
decimals: 6
|
|
184
|
+
},
|
|
185
|
+
["bsc" /* BSC */]: {
|
|
186
|
+
id: 56,
|
|
187
|
+
rpc: "https://bsc-rpc.publicnode.com",
|
|
188
|
+
usdt: "0x55d398326f99059fF775485246999027B3197955",
|
|
189
|
+
decimals: 18
|
|
190
|
+
},
|
|
191
|
+
["hyperevm" /* HyperEVM */]: {
|
|
192
|
+
id: 999,
|
|
193
|
+
rpc: "https://rpc.hyperliquid.xyz/evm",
|
|
194
|
+
usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
|
|
195
|
+
decimals: 6
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/services/layerzero.ts
|
|
200
|
+
var C = CHAIN_CONFIGS;
|
|
201
|
+
var BRIDGE_CHAIN_CONFIGS = {
|
|
202
|
+
// ─── Testnet ───────────────────────────────────────────────────────────────
|
|
203
|
+
["sepolia" /* Sepolia */]: { ...C["sepolia" /* Sepolia */], lzEid: 40161, oftAdapter: "0xc099cD946d5efCC35A99D64E808c1430cEf08126" },
|
|
204
|
+
["stable-testnet" /* StableTestnet */]: { ...C["stable-testnet" /* StableTestnet */], lzEid: 40374, oftAdapter: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736" },
|
|
205
|
+
// ─── Mainnet ──────────────────────────────────────────────────────────────
|
|
206
|
+
["stable" /* Stable */]: { ...C["stable" /* Stable */], lzEid: 30396, oftAdapter: "0xedaba024be4d87974d5aB11C6Dd586963CcCB027" },
|
|
207
|
+
["ethereum" /* Ethereum */]: { ...C["ethereum" /* Ethereum */], lzEid: 30101, oftAdapter: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee" },
|
|
208
|
+
["arbitrum" /* Arbitrum */]: { ...C["arbitrum" /* Arbitrum */], lzEid: 30110, oftAdapter: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92" },
|
|
209
|
+
["ink" /* Ink */]: { ...C["ink" /* Ink */], lzEid: 30339, oftAdapter: "0x1cB6De532588fCA4a21B7209DE7C456AF8434A65" },
|
|
210
|
+
["bera" /* Bera */]: { ...C["bera" /* Bera */], lzEid: 30362, oftAdapter: "0x3Dc96399109df5ceb2C226664A086140bD0379cB" },
|
|
211
|
+
["megaeth" /* MegaETH */]: { ...C["megaeth" /* MegaETH */], lzEid: 30398, oftAdapter: "0x9151434b16b9763660705744891fa906f660ecc5" },
|
|
212
|
+
["hyperevm" /* HyperEVM */]: { ...C["hyperevm" /* HyperEVM */], lzEid: 30367, oftAdapter: "0x904861a24F30EC96ea7CFC3bE9EA4B476d237e98" }
|
|
213
|
+
};
|
|
214
|
+
var OFT_ADAPTER_ABI = [
|
|
215
|
+
{
|
|
216
|
+
name: "quoteSend",
|
|
217
|
+
type: "function",
|
|
218
|
+
inputs: [
|
|
219
|
+
{
|
|
220
|
+
name: "_sendParam",
|
|
221
|
+
type: "tuple",
|
|
222
|
+
components: [
|
|
223
|
+
{ name: "dstEid", type: "uint32" },
|
|
224
|
+
{ name: "to", type: "bytes32" },
|
|
225
|
+
{ name: "amountLD", type: "uint256" },
|
|
226
|
+
{ name: "minAmountLD", type: "uint256" },
|
|
227
|
+
{ name: "extraOptions", type: "bytes" },
|
|
228
|
+
{ name: "composeMsg", type: "bytes" },
|
|
229
|
+
{ name: "oftCmd", type: "bytes" }
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
{ name: "_payInLzToken", type: "bool" }
|
|
233
|
+
],
|
|
234
|
+
outputs: [
|
|
235
|
+
{
|
|
236
|
+
name: "msgFee",
|
|
237
|
+
type: "tuple",
|
|
238
|
+
components: [
|
|
239
|
+
{ name: "nativeFee", type: "uint256" },
|
|
240
|
+
{ name: "lzTokenFee", type: "uint256" }
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
stateMutability: "view"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "send",
|
|
248
|
+
type: "function",
|
|
249
|
+
inputs: [
|
|
250
|
+
{
|
|
251
|
+
name: "_sendParam",
|
|
252
|
+
type: "tuple",
|
|
253
|
+
components: [
|
|
254
|
+
{ name: "dstEid", type: "uint32" },
|
|
255
|
+
{ name: "to", type: "bytes32" },
|
|
256
|
+
{ name: "amountLD", type: "uint256" },
|
|
257
|
+
{ name: "minAmountLD", type: "uint256" },
|
|
258
|
+
{ name: "extraOptions", type: "bytes" },
|
|
259
|
+
{ name: "composeMsg", type: "bytes" },
|
|
260
|
+
{ name: "oftCmd", type: "bytes" }
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "_fee",
|
|
265
|
+
type: "tuple",
|
|
266
|
+
components: [
|
|
267
|
+
{ name: "nativeFee", type: "uint256" },
|
|
268
|
+
{ name: "lzTokenFee", type: "uint256" }
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
{ name: "_refundAddress", type: "address" }
|
|
272
|
+
],
|
|
273
|
+
outputs: [],
|
|
274
|
+
stateMutability: "payable"
|
|
275
|
+
}
|
|
276
|
+
];
|
|
277
|
+
function buildLzOptions(gasLimit = 0n) {
|
|
278
|
+
return _viem.concat.call(void 0, [
|
|
279
|
+
_viem.numberToHex.call(void 0, 3, { size: 2 }),
|
|
280
|
+
// TYPE_3 header
|
|
281
|
+
_viem.numberToHex.call(void 0, 1, { size: 1 }),
|
|
282
|
+
// Executor worker ID
|
|
283
|
+
_viem.numberToHex.call(void 0, 17, { size: 2 }),
|
|
284
|
+
// option payload length = 17 bytes
|
|
285
|
+
_viem.numberToHex.call(void 0, 1, { size: 1 }),
|
|
286
|
+
// LzReceive option type
|
|
287
|
+
_viem.numberToHex.call(void 0, gasLimit, { size: 16 })
|
|
288
|
+
// gas limit as uint128
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
var LayerZeroService = class {
|
|
292
|
+
constructor(config) {
|
|
293
|
+
this.config = config;
|
|
294
|
+
}
|
|
295
|
+
async quoteBridge({ fromChain, toChain, amount, recipient }) {
|
|
296
|
+
const recipientAddr = _nullishCoalesce(recipient, () => ( "0x0000000000000000000000000000000000000000"));
|
|
297
|
+
this.validateBridgeParams({ fromChain, toChain, amount, recipient: recipientAddr }, "quoteBridge");
|
|
298
|
+
const { src, dst } = this.resolveChains(fromChain, toChain);
|
|
299
|
+
const oftAdapter = src.oftAdapter;
|
|
300
|
+
const amountLD = _viem.parseUnits.call(void 0, String(amount), src.decimals);
|
|
301
|
+
try {
|
|
302
|
+
const { nativeFee, lzTokenFee } = await _viem.createPublicClient.call(void 0, {
|
|
303
|
+
chain: makeChain(src.id, src.rpc),
|
|
304
|
+
transport: _viem.http.call(void 0, src.rpc)
|
|
305
|
+
}).readContract({
|
|
306
|
+
address: oftAdapter,
|
|
307
|
+
abi: OFT_ADAPTER_ABI,
|
|
308
|
+
functionName: "quoteSend",
|
|
309
|
+
args: [this.buildSendParams(dst, amountLD, recipientAddr), false]
|
|
310
|
+
});
|
|
311
|
+
return { nativeFee, lzTokenFee, toAmount: amount };
|
|
312
|
+
} catch (err) {
|
|
313
|
+
throw new StableQuoteError({
|
|
314
|
+
provider: "layerzero",
|
|
315
|
+
shortMessage: "quoteBridge: quoteSend call failed",
|
|
316
|
+
cause: err
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async bridge({ fromChain, toChain, fromToken, toToken, amount, recipient, quote }) {
|
|
321
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _2 => _2.config, 'access', _3 => _3.walletClient, 'optionalAccess', _4 => _4.account, 'optionalAccess', _5 => _5.address]), () => ( _optionalChain([this, 'access', _6 => _6.config, 'access', _7 => _7.account, 'optionalAccess', _8 => _8.address])));
|
|
322
|
+
if (!from) {
|
|
323
|
+
throw new StableValidationError(
|
|
324
|
+
"signer",
|
|
325
|
+
void 0,
|
|
326
|
+
"bridge: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const recipientAddr = _nullishCoalesce(recipient, () => ( from));
|
|
330
|
+
this.validateBridgeParams({ fromChain, toChain, amount, recipient: recipientAddr }, "bridge");
|
|
331
|
+
const { src, dst } = this.resolveChains(fromChain, toChain);
|
|
332
|
+
const oftAdapter = src.oftAdapter;
|
|
333
|
+
const srcChain = makeChain(src.id, src.rpc);
|
|
334
|
+
const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
335
|
+
account: this.config.account,
|
|
336
|
+
chain: srcChain,
|
|
337
|
+
transport: _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, src.rpc)))
|
|
338
|
+
})));
|
|
339
|
+
const pc = _viem.createPublicClient.call(void 0, { chain: srcChain, transport: _viem.http.call(void 0, src.rpc) });
|
|
340
|
+
try {
|
|
341
|
+
await wc.switchChain({ id: src.id });
|
|
342
|
+
} catch (err) {
|
|
343
|
+
throw new StableTransactionError({
|
|
344
|
+
phase: "switch_chain",
|
|
345
|
+
shortMessage: `bridge: wallet rejected or failed to switch to chain ${src.id}`,
|
|
346
|
+
chainId: src.id,
|
|
347
|
+
cause: err
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const amountLD = _viem.parseUnits.call(void 0, String(amount), src.decimals);
|
|
351
|
+
const sendParams = this.buildSendParams(dst, amountLD, recipientAddr);
|
|
352
|
+
const lzFee = _optionalChain([quote, 'optionalAccess', _9 => _9.nativeFee]) !== void 0 && _optionalChain([quote, 'optionalAccess', _10 => _10.lzTokenFee]) !== void 0 ? { nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee } : await this.quoteBridge({ fromChain, toChain, fromToken, toToken, amount, recipient: recipientAddr });
|
|
353
|
+
const { nativeFee = 0n, lzTokenFee = 0n } = lzFee;
|
|
354
|
+
const allowance = await pc.readContract({
|
|
355
|
+
address: src.usdt,
|
|
356
|
+
abi: _viem.erc20Abi,
|
|
357
|
+
functionName: "allowance",
|
|
358
|
+
args: [from, oftAdapter]
|
|
359
|
+
});
|
|
360
|
+
if (allowance < amountLD) {
|
|
361
|
+
let approveHash;
|
|
362
|
+
try {
|
|
363
|
+
approveHash = await wc.writeContract({
|
|
364
|
+
address: src.usdt,
|
|
365
|
+
abi: _viem.erc20Abi,
|
|
366
|
+
functionName: "approve",
|
|
367
|
+
args: [oftAdapter, amountLD],
|
|
368
|
+
account: wc.account,
|
|
369
|
+
chain: srcChain
|
|
370
|
+
});
|
|
371
|
+
} catch (err) {
|
|
372
|
+
throw new StableTransactionError({
|
|
373
|
+
phase: "approve",
|
|
374
|
+
shortMessage: "bridge: USDT0 approve failed",
|
|
375
|
+
chainId: src.id,
|
|
376
|
+
cause: err
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const approveReceipt = await pc.waitForTransactionReceipt({ hash: approveHash });
|
|
380
|
+
if (approveReceipt.status === "reverted") {
|
|
381
|
+
throw new StableTransactionError({
|
|
382
|
+
phase: "approve",
|
|
383
|
+
shortMessage: "bridge: USDT0 approve reverted on-chain",
|
|
384
|
+
txHash: approveReceipt.transactionHash,
|
|
385
|
+
chainId: src.id
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const hash = await wc.writeContract({
|
|
391
|
+
address: oftAdapter,
|
|
392
|
+
abi: OFT_ADAPTER_ABI,
|
|
393
|
+
functionName: "send",
|
|
394
|
+
args: [sendParams, { nativeFee, lzTokenFee }, from],
|
|
395
|
+
value: nativeFee,
|
|
396
|
+
account: wc.account,
|
|
397
|
+
chain: srcChain
|
|
398
|
+
});
|
|
399
|
+
return { txHash: hash, toAmount: amount };
|
|
400
|
+
} catch (err) {
|
|
401
|
+
throw new StableTransactionError({
|
|
402
|
+
phase: "send",
|
|
403
|
+
shortMessage: "bridge: OFT send failed",
|
|
404
|
+
chainId: src.id,
|
|
405
|
+
cause: err
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
validateBridgeParams(params, method) {
|
|
410
|
+
if (!_viem.isAddress.call(void 0, params.recipient)) {
|
|
411
|
+
throw new StableValidationError(
|
|
412
|
+
"recipient",
|
|
413
|
+
params.recipient,
|
|
414
|
+
`${method}: invalid recipient address "${params.recipient}"`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
if (!Number.isFinite(params.amount) || params.amount <= 0) {
|
|
418
|
+
throw new StableValidationError(
|
|
419
|
+
"amount",
|
|
420
|
+
params.amount,
|
|
421
|
+
`${method}: amount must be a finite positive number`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (params.fromChain === params.toChain) {
|
|
425
|
+
throw new StableValidationError(
|
|
426
|
+
"toChain",
|
|
427
|
+
params.toChain,
|
|
428
|
+
`${method}: fromChain and toChain must differ`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
resolveChains(fromChain, toChain) {
|
|
433
|
+
const src = BRIDGE_CHAIN_CONFIGS[fromChain];
|
|
434
|
+
const dst = BRIDGE_CHAIN_CONFIGS[toChain];
|
|
435
|
+
if (!src) throw new StableValidationError("fromChain", fromChain, `Unsupported source chain: "${fromChain}"`);
|
|
436
|
+
if (!dst) throw new StableValidationError("toChain", toChain, `Unsupported destination chain: "${toChain}"`);
|
|
437
|
+
if (!src.oftAdapter) {
|
|
438
|
+
throw new StableValidationError("fromChain", fromChain, `No OFT adapter configured for "${fromChain}"`);
|
|
439
|
+
}
|
|
440
|
+
return { src, dst };
|
|
441
|
+
}
|
|
442
|
+
buildSendParams(dst, amountLD, recipient) {
|
|
443
|
+
return {
|
|
444
|
+
dstEid: dst.lzEid,
|
|
445
|
+
to: _viem.padHex.call(void 0, recipient, { dir: "left", size: 32 }),
|
|
446
|
+
amountLD,
|
|
447
|
+
minAmountLD: amountLD,
|
|
448
|
+
extraOptions: buildLzOptions(),
|
|
449
|
+
composeMsg: "0x",
|
|
450
|
+
oftCmd: "0x"
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// src/services/lifi.ts
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
// src/helpers/retry.ts
|
|
467
|
+
var HttpError = class extends Error {
|
|
468
|
+
constructor(message, status, retryable) {
|
|
469
|
+
super(message);
|
|
470
|
+
this.status = status;
|
|
471
|
+
this.retryable = retryable;
|
|
472
|
+
this.name = "HttpError";
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
function isRetryableError(err) {
|
|
476
|
+
if (err instanceof HttpError) return err.retryable;
|
|
477
|
+
return err instanceof TypeError;
|
|
478
|
+
}
|
|
479
|
+
async function retryWithBackoff(fn, isRetryable = isRetryableError, opts = {}) {
|
|
480
|
+
const maxAttempts = _nullishCoalesce(opts.maxAttempts, () => ( 3));
|
|
481
|
+
const base = _nullishCoalesce(opts.baseDelayMs, () => ( 250));
|
|
482
|
+
const max = _nullishCoalesce(opts.maxDelayMs, () => ( 4e3));
|
|
483
|
+
let lastErr;
|
|
484
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
485
|
+
try {
|
|
486
|
+
return await fn();
|
|
487
|
+
} catch (err) {
|
|
488
|
+
lastErr = err;
|
|
489
|
+
if (attempt === maxAttempts - 1 || !isRetryable(err)) throw err;
|
|
490
|
+
const delay = Math.min(max, base * 2 ** attempt);
|
|
491
|
+
const jittered = delay * (0.75 + Math.random() * 0.5);
|
|
492
|
+
await new Promise((r) => setTimeout(r, jittered));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
throw lastErr;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/services/lifi.ts
|
|
499
|
+
var LiFiService = class {
|
|
500
|
+
constructor(config) {
|
|
501
|
+
this.config = config;
|
|
502
|
+
}
|
|
503
|
+
async quoteSwap(params) {
|
|
504
|
+
this._validateSwapParams(params);
|
|
505
|
+
const { fromToken, toToken, amount, fromDecimals = 6, toAddress } = params;
|
|
506
|
+
const from = this._requireFrom();
|
|
507
|
+
const chain = this.config.chainId.toString();
|
|
508
|
+
const fromAmountRaw = _viem.parseUnits.call(void 0, String(amount), fromDecimals).toString();
|
|
509
|
+
const qs = new URLSearchParams({
|
|
510
|
+
fromChain: chain,
|
|
511
|
+
toChain: chain,
|
|
512
|
+
fromToken,
|
|
513
|
+
toToken,
|
|
514
|
+
fromAmount: fromAmountRaw,
|
|
515
|
+
fromAddress: from,
|
|
516
|
+
...toAddress && { toAddress }
|
|
517
|
+
});
|
|
518
|
+
const quote = await this._fetchQuote(qs);
|
|
519
|
+
return this._toSwapQuote(quote);
|
|
520
|
+
}
|
|
521
|
+
async swap(params) {
|
|
522
|
+
this._validateSwapParams(params);
|
|
523
|
+
const quote = await _asyncNullishCoalesce(params.quote, async () => ( await this.quoteSwap(params)));
|
|
524
|
+
return this._executeSwapQuote(quote, this.config.rpc, this.config.chainId);
|
|
525
|
+
}
|
|
526
|
+
async quoteBridge(params) {
|
|
527
|
+
this._validateBridgeParams(params);
|
|
528
|
+
const { fromChain, toChain, fromToken, toToken, amount, fromDecimals = 6, recipient } = params;
|
|
529
|
+
const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access', _11 => _11.config, 'access', _12 => _12.walletClient, 'optionalAccess', _13 => _13.account, 'optionalAccess', _14 => _14.address]), () => ( _optionalChain([this, 'access', _15 => _15.config, 'access', _16 => _16.account, 'optionalAccess', _17 => _17.address]))), () => ( "0x0000000000000000000000000000000000000000"));
|
|
530
|
+
const src = CHAIN_CONFIGS[fromChain];
|
|
531
|
+
const dst = CHAIN_CONFIGS[toChain];
|
|
532
|
+
if (!src) throw new StableValidationError("fromChain", fromChain, `LI.FI bridge: unsupported source chain "${fromChain}"`);
|
|
533
|
+
if (!dst) throw new StableValidationError("toChain", toChain, `LI.FI bridge: unsupported destination chain "${toChain}"`);
|
|
534
|
+
const qs = new URLSearchParams({
|
|
535
|
+
fromChain: src.id.toString(),
|
|
536
|
+
toChain: dst.id.toString(),
|
|
537
|
+
fromToken,
|
|
538
|
+
toToken,
|
|
539
|
+
fromAmount: _viem.parseUnits.call(void 0, String(amount), fromDecimals).toString(),
|
|
540
|
+
fromAddress: from,
|
|
541
|
+
...recipient && { toAddress: recipient }
|
|
542
|
+
});
|
|
543
|
+
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
544
|
+
return { toAmount: quote.toAmount };
|
|
545
|
+
}
|
|
546
|
+
async bridge(params) {
|
|
547
|
+
this._validateBridgeParams(params);
|
|
548
|
+
const { fromChain, toChain, fromToken, toToken, amount, fromDecimals = 6, recipient } = params;
|
|
549
|
+
const from = this._requireFrom();
|
|
550
|
+
const src = CHAIN_CONFIGS[fromChain];
|
|
551
|
+
const dst = CHAIN_CONFIGS[toChain];
|
|
552
|
+
if (!src) {
|
|
553
|
+
throw new StableValidationError("fromChain", fromChain, `LI.FI bridge: unsupported source chain "${fromChain}"`);
|
|
554
|
+
}
|
|
555
|
+
if (!dst) {
|
|
556
|
+
throw new StableValidationError("toChain", toChain, `LI.FI bridge: unsupported destination chain "${toChain}"`);
|
|
557
|
+
}
|
|
558
|
+
const fromAmountRaw = _viem.parseUnits.call(void 0, String(amount), fromDecimals).toString();
|
|
559
|
+
const qs = new URLSearchParams({
|
|
560
|
+
fromChain: src.id.toString(),
|
|
561
|
+
toChain: dst.id.toString(),
|
|
562
|
+
fromToken,
|
|
563
|
+
toToken,
|
|
564
|
+
fromAmount: fromAmountRaw,
|
|
565
|
+
fromAddress: from,
|
|
566
|
+
...recipient && { toAddress: recipient }
|
|
567
|
+
});
|
|
568
|
+
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
569
|
+
return this._executeSwapQuote(quote, src.rpc, src.id);
|
|
570
|
+
}
|
|
571
|
+
_validateSwapParams({ fromToken, toToken, amount, toAddress }) {
|
|
572
|
+
if (!_viem.isAddress.call(void 0, fromToken)) {
|
|
573
|
+
throw new StableValidationError("fromToken", fromToken, `swap: invalid fromToken address "${fromToken}"`);
|
|
574
|
+
}
|
|
575
|
+
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
576
|
+
throw new StableValidationError("toToken", toToken, `swap: invalid toToken address "${toToken}"`);
|
|
577
|
+
}
|
|
578
|
+
if (fromToken.toLowerCase() === toToken.toLowerCase()) {
|
|
579
|
+
throw new StableValidationError("toToken", toToken, "swap: fromToken and toToken must differ");
|
|
580
|
+
}
|
|
581
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
582
|
+
throw new StableValidationError("amount", amount, "swap: amount must be a finite positive number");
|
|
583
|
+
}
|
|
584
|
+
if (toAddress !== void 0 && !_viem.isAddress.call(void 0, toAddress)) {
|
|
585
|
+
throw new StableValidationError("toAddress", toAddress, `swap: invalid toAddress "${toAddress}"`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
_validateBridgeParams({ fromChain, toChain, fromToken, toToken, amount, recipient }) {
|
|
589
|
+
if (!_viem.isAddress.call(void 0, fromToken)) {
|
|
590
|
+
throw new StableValidationError("fromToken", fromToken, `LI.FI bridge: invalid fromToken address "${fromToken}"`);
|
|
591
|
+
}
|
|
592
|
+
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
593
|
+
throw new StableValidationError("toToken", toToken, `LI.FI bridge: invalid toToken address "${toToken}"`);
|
|
594
|
+
}
|
|
595
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
596
|
+
throw new StableValidationError("amount", amount, "LI.FI bridge: amount must be a finite positive number");
|
|
597
|
+
}
|
|
598
|
+
if (recipient !== void 0 && !_viem.isAddress.call(void 0, recipient)) {
|
|
599
|
+
throw new StableValidationError("recipient", recipient, `LI.FI bridge: invalid recipient address "${recipient}"`);
|
|
600
|
+
}
|
|
601
|
+
if (fromChain === toChain) {
|
|
602
|
+
throw new StableValidationError("toChain", toChain, "LI.FI bridge: fromChain and toChain must differ");
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
_requireFrom() {
|
|
606
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _18 => _18.config, 'access', _19 => _19.walletClient, 'optionalAccess', _20 => _20.account, 'optionalAccess', _21 => _21.address]), () => ( _optionalChain([this, 'access', _22 => _22.config, 'access', _23 => _23.account, 'optionalAccess', _24 => _24.address])));
|
|
607
|
+
if (!from) {
|
|
608
|
+
throw new StableValidationError(
|
|
609
|
+
"signer",
|
|
610
|
+
void 0,
|
|
611
|
+
"LI.FI: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
return from;
|
|
615
|
+
}
|
|
616
|
+
async _fetchQuote(qs) {
|
|
617
|
+
const url = `https://li.quest/v1/quote?${qs}`;
|
|
618
|
+
try {
|
|
619
|
+
return await retryWithBackoff(() => this._fetchQuoteOnce(url));
|
|
620
|
+
} catch (err) {
|
|
621
|
+
if (err instanceof StableQuoteError) throw err;
|
|
622
|
+
if (err instanceof HttpError) {
|
|
623
|
+
throw new StableQuoteError({
|
|
624
|
+
provider: "lifi",
|
|
625
|
+
shortMessage: err.message,
|
|
626
|
+
httpStatus: err.status,
|
|
627
|
+
cause: err
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
throw new StableNetworkError({
|
|
631
|
+
shortMessage: "LI.FI: quote request failed before reaching the server",
|
|
632
|
+
url,
|
|
633
|
+
cause: err
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async _fetchQuoteOnce(url) {
|
|
638
|
+
const res = await fetch(url);
|
|
639
|
+
const body = await res.json().catch(() => void 0);
|
|
640
|
+
if (!res.ok) {
|
|
641
|
+
const providerMessage = isObject(body) && typeof body.message === "string" ? body.message : void 0;
|
|
642
|
+
const providerCode = isObject(body) && typeof body.code === "string" ? body.code : void 0;
|
|
643
|
+
if (res.status === 429 || res.status >= 500) {
|
|
644
|
+
throw new HttpError(`LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`, res.status, true);
|
|
645
|
+
}
|
|
646
|
+
throw new StableQuoteError({
|
|
647
|
+
provider: "lifi",
|
|
648
|
+
shortMessage: `LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`,
|
|
649
|
+
httpStatus: res.status,
|
|
650
|
+
providerCode,
|
|
651
|
+
body
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
if (!isLiFiQuoteShape(body)) {
|
|
655
|
+
throw new StableQuoteError({
|
|
656
|
+
provider: "lifi",
|
|
657
|
+
shortMessage: "LI.FI: unexpected quote response shape",
|
|
658
|
+
httpStatus: res.status,
|
|
659
|
+
body
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
return body;
|
|
663
|
+
}
|
|
664
|
+
_toSwapQuote(quote) {
|
|
665
|
+
return {
|
|
666
|
+
toAmount: Number(_viem.formatUnits.call(void 0, BigInt(quote.estimate.toAmount), quote.action.toToken.decimals)),
|
|
667
|
+
fromAmount: BigInt(quote.estimate.fromAmount),
|
|
668
|
+
fromToken: quote.action.fromToken.address,
|
|
669
|
+
approvalAddress: quote.estimate.approvalAddress,
|
|
670
|
+
transactionRequest: quote.transactionRequest
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
async _executeSwapQuote(quote, srcRpc, expectedChainId) {
|
|
674
|
+
const from = this._requireFrom();
|
|
675
|
+
const { transactionRequest } = quote;
|
|
676
|
+
const srcChainId = transactionRequest.chainId;
|
|
677
|
+
if (srcChainId !== expectedChainId) {
|
|
678
|
+
throw new StableQuoteError({
|
|
679
|
+
provider: "lifi",
|
|
680
|
+
shortMessage: `LI.FI: returned tx for chain ${srcChainId} but expected ${expectedChainId}`,
|
|
681
|
+
providerCode: "chain_mismatch",
|
|
682
|
+
body: quote
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
const srcChain = makeChain(srcChainId, srcRpc);
|
|
686
|
+
const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
687
|
+
account: this.config.account,
|
|
688
|
+
chain: srcChain,
|
|
689
|
+
transport: _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, srcRpc)))
|
|
690
|
+
})));
|
|
691
|
+
try {
|
|
692
|
+
await wc.switchChain({ id: srcChainId });
|
|
693
|
+
} catch (err) {
|
|
694
|
+
throw new StableTransactionError({
|
|
695
|
+
phase: "switch_chain",
|
|
696
|
+
shortMessage: `LI.FI: wallet rejected or failed to switch to chain ${srcChainId}`,
|
|
697
|
+
chainId: srcChainId,
|
|
698
|
+
cause: err
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
await this._ensureApproval(wc, srcChain, srcRpc, srcChainId, from, quote);
|
|
702
|
+
let hash;
|
|
703
|
+
try {
|
|
704
|
+
hash = await wc.sendTransaction({
|
|
705
|
+
to: transactionRequest.to,
|
|
706
|
+
data: transactionRequest.data,
|
|
707
|
+
value: transactionRequest.value && transactionRequest.value !== "0x" ? BigInt(transactionRequest.value) : 0n,
|
|
708
|
+
gas: transactionRequest.gasLimit ? BigInt(transactionRequest.gasLimit) : void 0,
|
|
709
|
+
account: wc.account,
|
|
710
|
+
chain: srcChain
|
|
711
|
+
});
|
|
712
|
+
} catch (err) {
|
|
713
|
+
throw new StableTransactionError({
|
|
714
|
+
phase: "send",
|
|
715
|
+
shortMessage: "LI.FI: swap/bridge transaction failed",
|
|
716
|
+
chainId: srcChainId,
|
|
717
|
+
cause: err
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return { txHash: hash, toAmount: quote.toAmount };
|
|
721
|
+
}
|
|
722
|
+
async _ensureApproval(wc, srcChain, srcRpc, srcChainId, from, quote) {
|
|
723
|
+
const tokenAddr = quote.fromToken;
|
|
724
|
+
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000" || tokenAddr.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
725
|
+
if (isNative || !quote.approvalAddress) return;
|
|
726
|
+
const pc = _viem.createPublicClient.call(void 0, { chain: srcChain, transport: _viem.http.call(void 0, srcRpc) });
|
|
727
|
+
const allowance = await pc.readContract({
|
|
728
|
+
address: tokenAddr,
|
|
729
|
+
abi: _viem.erc20Abi,
|
|
730
|
+
functionName: "allowance",
|
|
731
|
+
args: [from, quote.approvalAddress]
|
|
732
|
+
});
|
|
733
|
+
if (allowance >= quote.fromAmount) return;
|
|
734
|
+
let approveHash;
|
|
735
|
+
try {
|
|
736
|
+
approveHash = await wc.writeContract({
|
|
737
|
+
address: tokenAddr,
|
|
738
|
+
abi: _viem.erc20Abi,
|
|
739
|
+
functionName: "approve",
|
|
740
|
+
args: [quote.approvalAddress, quote.fromAmount],
|
|
741
|
+
account: wc.account,
|
|
742
|
+
chain: srcChain
|
|
743
|
+
});
|
|
744
|
+
} catch (err) {
|
|
745
|
+
throw new StableTransactionError({
|
|
746
|
+
phase: "approve",
|
|
747
|
+
shortMessage: "LI.FI: token approve failed",
|
|
748
|
+
chainId: srcChainId,
|
|
749
|
+
cause: err
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
const approveReceipt = await pc.waitForTransactionReceipt({ hash: approveHash });
|
|
753
|
+
if (approveReceipt.status === "reverted") {
|
|
754
|
+
throw new StableTransactionError({
|
|
755
|
+
phase: "approve",
|
|
756
|
+
shortMessage: "LI.FI: token approve reverted on-chain",
|
|
757
|
+
txHash: approveReceipt.transactionHash,
|
|
758
|
+
chainId: srcChainId
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
function isObject(v) {
|
|
764
|
+
return typeof v === "object" && v !== null;
|
|
765
|
+
}
|
|
766
|
+
function isLiFiQuoteShape(v) {
|
|
767
|
+
if (!isObject(v)) return false;
|
|
768
|
+
const tx = v.transactionRequest;
|
|
769
|
+
const est = v.estimate;
|
|
770
|
+
const act = v.action;
|
|
771
|
+
return isObject(tx) && typeof tx.to === "string" && typeof tx.data === "string" && typeof tx.chainId === "number" && isObject(est) && typeof est.fromAmount === "string" && typeof est.toAmount === "string" && isObject(act) && isObject(act.fromToken) && isObject(act.toToken);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/stable.ts
|
|
775
|
+
function isUsdt0Route(fromChain, toChain, fromToken, toToken) {
|
|
776
|
+
const src = BRIDGE_CHAIN_CONFIGS[fromChain];
|
|
777
|
+
const dst = BRIDGE_CHAIN_CONFIGS[toChain];
|
|
778
|
+
return !!src && !!dst && src.lzOutbound !== false && src.usdt.toLowerCase() === fromToken.toLowerCase() && dst.usdt.toLowerCase() === toToken.toLowerCase();
|
|
779
|
+
}
|
|
780
|
+
function validateTransferParams({ to, amount, token, tokenDecimals }) {
|
|
781
|
+
if (!_viem.isAddress.call(void 0, to)) {
|
|
782
|
+
throw new StableValidationError("to", to, `transfer: invalid recipient address "${to}"`);
|
|
783
|
+
}
|
|
784
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
785
|
+
throw new StableValidationError("amount", amount, "transfer: amount must be a finite positive number");
|
|
786
|
+
}
|
|
787
|
+
if (token !== void 0 && !_viem.isAddress.call(void 0, token)) {
|
|
788
|
+
throw new StableValidationError("token", token, `transfer: invalid token address "${token}"`);
|
|
789
|
+
}
|
|
790
|
+
if (tokenDecimals !== void 0 && (!Number.isInteger(tokenDecimals) || tokenDecimals < 0 || tokenDecimals > 77)) {
|
|
791
|
+
throw new StableValidationError("tokenDecimals", tokenDecimals, "transfer: tokenDecimals must be an integer between 0 and 77");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
|
|
795
|
+
if (tokenDecimals !== void 0) return tokenDecimals;
|
|
796
|
+
try {
|
|
797
|
+
return await publicClient.readContract({ address: token, abi: _viem.erc20Abi, functionName: "decimals" });
|
|
798
|
+
} catch (e2) {
|
|
799
|
+
throw new StableValidationError(
|
|
800
|
+
"tokenDecimals",
|
|
801
|
+
void 0,
|
|
802
|
+
`transfer: could not fetch decimals for token "${token}" \u2014 pass tokenDecimals explicitly`
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
async function executeTokenTransfer(wc, publicClient, token, to, amount, tokenDecimals, acc, chain) {
|
|
807
|
+
const code = await publicClient.getCode({ address: token });
|
|
808
|
+
if (!code || code === "0x") {
|
|
809
|
+
throw new StableValidationError(
|
|
810
|
+
"token",
|
|
811
|
+
token,
|
|
812
|
+
`transfer: no contract found at "${token}" on chain ${chain.id} \u2014 check the address and network`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
const decimals = await resolveTokenDecimals(publicClient, token, tokenDecimals);
|
|
816
|
+
return wc.writeContract({
|
|
817
|
+
address: token,
|
|
818
|
+
abi: _viem.erc20Abi,
|
|
819
|
+
functionName: "transfer",
|
|
820
|
+
args: [to, _viem.parseUnits.call(void 0, String(amount), decimals)],
|
|
821
|
+
account: acc,
|
|
822
|
+
chain
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
function createStable({
|
|
826
|
+
network = "mainnet" /* Mainnet */,
|
|
827
|
+
rpc: rpcOverride,
|
|
828
|
+
account,
|
|
829
|
+
transport,
|
|
830
|
+
walletClient
|
|
831
|
+
}) {
|
|
832
|
+
const chain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
|
|
833
|
+
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(/\/$/, "");
|
|
834
|
+
const signerConfig = { account, transport, walletClient };
|
|
835
|
+
const lz = new LayerZeroService(signerConfig);
|
|
836
|
+
const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
|
|
837
|
+
async function transfer(params) {
|
|
838
|
+
validateTransferParams(params);
|
|
839
|
+
const { from, to, amount, token, tokenDecimals } = params;
|
|
840
|
+
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _25 => _25.account]), () => ( account)), () => ( from));
|
|
841
|
+
const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
842
|
+
account: acc,
|
|
843
|
+
chain,
|
|
844
|
+
transport: _nullishCoalesce(transport, () => ( _viem.http.call(void 0, rpc)))
|
|
845
|
+
})));
|
|
846
|
+
try {
|
|
847
|
+
await wc.switchChain({ id: chain.id });
|
|
848
|
+
} catch (err) {
|
|
849
|
+
throw new StableTransactionError({
|
|
850
|
+
phase: "switch_chain",
|
|
851
|
+
shortMessage: `transfer: wallet rejected or failed to switch to chain ${chain.id}`,
|
|
852
|
+
chainId: chain.id,
|
|
853
|
+
cause: err
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
const publicClient = _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0, rpc) });
|
|
857
|
+
const hash = token !== void 0 ? await executeTokenTransfer(wc, publicClient, token, to, amount, tokenDecimals, acc, chain) : await wc.sendTransaction({ account: acc, chain, to, value: _viem.parseUnits.call(void 0, String(amount), 18) });
|
|
858
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
859
|
+
if (receipt.status === "reverted") {
|
|
860
|
+
throw new StableTransactionError({
|
|
861
|
+
phase: "receipt",
|
|
862
|
+
shortMessage: "transfer: transaction reverted on-chain",
|
|
863
|
+
txHash: receipt.transactionHash,
|
|
864
|
+
chainId: chain.id
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
return { txHash: receipt.transactionHash };
|
|
868
|
+
}
|
|
869
|
+
function quoteBridge(params) {
|
|
870
|
+
return isUsdt0Route(params.fromChain, params.toChain, params.fromToken, params.toToken) ? lz.quoteBridge(params) : lifi.quoteBridge(params);
|
|
871
|
+
}
|
|
872
|
+
function bridge(params) {
|
|
873
|
+
return isUsdt0Route(params.fromChain, params.toChain, params.fromToken, params.toToken) ? lz.bridge(params) : lifi.bridge(params);
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
transfer,
|
|
877
|
+
quoteBridge,
|
|
878
|
+
bridge,
|
|
879
|
+
quoteSwap: (params) => lifi.quoteSwap(params),
|
|
880
|
+
swap: (params) => lifi.swap(params)
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
exports.CHAIN_CONFIGS = CHAIN_CONFIGS; exports.Chain = Chain; exports.Network = Network; exports.StableError = StableError; exports.StableNetworkError = StableNetworkError; exports.StableQuoteError = StableQuoteError; exports.StableTransactionError = StableTransactionError; exports.StableValidationError = StableValidationError; exports.createStable = createStable;
|