@stablechain/sdk 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -4
- package/dist/index.cjs +277 -78
- package/dist/index.d.cts +288 -4
- package/dist/index.d.ts +288 -4
- package/dist/index.js +257 -58
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1738,6 +1738,7 @@ function isLiFiQuoteShape(v) {
|
|
|
1738
1738
|
|
|
1739
1739
|
|
|
1740
1740
|
|
|
1741
|
+
|
|
1741
1742
|
var C = CHAIN_CONFIGS;
|
|
1742
1743
|
var XAUT0_DEPLOYMENTS = {
|
|
1743
1744
|
["ethereum" /* Ethereum */]: { address: "0x68749665FF8D2d112Fa859AA293F07A622782F38", decimals: 6, oftAdapter: "0xb9c2321BB7D0Db468f570D10A424d1Cc8EFd696C", approvalRequired: true },
|
|
@@ -1749,11 +1750,17 @@ var XAUT0_DEPLOYMENTS = {
|
|
|
1749
1750
|
["plasma" /* Plasma */]: { address: "0x1B64B9025EEbb9A6239575dF9Ea4b9Ac46D4d193", decimals: 6, oftAdapter: "0x63aB93cBC9d4ecD9c4947b1A38F458147C08E6F7", approvalRequired: false },
|
|
1750
1751
|
["bsc" /* BSC */]: { address: "0x21cAef8A43163Eea865baeE23b9C2E327696A3bf", decimals: 6, oftAdapter: "0x53C3A64c8942288e12813C1F8457Db45980BcfC2", approvalRequired: false }
|
|
1751
1752
|
};
|
|
1753
|
+
var USDT0_LOCKBOX_CHAINS = /* @__PURE__ */ new Set(["ethereum" /* Ethereum */, "sepolia" /* Sepolia */]);
|
|
1752
1754
|
function bridgeChain(chain, lzEid, blockTimeSec, usdt0Adapter) {
|
|
1753
1755
|
const base = C[chain];
|
|
1754
1756
|
const tokens = {};
|
|
1755
1757
|
if (usdt0Adapter) {
|
|
1756
|
-
tokens.usdt0 = {
|
|
1758
|
+
tokens.usdt0 = {
|
|
1759
|
+
address: base.usdt,
|
|
1760
|
+
decimals: base.decimals,
|
|
1761
|
+
oftAdapter: usdt0Adapter,
|
|
1762
|
+
approvalRequired: USDT0_LOCKBOX_CHAINS.has(chain)
|
|
1763
|
+
};
|
|
1757
1764
|
}
|
|
1758
1765
|
const xaut0 = XAUT0_DEPLOYMENTS[chain];
|
|
1759
1766
|
if (xaut0) tokens.xaut0 = xaut0;
|
|
@@ -1998,6 +2005,22 @@ function buildLzOptions(gasLimit = 0n, nativeDrop = 0n, dropReceiver) {
|
|
|
1998
2005
|
]);
|
|
1999
2006
|
}
|
|
2000
2007
|
var FALLBACK_SEND_GAS = 400000n;
|
|
2008
|
+
var DEFAULT_FEE_BUFFER_BPS = 500;
|
|
2009
|
+
var MAX_FEE_BUFFER_BPS = 1e4;
|
|
2010
|
+
function padFee(nativeFee, bufferBps) {
|
|
2011
|
+
return nativeFee * BigInt(1e4 + bufferBps) / 10000n;
|
|
2012
|
+
}
|
|
2013
|
+
function resolveFeeBuffer(feeBufferBps, method) {
|
|
2014
|
+
if (feeBufferBps === void 0) return DEFAULT_FEE_BUFFER_BPS;
|
|
2015
|
+
if (!Number.isInteger(feeBufferBps) || feeBufferBps < 0 || feeBufferBps > MAX_FEE_BUFFER_BPS) {
|
|
2016
|
+
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
|
|
2017
|
+
"feeBufferBps",
|
|
2018
|
+
feeBufferBps,
|
|
2019
|
+
`${method}: feeBufferBps must be an integer between 0 and ${MAX_FEE_BUFFER_BPS}`
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
return feeBufferBps;
|
|
2023
|
+
}
|
|
2001
2024
|
function usableQuote(quote, amountLD) {
|
|
2002
2025
|
if (!_optionalChain([quote, 'optionalAccess', _41 => _41.oft])) return quote;
|
|
2003
2026
|
return quote.oft.amountSentLD <= amountLD ? quote : void 0;
|
|
@@ -2006,20 +2029,46 @@ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
|
2006
2029
|
var LayerZeroService = class {
|
|
2007
2030
|
constructor(config) {
|
|
2008
2031
|
this.config = config;
|
|
2032
|
+
this.rpcById = new Map(
|
|
2033
|
+
Object.entries(_nullishCoalesce(config.rpcOverrides, () => ( {}))).flatMap(([chain, rpc]) => {
|
|
2034
|
+
const id = _optionalChain([CHAIN_CONFIGS, 'access', _42 => _42[chain], 'optionalAccess', _43 => _43.id]);
|
|
2035
|
+
return id === void 0 || !rpc ? [] : [[id, rpc.replace(/\/$/, "")]];
|
|
2036
|
+
})
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* The RPC to read and broadcast on for a chain: the caller's override when they
|
|
2041
|
+
* gave one, else the built-in public endpoint. Every bridge read goes through
|
|
2042
|
+
* here — a backend quoting on a loop needs an endpoint it controls.
|
|
2043
|
+
*/
|
|
2044
|
+
rpcFor(cfg) {
|
|
2045
|
+
return _nullishCoalesce(this.rpcById.get(cfg.id), () => ( cfg.rpc));
|
|
2009
2046
|
}
|
|
2010
2047
|
async quoteBridge(params) {
|
|
2011
|
-
|
|
2048
|
+
return this.buildQuote(this.plan(params, "quoteBridge"), params, "quoteBridge");
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* The full quote for a planned send. `quoteBridge` reports one; the unsigned
|
|
2052
|
+
* build needs the same numbers and must not disagree with what a caller was
|
|
2053
|
+
* shown, so both come from here.
|
|
2054
|
+
*
|
|
2055
|
+
* `account` is who the gas simulation runs as — this service's signer when it
|
|
2056
|
+
* has one, and the address a prepared send will be signed by when it does not.
|
|
2057
|
+
*/
|
|
2058
|
+
async buildQuote(plan, params, method, opts = {}) {
|
|
2012
2059
|
const [{ fee, oft }, estimate] = await Promise.all([
|
|
2013
|
-
this.quotePathway(plan,
|
|
2060
|
+
this.quotePathway(plan, method, opts.requireOft),
|
|
2014
2061
|
this.estimateBridgeTime(params).catch(() => void 0)
|
|
2015
2062
|
]);
|
|
2016
|
-
const srcGas = await this.estimateSendGas(plan, fee, oft);
|
|
2063
|
+
const srcGas = await this.estimateSendGas(plan, fee, oft, opts.account);
|
|
2017
2064
|
return {
|
|
2018
2065
|
...fee,
|
|
2066
|
+
// What a send built from this quote will actually carry as its `value`.
|
|
2067
|
+
nativeFeeMax: padFee(fee.nativeFee, plan.feeBufferBps),
|
|
2019
2068
|
oft,
|
|
2020
2069
|
// What the recipient is actually credited: input minus any OFT fee and
|
|
2021
2070
|
// minus dust. Falls back to the input when quoteOFT could not be read.
|
|
2022
|
-
toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess',
|
|
2071
|
+
toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _44 => _44.amountReceived]), () => ( params.amount)),
|
|
2023
2072
|
srcGas,
|
|
2024
2073
|
totalCostWei: srcGas ? srcGas.costWei + fee.nativeFee : void 0,
|
|
2025
2074
|
estimate
|
|
@@ -2029,10 +2078,15 @@ var LayerZeroService = class {
|
|
|
2029
2078
|
* The LayerZero messaging fee for a send, plus the OFT's own accounting.
|
|
2030
2079
|
*
|
|
2031
2080
|
* The fee is required — a send cannot be built without it. The OFT accounting is
|
|
2032
|
-
* best-effort: an adapter that does not answer `quoteOFT` costs
|
|
2033
|
-
* breakdown and the exact output amount, not the whole quote.
|
|
2081
|
+
* best-effort for a quote: an adapter that does not answer `quoteOFT` costs a
|
|
2082
|
+
* caller the fee breakdown and the exact output amount, not the whole quote.
|
|
2083
|
+
*
|
|
2084
|
+
* `requireOft` flips that for the paths that go on to build a send. Without the
|
|
2085
|
+
* receipt the only slippage floor left is the full input amount, which reverts
|
|
2086
|
+
* with `SlippageExceeded` the moment a pathway charges a fee — so a send that
|
|
2087
|
+
* cannot read `quoteOFT` is one to refuse loudly rather than assemble blind.
|
|
2034
2088
|
*/
|
|
2035
|
-
async quotePathway(plan, method) {
|
|
2089
|
+
async quotePathway(plan, method, requireOft = false) {
|
|
2036
2090
|
const sendParam = this.buildSendParams(plan, 0n);
|
|
2037
2091
|
const [msgFee, oft] = await Promise.all([
|
|
2038
2092
|
plan.pc.readContract({
|
|
@@ -2047,39 +2101,43 @@ var LayerZeroService = class {
|
|
|
2047
2101
|
cause: err
|
|
2048
2102
|
});
|
|
2049
2103
|
}),
|
|
2050
|
-
this.readOftQuote(plan, sendParam)
|
|
2104
|
+
this.readOftQuote(plan, sendParam).catch((err) => {
|
|
2105
|
+
if (!requireOft) return void 0;
|
|
2106
|
+
throw new (0, _chunkQQ26UI3Fcjs.StableQuoteError)({
|
|
2107
|
+
provider: "layerzero",
|
|
2108
|
+
shortMessage: `${method}: quoteOFT call failed \u2014 the adapter's receipt is what floors the send at what it would actually deliver, and there is no safe default for it`,
|
|
2109
|
+
cause: err
|
|
2110
|
+
});
|
|
2111
|
+
})
|
|
2051
2112
|
]);
|
|
2052
2113
|
const { nativeFee, lzTokenFee } = msgFee;
|
|
2053
2114
|
return { fee: { nativeFee, lzTokenFee }, oft };
|
|
2054
2115
|
}
|
|
2055
2116
|
/**
|
|
2056
2117
|
* The adapter's own view of a send: accepted range, its fees, and what lands on
|
|
2057
|
-
* the far side.
|
|
2118
|
+
* the far side. Throws when the adapter will not answer; `quotePathway` decides
|
|
2119
|
+
* whether that is fatal for the caller it is serving.
|
|
2058
2120
|
*/
|
|
2059
2121
|
async readOftQuote(plan, sendParam) {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
};
|
|
2080
|
-
} catch (e5) {
|
|
2081
|
-
return void 0;
|
|
2082
|
-
}
|
|
2122
|
+
const [limit, feeDetails, receipt] = await plan.pc.readContract({
|
|
2123
|
+
address: plan.oftAdapter,
|
|
2124
|
+
abi: OFT_ADAPTER_ABI,
|
|
2125
|
+
functionName: "quoteOFT",
|
|
2126
|
+
args: [sendParam]
|
|
2127
|
+
});
|
|
2128
|
+
const decimals = plan.srcToken.decimals;
|
|
2129
|
+
return {
|
|
2130
|
+
amountSentLD: receipt.amountSentLD,
|
|
2131
|
+
amountReceivedLD: receipt.amountReceivedLD,
|
|
2132
|
+
amountReceived: Number(_viem.formatUnits.call(void 0, receipt.amountReceivedLD, decimals)),
|
|
2133
|
+
fees: feeDetails.map((detail) => ({
|
|
2134
|
+
amountLD: detail.feeAmountLD,
|
|
2135
|
+
amount: Number(_viem.formatUnits.call(void 0, detail.feeAmountLD, decimals)),
|
|
2136
|
+
description: detail.description
|
|
2137
|
+
})),
|
|
2138
|
+
minAmountLD: limit.minAmountLD,
|
|
2139
|
+
maxAmountLD: limit.maxAmountLD
|
|
2140
|
+
};
|
|
2083
2141
|
}
|
|
2084
2142
|
/**
|
|
2085
2143
|
* Source-chain cost of the `send` transaction.
|
|
@@ -2089,28 +2147,32 @@ var LayerZeroService = class {
|
|
|
2089
2147
|
* before approving does not have. When the simulation reverts, fall back to a
|
|
2090
2148
|
* measured constant so the caller still has a cost to show, flagged as such.
|
|
2091
2149
|
*/
|
|
2092
|
-
async estimateSendGas(plan, fee, oft) {
|
|
2150
|
+
async estimateSendGas(plan, fee, oft, sender) {
|
|
2093
2151
|
const gasPriceWei = await this.gasPrice(plan);
|
|
2094
2152
|
if (gasPriceWei === void 0) return void 0;
|
|
2095
2153
|
let gas = FALLBACK_SEND_GAS;
|
|
2096
2154
|
let source = "fallback";
|
|
2097
|
-
const account = this.signerAddress();
|
|
2155
|
+
const account = _nullishCoalesce(sender, () => ( this.signerAddress()));
|
|
2098
2156
|
if (account) {
|
|
2157
|
+
const sendFee = {
|
|
2158
|
+
nativeFee: padFee(fee.nativeFee, plan.feeBufferBps),
|
|
2159
|
+
lzTokenFee: fee.lzTokenFee
|
|
2160
|
+
};
|
|
2099
2161
|
try {
|
|
2100
2162
|
gas = await plan.pc.estimateContractGas({
|
|
2101
2163
|
address: plan.oftAdapter,
|
|
2102
2164
|
abi: OFT_ADAPTER_ABI,
|
|
2103
2165
|
functionName: "send",
|
|
2104
2166
|
args: [
|
|
2105
|
-
this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess',
|
|
2106
|
-
|
|
2167
|
+
this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _45 => _45.amountReceivedLD]), () => ( plan.amountLD))),
|
|
2168
|
+
sendFee,
|
|
2107
2169
|
account
|
|
2108
2170
|
],
|
|
2109
2171
|
account,
|
|
2110
|
-
value:
|
|
2172
|
+
value: sendFee.nativeFee
|
|
2111
2173
|
});
|
|
2112
2174
|
source = "simulated";
|
|
2113
|
-
} catch (
|
|
2175
|
+
} catch (e5) {
|
|
2114
2176
|
}
|
|
2115
2177
|
}
|
|
2116
2178
|
return { gas, gasPriceWei, costWei: gas * gasPriceWei, source };
|
|
@@ -2121,41 +2183,58 @@ var LayerZeroService = class {
|
|
|
2121
2183
|
const fees = await plan.pc.estimateFeesPerGas();
|
|
2122
2184
|
const price = _nullishCoalesce(fees.maxFeePerGas, () => ( fees.gasPrice));
|
|
2123
2185
|
if (price !== void 0) return price;
|
|
2124
|
-
} catch (
|
|
2186
|
+
} catch (e6) {
|
|
2125
2187
|
}
|
|
2126
2188
|
try {
|
|
2127
2189
|
return await plan.pc.getGasPrice();
|
|
2128
|
-
} catch (
|
|
2190
|
+
} catch (e7) {
|
|
2129
2191
|
return void 0;
|
|
2130
2192
|
}
|
|
2131
2193
|
}
|
|
2132
2194
|
/**
|
|
2133
|
-
*
|
|
2134
|
-
*
|
|
2195
|
+
* The `approve` amounts to send, in order, before `send` can pull `amountLD` —
|
|
2196
|
+
* empty when the adapter needs no allowance or the existing one already covers
|
|
2197
|
+
* the send.
|
|
2198
|
+
*
|
|
2199
|
+
* A short but non-zero allowance takes two approvals, not one. Legacy USDT on
|
|
2200
|
+
* Ethereum — the only token a lockbox pathway ever approves — refuses to
|
|
2201
|
+
* overwrite a non-zero allowance with another non-zero value:
|
|
2202
|
+
*
|
|
2203
|
+
* require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)))
|
|
2204
|
+
*
|
|
2205
|
+
* so a holder who once approved 5 USDT and now bridges 10 gets a reverted
|
|
2206
|
+
* approve and pays the gas for nothing. Zeroing first is harmless on every
|
|
2207
|
+
* other ERC-20, so it is unconditional rather than sniffed per token.
|
|
2135
2208
|
*/
|
|
2136
|
-
async
|
|
2137
|
-
|
|
2138
|
-
const allowance = await pc.readContract({
|
|
2139
|
-
address: srcToken.address,
|
|
2209
|
+
async approvalPlan(plan, owner, amountLD) {
|
|
2210
|
+
if (plan.srcToken.approvalRequired === false) return [];
|
|
2211
|
+
const allowance = await plan.pc.readContract({
|
|
2212
|
+
address: plan.srcToken.address,
|
|
2140
2213
|
abi: _viem.erc20Abi,
|
|
2141
2214
|
functionName: "allowance",
|
|
2142
|
-
args: [owner, oftAdapter]
|
|
2215
|
+
args: [owner, plan.oftAdapter]
|
|
2143
2216
|
});
|
|
2144
|
-
if (allowance >= amountLD) return;
|
|
2217
|
+
if (allowance >= amountLD) return [];
|
|
2218
|
+
return allowance > 0n ? [0n, amountLD] : [amountLD];
|
|
2219
|
+
}
|
|
2220
|
+
/** Send one `approve` and wait for it to land. */
|
|
2221
|
+
async sendApprove(plan, wc, value) {
|
|
2222
|
+
const { src, asset, srcToken, oftAdapter, pc, chain: srcChain } = plan;
|
|
2223
|
+
const what = value === 0n ? "allowance reset" : "approve";
|
|
2145
2224
|
let approveHash;
|
|
2146
2225
|
try {
|
|
2147
2226
|
approveHash = await wc.writeContract({
|
|
2148
2227
|
address: srcToken.address,
|
|
2149
2228
|
abi: _viem.erc20Abi,
|
|
2150
2229
|
functionName: "approve",
|
|
2151
|
-
args: [oftAdapter,
|
|
2230
|
+
args: [oftAdapter, value],
|
|
2152
2231
|
account: wc.account,
|
|
2153
2232
|
chain: srcChain
|
|
2154
2233
|
});
|
|
2155
2234
|
} catch (err) {
|
|
2156
2235
|
throw new (0, _chunkQQ26UI3Fcjs.StableTransactionError)({
|
|
2157
2236
|
phase: "approve",
|
|
2158
|
-
shortMessage: `bridge: ${asset}
|
|
2237
|
+
shortMessage: `bridge: ${asset} ${what} failed`,
|
|
2159
2238
|
chainId: src.id,
|
|
2160
2239
|
cause: err
|
|
2161
2240
|
});
|
|
@@ -2164,7 +2243,7 @@ var LayerZeroService = class {
|
|
|
2164
2243
|
if (approveReceipt.status === "reverted") {
|
|
2165
2244
|
throw new (0, _chunkQQ26UI3Fcjs.StableTransactionError)({
|
|
2166
2245
|
phase: "approve",
|
|
2167
|
-
shortMessage: `bridge: ${asset}
|
|
2246
|
+
shortMessage: `bridge: ${asset} ${what} reverted on-chain`,
|
|
2168
2247
|
txHash: approveReceipt.transactionHash,
|
|
2169
2248
|
chainId: src.id
|
|
2170
2249
|
});
|
|
@@ -2180,11 +2259,18 @@ var LayerZeroService = class {
|
|
|
2180
2259
|
);
|
|
2181
2260
|
}
|
|
2182
2261
|
const plan = this.plan({ ...params, recipient: _nullishCoalesce(params.recipient, () => ( from)) }, "bridge");
|
|
2183
|
-
const { src,
|
|
2262
|
+
const { src, oftAdapter, amountLD, chain: srcChain } = plan;
|
|
2263
|
+
const refundAddress = this.resolveRefundAddress(params.refundAddress, from, "bridge");
|
|
2184
2264
|
const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
2185
2265
|
account: this.config.account,
|
|
2186
2266
|
chain: srcChain,
|
|
2187
|
-
|
|
2267
|
+
// `config.transport` is the caller's *Stable-chain* transport. A browser
|
|
2268
|
+
// wallet transport is chain-agnostic — the wallet holds the keys and picks
|
|
2269
|
+
// the network — but an HTTP one is a broadcast channel bound to a single
|
|
2270
|
+
// chain, and broadcasting an Ethereum send to the Stable RPC fails. With a
|
|
2271
|
+
// local account we sign here and only need somewhere to broadcast, so the
|
|
2272
|
+
// source chain's own endpoint is always the right answer.
|
|
2273
|
+
transport: this.config.account ? _viem.http.call(void 0, plan.rpc) : _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, plan.rpc)))
|
|
2188
2274
|
})));
|
|
2189
2275
|
try {
|
|
2190
2276
|
await wc.switchChain({ id: src.id });
|
|
@@ -2197,22 +2283,23 @@ var LayerZeroService = class {
|
|
|
2197
2283
|
});
|
|
2198
2284
|
}
|
|
2199
2285
|
const quote = usableQuote(params.quote, amountLD);
|
|
2200
|
-
const { nativeFee, lzTokenFee, oft } = _optionalChain([quote, 'optionalAccess',
|
|
2201
|
-
const sendParams = this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess',
|
|
2202
|
-
|
|
2203
|
-
|
|
2286
|
+
const { nativeFee, lzTokenFee, oft } = _optionalChain([quote, 'optionalAccess', _46 => _46.nativeFee]) !== void 0 && _optionalChain([quote, 'optionalAccess', _47 => _47.lzTokenFee]) !== void 0 ? { nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee, oft: quote.oft } : await this.quotePathway(plan, "bridge", true).then((r) => ({ ...r.fee, oft: r.oft }));
|
|
2287
|
+
const sendParams = this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _48 => _48.amountReceivedLD]), () => ( amountLD)));
|
|
2288
|
+
const sendFee = padFee(nativeFee, plan.feeBufferBps);
|
|
2289
|
+
for (const value of await this.approvalPlan(plan, from, amountLD)) {
|
|
2290
|
+
await this.sendApprove(plan, wc, value);
|
|
2204
2291
|
}
|
|
2205
2292
|
try {
|
|
2206
2293
|
const hash = await wc.writeContract({
|
|
2207
2294
|
address: oftAdapter,
|
|
2208
2295
|
abi: OFT_ADAPTER_ABI,
|
|
2209
2296
|
functionName: "send",
|
|
2210
|
-
args: [sendParams, { nativeFee, lzTokenFee },
|
|
2211
|
-
value:
|
|
2297
|
+
args: [sendParams, { nativeFee: sendFee, lzTokenFee }, refundAddress],
|
|
2298
|
+
value: sendFee,
|
|
2212
2299
|
account: wc.account,
|
|
2213
2300
|
chain: srcChain
|
|
2214
2301
|
});
|
|
2215
|
-
return { txHash: hash, toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess',
|
|
2302
|
+
return { txHash: hash, toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _49 => _49.amountReceived]), () => ( params.amount)) };
|
|
2216
2303
|
} catch (err) {
|
|
2217
2304
|
throw new (0, _chunkQQ26UI3Fcjs.StableTransactionError)({
|
|
2218
2305
|
phase: "send",
|
|
@@ -2222,6 +2309,77 @@ var LayerZeroService = class {
|
|
|
2222
2309
|
});
|
|
2223
2310
|
}
|
|
2224
2311
|
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Everything needed to send a bridge, without signing or sending any of it: the
|
|
2314
|
+
* ordered transactions, the quote they were built from, and the `SendParam` the
|
|
2315
|
+
* send step encodes, decoded.
|
|
2316
|
+
*
|
|
2317
|
+
* This is the split-responsibility shape — a backend quotes, builds and checks
|
|
2318
|
+
* while the holder's wallet only signs — and the only way to inspect a bridge
|
|
2319
|
+
* before it exists. Send `steps` in order from `from`, each one confirmed before
|
|
2320
|
+
* the next: an allowance reset and an approve where the lockbox pathway needs
|
|
2321
|
+
* them, then the OFT `send` carrying its fee as `value`.
|
|
2322
|
+
*
|
|
2323
|
+
* Refuses to build when the adapter will not answer `quoteOFT`: the receipt is
|
|
2324
|
+
* what floors the send at what it would actually deliver, and guessing the floor
|
|
2325
|
+
* is how a send reverts with `SlippageExceeded` after the gas is spent.
|
|
2326
|
+
*/
|
|
2327
|
+
async prepareBridgeCalldata(params) {
|
|
2328
|
+
const method = "prepareBridgeCalldata";
|
|
2329
|
+
const from = params.from;
|
|
2330
|
+
if (!_viem.isAddress.call(void 0, from)) {
|
|
2331
|
+
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
|
|
2332
|
+
"from",
|
|
2333
|
+
params.from,
|
|
2334
|
+
`${method}: invalid from address "${params.from}" \u2014 the address that will sign these transactions`
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
const refundAddress = this.resolveRefundAddress(params.refundAddress, from, method);
|
|
2338
|
+
const plan = this.plan({ ...params, recipient: _nullishCoalesce(params.recipient, () => ( from)) }, method);
|
|
2339
|
+
const supplied = usableQuote(params.quote, plan.amountLD);
|
|
2340
|
+
const quote = _optionalChain([supplied, 'optionalAccess', _50 => _50.oft]) && supplied.nativeFee !== void 0 && supplied.lzTokenFee !== void 0 ? supplied : await this.buildQuote(plan, params, method, { account: from, requireOft: true });
|
|
2341
|
+
const oft = quote.oft;
|
|
2342
|
+
const nativeFee = padFee(quote.nativeFee, plan.feeBufferBps);
|
|
2343
|
+
const lzTokenFee = quote.lzTokenFee;
|
|
2344
|
+
const sendParam = this.buildSendParams(plan, oft.amountReceivedLD);
|
|
2345
|
+
const steps = (await this.approvalPlan(plan, from, plan.amountLD)).map((value) => ({
|
|
2346
|
+
chainId: plan.src.id,
|
|
2347
|
+
to: plan.srcToken.address,
|
|
2348
|
+
data: _viem.encodeFunctionData.call(void 0, {
|
|
2349
|
+
abi: _viem.erc20Abi,
|
|
2350
|
+
functionName: "approve",
|
|
2351
|
+
args: [plan.oftAdapter, value]
|
|
2352
|
+
}),
|
|
2353
|
+
value: 0n
|
|
2354
|
+
}));
|
|
2355
|
+
steps.push({
|
|
2356
|
+
chainId: plan.src.id,
|
|
2357
|
+
to: plan.oftAdapter,
|
|
2358
|
+
data: _viem.encodeFunctionData.call(void 0, {
|
|
2359
|
+
abi: OFT_ADAPTER_ABI,
|
|
2360
|
+
functionName: "send",
|
|
2361
|
+
args: [sendParam, { nativeFee, lzTokenFee }, refundAddress]
|
|
2362
|
+
}),
|
|
2363
|
+
value: nativeFee
|
|
2364
|
+
});
|
|
2365
|
+
return {
|
|
2366
|
+
steps,
|
|
2367
|
+
chainId: plan.src.id,
|
|
2368
|
+
asset: plan.asset,
|
|
2369
|
+
quote,
|
|
2370
|
+
send: {
|
|
2371
|
+
dstEid: sendParam.dstEid,
|
|
2372
|
+
recipient: plan.recipient,
|
|
2373
|
+
to: sendParam.to,
|
|
2374
|
+
amountLD: sendParam.amountLD,
|
|
2375
|
+
minAmountLD: sendParam.minAmountLD,
|
|
2376
|
+
extraOptions: sendParam.extraOptions,
|
|
2377
|
+
nativeFee,
|
|
2378
|
+
lzTokenFee,
|
|
2379
|
+
refundAddress
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2225
2383
|
/**
|
|
2226
2384
|
* Estimate how long a USDT0 bridge will take, end to end.
|
|
2227
2385
|
*
|
|
@@ -2244,8 +2402,8 @@ var LayerZeroService = class {
|
|
|
2244
2402
|
}) {
|
|
2245
2403
|
const src = BRIDGE_CHAIN_CONFIGS[fromChain];
|
|
2246
2404
|
const dst = BRIDGE_CHAIN_CONFIGS[toChain];
|
|
2247
|
-
const from = _nullishCoalesce(fromToken, () => ( _optionalChain([src, 'optionalAccess',
|
|
2248
|
-
const to = _nullishCoalesce(toToken, () => ( _optionalChain([dst, 'optionalAccess',
|
|
2405
|
+
const from = _nullishCoalesce(fromToken, () => ( _optionalChain([src, 'optionalAccess', _51 => _51.tokens, 'access', _52 => _52.usdt0, 'optionalAccess', _53 => _53.address])));
|
|
2406
|
+
const to = _nullishCoalesce(toToken, () => ( _optionalChain([dst, 'optionalAccess', _54 => _54.tokens, 'access', _55 => _55.usdt0, 'optionalAccess', _56 => _56.address])));
|
|
2249
2407
|
if (!(src && from)) {
|
|
2250
2408
|
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)("fromChain", fromChain, `estimateBridgeTime: no OFT adapter for "${fromChain}"`);
|
|
2251
2409
|
}
|
|
@@ -2277,7 +2435,7 @@ var LayerZeroService = class {
|
|
|
2277
2435
|
source: observedSeconds !== void 0 ? "observed" : "formula",
|
|
2278
2436
|
observedSeconds,
|
|
2279
2437
|
formulaSeconds,
|
|
2280
|
-
confirmations: Number.isFinite(_optionalChain([uln, 'optionalAccess',
|
|
2438
|
+
confirmations: Number.isFinite(_optionalChain([uln, 'optionalAccess', _57 => _57.confirmations])) ? _optionalChain([uln, 'optionalAccess', _58 => _58.confirmations]) : void 0,
|
|
2281
2439
|
dvnCount: Number.isFinite(dvnCount) ? dvnCount : void 0
|
|
2282
2440
|
};
|
|
2283
2441
|
}
|
|
@@ -2290,7 +2448,7 @@ var LayerZeroService = class {
|
|
|
2290
2448
|
const scanUrl = `${LZ_SCAN_UI}/tx/${srcTxHash}`;
|
|
2291
2449
|
let message;
|
|
2292
2450
|
try {
|
|
2293
|
-
message = await _asyncOptionalChain([(await scanFetch(`/messages/tx/${srcTxHash}`)), 'optionalAccess', async
|
|
2451
|
+
message = await _asyncOptionalChain([(await scanFetch(`/messages/tx/${srcTxHash}`)), 'optionalAccess', async _59 => _59.data, 'optionalAccess', async _60 => _60[0]]);
|
|
2294
2452
|
} catch (err) {
|
|
2295
2453
|
if (err instanceof ScanHttpError && err.status === 404) {
|
|
2296
2454
|
return { state: "pending", srcTxHash, scanUrl };
|
|
@@ -2302,15 +2460,15 @@ var LayerZeroService = class {
|
|
|
2302
2460
|
});
|
|
2303
2461
|
}
|
|
2304
2462
|
if (!message) return { state: "pending", srcTxHash, scanUrl };
|
|
2305
|
-
const raw = _nullishCoalesce(_optionalChain([message, 'access',
|
|
2306
|
-
const sentAt = _optionalChain([message, 'access',
|
|
2463
|
+
const raw = _nullishCoalesce(_optionalChain([message, 'access', _61 => _61.status, 'optionalAccess', _62 => _62.name]), () => ( ""));
|
|
2464
|
+
const sentAt = _optionalChain([message, 'access', _63 => _63.source, 'optionalAccess', _64 => _64.tx, 'optionalAccess', _65 => _65.blockTimestamp]);
|
|
2307
2465
|
return {
|
|
2308
2466
|
// Everything terminal that is not DELIVERED means the tokens will not land
|
|
2309
2467
|
// on their own; `raw` carries which of those it was.
|
|
2310
2468
|
state: raw === "DELIVERED" ? "delivered" : TERMINAL_FAILURE_STATUSES.has(raw) ? "failed" : "inflight",
|
|
2311
2469
|
raw,
|
|
2312
2470
|
srcTxHash,
|
|
2313
|
-
dstTxHash: _optionalChain([message, 'access',
|
|
2471
|
+
dstTxHash: _optionalChain([message, 'access', _66 => _66.destination, 'optionalAccess', _67 => _67.tx, 'optionalAccess', _68 => _68.txHash]),
|
|
2314
2472
|
elapsedSeconds: sentAt ? Math.max(0, Math.floor(Date.now() / 1e3) - sentAt) : void 0,
|
|
2315
2473
|
scanUrl
|
|
2316
2474
|
};
|
|
@@ -2318,7 +2476,8 @@ var LayerZeroService = class {
|
|
|
2318
2476
|
/** Effective ULN config the DVNs enforce for this pathway, read from the source chain. */
|
|
2319
2477
|
async getUlnConfig(src, dst, oapp) {
|
|
2320
2478
|
return cached(`uln:${src.lzEid}-${dst.lzEid}-${oapp.toLowerCase()}`, CACHE_TTL_MS, async () => {
|
|
2321
|
-
const
|
|
2479
|
+
const rpc = this.rpcFor(src);
|
|
2480
|
+
const pc = _viem.createPublicClient.call(void 0, { chain: makeChain(src.id, rpc), transport: _viem.http.call(void 0, rpc) });
|
|
2322
2481
|
const endpoint = await pc.readContract({ address: oapp, abi: OAPP_ABI, functionName: "endpoint" });
|
|
2323
2482
|
const sendLib = await pc.readContract({
|
|
2324
2483
|
address: endpoint,
|
|
@@ -2344,7 +2503,7 @@ var LayerZeroService = class {
|
|
|
2344
2503
|
const pathwayId = `${src.lzEid}-${dst.lzEid}-${srcOapp.toLowerCase()}-${dstOapp.toLowerCase()}`;
|
|
2345
2504
|
return cached(`observed:${pathwayId}`, CACHE_TTL_MS, async () => {
|
|
2346
2505
|
const { data } = await scanFetch(`/messages/pathway/${pathwayId}?limit=50`);
|
|
2347
|
-
const durations = (_nullishCoalesce(data, () => ( []))).filter((m) => _optionalChain([m, 'access',
|
|
2506
|
+
const durations = (_nullishCoalesce(data, () => ( []))).filter((m) => _optionalChain([m, 'access', _69 => _69.status, 'optionalAccess', _70 => _70.name]) === "DELIVERED").map((m) => (_nullishCoalesce(_optionalChain([m, 'access', _71 => _71.destination, 'optionalAccess', _72 => _72.tx, 'optionalAccess', _73 => _73.blockTimestamp]), () => ( 0))) - (_nullishCoalesce(_optionalChain([m, 'access', _74 => _74.source, 'optionalAccess', _75 => _75.tx, 'optionalAccess', _76 => _76.blockTimestamp]), () => ( 0)))).filter((d) => d > 0);
|
|
2348
2507
|
return durations.length >= 3 ? Math.round(median(durations)) : void 0;
|
|
2349
2508
|
});
|
|
2350
2509
|
}
|
|
@@ -2424,13 +2583,22 @@ var LayerZeroService = class {
|
|
|
2424
2583
|
`${method}: dstNativeDrop must not be negative`
|
|
2425
2584
|
);
|
|
2426
2585
|
}
|
|
2586
|
+
const feeBufferBps = resolveFeeBuffer(params.feeBufferBps, method);
|
|
2427
2587
|
const { src, dst, asset, srcToken, dstToken } = this.resolveRoute(
|
|
2428
2588
|
fromChain,
|
|
2429
2589
|
toChain,
|
|
2430
2590
|
fromToken,
|
|
2431
2591
|
toToken
|
|
2432
2592
|
);
|
|
2433
|
-
|
|
2593
|
+
if (params.fromDecimals !== void 0 && params.fromDecimals !== srcToken.decimals) {
|
|
2594
|
+
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
|
|
2595
|
+
"fromDecimals",
|
|
2596
|
+
params.fromDecimals,
|
|
2597
|
+
`${method}: ${asset} has ${srcToken.decimals} decimals on "${fromChain}", not ${params.fromDecimals}. The native OFT route parses the amount with the token's own decimals; fromDecimals applies to LI.FI routes only.`
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
const rpc = this.rpcFor(src);
|
|
2601
|
+
const chain = makeChain(src.id, rpc);
|
|
2434
2602
|
return {
|
|
2435
2603
|
src,
|
|
2436
2604
|
dst,
|
|
@@ -2441,13 +2609,31 @@ var LayerZeroService = class {
|
|
|
2441
2609
|
amountLD: _viem.parseUnits.call(void 0, String(amount), srcToken.decimals),
|
|
2442
2610
|
recipient: recipientAddr,
|
|
2443
2611
|
nativeDrop: _nullishCoalesce(dstNativeDrop, () => ( 0n)),
|
|
2612
|
+
rpc,
|
|
2613
|
+
feeBufferBps,
|
|
2444
2614
|
chain,
|
|
2445
|
-
pc: _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0,
|
|
2615
|
+
pc: _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0, rpc) })
|
|
2446
2616
|
};
|
|
2447
2617
|
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Where `send` returns messaging fee it did not need. Defaults to the sender,
|
|
2620
|
+
* which is what a caller wants unless they are routing refunds somewhere of
|
|
2621
|
+
* their own — a backend paying the fee on a user's behalf, say.
|
|
2622
|
+
*/
|
|
2623
|
+
resolveRefundAddress(refundAddress, sender, method) {
|
|
2624
|
+
if (refundAddress === void 0) return sender;
|
|
2625
|
+
if (!_viem.isAddress.call(void 0, refundAddress)) {
|
|
2626
|
+
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
|
|
2627
|
+
"refundAddress",
|
|
2628
|
+
refundAddress,
|
|
2629
|
+
`${method}: invalid refundAddress "${refundAddress}"`
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
return refundAddress;
|
|
2633
|
+
}
|
|
2448
2634
|
/** The address that will sign, or undefined when this service has no signer. */
|
|
2449
2635
|
signerAddress() {
|
|
2450
|
-
return _nullishCoalesce(_optionalChain([this, 'access',
|
|
2636
|
+
return _nullishCoalesce(_optionalChain([this, 'access', _77 => _77.config, 'access', _78 => _78.walletClient, 'optionalAccess', _79 => _79.account, 'optionalAccess', _80 => _80.address]), () => ( _optionalChain([this, 'access', _81 => _81.config, 'access', _82 => _82.account, 'optionalAccess', _83 => _83.address])));
|
|
2451
2637
|
}
|
|
2452
2638
|
/**
|
|
2453
2639
|
* `SendParam` for one call. `minAmountLD` is the caller's slippage floor: 0 when
|
|
@@ -2542,7 +2728,7 @@ async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
|
|
|
2542
2728
|
abi: _viem.erc20Abi,
|
|
2543
2729
|
functionName: "decimals"
|
|
2544
2730
|
});
|
|
2545
|
-
} catch (
|
|
2731
|
+
} catch (e8) {
|
|
2546
2732
|
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
|
|
2547
2733
|
"tokenDecimals",
|
|
2548
2734
|
void 0,
|
|
@@ -2576,6 +2762,7 @@ async function executeTokenTransfer(wc, publicClient, token, to, amount, tokenDe
|
|
|
2576
2762
|
function createStable({
|
|
2577
2763
|
network = "mainnet" /* Mainnet */,
|
|
2578
2764
|
rpc: rpcOverride,
|
|
2765
|
+
rpcOverrides,
|
|
2579
2766
|
account,
|
|
2580
2767
|
transport,
|
|
2581
2768
|
walletClient,
|
|
@@ -2589,7 +2776,7 @@ function createStable({
|
|
|
2589
2776
|
);
|
|
2590
2777
|
const signerConfig = { account, transport, walletClient };
|
|
2591
2778
|
const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
|
|
2592
|
-
const lz = new LayerZeroService(signerConfig);
|
|
2779
|
+
const lz = new LayerZeroService({ ...signerConfig, rpcOverrides });
|
|
2593
2780
|
const merkl = new MerklService({ ...signerConfig, rpc, chainId: chain.id, merklApiBase });
|
|
2594
2781
|
const morpho = earnConfig ? new MorphoService({
|
|
2595
2782
|
...signerConfig,
|
|
@@ -2614,7 +2801,7 @@ function createStable({
|
|
|
2614
2801
|
return morpho;
|
|
2615
2802
|
}
|
|
2616
2803
|
function requireSignerAddress() {
|
|
2617
|
-
const from = _nullishCoalesce(_optionalChain([walletClient, 'optionalAccess',
|
|
2804
|
+
const from = _nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _84 => _84.account, 'optionalAccess', _85 => _85.address]), () => ( _optionalChain([account, 'optionalAccess', _86 => _86.address])));
|
|
2618
2805
|
if (!from) {
|
|
2619
2806
|
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
|
|
2620
2807
|
"signer",
|
|
@@ -2627,7 +2814,7 @@ function createStable({
|
|
|
2627
2814
|
async function transfer(params) {
|
|
2628
2815
|
validateTransferParams(params);
|
|
2629
2816
|
const { from, to, amount, token, tokenDecimals } = params;
|
|
2630
|
-
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess',
|
|
2817
|
+
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _87 => _87.account]), () => ( account)), () => ( from));
|
|
2631
2818
|
const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
2632
2819
|
account: acc,
|
|
2633
2820
|
chain,
|
|
@@ -2683,6 +2870,16 @@ function createStable({
|
|
|
2683
2870
|
// promise like every other validation error, not a synchronous throw.
|
|
2684
2871
|
quoteBridge: async (params) => selectBridge(params) === "oft" ? lz.quoteBridge(params) : lifi.quoteBridge(params),
|
|
2685
2872
|
bridge: async (params) => selectBridge(params) === "oft" ? lz.bridge(params) : lifi.bridge(params),
|
|
2873
|
+
prepareBridgeCalldata: async (params) => {
|
|
2874
|
+
if (selectBridge(params) !== "oft") {
|
|
2875
|
+
throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
|
|
2876
|
+
"route",
|
|
2877
|
+
_nullishCoalesce(params.route, () => ( "auto")),
|
|
2878
|
+
`prepareBridgeCalldata: only the native OFT route can be built unsigned, and ${params.fromChain}\u2192${params.toChain} resolved to LI.FI`
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2881
|
+
return lz.prepareBridgeCalldata(params);
|
|
2882
|
+
},
|
|
2686
2883
|
estimateBridgeTime: (params) => lz.estimateBridgeTime(params),
|
|
2687
2884
|
trackBridge: (srcTxHash) => lz.trackBridge(srcTxHash),
|
|
2688
2885
|
quoteSwap: (params) => lifi.quoteSwap(params),
|
|
@@ -2751,4 +2948,6 @@ function createStableReader(config) {
|
|
|
2751
2948
|
|
|
2752
2949
|
|
|
2753
2950
|
|
|
2754
|
-
|
|
2951
|
+
|
|
2952
|
+
|
|
2953
|
+
exports.BRIDGE_CHAIN_CONFIGS = BRIDGE_CHAIN_CONFIGS; exports.CHAIN_CONFIGS = CHAIN_CONFIGS; exports.Chain = Chain; exports.ERC3009_ABI = _chunkQQ26UI3Fcjs.ERC3009_ABI; exports.Network = Network; exports.OFT_ADAPTER_ABI = OFT_ADAPTER_ABI; exports.STABLE_USDT_ADDRESS = _chunkQQ26UI3Fcjs.STABLE_USDT_ADDRESS; exports.STABLE_VAULT_ADDRESS = _chunkQQ26UI3Fcjs.STABLE_VAULT_ADDRESS; exports.StableBulkError = _chunkQQ26UI3Fcjs.StableBulkError; exports.StableError = _chunkQQ26UI3Fcjs.StableError; exports.StableNetworkError = _chunkQQ26UI3Fcjs.StableNetworkError; exports.StableQuoteError = _chunkQQ26UI3Fcjs.StableQuoteError; exports.StableTransactionError = _chunkQQ26UI3Fcjs.StableTransactionError; exports.StableValidationError = _chunkQQ26UI3Fcjs.StableValidationError; exports.bridgeAssetFor = bridgeAssetFor; exports.buildLzOptions = buildLzOptions; exports.cancelAuthorization = _chunkQQ26UI3Fcjs.cancelAuthorization; exports.clearBridgeEstimateCache = clearBridgeEstimateCache; exports.createStable = createStable; exports.createStableReader = createStableReader; exports.erc3009Actions = _chunkQQ26UI3Fcjs.erc3009Actions; exports.getAuthorizationState = _chunkQQ26UI3Fcjs.getAuthorizationState; exports.getAuthorizationUsage = _chunkQQ26UI3Fcjs.getAuthorizationUsage; exports.prepareTransferWithAuthorization = _chunkQQ26UI3Fcjs.prepareTransferWithAuthorization; exports.receiveWithAuthorization = _chunkQQ26UI3Fcjs.receiveWithAuthorization; exports.signReceiveAuthorization = _chunkQQ26UI3Fcjs.signReceiveAuthorization; exports.signTransferAuthorization = _chunkQQ26UI3Fcjs.signTransferAuthorization; exports.transferWithAuthorization = _chunkQQ26UI3Fcjs.transferWithAuthorization; exports.verifyAuthorization = _chunkQQ26UI3Fcjs.verifyAuthorization;
|