@stablechain/sdk 1.0.8 → 1.0.9
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 +66 -6
- package/dist/{chunk-VS6GBRDF.js → chunk-FN7OKBRH.js} +3 -1
- package/dist/{chunk-QQ26UI3F.cjs → chunk-MMZ7HVWZ.cjs} +3 -1
- package/dist/erc3009.cjs +2 -2
- package/dist/erc3009.js +1 -1
- package/dist/index.cjs +117 -106
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +14 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,7 +129,13 @@ const { txHash } = await stable.transfer({
|
|
|
129
129
|
});
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
**ERC-20 (USDC, EURC, …)**
|
|
132
|
+
**ERC-20 (PYUSD, USDC, EURC, …)**
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { STABLE_PYUSD_ADDRESS } from "@stablechain/sdk";
|
|
136
|
+
|
|
137
|
+
await stable.transfer({ to: "0xRecipient", amount: 25, token: STABLE_PYUSD_ADDRESS, tokenDecimals: 6 });
|
|
138
|
+
```
|
|
133
139
|
|
|
134
140
|
```ts
|
|
135
141
|
const { txHash } = await stable.transfer({
|
|
@@ -254,8 +260,9 @@ automatically. Switches the wallet to the source chain (MetaMask will prompt) be
|
|
|
254
260
|
sending.
|
|
255
261
|
|
|
256
262
|
Only a lockbox pathway needs an allowance at all — Ethereum for USDT0 and XAUt0, and
|
|
257
|
-
Sepolia on testnet. Every other deployment
|
|
258
|
-
|
|
263
|
+
Sepolia on testnet. Every other deployment burns from the caller, so no approve and no
|
|
264
|
+
allowance read is issued there. That includes PYUSD on Ethereum, which mints and burns
|
|
265
|
+
through the token instead of escrowing it. Where one is needed and the current
|
|
259
266
|
allowance is short but not zero, it takes **two** approvals: legacy USDT refuses to
|
|
260
267
|
overwrite a non-zero allowance with another non-zero value, so the SDK sends
|
|
261
268
|
`approve(0)` first.
|
|
@@ -337,7 +344,7 @@ one of them. Alongside them:
|
|
|
337
344
|
| Field | Type | What it is |
|
|
338
345
|
|-----------|-----------------|---------------------------------------------------------------------|
|
|
339
346
|
| `chainId` | `number` | The source chain every step must be sent on |
|
|
340
|
-
| `asset` | `"usdt0" \| "xaut0"` | Which OFT mesh this send moves through |
|
|
347
|
+
| `asset` | `"usdt0" \| "xaut0" \| "pyusd"` | Which OFT mesh this send moves through |
|
|
341
348
|
| `quote` | `BridgeQuote` | The quote the steps were built from — supplied or freshly read |
|
|
342
349
|
| `send` | `object` | The `send` step's arguments decoded: `dstEid`, `recipient`, `to`, `amountLD`, `minAmountLD`, `extraOptions`, `nativeFee`, `lzTokenFee`, `refundAddress` |
|
|
343
350
|
|
|
@@ -381,7 +388,7 @@ addresses name, LI.FI otherwise. Forcing `"oft"` on an unsupported pair throws
|
|
|
381
388
|
|
|
382
389
|
#### Native OFT assets
|
|
383
390
|
|
|
384
|
-
|
|
391
|
+
Each OFT asset has its own mesh, with its own adapter and token address per
|
|
385
392
|
chain. A bridge stays within one mesh: the OFT mints its own peer on the far side, so it
|
|
386
393
|
cannot hand back a different token. A cross-asset request (USDT0 in, XAUt0 out) is a
|
|
387
394
|
trade, not a bridge, and `"auto"` sends it to LI.FI.
|
|
@@ -409,6 +416,27 @@ chain burns from the caller and skips the approval transaction entirely.
|
|
|
409
416
|
|
|
410
417
|
BSC is XAUt0-only: it has no USDT0 deployment, so a BSC USDT bridge routes to LI.FI.
|
|
411
418
|
|
|
419
|
+
**PYUSD** (PayPal USD, 6 decimals) — Stable and Ethereum. The OFT is live between those
|
|
420
|
+
two chains only, so `"auto"` takes it for Ethereum ↔ Stable and sends every other PYUSD
|
|
421
|
+
pair to LI.FI.
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
// PYUSD: Ethereum → Stable, over the native OFT mesh
|
|
425
|
+
await stable.bridge({
|
|
426
|
+
fromChain: Chain.Ethereum,
|
|
427
|
+
toChain: Chain.Stable,
|
|
428
|
+
fromToken: "0x6c3ea9036406852006290770BEdFcAbA0e23A0e8", // PYUSD on Ethereum
|
|
429
|
+
toToken: STABLE_PYUSD_ADDRESS,
|
|
430
|
+
amount: 100, // 100 PYUSD
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Neither side is a lockbox: both adapters mint and burn through PYUSD itself, so no
|
|
435
|
+
approval is sent, even on Ethereum. Each direction is rate-limited by the adapter
|
|
436
|
+
(100,000,000 PYUSD per 24 hours as of 2026-10-27), and a send past the limit reverts.
|
|
437
|
+
From other chains, LI.FI had no PYUSD liquidity on Stable when last checked (2026-10-22),
|
|
438
|
+
so those bridges fail with "no available quotes".
|
|
439
|
+
|
|
412
440
|
Addresses per chain are in `BRIDGE_CHAIN_CONFIGS[chain].tokens`, keyed by asset:
|
|
413
441
|
|
|
414
442
|
```ts
|
|
@@ -419,7 +447,7 @@ stableChain.tokens.xaut0?.address; // XAUt0 ERC-20 on Stable
|
|
|
419
447
|
stableChain.tokens.xaut0?.oftAdapter; // its OFT
|
|
420
448
|
|
|
421
449
|
// Which asset an address refers to on a chain, if any
|
|
422
|
-
bridgeAssetFor(stableChain, someToken); // "usdt0" | "xaut0" | undefined
|
|
450
|
+
bridgeAssetFor(stableChain, someToken); // "usdt0" | "xaut0" | "pyusd" | undefined
|
|
423
451
|
```
|
|
424
452
|
|
|
425
453
|
---
|
|
@@ -631,6 +659,21 @@ if (check.ok) await stable.authorization.transferWithAuthorization({ signed });
|
|
|
631
659
|
|
|
632
660
|
`value` is a **raw `bigint`** in the token's smallest unit (USDT0 is 6 decimals) — this is the 6-dp ERC-20 path, not the 18-dp native one. `token` defaults to USDT0; `nonce` defaults to a cryptographically-random 32 bytes; the EIP-712 `domain` is read from the token (EIP-5267 `eip712Domain()`, falling back to `name()` + version `"1"`) unless overridden.
|
|
633
661
|
|
|
662
|
+
PYUSD on Stable implements ERC-3009 too. Pass `token: STABLE_PYUSD_ADDRESS` and the same
|
|
663
|
+
actions work with no domain override, because the `name()` + `"1"` fallback matches its
|
|
664
|
+
on-chain `DOMAIN_SEPARATOR`:
|
|
665
|
+
|
|
666
|
+
```ts
|
|
667
|
+
import { STABLE_PYUSD_ADDRESS } from "@stablechain/sdk";
|
|
668
|
+
|
|
669
|
+
const signed = await stable.authorization.signTransferAuthorization({
|
|
670
|
+
token: STABLE_PYUSD_ADDRESS,
|
|
671
|
+
to: "0xVendor",
|
|
672
|
+
value: 1_250_000n, // 1.25 PYUSD (6 dp)
|
|
673
|
+
validBefore: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
674
|
+
});
|
|
675
|
+
```
|
|
676
|
+
|
|
634
677
|
To relay gaslessly, pass `prepareTransferWithAuthorization`'s output to the [`@stablechain/enterprise`](../enterprise) gas-waiver rail.
|
|
635
678
|
|
|
636
679
|
> **Contract signers:** canonical ERC-3009 uses `ecrecover`, so Safe / EIP-1271 accounts cannot sign an authorization. Use an EOA (optionally a 7702 session key) for the payer.
|
|
@@ -653,6 +696,23 @@ const stable = createStable({
|
|
|
653
696
|
const { txHash } = await stable.earn.deposit({ amount: 100 }); // 100 USDT0
|
|
654
697
|
```
|
|
655
698
|
|
|
699
|
+
Earn works with any Morpho V2 vault, not just USDT0. The SDK reads the vault's own
|
|
700
|
+
`asset()` and that token's decimals, and approves that token, so a PYUSD vault needs only
|
|
701
|
+
its address. `deposit` and `bulkDeposit` accept `vault` per call. Withdraw, redeem and
|
|
702
|
+
the reader always use the vault set in config, so manage a PYUSD position with a client
|
|
703
|
+
configured for that vault:
|
|
704
|
+
|
|
705
|
+
```ts
|
|
706
|
+
const pyusdEarn = createStable({
|
|
707
|
+
network: Network.Mainnet,
|
|
708
|
+
earn: { vault: PYUSD_VAULT },
|
|
709
|
+
walletClient: myWalletClient,
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
await pyusdEarn.earn.deposit({ amount: 100 }); // 100 PYUSD
|
|
713
|
+
await pyusdEarn.earn.withdraw({ amount: 40 }); // 40 PYUSD back
|
|
714
|
+
```
|
|
715
|
+
|
|
656
716
|
| Param | Type | Description |
|
|
657
717
|
|---------------------|----------|-------------------------------------------------------------------|
|
|
658
718
|
| `amount` | `number` | Amount in human-readable units |
|
|
@@ -122,6 +122,7 @@ import { signTypedData } from "viem/actions";
|
|
|
122
122
|
|
|
123
123
|
// src/constants.ts
|
|
124
124
|
var STABLE_USDT_ADDRESS = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
|
|
125
|
+
var STABLE_PYUSD_ADDRESS = "0x28F1f38a9F7dB1f769A71aF0a13E8BACe2ab0E9f";
|
|
125
126
|
var STABLE_VAULT_ADDRESS = "0xb7Df8db22A5DBBFA9ebeb94b3910aec6a4f05c08";
|
|
126
127
|
|
|
127
128
|
// src/errors.ts
|
|
@@ -649,9 +650,10 @@ export {
|
|
|
649
650
|
StableTransactionError,
|
|
650
651
|
StableNetworkError,
|
|
651
652
|
StableBulkError,
|
|
652
|
-
ERC3009_ABI,
|
|
653
653
|
STABLE_USDT_ADDRESS,
|
|
654
|
+
STABLE_PYUSD_ADDRESS,
|
|
654
655
|
STABLE_VAULT_ADDRESS,
|
|
656
|
+
ERC3009_ABI,
|
|
655
657
|
signTransferAuthorization,
|
|
656
658
|
signReceiveAuthorization,
|
|
657
659
|
transferWithAuthorization,
|
|
@@ -122,6 +122,7 @@ var _actions = require('viem/actions');
|
|
|
122
122
|
|
|
123
123
|
// src/constants.ts
|
|
124
124
|
var STABLE_USDT_ADDRESS = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
|
|
125
|
+
var STABLE_PYUSD_ADDRESS = "0x28F1f38a9F7dB1f769A71aF0a13E8BACe2ab0E9f";
|
|
125
126
|
var STABLE_VAULT_ADDRESS = "0xb7Df8db22A5DBBFA9ebeb94b3910aec6a4f05c08";
|
|
126
127
|
|
|
127
128
|
// src/errors.ts
|
|
@@ -662,4 +663,5 @@ function erc3009Actions(client) {
|
|
|
662
663
|
|
|
663
664
|
|
|
664
665
|
|
|
665
|
-
|
|
666
|
+
|
|
667
|
+
exports.StableError = StableError; exports.StableValidationError = StableValidationError; exports.StableQuoteError = StableQuoteError; exports.StableTransactionError = StableTransactionError; exports.StableNetworkError = StableNetworkError; exports.StableBulkError = StableBulkError; exports.STABLE_USDT_ADDRESS = STABLE_USDT_ADDRESS; exports.STABLE_PYUSD_ADDRESS = STABLE_PYUSD_ADDRESS; exports.STABLE_VAULT_ADDRESS = STABLE_VAULT_ADDRESS; exports.ERC3009_ABI = ERC3009_ABI; exports.signTransferAuthorization = signTransferAuthorization; exports.signReceiveAuthorization = signReceiveAuthorization; exports.transferWithAuthorization = transferWithAuthorization; exports.receiveWithAuthorization = receiveWithAuthorization; exports.cancelAuthorization = cancelAuthorization; exports.prepareTransferWithAuthorization = prepareTransferWithAuthorization; exports.verifyAuthorization = verifyAuthorization; exports.getAuthorizationState = getAuthorizationState; exports.getAuthorizationUsage = getAuthorizationUsage; exports.erc3009Actions = erc3009Actions;
|
package/dist/erc3009.cjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunkMMZ7HVWZcjs = require('./chunk-MMZ7HVWZ.cjs');
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
@@ -23,4 +23,4 @@ var _chunkQQ26UI3Fcjs = require('./chunk-QQ26UI3F.cjs');
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
exports.ERC3009_ABI =
|
|
26
|
+
exports.ERC3009_ABI = _chunkMMZ7HVWZcjs.ERC3009_ABI; exports.cancelAuthorization = _chunkMMZ7HVWZcjs.cancelAuthorization; exports.erc3009Actions = _chunkMMZ7HVWZcjs.erc3009Actions; exports.getAuthorizationState = _chunkMMZ7HVWZcjs.getAuthorizationState; exports.getAuthorizationUsage = _chunkMMZ7HVWZcjs.getAuthorizationUsage; exports.prepareTransferWithAuthorization = _chunkMMZ7HVWZcjs.prepareTransferWithAuthorization; exports.receiveWithAuthorization = _chunkMMZ7HVWZcjs.receiveWithAuthorization; exports.signReceiveAuthorization = _chunkMMZ7HVWZcjs.signReceiveAuthorization; exports.signTransferAuthorization = _chunkMMZ7HVWZcjs.signTransferAuthorization; exports.transferWithAuthorization = _chunkMMZ7HVWZcjs.transferWithAuthorization; exports.verifyAuthorization = _chunkMMZ7HVWZcjs.verifyAuthorization;
|