@stablechain/sdk 1.0.6 → 1.0.8

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 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,15 +1750,21 @@ 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
  };
1752
- function bridgeChain(chain, lzEid, blockTimeSec, usdt0Adapter) {
1753
+ var USDT0_LOCKBOX_CHAINS = /* @__PURE__ */ new Set(["ethereum" /* Ethereum */, "sepolia" /* Sepolia */]);
1754
+ function bridgeChain(chain, lzEid, blockTimeSec, usdt0Adapter, minDeliverySec) {
1753
1755
  const base = C[chain];
1754
1756
  const tokens = {};
1755
1757
  if (usdt0Adapter) {
1756
- tokens.usdt0 = { address: base.usdt, decimals: base.decimals, oftAdapter: usdt0Adapter };
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;
1760
- return { ...base, lzEid, blockTimeSec, tokens, oftAdapter: usdt0Adapter };
1767
+ return { ...base, lzEid, blockTimeSec, minDeliverySec, tokens, oftAdapter: usdt0Adapter };
1761
1768
  }
1762
1769
  var BRIDGE_CHAIN_CONFIGS = {
1763
1770
  // ─── Testnet ───────────────────────────────────────────────────────────────
@@ -1765,18 +1772,18 @@ var BRIDGE_CHAIN_CONFIGS = {
1765
1772
  ["stable-testnet" /* StableTestnet */]: bridgeChain("stable-testnet" /* StableTestnet */, 40374, 0.7, "0x779Ded0c9e1022225f8E0630b35a9b54bE713736"),
1766
1773
  // ─── Mainnet ──────────────────────────────────────────────────────────────
1767
1774
  ["stable" /* Stable */]: bridgeChain("stable" /* Stable */, 30396, 0.7, "0xedaba024be4d87974d5aB11C6Dd586963CcCB027"),
1768
- ["ethereum" /* Ethereum */]: bridgeChain("ethereum" /* Ethereum */, 30101, 12, "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"),
1769
- ["arbitrum" /* Arbitrum */]: bridgeChain("arbitrum" /* Arbitrum */, 30110, 0.25, "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92"),
1775
+ ["ethereum" /* Ethereum */]: bridgeChain("ethereum" /* Ethereum */, 30101, 12, "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee", 1170),
1776
+ ["arbitrum" /* Arbitrum */]: bridgeChain("arbitrum" /* Arbitrum */, 30110, 0.25, "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92", 1350),
1770
1777
  ["ink" /* Ink */]: bridgeChain("ink" /* Ink */, 30339, 1, "0x1cB6De532588fCA4a21B7209DE7C456AF8434A65"),
1771
- ["bera" /* Bera */]: bridgeChain("bera" /* Bera */, 30362, 2, "0x3Dc96399109df5ceb2C226664A086140bD0379cB"),
1778
+ ["bera" /* Bera */]: bridgeChain("bera" /* Bera */, 30362, 2, "0x3Dc96399109df5ceb2C226664A086140bD0379cB", 135),
1772
1779
  ["megaeth" /* MegaETH */]: bridgeChain("megaeth" /* MegaETH */, 30398, 1, "0x9151434b16b9763660705744891fa906f660ecc5"),
1773
1780
  ["hyperevm" /* HyperEVM */]: bridgeChain("hyperevm" /* HyperEVM */, 30367, 1, "0x904861a24F30EC96ea7CFC3bE9EA4B476d237e98"),
1774
- ["polygon" /* Polygon */]: bridgeChain("polygon" /* Polygon */, 30109, 1.5, "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13"),
1781
+ ["polygon" /* Polygon */]: bridgeChain("polygon" /* Polygon */, 30109, 1.5, "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13", 70),
1775
1782
  ["plasma" /* Plasma */]: bridgeChain("plasma" /* Plasma */, 30383, 1, "0x02ca37966753bDdDf11216B73B16C1dE756A7CF9"),
1776
1783
  ["optimism" /* Optimism */]: bridgeChain("optimism" /* Optimism */, 30111, 2, "0xF03b4d9AC1D5d1E7c4cEf54C2A313b9fe051A0aD"),
1777
1784
  ["unichain" /* Unichain */]: bridgeChain("unichain" /* Unichain */, 30320, 1, "0xc07bE8994D035631c36fb4a89C918CeFB2f03EC3"),
1778
1785
  ["sei" /* Sei */]: bridgeChain("sei" /* Sei */, 30280, 0.5, "0x56Fe74A2e3b484b921c447357203431a3485CC60"),
1779
- ["flare" /* Flare */]: bridgeChain("flare" /* Flare */, 30295, 1.1, "0x567287d2A9829215a37e3B88843d32f9221E7588"),
1786
+ ["flare" /* Flare */]: bridgeChain("flare" /* Flare */, 30295, 1.1, "0x567287d2A9829215a37e3B88843d32f9221E7588", 600),
1780
1787
  ["xlayer" /* XLayer */]: bridgeChain("xlayer" /* XLayer */, 30274, 1, "0x94BCCa6bdfd6A61817Ab0E960bFedE4984505554"),
1781
1788
  ["rootstock" /* Rootstock */]: bridgeChain("rootstock" /* Rootstock */, 30333, 31, "0x1a594d5d5d1c426281C1064B07f23F57B2716B61"),
1782
1789
  // XAUt0 only — BSC has no USDT0 deployment. Block time measured 2026-08-20.
@@ -1957,6 +1964,14 @@ function median(values) {
1957
1964
  const mid = Math.floor(sorted.length / 2);
1958
1965
  return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
1959
1966
  }
1967
+ function resolveDeliverySeconds(formulaSeconds, observedSeconds, minDeliverySec) {
1968
+ const base = _nullishCoalesce(formulaSeconds, () => ( observedSeconds));
1969
+ if (base === void 0) return void 0;
1970
+ if (minDeliverySec !== void 0 && minDeliverySec > base) {
1971
+ return { seconds: minDeliverySec, source: "floor" };
1972
+ }
1973
+ return { seconds: base, source: formulaSeconds !== void 0 ? "formula" : "observed" };
1974
+ }
1960
1975
  var WORKER_EXECUTOR = 1;
1961
1976
  var OPTION_LZRECEIVE = 1;
1962
1977
  var OPTION_NATIVE_DROP = 2;
@@ -1998,6 +2013,22 @@ function buildLzOptions(gasLimit = 0n, nativeDrop = 0n, dropReceiver) {
1998
2013
  ]);
1999
2014
  }
2000
2015
  var FALLBACK_SEND_GAS = 400000n;
2016
+ var DEFAULT_FEE_BUFFER_BPS = 500;
2017
+ var MAX_FEE_BUFFER_BPS = 1e4;
2018
+ function padFee(nativeFee, bufferBps) {
2019
+ return nativeFee * BigInt(1e4 + bufferBps) / 10000n;
2020
+ }
2021
+ function resolveFeeBuffer(feeBufferBps, method) {
2022
+ if (feeBufferBps === void 0) return DEFAULT_FEE_BUFFER_BPS;
2023
+ if (!Number.isInteger(feeBufferBps) || feeBufferBps < 0 || feeBufferBps > MAX_FEE_BUFFER_BPS) {
2024
+ throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
2025
+ "feeBufferBps",
2026
+ feeBufferBps,
2027
+ `${method}: feeBufferBps must be an integer between 0 and ${MAX_FEE_BUFFER_BPS}`
2028
+ );
2029
+ }
2030
+ return feeBufferBps;
2031
+ }
2001
2032
  function usableQuote(quote, amountLD) {
2002
2033
  if (!_optionalChain([quote, 'optionalAccess', _41 => _41.oft])) return quote;
2003
2034
  return quote.oft.amountSentLD <= amountLD ? quote : void 0;
@@ -2006,20 +2037,46 @@ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
2006
2037
  var LayerZeroService = class {
2007
2038
  constructor(config) {
2008
2039
  this.config = config;
2040
+ this.rpcById = new Map(
2041
+ Object.entries(_nullishCoalesce(config.rpcOverrides, () => ( {}))).flatMap(([chain, rpc]) => {
2042
+ const id = _optionalChain([CHAIN_CONFIGS, 'access', _42 => _42[chain], 'optionalAccess', _43 => _43.id]);
2043
+ return id === void 0 || !rpc ? [] : [[id, rpc.replace(/\/$/, "")]];
2044
+ })
2045
+ );
2046
+ }
2047
+ /**
2048
+ * The RPC to read and broadcast on for a chain: the caller's override when they
2049
+ * gave one, else the built-in public endpoint. Every bridge read goes through
2050
+ * here — a backend quoting on a loop needs an endpoint it controls.
2051
+ */
2052
+ rpcFor(cfg) {
2053
+ return _nullishCoalesce(this.rpcById.get(cfg.id), () => ( cfg.rpc));
2009
2054
  }
2010
2055
  async quoteBridge(params) {
2011
- const plan = this.plan(params, "quoteBridge");
2056
+ return this.buildQuote(this.plan(params, "quoteBridge"), params, "quoteBridge");
2057
+ }
2058
+ /**
2059
+ * The full quote for a planned send. `quoteBridge` reports one; the unsigned
2060
+ * build needs the same numbers and must not disagree with what a caller was
2061
+ * shown, so both come from here.
2062
+ *
2063
+ * `account` is who the gas simulation runs as — this service's signer when it
2064
+ * has one, and the address a prepared send will be signed by when it does not.
2065
+ */
2066
+ async buildQuote(plan, params, method, opts = {}) {
2012
2067
  const [{ fee, oft }, estimate] = await Promise.all([
2013
- this.quotePathway(plan, "quoteBridge"),
2068
+ this.quotePathway(plan, method, opts.requireOft),
2014
2069
  this.estimateBridgeTime(params).catch(() => void 0)
2015
2070
  ]);
2016
- const srcGas = await this.estimateSendGas(plan, fee, oft);
2071
+ const srcGas = await this.estimateSendGas(plan, fee, oft, opts.account);
2017
2072
  return {
2018
2073
  ...fee,
2074
+ // What a send built from this quote will actually carry as its `value`.
2075
+ nativeFeeMax: padFee(fee.nativeFee, plan.feeBufferBps),
2019
2076
  oft,
2020
2077
  // What the recipient is actually credited: input minus any OFT fee and
2021
2078
  // minus dust. Falls back to the input when quoteOFT could not be read.
2022
- toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _42 => _42.amountReceived]), () => ( params.amount)),
2079
+ toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _44 => _44.amountReceived]), () => ( params.amount)),
2023
2080
  srcGas,
2024
2081
  totalCostWei: srcGas ? srcGas.costWei + fee.nativeFee : void 0,
2025
2082
  estimate
@@ -2029,10 +2086,15 @@ var LayerZeroService = class {
2029
2086
  * The LayerZero messaging fee for a send, plus the OFT's own accounting.
2030
2087
  *
2031
2088
  * 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 us the fee
2033
- * breakdown and the exact output amount, not the whole quote.
2089
+ * best-effort for a quote: an adapter that does not answer `quoteOFT` costs a
2090
+ * caller the fee breakdown and the exact output amount, not the whole quote.
2091
+ *
2092
+ * `requireOft` flips that for the paths that go on to build a send. Without the
2093
+ * receipt the only slippage floor left is the full input amount, which reverts
2094
+ * with `SlippageExceeded` the moment a pathway charges a fee — so a send that
2095
+ * cannot read `quoteOFT` is one to refuse loudly rather than assemble blind.
2034
2096
  */
2035
- async quotePathway(plan, method) {
2097
+ async quotePathway(plan, method, requireOft = false) {
2036
2098
  const sendParam = this.buildSendParams(plan, 0n);
2037
2099
  const [msgFee, oft] = await Promise.all([
2038
2100
  plan.pc.readContract({
@@ -2047,39 +2109,43 @@ var LayerZeroService = class {
2047
2109
  cause: err
2048
2110
  });
2049
2111
  }),
2050
- this.readOftQuote(plan, sendParam)
2112
+ this.readOftQuote(plan, sendParam).catch((err) => {
2113
+ if (!requireOft) return void 0;
2114
+ throw new (0, _chunkQQ26UI3Fcjs.StableQuoteError)({
2115
+ provider: "layerzero",
2116
+ 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`,
2117
+ cause: err
2118
+ });
2119
+ })
2051
2120
  ]);
2052
2121
  const { nativeFee, lzTokenFee } = msgFee;
2053
2122
  return { fee: { nativeFee, lzTokenFee }, oft };
2054
2123
  }
2055
2124
  /**
2056
2125
  * The adapter's own view of a send: accepted range, its fees, and what lands on
2057
- * the far side. Resolves to `undefined` rather than throwing — see `quoteSend`.
2126
+ * the far side. Throws when the adapter will not answer; `quotePathway` decides
2127
+ * whether that is fatal for the caller it is serving.
2058
2128
  */
2059
2129
  async readOftQuote(plan, sendParam) {
2060
- try {
2061
- const [limit, feeDetails, receipt] = await plan.pc.readContract({
2062
- address: plan.oftAdapter,
2063
- abi: OFT_ADAPTER_ABI,
2064
- functionName: "quoteOFT",
2065
- args: [sendParam]
2066
- });
2067
- const decimals = plan.srcToken.decimals;
2068
- return {
2069
- amountSentLD: receipt.amountSentLD,
2070
- amountReceivedLD: receipt.amountReceivedLD,
2071
- amountReceived: Number(_viem.formatUnits.call(void 0, receipt.amountReceivedLD, decimals)),
2072
- fees: feeDetails.map((detail) => ({
2073
- amountLD: detail.feeAmountLD,
2074
- amount: Number(_viem.formatUnits.call(void 0, detail.feeAmountLD, decimals)),
2075
- description: detail.description
2076
- })),
2077
- minAmountLD: limit.minAmountLD,
2078
- maxAmountLD: limit.maxAmountLD
2079
- };
2080
- } catch (e5) {
2081
- return void 0;
2082
- }
2130
+ const [limit, feeDetails, receipt] = await plan.pc.readContract({
2131
+ address: plan.oftAdapter,
2132
+ abi: OFT_ADAPTER_ABI,
2133
+ functionName: "quoteOFT",
2134
+ args: [sendParam]
2135
+ });
2136
+ const decimals = plan.srcToken.decimals;
2137
+ return {
2138
+ amountSentLD: receipt.amountSentLD,
2139
+ amountReceivedLD: receipt.amountReceivedLD,
2140
+ amountReceived: Number(_viem.formatUnits.call(void 0, receipt.amountReceivedLD, decimals)),
2141
+ fees: feeDetails.map((detail) => ({
2142
+ amountLD: detail.feeAmountLD,
2143
+ amount: Number(_viem.formatUnits.call(void 0, detail.feeAmountLD, decimals)),
2144
+ description: detail.description
2145
+ })),
2146
+ minAmountLD: limit.minAmountLD,
2147
+ maxAmountLD: limit.maxAmountLD
2148
+ };
2083
2149
  }
2084
2150
  /**
2085
2151
  * Source-chain cost of the `send` transaction.
@@ -2089,28 +2155,32 @@ var LayerZeroService = class {
2089
2155
  * before approving does not have. When the simulation reverts, fall back to a
2090
2156
  * measured constant so the caller still has a cost to show, flagged as such.
2091
2157
  */
2092
- async estimateSendGas(plan, fee, oft) {
2158
+ async estimateSendGas(plan, fee, oft, sender) {
2093
2159
  const gasPriceWei = await this.gasPrice(plan);
2094
2160
  if (gasPriceWei === void 0) return void 0;
2095
2161
  let gas = FALLBACK_SEND_GAS;
2096
2162
  let source = "fallback";
2097
- const account = this.signerAddress();
2163
+ const account = _nullishCoalesce(sender, () => ( this.signerAddress()));
2098
2164
  if (account) {
2165
+ const sendFee = {
2166
+ nativeFee: padFee(fee.nativeFee, plan.feeBufferBps),
2167
+ lzTokenFee: fee.lzTokenFee
2168
+ };
2099
2169
  try {
2100
2170
  gas = await plan.pc.estimateContractGas({
2101
2171
  address: plan.oftAdapter,
2102
2172
  abi: OFT_ADAPTER_ABI,
2103
2173
  functionName: "send",
2104
2174
  args: [
2105
- this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _43 => _43.amountReceivedLD]), () => ( plan.amountLD))),
2106
- fee,
2175
+ this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _45 => _45.amountReceivedLD]), () => ( plan.amountLD))),
2176
+ sendFee,
2107
2177
  account
2108
2178
  ],
2109
2179
  account,
2110
- value: fee.nativeFee
2180
+ value: sendFee.nativeFee
2111
2181
  });
2112
2182
  source = "simulated";
2113
- } catch (e6) {
2183
+ } catch (e5) {
2114
2184
  }
2115
2185
  }
2116
2186
  return { gas, gasPriceWei, costWei: gas * gasPriceWei, source };
@@ -2121,41 +2191,58 @@ var LayerZeroService = class {
2121
2191
  const fees = await plan.pc.estimateFeesPerGas();
2122
2192
  const price = _nullishCoalesce(fees.maxFeePerGas, () => ( fees.gasPrice));
2123
2193
  if (price !== void 0) return price;
2124
- } catch (e7) {
2194
+ } catch (e6) {
2125
2195
  }
2126
2196
  try {
2127
2197
  return await plan.pc.getGasPrice();
2128
- } catch (e8) {
2198
+ } catch (e7) {
2129
2199
  return void 0;
2130
2200
  }
2131
2201
  }
2132
2202
  /**
2133
- * Top the adapter's allowance up to `amountLD` when it is short. Only the
2134
- * lockbox chain reaches here — remote deployments burn from the caller.
2203
+ * The `approve` amounts to send, in order, before `send` can pull `amountLD` —
2204
+ * empty when the adapter needs no allowance or the existing one already covers
2205
+ * the send.
2206
+ *
2207
+ * A short but non-zero allowance takes two approvals, not one. Legacy USDT on
2208
+ * Ethereum — the only token a lockbox pathway ever approves — refuses to
2209
+ * overwrite a non-zero allowance with another non-zero value:
2210
+ *
2211
+ * require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)))
2212
+ *
2213
+ * so a holder who once approved 5 USDT and now bridges 10 gets a reverted
2214
+ * approve and pays the gas for nothing. Zeroing first is harmless on every
2215
+ * other ERC-20, so it is unconditional rather than sniffed per token.
2135
2216
  */
2136
- async ensureAllowance(plan, wc, owner, amountLD) {
2137
- const { src, asset, srcToken, oftAdapter, pc, chain: srcChain } = plan;
2138
- const allowance = await pc.readContract({
2139
- address: srcToken.address,
2217
+ async approvalPlan(plan, owner, amountLD) {
2218
+ if (plan.srcToken.approvalRequired === false) return [];
2219
+ const allowance = await plan.pc.readContract({
2220
+ address: plan.srcToken.address,
2140
2221
  abi: _viem.erc20Abi,
2141
2222
  functionName: "allowance",
2142
- args: [owner, oftAdapter]
2223
+ args: [owner, plan.oftAdapter]
2143
2224
  });
2144
- if (allowance >= amountLD) return;
2225
+ if (allowance >= amountLD) return [];
2226
+ return allowance > 0n ? [0n, amountLD] : [amountLD];
2227
+ }
2228
+ /** Send one `approve` and wait for it to land. */
2229
+ async sendApprove(plan, wc, value) {
2230
+ const { src, asset, srcToken, oftAdapter, pc, chain: srcChain } = plan;
2231
+ const what = value === 0n ? "allowance reset" : "approve";
2145
2232
  let approveHash;
2146
2233
  try {
2147
2234
  approveHash = await wc.writeContract({
2148
2235
  address: srcToken.address,
2149
2236
  abi: _viem.erc20Abi,
2150
2237
  functionName: "approve",
2151
- args: [oftAdapter, amountLD],
2238
+ args: [oftAdapter, value],
2152
2239
  account: wc.account,
2153
2240
  chain: srcChain
2154
2241
  });
2155
2242
  } catch (err) {
2156
2243
  throw new (0, _chunkQQ26UI3Fcjs.StableTransactionError)({
2157
2244
  phase: "approve",
2158
- shortMessage: `bridge: ${asset} approve failed`,
2245
+ shortMessage: `bridge: ${asset} ${what} failed`,
2159
2246
  chainId: src.id,
2160
2247
  cause: err
2161
2248
  });
@@ -2164,7 +2251,7 @@ var LayerZeroService = class {
2164
2251
  if (approveReceipt.status === "reverted") {
2165
2252
  throw new (0, _chunkQQ26UI3Fcjs.StableTransactionError)({
2166
2253
  phase: "approve",
2167
- shortMessage: `bridge: ${asset} approve reverted on-chain`,
2254
+ shortMessage: `bridge: ${asset} ${what} reverted on-chain`,
2168
2255
  txHash: approveReceipt.transactionHash,
2169
2256
  chainId: src.id
2170
2257
  });
@@ -2180,11 +2267,18 @@ var LayerZeroService = class {
2180
2267
  );
2181
2268
  }
2182
2269
  const plan = this.plan({ ...params, recipient: _nullishCoalesce(params.recipient, () => ( from)) }, "bridge");
2183
- const { src, srcToken, oftAdapter, amountLD, chain: srcChain } = plan;
2270
+ const { src, oftAdapter, amountLD, chain: srcChain } = plan;
2271
+ const refundAddress = this.resolveRefundAddress(params.refundAddress, from, "bridge");
2184
2272
  const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
2185
2273
  account: this.config.account,
2186
2274
  chain: srcChain,
2187
- transport: _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, src.rpc)))
2275
+ // `config.transport` is the caller's *Stable-chain* transport. A browser
2276
+ // wallet transport is chain-agnostic — the wallet holds the keys and picks
2277
+ // the network — but an HTTP one is a broadcast channel bound to a single
2278
+ // chain, and broadcasting an Ethereum send to the Stable RPC fails. With a
2279
+ // local account we sign here and only need somewhere to broadcast, so the
2280
+ // source chain's own endpoint is always the right answer.
2281
+ transport: this.config.account ? _viem.http.call(void 0, plan.rpc) : _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, plan.rpc)))
2188
2282
  })));
2189
2283
  try {
2190
2284
  await wc.switchChain({ id: src.id });
@@ -2197,22 +2291,23 @@ var LayerZeroService = class {
2197
2291
  });
2198
2292
  }
2199
2293
  const quote = usableQuote(params.quote, amountLD);
2200
- const { nativeFee, lzTokenFee, oft } = _optionalChain([quote, 'optionalAccess', _44 => _44.nativeFee]) !== void 0 && _optionalChain([quote, 'optionalAccess', _45 => _45.lzTokenFee]) !== void 0 ? { nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee, oft: quote.oft } : await this.quotePathway(plan, "bridge").then((r) => ({ ...r.fee, oft: r.oft }));
2201
- const sendParams = this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _46 => _46.amountReceivedLD]), () => ( amountLD)));
2202
- if (srcToken.approvalRequired !== false) {
2203
- await this.ensureAllowance(plan, wc, from, amountLD);
2294
+ 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 }));
2295
+ const sendParams = this.buildSendParams(plan, _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _48 => _48.amountReceivedLD]), () => ( amountLD)));
2296
+ const sendFee = padFee(nativeFee, plan.feeBufferBps);
2297
+ for (const value of await this.approvalPlan(plan, from, amountLD)) {
2298
+ await this.sendApprove(plan, wc, value);
2204
2299
  }
2205
2300
  try {
2206
2301
  const hash = await wc.writeContract({
2207
2302
  address: oftAdapter,
2208
2303
  abi: OFT_ADAPTER_ABI,
2209
2304
  functionName: "send",
2210
- args: [sendParams, { nativeFee, lzTokenFee }, from],
2211
- value: nativeFee,
2305
+ args: [sendParams, { nativeFee: sendFee, lzTokenFee }, refundAddress],
2306
+ value: sendFee,
2212
2307
  account: wc.account,
2213
2308
  chain: srcChain
2214
2309
  });
2215
- return { txHash: hash, toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _47 => _47.amountReceived]), () => ( params.amount)) };
2310
+ return { txHash: hash, toAmount: _nullishCoalesce(_optionalChain([oft, 'optionalAccess', _49 => _49.amountReceived]), () => ( params.amount)) };
2216
2311
  } catch (err) {
2217
2312
  throw new (0, _chunkQQ26UI3Fcjs.StableTransactionError)({
2218
2313
  phase: "send",
@@ -2222,19 +2317,96 @@ var LayerZeroService = class {
2222
2317
  });
2223
2318
  }
2224
2319
  }
2320
+ /**
2321
+ * Everything needed to send a bridge, without signing or sending any of it: the
2322
+ * ordered transactions, the quote they were built from, and the `SendParam` the
2323
+ * send step encodes, decoded.
2324
+ *
2325
+ * This is the split-responsibility shape — a backend quotes, builds and checks
2326
+ * while the holder's wallet only signs — and the only way to inspect a bridge
2327
+ * before it exists. Send `steps` in order from `from`, each one confirmed before
2328
+ * the next: an allowance reset and an approve where the lockbox pathway needs
2329
+ * them, then the OFT `send` carrying its fee as `value`.
2330
+ *
2331
+ * Refuses to build when the adapter will not answer `quoteOFT`: the receipt is
2332
+ * what floors the send at what it would actually deliver, and guessing the floor
2333
+ * is how a send reverts with `SlippageExceeded` after the gas is spent.
2334
+ */
2335
+ async prepareBridgeCalldata(params) {
2336
+ const method = "prepareBridgeCalldata";
2337
+ const from = params.from;
2338
+ if (!_viem.isAddress.call(void 0, from)) {
2339
+ throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
2340
+ "from",
2341
+ params.from,
2342
+ `${method}: invalid from address "${params.from}" \u2014 the address that will sign these transactions`
2343
+ );
2344
+ }
2345
+ const refundAddress = this.resolveRefundAddress(params.refundAddress, from, method);
2346
+ const plan = this.plan({ ...params, recipient: _nullishCoalesce(params.recipient, () => ( from)) }, method);
2347
+ const supplied = usableQuote(params.quote, plan.amountLD);
2348
+ 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 });
2349
+ const oft = quote.oft;
2350
+ const nativeFee = padFee(quote.nativeFee, plan.feeBufferBps);
2351
+ const lzTokenFee = quote.lzTokenFee;
2352
+ const sendParam = this.buildSendParams(plan, oft.amountReceivedLD);
2353
+ const steps = (await this.approvalPlan(plan, from, plan.amountLD)).map((value) => ({
2354
+ chainId: plan.src.id,
2355
+ to: plan.srcToken.address,
2356
+ data: _viem.encodeFunctionData.call(void 0, {
2357
+ abi: _viem.erc20Abi,
2358
+ functionName: "approve",
2359
+ args: [plan.oftAdapter, value]
2360
+ }),
2361
+ value: 0n
2362
+ }));
2363
+ steps.push({
2364
+ chainId: plan.src.id,
2365
+ to: plan.oftAdapter,
2366
+ data: _viem.encodeFunctionData.call(void 0, {
2367
+ abi: OFT_ADAPTER_ABI,
2368
+ functionName: "send",
2369
+ args: [sendParam, { nativeFee, lzTokenFee }, refundAddress]
2370
+ }),
2371
+ value: nativeFee
2372
+ });
2373
+ return {
2374
+ steps,
2375
+ chainId: plan.src.id,
2376
+ asset: plan.asset,
2377
+ quote,
2378
+ send: {
2379
+ dstEid: sendParam.dstEid,
2380
+ recipient: plan.recipient,
2381
+ to: sendParam.to,
2382
+ amountLD: sendParam.amountLD,
2383
+ minAmountLD: sendParam.minAmountLD,
2384
+ extraOptions: sendParam.extraOptions,
2385
+ nativeFee,
2386
+ lzTokenFee,
2387
+ refundAddress
2388
+ }
2389
+ };
2390
+ }
2225
2391
  /**
2226
2392
  * Estimate how long a USDT0 bridge will take, end to end.
2227
2393
  *
2228
- * Two independent inputs, both cheap:
2229
- * - **observed** — median delivery time of recent messages LayerZero Scan has
2230
- * already settled on this exact pathway.
2231
- * - **formula** — LayerZero's documented estimate from the on-chain ULN config:
2232
- * `srcBlockTime × confirmations + dstBlockTime × (2 + dvnCount)`.
2394
+ * The number comes from the pathway's live ULN config:
2395
+ * `srcBlockTime × confirmations + dstBlockTime × (2 + dvnCount)`, floored at the
2396
+ * source chain's {@link BridgeChainConfig.minDeliverySec} where the confirmation
2397
+ * count is not the whole wait. This is what the DVNs are actually bound by, so it
2398
+ * tracks a config change the moment it lands — and on every pathway that is purely
2399
+ * confirmation-bound it agrees with both usdt0.to's own estimate and the delivery
2400
+ * times Scan reports to well under a percent.
2233
2401
  *
2234
- * `observed` wins when available. The formula assumes `confirmations` is the whole
2235
- * wait, which holds for L1 sources but badly understates rollups: Arbitrum→Stable
2236
- * computes to ~15s (60 blocks × 0.25s) while real deliveries take ~18min, because
2237
- * the DVNs wait for the L2 batch to finalize on L1.
2402
+ * The observed median of recent deliveries on this exact pathway is fetched too
2403
+ * and reported as {@link BridgeTimeEstimate.observedSeconds}, but it no longer
2404
+ * drives `seconds`. History lags the config: after Optimism→Stable was raised to
2405
+ * 1000 confirmations, a 50-message window still reaching back 261 days put the
2406
+ * median at ~15min against a real ~33min, and pathways quiet for months (Unichain,
2407
+ * XAUt0 from Arbitrum) were answering from configs that no longer exist. It stays
2408
+ * the fallback for the case the formula cannot be computed at all — an RPC that
2409
+ * will not serve the ULN read.
2238
2410
  */
2239
2411
  async estimateBridgeTime({
2240
2412
  fromChain,
@@ -2244,8 +2416,8 @@ var LayerZeroService = class {
2244
2416
  }) {
2245
2417
  const src = BRIDGE_CHAIN_CONFIGS[fromChain];
2246
2418
  const dst = BRIDGE_CHAIN_CONFIGS[toChain];
2247
- const from = _nullishCoalesce(fromToken, () => ( _optionalChain([src, 'optionalAccess', _48 => _48.tokens, 'access', _49 => _49.usdt0, 'optionalAccess', _50 => _50.address])));
2248
- const to = _nullishCoalesce(toToken, () => ( _optionalChain([dst, 'optionalAccess', _51 => _51.tokens, 'access', _52 => _52.usdt0, 'optionalAccess', _53 => _53.address])));
2419
+ const from = _nullishCoalesce(fromToken, () => ( _optionalChain([src, 'optionalAccess', _51 => _51.tokens, 'access', _52 => _52.usdt0, 'optionalAccess', _53 => _53.address])));
2420
+ const to = _nullishCoalesce(toToken, () => ( _optionalChain([dst, 'optionalAccess', _54 => _54.tokens, 'access', _55 => _55.usdt0, 'optionalAccess', _56 => _56.address])));
2249
2421
  if (!(src && from)) {
2250
2422
  throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)("fromChain", fromChain, `estimateBridgeTime: no OFT adapter for "${fromChain}"`);
2251
2423
  }
@@ -2265,19 +2437,18 @@ var LayerZeroService = class {
2265
2437
  );
2266
2438
  formulaSeconds = Number.isFinite(computed) ? computed : void 0;
2267
2439
  }
2268
- const seconds = _nullishCoalesce(observedSeconds, () => ( formulaSeconds));
2269
- if (seconds === void 0) {
2440
+ const resolved = resolveDeliverySeconds(formulaSeconds, observedSeconds, src.minDeliverySec);
2441
+ if (!resolved) {
2270
2442
  throw new (0, _chunkQQ26UI3Fcjs.StableQuoteError)({
2271
2443
  provider: "layerzero",
2272
2444
  shortMessage: `estimateBridgeTime: no pathway data for ${fromChain}\u2192${toChain}`
2273
2445
  });
2274
2446
  }
2275
2447
  return {
2276
- seconds,
2277
- source: observedSeconds !== void 0 ? "observed" : "formula",
2448
+ ...resolved,
2278
2449
  observedSeconds,
2279
2450
  formulaSeconds,
2280
- confirmations: Number.isFinite(_optionalChain([uln, 'optionalAccess', _54 => _54.confirmations])) ? _optionalChain([uln, 'optionalAccess', _55 => _55.confirmations]) : void 0,
2451
+ confirmations: Number.isFinite(_optionalChain([uln, 'optionalAccess', _57 => _57.confirmations])) ? _optionalChain([uln, 'optionalAccess', _58 => _58.confirmations]) : void 0,
2281
2452
  dvnCount: Number.isFinite(dvnCount) ? dvnCount : void 0
2282
2453
  };
2283
2454
  }
@@ -2290,7 +2461,7 @@ var LayerZeroService = class {
2290
2461
  const scanUrl = `${LZ_SCAN_UI}/tx/${srcTxHash}`;
2291
2462
  let message;
2292
2463
  try {
2293
- message = await _asyncOptionalChain([(await scanFetch(`/messages/tx/${srcTxHash}`)), 'optionalAccess', async _56 => _56.data, 'optionalAccess', async _57 => _57[0]]);
2464
+ message = await _asyncOptionalChain([(await scanFetch(`/messages/tx/${srcTxHash}`)), 'optionalAccess', async _59 => _59.data, 'optionalAccess', async _60 => _60[0]]);
2294
2465
  } catch (err) {
2295
2466
  if (err instanceof ScanHttpError && err.status === 404) {
2296
2467
  return { state: "pending", srcTxHash, scanUrl };
@@ -2302,15 +2473,15 @@ var LayerZeroService = class {
2302
2473
  });
2303
2474
  }
2304
2475
  if (!message) return { state: "pending", srcTxHash, scanUrl };
2305
- const raw = _nullishCoalesce(_optionalChain([message, 'access', _58 => _58.status, 'optionalAccess', _59 => _59.name]), () => ( ""));
2306
- const sentAt = _optionalChain([message, 'access', _60 => _60.source, 'optionalAccess', _61 => _61.tx, 'optionalAccess', _62 => _62.blockTimestamp]);
2476
+ const raw = _nullishCoalesce(_optionalChain([message, 'access', _61 => _61.status, 'optionalAccess', _62 => _62.name]), () => ( ""));
2477
+ const sentAt = _optionalChain([message, 'access', _63 => _63.source, 'optionalAccess', _64 => _64.tx, 'optionalAccess', _65 => _65.blockTimestamp]);
2307
2478
  return {
2308
2479
  // Everything terminal that is not DELIVERED means the tokens will not land
2309
2480
  // on their own; `raw` carries which of those it was.
2310
2481
  state: raw === "DELIVERED" ? "delivered" : TERMINAL_FAILURE_STATUSES.has(raw) ? "failed" : "inflight",
2311
2482
  raw,
2312
2483
  srcTxHash,
2313
- dstTxHash: _optionalChain([message, 'access', _63 => _63.destination, 'optionalAccess', _64 => _64.tx, 'optionalAccess', _65 => _65.txHash]),
2484
+ dstTxHash: _optionalChain([message, 'access', _66 => _66.destination, 'optionalAccess', _67 => _67.tx, 'optionalAccess', _68 => _68.txHash]),
2314
2485
  elapsedSeconds: sentAt ? Math.max(0, Math.floor(Date.now() / 1e3) - sentAt) : void 0,
2315
2486
  scanUrl
2316
2487
  };
@@ -2318,7 +2489,8 @@ var LayerZeroService = class {
2318
2489
  /** Effective ULN config the DVNs enforce for this pathway, read from the source chain. */
2319
2490
  async getUlnConfig(src, dst, oapp) {
2320
2491
  return cached(`uln:${src.lzEid}-${dst.lzEid}-${oapp.toLowerCase()}`, CACHE_TTL_MS, async () => {
2321
- const pc = _viem.createPublicClient.call(void 0, { chain: makeChain(src.id, src.rpc), transport: _viem.http.call(void 0, src.rpc) });
2492
+ const rpc = this.rpcFor(src);
2493
+ const pc = _viem.createPublicClient.call(void 0, { chain: makeChain(src.id, rpc), transport: _viem.http.call(void 0, rpc) });
2322
2494
  const endpoint = await pc.readContract({ address: oapp, abi: OAPP_ABI, functionName: "endpoint" });
2323
2495
  const sendLib = await pc.readContract({
2324
2496
  address: endpoint,
@@ -2344,7 +2516,7 @@ var LayerZeroService = class {
2344
2516
  const pathwayId = `${src.lzEid}-${dst.lzEid}-${srcOapp.toLowerCase()}-${dstOapp.toLowerCase()}`;
2345
2517
  return cached(`observed:${pathwayId}`, CACHE_TTL_MS, async () => {
2346
2518
  const { data } = await scanFetch(`/messages/pathway/${pathwayId}?limit=50`);
2347
- const durations = (_nullishCoalesce(data, () => ( []))).filter((m) => _optionalChain([m, 'access', _66 => _66.status, 'optionalAccess', _67 => _67.name]) === "DELIVERED").map((m) => (_nullishCoalesce(_optionalChain([m, 'access', _68 => _68.destination, 'optionalAccess', _69 => _69.tx, 'optionalAccess', _70 => _70.blockTimestamp]), () => ( 0))) - (_nullishCoalesce(_optionalChain([m, 'access', _71 => _71.source, 'optionalAccess', _72 => _72.tx, 'optionalAccess', _73 => _73.blockTimestamp]), () => ( 0)))).filter((d) => d > 0);
2519
+ 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
2520
  return durations.length >= 3 ? Math.round(median(durations)) : void 0;
2349
2521
  });
2350
2522
  }
@@ -2424,13 +2596,22 @@ var LayerZeroService = class {
2424
2596
  `${method}: dstNativeDrop must not be negative`
2425
2597
  );
2426
2598
  }
2599
+ const feeBufferBps = resolveFeeBuffer(params.feeBufferBps, method);
2427
2600
  const { src, dst, asset, srcToken, dstToken } = this.resolveRoute(
2428
2601
  fromChain,
2429
2602
  toChain,
2430
2603
  fromToken,
2431
2604
  toToken
2432
2605
  );
2433
- const chain = makeChain(src.id, src.rpc);
2606
+ if (params.fromDecimals !== void 0 && params.fromDecimals !== srcToken.decimals) {
2607
+ throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
2608
+ "fromDecimals",
2609
+ params.fromDecimals,
2610
+ `${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.`
2611
+ );
2612
+ }
2613
+ const rpc = this.rpcFor(src);
2614
+ const chain = makeChain(src.id, rpc);
2434
2615
  return {
2435
2616
  src,
2436
2617
  dst,
@@ -2441,13 +2622,31 @@ var LayerZeroService = class {
2441
2622
  amountLD: _viem.parseUnits.call(void 0, String(amount), srcToken.decimals),
2442
2623
  recipient: recipientAddr,
2443
2624
  nativeDrop: _nullishCoalesce(dstNativeDrop, () => ( 0n)),
2625
+ rpc,
2626
+ feeBufferBps,
2444
2627
  chain,
2445
- pc: _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0, src.rpc) })
2628
+ pc: _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0, rpc) })
2446
2629
  };
2447
2630
  }
2631
+ /**
2632
+ * Where `send` returns messaging fee it did not need. Defaults to the sender,
2633
+ * which is what a caller wants unless they are routing refunds somewhere of
2634
+ * their own — a backend paying the fee on a user's behalf, say.
2635
+ */
2636
+ resolveRefundAddress(refundAddress, sender, method) {
2637
+ if (refundAddress === void 0) return sender;
2638
+ if (!_viem.isAddress.call(void 0, refundAddress)) {
2639
+ throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
2640
+ "refundAddress",
2641
+ refundAddress,
2642
+ `${method}: invalid refundAddress "${refundAddress}"`
2643
+ );
2644
+ }
2645
+ return refundAddress;
2646
+ }
2448
2647
  /** The address that will sign, or undefined when this service has no signer. */
2449
2648
  signerAddress() {
2450
- return _nullishCoalesce(_optionalChain([this, 'access', _74 => _74.config, 'access', _75 => _75.walletClient, 'optionalAccess', _76 => _76.account, 'optionalAccess', _77 => _77.address]), () => ( _optionalChain([this, 'access', _78 => _78.config, 'access', _79 => _79.account, 'optionalAccess', _80 => _80.address])));
2649
+ 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
2650
  }
2452
2651
  /**
2453
2652
  * `SendParam` for one call. `minAmountLD` is the caller's slippage floor: 0 when
@@ -2542,7 +2741,7 @@ async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
2542
2741
  abi: _viem.erc20Abi,
2543
2742
  functionName: "decimals"
2544
2743
  });
2545
- } catch (e9) {
2744
+ } catch (e8) {
2546
2745
  throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
2547
2746
  "tokenDecimals",
2548
2747
  void 0,
@@ -2576,6 +2775,7 @@ async function executeTokenTransfer(wc, publicClient, token, to, amount, tokenDe
2576
2775
  function createStable({
2577
2776
  network = "mainnet" /* Mainnet */,
2578
2777
  rpc: rpcOverride,
2778
+ rpcOverrides,
2579
2779
  account,
2580
2780
  transport,
2581
2781
  walletClient,
@@ -2589,7 +2789,7 @@ function createStable({
2589
2789
  );
2590
2790
  const signerConfig = { account, transport, walletClient };
2591
2791
  const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
2592
- const lz = new LayerZeroService(signerConfig);
2792
+ const lz = new LayerZeroService({ ...signerConfig, rpcOverrides });
2593
2793
  const merkl = new MerklService({ ...signerConfig, rpc, chainId: chain.id, merklApiBase });
2594
2794
  const morpho = earnConfig ? new MorphoService({
2595
2795
  ...signerConfig,
@@ -2614,7 +2814,7 @@ function createStable({
2614
2814
  return morpho;
2615
2815
  }
2616
2816
  function requireSignerAddress() {
2617
- const from = _nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _81 => _81.account, 'optionalAccess', _82 => _82.address]), () => ( _optionalChain([account, 'optionalAccess', _83 => _83.address])));
2817
+ const from = _nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _84 => _84.account, 'optionalAccess', _85 => _85.address]), () => ( _optionalChain([account, 'optionalAccess', _86 => _86.address])));
2618
2818
  if (!from) {
2619
2819
  throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
2620
2820
  "signer",
@@ -2627,7 +2827,7 @@ function createStable({
2627
2827
  async function transfer(params) {
2628
2828
  validateTransferParams(params);
2629
2829
  const { from, to, amount, token, tokenDecimals } = params;
2630
- const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _84 => _84.account]), () => ( account)), () => ( from));
2830
+ const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _87 => _87.account]), () => ( account)), () => ( from));
2631
2831
  const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
2632
2832
  account: acc,
2633
2833
  chain,
@@ -2683,6 +2883,16 @@ function createStable({
2683
2883
  // promise like every other validation error, not a synchronous throw.
2684
2884
  quoteBridge: async (params) => selectBridge(params) === "oft" ? lz.quoteBridge(params) : lifi.quoteBridge(params),
2685
2885
  bridge: async (params) => selectBridge(params) === "oft" ? lz.bridge(params) : lifi.bridge(params),
2886
+ prepareBridgeCalldata: async (params) => {
2887
+ if (selectBridge(params) !== "oft") {
2888
+ throw new (0, _chunkQQ26UI3Fcjs.StableValidationError)(
2889
+ "route",
2890
+ _nullishCoalesce(params.route, () => ( "auto")),
2891
+ `prepareBridgeCalldata: only the native OFT route can be built unsigned, and ${params.fromChain}\u2192${params.toChain} resolved to LI.FI`
2892
+ );
2893
+ }
2894
+ return lz.prepareBridgeCalldata(params);
2895
+ },
2686
2896
  estimateBridgeTime: (params) => lz.estimateBridgeTime(params),
2687
2897
  trackBridge: (srcTxHash) => lz.trackBridge(srcTxHash),
2688
2898
  quoteSwap: (params) => lifi.quoteSwap(params),
@@ -2751,4 +2961,6 @@ function createStableReader(config) {
2751
2961
 
2752
2962
 
2753
2963
 
2754
- 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.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.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;
2964
+
2965
+
2966
+ 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;