@stablechain/sdk 1.0.5 → 1.0.6

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 CHANGED
@@ -79,7 +79,7 @@ interface StableClient {
79
79
  transfer(params: TransferParams): Promise<OperationResult>;
80
80
  quoteBridge(params: BridgeParams): Promise<BridgeQuote>;
81
81
  bridge(params: BridgeParams): Promise<OperationResult>;
82
- estimateBridgeTime(params: Pick<BridgeParams, "fromChain" | "toChain">): Promise<BridgeTimeEstimate>;
82
+ estimateBridgeTime(params: Pick<BridgeParams, "fromChain" | "toChain"> & Partial<Pick<BridgeParams, "fromToken" | "toToken">>): Promise<BridgeTimeEstimate>;
83
83
  trackBridge(srcTxHash: string): Promise<BridgeStatus>;
84
84
  quoteSwap(params: SwapParams): Promise<SwapQuote>;
85
85
  swap(params: SwapParams): Promise<OperationResult>;
@@ -170,20 +170,65 @@ console.log("You receive:", quote.toAmount); // human units, on toChain
170
170
 
171
171
  `BridgeQuote.toAmount` is the expected destination-side amount in human units.
172
172
 
173
- On USDT0 (LayerZero OFT) routes the quote also carries `estimate` — how long delivery
174
- should take. See [`estimateBridgeTime`](#estimatebridgetimeparams--bridgetimeestimate).
173
+ On native OFT routes the quote is fully costed — `toAmount` comes from the adapter's
174
+ own `quoteOFT`, so it is the amount that actually lands, net of any OFT fee and of
175
+ dust dropped by decimal conversion:
176
+
177
+ | Field | Type | What it is |
178
+ |----------------|---------------------|-------------------------------------------------------------------------------|
179
+ | `toAmount` | `number` | What the recipient receives, human units |
180
+ | `nativeFee` | `bigint` | LayerZero messaging fee (wei), paid as the `send` call's `value` |
181
+ | `lzTokenFee` | `bigint` | Same fee if paid in LZ token instead — `0n` on every route the SDK sends |
182
+ | `oft` | `BridgeOftQuote` | The adapter's own accounting: `amountSentLD`, `amountReceivedLD`, `fees[]`, and the pathway's `minAmountLD`/`maxAmountLD` |
183
+ | `srcGas` | `BridgeGasEstimate` | Source-chain execution cost: `gas`, `gasPriceWei`, `costWei`, `source` |
184
+ | `totalCostWei` | `bigint` | `srcGas.costWei + nativeFee` — everything leaving the sender's native balance |
185
+ | `estimate` | `BridgeTimeEstimate`| Delivery time — see [`estimateBridgeTime`](#estimatebridgetimeparams--bridgetimeestimate) |
175
186
 
176
187
  ```ts
188
+ import { formatEther } from "viem";
189
+
190
+ console.log("You receive:", quote.toAmount);
191
+ for (const fee of quote.oft?.fees ?? []) {
192
+ // Empty on every pathway the SDK configures today; a deployment that charges an
193
+ // OFT-level fee reports it here. Negative means a credit to the sender.
194
+ console.log(`${fee.description}: ${fee.amount}`);
195
+ }
196
+ if (quote.totalCostWei) {
197
+ console.log(`Costs ${formatEther(quote.totalCostWei)} of native token to send`);
198
+ }
177
199
  if (quote.estimate) {
178
200
  console.log(`Arrives in ~${Math.round(quote.estimate.seconds / 60)} min`);
179
201
  }
180
202
  ```
181
203
 
204
+ `srcGas.source` says how much to trust the cost: `"simulated"` means the real `send`
205
+ was simulated, `"fallback"` means the simulation reverted — which is what happens when
206
+ you quote before approving the adapter — and `gas` is a typical-send constant instead.
207
+ Every field above is absent on LI.FI routes, and `oft`/`srcGas` are also absent when
208
+ the adapter or the RPC would not answer, so treat them as optional rather than assuming
209
+ a native route populates them.
210
+
211
+ Both `quoteBridge()` and `bridge()` accept `dstNativeDrop` — wei of the **destination**
212
+ chain's native token to airdrop to the recipient, what bridge UIs call *gas on
213
+ destination*. The executor prices it into `nativeFee`, so quote with the same value you
214
+ send with:
215
+
216
+ ```ts
217
+ const quote = await stable.quoteBridge({ ...params, dstNativeDrop: 10n ** 14n });
218
+ await stable.bridge({ ...params, dstNativeDrop: 10n ** 14n, quote });
219
+ ```
220
+
221
+ Each pathway's executor caps the drop it will accept; asking for more makes the quote
222
+ throw `StableQuoteError` rather than silently clamping.
223
+
182
224
  ---
183
225
 
184
226
  ### `bridge(params)`
185
227
 
186
- Bridge tokens cross-chain via LI.FI. Handles ERC-20 approval automatically. Switches the wallet to the source chain (MetaMask will prompt) before sending.
228
+ Bridge tokens cross-chain, over the native LayerZero OFT mesh where the pair supports
229
+ it and LI.FI otherwise — see [Bridge routes](#bridge-routes). Handles ERC-20 approval
230
+ automatically (and skips it where the OFT does not need one). Switches the wallet to
231
+ the source chain (MetaMask will prompt) before sending.
187
232
 
188
233
  ```ts
189
234
  import { Chain } from "@stablechain/sdk";
@@ -206,17 +251,80 @@ const { txHash, toAmount } = await stable.bridge({
206
251
  | `amount` | `number` | Amount to bridge (human-readable) |
207
252
  | `fromDecimals` | `number` | *(optional)* Source token decimals — defaults to `6` |
208
253
  | `recipient` | `string` | *(optional)* Destination address — defaults to the signer |
209
- | `quote` | `BridgeQuote` | *(optional)* Reserved for future use; the current implementation re-fetches a LI.FI quote regardless |
254
+ | `dstNativeDrop`| `bigint` | *(optional)* Destination native token (wei) to airdrop to the recipient — native OFT routes only |
255
+ | `quote` | `BridgeQuote` | *(optional)* On native OFT routes, a quote from `quoteBridge()` is reused as-is: it supplies the messaging fee and the slippage floor, skipping two reads. LI.FI routes re-fetch regardless |
256
+ | `route` | `BridgeRoute` | *(optional)* `"auto"` (default), `"oft"`, or `"lifi"` — see [Bridge routes](#bridge-routes) |
210
257
 
211
258
  `txHash` is the **source-chain** transaction hash. Destination delivery is asynchronous —
212
259
  poll [`trackBridge(txHash)`](#trackbridgesrctxhash--bridgestatus) to follow it.
213
260
 
214
261
  ---
215
262
 
263
+ ### Bridge routes
264
+
265
+ `bridge()` and `quoteBridge()` pick between two paths:
266
+
267
+ | Route | What it is | Cost | Speed |
268
+ |----------|-------------------------------------|---------------------------------|--------|
269
+ | `"oft"` | The token's native LayerZero OFT | No protocol fee — gas + LZ messaging fee | Slower |
270
+ | `"lifi"` | The [LI.FI](https://li.fi) aggregator | Charges a fee | Fast |
271
+
272
+ `route` defaults to `"auto"`: the OFT path when both chains deploy the asset the token
273
+ addresses name, LI.FI otherwise. Forcing `"oft"` on an unsupported pair throws
274
+ `StableValidationError` rather than silently charging a LI.FI fee.
275
+
276
+ > `route: "usdt0"` is a deprecated alias for `"oft"`. It does not restrict the route to
277
+ > USDT0 — the token addresses decide the asset.
278
+
279
+ #### Native OFT assets
280
+
281
+ Two assets have their own OFT mesh, each with its own adapter and token address per
282
+ chain. A bridge stays within one mesh: the OFT mints its own peer on the far side, so it
283
+ cannot hand back a different token. A cross-asset request (USDT0 in, XAUt0 out) is a
284
+ trade, not a bridge, and `"auto"` sends it to LI.FI.
285
+
286
+ **USDT0** — Stable, Ethereum, Arbitrum, Bera, Flare, HyperEVM, Ink, MegaETH, Optimism,
287
+ Plasma, Polygon, Rootstock, Sei, Unichain, XLayer.
288
+
289
+ **XAUt0** (Tether Gold, 6 decimals) — Stable, Ethereum, Arbitrum, BSC, HyperEVM, Ink,
290
+ Plasma, Polygon. Every pair among those eight is wired in both directions.
291
+
292
+ ```ts
293
+ // XAUt0: Ethereum → Stable, over the native OFT mesh
294
+ const { txHash } = await stable.bridge({
295
+ fromChain: Chain.Ethereum,
296
+ toChain: Chain.Stable,
297
+ fromToken: "0x68749665FF8D2d112Fa859AA293F07A622782F38", // XAUt on Ethereum
298
+ toToken: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb", // XAUt0 on Stable
299
+ amount: 2, // 2 XAUt0
300
+ });
301
+ ```
302
+
303
+ Ethereum is XAUt0's lockbox — canonical XAUt is escrowed there and minted elsewhere, so
304
+ it is the only chain where a send needs an ERC-20 approve. Bridging XAUt0 from any other
305
+ chain burns from the caller and skips the approval transaction entirely.
306
+
307
+ BSC is XAUt0-only: it has no USDT0 deployment, so a BSC USDT bridge routes to LI.FI.
308
+
309
+ Addresses per chain are in `BRIDGE_CHAIN_CONFIGS[chain].tokens`, keyed by asset:
310
+
311
+ ```ts
312
+ import { BRIDGE_CHAIN_CONFIGS, bridgeAssetFor, Chain } from "@stablechain/sdk";
313
+
314
+ const stableChain = BRIDGE_CHAIN_CONFIGS[Chain.Stable];
315
+ stableChain.tokens.xaut0?.address; // XAUt0 ERC-20 on Stable
316
+ stableChain.tokens.xaut0?.oftAdapter; // its OFT
317
+
318
+ // Which asset an address refers to on a chain, if any
319
+ bridgeAssetFor(stableChain, someToken); // "usdt0" | "xaut0" | undefined
320
+ ```
321
+
322
+ ---
323
+
216
324
  ### `estimateBridgeTime(params)` → `BridgeTimeEstimate`
217
325
 
218
- How long a **USDT0 (LayerZero OFT)** bridge will take, before you send it. Throws for
219
- chain pairs with no USDT0 adapter — LI.FI routes carry their own ETA in the quote.
326
+ How long a **native OFT (LayerZero)** bridge will take, before you send it. Throws for
327
+ chain pairs with no OFT adapter — LI.FI routes carry their own ETA in the quote.
220
328
 
221
329
  ```ts
222
330
  const est = await stable.estimateBridgeTime({
@@ -227,6 +335,19 @@ const est = await stable.estimateBridgeTime({
227
335
  console.log(`~${Math.round(est.seconds / 60)} min`); // ~42 min
228
336
  ```
229
337
 
338
+ Delivery time is per pathway, and USDT0 and XAUt0 are different pathways on the same
339
+ chain pair. Omitting the tokens estimates the USDT0 pathway; pass `fromToken`/`toToken`
340
+ to estimate a specific asset:
341
+
342
+ ```ts
343
+ const est = await stable.estimateBridgeTime({
344
+ fromChain: Chain.Ethereum,
345
+ toChain: Chain.Stable,
346
+ fromToken: "0x68749665FF8D2d112Fa859AA293F07A622782F38", // XAUt on Ethereum
347
+ toToken: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb", // XAUt0 on Stable
348
+ });
349
+ ```
350
+
230
351
  Nearly all of the wait is **source-chain finality**: the DVNs securing the pathway will
231
352
  not attest until the send transaction is buried under `confirmations` source blocks.
232
353
  Destination execution is a few blocks on top.
@@ -273,7 +394,7 @@ Roughly what to expect on mainnet, measured 2026-07-30:
273
394
 
274
395
  ### `trackBridge(srcTxHash)` → `BridgeStatus`
275
396
 
276
- Follow an in-flight USDT0 bridge, via LayerZero Scan. A message Scan has not indexed yet
397
+ Follow an in-flight native OFT bridge, via LayerZero Scan. A message Scan has not indexed yet
277
398
  reports `"pending"` rather than throwing, so it is safe to call immediately after `bridge()`.
278
399
 
279
400
  ```ts