@stablechain/sdk 1.0.4 → 1.0.5

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.js CHANGED
@@ -1,3 +1,25 @@
1
+ import {
2
+ ERC3009_ABI,
3
+ STABLE_USDT_ADDRESS,
4
+ STABLE_VAULT_ADDRESS,
5
+ StableBulkError,
6
+ StableError,
7
+ StableNetworkError,
8
+ StableQuoteError,
9
+ StableTransactionError,
10
+ StableValidationError,
11
+ cancelAuthorization,
12
+ erc3009Actions,
13
+ getAuthorizationState,
14
+ getAuthorizationUsage,
15
+ prepareTransferWithAuthorization,
16
+ receiveWithAuthorization,
17
+ signReceiveAuthorization,
18
+ signTransferAuthorization,
19
+ transferWithAuthorization,
20
+ verifyAuthorization
21
+ } from "./chunk-VS6GBRDF.js";
22
+
1
23
  // src/stable.ts
2
24
  import {
3
25
  http as http6,
@@ -25,85 +47,6 @@ import {
25
47
  parseUnits
26
48
  } from "viem";
27
49
 
28
- // src/errors.ts
29
- import { BaseError } from "viem";
30
- var StableError = class extends BaseError {
31
- };
32
- var StableValidationError = class extends StableError {
33
- constructor(field, value, reason, opts) {
34
- super(reason, {
35
- name: "StableValidationError",
36
- cause: opts?.cause,
37
- metaMessages: [`Field: ${field}`, `Value: ${formatValue(value)}`]
38
- });
39
- this.field = field;
40
- this.value = value;
41
- }
42
- };
43
- var StableQuoteError = class extends StableError {
44
- constructor(args) {
45
- const meta = [`Provider: ${args.provider}`];
46
- if (args.httpStatus !== void 0)
47
- meta.push(`HTTP status: ${args.httpStatus}`);
48
- if (args.providerCode) meta.push(`Provider code: ${args.providerCode}`);
49
- super(args.shortMessage, {
50
- name: "StableQuoteError",
51
- cause: args.cause,
52
- metaMessages: meta
53
- });
54
- this.provider = args.provider;
55
- this.httpStatus = args.httpStatus;
56
- this.providerCode = args.providerCode;
57
- this.body = args.body;
58
- }
59
- };
60
- var StableTransactionError = class extends StableError {
61
- constructor(args) {
62
- const meta = [`Phase: ${args.phase}`];
63
- if (args.chainId !== void 0) meta.push(`Chain ID: ${args.chainId}`);
64
- if (args.txHash) meta.push(`Tx hash: ${args.txHash}`);
65
- if (args.revertReason) meta.push(`Revert reason: ${args.revertReason}`);
66
- super(args.shortMessage, {
67
- name: "StableTransactionError",
68
- cause: args.cause,
69
- metaMessages: meta
70
- });
71
- this.phase = args.phase;
72
- this.txHash = args.txHash;
73
- this.chainId = args.chainId;
74
- this.revertReason = args.revertReason;
75
- }
76
- };
77
- var StableNetworkError = class extends StableError {
78
- constructor(args) {
79
- super(args.shortMessage, {
80
- name: "StableNetworkError",
81
- cause: args.cause,
82
- metaMessages: args.url ? [`URL: ${args.url}`] : void 0
83
- });
84
- this.url = args.url;
85
- }
86
- };
87
- var StableBulkError = class extends StableError {
88
- constructor(args) {
89
- super(args.shortMessage, { name: "StableBulkError" });
90
- this.results = args.results;
91
- this.succeeded = args.succeeded;
92
- this.failed = args.failed;
93
- }
94
- };
95
- function formatValue(v) {
96
- if (typeof v === "bigint") return `${v}n`;
97
- if (typeof v === "string") return JSON.stringify(v);
98
- if (v === null) return "null";
99
- if (v === void 0) return "undefined";
100
- try {
101
- return JSON.stringify(v);
102
- } catch {
103
- return String(v);
104
- }
105
- }
106
-
107
50
  // src/helpers/chain.ts
108
51
  import { defineChain } from "viem";
109
52
  function makeChain(id, rpc) {
@@ -807,19 +750,27 @@ import {
807
750
  import { stable as stableMainnet, stableTestnet } from "viem/chains";
808
751
 
809
752
  // src/types.ts
810
- var Chain = /* @__PURE__ */ ((Chain3) => {
811
- Chain3["Sepolia"] = "sepolia";
812
- Chain3["StableTestnet"] = "stable-testnet";
813
- Chain3["Stable"] = "stable";
814
- Chain3["Ethereum"] = "ethereum";
815
- Chain3["Arbitrum"] = "arbitrum";
816
- Chain3["Ink"] = "ink";
817
- Chain3["Bera"] = "bera";
818
- Chain3["MegaETH"] = "megaeth";
819
- Chain3["Base"] = "base";
820
- Chain3["BSC"] = "bsc";
821
- Chain3["HyperEVM"] = "hyperevm";
822
- return Chain3;
753
+ var Chain = /* @__PURE__ */ ((Chain2) => {
754
+ Chain2["Sepolia"] = "sepolia";
755
+ Chain2["StableTestnet"] = "stable-testnet";
756
+ Chain2["Stable"] = "stable";
757
+ Chain2["Ethereum"] = "ethereum";
758
+ Chain2["Arbitrum"] = "arbitrum";
759
+ Chain2["Ink"] = "ink";
760
+ Chain2["Bera"] = "bera";
761
+ Chain2["MegaETH"] = "megaeth";
762
+ Chain2["Base"] = "base";
763
+ Chain2["BSC"] = "bsc";
764
+ Chain2["HyperEVM"] = "hyperevm";
765
+ Chain2["Polygon"] = "polygon";
766
+ Chain2["Plasma"] = "plasma";
767
+ Chain2["Optimism"] = "optimism";
768
+ Chain2["Unichain"] = "unichain";
769
+ Chain2["Sei"] = "sei";
770
+ Chain2["Flare"] = "flare";
771
+ Chain2["XLayer"] = "xlayer";
772
+ Chain2["Rootstock"] = "rootstock";
773
+ return Chain2;
823
774
  })(Chain || {});
824
775
  var Network = /* @__PURE__ */ ((Network2) => {
825
776
  Network2["Mainnet"] = "mainnet";
@@ -1259,7 +1210,7 @@ var CHAIN_CONFIGS = {
1259
1210
  id: 11155111,
1260
1211
  rpc: "https://ethereum-sepolia-rpc.publicnode.com",
1261
1212
  usdt: "0xc4DCC311c028e341fd8602D8eB89c5de94625927",
1262
- decimals: 18
1213
+ decimals: 6
1263
1214
  },
1264
1215
  ["stable-testnet" /* StableTestnet */]: {
1265
1216
  id: 2201,
@@ -1277,13 +1228,15 @@ var CHAIN_CONFIGS = {
1277
1228
  ["ethereum" /* Ethereum */]: {
1278
1229
  id: 1,
1279
1230
  rpc: "https://ethereum-rpc.publicnode.com",
1280
- usdt: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
1231
+ // Canonical USDT. Not the OFT adapter — this field is used as an ERC-20
1232
+ // (allowance/approve/balanceOf) and as the token address for LI.FI.
1233
+ usdt: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
1281
1234
  decimals: 6
1282
1235
  },
1283
1236
  ["arbitrum" /* Arbitrum */]: {
1284
1237
  id: 42161,
1285
1238
  rpc: "https://arbitrum-one-rpc.publicnode.com",
1286
- usdt: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92",
1239
+ usdt: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
1287
1240
  decimals: 6
1288
1241
  },
1289
1242
  ["ink" /* Ink */]: {
@@ -1300,7 +1253,7 @@ var CHAIN_CONFIGS = {
1300
1253
  },
1301
1254
  ["megaeth" /* MegaETH */]: {
1302
1255
  id: 4326,
1303
- rpc: "https://carrot.megaeth.com",
1256
+ rpc: "https://mainnet.megaeth.com/rpc",
1304
1257
  usdt: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
1305
1258
  decimals: 6
1306
1259
  },
@@ -1321,6 +1274,55 @@ var CHAIN_CONFIGS = {
1321
1274
  rpc: "https://rpc.hyperliquid.xyz/evm",
1322
1275
  usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
1323
1276
  decimals: 6
1277
+ },
1278
+ ["polygon" /* Polygon */]: {
1279
+ id: 137,
1280
+ rpc: "https://polygon-bor-rpc.publicnode.com",
1281
+ // Polygon's canonical USDT, migrated to USDT0
1282
+ usdt: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
1283
+ decimals: 6
1284
+ },
1285
+ ["plasma" /* Plasma */]: {
1286
+ id: 9745,
1287
+ rpc: "https://rpc.plasma.to",
1288
+ usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
1289
+ decimals: 6
1290
+ },
1291
+ ["optimism" /* Optimism */]: {
1292
+ id: 10,
1293
+ rpc: "https://optimism-rpc.publicnode.com",
1294
+ usdt: "0x01bFF41798a0BcF287b996046Ca68b395DbC1071",
1295
+ decimals: 6
1296
+ },
1297
+ ["unichain" /* Unichain */]: {
1298
+ id: 130,
1299
+ rpc: "https://unichain-rpc.publicnode.com",
1300
+ usdt: "0x9151434b16b9763660705744891fA906F660EcC5",
1301
+ decimals: 6
1302
+ },
1303
+ ["sei" /* Sei */]: {
1304
+ id: 1329,
1305
+ rpc: "https://sei-evm-rpc.publicnode.com",
1306
+ usdt: "0x9151434b16b9763660705744891fA906F660EcC5",
1307
+ decimals: 6
1308
+ },
1309
+ ["flare" /* Flare */]: {
1310
+ id: 14,
1311
+ rpc: "https://flare-api.flare.network/ext/C/rpc",
1312
+ usdt: "0xe7cd86e13AC4309349F30B3435a9d337750fC82D",
1313
+ decimals: 6
1314
+ },
1315
+ ["xlayer" /* XLayer */]: {
1316
+ id: 196,
1317
+ rpc: "https://rpc.xlayer.tech",
1318
+ usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
1319
+ decimals: 6
1320
+ },
1321
+ ["rootstock" /* Rootstock */]: {
1322
+ id: 30,
1323
+ rpc: "https://public-node.rsk.co",
1324
+ usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
1325
+ decimals: 6
1324
1326
  }
1325
1327
  };
1326
1328
 
@@ -1738,16 +1740,24 @@ import {
1738
1740
  var C = CHAIN_CONFIGS;
1739
1741
  var BRIDGE_CHAIN_CONFIGS = {
1740
1742
  // ─── Testnet ───────────────────────────────────────────────────────────────
1741
- ["sepolia" /* Sepolia */]: { ...C["sepolia" /* Sepolia */], lzEid: 40161, oftAdapter: "0xc099cD946d5efCC35A99D64E808c1430cEf08126" },
1742
- ["stable-testnet" /* StableTestnet */]: { ...C["stable-testnet" /* StableTestnet */], lzEid: 40374, oftAdapter: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736" },
1743
+ ["sepolia" /* Sepolia */]: { ...C["sepolia" /* Sepolia */], lzEid: 40161, oftAdapter: "0xc099cD946d5efCC35A99D64E808c1430cEf08126", blockTimeSec: 12 },
1744
+ ["stable-testnet" /* StableTestnet */]: { ...C["stable-testnet" /* StableTestnet */], lzEid: 40374, oftAdapter: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736", blockTimeSec: 0.7 },
1743
1745
  // ─── Mainnet ──────────────────────────────────────────────────────────────
1744
- ["stable" /* Stable */]: { ...C["stable" /* Stable */], lzEid: 30396, oftAdapter: "0xedaba024be4d87974d5aB11C6Dd586963CcCB027" },
1745
- ["ethereum" /* Ethereum */]: { ...C["ethereum" /* Ethereum */], lzEid: 30101, oftAdapter: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee" },
1746
- ["arbitrum" /* Arbitrum */]: { ...C["arbitrum" /* Arbitrum */], lzEid: 30110, oftAdapter: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92" },
1747
- ["ink" /* Ink */]: { ...C["ink" /* Ink */], lzEid: 30339, oftAdapter: "0x1cB6De532588fCA4a21B7209DE7C456AF8434A65" },
1748
- ["bera" /* Bera */]: { ...C["bera" /* Bera */], lzEid: 30362, oftAdapter: "0x3Dc96399109df5ceb2C226664A086140bD0379cB" },
1749
- ["megaeth" /* MegaETH */]: { ...C["megaeth" /* MegaETH */], lzEid: 30398, oftAdapter: "0x9151434b16b9763660705744891fa906f660ecc5" },
1750
- ["hyperevm" /* HyperEVM */]: { ...C["hyperevm" /* HyperEVM */], lzEid: 30367, oftAdapter: "0x904861a24F30EC96ea7CFC3bE9EA4B476d237e98" }
1746
+ ["stable" /* Stable */]: { ...C["stable" /* Stable */], lzEid: 30396, oftAdapter: "0xedaba024be4d87974d5aB11C6Dd586963CcCB027", blockTimeSec: 0.7 },
1747
+ ["ethereum" /* Ethereum */]: { ...C["ethereum" /* Ethereum */], lzEid: 30101, oftAdapter: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee", blockTimeSec: 12 },
1748
+ ["arbitrum" /* Arbitrum */]: { ...C["arbitrum" /* Arbitrum */], lzEid: 30110, oftAdapter: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92", blockTimeSec: 0.25 },
1749
+ ["ink" /* Ink */]: { ...C["ink" /* Ink */], lzEid: 30339, oftAdapter: "0x1cB6De532588fCA4a21B7209DE7C456AF8434A65", blockTimeSec: 1 },
1750
+ ["bera" /* Bera */]: { ...C["bera" /* Bera */], lzEid: 30362, oftAdapter: "0x3Dc96399109df5ceb2C226664A086140bD0379cB", blockTimeSec: 2 },
1751
+ ["megaeth" /* MegaETH */]: { ...C["megaeth" /* MegaETH */], lzEid: 30398, oftAdapter: "0x9151434b16b9763660705744891fa906f660ecc5", blockTimeSec: 1 },
1752
+ ["hyperevm" /* HyperEVM */]: { ...C["hyperevm" /* HyperEVM */], lzEid: 30367, oftAdapter: "0x904861a24F30EC96ea7CFC3bE9EA4B476d237e98", blockTimeSec: 1 },
1753
+ ["polygon" /* Polygon */]: { ...C["polygon" /* Polygon */], lzEid: 30109, oftAdapter: "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13", blockTimeSec: 1.5 },
1754
+ ["plasma" /* Plasma */]: { ...C["plasma" /* Plasma */], lzEid: 30383, oftAdapter: "0x02ca37966753bDdDf11216B73B16C1dE756A7CF9", blockTimeSec: 1 },
1755
+ ["optimism" /* Optimism */]: { ...C["optimism" /* Optimism */], lzEid: 30111, oftAdapter: "0xF03b4d9AC1D5d1E7c4cEf54C2A313b9fe051A0aD", blockTimeSec: 2 },
1756
+ ["unichain" /* Unichain */]: { ...C["unichain" /* Unichain */], lzEid: 30320, oftAdapter: "0xc07bE8994D035631c36fb4a89C918CeFB2f03EC3", blockTimeSec: 1 },
1757
+ ["sei" /* Sei */]: { ...C["sei" /* Sei */], lzEid: 30280, oftAdapter: "0x56Fe74A2e3b484b921c447357203431a3485CC60", blockTimeSec: 0.5 },
1758
+ ["flare" /* Flare */]: { ...C["flare" /* Flare */], lzEid: 30295, oftAdapter: "0x567287d2A9829215a37e3B88843d32f9221E7588", blockTimeSec: 1.1 },
1759
+ ["xlayer" /* XLayer */]: { ...C["xlayer" /* XLayer */], lzEid: 30274, oftAdapter: "0x94BCCa6bdfd6A61817Ab0E960bFedE4984505554", blockTimeSec: 1 },
1760
+ ["rootstock" /* Rootstock */]: { ...C["rootstock" /* Rootstock */], lzEid: 30333, oftAdapter: "0x1a594d5d5d1c426281C1064B07f23F57B2716B61", blockTimeSec: 31 }
1751
1761
  };
1752
1762
  var OFT_ADAPTER_ABI = [
1753
1763
  {
@@ -1812,6 +1822,80 @@ var OFT_ADAPTER_ABI = [
1812
1822
  stateMutability: "payable"
1813
1823
  }
1814
1824
  ];
1825
+ var OAPP_ABI = [
1826
+ { name: "endpoint", type: "function", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] }
1827
+ ];
1828
+ var ENDPOINT_V2_ABI = [
1829
+ {
1830
+ name: "getSendLibrary",
1831
+ type: "function",
1832
+ stateMutability: "view",
1833
+ inputs: [{ name: "_sender", type: "address" }, { name: "_dstEid", type: "uint32" }],
1834
+ outputs: [{ type: "address" }]
1835
+ }
1836
+ ];
1837
+ var SEND_ULN_ABI = [
1838
+ {
1839
+ name: "getUlnConfig",
1840
+ type: "function",
1841
+ stateMutability: "view",
1842
+ inputs: [{ name: "_oapp", type: "address" }, { name: "_remoteEid", type: "uint32" }],
1843
+ outputs: [
1844
+ {
1845
+ name: "rtnConfig",
1846
+ type: "tuple",
1847
+ components: [
1848
+ { name: "confirmations", type: "uint64" },
1849
+ { name: "requiredDVNCount", type: "uint8" },
1850
+ { name: "optionalDVNCount", type: "uint8" },
1851
+ { name: "optionalDVNThreshold", type: "uint8" },
1852
+ { name: "requiredDVNs", type: "address[]" },
1853
+ { name: "optionalDVNs", type: "address[]" }
1854
+ ]
1855
+ }
1856
+ ]
1857
+ }
1858
+ ];
1859
+ var LZ_SCAN_API = "https://scan.layerzero-api.com/v1";
1860
+ var LZ_SCAN_UI = "https://layerzeroscan.com";
1861
+ var CACHE_TTL_MS = 5 * 6e4;
1862
+ var cache = /* @__PURE__ */ new Map();
1863
+ function clearBridgeEstimateCache() {
1864
+ cache.clear();
1865
+ }
1866
+ async function cached(key, ttlMs, load) {
1867
+ const hit = cache.get(key);
1868
+ if (hit && Date.now() - hit.at < ttlMs) return hit.value;
1869
+ const value = await load();
1870
+ cache.set(key, { at: Date.now(), value });
1871
+ return value;
1872
+ }
1873
+ var ScanHttpError = class extends Error {
1874
+ constructor(status, path) {
1875
+ super(`LayerZero Scan ${status} for ${path}`);
1876
+ this.status = status;
1877
+ this.name = "ScanHttpError";
1878
+ }
1879
+ };
1880
+ async function scanFetch(path, timeoutMs = 8e3) {
1881
+ const res = await fetch(`${LZ_SCAN_API}${path}`, { signal: AbortSignal.timeout(timeoutMs) });
1882
+ if (!res.ok) throw new ScanHttpError(res.status, path);
1883
+ return res.json();
1884
+ }
1885
+ var TERMINAL_FAILURE_STATUSES = /* @__PURE__ */ new Set([
1886
+ "FAILED",
1887
+ "BLOCKED",
1888
+ "PAYLOAD_STORED",
1889
+ "APPLICATION_BURNED",
1890
+ "APPLICATION_SKIPPED",
1891
+ "UNRESOLVABLE_COMMAND",
1892
+ "MALFORMED_COMMAND"
1893
+ ]);
1894
+ function median(values) {
1895
+ const sorted = [...values].sort((a, b) => a - b);
1896
+ const mid = Math.floor(sorted.length / 2);
1897
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
1898
+ }
1815
1899
  function buildLzOptions(gasLimit = 0n) {
1816
1900
  return concat([
1817
1901
  numberToHex(3, { size: 2 }),
@@ -1830,9 +1914,20 @@ var LayerZeroService = class {
1830
1914
  constructor(config) {
1831
1915
  this.config = config;
1832
1916
  }
1833
- async quoteBridge({ fromChain, toChain, fromToken, toToken, amount, recipient }) {
1917
+ async quoteBridge(params) {
1918
+ const [fee, estimate] = await Promise.all([
1919
+ this.quoteFee(params, "quoteBridge"),
1920
+ this.estimateBridgeTime(params).catch(() => void 0)
1921
+ ]);
1922
+ return { ...fee, toAmount: params.amount, estimate };
1923
+ }
1924
+ /**
1925
+ * LayerZero messaging fee for a send, with no delivery estimate attached — the
1926
+ * send path needs the fee and nothing else, and must not wait on Scan for it.
1927
+ */
1928
+ async quoteFee({ fromChain, toChain, fromToken, toToken, amount, recipient }, method) {
1834
1929
  const recipientAddr = recipient ?? "0x0000000000000000000000000000000000000000";
1835
- this.validateBridgeParams({ fromChain, toChain, amount, recipient: recipientAddr }, "quoteBridge");
1930
+ this.validateBridgeParams({ fromChain, toChain, amount, recipient: recipientAddr }, method);
1836
1931
  const { src, dst } = this.resolveChains(fromChain, toChain, fromToken, toToken);
1837
1932
  const oftAdapter = src.oftAdapter;
1838
1933
  const amountLD = parseUnits4(String(amount), src.decimals);
@@ -1846,11 +1941,11 @@ var LayerZeroService = class {
1846
1941
  functionName: "quoteSend",
1847
1942
  args: [this.buildSendParams(dst, amountLD, recipientAddr), false]
1848
1943
  });
1849
- return { nativeFee, lzTokenFee, toAmount: amount };
1944
+ return { nativeFee, lzTokenFee };
1850
1945
  } catch (err) {
1851
1946
  throw new StableQuoteError({
1852
1947
  provider: "layerzero",
1853
- shortMessage: "quoteBridge: quoteSend call failed",
1948
+ shortMessage: `${method}: quoteSend call failed`,
1854
1949
  cause: err
1855
1950
  });
1856
1951
  }
@@ -1887,8 +1982,7 @@ var LayerZeroService = class {
1887
1982
  }
1888
1983
  const amountLD = parseUnits4(String(amount), src.decimals);
1889
1984
  const sendParams = this.buildSendParams(dst, amountLD, recipientAddr);
1890
- const lzFee = quote?.nativeFee !== void 0 && quote?.lzTokenFee !== void 0 ? { nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee } : await this.quoteBridge({ fromChain, toChain, fromToken, toToken, amount, recipient: recipientAddr });
1891
- const { nativeFee = 0n, lzTokenFee = 0n } = lzFee;
1985
+ const { nativeFee, lzTokenFee } = quote?.nativeFee !== void 0 && quote?.lzTokenFee !== void 0 ? { nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee } : await this.quoteFee({ fromChain, toChain, fromToken, toToken, amount, recipient: recipientAddr }, "bridge");
1892
1986
  const allowance = await pc.readContract({
1893
1987
  address: src.usdt,
1894
1988
  abi: erc20Abi4,
@@ -1944,6 +2038,125 @@ var LayerZeroService = class {
1944
2038
  });
1945
2039
  }
1946
2040
  }
2041
+ /**
2042
+ * Estimate how long a USDT0 bridge will take, end to end.
2043
+ *
2044
+ * Two independent inputs, both cheap:
2045
+ * - **observed** — median delivery time of recent messages LayerZero Scan has
2046
+ * already settled on this exact pathway.
2047
+ * - **formula** — LayerZero's documented estimate from the on-chain ULN config:
2048
+ * `srcBlockTime × confirmations + dstBlockTime × (2 + dvnCount)`.
2049
+ *
2050
+ * `observed` wins when available. The formula assumes `confirmations` is the whole
2051
+ * wait, which holds for L1 sources but badly understates rollups: Arbitrum→Stable
2052
+ * computes to ~15s (60 blocks × 0.25s) while real deliveries take ~18min, because
2053
+ * the DVNs wait for the L2 batch to finalize on L1.
2054
+ */
2055
+ async estimateBridgeTime({ fromChain, toChain }) {
2056
+ const src = BRIDGE_CHAIN_CONFIGS[fromChain];
2057
+ const dst = BRIDGE_CHAIN_CONFIGS[toChain];
2058
+ if (!src?.oftAdapter) {
2059
+ throw new StableValidationError("fromChain", fromChain, `estimateBridgeTime: no OFT adapter for "${fromChain}"`);
2060
+ }
2061
+ if (!dst?.oftAdapter) {
2062
+ throw new StableValidationError("toChain", toChain, `estimateBridgeTime: no OFT adapter for "${toChain}"`);
2063
+ }
2064
+ const [uln, observedSeconds] = await Promise.all([
2065
+ this.getUlnConfig(src, dst).catch(() => void 0),
2066
+ this.observedBridgeSeconds(src, dst).catch(() => void 0)
2067
+ ]);
2068
+ const dvnCount = uln ? uln.requiredDVNCount + uln.optionalDVNCount : void 0;
2069
+ let formulaSeconds;
2070
+ if (uln && dvnCount !== void 0 && src.blockTimeSec && dst.blockTimeSec) {
2071
+ const computed = Math.round(
2072
+ uln.confirmations * src.blockTimeSec + dst.blockTimeSec * (2 + dvnCount)
2073
+ );
2074
+ formulaSeconds = Number.isFinite(computed) ? computed : void 0;
2075
+ }
2076
+ const seconds = observedSeconds ?? formulaSeconds;
2077
+ if (seconds === void 0) {
2078
+ throw new StableQuoteError({
2079
+ provider: "layerzero",
2080
+ shortMessage: `estimateBridgeTime: no pathway data for ${fromChain}\u2192${toChain}`
2081
+ });
2082
+ }
2083
+ return {
2084
+ seconds,
2085
+ source: observedSeconds !== void 0 ? "observed" : "formula",
2086
+ observedSeconds,
2087
+ formulaSeconds,
2088
+ confirmations: Number.isFinite(uln?.confirmations) ? uln?.confirmations : void 0,
2089
+ dvnCount: Number.isFinite(dvnCount) ? dvnCount : void 0
2090
+ };
2091
+ }
2092
+ /**
2093
+ * Follow an in-flight bridge by its source transaction hash. Poll this after
2094
+ * `bridge()` to drive a progress UI; a message LayerZero Scan has not indexed
2095
+ * yet reports `"pending"` rather than throwing.
2096
+ */
2097
+ async trackBridge(srcTxHash) {
2098
+ const scanUrl = `${LZ_SCAN_UI}/tx/${srcTxHash}`;
2099
+ let message;
2100
+ try {
2101
+ message = (await scanFetch(`/messages/tx/${srcTxHash}`))?.data?.[0];
2102
+ } catch (err) {
2103
+ if (err instanceof ScanHttpError && err.status === 404) {
2104
+ return { state: "pending", srcTxHash, scanUrl };
2105
+ }
2106
+ throw new StableQuoteError({
2107
+ provider: "layerzero",
2108
+ shortMessage: `trackBridge: LayerZero Scan lookup failed for ${srcTxHash}`,
2109
+ cause: err
2110
+ });
2111
+ }
2112
+ if (!message) return { state: "pending", srcTxHash, scanUrl };
2113
+ const raw = message.status?.name ?? "";
2114
+ const sentAt = message.source?.tx?.blockTimestamp;
2115
+ return {
2116
+ // Everything terminal that is not DELIVERED means the tokens will not land
2117
+ // on their own; `raw` carries which of those it was.
2118
+ state: raw === "DELIVERED" ? "delivered" : TERMINAL_FAILURE_STATUSES.has(raw) ? "failed" : "inflight",
2119
+ raw,
2120
+ srcTxHash,
2121
+ dstTxHash: message.destination?.tx?.txHash,
2122
+ elapsedSeconds: sentAt ? Math.max(0, Math.floor(Date.now() / 1e3) - sentAt) : void 0,
2123
+ scanUrl
2124
+ };
2125
+ }
2126
+ /** Effective ULN config the DVNs enforce for this pathway, read from the source chain. */
2127
+ async getUlnConfig(src, dst) {
2128
+ return cached(`uln:${src.lzEid}-${dst.lzEid}`, CACHE_TTL_MS, async () => {
2129
+ const pc = createPublicClient5({ chain: makeChain(src.id, src.rpc), transport: http5(src.rpc) });
2130
+ const oapp = src.oftAdapter;
2131
+ const endpoint = await pc.readContract({ address: oapp, abi: OAPP_ABI, functionName: "endpoint" });
2132
+ const sendLib = await pc.readContract({
2133
+ address: endpoint,
2134
+ abi: ENDPOINT_V2_ABI,
2135
+ functionName: "getSendLibrary",
2136
+ args: [oapp, dst.lzEid]
2137
+ });
2138
+ const cfg = await pc.readContract({
2139
+ address: sendLib,
2140
+ abi: SEND_ULN_ABI,
2141
+ functionName: "getUlnConfig",
2142
+ args: [oapp, dst.lzEid]
2143
+ });
2144
+ return {
2145
+ confirmations: Number(cfg.confirmations),
2146
+ requiredDVNCount: Number(cfg.requiredDVNCount),
2147
+ optionalDVNCount: Number(cfg.optionalDVNCount)
2148
+ };
2149
+ });
2150
+ }
2151
+ /** Median delivery time of recently delivered messages on this pathway, in seconds. */
2152
+ async observedBridgeSeconds(src, dst) {
2153
+ const pathwayId = `${src.lzEid}-${dst.lzEid}-${src.oftAdapter.toLowerCase()}-${dst.oftAdapter.toLowerCase()}`;
2154
+ return cached(`observed:${pathwayId}`, CACHE_TTL_MS, async () => {
2155
+ const { data } = await scanFetch(`/messages/pathway/${pathwayId}?limit=50`);
2156
+ const durations = (data ?? []).filter((m) => m.status?.name === "DELIVERED").map((m) => (m.destination?.tx?.blockTimestamp ?? 0) - (m.source?.tx?.blockTimestamp ?? 0)).filter((d) => d > 0);
2157
+ return durations.length >= 3 ? Math.round(median(durations)) : void 0;
2158
+ });
2159
+ }
1947
2160
  validateBridgeParams(params, method) {
1948
2161
  if (!isAddress4(params.recipient)) {
1949
2162
  throw new StableValidationError(
@@ -2119,6 +2332,12 @@ function createStable({
2119
2332
  chainId: chain.id,
2120
2333
  vault: earnConfig.vault
2121
2334
  }) : null;
2335
+ const erc3009Client = walletClient ? walletClient : account ? createWalletClient5({
2336
+ account,
2337
+ chain,
2338
+ transport: transport ?? http6(rpc)
2339
+ }) : createPublicClient6({ chain, transport: http6(rpc) });
2340
+ const authorization = erc3009Actions(erc3009Client);
2122
2341
  function requireEarn() {
2123
2342
  if (!morpho) {
2124
2343
  throw new StableValidationError(
@@ -2197,8 +2416,11 @@ function createStable({
2197
2416
  transfer,
2198
2417
  quoteBridge: (params) => selectBridge(params) === "usdt0" ? lz.quoteBridge(params) : lifi.quoteBridge(params),
2199
2418
  bridge: (params) => selectBridge(params) === "usdt0" ? lz.bridge(params) : lifi.bridge(params),
2419
+ estimateBridgeTime: (params) => lz.estimateBridgeTime(params),
2420
+ trackBridge: (srcTxHash) => lz.trackBridge(srcTxHash),
2200
2421
  quoteSwap: (params) => lifi.quoteSwap(params),
2201
2422
  swap: (params) => lifi.swap(params),
2423
+ authorization,
2202
2424
  earn: {
2203
2425
  deposit: (params) => requireEarn().deposit(params),
2204
2426
  bulkDeposit: (items, options) => requireEarn().bulkDeposit(items, options),
@@ -2234,14 +2456,11 @@ function createStableReader(config) {
2234
2456
  }
2235
2457
  };
2236
2458
  }
2237
-
2238
- // src/constants.ts
2239
- var STABLE_USDT_ADDRESS = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
2240
- var STABLE_VAULT_ADDRESS = "0xb7Df8db22A5DBBFA9ebeb94b3910aec6a4f05c08";
2241
2459
  export {
2242
2460
  BRIDGE_CHAIN_CONFIGS,
2243
2461
  CHAIN_CONFIGS,
2244
2462
  Chain,
2463
+ ERC3009_ABI,
2245
2464
  Network,
2246
2465
  STABLE_USDT_ADDRESS,
2247
2466
  STABLE_VAULT_ADDRESS,
@@ -2251,6 +2470,17 @@ export {
2251
2470
  StableQuoteError,
2252
2471
  StableTransactionError,
2253
2472
  StableValidationError,
2473
+ cancelAuthorization,
2474
+ clearBridgeEstimateCache,
2254
2475
  createStable,
2255
- createStableReader
2476
+ createStableReader,
2477
+ erc3009Actions,
2478
+ getAuthorizationState,
2479
+ getAuthorizationUsage,
2480
+ prepareTransferWithAuthorization,
2481
+ receiveWithAuthorization,
2482
+ signReceiveAuthorization,
2483
+ signTransferAuthorization,
2484
+ transferWithAuthorization,
2485
+ verifyAuthorization
2256
2486
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stablechain/sdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Stable SDK",
5
5
  "keywords": ["stable", "usdt", "gasless", "web3", "yield"],
6
6
  "license": "MIT",
@@ -22,6 +22,11 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "import": "./dist/index.js",
24
24
  "default": "./dist/index.cjs"
25
+ },
26
+ "./erc3009": {
27
+ "types": "./dist/erc3009.d.ts",
28
+ "import": "./dist/erc3009.js",
29
+ "default": "./dist/erc3009.cjs"
25
30
  }
26
31
  },
27
32
  "scripts": {