@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 +129 -8
- package/dist/index.cjs +426 -158
- package/dist/index.d.cts +148 -17
- package/dist/index.d.ts +148 -17
- package/dist/index.js +416 -148
- package/package.json +1 -1
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
|
|
174
|
-
|
|
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
|
|
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
|
-
| `
|
|
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 **
|
|
219
|
-
chain pairs with no
|
|
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
|
|
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
|