diff --git a/content/docs/ai/mcp-toolkit/configuration.mdx b/content/docs/ai/mcp-toolkit/configuration.mdx index 058d0533..c0f2e384 100644 --- a/content/docs/ai/mcp-toolkit/configuration.mdx +++ b/content/docs/ai/mcp-toolkit/configuration.mdx @@ -111,8 +111,7 @@ server.registerProtocol('ethereum', 'velora', VeloraProtocolEvm) server.registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) server.registerProtocol('ethereum', 'aave', AaveProtocolEvm) server.registerProtocol('ethereum', 'moonpay', MoonPayProtocol, { - apiKey: process.env.MOONPAY_API_KEY, - secretKey: process.env.MOONPAY_SECRET_KEY + apiKey: process.env.MOONPAY_API_KEY }) ``` @@ -240,7 +239,6 @@ server.registerTool( | `WDK_SEED` | Yes | BIP-39 mnemonic for wallet key derivation | | `WDK_INDEXER_API_KEY` | No | API key for WDK Indexer | | `MOONPAY_API_KEY` | No | API key for MoonPay fiat on/off-ramp | -| `MOONPAY_SECRET_KEY` | No | Secret key for MoonPay | *** diff --git a/content/docs/ai/mcp-toolkit/get-started.mdx b/content/docs/ai/mcp-toolkit/get-started.mdx index 427ca32f..1748f9e9 100644 --- a/content/docs/ai/mcp-toolkit/get-started.mdx +++ b/content/docs/ai/mcp-toolkit/get-started.mdx @@ -264,7 +264,6 @@ server.registerProtocol('ethereum', 'velora', VeloraProtocolEvm) server.registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) server.registerProtocol('ethereum', 'aave', AaveProtocolEvm) server.registerProtocol('ethereum', 'moonpay', MoonPayProtocol, { - secretKey: process.env.MOONPAY_SECRET_KEY, apiKey: process.env.MOONPAY_API_KEY }) @@ -287,7 +286,6 @@ server.registerTools([ | `WDK_SEED` | Yes | BIP-39 seed phrase for wallet derivation | | `WDK_INDEXER_API_KEY` | No | Enables `INDEXER_TOOLS` - [get a key](/tools/indexer-api/get-started/#request-api-key) | | `MOONPAY_API_KEY` | No | Enables `FIAT_TOOLS` - [MoonPay Dashboard](https://dashboard.moonpay.com/) | -| `MOONPAY_SECRET_KEY` | No | Required with `MOONPAY_API_KEY` | *** diff --git a/content/docs/ai/mcp-toolkit/langchain.mdx b/content/docs/ai/mcp-toolkit/langchain.mdx index f33bf712..bd1c03eb 100644 --- a/content/docs/ai/mcp-toolkit/langchain.mdx +++ b/content/docs/ai/mcp-toolkit/langchain.mdx @@ -54,7 +54,7 @@ The command has built-in definitions for 13 chains and 4 protocol modules. When | [`@tetherto/wdk-protocol-swap-velora-evm`](/sdk/swap-modules/swap-velora-evm) | Swap tools (Ethereum, Arbitrum) | -- | | [`@tetherto/wdk-protocol-bridge-usdt0-evm`](/sdk/bridge-modules/bridge-usdt0-evm) | Bridge tools (Ethereum, Arbitrum) | -- | | [`@tetherto/wdk-protocol-lending-aave-evm`](/sdk/lending-modules/lending-aave-evm) | Lending tools (Ethereum) | -- | -| [`@tetherto/wdk-protocol-fiat-moonpay`](/sdk/fiat-modules/fiat-moonpay) | Fiat tools (Ethereum) | Requires `MOONPAY_*` env vars | +| [`@tetherto/wdk-protocol-fiat-moonpay`](/sdk/fiat-modules/fiat-moonpay) | Fiat tools (Ethereum) | Requires `MOONPAY_API_KEY` plus a non-sensitive `MOONPAY_SECRET_KEY=unused` sentinel in the released CLI; URLs remain unsigned | Missing packages are silently skipped. Install only the modules you need and `serve` will pick them up. For chains or protocols **not** in the built-in registry, use a [custom config file](#custom-config-file). @@ -184,8 +184,12 @@ Control `serve` behavior through environment variables: | `WDK_RPC_\` | No | Built-in defaults | Override the RPC endpoint for a chain (e.g. `WDK_RPC_ETHEREUM=https://my-rpc.com`) | | `WDK_CONFIG` | No | -- | Path to a `wdk.config.json` file for custom chains and protocols | | `WDK_INDEXER_API_KEY` | No | -- | Enables indexer tools for balance and transfer history queries | -| `MOONPAY_API_KEY` | No | -- | Enables fiat on/off-ramp tools | -| `MOONPAY_SECRET_KEY` | No | -- | Required with `MOONPAY_API_KEY` | +| `MOONPAY_API_KEY` | No | -- | With the sentinel below, passes the released `serve` CLI's MoonPay registration gate | +| `MOONPAY_SECRET_KEY` | No | -- | Set to a non-sensitive sentinel such as `unused` only to satisfy the released CLI gate; never provide the real MoonPay signing secret | + + +The released `serve` CLI checks for both MoonPay variables but passes a configuration field that MoonPay beta.3 does not use. Its generated widget URLs therefore remain unsigned. Never give `serve` the real signing secret. For signed MoonPay flows, use programmatic registration with the [`signUrl` configuration](/sdk/fiat-modules/fiat-moonpay/configuration#basic-configuration) and keep the signing secret in an authenticated backend. + ### Custom Config File diff --git a/content/docs/overview/about.mdx b/content/docs/overview/about.mdx index a33eada6..cb0b77b7 100644 --- a/content/docs/overview/about.mdx +++ b/content/docs/overview/about.mdx @@ -89,6 +89,7 @@ WDK natively supports a broad set of blockchains and standards out of the box: | [TRON](/sdk/wallet-modules/wallet-tron/) | ✅ | | [TRON Gasfree](/sdk/wallet-modules/wallet-tron-gasfree/) | ✅ | | [Solana](/sdk/wallet-modules/wallet-solana/) | ✅ | +| [Aptos](/sdk/wallet-modules/wallet-aptos/) | ✅ | | [Spark/Lightning](/sdk/wallet-modules/wallet-spark/) | ✅ | diff --git a/content/docs/overview/changelog.mdx b/content/docs/overview/changelog.mdx index a6efd257..d7ec02ad 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -10,6 +10,139 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### July 30, 2026 + +**What's New** +- **wdk-core** ([v1.0.0-beta.15](https://github.com/tetherto/wdk/releases/tag/v1.0.0-beta.15)): Add global and account-scoped SDA protocol registration, `getSdaProtocol()`, and policy coverage for deposit-address creation, renewal, recovery, and disablement. Governed proxies now hide `keyPair` and underscore-prefixed members from direct access and own-property reflection and reject freezing; retained raw references, prototype inspection, and nested module calls remain outside that proxy boundary. +- **wallet-evm-erc-4337** ([v1.0.0-beta.14](https://github.com/tetherto/wdk-wallet-evm-erc-4337/releases/tag/v1.0.0-beta.14)): Add opt-in ERC-4337 nonce lanes with `parallel` and `nonceKey` for sign, send, and transfer flows, replacing local sequential nonce reservation. Lane operations rebuild rather than reuse quote-cache UserOperations; same-lane operations require sequential inclusion and overlapping calls can collide. The beta.14 per-call declarations omit the runtime-supported lane fields. +- **create-wdk-module** ([v1.0.0-beta.3](https://github.com/tetherto/create-wdk-module/releases/tag/v1.0.0-beta.3)): Add the `sda` scaffold type and `wdk-protocol-sda-` template. The generated provider contains empty method stubs and todo-only tests, so implement both required methods and implement or remove every optional override before publishing. +- **wdk-utils** ([v1.0.0-beta.11](https://github.com/tetherto/wdk-utils/releases/tag/v1.0.0-beta.11)): [Breaking] Add CAIP-2-aware `validateAddress()` dispatch for Bitcoin, EVM, Solana, Spark, and Tron. Bitcoin success results replace `network` with `compatibleNetworks` and rename mainnet to `bitcoin`; Spark successes also add `compatibleNetworks`. + +**Fixes** +- **worklet-bundler** ([v1.0.0-beta.7](https://github.com/tetherto/wdk-worklet-bundler/releases/tag/v1.0.0-beta.7)): Check root, nested, scoped, and symlinked package trees before deferring an optional peer, preventing installed dependencies from being omitted and later failing with `MODULE_NOT_FOUND`. Public exports and configuration signatures are unchanged. + +**Changes** +- **wallet-btc** ([v1.0.0-beta.12](https://github.com/tetherto/wdk-wallet-btc/releases/tag/v1.0.0-beta.12)): Publish a version-only source update plus a repository lockfile refresh from `valibot` 1.4.1 to 1.4.2. Runtime source, declarations, declared dependencies, and the Node.js requirement are unchanged. +- **wallet-ton** ([v1.0.0-beta.12](https://github.com/tetherto/wdk-wallet-ton/releases/tag/v1.0.0-beta.12)): Publish a version-only source update plus a development lockfile refresh from `fast-uri` 3.1.2 to 3.1.4. Runtime source, declarations, declared dependencies, and existing mainnet endpoint caveats are unchanged. +- **wallet-ton-gasless** ([v1.0.0-beta.8](https://github.com/tetherto/wdk-wallet-ton-gasless/releases/tag/v1.0.0-beta.8)): Publish a version-only source update plus a development lockfile refresh from `fast-uri` 3.1.2 to 3.1.4. Runtime source, declarations, declared dependencies, and existing mainnet endpoint caveats are unchanged. +- **swidge-symbiosis** ([v1.2.0](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.2.0)): Add a 30-second default API timeout and configurable `X-Partner-Id`, validate a positive integer input amount, reset non-zero insufficient EVM allowances before approval, and recognize partner fees by their `Partner fee` description. TON joins Tron and Solana as quote-only because WDK TON accounts cannot submit the raw BoC route payload; Monero and Zcash custodial routes are excluded from discovery and resolution. +- **swidge-symbiosis** ([v1.1.2](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.1.2)): Move publishing to version-tagged npm trusted publishing without changing the public module API. + +--- + +### July 29, 2026 + +**Changes** +- **lending-aave-evm** ([v1.0.0-beta.5](https://github.com/tetherto/wdk-protocol-lending-aave-evm/releases/tag/v1.0.0-beta.5)): Refresh the wallet and ethers dependencies without changing the Aave adapter's public runtime or type surface. ERC-4337 integrations must now provide the dependency's required `safeModulesVersion`; token-paymaster mode also requires a paymaster address, URL, and token configuration. + +--- + +### July 28, 2026 + +**What's New** +- **swidge-symbiosis** ([v1.1.1](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.1.1)): Add typed provider errors, expose named `SymbiosisProtocol` and `ISwidgeProtocol` exports alongside the default export, and map Symbiosis status code `2` to WDK `pending`. + +--- + +### July 27, 2026 + +**What's New** +- **wallet-solana-gasless** ([v1.0.0-beta.2](https://github.com/tetherto/wdk-wallet-solana-gasless/releases/tag/v1.0.0-beta.2)): Let owned accounts quote and submit the fully signed transaction returned by `signTransaction()`. Quoting decodes the embedded paymaster-token fee without broadcasting; sending applies `transactionMaxFee` and submits the exact base64 wire transaction through Solana RPC without contacting the paymaster again. The signed payload keeps its existing blockhash or durable-nonce lifetime and is not refreshed or re-signed. + +--- + +### July 24, 2026 + +**What's New** +- **bridge-usdt0-evm** ([v1.0.0-beta.7](https://github.com/tetherto/wdk-protocol-bridge-usdt0-evm/releases/tag/v1.0.0-beta.7)): Extend the ERC-4337 transaction-value helper to Ethereum, Plasma, and Polygon in addition to Arbitrum. On supported helper routes, an ERC-4337 bridge call bundles approval and bridging into one UserOperation instead of requiring a separate `approve()` submission. ERC-4337 network-fee units depend on the selected paymaster mode, while the bridge fee remains in bridged-token base units; use `bridgeMaxFee` only where those denominations are coherent, and note that the release rejects a total equal to the cap. +- **react-native-core** ([v1.0.0-beta.15](https://github.com/tetherto/wdk-react-native-core/releases/tag/v1.0.0-beta.15)): Add `swidge` to `useProtocol()` and align the worklet dependency with Pear Worklet beta.10. The npm artifact still omits the declared default `dist/index.js` and `dist/index.d.ts` files, so only resolvers that select the React Native source condition can load the published package as declared. + +--- + +### July 23, 2026 + +**What's New** +- **pear-wrk-wdk** ([v1.0.0-beta.10](https://github.com/tetherto/pear-wrk-wdk/releases/tag/v1.0.0-beta.10)): Add `swidge` routing to the shared `callMethod()` handler for both HRPC and JSON-RPC. Pass `protocolName` in the call options so the worklet can resolve the requested account-level Swidge provider. +- **wallet-evm** ([v1.0.0-beta.16](https://github.com/tetherto/wdk-wallet-evm/releases/tag/v1.0.0-beta.16)): Widen the declared writable quote and send inputs to accept serialized transaction strings. Quoting a serialized transaction is non-broadcasting and uses its parsed fields. Do not pass a serialized transaction to `sendTransaction()` in this release: the runtime repopulates and signs a new transaction rather than broadcasting the supplied bytes. + +**Changes** +- **wallet-evm-erc-4337** ([v1.0.0-beta.13](https://github.com/tetherto/wdk-wallet-evm-erc-4337/releases/tag/v1.0.0-beta.13)): Add Hardhat and regression-test coverage without changing the beta.12 production API or runtime behavior. +- **lending-morpho-evm** ([v1.0.5](https://github.com/morpho-org/sdks/releases/tag/%40morpho-org/wdk-protocol-lending-morpho-evm-v1.0.5)): Raise `@morpho-org/morpho-sdk` from `^5.3.2` to `^5.4.0` without changing the WDK adapter's compiled JavaScript, declarations, exports, configuration, or README. + +--- + +### July 22, 2026 + +**What's New** +- **wdk-utils** ([v1.0.0-beta.10](https://github.com/tetherto/wdk-utils/releases/tag/v1.0.0-beta.10)): Add structural Solana address validation plus `splitMnemonic()` and `combineMnemonic()` for threshold-based English BIP-39 recovery shares. Shares are unencrypted sensitive material, and their embedded checksum detects accidental corruption rather than authenticating participants. React Native must provide secure `crypto.getRandomValues` before encryption or share generation. + +**Fixes** +- **wallet-evm-7702-gasless** ([v1.0.0-beta.2](https://github.com/tetherto/wdk-wallet-evm-7702-gasless/releases/tag/v1.0.0-beta.2)): Check the current EntryPoint v0.8 account nonce before consuming a cached quote, and rebuild the UserOperation when the cached nonce is stale. Cache identity still excludes per-call fee-mode configuration, so use the same fee-mode and paymaster settings for a quote and its matching send or transfer. +- **failover-provider** ([v1.0.0-beta.3](https://github.com/tetherto/wdk-failover-provider/releases/tag/v1.0.0-beta.3)): Fix getter forwarding, proxy invariants, callable-thenable detection, and concurrent provider switching for synchronous and asynchronous reads. The proxy still forwards reads and method calls only, not writes, setters, enumeration, or reflection. + +**Changes** +- **fiat-moonpay** ([v1.0.0-beta.3](https://github.com/tetherto/wdk-protocol-fiat-moonpay/releases/tag/v1.0.0-beta.3)): Refresh package and publishing dependencies without changing the public MoonPay runtime API. The current config accepts `apiKey`, optional backend `signUrl`, `cacheTime`, and `environment`; it does not accept `secretKey`. +- **react-native-secure-storage** ([v1.0.0-beta.5](https://github.com/tetherto/wdk-react-native-secure-storage/releases/tag/v1.0.0-beta.5)): Publish dependency and workflow maintenance with no source API change. The npm artifact omits the `dist/` JavaScript and declaration files still referenced by its default and type entrypoints; pin beta.4 unless the consuming resolver explicitly uses the React Native source entry. + +--- + +### July 21, 2026 + +**What's New** +- **wdk-wallet** ([v1.0.0-beta.15](https://github.com/tetherto/wdk-wallet/releases/tag/v1.0.0-beta.15)): Export `UnsupportedOperationError`, `ValueError`, and `NoSuchElementError` from the base wallet package. + +--- + +### July 20, 2026 + +**What's New** +- **wallet-aptos** ([v1.0.0-beta.1](https://github.com/tetherto/wdk-wallet-aptos/releases/tag/v1.0.0-beta.1)): Introduce Aptos wallets with hardened SLIP-0010 Ed25519 derivation, APT and fungible-asset balances and transfers, provider failover, provider-backed transaction preparation and signing, message signatures, and read-only accounts. +- **p2p-address-book** ([v1.0.0-beta.2](https://github.com/tetherto/wdk-p2p-address-book/releases/tag/v1.0.0-beta.2)): Publish the encrypted, multi-writer P2P wallet contact store and make replication startup asynchronous so local initialization no longer waits for the initial swarm flush. Treat construction as local readiness rather than proof that peers are connected or records have converged. + +--- + +### July 16, 2026 + +**What's New** +- **wallet-evm-erc-4337** ([v1.0.0-beta.12](https://github.com/tetherto/wdk-wallet-evm-erc-4337/releases/tag/v1.0.0-beta.12)): Let writable accounts quote and submit a signed EntryPoint v0.7 `UserOperationV7`. Submission preserves its baked nonce and gas fields and skips a fresh `transactionMaxFee` check, so accept only a trusted operation prepared for the same account and configuration and submit it promptly. +- **wallet-solana** ([v1.0.0-beta.12](https://github.com/tetherto/wdk-wallet-solana/releases/tag/v1.0.0-beta.12)): Let writable accounts quote a fully signed transaction without broadcasting and send its exact serialized wire payload after applying `transactionMaxFee`. A signed transaction seals its recent blockhash or durable-nonce state, so submit it within the relevant validity window. +- **wallet-ton** ([v1.0.0-beta.11](https://github.com/tetherto/wdk-wallet-ton/releases/tag/v1.0.0-beta.11)): Let writable accounts quote and submit the signed transfer-body `Cell` returned by `signTransaction()`. The cell seals the wallet sequence number; rebuild it after intervening account activity. +- **wallet-tron** ([v1.0.0-beta.9](https://github.com/tetherto/wdk-wallet-tron/releases/tag/v1.0.0-beta.9)): Export `TronSignedTransaction` and let writable accounts quote it without broadcasting or submit the exact signed transaction through TronWeb after applying `transactionMaxFee`. +- **worklet-bundler** ([v1.0.0-beta.6](https://github.com/tetherto/wdk-worklet-bundler/releases/tag/v1.0.0-beta.6)): Defer missing optional peer dependencies through `bare-pack --defer` by default. Use `--no-defer-optional-peers` or `deferOptionalPeers: false` for a strict build; otherwise an optional feature can build successfully and fail later when its deferred peer is first required. +- **@lifi/wdk-protocol-swidge-lifi** ([v0.5.1](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi/v/0.5.1)): Map each quoted fee to its LI.FI cost-token chain, and omit the optional chain when LI.FI does not provide one instead of assigning every fee to the source chain. + +**Fixes** +- **react-native-core** ([v1.0.0-beta.14](https://github.com/tetherto/wdk-react-native-core/releases/tag/v1.0.0-beta.14)): Align Pear Worklet to beta.9 so the beta.13 generic-module HRPC methods are available, and move `react-native-bare-kit` to an explicit peer dependency that applications must install. + +**Changes** +- **asset-registry** ([v1.0.0-beta.2](https://github.com/tetherto/wdk-asset-registry/releases/tag/v1.0.0-beta.2)): [Breaking] Make `getTokenByAddress()` case-sensitive by default. Pass `{ caseSensitive: false }` for normalized case-insensitive address lookup; symbol and chain helpers remain case-insensitive by default. + +--- + +### July 09, 2026 + +**What's New** +- **pear-wrk-wdk** ([v1.0.0-beta.9](https://github.com/tetherto/pear-wrk-wdk/releases/tag/v1.0.0-beta.9)): Add HRPC-only generic-module construction, method calls, lifecycle handling, and host events, plus a separate length-prefixed JSON-RPC server entrypoint for native hosts. JSON-RPC supports the existing WDK, wallet, protocol, and secret operations but not wallet resets or generic modules in this release. Production logging now defaults to ERROR, JSC object logging is serialized safely, and temporary secret-buffer cleanup is improved. +- **wdk-utils** ([v1.0.0-beta.9](https://github.com/tetherto/wdk-utils/releases/tag/v1.0.0-beta.9)): Add `deriveSeedKey()` for domain-separated HKDF-SHA256 byte keys and `deriveSeedKeyPair()` for deterministic Ed25519 keypairs. Both require caller-supplied `salt` and `info` values and expect high-entropy seed bytes rather than mnemonic words. + +**Changes** +- **wallet-tron-gasfree** ([v1.0.0-beta.8](https://github.com/tetherto/wdk-wallet-tron-gasfree/releases/tag/v1.0.0-beta.8)): Add `transactionMaxFee` to the shared config type while native quote, sign, and send methods remain unsupported, so the field has no runtime enforcement path. GasFree TRC20 transfers continue to use per-call `transferMaxFee`, and provider transfer and activation fee fields are converted to `bigint` before addition. + +--- + +### July 08, 2026 + +**What's New** +- **wdk-wallet** ([v1.0.0-beta.14](https://github.com/tetherto/wdk-wallet/releases/tag/v1.0.0-beta.14)): Add optional `minAmountOut` to shared swap and swidge options, in destination-token base units, and forward it through the Swidge swap adapters. The base package does not validate or enforce the minimum; concrete provider behavior remains provider-defined. +- **wallet-btc** ([v1.0.0-beta.11](https://github.com/tetherto/wdk-wallet-btc/releases/tag/v1.0.0-beta.11)): Let writable Bitcoin accounts quote and broadcast signed raw transaction hex. Signed-hex quotes fetch referenced previous transactions through the configured client without broadcasting, while signed-hex sends broadcast the exact payload and apply `transactionMaxFee` when configured. Read-only accounts still quote transaction objects only. An updated descriptor dependency raises the Node.js minimum to 20.19.0. +- **react-native-core** ([v1.0.0-beta.13](https://github.com/tetherto/wdk-react-native-core/releases/tag/v1.0.0-beta.13)): Add the `useModule()` hook, `ModuleService`, generic-module event subscriptions, and runtime `WdkConfigs.modules`. The published package still pins Pear Worklet beta.8, which lacks the required module HRPC methods, so the new API is not runnable through the default dependency graph. The React Native source entry is present, but the declared default JavaScript and type outputs under `dist/` are missing from this tag. +- **worklet-bundler** ([v1.0.0-beta.5](https://github.com/tetherto/wdk-worklet-bundler/releases/tag/v1.0.0-beta.5)): Restore the built CLI and API files missing from beta.4, making the published HRPC/JSON-RPC transport, HRPC generic-module config, native addon linking, `addons.yml`, and ESM-to-CJS options available. Native linking now includes `bare-posix` automatically. + +**Fixes** +- **wdk-core** ([v1.0.0-beta.14](https://github.com/tetherto/wdk/releases/tag/v1.0.0-beta.14)): Export the type-only `WdkAccount` intersection and use it as the declared return type of `getAccount()` and `getAccountByPath()`, so writable wallet and protocol methods are represented together. Runtime account behavior is unchanged. + +--- + ### July 07, 2026 **What's New** @@ -211,7 +344,7 @@ Stay up to date with the latest improvements, new features, and bug fixes across ### May 19, 2026 **What's New** -- **[Swidge Protocol Interface](/sdk/swidge-modules)**: New page covering WDK's preferred route interface for new swap, bridge, and combined route providers, including discovery, quote, execution, status, fee, and migration guidance for the existing standalone swap and bridge interfaces. +- **[Swidge modules](/sdk/swidge-modules)**: Add a catalog for released swap, bridge, and combined-route provider modules. --- diff --git a/content/docs/resources/concepts.mdx b/content/docs/resources/concepts.mdx index cdb06bcd..e426b99a 100644 --- a/content/docs/resources/concepts.mdx +++ b/content/docs/resources/concepts.mdx @@ -14,8 +14,8 @@ Account Abstraction is a blockchain technology that separates the concept of a u WDK provides Account Abstraction support through specialized wallet modules: -- `@tetherto/wdk-wallet-evm-erc4337` - EVM chains with ERC-4337 standard -- `@tetherto/wdk-wallet-ton-gasless` - TON blockchain with gasless transactions +- `@tetherto/wdk-wallet-evm-erc-4337` - EVM chains with ERC-4337 standard +- `@tetherto/wdk-wallet-ton-gasless` - TON blockchain with gasless Jetton transfers - `@tetherto/wdk-wallet-tron-gasfree` - TRON blockchain with gas-free transactions These modules allow developers to implement gasless transaction flows where users can pay fees in tokens like USD₮ or XAU₮ instead of native blockchain tokens. diff --git a/content/docs/sdk/all-modules.mdx b/content/docs/sdk/all-modules.mdx index 45d896e2..bf9099f4 100644 --- a/content/docs/sdk/all-modules.mdx +++ b/content/docs/sdk/all-modules.mdx @@ -1,6 +1,6 @@ --- title: All Modules -description: Complete list of all available WDK modules including wallet, pricing, swidge, swap, bridge, lending, and fiat modules. +description: Complete list of available WDK wallet, pricing, swidge, swap, bridge, lending, and fiat interfaces and modules. docType: reference schemaType: TechArticle --- @@ -13,7 +13,7 @@ The orchestrator that manages all WDK modules. | Module | Description | Documentation | |--------|-------------|---------------| -| [`@tetherto/wdk-core`](https://github.com/tetherto/wdk-core) | Central orchestrator for all WDK modules | [Docs](/sdk/core-module/) | +| [`@tetherto/wdk`](https://github.com/tetherto/wdk) | Central orchestrator for all WDK modules | [Docs](/sdk/core-module/) | ## Wallet Modules @@ -23,24 +23,33 @@ Wallet modules provide blockchain-specific wallet functionality for managing add |--------|------------|-------------|---------------| | [`@tetherto/wdk-wallet-btc`](https://github.com/tetherto/wdk-wallet-btc) | Bitcoin | Bitcoin SegWit wallet with BIP-39/BIP-44 support | [Docs](/sdk/wallet-modules/wallet-btc/) | | [`@tetherto/wdk-wallet-evm`](https://github.com/tetherto/wdk-wallet-evm) | EVM | Ethereum and EVM-compatible chains wallet | [Docs](/sdk/wallet-modules/wallet-evm/) | -| [`@tetherto/wdk-wallet-evm-erc4337`](https://github.com/tetherto/wdk-wallet-evm-erc-4337) | EVM | ERC-4337 Account Abstraction for EVM chains | [Docs](/sdk/wallet-modules/wallet-evm-erc-4337/) | +| [`@tetherto/wdk-wallet-evm-erc-4337`](https://github.com/tetherto/wdk-wallet-evm-erc-4337) | EVM | ERC-4337 Account Abstraction for EVM chains | [Docs](/sdk/wallet-modules/wallet-evm-erc-4337/) | | [`@tetherto/wdk-wallet-evm-7702-gasless`](https://github.com/tetherto/wdk-wallet-evm-7702-gasless) | EVM | EIP-7702 gasless account abstraction for EVM chains | [Docs](/sdk/wallet-modules/wallet-evm-7702-gasless/) | | [`@tetherto/wdk-wallet-ton`](https://github.com/tetherto/wdk-wallet-ton) | TON | TON blockchain wallet | [Docs](/sdk/wallet-modules/wallet-ton/) | -| [`@tetherto/wdk-wallet-ton-gasless`](https://github.com/tetherto/wdk-wallet-ton-gasless) | TON | Gasless transactions on TON | [Docs](/sdk/wallet-modules/wallet-ton-gasless/) | +| [`@tetherto/wdk-wallet-ton-gasless`](https://github.com/tetherto/wdk-wallet-ton-gasless) | TON | Gasless Jetton transfers on TON | [Docs](/sdk/wallet-modules/wallet-ton-gasless/) | | [`@tetherto/wdk-wallet-tron`](https://github.com/tetherto/wdk-wallet-tron) | TRON | TRON blockchain wallet | [Docs](/sdk/wallet-modules/wallet-tron/) | | [`@tetherto/wdk-wallet-tron-gasfree`](https://github.com/tetherto/wdk-wallet-tron-gasfree) | TRON | Gas-free transactions on TRON | [Docs](/sdk/wallet-modules/wallet-tron-gasfree/) | | [`@tetherto/wdk-wallet-solana`](https://github.com/tetherto/wdk-wallet-solana) | Solana | Solana blockchain wallet | [Docs](/sdk/wallet-modules/wallet-solana/) | | [`@tetherto/wdk-wallet-solana-gasless`](https://github.com/tetherto/wdk-wallet-solana-gasless) | Solana | Gasless Solana transactions through a Kora-compatible paymaster | [Docs](/sdk/wallet-modules/wallet-solana-gasless/) | +| [`@tetherto/wdk-wallet-aptos`](https://github.com/tetherto/wdk-wallet-aptos) | Aptos | Aptos blockchain wallet with native APT and fungible asset support | [Docs](/sdk/wallet-modules/wallet-aptos/) | | [`@tetherto/wdk-wallet-spark`](https://github.com/tetherto/wdk-wallet-spark) | Spark | Spark/Lightning Bitcoin L2 wallet | [Docs](/sdk/wallet-modules/wallet-spark/) | -## Swidge Interface +## Swidge Modules -Swidge is the preferred interface for new protocol providers that can quote and execute asset routes. A route can be swap-only, bridge-only, or a combined swap and bridge route. +Swidge providers can quote and execute asset routes. A route can be swap-only, bridge-only, or a combined swap and bridge route. -| Module | Provider | Description | Documentation | -|--------|----------|-------------|---------------| -| [`wdk-protocol-swidge-orchestra`](https://www.npmjs.com/package/wdk-protocol-swidge-orchestra) | Flashnet Orchestra | Swidge provider for BTC and stablecoin routes returned by Orchestra. Treat the live [Orchestra route matrix](https://orchestration.flashnet.xyz/v1/orchestration/routes) as provider-level data and filter through package discovery before exposing routes. | [Docs](/sdk/swidge-modules/swidge-orchestra/) | -| [`@rhino.fi/wdk-protocol-swidge-rhinofi`](https://www.npmjs.com/package/@rhino.fi/wdk-protocol-swidge-rhinofi) | Rhino.fi | Swidge routes for Rhino.fi cross-chain swap and bridge operations | [Docs](/sdk/swidge-modules/swidge-rhinofi/) | + +Rows marked Community are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +| Module | Provider | Ownership | Description | Documentation | +|--------|----------|-----------|-------------|---------------| +| [`wdk-protocol-swidge-orchestra`](https://www.npmjs.com/package/wdk-protocol-swidge-orchestra) | Flashnet Orchestra | Community | Swidge provider for BTC and stablecoin routes returned by Orchestra. Treat the live [Orchestra route matrix](https://orchestration.flashnet.xyz/v1/orchestration/routes) as provider-level data and filter through package discovery before exposing routes. | [Docs](/sdk/swidge-modules/swidge-orchestra/) | +| [`@rhino.fi/wdk-protocol-swidge-rhinofi`](https://www.npmjs.com/package/@rhino.fi/wdk-protocol-swidge-rhinofi) | Rhino.fi | Community | Swidge routes for Rhino.fi cross-chain swap and bridge operations | [Docs](/sdk/swidge-modules/swidge-rhinofi/) | +| [`@symbiosis-finance/wdk-protocol-swidge-symbiosis`](https://www.npmjs.com/package/@symbiosis-finance/wdk-protocol-swidge-symbiosis) | Symbiosis | Community | Runtime-discovered exact-input quotes with EVM and Bitcoin source execution through the Symbiosis API | [Docs](/sdk/swidge-modules/swidge-symbiosis/) | +| [`@lifi/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi) | LI.FI | Community | Swidge routes for LI.FI swap, bridge, and combined swap-plus-bridge operations | [Docs](/sdk/swidge-modules/swidge-lifi/) | ## Pricing Modules @@ -71,9 +80,15 @@ Cross-chain bridge functionality for token transfers between blockchains. DeFi lending and borrowing functionality. -| Module | Blockchain | Status | Description | Documentation | -|--------|------------|--------|-------------|---------------| -| [`@tetherto/wdk-protocol-lending-aave-evm`](https://github.com/tetherto/wdk-protocol-lending-aave-evm) | EVM | Ready | Aave protocol integration for EVM | [Docs](/sdk/lending-modules/lending-aave-evm/) | + +Rows marked Community are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +| Module | Blockchain | Ownership | Description | Documentation | +|--------|------------|-----------|-------------|---------------| +| [`@tetherto/wdk-protocol-lending-aave-evm`](https://github.com/tetherto/wdk-protocol-lending-aave-evm) | EVM | Tether | Aave protocol integration for EVM | [Docs](/sdk/lending-modules/lending-aave-evm/) | | [`@morpho-org/wdk-protocol-lending-morpho-evm`](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) | EVM | Community | Morpho Vault V2 and Morpho Blue lending integration for EVM | [Docs](/sdk/lending-modules/lending-morpho-evm/) | ## Fiat Modules @@ -88,11 +103,18 @@ On-ramp and off-ramp functionality for fiat currency integration. Modules built by the WDK community. See the [Community Modules](/sdk/community-modules/) page for more details. -| Module | Blockchain | Description | Documentation | -|--------|------------|-------------|---------------| -| [`@utexo/wdk-wallet-rgb`](https://github.com/UTEXO-Protocol/wdk-wallet-rgb) | Bitcoin (RGB) | RGB protocol wallet integration | [Docs](/sdk/community-modules/wdk-wallet-rgb/) | -| [`@arkade-os/wdk`](https://github.com/arkade-os/arkade-wdk) | Arkade | Bitcoin wallet module built on the Arkade SDK | [README](https://github.com/arkade-os/arkade-wdk#readme) | -| [`@base58-io/wdk-wallet-cosmos`](https://github.com/base58-io/wdk-wallet-cosmos) | Cosmos | Cosmos-compatible wallet integration | [Docs](/sdk/community-modules/wdk-wallet-cosmos/) | -| [`@morpho-org/wdk-protocol-lending-morpho-evm`](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) | EVM | Morpho Vault V2 and Morpho Blue lending integration | [Docs](/sdk/lending-modules/lending-morpho-evm/) | + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +| Module | Category | Description | Documentation | +|--------|----------|-------------|---------------| +| [`@utexo/wdk-wallet-rgb`](https://github.com/UTEXO-Protocol/wdk-wallet-rgb) | Wallet | RGB protocol wallet integration | [Docs](/sdk/community-modules/wdk-wallet-rgb/) | +| [`@base58-io/wdk-wallet-cosmos`](https://github.com/base58-io/wdk-wallet-cosmos) | Wallet | Cosmos-compatible wallet integration | [Docs](/sdk/community-modules/wdk-wallet-cosmos/) | +| [`@morpho-org/wdk-protocol-lending-morpho-evm`](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) | Lending | Morpho Vault V2 and Morpho Blue lending integration | [Docs](/sdk/lending-modules/lending-morpho-evm/) | | [`wdk-protocol-swidge-orchestra`](https://github.com/flashnetxyz/wdk-protocol-swidge-orchestra) | Swidge | Flashnet Orchestra BTC and stablecoin route integration | [Docs](/sdk/swidge-modules/swidge-orchestra/) | -| [`@rhino.fi/wdk-protocol-swidge-rhinofi`](https://www.npmjs.com/package/@rhino.fi/wdk-protocol-swidge-rhinofi) | Rhino.fi | Swidge cross-chain route integration | [Docs](/sdk/swidge-modules/swidge-rhinofi/) | +| [`@rhino.fi/wdk-protocol-swidge-rhinofi`](https://www.npmjs.com/package/@rhino.fi/wdk-protocol-swidge-rhinofi) | Swidge | Rhino.fi cross-chain route integration | [Docs](/sdk/swidge-modules/swidge-rhinofi/) | +| [`@symbiosis-finance/wdk-protocol-swidge-symbiosis`](https://www.npmjs.com/package/@symbiosis-finance/wdk-protocol-swidge-symbiosis) | Swidge | Same-chain and cross-chain exact-input routes through Symbiosis | [Docs](/sdk/swidge-modules/swidge-symbiosis/) | +| [`@lifi/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi) | Swidge | LI.FI swap and bridge route integration | [Docs](/sdk/swidge-modules/swidge-lifi/) | diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/api-reference.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/api-reference.mdx index 23cd616f..702c16d7 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/api-reference.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/api-reference.mdx @@ -26,7 +26,7 @@ new Usdt0ProtocolEvm(account, config?) **Parameters:** - `account` (WalletAccountEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvm | WalletAccountReadOnlyEvmErc4337): The wallet account to use for bridge operations - `config` (BridgeProtocolConfig, optional): Configuration object - - `bridgeMaxFee` (number | bigint, optional): Maximum total bridge cost in wei + - `bridgeMaxFee` (number | bigint, optional): Rejects `bridge()` when the implementation's combined fee value is at or above this cap **Example:** ```javascript @@ -38,7 +38,7 @@ const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { }) const bridgeProtocol = new Usdt0ProtocolEvm(account, { - bridgeMaxFee: 1000000000000000n // Maximum bridge fee in wei + bridgeMaxFee: 1000000000000000n // Standard Ethereum account: source native base units }) ``` @@ -46,13 +46,15 @@ const bridgeProtocol = new Usdt0ProtocolEvm(account, { | Method | Description | Returns | Throws | |--------|-------------|---------|--------| -| `bridge(options, config?)` | Bridges tokens to another blockchain | `Promise` | If no provider or fee exceeds max | +| `bridge(options, config?)` | Bridges tokens to another blockchain | `Promise` | If no provider or the combined fee is at or above the cap | | `quoteBridge(options, config?)` | Estimates the cost of a bridge operation | `Promise>` | If no provider | +| `getSupportedChains()` | Returns chain descriptors from bundled configuration | `Promise` | — | +| `getSupportedTokens(options?)` | Returns configured USD₮0 or XAU₮0 descriptors, with optional filters | `Promise` | — | #### `bridge(options, config?)` Bridges tokens to a different blockchain using the USD₮0 protocol. -Before calling `bridge()`, approve the token allowance for the source-chain bridge spender. If you pass `oftContractAddress`, use the same address as the approval `spender`. +With a standard EVM account, approve the source-chain bridge spender before calling `bridge()`. If you pass `oftContractAddress`, use the same address as the approval `spender`. Supported ERC-4337 accounts do not need a separate approval call; the protocol bundles an approval and helper call into one UserOperation. **Parameters:** - `options` (BridgeOptions): Bridge operation options @@ -62,8 +64,7 @@ Before calling `bridge()`, approve the token allowance for the source-chain brid - `amount` (number | bigint): Amount to bridge in token base units - `oftContractAddress` (string, optional): Custom OFT contract address to use instead of auto-resolving from the source chain - `dstEid` (number, optional): Custom LayerZero destination endpoint ID override -- `config` (`Pick & Pick`, optional): Override configuration for ERC-4337 accounts - - `paymasterToken` (`{ address: string }`, optional): Paymaster token configuration for gas fees +- `config` (`Erc4337BridgeConfig`, optional): ERC-4337 gas-payment overrides plus optional `bridgeMaxFee` - `bridgeMaxFee` (number | bigint, optional): Override maximum bridge fee **Returns:** `Promise` - Bridge operation result @@ -71,18 +72,20 @@ Before calling `bridge()`, approve the token allowance for the source-chain brid **Throws:** - Error if account is read-only - Error if no provider is configured -- Error if bridge fee exceeds maximum allowed +- Error if the combined fee value is equal to or greater than `bridgeMaxFee` **Example:** ```javascript // Standard EVM account -await account.approve({ +const standardBridgeProtocol = new Usdt0ProtocolEvm(standardAccount) + +await standardAccount.approve({ token: '0x...', // USDT contract address spender: '0x...', // OFT or bridge spender address amount: 1000000n }) -const result = await bridgeProtocol.bridge({ +const result = await standardBridgeProtocol.bridge({ targetChain: 'arbitrum', recipient: '0x...', // Recipient address token: '0x...', // ₮ contract address @@ -91,47 +94,41 @@ const result = await bridgeProtocol.bridge({ }) console.log('Bridge hash:', result.hash) -console.log('Total fee:', result.fee) +console.log('Account transaction fee:', result.fee) console.log('Bridge fee:', result.bridgeFee) -// ERC-4337 account -await account.approve({ - token: '0x...', // USDT contract address - spender: '0x...', // OFT or bridge spender address - amount: 1000000n -}) +// ERC-4337 account: approval is bundled automatically +const erc4337BridgeProtocol = new Usdt0ProtocolEvm(erc4337Account) -const result2 = await bridgeProtocol.bridge({ +const result2 = await erc4337BridgeProtocol.bridge({ targetChain: 'arbitrum', recipient: '0x...', // Recipient address token: '0x...', // USDT contract address amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender + oftContractAddress: '0x...' // Optional custom OFT contract }, { - paymasterToken: { address: '0x...' }, // Paymaster token configuration - bridgeMaxFee: 1000000000000000n + paymasterToken: { address: '0x...' } // Paymaster token configuration }) console.log('Bridge hash:', result2.hash) // Single hash for bundled operations -console.log('Total fee:', result2.fee) +console.log('Account fee:', result2.fee) console.log('Bridge fee:', result2.bridgeFee) ``` #### `quoteBridge(options, config?)` Estimates the cost of a bridge operation without executing it. -Some providers estimate the same bridge transaction that `bridge()` sends. If that estimate fails because token allowance is missing, approve the source-chain bridge spender before calling `quoteBridge()`. +For standard EVM accounts, some providers estimate the same bridge transaction that `bridge()` sends. If that estimate fails because token allowance is missing, approve the source-chain bridge spender before calling `quoteBridge()`. ERC-4337 quotes include the bundled approval transaction. **Parameters:** - `options` (BridgeOptions): Bridge operation options (same as bridge method) -- `config` (`Pick`, optional): Override configuration for ERC-4337 accounts - - `paymasterToken` (`{ address: string }`, optional): Paymaster token configuration for gas fees +- `config` (`Erc4337QuoteConfig`, optional): ERC-4337 gas-payment overrides **Returns:** `Promise>` - Bridge cost estimate **Throws:** Error if no provider is configured -**Example:** +**Standard-account example:** ```javascript const quote = await bridgeProtocol.quoteBridge({ targetChain: 'polygon', @@ -140,11 +137,11 @@ const quote = await bridgeProtocol.quoteBridge({ amount: 1000000n }) -console.log('Estimated fee:', quote.fee, 'wei') -console.log('Bridge fee:', quote.bridgeFee, 'wei') +console.log('Estimated transaction fee:', quote.fee) +console.log('Bridge fee:', quote.bridgeFee) // Check if fees are acceptable -if (quote.fee + quote.bridgeFee > 1000000000000000n) { +if (quote.fee + quote.bridgeFee >= 1000000000000000n) { console.log('Bridge fees too high') } else { // Proceed with bridge @@ -164,6 +161,35 @@ if (quote.fee + quote.bridgeFee > 1000000000000000n) { } ``` +#### `getSupportedChains()` + +Returns the chain descriptors from the package's bundled configuration. This method does not make a network request. + +```javascript +const chains = await bridgeProtocol.getSupportedChains() +// [{ id: 'ethereum', name: 'Ethereum', type: 'evm', nativeToken: 'ETH' }, ...] +``` + +#### `getSupportedTokens(options?)` + +Returns USD₮0 and XAU₮0 descriptors for chains that have a matching bridge contract in bundled configuration. Optional `fromChain` or `toChain` filters accept a chain key, chain ID, or endpoint ID. `fromToken` filters by token symbol. + +```javascript +const allTokens = await bridgeProtocol.getSupportedTokens() +const ethereumTokens = await bridgeProtocol.getSupportedTokens({ + fromChain: 'ethereum' +}) +const xaut0Tokens = await bridgeProtocol.getSupportedTokens({ + fromToken: 'XAUT0' +}) +``` + +The returned descriptors omit on-chain token addresses because this discovery method does not resolve them. Solana, TON, and TRON are destination-only entries and are not returned by `getSupportedTokens()`. + + +These discovery methods expose separate static chain and token lists. They do not prove that a source token has an on-chain peer for a destination. Confirm the exact pair with `quoteBridge()` before execution. + + ## Types ### BridgeOptions @@ -184,8 +210,8 @@ interface BridgeOptions { ```typescript interface BridgeResult { hash: string; // Main bridge transaction hash - fee: bigint; // Total gas cost in wei - bridgeFee: bigint; // Bridge protocol fee in wei + fee: bigint; // Account quote fee; unit depends on account gas-payment mode + bridgeFee: bigint; // Standard: source native unit; ERC-4337 helper: bridged-token base units } ``` @@ -193,17 +219,60 @@ interface BridgeResult { ```typescript interface BridgeProtocolConfig { - bridgeMaxFee?: number | bigint; // Maximum total bridge cost in wei + bridgeMaxFee?: number | bigint; // Reject when the implementation's fee + bridgeFee is at or above this value +} +``` + +```typescript +type Erc4337QuoteConfig = Partial< + | EvmErc4337WalletPaymasterTokenConfig + | EvmErc4337WalletSponsorshipPolicyConfig + | EvmErc4337WalletNativeCoinsConfig +> + +type Erc4337BridgeConfig = Erc4337QuoteConfig & { + bridgeMaxFee?: number | bigint } ``` -### EvmErc4337WalletConfig +### Fee units and `bridgeMaxFee` + +| Account flow | `fee` | `bridgeFee` | +|---|---|---| +| Standard EVM | Source-chain native base units | Source-chain native base units | +| ERC-4337 with native gas | Source-chain native base units | Bridged-token base units | +| ERC-4337 with token-paid gas | Paymaster-token base units | Bridged-token base units | +| ERC-4337 with sponsored gas | `0` | Bridged-token base units | + + +In `1.0.0-beta.7`, the ERC-4337 path numerically adds `fee + bridgeFee` when checking `bridgeMaxFee`, even when the fields have different denominations. Do not interpret that sum as a total monetary cost. Set an ERC-4337 cap only after confirming that the selected account payment mode produces compatible units. The equality boundary is rejected. + + +`Erc4337QuoteConfig` is a partial operation-level override of the wallet's token-paid, sponsored, or native-gas configuration. Sponsored gas uses `isSponsored: true` and can include `sponsorshipPolicyId`; native gas uses `useNativeCoins: true`. See the [ERC-4337 wallet configuration](/sdk/wallet-modules/wallet-evm-erc-4337/configuration) for the complete account requirements. + +### Discovery types ```typescript -interface EvmErc4337WalletConfig { - paymasterToken?: { // Token to use for paying gas fees - address: string; - }; +type SwidgeSupportedChain = { + id: string | number + name: string + type: string + nativeToken: string +} + +type SwidgeSupportedToken = { + token: string + chain: string | number + symbol: string + decimals: number + address?: string + name?: string +} + +type SwidgeSupportedTokensOptions = { + fromChain?: string | number + fromToken?: string + toChain?: string | number } ``` @@ -212,13 +281,13 @@ interface EvmErc4337WalletConfig { The bridge protocol supports the following chains: **Source Chains (EVM):** -- `'ethereum'` (Chain ID: 1) -- `'arbitrum'` (Chain ID: 42161) - ERC-4337 support +- `'ethereum'` (Chain ID: 1) - ERC-4337 helper support +- `'arbitrum'` (Chain ID: 42161) - ERC-4337 helper support - `'optimism'` (Chain ID: 10) -- `'polygon'` (Chain ID: 137) +- `'polygon'` (Chain ID: 137) - ERC-4337 helper support - `'berachain'` (Chain ID: 80094) - `'ink'` (Chain ID: 57073) -- `'plasma'` (Chain ID: 9745) +- `'plasma'` (Chain ID: 9745) - ERC-4337 helper support - `'conflux'` (Chain ID: 1030) - `'corn'` (Chain ID: 21000000) - `'avalanche'` (Chain ID: 43114) @@ -235,12 +304,14 @@ The bridge protocol supports the following chains: - `'unichain'` (Chain ID: 130) - `'xlayer'` (Chain ID: 196) -**Destination Chains:** +**Configured destination keys:** - **EVM destinations**: same as source-chain set above - `'solana'` (EID: 30168) - `'ton'` (EID: 30343) - `'tron'` (EID: 30420) +The configured keys are not a Cartesian route matrix. Route execution still requires a matching source contract and a destination peer configured on-chain. + ## Error Handling The bridge protocol throws specific errors for different failure cases: @@ -294,9 +365,7 @@ async function bridgeTokens() { }) // Create bridge protocol - const bridgeProtocol = new Usdt0ProtocolEvm(account, { - bridgeMaxFee: 1000000000000000n - }) + const bridgeProtocol = new Usdt0ProtocolEvm(account) // Get quote first const quote = await bridgeProtocol.quoteBridge({ @@ -320,7 +389,7 @@ async function bridgeTokens() { recipient: '0x...', // Recipient address token: '0x...', // USDT contract address amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender + oftContractAddress: '0x...' // Optional custom OFT contract }) console.log('Bridge result:', result) @@ -397,28 +466,20 @@ async function gaslessBridge() { }) // Create bridge protocol - const bridgeProtocol = new Usdt0ProtocolEvm(account, { - bridgeMaxFee: 1000000000000000n - }) - - // Bridge with gasless transactions - await account.approve({ - token: '0x...', // USDT contract address - spender: '0x...', // OFT or bridge spender address - amount: 1000000n - }) + const bridgeProtocol = new Usdt0ProtocolEvm(account) + // The protocol bundles approval and the helper call in one UserOperation. const result = await bridgeProtocol.bridge({ targetChain: 'polygon', recipient: '0x...', // Recipient address token: '0x...', // USDT contract address amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender + oftContractAddress: '0x...' // Optional custom OFT contract }, { paymasterToken: { address: '0x...' } // Paymaster token configuration }) - console.log('Gasless bridge result:', result) + console.log('ERC-4337 bridge result:', result) return result } ``` diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/configuration.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/configuration.mdx index 4b16a36b..b569d83d 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/configuration.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/configuration.mdx @@ -21,7 +21,7 @@ const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { // Create bridge protocol with configuration const bridgeProtocol = new Usdt0ProtocolEvm(account, { - bridgeMaxFee: 1000000000000000n // Optional: Maximum bridge fee in wei + bridgeMaxFee: 1000000000000000n // Optional standard-account cap in source native base units }) ``` @@ -60,16 +60,21 @@ const bridgeProtocol = new Usdt0ProtocolEvm(account, { ### Bridge Max Fee -The `bridgeMaxFee` option sets a maximum limit for total bridge costs to prevent unexpectedly high fees. +The `bridgeMaxFee` option rejects `bridge()` when the implementation's `fee + bridgeFee` value is equal to or greater than the cap. **Type:** `number | bigint` (optional) -**Unit:** Wei (1 ETH = 1000000000000000000 Wei) + +For a standard EVM account, both fields use the source chain's native base unit. For example, Ethereum and Arbitrum report the values in wei. + + +For an ERC-4337 helper flow, `bridgeFee` is in bridged-token base units. The account's `fee` uses native base units for native gas, paymaster-token base units for token-paid gas, or zero for sponsored gas. In `1.0.0-beta.7`, the protocol numerically adds these values when enforcing `bridgeMaxFee`. Do not treat that sum as one currency or set a cap until your payment mode uses compatible units. + **Examples:** ```javascript const config = { - // Set maximum bridge fee to 0.001 ETH + // Standard Ethereum account: reject fee + bridgeFee at or above 0.001 ETH bridgeMaxFee: 1000000000000000n, } @@ -114,37 +119,40 @@ const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { provider: window.ethereum }) -// Option 3: Using custom JsonRpcProvider -import { JsonRpcProvider } from 'ethers' -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: new JsonRpcProvider('https://eth.drpc.org') -}) ``` +Pass either an RPC URL string or a genuine EIP-1193 provider. An ethers `JsonRpcProvider` is not an EIP-1193 provider and is not accepted by this wallet release. + ## ERC-4337 Configuration When using ERC-4337 accounts, you can override configuration options during bridge operations: ```javascript // Bridge with ERC-4337 account -await account.approve({ - token: '0x...', // USDT contract address - spender: '0x...', // OFT or bridge spender address - amount: 1000000n -}) - const result = await bridgeProtocol.bridge({ targetChain: 'arbitrum', recipient: '0x...', // Recipient address token: '0x...', // USDT contract address amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender + oftContractAddress: '0x...' // Optional custom OFT contract }, { - paymasterToken: { address: '0x...' }, // Paymaster token for gasless transactions - bridgeMaxFee: 1000000000000000n // Override maximum bridge fee + paymasterToken: { address: '0x...' } // Paymaster token for gasless transactions }) ``` +The protocol builds the token approval and transaction-value-helper call and submits them as one UserOperation. Do not call `account.approve()` separately for this flow. + +ERC-4337 helper bridging is configured for these source chains: + +| Source chain | Chain ID | +|---|---:| +| Ethereum | 1 | +| Arbitrum | 42161 | +| Plasma | 9745 | +| Polygon | 137 | + +Other supported EVM source chains require a standard EVM account. + ### Paymaster Token The `paymasterToken` option specifies which token to use for paying gas fees in ERC-4337 accounts. @@ -155,18 +163,12 @@ The `paymasterToken` option specifies which token to use for paying gas fees in **Example:** ```javascript -await account.approve({ - token: '0x...', // USDT contract address - spender: '0x...', // OFT or bridge spender address - amount: 1000000n -}) - const result = await bridgeProtocol.bridge({ targetChain: 'arbitrum', recipient: '0x...', // Recipient address token: '0x...', // USDT contract address amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender + oftContractAddress: '0x...' // Optional custom OFT contract }, { paymasterToken: { address: '0x...' // Paymaster token address @@ -197,7 +199,7 @@ const polygonAccount = new WalletAccountEvm(seedPhrase, "0'/0/0", { ## Bridge Options -When calling the bridge method, you need to provide bridge options: +When calling the bridge method, you need to provide bridge options. The following allowance step applies to a standard EVM account: ```javascript const bridgeOptions = { @@ -261,7 +263,7 @@ The optional `dstEid` option lets you override the default LayerZero destination ## Error Handling -The bridge protocol will throw errors for invalid configurations: +The bridge protocol will throw errors for invalid configurations. This example uses a standard EVM account, so it approves the bridge spender first: ```javascript try { diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-cross-ecosystem.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-cross-ecosystem.mdx index 05b51185..2cc02289 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-cross-ecosystem.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-cross-ecosystem.mdx @@ -7,9 +7,20 @@ This guide covers [prerequisites](#prerequisites) and how to [bridge to Solana]( ## Prerequisites -A [`Usdt0ProtocolEvm`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#usdt0protocolevm) backed by a non-read-only EVM account, with enough USD₮ (and native gas for non-4337 accounts) on the source chain. Approve the source-chain bridge spender before calling [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config). Recipient strings must match each network’s address encoding. +A [`Usdt0ProtocolEvm`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#usdt0protocolevm) backed by a non-read-only EVM account, with enough source tokens and native gas where required. Standard EVM accounts must approve the source-chain bridge spender before calling [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config). Supported ERC-4337 accounts bundle approval into the bridge UserOperation. Recipient strings must match each network’s address encoding. -For `USDT_BRIDGE_SPENDER_ADDRESS`, use the source-chain OFT or bridge contract for the route. See [Bridge Tokens](/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-tokens#prerequisites) for address sources. +The examples below use a standard EVM account. For `USDT_BRIDGE_SPENDER_ADDRESS`, use a verified source-chain OFT or bridge contract that supports the selected destination. See [Bridge Tokens](/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-tokens#prerequisites) for address sources. + +## Verify the route + +For Solana, TON, and TRON targets, beta.7 does not auto-resolve the source chain's ordinary USD₮0 OFT. Its bundled auto-resolution candidates are: + +| Source token contract family | EVM source chains with a bundled candidate | +|---|---| +| USD₮0 legacy mesh | Ethereum, Arbitrum, Celo | +| XAU₮0 OFT | Ethereum, Arbitrum, Avalanche, Celo, HyperEVM, Ink, Monad, Plasma, Polygon, Stable | + +A listed source contract can still lack an on-chain peer for a particular destination. `getSupportedChains()` and `getSupportedTokens()` expose static configuration, not a verified route matrix. After any required standard-account approval, call `quoteBridge()` for the exact source token and destination before calling `bridge()`. For another verified deployment, pass its route-specific `oftContractAddress` and, when needed, `dstEid`. ## Bridge to Solana diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-tokens.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-tokens.mdx index 2ebeb9c8..1e4a7aa3 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-tokens.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-tokens.mdx @@ -3,7 +3,7 @@ title: Bridge Tokens description: EVM-to-EVM bridging, quotes, fee caps, and optional OFT or endpoint overrides. --- -This guide covers [prerequisites](#prerequisites), how to [run a standard EVM-to-EVM bridge](#run-a-standard-evm-to-evm-bridge), [quote bridge fees](#quote-bridge-fees), [override OFT routing](#override-oft-contract-and-destination-endpoint), and [set `bridgeMaxFee`](#cap-fees-with-bridgemaxfee) on the protocol. +This guide covers standard EVM accounts: [prerequisites](#prerequisites), how to [run a standard EVM-to-EVM bridge](#run-a-standard-evm-to-evm-bridge), [quote bridge fees](#quote-bridge-fees), [override OFT routing](#override-oft-contract-and-destination-endpoint), and [set `bridgeMaxFee`](#cap-fees-with-bridgemaxfee) on the protocol. ## Prerequisites @@ -37,8 +37,8 @@ const result = await bridgeProtocol.bridge({ }) console.log('Bridge transaction hash:', result.hash) -console.log('Total fee:', result.fee, 'wei') -console.log('Bridge fee:', result.bridgeFee, 'wei') +console.log('Transaction fee:', result.fee, 'source native base units') +console.log('Bridge fee:', result.bridgeFee, 'source native base units') ``` @@ -57,8 +57,8 @@ const quote = await bridgeProtocol.quoteBridge({ amount: 1000000n }) -console.log('Estimated fee:', quote.fee, 'wei') -console.log('Bridge fee:', quote.bridgeFee, 'wei') +console.log('Estimated transaction fee:', quote.fee, 'source native base units') +console.log('Bridge fee:', quote.bridgeFee, 'source native base units') ``` Compare `quote.fee` and `quote.bridgeFee` to your risk limits before calling [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config). @@ -106,7 +106,7 @@ const customQuote = await bridgeProtocol.quoteBridge({ dstEid: Number(process.env.CUSTOM_DST_EID) }) -console.log('Custom route quote fee:', customQuote.fee, 'wei') +console.log('Custom route transaction fee:', customQuote.fee, 'source native base units') ``` @@ -115,7 +115,7 @@ Invalid pairings of `oftContractAddress` and `dstEid` fail at execution time. Va ## Cap fees with bridgeMaxFee -You can pass `bridgeMaxFee` into the [`new Usdt0ProtocolEvm(account, config?)`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#constructor) constructor so [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) rejects operations whose cost exceeds the cap: +You can pass `bridgeMaxFee` into the [`new Usdt0ProtocolEvm(account, config?)`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#constructor) constructor so [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) rejects a standard-account operation when `fee + bridgeFee` is equal to or greater than the cap: ```javascript title="Protocol-level bridgeMaxFee" import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' @@ -126,7 +126,7 @@ const cappedBridge = new Usdt0ProtocolEvm(account, { ``` -If the quote exceeds `bridgeMaxFee`, [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) throws (for example when the message includes `Exceeded maximum fee`). ERC-4337 accounts can also pass `bridgeMaxFee` in the second argument to [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config); see [Bridge with ERC-4337](/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337). +For standard accounts, both values use source-chain native base units. ERC-4337 accounts can also pass `bridgeMaxFee` in the second argument to [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config), but their returned fee fields can use different denominations. See [Bridge with ERC-4337](/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337) before applying that cap. ## Next Steps diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337.mdx index 6efddbbd..4dc88385 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337.mdx @@ -9,6 +9,7 @@ This guide covers [prerequisites](#prerequisites), how to [create an ERC-4337 ac * `@tetherto/wdk-wallet-evm-erc-4337` installed alongside [@tetherto/wdk-protocol-bridge-usdt0-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-bridge-usdt0-evm). * Bundler and paymaster endpoints for your chain (example uses Arbitrum public URLs from the API reference). +* An ERC-4337 source chain with a configured transaction-value helper: Ethereum, Arbitrum, Plasma, or Polygon. ## Create a WalletAccountEvmErc4337 account @@ -35,27 +36,19 @@ You can wrap that account with the [`new Usdt0ProtocolEvm(account, config?)`](/s ```javascript title="Usdt0ProtocolEvm with ERC-4337 account" import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' -const bridgeProtocol = new Usdt0ProtocolEvm(account, { - bridgeMaxFee: 1000000000000000n -}) +const bridgeProtocol = new Usdt0ProtocolEvm(account) ``` ## Run a gasless bridge with paymaster options -You can execute [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) with a second argument that includes `paymasterToken` (and optional `bridgeMaxFee` override) as described in the [methods table](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#methods). Approve the source-chain bridge spender first, then call [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config). Each call returns one hash for the submitted user operation. +You can execute [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) with a second argument that includes `paymasterToken` and an optional `bridgeMaxFee` override. Do not submit a separate `account.approve()` call for this flow. The protocol builds an ERC20 approval to the source-chain transaction-value helper and the helper bridge call, then submits both in one UserOperation. ```javascript title="Gasless bridge with paymasterToken" -const USDT_TOKEN_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7' +const USDT_TOKEN_ADDRESS = process.env.USDT_SOURCE_TOKEN_ADDRESS const USDT0_OFT_ADDRESS = process.env.USDT0_OFT_ADDRESS const amount = 1000000n const paymasterToken = { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' } -await account.approve({ - token: USDT_TOKEN_ADDRESS, - spender: USDT0_OFT_ADDRESS, - amount -}) - const result = await bridgeProtocol.bridge( { targetChain: 'polygon', @@ -65,18 +58,21 @@ const result = await bridgeProtocol.bridge( oftContractAddress: USDT0_OFT_ADDRESS }, { - paymasterToken, - bridgeMaxFee: 1000000000000000n + paymasterToken } ) console.log('Bridge hash:', result.hash) -console.log('Total fee:', result.fee) +console.log('Account fee:', result.fee) console.log('Bridge fee:', result.bridgeFee) ``` -The approval and bridge calls are separate user operations. Persist both hashes if your app needs to audit the complete flow. +The bundled approval and helper call produce one UserOperation hash. The protocol approves enough source token for the amount plus its helper-calculated bridge fee and tolerance. + + + +In `1.0.0-beta.7`, `bridgeFee` for this helper flow is in bridged-token base units. The account's `fee` is in native base units for native gas, paymaster-token base units for token-paid gas, or zero for sponsored gas. The protocol numerically adds those values when enforcing `bridgeMaxFee`. Do not interpret the sum as one currency or set an ERC-4337 cap until your integration has confirmed compatible units for its payment mode. diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/get-started.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/get-started.mdx index 4f796704..6a6ed0ad 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/get-started.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/get-started.mdx @@ -38,7 +38,7 @@ const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { ## Instantiate the bridge protocol -You can create a [`Usdt0ProtocolEvm`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference) instance with the [`new Usdt0ProtocolEvm(account, config?)`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference) constructor. Optional `bridgeMaxFee` caps the total bridge cost in wei (see [BridgeProtocolConfig](/sdk/bridge-modules/bridge-usdt0-evm/api-reference)): +You can create a [`Usdt0ProtocolEvm`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference) instance with the [`new Usdt0ProtocolEvm(account, config?)`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference) constructor. Optional `bridgeMaxFee` rejects a bridge when the implementation's combined fee value is at or above the cap: ```javascript title="Construct Usdt0ProtocolEvm" import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' @@ -48,6 +48,8 @@ const bridgeProtocol = new Usdt0ProtocolEvm(account, { }) ``` +For standard EVM accounts, `fee` and `bridgeFee` are source-chain native base units. ERC-4337 fee units depend on the account's gas-payment mode while `bridgeFee` is returned in bridged-token base units. Read [Fee units and `bridgeMaxFee`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#fee-units-and-bridgemaxfee) before setting a cap for an ERC-4337 flow. + The account must not be read-only. Read-only accounts cannot call [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference). @@ -83,7 +85,7 @@ Bridge operations use EVM source chains listed in the [API reference](/sdk/bridg | Unichain | `unichain` | 130 | | XLayer | `xlayer` | 196 | -Arbitrum supports ERC-4337 workflows (see [Bridge with ERC-4337](/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337)). +ERC-4337 helper workflows are available when the source chain is Ethereum, Arbitrum, Plasma, or Polygon. Other listed source chains support standard EVM accounts only. See [Bridge with ERC-4337](/sdk/bridge-modules/bridge-usdt0-evm/guides/bridge-with-4337). **Non-EVM destinations** diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/handle-errors.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/handle-errors.mdx index e38838f2..72b65c12 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/handle-errors.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/guides/handle-errors.mdx @@ -7,11 +7,11 @@ This guide describes [errors thrown by the bridge](#errors-from-the-bridge-proto ## Errors from the bridge protocol -Calls to [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) and [`quoteBridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#quotebridgeoptions-config) throw when the account is read-only, no provider is configured, the route is invalid, fees exceed [`bridgeMaxFee`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeprotocolconfig), or the destination matches the source chain. The [API reference](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#error-handling) lists representative `error.message` substrings. +Calls to [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) and [`quoteBridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#quotebridgeoptions-config) throw when the account is read-only, no provider is configured, the route is invalid, the combined fee value is at or above [`bridgeMaxFee`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeprotocolconfig), or the destination matches the source chain. The [API reference](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#error-handling) lists representative `error.message` substrings. ## Catch and branch on error messages -You can wrap [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) in `try/catch` and inspect `error.message` for stable substrings: +This standard-account example wraps [`bridge()`](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#bridgeoptions-config) in `try/catch` and inspects `error.message` for stable substrings: ```javascript title="Handle bridge errors" try { diff --git a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/index.mdx b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/index.mdx index 37caa4e0..4696d29c 100644 --- a/content/docs/sdk/bridge-modules/bridge-usdt0-evm/index.mdx +++ b/content/docs/sdk/bridge-modules/bridge-usdt0-evm/index.mdx @@ -11,8 +11,8 @@ Use the USD₮0 bridge module to move USD₮0 and XAU₮0 across supported chain - **Cross-Chain Bridge**: Move USD₮0 tokens between supported blockchains - **LayerZero Integration**: Uses LayerZero protocol for secure cross-chain transfers -- **Expanded Multi-Chain Support**: Bridge across 25+ networks including all major EVM chains -- **Non-EVM Destinations**: Bridge to Solana, TON, and TRON from any supported EVM chain +- **Expanded Multi-Chain Support**: Discover 25 configured EVM and non-EVM network keys +- **Non-EVM Destinations**: Bridge toward Solana, TON, and TRON when the source token has a compatible route contract - **Account Abstraction**: Works with both standard EVM wallets and ERC-4337 smart accounts - **Fee Management**: Built-in fee calculation and bridge cost estimation - **Token Support**: Supports USD₮0 and XAU₮0 ecosystem tokens @@ -60,10 +60,33 @@ All source chains above, plus: | TON | 30343 | | TRON | 30420 | +### Non-EVM route candidates + +For auto-resolved routes toward Solana, TON, or TRON, beta.7 skips the source chain's ordinary `oftContract`. The bundled configuration can instead consider these source contracts: + +| Source token contract family | EVM source chains with a bundled candidate | +|---|---| +| USD₮0 legacy mesh | Ethereum, Arbitrum, Celo | +| XAU₮0 OFT | Ethereum, Arbitrum, Avalanche, Celo, HyperEVM, Ink, Monad, Plasma, Polygon, Stable | + +This table shows source-side contract availability, not a guarantee that every listed source reaches every non-EVM destination. The selected contract must support the destination endpoint on-chain. Confirm the exact source-token-destination route with `quoteBridge()` before execution, or provide a verified route-specific `oftContractAddress` and optional `dstEid`. + + +`getSupportedChains()` and `getSupportedTokens()` read the static chain and token configuration. They do not validate a source-to-destination pair or prove that its LayerZero peer is configured. + + Token support is determined by the contracts deployed on each chain. The protocol checks for `oftContract`, `legacyMeshContract`, and `xautOftContract` to determine available tokens. + +Standard EVM accounts can use every supported EVM source route with a matching token deployment. ERC-4337 helper bridging is available from Ethereum, Arbitrum, Plasma, and Polygon. The ERC-4337 flow bundles token approval and the helper call into one UserOperation. + + + +ERC-4337 `fee` and `bridgeFee` values can use different denominations in `1.0.0-beta.7`. Review the [fee-unit limitation](/sdk/bridge-modules/bridge-usdt0-evm/api-reference#fee-units-and-bridgemaxfee) before configuring `bridgeMaxFee`. + + ## Next Steps diff --git a/content/docs/sdk/bridge-modules/index.mdx b/content/docs/sdk/bridge-modules/index.mdx index 6d9d699c..1e03c3cf 100644 --- a/content/docs/sdk/bridge-modules/index.mdx +++ b/content/docs/sdk/bridge-modules/index.mdx @@ -31,6 +31,6 @@ You can also: ## Swidge provider routes -For new swap or bridge provider integrations, prefer the [Swidge protocol interface](/sdk/swidge-modules). Swidge can represent bridge-only routes, swap-only routes, and combined bridge-and-swap routes. Existing standalone bridge module references remain available for released modules that have not moved to Swidge. +For new swap or bridge provider integrations, choose a released [Swidge provider module](/sdk/swidge-modules). Swidge can represent bridge-only routes, swap-only routes, and combined bridge-and-swap routes. Existing standalone bridge module references remain available for released modules that have not moved to Swidge. For a released community provider, see [Orchestra](/sdk/swidge-modules/swidge-orchestra), which implements Swidge for BTC and stablecoin routes returned by Flashnet Orchestra. diff --git a/content/docs/sdk/community-modules/index.mdx b/content/docs/sdk/community-modules/index.mdx index a0d4a118..a03fee57 100644 --- a/content/docs/sdk/community-modules/index.mdx +++ b/content/docs/sdk/community-modules/index.mdx @@ -23,6 +23,11 @@ Tether and the WDK Team do not endorse or assume responsibility for their code, | [@morpho-org/wdk-protocol-lending-morpho-evm](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) ([GitHub](https://github.com/morpho-org/sdks/tree/main/packages/wdk-protocol-lending-morpho-evm)) | Lending Module | Morpho EVM lending module for vault deposits, collateral supply, borrowing, repayment, and position reads | [Docs](/sdk/lending-modules/lending-morpho-evm/) | [Morpho Association](https://morpho.org/) | | [wdk-protocol-swidge-orchestra](https://www.npmjs.com/package/wdk-protocol-swidge-orchestra) ([GitHub](https://github.com/flashnetxyz/wdk-protocol-swidge-orchestra)) | Swidge Module | Flashnet Orchestra Swidge provider for BTC and stablecoin routes returned by Orchestra | [Docs](/sdk/swidge-modules/swidge-orchestra/) | [Flashnet](https://github.com/flashnetxyz) | | [@rhino.fi/wdk-protocol-swidge-rhinofi](https://www.npmjs.com/package/@rhino.fi/wdk-protocol-swidge-rhinofi) ([GitHub](https://github.com/rhinofi/wdk-protocol-swidge-rhinofi)) | Swidge Module | Rhino.fi cross-chain swap and bridge routes through the WDK swidge interface | [Docs](/sdk/swidge-modules/swidge-rhinofi/) | [Rhino.fi](https://rhino.fi/) | +| [@lifi/wdk-protocol-swidge-lifi](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi) ([GitHub](https://github.com/lifinance/wdk-lifi-swidge-protocol)) | Swidge Module | LI.FI swap and bridge routes through the WDK swidge interface | [Docs](/sdk/swidge-modules/swidge-lifi/) | [LI.FI](https://li.fi/) | +| [@moonpay/wdk-protocol-swidge-moonpay-trade](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade) | Swidge Module | Routes through MoonPay Trade | [README](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade#readme) | [MoonPay](https://www.moonpay.com/) | +| [@swapdk/wdk-protocol-swidge-swapdk](https://www.npmjs.com/package/@swapdk/wdk-protocol-swidge-swapdk) ([GitHub](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk)) | Swidge Module | Routes through SwapDK | [README](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk#readme) | [SwapDK](https://swapdk.com/) | +| [@gobob/wdk-protocol-swidge-gateway](https://www.npmjs.com/package/@gobob/wdk-protocol-swidge-gateway) ([GitHub](https://github.com/bob-collective/wdk-protocol-swidge-gateway)) | Swidge Module | Routes through the BOB Gateway | [README](https://github.com/bob-collective/wdk-protocol-swidge-gateway#readme) | [BOB](https://www.gobob.xyz/) | +| [@symbiosis-finance/wdk-protocol-swidge-symbiosis](https://www.npmjs.com/package/@symbiosis-finance/wdk-protocol-swidge-symbiosis) ([GitHub](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis)) | Swidge Module | Runtime-discovered exact-input quotes with EVM and Bitcoin source execution through the Symbiosis API | [Docs](/sdk/swidge-modules/swidge-symbiosis/) | [Symbiosis](https://symbiosis.finance/) | --- @@ -34,7 +39,7 @@ Want to extend WDK with your own custom module? Use the `create-wdk-module` CLI npx @tetherto/create-wdk-module@latest ``` -The CLI generates source files, tests, TypeScript type definitions, and CI workflows for all six module types (wallet, swap, bridge, lending, fiat, swidge). See the [Create WDK Module documentation](/tools/create-wdk-module) for the full guide, CLI options, and generated project structure. +The CLI generates source files, tests, TypeScript type definitions, and CI workflows for supported wallet and protocol module types. See the [Create WDK Module documentation](/tools/create-wdk-module) for the full guide, CLI options, and generated project structure. You can also: diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/api-reference.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/api-reference.mdx index 05919dbd..d8a8cf5b 100644 --- a/content/docs/sdk/community-modules/wdk-wallet-cosmos/api-reference.mdx +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/api-reference.mdx @@ -1,114 +1,282 @@ --- title: Cosmos wallet API reference -description: API reference for the independently maintained @base58-io/wdk-wallet-cosmos module. +description: Public API reference for @base58-io/wdk-wallet-cosmos version 1.0.0-beta.4. docType: reference -schemaType: TechArticle +schemaType: APIReference +icon: Code --- -This page summarizes the public API exposed by `@base58-io/wdk-wallet-cosmos` based on the package README, `package.json`, generated TypeScript declarations, and source files from commit `b924dba`. +This reference covers the public package surface published in [`@base58-io/wdk-wallet-cosmos@1.0.0-beta.4`](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos). -This module is developed and maintained independently by a third-party contributor. Tether and the WDK team do not endorse or assume responsibility for its code, security, or maintenance. Review the package and use your own judgment before using it with real funds. +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. +Repository `main` contains APIs that are not part of this release. Use the [`v1.0.0-beta.4` source tag](https://github.com/base58-io/wdk-wallet-cosmos/tree/v1.0.0-beta.4) when comparing this reference with code. + ## Package | Field | Value | |---|---| | Package | `@base58-io/wdk-wallet-cosmos` | -| Repository | [base58-io/wdk-wallet-cosmos](https://github.com/base58-io/wdk-wallet-cosmos) | -| Main entry | `index.js` | -| Bare entry | `bare.js` | -| Types | `types/index.d.ts` | +| Version | `1.0.0-beta.4` | +| Module format | ESM | +| Default entry | `index.js` | +| Bare conditional entry | `bare.js` | +| Type declarations | `types/index.d.ts` | +| Runtime engines | Not declared in `package.json` | +| Peer dependencies | None | +| WDK wallet dependency | `@tetherto/wdk-wallet@1.0.0-beta.8` | -## Exports +The package export map exposes the root module and a `./package` subpath for `package.json`. Internal files are not supported public entrypoints. -| Export | Description | -|---|---| -| `default` | `WalletManagerCosmos` | -| `WalletAccountCosmos` | Cosmos wallet account implementation | -| `CosmosWalletConfig` | Wallet configuration type | -| `ResolvedChainConfig` | Resolved chain configuration type | -| `resolveChainConfig(config)` | Resolves chain config from chain-registry or custom fields | -| `getAvailableChains()` | Returns chain names available through the bundled chain-registry data | -| `isKnownChain(chainName)` | Returns whether a chain name exists in the registry data | - -## `CosmosWalletConfig` +## Root exports -| Field | Required | Description | -|---|---:|---| -| `chainName` | No | Chain name from chain-registry, such as `cosmoshub`, `osmosis`, or `juno`. | -| `rpcEndpoints` | No | RPC endpoint URLs. If provided, they override registry endpoints. | -| `retryCount` | No | Maximum retry rounds for RPC fallback. Default is `3`. | -| `retryDelay` | No | Base retry delay in milliseconds. Default is `150`. | -| `addressPrefix` | No | Bech32 address prefix. Defaults to `cosmos` when not resolved from registry. | -| `nativeDenom` | No | Native token denomination. Defaults to `uatom` when not resolved from registry. | -| `coinType` | No | BIP-44 coin type. Defaults to `118` when not resolved from registry. | -| `gasPrice` | No | Gas price with denomination, such as `0.025uatom`. | -| `transferMaxFee` | No | Maximum fee amount for transfer operations. | -| `ibcChannels` | No | IBC channel map keyed by destination Bech32 prefix. | +| Export | Kind | Description | +|---|---|---| +| `default` | Runtime | `WalletManagerCosmos` | +| `WalletAccountCosmos` | Runtime | Seed-backed Cosmos account implementation | +| `resolveChainConfig(config)` | Runtime | Resolves registry or custom chain configuration | +| `getAvailableChains()` | Runtime | Returns bundled registry names whose chain type is `cosmos` | +| `isKnownChain(chainName)` | Runtime | Checks whether a name exists in the bundled registry | +| `FeeRates` | Type only | Normal and fast fee amounts | +| `KeyPair` | Type only | Public key and sensitive private-key fields | +| `TransactionResult` | Type only | Transaction hash and fee | +| `TransferOptions` | Type only | Denomination, recipient, and amount | +| `TransferResult` | Type only | Transfer hash and fee | +| `CosmosWalletConfig` | Type only | Input wallet configuration | +| `ResolvedChainConfig` | Type only | Resolved chain configuration | ## `WalletManagerCosmos` +### Constructor + ```js -import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' +new WalletManagerCosmos(seed, config?) +``` + +| Parameter | Type | Required | Description | +|---|---|---:|---| +| `seed` | `string \| Uint8Array` | Yes | BIP-39 mnemonic or seed bytes | +| `config` | `CosmosWalletConfig` | No | Chain, RPC, fee, retry, and IBC configuration | + +The released constructor does not accept an external signer. Named signer overloads visible on repository `main` are not published in `1.0.0-beta.4`. + +### Inherited static methods -const manager = new WalletManagerCosmos(seed, config) +| Method | Returns | Description | +|---|---|---| +| `WalletManagerCosmos.getRandomSeedPhrase(wordCount = 12)` | `string` | Generates a 12- or 24-word BIP-39 mnemonic | +| `WalletManagerCosmos.isValidSeedPhrase(seedPhrase)` | `boolean` | Validates a BIP-39 mnemonic | + +### Methods + +| Method | Returns | Behavior | +|---|---|---| +| `getAccount(index = 0)` | `Promise` | Derives and caches `0'/0/{index}` below the chain coin type | +| `getAccountByPath(path)` | `Promise` | Derives and caches a relative suffix such as `0'/0/5` | +| `getFeeRates()` | `Promise` | Calculates normal and fast amounts for the fixed gas limit | +| `dispose()` | `void` | Disposes cached accounts, zeros the manager seed bytes, and marks the manager unusable | + +### Properties + +| Property | Type | Description | +|---|---|---| +| `seed` | `Uint8Array` | Inherited sensitive seed bytes; do not log or retain | +| `isDisposed` | `boolean` | Whether `dispose()` has been called | + +`getAccount()` caches by relative derivation path. Disposing a cached account directly does not evict it from the manager; prefer disposing the manager at the end of its lifecycle. + +### `getFeeRates()` + +```js +const { normal, fast } = await manager.getFeeRates() ``` -| Method or property | Description | -|---|---| -| `constructor(seed, config)` | Creates a manager from a BIP-39 seed phrase or seed bytes. | -| `getAccount(index = 0)` | Returns the Cosmos account at a BIP-44 index. | -| `getAccountByPath(path)` | Returns the Cosmos account for a specific BIP-44 derivation suffix. | -| `getFeeRates()` | Returns normal and fast fee rates from chain-registry fee tiers or configured gas price. Requires RPC configuration. | -| `isDisposed` | Returns whether the manager has been disposed. | -| `dispose()` | Disposes cached wallet accounts and clears manager state. | +The returned values are deterministic fee amounts in the selected fee denomination: + +- registry configuration uses average and high gas-price tiers; +- explicit gas-price configuration returns the same amount for both priorities; +- final fallback uses `0.025` and `0.04`; +- all calculations use a gas limit of `200000`. + +The method requires at least one configured RPC endpoint but does not make an RPC request. ## `WalletAccountCosmos` -| Method or property | Description | +Create accounts through `WalletManagerCosmos`. The exported static factory is also public: + +```js +const account = await WalletAccountCosmos.create(seed, "0'/0/0", config) +``` + +Do not call the class constructor directly. Its parameters are implementation details. + +### Account methods + +| Method | Returns | Behavior | +|---|---|---| +| `getAddress()` | `Promise` | Returns the locally derived Bech32 address | +| `getBalance(denom?)` | `Promise` | Reads one denomination; defaults to `nativeDenom` | +| `getTokenBalance(denom)` | `Promise` | Alias behavior for a denomination-specific balance | +| `getTokenBalances(denoms)` | `Promise>` | Reads all balances and returns requested denominations that are present | +| `quoteTransfer(options)` | `Promise<{ fee: bigint }>` | Calculates a fixed-gas transfer fee without broadcasting | +| `transfer(options)` | `Promise` | Sends a bank transfer or configured IBC transfer | +| `sign(message)` | `Promise` | Returns a JSON-encoded ADR-36 `StdSignature` | +| `verify(message, signature)` | `Promise` | Verifies ADR-36 data against this account | +| `signTransaction(transaction)` | `Promise` | Returns a CosmJS signed `TxRaw` without broadcasting | +| `quoteSendTransaction(transaction)` | `Promise<{ fee: bigint }>` | Calculates a fixed-gas native-send fee | +| `sendTransaction(transaction)` | `Promise` | Signs and broadcasts a native bank send | +| `getTransactionReceipt(hash)` | `Promise` | Returns the indexed transaction or throws when it is not found | +| `toReadOnlyAccount()` | Never succeeds | Throws because read-only accounts are not implemented | +| `dispose()` | `void` | Zeros the module-owned private-key buffer and marks the account unusable | + +### Account properties + +| Property | Type | Description | +|---|---|---| +| `index` | `number` | Last component of the full derivation path | +| `path` | `string` | Full path such as `m/44'/118'/0'/0/0` | +| `keyPair` | `KeyPair` | Public key and the underlying sensitive private-key buffer | +| `isDisposed` | `boolean` | Whether `dispose()` has been called | + + +`keyPair.privateKey` exposes the account's underlying private-key bytes. Avoid using this property unless an integration requires it. Never log, serialize, or retain the value, and do not assume `dispose()` can erase copies held elsewhere. + + +### Balance behavior + +`getBalance()`, `getTokenBalance()`, and `getTokenBalances()` require RPC endpoints. Values are returned in base units. + +`getTokenBalances(denoms)` calls the RPC all-balances query and filters it. A requested denomination with no returned balance is omitted rather than included with `0n`. + +### Message signing + +`sign(message)` signs UTF-8 text with ADR-36 and returns a JSON string containing the public key and base64 signature. `verify()`: + +- binds the signature public key to this account's Bech32 address; +- returns `false` for a different message or account; +- returns `false`, rather than throwing, for malformed signature input. + +### Transaction input + +`signTransaction()`, `quoteSendTransaction()`, and `sendTransaction()` consume the shared WDK transaction shape: + +```ts +type Transaction = { + to: string + value: number | bigint +} +``` + +The account converts this input to one `/cosmos.bank.v1beta1.MsgSend`: + +- denomination is always the configured `nativeDenom`; +- amount is `value` converted to a string; +- memo is fixed to `Transfer via WDK`; +- gas is fixed at `200000`. + +`signTransaction()` needs RPC to obtain signing context and returns a signed CosmJS `TxRaw`. Its generated beta declaration types the result as `unknown`. + + +`sendTransaction()` in `1.0.0-beta.4` accepts only the unsigned WDK transaction shape. It does not accept or broadcast the signed value returned by `signTransaction()`. + + +`quoteSendTransaction()` ignores the transaction contents after receiving them. It checks for configured endpoints, calculates the fixed-gas fee, and applies `transferMaxFee`; it does not simulate or validate the transaction through RPC. + +### Transfer input + +```ts +type TransferOptions = { + token: string + recipient: string + amount: number | bigint +} +``` + +| Field | Meaning | |---|---| -| `static create(seed, path, config)` | Creates an account for a BIP-44 path. | -| `getAddress()` | Returns the Bech32 account address. | -| `getBalance(denom?)` | Returns a token balance in base units. Defaults to the configured native denomination. | -| `getTokenBalance(denom)` | Returns a single token balance in base units. | -| `getTokenBalances(denoms)` | Returns balances for multiple denominations. | -| `transfer(options)` | Sends a token transfer and returns the transaction hash and fee. | -| `quoteTransfer(options)` | Quotes a transfer fee without sending. | -| `keyPair` | Returns the account key pair. | -| `sign(message)` | Signs an arbitrary message using ADR-36. | -| `verify(message, signature)` | Verifies an ADR-36 signature. | -| `signTransaction(transaction)` | Signs a Cosmos transaction without broadcasting it. | -| `sendTransaction(transaction)` | Signs and broadcasts a Cosmos transaction. | -| `quoteSendTransaction(transaction)` | Quotes a Cosmos transaction fee. | -| `getTransactionReceipt(hash)` | Looks up a transaction by hash through RPC. | -| `index` | Account index parsed from the derivation path. | -| `path` | Full account derivation path. | -| `isDisposed` | Returns whether the account has been disposed. | -| `dispose()` | Erases sensitive account data from memory and prevents further use. | - -## Transfer options - -The module uses WDK `TransferOptions` for token transfers. - -| Field | Required | Description | -|---|---:|---| -| `token` | Yes | Cosmos denomination, such as `uatom`. | -| `recipient` | Yes | Destination Bech32 address. | -| `amount` | Yes | Amount in base units. | +| `token` | Cosmos denomination such as `uatom` or an IBC denomination | +| `recipient` | Destination Bech32 address | +| `amount` | Integer amount in base units | -## Helper functions +For matching Bech32 prefixes, `transfer()` calls a bank send. For a different prefix, it selects `ibcChannels[recipientPrefix]` and broadcasts IBC `MsgTransfer` with a fixed 600-second timestamp timeout. + + +`transfer()` checks `transferMaxFee` only after the bank or IBC operation has been signed and broadcast. A transfer can succeed on-chain and then throw the fee-limit error. Call `quoteTransfer()`, enforce an application limit, and validate the operation before `transfer()`. + + +`quoteTransfer()` checks that an IBC channel mapping exists for a different prefix. It does not query RPC, simulate gas, validate the sender balance, or prove that the channel is active. + +### Transaction receipts -| Function | Description | +`getTransactionReceipt(hash)` performs one `StargateClient.getTx()` lookup. It returns the raw indexed transaction object when found. When the transaction is not yet indexed or does not exist, it throws: + +```text +Transaction not found: +``` + +The method does not poll. + +## `CosmosWalletConfig` + +| Field | Type | Required | Resolved behavior | +|---|---|---:|---| +| `chainName` | `string` | No | Selects bundled registry metadata; unknown names throw | +| `rpcEndpoints` | `string[]` | No | Replaces registry endpoints when non-empty | +| `retryCount` | `number` | No | Defaults to `3` retry rounds | +| `retryDelay` | `number` | No | Defaults to `150` milliseconds | +| `addressPrefix` | `string` | No | Registry prefix or `cosmos` | +| `nativeDenom` | `string` | No | First registry fee denomination or `uatom` | +| `coinType` | `number` | No | Registry SLIP-44 value or `118` | +| `gasPrice` | `string` | No | Compact amount and denomination such as `0.025uatom` | +| `transferMaxFee` | `number \| bigint` | No | Quote limit and post-broadcast `transfer()` check | +| `ibcChannels` | `Record` | No | IBC source channels keyed by destination prefix | + +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration) for precedence and safety details. + +## `ResolvedChainConfig` + +`resolveChainConfig()` returns: + +| Field | Type | |---|---| -| `resolveChainConfig(config)` | Resolves a chain config from `chainName`, custom config, and chain-registry metadata. Throws if `chainName` is unknown. | -| `getAvailableChains()` | Returns known chain names from chain-registry. | -| `isKnownChain(chainName)` | Returns `true` when `chainName` is available in chain-registry. | +| `rpcEndpoints` | `string[]` | +| `retryCount` | `number` | +| `retryDelay` | `number` | +| `addressPrefix` | `string` | +| `nativeDenom` | `string` | +| `coinType` | `number` | +| `gasPrice` | `string \| undefined` | +| `gasPriceStep` | `{ low: number, average: number, high: number, denom: string } \| undefined` | +| `transferMaxFee` | `number \| bigint \| undefined` | +| `chainId` | `string \| undefined` | +| `prettyName` | `string \| undefined` | +| `ibcChannels` | `Record \| undefined` | + +## Helper functions + +### `resolveChainConfig(config?)` + +Returns registry-backed or custom resolved configuration. An unknown `chainName` throws and instructs the caller to use custom configuration. + +### `getAvailableChains()` + +Returns chain names whose bundled registry entry has `chainType === 'cosmos'`. + +### `isKnownChain(chainName)` + +Returns whether any bundled registry entry has the supplied name. It does not test RPC reachability. + +## Error and lifecycle behavior -## Limitations +- Invalid mnemonic and derivation paths reject account creation. +- RPC-backed methods throw when the endpoint list is empty. +- Most Cosmos ABCI, JSON-RPC validation, funds, gas, sequence, and signing errors are not retried. +- Network-shaped errors can fall back or retry. A write error can therefore have an ambiguous on-chain outcome. +- Every account operation except `toReadOnlyAccount()` checks disposal state; the read-only method always throws its unsupported error. +- `dispose()` makes manager and account methods unusable, but it cannot revoke seed or key copies held by application code. -- RPC endpoints are required for balance reads, fee quotes, transfers, and receipt lookup. -- `toReadOnlyAccount()` is present on the account class but throws because read-only accounts are not implemented. -- `transferMaxFee` rejects transfer or send quotes when the estimated fee is greater than or equal to the configured limit. -- IBC transfer support depends on `ibcChannels` configuration for the destination Bech32 prefix. +See [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for safe write and recovery guidance. diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/configuration.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/configuration.mdx new file mode 100644 index 00000000..9d8a561c --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/configuration.mdx @@ -0,0 +1,157 @@ +--- +title: Configuration +description: Configure chain metadata, RPC fallback, fees, and IBC channels for the Base58 Cosmos wallet module. +docType: reference +schemaType: TechArticle +icon: Settings +--- + +`WalletManagerCosmos` accepts an optional `CosmosWalletConfig` object. The same configuration is resolved for every account created by that manager. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Configuration options + +| Field | Type | Default | Behavior | +|---|---|---|---| +| `chainName` | `string` | None | Looks up bundled `chain-registry` metadata. An unknown name throws. | +| `rpcEndpoints` | `string[]` | Registry endpoints or `[]` | Replaces registry endpoints when the array is non-empty. | +| `retryCount` | `number` | `3` | Maximum retry rounds after the first round. | +| `retryDelay` | `number` | `150` | Base delay in milliseconds for exponential backoff. | +| `addressPrefix` | `string` | Registry prefix or `cosmos` | Bech32 prefix used to derive the account address. | +| `nativeDenom` | `string` | First registry fee denomination or `uatom` | Denomination used by `getBalance()` and native transaction methods. | +| `coinType` | `number` | Registry SLIP-44 value or `118` | Coin type inserted into the BIP-44 derivation path. | +| `gasPrice` | `string` | Registry average tier or none | Gas price in compact `amount+denom` form, such as `0.025uatom`. | +| `transferMaxFee` | `number \| bigint` | None | Limit consulted by quote methods and, after broadcast, by `transfer()`. | +| `ibcChannels` | `Record` | None | Source-channel map keyed by destination Bech32 prefix. | + +The published package does not expose `transactionMaxFee`. That option exists only in unreleased repository code and must not be used with `1.0.0-beta.4`. + +## Choose a configuration mode + +### Registry-backed configuration + +Use a chain name to resolve the Bech32 prefix, native denomination, coin type, RPC endpoints, chain ID, and fee metadata from the bundled `chain-registry@2.0.197` data. + +```js +const cosmosHubConfig = { + chainName: 'cosmoshub', +} +``` + +Registry data is packaged data, not a runtime discovery service. Verify endpoints and chain parameters before production use. + +### Custom chain configuration + +Omit `chainName` when you need to control every chain-specific field. + +```js +const customChainConfig = { + rpcEndpoints: [ + 'https://rpc-1.example.invalid', + 'https://rpc-2.example.invalid', + ], + addressPrefix: 'cosmos', + nativeDenom: 'uatom', + coinType: 118, + gasPrice: '0.025uatom', + retryCount: 3, + retryDelay: 150, +} +``` + +Replace the example endpoints with trusted endpoints for the intended chain. + +### Registry metadata with custom endpoints + +Providing both values keeps registry metadata but replaces the registry endpoint list. + +```js +const hybridConfig = { + chainName: 'cosmoshub', + rpcEndpoints: [ + 'https://rpc-1.example.invalid', + 'https://rpc-2.example.invalid', + ], +} +``` + +The custom endpoints are not appended to the registry list. + +## Fee and gas behavior + +The release uses a fixed gas limit of `200000` for bank sends and transfers. It calculates the fee as: + +```text +ceil(gas price amount × 200000) +``` + +The account selects a gas price in this order: + +1. The average `gasPriceStep` from registry fee metadata. +2. The explicit `gasPrice` string. +3. The fallback average gas price `0.025` in `nativeDenom`. + + +When `chainName` resolves a registry `gasPriceStep`, that registry tier takes precedence over an explicit `gasPrice`. To use only an explicit gas price, use a complete custom configuration without `chainName`. + + +`getFeeRates()` returns deterministic fee amounts for the same fixed gas limit. With registry tiers, `normal` uses the average tier and `fast` uses the high tier. With only an explicit gas price, both values are the same. The method requires a non-empty RPC endpoint configuration but does not query RPC. + +Fee quotes also use this deterministic calculation. They do not simulate the transaction, check the sender's balance, or validate that the fee is currently accepted by the chain. + +### Fee limits + +`quoteTransfer()` and `quoteSendTransaction()` throw when the calculated fee is greater than or equal to `transferMaxFee`. + + +In `1.0.0-beta.4`, `transfer()` checks `transferMaxFee` after signing and broadcasting, and `sendTransaction()` does not enforce it. Always quote and enforce an application-level fee limit before either write. Do not treat `transferMaxFee` alone as a pre-broadcast guard. + + +## RPC fallback + +RPC-backed operations try endpoints in array order. A default `retryCount` of `3` permits the initial round plus three retry rounds. The delay between rounds grows exponentially from `retryDelay`. + +Network-shaped failures such as timeouts, connection resets, DNS failures, HTTP `429`, and HTTP `5xx` responses can move to another endpoint or retry. Chain and transaction failures such as insufficient funds, invalid sequence, invalid address, out of gas, or invalid chain ID fail immediately. + + +A timeout or connection failure during a write does not prove that the transaction was not accepted. Check the transaction hash, account sequence, and chain state before attempting the write again. + + +## IBC channels + +`transfer()` compares the recipient's Bech32 prefix with `addressPrefix`: + +- matching prefixes use a Cosmos bank send; +- different prefixes require a matching entry in `ibcChannels` and use IBC `MsgTransfer`. + +```js +const ibcConfig = { + chainName: 'cosmoshub', + ibcChannels: { + osmo: { + sourceChannel: '', + }, + }, +} +``` + +Replace the placeholder with the source channel on the configured source chain. The module does not discover or validate channel topology. It uses source port `transfer` and a fixed 600-second timestamp timeout. + +## Security and cleanup + +- Use RPC endpoints you trust for balances, account metadata, transaction signing context, broadcast results, and receipts. +- Do not log mnemonic, seed, or `keyPair.privateKey` values. +- Call `manager.dispose()` in `finally`; it disposes cached accounts and zeros module-owned seed and private-key buffers. +- Disposal cannot erase copies retained by application code or guarantee cleanup inside every dependency. + +## Next steps + +- [Get started](/sdk/community-modules/wdk-wallet-cosmos/guides/get-started) +- [Transfer tokens and use IBC](/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens) +- [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) +- [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/check-balances.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/check-balances.mdx new file mode 100644 index 00000000..ea616b88 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/check-balances.mdx @@ -0,0 +1,79 @@ +--- +title: Check balances +description: Read native and denomination-specific Cosmos balances through RPC. +docType: how-to +schemaType: TechArticle +--- + +Balance methods return integer base-unit amounts as `bigint`. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Read the native balance + +`getBalance()` uses the configured `nativeDenom`. Pass a denomination to query a different balance. + +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' + +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') + +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) + +try { + const account = await manager.getAccount(0) + + const nativeBalance = await account.getBalance() + const atomBalance = await account.getBalance('uatom') + + console.log({ + nativeBaseUnits: nativeBalance.toString(), + atomBaseUnits: atomBalance.toString(), + }) +} finally { + manager.dispose() +} +``` + +Do not treat base units as display units. Apply denomination metadata and decimal formatting in your application. + +## Read token balances + +Use `getTokenBalance(denom)` for one denomination or `getTokenBalances(denoms)` to filter the account's full balance response: + +```js +const atomBalance = await account.getTokenBalance('uatom') + +const balances = await account.getTokenBalances([ + 'uatom', + 'ibc/', +]) + +console.log(atomBalance.toString()) +console.log(balances) +``` + +Replace the IBC placeholder with a trusted denomination trace hash for the configured chain. + +`getTokenBalances()` omits a requested denomination when the RPC response has no entry for it. If your application wants an explicit zero, apply that policy after the call: + +```js +const atom = balances.uatom ?? 0n +``` + +## RPC and account requirements + +- Balance calls require at least one configured RPC endpoint. +- Registry endpoints can become stale or rate limited; configure trusted alternatives when needed. +- The module retries network-shaped failures according to `retryCount` and `retryDelay`. +- Chain and query errors fail immediately. +- `toReadOnlyAccount()` is not implemented in `1.0.0-beta.4`, so a seed-backed account is required even for reads. + +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration) for endpoint precedence and [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for retry guidance. diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/get-started.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/get-started.mdx new file mode 100644 index 00000000..663c0856 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/get-started.mdx @@ -0,0 +1,84 @@ +--- +title: Get started +description: Install the released Base58 Cosmos wallet module and derive a Cosmos account. +docType: getting-started +schemaType: TechArticle +--- + +This guide uses the published `1.0.0-beta.4` package. The repository's default branch can contain unreleased APIs that are not available in this version. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## 1. Install the package + +Install the exact version documented by this guide: + +```bash +npm install @base58-io/wdk-wallet-cosmos@1.0.0-beta.4 +``` + +The package is ESM and does not declare a Node.js engine requirement. It also provides a Bare conditional entry through the same package specifier. + +## 2. Configure the chain + +`chainName` loads bundled chain metadata, including the Bech32 prefix, native denomination, coin type, and RPC endpoints. + +```js +const config = { + chainName: 'cosmoshub', +} +``` + +Bundled registry data is packaged metadata, not live chain discovery. Verify the resolved endpoints and chain parameters before production use. See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration) for custom endpoints and fee behavior. + +## 3. Derive an account + +Load the mnemonic from secure storage and dispose the manager even when an operation fails: + +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' + +const seedPhrase = process.env.WDK_SEED_PHRASE + +if ( + !seedPhrase || + !WalletManagerCosmos.isValidSeedPhrase(seedPhrase) +) { + throw new Error('A valid WDK_SEED_PHRASE is required') +} + +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) + +try { + const account = await manager.getAccount(0) + const address = await account.getAddress() + + console.log('Cosmos address:', address) +} finally { + manager.dispose() +} +``` + +Address derivation is local. Balance, signing-context, broadcast, and receipt methods require at least one configured RPC endpoint. + + +Never hardcode, log, or commit a mnemonic, seed, or `keyPair.privateKey`. `dispose()` clears buffers owned by the module, but it cannot erase the environment string or copies retained by your application or its dependencies. + + +## Released limitations + +In `1.0.0-beta.4`: + +- accounts are derived from the manager seed; named and external signers are not supported; +- `toReadOnlyAccount()` is not implemented; +- fee quotes use fixed gas and do not simulate through RPC; +- `signTransaction()` cannot be handed to `sendTransaction()` for broadcast; +- `transferMaxFee` is not a reliable pre-broadcast guard for every write. + +Continue with [Manage accounts](/sdk/community-modules/wdk-wallet-cosmos/guides/manage-accounts) or review the [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference). diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors.mdx new file mode 100644 index 00000000..9840d5f5 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors.mdx @@ -0,0 +1,135 @@ +--- +title: Handle errors +description: Handle Cosmos wallet validation, RPC, fee, receipt, retry, and lifecycle failures safely. +docType: how-to +schemaType: TechArticle +--- + +Classify an error before deciding whether an operation is safe to repeat. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Common failures + +| Failure | Likely cause | Safe response | +|---|---|---| +| Invalid mnemonic or derivation path | Seed or account path is invalid | Reject the input before creating an account | +| Unknown chain name | `chainName` is absent from bundled registry data | Use a supported name or provide a complete custom configuration | +| No RPC endpoints | An RPC-backed method resolved an empty endpoint list | Configure at least one trusted endpoint | +| Invalid Bech32 address or prefix | Recipient format does not match the intended flow | Validate the address, checksum, and expected source or destination prefix | +| Missing IBC channel mapping | Destination prefix has no `ibcChannels` entry | Configure and independently verify the source channel | +| Fee-limit error | The deterministic quote met or exceeded `transferMaxFee` | Apply an application fee policy before the write | +| `Transaction not found: ` | The one-shot receipt lookup found no indexed result | Wait according to application policy and query again | +| Disposed manager or account | A method was called after `dispose()` | Create a new manager lifecycle; do not reuse the disposed cached account | +| Read-only conversion error | `toReadOnlyAccount()` is unsupported in this release | Use a short-lived seed-backed account or a different integration | + + +For `transfer()`, a fee-limit error can occur after the transaction was broadcast. Do not interpret that error as proof that the transfer failed. + + +## Validate before a write + +Accept only positive integer base-unit amounts and validate chain-specific identifiers before constructing an operation: + +```js +function assertBaseUnitAmount(amount) { + const isValidBigInt = ( + typeof amount === 'bigint' && + amount > 0n + ) + const isValidNumber = ( + typeof amount === 'number' && + Number.isSafeInteger(amount) && + amount > 0 + ) + + if (!isValidBigInt && !isValidNumber) { + throw new TypeError('Amount must be a positive safe integer') + } +} + +assertBaseUnitAmount(transfer.amount) +``` + +Also verify: + +- the intended chain ID and trusted RPC endpoints; +- the recipient's Bech32 checksum and expected prefix; +- the denomination against an application allowlist; +- the sender balance and application spending policy; +- the IBC source channel, destination chain, and route status; +- the quote against an application-owned maximum fee. + +Quotes use configured metadata and fixed gas. They do not prove the operation will pass current chain validation. + +## Distinguish retryable failures + +The module can fall back or retry network-shaped failures such as timeouts, connection resets, DNS failures, HTTP `429`, and HTTP `5xx` responses. Cosmos ABCI and JSON-RPC transaction failures such as insufficient funds, invalid sequence, invalid address, out of gas, or invalid chain ID fail immediately. + +```js +function getErrorMessage(error) { + return error instanceof Error ? error.message : String(error) +} + +try { + const result = await account.sendTransaction(transaction) + console.log('Broadcast hash:', result.hash) +} catch (error) { + console.error('Broadcast status is unresolved:', getErrorMessage(error)) + throw error +} +``` + +Do not build application retry logic from message matching alone. Preserve the original error and use it for diagnostics, but make retry decisions from the operation type and verified chain state. + +## Resolve ambiguous writes + +A network failure can happen after a node accepts the transaction but before your application receives the response. Blindly repeating `sendTransaction()` or `transfer()` can create a second valid payment. + +When a write throws: + +1. Treat its outcome as unknown. +2. Query a known hash when one is available. +3. Check the sender sequence, balances, and a trusted chain index. +4. Reconcile the intended payment in your application ledger. +5. Retry only after establishing that the first transaction was not accepted. + +`getTransactionReceipt()` performs one lookup and throws while a valid transaction is still waiting to be indexed. Poll with a bounded application policy rather than treating the first miss as final. + +## Quote before every write + +```js +const applicationMaxFee = 5_000n +const { fee } = await account.quoteSendTransaction(transaction) + +if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') +} + +const result = await account.sendTransaction(transaction) +``` + +`sendTransaction()` does not enforce `transferMaxFee`. `transfer()` checks that option only after broadcast. The published package does not expose `transactionMaxFee`. + +## Always dispose sensitive state + +```js +const manager = new WalletManagerCosmos(seedPhrase, config) + +try { + const account = await manager.getAccount(0) + // Perform the minimum required work. +} finally { + manager.dispose() +} +``` + +Disposal zeros the manager's module-owned seed buffer and cached accounts' module-owned private-key buffers. It cannot erase copies held in environment strings, application variables, logs, or dependencies. + +Avoid `account.keyPair` unless an integration strictly requires it. Its `privateKey` field exposes the underlying sensitive buffer; retaining a reference can defeat cleanup assumptions. + +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration#rpc-fallback) for retry precedence and the [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference#error-and-lifecycle-behavior) for exact released behavior. diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/manage-accounts.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/manage-accounts.mdx new file mode 100644 index 00000000..55372829 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/manage-accounts.mdx @@ -0,0 +1,82 @@ +--- +title: Manage accounts +description: Derive, cache, and dispose Cosmos accounts by index or BIP-44 path. +docType: how-to +schemaType: TechArticle +--- + +`WalletManagerCosmos` derives secp256k1 accounts below the configured Cosmos coin type. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Derive accounts by index + +`getAccount(index)` derives the relative path `0'/0/{index}`. With the Cosmos Hub coin type `118`, index `5` resolves to `m/44'/118'/0'/0/5`. + +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' + +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') + +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) + +try { + const firstAccount = await manager.getAccount(0) + const sixthAccount = await manager.getAccount(5) + + console.log({ + firstAddress: await firstAccount.getAddress(), + sixthAddress: await sixthAccount.getAddress(), + sixthPath: sixthAccount.path, + }) +} finally { + manager.dispose() +} +``` + +`getAccount()` defaults to index `0`. + +## Derive an account by path + +Pass the relative suffix below `m/44'/'/`: + +```js +const account = await manager.getAccountByPath("0'/0/5") + +console.log(account.path) +// m/44'/118'/0'/0/5 when coinType is 118 +``` + +Use the same `try`/`finally` manager lifecycle shown above. Invalid derivation paths reject account creation. + +## Understand caching + +The manager caches accounts by relative derivation path. Repeating either lookup for the same path returns the cached account instance: + +```js +const byIndex = await manager.getAccount(5) +const byPath = await manager.getAccountByPath("0'/0/5") + +console.log(byIndex === byPath) // true +``` + +Prefer `manager.dispose()` when the wallet lifecycle ends. Disposing one cached account directly does not evict it; a later lookup for the same path returns that disposed instance. + +## Signer and read-only limits + +The released manager derives accounts only from its mnemonic or seed bytes. Named signers and externally supplied signer implementations shown on the repository's default branch are not part of `1.0.0-beta.4`. + +`account.toReadOnlyAccount()` always throws in this release. Even balance-only flows must create a seed-backed account, so keep the manager lifecycle as short as possible and dispose it after use. + + +The public `account.keyPair` property exposes the underlying private-key bytes. Avoid accessing it. Never log, serialize, or retain those bytes, and do not assume disposal can erase copies made by application code. + + +Next, [check balances](/sdk/community-modules/wdk-wallet-cosmos/guides/check-balances) or [sign and verify messages](/sdk/community-modules/wdk-wallet-cosmos/guides/sign-verify-messages). diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/send-transactions.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/send-transactions.mdx new file mode 100644 index 00000000..e9611b0c --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/send-transactions.mdx @@ -0,0 +1,124 @@ +--- +title: Send transactions +description: Quote, sign, broadcast, and look up native Cosmos bank-send transactions. +docType: how-to +schemaType: TechArticle +--- + +The native transaction methods create one Cosmos bank send using the configured `nativeDenom`. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + + +Broadcast transactions are irreversible. Validate the chain, recipient, amount, account balance, sequence, and fee policy before calling `sendTransaction()`. A timeout does not prove that the chain rejected the transaction. + + +## Build the transaction + +`quoteSendTransaction()`, `signTransaction()`, and `sendTransaction()` accept the shared WDK transaction shape: + +```js +const transaction = { + to: 'cosmos1', + value: 1_000n, +} +``` + +Replace the recipient placeholder with a validated address. The module: + +- sends only the configured `nativeDenom`; +- converts `value` to an integer string; +- uses one `/cosmos.bank.v1beta1.MsgSend`; +- uses the fixed memo `Transfer via WDK`; +- uses a fixed gas limit of `200000`. + +Use [`transfer()`](/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens) when you need to choose a denomination. + +## Quote before broadcast + +Apply an application-controlled fee limit before the write: + +```js +const applicationMaxFee = 5_000n +const quote = await account.quoteSendTransaction(transaction) + +if (quote.fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') +} +``` + +Choose a limit that is appropriate for the chain and fee denomination. The quote is a deterministic calculation from configured metadata and the fixed gas limit. It ignores the recipient and amount; it does not query RPC, simulate the transaction, inspect the balance, or validate the recipient. + +`sendTransaction()` does not enforce `transferMaxFee` in `1.0.0-beta.4`, and the package does not expose `transactionMaxFee`. Keep the application check immediately before the write. + +## Broadcast a native send + +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' + +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') + +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) + +try { + const account = await manager.getAccount(0) + const transaction = { + to: 'cosmos1', + value: 1_000n, + } + + const applicationMaxFee = 5_000n + const { fee } = await account.quoteSendTransaction(transaction) + if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') + } + + const result = await account.sendTransaction(transaction) + console.log({ + hash: result.hash, + fee: result.fee.toString(), + }) +} finally { + manager.dispose() +} +``` + +## Sign without broadcasting + +`signTransaction()` obtains the account number, sequence, and chain ID through RPC, then returns a signed CosmJS `TxRaw`: + +```js +const signedTransaction = await account.signTransaction(transaction) +``` + + +`sendTransaction()` accepts only the unsigned `{ to, value }` shape in this release. It cannot accept or broadcast the signed value returned by `signTransaction()`. The generated declaration also types the signed return value as `unknown`. + + +## Look up a receipt + +After a successful broadcast, query the returned hash: + +```js +const receipt = await account.getTransactionReceipt(result.hash) +``` + +The method performs one indexed-transaction lookup. It does not poll. If the transaction has not been indexed yet or does not exist, it throws `Transaction not found: `. + +## Avoid duplicate writes + +RPC endpoints are tried in order, and network-shaped failures can be retried. If a connection fails after submission, the write may have reached the chain even though the call throws. Before submitting again: + +1. Check the known transaction hash when one is available. +2. Inspect the sender's sequence, balance, and trusted chain index. +3. Reconcile the intended payment in application state. +4. Repeat only after establishing that the first write was not accepted. + +See [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for a recovery pattern. diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/sign-verify-messages.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/sign-verify-messages.mdx new file mode 100644 index 00000000..560b1ad2 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/sign-verify-messages.mdx @@ -0,0 +1,86 @@ +--- +title: Sign and verify messages +description: Create and verify ADR-36 arbitrary-data signatures with a Cosmos account. +docType: how-to +schemaType: TechArticle +--- + +The released module signs UTF-8 messages using the Cosmos ADR-36 arbitrary-data convention. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Sign a message + +Bind the message to your application's domain, purpose, audience, and nonce before signing: + +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' + +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') + +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) + +try { + const account = await manager.getAccount(0) + const message = [ + 'example.com authentication', + 'audience: example-api', + 'nonce: ', + ].join('\n') + + const signature = await account.sign(message) + const verified = await account.verify(message, signature) + + console.log('Signature verified:', verified) +} finally { + manager.dispose() +} +``` + +`sign()` returns a JSON string containing an ADR-36 `StdSignature`, including the public key and base64 signature. + +## Verify expected failures + +`verify()` binds the signature public key to the current account address. It returns `false` when: + +- the message differs; +- the signature belongs to another account; +- the signature input is malformed. + +```js +const verified = await account.verify( + 'a different message', + signature, +) + +console.log(verified) // false +``` + +## Apply application-level context + +ADR-36 signs the text you provide. It does not add an application domain, expiry, audience, nonce policy, or replay protection for you. + +Before accepting a signature: + +1. Construct a canonical message format. +2. Include the intended domain and action. +3. Include a single-use nonce and an expiry when appropriate. +4. Compare the expected account and authorization context. +5. Mark the nonce as consumed after successful verification. + + +An ADR-36 message signature is not a signed Cosmos transaction. Do not treat it as authorization to broadcast a bank or IBC transfer unless your application defines and enforces that authorization protocol. + + +## Released account limitation + +`toReadOnlyAccount()` is not implemented in `1.0.0-beta.4`. The package API therefore requires a seed-backed `WalletAccountCosmos` even when your immediate task is verification. Keep that account's lifecycle short, call `manager.dispose()` in `finally`, and avoid accessing the public `keyPair.privateKey` field. + +See the [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference#message-signing) for return and failure behavior. diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens.mdx new file mode 100644 index 00000000..2e3427ef --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens.mdx @@ -0,0 +1,120 @@ +--- +title: Transfer tokens and use IBC +description: Send Cosmos denominations on one chain or through a configured IBC channel. +docType: how-to +schemaType: TechArticle +--- + +`transfer()` sends a selected Cosmos denomination with a bank send or an IBC transfer. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + + +Broadcast transfers are irreversible. Quote and enforce your own fee limit before `transfer()`. In `1.0.0-beta.4`, the configured `transferMaxFee` check happens only after the transaction has already been signed and broadcast. + + +## Send on the same chain + +When the recipient prefix matches the configured `addressPrefix`, the module broadcasts a Cosmos bank send: + +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' + +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') + +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) + +try { + const account = await manager.getAccount(0) + const transfer = { + token: 'uatom', + recipient: 'cosmos1', + amount: 1_000n, + } + + const applicationMaxFee = 5_000n + const { fee } = await account.quoteTransfer(transfer) + if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') + } + + const result = await account.transfer(transfer) + console.log({ + hash: result.hash, + fee: result.fee.toString(), + }) +} finally { + manager.dispose() +} +``` + +Amounts are integer base units. Replace all address, denomination, and fee-limit examples with values validated for the configured chain. + +A matching Bech32 prefix is only the module's routing heuristic; it does not prove that the recipient belongs to the same chain. + +## Configure an IBC transfer + +When the recipient prefix differs, map that prefix to a source channel on the configured source chain: + +```js +const config = { + chainName: 'cosmoshub', + ibcChannels: { + osmo: { + sourceChannel: '', + }, + }, +} +``` + +Then quote and transfer to the destination-prefix address: + +```js +const transfer = { + token: 'uatom', + recipient: 'osmo1', + amount: 1_000n, +} + +const { fee } = await account.quoteTransfer(transfer) +if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') +} + +const result = await account.transfer(transfer) +``` + +Keep the manager inside a `try`/`finally` lifecycle and call `manager.dispose()` as shown in the same-chain example. + +The released IBC flow: + +- keys `ibcChannels` by destination Bech32 prefix; +- uses the configured mapping as the source channel; +- uses source port `transfer`; +- uses a fixed 600-second timestamp timeout; +- does not discover routes, validate channel topology, or track packet acknowledgement. + +## Understand quote limits + +`quoteTransfer()` checks that a channel mapping exists when the prefixes differ. It then calculates a fee from configured gas metadata and the fixed gas limit of `200000`. + +The quote does not: + +- call RPC or simulate gas; +- check the sender balance; +- validate that the channel is open; +- prove that the destination chain or relayer is available; +- validate the complete transfer against current chain state. + + +An over-limit `transfer()` can succeed on-chain and then throw the module's fee-limit error because the check occurs after broadcast. Treat the pre-write quote and application limit as required controls, and reconcile chain state before retrying any failed call. + + +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration#ibc-channels) for channel configuration and [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for ambiguous-write guidance. diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/index.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/index.mdx index 8f77819e..a05d9853 100644 --- a/content/docs/sdk/community-modules/wdk-wallet-cosmos/index.mdx +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/index.mdx @@ -1,79 +1,59 @@ --- title: Cosmos wallet -description: Use the Base58 Cosmos wallet module for Cosmos-compatible chains. +description: Create Cosmos-compatible accounts, read balances, and send bank or IBC transfers with the Base58 community wallet module. docType: explanation schemaType: TechArticle --- -`@base58-io/wdk-wallet-cosmos` is a WDK wallet module for Cosmos-compatible blockchains. It provides Bech32 accounts, Cosmos transaction signing, balance reads, token transfers, chain-registry configuration, and RPC fallback. - -Powered by [`@base58-io/wdk-wallet-cosmos`](https://github.com/base58-io/wdk-wallet-cosmos). +Use [`@base58-io/wdk-wallet-cosmos@1.0.0-beta.4`](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos) to derive Bech32 accounts and interact with Cosmos SDK chains through WDK wallet interfaces. -This module is developed and maintained independently by a third-party contributor. Tether and the WDK team do not endorse or assume responsibility for its code, security, or maintenance. Review the package and use your own judgment before using it with real funds. +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. -## When to use it +These pages describe the published [`v1.0.0-beta.4`](https://github.com/base58-io/wdk-wallet-cosmos/releases/tag/v1.0.0-beta.4) package. The repository's default branch can contain unreleased APIs that are not available in this version. -Use this module when your application needs wallet support for Cosmos-compatible chains. +## What you can build -| Use case | Module | +| Capability | Released behavior | |---|---| -| Cosmos-compatible wallets using chain-registry or custom RPC config | `@base58-io/wdk-wallet-cosmos` | -| Bitcoin wallets | [`@tetherto/wdk-wallet-btc`](/sdk/wallet-modules/wallet-btc) | -| EVM wallets | [`@tetherto/wdk-wallet-evm`](/sdk/wallet-modules/wallet-evm) | - -## Requirements - -The module requires: - -- a BIP-39 seed phrase or seed bytes; -- either a `chainName` from chain-registry or custom chain config; -- RPC endpoints for balance reads, fee quotes, transfers, and receipts; -- gas price and denomination config when defaults from chain-registry are not enough. - -## Install - -```bash -npm install @base58-io/wdk-wallet-cosmos -``` - -## Minimal setup +| Accounts | Derive and cache secp256k1 accounts with chain-specific Bech32 prefixes | +| Balances | Read one or more Cosmos denominations through RPC | +| Native sends | Sign or broadcast a bank send for the configured native denomination | +| Token transfers | Send a denomination on the same chain | +| IBC transfers | Send through a configured source channel when the recipient prefix differs | +| Message signing | Sign and verify ADR-36 arbitrary messages | +| Network access | Select bundled chain-registry metadata or provide custom RPC endpoints | -```js -import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' +## Released beta limitations -const manager = new WalletManagerCosmos(seedPhrase, { - chainName: 'cosmoshub' -}) +- The manager accepts a BIP-39 mnemonic or seed bytes. It does not support named or external signers in `1.0.0-beta.4`. +- `toReadOnlyAccount()` is not implemented. Balance reads therefore use a seed-backed account. +- Fee quotes use a fixed gas limit and configured metadata; they do not simulate the transaction through RPC. +- `signTransaction()` returns a signed Cosmos transaction, but `sendTransaction()` cannot broadcast that signed value in this release. +- `transferMaxFee` is checked by `transfer()` only after broadcast. Quote and enforce an application limit before every write. -const account = await manager.getAccount(0) -const address = await account.getAddress() - -console.log(address) -``` - -Use custom RPC configuration for local or unsupported chains: - -```js -const manager = new WalletManagerCosmos(seedPhrase, { - rpcEndpoints: ['http://localhost:26657'], - addressPrefix: 'cosmos', - nativeDenom: 'uatom', - gasPrice: '0.025uatom' -}) -``` - -## Reference +## Next steps + +Choose the account, balance, transfer, signing, or troubleshooting flow you need. + + +Install the pinned beta and derive your first Cosmos account. + + +Configure chain metadata, RPC fallback, fees, and IBC channels. + -Review exports, configuration, wallet manager methods, account methods, and helper functions. +Review the public API published in `1.0.0-beta.4`. - -Open the package source and README on GitHub. + +Inspect the source that corresponds to the documented package. -Review the community module disclaimer and submission flow. +Review the community module catalog and submission guidance. diff --git a/content/docs/sdk/community-modules/wdk-wallet-cosmos/usage.mdx b/content/docs/sdk/community-modules/wdk-wallet-cosmos/usage.mdx new file mode 100644 index 00000000..5a999105 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-cosmos/usage.mdx @@ -0,0 +1,45 @@ +--- +title: Usage +description: Choose a task-focused guide for the Base58 Cosmos community wallet module. +docType: how-to +schemaType: TechArticle +icon: BookOpen +--- + +Use these guides with `@base58-io/wdk-wallet-cosmos@1.0.0-beta.4`. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + + + +Install the pinned package and derive a Bech32 account. + + +Use account indexes and Cosmos BIP-44 derivation paths. + + +Read native and denomination-specific balances through RPC. + + +Quote, sign, send, and look up native-denomination transactions. + + +Send Cosmos denominations on one chain or through a configured IBC channel. + + +Create and verify ADR-36 signatures. + + +Handle RPC fallback, ambiguous writes, fee limits, and cleanup. + + +Review registry, RPC, fee, retry, and IBC options. + + +Review exports, types, methods, return values, and limitations. + + diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/api-reference.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/api-reference.mdx index 10b05834..03688f66 100644 --- a/content/docs/sdk/community-modules/wdk-wallet-rgb/api-reference.mdx +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/api-reference.mdx @@ -1,135 +1,177 @@ --- title: RGB wallet API reference -description: API reference for the independently maintained @utexo/wdk-wallet-rgb module. +description: Public API reference for the released @utexo/wdk-wallet-rgb 2.0.3 community module. docType: reference schemaType: TechArticle --- -This page summarizes the public API exposed by `@utexo/wdk-wallet-rgb` based on the package README, `package.json`, generated TypeScript declarations, and source files from commit `6d28bc9`. +This page covers the public declarations and runtime methods in [`@utexo/wdk-wallet-rgb@2.0.3`](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/releases/tag/v2.0.3). -This module is developed and maintained independently by a third-party contributor. Tether and the WDK team do not endorse or assume responsibility for its code, security, or maintenance. Review the package and use your own judgment before using it with real funds. +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. ## Package | Field | Value | |---|---| -| Package | `@utexo/wdk-wallet-rgb` | +| Package | `@utexo/wdk-wallet-rgb@2.0.3` | | Repository | [UTEXO-Protocol/wdk-wallet-rgb](https://github.com/UTEXO-Protocol/wdk-wallet-rgb) | -| Main entry | `index.js` | -| Bare entry | `bare.js` | -| Types | `types/index.d.ts` | +| Module format | ESM | +| Entries | `index.js`; conditional Bare entry `bare.js` | +| Declarations | `types/index.d.ts` | ## Exports | Export | Description | |---|---| | `default` | `WalletManagerRgb` | -| `WalletAccountRgb` | Full RGB wallet account implementation | -| `WalletAccountReadOnlyRgb` | Read-only RGB wallet account implementation | -| `RgbWalletConfig` | RGB wallet configuration type | -| `RgbTransaction` | Native BTC transaction shape for the RGB wallet | -| `TransferOptions` | RGB transfer options | -| `RgbTransactionReceipt` | RGB transaction receipt type from `@utexo/rgb-sdk` | -| `RgbTransferReceipt` | RGB transfer receipt type from `@utexo/rgb-sdk` | +| `WalletAccountRgb` | Full account class | +| `WalletAccountReadOnlyRgb` | Query-only account class | +| Types | `RgbWalletConfig`, `RgbTransaction`, `TransferOptions`, `RgbTransactionReceipt`, `RgbTransferReceipt`, and WDK fee/key/result aliases | ## `RgbWalletConfig` -| Field | Required | Description | +| Field | Runtime requirement | Description | |---|---:|---| -| `network` | Yes | Target RGB network. Runtime source maps `mainnet`, `testnet`, `testnet4`, `signet`, `utexo`, and `regtest`; generated typedefs currently list `mainnet`, `testnet`, and `regtest`. | -| `dataDir` | Yes | Persistent app-private path where RGB wallet state is stored. Losing this directory can lose RGB asset state. | -| `keys` | No | Derived RGB wallet keys. The manager derives these from the seed before creating an account. | -| `indexerUrl` | No | Electrs indexer URL. | -| `transportEndpoint` | No | RGB transport endpoint. | -| `transferMaxFee` | No | Maximum fee amount for transfer operations. | +| `network` | Required | `'mainnet'`, `'testnet'`, or `'regtest'`. | +| `dataDir` | Optional at construction | Local RGB state path. Treat it as operationally required and persistent. | +| `indexerUrl` | Optional | Electrs-compatible indexer endpoint. | +| `transportEndpoint` | Optional | RGB consignment transport endpoint. | +| `keys` | Internal | Generated by `WalletManagerRgb`; applications normally do not set it. | +| `transferMaxFee` | Not effective through the manager path | Declared on the config/account, but `WalletManagerRgb.getAccount()` does not forward it to the account in v2.0.3. Enforce a fee limit in application code after quoting. | + + +The generated `RgbWalletConfig` TypeScript alias exposes only `network` and `keys`, although the released runtime and JSDoc accept `dataDir`, `indexerUrl`, `transportEndpoint`, and `transferMaxFee`. JavaScript can pass the operational fields; TypeScript consumers may need a local, release-scoped augmentation until the package declarations are corrected. + ## `WalletManagerRgb` -```js -import WalletManagerRgb from '@utexo/wdk-wallet-rgb' +| Member | Returns | Behavior | +|---|---|---| +| `constructor(seed, config)` | `WalletManagerRgb` | Accepts a BIP-39 mnemonic string or seed bytes. Runtime requires `config.network`. | +| `getAccount(index = 0)` | `Promise` | Creates or returns the only account. Any nonzero index throws. | +| `restoreAccountFromBackup(config)` | `Promise` | Restores the backup into `config.dataDir`, creates the account, and caches it at index `0`. | +| `getAccountByPath(path)` | `Promise` | Always throws; arbitrary paths are unsupported. | +| `getFeeRates()` | `Promise<{normal: bigint, fast: bigint}>` | Reads mempool.space recommended fees without selecting the wallet network. | +| `dispose()` | `void` | Clears manager-owned derived-key fields and disposes cached accounts. | -const manager = new WalletManagerRgb(seed, config) -``` +## `WalletAccountRgb` -| Method | Description | -|---|---| -| `constructor(seed, config)` | Creates a manager from a BIP-39 seed phrase or seed bytes. `network` and `dataDir` are required in config. | -| `getAccount(index = 0)` | Returns the RGB account. RGB supports only account index `0`; other indexes throw. | -| `restoreAccountFromBackup(restoreConfig)` | Restores account state from encrypted backup material. | -| `getAccountByPath(path)` | Always throws because RGB does not support arbitrary BIP-44 account paths in this module. | -| `getFeeRates()` | Fetches normal and fast Bitcoin fee rates from mempool.space. | -| `dispose()` | Clears cached account and key material from the manager. | +### Static factories + +| Member | Returns | Notes | +|---|---|---| +| `WalletAccountRgb.at(seed, config)` | `Promise` | Low-level factory used by `WalletManagerRgb.getAccount()`. It requires `config.network` and generated `config.keys`, and accepts `dataDir`, `indexerUrl`, and `transportEndpoint`. Applications should normally use `manager.getAccount(0)` so the manager derives the keys and caches the account. | +| `WalletAccountRgb.fromBackup(seed, config)` | `Promise` | Low-level restore factory used by `WalletManagerRgb.restoreAccountFromBackup()`. It requires `config.network`, generated `config.keys`, `backupFilePath`, `password`, and `dataDir`; it restores before opening the account. Prefer the manager method so keys are derived and the restored account is cached at index `0`. | + +Both declarations make `config` optional, but the v2.0.3 runtime throws when these required fields are absent. + +### Identity and WDK methods + +| Member | Returns | Notes | +|---|---|---| +| `index` | `0` | The only supported account index. | +| `path` | `string` | `m/86'/0'/0'` on mainnet; `m/86'/1'/0'` otherwise. | +| `coloredPath` | `string` | `m/86'/827166'/0'` on mainnet; `m/86'/827167'/0'` otherwise. | +| `keyPair` | `RgbKeyPair` | Includes WDK key bytes plus RGB xpubs and fingerprint. Treat every returned key field as sensitive. | +| `getAddress()` | `string` | Returns the current Bitcoin address synchronously. | +| `getBalance()` | `Promise` | Settled Bitcoin balance in satoshis. | +| `getTokenBalance(assetId)` | `Promise` | Settled RGB amount in the asset's base unit. | +| `sign(message)` | `Promise` | Signs through the underlying RGB wallet. | +| `verify(message, signature)` | `Promise` | Full-account verification. | +| `sendTransaction(tx)` | `Promise<{hash, fee}>` | Sends Bitcoin with `sendBtcBegin → signPsbt → sendBtcEnd`. | +| `quoteSendTransaction(tx)` | `Promise<{fee}>` | Builds and signs a PSBT to estimate the fee. | +| `transfer(options)` | `Promise<{hash, fee}>` | Sends an RGB asset to an `rgb:` invoice. | +| `quoteTransfer(options)` | `Promise<{fee}>` | Builds and signs an RGB PSBT to estimate the fee. | +| `getTransfers(options?)` | `RgbTransfer[]` | Filters and paginates; returns `[]` for both no results and any underlying error. | +| `toReadOnlyAccount()` | `Promise` in the declarations | The v2.0.3 runtime returns synchronously, but `await` works with both behaviors and satisfies the published type. | +| `dispose()` | `void` | Zeroes the wrapper's derived private-key bytes and disposes its RGB wallet. | + +### RGB, UTXO, and state methods + +| Method | Returns | Notes | +|---|---|---| +| `getRgbWallet()` | RGB SDK `WalletManager` | Advanced escape hatch; its API and lifecycle are maintained by `@utexo/rgb-sdk`. | +| `listAssets()` | `ListAssets[]` | Current asset inventory. | +| `issueAssetNia({ticker, name, amounts, precision})` | `IssueAssetNIA` | Issues a Non-Inflatable Asset. Other issuance schemas are not declared by this release. | +| `receiveAsset({assetId?, amount, witness})` | `InvoiceReceiveData` | Creates a witness invoice when `witness` is true, otherwise a blind invoice. | +| `sendBegin(options)` | `string` | Creates a base64 PSBT for an RGB send. | +| `signPsbt(psbt)` | `Promise` | Signs a base64 PSBT. | +| `sendEnd({signedPsbt})` | `SendResult` | Finalizes and broadcasts the RGB send. | +| `createUtxos(options)` | `Promise` | Combined create/sign/finalize flow. | +| `createUtxosBegin(options)` | `string` | Creates a UTXO-creation PSBT. | +| `createUtxosEnd({signedPsbt})` | `number` | Finalizes UTXO creation. | +| `listUnspents()` | `Unspent[]` | Current RGB wallet UTXOs. | +| `listTransactions()` | `RgbTransactionReceipt[]` | Bitcoin transaction records. | +| `listTransfers(assetId?)` | `RgbTransfer[]` | Native transfer list with optional asset filter. | +| `failTransfers(request)` | `boolean` | Forwards transfer-failure handling to the RGB SDK. The v2.0.3 declaration and JSDoc disagree on the parameter name/type; inspect the matching SDK before calling it. | +| `createBackup({password, backupPath})` | Backup response | Creates an encrypted backup file. | +| `restoreFromBackup({password, backupFilePath, dataDir})` | Restore response | Low-level account restore. Prefer the manager restore flow before opening the destination. | +| `refreshWallet()` | `void` | Refreshes RGB transfer state. | +| `registerWallet()` | `Promise<{address, btcBalance}>` | Registers the wallet and returns its address and Bitcoin balance. | +| `syncWallet()` | `void` | Synchronizes with the Bitcoin chain. | -## `WalletAccountRgb` + +Version 2.0.3 has a declaration/runtime mismatch for `sendEnd()`: the published declaration requires `signed_psbt`, while the runtime reads `signedPsbt`. Pass the runtime-correct camel-case field and use a narrow type assertion until the upstream declaration is corrected: -| Method or property | Description | -|---|---| -| `static at(seed, config)` | Creates a full RGB wallet account. | -| `static fromBackup(seed, config)` | Creates an account from encrypted backup material. | -| `index` | Account index. | -| `path` | WDK-compatible path representation for the RGB account. | -| `coloredPath` | RGB colored-account path representation. | -| `keyPair` | Public key material and RGB-specific extended public keys. | -| `getAddress()` | Returns the wallet address. | -| `sign(message)` | Signs a message. | -| `verify(message, signature)` | Verifies a message signature. | -| `sendTransaction(options)` | Sends native BTC through the RGB wallet flow. | -| `quoteSendTransaction(options)` | Quotes native BTC send cost. | -| `transfer(options)` | Transfers an RGB asset. | -| `quoteTransfer(options)` | Quotes RGB asset transfer cost. | -| `toReadOnlyAccount()` | Returns a `WalletAccountReadOnlyRgb`. | -| `dispose()` | Erases account key material managed by this wrapper. | -| `getRgbWallet()` | Returns the underlying RGB SDK wallet manager. | - -## RGB-specific account methods - -| Method | Description | -|---|---| -| `listAssets()` | Lists RGB assets in the wallet. | -| `issueAssetNia(options)` | Issues a Non-Inflatable Asset. | -| `receiveAsset(options)` | Creates a blind or witness receive invoice. | -| `sendBegin(options)` | Begins an RGB send and returns a PSBT. | -| `signPsbt(psbt)` | Signs a PSBT. | -| `sendEnd(options)` | Broadcasts a signed RGB send. | -| `createUtxos(options)` | Creates UTXOs using the combined flow. | -| `createUtxosBegin(options)` | Begins UTXO creation and returns a PSBT. | -| `createUtxosEnd(options)` | Finalizes UTXO creation from a signed PSBT. | -| `listUnspents()` | Lists UTXOs. | -| `listTransactions()` | Lists Bitcoin transactions. | -| `getTransfers(options)` | Lists RGB transfers with optional asset filtering and pagination. | -| `listTransfers(assetId)` | Lists RGB transfers for an optional asset ID. | -| `failTransfers(transferId)` | Marks a transfer as failed. | -| `createBackup(options)` | Creates an encrypted wallet backup. | -| `restoreFromBackup(params)` | Restores a wallet from encrypted backup material. | +```typescript +const request = { signedPsbt } as unknown as Parameters[0] +const result = account.sendEnd(request) +``` + ## `WalletAccountReadOnlyRgb` -| Method | Description | -|---|---| -| `verify(message, signature)` | Verifies a message signature without private key access. | -| `getBalance()` | Returns the Bitcoin balance in satoshis. | -| `getTokenBalance(tokenAddress)` | Returns the settled RGB asset balance for the given asset ID. | -| `quoteSendTransaction(tx)` | Throws; use `WalletAccountRgb` for this operation. | -| `quoteTransfer(options)` | Throws; use `WalletAccountRgb` for this operation. | -| `getTransactionReceipt(hash)` | Looks up a Bitcoin transaction receipt by hash. | -| `getTransferReceipt(hash)` | Looks up an RGB transfer receipt by hash. | +The read-only class is constructed from an address and configuration. It inherits balance methods and exposes: + +| Method | Returns | Behavior | +|---|---|---| +| `getBalance()` | `Promise` | Settled Bitcoin balance. | +| `getTokenBalance(assetId)` | `Promise` | Settled RGB balance. | +| `getTransactionReceipt(hash)` | `Promise` | Bitcoin receipt or `null`. | +| `getTransferReceipt(hash)` | `Promise` | RGB transfer receipt or `null`. | +| `quoteSendTransaction()` | Rejected promise | Read-only accounts cannot construct or sign a quote PSBT. | +| `quoteTransfer()` | Rejected promise | Read-only accounts cannot construct or sign a quote PSBT. | + +The v2.0.3 read-only class does not implement `verify()`. Use a live full account or a separately validated public-key verification path. + +## Input shapes -## Transfer options +### Bitcoin transaction -| Field | Required | Description | +| Field | Required | Meaning | |---|---:|---| +| `to` | Yes | Bitcoin address. | +| `value` | Yes | Satoshis as `number` or `bigint`. | +| `feeRate` | No | sat/vbyte; send defaults to `1`, while quote obtains an estimate. | + +### RGB transfer + +| Field | Required | Meaning | +|---|---:|---| +| `recipient` | Yes | Single-use RGB invoice beginning with `rgb:`. | | `token` | Yes | RGB asset ID. | -| `recipient` | Yes | RGB invoice recipient. | -| `amount` | Yes | Transfer amount. | -| `feeRate` | No | Fee rate in sat/vbyte. | +| `amount` | Yes | Asset base units as `number` or `bigint`. | +| `feeRate` | No | Bitcoin fee rate in sat/vbyte. | | `minConfirmations` | No | Minimum confirmations. | -| `witnessData` | No | Witness data for transfer construction. | +| `witnessData` | No | Optional `{amountSat, blinding}` witness data. | + +## Release-specific cautions + +- `getTransfers()` suppresses native errors and returns `[]`; use direct state checks when an empty result is consequential. +- `sendTransaction()` wraps Bitcoin-send failures with the text `RGB transfer failed`, so the prefix does not identify the failed operation. +- Quote methods construct and sign PSBTs. Treat them as wallet operations, not pure arithmetic. +- `getFeeRates()` reads main mempool.space recommendations without choosing `testnet` or `regtest`. +- `transferMaxFee` is dropped by the normal manager-to-account construction path. Quote and enforce your own limit before sending. +- Preserve and back up `dataDir`; do not assume the mnemonic alone reconstructs RGB state. -## Limitations +## Guides -- RGB account creation is limited to account index `0`. -- `getAccountByPath()` is not supported. -- Read-only accounts cannot quote transactions or transfers. -- `dataDir` is part of the wallet state and needs durable storage. +- [Configure the wallet](/sdk/community-modules/wdk-wallet-rgb/configuration) +- [Issue and receive assets](/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets) +- [Transfer assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Back up, restore, and migrate](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/configuration.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/configuration.mdx new file mode 100644 index 00000000..8082035c --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/configuration.mdx @@ -0,0 +1,115 @@ +--- +title: RGB wallet configuration +description: Configure network, durable state, indexing, transport, and fee controls for @utexo/wdk-wallet-rgb 2.0.3. +docType: reference +schemaType: TechArticle +icon: Settings +--- + +`WalletManagerRgb` accepts a BIP-39 mnemonic or seed bytes and an RGB wallet configuration. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Recommended configuration + +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' + +const manager = new WalletManagerRgb(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-onchain', + indexerUrl: 'tcp://127.0.0.1:50001', + transportEndpoint: 'rpc://127.0.0.1:3000/json-rpc', +}) +``` + +Replace the paths and endpoints with values for your environment. Do not use a public example endpoint without evaluating its availability, privacy, and trust model. + +## Options + +| Field | Type | Runtime default | Guidance | +|---|---|---|---| +| `network` | `'mainnet' \| 'testnet' \| 'regtest'` | None | Required by the manager. | +| `dataDir` | `string` | Temporary directory | Use a durable, app-private path and include it in backup and restore testing. | +| `indexerUrl` | `string` | RGB SDK default | Use a trusted Electrs-compatible endpoint for the selected network. | +| `transportEndpoint` | `string` | RGB SDK default | Used for RGB consignment exchange. Validate its scheme, network, and availability. | +| `keys` | RGB SDK generated keys | Derived by the manager | Internal account-construction field; do not replace manager derivation in normal use. | +| `transferMaxFee` | `number \| bigint` | None | Not forwarded by `WalletManagerRgb.getAccount()` in v2.0.3; do not rely on it through the manager path. | + + +The generated v2.0.3 TypeScript alias omits `dataDir`, `indexerUrl`, `transportEndpoint`, and `transferMaxFee`, although the released runtime and source JSDoc accept them. Keep any local type augmentation pinned to this package version and remove it when upstream declarations converge. + + +## Network + +Only these values are supported by the released declarations and account path logic: + +| Network | Vanilla path | Colored path | +|---|---|---| +| `mainnet` | `m/86'/0'/0'` | `m/86'/827166'/0'` | +| `testnet` | `m/86'/1'/0'` | `m/86'/827167'/0'` | +| `regtest` | `m/86'/1'/0'` | `m/86'/827167'/0'` | + +Do not configure `signet`, `testnet4`, or a custom network for this release even if a transitive RGB dependency recognizes additional names. + +## Local state + +The wallet stores RGB records under `dataDir`. Use a path that is: + +- persistent across restarts and upgrades; +- private to the application and OS user; +- unavailable to concurrent wallet instances; +- covered by encrypted backup and tested restoration; +- distinct from [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning). + + +The seed derives wallet keys, but it does not replace the local RGB database. Do not delete `dataDir` or treat a mnemonic-only recovery drill as proof that RGB state is recoverable. + + +## Indexer and transport + +`indexerUrl` supplies Bitcoin chain data. `transportEndpoint` carries RGB consignments. Both services can observe request metadata and can be unavailable, stale, or malicious. + +Before production use: + +1. Bind each endpoint to the configured Bitcoin network. +2. Apply TLS or an authenticated private network where supported. +3. Set application-level timeouts and operational monitoring. +4. Reconcile transfer state before retrying a timed-out write. +5. Test failover without assuming a failed response means a failed broadcast. + +## Fee policy + +`manager.getFeeRates()` reads `https://mempool.space/api/v1/fees/recommended` and returns `normal` and `fast` as `bigint`. The request does not select `testnet` or `regtest`, so use it only as a mainnet-oriented display hint. + +`quoteSendTransaction()` and `quoteTransfer()` create and sign PSBTs to estimate a fee. Apply an application-owned limit before sending: + +```js +const maximumFee = 2_000n +const quote = await account.quoteTransfer(transfer) + +if (quote.fee > maximumFee) { + throw new Error('Quoted RGB transfer fee exceeds the application limit') +} + +const result = await account.transfer(transfer) +``` + +Do not use `transferMaxFee` as the sole guard. The manager omits that field when it constructs the released account. + +## Runtime artifacts + +The released dependency graph provides native artifacts for Linux x64, Linux arm64, and macOS arm64. It does not provide a verified Windows or Intel macOS artifact for this version. The package does not declare a Node.js engine range. + +Validate installation, native loading, backup/restore, and real network calls on the exact deployment target. + +## Next steps + +- [Get started](/sdk/community-modules/wdk-wallet-rgb/guides/get-started) +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate.mdx new file mode 100644 index 00000000..9ea065ce --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate.mdx @@ -0,0 +1,111 @@ +--- +title: Back up, restore, and migrate the RGB wallet +description: Protect local RGB state, restore encrypted backups, and choose a privacy-aware v1-to-v2 migration. +docType: how-to +schemaType: TechArticle +--- + +The seed and local RGB database are separate recovery inputs. Test both before funding the wallet. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Create an encrypted backup + +```js +const backup = account.createBackup({ + password: backupPassword, + backupPath: '/secure-backups/rgb-wallet.backup', +}) + +console.log(backup.message) +``` + +Store the backup and password as sensitive recovery material, with access controls and separation appropriate to your threat model. Test readability and retention; a successful method return is not a completed recovery drill. + +## Restore into an empty directory + +Create a fresh manager with the same seed and network. Call `restoreAccountFromBackup()` before opening a normal account in the destination directory. + +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' + +const restoredManager = new WalletManagerRgb(seedPhrase, { + network: 'testnet', + indexerUrl: trustedIndexerUrl, + transportEndpoint: trustedTransportEndpoint, +}) + +try { + const restored = await restoredManager.restoreAccountFromBackup({ + backupFilePath: '/secure-backups/rgb-wallet.backup', + password: backupPassword, + dataDir: '/app-private/wdk/rgb-restored', + }) + + await restored.registerWallet() + restored.syncWallet() + restored.refreshWallet() + + console.log({ + address: restored.getAddress(), + assets: restored.listAssets(), + transactions: restored.listTransactions(), + }) +} finally { + restoredManager.dispose() +} +``` + +Use an empty, private destination and do not run the original and restored wallet concurrently against the same state. + +## Validate the restore + +Compare more than the address: + +- vanilla and colored derivation paths; +- asset IDs, precision, and settled balances; +- RGB transfer history and status; +- Bitcoin transactions and unspents; +- ability to create a new backup; +- a low-value receive and transfer on a test network. + +## Migrate from v1 + +Version 1 relied on a remote RGB Node. The maintainer migration guide warns that the node operator may have learned wallet xpubs and transaction-graph metadata. + +Choose one of two paths: + +### Privacy-preserving reset + +1. Create a v2 wallet with a new seed and new persistent `dataDir`. +2. Generate recipient invoices on the new wallet. +3. Transfer assets from the old wallet. +4. Verify settlement and back up the new state. +5. Retire the old seed according to your incident and retention policy. + +This is the maintainer-recommended path when historical metadata exposure matters. + +### Same-seed state migration + +1. In a separate environment pinned to the v1 package, create and securely download the v1 backup. +2. Stop and dispose the v1 wallet. +3. Install v2 and restore the backup into a new local directory. +4. Open v2 with the same seed, network, and restored `dataDir`. +5. Verify all state before retiring the remote-node setup. + + +A same-seed restore preserves identity and state but cannot undo information already disclosed to a legacy remote node. An upgrade is not a privacy reset. + + +Use normal package imports in each pinned environment. A package version suffix inside an ESM import specifier, such as `import x from 'package@version'`, is not valid npm package import syntax. + +## Next steps + +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) +- [Maintainer migration guide](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/blob/v2.0.3/MIGRATION.md) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/balances-history.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/balances-history.mdx new file mode 100644 index 00000000..1a4cc7b2 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/balances-history.mdx @@ -0,0 +1,105 @@ +--- +title: Read RGB balances and history +description: Read settled Bitcoin and RGB balances, transactions, transfers, and receipts from the on-chain RGB wallet. +docType: how-to +schemaType: TechArticle +--- + +Use the account's read methods after synchronizing Bitcoin and RGB transfer state. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Synchronize first + +```js +account.syncWallet() +account.refreshWallet() +``` + +The freshness of every result depends on the selected indexer, transport endpoint, and local `dataDir`. + +## Read balances + +```js +const bitcoinSats = await account.getBalance() +const assetUnits = await account.getTokenBalance(assetId) + +console.log({ + bitcoinSats: bitcoinSats.toString(), + assetUnits: assetUnits.toString(), +}) +``` + +Both WDK balance methods return settled values as `bigint`. RGB values are asset base units; apply the asset's precision only when formatting for display. + +To inspect the native asset records: + +```js +const assets = account.listAssets() +``` + +Native result objects come from the pinned `@utexo/rgb-sdk`. Validate the fields your application consumes instead of assuming an unreleased repository shape. + +## Read Bitcoin history and UTXOs + +```js +const transactions = account.listTransactions() +const unspents = account.listUnspents() +``` + +These methods are synchronous wrappers over local/native state. A returned record is not, by itself, proof of finality; inspect its status and confirmations. + +## Read RGB transfer history + +Use `listTransfers()` when an error must remain observable: + +```js +const allTransfers = account.listTransfers() +const oneAssetTransfers = account.listTransfers(assetId) +``` + +Use `getTransfers()` for local filtering and pagination: + +```js +const page = account.getTransfers({ + assetId, + limit: 20, + skip: 0, +}) +``` + + +`getTransfers()` catches every underlying error and returns `[]`. An empty array therefore means either “no matching transfers” or “the native query failed.” Do not use it alone for reconciliation, audit, or retry decisions. + + +## Read receipts + +```js +const bitcoinReceipt = await account.getTransactionReceipt(txid) +const rgbReceipt = await account.getTransferReceipt(transferHash) +``` + +Each method returns a receipt or `null`. Treat `null` as pending, absent, or not yet indexed—not proof that a previous write failed. + +## Read-only access + +```js +const readOnly = await account.toReadOnlyAccount() + +const [btcBalance, rgbBalance] = await Promise.all([ + readOnly.getBalance(), + readOnly.getTokenBalance(assetId), +]) +``` + +Keep the originating wallet state and endpoints available for the lifetime of the read-only view. + +## Next steps + +- [Manage Bitcoin and UTXOs](/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos) +- [Transfer RGB assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos.mdx new file mode 100644 index 00000000..bf7bffe3 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos.mdx @@ -0,0 +1,89 @@ +--- +title: Send Bitcoin and manage RGB UTXOs +description: Quote and send Bitcoin, inspect unspents, and create UTXOs with @utexo/wdk-wallet-rgb 2.0.3. +docType: how-to +schemaType: TechArticle +--- + +RGB transfers require suitable Bitcoin UTXOs. The account exposes both WDK Bitcoin sends and RGB SDK UTXO helpers. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Inspect unspents + +```js +account.syncWallet() + +const unspents = account.listUnspents() +console.log(unspents) +``` + +Check native status fields and confirmation requirements before selecting an output for a consequential flow. + +## Create RGB-compatible UTXOs + +Use the combined method when the wallet can construct and sign the whole operation: + +```js +const created = await account.createUtxos({ + upTo: true, + num: 5, + size: 1_000, + feeRate: 2, +}) + +console.log('UTXOs created:', created) +``` + +`size` is satoshis and `feeRate` is sat/vbyte. Confirm appropriate values for the current network and RGB workflow. + +The release also exposes `createUtxosBegin()`, `signPsbt()`, and `createUtxosEnd()` for a lower-level PSBT flow. Use the exact v2.0.3 declaration and pinned RGB SDK shape when integrating those methods. + +## Quote a Bitcoin send + +```js +const transaction = { + to: recipientAddress, + value: 50_000n, + feeRate: 2, +} + +const quote = await account.quoteSendTransaction(transaction) +console.log('Estimated fee:', quote.fee.toString()) +``` + + +In v2.0.3, `quoteSendTransaction()` ignores the supplied `feeRate`, obtains its own one-block estimate, and constructs and signs a PSBT. `sendTransaction()` then uses the supplied `feeRate` or defaults to `1`. The quoted fee can therefore differ from the send path. Recheck policy and resulting state instead of treating the quote as a binding guarantee. + + +## Send Bitcoin + +```js +const maximumQuotedFee = 2_000n +const quote = await account.quoteSendTransaction(transaction) + +if (quote.fee > maximumQuotedFee) { + throw new Error('Quoted Bitcoin fee exceeds the application limit') +} + +const result = await account.sendTransaction(transaction) +console.log('Transaction ID:', result.hash) +``` + +`sendTransaction()` runs `sendBtcBegin → signPsbt → sendBtcEnd` and returns `{hash, fee}`. + + +Bitcoin-send failures are wrapped with the message prefix `RGB transfer failed` in this release. Classify the operation from your call context, not that text. A timeout also does not prove that broadcast failed; reconcile by transaction ID, UTXOs, and history before retrying. + + +`manager.getFeeRates()` reads main mempool.space recommendations without selecting the wallet network. Do not treat it as authoritative for `testnet` or `regtest`. + +## Next steps + +- [Issue and receive assets](/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets) +- [Transfer assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/get-started.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/get-started.mdx new file mode 100644 index 00000000..f0b51e54 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/get-started.mdx @@ -0,0 +1,83 @@ +--- +title: Get started with the RGB wallet +description: Install @utexo/wdk-wallet-rgb 2.0.3 and create its single on-chain RGB account. +docType: how-to +schemaType: TechArticle +--- + +This guide installs the documented release, creates a durable local wallet, and reads its first address. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## 1. Check the target runtime + +The v2.0.3 dependency set publishes native artifacts for Linux x64, Linux arm64, and macOS arm64. It does not publish a verified Windows or Intel macOS artifact, and the package does not declare a Node.js engine range. + +Test installation and native loading on the exact deployment target before integrating wallet state. + +## 2. Install the released package + +```bash +npm install @utexo/wdk-wallet-rgb@2.0.3 +``` + +## 3. Choose durable state and services + +Create an app-private, persistent directory. Configure an indexer and transport endpoint for the same network. + +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' + +const seedPhrase = await loadSeedFromSecretStorage() + +const manager = new WalletManagerRgb(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-onchain', + indexerUrl: 'tcp://127.0.0.1:50001', + transportEndpoint: 'rpc://127.0.0.1:3000/json-rpc', +}) +``` + +`loadSeedFromSecretStorage()` represents your application's secret-management boundary. Do not embed a mnemonic in source, logs, telemetry, or crash reports. + +## 4. Get the account + +RGB supports only account index `0`. + +```js +try { + const account = await manager.getAccount(0) + const address = account.getAddress() + + console.log('RGB-aware Bitcoin address:', address) +} finally { + manager.dispose() +} +``` + +`getAddress()` is synchronous in the released runtime. Using `await` on its value is harmless, but it is not required. + +## 5. Verify recovery before funding + +Before using real funds: + +1. Register and synchronize the wallet against trusted services. +2. Create an encrypted backup. +3. Restore into an empty test directory with the same seed. +4. Confirm addresses, assets, balances, transfers, and UTXOs. +5. Repeat on the exact deployment runtime. + + +Do not assume the seed alone restores RGB state. Preserve and test recovery of `dataDir` and encrypted backups. + + +## Next steps + +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Issue and receive assets](/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets) +- [Back up, restore, and migrate](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Configuration](/sdk/community-modules/wdk-wallet-rgb/configuration) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors.mdx new file mode 100644 index 00000000..6506ee24 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors.mdx @@ -0,0 +1,109 @@ +--- +title: Handle RGB wallet errors +description: Handle native failures, ambiguous transfer history, fee-policy gaps, and secure cleanup in @utexo/wdk-wallet-rgb 2.0.3. +docType: how-to +schemaType: TechArticle +--- + +The v2.0.3 package does not expose a typed public error hierarchy. Handle failures by operation, preserve the original cause, and reconcile state before retrying writes. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Preserve operation context + +```js +async function transferRgb(account, transfer) { + try { + return await account.transfer(transfer) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + + reportWalletFailure({ + operation: 'rgb_transfer', + message, + }) + + throw error + } +} +``` + +Do not log the seed, keys, backup password, complete invoice, or user-identifying endpoint credentials. + +## Account for misleading and suppressed errors + +Two release-specific behaviors require explicit handling: + +- `sendTransaction()` wraps Bitcoin-send failures with `RGB transfer failed: ...`. The prefix is misleading; classify it as the Bitcoin operation you invoked. +- `getTransfers()` catches every native error and returns `[]`. Use `listTransfers()` plus synchronization when failure visibility matters. + +```js +try { + account.syncWallet() + account.refreshWallet() + const transfers = account.listTransfers(assetId) + renderTransfers(transfers) +} catch (error) { + renderTransferStateUnavailable() + throw error +} +``` + +## Reconcile before retrying + +Indexer, transport, or broadcast calls can succeed remotely and fail locally. After a timeout or connection loss: + +1. Preserve any returned transaction or transfer identifier. +2. Synchronize Bitcoin and refresh RGB state. +3. Inspect transactions, transfers, receipts, and UTXOs. +4. Retry only when an idempotency or reconciliation rule proves it safe. + +Never regenerate and resend against a single-use invoice merely because the first response timed out. + +## Enforce fees in application code + +The normal manager path drops `transferMaxFee`. Quote, compare with an application limit, and then send. + +For Bitcoin sends, remember that v2.0.3 quote logic uses its own estimated fee rate while the send uses the supplied `feeRate` or `1`. Treat the quote as advisory and reconcile the actual result. + +## Handle common setup failures + +| Failure area | Check | +|---|---| +| Manager construction | `network` is exactly `mainnet`, `testnet`, or `regtest`. | +| Account creation | Seed is present and native artifact supports the host. | +| Empty or stale state | Correct persistent `dataDir`, network, indexer, and transport endpoint. | +| Transfer rejection | Complete `rgb:` invoice, matching asset ID, base-unit amount, UTXOs, confirmations, and fee rate. | +| Restore rejection | Backup path, password, empty destination, matching seed, and call order before opening the account. | + +## Clean up without hiding the primary failure + +```js +let operationError + +try { + await runWalletFlow(manager) +} catch (error) { + operationError = error + throw error +} finally { + try { + manager.dispose() + } catch (cleanupError) { + reportCleanupFailure(cleanupError, { operationError }) + } +} +``` + +The account zeroes its wrapper-owned derived private-key bytes during disposal, and the manager clears its derived-key fields. Cleanup cannot erase external copies or compensate for logged secrets. + +## Next steps + +- [Configuration](/sdk/community-modules/wdk-wallet-rgb/configuration) +- [Transfer assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Back up, restore, and migrate](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets.mdx new file mode 100644 index 00000000..0ec8cedf --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets.mdx @@ -0,0 +1,90 @@ +--- +title: Issue and receive RGB assets +description: Issue NIA assets and create blind or witness receive invoices with the on-chain RGB wallet. +docType: how-to +schemaType: TechArticle +--- + +The v2.0.3 account declares Non-Inflatable Asset issuance and blind or witness receive invoices. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Prepare the wallet + +Register, synchronize, and confirm that the wallet has suitable Bitcoin UTXOs: + +```js +await account.registerWallet() +account.syncWallet() + +const unspents = account.listUnspents() +if (unspents.length === 0) { + throw new Error('Fund or create RGB-compatible UTXOs before continuing') +} +``` + +## Issue an NIA + +```js +const issued = account.issueAssetNia({ + ticker: 'DEMO', + name: 'Demo Asset', + amounts: [1_000], + precision: 0, +}) + +console.log(issued) +``` + +`amounts` contains issued allocations in asset base units. Validate ticker, name, precision, supply, and destination policy before issuance because issuance is a consequential state change. + + +The released WDK account declares only `issueAssetNia()`. Do not infer UDA, CFA, IFA, inflation, or atomic-swap support from another RGB repository or unreleased branch. + + +## Create a blind receive invoice + +```js +const receive = account.receiveAsset({ + assetId, + amount: 100, + witness: false, +}) + +console.log(receive.invoice) +``` + +A blind receive normally consumes an available allocation-capable UTXO. + +## Create a witness receive invoice + +```js +const receive = account.receiveAsset({ + assetId, + amount: 100, + witness: true, +}) +``` + +Witness receive uses an on-chain witness flow. Select the mode according to the recipient's wallet state, privacy model, and fee requirements. + +## Handle invoices safely + +- Generate the invoice on the receiving wallet. +- Transmit the complete invoice over an authenticated channel. +- Verify the asset ID and amount in your application. +- Treat the invoice as single-use. +- Do not log invoices with user-identifying metadata. +- Refresh transfer state before deciding whether an expired or timed-out receive failed. + +The sender needs the invoice string beginning with `rgb:`. An ordinary Bitcoin address is not an RGB transfer recipient. + +## Next steps + +- [Create and inspect UTXOs](/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos) +- [Transfer an asset](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage.mdx new file mode 100644 index 00000000..e4ab1034 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage.mdx @@ -0,0 +1,97 @@ +--- +title: Manage the RGB account and storage +description: Manage the single RGB account, derivation paths, durable local state, and read-only access. +docType: how-to +schemaType: TechArticle +--- + +The on-chain RGB module owns one account and a local RGB database. Treat both limits as part of the wallet identity. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Use account index 0 + +```js +const account = await manager.getAccount(0) + +console.log(account.index) // 0 +console.log(account.path) // BIP-86 vanilla path +console.log(account.coloredPath) // RGB colored path +``` + +`manager.getAccount(1)` throws. `manager.getAccountByPath()` always throws. + +| Network | `path` | `coloredPath` | +|---|---|---| +| `mainnet` | `m/86'/0'/0'` | `m/86'/827166'/0'` | +| `testnet` or `regtest` | `m/86'/1'/0'` | `m/86'/827167'/0'` | + +## Persist the local state + +Use a durable, app-private `dataDir`: + +```js +const manager = new WalletManagerRgb(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-onchain', +}) +``` + +The runtime falls back to temporary storage if the field is omitted. That fallback is unsuitable for a durable wallet. + +Protect the directory from: + +- deletion by cache or temporary-file cleanup; +- concurrent access by multiple wallet instances; +- unencrypted device or cloud backups; +- reuse by [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning); +- accidental cross-network reuse. + +The on-chain and Lightning RGB modules have different identities and databases. Give them separate paths even when they use the same BIP-39 mnemonic. + +## Register and synchronize + +```js +const { address, btcBalance } = await account.registerWallet() + +account.syncWallet() +account.refreshWallet() + +console.log({ address, btcBalance }) +``` + +`syncWallet()` synchronizes Bitcoin state. `refreshWallet()` refreshes RGB transfer state. The released runtime exposes both synchronously; failures can still propagate from native code. + +## Create a read-only view + +```js +const readOnly = await account.toReadOnlyAccount() + +const btc = await readOnly.getBalance() +const rgb = await readOnly.getTokenBalance(assetId) +``` + +The read-only account can query balances and receipts. It cannot quote transactions or transfers, and v2.0.3 does not implement message verification on the read-only class. + +## Dispose at the owner boundary + +```js +try { + const account = await manager.getAccount(0) + // Use the account. +} finally { + manager.dispose() +} +``` + +The manager disposes cached accounts and clears manager-owned derived-key fields. Disposal cannot erase mnemonic or key copies retained by application code or dependencies. + +## Next steps + +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) +- [Back up and restore](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/sign-verify-messages.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/sign-verify-messages.mdx new file mode 100644 index 00000000..389fc3ab --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/sign-verify-messages.mdx @@ -0,0 +1,66 @@ +--- +title: Sign and verify messages with the RGB wallet +description: Sign and verify application messages with the full on-chain RGB account. +docType: how-to +schemaType: TechArticle +--- + +The full v2.0.3 account exposes Bitcoin message signing and verification through the underlying RGB wallet. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Sign a domain-separated message + +```js +const message = [ + 'example-wallet-auth', + 'version=1', + `origin=${expectedOrigin}`, + `nonce=${serverNonce}`, + `expires=${expiresAt}`, +].join('\n') + +const signature = await account.sign(message) +``` + +Include an application name, purpose, origin, nonce, expiry, and version. Do not ask users to sign opaque or transaction-like data. + +## Verify with the full account + +```js +const valid = await account.verify(message, signature) + +if (!valid) { + throw new Error('Invalid RGB wallet message signature') +} +``` + +Verify the exact bytes and application context that were presented to the signer. Reject reused nonces and expired challenges at the application boundary. + +## Read-only limitation + +```js +const readOnly = await account.toReadOnlyAccount() +``` + +The released `WalletAccountReadOnlyRgb` does not implement `verify()`. Do not copy an API claim from a later commit or another wallet module. If verification must run without the live full account, use a separately reviewed verifier with the correct public key, signature format, and domain rules. + +## Protect key material + +- Do not log `account.keyPair`, signatures attached to sensitive challenges, or seed material. +- Keep challenge generation server-side when using signatures for authentication. +- Bind signatures to one origin and one intended action. +- Call `manager.dispose()` when the wallet session ends. +- Remember that disposal cannot erase key copies retained by application code. + +Message signing does not authorize a Bitcoin or RGB transfer unless your application explicitly gives the signed message that meaning. Keep authentication and transaction approval domains separate. + +## Next steps + +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets.mdx new file mode 100644 index 00000000..b734175a --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets.mdx @@ -0,0 +1,100 @@ +--- +title: Transfer RGB assets +description: Quote and send an on-chain RGB asset to a recipient-generated invoice. +docType: how-to +schemaType: TechArticle +--- + +An RGB transfer is invoice-driven: the recipient generates an invoice and the sender funds, signs, and broadcasts the transfer. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## 1. Obtain a recipient invoice + +On the receiving wallet: + +```js +const receive = recipientAccount.receiveAsset({ + assetId, + amount: 100, + witness: false, +}) + +const rgbInvoice = receive.invoice +``` + +Transfer the invoice over an authenticated channel. The sender should validate that it begins with `rgb:` and that the intended asset and amount match the user-confirmed action. + +## 2. Build the transfer + +```js +const transfer = { + recipient: rgbInvoice, + token: assetId, + amount: 100n, + feeRate: 2, + minConfirmations: 1, +} +``` + +`amount` is in asset base units. `feeRate` is the Bitcoin fee rate in sat/vbyte. + +## 3. Quote and enforce policy + +```js +const maximumFee = 2_000n +const quote = await senderAccount.quoteTransfer(transfer) + +if (quote.fee > maximumFee) { + throw new Error('Quoted RGB transfer fee exceeds the application limit') +} +``` + +`quoteTransfer()` creates and signs a transfer PSBT to estimate its fee. It is not a pure arithmetic call, and changing wallet state or fee inputs after the quote can invalidate the result. + + +Although `transferMaxFee` exists in the v2.0.3 config source, `WalletManagerRgb.getAccount()` does not forward it to the account. Enforce an application-owned limit on every transfer instead of relying on that option. + + +## 4. Send once + +```js +const result = await senderAccount.transfer(transfer) + +console.log({ + txid: result.hash, + estimatedFee: result.fee.toString(), +}) +``` + +The high-level method runs `sendBegin → signPsbt → sendEnd`. The release also exposes those primitives for advanced PSBT orchestration; keep their exact argument naming pinned to v2.0.3 and the included `@utexo/rgb-sdk`. + +## 5. Reconcile state + +```js +senderAccount.refreshWallet() + +const transfers = senderAccount.listTransfers(assetId) +const receipt = await senderAccount.getTransferReceipt(result.hash) +``` + +Do not resend automatically after a timeout. The write may have reached the transport endpoint or Bitcoin network even when the caller did not receive a success response. + +## Operational cautions + +- Treat each recipient invoice as single-use. +- Do not substitute a Bitcoin address for the `rgb:` invoice. +- Confirm the asset ID, asset precision, base-unit amount, and network. +- Ensure suitable RGB allocations and Bitcoin UTXOs exist before quoting. +- Preserve the sender and recipient `dataDir` state until settlement is reconciled. +- `getTransfers()` hides native errors as `[]`; use `listTransfers()` when failure visibility matters. + +## Next steps + +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) +- [Back up and restore](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/index.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/index.mdx index 53186053..8b634fe3 100644 --- a/content/docs/sdk/community-modules/wdk-wallet-rgb/index.mdx +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/index.mdx @@ -1,72 +1,85 @@ --- title: RGB wallet -description: Use the UTEXO RGB wallet module for RGB assets on Bitcoin. +description: Use the community-maintained UTEXO wallet for on-chain RGB assets, Bitcoin UTXOs, and local RGB state. docType: explanation schemaType: TechArticle --- -`@utexo/wdk-wallet-rgb` is a WDK wallet module for RGB assets on Bitcoin. It wraps the UTEXO RGB runtime behind WDK wallet manager and account classes. +`@utexo/wdk-wallet-rgb` is a community-maintained WDK wallet module for on-chain RGB assets on Bitcoin. It wraps `@utexo/rgb-sdk` behind WDK wallet manager and account classes. -Powered by [`@utexo/wdk-wallet-rgb`](https://github.com/UTEXO-Protocol/wdk-wallet-rgb). +These pages describe the released [`@utexo/wdk-wallet-rgb@2.0.3`](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/releases/tag/v2.0.3). -This module is developed and maintained independently by a third-party contributor. Tether and the WDK team do not endorse or assume responsibility for its code, security, or maintenance. Review the package and use your own judgment before using it with real funds. +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. -## When to use it +## Choose the correct RGB module -Use this module when your application needs RGB asset support on Bitcoin and can persist local RGB wallet state. +The on-chain wallet and RGB Lightning wallet are separate wallets. They derive different wallet identities, own separate `rgb-lib` state, and do not share asset records. -| Use case | Module | +| Requirement | Module | |---|---| -| RGB asset issuance, balances, and transfers | `@utexo/wdk-wallet-rgb` | -| Base-layer Bitcoin wallet flows without RGB assets | [`@tetherto/wdk-wallet-btc`](/sdk/wallet-modules/wallet-btc) | -| Lightning payments through Spark | [`@tetherto/wdk-wallet-spark`](/sdk/wallet-modules/wallet-spark) | +| Issue NIA assets, receive or send on-chain RGB assets, and manage RGB-aware Bitcoin UTXOs | `@utexo/wdk-wallet-rgb` | +| Hold and transfer RGB assets through LDK channels, BOLT11 invoices, or an LSP | [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) | +| Manage Bitcoin without RGB state | [`@tetherto/wdk-wallet-btc`](/sdk/wallet-modules/wallet-btc) | + +Give the on-chain and Lightning modules different persistent `dataDir` values. Copying an asset ID between them does not copy its wallet records or balance. ## Requirements -The module requires: +For durable use, provide: + +- a BIP-39 mnemonic or seed bytes; +- `mainnet`, `testnet`, or `regtest`; +- a persistent, app-private `dataDir`; +- a trusted Electrs-compatible indexer; +- an RGB transport endpoint for consignment exchange. -- a BIP-39 seed phrase or seed bytes; -- a supported RGB network; -- a persistent app-private `dataDir` for RGB wallet state; -- access to a Bitcoin indexer; -- access to an RGB transport endpoint. +The runtime allows `dataDir` to be omitted and then uses temporary storage. Do not rely on that behavior for a persistent wallet. A seed recreates key material, but it is not a substitute for preserving and backing up the local RGB state. -The package README warns that losing `dataDir` invalidates RGB asset balances even though seed-derived BTC addresses still exist. Pick a durable app-private path and include it in your backup strategy. +## Released runtime support -## Install +The package is ESM and exposes a conditional Bare entry. Its pinned native RGB dependencies publish artifacts for: -```bash -npm install @utexo/wdk-wallet-rgb -``` +- Linux x64 and arm64; +- macOS arm64. -## Minimal setup +No Windows or Intel macOS artifact was published for this release. The package does not declare a Node.js engine range; verify the exact host and native artifact before deployment. -```js -import WalletManagerRgb from '@utexo/wdk-wallet-rgb' + +The maintainer README labels the package beta despite the `2.0.3` version. Test backup, restore, fee, indexer, transport, and failure paths on the target runtime before handling real funds. + -const manager = new WalletManagerRgb(seedPhrase, { - network: 'regtest', - dataDir: './wallet-data' -}) +## Capabilities and boundaries -const account = await manager.getAccount() -const address = await account.getAddress() +- One account at index `0`, with BIP-86 vanilla and colored derivation paths. +- Settled Bitcoin and RGB balance queries, transaction and transfer history, and receipts. +- NIA issuance and blind or witness receive invoices. +- High-level RGB transfers and lower-level PSBT transfer steps. +- Bitcoin sends and RGB-compatible UTXO creation. +- Encrypted local-state backup and restore. +- Full-account message signing and verification. -console.log(address) -``` +This release does not expose multiple accounts, arbitrary derivation paths, or UDA/CFA issuance through its declared WDK account API. -## Reference +## Start building - -Review exports, configuration, wallet manager methods, and account methods. + +Install the released package and create the single RGB account. + + +Follow task-focused guides for assets, UTXOs, storage, migration, and errors. - -Open the package source and README on GitHub. + +Choose a network, durable storage, indexer, and transport endpoint. + + +Review the public v2.0.3 manager and account surface. - -Review the community module disclaimer and submission flow. + +Inspect the exact released source and security guidance. diff --git a/content/docs/sdk/community-modules/wdk-wallet-rgb/usage.mdx b/content/docs/sdk/community-modules/wdk-wallet-rgb/usage.mdx new file mode 100644 index 00000000..02d70ca3 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-wallet-rgb/usage.mdx @@ -0,0 +1,51 @@ +--- +title: RGB wallet usage +description: Task-focused guides for the released @utexo/wdk-wallet-rgb 2.0.3 community module. +docType: how-to +schemaType: TechArticle +icon: BookOpen +--- + +Use these guides for the single-account, on-chain RGB wallet in `@utexo/wdk-wallet-rgb@2.0.3`. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + + + +Install the package and open the index-0 account. + + +Persist local RGB state and understand the account and derivation boundaries. + + +Read Bitcoin and RGB balances, transactions, transfers, and receipts. + + +Send Bitcoin and create the UTXOs needed by RGB workflows. + + +Issue NIA assets and create blind or witness invoices. + + +Quote and send RGB assets to a recipient-generated invoice. + + +Protect local state and move from the legacy remote-node architecture. + + +Use the full account's message-signing surface. + + +Handle ambiguous history, fee, network, and cleanup failures. + + + +## Reference + +- [Configuration](/sdk/community-modules/wdk-wallet-rgb/configuration) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) +- [v2.0.3 release](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/releases/tag/v2.0.3) diff --git a/content/docs/sdk/core-module/api-reference.mdx b/content/docs/sdk/core-module/api-reference.mdx index e5aaded8..d72f061d 100644 --- a/content/docs/sdk/core-module/api-reference.mdx +++ b/content/docs/sdk/core-module/api-reference.mdx @@ -12,7 +12,8 @@ icon: Code |-------|-------------|---------| | [WDK](#wdk) | Main class for managing wallets across multiple blockchains. Orchestrates wallet managers, protocols, middleware, and local transaction policies. | [Constructor](#constructor), [Methods](#methods) | | [IWalletAccount](#iwalletaccount) | Base writable wallet account interface from `@tetherto/wdk-wallet`. | [Methods](#methods-1) | -| [IWalletAccountWithProtocols](#iwalletaccountwithprotocols) | Extended wallet account interface that supports protocol registration and access. Extends `IWalletAccount`. | [Methods](#methods-2) | +| [IWalletAccountWithProtocols](#iwalletaccountwithprotocols) | Protocol registration and access surface added to a wallet account. | [Methods](#methods-2) | +| [WdkAccount](#wdkaccount) | Consumer-facing account type returned by `getAccount()` and `getAccountByPath()`. | Type alias | ## WDK @@ -47,8 +48,8 @@ const wdk2 = new WDK(seedBytes) | `registerProtocol(blockchain, label, protocol, config)` | Registers a protocol globally for a blockchain | `WDK` | - | | `registerMiddleware(blockchain, middleware)` | Registers middleware for account decoration | `WDK` | - | | `registerPolicy(policies, options?)` | Registers local transaction policies for wallet account and protocol write methods | `WDK` | If policy configuration is invalid | -| `getAccount(blockchain, index?)` | Returns a wallet account for a blockchain and index | `Promise` | If wallet not registered | -| `getAccountByPath(blockchain, path)` | Returns a wallet account for a blockchain and derivation path | `Promise` | If wallet not registered | +| `getAccount(blockchain, index?)` | Returns a wallet account for a blockchain and index | `Promise` | If wallet not registered | +| `getAccountByPath(blockchain, path)` | Returns a wallet account for a blockchain and derivation path | `Promise` | If wallet not registered | | `getFeeRates(blockchain)` | Returns current fee rates for a registered blockchain | `Promise` | If wallet not registered | | `dispose(blockchains?)` | Disposes all registered wallets, or only the named blockchains, and clears keys and account state managed by WDK | `void` | - | @@ -95,19 +96,21 @@ const wdk2 = new WDK(seedPhrase) ##### `registerProtocol(blockchain, label, protocol, config)` Registers a protocol globally for all accounts of a specific blockchain. -For swidge integrations, pass a concrete provider class that extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. +For swidge or Smart Deposit Address (SDA) integrations, pass a concrete provider class that extends the corresponding base class from `@tetherto/wdk-wallet/protocols`. **Type Parameters:** -- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol | typeof FiatProtocol | typeof SwidgeProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocols` classes +- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol | typeof FiatProtocol | typeof SwidgeProtocol | typeof SdaProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocols` classes **Parameters:** - `blockchain` (string): The name of the blockchain -- `label` (string): Unique label for the protocol (must be unique per blockchain and protocol type) +- `label` (string): Registry label for the protocol. Registering the same blockchain, protocol type, and label again replaces the previous global registration. - `protocol` (P): The protocol class - `config` (`ConstructorParameters

[1]`): The protocol configuration **Returns:** `WDK` - The WDK instance (supports method chaining) +Global registration stores the provider class and config without constructing or validating the provider. Provider-constructor errors therefore surface when an account retrieves the protocol. A global registration takes precedence over an account-scoped registration with the same type and label. + **Example:** ```javascript title="Register Protocols" import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' @@ -124,6 +127,9 @@ wdk.registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) // Register a concrete swidge provider for Ethereum wdk.registerProtocol('ethereum', 'swidge', MySwidgeProtocol, swidgeProtocolConfig) +// Register an illustrative SDA provider for Ethereum +wdk.registerProtocol('ethereum', 'deposits', MySdaProtocol, sdaProtocolConfig) + // Method chaining const wdk2 = new WDK(seedPhrase) .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) @@ -246,7 +252,7 @@ try { } ``` -Supported `PolicyOperation` values are `sendTransaction`, `signTransaction`, `transfer`, `approve`, `sign`, `signTypedData`, `signAuthorization`, `delegate`, `revokeDelegation`, `swap`, `bridge`, `supply`, `withdraw`, `borrow`, `repay`, `buy`, `sell`, `swidge`, and `*`. +Supported `PolicyOperation` values are `sendTransaction`, `signTransaction`, `transfer`, `approve`, `sign`, `signTypedData`, `signAuthorization`, `delegate`, `revokeDelegation`, `swap`, `bridge`, `supply`, `withdraw`, `borrow`, `repay`, `buy`, `sell`, `swidge`, `createDepositAddress`, `renewDepositAddress`, `recoverDepositAddress`, `disableDepositAddress`, and `*`. Use `sign` for message-style signing in this release. `signMessage` and `signHash` are not valid policy operation names. @@ -259,7 +265,7 @@ Returns a wallet account for a specific blockchain and index using BIP-44 deriva - `blockchain` (string): The name of the blockchain (e.g., "ethereum") - `index` (number, optional): The index of the account to get (default: 0) -**Returns:** `Promise` - The wallet account with protocol support. When a registered policy targets the account, the returned runtime account is policy-enforced and exposes matching `simulate` helpers. +**Returns:** `Promise` - The writable wallet account with protocol support. When a registered policy targets the account, the returned runtime account is policy-enforced and exposes matching `simulate` helpers. **Throws:** Error if no wallet has been registered for the given blockchain. Throws `PolicyConfigurationError` if a registered policy applies but the wallet account does not expose a read-only account view. @@ -289,7 +295,7 @@ Returns a wallet account for a specific blockchain and BIP-44 derivation path. - `blockchain` (string): The name of the blockchain (e.g., "ethereum") - `path` (string): The derivation path (e.g., "0'/0/0") -**Returns:** `Promise` - The wallet account with protocol support. When a registered policy targets the account, the returned runtime account is policy-enforced and exposes matching `simulate` helpers. +**Returns:** `Promise` - The writable wallet account with protocol support. When a registered policy targets the account, the returned runtime account is policy-enforced and exposes matching `simulate` helpers. **Throws:** Error if no wallet has been registered for the given blockchain. Throws `PolicyConfigurationError` if a registered policy applies but the wallet account does not expose a read-only account view. @@ -419,7 +425,7 @@ console.log('Signed transaction:', signedTransaction) ## IWalletAccountWithProtocols -Extended wallet account interface that supports protocol registration and access. Extends `IWalletAccount` from `@tetherto/wdk-wallet`. +Protocol registration and access surface that WDK adds to a wallet account. The consumer-facing [`WdkAccount`](#wdkaccount) type combines this interface with `IWalletAccount` from `@tetherto/wdk-wallet`. ### Methods @@ -431,15 +437,16 @@ Extended wallet account interface that supports protocol registration and access | `getLendingProtocol(label)` | Returns the lending protocol with the given label | `ILendingProtocol` | If protocol not found | | `getFiatProtocol(label)` | Returns the fiat protocol with the given label | `IFiatProtocol` | If protocol not found | | `getSwidgeProtocol(label)` | Returns the swidge protocol with the given label | `ISwidgeProtocol` | If protocol not found | +| `getSdaProtocol(label)` | Returns the SDA protocol with the given label | `ISdaProtocol` | If protocol not found | ##### `registerProtocol(label, protocol, config)` Registers a new protocol for this specific account. **Type Parameters:** -- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol | typeof FiatProtocol | typeof SwidgeProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocols` classes +- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol | typeof FiatProtocol | typeof SwidgeProtocol | typeof SdaProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocols` classes **Parameters:** -- `label` (string): Unique label for the protocol (must be unique per account and protocol type) +- `label` (string): Registry label for the protocol. Registering the same protocol type and label again replaces the account-scoped instance. - `protocol` (P): The protocol class - `config` (`ConstructorParameters

[1]`): The protocol configuration @@ -456,8 +463,8 @@ account.registerProtocol('usdt0', Usdt0ProtocolEvm, { apiKey: 'YOUR_API_KEY' }) -// Method chaining -const account2 = await wdk.getAccount('ethereum', 1) +// Method chaining on the resolved account +const account2 = (await wdk.getAccount('ethereum', 1)) .registerProtocol('usdt0', Usdt0ProtocolEvm, usdt0ProtocolConfig) ``` @@ -614,6 +621,30 @@ const quote = await swidge.quoteSwidge({ }) ``` +##### `getSdaProtocol(label)` +Returns the Smart Deposit Address protocol with the given label. + +The example uses `MySdaProtocol` as an illustrative provider class extending `SdaProtocol`; it does not imply that a provider package is available in the WDK documentation catalog. + +**Parameters:** +- `label` (string): The protocol label + +**Returns:** `ISdaProtocol` - The SDA protocol instance + +**Throws:** `Error` with `No sda protocol registered for label:

[1]`): The protocol configuration +Do not call the class constructor directly. Its parameters are implementation details. -**Returns:** `WDK` - The wdk manager instance (supports method chaining) +### Account methods -**Example:** -```javascript title="Register Protocols" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +| Method | Returns | Behavior | +|---|---|---| +| `getAddress()` | `Promise` | Returns the locally derived Bech32 address | +| `getBalance(denom?)` | `Promise` | Reads one denomination; defaults to `nativeDenom` | +| `getTokenBalance(denom)` | `Promise` | Alias behavior for a denomination-specific balance | +| `getTokenBalances(denoms)` | `Promise>` | Reads all balances and returns requested denominations that are present | +| `quoteTransfer(options)` | `Promise<{ fee: bigint }>` | Calculates a fixed-gas transfer fee without broadcasting | +| `transfer(options)` | `Promise` | Sends a bank transfer or configured IBC transfer | +| `sign(message)` | `Promise` | Returns a JSON-encoded ADR-36 `StdSignature` | +| `verify(message, signature)` | `Promise` | Verifies ADR-36 data against this account | +| `signTransaction(transaction)` | `Promise` | Returns a CosmJS signed `TxRaw` without broadcasting | +| `quoteSendTransaction(transaction)` | `Promise<{ fee: bigint }>` | Calculates a fixed-gas native-send fee | +| `sendTransaction(transaction)` | `Promise` | Signs and broadcasts a native bank send | +| `getTransactionReceipt(hash)` | `Promise` | Returns the indexed transaction or throws when it is not found | +| `toReadOnlyAccount()` | Never succeeds | Throws because read-only accounts are not implemented | +| `dispose()` | `void` | Zeros the module-owned private-key buffer and marks the account unusable | + +### Account properties -// Register swap protocol for Ethereum -wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: 'YOUR_velora_API_KEY' -}) +| Property | Type | Description | +|---|---|---| +| `index` | `number` | Last component of the full derivation path | +| `path` | `string` | Full path such as `m/44'/118'/0'/0/0` | +| `keyPair` | `KeyPair` | Public key and the underlying sensitive private-key buffer | +| `isDisposed` | `boolean` | Whether `dispose()` has been called | -// Register bridge protocol for Ethereum -wdk.registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) + +`keyPair.privateKey` exposes the account's underlying private-key bytes. Avoid using this property unless an integration requires it. Never log, serialize, or retain the value, and do not assume `dispose()` can erase copies held elsewhere. + -// Method chaining -const wdk2 = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) -``` +### Balance behavior -##### `registerMiddleware(blockchain, middleware)` -Registers middleware for account decoration and enhanced functionality. +`getBalance()`, `getTokenBalance()`, and `getTokenBalances()` require RPC endpoints. Values are returned in base units. -**Parameters:** -- `blockchain` (string): The name of the blockchain -- `middleware` (`(account: A) => Promise`): Middleware function called when deriving accounts +`getTokenBalances(denoms)` calls the RPC all-balances query and filters it. A requested denomination with no returned balance is omitted rather than included with `0n`. -**Returns:** `WDK` - The wdk manager instance (supports method chaining) +### Message signing -**Example:** -```javascript title="Register Middleware" -// Simple logging middleware -wdk.registerMiddleware('ethereum', async (account) => { - console.log('New account:', await account.getAddress()) -}) +`sign(message)` signs UTF-8 text with ADR-36 and returns a JSON string containing the public key and base64 signature. `verify()`: -// Failover cascade middleware -import { getFailoverCascadeMiddleware } from '@tetherto/wdk-wrapper-failover-cascade' +- binds the signature public key to this account's Bech32 address; +- returns `false` for a different message or account; +- returns `false`, rather than throwing, for malformed signature input. -wdk.registerMiddleware('ethereum', getFailoverCascadeMiddleware({ - fallbackOptions: { - retries: 3, - delay: 1000 - } -})) +### Transaction input -// Method chaining -const wdk2 = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) - .registerMiddleware('ethereum', async (account) => { - console.log('New account:', await account.getAddress()) - }) -``` +`signTransaction()`, `quoteSendTransaction()`, and `sendTransaction()` consume the shared WDK transaction shape: -##### `getAccount(blockchain, index?)` -Returns a wallet account for a specific blockchain and index using BIP-44 derivation. +```ts +type Transaction = { + to: string + value: number | bigint +} +``` -**Parameters:** -- `blockchain` (string): The name of the blockchain (e.g., "ethereum") -- `index` (number, optional): The index of the account to get (default: 0) +The account converts this input to one `/cosmos.bank.v1beta1.MsgSend`: -**Returns:** `Promise` - The wallet account with protocol support +- denomination is always the configured `nativeDenom`; +- amount is `value` converted to a string; +- memo is fixed to `Transfer via WDK`; +- gas is fixed at `200000`. -**Throws:** Error if no wallet has been registered for the given blockchain +`signTransaction()` needs RPC to obtain signing context and returns a signed CosmJS `TxRaw`. Its generated beta declaration types the result as `unknown`. -**Example:** -```javascript title="Get Account" -// Get first account (index 0) -const account = await wdk.getAccount('ethereum', 0) + +`sendTransaction()` in `1.0.0-beta.4` accepts only the unsigned WDK transaction shape. It does not accept or broadcast the signed value returned by `signTransaction()`. + -// Get second account (index 1) -const account1 = await wdk.getAccount('ethereum', 1) +`quoteSendTransaction()` ignores the transaction contents after receiving them. It checks for configured endpoints, calculates the fixed-gas fee, and applies `transferMaxFee`; it does not simulate or validate the transaction through RPC. -// Default index (0) -const defaultAccount = await wdk.getAccount('ethereum') +### Transfer input -// This will throw an error if no wallet registered for 'tron' -try { - const tronAccount = await wdk.getAccount('tron', 0) -} catch (error) { - console.error('No wallet registered for tron blockchain') +```ts +type TransferOptions = { + token: string + recipient: string + amount: number | bigint } ``` -##### `getAccountByPath(blockchain, path)` -Returns a wallet account for a specific blockchain and BIP-44 derivation path. +| Field | Meaning | +|---|---| +| `token` | Cosmos denomination such as `uatom` or an IBC denomination | +| `recipient` | Destination Bech32 address | +| `amount` | Integer amount in base units | -**Parameters:** -- `blockchain` (string): The name of the blockchain (e.g., "ethereum") -- `path` (string): The derivation path (e.g., "0'/0/0") +For matching Bech32 prefixes, `transfer()` calls a bank send. For a different prefix, it selects `ibcChannels[recipientPrefix]` and broadcasts IBC `MsgTransfer` with a fixed 600-second timestamp timeout. + + +`transfer()` checks `transferMaxFee` only after the bank or IBC operation has been signed and broadcast. A transfer can succeed on-chain and then throw the fee-limit error. Call `quoteTransfer()`, enforce an application limit, and validate the operation before `transfer()`. + -**Returns:** `Promise` - The wallet account with protocol support +`quoteTransfer()` checks that an IBC channel mapping exists for a different prefix. It does not query RPC, simulate gas, validate the sender balance, or prove that the channel is active. -**Throws:** Error if no wallet has been registered for the given blockchain +### Transaction receipts -**Example:** -```javascript title="Get Account by Path" -// Full path: m/44'/60'/0'/0/1 -const account = await wdk.getAccountByPath('ethereum', "0'/0/1") +`getTransactionReceipt(hash)` performs one `StargateClient.getTx()` lookup. It returns the raw indexed transaction object when found. When the transaction is not yet indexed or does not exist, it throws: -// Different derivation path -const customAccount = await wdk.getAccountByPath('ton', "1'/2/3") +```text +Transaction not found: ``` -##### `getFeeRates()` -Returns current fee rates for all registered blockchains. +The method does not poll. -**Returns:** `Promise` - The fee rates in base units +## `CosmosWalletConfig` -**Example:** -```javascript title="Get Fee Rates" -const feeRates = await wdk.getFeeRates() -console.log('Fee rates:', feeRates) -``` +| Field | Type | Required | Resolved behavior | +|---|---|---:|---| +| `chainName` | `string` | No | Selects bundled registry metadata; unknown names throw | +| `rpcEndpoints` | `string[]` | No | Replaces registry endpoints when non-empty | +| `retryCount` | `number` | No | Defaults to `3` retry rounds | +| `retryDelay` | `number` | No | Defaults to `150` milliseconds | +| `addressPrefix` | `string` | No | Registry prefix or `cosmos` | +| `nativeDenom` | `string` | No | First registry fee denomination or `uatom` | +| `coinType` | `number` | No | Registry SLIP-44 value or `118` | +| `gasPrice` | `string` | No | Compact amount and denomination such as `0.025uatom` | +| `transferMaxFee` | `number \| bigint` | No | Quote limit and post-broadcast `transfer()` check | +| `ibcChannels` | `Record` | No | IBC source channels keyed by destination prefix | -##### `dispose(blockchains?)` -Disposes all registered wallets when called without arguments, or only the wallets for the named blockchains when you pass a string array. +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration) for precedence and safety details. -**Parameters:** -- `blockchains` (string[], optional): The blockchain identifiers to dispose. Omit this parameter to dispose every registered wallet. +## `ResolvedChainConfig` -**Example:** -```javascript title="Dispose WDK" -// Clean up all sensitive data -wdk.dispose() +`resolveChainConfig()` returns: -// Dispose only one registered wallet -wdk.dispose(['ethereum']) -``` +| Field | Type | +|---|---| +| `rpcEndpoints` | `string[]` | +| `retryCount` | `number` | +| `retryDelay` | `number` | +| `addressPrefix` | `string` | +| `nativeDenom` | `string` | +| `coinType` | `number` | +| `gasPrice` | `string \| undefined` | +| `gasPriceStep` | `{ low: number, average: number, high: number, denom: string } \| undefined` | +| `transferMaxFee` | `number \| bigint \| undefined` | +| `chainId` | `string \| undefined` | +| `prettyName` | `string \| undefined` | +| `ibcChannels` | `Record \| undefined` | -### Static Methods +## Helper functions -| Method | Description | Returns | -|--------|-------------|---------| -| `getRandomSeedPhrase(wordCount?)` | Returns a random BIP-39 seed phrase (12 or 24 words) | `string` | -| `isValidSeedPhrase(seedPhrase)` | Checks if a seed phrase is valid | `boolean` | +### `resolveChainConfig(config?)` -##### `getRandomSeedPhrase(wordCount?)` -Returns a random BIP-39 seed phrase. Supports both 12-word (128-bit entropy) and 24-word (256-bit entropy) seed phrases. +Returns registry-backed or custom resolved configuration. An unknown `chainName` throws and instructs the caller to use custom configuration. -**Parameters:** -- `wordCount` (12 | 24, optional): The number of words in the seed phrase. Defaults to 12. +### `getAvailableChains()` -**Returns:** `string` - The seed phrase +Returns chain names whose bundled registry entry has `chainType === 'cosmos'`. -**Example:** -```javascript title="Generate Random Seed" -// Generate 12-word seed phrase (default) -const seedPhrase12 = WDK.getRandomSeedPhrase() -console.log('Generated 12-word seed:', seedPhrase12) -// Output: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" +### `isKnownChain(chainName)` -// Generate 24-word seed phrase (higher security) -const seedPhrase24 = WDK.getRandomSeedPhrase(24) -console.log('Generated 24-word seed:', seedPhrase24) -// Output: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art" -``` +Returns whether any bundled registry entry has the supplied name. It does not test RPC reachability. -##### `isValidSeedPhrase(seedPhrase)` -Checks if a seed phrase is valid according to BIP-39 standards. +## Error and lifecycle behavior -**Parameters:** -- `seedPhrase` (string): The seed phrase to validate +- Invalid mnemonic and derivation paths reject account creation. +- RPC-backed methods throw when the endpoint list is empty. +- Most Cosmos ABCI, JSON-RPC validation, funds, gas, sequence, and signing errors are not retried. +- Network-shaped errors can fall back or retry. A write error can therefore have an ambiguous on-chain outcome. +- Every account operation except `toReadOnlyAccount()` checks disposal state; the read-only method always throws its unsupported error. +- `dispose()` makes manager and account methods unusable, but it cannot revoke seed or key copies held by application code. -**Returns:** `boolean` - True if the seed phrase is valid +See [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for safe write and recovery guidance. -**Example:** -```javascript title="Validate Seed Phrase" -const isValid = WDK.isValidSeedPhrase('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about') -console.log('Seed phrase valid:', isValid) // true +*** -const isInvalid = WDK.isValidSeedPhrase('invalid seed phrase') -console.log('Seed phrase valid:', isInvalid) // false -``` +## Configuration +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/configuration +Description: Configure chain metadata, RPC fallback, fees, and IBC channels for the Base58 Cosmos wallet module. -## IWalletAccount +`WalletManagerCosmos` accepts an optional `CosmosWalletConfig` object. The same configuration is resolved for every account created by that manager. -Base writable wallet account interface exposed by `@tetherto/wdk-wallet`. Blockchain modules implement this interface and may narrow the transaction type accepted by `signTransaction()` and `sendTransaction()`. + +Community modules are developed and maintained independently by third-party contributors. -### Methods +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -| Method | Description | Returns | Throws | -|--------|-------------|---------|--------| -| `getAddress()` | Returns the account address | `Promise` | - | -| `sign(message)` | Signs a message with the account private key | `Promise` | - | -| `signTransaction(tx)` | Signs a transaction without broadcasting it | `Promise` | If the transaction is invalid for the module | -| `verify(message, signature)` | Verifies a message signature | `Promise` | - | -| `sendTransaction(tx)` | Signs, broadcasts, and returns the transaction result | `Promise` | If provider access or broadcast fails | -| `transfer(options)` | Transfers a token where supported by the module | `Promise` | If the module does not support token transfers | -| `toReadOnlyAccount()` | Returns a read-only account copy | `Promise` | - | -| `dispose()` | Clears sensitive account material from memory | `void` | - | +## Configuration options -##### `signTransaction(tx)` -Signs a transaction with the account private key and returns the signed transaction payload without broadcasting it. Use this when your app needs offline signing, external transaction submission, or a separate review step before broadcast. +| Field | Type | Default | Behavior | +|---|---|---|---| +| `chainName` | `string` | None | Looks up bundled `chain-registry` metadata. An unknown name throws. | +| `rpcEndpoints` | `string[]` | Registry endpoints or `[]` | Replaces registry endpoints when the array is non-empty. | +| `retryCount` | `number` | `3` | Maximum retry rounds after the first round. | +| `retryDelay` | `number` | `150` | Base delay in milliseconds for exponential backoff. | +| `addressPrefix` | `string` | Registry prefix or `cosmos` | Bech32 prefix used to derive the account address. | +| `nativeDenom` | `string` | First registry fee denomination or `uatom` | Denomination used by `getBalance()` and native transaction methods. | +| `coinType` | `number` | Registry SLIP-44 value or `118` | Coin type inserted into the BIP-44 derivation path. | +| `gasPrice` | `string` | Registry average tier or none | Gas price in compact `amount+denom` form, such as `0.025uatom`. | +| `transferMaxFee` | `number \| bigint` | None | Limit consulted by quote methods and, after broadcast, by `transfer()`. | +| `ibcChannels` | `Record` | None | Source-channel map keyed by destination Bech32 prefix. | -**Parameters:** -- `tx` (Transaction): Module-specific transaction object. For example, EVM accounts accept `EvmTransaction`, and Bitcoin accounts accept `BtcTransaction`. +The published package does not expose `transactionMaxFee`. That option exists only in unreleased repository code and must not be used with `1.0.0-beta.4`. -**Returns:** `Promise` - The signed transaction payload. Wallet modules may narrow this return type, such as a hex string for EVM and Bitcoin transactions. +## Choose a configuration mode -**Example:** -```typescript title="Sign Without Broadcasting" -const account = await wdk.getAccount('ethereum', 0) +### Registry-backed configuration -const signedTransaction = await account.signTransaction({ - to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', - value: 1000000000000000n -}) +Use a chain name to resolve the Bech32 prefix, native denomination, coin type, RPC endpoints, chain ID, and fee metadata from the bundled `chain-registry@2.0.197` data. -console.log('Signed transaction:', signedTransaction) +```js +const cosmosHubConfig = { + chainName: 'cosmoshub', +} ``` -## IWalletAccountWithProtocols +Registry data is packaged data, not a runtime discovery service. Verify endpoints and chain parameters before production use. -Extended wallet account interface that supports protocol registration and access. Extends `IWalletAccount` from `@tetherto/wdk-wallet`. +### Custom chain configuration -### Methods +Omit `chainName` when you need to control every chain-specific field. -| Method | Description | Returns | Throws | -|--------|-------------|---------|--------| -| `registerProtocol(label, protocol, config)` | Registers a protocol for this specific account | `IWalletAccountWithProtocols` | - | -| `getSwapProtocol(label)` | Returns the swap protocol with the given label | `ISwapProtocol` | If protocol not found | -| `getBridgeProtocol(label)` | Returns the bridge protocol with the given label | `IBridgeProtocol` | If protocol not found | -| `getLendingProtocol(label)` | Returns the lending protocol with the given label | `ILendingProtocol` | If protocol not found | +```js +const customChainConfig = { + rpcEndpoints: [ + 'https://rpc-1.example.invalid', + 'https://rpc-2.example.invalid', + ], + addressPrefix: 'cosmos', + nativeDenom: 'uatom', + coinType: 118, + gasPrice: '0.025uatom', + retryCount: 3, + retryDelay: 150, +} +``` -##### `registerProtocol(label, protocol, config)` -Registers a new protocol for this specific account. +Replace the example endpoints with trusted endpoints for the intended chain. -**Type Parameters:** -- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocol`'s classes +### Registry metadata with custom endpoints -**Parameters:** -- `label` (string): Unique label for the protocol (must be unique per account and protocol type) -- `protocol` (P): The protocol class -- `config` (`ConstructorParameters

[1]`): The protocol configuration +Providing both values keeps registry metadata but replaces the registry endpoint list. -**Returns:** `IWalletAccountWithProtocols` - The account instance (supports method chaining) +```js +const hybridConfig = { + chainName: 'cosmoshub', + rpcEndpoints: [ + 'https://rpc-1.example.invalid', + 'https://rpc-2.example.invalid', + ], +} +``` -**Example:** -```javascript title="Register Protocol for Account" -import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +The custom endpoints are not appended to the registry list. -const account = await wdk.getAccount('ethereum', 0) +## Fee and gas behavior -// Register protocol for this specific account -account.registerProtocol('usdt0', Usdt0ProtocolEvm, { - apiKey: 'YOUR_API_KEY' -}) +The release uses a fixed gas limit of `200000` for bank sends and transfers. It calculates the fee as: -// Method chaining -const account2 = await wdk.getAccount('ethereum', 1) - .registerProtocol('usdt0', Usdt0ProtocolEvm, usdt0ProtocolConfig) +```text +ceil(gas price amount × 200000) ``` -##### `getSwapProtocol(label)` -Returns the swap protocol with the given label. - -**Parameters:** -- `label` (string): The protocol label - -**Returns:** `ISwapProtocol` - The swap protocol instance +The account selects a gas price in this order: -**Throws:** Error if no swap protocol with the given label has been registered +1. The average `gasPriceStep` from registry fee metadata. +2. The explicit `gasPrice` string. +3. The fallback average gas price `0.025` in `nativeDenom`. -**Example:** -```javascript title="Get Swap Protocol" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' + +When `chainName` resolves a registry `gasPriceStep`, that registry tier takes precedence over an explicit `gasPrice`. To use only an explicit gas price, use a complete custom configuration without `chainName`. + -// Register swap protocol -account.registerProtocol('velora', veloraProtocolEvm, veloraProtocolConfig) +`getFeeRates()` returns deterministic fee amounts for the same fixed gas limit. With registry tiers, `normal` uses the average tier and `fast` uses the high tier. With only an explicit gas price, both values are the same. The method requires a non-empty RPC endpoint configuration but does not query RPC. -// Get swap protocol -const velora = account.getSwapProtocol('velora') +Fee quotes also use this deterministic calculation. They do not simulate the transaction, check the sender's balance, or validate that the fee is currently accepted by the chain. -// Use the protocol -const swapResult = await velora.swap({ - tokenIn: '0x...', - tokenOut: '0x...', - tokenInAmount: 1000000n -}) +### Fee limits -// This will throw an error -// try { -// const uniswap = account.getSwapProtocol('uniswap') -// } catch (error) { -// console.error('No swap protocol with label "uniswap" found') -// } -``` +`quoteTransfer()` and `quoteSendTransaction()` throw when the calculated fee is greater than or equal to `transferMaxFee`. -##### `getBridgeProtocol(label)` -Returns the bridge protocol with the given label. + +In `1.0.0-beta.4`, `transfer()` checks `transferMaxFee` after signing and broadcasting, and `sendTransaction()` does not enforce it. Always quote and enforce an application-level fee limit before either write. Do not treat `transferMaxFee` alone as a pre-broadcast guard. + -**Parameters:** -- `label` (string): The protocol label +## RPC fallback -**Returns:** `IBridgeProtocol` - The bridge protocol instance +RPC-backed operations try endpoints in array order. A default `retryCount` of `3` permits the initial round plus three retry rounds. The delay between rounds grows exponentially from `retryDelay`. -**Throws:** Error if no bridge protocol with the given label has been registered +Network-shaped failures such as timeouts, connection resets, DNS failures, HTTP `429`, and HTTP `5xx` responses can move to another endpoint or retry. Chain and transaction failures such as insufficient funds, invalid sequence, invalid address, out of gas, or invalid chain ID fail immediately. -**Example:** -```javascript title="Get Bridge Protocol" -import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' + +A timeout or connection failure during a write does not prove that the transaction was not accepted. Check the transaction hash, account sequence, and chain state before attempting the write again. + -// Register bridge protocol -account.registerProtocol('usdt0', Usdt0ProtocolEvm) +## IBC channels -// Get bridge protocol -const usdt0 = account.getBridgeProtocol('usdt0') +`transfer()` compares the recipient's Bech32 prefix with `addressPrefix`: -// Use the protocol -await account.approve({ - token: '0x...', - spender: '0x...', // OFT or bridge spender address - amount: 1000000n -}) +- matching prefixes use a Cosmos bank send; +- different prefixes require a matching entry in `ibcChannels` and use IBC `MsgTransfer`. -const bridgeResult = await usdt0.bridge({ - targetChain: 'arbitrum', - recipient: '0x...', - token: '0x...', - amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender -}) +```js +const ibcConfig = { + chainName: 'cosmoshub', + ibcChannels: { + osmo: { + sourceChannel: '', + }, + }, +} ``` -##### `getLendingProtocol(label)` -Returns the lending protocol with the given label. +Replace the placeholder with the source channel on the configured source chain. The module does not discover or validate channel topology. It uses source port `transfer` and a fixed 600-second timestamp timeout. -**Parameters:** -- `label` (string): The protocol label +## Security and cleanup -**Returns:** `ILendingProtocol` - The lending protocol instance +- Use RPC endpoints you trust for balances, account metadata, transaction signing context, broadcast results, and receipts. +- Do not log mnemonic, seed, or `keyPair.privateKey` values. +- Call `manager.dispose()` in `finally`; it disposes cached accounts and zeros module-owned seed and private-key buffers. +- Disposal cannot erase copies retained by application code or guarantee cleanup inside every dependency. -**Throws:** Error if no lending protocol with the given label has been registered +## Next steps -**Example:** -```javascript title="Get Lending Protocol" -import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' +- [Get started](/sdk/community-modules/wdk-wallet-cosmos/guides/get-started) +- [Transfer tokens and use IBC](/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens) +- [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) +- [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference) -// Register lending protocol -account.registerProtocol('aave', AaveProtocolEvm, aaveProtocolConfig) +*** -// Get lending protocol -const aave = account.getLendingProtocol('aave') +## Check balances +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/guides/check-balances +Description: Read native and denomination-specific Cosmos balances through RPC. -// Use the protocol -const supplyResult = await aave.supply({ - token: '0x...', - amount: 1000000n -}) -``` +Balance methods return integer base-unit amounts as `bigint`. -## Complete Example + +Community modules are developed and maintained independently by third-party contributors. -```javascript title="Complete WDK Flow" -import WDK from '@tetherto/wdk' -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerTon from '@tetherto/wdk-wallet-ton' -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -// Initialize WDK Manager -const wdk = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, { - provider: 'https://eth.drpc.org' - }) - .registerWallet('ton', WalletManagerTon, { - tonApiKey: 'YOUR_TON_API_KEY', - tonApiEndpoint: 'https://tonapi.io' - }) - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: 'YOUR_velora_API_KEY' - }) - .registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) +## Read the native balance -// Get accounts -const accountEth = await wdk.getAccount('ethereum', 3) -const accountTon = await wdk.getAccountByPath('ton', "1'/2/3") +`getBalance()` uses the configured `nativeDenom`. Pass a denomination to query a different balance. -// Use wallet account methods -const { hash, fee } = await accountEth.sendTransaction({ - to: '0x...', - value: 1000000000000000000n // 1 ETH -}) +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -// Use protocols -const velora = accountEth.getSwapProtocol('velora') -const swapResult = await velora.swap(swapOptions) +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -const usdt0 = accountEth.getBridgeProtocol('usdt0') -// bridgeOptions.oftContractAddress is the source-chain bridge spender. -await accountEth.approve({ - token: bridgeOptions.token, - spender: bridgeOptions.oftContractAddress, - amount: bridgeOptions.amount +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', }) -const bridgeResult = await usdt0.bridge(bridgeOptions) - -// Clean up -wdk.dispose() -``` -## Types +try { + const account = await manager.getAccount(0) -### FeeRates + const nativeBalance = await account.getBalance() + const atomBalance = await account.getBalance('uatom') -```typescript title="Type: FeeRates" -interface FeeRates { - [blockchain: string]: { - normal: number; - fast: number; - }; + console.log({ + nativeBaseUnits: nativeBalance.toString(), + atomBaseUnits: atomBalance.toString(), + }) +} finally { + manager.dispose() } ``` -### Middleware Function - -```typescript title="Type: MiddlewareFunction" -type MiddlewareFunction = ( - account: A -) => Promise; -``` +Do not treat base units as display units. Apply denomination metadata and decimal formatting in your application. -### Protocol Types +## Read token balances -```typescript title="Types: Protocol Interfaces" -// Swap Protocol -interface ISwapProtocol { - swap(options: SwapOptions): Promise; -} +Use `getTokenBalance(denom)` for one denomination or `getTokenBalances(denoms)` to filter the account's full balance response: -// Bridge Protocol -interface IBridgeProtocol { - bridge(options: BridgeOptions): Promise; -} +```js +const atomBalance = await account.getTokenBalance('uatom') -// Lending Protocol -interface ILendingProtocol { - supply(options: LendingOptions): Promise; - withdraw(options: LendingOptions): Promise; - borrow(options: LendingOptions): Promise; - repay(options: LendingOptions): Promise; -} +const balances = await account.getTokenBalances([ + 'uatom', + 'ibc/', +]) -// Swidge Protocol (unified swap + bridge + route) -interface ISwidgeProtocol extends ISwapProtocol, IBridgeProtocol { - quoteSwidge(options: SwidgeOptions): Promise; - swidge(options: SwidgeOptions, config?: SwidgeProtocolConfig): Promise; - getSwidgeStatus(id: string, options?: SwidgeStatusOptions): Promise; - getSupportedChains(): Promise; - getSupportedTokens(options?: SwidgeSupportedTokensOptions): Promise; -} +console.log(atomBalance.toString()) +console.log(balances) ``` -### Swidge Protocol - -`SwidgeProtocol` is an abstract base class exported from `@tetherto/wdk-wallet/protocols` for provider packages that implement a single, route-aware surface for same-chain swaps and cross-chain bridges. It implements `ISwidgeProtocol`, which extends both `ISwapProtocol` and `IBridgeProtocol`, so the base class derives `swap()`, `quoteSwap()`, `bridge()`, and `quoteBridge()` by delegating to `swidge()` and `quoteSwidge()`. Provider subclasses implement the abstract methods below. - -| Method | Description | Returns | -|--------|-------------|---------| -| `quoteSwidge(options)` | Returns a non-binding quote for a swap/bridge operation | `Promise` | -| `swidge(options, config?)` | Executes a swap/bridge operation | `Promise` | -| `getSwidgeStatus(id, options?)` | Returns the current status of an in-flight operation | `Promise` | -| `getSupportedChains()` | Returns the chains the provider supports | `Promise` | -| `getSupportedTokens(options?)` | Returns the tokens the provider supports, optionally route-scoped | `Promise` | - -The `SwidgeOptions` input combines common fields (`fromToken`, `toToken`, optional `toChain`, `recipient`, `refundAddress`, `slippage`) with either an exact-in (`fromTokenAmount`) or exact-out (`toTokenAmount`) amount. The optional `SwidgeProtocolConfig` accepts `maxNetworkFeeBps` and `maxProtocolFeeBps` to cap acceptable fees. - -See the [Swidge Protocol Interface](/sdk/swidge-modules) page for the full discovery, quote, execution, status, fee, and result shapes. - -```typescript title="Type: ISwidgeProtocol Options and Results" -type SwidgeProtocolConfig = { - maxNetworkFeeBps?: number | bigint; - maxProtocolFeeBps?: number | bigint; -}; +Replace the IBC placeholder with a trusted denomination trace hash for the configured chain. -type SwidgeOptions = { - fromToken: string; - toToken: string; - toChain?: string | number; // defaults to the source chain (same-chain swap) - recipient?: string; - refundAddress?: string; - slippage?: number; // decimal, e.g. 0.01 for 1% -} & ( - | { fromTokenAmount: number | bigint } // exact-in - | { toTokenAmount: number | bigint } // exact-out -); +`getTokenBalances()` omits a requested denomination when the RPC response has no entry for it. If your application wants an explicit zero, apply that policy after the call: -type SwidgeStatus = - | 'pending' | 'action-required' | 'completed' | 'failed' - | 'refund-pending' | 'refunded' | 'cancelled' | 'expired' | 'partial'; +```js +const atom = balances.uatom ?? 0n ``` -*** +## RPC and account requirements -## Next Steps +- Balance calls require at least one configured RPC endpoint. +- Registry endpoints can become stale or rate limited; configure trusted alternatives when needed. +- The module retries network-shaped failures according to `retryCount` and `retryDelay`. +- Chain and query errors fail immediately. +- `toReadOnlyAccount()` is not implemented in `1.0.0-beta.4`, so a seed-backed account is required even for reads. - - -Get started with WDK's configuration - - -Get started with WDK's Usage - - -Explore blockchain-specific wallet modules - - -Cross-chain USD₮0 bridges - - +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration) for endpoint precedence and [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for retry guidance. *** -### Need Help? - - +## Get started +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/guides/get-started +Description: Install the released Base58 Cosmos wallet module and derive a Cosmos account. -*** +This guide uses the published `1.0.0-beta.4` package. The repository's default branch can contain unreleased APIs that are not available in this version. -## WDK Core Configuration -URL: https://docs.wdk.tether.io/sdk/core-module/configuration -Description: Configuration options and settings for @tetherto/wdk + +Community modules are developed and maintained independently by third-party contributors. -# Configuration +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -## WDK Manager Configuration +## 1. Install the package -```javascript title="Create WDK Instance" -import WDK from '@tetherto/wdk' +Install the exact version documented by this guide: -const wdk = new WDK(seedPhrase) +```bash +npm install @base58-io/wdk-wallet-cosmos@1.0.0-beta.4 ``` +The package is ESM and does not declare a Node.js engine requirement. It also provides a Bare conditional entry through the same package specifier. -The WDK Manager itself only requires a seed phrase for initialization. Configuration is done through the registration of wallets and protocols. - -## Wallet Registration Configuration +## 2. Configure the chain -```javascript title="Register WDK Wallet" -import WDK from '@tetherto/wdk' -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerTon from '@tetherto/wdk-wallet-ton' +`chainName` loads bundled chain metadata, including the Bech32 prefix, native denomination, coin type, and RPC endpoints. -const wdk = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, { - provider: 'https://eth.drpc.org' - }) - .registerWallet('ton', WalletManagerTon, { - tonApiKey: 'YOUR_TON_API_KEY', - tonApiEndpoint: 'https://tonapi.io' - }) +```js +const config = { + chainName: 'cosmoshub', +} ``` +Bundled registry data is packaged metadata, not live chain discovery. Verify the resolved endpoints and chain parameters before production use. See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration) for custom endpoints and fee behavior. -## Protocol Registration Configuration - -```javascript title="Register WDK Protocol" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +## 3. Derive an account -const wdk = new WDK(seedPhrase) - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: 'YOUR_velora_API_KEY' - }) -``` +Load the mnemonic from secure storage and dispose the manager even when an operation fails: +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -## Configuration Options +const seedPhrase = process.env.WDK_SEED_PHRASE -### Wallet Configuration +if ( + !seedPhrase || + !WalletManagerCosmos.isValidSeedPhrase(seedPhrase) +) { + throw new Error('A valid WDK_SEED_PHRASE is required') +} -Each wallet manager requires its own configuration object when registered. The configuration depends on the specific wallet module being used. +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -#### EVM Wallet Configuration +try { + const account = await manager.getAccount(0) + const address = await account.getAddress() -```javascript title="Ethereum WDK Wallet Configuration" -const ethereumWalletConfig = { - provider: 'https://eth.drpc.org', // RPC endpoint - // Additional EVM-specific configuration options + console.log('Cosmos address:', address) +} finally { + manager.dispose() } - -wdk.registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) ``` +Address derivation is local. Balance, signing-context, broadcast, and receipt methods require at least one configured RPC endpoint. -#### TON Wallet Configuration + +Never hardcode, log, or commit a mnemonic, seed, or `keyPair.privateKey`. `dispose()` clears buffers owned by the module, but it cannot erase the environment string or copies retained by your application or its dependencies. + -```javascript title="TON WDK Wallet Configuration" -const tonWalletConfig = { - tonClient: { - secretKey: 'YOUR_TON_API_KEY', - url: 'https://toncenter.com/api/v2/jsonRPC' - } -} +## Released limitations -wdk.registerWallet('ton', WalletManagerTon, tonWalletConfig) -``` +In `1.0.0-beta.4`: +- accounts are derived from the manager seed; named and external signers are not supported; +- `toReadOnlyAccount()` is not implemented; +- fee quotes use fixed gas and do not simulate through RPC; +- `signTransaction()` cannot be handed to `sendTransaction()` for broadcast; +- `transferMaxFee` is not a reliable pre-broadcast guard for every write. -### Protocol Configuration +Continue with [Manage accounts](/sdk/community-modules/wdk-wallet-cosmos/guides/manage-accounts) or review the [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference). -Protocols also require their own configuration objects when registered. +*** -#### Swap Protocol Configuration +## Handle errors +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors +Description: Handle Cosmos wallet validation, RPC, fee, receipt, retry, and lifecycle failures safely. -```javascript title="Swap WDK Protocol Configuration" -const veloraProtocolConfig = { - apiKey: 'YOUR_velora_API_KEY', - baseUrl: 'https://apiv5.velora.io' -} +Classify an error before deciding whether an operation is safe to repeat. -wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) -``` + +Community modules are developed and maintained independently by third-party contributors. +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + +## Common failures +| Failure | Likely cause | Safe response | +|---|---|---| +| Invalid mnemonic or derivation path | Seed or account path is invalid | Reject the input before creating an account | +| Unknown chain name | `chainName` is absent from bundled registry data | Use a supported name or provide a complete custom configuration | +| No RPC endpoints | An RPC-backed method resolved an empty endpoint list | Configure at least one trusted endpoint | +| Invalid Bech32 address or prefix | Recipient format does not match the intended flow | Validate the address, checksum, and expected source or destination prefix | +| Missing IBC channel mapping | Destination prefix has no `ibcChannels` entry | Configure and independently verify the source channel | +| Fee-limit error | The deterministic quote met or exceeded `transferMaxFee` | Apply an application fee policy before the write | +| `Transaction not found: ` | The one-shot receipt lookup found no indexed result | Wait according to application policy and query again | +| Disposed manager or account | A method was called after `dispose()` | Create a new manager lifecycle; do not reuse the disposed cached account | +| Read-only conversion error | `toReadOnlyAccount()` is unsupported in this release | Use a short-lived seed-backed account or a different integration | -### Middleware Configuration + +For `transfer()`, a fee-limit error can occur after the transaction was broadcast. Do not interpret that error as proof that the transfer failed. + -Middleware functions can be registered to enhance account functionality. +## Validate before a write -```javascript title="Middleware WDK Protocol Configuration" -// Simple logging middleware -wdk.registerMiddleware('ethereum', async (account) => { - console.log('New account created:', await account.getAddress()) -}) -``` +Accept only positive integer base-unit amounts and validate chain-specific identifiers before constructing an operation: -## Environment Variables +```js +function assertBaseUnitAmount(amount) { + const isValidBigInt = ( + typeof amount === 'bigint' && + amount > 0n + ) + const isValidNumber = ( + typeof amount === 'number' && + Number.isSafeInteger(amount) && + amount > 0 + ) -For production applications, consider using environment variables for sensitive configuration: + if (!isValidBigInt && !isValidNumber) { + throw new TypeError('Amount must be a positive safe integer') + } +} -```javascript title="WDK environment variables Configuration" -const wdk = new WDK(process.env.SEED_PHRASE) - .registerWallet('ethereum', WalletManagerEvm, { - provider: process.env.ETHEREUM_RPC_URL - }) - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: process.env.velora_API_KEY - }) +assertBaseUnitAmount(transfer.amount) ``` +Also verify: -## Configuration Validation - -The WDK Manager will validate configurations when wallets and protocols are registered: +- the intended chain ID and trusted RPC endpoints; +- the recipient's Bech32 checksum and expected prefix; +- the denomination against an application allowlist; +- the sender balance and application spending policy; +- the IBC source channel, destination chain, and route status; +- the quote against an application-owned maximum fee. -- **Wallet Registration**: Ensures the wallet class extends the required base class -- **Protocol Registration**: Validates that protocol labels are unique per blockchain and protocol type -- **Middleware Registration**: Validates that middleware functions have the correct signature +Quotes use configured metadata and fixed gas. They do not prove the operation will pass current chain validation. -## Error Handling +## Distinguish retryable failures -Configuration errors will be thrown during registration: +The module can fall back or retry network-shaped failures such as timeouts, connection resets, DNS failures, HTTP `429`, and HTTP `5xx` responses. Cosmos ABCI and JSON-RPC transaction failures such as insufficient funds, invalid sequence, invalid address, out of gas, or invalid chain ID fail immediately. -```javascript title="Configuration errors" -try { - wdk.registerWallet('ethereum', InvalidWalletClass, config) -} catch (error) { - console.error('Wallet registration failed:', error.message) +```js +function getErrorMessage(error) { + return error instanceof Error ? error.message : String(error) } try { - wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, invalidConfig) + const result = await account.sendTransaction(transaction) + console.log('Broadcast hash:', result.hash) } catch (error) { - console.error('Protocol registration failed:', error.message) + console.error('Broadcast status is unresolved:', getErrorMessage(error)) + throw error } ``` -*** - -## Next Steps +Do not build application retry logic from message matching alone. Preserve the original error and use it for diagnostics, but make retry decisions from the operation type and verified chain state. +## Resolve ambiguous writes - - -Get started with WDK's usage - - -Get started with WDK's API - - -Explore blockchain-specific wallet modules - - -Cross-chain USD₮0 bridges - - +A network failure can happen after a node accepts the transaction but before your application receives the response. Blindly repeating `sendTransaction()` or `transfer()` can create a second valid payment. -*** +When a write throws: -### Need Help? +1. Treat its outcome as unknown. +2. Query a known hash when one is available. +3. Check the sender sequence, balances, and a trusted chain index. +4. Reconcile the intended payment in your application ledger. +5. Retry only after establishing that the first transaction was not accepted. - +`getTransactionReceipt()` performs one lookup and throws while a valid transaction is still waiting to be indexed. Poll with a bounded application policy rather than treating the first miss as final. -*** +## Quote before every write -## Manage Accounts -URL: https://docs.wdk.tether.io/sdk/core-module/guides/account-management -Description: Learn how to work with accounts and addresses. +```js +const applicationMaxFee = 5_000n +const { fee } = await account.quoteSendTransaction(transaction) -This guide explains how to access accounts from your registered wallets. An "Account" object in WDK is your interface for inspecting balances and sending transactions on a specific blockchain. +if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') +} -## Retrieve Accounts +const result = await account.sendTransaction(transaction) +``` -You can retrieve an account using a simple index or a custom derivation path. +`sendTransaction()` does not enforce `transferMaxFee`. `transfer()` checks that option only after broadcast. The published package does not expose `transactionMaxFee`. -### By Index (Recommended) +## Always dispose sensitive state -The simplest way to get an account is by its index (starting at `0`). This uses the default derivation path for the specified blockchain. +```js +const manager = new WalletManagerCosmos(seedPhrase, config) -```typescript title="Get Account by Index" -// Get the first account (index 0) for Ethereum and TON -const ethAccount = await wdk.getAccount('ethereum', 0) -const tonAccount = await wdk.getAccount('ton', 0) +try { + const account = await manager.getAccount(0) + // Perform the minimum required work. +} finally { + manager.dispose() +} ``` -### By Derivation Path (Advanced) +Disposal zeros the manager's module-owned seed buffer and cached accounts' module-owned private-key buffers. It cannot erase copies held in environment strings, application variables, logs, or dependencies. -If you need a specific hierarchy, you can request an account by its unique derivation path. +Avoid `account.keyPair` unless an integration strictly requires it. Its `privateKey` field exposes the underlying sensitive buffer; retaining a reference can defeat cleanup assumptions. -```typescript title="Get Account by Path" -// Custom path for Ethereum -const customEthAccount = await wdk.getAccountByPath('ethereum', "0'/0/1") -``` +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration#rpc-fallback) for retry precedence and the [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference#error-and-lifecycle-behavior) for exact released behavior. - -The WDK instance caches accounts. If you call `getAccount` twice using the same index, the function will return the same `Account` object instance. - +*** + +## Manage accounts +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/guides/manage-accounts +Description: Derive, cache, and dispose Cosmos accounts by index or BIP-44 path. + +`WalletManagerCosmos` derives secp256k1 accounts below the configured Cosmos coin type. -**Network Mismatch Warning** -Ensure your WDK instance configuration matches your account environment. -* If using **Testnet** keys, ensure you registered the wallet with a **Testnet RPC** (e.g., `https://sepolia.drpc.org` for ETH, `https://testnet.toncenter.com/api/v2/jsonRPC` for TON). -* If using **Mainnet** keys, ensure you registered the wallet with a **Mainnet RPC** (e.g., `https://eth.drpc.org` for ETH, `https://toncenter.com/api/v2/jsonRPC` for TON). -Using a Mainnet key on a Testnet RPC (or vice versa) will result in "Network not allowed" or zero balance errors. +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. -## View Addresses +## Derive accounts by index -Once you have an account object, you can retrieve its public blockchain address using the `getAddress` function. +`getAccount(index)` derives the relative path `0'/0/{index}`. With the Cosmos Hub coin type `118`, index `5` resolves to `m/44'/118'/0'/0/5`. -```typescript title="Get Addresses" -const ethAddress = await ethAccount.getAddress() -console.log('Ethereum address:', ethAddress) -``` +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -## Check Balances +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -You can check the native token balance of any account (e.g., ETH on Ethereum, TON on TON) by using the `getBalance()` function. +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -```typescript title="Get Balance" try { - const balance = await ethAccount.getBalance() - console.log('Balance:', balance) -} catch (error) { - console.error('Failed to fetch balance:', error) + const firstAccount = await manager.getAccount(0) + const sixthAccount = await manager.getAccount(5) + + console.log({ + firstAddress: await firstAccount.getAddress(), + sixthAddress: await sixthAccount.getAddress(), + sixthPath: sixthAccount.path, + }) +} finally { + manager.dispose() } ``` -### Multi-Chain Balance Check +`getAccount()` defaults to index `0`. -Because WDK offers a unified interface, you can easily iterate through multiple chains to fetch balances. +## Derive an account by path -The following example: -1. Iterates over an array of user defined chains. -2. Retrieves the first account using the respective chain's `getAccount(index)` function. -3. Retrieves the first account's balance using the `getBalance()` function. -4. Logs the balance to the console. +Pass the relative suffix below `m/44'/'/`: -```typescript title="Check All Balances" -const chains = ['ethereum', 'ton', 'bitcoin'] +```js +const account = await manager.getAccountByPath("0'/0/5") -for (const chain of chains) { - try { - const account = await wdk.getAccount(chain, 0) - const balance = await account.getBalance() - console.log(`${chain} balance:`, balance) - } catch (error) { - console.log(`${chain}: Wallet not registered or unavailable`) - } -} +console.log(account.path) +// m/44'/118'/0'/0/5 when coinType is 118 ``` -## Next Steps +Use the same `try`/`finally` manager lifecycle shown above. Invalid derivation paths reject account creation. -Now that you can access your accounts, learn how to [send transactions](/sdk/core-module/guides/transactions). +## Understand caching -*** +The manager caches accounts by relative derivation path. Repeating either lookup for the same path returns the cached account instance: -## Error Handling -URL: https://docs.wdk.tether.io/sdk/core-module/guides/error-handling -Description: Learn about common errors and best practices. +```js +const byIndex = await manager.getAccount(5) +const byPath = await manager.getAccountByPath("0'/0/5") -# Error Handling & Best Practices +console.log(byIndex === byPath) // true +``` -This guide covers recommended patterns for error handling and security when using the WDK. +Prefer `manager.dispose()` when the wallet lifecycle ends. Disposing one cached account directly does not evict it; a later lookup for the same path returns that disposed instance. -## Handling Common Errors +## Signer and read-only limits -When interacting with multiple chains and protocols, various runtime issues may occur. +The released manager derives accounts only from its mnemonic or seed bytes. Named signers and externally supplied signer implementations shown on the repository's default branch are not part of `1.0.0-beta.4`. -### Missing Registration +`account.toReadOnlyAccount()` always throws in this release. Even balance-only flows must create a seed-backed account, so keep the manager lifecycle as short as possible and dispose it after use. -The most common error is attempting to access a wallet or protocol that hasn't been registered. + +The public `account.keyPair` property exposes the underlying private-key bytes. Avoid accessing it. Never log, serialize, or retain those bytes, and do not assume disposal can erase copies made by application code. + -```typescript title="Check Registration Pattern" -try { - // This will throw if 'tron' was never registered via .registerWallet() - const tronAccount = await wdk.getAccount('tron', 0) -} catch (error) { - console.error('Tron wallet not available:', error.message) -} -``` +Next, [check balances](/sdk/community-modules/wdk-wallet-cosmos/guides/check-balances) or [sign and verify messages](/sdk/community-modules/wdk-wallet-cosmos/guides/sign-verify-messages). - -Always use `try/catch` blocks when initializing sessions or accessing dynamic features. - +*** -## Memory Management +## Send transactions +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/guides/send-transactions +Description: Quote, sign, broadcast, and look up native Cosmos bank-send transactions. -For security, clear sensitive data from memory when a session is complete. The WDK provides [`dispose()`](/sdk/core-module/api-reference) for this purpose. +The native transaction methods create one Cosmos bank send using the configured `nativeDenom`. -### Disposing the Instance + +Community modules are developed and maintained independently by third-party contributors. -You can clear every registered wallet using [`dispose()`](/sdk/core-module/api-reference): +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -```typescript title="Dispose WDK" -function endSession(wdk) { - // 1. Clean up sensitive data - wdk.dispose() + +Broadcast transactions are irreversible. Validate the chain, recipient, amount, account balance, sequence, and fee policy before calling `sendTransaction()`. A timeout does not prove that the chain rejected the transaction. + - // 2. Modify app state to reflect logged-out status - // ... +## Build the transaction - console.log('Session ended, wallet data cleared.') +`quoteSendTransaction()`, `signTransaction()`, and `sendTransaction()` accept the shared WDK transaction shape: + +```js +const transaction = { + to: 'cosmos1', + value: 1_000n, } ``` -### Disposing Specific Wallets +Replace the recipient placeholder with a validated address. The module: -You can dispose only the wallets you no longer need using [`dispose()`](/sdk/core-module/api-reference): +- sends only the configured `nativeDenom`; +- converts `value` to an integer string; +- uses one `/cosmos.bank.v1beta1.MsgSend`; +- uses the fixed memo `Transfer via WDK`; +- uses a fixed gas limit of `200000`. -```typescript title="Dispose Specific Wallets" -// Keep the TON wallet registered, but dispose the Ethereum wallet -wdk.dispose(['ethereum']) -``` +Use [`transfer()`](/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens) when you need to choose a denomination. - -**After Disposal:** Once a wallet is disposed, any later call that depends on that wallet registration will fail until you register it again. If you call `wdk.dispose()` without arguments, you must instantiate a new WDK instance or register fresh wallets before resuming operations. - +## Quote before broadcast -## Security Best Practices +Apply an application-controlled fee limit before the write: -### Environment Variables +```js +const applicationMaxFee = 5_000n +const quote = await account.quoteSendTransaction(transaction) -Never hardcode API keys or seed phrases in your source code. Use environment variables (e.g., `process.env.TON_API_KEY`). +if (quote.fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') +} +``` -### Secure Storage +Choose a limit that is appropriate for the chain and fee denomination. The quote is a deterministic calculation from configured metadata and the fixed gas limit. It ignores the recipient and amount; it does not query RPC, simulate the transaction, inspect the balance, or validate the recipient. -If you persist a session, never store the raw seed phrase in local storage. Use secure operating system storage (like Keychain on macOS or Keystore on Android). +`sendTransaction()` does not enforce `transferMaxFee` in `1.0.0-beta.4`, and the package does not expose `transactionMaxFee`. Keep the application check immediately before the write. -*** +## Broadcast a native send -## Getting Started -URL: https://docs.wdk.tether.io/sdk/core-module/guides/getting-started -Description: Install and instantiate the WDK Core module. +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -This guide explains how to install the [`@tetherto/wdk`](https://www.npmjs.com/package/@tetherto/wdk) package and create a new instance to start managing your wallets. +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -## 1. Installation +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -### Prerequisites +try { + const account = await manager.getAccount(0) + const transaction = { + to: 'cosmos1', + value: 1_000n, + } -Before you begin, ensure you have the following installed: + const applicationMaxFee = 5_000n + const { fee } = await account.quoteSendTransaction(transaction) + if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') + } -* **[Node.js](https://nodejs.org/)**: version 18 or higher. -* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. + const result = await account.sendTransaction(transaction) + console.log({ + hash: result.hash, + fee: result.fee.toString(), + }) +} finally { + manager.dispose() +} +``` -### Install Package +## Sign without broadcasting -To install the WDK Core package, run the following command in your terminal: +`signTransaction()` obtains the account number, sequence, and chain ID through RPC, then returns a signed CosmJS `TxRaw`: -```bash -npm install @tetherto/wdk +```js +const signedTransaction = await account.signTransaction(transaction) ``` -This package allows you to manage different blockchain wallets and protocols through a single interface. + +`sendTransaction()` accepts only the unsigned `{ to, value }` shape in this release. It cannot accept or broadcast the signed value returned by `signTransaction()`. The generated declaration also types the signed return value as `unknown`. + -## 2. Instantiation +## Look up a receipt -To use WDK, you must create an instance of the `WDK` class. This instance acts as the central manager for all your wallets and protocols. +After a successful broadcast, query the returned hash: -### Import the Module +```js +const receipt = await account.getTransactionReceipt(result.hash) +``` -First, import the `WDK` class from the package: +The method performs one indexed-transaction lookup. It does not poll. If the transaction has not been indexed yet or does not exist, it throws `Transaction not found: `. -```typescript title="Import WDK Core" -import WDK from '@tetherto/wdk' -``` +## Avoid duplicate writes -### Initialize WDK +RPC endpoints are tried in order, and network-shaped failures can be retried. If a connection fails after submission, the write may have reached the chain even though the call throws. Before submitting again: -You can initialize `WDK` in two ways: with a [new seed phrase](#generate-a-new-wallet) or an [existing one](#restore-an-existing-wallet). +1. Check the known transaction hash when one is available. +2. Inspect the sender's sequence, balance, and trusted chain index. +3. Reconcile the intended payment in application state. +4. Repeat only after establishing that the first write was not accepted. -#### Generate a New Wallet +See [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for a recovery pattern. -If you are creating a fresh wallet for a user, use the static `getRandomSeedPhrase()` method to generate a secure mnemonic. +*** -```typescript title="Create new WDK Instance" -// 1. Generate a secure random seed phrase -// Generate 24-word seed phrase for higher security -const seedPhrase = WDK.getRandomSeedPhrase(24) +## Sign and verify messages +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/guides/sign-verify-messages +Description: Create and verify ADR-36 arbitrary-data signatures with a Cosmos account. -// Or use 12-word seed phrase (default) -// const seedPhrase = WDK.getRandomSeedPhrase() +The released module signs UTF-8 messages using the Cosmos ADR-36 arbitrary-data convention. -// 2. Initialize the WDK instance with the new seed -const wdk = new WDK(seedPhrase) -``` + +Community modules are developed and maintained independently by third-party contributors. - -**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. -#### Restore an Existing Wallet - -If a user already has a seed phrase (e.g., from a previous session or another wallet), you can pass it directly to the constructor. +## Sign a message -```typescript title="Restore WDK Instance" -// Replace this string with the user's actual seed phrase -const existingSeed = 'witch collapse practice feed shame open despair creek road again ice ...' +Bind the message to your application's domain, purpose, audience, and nonce before signing: -const wdk = new WDK(existingSeed) -``` +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -## Next Steps +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -With your WDK instance ready, you can now [register wallet modules](/sdk/core-module/guides/wallet-registration) to interact with specific blockchains like [Ethereum](/sdk/wallet-modules/wallet-evm/), [TON](/sdk/wallet-modules/wallet-ton/), or [Bitcoin](/sdk/wallet-modules/wallet-btc/). +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -*** +try { + const account = await manager.getAccount(0) + const message = [ + 'example.com authentication', + 'audience: example-api', + 'nonce: ', + ].join('\n') -## Configure Middleware -URL: https://docs.wdk.tether.io/sdk/core-module/guides/middleware -Description: Learn how to intercept and enhance wallet operations with middleware. + const signature = await account.sign(message) + const verified = await account.verify(message, signature) -Middleware allows you to intercept wallet operations. You can use this to add [logging](#logging), implement retry logic, or handle [failovers for RPC providers](#failover-protection-with-provider-failover). + console.log('Signature verified:', verified) +} finally { + manager.dispose() +} +``` -## Register Middleware +`sign()` returns a JSON string containing an ADR-36 `StdSignature`, including the public key and base64 signature. -When registering middleware, you should reference a specific chain. The middleware function runs every time an account is instantiated or an operation is performed, depending on the implementation. +## Verify expected failures -### Logging +`verify()` binds the signature public key to the current account address. It returns `false` when: -This simple middleware logs a message whenever a new account is accessed. +- the message differs; +- the signature belongs to another account; +- the signature input is malformed. -```typescript title="Logging Middleware" -wdk.registerMiddleware('ethereum', async (account) => { - const address = await account.getAddress() - console.log('Accessed Ethereum account:', address) +```js +const verified = await account.verify( + 'a different message', + signature, +) - // You can also attach custom properties or wrap methods here -}) +console.log(verified) // false ``` -## Failover Protection with Provider Failover +## Apply application-level context -The [`@tetherto/wdk-provider-failover`](https://www.npmjs.com/package/@tetherto/wdk-provider-failover) package provides a resilient wrapper for wallet instances. Unlike standard middleware, you wrap your wallet class instantiation directly. +ADR-36 signs the text you provide. It does not add an application domain, expiry, audience, nonce policy, or replay protection for you. -### Install `@tetherto/wdk-provider-failover` +Before accepting a signature: -You can install the `@tetherto/wdk-provider-failover` using npm with the following command: +1. Construct a canonical message format. +2. Include the intended domain and action. +3. Include a single-use nonce and an expiry when appropriate. +4. Compare the expected account and authorization context. +5. Mark the nonce as consumed after successful verification. -```bash -npm install @tetherto/wdk-provider-failover -``` - -### Use `createFallbackWallet` + +An ADR-36 message signature is not a signed Cosmos transaction. Do not treat it as authorization to broadcast a bank or IBC transfer unless your application defines and enforces that authorization protocol. + -You can import the `createFallbackWallet` function to ensure that if your primary RPC fails, the wallet automatically retries with the fallback providers. +## Released account limitation -With this configuration, if `sendTransaction` fails due to a network error, the WDK will automatically retry using the fallback providers without throwing an error to your application. +`toReadOnlyAccount()` is not implemented in `1.0.0-beta.4`. The package API therefore requires a seed-backed `WalletAccountCosmos` even when your immediate task is verification. Keep that account's lifecycle short, call `manager.dispose()` in `finally`, and avoid accessing the public `keyPair.privateKey` field. -```typescript title="Failover Wrapper Usage" -import { createFallbackWallet } from '@tetherto/wdk-provider-failover' -import { WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' +See the [API reference](/sdk/community-modules/wdk-wallet-cosmos/api-reference#message-signing) for return and failure behavior. -const wallet = createFallbackWallet( - WalletAccountReadOnlyEvm, - ['0x...'], // constructor args - { - primary: { provider: 'https://mainnet.infura.io/v3/YOUR_KEY' }, - fallbacks: [ - { provider: 'https://eth.llamarpc.com' }, - { provider: 'https://ethereum.publicnode.com' } - ] - } -) +*** -// Use the wallet instance directly -const balance = await wallet.getBalance() -``` +## Transfer tokens and use IBC +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens +Description: Send Cosmos denominations on one chain or through a configured IBC channel. -## Next Steps +`transfer()` sends a selected Cosmos denomination with a bank send or an IBC transfer. -Learn about [error handling and best practices](/sdk/core-module/guides/error-handling) to ensure your application is robust and secure. + +Community modules are developed and maintained independently by third-party contributors. -*** +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -## Integrate Protocols -URL: https://docs.wdk.tether.io/sdk/core-module/guides/protocol-integration -Description: Learn how to use Swidge, Swap, Bridge, and Lending protocols. + +Broadcast transfers are irreversible. Quote and enforce your own fee limit before `transfer()`. In `1.0.0-beta.4`, the configured `transferMaxFee` check happens only after the transaction has already been signed and broadcast. + -The WDK Core module supports registering external protocols. This allows you to extend the basic wallet functionality with advanced features like [swidge routes](/sdk/swidge-modules), [token swapping](#swapping-tokens), [cross-chain bridging](#bridging-assets), and lending. +## Send on the same chain -## Register Protocols +When the recipient prefix matches the configured `addressPrefix`, the module broadcasts a Cosmos bank send: -You can register protocols globally (for all new accounts). +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -### Global Registration (Recommended) +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -Global registration ensures that every account you retrieve already has the protocol ready to use. You can do this by chaining a call to `.registerProtocol()` on the WDK instance. +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -### 1. Install Protocol Modules +try { + const account = await manager.getAccount(0) + const transfer = { + token: 'uatom', + recipient: 'cosmos1', + amount: 1_000n, + } -Install the [`@tetherto/wdk-protocol-swap-velora-evm`](https://www.npmjs.com/package/@tetherto/wdk-protocol-swap-velora-evm) and [`@tetherto/wdk-protocol-bridge-usdt0-evm`](https://www.npmjs.com/package/@tetherto/wdk-protocol-bridge-usdt0-evm) packages: + const applicationMaxFee = 5_000n + const { fee } = await account.quoteTransfer(transfer) + if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') + } -```bash -npm install @tetherto/wdk-protocol-swap-velora-evm && npm install @tetherto/wdk-protocol-bridge-usdt0-evm + const result = await account.transfer(transfer) + console.log({ + hash: result.hash, + fee: result.fee.toString(), + }) +} finally { + manager.dispose() +} ``` -### 2. Register in Code +Amounts are integer base units. Replace all address, denomination, and fee-limit examples with values validated for the configured chain. -Now, import the protocol modules and register them with your WDK instance. This makes the protocol methods available to any account derived from that instance. +A matching Bech32 prefix is only the module's routing heuristic; it does not prove that the recipient belongs to the same chain. -First, import the necessary modules: +## Configure an IBC transfer -```typescript title="Import Protocols" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -import usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +When the recipient prefix differs, map that prefix to a source channel on the configured source chain: + +```js +const config = { + chainName: 'cosmoshub', + ibcChannels: { + osmo: { + sourceChannel: '', + }, + }, +} ``` -Then, register the protocols for the specific chains they support: +Then quote and transfer to the destination-prefix address: -```typescript title="Register Protocols" -// Register protocols for specific chains -const wdk = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, ethConfig) +```js +const transfer = { + token: 'uatom', + recipient: 'osmo1', + amount: 1_000n, +} - // Register Velora Swap for Ethereum - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: 'YOUR_API_KEY' - }) +const { fee } = await account.quoteTransfer(transfer) +if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') +} - // Register USDT0 Bridge for Ethereum - .registerProtocol('ethereum', 'usdt0', usdt0ProtocolEvm, { - ethereumRpcUrl: 'https://eth.drpc.org' // Configuration depends on the module - }) +const result = await account.transfer(transfer) ``` -## Use Protocols +Keep the manager inside a `try`/`finally` lifecycle and call `manager.dispose()` as shown in the same-chain example. -Once [registered](#register-protocols), you can access the protocol instance using the specific getter methods, such as `getSwapProtocol`, `getBridgeProtocol`, `getLendingProtocol`, or `getFiatProtocol`. +The released IBC flow: -### Swidge Routes +- keys `ibcChannels` by destination Bech32 prefix; +- uses the configured mapping as the source channel; +- uses source port `transfer`; +- uses a fixed 600-second timestamp timeout; +- does not discover routes, validate channel topology, or track packet acknowledgement. -Use a swidge provider module for new swap, bridge, or combined route integrations. The shared swidge interface discovers supported chains and tokens with `getSupportedChains()` and `getSupportedTokens()`, quotes with `quoteSwidge()`, executes with `swidge()`, and tracks asynchronous settlement with `getSwidgeStatus()`. The provider can decide whether the route is fulfilled as a same-chain swap, same-token bridge, combined route, intent, solver route, or aggregator route. The example below assumes `swidge` is an instance of a concrete provider module that implements the shared interface. +## Understand quote limits -```typescript title="Swidge route flow" -const chains = await swidge.getSupportedChains() -const tokens = await swidge.getSupportedTokens({ - fromChain: 'ethereum', - toChain: 'arbitrum' -}) +`quoteTransfer()` checks that a channel mapping exists when the prefixes differ. It then calculates a fee from configured gas metadata and the fixed gas limit of `200000`. -const options = { - fromToken: '0xSourceToken...', - toToken: '0xDestinationToken...', - toChain: 'arbitrum', - recipient: '0xRecipient...', - fromTokenAmount: 1000000n, - slippage: 0.01 -} +The quote does not: -const quote = await swidge.quoteSwidge(options) +- call RPC or simulate gas; +- check the sender balance; +- validate that the channel is open; +- prove that the destination chain or relayer is available; +- validate the complete transfer against current chain state. -const result = await swidge.swidge(options, { - maxNetworkFeeBps: 50, - maxProtocolFeeBps: 25 -}) + +An over-limit `transfer()` can succeed on-chain and then throw the module's fee-limit error because the check occurs after broadcast. Treat the pre-write quote and application limit as required controls, and reconcile chain state before retrying any failed call. + -const status = await swidge.getSwidgeStatus(result.id, { - toChain: 'arbitrum' -}) -``` +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration#ibc-channels) for channel configuration and [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for ambiguous-write guidance. -Use discovery results to build token and chain selectors, but continue to show the quote details before execution. `swidge()` is the write step in the shared swidge flow. +*** - -Existing swap and bridge modules keep their current accessors for released modules. Prefer the [swidge protocol interface](/sdk/swidge-modules) for new protocol integrations because the standalone swap and bridge interfaces are expected to be deprecated after swidge provider coverage is available. - +## Usage +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/usage +Description: Choose a task-focused guide for the Base58 Cosmos community wallet module. -### Swapping Tokens +Use these guides with `@base58-io/wdk-wallet-cosmos@1.0.0-beta.4`. -Use `getSwapProtocol` to access registered swap services on any wallet account. + +Community modules are developed and maintained independently by third-party contributors. -```typescript title="Swap Tokens" -const ethAccount = await wdk.getAccount('ethereum', 0) -const velora = ethAccount.getSwapProtocol('velora') +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -const result = await velora.swap({ - tokenIn: '0x...', // Address of token to sell - tokenOut: '0x...', // Address of token to buy - tokenInAmount: 1000000n // Amount to swap -}) -``` + + +Install the pinned package and derive a Bech32 account. + + +Use account indexes and Cosmos BIP-44 derivation paths. + + +Read native and denomination-specific balances through RPC. + + +Quote, sign, send, and look up native-denomination transactions. + + +Send Cosmos denominations on one chain or through a configured IBC channel. + + +Create and verify ADR-36 signatures. + + +Handle RPC fallback, ambiguous writes, fee limits, and cleanup. + + +Review registry, RPC, fee, retry, and IBC options. + + +Review exports, types, methods, return values, and limitations. + + -### Bridging Assets +*** -1. Use `getBridgeProtocol` to access cross-chain bridges. -2. Approve the source-chain bridge spender for the token and amount. -3. Call `bridge` from the bridge protocol to send tokens from one protocol to another. +## RGB wallet +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb +Description: Use the community-maintained UTEXO wallet for on-chain RGB assets, Bitcoin UTXOs, and local RGB state. -```typescript title="Bridge Assets" -const ethAccount = await wdk.getAccount('ethereum', 0) -const usdt0 = ethAccount.getBridgeProtocol('usdt0') +`@utexo/wdk-wallet-rgb` is a community-maintained WDK wallet module for on-chain RGB assets on Bitcoin. It wraps `@utexo/rgb-sdk` behind WDK wallet manager and account classes. -await ethAccount.approve({ - token: '0x...', // ERC20 Token Address - spender: '0x...', // OFT or bridge spender address - amount: 1000000n -}) +These pages describe the released [`@utexo/wdk-wallet-rgb@2.0.3`](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/releases/tag/v2.0.3). -const result = await usdt0.bridge({ - targetChain: 'ton', - recipient: 'UQBla...', // TON address - token: '0x...', // ERC20 Token Address - amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender -}) -``` + +Community modules are developed and maintained independently by third-party contributors. - -**Protocol Availability:** If you try to access a protocol that hasn't been registered (e.g., `getSwapProtocol('uniswap')`), the SDK will throw an error. always ensure registration matches the ID you request. +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. -## Next Steps +## Choose the correct RGB module -Learn how to [configure middleware](/sdk/core-module/guides/middleware) to add logging or failover protection to your wallet interactions. +The on-chain wallet and RGB Lightning wallet are separate wallets. They derive different wallet identities, own separate `rgb-lib` state, and do not share asset records. -*** +| Requirement | Module | +|---|---| +| Issue NIA assets, receive or send on-chain RGB assets, and manage RGB-aware Bitcoin UTXOs | `@utexo/wdk-wallet-rgb` | +| Hold and transfer RGB assets through LDK channels, BOLT11 invoices, or an LSP | [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) | +| Manage Bitcoin without RGB state | [`@tetherto/wdk-wallet-btc`](/sdk/wallet-modules/wallet-btc) | -## Send Transactions -URL: https://docs.wdk.tether.io/sdk/core-module/guides/transactions -Description: Learn how to send native tokens on different blockchains. +Give the on-chain and Lightning modules different persistent `dataDir` values. Copying an asset ID between them does not copy its wallet records or balance. -You can [send native tokens](#send-native-tokens), [sign a transaction without broadcasting it](#sign-without-broadcasting), [handle transaction responses](#handling-responses), and [orchestrate multi-chain payments](#multi-chain-transactions) from WDK wallet accounts. +## Requirements - -**Get Testnet Funds:** To test these transactions without spending real money, ensure you are on a testnet and have obtained funds. See [Testnet Funds & Faucets](/resources/concepts#testnet-funds--faucets) for a list of available faucets. - +For durable use, provide: + +- a BIP-39 mnemonic or seed bytes; +- `mainnet`, `testnet`, or `regtest`; +- a persistent, app-private `dataDir`; +- a trusted Electrs-compatible indexer; +- an RGB transport endpoint for consignment exchange. + +The runtime allows `dataDir` to be omitted and then uses temporary storage. Do not rely on that behavior for a persistent wallet. A seed recreates key material, but it is not a substitute for preserving and backing up the local RGB state. + +## Released runtime support + +The package is ESM and exposes a conditional Bare entry. Its pinned native RGB dependencies publish artifacts for: + +- Linux x64 and arm64; +- macOS arm64. + +No Windows or Intel macOS artifact was published for this release. The package does not declare a Node.js engine range; verify the exact host and native artifact before deployment. -**BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. +The maintainer README labels the package beta despite the `2.0.3` version. Test backup, restore, fee, indexer, transport, and failure paths on the target runtime before handling real funds. -## Send Native Tokens +## Capabilities and boundaries -The `sendTransaction` method allows you to transfer value. It accepts a unified configuration object, though specific parameters (like `value` formatting) may vary slightly depending on the blockchain. +- One account at index `0`, with BIP-86 vanilla and colored derivation paths. +- Settled Bitcoin and RGB balance queries, transaction and transfer history, and receipts. +- NIA issuance and blind or witness receive invoices. +- High-level RGB transfers and lower-level PSBT transfer steps. +- Bitcoin sends and RGB-compatible UTXO creation. +- Encrypted local-state backup and restore. +- Full-account message signing and verification. -### Ethereum Example +This release does not expose multiple accounts, arbitrary derivation paths, or UDA/CFA issuance through its declared WDK account API. - -On EVM chains, values are typically expressed in Wei (1 ETH = 10^18 Wei). - +## Start building -The following example will: + + +Install the released package and create the single RGB account. + + +Follow task-focused guides for assets, UTXOs, storage, migration, and errors. + + +Choose a network, durable storage, indexer, and transport endpoint. + + +Review the public v2.0.3 manager and account surface. + + +Inspect the exact released source and security guidance. + + -1. Retrieve the first Ethereum account (see [Manage Accounts](/sdk/core-module/guides/account-management)) -2. Send 0.001 ETH (1000000000000000 wei) to an account using `sendTransaction`. +*** -```typescript title="Send ETH" -const ethAccount = await wdk.getAccount('ethereum', 0) +## RGB wallet API reference +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/api-reference +Description: Public API reference for the released @utexo/wdk-wallet-rgb 2.0.3 community module. -const result = await ethAccount.sendTransaction({ - to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', - value: 1000000000000000n // 0.001 ETH (in Wei) -}) +This page covers the public declarations and runtime methods in [`@utexo/wdk-wallet-rgb@2.0.3`](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/releases/tag/v2.0.3). -console.log('Transaction sent! Hash:', result.hash) -``` + +Community modules are developed and maintained independently by third-party contributors. -### TON Example +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + - -On TON, values are expressed in Nanotons (1 TON = 10^9 Nanotons). +## Package + +| Field | Value | +|---|---| +| Package | `@utexo/wdk-wallet-rgb@2.0.3` | +| Repository | [UTEXO-Protocol/wdk-wallet-rgb](https://github.com/UTEXO-Protocol/wdk-wallet-rgb) | +| Module format | ESM | +| Entries | `index.js`; conditional Bare entry `bare.js` | +| Declarations | `types/index.d.ts` | + +## Exports + +| Export | Description | +|---|---| +| `default` | `WalletManagerRgb` | +| `WalletAccountRgb` | Full account class | +| `WalletAccountReadOnlyRgb` | Query-only account class | +| Types | `RgbWalletConfig`, `RgbTransaction`, `TransferOptions`, `RgbTransactionReceipt`, `RgbTransferReceipt`, and WDK fee/key/result aliases | + +## `RgbWalletConfig` + +| Field | Runtime requirement | Description | +|---|---:|---| +| `network` | Required | `'mainnet'`, `'testnet'`, or `'regtest'`. | +| `dataDir` | Optional at construction | Local RGB state path. Treat it as operationally required and persistent. | +| `indexerUrl` | Optional | Electrs-compatible indexer endpoint. | +| `transportEndpoint` | Optional | RGB consignment transport endpoint. | +| `keys` | Internal | Generated by `WalletManagerRgb`; applications normally do not set it. | +| `transferMaxFee` | Not effective through the manager path | Declared on the config/account, but `WalletManagerRgb.getAccount()` does not forward it to the account in v2.0.3. Enforce a fee limit in application code after quoting. | + + +The generated `RgbWalletConfig` TypeScript alias exposes only `network` and `keys`, although the released runtime and JSDoc accept `dataDir`, `indexerUrl`, `transportEndpoint`, and `transferMaxFee`. JavaScript can pass the operational fields; TypeScript consumers may need a local, release-scoped augmentation until the package declarations are corrected. -The following example will: +## `WalletManagerRgb` -1. Retrieve the first TON account -2. Send 1 TON (1000000000 nton) to an account using `sendTransaction`. +| Member | Returns | Behavior | +|---|---|---| +| `constructor(seed, config)` | `WalletManagerRgb` | Accepts a BIP-39 mnemonic string or seed bytes. Runtime requires `config.network`. | +| `getAccount(index = 0)` | `Promise` | Creates or returns the only account. Any nonzero index throws. | +| `restoreAccountFromBackup(config)` | `Promise` | Restores the backup into `config.dataDir`, creates the account, and caches it at index `0`. | +| `getAccountByPath(path)` | `Promise` | Always throws; arbitrary paths are unsupported. | +| `getFeeRates()` | `Promise<{normal: bigint, fast: bigint}>` | Reads mempool.space recommended fees without selecting the wallet network. | +| `dispose()` | `void` | Clears manager-owned derived-key fields and disposes cached accounts. | -```typescript title="Send TON" -// Send TON transaction -const tonAccount = await wdk.getAccount('ton', 0) -const tonResult = await tonAccount.sendTransaction({ - to: 'UQCz5ON7jjK32HnqPushubsHxgsXgeSZDZPvh8P__oqol90r', - value: 1000000000n // 1 TON (in nanotons) -}) -console.log('TON transaction:', tonResult.hash) -``` +## `WalletAccountRgb` -## Sign Without Broadcasting +### Static factories -Use [`account.signTransaction()`](/sdk/core-module/api-reference#signtransactiontx) when your app needs a signed transaction payload but does not want WDK to broadcast it immediately. Wallet modules accept their own transaction shape and may return a module-specific signed payload. +| Member | Returns | Notes | +|---|---|---| +| `WalletAccountRgb.at(seed, config)` | `Promise` | Low-level factory used by `WalletManagerRgb.getAccount()`. It requires `config.network` and generated `config.keys`, and accepts `dataDir`, `indexerUrl`, and `transportEndpoint`. Applications should normally use `manager.getAccount(0)` so the manager derives the keys and caches the account. | +| `WalletAccountRgb.fromBackup(seed, config)` | `Promise` | Low-level restore factory used by `WalletManagerRgb.restoreAccountFromBackup()`. It requires `config.network`, generated `config.keys`, `backupFilePath`, `password`, and `dataDir`; it restores before opening the account. Prefer the manager method so keys are derived and the restored account is cached at index `0`. | -```typescript title="Sign An EVM Transaction" -const ethAccount = await wdk.getAccount('ethereum', 0) +Both declarations make `config` optional, but the v2.0.3 runtime throws when these required fields are absent. -const signedTransaction = await ethAccount.signTransaction({ - to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', - value: 1000000000000000n -}) +### Identity and WDK methods -console.log('Signed transaction:', signedTransaction) -``` +| Member | Returns | Notes | +|---|---|---| +| `index` | `0` | The only supported account index. | +| `path` | `string` | `m/86'/0'/0'` on mainnet; `m/86'/1'/0'` otherwise. | +| `coloredPath` | `string` | `m/86'/827166'/0'` on mainnet; `m/86'/827167'/0'` otherwise. | +| `keyPair` | `RgbKeyPair` | Includes WDK key bytes plus RGB xpubs and fingerprint. Treat every returned key field as sensitive. | +| `getAddress()` | `string` | Returns the current Bitcoin address synchronously. | +| `getBalance()` | `Promise` | Settled Bitcoin balance in satoshis. | +| `getTokenBalance(assetId)` | `Promise` | Settled RGB amount in the asset's base unit. | +| `sign(message)` | `Promise` | Signs through the underlying RGB wallet. | +| `verify(message, signature)` | `Promise` | Full-account verification. | +| `sendTransaction(tx)` | `Promise<{hash, fee}>` | Sends Bitcoin with `sendBtcBegin → signPsbt → sendBtcEnd`. | +| `quoteSendTransaction(tx)` | `Promise<{fee}>` | Builds and signs a PSBT to estimate the fee. | +| `transfer(options)` | `Promise<{hash, fee}>` | Sends an RGB asset to an `rgb:` invoice. | +| `quoteTransfer(options)` | `Promise<{fee}>` | Builds and signs an RGB PSBT to estimate the fee. | +| `getTransfers(options?)` | `RgbTransfer[]` | Filters and paginates; returns `[]` for both no results and any underlying error. | +| `toReadOnlyAccount()` | `Promise` in the declarations | The v2.0.3 runtime returns synchronously, but `await` works with both behaviors and satisfies the published type. | +| `dispose()` | `void` | Zeroes the wrapper's derived private-key bytes and disposes its RGB wallet. | + +### RGB, UTXO, and state methods + +| Method | Returns | Notes | +|---|---|---| +| `getRgbWallet()` | RGB SDK `WalletManager` | Advanced escape hatch; its API and lifecycle are maintained by `@utexo/rgb-sdk`. | +| `listAssets()` | `ListAssets[]` | Current asset inventory. | +| `issueAssetNia({ticker, name, amounts, precision})` | `IssueAssetNIA` | Issues a Non-Inflatable Asset. Other issuance schemas are not declared by this release. | +| `receiveAsset({assetId?, amount, witness})` | `InvoiceReceiveData` | Creates a witness invoice when `witness` is true, otherwise a blind invoice. | +| `sendBegin(options)` | `string` | Creates a base64 PSBT for an RGB send. | +| `signPsbt(psbt)` | `Promise` | Signs a base64 PSBT. | +| `sendEnd({signedPsbt})` | `SendResult` | Finalizes and broadcasts the RGB send. | +| `createUtxos(options)` | `Promise` | Combined create/sign/finalize flow. | +| `createUtxosBegin(options)` | `string` | Creates a UTXO-creation PSBT. | +| `createUtxosEnd({signedPsbt})` | `number` | Finalizes UTXO creation. | +| `listUnspents()` | `Unspent[]` | Current RGB wallet UTXOs. | +| `listTransactions()` | `RgbTransactionReceipt[]` | Bitcoin transaction records. | +| `listTransfers(assetId?)` | `RgbTransfer[]` | Native transfer list with optional asset filter. | +| `failTransfers(request)` | `boolean` | Forwards transfer-failure handling to the RGB SDK. The v2.0.3 declaration and JSDoc disagree on the parameter name/type; inspect the matching SDK before calling it. | +| `createBackup({password, backupPath})` | Backup response | Creates an encrypted backup file. | +| `restoreFromBackup({password, backupFilePath, dataDir})` | Restore response | Low-level account restore. Prefer the manager restore flow before opening the destination. | +| `refreshWallet()` | `void` | Refreshes RGB transfer state. | +| `registerWallet()` | `Promise<{address, btcBalance}>` | Registers the wallet and returns its address and Bitcoin balance. | +| `syncWallet()` | `void` | Synchronizes with the Bitcoin chain. | - -`signTransaction()` only signs. Use `sendTransaction()` when you want WDK to sign, broadcast, and return the transaction hash. + +Version 2.0.3 has a declaration/runtime mismatch for `sendEnd()`: the published declaration requires `signed_psbt`, while the runtime reads `signedPsbt`. Pass the runtime-correct camel-case field and use a narrow type assertion until the upstream declaration is corrected: + +```typescript +const request = { signedPsbt } as unknown as Parameters[0] +const result = account.sendEnd(request) +``` -## Handling Responses +## `WalletAccountReadOnlyRgb` -The `sendTransaction` method returns a [transaction result object](/sdk/core-module/api-reference). The most important field is typically `hash`, which represents the transaction ID on the blockchain. You can use this hash to track the status of your payment on a block explorer. +The read-only class is constructed from an address and configuration. It inherits balance methods and exposes: -## Multi-Chain Transactions +| Method | Returns | Behavior | +|---|---|---| +| `getBalance()` | `Promise` | Settled Bitcoin balance. | +| `getTokenBalance(assetId)` | `Promise` | Settled RGB balance. | +| `getTransactionReceipt(hash)` | `Promise` | Bitcoin receipt or `null`. | +| `getTransferReceipt(hash)` | `Promise` | RGB transfer receipt or `null`. | +| `quoteSendTransaction()` | Rejected promise | Read-only accounts cannot construct or sign a quote PSBT. | +| `quoteTransfer()` | Rejected promise | Read-only accounts cannot construct or sign a quote PSBT. | -You can orchestrate payments across different chains in a single function by acting on multiple account objects sequentially. +The v2.0.3 read-only class does not implement `verify()`. Use a live full account or a separately validated public-key verification path. -The following example will: -1. Retrieve an ETH and ton account using the `getAccount()` method. -2. Send ETH and `await` the transaction. -3. Send TON and `await` the transaction. +## Input shapes -```typescript title="Multi-Chain Payment" -async function sendCrossChainPayments(wdk) { - const ethAccount = await wdk.getAccount('ethereum', 0) - const tonAccount = await wdk.getAccount('ton', 0) +### Bitcoin transaction - // 1. Send ETH - await ethAccount.sendTransaction({ - to: '0x...', - value: 1000000000000000000n - }) +| Field | Required | Meaning | +|---|---:|---| +| `to` | Yes | Bitcoin address. | +| `value` | Yes | Satoshis as `number` or `bigint`. | +| `feeRate` | No | sat/vbyte; send defaults to `1`, while quote obtains an estimate. | - // 2. Send TON - await tonAccount.sendTransaction({ - to: 'EQ...', - value: 1000000000n - }) -} -``` +### RGB transfer -## Next Steps +| Field | Required | Meaning | +|---|---:|---| +| `recipient` | Yes | Single-use RGB invoice beginning with `rgb:`. | +| `token` | Yes | RGB asset ID. | +| `amount` | Yes | Asset base units as `number` or `bigint`. | +| `feeRate` | No | Bitcoin fee rate in sat/vbyte. | +| `minConfirmations` | No | Minimum confirmations. | +| `witnessData` | No | Optional `{amountSat, blinding}` witness data. | -For more complex interactions like swapping tokens or bridging assets, learn how to [integrate protocols](/sdk/core-module/guides/protocol-integration). +## Release-specific cautions -*** +- `getTransfers()` suppresses native errors and returns `[]`; use direct state checks when an empty result is consequential. +- `sendTransaction()` wraps Bitcoin-send failures with the text `RGB transfer failed`, so the prefix does not identify the failed operation. +- Quote methods construct and sign PSBTs. Treat them as wallet operations, not pure arithmetic. +- `getFeeRates()` reads main mempool.space recommendations without choosing `testnet` or `regtest`. +- `transferMaxFee` is dropped by the normal manager-to-account construction path. Quote and enforce your own limit before sending. +- Preserve and back up `dataDir`; do not assume the mnemonic alone reconstructs RGB state. -## Register Wallets -URL: https://docs.wdk.tether.io/sdk/core-module/guides/wallet-registration -Description: Learn how to register wallet modules for different blockchains. +## Guides -This guide explains how to register wallet modules with your WDK instance. The WDK Core module itself doesn't contain blockchain-specific logic; instead, you register separate modules for each chain you want to support (e.g., Ethereum, TON, Bitcoin). +- [Configure the wallet](/sdk/community-modules/wdk-wallet-rgb/configuration) +- [Issue and receive assets](/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets) +- [Transfer assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Back up, restore, and migrate](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) -## How it works +*** -The WDK uses a builder pattern, allowing you to chain `.registerWallet()` calls. Each call connects a blockchain-specific manager to your central WDK instance. +## RGB wallet configuration +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/configuration +Description: Configure network, durable state, indexing, transport, and fee controls for @utexo/wdk-wallet-rgb 2.0.3. -### Parameters +`WalletManagerRgb` accepts a BIP-39 mnemonic or seed bytes and an RGB wallet configuration. -The `registerWallet` method (see [API Reference](/sdk/core-module/api-reference)) requires three arguments: + +Community modules are developed and maintained independently by third-party contributors. -1. **Symbol**: A unique string identifier for the chain (e.g., `'ethereum'`, `'ton'`). You will use this ID later to retrieve accounts. -2. **Manager Class**: The wallet manager class imported from the specific module (e.g., `WalletManagerEvm`). -3. **Configuration**: An object containing the chain-specific settings (e.g., RPC providers, API keys). +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -## Installation +## Recommended configuration -Install the [wallet managers](/sdk/wallet-modules/) for the blockchains you want to support: +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' -```bash -npm install @tetherto/wdk-wallet-evm @tetherto/wdk-wallet-tron @tetherto/wdk-wallet-btc +const manager = new WalletManagerRgb(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-onchain', + indexerUrl: 'tcp://127.0.0.1:50001', + transportEndpoint: 'rpc://127.0.0.1:3000/json-rpc', +}) ``` -## Example: Registering Multiple Wallets +Replace the paths and endpoints with values for your environment. Do not use a public example endpoint without evaluating its availability, privacy, and trust model. -### Import the Wallet Manager Packages +## Options -First, import the necessary wallet manager packages: +| Field | Type | Runtime default | Guidance | +|---|---|---|---| +| `network` | `'mainnet' \| 'testnet' \| 'regtest'` | None | Required by the manager. | +| `dataDir` | `string` | Temporary directory | Use a durable, app-private path and include it in backup and restore testing. | +| `indexerUrl` | `string` | RGB SDK default | Use a trusted Electrs-compatible endpoint for the selected network. | +| `transportEndpoint` | `string` | RGB SDK default | Used for RGB consignment exchange. Validate its scheme, network, and availability. | +| `keys` | RGB SDK generated keys | Derived by the manager | Internal account-construction field; do not replace manager derivation in normal use. | +| `transferMaxFee` | `number \| bigint` | None | Not forwarded by `WalletManagerRgb.getAccount()` in v2.0.3; do not rely on it through the manager path. | -```typescript title="Import Modules" -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerTron from '@tetherto/wdk-wallet-tron' -import WalletManagerBtc from '@tetherto/wdk-wallet-btc' -``` + +The generated v2.0.3 TypeScript alias omits `dataDir`, `indexerUrl`, `transportEndpoint`, and `transferMaxFee`, although the released runtime and source JSDoc accept them. Keep any local type augmentation pinned to this package version and remove it when upstream declarations converge. + -### Register the Wallets +## Network -Then, [instantiate WDK](/sdk/core-module/guides/getting-started#initialize-wdk) and chain the registration calls: +Only these values are supported by the released declarations and account path logic: -```typescript title="Register Wallets" -const wdk = new WDK(seedPhrase) - // 1. Register Ethereum - .registerWallet('ethereum', WalletManagerEvm, { - provider: 'https://eth.drpc.org' - }) - // 2. Register TRON - .registerWallet('tron', WalletManagerTron, { - provider: 'https://api.trongrid.io' - }) - // 3. Register Bitcoin - .registerWallet('bitcoin', WalletManagerBtc, { - provider: 'https://blockstream.info/api' - }) -``` +| Network | Vanilla path | Colored path | +|---|---|---| +| `mainnet` | `m/86'/0'/0'` | `m/86'/827166'/0'` | +| `testnet` | `m/86'/1'/0'` | `m/86'/827167'/0'` | +| `regtest` | `m/86'/1'/0'` | `m/86'/827167'/0'` | - -**RPC Providers:** The examples use public RPC endpoints for demonstration. We do not endorse any specific provider. -* **Testnets:** You can find public RPCs for Ethereum and other EVM chains on [Chainlist](https://chainlist.org). -* **Mainnet:** For production environments, we recommend using reliable, paid RPC providers to ensure stability. - +Do not configure `signet`, `testnet4`, or a custom network for this release even if a transitive RGB dependency recognizes additional names. - -**TRON Networks:** Choose the correct provider for your environment. -* **Mainnet:** `https://api.trongrid.io` -* **Shasta (Testnet):** `https://api.shasta.trongrid.io` +## Local state + +The wallet stores RGB records under `dataDir`. Use a path that is: + +- persistent across restarts and upgrades; +- private to the application and OS user; +- unavailable to concurrent wallet instances; +- covered by encrypted backup and tested restoration; +- distinct from [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning). + + +The seed derives wallet keys, but it does not replace the local RGB database. Do not delete `dataDir` or treat a mnemonic-only recovery drill as proof that RGB state is recoverable. -## Next Steps +## Indexer and transport -Once your wallets are registered, you can [manage accounts and specific addresses](/sdk/core-module/guides/account-management). +`indexerUrl` supplies Bitcoin chain data. `transportEndpoint` carries RGB consignments. Both services can observe request metadata and can be unavailable, stale, or malicious. -*** +Before production use: -## Usage -URL: https://docs.wdk.tether.io/sdk/core-module/usage -Description: Guide to using the WDK Core module. +1. Bind each endpoint to the configured Bitcoin network. +2. Apply TLS or an authenticated private network where supported. +3. Set application-level timeouts and operational monitoring. +4. Reconcile transfer state before retrying a timed-out write. +5. Test failover without assuming a failed response means a failed broadcast. -The WDK Core module is the central orchestrator for your wallet interactions. +## Fee policy - - -Install and instantiate the WDK. - - -Connect specific blockchains (Ethereum, TON, etc.). - - -Retrieve accounts and check balances. - - -Transfer native tokens. - - -Use Swidge, Swap, Bridge, and Lending protocols. - - -Add logging and failover protection. - - -Best practices for security and stability. - - +`manager.getFeeRates()` reads `https://mempool.space/api/v1/fees/recommended` and returns `normal` and `fast` as `bigint`. The request does not select `testnet` or `regtest`, so use it only as a mainnet-oriented display hint. -*** +`quoteSendTransaction()` and `quoteTransfer()` create and sign PSBTs to estimate a fee. Apply an application-owned limit before sending: -## Fiat Modules Overview -URL: https://docs.wdk.tether.io/sdk/fiat-modules -Description: Explore WDK fiat modules for on-ramp and off-ramp integrations. +```js +const maximumFee = 2_000n +const quote = await account.quoteTransfer(transfer) -The Wallet Development Kit (WDK) provides fiat modules that enable on-ramp and off-ramp functionality, allowing users to seamlessly convert between fiat currencies and cryptocurrencies within your application. +if (quote.fee > maximumFee) { + throw new Error('Quoted RGB transfer fee exceeds the application limit') +} -## Fiat Protocol Modules +const result = await account.transfer(transfer) +``` -On-ramp and off-ramp functionality for fiat currency integration: +Do not use `transferMaxFee` as the sole guard. The manager omits that field when it constructs the released account. -| Module | Provider | Status | Documentation | -|--------|----------|--------|---------------| -| [`@tetherto/wdk-protocol-fiat-moonpay`](https://github.com/tetherto/wdk-protocol-fiat-moonpay) | MoonPay | ✅ Ready | [Documentation](/sdk/fiat-modules/fiat-moonpay/) | +## Runtime artifacts -## Features +The released dependency graph provides native artifacts for Linux x64, Linux arm64, and macOS arm64. It does not provide a verified Windows or Intel macOS artifact for this version. The package does not declare a Node.js engine range. -Fiat modules provide: +Validate installation, native loading, backup/restore, and real network calls on the exact deployment target. -- **On-Ramp**: Allow users to purchase cryptocurrency using fiat currencies (credit card, bank transfer, etc.) -- **Off-Ramp**: Enable users to sell cryptocurrency and receive fiat currencies -- **Multiple Payment Methods**: Support for various payment options depending on the provider -- **KYC Integration**: Built-in Know Your Customer verification flows -- **Multi-Currency Support**: Support for multiple fiat and cryptocurrencies +## Next steps -## Next Steps +- [Get started](/sdk/community-modules/wdk-wallet-rgb/guides/get-started) +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) -To get started with WDK fiat modules, follow these steps: +*** -1. Get up and running quickly with our [Quickstart Guide](/start-building/nodejs-bare-quickstart) -2. Choose the fiat module that best fits your needs from the table above -3. Check specific documentation for the module you wish to use +## Back up, restore, and migrate the RGB wallet +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate +Description: Protect local RGB state, restore encrypted backups, and choose a privacy-aware v1-to-v2 migration. -You can also: +The seed and local RGB database are separate recovery inputs. Test both before funding the wallet. -- Learn about key concepts in our [Concepts](/resources/concepts) page -- Explore [wallet modules](/sdk/wallet-modules/) to manage user wallets -- Check our [examples](/examples-and-starters/react-native-starter) for production-ready implementations + +Community modules are developed and maintained independently by third-party contributors. -*** +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -## Fiat MoonPay Overview -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay -Description: Overview of the @tetherto/wdk-protocol-fiat-moonpay module +## Create an encrypted backup -# @tetherto/wdk-protocol-fiat-moonpay Overview +```js +const backup = account.createBackup({ + password: backupPassword, + backupPath: '/secure-backups/rgb-wallet.backup', +}) -A WDK module for integrating MoonPay's fiat on-ramp and off-ramp services. This module generates signed or unsigned widget URLs that allow users to buy and sell cryptocurrency using fiat currencies directly within your application. Provide a `signUrl` callback if you want the protocol to return signed URLs from a trusted backend, or omit it to use the unsigned widget URLs directly. +console.log(backup.message) +``` -Get started by reading the [Usage](/sdk/fiat-modules/fiat-moonpay/usage) guide. +Store the backup and password as sensitive recovery material, with access controls and separation appropriate to your threat model. Test readability and retention; a successful method return is not a completed recovery drill. - -This module requires a MoonPay developer account. [Create your account here](https://dashboard.moonpay.com/signup). - +## Restore into an empty directory -If you want MoonPay to sign the widget URLs before they are returned, provide a `signUrl` callback that talks to a trusted backend signer. If you omit `signUrl`, the protocol returns unsigned widget URLs directly. +Create a fresh manager with the same seed and network. Call `restoreAccountFromBackup()` before opening a normal account in the destination directory. -## Features +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' -- **Fiat On-Ramp**: Generate signed or unsigned widget URLs for users to buy cryptocurrency with fiat -- **Fiat Off-Ramp**: Generate signed or unsigned widget URLs for users to sell cryptocurrency with fiat -- **Price Quotes**: Get real-time quotes for buy and sell operations -- **Transaction Tracking**: Retrieve transaction status and details -- **Currency Support**: Query supported cryptocurrencies, fiat currencies, and countries -- **Customizable Widget**: Configure colors, themes, language, and behavior +const restoredManager = new WalletManagerRgb(seedPhrase, { + network: 'testnet', + indexerUrl: trustedIndexerUrl, + transportEndpoint: trustedTransportEndpoint, +}) -## Supported Payment Methods +try { + const restored = await restoredManager.restoreAccountFromBackup({ + backupFilePath: '/secure-backups/rgb-wallet.backup', + password: backupPassword, + dataDir: '/app-private/wdk/rgb-restored', + }) -- Credit and debit cards (Visa, Mastercard, etc.) -- Bank transfers (ACH, SEPA, etc.) -- Apple Pay and Google Pay -- Local payment methods (varies by region) + await restored.registerWallet() + restored.syncWallet() + restored.refreshWallet() -For the full list of supported payment methods by country, see [MoonPay's Supported Payment Methods](https://support.moonpay.com/en/articles/380823-moonpay-s-supported-payment-methods). + console.log({ + address: restored.getAddress(), + assets: restored.listAssets(), + transactions: restored.listTransactions(), + }) +} finally { + restoredManager.dispose() +} +``` -## Supported Cryptocurrencies +Use an empty, private destination and do not run the original and restored wallet concurrently against the same state. -This module supports purchasing and selling cryptocurrencies on networks compatible with WDK wallet modules, including: +## Validate the restore -- Ethereum and EVM-compatible chains (ETH, USD₮, etc.) -- Bitcoin (BTC) -- TRON (TRX, USD₮) -- TON -- Solana (SOL, USD₮) +Compare more than the address: -## Next Steps +- vanilla and colored derivation paths; +- asset IDs, precision, and settled balances; +- RGB transfer history and status; +- Bitcoin transactions and unspents; +- ability to create a new backup; +- a low-value receive and transfer on a test network. - - -Set up your MoonPay API key, optional signing callback, and environment - - -Learn how to integrate MoonPay in your application - - -Complete API documentation for the module - - +## Migrate from v1 ---- +Version 1 relied on a remote RGB Node. The maintainer migration guide warns that the node operator may have learned wallet xpubs and transaction-graph metadata. -### MoonPay Resources +Choose one of two paths: -- [MoonPay Dashboard](https://dashboard.moonpay.com/signup) - Create your developer account -- [MoonPay Support Center](https://support.moonpay.com/) - Official MoonPay documentation and support -- [Supported Payment Methods](https://support.moonpay.com/en/articles/380823-moonpay-s-supported-payment-methods) - Full list by country +### Privacy-preserving reset ---- +1. Create a v2 wallet with a new seed and new persistent `dataDir`. +2. Generate recipient invoices on the new wallet. +3. Transfer assets from the old wallet. +4. Verify settlement and back up the new state. +5. Retire the old seed according to your incident and retention policy. -### Need Help? +This is the maintainer-recommended path when historical metadata exposure matters. - +### Same-seed state migration -*** +1. In a separate environment pinned to the v1 package, create and securely download the v1 backup. +2. Stop and dispose the v1 wallet. +3. Install v2 and restore the backup into a new local directory. +4. Open v2 with the same seed, network, and restored `dataDir`. +5. Verify all state before retiring the remote-node setup. -## Fiat MoonPay API Reference -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/api-reference -Description: API Reference for the @tetherto/wdk-protocol-fiat-moonpay module + +A same-seed restore preserves identity and state but cannot undo information already disclosed to a legacy remote node. An upgrade is not a privacy reset. + -# API Reference +Use normal package imports in each pinned environment. A package version suffix inside an ESM import specifier, such as `import x from 'package@version'`, is not valid npm package import syntax. -Complete API documentation for the `@tetherto/wdk-protocol-fiat-moonpay` module. +## Next steps -## Constructor +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) +- [Maintainer migration guide](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/blob/v2.0.3/MIGRATION.md) -### `new MoonPayProtocol(account, config)` +*** -Creates a new MoonPayProtocol instance. +## Read RGB balances and history +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/balances-history +Description: Read settled Bitcoin and RGB balances, transactions, transfers, and receipts from the on-chain RGB wallet. -**Parameters:** +Use the account's read methods after synchronizing Bitcoin and RGB transfer state. -| Name | Type | Description | -|------|------|-------------| -| `account` | `IWalletAccount` \| `IWalletAccountReadOnly` \| `undefined` | Wallet account for transactions | -| `config` | `MoonPayProtocolConfig` | Configuration object | + +Community modules are developed and maintained independently by third-party contributors. -**Config Options:** +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -| Name | Type | Required | Default | Description | -|------|------|----------|---------|-------------| -| `apiKey` | string | Yes | - | Your MoonPay publishable API key | -| `signUrl` | function | No | - | Callback used to sign buy and sell widget URLs through a trusted backend | -| `cacheTime` | number | No | `600000` | Cache duration for currencies (ms) | -| `environment` | `'production' \| 'sandbox'` | No | `production` | MoonPay widget URL endpoint set | +## Synchronize first -**Example:** +```js +account.syncWallet() +account.refreshWallet() +``` -```typescript -import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; +The freshness of every result depends on the selected indexer, transport endpoint, and local `dataDir`. -const moonpay = new MoonPayProtocol(walletAccount, { - apiKey: 'pk_live_xxxxx', - signUrl: async (urlForSignature) => urlForSignature, - environment: 'production', -}); -``` +## Read balances ---- +```js +const bitcoinSats = await account.getBalance() +const assetUnits = await account.getTokenBalance(assetId) -## Methods +console.log({ + bitcoinSats: bitcoinSats.toString(), + assetUnits: assetUnits.toString(), +}) +``` -### `buy(options)` +Both WDK balance methods return settled values as `bigint`. RGB values are asset base units; apply the asset's precision only when formatting for display. -Generates a MoonPay widget URL for purchasing cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. +To inspect the native asset records: -**Parameters:** +```js +const assets = account.listAssets() +``` -| Name | Type | Required | Description | -|------|------|----------|-------------| -| `options.cryptoAsset` | string | Yes | Cryptocurrency code (e.g., 'eth', 'btc') | -| `options.fiatCurrency` | string | Yes | Fiat currency code (e.g., 'usd', 'eur') | -| `options.cryptoAmount` | number \| bigint | No* | Amount in smallest crypto units | -| `options.fiatAmount` | number \| bigint | No* | Amount in smallest fiat units (cents) | -| `options.recipient` | string | No | Wallet address (uses account address if not provided) | -| `options.config` | MoonPayBuyParams | No | Widget configuration options | +Native result objects come from the pinned `@utexo/rgb-sdk`. Validate the fields your application consumes instead of assuming an unreleased repository shape. -*Either `cryptoAmount` or `fiatAmount` must be provided, but not both. +## Read Bitcoin history and UTXOs -**Returns:** `Promise\<{ buyUrl: string }\>` +```js +const transactions = account.listTransactions() +const unspents = account.listUnspents() +``` ---- +These methods are synchronous wrappers over local/native state. A returned record is not, by itself, proof of finality; inspect its status and confirmations. -### `sell(options)` +## Read RGB transfer history -Generates a MoonPay widget URL for selling cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. +Use `listTransfers()` when an error must remain observable: -**Parameters:** +```js +const allTransfers = account.listTransfers() +const oneAssetTransfers = account.listTransfers(assetId) +``` -| Name | Type | Required | Description | -|------|------|----------|-------------| -| `options.cryptoAsset` | string | Yes | Cryptocurrency code | -| `options.fiatCurrency` | string | Yes | Fiat currency code | -| `options.cryptoAmount` | number \| bigint | No* | Amount in smallest crypto units | -| `options.fiatAmount` | number \| bigint | No* | Amount in smallest fiat units | -| `options.refundAddress` | string | No | Refund wallet address | -| `options.config` | MoonPaySellParams | No | Widget configuration options | +Use `getTransfers()` for local filtering and pagination: -**Returns:** `Promise\<{ sellUrl: string }\>` +```js +const page = account.getTransfers({ + assetId, + limit: 20, + skip: 0, +}) +``` ---- + +`getTransfers()` catches every underlying error and returns `[]`. An empty array therefore means either “no matching transfers” or “the native query failed.” Do not use it alone for reconciliation, audit, or retry decisions. + -### `quoteBuy(options)` +## Read receipts -Gets a price quote for a cryptocurrency purchase. +```js +const bitcoinReceipt = await account.getTransactionReceipt(txid) +const rgbReceipt = await account.getTransferReceipt(transferHash) +``` -**Parameters:** +Each method returns a receipt or `null`. Treat `null` as pending, absent, or not yet indexed—not proof that a previous write failed. -| Name | Type | Required | Description | -|------|------|----------|-------------| -| `options.cryptoAsset` | string | Yes | Cryptocurrency code | -| `options.fiatCurrency` | string | Yes | Fiat currency code | -| `options.cryptoAmount` | number \| bigint | No* | Amount in smallest crypto units | -| `options.fiatAmount` | number \| bigint | No* | Amount in smallest fiat units | -| `options.config` | MoonPayQuoteBuyParams | No | Quote parameters | +## Read-only access -**Returns:** `Promise\` +```js +const readOnly = await account.toReadOnlyAccount() -```typescript -{ - cryptoAmount: bigint, // Crypto amount you'll receive - fiatAmount: bigint, // Fiat amount to pay - fee: bigint, // Total fee amount - rate: string, // Exchange rate - metadata: MoonPayBuyQuoteMetadata -} +const [btcBalance, rgbBalance] = await Promise.all([ + readOnly.getBalance(), + readOnly.getTokenBalance(assetId), +]) ``` ---- +Keep the originating wallet state and endpoints available for the lifetime of the read-only view. -### `quoteSell(options)` +## Next steps -Gets a price quote for selling cryptocurrency. +- [Manage Bitcoin and UTXOs](/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos) +- [Transfer RGB assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) -**Parameters:** +*** -| Name | Type | Required | Description | -|------|------|----------|-------------| -| `options.cryptoAsset` | string | Yes | Cryptocurrency code | -| `options.fiatCurrency` | string | Yes | Fiat currency code | -| `options.cryptoAmount` | number \| bigint | Yes | Amount in smallest crypto units | -| `options.config` | MoonPayQuoteSellParams | No | Quote parameters | +## Send Bitcoin and manage RGB UTXOs +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos +Description: Quote and send Bitcoin, inspect unspents, and create UTXOs with @utexo/wdk-wallet-rgb 2.0.3. -**Returns:** `Promise\` +RGB transfers require suitable Bitcoin UTXOs. The account exposes both WDK Bitcoin sends and RGB SDK UTXO helpers. -```typescript -{ - cryptoAmount: bigint, // Crypto amount to sell - fiatAmount: bigint, // Fiat amount you'll receive - fee: bigint, // Total fee amount - rate: string, // Exchange rate - metadata: MoonPaySellQuoteMetadata -} + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Inspect unspents + +```js +account.syncWallet() + +const unspents = account.listUnspents() +console.log(unspents) ``` ---- +Check native status fields and confirmation requirements before selecting an output for a consequential flow. -### `getSupportedCryptoAssets()` +## Create RGB-compatible UTXOs -Fetches the list of supported cryptocurrencies. Results are cached. +Use the combined method when the wallet can construct and sign the whole operation: -**Returns:** `Promise\` +```js +const created = await account.createUtxos({ + upTo: true, + num: 5, + size: 1_000, + feeRate: 2, +}) -```typescript -{ - code: string, // Currency code (e.g., 'eth') - decimals: number, // Decimal places - networkCode: string, // Network identifier - name: string, // Display name - metadata: MoonPayCryptoCurrencyDetails -} +console.log('UTXOs created:', created) ``` ---- - -### `getSupportedFiatCurrencies()` +`size` is satoshis and `feeRate` is sat/vbyte. Confirm appropriate values for the current network and RGB workflow. -Fetches the list of supported fiat currencies. Results are cached. +The release also exposes `createUtxosBegin()`, `signPsbt()`, and `createUtxosEnd()` for a lower-level PSBT flow. Use the exact v2.0.3 declaration and pinned RGB SDK shape when integrating those methods. -**Returns:** `Promise\` +## Quote a Bitcoin send -```typescript -{ - code: string, // Currency code (e.g., 'usd') - decimals: number, // Decimal places - name: string, // Display name - metadata: MoonPayFiatCurrencyDetails +```js +const transaction = { + to: recipientAddress, + value: 50_000n, + feeRate: 2, } -``` ---- +const quote = await account.quoteSendTransaction(transaction) +console.log('Estimated fee:', quote.fee.toString()) +``` -### `getSupportedCountries()` + +In v2.0.3, `quoteSendTransaction()` ignores the supplied `feeRate`, obtains its own one-block estimate, and constructs and signs a PSBT. `sendTransaction()` then uses the supplied `feeRate` or defaults to `1`. The quoted fee can therefore differ from the send path. Recheck policy and resulting state instead of treating the quote as a binding guarantee. + -Fetches the list of supported countries. +## Send Bitcoin -**Returns:** `Promise\` +```js +const maximumQuotedFee = 2_000n +const quote = await account.quoteSendTransaction(transaction) -```typescript -{ - code: string, // ISO country code - name: string, // Country name - isBuyAllowed: boolean, // Buy operations allowed - isSellAllowed: boolean,// Sell operations allowed - metadata: MoonPayCountryDetail +if (quote.fee > maximumQuotedFee) { + throw new Error('Quoted Bitcoin fee exceeds the application limit') } + +const result = await account.sendTransaction(transaction) +console.log('Transaction ID:', result.hash) ``` ---- +`sendTransaction()` runs `sendBtcBegin → signPsbt → sendBtcEnd` and returns `{hash, fee}`. -### `getTransactionDetail(txId, direction?)` + +Bitcoin-send failures are wrapped with the message prefix `RGB transfer failed` in this release. Classify the operation from your call context, not that text. A timeout also does not prove that broadcast failed; reconcile by transaction ID, UTXOs, and history before retrying. + -Retrieves details of a specific transaction. +`manager.getFeeRates()` reads main mempool.space recommendations without selecting the wallet network. Do not treat it as authoritative for `testnet` or `regtest`. -**Parameters:** +## Next steps -| Name | Type | Required | Default | Description | -|------|------|----------|---------|-------------| -| `txId` | string | Yes | - | MoonPay transaction ID | -| `direction` | `'buy' \| 'sell'` | No | `'buy'` | Transaction type | +- [Issue and receive assets](/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets) +- [Transfer assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) -**Returns:** `Promise\` +*** -```typescript -{ - status: 'completed' | 'failed' | 'in_progress', - cryptoAsset: string, - fiatCurrency: string, - metadata: MoonPayBuyTransaction | MoonPaySellTransaction -} -``` +## Get started with the RGB wallet +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/get-started +Description: Install @utexo/wdk-wallet-rgb 2.0.3 and create its single on-chain RGB account. ---- +This guide installs the documented release, creates a durable local wallet, and reads its first address. -## Types + +Community modules are developed and maintained independently by third-party contributors. -### `MoonPayProtocolConfig` +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -```typescript -interface MoonPayProtocolConfig { - apiKey: string; - signUrl?: (urlForSignature: string) => Promise; - cacheTime?: number; - environment?: 'production' | 'sandbox'; -} -``` +## 1. Check the target runtime -### `MoonPayBuyParams` +The v2.0.3 dependency set publishes native artifacts for Linux x64, Linux arm64, and macOS arm64. It does not publish a verified Windows or Intel macOS artifact, and the package does not declare a Node.js engine range. -Widget configuration options for `buy()` operations: +Test installation and native loading on the exact deployment target before integrating wallet state. -```typescript -interface MoonPayBuyParams { - // UI options (shared with MoonPaySellParams) - colorCode?: string; - theme?: 'dark' | 'light'; - themeId?: string; - language?: string; - showAllCurrencies?: boolean; - showOnlyCurrencies?: string; - showWalletAddressForm?: boolean; - redirectURL?: string; - unsupportedRegionRedirectUrl?: string; - skipUnsupportedRegionScreen?: boolean; +## 2. Install the released package - // Buy-specific options - defaultCurrencyCode?: string; - walletAddress?: string; - walletAddressTag?: string; - walletAddresses?: string; - walletAddressTags?: string; - contractAddress?: string; - networkCode?: string; - lockAmount?: boolean; - email?: string; - externalTransactionId?: string; - externalCustomerId?: string; - paymentMethod?: string; -} +```bash +npm install @utexo/wdk-wallet-rgb@2.0.3 ``` -### `MoonPaySellParams` +## 3. Choose durable state and services -Widget configuration options for `sell()` operations: +Create an app-private, persistent directory. Configure an indexer and transport endpoint for the same network. -```typescript -interface MoonPaySellParams { - // UI options (shared with MoonPayBuyParams) - colorCode?: string; - theme?: 'dark' | 'light'; - themeId?: string; - language?: string; - showAllCurrencies?: boolean; - showOnlyCurrencies?: string; - showWalletAddressForm?: boolean; - redirectURL?: string; - unsupportedRegionRedirectUrl?: string; - skipUnsupportedRegionScreen?: boolean; +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' - // Sell-specific options - defaultBaseCurrencyCode?: string; - refundWalletAddresses?: string; - lockAmount?: boolean; - email?: string; - externalTransactionId?: string; - externalCustomerId?: string; - paymentMethod?: string; -} +const seedPhrase = await loadSeedFromSecretStorage() + +const manager = new WalletManagerRgb(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-onchain', + indexerUrl: 'tcp://127.0.0.1:50001', + transportEndpoint: 'rpc://127.0.0.1:3000/json-rpc', +}) ``` -### `MoonPayQuoteBuyParams` +`loadSeedFromSecretStorage()` represents your application's secret-management boundary. Do not embed a mnemonic in source, logs, telemetry, or crash reports. -```typescript -interface MoonPayQuoteBuyParams { - extraFeePercentage?: number; // 0-10% - paymentMethod?: string; - areFeesIncluded?: boolean; - walletAddress?: string; -} -``` +## 4. Get the account -### `MoonPayQuoteSellParams` +RGB supports only account index `0`. -```typescript -interface MoonPayQuoteSellParams { - extraFeePercentage?: number; // 0-10% - payoutMethod?: string; +```js +try { + const account = await manager.getAccount(0) + const address = account.getAddress() + + console.log('RGB-aware Bitcoin address:', address) +} finally { + manager.dispose() } ``` ---- +`getAddress()` is synchronous in the released runtime. Using `await` on its value is harmless, but it is not required. -## Next Steps +## 5. Verify recovery before funding -- [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) - Setup and configuration options -- [Usage Guide](/sdk/fiat-modules/fiat-moonpay/usage) - Common usage patterns +Before using real funds: -*** +1. Register and synchronize the wallet against trusted services. +2. Create an encrypted backup. +3. Restore into an empty test directory with the same seed. +4. Confirm addresses, assets, balances, transfers, and UTXOs. +5. Repeat on the exact deployment runtime. -## Fiat MoonPay Configuration -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/configuration -Description: Configuration options for the @tetherto/wdk-protocol-fiat-moonpay module + +Do not assume the seed alone restores RGB state. Preserve and test recovery of `dataDir` and encrypted backups. + -# Configuration +## Next steps -This page covers all configuration options for the MoonPay fiat module, including optional URL signing and environment selection. +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Issue and receive assets](/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets) +- [Back up, restore, and migrate](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Configuration](/sdk/community-modules/wdk-wallet-rgb/configuration) -## Prerequisites +*** -Before using this module, you need: +## Handle RGB wallet errors +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors +Description: Handle native failures, ambiguous transfer history, fee-policy gaps, and secure cleanup in @utexo/wdk-wallet-rgb 2.0.3. -1. A MoonPay developer account - [Create an account on MoonPay Dashboard](https://dashboard.moonpay.com/signup) -2. A publishable API key from your dashboard -3. If you want signed widget URLs, a trusted backend signing endpoint for the `signUrl` callback +The v2.0.3 package does not expose a typed public error hierarchy. Handle failures by operation, preserve the original cause, and reconcile state before retrying writes. -## Installation + +Community modules are developed and maintained independently by third-party contributors. -```bash -npm install @tetherto/wdk-protocol-fiat-moonpay -``` +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -## Basic Configuration +## Preserve operation context -```typescript -import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; +```js +async function transferRgb(account, transfer) { + try { + return await account.transfer(transfer) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) -const moonpay = new MoonPayProtocol(walletAccount, { - apiKey: 'pk_live_xxxxx', // Your MoonPay publishable API key - signUrl: async (urlForSignature) => { - const response = await fetch('/api/moonpay/sign-url', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ urlForSignature }), - }); + reportWalletFailure({ + operation: 'rgb_transfer', + message, + }) - if (!response.ok) { - throw new Error(`Failed to sign MoonPay URL: ${response.status} ${response.statusText}`); - } + throw error + } +} +``` - const { signedUrl } = await response.json(); +Do not log the seed, keys, backup password, complete invoice, or user-identifying endpoint credentials. - return signedUrl; - }, - environment: 'sandbox', -}); -``` +## Account for misleading and suppressed errors -## Configuration Options +Two release-specific behaviors require explicit handling: -| Option | Type | Required | Default | Description | -|--------|------|----------|---------|-------------| -| `apiKey` | string | Yes | - | Your MoonPay publishable API key | -| `signUrl` | function | No | - | Callback used to sign buy and sell widget URLs through a trusted backend | -| `cacheTime` | number | No | `600000` (10 min) | Duration in milliseconds to cache supported currencies | -| `environment` | `'production' \| 'sandbox'` | No | `production` | MoonPay widget URL endpoint set | +- `sendTransaction()` wraps Bitcoin-send failures with `RGB transfer failed: ...`. The prefix is misleading; classify it as the Bitcoin operation you invoked. +- `getTransfers()` catches every native error and returns `[]`. Use `listTransfers()` plus synchronization when failure visibility matters. -## Constructor Overloads +```js +try { + account.syncWallet() + account.refreshWallet() + const transfers = account.listTransfers(assetId) + renderTransfers(transfers) +} catch (error) { + renderTransferStateUnavailable() + throw error +} +``` -The `MoonPayProtocol` class supports three constructor patterns: +## Reconcile before retrying -```typescript -// Without account (for public read operations like fetching supported currencies) -const moonpay = new MoonPayProtocol(undefined, config); +Indexer, transport, or broadcast calls can succeed remotely and fail locally. After a timeout or connection loss: -// With read-only account -const moonpay = new MoonPayProtocol(readOnlyAccount, config); +1. Preserve any returned transaction or transfer identifier. +2. Synchronize Bitcoin and refresh RGB state. +3. Inspect transactions, transfers, receipts, and UTXOs. +4. Retry only when an idempotency or reconciliation rule proves it safe. -// With full wallet account (for buy/sell operations) -const moonpay = new MoonPayProtocol(walletAccount, config); -``` +Never regenerate and resend against a single-use invoice merely because the first response timed out. -## Environment Configuration +## Enforce fees in application code -### Sandbox (Testing) +The normal manager path drops `transferMaxFee`. Quote, compare with an application limit, and then send. -Use sandbox endpoints for development and testing: +For Bitcoin sends, remember that v2.0.3 quote logic uses its own estimated fee rate while the send uses the supplied `feeRate` or `1`. Treat the quote as advisory and reconcile the actual result. -```typescript -const moonpay = new MoonPayProtocol(walletAccount, { - apiKey: 'pk_test_xxxxx', - signUrl: async (urlForSignature) => { - const response = await fetch('/api/moonpay/sign-url', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ urlForSignature }), - }); +## Handle common setup failures - return (await response.json()).signedUrl; - }, - environment: 'sandbox', -}); -``` +| Failure area | Check | +|---|---| +| Manager construction | `network` is exactly `mainnet`, `testnet`, or `regtest`. | +| Account creation | Seed is present and native artifact supports the host. | +| Empty or stale state | Correct persistent `dataDir`, network, indexer, and transport endpoint. | +| Transfer rejection | Complete `rgb:` invoice, matching asset ID, base-unit amount, UTXOs, confirmations, and fee rate. | +| Restore rejection | Backup path, password, empty destination, matching seed, and call order before opening the account. | -In sandbox mode: -- No real transactions are processed -- Use test card numbers provided by MoonPay -- KYC verification is simulated +## Clean up without hiding the primary failure -If you do not need signed URLs, omit `signUrl` and the protocol returns unsigned widget URLs directly. +```js +let operationError -### Production +try { + await runWalletFlow(manager) +} catch (error) { + operationError = error + throw error +} finally { + try { + manager.dispose() + } catch (cleanupError) { + reportCleanupFailure(cleanupError, { operationError }) + } +} +``` -For production deployments, use live API keys and the production endpoint set: +The account zeroes its wrapper-owned derived private-key bytes during disposal, and the manager clears its derived-key fields. Cleanup cannot erase external copies or compensate for logged secrets. -```typescript -const moonpay = new MoonPayProtocol(walletAccount, { - apiKey: 'pk_live_xxxxx', - signUrl: async (urlForSignature) => { - const response = await fetch('/api/moonpay/sign-url', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ urlForSignature }), - }); +## Next steps - return (await response.json()).signedUrl; - }, - environment: 'production', -}); -``` +- [Configuration](/sdk/community-modules/wdk-wallet-rgb/configuration) +- [Transfer assets](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Back up, restore, and migrate](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) -## Widget Customization +*** -When calling `buy()` or `sell()`, you can customize the MoonPay widget appearance: +## Issue and receive RGB assets +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets +Description: Issue NIA assets and create blind or witness receive invoices with the on-chain RGB wallet. -```typescript -const result = await moonpay.buy({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - fiatAmount: 10000n, // $100.00 in cents - config: { - colorCode: '#3B82F6', // Your brand color (hex) - theme: 'dark', // 'dark' or 'light' - language: 'en', // ISO 639-1 language code - redirectURL: 'https://yourapp.com/callback', - }, -}); -``` +The v2.0.3 account declares Non-Inflatable Asset issuance and blind or witness receive invoices. -### Available Buy Widget Options + +Community modules are developed and maintained independently by third-party contributors. -| Option | Type | Description | -|--------|------|-------------| -| `colorCode` | string | Hexadecimal color for widget accent | -| `theme` | `'dark' \| 'light'` | Widget appearance theme | -| `themeId` | string | ID of a custom theme | -| `language` | string | ISO 639-1 language code | -| `showAllCurrencies` | boolean | Show all supported cryptocurrencies | -| `showOnlyCurrencies` | string | Comma-separated currency codes to display | -| `showWalletAddressForm` | boolean | Show wallet address input form | -| `redirectURL` | string | URL to redirect after completion | -| `unsupportedRegionRedirectUrl` | string | URL for unsupported regions | -| `skipUnsupportedRegionScreen` | boolean | Skip unsupported region screen | -| `defaultCurrencyCode` | string | Pre-selected cryptocurrency code | -| `walletAddress` | string | Pre-filled wallet address | -| `walletAddressTag` | string | Wallet address memo/tag (for EOS, XRP, etc.) | -| `walletAddresses` | string | JSON string of wallet addresses for multiple currencies | -| `walletAddressTags` | string | JSON string of address tags for multiple currencies | -| `contractAddress` | string | Token contract address (DeFi Buy only) | -| `networkCode` | string | Network for the token contract (DeFi Buy only) | -| `lockAmount` | boolean | Prevent user from changing amount | -| `email` | string | Pre-fill customer email | -| `externalTransactionId` | string | Your transaction identifier | -| `externalCustomerId` | string | Your customer identifier | -| `paymentMethod` | string | Pre-select payment method | +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -### Available Sell Widget Options +## Prepare the wallet -For `sell()`, the widget config uses `MoonPaySellParams` with different options: +Register, synchronize, and confirm that the wallet has suitable Bitcoin UTXOs: -| Option | Type | Description | -|--------|------|-------------| -| `colorCode` | string | Hexadecimal color for widget accent | -| `theme` | `'dark'` \| `'light'` | Widget appearance theme | -| `themeId` | string | ID of a custom theme | -| `language` | string | ISO 639-1 language code | -| `showAllCurrencies` | boolean | Show all supported cryptocurrencies | -| `showOnlyCurrencies` | string | Comma-separated currency codes to display | -| `showWalletAddressForm` | boolean | Show wallet address input form | -| `redirectURL` | string | URL to redirect after completion | -| `unsupportedRegionRedirectUrl` | string | URL for unsupported regions | -| `skipUnsupportedRegionScreen` | boolean | Skip unsupported region screen | -| `defaultBaseCurrencyCode` | string | Pre-selected cryptocurrency to sell | -| `refundWalletAddresses` | string | JSON string of wallet addresses for refunds | -| `lockAmount` | boolean | Prevent user from changing amount | -| `email` | string | Pre-fill customer email | -| `externalTransactionId` | string | Your transaction identifier | -| `externalCustomerId` | string | Your customer identifier | -| `paymentMethod` | string | Pre-select payout method | +```js +await account.registerWallet() +account.syncWallet() -## Next Steps +const unspents = account.listUnspents() +if (unspents.length === 0) { + throw new Error('Fund or create RGB-compatible UTXOs before continuing') +} +``` -- [Usage Guide](/sdk/fiat-modules/fiat-moonpay/usage) - Learn how to integrate MoonPay -- [API Reference](/sdk/fiat-modules/fiat-moonpay/api-reference) - Complete API documentation +## Issue an NIA -*** +```js +const issued = account.issueAssetNia({ + ticker: 'DEMO', + name: 'Demo Asset', + amounts: [1_000], + precision: 0, +}) -## Buy and Sell -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell -Description: On-ramp, off-ramp, quotes, supported assets, widget options, and custom recipients. +console.log(issued) +``` -This guide explains [buying crypto (on-ramp)](#buy-crypto-on-ramp), [selling crypto (off-ramp)](#sell-crypto-off-ramp), [quotes](#get-price-quotes), [supported currencies](#supported-currencies-and-countries), [widget customization](#widget-customization), and [custom recipients](#custom-recipient-addresses). It assumes a [`MoonPayProtocol`](/sdk/fiat-modules/fiat-moonpay/api-reference) instance named `moonpay`. +`amounts` contains issued allocations in asset base units. Validate ticker, name, precision, supply, and destination policy before issuance because issuance is a consequential state change. -Amounts use smallest units: fiat in minor units (cents), crypto in on-chain base units (for example wei for ETH). +The released WDK account declares only `issueAssetNia()`. Do not infer UDA, CFA, IFA, inflation, or atomic-swap support from another RGB repository or unreleased branch. -## Buy crypto (on-ramp) - -You can build a signed purchase URL with [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) when you know the fiat spend: +## Create a blind receive invoice -```typescript title="Buy with fiat amount" -const result = await moonpay.buy({ - cryptoAsset: 'usdt', - fiatCurrency: 'usd', - fiatAmount: 10000n +```js +const receive = account.receiveAsset({ + assetId, + amount: 100, + witness: false, }) -window.open(result.buyUrl, '_blank') +console.log(receive.invoice) ``` -You can request a fixed crypto amount instead by passing `cryptoAmount` to [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +A blind receive normally consumes an available allocation-capable UTXO. -```typescript title="Buy with crypto amount" -const result = await moonpay.buy({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 100000000000000000n -}) +## Create a witness receive invoice -window.open(result.buyUrl, '_blank') +```js +const receive = account.receiveAsset({ + assetId, + amount: 100, + witness: true, +}) ``` -## Sell crypto (off-ramp) +Witness receive uses an on-chain witness flow. Select the mode according to the recipient's wallet state, privacy model, and fee requirements. -You can generate a sell widget URL with [`sell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +## Handle invoices safely -```typescript title="Sell ETH for USD" -const result = await moonpay.sell({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 500000000000000000n -}) +- Generate the invoice on the receiving wallet. +- Transmit the complete invoice over an authenticated channel. +- Verify the asset ID and amount in your application. +- Treat the invoice as single-use. +- Do not log invoices with user-identifying metadata. +- Refresh transfer state before deciding whether an expired or timed-out receive failed. -window.open(result.sellUrl, '_blank') -``` +The sender needs the invoice string beginning with `rgb:`. An ordinary Bitcoin address is not an RGB transfer recipient. -## Get price quotes +## Next steps -You can preview economics before opening the widget using [`quoteBuy()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +- [Create and inspect UTXOs](/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos) +- [Transfer an asset](/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets) +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) -```typescript title="Buy quote" -const buyQuote = await moonpay.quoteBuy({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - fiatAmount: 10000n -}) +*** -console.log('Crypto amount:', buyQuote.cryptoAmount) -console.log('Fee:', buyQuote.fee) -console.log('Exchange rate:', buyQuote.rate) -``` +## Manage the RGB account and storage +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage +Description: Manage the single RGB account, derivation paths, durable local state, and read-only access. -You can estimate proceeds for a sell with [`quoteSell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +The on-chain RGB module owns one account and a local RGB database. Treat both limits as part of the wallet identity. -```typescript title="Sell quote" -const sellQuote = await moonpay.quoteSell({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 500000000000000000n -}) + +Community modules are developed and maintained independently by third-party contributors. -console.log('Fiat amount:', sellQuote.fiatAmount) -``` +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -## Supported currencies and countries +## Use account index 0 -You can list tradable assets with [`getSupportedCryptoAssets()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +```js +const account = await manager.getAccount(0) -```typescript title="Supported crypto" -const cryptoAssets = await moonpay.getSupportedCryptoAssets() -console.log(cryptoAssets) +console.log(account.index) // 0 +console.log(account.path) // BIP-86 vanilla path +console.log(account.coloredPath) // RGB colored path ``` -You can list fiat currencies with [`getSupportedFiatCurrencies()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +`manager.getAccount(1)` throws. `manager.getAccountByPath()` always throws. -```typescript title="Supported fiat" -const fiatCurrencies = await moonpay.getSupportedFiatCurrencies() -console.log(fiatCurrencies) -``` +| Network | `path` | `coloredPath` | +|---|---|---| +| `mainnet` | `m/86'/0'/0'` | `m/86'/827166'/0'` | +| `testnet` or `regtest` | `m/86'/1'/0'` | `m/86'/827167'/0'` | -You can check regional availability with [`getSupportedCountries()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +## Persist the local state -```typescript title="Supported countries" -const countries = await moonpay.getSupportedCountries() -console.log(countries) +Use a durable, app-private `dataDir`: + +```js +const manager = new WalletManagerRgb(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-onchain', +}) ``` -## Widget customization +The runtime falls back to temporary storage if the field is omitted. That fallback is unsuitable for a durable wallet. -You can pass UI options under `config` to [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) (see [`MoonPayBuyParams`](/sdk/fiat-modules/fiat-moonpay/api-reference)): +Protect the directory from: -```typescript title="Themed buy widget" -const result = await moonpay.buy({ - cryptoAsset: 'usdt', - fiatCurrency: 'eur', - fiatAmount: 5000n, - config: { - colorCode: '#1f2937', - theme: 'dark', - language: 'de', - redirectURL: 'https://yourapp.com/payment-complete', - lockAmount: true, - email: 'user@example.com', - externalCustomerId: 'user_123' - } -}) +- deletion by cache or temporary-file cleanup; +- concurrent access by multiple wallet instances; +- unencrypted device or cloud backups; +- reuse by [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning); +- accidental cross-network reuse. -window.open(result.buyUrl, '_blank') +The on-chain and Lightning RGB modules have different identities and databases. Give them separate paths even when they use the same BIP-39 mnemonic. + +## Register and synchronize + +```js +const { address, btcBalance } = await account.registerWallet() + +account.syncWallet() +account.refreshWallet() + +console.log({ address, btcBalance }) ``` -## Custom recipient addresses +`syncWallet()` synchronizes Bitcoin state. `refreshWallet()` refreshes RGB transfer state. The released runtime exposes both synchronously; failures can still propagate from native code. -By default [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) credits the connected wallet. You can override the destination with `recipient`: +## Create a read-only view -```typescript title="Custom buy recipient" -const result = await moonpay.buy({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - fiatAmount: 10000n, - recipient: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' -}) +```js +const readOnly = await account.toReadOnlyAccount() -window.open(result.buyUrl, '_blank') +const btc = await readOnly.getBalance() +const rgb = await readOnly.getTokenBalance(assetId) ``` -You can set a refund destination on sells with `refundAddress` on [`sell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +The read-only account can query balances and receipts. It cannot quote transactions or transfers, and v2.0.3 does not implement message verification on the read-only class. -```typescript title="Custom sell refund address" -const result = await moonpay.sell({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 500000000000000000n, - refundAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' -}) +## Dispose at the owner boundary -window.open(result.sellUrl, '_blank') +```js +try { + const account = await manager.getAccount(0) + // Use the account. +} finally { + manager.dispose() +} ``` -## Next Steps +The manager disposes cached accounts and clears manager-owned derived-key fields. Disposal cannot erase mnemonic or key copies retained by application code or dependencies. -- [Manage transactions](manage-transactions) -- [Get started](get-started) -- [API reference](/sdk/fiat-modules/fiat-moonpay/api-reference) +## Next steps + +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) +- [Back up and restore](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) *** -## Get Started -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/guides/get-started -Description: Install the package and initialize MoonPayProtocol with your wallet and keys. +## Sign and verify messages with the RGB wallet +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/sign-verify-messages +Description: Sign and verify application messages with the full on-chain RGB account. -This guide covers [installation](#installation) and [initializing the protocol](#initialize-moonpayprotocol). You need [Node.js](https://nodejs.org/), [npm](https://www.npmjs.com/), and MoonPay API keys from your MoonPay dashboard. +The full v2.0.3 account exposes Bitcoin message signing and verification through the underlying RGB wallet. -## Installation + +Community modules are developed and maintained independently by third-party contributors. -Run the following to install [@tetherto/wdk-protocol-fiat-moonpay](https://www.npmjs.com/package/@tetherto/wdk-protocol-fiat-moonpay): +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + -```bash title="Install with npm" -npm install @tetherto/wdk-protocol-fiat-moonpay +## Sign a domain-separated message + +```js +const message = [ + 'example-wallet-auth', + 'version=1', + `origin=${expectedOrigin}`, + `nonce=${serverNonce}`, + `expires=${expiresAt}`, +].join('\n') + +const signature = await account.sign(message) ``` -## Initialize MoonPayProtocol +Include an application name, purpose, origin, nonce, expiry, and version. Do not ask users to sign opaque or transaction-like data. -You can create a fiat ramp client with [`new MoonPayProtocol(account, config)`](/sdk/fiat-modules/fiat-moonpay/api-reference): +## Verify with the full account -```typescript title="Construct MoonPayProtocol" -import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay' +```js +const valid = await account.verify(message, signature) -const moonpay = new MoonPayProtocol(walletAccount, { - apiKey: process.env.MOONPAY_PUBLISHABLE_KEY, - secretKey: process.env.MOONPAY_SECRET_KEY -}) +if (!valid) { + throw new Error('Invalid RGB wallet message signature') +} +``` + +Verify the exact bytes and application context that were presented to the signer. Reject reused nonces and expired challenges at the application boundary. + +## Read-only limitation + +```js +const readOnly = await account.toReadOnlyAccount() ``` +The released `WalletAccountReadOnlyRgb` does not implement `verify()`. Do not copy an API claim from a later commit or another wallet module. If verification must run without the live full account, use a separately reviewed verifier with the correct public key, signature format, and domain rules. + +## Protect key material + +- Do not log `account.keyPair`, signatures attached to sensitive challenges, or seed material. +- Keep challenge generation server-side when using signatures for authentication. +- Bind signatures to one origin and one intended action. +- Call `manager.dispose()` when the wallet session ends. +- Remember that disposal cannot erase key copies retained by application code. + +Message signing does not authorize a Bitcoin or RGB transfer unless your application explicitly gives the signed message that meaning. Keep authentication and transaction approval domains separate. + +## Next steps + +- [Manage account storage](/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) + +*** + +## Transfer RGB assets +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets +Description: Quote and send an on-chain RGB asset to a recipient-generated invoice. + +An RGB transfer is invoice-driven: the recipient generates an invoice and the sender funds, signs, and broadcasts the transfer. + -Never ship a secret key to browsers. Run server-side signing where your architecture allows, and rotate keys if they leak. +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. -See [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) for `cacheTime` and related options. +## 1. Obtain a recipient invoice -## Next Steps +On the receiving wallet: -- [Buy and sell](buy-and-sell) -- [Manage transactions](manage-transactions) +```js +const receive = recipientAccount.receiveAsset({ + assetId, + amount: 100, + witness: false, +}) -*** +const rgbInvoice = receive.invoice +``` -## Manage Transactions -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions -Description: Poll MoonPay for transaction status and inspect returned details. +Transfer the invoice over an authenticated channel. The sender should validate that it begins with `rgb:` and that the intended asset and amount match the user-confirmed action. -This guide shows how to [check transaction status](#check-transaction-status) and [read transaction details](#read-transaction-details) with [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference). Pass the identifier MoonPay returns after checkout (for example from your redirect URL or webhook payload). +## 2. Build the transfer -## Check transaction status +```js +const transfer = { + recipient: rgbInvoice, + token: assetId, + amount: 100n, + feeRate: 2, + minConfirmations: 1, +} +``` -You can read the high-level state of a buy with [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +`amount` is in asset base units. `feeRate` is the Bitcoin fee rate in sat/vbyte. -```typescript title="Buy transaction status" -const buyTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'buy') +## 3. Quote and enforce policy -console.log('Status:', buyTx.status) +```js +const maximumFee = 2_000n +const quote = await senderAccount.quoteTransfer(transfer) + +if (quote.fee > maximumFee) { + throw new Error('Quoted RGB transfer fee exceeds the application limit') +} ``` -`status` is one of `completed`, `failed`, or `in_progress` as described in the API reference. +`quoteTransfer()` creates and signs a transfer PSBT to estimate its fee. It is not a pure arithmetic call, and changing wallet state or fee inputs after the quote can invalidate the result. -## Read transaction details + +Although `transferMaxFee` exists in the v2.0.3 config source, `WalletManagerRgb.getAccount()` does not forward it to the account. Enforce an application-owned limit on every transfer instead of relying on that option. + -You can load the same record to inspect assets and currencies using [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +## 4. Send once -```typescript title="Buy transaction fields" -const buyTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'buy') +```js +const result = await senderAccount.transfer(transfer) -console.log('Crypto asset:', buyTx.cryptoAsset) -console.log('Fiat currency:', buyTx.fiatCurrency) -console.log('Metadata:', buyTx.metadata) +console.log({ + txid: result.hash, + estimatedFee: result.fee.toString(), +}) ``` -You can query a sell the same way by passing `sell` as the direction to [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +The high-level method runs `sendBegin → signPsbt → sendEnd`. The release also exposes those primitives for advanced PSBT orchestration; keep their exact argument naming pinned to v2.0.3 and the included `@utexo/rgb-sdk`. -```typescript title="Sell transaction details" -const sellTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'sell') +## 5. Reconcile state -console.log('Status:', sellTx.status) -console.log('Crypto asset:', sellTx.cryptoAsset) -console.log('Fiat currency:', sellTx.fiatCurrency) +```js +senderAccount.refreshWallet() + +const transfers = senderAccount.listTransfers(assetId) +const receipt = await senderAccount.getTransferReceipt(result.hash) ``` - -The second argument defaults to `buy` when omitted; set it explicitly for sell flows. - +Do not resend automatically after a timeout. The write may have reached the transport endpoint or Bitcoin network even when the caller did not receive a success response. -## Next Steps +## Operational cautions -- [Buy and sell](buy-and-sell) -- [Get started](get-started) -- [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) +- Treat each recipient invoice as single-use. +- Do not substitute a Bitcoin address for the `rgb:` invoice. +- Confirm the asset ID, asset precision, base-unit amount, and network. +- Ensure suitable RGB allocations and Bitcoin UTXOs exist before quoting. +- Preserve the sender and recipient `dataDir` state until settlement is reconciled. +- `getTransfers()` hides native errors as `[]`; use `listTransfers()` when failure visibility matters. + +## Next steps + +- [Read balances and history](/sdk/community-modules/wdk-wallet-rgb/guides/balances-history) +- [Back up and restore](/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate) +- [Handle errors](/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors) *** -## Fiat MoonPay Usage -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/usage -Description: How to use the @tetherto/wdk-protocol-fiat-moonpay module +## RGB wallet usage +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-rgb/usage +Description: Task-focused guides for the released @utexo/wdk-wallet-rgb 2.0.3 community module. -# Usage +Use these guides for the single-account, on-chain RGB wallet in `@utexo/wdk-wallet-rgb@2.0.3`. -The [@tetherto/wdk-protocol-fiat-moonpay](https://www.npmjs.com/package/@tetherto/wdk-protocol-fiat-moonpay) module builds signed MoonPay widget URLs and quotes for on-ramp and off-ramp flows. Use the guides below for setup, trading, and transaction follow-up. + +Community modules are developed and maintained independently by third-party contributors. +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + - -Install the package and initialize MoonPayProtocol. + +Install the package and open the index-0 account. - -On-ramp, off-ramp, quotes, supported assets, widget options, recipients. + +Persist local RGB state and understand the account and derivation boundaries. - -Check status and load transaction details from MoonPay. + +Read Bitcoin and RGB balances, transactions, transfers, and receipts. - - - - - -Get started with WDK in a Node.js environment + +Send Bitcoin and create the UTXOs needed by RGB workflows. - -API keys, caching, and MoonPay configuration options + +Issue NIA assets and create blind or witness invoices. - -Constructor, methods, and types for MoonPayProtocol + +Quote and send RGB assets to a recipient-generated invoice. + + +Protect local state and move from the legacy remote-node architecture. + + +Use the full account's message-signing surface. + + +Handle ambiguous history, fee, network, and cleanup failures. - - -*** - -## Get Started -URL: https://docs.wdk.tether.io/sdk/get-started -Description: Learn about the SDK and modules architecture - -The SDK is a comprehensive, modular plug-in framework designed to simplify multi-chain wallet development. - -It is built on some core principles: **self-custodial and stateless** (private keys never leave your app and no data is stored by WDK), **unified interface** (consistent API across all blockchains), and **cross-platform compatibility** (works seamlessly from Node.js to React Native to embedded systems). - -#### Capabilities +## Reference -* **Multi-Chain Support**: Bitcoin, Ethereum, TON, TRON, Solana, Spark, and more -* **Account Abstraction**: Gasless transactions on supported chains -* **DeFi Integration**: Plug-in support for swidge routes, swaps, bridges, and lending protocols -* **Extensible Design**: Add custom modules for new blockchains or protocols +- [Configuration](/sdk/community-modules/wdk-wallet-rgb/configuration) +- [API reference](/sdk/community-modules/wdk-wallet-rgb/api-reference) +- [v2.0.3 release](https://github.com/UTEXO-Protocol/wdk-wallet-rgb/releases/tag/v2.0.3) *** -### Modular Architecture - -WDK's architecture is built around the concept of composable modules. Each module is a specialized component that handles specific functionality, allowing you to build exactly what you need without unnecessary complexity. - - -Each module has a single responsibility. Wallet modules handle blockchain operations, protocol modules manage DeFi interactions, and the core module orchestrates everything. +## WDK Core +URL: https://docs.wdk.tether.io/sdk/core-module +Description: Register wallet, protocol, middleware, and transaction policy modules through the WDK core runtime. -New functionality is added through modules rather than modifying core code. Also, modules are configured through simple objects, making them easy to customize for different environments and use cases. +WDK Core is the main runtime for registering and managing wallet, protocol, middleware, and transaction policy modules through one interface. -*** +Use WDK Core to: -#### Module Types +- Register wallet managers for the chains your app supports. +- Attach protocol providers to accounts globally or per account. +- Decorate accounts with middleware before they reach the application. +- Register local transaction policies that allow, deny, or simulate write operations before they execute. -WDK modules are organized into six main categories, each serving a specific purpose in the blockchain application stack: +## Next Steps - -Main orchestrator and shared utilities + +Get started with WDK in a Node.js environment - -Blockchain-specific wallet operations + +Get started with WDK's configuration - -Swap-only, bridge-only, or combined asset routes + +Get started with WDK's API - -Token swapping across DEXs + +Get started with WDK's usage - -Cross-chain asset transfers + +Learn what `dispose()` clears and how to clean up app-owned seed buffers. - -DeFi lending and borrowing + +Allow, deny, and simulate local write operations before execution *** -### How to use the SDK +## Need Help? -The WDK SDK uses a registration-based system where modules are added to a central orchestrator. This creates a unified interface while maintaining module independence. + -#### Registration Flow +*** -**1. Core Module Initialization** +## WDK Core API Reference +URL: https://docs.wdk.tether.io/sdk/core-module/api-reference +Description: Complete API documentation for @tetherto/wdk -```typescript title="Initialize WDK" -import WDK from '@tetherto/wdk' +## Table of Contents -// Generate 24-word seed phrase for higher security -const seedPhrase = WDK.getRandomSeedPhrase(24) +| Class | Description | Methods | +|-------|-------------|---------| +| [WDK](#wdk) | Main class for managing wallets across multiple blockchains. Orchestrates wallet managers, protocols, middleware, and local transaction policies. | [Constructor](#constructor), [Methods](#methods) | +| [IWalletAccount](#iwalletaccount) | Base writable wallet account interface from `@tetherto/wdk-wallet`. | [Methods](#methods-1) | +| [IWalletAccountWithProtocols](#iwalletaccountwithprotocols) | Protocol registration and access surface added to a wallet account. | [Methods](#methods-2) | +| [WdkAccount](#wdkaccount) | Consumer-facing account type returned by `getAccount()` and `getAccountByPath()`. | Type alias | -// Or use 12-word seed phrase (default) -// const seedPhrase = WDK.getRandomSeedPhrase() +## WDK -const wdk = new WDK(seedPhrase) +The main class for managing wallets across multiple blockchains. This class serves as an orchestrator that allows you to register different wallet managers and protocols, providing a unified interface for multi-chain operations. + +### Constructor + +```javascript title="Constructor" +new WDK(seed) ``` -**2. Wallet Module Registration** +**Parameters:** +- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes -```typescript title="Register Wallets" -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerBtc from '@tetherto/wdk-wallet-btc' +**Example:** +```javascript title="Initialize WDK" +import WDK from '@tetherto/wdk' -const wdkWithWallets = wdk - .registerWallet('ethereum', WalletManagerEvm, { - provider: 'https://eth.drpc.org' - }) - .registerWallet('bitcoin', WalletManagerBtc, { - provider: 'https://blockstream.info/api' - }) +// With seed phrase +const wdk = new WDK('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about') + +// With seed bytes +const seedBytes = new Uint8Array([...]) +const wdk2 = new WDK(seedBytes) ``` -**3. Protocol Module Registration** +### Methods -```typescript title="Register Protocols" -import SwapveloraEvm from '@tetherto/wdk-protocol-swap-velora-evm' +| Method | Description | Returns | Throws | +|--------|-------------|---------|--------| +| `registerWallet(blockchain, wallet, config)` | Registers a new wallet manager for a blockchain | `WDK` | If a wallet is already registered for that blockchain | +| `registerProtocol(blockchain, label, protocol, config)` | Registers a protocol globally for a blockchain | `WDK` | - | +| `registerMiddleware(blockchain, middleware)` | Registers middleware for account decoration | `WDK` | - | +| `registerPolicy(policies, options?)` | Registers local transaction policies for wallet account and protocol write methods | `WDK` | If policy configuration is invalid | +| `getAccount(blockchain, index?)` | Returns a wallet account for a blockchain and index | `Promise` | If wallet not registered | +| `getAccountByPath(blockchain, path)` | Returns a wallet account for a blockchain and derivation path | `Promise` | If wallet not registered | +| `getFeeRates(blockchain)` | Returns current fee rates for a registered blockchain | `Promise` | If wallet not registered | +| `dispose(blockchains?)` | Disposes all registered wallets, or only the named blockchains, and clears keys and account state managed by WDK | `void` | - | -const wdkWithProtocols = wdkWithWallets - .registerProtocol('swap-velora-evm', SwapveloraEvm) -``` +##### `registerWallet(blockchain, wallet, config)` +Registers a new wallet manager for a specific blockchain. -#### Unified Operations +**Type Parameters:** +- `W`: `typeof WalletManager` - A class that extends the `@tetherto/wdk-wallet`'s `WalletManager` class -Once registered, all modules work through the same interface: +**Parameters:** +- `blockchain` (string): The name of the blockchain (e.g., "ethereum", "ton", "bitcoin") +- `wallet` (W): The wallet manager class +- `config` (`ConstructorParameters[1]`): The configuration object for the wallet -```typescript title="Unified Operations" -// Get accounts from different blockchains using the same method -const ethAccount = await wdkWithProtocols.getAccount('ethereum', 0) -const btcAccount = await wdkWithProtocols.getAccount('bitcoin', 0) +**Returns:** `WDK` - The WDK instance (supports method chaining) -// Check balances using unified interface -const ethBalance = await ethAccount.getBalance() -const btcBalance = await btcAccount.getBalance() +**Throws:** Error if a wallet is already registered for the same blockchain. Call `dispose([blockchain])` before registering a replacement wallet for that blockchain. -// Send transactions with consistent API -const ethTx = await ethAccount.sendTransaction({ - to: '0x...', - value: '1000000000000000000' -}) +**Example:** +```javascript title="Register Wallets" +import WDK from '@tetherto/wdk' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import WalletManagerTon from '@tetherto/wdk-wallet-ton' -const btcTx = await btcAccount.sendTransaction({ - to: '1A1z...', - value: 100000000 +const wdk = new WDK(seedPhrase) + +// Register EVM wallet +wdk.registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' }) -// Use DeFi protocols through the same interface -const swapResult = await wdkWithProtocols.executeProtocol('swap-velora-evm', { - fromToken: 'ETH', - toToken: 'USDT', - amount: '1000000000000000000' +// Register TON wallet +wdk.registerWallet('ton', WalletManagerTon, { + tonApiKey: 'YOUR_TON_API_KEY', + tonApiEndpoint: 'https://tonapi.io' }) + +// Method chaining +const wdk2 = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerWallet('ton', WalletManagerTon, tonWalletConfig) ``` -*** +##### `registerProtocol(blockchain, label, protocol, config)` +Registers a protocol globally for all accounts of a specific blockchain. -### Creating Custom Modules +For swidge or Smart Deposit Address (SDA) integrations, pass a concrete provider class that extends the corresponding base class from `@tetherto/wdk-wallet/protocols`. -WDK's modular architecture makes it straightforward to add support for new blockchains or protocols. Each module type has a specific interface that must be implemented. +**Type Parameters:** +- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol | typeof FiatProtocol | typeof SwidgeProtocol | typeof SdaProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocols` classes -#### Wallet Module Interface +**Parameters:** +- `blockchain` (string): The name of the blockchain +- `label` (string): Registry label for the protocol. Registering the same blockchain, protocol type, and label again replaces the previous global registration. +- `protocol` (P): The protocol class +- `config` (`ConstructorParameters

[1]`): The protocol configuration -```typescript title="Custom Wallet Module Setup" -interface WalletModule { - // Account management - getAccount(index: number): Promise - getAddress(index: number): Promise - getBalance(index: number): Promise +**Returns:** `WDK` - The WDK instance (supports method chaining) - // Transaction operations - sendTransaction(params: TransactionParams): Promise - estimateTransaction(params: TransactionParams): Promise +Global registration stores the provider class and config without constructing or validating the provider. Provider-constructor errors therefore surface when an account retrieves the protocol. A global registration takes precedence over an account-scoped registration with the same type and label. - // Key management - signMessage(message: string, index: number): Promise - verifySignature(message: string, signature: string, address: string): Promise +**Example:** +```javascript title="Register Protocols" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' - // Blockchain-specific operations - getTransactionHistory(index: number, limit?: number): Promise - getTokenBalance(index: number, tokenAddress: string): Promise -} -``` +// Register swap protocol for Ethereum +wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, { + apiKey: 'YOUR_velora_API_KEY' +}) -#### Protocol Module Interface +// Register bridge protocol for Ethereum +wdk.registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) -```typescript title="Custom Protocol Module Setup" -interface ProtocolModule { - // Protocol execution - execute(params: ProtocolParams): Promise - estimate(params: ProtocolParams): Promise +// Register a concrete swidge provider for Ethereum +wdk.registerProtocol('ethereum', 'swidge', MySwidgeProtocol, swidgeProtocolConfig) - // Supported operations - getSupportedTokens(): Promise - getSupportedChains(): Promise - getOperationTypes(): Promise +// Register an illustrative SDA provider for Ethereum +wdk.registerProtocol('ethereum', 'deposits', MySdaProtocol, sdaProtocolConfig) - // Protocol-specific methods - getLiquidityPools?(): Promise - getLendingRates?(): Promise - getBridgeRoutes?(): Promise -} +// Method chaining +const wdk2 = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) ``` -#### Module Implementation Example - -```typescript title="Custom Wallet Module Implementation" -class CustomWalletModule implements WalletModule { - private provider: string - private chainId: number +##### `registerMiddleware(blockchain, middleware)` +Registers middleware for account decoration and enhanced functionality. - constructor(config: { provider: string; chainId: number }) { - this.provider = config.provider - this.chainId = config.chainId - } +**Parameters:** +- `blockchain` (string): The name of the blockchain +- `middleware` (`(account: A) => Promise`): Middleware function called when deriving accounts - async getAccount(index: number): Promise { - // Implement account derivation logic - const privateKey = await this.derivePrivateKey(index) - return new CustomAccount(privateKey, this.provider) - } +**Returns:** `WDK` - The WDK instance (supports method chaining) - async getAddress(index: number): Promise { - const account = await this.getAccount(index) - return account.getAddress() - } +**Example:** +```javascript title="Register Middleware" +// Simple logging middleware +wdk.registerMiddleware('ethereum', async (account) => { + console.log('New account:', await account.getAddress()) +}) - async getBalance(index: number): Promise { - const address = await this.getAddress(index) - // Implement balance fetching logic - const balance = await this.fetchBalance(address) - return new BigNumber(balance) - } +// Failover cascade middleware +import { getFailoverCascadeMiddleware } from '@tetherto/wdk-wrapper-failover-cascade' - async sendTransaction(params: TransactionParams): Promise { - // Implement transaction sending logic - const account = await this.getAccount(params.accountIndex) - const tx = await account.sendTransaction(params) - return tx +wdk.registerMiddleware('ethereum', getFailoverCascadeMiddleware({ + fallbackOptions: { + retries: 3, + delay: 1000 } +})) - // Additional methods... -} +// Method chaining +const wdk2 = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerMiddleware('ethereum', async (account) => { + console.log('New account:', await account.getAddress()) + }) ``` -#### Module Registration +##### `registerPolicy(policies, options?)` +Registers one or more local transaction policies on the WDK instance. -```typescript title="Custom Wallet Module Registration" -// Register your custom module -const wdkWithCustom = wdk.registerWallet('custom-chain', CustomWalletModule, { - provider: 'https://custom-rpc-endpoint.com', - chainId: 12345 -}) +Policies are evaluated before wrapped account and protocol write methods execute. Matching `DENY` rules and governed-account default-deny outcomes throw `PolicyViolationError`; matching `ALLOW` rules permit the call only when no higher-priority `DENY` rule applies. Governed runtime accounts also expose `account.simulate.(...)` mirrors so you can dry-run policy evaluation without sending, signing, or broadcasting. -// Use it like any other module -const customAccount = await wdkWithCustom.getAccount('custom-chain', 0) -const balance = await customAccount.getBalance() -``` +Evaluation order: +1. Account-scoped policies run before project-scoped policies. +2. Policies and rules run in registration order within their scope. +3. A matching account-scoped `DENY` blocks immediately. +4. A matching account-scoped `ALLOW` with `override_broader_scope: true` allows immediately and skips project-scoped policies. +5. Project-scoped `DENY` rules block after account-scoped rules unless an override allow already matched. +6. If no `DENY` matches and at least one `ALLOW` matched, WDK allows the call. +7. Governed wrapped operations deny by default when no rule addresses the operation or no addressed rule matches. -*** +For policy scoping, default-deny behavior, account-level overrides, and protocol simulation examples, see [Transaction Policies](/sdk/core-module/guides/transaction-policies). -### Quickstart Paths +**Parameters:** +- `policies` (`Policy | Policy[]`): A single policy or an array of policies to register. +- `options` (`RegisterPolicyOptions`, optional): Engine-level settings. `conditionTimeoutMs` defaults to `30000` milliseconds; the most recent value wins. -Ready to start building? Choose your development environment: +**Returns:** `WDK` - The WDK instance (supports method chaining) - - -Get started with WDK in a Node.js environment - - -Build mobile wallets with React Native Expo - - - -*** - -## Need Help? - - - -*** - -## Lending Modules Overview -URL: https://docs.wdk.tether.io/sdk/lending-modules -Description: Explore WDK lending modules for integrating lending protocols with WDK. +**Throws:** `PolicyConfigurationError` if a policy or option fails validation, if an account-scoped policy omits its account binding, or if a policy references a wallet identifier that has not been registered. Governed write calls also throw `PolicyConfigurationError` when WDK cannot snapshot a method argument safely. -The Wallet Development Kit (WDK) provides a set of modules that support connection with lending protocols on different blockchain networks. All modules share a common interface, ensuring consistent behavior across different blockchain implementations. +**Example:** +```typescript title="Register A Send Policy" +import WDK, { PolicyViolationError } from '@tetherto/wdk' -## Lending & Borrowing Protocol Modules +const wdk = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerPolicy({ + id: 'eth-send-limit', + name: 'ETH send limit', + scope: 'project', + wallet: 'ethereum', + rules: [ + { + name: 'allow-normal-operations', + operation: '*', + action: 'ALLOW', + reason: 'Default local approval', + conditions: [() => true] + }, + { + name: 'block-large-send', + operation: 'sendTransaction', + action: 'DENY', + reason: 'Transaction value exceeds local policy', + conditions: [ + ({ params }) => { + const value = (params as { value?: bigint } | null)?.value + return typeof value === 'bigint' && value > 1000000000000000000n + } + ] + } + ] + }) -DeFi lending functionality for different lending & borrowing protocols +const account = await wdk.getAccount('ethereum', 0) -| Module | Route | Status | Documentation | -|--------|-------|--------|---------------| -| [`@tetherto/wdk-protocol-lending-aave-evm`](https://github.com/tetherto/wdk-protocol-lending-aave-evm) | EVM | ✅ Ready | [Documentation](/sdk/lending-modules/lending-aave-evm/) | -| [`@morpho-org/wdk-protocol-lending-morpho-evm`](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) | EVM | Community | [Documentation](/sdk/lending-modules/lending-morpho-evm/) | +const simulation = await (account as any).simulate.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000000n +}) -## Next Steps +if (simulation.decision === 'DENY') { + console.warn(simulation.reason) +} -Compare the available EVM lending modules and open the implementation that matches your protocol target: +try { + await account.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000000n + }) +} catch (error) { + if (error instanceof PolicyViolationError) { + console.error(error.reason) + } +} +``` - - -Use the Tether-maintained Aave V3 lending module for EVM accounts. - - -Use the community Morpho module for Vault V2 and Morpho Blue EVM flows. - - -Install the Morpho module, create the client, and review prerequisites. - - +Supported `PolicyOperation` values are `sendTransaction`, `signTransaction`, `transfer`, `approve`, `sign`, `signTypedData`, `signAuthorization`, `delegate`, `revokeDelegation`, `swap`, `bridge`, `supply`, `withdraw`, `borrow`, `repay`, `buy`, `sell`, `swidge`, `createDepositAddress`, `renewDepositAddress`, `recoverDepositAddress`, `disableDepositAddress`, and `*`. -*** +Use `sign` for message-style signing in this release. `signMessage` and `signHash` are not valid policy operation names. -## Need Help? +Policy-enforced calls snapshot method arguments before evaluation and forward the same approved values to the wallet method. Pass structured-cloneable values such as primitives, plain objects, arrays, `bigint`, and typed arrays. Non-cloneable governed arguments fail closed with `PolicyConfigurationError`. - +##### `getAccount(blockchain, index?)` +Returns a wallet account for a specific blockchain and index using BIP-44 derivation. -*** +**Parameters:** +- `blockchain` (string): The name of the blockchain (e.g., "ethereum") +- `index` (number, optional): The index of the account to get (default: 0) -## Lending Aave EVM Overview -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm -Description: Overview of the @tetherto/wdk-protocol-lending-aave-evm module +**Returns:** `Promise` - The writable wallet account with protocol support. When a registered policy targets the account, the returned runtime account is policy-enforced and exposes matching `simulate` helpers. -A lightweight package that lets EVM wallet accounts interact with Aave V3: supply, withdraw, borrow, repay, and read account data. It works with both standard EVM wallets and ERC‑4337 smart accounts. +**Throws:** Error if no wallet has been registered for the given blockchain. Throws `PolicyConfigurationError` if a registered policy applies but the wallet account does not expose a read-only account view. -## Features +**Example:** +```javascript title="Get Account" +// Get first account (index 0) +const account = await wdk.getAccount('ethereum', 0) -- **Supply/Withdraw**: Add and remove supported assets from Aave pools -- **Borrow/Repay**: Borrow assets and repay debt -- **Account Data**: Read collateral, debt, health factor, and more -- **Quote System**: Estimate fees before sending transactions -- **AA Support**: Works with standard EVM and ERC‑4337 smart accounts -- **TypeScript Support**: Full TypeScript definitions +// Get second account (index 1) +const account1 = await wdk.getAccount('ethereum', 1) -## Supported Networks +// Default index (0) +const defaultAccount = await wdk.getAccount('ethereum') -Works on Aave V3 supported EVM networks (e.g., Ethereum, Arbitrum, Base, Optimism, Polygon, Avalanche, BNB, Celo, Gnosis, Linea, Scroll, Soneium, Sonic, ZkSync, Metis). A working RPC provider and correct token addresses are required. +// This will throw an error if no wallet registered for 'tron' +try { + const tronAccount = await wdk.getAccount('tron', 0) +} catch (error) { + console.error('No wallet registered for tron blockchain') +} +``` -## Wallet Compatibility +##### `getAccountByPath(blockchain, path)` +Returns a wallet account for a specific blockchain and BIP-44 derivation path. -- **Standard EVM Wallets**: `@tetherto/wdk-wallet-evm` -- **ERC‑4337 Smart Accounts**: `@tetherto/wdk-wallet-evm-erc-4337` -- **Read‑Only Accounts**: For quoting and reading account data without sending transactions +**Parameters:** +- `blockchain` (string): The name of the blockchain (e.g., "ethereum") +- `path` (string): The derivation path (e.g., "0'/0/0") -## Key Components +**Returns:** `Promise` - The writable wallet account with protocol support. When a registered policy targets the account, the returned runtime account is policy-enforced and exposes matching `simulate` helpers. -- **Aave V3 Integration**: Supply, withdraw, borrow, repay primitives -- **Quote Helpers**: `quoteSupply`, `quoteWithdraw`, `quoteBorrow`, `quoteRepay` -- **Collateral Controls**: Toggle collateral usage; set user eMode +**Throws:** Error if no wallet has been registered for the given blockchain. Throws `PolicyConfigurationError` if a registered policy applies but the wallet account does not expose a read-only account view. -## Next Steps +**Example:** +```javascript title="Get Account by Path" +// Full path: m/44'/60'/0'/0/1 +const account = await wdk.getAccountByPath('ethereum', "0'/0/1") - - -How to supply, withdraw, borrow and repay with Aave - - -Service setup, account config, ERC‑4337 options - - -Full API for Aave Protocol Evm methods and types - - +// Different derivation path +const customAccount = await wdk.getAccountByPath('ton', "1'/2/3") +``` -*** +##### `getFeeRates(blockchain)` +Returns current fee rates for a registered blockchain. -## Lending Aave EVM API Reference -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/api-reference -Description: API Reference for @tetherto/wdk-protocol-lending-aave-evm +**Parameters:** +- `blockchain` (string): The blockchain identifier passed to `registerWallet()` -# API Reference +**Returns:** `Promise` - The fee rates in base units -## Class: AaveProtocolEvm +**Throws:** Error if no wallet has been registered for the given blockchain. -Main class for Aave V3 lending on EVM. +**Example:** +```javascript title="Get Fee Rates" +const feeRates = await wdk.getFeeRates('ethereum') +console.log('Fee rates:', feeRates) +``` -### Constructor +##### `dispose(blockchains?)` +Disposes all registered wallets when called without arguments, or only the wallets for the named blockchains when you pass a string array. -```javascript -new AaveProtocolEvm(account) -``` +This clears keys and account state managed by WDK, including private keys held by registered wallets. It does not mutate or zero the seed value passed to `new WDK(seed)`. See [Seed Lifecycle](/sdk/core-module/guides/error-handling#seed-lifecycle) for the recommended cleanup pattern. -Parameters: -- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` +**Parameters:** +- `blockchains` (string[], optional): The blockchain identifiers to dispose. Omit this parameter to dispose every registered wallet. -Example: +**Example:** +```javascript title="Dispose WDK" +// Dispose all registered wallets +wdk.dispose() -```javascript -const aave = new AaveProtocolEvm(account) +// Dispose only one registered wallet +wdk.dispose(['ethereum']) ``` -### Methods +### Static Methods | Method | Description | Returns | |--------|-------------|---------| -| `supply(options, config?)` | Add tokens to the pool | `Promise<{hash: string, fee: bigint, approveHash?: string, resetAllowanceHash?: string}>` | -| `quoteSupply(options, config?)` | Estimate cost to add tokens | `Promise<{fee: bigint}>` | -| `withdraw(options, config?)` | Remove tokens from the pool | `Promise<{hash: string, fee: bigint}>` | -| `quoteWithdraw(options, config?)` | Estimate cost to withdraw | `Promise<{fee: bigint}>` | -| `borrow(options, config?)` | Borrow tokens | `Promise<{hash: string, fee: bigint}>` | -| `quoteBorrow(options, config?)` | Estimate borrowing cost | `Promise<{fee: bigint}>` | -| `repay(options, config?)` | Repay borrowed tokens | `Promise<{hash: string, fee: bigint}>` | -| `quoteRepay(options, config?)` | Estimate repayment cost | `Promise<{fee: bigint}>` | -| `setUseReserveAsCollateral(token, use, config?)` | Toggle token as collateral | `Promise<{hash: string, fee: bigint}>` | -| `setUserEMode(categoryId, config?)` | Set user eMode | `Promise<{hash: string, fee: bigint}>` | -| `getAccountData(account?)` | Read account stats | `Promise<{ totalCollateralBase: bigint, totalDebtBase: bigint, availableBorrowsBase: bigint, currentLiquidationThreshold: bigint, ltv: bigint, healthFactor: bigint }>` | - ---- - -When `AaveProtocolEvm` is initialized with an ERC‑4337 smart account, the optional `config` argument on mutating and quote methods accepts the same gas-payment override families documented in [`@tetherto/wdk-wallet-evm-erc-4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): paymaster token, sponsorship policy, and native coins. +| `getRandomSeedPhrase(wordCount?)` | Returns a random BIP-39 seed phrase (12 or 24 words) | `string` | +| `isValidSeed(seed)` | Checks if a seed phrase or seed bytes value is valid | `boolean` | -### `supply(options, config?)` -Add tokens to the pool. +##### `getRandomSeedPhrase(wordCount?)` +Returns a random BIP-39 seed phrase. Supports both 12-word (128-bit entropy) and 24-word (256-bit entropy) seed phrases. -Options: -- `token` (`string`): token address -- `amount` (`number | bigint`): amount in base units -- `onBehalfOf` (`string`, optional) +**Parameters:** +- `wordCount` (12 | 24, optional): The number of words in the seed phrase. Defaults to 12. -Returns: -- May include `approveHash` and `resetAllowanceHash` for standard accounts (e.g., USD₮ allowance reset on Ethereum mainnet) +**Returns:** `string` - The seed phrase -Example: +**Example:** +```javascript title="Generate Random Seed" +// Generate 12-word seed phrase (default) +const seedPhrase12 = WDK.getRandomSeedPhrase() +console.log('Generated 12-word seed:', seedPhrase12) +// Output: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" -```javascript -const res = await aave.supply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +// Generate 24-word seed phrase (higher security) +const seedPhrase24 = WDK.getRandomSeedPhrase(24) +console.log('Generated 24-word seed:', seedPhrase24) +// Output: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art" ``` ---- - -### `quoteSupply(options, config?)` -Estimate fee to add tokens. - -```javascript -const q = await aave.quoteSupply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +##### `isValidSeed(seed)` +Checks if a seed phrase or seed bytes value is valid. ---- +**Parameters:** +- `seed` (string | Uint8Array): The seed phrase or seed bytes to validate -### `withdraw(options, config?)` -Remove tokens from the pool. +**Returns:** `boolean` - True if the seed is valid -Options: -- `token` (`string`) -- `amount` (`number | bigint`) -- `to` (`string`, optional) +**Example:** +```javascript title="Validate Seed" +const isValid = WDK.isValidSeed('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about') +console.log('Seed phrase valid:', isValid) // true -```javascript -const tx = await aave.withdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +const isInvalid = WDK.isValidSeed('invalid seed phrase') +console.log('Seed phrase valid:', isInvalid) // false ``` ---- +## IWalletAccount -### `quoteWithdraw(options, config?)` -Estimate fee to withdraw tokens. +Base writable wallet account interface exposed by `@tetherto/wdk-wallet`. Blockchain modules implement this interface and may narrow the transaction type accepted by `signTransaction()` and `sendTransaction()`. -```javascript -const q = await aave.quoteWithdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +### Methods ---- +| Method | Description | Returns | Throws | +|--------|-------------|---------|--------| +| `getAddress()` | Returns the account address | `Promise` | - | +| `sign(message)` | Signs a message with the account private key | `Promise` | - | +| `signTransaction(tx)` | Signs a transaction without broadcasting it | `Promise` | If the transaction is invalid for the module | +| `verify(message, signature)` | Verifies a message signature | `Promise` | - | +| `sendTransaction(tx)` | Signs, broadcasts, and returns the transaction result | `Promise` | If provider access or broadcast fails | +| `transfer(options)` | Transfers a token where supported by the module | `Promise` | If the module does not support token transfers | +| `toReadOnlyAccount()` | Returns a read-only account copy | `Promise` | - | +| `dispose()` | Clears sensitive account material from memory | `void` | - | -### `borrow(options, config?)` -Borrow tokens. +##### `signTransaction(tx)` +Signs a transaction with the account private key and returns the signed transaction payload without broadcasting it. Use this when your app needs offline signing, external transaction submission, or a separate review step before broadcast. -Options: -- `token` (`string`) -- `amount` (`number | bigint`) -- `onBehalfOf` (`string`, optional) +**Parameters:** +- `tx` (Transaction): Module-specific transaction object. For example, EVM accounts accept `EvmTransaction`, and Bitcoin accounts accept `BtcTransaction`. -```javascript -const tx = await aave.borrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +**Returns:** `Promise` - The signed transaction payload. Wallet modules may narrow this return type, such as a hex string for EVM and Bitcoin transactions. ---- +**Example:** +```typescript title="Sign Without Broadcasting" +const account = await wdk.getAccount('ethereum', 0) -### `quoteBorrow(options, config?)` -Estimate fee to borrow tokens. +const signedTransaction = await account.signTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 1000000000000000n +}) -```javascript -const q = await aave.quoteBorrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +console.log('Signed transaction:', signedTransaction) ``` ---- +## IWalletAccountWithProtocols -### `repay(options, config?)` -Repay borrowed tokens. +Protocol registration and access surface that WDK adds to a wallet account. The consumer-facing [`WdkAccount`](#wdkaccount) type combines this interface with `IWalletAccount` from `@tetherto/wdk-wallet`. -Options: -- `token` (`string`) -- `amount` (`number | bigint`) -- `onBehalfOf` (`string`, optional) +### Methods -```javascript -const tx = await aave.repay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +| Method | Description | Returns | Throws | +|--------|-------------|---------|--------| +| `registerProtocol(label, protocol, config)` | Registers a protocol for this specific account | `IWalletAccountWithProtocols` | - | +| `getSwapProtocol(label)` | Returns the swap protocol with the given label | `ISwapProtocol` | If protocol not found | +| `getBridgeProtocol(label)` | Returns the bridge protocol with the given label | `IBridgeProtocol` | If protocol not found | +| `getLendingProtocol(label)` | Returns the lending protocol with the given label | `ILendingProtocol` | If protocol not found | +| `getFiatProtocol(label)` | Returns the fiat protocol with the given label | `IFiatProtocol` | If protocol not found | +| `getSwidgeProtocol(label)` | Returns the swidge protocol with the given label | `ISwidgeProtocol` | If protocol not found | +| `getSdaProtocol(label)` | Returns the SDA protocol with the given label | `ISdaProtocol` | If protocol not found | -Returns: -- For standard accounts, may include `approveHash` / `resetAllowanceHash` when applicable. +##### `registerProtocol(label, protocol, config)` +Registers a new protocol for this specific account. ---- +**Type Parameters:** +- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol | typeof FiatProtocol | typeof SwidgeProtocol | typeof SdaProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocols` classes -### `quoteRepay(options, config?)` -Estimate fee to repay borrowed tokens. +**Parameters:** +- `label` (string): Registry label for the protocol. Registering the same protocol type and label again replaces the account-scoped instance. +- `protocol` (P): The protocol class +- `config` (`ConstructorParameters

[1]`): The protocol configuration -```javascript -const q = await aave.quoteRepay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +**Returns:** `IWalletAccountWithProtocols` - The account instance (supports method chaining) ---- +**Example:** +```javascript title="Register Protocol for Account" +import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' -### `setUseReserveAsCollateral(token, use, config?)` -Toggle token as collateral for the user. +const account = await wdk.getAccount('ethereum', 0) -```javascript -const tx = await aave.setUseReserveAsCollateral('TOKEN_ADDRESS', true) +// Register protocol for this specific account +account.registerProtocol('usdt0', Usdt0ProtocolEvm, { + apiKey: 'YOUR_API_KEY' +}) + +// Method chaining on the resolved account +const account2 = (await wdk.getAccount('ethereum', 1)) + .registerProtocol('usdt0', Usdt0ProtocolEvm, usdt0ProtocolConfig) ``` ---- +##### `getSwapProtocol(label)` +Returns the swap protocol with the given label. -### `setUserEMode(categoryId, config?)` -Set user eMode category. +**Parameters:** +- `label` (string): The protocol label -```javascript -const tx = await aave.setUserEMode(1) -``` +**Returns:** `ISwapProtocol` - The swap protocol instance ---- +**Throws:** Error if no swap protocol with the given label has been registered -### `getAccountData(account?)` -Read account stats like total collateral, debt, and health. +**Example:** +```javascript title="Get Swap Protocol" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -```javascript -const data = await aave.getAccountData() -``` +// Register swap protocol +account.registerProtocol('velora', veloraProtocolEvm, veloraProtocolConfig) -Returns the following structure: +// Get swap protocol +const velora = account.getSwapProtocol('velora') -```javascript -{ - totalCollateralBase: bigint, - totalDebtBase: bigint, - availableBorrowsBase: bigint, - currentLiquidationThreshold: bigint, - ltv: bigint, - healthFactor: bigint -} +// Use the protocol +const swapResult = await velora.swap({ + tokenIn: '0x...', + tokenOut: '0x...', + tokenInAmount: 1000000n +}) + +// This will throw an error +// try { +// const uniswap = account.getSwapProtocol('uniswap') +// } catch (error) { +// console.error('No swap protocol with label "uniswap" found') +// } ``` ---- +##### `getBridgeProtocol(label)` +Returns the bridge protocol with the given label. -## ERC‑4337 Config Override (optional) +**Parameters:** +- `label` (string): The protocol label -When the protocol uses `WalletAccountEvmErc4337` or `WalletAccountReadOnlyEvmErc4337`, the optional `config` argument on `supply`, `quoteSupply`, `withdraw`, `quoteWithdraw`, `borrow`, `quoteBorrow`, `repay`, `quoteRepay`, `setUseReserveAsCollateral`, and `setUserEMode` accepts the wallet module's per-call gas-payment overrides. +**Returns:** `IBridgeProtocol` - The bridge protocol instance -- **Paymaster token mode**: `paymasterUrl`, `paymasterAddress`, `paymasterToken`, `transferMaxFee` -- **Sponsorship policy mode**: `isSponsored`, `paymasterUrl`, `sponsorshipPolicyId` -- **Native coin mode**: `useNativeCoins`, `transferMaxFee` +**Throws:** Error if no bridge protocol with the given label has been registered -Example: +**Example:** +```javascript title="Get Bridge Protocol" +import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' -```javascript -const res = await aave.supply( - { token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) -``` +// Register bridge protocol +account.registerProtocol('usdt0', Usdt0ProtocolEvm) -## Rules & Notes +// Get bridge protocol +const usdt0 = account.getBridgeProtocol('usdt0') -- `token` must be a valid (non‑zero) address -- `amount` > 0 and in token base units (use BigInt) -- `onBehalfOf`/`to` (if set) must be valid, non‑zero addresses -- A provider is required to read/send transactions -- For USD₮ on mainnet, allowance may be reset to 0 then set again before actions +// Use the protocol +await account.approve({ + token: '0x...', + spender: '0x...', // OFT or bridge spender address + amount: 1000000n +}) +const bridgeResult = await usdt0.bridge({ + targetChain: 'arbitrum', + recipient: '0x...', + token: '0x...', + amount: 1000000n, + oftContractAddress: '0x...' // Same address used as approval spender +}) +``` - - -Get started with WDK in a Node.js environment - - -Get started with WDK's Lending Aave EVM Protocol configuration - - -Get started with WDK's Lending Aave EVM Protocol usage - - +##### `getLendingProtocol(label)` +Returns the lending protocol with the given label. -*** +**Parameters:** +- `label` (string): The protocol label -### Need Help? +**Returns:** `ILendingProtocol` - The lending protocol instance - +**Throws:** Error if no lending protocol with the given label has been registered -*** +**Example:** +```javascript title="Get Lending Protocol" +import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' -## Lending Aave EVM Configuration -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/configuration -Description: Configuration options and settings for @tetherto/wdk-protocol-lending-aave-evm +// Register lending protocol +account.registerProtocol('aave', AaveProtocolEvm, aaveProtocolConfig) -# Configuration +// Get lending protocol +const aave = account.getLendingProtocol('aave') -## Service Setup +// Use the protocol +const supplyResult = await aave.supply({ + token: '0x...', + amount: 1000000n +}) +``` -```javascript -import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +##### `getFiatProtocol(label)` +Returns the fiat protocol with the given label. -// Create wallet account first -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +**Parameters:** +- `label` (string): The protocol label -// Create lending service -const aave = new AaveProtocolEvm(account) -``` +**Returns:** `IFiatProtocol` - The fiat protocol instance -## Account Configuration +**Throws:** Error if no fiat protocol with the given label has been registered -The service uses the wallet account configuration to connect to the target network and sign transactions. +**Example:** +```javascript title="Get Fiat Protocol" +import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay' -```javascript -import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' +// Register fiat protocol +account.registerProtocol('moonpay', MoonPayProtocol, moonpayProtocolConfig) -// Full access account -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +// Get fiat protocol +const moonpay = account.getFiatProtocol('moonpay') -// Read-only account (quotes, reads) -const readOnly = new WalletAccountReadOnlyEvm('0xYourAddress', { - provider: 'https://ethereum-rpc.publicnode.com' +const buyUrl = await moonpay.buy({ + cryptoAsset: 'usdt', + fiatCurrency: 'usd', + fiatAmount: 10000n }) - -const aave = new AaveProtocolEvm(account) ``` -## ERC‑4337 (Account Abstraction) +##### `getSwidgeProtocol(label)` +Returns the swidge protocol with the given label. -When using ERC‑4337 smart accounts, every mutating method and quote helper accepts an optional `config` override. In `v1.0.0-beta.4`, that override matches the three gas-payment families exposed by [`@tetherto/wdk-wallet-evm-erc-4337`](/sdk/wallet-modules/wallet-evm-erc-4337/configuration): paymaster token, sponsorship policy, or native coins. +The examples below use `MySwidgeProtocol` as the concrete provider class supplied by your swidge provider module. -Use the fields that match the gas-payment mode you want for that call. For the full field-level definitions, see the [`@tetherto/wdk-wallet-evm-erc-4337` configuration docs](/sdk/wallet-modules/wallet-evm-erc-4337/configuration) and [`Config Override`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) reference. +**Parameters:** +- `label` (string): The protocol label -```javascript -import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' +**Returns:** `ISwidgeProtocol` - The swidge protocol instance -const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { - chainId: 1, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: 'YOUR_BUNDLER_URL', - paymasterUrl: 'YOUR_PAYMASTER_URL' -}) +**Throws:** Error if no swidge protocol with the given label has been registered -const aaveAA = new AaveProtocolEvm(aa) +**Example:** +```javascript title="Get Swidge Protocol" +// Register a concrete provider class that extends SwidgeProtocol +account.registerProtocol('swidge', MySwidgeProtocol, swidgeProtocolConfig) -const result = await aaveAA.supply({ token: '0xdAC17F...ec7', amount: 1000000n }, { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } +// Get swidge protocol +const swidge = account.getSwidgeProtocol('swidge') + +const quote = await swidge.quoteSwidge({ + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', + toChain: 'arbitrum', + fromTokenAmount: 1000000n }) ``` -### Supported Override Families +##### `getSdaProtocol(label)` +Returns the Smart Deposit Address protocol with the given label. -- **Paymaster token mode**: `paymasterUrl`, `paymasterAddress`, `paymasterToken`, `transferMaxFee` -- **Sponsorship policy mode**: `isSponsored`, `paymasterUrl`, `sponsorshipPolicyId` -- **Native coin mode**: `useNativeCoins`, `transferMaxFee` +The example uses `MySdaProtocol` as an illustrative provider class extending `SdaProtocol`; it does not imply that a provider package is available in the WDK documentation catalog. -## Network Support +**Parameters:** +- `label` (string): The protocol label -Aave V3 spans multiple EVM chains (Ethereum, Arbitrum, Base, Optimism, Polygon, Avalanche, BNB, Celo, Gnosis, Linea, Scroll, Soneium, Sonic, ZkSync, Metis). Ensure the correct RPC and token addresses for the target chain. +**Returns:** `ISdaProtocol` - The SDA protocol instance -```javascript -// Ethereum Mainnet -const eth = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +**Throws:** `Error` with `No sda protocol registered for label: ( + account: A +) => Promise; ``` -## Create the lending client +### Policy Types + +```typescript title="Types: Policy Engine" +type PolicyAction = 'ALLOW' | 'DENY' +type PolicyScope = 'project' | 'account' + +type PolicyOperation = + | 'sendTransaction' + | 'signTransaction' + | 'transfer' + | 'approve' + | 'sign' + | 'signTypedData' + | 'signAuthorization' + | 'delegate' + | 'revokeDelegation' + | 'swap' + | 'bridge' + | 'supply' + | 'withdraw' + | 'borrow' + | 'repay' + | 'buy' + | 'sell' + | 'swidge' + | 'createDepositAddress' + | 'renewDepositAddress' + | 'recoverDepositAddress' + | 'disableDepositAddress' + | '*' + +type PolicyCondition = (context: PolicyContext) => boolean | Promise + +interface PolicyContext { + operation: PolicyOperation + wallet: string + account: IWalletAccountReadOnly + params: unknown + args: readonly unknown[] +} + +interface PolicyRule { + name: string + operation: PolicyOperation | PolicyOperation[] + action: PolicyAction + conditions: PolicyCondition[] + reason?: string + override_broader_scope?: boolean + state?: Record + onSuccess?: (c: PolicyContext) => void | Promise +} -You can attach Aave V3 actions to an EVM account from [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference) with [`new AaveProtocolEvm(account)`](/sdk/lending-modules/lending-aave-evm/api-reference) on [`AaveProtocolEvm`](/sdk/lending-modules/lending-aave-evm/api-reference): +interface Policy { + id: string + name: string + scope: PolicyScope + wallet?: string | string[] + accounts?: Array + rules: PolicyRule[] +} -```javascript title="Create AaveProtocolEvm" -import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +interface RegisterPolicyOptions { + state?: Record + conditionTimeoutMs?: number +} -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +interface SimulationTraceEntry { + scope: PolicyScope + policy_id: string + rule_name: string + matched: boolean + error?: string +} -const aave = new AaveProtocolEvm(account) +interface SimulationResult { + decision: 'ALLOW' | 'DENY' + policy_id: string | null + matched_rule: string | null + reason: string | null + trace: SimulationTraceEntry[] +} ``` -## Prerequisites - - -**Token balance:** To supply or repay, hold the ERC-20 in the wallet. **Gas:** Keep native balance (ETH on Ethereum, and so on) for transaction fees unless you use sponsored ERC-4337 flows. **Networks:** This module targets mainnet deployments; confirm your RPC matches [supported networks](/sdk/lending-modules/lending-aave-evm/configuration). - +`scope: 'project'` can apply across all registered wallets or only the wallets named in `wallet`. `scope: 'account'` requires both `wallet` and `accounts`; account entries can be a derivation path string or an account index number. -Use contract addresses for Aave-supported reserves. On Ethereum mainnet, USD₮ uses `0xdAC17F958D2ee523a2206206994597C13D831ec7` (use `USDT` in code identifiers and literals). +`override_broader_scope` is valid only on account-scoped `ALLOW` rules. When that rule matches, WDK allows the call without evaluating project-scoped policies. -## Next Steps +`state` and `onSuccess` are reserved for future engine-managed state and are ignored at runtime in this beta. Conditions can use app-owned state through closures or external stores, but keep that state outside `rule.state`; WDK does not persist or update `state`, run `onSuccess`, or provide built-in counters or cumulative spend accounting. -- [Lending operations](lending-operations) -- [Handle errors](handle-errors) +Simulation results returned by the runtime `account.simulate.(...)` mirrors include `decision`, `policy_id`, `matched_rule`, `reason`, and a `trace` array that records evaluated rules. `reason` can include a rule reason or one of the engine outcomes: `matched`, `override`, `no-applicable-rule`, or `governed-but-unmatched`. -*** +### Protocol Types -## Handle Errors -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/guides/handle-errors -Description: Catch lending failures and release wallet secrets safely. +```typescript title="Types: Protocol Interfaces" +// Swap Protocol +interface ISwapProtocol { + swap(options: SwapOptions): Promise; +} -This guide explains how to [handle operation errors](#operation-errors) and follow [best practices](#best-practices) for disposing wallet state. +// Bridge Protocol +interface IBridgeProtocol { + bridge(options: BridgeOptions): Promise; +} -## Operation errors +// Lending Protocol +interface ILendingProtocol { + supply(options: LendingOptions): Promise; + withdraw(options: LendingOptions): Promise; + borrow(options: LendingOptions): Promise; + repay(options: LendingOptions): Promise; +} -You can catch failures from [`supply()`](/sdk/lending-modules/lending-aave-evm/api-reference), [`withdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference), [`borrow()`](/sdk/lending-modules/lending-aave-evm/api-reference), and [`repay()`](/sdk/lending-modules/lending-aave-evm/api-reference) with `try/catch`: +// Swidge Protocol (unified swap + bridge + route) +interface ISwidgeProtocol extends ISwapProtocol, IBridgeProtocol { + quoteSwidge(options: SwidgeOptions): Promise; + swidge(options: SwidgeOptions, config?: SwidgeProtocolConfig): Promise; + getSwidgeStatus(id: string, options?: SwidgeStatusOptions): Promise; + getSupportedChains(): Promise; + getSupportedTokens(options?: SwidgeSupportedTokensOptions): Promise; +} -```javascript title="Handle a failed supply" -try { - await aave.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 0n - }) -} catch (e) { - console.error('Lending failed:', e.message) - if (e.message.includes('zero')) { - console.log('Amount must be greater than zero') - } +// Smart Deposit Address Protocol +interface ISdaProtocol { + getSupportedRoutes(options?: SdaRoutesOptions): Promise; + createDepositAddress(options: SdaCreateDepositAddressOptions): Promise; + quoteDeposit(options: SdaDepositOptions): Promise; + deriveDepositAddress(options: SdaCreateDepositAddressOptions): Promise; + getDepositAddress(id: string): Promise; + renewDepositAddress(id: string): Promise; + getTransfers(address: string, options?: SdaTransfersOptions): Promise; + getTransfersByRecipient(destinationChain: string | number, recipient: string, options?: SdaTransfersOptions): Promise; + getTransfer(id: string): Promise; + recoverDepositAddress(options: SdaRecoveryOptions): Promise; + disableDepositAddress(id: string): Promise; } ``` -You can isolate quote failures from [`quoteSupply()`](/sdk/lending-modules/lending-aave-evm/api-reference) (or the other `quote*` methods) when you only need an estimate: +`SdaProtocol` requires provider subclasses to implement `getSupportedRoutes()` and `createDepositAddress()`. The remaining methods are optional in the base class and throw `UnsupportedOperationError` unless the provider implements them. Output assets are provider- and route-specific; USDT is a common example, not a base-interface guarantee. -```javascript title="Handle quote errors" -try { - const q = await aave.quoteBorrow({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) - console.log('Borrow fee (wei):', q.fee) -} catch (e) { - console.error('Quote failed:', e.message) -} -``` +### Swidge Protocol - -See [Rules & Notes](/sdk/lending-modules/lending-aave-evm/api-reference) for address and amount validation expectations. - +`SwidgeProtocol` is an abstract base class exported from `@tetherto/wdk-wallet/protocols` for provider packages that implement a single, route-aware surface for same-chain swaps and cross-chain bridges. It implements `ISwidgeProtocol`, which extends both `ISwapProtocol` and `IBridgeProtocol`, so the base class derives `swap()`, `quoteSwap()`, `bridge()`, and `quoteBridge()` by delegating to `swidge()` and `quoteSwidge()`. Provider subclasses implement the abstract methods below. -## Best Practices +| Method | Description | Returns | +|--------|-------------|---------| +| `quoteSwidge(options)` | Returns a non-binding quote for a swap/bridge operation | `Promise` | +| `swidge(options, config?)` | Executes a swap/bridge operation | `Promise` | +| `getSwidgeStatus(id, options?)` | Returns the current status of an in-flight operation | `Promise` | +| `getSupportedChains()` | Returns the chains the provider supports | `Promise` | +| `getSupportedTokens(options?)` | Returns the tokens the provider supports, optionally route-scoped | `Promise` | -You can wipe private keys after lending work by calling [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference), or [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference): +The `SwidgeOptions` input combines common fields (`fromToken`, `toToken`, optional `toChain`, `recipient`, `refundAddress`, `slippage`) with either an exact-in (`fromTokenAmount`) or exact-out (`toTokenAmount`) amount. The optional `SwidgeProtocolConfig` accepts `maxNetworkFeeBps` and `maxProtocolFeeBps` to cap acceptable fees. -```javascript title="Dispose after lending session" -try { - await aave.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) -} finally { - account.dispose() -} +See the provider catalog in [Swidge modules](/sdk/swidge-modules), then use the selected provider's API reference for its released discovery, quote, execution, status, fee, and result behavior. + +```typescript title="Type: ISwidgeProtocol Options and Results" +type SwidgeProtocolConfig = { + maxNetworkFeeBps?: number | bigint; + maxProtocolFeeBps?: number | bigint; +}; + +type SwidgeOptions = { + fromToken: string; + toToken: string; + toChain?: string | number; // defaults to the source chain (same-chain swap) + recipient?: string; + refundAddress?: string; + slippage?: number; // decimal, e.g. 0.01 for 1% + minAmountOut?: number | bigint; // destination-token base units; provider-defined enforcement +} & ( + | { fromTokenAmount: number | bigint } // exact-in + | { toTokenAmount: number | bigint } // exact-out +); + +type SwidgeStatus = + | 'pending' | 'action-required' | 'completed' | 'failed' + | 'refund-pending' | 'refunded' | 'cancelled' | 'expired' | 'partial'; ``` -For ERC-4337 accounts, use [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) on the smart-account type. Clear references to [`AaveProtocolEvm`](/sdk/lending-modules/lending-aave-evm/api-reference) when the session ends. +*** ## Next Steps -- [Lending operations](lending-operations) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-aave-evm/api-reference) + + +Get started with WDK's configuration + + +Get started with WDK's Usage + + +Explore blockchain-specific wallet modules + + +Cross-chain USD₮0 bridges + + *** -## Lending Operations -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/guides/lending-operations -Description: Supply, withdraw, borrow, repay, quote fees, use ERC-4337, and read account data. +### Need Help? -This guide walks through [supply](#supply), [withdraw](#withdraw), [borrow](#borrow), [repay](#repay), [quotes](#quotes-before-sending), [ERC-4337 usage](#erc-4337-smart-accounts), and [reading account data](#reading-account-data). It assumes an [`AaveProtocolEvm`](/sdk/lending-modules/lending-aave-evm/api-reference) instance named `aave` and the USD₮ contract on Ethereum mainnet `0xdAC17F958D2ee523a2206206994597C13D831ec7`. + -## Supply +*** -You can deposit reserves into the pool using [`supply()`](/sdk/lending-modules/lending-aave-evm/api-reference): +## WDK Core Configuration +URL: https://docs.wdk.tether.io/sdk/core-module/configuration +Description: Configuration options and settings for @tetherto/wdk -```javascript title="Supply USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +# Configuration -const tx = await aave.supply({ token: USDT, amount: 1000000n }) -console.log('Supply tx hash:', tx.hash) +## WDK Manager Configuration + +```javascript title="Create WDK Instance" +import WDK from '@tetherto/wdk' + +const wdk = new WDK(seedPhrase) ``` -## Withdraw -You can remove supplied liquidity using [`withdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference): +The WDK Manager itself only requires a seed phrase for initialization. Configuration is done through the registration of wallets and protocols. -```javascript title="Withdraw USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +## Wallet Registration Configuration -const tx = await aave.withdraw({ token: USDT, amount: 1000000n }) -console.log('Withdraw tx hash:', tx.hash) +```javascript title="Register WDK Wallet" +import WDK from '@tetherto/wdk' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import WalletManagerTon from '@tetherto/wdk-wallet-ton' + +const wdk = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' + }) + .registerWallet('ton', WalletManagerTon, { + tonApiKey: 'YOUR_TON_API_KEY', + tonApiEndpoint: 'https://tonapi.io' + }) ``` -## Borrow -You can draw debt against your collateral using [`borrow()`](/sdk/lending-modules/lending-aave-evm/api-reference): +## Protocol Registration Configuration -```javascript title="Borrow USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +```javascript title="Register WDK Protocol" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -const tx = await aave.borrow({ token: USDT, amount: 1000000n }) -console.log('Borrow tx hash:', tx.hash) +const wdk = new WDK(seedPhrase) + .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { + apiKey: 'YOUR_velora_API_KEY' + }) ``` -## Repay - -You can pay down debt using [`repay()`](/sdk/lending-modules/lending-aave-evm/api-reference): -```javascript title="Repay USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +## Configuration Options -const tx = await aave.repay({ token: USDT, amount: 1000000n }) -console.log('Repay tx hash:', tx.hash) -``` +### Wallet Configuration -## Quotes before sending +Each wallet manager requires its own configuration object when registered. The configuration depends on the specific wallet module being used. -You can estimate the supply fee with [`quoteSupply()`](/sdk/lending-modules/lending-aave-evm/api-reference): +#### EVM Wallet Configuration -```javascript title="Quote supply fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +```javascript title="Ethereum WDK Wallet Configuration" +const ethereumWalletConfig = { + provider: 'https://eth.drpc.org', // RPC endpoint + // Additional EVM-specific configuration options +} -const supplyQuote = await aave.quoteSupply({ token: USDT, amount: 1000000n }) -console.log('Supply fee (wei):', supplyQuote.fee) +wdk.registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) ``` -You can estimate the withdraw fee with [`quoteWithdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference): -```javascript title="Quote withdraw fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +#### TON Wallet Configuration -const withdrawQuote = await aave.quoteWithdraw({ token: USDT, amount: 1000000n }) -console.log('Withdraw fee (wei):', withdrawQuote.fee) +```javascript title="TON WDK Wallet Configuration" +const tonWalletConfig = { + tonClient: { + secretKey: 'YOUR_TON_API_KEY', + url: 'https://toncenter.com/api/v2/jsonRPC' + } +} + +wdk.registerWallet('ton', WalletManagerTon, tonWalletConfig) ``` -You can estimate the borrow fee with [`quoteBorrow()`](/sdk/lending-modules/lending-aave-evm/api-reference): -```javascript title="Quote borrow fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +### Protocol Configuration -const borrowQuote = await aave.quoteBorrow({ token: USDT, amount: 1000000n }) -console.log('Borrow fee (wei):', borrowQuote.fee) -``` +Protocols also require their own configuration objects when registered. -You can estimate the repay fee with [`quoteRepay()`](/sdk/lending-modules/lending-aave-evm/api-reference): +#### Swap Protocol Configuration -```javascript title="Quote repay fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +```javascript title="Swap WDK Protocol Configuration" +const veloraProtocolConfig = { + apiKey: 'YOUR_velora_API_KEY', + baseUrl: 'https://apiv5.velora.io' +} -const repayQuote = await aave.quoteRepay({ token: USDT, amount: 1000000n }) -console.log('Repay fee (wei):', repayQuote.fee) +wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) ``` - -Health factor and collateralization limits still apply. A quote does not guarantee the transaction will succeed if on-chain state changes. - -## ERC-4337 smart accounts -You can run the same methods through [`WalletAccountEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and pass a second `config` argument to override per-call gas payment settings. In `v1.0.0-beta.4`, the lending methods accept the same override families as the wallet module: paymaster token, sponsorship policy, and native coins. See the [ERC-4337 config override](/sdk/lending-modules/lending-aave-evm/api-reference) section for the full field list. -```javascript title="Supply with paymaster" -import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' -import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' +### Middleware Configuration -const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { - chainId: 42161, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: process.env.BUNDLER_URL, - paymasterUrl: process.env.PAYMASTER_URL +Middleware functions can be registered to enhance account functionality. + +```javascript title="Middleware WDK Protocol Configuration" +// Simple logging middleware +wdk.registerMiddleware('ethereum', async (account) => { + console.log('New account created:', await account.getAddress()) }) +``` -const aaveAA = new AaveProtocolEvm(aa) +## Environment Variables -const result = await aaveAA.supply( - { token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) -console.log('Supply hash:', result.hash) +For production applications, consider using environment variables for sensitive configuration: + +```javascript title="WDK environment variables Configuration" +const wdk = new WDK(process.env.SEED_PHRASE) + .registerWallet('ethereum', WalletManagerEvm, { + provider: process.env.ETHEREUM_RPC_URL + }) + .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { + apiKey: process.env.velora_API_KEY + }) ``` -You can use the same second argument to: -- override paymaster-token mode with `paymasterUrl`, `paymasterAddress`, `paymasterToken`, or `transferMaxFee` -- switch one call to sponsorship mode with `isSponsored`, `paymasterUrl`, and `sponsorshipPolicyId` -- switch one call to native-coin gas mode with `useNativeCoins` and `transferMaxFee` +## Configuration Validation -Use token addresses that exist on the same chain as the smart account RPC. +Registration does not validate every downstream module configuration: -## Reading account data +- **Wallet Registration**: WDK constructs the wallet manager during registration, so wallet-constructor validation errors surface immediately. Registering a second wallet under the same blockchain throws. +- **Protocol Registration**: Global registration stores a supported protocol class and config for later construction. Reusing the same blockchain, protocol type, and label replaces the previous global registration. Provider-constructor and config errors surface when an account retrieves the protocol. +- **Middleware Registration**: WDK stores the middleware for later account decoration. Errors thrown by the middleware surface when an account is retrieved. -You can inspect collateral, debt, and health using [`getAccountData()`](/sdk/lending-modules/lending-aave-evm/api-reference): +In JavaScript, a protocol class that does not extend a supported WDK protocol base class is ignored rather than rejected. Keep protocol classes typed, use distinct labels, and test retrieval during application startup. A global registration shadows an account-scoped provider with the same type and label. -```javascript title="Read Aave account data" -const data = await aave.getAccountData() +## Error Handling -console.log({ - totalCollateralBase: data.totalCollateralBase, - totalDebtBase: data.totalDebtBase, - availableBorrowsBase: data.availableBorrowsBase, - currentLiquidationThreshold: data.currentLiquidationThreshold, - ltv: data.ltv, - healthFactor: data.healthFactor -}) -``` +Handle wallet registration errors at registration time and protocol errors at retrieval time: -## Next Steps +```javascript title="Configuration errors" +try { + wdk.registerWallet('ethereum', InvalidWalletClass, config) +} catch (error) { + console.error('Wallet registration failed:', error.message) +} -- [Handle errors](handle-errors) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-aave-evm/api-reference) +try { + wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, invalidConfig) + const account = await wdk.getAccount('ethereum', 0) + account.getSwapProtocol('velora') +} catch (error) { + console.error('Protocol construction or retrieval failed:', error.message) +} +``` *** -## Lending Aave EVM Guides -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/usage -Description: How to install and use @tetherto/wdk-protocol-lending-aave-evm on EVM - -# Usage - -The [@tetherto/wdk-protocol-lending-aave-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-lending-aave-evm) module exposes Aave V3 supply, borrow, and repayment flows for EVM accounts. Follow the guides below for setup, day-to-day operations, and error handling. +## Next Steps - -Install the package, create AaveProtocolEvm, and review prerequisites. + +Get started with WDK's usage - -Supply, withdraw, borrow, repay, quotes, ERC-4337, and account data. + +Get started with WDK's API - -Handle failures and dispose wallet secrets when finished. + +Explore blockchain-specific wallet modules + + +Cross-chain USD₮0 bridges +*** - - -Get started with WDK in a Node.js environment - - -Networks and deployment settings for the Aave lending protocol - - -Methods and parameters for AaveProtocolEvm - - +### Need Help? *** -## Lending Morpho EVM Overview -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm -Description: Overview of the @morpho-org/wdk-protocol-lending-morpho-evm community module - - -Community modules are developed and maintained independently by third-party contributors. - -Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. - +## Manage Accounts +URL: https://docs.wdk.tether.io/sdk/core-module/guides/account-management +Description: Learn how to work with accounts and addresses. -The [@morpho-org/wdk-protocol-lending-morpho-evm](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) community module lets WDK-compatible EVM wallet accounts interact with Morpho Vault V2 earn targets and Morpho Blue markets through [`@morpho-org/morpho-sdk`](https://www.npmjs.com/package/@morpho-org/morpho-sdk). +This guide explains how to access accounts from your registered wallets. An "Account" object in WDK is your interface for inspecting balances and sending transactions on a specific blockchain. -## Features +## Retrieve Accounts -- **Vault earn flows**: Deposit into and withdraw from configured Morpho Vault V2 targets -- **Market collateral**: Supply and withdraw collateral in a configured Morpho Blue market -- **Borrow/Repay**: Borrow from and repay a configured Morpho Blue market -- **Requirements API**: Surface Morpho SDK approval, signature, and authorization requirements -- **Quotes**: Estimate transaction costs before sending -- **Account Reads**: Read vault, market, and combined account position data -- **Account Support**: Works with standard EVM accounts and ERC-4337 smart accounts +You can retrieve an account using a simple index or a custom derivation path. -## Supported Targets +### By Index (Recommended) -The module supports curated Ethereum mainnet presets and explicit Morpho target configuration. +The simplest way to get an account is by its index (starting at `0`). This uses the default derivation path for the specified blockchain. -### Earn Presets +```typescript title="Get Account by Index" +// Get the first account (index 0) for Ethereum and TON +const ethAccount = await wdk.getAccount('ethereum', 0) +const tonAccount = await wdk.getAccount('ton', 0) +``` -| Preset | Vault | -|--------|-------| -| `sky-money-usdt-savings` | sky.money USDT Savings V2 | -| `steakhouse-prime-instant` | Steakhouse Prime Instant V2 | +### By Derivation Path (Advanced) -### Borrow Presets +If you need a specific hierarchy, you can request an account by its unique derivation path. -| Preset | Collateral | -|--------|------------| -| `susds` | sUSDS | -| `wsteth` | wstETH | -| `wbtc` | WBTC | -| `xaut` | XAUt | - -## Wallet Compatibility - -- **Standard EVM Wallets**: `@tetherto/wdk-wallet-evm` -- **ERC-4337 Smart Accounts**: `@tetherto/wdk-wallet-evm-erc-4337` -- **Read-Only Accounts**: For quoting and reading configured vault or market positions without sending transactions - -## Key Components - -- **MorphoProtocolEvm**: Main class for Morpho lending operations -- **Morpho Protocol Options**: Configure vaults, markets, presets, chain guards, slippage, signatures, and deployless reads -- **Requirement Helpers**: `getSupplyRequirements`, `getSupplyCollateralRequirements`, `getBorrowRequirements`, and `getRepayRequirements` -- **Position Reads**: `getVaultPosition`, `getMarketPosition`, and `getAccountData` - -## Next Steps +```typescript title="Get Account by Path" +// Custom path for Ethereum +const customEthAccount = await wdk.getAccountByPath('ethereum', "0'/0/1") +``` - - -Install the package, create MorphoProtocolEvm, and review prerequisites. - - -How to use Morpho vault, market, quote, requirement, and position flows. - - -Install the package and configure presets, explicit targets, and options. - - -Methods, options, presets, and return shapes for MorphoProtocolEvm. - - + +The WDK instance caches accounts. If you call `getAccount` twice using the same index, the function will return the same `Account` object instance. + -*** + +**Network Mismatch Warning** +Ensure your WDK instance configuration matches your account environment. +* If using **Testnet** keys, ensure you registered the wallet with a **Testnet RPC** (e.g., `https://sepolia.drpc.org` for ETH, `https://testnet.toncenter.com/api/v2/jsonRPC` for TON). +* If using **Mainnet** keys, ensure you registered the wallet with a **Mainnet RPC** (e.g., `https://eth.drpc.org` for ETH, `https://toncenter.com/api/v2/jsonRPC` for TON). +Using a Mainnet key on a Testnet RPC (or vice versa) will result in "Network not allowed" or zero balance errors. + -## Lending Morpho EVM API Reference -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/api-reference -Description: API Reference for @morpho-org/wdk-protocol-lending-morpho-evm +## View Addresses -# API Reference +Once you have an account object, you can retrieve its public blockchain address using the `getAddress` function. -## Class: MorphoProtocolEvm +```typescript title="Get Addresses" +const ethAddress = await ethAccount.getAddress() +console.log('Ethereum address:', ethAddress) +``` -Main class for Morpho Vault V2 and Morpho Blue lending on EVM. +## Check Balances -### Constructor +You can check the native token balance of any account (e.g., ETH on Ethereum, TON on TON) by using the `getBalance()` function. -```javascript -new MorphoProtocolEvm(account, options) +```typescript title="Get Balance" +const balance = await ethAccount.getBalance() +console.log('Balance:', balance) ``` -Parameters: +### Multi-Chain Balance Check -- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` -- `options`: `MorphoProtocolOptions` +Because WDK offers a unified interface, you can easily iterate through multiple chains to fetch balances. -Example: +The following example: +1. Iterates over an array of user defined chains. +2. Retrieves the first account using the respective chain's `getAccount(index)` function. +3. Retrieves the first account's balance using the `getBalance()` function. +4. Logs the balance to the console. -```javascript -const morpho = new MorphoProtocolEvm(account, { - presets: { - earn: 'sky-money-usdt-savings', - borrow: 'wsteth' - } -}) +```typescript title="Check All Balances" +const chains = ['ethereum', 'ton', 'bitcoin'] + +for (const chain of chains) { + const account = await wdk.getAccount(chain, 0) + const balance = await account.getBalance() + console.log(`${chain} balance:`, balance) +} ``` -### Methods +## Next Steps -| Method | Description | Returns | -|--------|-------------|---------| -| `supply(options, config?)` | Deposit assets into the configured vault | `Promise` | -| `getSupplyRequirements(options, requirementOptions?)` | Return approval or signature requirements for vault deposit | `Promise` | -| `quoteSupply(options, config?)` | Quote vault deposit | `Promise>` | -| `withdraw(options, config?)` | Withdraw assets from the configured vault | `Promise` | -| `quoteWithdraw(options, config?)` | Quote vault withdrawal | `Promise>` | -| `supplyCollateral(options, config?)` | Supply collateral to the configured market | `Promise` | -| `getSupplyCollateralRequirements(options, requirementOptions?)` | Return approval or signature requirements for collateral supply | `Promise` | -| `quoteSupplyCollateral(options, config?)` | Quote collateral supply | `Promise>` | -| `borrow(options, config?)` | Borrow from the configured market | `Promise` | -| `getBorrowRequirements(options)` | Return Morpho authorization requirements for borrow | `Promise` | -| `quoteBorrow(options, config?)` | Quote borrow | `Promise>` | -| `repay(options, config?)` | Repay the configured market | `Promise` | -| `getRepayRequirements(options, requirementOptions?)` | Return approval or signature requirements for repay | `Promise` | -| `quoteRepay(options, config?)` | Quote repay | `Promise>` | -| `withdrawCollateral(options, config?)` | Withdraw collateral from the configured market | `Promise` | -| `quoteWithdrawCollateral(options, config?)` | Quote collateral withdrawal | `Promise>` | -| `getVaultPosition(account?)` | Read configured vault position | `Promise` | -| `getMarketPosition(account?)` | Read configured market position | `Promise` | -| `getAccountData(account?)` | Read combined configured vault and market position | `Promise` | -| `getVaultAddress()` | Return the configured vault address | `Address` | -| `getBorrowMarketId()` | Return the configured borrow market id | `string` | +Now that you can access your accounts, learn how to [send transactions](/sdk/core-module/guides/transactions). ---- +*** -## Requirements and Results +## Error Handling +URL: https://docs.wdk.tether.io/sdk/core-module/guides/error-handling +Description: Learn about common errors and best practices. -- `ApprovalOrSignatureRequirement`: returned by supply, collateral supply, and repay requirement helpers. Each item is either a Morpho SDK approval transaction with `to`, `value`, and `data`, or a signature requirement with `sign(client, userAddress)`. -- `RequirementAuthorization`: returned by `getBorrowRequirements()`. These are Morpho authorization transactions with `to`, `value`, and `data`. -- `RequirementSignature`: returned by a signature requirement's `sign(client, userAddress)` helper. Pass it to `supply()`, `supplyCollateral()`, or `repay()` as `requirementSignature`. -- Write methods return the WDK wallet transaction result, including `hash` and `fee`. Quote methods return the same protocol result without `hash`. +# Error Handling & Best Practices ---- +This guide covers recommended patterns for error handling and security when using the WDK. -### `supply(options, config?)` +## Handling Common Errors -Deposit assets into the configured Morpho vault. +When interacting with multiple chains and protocols, various runtime issues may occur. -Options: +### Missing Registration -- `token` (`string`): configured vault asset -- `amount` (`number | bigint`, optional when `nativeAmount` is set): ERC-20 amount in base units -- `nativeAmount` (`number | bigint`, optional): native amount to wrap and supply -- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set -- `requirementSignature` (`RequirementSignature`, optional): signature returned by a Morpho SDK requirement -- `slippageTolerance` (`bigint`, optional): per-call Morpho SDK slippage tolerance +The most common error is attempting to access a wallet or protocol that hasn't been registered. -```javascript -const tx = await morpho.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) +```typescript title="Check Registration Pattern" +try { + // This will throw if 'tron' was never registered via .registerWallet() + const tronAccount = await wdk.getAccount('tron', 0) +} catch (error) { + console.error('Tron wallet not available:', error.message) +} ``` -### `getSupplyRequirements(options, requirementOptions?)` - -Return Morpho SDK approval or signature requirements for a vault deposit. Use it before `supply()` when the account has not approved the required spender or when signature support is enabled. - -```javascript -const requirements = await morpho.getSupplyRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) -``` + +Always use `try/catch` blocks when initializing sessions or accessing dynamic features. + -### `quoteSupply(options, config?)` +## Memory Management -Quote the fee for a vault deposit transaction. +For security, clear wallet state from memory when a session is complete. The WDK provides [`dispose()`](/sdk/core-module/api-reference) for this purpose. -```javascript -const quote = await morpho.quoteSupply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) -``` +### Seed Lifecycle ---- +WDK does not own the seed you pass to `new WDK(seed)`. The seed comes from your app, so your app is responsible for storing it, decrypting it, and clearing it when it is no longer needed. -### `withdraw(options, config?)` +Use this lifecycle for sessions that need explicit cleanup: -Withdraw assets from the configured Morpho vault. +1. Decrypt or load the seed into a mutable buffer. +2. Initialize and use WDK. +3. Call [`dispose()`](/sdk/core-module/api-reference#disposeblockchains) on the WDK instance. +4. Zero the seed buffer when no WDK instance or wallet needs it anymore. -Options: +```typescript title="Seed lifecycle cleanup" +import WDK from '@tetherto/wdk' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -- `token` (`string`): configured vault asset -- `amount` (`number | bigint`): amount in base units -- `to` (`string`, optional): must equal the connected wallet address when set +type SeedDecrypter = (encryptedSeed: Uint8Array) => Promise -```javascript -const tx = await morpho.withdraw({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) -``` +async function runWalletSession( + encryptedSeed: Uint8Array, + decryptSeedBytes: SeedDecrypter +) { + let seedBytes: Uint8Array | undefined + let wdk: WDK | undefined -### `quoteWithdraw(options, config?)` + try { + seedBytes = await decryptSeedBytes(encryptedSeed) -Quote the fee for a vault withdrawal transaction. + wdk = new WDK(seedBytes) + .registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' + }) -```javascript -const quote = await morpho.quoteWithdraw({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) + const account = await wdk.getAccount('ethereum', 0) + const address = await account.getAddress() + return address + } finally { + wdk?.dispose() + seedBytes?.fill(0) + } +} ``` ---- +In this example, `decryptSeedBytes()` represents your app's secure storage or decryption layer. It should return seed bytes as a `Uint8Array`. -### `supplyCollateral(options, config?)` + +[`dispose()`](/sdk/core-module/api-reference#disposeblockchains) clears keys and account state managed by WDK, including private keys held by registered wallets. It does not mutate or zero the seed value you passed to WDK. If your app requires explicit seed cleanup, prefer a mutable `Uint8Array`; JavaScript strings cannot be reliably zeroed. + -Supply collateral to the configured Morpho Blue market. +### Disposing the Instance -Options: +You can dispose every registered wallet using [`dispose()`](/sdk/core-module/api-reference): -- `token` (`string`): configured market collateral token -- `amount` (`number | bigint`, optional when `nativeAmount` is set): ERC-20 amount in base units -- `nativeAmount` (`number | bigint`, optional): native amount to wrap and supply -- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set -- `requirementSignature` (`RequirementSignature`, optional): signature returned by a Morpho SDK requirement +```typescript title="Dispose WDK" +function endSession(wdk) { + // 1. Dispose registered wallets and account private keys + wdk.dispose() -```javascript -const tx = await morpho.supplyCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) + // 2. Modify app state to reflect logged-out status + // ... + + console.log('Session ended, wallet data cleared.') +} ``` -### `getSupplyCollateralRequirements(options, requirementOptions?)` +### Disposing Specific Wallets -Return Morpho SDK approval or signature requirements for collateral supply. +You can dispose only the wallets you no longer need using [`dispose()`](/sdk/core-module/api-reference): -```javascript -const requirements = await morpho.getSupplyCollateralRequirements({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) +```typescript title="Dispose Specific Wallets" +// Keep the TON wallet registered, but dispose the Ethereum wallet +wdk.dispose(['ethereum']) ``` -### `quoteSupplyCollateral(options, config?)` + +**After Disposal:** Once a wallet is disposed, any later call that depends on that wallet registration will fail until you register it again. If you call `wdk.dispose()` without arguments, you must instantiate a new WDK instance or register fresh wallets before resuming operations. + -Quote the fee for supplying collateral. +## Security Best Practices -```javascript -const quote = await morpho.quoteSupplyCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) -``` +### Environment Variables ---- +Never hardcode API keys or seed phrases in your source code. Use environment variables (e.g., `process.env.TON_API_KEY`). -### `borrow(options, config?)` +### Secure Storage -Borrow assets from the configured Morpho Blue market. +If you persist a session, never store the raw seed phrase in local storage. Use secure operating system storage (like Keychain on macOS or Keystore on Android). -Options: +*** -- `token` (`string`): configured market loan token -- `amount` (`number | bigint`): amount in base units -- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set -- `reallocations` (`readonly VaultReallocation[]`, optional): Morpho Vault V2 reallocations -- `slippageTolerance` (`bigint`, optional): per-call Morpho SDK slippage tolerance +## Getting Started +URL: https://docs.wdk.tether.io/sdk/core-module/guides/getting-started +Description: Install and instantiate the WDK Core module. -```javascript -const tx = await morpho.borrow({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) -``` +This guide explains how to install the [`@tetherto/wdk`](https://www.npmjs.com/package/@tetherto/wdk) package and create a new instance to start managing your wallets. -### `getBorrowRequirements(options)` +## 1. Installation -Return Morpho authorization requirements for borrow flows. +### Prerequisites -```javascript -const requirements = await morpho.getBorrowRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) -``` +Before you begin, ensure you have the following installed: -### `quoteBorrow(options, config?)` +* **[Node.js](https://nodejs.org/)**: version 18 or higher. +* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. -Quote the fee for borrowing. +### Install Package -```javascript -const quote = await morpho.quoteBorrow({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) +To install the WDK Core package, run the following command in your terminal: + +```bash +npm install @tetherto/wdk ``` ---- +This package allows you to manage different blockchain wallets and protocols through a single interface. -### `repay(options, config?)` +## 2. Instantiation -Repay assets to the configured Morpho Blue market. +To use WDK, you must create an instance of the `WDK` class. This instance acts as the central manager for all your wallets and protocols. -Options: +### Import the Module -- `token` (`string`): configured market loan token -- `amount` (`number | bigint | "max"`): amount in base units, or `"max"` to repay current borrow shares -- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set -- `requirementSignature` (`RequirementSignature`, optional): signature returned by a Morpho SDK requirement -- `slippageTolerance` (`bigint`, optional): per-call Morpho SDK slippage tolerance +First, import the `WDK` class from the package: -```javascript -const tx = await morpho.repay({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 'max' -}) +```typescript title="Import WDK Core" +import WDK from '@tetherto/wdk' ``` -When `amount` is `"max"`, Morpho repays borrow shares. Re-run `getRepayRequirements({ amount: "max" })` immediately before sending `repay()` so the approval or permit reflects current market state, or approve `getChainAddresses(chainId).bundler3.generalAdapter1` with a small buffer above current debt and pass a non-zero `slippageTolerance`. `slippageTolerance` caps the repay share price or transfer amount; it is not approval slippage. +### Initialize WDK -### `getRepayRequirements(options, requirementOptions?)` +You can initialize `WDK` in two ways: with a [new seed phrase](#generate-a-new-wallet) or an [existing one](#restore-an-existing-wallet). -Return Morpho SDK approval or signature requirements for repayment. +#### Generate a New Wallet -```javascript -const requirements = await morpho.getRepayRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 'max' -}) -``` +If you are creating a fresh wallet for a user, use the static `getRandomSeedPhrase()` method to generate a secure mnemonic. -### `quoteRepay(options, config?)` +```typescript title="Create new WDK Instance" +// 1. Generate a secure random seed phrase +// Generate 24-word seed phrase for higher security +const seedPhrase = WDK.getRandomSeedPhrase(24) -Quote the fee for repayment. +// Or use 12-word seed phrase (default) +// const seedPhrase = WDK.getRandomSeedPhrase() -```javascript -const quote = await morpho.quoteRepay({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 'max' -}) +// 2. Initialize the WDK instance with the new seed +const wdk = new WDK(seedPhrase) ``` ---- + +**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. + -### `withdrawCollateral(options, config?)` +For cleanup expectations when a wallet session ends, see [Seed Lifecycle](/sdk/core-module/guides/error-handling#seed-lifecycle). -Withdraw collateral from the configured Morpho Blue market. +#### Restore an Existing Wallet -Options: +If a user already has a seed phrase (e.g., from a previous session or another wallet), you can pass it directly to the constructor. -- `token` (`string`): configured market collateral token -- `amount` (`number | bigint`): amount in base units -- `to` (`string`, optional): must equal the connected wallet address when set +```typescript title="Restore WDK Instance" +// Replace this string with the user's actual seed phrase +const existingSeed = 'witch collapse practice feed shame open despair creek road again ice ...' -```javascript -const tx = await morpho.withdrawCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) +const wdk = new WDK(existingSeed) ``` -### `quoteWithdrawCollateral(options, config?)` - -Quote the fee for withdrawing collateral. +For cleanup expectations when a wallet session ends, see [Seed Lifecycle](/sdk/core-module/guides/error-handling#seed-lifecycle). -```javascript -const quote = await morpho.quoteWithdrawCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) -``` +## Next Steps ---- +With your WDK instance ready, you can now [register wallet modules](/sdk/core-module/guides/wallet-registration) to interact with specific blockchains like [Ethereum](/sdk/wallet-modules/wallet-evm/), [TON](/sdk/wallet-modules/wallet-ton/), or [Bitcoin](/sdk/wallet-modules/wallet-btc/). -## Position Reads +*** -### `getVaultPosition(account?)` +## Configure Middleware +URL: https://docs.wdk.tether.io/sdk/core-module/guides/middleware +Description: Learn how to intercept and enhance wallet operations with middleware. -Read this or another account's configured vault position. +Middleware allows you to intercept wallet operations. You can use this to add [logging](#logging), implement retry logic, or route provider failover to the supported failover provider. -Returns: +## Register Middleware -```javascript -{ - shares: bigint, - assets: bigint, - vaultAddress: Address -} -``` +When registering middleware, you should reference a specific chain. The middleware function runs every time an account is instantiated or an operation is performed, depending on the implementation. -### `getMarketPosition(account?)` +### Logging -Read this or another account's configured market position. +This simple middleware logs a message whenever a new account is accessed. -Returns: +```typescript title="Logging Middleware" +wdk.registerMiddleware('ethereum', async (account) => { + const address = await account.getAddress() + console.log('Accessed Ethereum account:', address) -```javascript -{ - supplyShares: bigint, - borrowShares: bigint, - borrowAssets: bigint, - collateral: bigint, - marketId: string -} + // You can also attach custom properties or wrap methods here +}) ``` -### `getAccountData(account?)` +## Use provider failover -Read combined configured vault and market position data. +Provider failover is handled outside core middleware. Use the current [`@tetherto/wdk-failover-provider`](https://www.npmjs.com/package/@tetherto/wdk-failover-provider) package when you need provider-level retry and fallback across RPC endpoints. -Returns: +Install the failover provider package: -```javascript -{ - vaultShares: bigint, - vaultAssets: bigint, - marketSupplyShares: bigint, - marketBorrowShares: bigint, - marketBorrowAssets: bigint, - collateral: bigint, - vaultAddress: Address, - marketId: string -} +```bash +npm install @tetherto/wdk-failover-provider ``` -## Rules & Notes +Import the provider in your wallet or infrastructure setup: -- The wallet account must include a provider. -- Write methods require a writable EVM account. -- `token`, `onBehalfOf`, `to`, and `account` addresses must be valid when provided. -- For vault supply and collateral supply, pass `amount`, `nativeAmount`, or both, and make sure the combined supplied amount is greater than zero. -- Withdraw, borrow, and collateral-withdraw amounts must be greater than zero. `repay()` also accepts `amount: "max"`. -- Vault operations require the token to match the configured vault asset. -- Market borrow and repay operations require the token to match the configured market loan token. -- Collateral operations require the token to match the configured market collateral token. -- `onBehalfOf` and vault or collateral withdrawal `to` must equal the connected wallet address when set. -- `slippageTolerance` applies to vault supply, borrow, and repay calls in the published adapter. Collateral supply uses the Morpho SDK collateral-supply action defaults. -- Use `get*Requirements` methods before final actions when the Morpho SDK reports approval, signature, or authorization requirements. +```typescript +import FailoverProvider from '@tetherto/wdk-failover-provider' +``` - - -Presets, explicit targets, and Morpho SDK options - - -Get started with Morpho lending operations - - +Use this when your app depends on external RPC providers and needs a backup route if the primary provider is slow or unavailable. -*** +See [Add provider failover](/tools/failover-provider/), [failover configuration](/tools/failover-provider/configuration/), and the [failover API reference](/tools/failover-provider/api-reference/) for the supported setup. -### Need Help? +## Next Steps - +Learn about [error handling and best practices](/sdk/core-module/guides/error-handling) to ensure your application is robust and secure. *** -## Lending Morpho EVM Configuration -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/configuration -Description: Configuration options and settings for @morpho-org/wdk-protocol-lending-morpho-evm - -# Configuration +## Integrate Protocols +URL: https://docs.wdk.tether.io/sdk/core-module/guides/protocol-integration +Description: Register and access WDK protocol providers from wallet accounts. -## Installation +The WDK Core module supports registering external protocol providers. This lets you extend wallet accounts with protocol-specific discovery, quote, and write operations. -Install the Morpho lending module with the EVM wallet module used by the examples and the `viem` peer dependency: +## Register Protocols -```bash title="Install with npm" -npm install @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem -``` +You can register protocols globally (for all new accounts). -```bash title="Install with pnpm" -pnpm add @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem -``` +### Global Registration (Recommended) -The package declares Node.js `22.13` or later in its published engine metadata. If you use ERC-4337 smart accounts, also install `@tetherto/wdk-wallet-evm-erc-4337`. +Global registration ensures that every account you retrieve already has the protocol ready to use. You can do this by chaining a call to `.registerProtocol()` on the WDK instance. -## Service Setup +### 1. Install Protocol Modules -Create a WDK-compatible EVM wallet account, then pass it to `MorphoProtocolEvm` with either presets or explicit Morpho targets. +Install the [`@tetherto/wdk-protocol-swap-velora-evm`](https://www.npmjs.com/package/@tetherto/wdk-protocol-swap-velora-evm) and [`@tetherto/wdk-protocol-bridge-usdt0-evm`](https://www.npmjs.com/package/@tetherto/wdk-protocol-bridge-usdt0-evm) packages: -```javascript title="Create MorphoProtocolEvm with presets" -import MorphoProtocolEvm from '@morpho-org/wdk-protocol-lending-morpho-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +```bash +npm install @tetherto/wdk-protocol-swap-velora-evm && npm install @tetherto/wdk-protocol-bridge-usdt0-evm +``` -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +### 2. Register in Code -const morpho = new MorphoProtocolEvm(account, { - presets: { - earn: 'sky-money-usdt-savings', - borrow: 'wsteth' - } -}) -``` +Now, import the protocol modules and register them with your WDK instance. This makes the protocol methods available to any account derived from that instance. -## Constructor +First, import the necessary modules: -```javascript -new MorphoProtocolEvm(account, options) +```typescript title="Import Protocols" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +import usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' ``` -Parameters: - -- `account`: `WalletAccountEvm`, `WalletAccountReadOnlyEvm`, `WalletAccountEvmErc4337`, or `WalletAccountReadOnlyEvmErc4337` -- `options`: Morpho target configuration - -The wallet account must include a provider. Read-only accounts can read positions and quote transactions; mutating methods require a writable account. +Then, register the protocols for the specific chains they support: -## Presets +```typescript title="Register Protocols" +// Register protocols for specific chains +const wdk = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethConfig) -Built-in presets target Ethereum mainnet USDT earn and borrow flows. + // Register Velora Swap for Ethereum + .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { + apiKey: 'YOUR_API_KEY' + }) -```javascript title="Use built-in presets" -const morpho = new MorphoProtocolEvm(account, { - presets: { - earn: 'steakhouse-prime-instant', - borrow: 'wbtc' - } -}) + // Register USDT0 Bridge for Ethereum + .registerProtocol('ethereum', 'usdt0', usdt0ProtocolEvm, { + ethereumRpcUrl: 'https://eth.drpc.org' // Configuration depends on the module + }) ``` -Borrow presets: +## Use Protocols -| Preset | Chain ID | Market ID | Collateral | LLTV | -|--------|----------|-----------|------------|------| -| `susds` | `1` | `0x3274643db77a064abd3bc851de77556a4ad2e2f502f4f0c80845fa8f909ecf0b` | sUSDS | 96.5% | -| `wsteth` | `1` | `0xe7e9694b754c4d4f7e21faf7223f6fa71abaeb10296a4c43a54a7977149687d2` | wstETH | 86% | -| `wbtc` | `1` | `0xa921ef34e2fc7a27ccc50ae7e4b154e16c9799d3387076c421423ef52ac4df99` | WBTC | 86% | -| `xaut` | `1` | `0xb7843fe78e7e7fd3106a1b939645367967d1f986c2e45edb8932ad1896450877` | XAUt | 77% | +Once [registered](#register-protocols), access the protocol instance with its typed getter, such as `getSwapProtocol()`, `getBridgeProtocol()`, `getLendingProtocol()`, `getFiatProtocol()`, [`getSwidgeProtocol()`](/sdk/core-module/api-reference#getswidgeprotocollabel), or [`getSdaProtocol()`](/sdk/core-module/api-reference#getsdaprotocollabel). -Earn presets: +### Smart Deposit Addresses -| Preset | Chain ID | Vault Address | Vault | -|--------|----------|---------------|-------| -| `sky-money-usdt-savings` | `1` | `0x23f5E9c35820f4baB695Ac1F19c203cC3f8e1e11` | sky.money USDT Savings V2 | -| `steakhouse-prime-instant` | `1` | `0xbeef003C68896c7D2c3c60d363e8d71a49Ab2bf9` | Steakhouse Prime Instant V2 | +WDK Core beta.15 registers and retrieves SDA providers through the same account protocol surface. The concrete provider must extend `SdaProtocol`; `MySdaProtocol` below is illustrative. -## Explicit Targets +```typescript title="Register And Access An SDA Provider" +const wdk = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethConfig) + .registerProtocol('ethereum', 'deposits', MySdaProtocol, sdaProtocolConfig) -Use explicit targets when you need a vault or market outside the built-in presets. +const account = await wdk.getAccount('ethereum', 0) +const deposits = account.getSdaProtocol('deposits') -```javascript title="Use explicit Morpho targets" -const morpho = new MorphoProtocolEvm(account, { - chainId: 1, - earnVaultAddress: '0x23f5E9c35820f4baB695Ac1F19c203cC3f8e1e11', - borrowMarketId: '0xe7e9694b754c4d4f7e21faf7223f6fa71abaeb10296a4c43a54a7977149687d2' +const routes = await deposits.getSupportedRoutes({ + sourceChain: 'ethereum', + outputAsset: 'USDT' }) ``` -When you use `earnVaultAddress`, `borrowMarketParams`, or `borrowMarketId` directly, pass `chainId`. The adapter uses it to guard transaction building if a browser wallet switches chains. +Every SDA provider implements route discovery and deposit-address creation. Quote, derivation, renewal, lookup, history, recovery, and disable operations are optional. Check the concrete provider's contract before calling them; the base implementations throw `UnsupportedOperationError`. Output assets are route-specific, with USDT shown only as an example. -Use `borrowMarketParams` when you already know the full Morpho Blue market configuration: +### Swidge Routes -```javascript title="Use explicit Morpho Blue market params" -const morpho = new MorphoProtocolEvm(account, { - chainId: 1, - borrowMarketParams: { - loanToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - collateralToken: 'COLLATERAL_TOKEN_ADDRESS', - oracle: 'ORACLE_ADDRESS', - irm: 'INTEREST_RATE_MODEL_ADDRESS', - lltv: 860000000000000000n - } -}) -``` +Use a swidge provider module for new swap, bridge, or combined route integrations. Retrieve a registered provider with [`getSwidgeProtocol()`](/sdk/core-module/api-reference#getswidgeprotocollabel). The shared swidge interface discovers supported chains and tokens with `getSupportedChains()` and `getSupportedTokens()`, quotes with `quoteSwidge()`, executes with `swidge()`, and tracks asynchronous settlement with `getSwidgeStatus()`. The provider can decide whether the route is fulfilled as a same-chain swap, same-token bridge, combined route, intent, solver route, or aggregator route. -`InputMarketParams` contains `loanToken`, `collateralToken`, `oracle`, `irm`, and `lltv`. Confirm explicit market params against Morpho market data before using them in production. +```typescript title="Swidge route flow" +const account = await wdk.getAccount('ethereum', 0) +const swidge = account.getSwidgeProtocol('swidge') -## Options +const chains = await swidge.getSupportedChains() +const tokens = await swidge.getSupportedTokens({ + fromChain: 'ethereum', + toChain: 'arbitrum' +}) -| Option | Type | Description | -|--------|------|-------------| -| `chainId` | `number \| bigint` | Required with explicit Morpho targets | -| `earnVaultAddress` | `string` | Explicit Morpho Vault V2 address | -| `borrowMarketParams` | `InputMarketParams` | Explicit Morpho Blue market params | -| `borrowMarketId` | `string` | Market id used to fetch market params on-chain | -| `presets` | `{ earn?: string, borrow?: string }` | Built-in earn and borrow target names | -| `slippageTolerance` | `bigint` | Morpho SDK slippage tolerance in WAD precision | -| `supportSignature` | `boolean` | Enables Morpho SDK permit or Permit2 requirements | -| `supportDeployless` | `boolean` | Enables Morpho SDK deployless reads | -| `metadata` | `Metadata` | Optional Morpho SDK metadata passed to action encoders | +const options = { + fromToken: '0xSourceToken...', + toToken: '0xDestinationToken...', + toChain: 'arbitrum', + recipient: '0xRecipient...', + fromTokenAmount: 1000000n, + slippage: 0.01 +} -## Native Amounts +const quote = await swidge.quoteSwidge(options) -For vault deposits and collateral supply, pass either `amount`, `nativeAmount`, or both. `nativeAmount` follows Morpho SDK semantics and is only valid when the configured vault asset or collateral token is the wrapped native token for the chain. +const result = await swidge.swidge(options, { + maxNetworkFeeBps: 50, + maxProtocolFeeBps: 25 +}) -```javascript title="Supply with native amount" -await morpho.supply({ - token: 'WRAPPED_NATIVE_TOKEN_ADDRESS', - nativeAmount: 1000000000000000n +const status = await swidge.getSwidgeStatus(result.id, { + toChain: 'arbitrum' }) ``` -## ERC-4337 Config Overrides +Use discovery results to build token and chain selectors, but continue to show the quote details before execution. `swidge()` is the write step in the shared swidge flow. -When using `WalletAccountEvmErc4337`, mutating methods and quote helpers accept an optional second `config` argument for the wallet module's per-call gas payment settings. + +Existing swap and bridge modules keep their current accessors for released modules. Choose a released [Swidge provider module](/sdk/swidge-modules) for new protocol integrations because the standalone swap and bridge interfaces are expected to be deprecated after Swidge provider coverage is available. + -```javascript title="Supply with an ERC-4337 config override" -await morpho.supply( - { - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) -``` +### Swapping Tokens -See the [`@tetherto/wdk-wallet-evm-erc-4337` configuration docs](/sdk/wallet-modules/wallet-evm-erc-4337/configuration) for paymaster token, sponsorship policy, and native coin override fields. +Use `getSwapProtocol` to access registered swap services on any wallet account. - - -Get started with WDK in a Node.js environment - - -Methods and parameters for MorphoProtocolEvm - - -Get started with Morpho lending operations - - +```typescript title="Swap Tokens" +const ethAccount = await wdk.getAccount('ethereum', 0) +const velora = ethAccount.getSwapProtocol('velora') -*** +const result = await velora.swap({ + tokenIn: '0x...', // Address of token to sell + tokenOut: '0x...', // Address of token to buy + tokenInAmount: 1000000n // Amount to swap +}) +``` -### Need Help? +### Bridging Assets - +1. Use `getBridgeProtocol` to access cross-chain bridges. +2. Approve the source-chain bridge spender for the token and amount. +3. Call `bridge` from the bridge protocol to send tokens from one protocol to another. -*** +```typescript title="Bridge Assets" +const ethAccount = await wdk.getAccount('ethereum', 0) +const usdt0 = ethAccount.getBridgeProtocol('usdt0') -## Get Started -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/guides/get-started -Description: Install the package, create MorphoProtocolEvm, and review prerequisites. +await ethAccount.approve({ + token: '0x...', // ERC20 Token Address + spender: '0x...', // OFT or bridge spender address + amount: 1000000n +}) -This guide covers [installation](#installation), [creating the lending client](#create-the-lending-client), and [prerequisites](#prerequisites). Use [Node.js](https://nodejs.org/) `22.13` or later and [npm](https://www.npmjs.com/) on your machine. +const result = await usdt0.bridge({ + targetChain: 'ton', + recipient: 'UQBla...', // TON address + token: '0x...', // ERC20 Token Address + amount: 1000000n, + oftContractAddress: '0x...' // Same address used as approval spender +}) +``` -## Installation + +**Protocol Availability:** If you try to access a protocol that hasn't been registered (e.g., `getSwapProtocol('uniswap')`), the SDK will throw an error. always ensure registration matches the ID you request. + -Run the following to install [@morpho-org/wdk-protocol-lending-morpho-evm](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm), the EVM wallet module used by the examples, and the `viem` peer dependency: +## Next Steps -```bash title="Install with npm" -npm install @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem -``` +Learn how to [configure middleware](/sdk/core-module/guides/middleware) to add logging or failover protection to your wallet interactions. -```bash title="Install with pnpm" -pnpm add @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem -``` +*** -If you use ERC-4337 smart accounts, also install `@tetherto/wdk-wallet-evm-erc-4337`. +## Transaction Policies +URL: https://docs.wdk.tether.io/sdk/core-module/guides/transaction-policies +Description: Register local ALLOW and DENY rules for WDK account and protocol write methods. -## Create the lending client +Local transaction policies let a WDK app evaluate rules before account or protocol write methods execute. Use them for local approval limits, account-level exceptions, preflight checks, or UI flows that need a dry-run verdict before calling a wallet method. -You can attach Morpho actions to an EVM account from [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference) with [`new MorphoProtocolEvm(account, options)`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +Transaction policies are local pre-execution controls. They do not enforce rules on-chain, replace smart-contract permissions, or validate live token metadata, balances, prices, or contract state. + -```javascript title="Create MorphoProtocolEvm" -import MorphoProtocolEvm from '@morpho-org/wdk-protocol-lending-morpho-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +## Policy Structure -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +A transaction policy is a named local configuration object registered with `wdk.registerPolicy()`. Each policy chooses where it applies, then evaluates its ordered `rules` array before a governed account or protocol write method runs. -const morpho = new MorphoProtocolEvm(account, { - presets: { - earn: 'sky-money-usdt-savings', - borrow: 'wsteth' - } -}) -``` +| Concept | What you configure | +| --- | --- | +| Scope | `scope: 'project'` for project or wallet-level rules, or `scope: 'account'` for selected account indices or derivation paths | +| Wallet | Optional `wallet` bindings for project policies, required `wallet` bindings for account policies | +| Rules | Ordered `rules` array evaluated before a governed account or protocol write method runs | +| Action | `ALLOW` to permit a matching governed call, or `DENY` to block it with `PolicyViolationError` | +| Operation | The wallet or protocol write operation a rule addresses, such as `sendTransaction`, `transfer`, `swap`, or `*` | +| Conditions | Functions that receive `PolicyContext` and return truthy when the rule should match | -## Prerequisites +Use `scope: 'project'` for rules that apply across all wallets or selected wallet identifiers. Use `scope: 'account'` with `wallet` and `accounts` for rules that apply only to specific account indices or derivation paths. + +Each rule addresses one operation, multiple operations, or `*`. A matching `ALLOW` can permit the governed call, while a matching `DENY` blocks the call with `PolicyViolationError`. -**Runtime:** Use Node.js `22.13` or later. **Token balance:** To supply, supply collateral, or repay, hold the required ERC-20 in the wallet. **Gas:** Keep native balance for transaction fees unless you use sponsored ERC-4337 flows. **Targets:** Confirm the configured vault or market matches the token and chain you plan to use. +WDK does not manage durable policy state for you. Conditions can inspect the current `PolicyContext` and app-owned inputs, including in-memory or externally stored state, but WDK does not persist `rule.state`, update counters, or run `onSuccess` hooks. Keep app-owned state outside `rule.state`; that field is reserved for future runtime semantics. -The built-in presets target Ethereum mainnet. If you configure an explicit vault address, market id, or market params, set `chainId` in [`MorphoProtocolOptions`](/sdk/lending-modules/lending-morpho-evm/configuration). +## Register Policies -## Requirements before actions +Register wallets before policies. `wdk.registerPolicy()` validates wallet bindings synchronously and throws `PolicyConfigurationError` if a policy references a wallet identifier that has not been registered. -Morpho SDK actions can return approval, signature, or authorization requirements. Call the matching `get*Requirements` method before the final action when the account has not already satisfied those requirements. +The example below allows normal operations, then denies ETH sends above a local approval limit. The wildcard `ALLOW` rule is intentional: once a policy governs an account, wrapped write operations are default-denied unless a matching `ALLOW` permits them. -```javascript title="Check supply requirements" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +```typescript title="Register A Local Send Limit" +import WDK, { PolicyViolationError } from '@tetherto/wdk' -const requirements = await morpho.getSupplyRequirements({ - token: USDT, - amount: 1000000n -}) +const wdk = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerPolicy({ + id: 'eth-local-send-limit', + name: 'ETH local send limit', + scope: 'project', + wallet: 'ethereum', + rules: [ + { + name: 'allow-normal-operations', + operation: '*', + action: 'ALLOW', + reason: 'Default local approval', + conditions: [() => true] + }, + { + name: 'deny-large-eth-send', + operation: 'sendTransaction', + action: 'DENY', + reason: 'Amount exceeds the local approval limit', + conditions: [ + ({ params }) => { + const value = (params as { value?: bigint } | null)?.value + return typeof value === 'bigint' && value > 1000000000000000000n + } + ] + } + ] + }) -console.log('Requirements:', requirements) +const account = await wdk.getAccount('ethereum', 0) + +try { + await account.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000000n + }) +} catch (error) { + if (error instanceof PolicyViolationError) { + console.error(error.reason) + } +} ``` -Send any returned transaction requirements with your EVM account flow before calling the final action. For signature requirements, call the requirement's `sign(client, userAddress)` helper, then pass the returned `requirementSignature` to `supply`, `supplyCollateral`, or `repay`. +## Scope Policies -## Next Steps +Policies can target a whole project, selected wallets, or selected accounts. -- [Lending operations](lending-operations) -- [Handle errors](handle-errors) +| Scope | Required fields | Applies to | +| --- | --- | --- | +| `project` without `wallet` | `scope`, `rules` | All registered wallets | +| `project` with `wallet` | `scope`, `wallet`, `rules` | One wallet identifier or a list of wallet identifiers | +| `account` | `scope`, `wallet`, `accounts`, `rules` | Specific account indices or derivation paths for one wallet | -*** +Account entries can be non-negative account indices or derivation-path strings. Index entries match accounts returned by `getAccount(wallet, index)`. Path entries match accounts returned by path-based retrieval. -## Handle Errors -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/guides/handle-errors -Description: Catch Morpho lending failures and release wallet secrets safely. +## Evaluation Rules -This guide explains how to [handle operation errors](#operation-errors), [handle requirement errors](#requirement-errors), and follow [best practices](#best-practices) for disposing wallet state. +WDK evaluates policies in this order: -## Operation errors +1. If no registered policy applies to the account, WDK returns the original account. No policy proxy or `simulate` mirror is added. +2. If at least one policy applies, the account is governed. WDK wraps every supported write or signing operation that exists on that account, plus registered protocol write methods. +3. If no rule addresses the attempted operation, WDK blocks the call with `PolicyViolationError` and `reason: 'no-applicable-rule'`. +4. Account-scoped policies run before project-scoped policies. Within each scope, policies and rules run in registration order. +5. A matching account-scoped `DENY` blocks immediately. A matching account-scoped `ALLOW` is recorded unless it has `override_broader_scope: true`. +6. A matching account-scoped `ALLOW` with `override_broader_scope: true` allows the call immediately and skips project-scoped policies. This option is only valid on account-scoped `ALLOW` rules. +7. Project-scoped rules run after account-scoped rules. A matching project-scoped `DENY` blocks. If no `DENY` matches and at least one `ALLOW` matched, WDK allows the call. +8. If rules addressed the operation but none matched, WDK blocks with `reason: 'governed-but-unmatched'`. -You can catch failures from [`supply()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`withdraw()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`supplyCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`borrow()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`repay()`](/sdk/lending-modules/lending-morpho-evm/api-reference), and [`withdrawCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference) with `try/catch`: +Conditions run in array order and every condition must return truthy for the rule to match. If an `ALLOW` condition throws or times out, WDK treats that rule as unmatched. If a `DENY` condition throws or times out, WDK blocks the call. -```javascript title="Handle a failed supply" -try { - await morpho.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 0n - }) -} catch (e) { - console.error('Morpho lending failed:', e.message) +You can create an account-scoped exception using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): - if (e.message.includes('amount')) { - console.log('Amount must be greater than zero') +```typescript title="Account-Level Exception" +wdk.registerPolicy([ + { + id: 'project-send-limit', + name: 'Project send limit', + scope: 'project', + wallet: 'ethereum', + rules: [ + { + name: 'allow-normal-operations', + operation: '*', + action: 'ALLOW', + conditions: [() => true] + }, + { + name: 'deny-large-send', + operation: 'sendTransaction', + action: 'DENY', + reason: 'Project send limit exceeded', + conditions: [ + ({ params }) => { + const value = (params as { value?: bigint } | null)?.value + return typeof value === 'bigint' && value > 1000000000000000n + } + ] + } + ] + }, + { + id: 'treasury-account-override', + name: 'Treasury account override', + scope: 'account', + wallet: 'ethereum', + accounts: [0], + rules: [ + { + name: 'allow-treasury-sends', + operation: 'sendTransaction', + action: 'ALLOW', + override_broader_scope: true, + reason: 'Treasury account has a higher local approval limit', + conditions: [ + ({ params }) => { + const value = (params as { value?: bigint } | null)?.value + return typeof value === 'bigint' && value <= 10000000000000000n + } + ] + } + ] } -} +]) ``` -Common failure causes include: +In the example above, the treasury account can send up to `0.01 ETH` because the account-scoped `ALLOW` rule matches and skips the project-scoped limit. If the account rule does not match, project-scoped rules still run and can block the call. -- wallet account does not have a provider configured -- write method is called with a read-only account -- token does not match the configured vault asset, market loan token, or market collateral token -- explicit target is used without the required `chainId` -- connected chain does not match the configured Morpho target -- amount is zero, invalid, or larger than the account balance -- `onBehalfOf` or `to` does not match the connected wallet address when required +## Supported Operations -Use this checklist to map the most common failures to fixes: +Use these operation names in `PolicyRule.operation`: -| Symptom | Likely cause | Fix | -|---------|--------------|-----| -| Constructor fails before any operation | Wallet account has no provider | Create the EVM account with a provider or use a read-only account with an RPC provider | -| Write method fails on a read-only account | Read-only account can quote and read, but cannot send transactions | Use `WalletAccountEvm` or `WalletAccountEvmErc4337` for mutating methods | -| Explicit target fails during setup | `chainId` is missing, invalid, or does not match the wallet chain | Pass the expected `chainId` with `earnVaultAddress`, `borrowMarketId`, or `borrowMarketParams` | -| Token mismatch error | The supplied token is not the configured vault asset, market loan token, or collateral token | Use the token from the configured vault or market target | -| Zero amount error | `amount` and `nativeAmount` are both absent or zero | Pass a positive ERC-20 `amount`, a positive `nativeAmount`, or `amount: "max"` for repay | -| Requirement lookup returns approval or authorization | Allowance, permit, Permit2, or Morpho authorization is missing | Send returned transaction requirements or sign the returned signature requirement before the final action | -| Final action fails after requirements | Allowance, balance, signature, authorization, quote, or market state changed after the requirement lookup | Re-run the matching `get*Requirements()` method and rebuild the final action | -| Quote succeeds but write fails | On-chain state changed, the account lacks balance, or requirements were not satisfied | Re-check requirements, balances, token addresses, and chain before sending | +| Operation | Method family | +| --- | --- | +| `sendTransaction` | Native transaction send | +| `signTransaction` | Transaction signing without broadcast | +| `transfer` | Token transfer methods | +| `approve` | Token allowance approvals | +| `sign` | Message or payload signing | +| `signTypedData` | EIP-712 style typed-data signing | +| `signAuthorization` | Authorization signing | +| `delegate` | Delegation writes | +| `revokeDelegation` | Delegation revocation | +| `swap` | Swap protocol execution | +| `bridge` | Bridge protocol execution | +| `supply`, `withdraw`, `borrow`, `repay` | Lending protocol writes | +| `buy`, `sell` | Fiat protocol writes | +| `swidge` | Combined swap and bridge route execution | +| `createDepositAddress`, `renewDepositAddress`, `recoverDepositAddress`, `disableDepositAddress` | Smart Deposit Address writes | +| `*` | Wildcard rule for all wrapped write operations | + +Use `sign` for message-style signing in this release. `signMessage` and `signHash` are not valid `PolicyOperation` values. + +## Common Policy Patterns + +WDK policies use JavaScript condition functions. Inspect the `params` and `args` passed by the wallet or protocol method you are governing, then return `true` only when that rule should match. -## Requirement errors + +WDK does not fetch prices, decode calldata, maintain address lists, or manage durable policy state for you. Keep app-owned inputs current, and handle persistence and concurrency when a condition depends on counters or cumulative limits. + -Requirement helpers can fail if the configured target, token, account, or provider cannot produce a valid Morpho SDK action. +You can allow sends to approved recipients using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): -```javascript title="Handle requirement errors" -try { - const requirements = await morpho.getBorrowRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) +```typescript title="Address Allowlist" +const allowedRecipients = new Set([ + '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'.toLowerCase() +]) - console.log('Borrow requirements:', requirements) -} catch (e) { - console.error('Requirement lookup failed:', e.message) -} +wdk.registerPolicy({ + id: 'approved-recipients', + name: 'Approved recipients', + scope: 'project', + wallet: 'ethereum', + rules: [ + { + name: 'allow-approved-send', + operation: 'sendTransaction', + action: 'ALLOW', + conditions: [ + ({ params }) => { + const to = (params as { to?: string } | null)?.to + return typeof to === 'string' && allowedRecipients.has(to.toLowerCase()) + } + ] + } + ] +}) ``` -If a final action fails after requirements were returned, re-check the requirements. Allowances, signatures, authorizations, or on-chain market state can change between the requirement lookup and the final transaction. - -## Quote errors - -You can isolate quote failures from write failures when you only need an estimate: - -```javascript title="Handle quote errors" -try { - const quote = await morpho.quoteBorrow({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) +You can require both a chain and value limit using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): - console.log('Borrow fee:', quote.fee) -} catch (e) { - console.error('Quote failed:', e.message) -} +```typescript title="Network And Value Gate" +wdk.registerPolicy({ + id: 'base-small-sends', + name: 'Base small sends', + scope: 'project', + wallet: 'ethereum', + rules: [ + { + name: 'allow-base-small-send', + operation: 'sendTransaction', + action: 'ALLOW', + conditions: [ + ({ params }) => { + const tx = params as { chainId?: number | string; value?: bigint } | null + const value = tx?.value + + return String(tx?.chainId) === '8453' && + typeof value === 'bigint' && + value <= 1000000000000000n + } + ] + } + ] +}) ``` - -See [Rules & Notes](/sdk/lending-modules/lending-morpho-evm/api-reference) for address, token, amount, and target validation expectations. - - -## Best Practices +You can restrict typed-data signing to approved domains using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): -Dispose wallet secrets after a lending session by calling [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference), or the matching dispose method on your smart account type. +```typescript title="Typed Data Domain Gate" +const approvedTypedDataDomains = new Set([ + '1:0x000000000022d473030f116ddee9f6b43ac78ba3' +]) -```javascript title="Dispose after a Morpho lending session" -try { - await morpho.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) -} finally { - account.dispose() -} +wdk.registerPolicy({ + id: 'approved-typed-data-domains', + name: 'Approved typed data domains', + scope: 'project', + wallet: 'ethereum', + rules: [ + { + name: 'allow-approved-typed-data-domain', + operation: 'signTypedData', + action: 'ALLOW', + conditions: [ + ({ params }) => { + const typedData = params as { + domain?: { chainId?: number | string; verifyingContract?: string } + } | null + const verifyingContract = typedData?.domain?.verifyingContract + const domainKey = `${typedData?.domain?.chainId}:${verifyingContract}`.toLowerCase() + + return typeof verifyingContract === 'string' && + approvedTypedDataDomains.has(domainKey) + } + ] + } + ] +}) ``` -For ERC-4337 accounts, use [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) on the smart-account type. Clear references to [`MorphoProtocolEvm`](/sdk/lending-modules/lending-morpho-evm/api-reference) when the session ends. +You can gate protocol write methods by their method parameters using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): -## Next Steps +```typescript title="Protocol Write Gate" +wdk.registerPolicy({ + id: 'small-swaps-only', + name: 'Small swaps only', + scope: 'project', + wallet: 'ethereum', + rules: [ + { + name: 'allow-small-swaps', + operation: 'swap', + action: 'ALLOW', + conditions: [ + ({ params }) => { + const swap = params as { tokenInAmount?: bigint } | null + const tokenInAmount = swap?.tokenInAmount + + return typeof tokenInAmount === 'bigint' && + tokenInAmount <= 1000000000n + } + ] + } + ] +}) +``` -- [Lending operations](lending-operations) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-morpho-evm/api-reference) +## Inspect Policy Context -*** +Conditions receive a frozen `PolicyContext`: -## Lending Operations -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/guides/lending-operations -Description: Supply, withdraw, manage collateral, borrow, repay, quote fees, handle requirements, and read positions. +| Field | Description | +| --- | --- | +| `operation` | The operation being evaluated | +| `wallet` | The wallet identifier bound to the account | +| `account` | Read-only account view exposed by the wallet module | +| `params` | The first argument passed to the wrapped method | +| `args` | All arguments passed to the wrapped method | -This guide walks through [vault supply](#vault-supply), [vault withdraw](#vault-withdraw), [collateral](#collateral), [borrow](#borrow), [repay](#repay), [requirements](#requirements), [quotes](#quotes-before-sending), [ERC-4337 usage](#erc-4337-smart-accounts), and [position reads](#reading-positions). It assumes a [`MorphoProtocolEvm`](/sdk/lending-modules/lending-morpho-evm/api-reference) instance named `morpho`. +For governed write calls, WDK snapshots the method arguments once before policy evaluation. Conditions see that snapshot, and WDK forwards the same approved values to the underlying wallet method. This prevents a caller from mutating a transaction object while an asynchronous policy condition is running. -## Vault supply +Conditions can be synchronous or asynchronous. `conditionTimeoutMs` defaults to `30000` milliseconds and can be set through `registerPolicy(policies, options)`. If a `DENY` condition throws or times out, WDK blocks the call. If an `ALLOW` condition throws or times out, WDK treats that allow rule as unmatched. -Deposit into the configured Morpho Vault V2 target with [`supply()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +## Simulate Before Execution -```javascript title="Supply USDT to the configured vault" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +When a policy applies to an account, WDK adds runtime `simulate` mirrors for wrapped account and protocol write methods. Simulation returns the policy verdict and does not call the underlying wallet or protocol method. -const requirements = await morpho.getSupplyRequirements({ - token: USDT, - amount: 1000000n -}) +You can dry-run a governed account method through the runtime `simulate` mirror: -console.log('Supply requirements:', requirements) +```typescript title="Dry-Run A Transaction Policy" +const account = await wdk.getAccount('ethereum', 0) -const tx = await morpho.supply({ - token: USDT, - amount: 1000000n +const result = await (account as any).simulate.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000n }) -console.log('Supply tx hash:', tx.hash) +console.log(result.decision, result.reason, result.trace) ``` -The `token` must match the configured vault asset. +Simulation results include `decision`, `policy_id`, `matched_rule`, `reason`, and `trace`. Protocol write methods are also mirrored, for example `account.simulate.getSwapProtocol(label).swap(...)` or `account.simulate.getSdaProtocol(label).createDepositAddress(...)`. -## Vault withdraw + +In this beta, `simulate` is added at runtime but is not typed on the account return type. Use a local helper interface or a narrow `as any` cast at the call site. + -Withdraw from the configured vault with [`withdraw()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +## Handle Errors -```javascript title="Withdraw USDT from the configured vault" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +`PolicyConfigurationError` means WDK rejected the policy setup or could not safely evaluate a governed call. Common causes include invalid scopes, actions, operation names, condition functions, timeout options, missing account bindings, wallet identifiers that have not been registered, or governed method arguments that are not structured-cloneable. -const tx = await morpho.withdraw({ - token: USDT, - amount: 1000000n -}) +`PolicyViolationError` means an enforced write call was blocked by a matching `DENY` rule or by default-deny when no `ALLOW` rule matched. Catch it around the write call and surface the `reason` to the user or approval workflow. -console.log('Withdraw tx hash:', tx.hash) -``` +## Runtime Caveats -If you pass `to`, it must equal the connected wallet address. +- Policies wrap the WDK account/protocol proxy surface. On governed proxies, beta.15 treats `keyPair` and string members beginning with `_` as absent from direct property access, membership checks, own-property descriptors, and own-key enumeration. The proxy also refuses `Object.preventExtensions()` and `Object.freeze()` with `TypeError` so those hiding rules remain valid. +- This is not a complete sandbox. Prototype inspection, separately retained raw account or protocol references, and nested calls made inside a module remain outside the proxy interception path. +- Quote and read methods are not wrapped. Policies apply to write methods such as sends, signs, swaps, bridges, lending writes, fiat writes, swidge execution, and SDA address creation, renewal, recovery, or disablement. +- Policy conditions receive local method arguments. WDK does not decode calldata, fetch prices, validate token metadata, or calculate fiat value unless your condition function does that work. +- Wallet accounts must expose a read-only account view when a policy applies, otherwise `getAccount()` fails with `PolicyConfigurationError`. +- Governed write-call arguments must be structured-cloneable, such as primitives, plain objects, arrays, `bigint`, and typed arrays. Functions, live class instances, and other non-cloneable values fail closed with `PolicyConfigurationError` instead of being forwarded unsafely. +- Engine-managed state hooks are not active in this beta. The schema accepts `state` and `onSuccess`, but WDK does not pass `state` into conditions, update it after execution, persist it, or call `onSuccess`. Conditions can still use app-owned state through closures or external stores; keep that state outside `rule.state`, and have your app own durability, concurrency, and rollback behavior. -## Collateral +## Next Steps -Supply collateral to the configured Morpho Blue market with [`supplyCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +- Review [`registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options) in the API reference. +- Use [Send Transactions](/sdk/core-module/guides/transactions) for base account send flows. +- Use [Protocol Integration](/sdk/core-module/guides/protocol-integration) for protocol registration and method setup. -```javascript title="Supply collateral" -const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' +*** -const requirements = await morpho.getSupplyCollateralRequirements({ - token: COLLATERAL, - amount: 1000000000000000000n -}) +## Need Help? -console.log('Collateral requirements:', requirements) + -const tx = await morpho.supplyCollateral({ - token: COLLATERAL, - amount: 1000000000000000000n -}) +*** -console.log('Collateral supply tx hash:', tx.hash) -``` +## Send Transactions +URL: https://docs.wdk.tether.io/sdk/core-module/guides/transactions +Description: Learn how to send native tokens on different blockchains. -Withdraw collateral with [`withdrawCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +You can [send native tokens](#send-native-tokens), [sign a transaction without broadcasting it](#sign-without-broadcasting), [handle transaction responses](#handling-responses), and [orchestrate multi-chain payments](#multi-chain-transactions) from WDK wallet accounts. -```javascript title="Withdraw collateral" -const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' + +**Get Testnet Funds:** To test these transactions without spending real money, ensure you are on a testnet and have obtained funds. See [Testnet Funds & Faucets](/resources/concepts#testnet-funds--faucets) for a list of available faucets. + -const tx = await morpho.withdrawCollateral({ - token: COLLATERAL, - amount: 1000000000000000000n -}) + +**BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. + -console.log('Collateral withdrawal tx hash:', tx.hash) -``` +## Send Native Tokens -The collateral token must match the configured market collateral token. If you pass `to`, it must equal the connected wallet address. +The `sendTransaction` method allows you to transfer value. It accepts a unified configuration object, though specific parameters (like `value` formatting) may vary slightly depending on the blockchain. -## Borrow +### Ethereum Example -Borrow from the configured market with [`borrow()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +On EVM chains, values are typically expressed in Wei (1 ETH = 10^18 Wei). + -```javascript title="Borrow USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +The following example will: -const requirements = await morpho.getBorrowRequirements({ - token: USDT, - amount: 1000000n -}) +1. Retrieve the first Ethereum account (see [Manage Accounts](/sdk/core-module/guides/account-management)) +2. Send 0.001 ETH (1000000000000000 wei) to an account using `sendTransaction`. -console.log('Borrow requirements:', requirements) +```typescript title="Send ETH" +const ethAccount = await wdk.getAccount('ethereum', 0) -const tx = await morpho.borrow({ - token: USDT, - amount: 1000000n +const result = await ethAccount.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 1000000000000000n // 0.001 ETH (in Wei) }) -console.log('Borrow tx hash:', tx.hash) +console.log('Transaction sent! Hash:', result.hash) ``` -The borrow token must match the configured market loan token. +### TON Example -## Repay + +On TON, values are expressed in Nanotons (1 TON = 10^9 Nanotons). + -Repay by asset amount, or pass `amount: 'max'` to repay current borrow shares: +The following example will: -```javascript title="Repay max borrow shares" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +1. Retrieve the first TON account +2. Send 1 TON (1000000000 nton) to an account using `sendTransaction`. -const requirements = await morpho.getRepayRequirements({ - token: USDT, - amount: 'max' +```typescript title="Send TON" +// Send TON transaction +const tonAccount = await wdk.getAccount('ton', 0) +const tonResult = await tonAccount.sendTransaction({ + to: 'UQCz5ON7jjK32HnqPushubsHxgsXgeSZDZPvh8P__oqol90r', + value: 1000000000n // 1 TON (in nanotons) }) +console.log('TON transaction:', tonResult.hash) +``` -console.log('Repay requirements:', requirements) +## Sign Without Broadcasting -const tx = await morpho.repay({ - token: USDT, - amount: 'max' +Use [`account.signTransaction()`](/sdk/core-module/api-reference#signtransactiontx) when your app needs a signed transaction payload but does not want WDK to broadcast it immediately. Wallet modules accept their own transaction shape and may return a module-specific signed payload. + +```typescript title="Sign An EVM Transaction" +const ethAccount = await wdk.getAccount('ethereum', 0) + +const signedTransaction = await ethAccount.signTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 1000000000000000n }) -console.log('Repay tx hash:', tx.hash) +console.log('Signed transaction:', signedTransaction) ``` - -For `amount: "max"`, Morpho repays borrow shares. The loan-token transfer amount returned by `getRepayRequirements()` is computed from live market state, so accrued interest can make a delayed approval or permit insufficient. Re-run `getRepayRequirements({ amount: "max" })` immediately before `repay()`, or approve `getChainAddresses(chainId).bundler3.generalAdapter1` with a small buffer above current debt and pass a non-zero `slippageTolerance`. `slippageTolerance` caps the repay share price or transfer amount; it is not approval slippage. Residual loan tokens pulled in shares mode are skimmed back to the user. + +`signTransaction()` only signs. Use `sendTransaction()` when you want WDK to sign, broadcast, and return the transaction hash. -The repay token must match the configured market loan token. +## Apply Local Transaction Policies -## Requirements +Use [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options) to evaluate local ALLOW and DENY rules before account or protocol write methods run. Policies can target a full wallet identifier or selected account indices and derivation paths. -Morpho SDK actions can require approvals, Permit or Permit2 signatures, or Morpho authorization before the final action. - -| Requirement source | Final action | -|--------------------|--------------| -| `getSupplyRequirements()` | `supply()` | -| `getSupplyCollateralRequirements()` | `supplyCollateral()` | -| `getBorrowRequirements()` | `borrow()` | -| `getRepayRequirements()` | `repay()` | +When a policy governs an account, wrapped write operations are default-denied unless a matching `ALLOW` permits them. For approval limits, start with an explicit `ALLOW` baseline and add narrower `DENY` rules for blocked cases. -For EOA accounts, send returned transaction requirements before the final operation. For signature requirements, call the returned requirement's `sign(client, userAddress)` method and pass the result as `requirementSignature`. - -```javascript title="Resolve EOA requirements before the final action" -async function resolveRequirements({ account, walletClient, userAddress, requirements }) { - let requirementSignature - - for (const requirement of requirements) { - if (typeof requirement.sign === 'function') { - requirementSignature = await requirement.sign(walletClient, userAddress) - continue - } +See [Transaction Policies](/sdk/core-module/guides/transaction-policies) for policy scope, evaluation order, simulation, and error-handling examples. - await account.sendTransaction({ - to: requirement.to, - value: requirement.value, - data: requirement.data - }) - } +## Handling Responses - return requirementSignature -} +The `sendTransaction` method returns a [transaction result object](/sdk/core-module/api-reference). The most important field is typically `hash`, which represents the transaction ID on the blockchain. You can use this hash to track the status of your payment on a block explorer. -const requirements = await morpho.getSupplyRequirements({ - token: USDT, - amount: 1000000n -}) +## Multi-Chain Transactions -const requirementSignature = await resolveRequirements({ - account, - walletClient, - userAddress, - requirements -}) +You can orchestrate payments across different chains in a single function by acting on multiple account objects sequentially. -const tx = await morpho.supply({ - token: USDT, - amount: 1000000n, - requirementSignature -}) -``` +The following example will: +1. Retrieve an ETH and ton account using the `getAccount()` method. +2. Send ETH and `await` the transaction. +3. Send TON and `await` the transaction. -Create `walletClient` with `viem` using the same signer and address as the WDK EVM account. Borrow requirements are authorization transactions, so send them before `borrow()`: +```typescript title="Multi-Chain Payment" +async function sendCrossChainPayments(wdk) { + const ethAccount = await wdk.getAccount('ethereum', 0) + const tonAccount = await wdk.getAccount('ton', 0) -```javascript title="Resolve borrow authorization requirements" -const requirements = await morpho.getBorrowRequirements({ - token: USDT, - amount: 1000000n -}) + // 1. Send ETH + await ethAccount.sendTransaction({ + to: '0x...', + value: 1000000000000000000n + }) -for (const requirement of requirements) { - await account.sendTransaction({ - to: requirement.to, - value: requirement.value, - data: requirement.data + // 2. Send TON + await tonAccount.sendTransaction({ + to: 'EQ...', + value: 1000000000n }) } - -await morpho.borrow({ - token: USDT, - amount: 1000000n -}) ``` -For ERC-4337 accounts, you can batch returned transaction requirements with your account-level flow when supported by the wallet module. Signature requirements still need to be signed before the final action. +## Next Steps - -Morpho SDK enforces builder and executor invariants for bundled actions. In this WDK adapter, `onBehalfOf` and vault or collateral withdrawal `to` must equal the connected wallet address when set. - +For more complex interactions like swapping tokens or bridging assets, learn how to [integrate protocols](/sdk/core-module/guides/protocol-integration). To guard writes before they execute, add [local transaction policies](/sdk/core-module/guides/transaction-policies). -## Quotes before sending +*** -Quote helpers build the target transaction and return the account-level fee estimate without sending it: +## Register Wallets +URL: https://docs.wdk.tether.io/sdk/core-module/guides/wallet-registration +Description: Learn how to register wallet modules for different blockchains. -```javascript title="Quote Morpho operations" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' -const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' +This guide explains how to register wallet modules with your WDK instance. The WDK Core module itself doesn't contain blockchain-specific logic; instead, you register separate modules for each chain you want to support (e.g., Ethereum, TON, Bitcoin). -const supplyQuote = await morpho.quoteSupply({ token: USDT, amount: 1000000n }) -const withdrawQuote = await morpho.quoteWithdraw({ token: USDT, amount: 1000000n }) -const collateralQuote = await morpho.quoteSupplyCollateral({ - token: COLLATERAL, - amount: 1000000000000000000n -}) -const borrowQuote = await morpho.quoteBorrow({ token: USDT, amount: 1000000n }) -const repayQuote = await morpho.quoteRepay({ token: USDT, amount: 'max' }) +## How it works -console.log({ - supplyQuote, - withdrawQuote, - collateralQuote, - borrowQuote, - repayQuote -}) -``` +The WDK uses a builder pattern, allowing you to chain `.registerWallet()` calls. Each call connects a blockchain-specific manager to your central WDK instance. - -Quotes do not guarantee that a later transaction will succeed if balances, allowances, authorization, market state, or vault state change. - +### Parameters -## ERC-4337 smart accounts +The `registerWallet` method (see [API Reference](/sdk/core-module/api-reference)) requires three arguments: -You can use the same methods with [`WalletAccountEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and pass a second `config` argument for per-call gas payment overrides. +1. **Symbol**: A unique string identifier for the chain (e.g., `'ethereum'`, `'ton'`). You will use this ID later to retrieve accounts. +2. **Manager Class**: The wallet manager class imported from the specific module (e.g., `WalletManagerEvm`). +3. **Configuration**: An object containing the chain-specific settings (e.g., RPC providers, API keys). -```javascript title="Supply with an ERC-4337 paymaster override" -const result = await morpho.supply( - { - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) +## Installation -console.log('Supply hash:', result.hash) -``` +Install the [wallet managers](/sdk/wallet-modules/) for the blockchains you want to support: -Use token addresses that exist on the same chain as the smart account RPC. +```bash +npm install @tetherto/wdk-wallet-evm @tetherto/wdk-wallet-tron @tetherto/wdk-wallet-btc +``` -## Reading positions +## Example: Registering Multiple Wallets -Read the configured vault position with [`getVaultPosition()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +### Import the Wallet Manager Packages -```javascript title="Read configured vault position" -const vaultPosition = await morpho.getVaultPosition() +First, import the necessary wallet manager packages: -console.log({ - shares: vaultPosition.shares, - assets: vaultPosition.assets, - vaultAddress: vaultPosition.vaultAddress -}) +```typescript title="Import Modules" +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import WalletManagerTron from '@tetherto/wdk-wallet-tron' +import WalletManagerBtc from '@tetherto/wdk-wallet-btc' ``` -Read the configured market position with [`getMarketPosition()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +### Register the Wallets -```javascript title="Read configured market position" -const marketPosition = await morpho.getMarketPosition() +Then, [instantiate WDK](/sdk/core-module/guides/getting-started#initialize-wdk) and chain the registration calls: -console.log({ - supplyShares: marketPosition.supplyShares, - borrowShares: marketPosition.borrowShares, - borrowAssets: marketPosition.borrowAssets, - collateral: marketPosition.collateral, - marketId: marketPosition.marketId -}) +```typescript title="Register Wallets" +const wdk = new WDK(seedPhrase) + // 1. Register Ethereum + .registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' + }) + // 2. Register TRON + .registerWallet('tron', WalletManagerTron, { + provider: 'https://api.trongrid.io' + }) + // 3. Register Bitcoin + .registerWallet('bitcoin', WalletManagerBtc, { + provider: 'https://blockstream.info/api' + }) ``` -Read both configured positions with [`getAccountData()`](/sdk/lending-modules/lending-morpho-evm/api-reference): - -```javascript title="Read combined Morpho account data" -const data = await morpho.getAccountData() + +**RPC Providers:** The examples use public RPC endpoints for demonstration. We do not endorse any specific provider. +* **Testnets:** You can find public RPCs for Ethereum and other EVM chains on [Chainlist](https://chainlist.org). +* **Mainnet:** For production environments, we recommend using reliable, paid RPC providers to ensure stability. + -console.log({ - vaultAssets: data.vaultAssets, - marketBorrowAssets: data.marketBorrowAssets, - collateral: data.collateral, - vaultAddress: data.vaultAddress, - marketId: data.marketId -}) -``` + +**TRON Networks:** Choose the correct provider for your environment. +* **Mainnet:** `https://api.trongrid.io` +* **Shasta (Testnet):** `https://api.shasta.trongrid.io` + ## Next Steps -- [Handle errors](handle-errors) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-morpho-evm/api-reference) +Once your wallets are registered, you can [manage accounts and specific addresses](/sdk/core-module/guides/account-management). *** -## Lending Morpho EVM Guides -URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/usage -Description: How to install and use @morpho-org/wdk-protocol-lending-morpho-evm on EVM - -# Usage +## Usage +URL: https://docs.wdk.tether.io/sdk/core-module/usage +Description: Guide to using the WDK Core module. -The [@morpho-org/wdk-protocol-lending-morpho-evm](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) community module exposes Morpho Vault V2 and Morpho Blue operations for WDK-compatible EVM accounts. Follow the guides below for setup, lending operations, and error handling. +The WDK Core module is the central orchestrator for your wallet interactions. - -Install the package, create MorphoProtocolEvm, and review prerequisites. + +Install and instantiate the WDK. - -Supply, withdraw, manage collateral, borrow, repay, quotes, requirements, and position reads. + +Connect specific blockchains (Ethereum, TON, etc.). - -Handle target, token, requirement, and transaction failures. + +Retrieve accounts and check balances. - - - - -Get started with WDK in a Node.js environment + +Transfer native tokens. - -Presets, explicit targets, and Morpho SDK options + +Allow, deny, and simulate writes before execution. - -Methods and parameters for MorphoProtocolEvm + +Register and access protocol providers from wallet accounts. + + +Add logging and failover protection. + + +Handle errors, dispose wallets, and clean up app-owned seed buffers. - - *** -## Swap Modules Overview -URL: https://docs.wdk.tether.io/sdk/swap-modules -Description: Explore WDK swap modules for token swap integrations across supported providers. - -The Swap Development Kit (WDK) provides a set of modules that support swap on top of multiple blockchain networks. All modules share a common interface, ensuring consistent behavior across different blockchain implementations. +## Fiat Modules Overview +URL: https://docs.wdk.tether.io/sdk/fiat-modules +Description: Explore WDK fiat modules for on-ramp and off-ramp integrations. - -WDK is introducing the [swidge interface](/sdk/swidge-modules) as the preferred interface for new swap, bridge, and combined route providers. Existing swap modules remain supported, but new protocol integrations should prefer swidge because the standalone swap interface is expected to be deprecated in a future release once swidge provider coverage is available. - +The Wallet Development Kit (WDK) provides fiat modules that enable on-ramp and off-ramp functionality, allowing users to seamlessly convert between fiat currencies and cryptocurrencies within your application. -## Swap Protocol Modules +## Fiat Protocol Modules -DeFi swap functionality for token exchanges across different DEXs: +On-ramp and off-ramp functionality for fiat currency integration: -| Module | Blockchain | Status | Documentation | -|--------|------------|--------|---------------| -| [`@tetherto/wdk-protocol-swap-velora-evm`](https://github.com/tetherto/wdk-protocol-swap-velora-evm) | EVM | ✅ Ready | [Documentation](/sdk/swap-modules/swap-velora-evm/) | +| Module | Provider | Status | Documentation | +|--------|----------|--------|---------------| +| [`@tetherto/wdk-protocol-fiat-moonpay`](https://github.com/tetherto/wdk-protocol-fiat-moonpay) | MoonPay | ✅ Ready | [Documentation](/sdk/fiat-modules/fiat-moonpay/) | -## Next steps +## Features -To get started with WDK modules, follow these steps: +Fiat modules provide: -1. Get up and running quickly with our [Quickstart Guide](/start-building/nodejs-bare-quickstart) -2. Choose the modules that best fit your needs from the tables above -3. Check specific documentation for modules you wish to use +- **On-Ramp**: Allow users to purchase cryptocurrency using fiat currencies (credit card, bank transfer, etc.) +- **Off-Ramp**: Enable users to sell cryptocurrency and receive fiat currencies +- **Multiple Payment Methods**: Support for various payment options depending on the provider +- **KYC Integration**: Built-in Know Your Customer verification flows +- **Multi-Currency Support**: Support for multiple fiat and cryptocurrencies -You can also: +## Next Steps -- Learn about key concepts like [Account Abstraction](/resources/concepts#account-abstraction) and other important definitions -- Use one of our ready-to-use examples to be production ready +To get started with WDK fiat modules, follow these steps: -## Swidge provider routes +1. Get up and running quickly with our [Quickstart Guide](/start-building/nodejs-bare-quickstart) +2. Choose the fiat module that best fits your needs from the table above +3. Check specific documentation for the module you wish to use -For new swap or bridge provider integrations, prefer the [Swidge protocol interface](/sdk/swidge-modules). Swidge can represent swap-only routes, bridge-only routes, and combined swap-and-bridge routes. Existing standalone swap module references remain available for released modules that have not moved to Swidge. +You can also: -For a released community provider, see [Orchestra](/sdk/swidge-modules/swidge-orchestra), which implements Swidge for BTC and stablecoin routes returned by Flashnet Orchestra. +- Learn about key concepts in our [Concepts](/resources/concepts) page +- Explore [wallet modules](/sdk/wallet-modules/) to manage user wallets +- Check our [examples](/examples-and-starters/react-native-starter) for production-ready implementations *** -## Swap velora EVM Overview -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm -Description: Overview of the @tetherto/wdk-protocol-swap-velora-evm module - -A lightweight package that lets EVM wallet accounts swap tokens using the velora aggregator. It provides a clean SDK for token swaps on EVM chains and works with both standard wallets and ERC‑4337 smart accounts. - -## Features +## On-ramp and off-ramp with MoonPay +URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay +Description: Generate MoonPay widget URLs for buying and selling crypto with fiat inside a WDK app. -- **Token Swapping**: Execute token swaps through velora on supported EVM networks -- **Account Abstraction**: Compatible with standard EVM accounts and ERC‑4337 smart accounts -- **Fee Controls**: Optional `swapMaxFee` to cap gas costs -- **Allowance Safety**: Handles USD₮ mainnet pattern (reset allowance to 0 before approve) -- **Provider Flexibility**: Works with JSON‑RPC URLs and EIP‑1193 providers -- **TypeScript Support**: Full TypeScript definitions included +Use the MoonPay fiat module to generate signed or unsigned widget URLs that let users buy and sell cryptocurrency with fiat inside your application. Provide a `signUrl` callback to return signed URLs from a trusted backend, or omit it to use unsigned widget URLs directly. -## Supported Networks +Get started by reading the [Usage](/sdk/fiat-modules/fiat-moonpay/usage) guide. -Works with EVM networks supported by velora (e.g., Ethereum, Polygon, Arbitrum, etc.). A working RPC provider is required. + +This module requires a MoonPay developer account. [Create your account here](https://dashboard.moonpay.com/signup). + -## Wallet Compatibility +## Features -The swap service supports multiple EVM wallet types: +- **Fiat On-Ramp**: Generate signed or unsigned widget URLs for users to buy cryptocurrency with fiat +- **Fiat Off-Ramp**: Generate signed or unsigned widget URLs for users to sell cryptocurrency with fiat +- **Price Quotes**: Get real-time quotes for buy and sell operations +- **Transaction Tracking**: Retrieve transaction status and details +- **Currency Support**: Query supported cryptocurrencies, fiat currencies, and countries +- **Customizable Widget**: Configure colors, themes, language, and behavior -- **Standard EVM Wallets**: `@tetherto/wdk-wallet-evm` accounts -- **ERC‑4337 Smart Accounts**: `@tetherto/wdk-wallet-evm-erc-4337` accounts with bundler/paymaster -- **Read‑Only Accounts**: For quoting swaps without sending transactions +## Check Current Availability -## Key Components +Assets, networks, payment methods, and regional eligibility are controlled by MoonPay and can vary by account, environment, country, and transaction direction. Do not build a permanent allowlist from this page. -- **velora Integration**: Uses velora aggregator for routing and quotes -- **Quote System**: Pre‑transaction fee and amount estimation via `quoteSwap` -- **AA Integration**: Optional paymaster token and fee cap overrides when using ERC‑4337 -- **Allowance Management**: Approve flow handled automatically when required +Use `getSupportedCryptoAssets()`, `getSupportedFiatCurrencies()`, and `getSupportedCountries()` at runtime, then let the MoonPay widget perform its final eligibility checks. For provider-level payment-method details, see [MoonPay's supported payment methods](https://support.moonpay.com/en/articles/380823-moonpay-s-supported-payment-methods). ## Next Steps - -Get started with WDK in a Node.js environment - - -Get started with WDK's velora Swap Protocol configuration + +Set up your MoonPay API key, optional signing callback, and environment - -Get started with WDK's velora Swap Protocol API + +Learn how to integrate MoonPay in your application - -Get started with WDK's velora Swap Protocol usage + +Complete API documentation for the module -*** - -## Need Help? - - +--- -*** +### MoonPay Resources -## API Reference -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/api-reference -Description: API Reference for @tetherto/wdk-protocol-swap-velora-evm +- [MoonPay Dashboard](https://dashboard.moonpay.com/signup) - Create your developer account +- [MoonPay Support Center](https://support.moonpay.com/) - Official MoonPay documentation and support +- [Supported Payment Methods](https://support.moonpay.com/en/articles/380823-moonpay-s-supported-payment-methods) - Full list by country -## Class: VeloraProtocolEvm +--- -Main class for velora token swaps on EVM. +### Need Help? -### Constructor + -```javascript -new VeloraProtocolEvm(account, config?) -``` +*** -Parameters: -- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` -- `config` (optional): - - `swapMaxFee` (`bigint`): maximum total gas fee allowed (wei) +## Fiat MoonPay API Reference +URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/api-reference +Description: API Reference for the @tetherto/wdk-protocol-fiat-moonpay module -Example: +# API Reference -```javascript -const swap = new VeloraProtocolEvm(account, { swapMaxFee: 200000000000000n }) -``` +Complete API documentation for the `@tetherto/wdk-protocol-fiat-moonpay` module. -### Methods +## Constructor -| Method | Description | Returns | -|--------|-------------|---------| -| `swap(options, config?)` | Perform a token swap | `Promise<{hash: string, fee: bigint, tokenInAmount: bigint, tokenOutAmount: bigint, approveHash?: string, resetAllowanceHash?: string}>` | -| `quoteSwap(options, config?)` | Get estimated fee and amounts | `Promise<{fee: bigint, tokenInAmount: bigint, tokenOutAmount: bigint}>` | +### `new MoonPayProtocol(account, config)` ---- +Creates a new MoonPayProtocol instance. -### `swap(options, config?)` -Execute a swap via velora. +**Parameters:** -Options: -- `tokenIn` (`string`): Address of the ERC‑20 token to sell -- `tokenOut` (`string`): Address of the ERC‑20 token to buy -- `tokenInAmount` (`bigint`, optional): Exact input amount (base units) -- `tokenOutAmount` (`bigint`, optional): Exact output amount (base units) -- `to` (`string`, optional): Recipient address (defaults to account address) +| Name | Type | Description | +|------|------|-------------| +| `account` | `IWalletAccount` \| `IWalletAccountReadOnly` \| `undefined` | Wallet account for transactions | +| `config` | `MoonPayProtocolConfig` | Configuration object | -Config (ERC‑4337 only): -- `paymasterToken` (`string`, optional): Token symbol/address for fee sponsorship -- `swapMaxFee` (`bigint`, optional): Per‑swap fee cap (wei) +**Config Options:** -Returns: -- Standard account: `{ hash, fee, tokenInAmount, tokenOutAmount, approveHash?, resetAllowanceHash? }` -- ERC‑4337 account: `{ hash, fee, tokenInAmount, tokenOutAmount }` (approve may be bundled) +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `apiKey` | string | Yes | - | Your MoonPay publishable API key | +| `signUrl` | function | No | - | Callback used to sign buy and sell widget URLs through a trusted backend | +| `cacheTime` | number | No | `600000` | Cache duration for currencies (ms) | +| `environment` | `'production' \| 'sandbox'` | No | `production` | MoonPay widget URL endpoint set | -Notes: -- On Ethereum mainnet, selling USD₮ may first set allowance to 0, then approve. -- Requires a provider; requires a non read‑only account to send transactions. +**Example:** -Example: +```typescript +import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; -```javascript -const tx = await swap.swap({ - tokenIn: '0xdAC17F...ec7', // USD₮ - tokenOut: '0xC02a...6Cc2', // WETH - tokenInAmount: 1000000n -}) +const moonpay = new MoonPayProtocol(walletAccount, { + apiKey: 'pk_live_xxxxx', + environment: 'production', +}); ``` +Omitting `signUrl` returns unsigned widget URLs. For signed URLs, use the backend callback pattern in [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration); returning the input unchanged does not sign it. + --- -### `quoteSwap(options, config?)` -Get estimated fee and token in/out amounts. +## Methods -Options are the same as `swap`. +### `buy(options)` -Returns: `{ fee, tokenInAmount, tokenOutAmount }` +Generates a MoonPay widget URL for purchasing cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. -Config (ERC‑4337 only): -- `paymasterToken` (`string`, optional): Token symbol/address for fee sponsorship +**Parameters:** -Works with read‑only accounts. +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `options.cryptoAsset` | string | Yes | Cryptocurrency code (e.g., 'eth', 'btc') | +| `options.fiatCurrency` | string | Yes | Fiat currency code (e.g., 'usd', 'eur') | +| `options.cryptoAmount` | number \| bigint | No* | Amount in smallest crypto units | +| `options.fiatAmount` | number \| bigint | No* | Amount in smallest fiat units (cents) | +| `options.recipient` | string | No | Wallet address (uses account address if not provided) | +| `options.config` | MoonPayBuyParams | No | Widget configuration options | -Example: +*Either `cryptoAmount` or `fiatAmount` must be provided, but not both. -```javascript -const quote = await swap.quoteSwap({ - tokenIn: '0xdAC17F...ec7', // USD₮ - tokenOut: '0xC02a...6Cc2', // WETH - tokenOutAmount: 500000000000000000n // 0.5 WETH -}) -``` +**Returns:** `Promise\<{ buyUrl: string }\>` --- -## Errors - -Common errors include: -- Insufficient liquidity / no route for pair -- Fee exceeds `swapMaxFee` -- Read‑only account cannot send swaps -- Provider/RPC errors (invalid endpoint, network mismatch) - ---- +### `sell(options)` -## Types +Generates a MoonPay widget URL for selling cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. -- `swapMaxFee: bigint` — Upper bound for gas fees (wei) -- `tokenInAmount/tokenOutAmount: bigint` — ERC‑20 base units -- `paymasterToken: string` — token symbol or address (AA only) +**Parameters:** - - -Get started with WDK in a Node.js environment - - -Get started with WDK's Swap velora EVM Protocol configuration - - -Get started with WDK's Swap velora EVM Protocol usage - - +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `options.cryptoAsset` | string | Yes | Cryptocurrency code | +| `options.fiatCurrency` | string | Yes | Fiat currency code | +| `options.cryptoAmount` | number \| bigint | No* | Amount in smallest crypto units | +| `options.fiatAmount` | number \| bigint | No* | Amount in smallest fiat units | +| `options.refundAddress` | string | No | Refund wallet address | +| `options.config` | MoonPaySellParams | No | Widget configuration options | -*** +**Returns:** `Promise\<{ sellUrl: string }\>` -## Need Help? +--- - +### `quoteBuy(options)` -*** +Gets a price quote for a cryptocurrency purchase. -## Configuration -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/configuration -Description: Configuration options and settings for @tetherto/wdk-protocol-swap-velora-evm +**Parameters:** -## Swap Service Configuration +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `options.cryptoAsset` | string | Yes | Cryptocurrency code | +| `options.fiatCurrency` | string | Yes | Fiat currency code | +| `options.cryptoAmount` | number \| bigint | No* | Amount in smallest crypto units | +| `options.fiatAmount` | number \| bigint | No* | Amount in smallest fiat units | +| `options.config` | MoonPayQuoteBuyParams | No | Quote parameters | -The `VeloraProtocolEvm` accepts a configuration object that defines fee controls and behavior: +**Returns:** `Promise\` -```javascript -import VeloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +```typescript +{ + cryptoAmount: bigint, // Crypto amount you'll receive + fiatAmount: bigint, // Fiat amount to pay + fee: bigint, // Total fee amount + rate: string, // Exchange rate + metadata: MoonPayBuyQuoteMetadata +} +``` + +--- + +### `quoteSell(options)` + +Gets a price quote for selling cryptocurrency. + +**Parameters:** + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `options.cryptoAsset` | string | Yes | Cryptocurrency code | +| `options.fiatCurrency` | string | Yes | Fiat currency code | +| `options.cryptoAmount` | number \| bigint | Yes | Amount in smallest crypto units | +| `options.config` | MoonPayQuoteSellParams | No | Quote parameters | + +**Returns:** `Promise\` + +```typescript +{ + cryptoAmount: bigint, // Crypto amount to sell + fiatAmount: bigint, // Fiat amount you'll receive + fee: bigint, // Total fee amount + rate: string, // Exchange rate + metadata: MoonPaySellQuoteMetadata +} +``` + +--- + +### `getSupportedCryptoAssets()` + +Fetches the list of supported cryptocurrencies. Results are cached. + +**Returns:** `Promise\` + +```typescript +{ + code: string, // Currency code (e.g., 'eth') + decimals: number, // Decimal places + networkCode: string, // Network identifier + name: string, // Display name + metadata: MoonPayCryptoCurrencyDetails +} +``` + +--- + +### `getSupportedFiatCurrencies()` + +Fetches the list of supported fiat currencies. Results are cached. + +**Returns:** `Promise\` + +```typescript +{ + code: string, // Currency code (e.g., 'usd') + decimals: number, // Decimal places + name: string, // Display name + metadata: MoonPayFiatCurrencyDetails +} +``` + +--- + +### `getSupportedCountries()` + +Fetches the list of supported countries. + +**Returns:** `Promise\` + +```typescript +{ + code: string, // ISO country code + name: string, // Country name + isBuyAllowed: boolean, // Buy operations allowed + isSellAllowed: boolean,// Sell operations allowed + metadata: MoonPayCountryDetail +} +``` + +--- + +### `getTransactionDetail(txId, direction?)` + +Retrieves details of a specific transaction. + +**Parameters:** + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `txId` | string | Yes | - | MoonPay transaction ID | +| `direction` | `'buy' \| 'sell'` | No | `'buy'` | Transaction type | + +**Returns:** `Promise\` + +```typescript +{ + status: 'completed' | 'failed' | 'in_progress', + cryptoAsset: string, + fiatCurrency: string, + metadata: MoonPayBuyTransaction | MoonPaySellTransaction +} +``` + +Treat `metadata` as potentially sensitive provider data. Avoid logging or retaining the complete object when the application needs only the normalized status and asset fields. + +--- + +## Types + +### `MoonPayProtocolConfig` + +```typescript +interface MoonPayProtocolConfig { + apiKey: string; + signUrl?: (urlForSignature: string) => Promise; + cacheTime?: number; + environment?: 'production' | 'sandbox'; +} +``` + +### `MoonPayBuyParams` + +Widget configuration options for `buy()` operations: + +```typescript +interface MoonPayBuyParams { + // UI options (shared with MoonPaySellParams) + colorCode?: string; + theme?: 'dark' | 'light'; + themeId?: string; + language?: string; + showAllCurrencies?: boolean; + showOnlyCurrencies?: string; + showWalletAddressForm?: boolean; + redirectURL?: string; + unsupportedRegionRedirectUrl?: string; + skipUnsupportedRegionScreen?: boolean; + + // Buy-specific options + defaultCurrencyCode?: string; + walletAddress?: string; + walletAddressTag?: string; + walletAddresses?: string; + walletAddressTags?: string; + contractAddress?: string; + networkCode?: string; + lockAmount?: boolean; + email?: string; + externalTransactionId?: string; + externalCustomerId?: string; + paymentMethod?: string; +} +``` + +### `MoonPaySellParams` + +Widget configuration options for `sell()` operations: + +```typescript +interface MoonPaySellParams { + // UI options (shared with MoonPayBuyParams) + colorCode?: string; + theme?: 'dark' | 'light'; + themeId?: string; + language?: string; + showAllCurrencies?: boolean; + showOnlyCurrencies?: string; + showWalletAddressForm?: boolean; + redirectURL?: string; + unsupportedRegionRedirectUrl?: string; + skipUnsupportedRegionScreen?: boolean; + + // Sell-specific options + defaultBaseCurrencyCode?: string; + refundWalletAddresses?: string; + lockAmount?: boolean; + email?: string; + externalTransactionId?: string; + externalCustomerId?: string; + paymentMethod?: string; +} +``` + +### `MoonPayQuoteBuyParams` + +```typescript +interface MoonPayQuoteBuyParams { + extraFeePercentage?: number; // 0-10% + paymentMethod?: string; + areFeesIncluded?: boolean; + walletAddress?: string; +} +``` + +### `MoonPayQuoteSellParams` + +```typescript +interface MoonPayQuoteSellParams { + extraFeePercentage?: number; // 0-10% + payoutMethod?: string; +} +``` + +--- + +## Next Steps + +- [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) - Setup and configuration options +- [Usage Guide](/sdk/fiat-modules/fiat-moonpay/usage) - Common usage patterns + +*** + +## Fiat MoonPay Configuration +URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/configuration +Description: Configuration options for the @tetherto/wdk-protocol-fiat-moonpay module + +# Configuration + +This page covers all configuration options for the MoonPay fiat module, including optional URL signing and environment selection. + +## Prerequisites + +Before using this module, you need: + +1. A MoonPay developer account - [Create an account on MoonPay Dashboard](https://dashboard.moonpay.com/signup) +2. A publishable API key from your dashboard +3. If you want signed widget URLs, a trusted backend signing endpoint for the `signUrl` callback + +## Installation + +```bash +npm install @tetherto/wdk-protocol-fiat-moonpay +``` + +## Basic Configuration + +```typescript +import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; + +const moonpay = new MoonPayProtocol(walletAccount, { + apiKey: 'pk_live_xxxxx', // Your MoonPay publishable API key + signUrl: async (urlForSignature) => { + const response = await fetch('/api/moonpay/sign-url', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ urlForSignature }), + }); + + if (!response.ok) { + throw new Error(`Failed to sign MoonPay URL: ${response.status} ${response.statusText}`); + } + + const { signedUrl } = await response.json(); + + return signedUrl; + }, + environment: 'sandbox', +}); +``` + +## Configuration Options + +| Option | Type | Required | Default | Description | +|--------|------|----------|---------|-------------| +| `apiKey` | string | Yes | - | Your MoonPay publishable API key | +| `signUrl` | function | No | - | Callback used to sign buy and sell widget URLs through a trusted backend | +| `cacheTime` | number | No | `600000` (10 min) | Duration in milliseconds to cache supported currencies | +| `environment` | `'production' \| 'sandbox'` | No | `production` | MoonPay widget URL endpoint set | + + +Keep the MoonPay signing secret on the backend. Authenticate the caller and validate or reconstruct the MoonPay URL before signing it, including its origin and partner-owned parameters. A backend that signs any caller-supplied URL becomes a signing oracle. + + +## Constructor Overloads + +The `MoonPayProtocol` class supports three constructor patterns: + +```typescript +// Without account (for public read operations like fetching supported currencies) +const moonpay = new MoonPayProtocol(undefined, config); + +// With read-only account +const moonpay = new MoonPayProtocol(readOnlyAccount, config); + +// With full wallet account (for buy/sell operations) +const moonpay = new MoonPayProtocol(walletAccount, config); +``` + +## Environment Configuration + +### Sandbox (Testing) + +Use sandbox endpoints for development and testing: + +```typescript +const moonpay = new MoonPayProtocol(walletAccount, { + apiKey: 'pk_test_xxxxx', + signUrl: async (urlForSignature) => { + const response = await fetch('/api/moonpay/sign-url', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ urlForSignature }), + }); + + return (await response.json()).signedUrl; + }, + environment: 'sandbox', +}); +``` + +In sandbox mode: +- No real transactions are processed +- Use test card numbers provided by MoonPay +- KYC verification is simulated + +If you do not need signed URLs, omit `signUrl` and the protocol returns unsigned widget URLs directly. + +### Production + +For production deployments, use live API keys and the production endpoint set: + +```typescript +const moonpay = new MoonPayProtocol(walletAccount, { + apiKey: 'pk_live_xxxxx', + signUrl: async (urlForSignature) => { + const response = await fetch('/api/moonpay/sign-url', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ urlForSignature }), + }); + + return (await response.json()).signedUrl; + }, + environment: 'production', +}); +``` + +## Widget Customization + +When calling `buy()` or `sell()`, you can customize the MoonPay widget appearance: + +```typescript +const result = await moonpay.buy({ + cryptoAsset: 'eth', + fiatCurrency: 'usd', + fiatAmount: 10000n, // $100.00 in cents + config: { + colorCode: '#3B82F6', // Your brand color (hex) + theme: 'dark', // 'dark' or 'light' + language: 'en', // ISO 639-1 language code + redirectURL: 'https://yourapp.com/callback', + }, +}); +``` + +### Available Buy Widget Options + +| Option | Type | Description | +|--------|------|-------------| +| `colorCode` | string | Hexadecimal color for widget accent | +| `theme` | `'dark' \| 'light'` | Widget appearance theme | +| `themeId` | string | ID of a custom theme | +| `language` | string | ISO 639-1 language code | +| `showAllCurrencies` | boolean | Show all supported cryptocurrencies | +| `showOnlyCurrencies` | string | Comma-separated currency codes to display | +| `showWalletAddressForm` | boolean | Show wallet address input form | +| `redirectURL` | string | URL to redirect after completion | +| `unsupportedRegionRedirectUrl` | string | URL for unsupported regions | +| `skipUnsupportedRegionScreen` | boolean | Skip unsupported region screen | +| `defaultCurrencyCode` | string | Pre-selected cryptocurrency code | +| `walletAddress` | string | Pre-filled wallet address | +| `walletAddressTag` | string | Wallet address memo/tag (for EOS, XRP, etc.) | +| `walletAddresses` | string | JSON string of wallet addresses for multiple currencies | +| `walletAddressTags` | string | JSON string of address tags for multiple currencies | +| `contractAddress` | string | Token contract address (DeFi Buy only) | +| `networkCode` | string | Network for the token contract (DeFi Buy only) | +| `lockAmount` | boolean | Prevent user from changing amount | +| `email` | string | Pre-fill customer email | +| `externalTransactionId` | string | Your transaction identifier | +| `externalCustomerId` | string | Your customer identifier | +| `paymentMethod` | string | Pre-select payment method | + +### Available Sell Widget Options + +For `sell()`, the widget config uses `MoonPaySellParams` with different options: + +| Option | Type | Description | +|--------|------|-------------| +| `colorCode` | string | Hexadecimal color for widget accent | +| `theme` | `'dark'` \| `'light'` | Widget appearance theme | +| `themeId` | string | ID of a custom theme | +| `language` | string | ISO 639-1 language code | +| `showAllCurrencies` | boolean | Show all supported cryptocurrencies | +| `showOnlyCurrencies` | string | Comma-separated currency codes to display | +| `showWalletAddressForm` | boolean | Show wallet address input form | +| `redirectURL` | string | URL to redirect after completion | +| `unsupportedRegionRedirectUrl` | string | URL for unsupported regions | +| `skipUnsupportedRegionScreen` | boolean | Skip unsupported region screen | +| `defaultBaseCurrencyCode` | string | Pre-selected cryptocurrency to sell | +| `refundWalletAddresses` | string | JSON string of wallet addresses for refunds | +| `lockAmount` | boolean | Prevent user from changing amount | +| `email` | string | Pre-fill customer email | +| `externalTransactionId` | string | Your transaction identifier | +| `externalCustomerId` | string | Your customer identifier | +| `paymentMethod` | string | Pre-select payout method | + +## Next Steps + +- [Usage Guide](/sdk/fiat-modules/fiat-moonpay/usage) - Learn how to integrate MoonPay +- [API Reference](/sdk/fiat-modules/fiat-moonpay/api-reference) - Complete API documentation + +*** + +## Buy and Sell +URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell +Description: On-ramp, off-ramp, quotes, supported assets, widget options, and custom recipients. + +This guide explains [buying crypto (on-ramp)](#buy-crypto-on-ramp), [selling crypto (off-ramp)](#sell-crypto-off-ramp), [quotes](#get-price-quotes), [supported currencies](#supported-currencies-and-countries), [widget customization](#widget-customization), and [custom recipients](#custom-recipient-addresses). It assumes a [`MoonPayProtocol`](/sdk/fiat-modules/fiat-moonpay/api-reference) instance named `moonpay`. + + +Amounts use smallest units: fiat in minor units (cents), crypto in on-chain base units (for example wei for ETH). + + +## Buy crypto (on-ramp) + +You can build a purchase URL with [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) when you know the fiat spend. The URL is signed only when the protocol was configured with a `signUrl` callback: + +```typescript title="Buy with fiat amount" +const result = await moonpay.buy({ + cryptoAsset: 'usdt', + fiatCurrency: 'usd', + fiatAmount: 10000n +}) + +window.open(result.buyUrl, '_blank') +``` + +You can request a fixed crypto amount instead by passing `cryptoAmount` to [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Buy with crypto amount" +const result = await moonpay.buy({ + cryptoAsset: 'eth', + fiatCurrency: 'usd', + cryptoAmount: 100000000000000000n +}) + +window.open(result.buyUrl, '_blank') +``` + +## Sell crypto (off-ramp) + +You can generate a sell widget URL with [`sell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Sell ETH for USD" +const result = await moonpay.sell({ + cryptoAsset: 'eth', + fiatCurrency: 'usd', + cryptoAmount: 500000000000000000n +}) + +window.open(result.sellUrl, '_blank') +``` + +## Get price quotes + +You can preview economics before opening the widget using [`quoteBuy()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Buy quote" +const buyQuote = await moonpay.quoteBuy({ + cryptoAsset: 'eth', + fiatCurrency: 'usd', + fiatAmount: 10000n +}) + +console.log('Crypto amount:', buyQuote.cryptoAmount) +console.log('Fee:', buyQuote.fee) +console.log('Exchange rate:', buyQuote.rate) +``` + +You can estimate proceeds for a sell with [`quoteSell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Sell quote" +const sellQuote = await moonpay.quoteSell({ + cryptoAsset: 'eth', + fiatCurrency: 'usd', + cryptoAmount: 500000000000000000n +}) + +console.log('Fiat amount:', sellQuote.fiatAmount) +``` + +## Supported currencies and countries + +You can list tradable assets with [`getSupportedCryptoAssets()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Supported crypto" +const cryptoAssets = await moonpay.getSupportedCryptoAssets() +console.log(cryptoAssets) +``` + +You can list fiat currencies with [`getSupportedFiatCurrencies()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Supported fiat" +const fiatCurrencies = await moonpay.getSupportedFiatCurrencies() +console.log(fiatCurrencies) +``` + +You can check regional availability with [`getSupportedCountries()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Supported countries" +const countries = await moonpay.getSupportedCountries() +console.log(countries) +``` + +## Widget customization + +You can pass UI options under `config` to [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) (see [`MoonPayBuyParams`](/sdk/fiat-modules/fiat-moonpay/api-reference)): + +```typescript title="Themed buy widget" +const result = await moonpay.buy({ + cryptoAsset: 'usdt', + fiatCurrency: 'eur', + fiatAmount: 5000n, + config: { + colorCode: '#1f2937', + theme: 'dark', + language: 'de', + redirectURL: 'https://yourapp.com/payment-complete', + lockAmount: true, + email: 'user@example.com', + externalCustomerId: 'user_123' + } +}) + +window.open(result.buyUrl, '_blank') +``` + +## Custom recipient addresses + +By default [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) credits the connected wallet. You can override the destination with `recipient`: + +```typescript title="Custom buy recipient" +const result = await moonpay.buy({ + cryptoAsset: 'eth', + fiatCurrency: 'usd', + fiatAmount: 10000n, + recipient: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' +}) + +window.open(result.buyUrl, '_blank') +``` + +You can set a refund destination on sells with `refundAddress` on [`sell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Custom sell refund address" +const result = await moonpay.sell({ + cryptoAsset: 'eth', + fiatCurrency: 'usd', + cryptoAmount: 500000000000000000n, + refundAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' +}) + +window.open(result.sellUrl, '_blank') +``` + +## Next Steps + +- [Manage transactions](/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions/) +- [Get started](/sdk/fiat-modules/fiat-moonpay/guides/get-started/) +- [API reference](/sdk/fiat-modules/fiat-moonpay/api-reference) + +*** + +## Get Started +URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/guides/get-started +Description: Install the package and initialize MoonPayProtocol with your wallet and keys. + +This guide covers [installation](#installation) and [initializing the protocol](#initialize-moonpayprotocol). You need [Node.js](https://nodejs.org/), [npm](https://www.npmjs.com/), and MoonPay API keys from your MoonPay dashboard. + +## Installation + +Run the following to install [@tetherto/wdk-protocol-fiat-moonpay](https://www.npmjs.com/package/@tetherto/wdk-protocol-fiat-moonpay): + +```bash title="Install with npm" +npm install @tetherto/wdk-protocol-fiat-moonpay +``` + +## Initialize MoonPayProtocol + +You can create a fiat ramp client with [`new MoonPayProtocol(account, config)`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Construct MoonPayProtocol" +import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay' + +const moonpay = new MoonPayProtocol(walletAccount, { + apiKey: process.env.MOONPAY_PUBLISHABLE_KEY, + environment: 'sandbox' +}) +``` + + +The package does not accept a MoonPay secret key. Omit `signUrl` to return unsigned widget URLs, or provide a callback that sends the URL to an authenticated backend. Keep the signing secret on that backend. + + +See [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) for secure URL signing, `cacheTime`, and related options. + +## Next Steps + +- [Buy and sell](/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell/) +- [Manage transactions](/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions/) + +*** + +## Manage Transactions +URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions +Description: Poll MoonPay for transaction status and inspect returned details. + +This guide shows how to [check transaction status](#check-transaction-status) and [read transaction details](#read-transaction-details) with [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference). Pass the identifier MoonPay returns after checkout (for example from your redirect URL or webhook payload). + +## Check transaction status + +You can read the high-level state of a buy with [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Buy transaction status" +const buyTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'buy') + +console.log('Status:', buyTx.status) +``` + +`status` is one of `completed`, `failed`, or `in_progress` as described in the API reference. + +## Read transaction details + +You can load the same record to inspect assets and currencies using [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Buy transaction fields" +const buyTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'buy') + +console.log('Crypto asset:', buyTx.cryptoAsset) +console.log('Fiat currency:', buyTx.fiatCurrency) +``` + + +The provider metadata can contain customer, payment, or transaction details. Minimize retention and do not write the raw object to application logs or analytics. + + +You can query a sell the same way by passing `sell` as the direction to [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +```typescript title="Sell transaction details" +const sellTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'sell') + +console.log('Status:', sellTx.status) +console.log('Crypto asset:', sellTx.cryptoAsset) +console.log('Fiat currency:', sellTx.fiatCurrency) +``` + + +The second argument defaults to `buy` when omitted; set it explicitly for sell flows. + + +## Next Steps + +- [Buy and sell](/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell/) +- [Get started](/sdk/fiat-modules/fiat-moonpay/guides/get-started/) +- [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) + +*** + +## Fiat MoonPay Usage +URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay/usage +Description: How to use the @tetherto/wdk-protocol-fiat-moonpay module + +# Usage + +The [@tetherto/wdk-protocol-fiat-moonpay](https://www.npmjs.com/package/@tetherto/wdk-protocol-fiat-moonpay) module builds signed or unsigned MoonPay widget URLs and quotes for on-ramp and off-ramp flows. Use the guides below for setup, trading, and transaction follow-up. + + + + +Install the package and initialize MoonPayProtocol. + + +On-ramp, off-ramp, quotes, supported assets, widget options, recipients. + + +Check status and load transaction details from MoonPay. + + + + + + +Get started with WDK in a Node.js environment + + +API keys, caching, and MoonPay configuration options + + +Constructor, methods, and types for MoonPayProtocol + + + + + +*** + +## Get Started +URL: https://docs.wdk.tether.io/sdk/get-started +Description: Learn about the SDK and modules architecture + +The SDK is a comprehensive, modular plug-in framework designed to simplify multi-chain wallet development. + +It is built on some core principles: **self-custodial and stateless** (private keys never leave your app and no data is stored by WDK), **unified interface** (consistent API across all blockchains), and **cross-platform compatibility** (works seamlessly from Node.js to React Native to embedded systems). + +#### Capabilities + +* **Multi-Chain Support**: Bitcoin, Ethereum, TON, TRON, Solana, Spark, and more +* **Account Abstraction**: Gasless transactions on supported chains +* **DeFi Integration**: Plug-in support for swidge routes, swaps, bridges, and lending protocols +* **Extensible Design**: Add custom modules for new blockchains or protocols + +*** + +### Modular Architecture + +WDK's architecture is built around the concept of composable modules. Each module is a specialized component that handles specific functionality, allowing you to build exactly what you need without unnecessary complexity. + + +Each module has a single responsibility. Wallet modules handle blockchain operations, protocol modules manage DeFi interactions, and the core module orchestrates everything. + +New functionality is added through modules rather than modifying core code. Also, modules are configured through simple objects, making them easy to customize for different environments and use cases. + +*** + +#### Module Types + +WDK modules are organized into six main categories, each serving a specific purpose in the blockchain application stack: + + + +Main orchestrator and shared utilities + + +Blockchain-specific wallet operations + + +Swap-only, bridge-only, or combined asset routes + + +Token swapping across DEXs + + +Cross-chain asset transfers + + +DeFi lending and borrowing + + + +*** + +### How to use the SDK + +The WDK SDK uses a registration-based system where modules are added to a central orchestrator. This creates a unified interface while maintaining module independence. + +#### Registration Flow + +**1. Core Module Initialization** + +```typescript title="Initialize WDK" +import WDK from '@tetherto/wdk' + +// Generate 24-word seed phrase for higher security +const seedPhrase = WDK.getRandomSeedPhrase(24) + +// Or use 12-word seed phrase (default) +// const seedPhrase = WDK.getRandomSeedPhrase() + +const wdk = new WDK(seedPhrase) +``` + +For production seed cleanup, see [Seed Lifecycle](/sdk/core-module/guides/error-handling#seed-lifecycle). + +**2. Wallet Module Registration** + +```typescript title="Register Wallets" +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import WalletManagerBtc from '@tetherto/wdk-wallet-btc' + +const wdkWithWallets = wdk + .registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' + }) + .registerWallet('bitcoin', WalletManagerBtc, { + provider: 'https://blockstream.info/api' + }) +``` + +**3. Protocol Module Registration** + +```typescript title="Register Protocols" +import SwapveloraEvm from '@tetherto/wdk-protocol-swap-velora-evm' + +const wdkWithProtocols = wdkWithWallets + .registerProtocol('swap-velora-evm', SwapveloraEvm) +``` + +#### Unified Operations + +Once registered, all modules work through the same interface: + +```typescript title="Unified Operations" +// Get accounts from different blockchains using the same method +const ethAccount = await wdkWithProtocols.getAccount('ethereum', 0) +const btcAccount = await wdkWithProtocols.getAccount('bitcoin', 0) + +// Check balances using unified interface +const ethBalance = await ethAccount.getBalance() +const btcBalance = await btcAccount.getBalance() + +// Send transactions with consistent API +const ethTx = await ethAccount.sendTransaction({ + to: '0x...', + value: '1000000000000000000' +}) + +const btcTx = await btcAccount.sendTransaction({ + to: '1A1z...', + value: 100000000 +}) + +// Use DeFi protocols through the same interface +const swapResult = await wdkWithProtocols.executeProtocol('swap-velora-evm', { + fromToken: 'ETH', + toToken: 'USDT', + amount: '1000000000000000000' +}) +``` + +*** + +### Creating Custom Modules + +WDK's modular architecture makes it straightforward to add support for new blockchains or protocols. Each module type has a specific interface that must be implemented. + +#### Wallet Module Interface + +```typescript title="Custom Wallet Module Setup" +interface WalletModule { + // Account management + getAccount(index: number): Promise + getAddress(index: number): Promise + getBalance(index: number): Promise + + // Transaction operations + sendTransaction(params: TransactionParams): Promise + estimateTransaction(params: TransactionParams): Promise + + // Key management + signMessage(message: string, index: number): Promise + verifySignature(message: string, signature: string, address: string): Promise + + // Blockchain-specific operations + getTransactionHistory(index: number, limit?: number): Promise + getTokenBalance(index: number, tokenAddress: string): Promise +} +``` + +#### Protocol Module Interface + +```typescript title="Custom Protocol Module Setup" +interface ProtocolModule { + // Protocol execution + execute(params: ProtocolParams): Promise + estimate(params: ProtocolParams): Promise + + // Supported operations + getSupportedTokens(): Promise + getSupportedChains(): Promise + getOperationTypes(): Promise + + // Protocol-specific methods + getLiquidityPools?(): Promise + getLendingRates?(): Promise + getBridgeRoutes?(): Promise +} +``` + +#### Module Implementation Example + +```typescript title="Custom Wallet Module Implementation" +class CustomWalletModule implements WalletModule { + private provider: string + private chainId: number + + constructor(config: { provider: string; chainId: number }) { + this.provider = config.provider + this.chainId = config.chainId + } + + async getAccount(index: number): Promise { + // Implement account derivation logic + const privateKey = await this.derivePrivateKey(index) + return new CustomAccount(privateKey, this.provider) + } + + async getAddress(index: number): Promise { + const account = await this.getAccount(index) + return account.getAddress() + } + + async getBalance(index: number): Promise { + const address = await this.getAddress(index) + // Implement balance fetching logic + const balance = await this.fetchBalance(address) + return new BigNumber(balance) + } + + async sendTransaction(params: TransactionParams): Promise { + // Implement transaction sending logic + const account = await this.getAccount(params.accountIndex) + const tx = await account.sendTransaction(params) + return tx + } + + // Additional methods... +} +``` + +#### Module Registration + +```typescript title="Custom Wallet Module Registration" +// Register your custom module +const wdkWithCustom = wdk.registerWallet('custom-chain', CustomWalletModule, { + provider: 'https://custom-rpc-endpoint.com', + chainId: 12345 +}) + +// Use it like any other module +const customAccount = await wdkWithCustom.getAccount('custom-chain', 0) +const balance = await customAccount.getBalance() +``` + + +*** + +### Quickstart Paths + +Ready to start building? Choose your development environment: + + + +Get started with WDK in a Node.js environment + + +Build mobile wallets with React Native Expo + + + +*** + +## Need Help? + + + +*** + +## Lending Modules Overview +URL: https://docs.wdk.tether.io/sdk/lending-modules +Description: Explore WDK lending modules for integrating lending protocols with WDK. + +The Wallet Development Kit (WDK) provides a set of modules that support connection with lending protocols on different blockchain networks. All modules share a common interface, ensuring consistent behavior across different blockchain implementations. + + +Rows marked Community are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Lending & Borrowing Protocol Modules + +DeFi lending functionality for different lending & borrowing protocols + +| Module | Route | Ownership | Documentation | +|--------|-------|-----------|---------------| +| [`@tetherto/wdk-protocol-lending-aave-evm`](https://github.com/tetherto/wdk-protocol-lending-aave-evm) | EVM | Tether | [Documentation](/sdk/lending-modules/lending-aave-evm/) | +| [`@morpho-org/wdk-protocol-lending-morpho-evm`](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) | EVM | Community | [Documentation](/sdk/lending-modules/lending-morpho-evm/) | + +## Next Steps + +Compare the available EVM lending modules and open the implementation that matches your protocol target: + + + +Use the Tether-maintained Aave V3 lending module for EVM accounts. + + +Use the community Morpho module for Vault V2 and Morpho Blue EVM flows. + + +Install the Morpho module, create the client, and review prerequisites. + + + +*** + +## Need Help? + + + +*** + +## Lend with Aave +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm +Description: Supply, withdraw, borrow, repay, and read Aave V3 account data from WDK EVM accounts. + +Use the Aave lending module to supply, withdraw, borrow, repay, and read account data from WDK EVM accounts. It works with standard EVM wallets and ERC-4337 smart accounts. + +## Features + +- **Supply/Withdraw**: Add and remove supported assets from Aave pools +- **Borrow/Repay**: Borrow assets and repay debt +- **Account Data**: Read collateral, debt, health factor, and more +- **Quote System**: Estimate fees before sending transactions +- **AA Support**: Works with standard EVM and ERC‑4337 smart accounts +- **TypeScript Support**: Full TypeScript definitions + +## Supported Networks + +Works on Aave V3 supported EVM networks (e.g., Ethereum, Arbitrum, Base, Optimism, Polygon, Avalanche, BNB, Celo, Gnosis, Linea, Scroll, Soneium, Sonic, ZkSync, Metis). A working RPC provider and correct token addresses are required. + +## Wallet Compatibility + +- **Standard EVM Wallets**: `@tetherto/wdk-wallet-evm` +- **ERC‑4337 Smart Accounts**: `@tetherto/wdk-wallet-evm-erc-4337` +- **Read‑Only Accounts**: For quoting and reading account data without sending transactions + +## Key Components + +- **Aave V3 Integration**: Supply, withdraw, borrow, repay primitives +- **Quote Helpers**: `quoteSupply`, `quoteWithdraw`, `quoteBorrow`, `quoteRepay` +- **Collateral Controls**: Toggle collateral usage; set user eMode + +## Next Steps + + + +How to supply, withdraw, borrow and repay with Aave + + +Service setup, account config, ERC‑4337 options + + +Full API for Aave Protocol Evm methods and types + + + +*** + +## Lending Aave EVM API Reference +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/api-reference +Description: API Reference for @tetherto/wdk-protocol-lending-aave-evm + +# API Reference + +## Class: AaveProtocolEvm + +Main class for Aave V3 lending on EVM. + +### Constructor + +```javascript +new AaveProtocolEvm(account) +``` + +Parameters: +- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` + +Example: + +```javascript +const aave = new AaveProtocolEvm(account) +``` + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `supply(options, config?)` | Add tokens to the pool | `Promise<{hash: string, fee: bigint, approveHash?: string, resetAllowanceHash?: string}>` | +| `quoteSupply(options, config?)` | Estimate cost to add tokens | `Promise<{fee: bigint}>` | +| `withdraw(options, config?)` | Remove tokens from the pool | `Promise<{hash: string, fee: bigint}>` | +| `quoteWithdraw(options, config?)` | Estimate cost to withdraw | `Promise<{fee: bigint}>` | +| `borrow(options, config?)` | Borrow tokens | `Promise<{hash: string, fee: bigint}>` | +| `quoteBorrow(options, config?)` | Estimate borrowing cost | `Promise<{fee: bigint}>` | +| `repay(options, config?)` | Repay borrowed tokens | `Promise<{hash: string, fee: bigint}>` | +| `quoteRepay(options, config?)` | Estimate repayment cost | `Promise<{fee: bigint}>` | +| `setUseReserveAsCollateral(token, use, config?)` | Toggle token as collateral | `Promise<{hash: string, fee: bigint}>` | +| `setUserEMode(categoryId, config?)` | Set user eMode | `Promise<{hash: string, fee: bigint}>` | +| `getAccountData(account?)` | Read account stats | `Promise<{ totalCollateralBase: bigint, totalDebtBase: bigint, availableBorrowsBase: bigint, currentLiquidationThreshold: bigint, ltv: bigint, healthFactor: bigint }>` | + +--- + +When `AaveProtocolEvm` is initialized with an ERC‑4337 smart account, the optional `config` argument on mutating and quote methods accepts the same gas-payment override families documented in [`@tetherto/wdk-wallet-evm-erc-4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): paymaster token, sponsorship policy, and native coins. + +### `supply(options, config?)` +Add tokens to the pool. + +Options: +- `token` (`string`): token address +- `amount` (`number | bigint`): amount in base units +- `onBehalfOf` (`string`, optional) + +Returns: +- May include `approveHash` and `resetAllowanceHash` for standard accounts (e.g., USD₮ allowance reset on Ethereum mainnet) + +Example: + +```javascript +const res = await aave.supply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +--- + +### `quoteSupply(options, config?)` +Estimate fee to add tokens. + +```javascript +const q = await aave.quoteSupply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +--- + +### `withdraw(options, config?)` +Remove tokens from the pool. + +Options: +- `token` (`string`) +- `amount` (`number | bigint`) +- `to` (`string`, optional) + +```javascript +const tx = await aave.withdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +--- + +### `quoteWithdraw(options, config?)` +Estimate fee to withdraw tokens. + +```javascript +const q = await aave.quoteWithdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +--- + +### `borrow(options, config?)` +Borrow tokens. + +Options: +- `token` (`string`) +- `amount` (`number | bigint`) +- `onBehalfOf` (`string`, optional) + +```javascript +const tx = await aave.borrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +--- + +### `quoteBorrow(options, config?)` +Estimate fee to borrow tokens. + +```javascript +const q = await aave.quoteBorrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +--- + +### `repay(options, config?)` +Repay borrowed tokens. + +Options: +- `token` (`string`) +- `amount` (`number | bigint`) +- `onBehalfOf` (`string`, optional) + +```javascript +const tx = await aave.repay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +Returns: +- For standard accounts, may include `approveHash` / `resetAllowanceHash` when applicable. + +--- + +### `quoteRepay(options, config?)` +Estimate fee to repay borrowed tokens. + +```javascript +const q = await aave.quoteRepay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +--- + +### `setUseReserveAsCollateral(token, use, config?)` +Toggle token as collateral for the user. + +```javascript +const tx = await aave.setUseReserveAsCollateral('TOKEN_ADDRESS', true) +``` + +--- + +### `setUserEMode(categoryId, config?)` +Set user eMode category. + +```javascript +const tx = await aave.setUserEMode(1) +``` + +--- + +### `getAccountData(account?)` +Read account stats like total collateral, debt, and health. + +```javascript +const data = await aave.getAccountData() +``` + +Returns the following structure: + +```javascript +{ + totalCollateralBase: bigint, + totalDebtBase: bigint, + availableBorrowsBase: bigint, + currentLiquidationThreshold: bigint, + ltv: bigint, + healthFactor: bigint +} +``` + +--- + +## ERC‑4337 Config Override (optional) + +When the protocol uses `WalletAccountEvmErc4337` or `WalletAccountReadOnlyEvmErc4337`, the optional `config` argument on `supply`, `quoteSupply`, `withdraw`, `quoteWithdraw`, `borrow`, `quoteBorrow`, `repay`, `quoteRepay`, `setUseReserveAsCollateral`, and `setUserEMode` accepts the wallet module's per-call gas-payment overrides. + +- **Paymaster token mode**: `paymasterUrl`, `paymasterAddress`, `paymasterToken`, `transferMaxFee` +- **Sponsorship policy mode**: `isSponsored`, `paymasterUrl`, `sponsorshipPolicyId` +- **Native coin mode**: `useNativeCoins`, `transferMaxFee` + +Example: + +```javascript +const res = await aave.supply( + { token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n }, + { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } + } +) +``` + +## Rules & Notes + +- `token` must be a valid (non‑zero) address +- `amount` > 0 and in token base units (use BigInt) +- `onBehalfOf`/`to` (if set) must be valid, non‑zero addresses +- A provider is required to read/send transactions +- For USD₮ on mainnet, allowance may be reset to 0 then set again before actions + + + + +Get started with WDK in a Node.js environment + + +Get started with WDK's Lending Aave EVM Protocol configuration + + +Get started with WDK's Lending Aave EVM Protocol usage + + + +*** + +### Need Help? + + + +*** + +## Lending Aave EVM Configuration +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/configuration +Description: Configuration options and settings for @tetherto/wdk-protocol-lending-aave-evm + +# Configuration + +## Service Setup + +```javascript +import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' + +// Create wallet account first +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +// Create lending service +const aave = new AaveProtocolEvm(account) +``` + +## Account Configuration + +The service uses the wallet account configuration to connect to the target network and sign transactions. + +```javascript +import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' + +// Full access account +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +// Read-only account (quotes, reads) +const readOnly = new WalletAccountReadOnlyEvm('0xYourAddress', { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +const aave = new AaveProtocolEvm(account) +``` + +## ERC‑4337 (Account Abstraction) + +When using ERC‑4337 smart accounts, every mutating method and quote helper accepts an optional `config` override. In `v1.0.0-beta.5`, that override matches the three gas-payment families exposed by [`@tetherto/wdk-wallet-evm-erc-4337`](/sdk/wallet-modules/wallet-evm-erc-4337/configuration): paymaster token, sponsorship policy, or native coins. + +Use the fields that match the gas-payment mode you want for that call. For the full field-level definitions, see the [`@tetherto/wdk-wallet-evm-erc-4337` configuration docs](/sdk/wallet-modules/wallet-evm-erc-4337/configuration) and [`Config Override`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) reference. + +```javascript +import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' + +const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { + chainId: 1, + provider: 'https://ethereum-rpc.publicnode.com', + bundlerUrl: 'YOUR_BUNDLER_URL', + paymasterUrl: 'YOUR_PAYMASTER_URL', + paymasterAddress: process.env.PAYMASTER_ADDRESS, + safeModulesVersion: '0.3.0' +}) + +const aaveAA = new AaveProtocolEvm(aa) + +const result = await aaveAA.supply({ token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n }, { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } +}) +``` + +`safeModulesVersion` is required when initializing the smart account. In paymaster-token mode, `paymasterAddress` identifies the paymaster contract that charges the selected token; set `PAYMASTER_ADDRESS` to the address for the paymaster service and chain you configure. Do not reuse an address from a different provider or chain. + +### Supported Override Families + +- **Paymaster token mode**: `paymasterUrl`, `paymasterAddress`, `paymasterToken`, `transferMaxFee` +- **Sponsorship policy mode**: `isSponsored`, `paymasterUrl`, `sponsorshipPolicyId` +- **Native coin mode**: `useNativeCoins`, `transferMaxFee` + +## Network Support + +Aave V3 spans multiple EVM chains (Ethereum, Arbitrum, Base, Optimism, Polygon, Avalanche, BNB, Celo, Gnosis, Linea, Scroll, Soneium, Sonic, ZkSync, Metis). Ensure the correct RPC and token addresses for the target chain. + +```javascript +// Ethereum Mainnet +const eth = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +// Arbitrum +const arb = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://arb1.arbitrum.io/rpc' +}) +``` + +## Operation Options + +Each operation accepts a simple options object: + +```javascript +// Supply +await aave.supply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) + +// Withdraw +await aave.withdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) + +// Borrow +await aave.borrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) + +// Repay +await aave.repay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +``` + +### Common Parameters + +- `token` (`string`): ERC‑20 token address +- `amount` (`number | bigint`): token amount in base units +- `onBehalfOf` (`string`, optional): another address to act for (supply/borrow/repay) +- `to` (`string`, optional): destination address (withdraw) + +> Note: `amount` must be > 0. Addresses must be valid/non‑zero. A provider is required for any write. + + + + +Get started with WDK in a Node.js environment + + +Get started with WDK's Lending Aave EVM Protocol API + + +Get started with WDK's Lending Aave EVM Protocol usage + + + +*** + +### Need Help? + + + +*** + +## Get Started +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/guides/get-started +Description: Install the package, create AaveProtocolEvm, and review prerequisites. + +This guide covers [installation](#installation), [creating the lending client](#create-the-lending-client), and [prerequisites](#prerequisites). Use [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) on your machine. + +## Installation + +Run the following to install [@tetherto/wdk-protocol-lending-aave-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-lending-aave-evm): + +```bash title="Install with npm" +npm install @tetherto/wdk-protocol-lending-aave-evm +``` + +## Create the lending client + +You can attach Aave V3 actions to an EVM account from [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference) with [`new AaveProtocolEvm(account)`](/sdk/lending-modules/lending-aave-evm/api-reference) on [`AaveProtocolEvm`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Create AaveProtocolEvm" +import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +const aave = new AaveProtocolEvm(account) +``` + +## Prerequisites + + +**Token balance:** To supply or repay, hold the ERC-20 in the wallet. **Gas:** Keep native balance (ETH on Ethereum, and so on) for transaction fees unless you use sponsored ERC-4337 flows. **Networks:** This module targets mainnet deployments; confirm your RPC matches [supported networks](/sdk/lending-modules/lending-aave-evm/configuration). + + +Use contract addresses for Aave-supported reserves. On Ethereum mainnet, USD₮ uses `0xdAC17F958D2ee523a2206206994597C13D831ec7` (use `USDT` in code identifiers and literals). + +## Next Steps + +- [Lending operations](/sdk/lending-modules/lending-aave-evm/guides/lending-operations/) +- [Handle errors](/sdk/lending-modules/lending-aave-evm/guides/handle-errors/) + +*** + +## Handle Errors +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/guides/handle-errors +Description: Catch lending failures and release wallet secrets safely. + +This guide explains how to [handle operation errors](#operation-errors) and follow [best practices](#best-practices) for disposing wallet state. + +## Operation errors + +You can catch failures from [`supply()`](/sdk/lending-modules/lending-aave-evm/api-reference), [`withdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference), [`borrow()`](/sdk/lending-modules/lending-aave-evm/api-reference), and [`repay()`](/sdk/lending-modules/lending-aave-evm/api-reference) with `try/catch`: + +```javascript title="Handle a failed supply" +try { + await aave.supply({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 0n + }) +} catch (e) { + console.error('Lending failed:', e.message) + if (e.message.includes('zero')) { + console.log('Amount must be greater than zero') + } +} +``` + +You can isolate quote failures from [`quoteSupply()`](/sdk/lending-modules/lending-aave-evm/api-reference) (or the other `quote*` methods) when you only need an estimate: + +```javascript title="Handle quote errors" +try { + const q = await aave.quoteBorrow({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n + }) + console.log('Borrow fee (wei):', q.fee) +} catch (e) { + console.error('Quote failed:', e.message) +} +``` + + +See [Rules & Notes](/sdk/lending-modules/lending-aave-evm/api-reference) for address and amount validation expectations. + + +## Best Practices + +You can wipe private keys after lending work by calling [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference), or [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference): + +```javascript title="Dispose after lending session" +try { + await aave.supply({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n + }) +} finally { + account.dispose() +} +``` + +For ERC-4337 accounts, use [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) on the smart-account type. Clear references to [`AaveProtocolEvm`](/sdk/lending-modules/lending-aave-evm/api-reference) when the session ends. + +## Next Steps + +- [Lending operations](/sdk/lending-modules/lending-aave-evm/guides/lending-operations/) +- [Get started](/sdk/lending-modules/lending-aave-evm/guides/get-started/) +- [API reference](/sdk/lending-modules/lending-aave-evm/api-reference) + +*** + +## Lending Operations +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/guides/lending-operations +Description: Supply, withdraw, borrow, repay, quote fees, use ERC-4337, and read account data. + +This guide walks through [supply](#supply), [withdraw](#withdraw), [borrow](#borrow), [repay](#repay), [quotes](#quotes-before-sending), [ERC-4337 usage](#erc-4337-smart-accounts), and [reading account data](#reading-account-data). It assumes an [`AaveProtocolEvm`](/sdk/lending-modules/lending-aave-evm/api-reference) instance named `aave` and the USD₮ contract on Ethereum mainnet `0xdAC17F958D2ee523a2206206994597C13D831ec7`. + +## Supply + +You can deposit reserves into the pool using [`supply()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Supply USDT" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const tx = await aave.supply({ token: USDT, amount: 1000000n }) +console.log('Supply tx hash:', tx.hash) +``` + +## Withdraw + +You can remove supplied liquidity using [`withdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Withdraw USDT" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const tx = await aave.withdraw({ token: USDT, amount: 1000000n }) +console.log('Withdraw tx hash:', tx.hash) +``` + +## Borrow + +You can draw debt against your collateral using [`borrow()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Borrow USDT" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const tx = await aave.borrow({ token: USDT, amount: 1000000n }) +console.log('Borrow tx hash:', tx.hash) +``` + +## Repay + +You can pay down debt using [`repay()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Repay USDT" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const tx = await aave.repay({ token: USDT, amount: 1000000n }) +console.log('Repay tx hash:', tx.hash) +``` + +## Quotes before sending + +You can estimate the supply fee with [`quoteSupply()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Quote supply fee" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const supplyQuote = await aave.quoteSupply({ token: USDT, amount: 1000000n }) +console.log('Supply fee (wei):', supplyQuote.fee) +``` + +You can estimate the withdraw fee with [`quoteWithdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Quote withdraw fee" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const withdrawQuote = await aave.quoteWithdraw({ token: USDT, amount: 1000000n }) +console.log('Withdraw fee (wei):', withdrawQuote.fee) +``` + +You can estimate the borrow fee with [`quoteBorrow()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Quote borrow fee" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const borrowQuote = await aave.quoteBorrow({ token: USDT, amount: 1000000n }) +console.log('Borrow fee (wei):', borrowQuote.fee) +``` + +You can estimate the repay fee with [`quoteRepay()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Quote repay fee" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const repayQuote = await aave.quoteRepay({ token: USDT, amount: 1000000n }) +console.log('Repay fee (wei):', repayQuote.fee) +``` + + +Health factor and collateralization limits still apply. A quote does not guarantee the transaction will succeed if on-chain state changes. + + +## ERC-4337 smart accounts + +You can run the same methods through [`WalletAccountEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and pass a second `config` argument to override per-call gas payment settings. In `v1.0.0-beta.5`, the lending methods accept the same override families as the wallet module: paymaster token, sponsorship policy, and native coins. See the [ERC-4337 config override](/sdk/lending-modules/lending-aave-evm/api-reference) section for the full field list. + +```javascript title="Supply with paymaster" +import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' +import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' + +const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { + chainId: 1, + provider: 'https://ethereum-rpc.publicnode.com', + bundlerUrl: process.env.BUNDLER_URL, + paymasterUrl: process.env.PAYMASTER_URL, + paymasterAddress: process.env.PAYMASTER_ADDRESS, + safeModulesVersion: '0.3.0' +}) + +const aaveAA = new AaveProtocolEvm(aa) + +const result = await aaveAA.supply( + { token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n }, + { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } + } +) +console.log('Supply hash:', result.hash) +``` + +`safeModulesVersion` is required for smart account initialization. For paymaster-token mode, set `PAYMASTER_ADDRESS` to the paymaster contract deployed for the selected service and chain; it tells the wallet which contract charges the configured paymaster token. Do not substitute an address from another provider or network. + +You can use the same second argument to: + +- override paymaster-token mode with `paymasterUrl`, `paymasterAddress`, `paymasterToken`, or `transferMaxFee` +- switch one call to sponsorship mode with `isSponsored`, `paymasterUrl`, and `sponsorshipPolicyId` +- switch one call to native-coin gas mode with `useNativeCoins` and `transferMaxFee` + +Use token addresses that exist on the same chain as the smart account RPC. + +## Reading account data + +You can inspect collateral, debt, and health using [`getAccountData()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +```javascript title="Read Aave account data" +const data = await aave.getAccountData() + +console.log({ + totalCollateralBase: data.totalCollateralBase, + totalDebtBase: data.totalDebtBase, + availableBorrowsBase: data.availableBorrowsBase, + currentLiquidationThreshold: data.currentLiquidationThreshold, + ltv: data.ltv, + healthFactor: data.healthFactor +}) +``` + +## Next Steps + +- [Handle errors](/sdk/lending-modules/lending-aave-evm/guides/handle-errors/) +- [Get started](/sdk/lending-modules/lending-aave-evm/guides/get-started/) +- [API reference](/sdk/lending-modules/lending-aave-evm/api-reference) + +*** + +## Lending Aave EVM Guides +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-aave-evm/usage +Description: How to install and use @tetherto/wdk-protocol-lending-aave-evm on EVM + +# Usage + +The [@tetherto/wdk-protocol-lending-aave-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-lending-aave-evm) module exposes Aave V3 supply, borrow, and repayment flows for EVM accounts. Follow the guides below for setup, day-to-day operations, and error handling. + + + + +Install the package, create AaveProtocolEvm, and review prerequisites. + + +Supply, withdraw, borrow, repay, quotes, ERC-4337, and account data. + + +Handle failures and dispose wallet secrets when finished. + + + + + + +Get started with WDK in a Node.js environment + + +Networks and deployment settings for the Aave lending protocol + + +Methods and parameters for AaveProtocolEvm + + + + + +*** + +## Lend with Morpho +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm +Description: Use Morpho Vault V2 earn targets and Morpho Blue markets from WDK-compatible EVM accounts. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +Use the Morpho lending community module to interact with Morpho Vault V2 earn targets and Morpho Blue markets from WDK-compatible EVM accounts through [`@morpho-org/morpho-sdk`](https://www.npmjs.com/package/@morpho-org/morpho-sdk). + +## Features + +- **Vault earn flows**: Deposit into and withdraw from configured Morpho Vault V2 targets +- **Market collateral**: Supply and withdraw collateral in a configured Morpho Blue market +- **Borrow/Repay**: Borrow from and repay a configured Morpho Blue market +- **Requirements API**: Surface Morpho SDK approval, signature, and authorization requirements +- **Quotes**: Estimate transaction costs before sending +- **Account Reads**: Read vault, market, and combined account position data +- **Account Support**: Works with standard EVM accounts and ERC-4337 smart accounts + +## Supported Targets + +The module supports curated Ethereum mainnet presets and explicit Morpho target configuration. + +### Earn Presets + +| Preset | Vault | +|--------|-------| +| `sky-money-usdt-savings` | sky.money USDT Savings V2 | +| `steakhouse-prime-instant` | Steakhouse Prime Instant V2 | + +### Borrow Presets + +| Preset | Collateral | +|--------|------------| +| `susds` | sUSDS | +| `wsteth` | wstETH | +| `wbtc` | WBTC | +| `xaut` | XAUt | + +## Wallet Compatibility + +- **Standard EVM Wallets**: `@tetherto/wdk-wallet-evm` +- **ERC-4337 Smart Accounts**: `@tetherto/wdk-wallet-evm-erc-4337` +- **Read-Only Accounts**: For quoting and reading configured vault or market positions without sending transactions + +## Key Components + +- **MorphoProtocolEvm**: Main class for Morpho lending operations +- **Morpho Protocol Options**: Configure vaults, markets, presets, chain guards, slippage, signatures, and deployless reads +- **Requirement Helpers**: `getSupplyRequirements`, `getSupplyCollateralRequirements`, `getBorrowRequirements`, and `getRepayRequirements` +- **Position Reads**: `getVaultPosition`, `getMarketPosition`, and `getAccountData` + +## Next Steps + + + +Install the package, create MorphoProtocolEvm, and review prerequisites. + + +How to use Morpho vault, market, quote, requirement, and position flows. + + +Install the package and configure presets, explicit targets, and options. + + +Methods, options, presets, and return shapes for MorphoProtocolEvm. + + + +*** + +## Lending Morpho EVM API Reference +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/api-reference +Description: API Reference for @morpho-org/wdk-protocol-lending-morpho-evm + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +# API Reference + +## Class: MorphoProtocolEvm + +Main class for Morpho Vault V2 and Morpho Blue lending on EVM. + +### Constructor + +```javascript +new MorphoProtocolEvm(account, options) +``` + +Parameters: + +- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` +- `options`: `MorphoProtocolOptions` + +Example: + +```javascript +const morpho = new MorphoProtocolEvm(account, { + presets: { + earn: 'sky-money-usdt-savings', + borrow: 'wsteth' + } +}) +``` + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `supply(options, config?)` | Deposit assets into the configured vault | `Promise` | +| `getSupplyRequirements(options, requirementOptions?)` | Return approval or signature requirements for vault deposit | `Promise` | +| `quoteSupply(options, config?)` | Quote vault deposit | `Promise>` | +| `withdraw(options, config?)` | Withdraw assets from the configured vault | `Promise` | +| `quoteWithdraw(options, config?)` | Quote vault withdrawal | `Promise>` | +| `supplyCollateral(options, config?)` | Supply collateral to the configured market | `Promise` | +| `getSupplyCollateralRequirements(options, requirementOptions?)` | Return approval or signature requirements for collateral supply | `Promise` | +| `quoteSupplyCollateral(options, config?)` | Quote collateral supply | `Promise>` | +| `borrow(options, config?)` | Borrow from the configured market | `Promise` | +| `getBorrowRequirements(options)` | Return a Morpho authorization transaction or signable authorization request for borrow | `Promise<(RequirementAuthorization \| RequirementSignatureRequest)[]>` | +| `quoteBorrow(options, config?)` | Quote borrow | `Promise>` | +| `repay(options, config?)` | Repay the configured market | `Promise` | +| `getRepayRequirements(options, requirementOptions?)` | Return approval or signature requirements for repay | `Promise` | +| `quoteRepay(options, config?)` | Quote repay | `Promise>` | +| `withdrawCollateral(options, config?)` | Withdraw collateral from the configured market | `Promise` | +| `quoteWithdrawCollateral(options, config?)` | Quote collateral withdrawal | `Promise>` | +| `getVaultPosition(account?)` | Read configured vault position | `Promise` | +| `getMarketPosition(account?)` | Read configured market position | `Promise` | +| `getAccountData(account?)` | Read combined configured vault and market position | `Promise` | +| `getVaultAddress()` | Return the configured vault address | `Address` | +| `getBorrowMarketId()` | Return the configured borrow market id | `string` | + +--- + +## Requirements and Results + +- `ApprovalOrSignatureRequirement`: returned by supply, collateral supply, and repay requirement helpers. Each item is either a Morpho SDK approval transaction with `to`, `value`, and `data`, or a signature requirement with `sign(client, userAddress)`. +- `RequirementAuthorization`: a Morpho authorization transaction with `to`, `value`, and `data`. Send it before `borrow()`. +- `RequirementSignatureRequest`: a signable Morpho authorization request returned by `getBorrowRequirements()` when `supportSignature: true` is enabled. +- `RequirementSignature`: returned by a signature request's `sign(client, userAddress)` helper. Pass it to `supply()`, `supplyCollateral()`, `borrow()`, or `repay()` as `requirementSignature`. +- Write methods return the WDK wallet transaction result, including `hash` and `fee`. Quote methods return the same protocol result without `hash`. + +--- + +### `supply(options, config?)` + +Deposit assets into the configured Morpho vault. + +Options: + +- `token` (`string`): configured vault asset +- `amount` (`number | bigint`, optional when `nativeAmount` is set): ERC-20 amount in base units +- `nativeAmount` (`number | bigint`, optional): native amount to wrap and supply +- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set +- `requirementSignature` (`RequirementSignature`, optional): signature returned by a Morpho SDK requirement +- `slippageTolerance` (`bigint`, optional): per-call Morpho SDK slippage tolerance + +```javascript +const tx = await morpho.supply({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +### `getSupplyRequirements(options, requirementOptions?)` + +Return Morpho SDK approval or signature requirements for a vault deposit. Use it before `supply()` when the account has not approved the required spender or when signature support is enabled. + +```javascript +const requirements = await morpho.getSupplyRequirements({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +### `quoteSupply(options, config?)` + +Quote the fee for a vault deposit transaction. + +```javascript +const quote = await morpho.quoteSupply({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +--- + +### `withdraw(options, config?)` + +Withdraw assets from the configured Morpho vault. + +Options: + +- `token` (`string`): configured vault asset +- `amount` (`number | bigint`): amount in base units +- `to` (`string`, optional): must equal the connected wallet address when set + +```javascript +const tx = await morpho.withdraw({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +### `quoteWithdraw(options, config?)` + +Quote the fee for a vault withdrawal transaction. + +```javascript +const quote = await morpho.quoteWithdraw({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +--- + +### `supplyCollateral(options, config?)` + +Supply collateral to the configured Morpho Blue market. + +Options: + +- `token` (`string`): configured market collateral token +- `amount` (`number | bigint`, optional when `nativeAmount` is set): ERC-20 amount in base units +- `nativeAmount` (`number | bigint`, optional): native amount to wrap and supply +- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set +- `requirementSignature` (`RequirementSignature`, optional): signature returned by a Morpho SDK requirement + +```javascript +const tx = await morpho.supplyCollateral({ + token: 'COLLATERAL_TOKEN_ADDRESS', + amount: 1000000000000000000n +}) +``` + +### `getSupplyCollateralRequirements(options, requirementOptions?)` + +Return Morpho SDK approval or signature requirements for collateral supply. + +```javascript +const requirements = await morpho.getSupplyCollateralRequirements({ + token: 'COLLATERAL_TOKEN_ADDRESS', + amount: 1000000000000000000n +}) +``` + +### `quoteSupplyCollateral(options, config?)` + +Quote the fee for supplying collateral. + +```javascript +const quote = await morpho.quoteSupplyCollateral({ + token: 'COLLATERAL_TOKEN_ADDRESS', + amount: 1000000000000000000n +}) +``` + +--- + +### `borrow(options, config?)` + +Borrow assets from the configured Morpho Blue market. + +Options: + +- `token` (`string`): configured market loan token +- `amount` (`number | bigint`): amount in base units +- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set +- `reallocations` (`readonly VaultReallocation[]`, optional): Morpho Vault V2 reallocations +- `requirementSignature` (`RequirementSignature`, optional): signed authorization returned by `getBorrowRequirements()` when `supportSignature: true` is enabled; the adapter includes it as `setAuthorizationWithSig` in the borrow bundle +- `slippageTolerance` (`bigint`, optional): per-call Morpho SDK slippage tolerance + +```javascript +const tx = await morpho.borrow({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +### `getBorrowRequirements(options)` + +Return Morpho authorization requirements for borrow flows. The result is an array of authorization transactions, unless `supportSignature: true` is enabled and Morpho returns a signable `RequirementSignatureRequest` instead. Send transaction requirements before `borrow()`; sign a signature request and pass the resulting `requirementSignature` to `borrow()`. + +```javascript +const requirements = await morpho.getBorrowRequirements({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +### `quoteBorrow(options, config?)` + +Quote the fee for borrowing. + +```javascript +const quote = await morpho.quoteBorrow({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n +}) +``` + +--- + +### `repay(options, config?)` + +Repay assets to the configured Morpho Blue market. + +Options: + +- `token` (`string`): configured market loan token +- `amount` (`number | bigint | "max"`): amount in base units, or `"max"` to repay current borrow shares +- `onBehalfOf` (`string`, optional): must equal the connected wallet address when set +- `requirementSignature` (`RequirementSignature`, optional): signature returned by a Morpho SDK requirement +- `slippageTolerance` (`bigint`, optional): per-call Morpho SDK slippage tolerance + +```javascript +const tx = await morpho.repay({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 'max' +}) +``` + +When `amount` is `"max"`, Morpho repays borrow shares. Re-run `getRepayRequirements({ amount: "max" })` immediately before sending `repay()` so the approval or permit reflects current market state, or approve `getChainAddresses(chainId).bundler3.generalAdapter1` with a small buffer above current debt and pass a non-zero `slippageTolerance`. `slippageTolerance` caps the repay share price or transfer amount; it is not approval slippage. + +### `getRepayRequirements(options, requirementOptions?)` + +Return Morpho SDK approval or signature requirements for repayment. + +```javascript +const requirements = await morpho.getRepayRequirements({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 'max' +}) +``` + +### `quoteRepay(options, config?)` + +Quote the fee for repayment. + +```javascript +const quote = await morpho.quoteRepay({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 'max' +}) +``` + +--- + +### `withdrawCollateral(options, config?)` + +Withdraw collateral from the configured Morpho Blue market. + +Options: + +- `token` (`string`): configured market collateral token +- `amount` (`number | bigint`): amount in base units +- `to` (`string`, optional): must equal the connected wallet address when set + +```javascript +const tx = await morpho.withdrawCollateral({ + token: 'COLLATERAL_TOKEN_ADDRESS', + amount: 1000000000000000000n +}) +``` + +### `quoteWithdrawCollateral(options, config?)` + +Quote the fee for withdrawing collateral. + +```javascript +const quote = await morpho.quoteWithdrawCollateral({ + token: 'COLLATERAL_TOKEN_ADDRESS', + amount: 1000000000000000000n +}) +``` + +--- + +## Position Reads + +### `getVaultPosition(account?)` + +Read this or another account's configured vault position. + +Returns: + +```javascript +{ + shares: bigint, + assets: bigint, + vaultAddress: Address +} +``` + +### `getMarketPosition(account?)` + +Read this or another account's configured market position. + +Returns: + +```javascript +{ + supplyShares: bigint, + borrowShares: bigint, + borrowAssets: bigint, + collateral: bigint, + marketId: string +} +``` + +### `getAccountData(account?)` + +Read combined configured vault and market position data. + +Returns: + +```javascript +{ + vaultShares: bigint, + vaultAssets: bigint, + marketSupplyShares: bigint, + marketBorrowShares: bigint, + marketBorrowAssets: bigint, + collateral: bigint, + vaultAddress: Address, + marketId: string +} +``` + +## Rules & Notes + +- The wallet account must include a provider. +- Write methods require a writable EVM account. +- `token`, `onBehalfOf`, `to`, and `account` addresses must be valid when provided. +- For vault supply and collateral supply, pass `amount`, `nativeAmount`, or both, and make sure the combined supplied amount is greater than zero. +- Withdraw, borrow, and collateral-withdraw amounts must be greater than zero. `repay()` also accepts `amount: "max"`. +- Vault operations require the token to match the configured vault asset. +- Market borrow and repay operations require the token to match the configured market loan token. +- Collateral operations require the token to match the configured market collateral token. +- `onBehalfOf` and vault or collateral withdrawal `to` must equal the connected wallet address when set. +- `slippageTolerance` applies to vault supply, borrow, and repay calls in the published adapter. Collateral supply uses the Morpho SDK collateral-supply action defaults. +- Use `get*Requirements` methods before final actions when the Morpho SDK reports approval, signature, or authorization requirements. + + + +Presets, explicit targets, and Morpho SDK options + + +Get started with Morpho lending operations + + + +*** + +### Need Help? + + + +*** + +## Lending Morpho EVM Configuration +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/configuration +Description: Configuration options and settings for @morpho-org/wdk-protocol-lending-morpho-evm + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +# Configuration + +## Installation + +Install the Morpho lending module with the EVM wallet module used by the examples and the `viem` peer dependency: + +```bash title="Install with npm" +npm install @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem +``` + +```bash title="Install with pnpm" +pnpm add @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem +``` + +The package declares Node.js `22.13` or later in its published engine metadata. If you use ERC-4337 smart accounts, also install `@tetherto/wdk-wallet-evm-erc-4337`. + +## Service Setup + +Create a WDK-compatible EVM wallet account, then pass it to `MorphoProtocolEvm` with either presets or explicit Morpho targets. + +```javascript title="Create MorphoProtocolEvm with presets" +import MorphoProtocolEvm from '@morpho-org/wdk-protocol-lending-morpho-evm' +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +const morpho = new MorphoProtocolEvm(account, { + presets: { + earn: 'sky-money-usdt-savings', + borrow: 'wsteth' + } +}) +``` + +## Constructor + +```javascript +new MorphoProtocolEvm(account, options) +``` + +Parameters: + +- `account`: `WalletAccountEvm`, `WalletAccountReadOnlyEvm`, `WalletAccountEvmErc4337`, or `WalletAccountReadOnlyEvmErc4337` +- `options`: Morpho target configuration + +The wallet account must include a provider. Read-only accounts can read positions and quote transactions; mutating methods require a writable account. + +## Presets + +Built-in presets target Ethereum mainnet USDT earn and borrow flows. + +```javascript title="Use built-in presets" +const morpho = new MorphoProtocolEvm(account, { + presets: { + earn: 'steakhouse-prime-instant', + borrow: 'wbtc' + } +}) +``` + +Borrow presets: + +| Preset | Chain ID | Market ID | Collateral | LLTV | +|--------|----------|-----------|------------|------| +| `susds` | `1` | `0x3274643db77a064abd3bc851de77556a4ad2e2f502f4f0c80845fa8f909ecf0b` | sUSDS | 96.5% | +| `wsteth` | `1` | `0xe7e9694b754c4d4f7e21faf7223f6fa71abaeb10296a4c43a54a7977149687d2` | wstETH | 86% | +| `wbtc` | `1` | `0xa921ef34e2fc7a27ccc50ae7e4b154e16c9799d3387076c421423ef52ac4df99` | WBTC | 86% | +| `xaut` | `1` | `0xb7843fe78e7e7fd3106a1b939645367967d1f986c2e45edb8932ad1896450877` | XAUt | 77% | + +Earn presets: + +| Preset | Chain ID | Vault Address | Vault | +|--------|----------|---------------|-------| +| `sky-money-usdt-savings` | `1` | `0x23f5E9c35820f4baB695Ac1F19c203cC3f8e1e11` | sky.money USDT Savings V2 | +| `steakhouse-prime-instant` | `1` | `0xbeef003C68896c7D2c3c60d363e8d71a49Ab2bf9` | Steakhouse Prime Instant V2 | + +## Explicit Targets + +Use explicit targets when you need a vault or market outside the built-in presets. + +```javascript title="Use explicit Morpho targets" +const morpho = new MorphoProtocolEvm(account, { + chainId: 1, + earnVaultAddress: '0x23f5E9c35820f4baB695Ac1F19c203cC3f8e1e11', + borrowMarketId: '0xe7e9694b754c4d4f7e21faf7223f6fa71abaeb10296a4c43a54a7977149687d2' +}) +``` + +When you use `earnVaultAddress`, `borrowMarketParams`, or `borrowMarketId` directly, pass `chainId`. The adapter uses it to guard transaction building if a browser wallet switches chains. + +Use `borrowMarketParams` when you already know the full Morpho Blue market configuration: + +```javascript title="Use explicit Morpho Blue market params" +const morpho = new MorphoProtocolEvm(account, { + chainId: 1, + borrowMarketParams: { + loanToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + collateralToken: 'COLLATERAL_TOKEN_ADDRESS', + oracle: 'ORACLE_ADDRESS', + irm: 'INTEREST_RATE_MODEL_ADDRESS', + lltv: 860000000000000000n + } +}) +``` + +`InputMarketParams` contains `loanToken`, `collateralToken`, `oracle`, `irm`, and `lltv`. Confirm explicit market params against Morpho market data before using them in production. + +## Options + +| Option | Type | Description | +|--------|------|-------------| +| `chainId` | `number \| bigint` | Required with explicit Morpho targets | +| `earnVaultAddress` | `string` | Explicit Morpho Vault V2 address | +| `borrowMarketParams` | `InputMarketParams` | Explicit Morpho Blue market params | +| `borrowMarketId` | `string` | Market id used to fetch market params on-chain | +| `presets` | `{ earn?: string, borrow?: string }` | Built-in earn and borrow target names | +| `slippageTolerance` | `bigint` | Morpho SDK slippage tolerance in WAD precision | +| `supportSignature` | `boolean` | Enables Morpho SDK permit or Permit2 requirements | +| `supportDeployless` | `boolean` | Enables Morpho SDK deployless reads | +| `metadata` | `Metadata` | Optional Morpho SDK metadata passed to action encoders | + +## Native Amounts + +For vault deposits and collateral supply, pass either `amount`, `nativeAmount`, or both. `nativeAmount` follows Morpho SDK semantics and is only valid when the configured vault asset or collateral token is the wrapped native token for the chain. + +```javascript title="Supply with native amount" +await morpho.supply({ + token: 'WRAPPED_NATIVE_TOKEN_ADDRESS', + nativeAmount: 1000000000000000n +}) +``` + +## ERC-4337 Config Overrides + +When using `WalletAccountEvmErc4337`, mutating methods and quote helpers accept an optional second `config` argument for the wallet module's per-call gas payment settings. + +```javascript title="Supply with an ERC-4337 config override" +await morpho.supply( + { + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n + }, + { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } + } +) +``` + +See the [`@tetherto/wdk-wallet-evm-erc-4337` configuration docs](/sdk/wallet-modules/wallet-evm-erc-4337/configuration) for paymaster token, sponsorship policy, and native coin override fields. + + + +Get started with WDK in a Node.js environment + + +Methods and parameters for MorphoProtocolEvm + + +Get started with Morpho lending operations + + + +*** + +### Need Help? + + + +*** + +## Get Started +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/guides/get-started +Description: Install the package, create MorphoProtocolEvm, and review prerequisites. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide covers [installation](#installation), [creating the lending client](#create-the-lending-client), and [prerequisites](#prerequisites). Use [Node.js](https://nodejs.org/) `22.13` or later and [npm](https://www.npmjs.com/) on your machine. + +## Installation + +Run the following to install [@morpho-org/wdk-protocol-lending-morpho-evm](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm), the EVM wallet module used by the examples, and the `viem` peer dependency: + +```bash title="Install with npm" +npm install @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem +``` + +```bash title="Install with pnpm" +pnpm add @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem +``` + +If you use ERC-4337 smart accounts, also install `@tetherto/wdk-wallet-evm-erc-4337`. + +## Create the lending client + +You can attach Morpho actions to an EVM account from [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference) with [`new MorphoProtocolEvm(account, options)`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Create MorphoProtocolEvm" +import MorphoProtocolEvm from '@morpho-org/wdk-protocol-lending-morpho-evm' +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +const morpho = new MorphoProtocolEvm(account, { + presets: { + earn: 'sky-money-usdt-savings', + borrow: 'wsteth' + } +}) +``` + +## Prerequisites + + +**Runtime:** Use Node.js `22.13` or later. **Token balance:** To supply, supply collateral, or repay, hold the required ERC-20 in the wallet. **Gas:** Keep native balance for transaction fees unless you use sponsored ERC-4337 flows. **Targets:** Confirm the configured vault or market matches the token and chain you plan to use. + + +The built-in presets target Ethereum mainnet. If you configure an explicit vault address, market id, or market params, set `chainId` in [`MorphoProtocolOptions`](/sdk/lending-modules/lending-morpho-evm/configuration). + +## Requirements before actions + +Morpho SDK actions can return approval, signature, or authorization requirements. Call the matching `get*Requirements` method before the final action when the account has not already satisfied those requirements. + +```javascript title="Check supply requirements" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const requirements = await morpho.getSupplyRequirements({ + token: USDT, + amount: 1000000n +}) + +console.log('Requirements:', requirements) +``` + +Send any returned transaction requirements with your EVM account flow before calling the final action. For signature requirements, call the requirement's `sign(client, userAddress)` helper, then pass the returned `requirementSignature` to `supply`, `supplyCollateral`, or `repay`. + +## Next Steps + +- [Lending operations](/sdk/lending-modules/lending-morpho-evm/guides/lending-operations/) +- [Handle errors](/sdk/lending-modules/lending-morpho-evm/guides/handle-errors/) + +*** + +## Handle Errors +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/guides/handle-errors +Description: Catch Morpho lending failures and release wallet secrets safely. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide explains how to [handle operation errors](#operation-errors), [handle requirement errors](#requirement-errors), and follow [best practices](#best-practices) for disposing wallet state. + +## Operation errors + +You can catch failures from [`supply()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`withdraw()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`supplyCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`borrow()`](/sdk/lending-modules/lending-morpho-evm/api-reference), [`repay()`](/sdk/lending-modules/lending-morpho-evm/api-reference), and [`withdrawCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference) with `try/catch`: + +```javascript title="Handle a failed supply" +try { + await morpho.supply({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 0n + }) +} catch (e) { + console.error('Morpho lending failed:', e.message) + + if (e.message.includes('amount')) { + console.log('Amount must be greater than zero') + } +} +``` + +Common failure causes include: + +- wallet account does not have a provider configured +- write method is called with a read-only account +- token does not match the configured vault asset, market loan token, or market collateral token +- explicit target is used without the required `chainId` +- connected chain does not match the configured Morpho target +- amount is zero, invalid, or larger than the account balance +- `onBehalfOf` or `to` does not match the connected wallet address when required + +Use this checklist to map the most common failures to fixes: + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| Constructor fails before any operation | Wallet account has no provider | Create the EVM account with a provider or use a read-only account with an RPC provider | +| Write method fails on a read-only account | Read-only account can quote and read, but cannot send transactions | Use `WalletAccountEvm` or `WalletAccountEvmErc4337` for mutating methods | +| Explicit target fails during setup | `chainId` is missing, invalid, or does not match the wallet chain | Pass the expected `chainId` with `earnVaultAddress`, `borrowMarketId`, or `borrowMarketParams` | +| Token mismatch error | The supplied token is not the configured vault asset, market loan token, or collateral token | Use the token from the configured vault or market target | +| Zero amount error | `amount` and `nativeAmount` are both absent or zero | Pass a positive ERC-20 `amount`, a positive `nativeAmount`, or `amount: "max"` for repay | +| Requirement lookup returns approval or authorization | Allowance, permit, Permit2, or Morpho authorization is missing | Send returned transaction requirements or sign the returned signature requirement before the final action | +| Final action fails after requirements | Allowance, balance, signature, authorization, quote, or market state changed after the requirement lookup | Re-run the matching `get*Requirements()` method and rebuild the final action | +| Quote succeeds but write fails | On-chain state changed, the account lacks balance, or requirements were not satisfied | Re-check requirements, balances, token addresses, and chain before sending | + +## Requirement errors + +Requirement helpers can fail if the configured target, token, account, or provider cannot produce a valid Morpho SDK action. + +```javascript title="Handle requirement errors" +try { + const requirements = await morpho.getBorrowRequirements({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n + }) + + console.log('Borrow requirements:', requirements) +} catch (e) { + console.error('Requirement lookup failed:', e.message) +} +``` + +If a final action fails after requirements were returned, re-check the requirements. Allowances, signatures, authorizations, or on-chain market state can change between the requirement lookup and the final transaction. + +## Quote errors + +You can isolate quote failures from write failures when you only need an estimate: + +```javascript title="Handle quote errors" +try { + const quote = await morpho.quoteBorrow({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n + }) + + console.log('Borrow fee:', quote.fee) +} catch (e) { + console.error('Quote failed:', e.message) +} +``` + + +See [Rules & Notes](/sdk/lending-modules/lending-morpho-evm/api-reference) for address, token, amount, and target validation expectations. + + +## Best Practices + +Dispose wallet secrets after a lending session by calling [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference), or the matching dispose method on your smart account type. + +```javascript title="Dispose after a Morpho lending session" +try { + await morpho.supply({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n + }) +} finally { + account.dispose() +} +``` + +For ERC-4337 accounts, use [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) on the smart-account type. Clear references to [`MorphoProtocolEvm`](/sdk/lending-modules/lending-morpho-evm/api-reference) when the session ends. + +## Next Steps + +- [Lending operations](/sdk/lending-modules/lending-morpho-evm/guides/lending-operations/) +- [Get started](/sdk/lending-modules/lending-morpho-evm/guides/get-started/) +- [API reference](/sdk/lending-modules/lending-morpho-evm/api-reference) + +*** + +## Lending Operations +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/guides/lending-operations +Description: Supply, withdraw, manage collateral, borrow, repay, quote fees, handle requirements, and read positions. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide walks through [vault supply](#vault-supply), [vault withdraw](#vault-withdraw), [collateral](#collateral), [borrow](#borrow), [repay](#repay), [requirements](#requirements), [quotes](#quotes-before-sending), [ERC-4337 usage](#erc-4337-smart-accounts), and [position reads](#reading-positions). It assumes a [`MorphoProtocolEvm`](/sdk/lending-modules/lending-morpho-evm/api-reference) instance named `morpho`. + +## Vault supply + +Deposit into the configured Morpho Vault V2 target with [`supply()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Supply USDT to the configured vault" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const requirements = await morpho.getSupplyRequirements({ + token: USDT, + amount: 1000000n +}) + +console.log('Supply requirements:', requirements) + +const tx = await morpho.supply({ + token: USDT, + amount: 1000000n +}) + +console.log('Supply tx hash:', tx.hash) +``` + +The `token` must match the configured vault asset. + +## Vault withdraw + +Withdraw from the configured vault with [`withdraw()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Withdraw USDT from the configured vault" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const tx = await morpho.withdraw({ + token: USDT, + amount: 1000000n +}) + +console.log('Withdraw tx hash:', tx.hash) +``` + +If you pass `to`, it must equal the connected wallet address. + +## Collateral + +Supply collateral to the configured Morpho Blue market with [`supplyCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Supply collateral" +const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' + +const requirements = await morpho.getSupplyCollateralRequirements({ + token: COLLATERAL, + amount: 1000000000000000000n +}) + +console.log('Collateral requirements:', requirements) + +const tx = await morpho.supplyCollateral({ + token: COLLATERAL, + amount: 1000000000000000000n +}) + +console.log('Collateral supply tx hash:', tx.hash) +``` + +Withdraw collateral with [`withdrawCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Withdraw collateral" +const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' + +const tx = await morpho.withdrawCollateral({ + token: COLLATERAL, + amount: 1000000000000000000n +}) + +console.log('Collateral withdrawal tx hash:', tx.hash) +``` + +The collateral token must match the configured market collateral token. If you pass `to`, it must equal the connected wallet address. + +## Borrow + +Borrow from the configured market with [`borrow()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Borrow USDT" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const requirements = await morpho.getBorrowRequirements({ + token: USDT, + amount: 1000000n +}) + +console.log('Borrow requirements:', requirements) + +const tx = await morpho.borrow({ + token: USDT, + amount: 1000000n +}) + +console.log('Borrow tx hash:', tx.hash) +``` + +The borrow token must match the configured market loan token. + +## Repay + +Repay by asset amount, or pass `amount: 'max'` to repay current borrow shares: + +```javascript title="Repay max borrow shares" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' + +const requirements = await morpho.getRepayRequirements({ + token: USDT, + amount: 'max' +}) + +console.log('Repay requirements:', requirements) + +const tx = await morpho.repay({ + token: USDT, + amount: 'max' +}) + +console.log('Repay tx hash:', tx.hash) +``` + + +For `amount: "max"`, Morpho repays borrow shares. The loan-token transfer amount returned by `getRepayRequirements()` is computed from live market state, so accrued interest can make a delayed approval or permit insufficient. Re-run `getRepayRequirements({ amount: "max" })` immediately before `repay()`, or approve `getChainAddresses(chainId).bundler3.generalAdapter1` with a small buffer above current debt and pass a non-zero `slippageTolerance`. `slippageTolerance` caps the repay share price or transfer amount; it is not approval slippage. Residual loan tokens pulled in shares mode are skimmed back to the user. + + +The repay token must match the configured market loan token. + +## Requirements + +Morpho SDK actions can require approvals, Permit or Permit2 signatures, or Morpho authorization before the final action. + +| Requirement source | Final action | +|--------------------|--------------| +| `getSupplyRequirements()` | `supply()` | +| `getSupplyCollateralRequirements()` | `supplyCollateral()` | +| `getBorrowRequirements()` | `borrow()` | +| `getRepayRequirements()` | `repay()` | + +For EOA accounts, send returned transaction requirements before the final operation. For signature requirements, call the returned requirement's `sign(client, userAddress)` method and pass the result as `requirementSignature`. + +```javascript title="Resolve EOA requirements before the final action" +async function resolveRequirements({ account, walletClient, userAddress, requirements }) { + let requirementSignature + + for (const requirement of requirements) { + if (typeof requirement.sign === 'function') { + requirementSignature = await requirement.sign(walletClient, userAddress) + continue + } + + await account.sendTransaction({ + to: requirement.to, + value: requirement.value, + data: requirement.data + }) + } + + return requirementSignature +} + +const requirements = await morpho.getSupplyRequirements({ + token: USDT, + amount: 1000000n +}) + +const requirementSignature = await resolveRequirements({ + account, + walletClient, + userAddress, + requirements +}) + +const tx = await morpho.supply({ + token: USDT, + amount: 1000000n, + requirementSignature +}) +``` + +Create `walletClient` with `viem` using the same signer and address as the WDK EVM account. Borrow requirements can be authorization transactions or, when `supportSignature: true` is enabled, signable authorization requests. Send only transaction requirements; sign a request and pass the resulting signature to `borrow()`: + +```javascript title="Resolve borrow authorization requirements" +const requirements = await morpho.getBorrowRequirements({ + token: USDT, + amount: 1000000n +}) + +let requirementSignature + +for (const requirement of requirements) { + if (typeof requirement.sign === 'function') { + requirementSignature = await requirement.sign(walletClient, userAddress) + continue + } + + await account.sendTransaction({ + to: requirement.to, + value: requirement.value, + data: requirement.data + }) +} + +await morpho.borrow({ + token: USDT, + amount: 1000000n, + requirementSignature +}) +``` + +For ERC-4337 accounts, you can batch returned transaction requirements with your account-level flow when supported by the wallet module. Signature requirements still need to be signed before the final action. + + +Morpho SDK enforces builder and executor invariants for bundled actions. In this WDK adapter, `onBehalfOf` and vault or collateral withdrawal `to` must equal the connected wallet address when set. + + +## Quotes before sending + +Quote helpers build the target transaction and return the account-level fee estimate without sending it: + +```javascript title="Quote Morpho operations" +const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' + +const supplyQuote = await morpho.quoteSupply({ token: USDT, amount: 1000000n }) +const withdrawQuote = await morpho.quoteWithdraw({ token: USDT, amount: 1000000n }) +const collateralQuote = await morpho.quoteSupplyCollateral({ + token: COLLATERAL, + amount: 1000000000000000000n +}) +const borrowQuote = await morpho.quoteBorrow({ token: USDT, amount: 1000000n }) +const repayQuote = await morpho.quoteRepay({ token: USDT, amount: 'max' }) + +console.log({ + supplyQuote, + withdrawQuote, + collateralQuote, + borrowQuote, + repayQuote +}) +``` + + +Quotes do not guarantee that a later transaction will succeed if balances, allowances, authorization, market state, or vault state change. + + +## ERC-4337 smart accounts + +You can use the same methods with [`WalletAccountEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and pass a second `config` argument for per-call gas payment overrides. + +```javascript title="Supply with an ERC-4337 paymaster override" +const result = await morpho.supply( + { + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1000000n + }, + { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } + } +) + +console.log('Supply hash:', result.hash) +``` + +Use token addresses that exist on the same chain as the smart account RPC. + +## Reading positions + +Read the configured vault position with [`getVaultPosition()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Read configured vault position" +const vaultPosition = await morpho.getVaultPosition() + +console.log({ + shares: vaultPosition.shares, + assets: vaultPosition.assets, + vaultAddress: vaultPosition.vaultAddress +}) +``` + +Read the configured market position with [`getMarketPosition()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Read configured market position" +const marketPosition = await morpho.getMarketPosition() + +console.log({ + supplyShares: marketPosition.supplyShares, + borrowShares: marketPosition.borrowShares, + borrowAssets: marketPosition.borrowAssets, + collateral: marketPosition.collateral, + marketId: marketPosition.marketId +}) +``` + +Read both configured positions with [`getAccountData()`](/sdk/lending-modules/lending-morpho-evm/api-reference): + +```javascript title="Read combined Morpho account data" +const data = await morpho.getAccountData() + +console.log({ + vaultAssets: data.vaultAssets, + marketBorrowAssets: data.marketBorrowAssets, + collateral: data.collateral, + vaultAddress: data.vaultAddress, + marketId: data.marketId +}) +``` + +## Next Steps + +- [Handle errors](/sdk/lending-modules/lending-morpho-evm/guides/handle-errors/) +- [Get started](/sdk/lending-modules/lending-morpho-evm/guides/get-started/) +- [API reference](/sdk/lending-modules/lending-morpho-evm/api-reference) + +*** + +## Lending Morpho EVM Guides +URL: https://docs.wdk.tether.io/sdk/lending-modules/lending-morpho-evm/usage +Description: How to install and use @morpho-org/wdk-protocol-lending-morpho-evm on EVM + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +# Usage + +The [@morpho-org/wdk-protocol-lending-morpho-evm](https://www.npmjs.com/package/@morpho-org/wdk-protocol-lending-morpho-evm) community module exposes Morpho Vault V2 and Morpho Blue operations for WDK-compatible EVM accounts. Follow the guides below for setup, lending operations, and error handling. + + + +Install the package, create MorphoProtocolEvm, and review prerequisites. + + +Supply, withdraw, manage collateral, borrow, repay, quotes, requirements, and position reads. + + +Handle target, token, requirement, and transaction failures. + + + + + +Get started with WDK in a Node.js environment + + +Presets, explicit targets, and Morpho SDK options + + +Methods and parameters for MorphoProtocolEvm + + + + + +*** + +## Pricing Modules Overview +URL: https://docs.wdk.tether.io/sdk/pricing-modules +Description: Explore WDK pricing modules for current prices, price data, and historical price series. + +WDK pricing modules provide `PricingClient` implementations for external market data sources. Use them directly when you need raw provider results, or pass them into `PricingProvider` when you want caching and ordered failover across multiple clients. + +## Pricing Client Modules + +| Module | Provider | Status | Documentation | +|--------|----------|--------|---------------| +| [`@tetherto/wdk-pricing-bitfinex-http`](https://github.com/tetherto/wdk-pricing-bitfinex-http) | Bitfinex | Ready | [Price Rates](/tools/price-rates/) | +| [`@tetherto/wdk-pricing-coingecko-http`](https://github.com/tetherto/wdk-pricing-coingecko-http) | CoinGecko | Ready | [Documentation](/sdk/pricing-modules/pricing-coingecko-http/) | + +## Provider Compatibility + +Pricing clients implement the shared [`PricingClient`](/tools/price-rates/api-reference#interface-pricingclient-abstract) surface: + +- `getCurrentPrice(from, to)` +- `getMultiCurrentPrices(list)` +- `getMultiPriceData(list)` +- `getHistoricalPrice(from, to, opts?)` + +Wrap one client with `PricingProvider` for in-memory last-price caching, or pass an ordered array of clients to fail over when one data source is unavailable. + +## Next Steps + + + +Fetch current and historical prices through CoinGecko. + + +Use Bitfinex and the shared pricing provider. + + +Review the CoinGecko client methods and options. + + + +*** + +## Need Help? + + + +*** + +## Pricing CoinGecko HTTP Overview +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http +Description: Overview of the @tetherto/wdk-pricing-coingecko-http module + +The `@tetherto/wdk-pricing-coingecko-http` module provides a CoinGecko-backed `PricingClient` for current prices, batched price lookups, price data with 24-hour change, and historical price series. + +Use it when you want a CoinGecko data source for WDK price displays, balance valuation, charts, or as a fallback client behind [`PricingProvider`](/tools/price-rates/api-reference#class-pricingprovider). + + +This module is published as `v1.0.0-beta.1`. It uses CoinGecko's HTTP API and is subject to CoinGecko rate limits, data availability, and API-key tier behavior. + + +## Features + +- **Current prices**: Fetch one asset/currency pair with `getCurrentPrice()` +- **Batch prices**: Fetch multiple pairs in one request with `getMultiCurrentPrices()` +- **Price data**: Fetch last price plus derived 24-hour change with `getMultiPriceData()` +- **Historical series**: Fetch range-based price points with optional downsampling +- **Coin ID mapping**: Extend or override ticker-to-CoinGecko ID mappings per client +- **API key support**: Use Demo or Pro CoinGecko API keys with automatic auth-header selection +- **Bare runtime entrypoint**: Import through the package's `bare` export in Bare environments + +## Default Assets + +The built-in ticker map covers: + +| Symbol | CoinGecko ID | +|--------|--------------| +| `BTC` | `bitcoin` | +| `ETH` | `ethereum` | +| `USDT` | `tether` | +| `XAUT` | `tether-gold` | +| `USAT` | `usa` | + +Add other assets through the `coinIds` constructor option. + +## Provider Integration + +`CoingeckoPricingClient` extends the shared `PricingClient` base class from `@tetherto/wdk-pricing-provider`. You can pass it directly to `PricingProvider`: + +```javascript title="Use with PricingProvider" +import { PricingProvider } from '@tetherto/wdk-pricing-provider' +import { CoingeckoPricingClient } from '@tetherto/wdk-pricing-coingecko-http' + +const provider = new PricingProvider({ + client: new CoingeckoPricingClient() +}) + +const btcUsd = await provider.getLastPrice('BTC', 'USD') +``` + +## Next Steps + + + +Install the module and follow the task guides. + + +Configure API keys, Pro host access, and custom coin IDs. + + +Review constructor options, methods, return values, and errors. + + + +*** + +## Need Help? + + + +*** + +## Pricing CoinGecko HTTP API Reference +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http/api-reference +Description: API Reference for @tetherto/wdk-pricing-coingecko-http. + +# API Reference + +## Package + +```bash +npm install @tetherto/wdk-pricing-coingecko-http +``` + +```javascript title="Import" +import { CoingeckoPricingClient } from '@tetherto/wdk-pricing-coingecko-http' +``` + +## Class: `CoingeckoPricingClient` + +CoinGecko-backed implementation of `PricingClient` from `@tetherto/wdk-pricing-provider`. + +### Constructor + +```javascript +new CoingeckoPricingClient(options?) +``` + +| Option | Type | Description | +|--------|------|-------------| +| `baseURL` | `string` | CoinGecko API base URL. Defaults to `https://api.coingecko.com/api/v3`. | +| `coinIds` | `Record` | Symbol-to-CoinGecko-ID overrides merged with the built-in map. | +| `apiKey` | `string` | CoinGecko API key. Uses the Demo or Pro header based on `baseURL`. | + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `getCurrentPrice(from, to)` | Fetch current price for one pair | `Promise` | +| `getMultiCurrentPrices(list)` | Fetch current prices for many pairs in one request | `Promise>` | +| `getMultiPriceData(list)` | Fetch last price plus 24-hour change for many pairs | `Promise>` | +| `getHistoricalPrice(from, to, opts)` | Fetch historical prices for a pair over a time range | `Promise` | + +`from` is a ticker symbol resolved through `coinIds`; `to` is a CoinGecko `vs_currency` code. Both are case-insensitive. + +### `getCurrentPrice(from, to)` + +```javascript title="Current price" +const price = await client.getCurrentPrice('BTC', 'USD') +``` + +Returns the current price as a number, or `null` when CoinGecko returns no data for the pair. + +Throws when `from` has no configured CoinGecko ID. + +### `getMultiCurrentPrices(list)` + +```javascript title="Batch current prices" +const prices = await client.getMultiCurrentPrices([ + { from: 'BTC', to: 'USD' }, + { from: 'ETH', to: 'EUR' } +]) +``` + +The client de-duplicates CoinGecko IDs and quote currencies before calling `/simple/price`. Results are returned in the same order as the input list. Missing entries return `null`. + +An empty input list returns an empty array. + +### `getMultiPriceData(list)` + +```javascript title="Batch price data" +const data = await client.getMultiPriceData([ + { from: 'BTC', to: 'USD' } +]) +``` + +Returns `PriceData` objects: + +```typescript +type PriceData = { + lastPrice: number + dailyChange: number + dailyChangeRelative: number +} +``` + +CoinGecko returns 24-hour change as a percentage. The client derives `dailyChange` from `lastPrice` and that percentage, so the absolute change is an approximation. + +### `getHistoricalPrice(from, to, opts)` + +```javascript title="Historical prices" +const series = await client.getHistoricalPrice('BTC', 'USD', { + start: Date.now() - 7 * 24 * 60 * 60 * 1000, + end: Date.now(), + maxEntries: 100 +}) +``` + +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `start` | `number` | Yes | Range start as a Unix timestamp in milliseconds. | +| `end` | `number` | Yes | Range end as a Unix timestamp in milliseconds. | +| `maxEntries` | `number` | No | Evenly downsample the returned series to at most this many points. | + +Returns points ordered oldest first: + +```typescript +type HistoricalPriceResult = { + price: number + timestamp: number +} +``` + +When `maxEntries` is set, the client keeps the first and last point and samples the middle of the series evenly. Without `maxEntries`, it returns every point from CoinGecko. + +Throws when `start` or `end` is missing. On the public or Demo API host, it also throws when `start` is older than the trailing 365 days. Use the Pro host and Pro key for older ranges. + +## Error Handling + +| Condition | Behavior | +|-----------|----------| +| Unknown `from` symbol | Throws `Unknown symbol: ...` | +| Missing `start` or `end` | Throws `start and end timestamps are required` | +| Historical range older than 365 days on non-Pro host | Throws `Start date older than 365 days requires a CoinGecko Pro API key` | +| CoinGecko has no data for a pair | Resolves to `null` for current and batched price methods | +| CoinGecko rate limit or network error | Rejects with the underlying HTTP client error | + +*** + +## Need Help? + + + +*** + +## Pricing CoinGecko HTTP Configuration +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http/configuration +Description: Configure @tetherto/wdk-pricing-coingecko-http options. + +# Configuration + +Create a `CoingeckoPricingClient` with no options for public CoinGecko API access: + +```javascript title="Public CoinGecko client" +import { CoingeckoPricingClient } from '@tetherto/wdk-pricing-coingecko-http' + +const client = new CoingeckoPricingClient() +``` + +The public host is `https://api.coingecko.com/api/v3`. + +## Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `baseURL` | `string` | `https://api.coingecko.com/api/v3` | CoinGecko API base URL. Use `https://pro-api.coingecko.com/api/v3` with a Pro key. | +| `apiKey` | `string` | none | CoinGecko API key. The client chooses `x-cg-demo-api-key` or `x-cg-pro-api-key` from `baseURL`. | +| `coinIds` | `Record` | built-in map | Symbol-to-CoinGecko-ID overrides merged on top of defaults. | + +## API Keys + +Pass `apiKey` when you want CoinGecko authenticated requests: + +```javascript title="Demo API key" +const demoClient = new CoingeckoPricingClient({ + apiKey: process.env.COINGECKO_API_KEY +}) +``` + +Use the Pro API host with a Pro key: + +```javascript title="Pro API key" +const proClient = new CoingeckoPricingClient({ + baseURL: 'https://pro-api.coingecko.com/api/v3', + apiKey: process.env.COINGECKO_PRO_API_KEY +}) +``` + +When `baseURL` includes `pro-api.coingecko.com`, the client sends the key with `x-cg-pro-api-key`. Otherwise it sends `x-cg-demo-api-key`. + +## Coin ID Overrides + +CoinGecko uses asset IDs such as `bitcoin`, `ethereum`, and `tether-gold`. Tickers are not always enough to derive the correct ID, so the client keeps a small default map and lets you extend it. + +```javascript title="Add custom symbols" +const client = new CoingeckoPricingClient({ + coinIds: { + PEPE: 'pepe', + SHIB: 'shiba-inu' + } +}) + +const pepeUsd = await client.getCurrentPrice('PEPE', 'USD') +``` + +You can also override a built-in mapping: + +```javascript title="Override a default symbol" +const client = new CoingeckoPricingClient({ + coinIds: { + BTC: 'wrapped-bitcoin' + } +}) +``` + +## Provider Failover + +Use `CoingeckoPricingClient` as a fallback behind another pricing client by passing an ordered array to `PricingProvider`: + +```javascript title="Fail over to CoinGecko" +import { PricingProvider } from '@tetherto/wdk-pricing-provider' +import { BitfinexPricingClient } from '@tetherto/wdk-pricing-bitfinex-http' +import { CoingeckoPricingClient } from '@tetherto/wdk-pricing-coingecko-http' + +const provider = new PricingProvider({ + client: [ + new BitfinexPricingClient(), + new CoingeckoPricingClient({ apiKey: process.env.COINGECKO_API_KEY }) + ], + retries: 1 +}) + +const btcUsd = await provider.getLastPrice('BTC', 'USD') +``` + +The provider failover layer retries connection errors. A pair that resolves to `null` is still an unavailable result for that client. + +## Runtime Notes + +- Install `@tetherto/wdk-pricing-provider` when you want caching or failover through `PricingProvider`. +- The package exposes a Bare runtime entrypoint through its package export map. +- Integration tests hit the live CoinGecko API and can fail with `429` under repeated free-tier runs. + +*** + +## Fetch Current Prices +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http/guides/fetch-current-prices +Description: Fetch current prices and batched price data with CoingeckoPricingClient. + +This guide shows how to read current prices for one pair, batch multiple pairs, and use the client through `PricingProvider`. + +## One Pair + +Use `getCurrentPrice(from, to)` for a single ticker/currency pair: + +```javascript title="Fetch one pair" +const price = await client.getCurrentPrice('BTC', 'USD') + +if (price === null) { + // Show an unavailable-price state. +} else { + console.log(price) +} +``` + +## Multiple Pairs + +Use `getMultiCurrentPrices(list)` to batch pairs into one `/simple/price` request: + +```javascript title="Fetch many pairs" +const prices = await client.getMultiCurrentPrices([ + { from: 'BTC', to: 'USD' }, + { from: 'ETH', to: 'USD' }, + { from: 'BTC', to: 'EUR' } +]) + +console.log(prices) +``` + +The result order matches the input order. If CoinGecko does not return data for one entry, that entry is `null`. + +## Price Data with Daily Change + +Use `getMultiPriceData(list)` when you need last price and 24-hour change: + +```javascript title="Fetch price data" +const data = await client.getMultiPriceData([ + { from: 'BTC', to: 'USD' }, + { from: 'ETH', to: 'USD' } +]) + +for (const entry of data) { + if (entry === null) continue + + console.log(entry.lastPrice) + console.log(entry.dailyChange) + console.log(entry.dailyChangeRelative) +} +``` + +CoinGecko returns the 24-hour change as a percentage. The client derives the absolute `dailyChange`, so use it as an approximation. + +## Use PricingProvider + +Wrap the client with `PricingProvider` for last-price caching: + +```javascript title="Cached last prices" +import { PricingProvider } from '@tetherto/wdk-pricing-provider' +import { CoingeckoPricingClient } from '@tetherto/wdk-pricing-coingecko-http' + +const provider = new PricingProvider({ + client: new CoingeckoPricingClient(), + priceCacheDurationMs: 60 * 60 * 1000 +}) + +const last = await provider.getLastPrice('BTC', 'USD') +``` + +Use an ordered client array when CoinGecko should act as a fallback: + +```javascript title="Failover pricing" +const provider = new PricingProvider({ + client: [primaryClient, new CoingeckoPricingClient()], + retries: 1 +}) +``` + +## Next Steps + +- [Fetch historical prices](/sdk/pricing-modules/pricing-coingecko-http/guides/fetch-historical-prices) +- [Handle errors](/sdk/pricing-modules/pricing-coingecko-http/guides/handle-errors) +- [API reference](/sdk/pricing-modules/pricing-coingecko-http/api-reference) + +*** + +## Fetch Historical Prices +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http/guides/fetch-historical-prices +Description: Fetch historical price series with CoingeckoPricingClient. + +Use `getHistoricalPrice(from, to, opts)` for chart data over a time range. + +## Fetch a Range + +Pass `start` and `end` as Unix timestamps in milliseconds: + +```javascript title="Fetch seven days of BTC/USD" +const end = Date.now() +const start = end - 7 * 24 * 60 * 60 * 1000 + +const series = await client.getHistoricalPrice('BTC', 'USD', { + start, + end +}) + +console.log(series) +``` + +Each point has a `price` and `timestamp`: + +```typescript +type HistoricalPriceResult = { + price: number + timestamp: number +} +``` + +Results are ordered oldest first. + +## Downsample Long Series + +Use `maxEntries` when you only need a fixed number of points for a chart: + +```javascript title="Downsample to 100 points" +const series = await client.getHistoricalPrice('BTC', 'USD', { + start, + end, + maxEntries: 100 +}) +``` + +The client keeps the first and last point and samples the middle of the series evenly. + +## Free and Pro Ranges + +CoinGecko's public and Demo API host supports historical data inside the trailing 365-day window. For older ranges, configure the Pro host and a Pro key: + +```javascript title="Older historical range with Pro" +const client = new CoingeckoPricingClient({ + baseURL: 'https://pro-api.coingecko.com/api/v3', + apiKey: process.env.COINGECKO_PRO_API_KEY +}) +``` + +## Next Steps + +- [Configure the client](/sdk/pricing-modules/pricing-coingecko-http/configuration) +- [Handle errors](/sdk/pricing-modules/pricing-coingecko-http/guides/handle-errors) +- [API reference](/sdk/pricing-modules/pricing-coingecko-http/api-reference) + +*** + +## Get Started +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http/guides/get-started +Description: Install @tetherto/wdk-pricing-coingecko-http and create a CoinGecko pricing client. + +This guide covers installing the package, creating a `CoingeckoPricingClient`, and making a first current-price request. + +## Installation + +```bash title="Install with npm" +npm install @tetherto/wdk-pricing-coingecko-http +``` + +Install the shared provider package when you want caching or failover: + +```bash title="Install with PricingProvider" +npm install @tetherto/wdk-pricing-coingecko-http @tetherto/wdk-pricing-provider +``` + +## Create a Client + +```javascript title="Create a public CoinGecko client" +import { CoingeckoPricingClient } from '@tetherto/wdk-pricing-coingecko-http' + +const client = new CoingeckoPricingClient() +``` + +The no-options constructor uses CoinGecko's public API host. + +## Fetch a Price + +```javascript title="Fetch BTC/USD" +const btcUsd = await client.getCurrentPrice('BTC', 'USD') + +if (btcUsd === null) { + console.log('BTC/USD is unavailable from CoinGecko') +} else { + console.log('BTC/USD:', btcUsd) +} +``` + +`BTC` is resolved to the CoinGecko ID `bitcoin`, and `USD` is sent as `usd`. + +## Add an API Key + +```javascript title="Use a Demo API key" +const client = new CoingeckoPricingClient({ + apiKey: process.env.COINGECKO_API_KEY +}) +``` + +For CoinGecko Pro, set the Pro base URL too: + +```javascript title="Use a Pro API key" +const client = new CoingeckoPricingClient({ + baseURL: 'https://pro-api.coingecko.com/api/v3', + apiKey: process.env.COINGECKO_PRO_API_KEY +}) +``` + +## Next Steps + +- [Fetch current prices](/sdk/pricing-modules/pricing-coingecko-http/guides/fetch-current-prices) +- [Fetch historical prices](/sdk/pricing-modules/pricing-coingecko-http/guides/fetch-historical-prices) +- [Configure symbols and API keys](/sdk/pricing-modules/pricing-coingecko-http/configuration) + +*** + +## Handle Errors +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http/guides/handle-errors +Description: Handle unavailable pairs, unknown symbols, range limits, and CoinGecko rate limits. + +This guide covers the main error and unavailable-data cases from `CoingeckoPricingClient`. + +## Unknown Symbols + +The client throws when `from` is not in its `coinIds` map: + +```javascript title="Add missing symbols" +const client = new CoingeckoPricingClient({ + coinIds: { + PEPE: 'pepe' + } +}) +``` + +Handle the error if symbols come from user input: + +```javascript title="Catch unknown symbols" +try { + const price = await client.getCurrentPrice(userSymbol, 'USD') + console.log(price) +} catch (error) { + if (error.message.includes('Unknown symbol')) { + console.log('Add this symbol to coinIds before requesting it.') + } +} +``` + +## Unavailable Pairs + +Current-price methods resolve to `null` when CoinGecko returns no data for a pair: + +```javascript title="Handle null prices" +const price = await client.getCurrentPrice('BTC', 'USD') + +if (price === null) { + console.log('Price unavailable') +} +``` + +Batch methods preserve input order and place `null` only at unavailable entries. + +## Historical Range Limits + +`getHistoricalPrice()` requires both `start` and `end`: + +```javascript title="Required range" +await client.getHistoricalPrice('BTC', 'USD', { + start, + end +}) +``` + +On the public or Demo API host, a `start` value older than the trailing 365 days throws. Use CoinGecko Pro for older ranges. + +## Rate Limits and Network Errors + +CoinGecko rate-limit and network failures reject with the underlying HTTP client error. Handle those failures separately from `null` results: + +```javascript title="Separate unavailable data from request failures" +try { + const price = await client.getCurrentPrice('BTC', 'USD') + + if (price === null) { + console.log('Pair unavailable') + } +} catch (error) { + console.error('CoinGecko request failed:', error.message) +} +``` + +When using `PricingProvider` with multiple clients, connection errors can trigger failover to the next client in the ordered list. A `null` result means the client completed the request but did not resolve that pair. + +## Next Steps + +- [Fetch current prices](/sdk/pricing-modules/pricing-coingecko-http/guides/fetch-current-prices) +- [Fetch historical prices](/sdk/pricing-modules/pricing-coingecko-http/guides/fetch-historical-prices) +- [Configuration](/sdk/pricing-modules/pricing-coingecko-http/configuration) + +*** + +## Pricing CoinGecko HTTP Guides +URL: https://docs.wdk.tether.io/sdk/pricing-modules/pricing-coingecko-http/usage +Description: Install and use @tetherto/wdk-pricing-coingecko-http for current and historical prices. + +# Usage + +The `@tetherto/wdk-pricing-coingecko-http` module exposes a CoinGecko-backed pricing client for spot prices, batched price data, and historical series. + + + +Install the package and create a CoinGecko pricing client. + + +Fetch one pair, batch multiple pairs, and wrap the client with PricingProvider. + + +Read historical price points and downsample chart data. + + +Handle unknown symbols, unavailable pairs, old free-tier ranges, and rate limits. + + + + + +Configure API hosts, API keys, and symbol mappings. + + +Review method signatures and return values. + + + + + +*** + +## Swap Modules Overview +URL: https://docs.wdk.tether.io/sdk/swap-modules +Description: Explore WDK swap modules for token swap integrations across supported providers. + +The Swap Development Kit (WDK) provides a set of modules that support swap on top of multiple blockchain networks. All modules share a common interface, ensuring consistent behavior across different blockchain implementations. + +## Swap Protocol Modules + +DeFi swap functionality for token exchanges across different DEXs: + +| Module | Blockchain | Status | Documentation | +|--------|------------|--------|---------------| +| [`@tetherto/wdk-protocol-swap-velora-evm`](https://github.com/tetherto/wdk-protocol-swap-velora-evm) | EVM | ✅ Ready | [Documentation](/sdk/swap-modules/swap-velora-evm/) | + +## Next steps + +To get started with WDK modules, follow these steps: + +1. Get up and running quickly with our [Quickstart Guide](/start-building/nodejs-bare-quickstart) +2. Choose the modules that best fit your needs from the tables above +3. Check specific documentation for modules you wish to use + +You can also: + +- Learn about key concepts like [Account Abstraction](/resources/concepts#account-abstraction) and other important definitions +- Use one of our ready-to-use examples to be production ready + +## Swidge provider routes + +For new swap or bridge provider integrations, choose a released [Swidge provider module](/sdk/swidge-modules). Swidge can represent swap-only routes, bridge-only routes, and combined swap-and-bridge routes. Existing standalone swap module references remain available for released modules that have not moved to Swidge. + +For a released community provider, see [Orchestra](/sdk/swidge-modules/swidge-orchestra), which implements Swidge for BTC and stablecoin routes returned by Flashnet Orchestra. + +*** + +## Swap tokens with Velora +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm +Description: Quote and execute EVM token swaps through Velora for standard and smart-account wallet flows. + +Use the Velora swap module to quote and execute token swaps on EVM chains. It works with standard EVM accounts and ERC-4337 smart accounts. + +## Features + +- **Token Swapping**: Execute token swaps through velora on supported EVM networks +- **Account Abstraction**: Compatible with standard EVM accounts and ERC‑4337 smart accounts +- **Fee Controls**: Optional `swapMaxFee` to cap gas costs +- **Approval-aware swaps**: Approve input tokens with the wallet account before swapping when allowance is required +- **Provider Flexibility**: Works with JSON‑RPC URLs and EIP‑1193 providers +- **TypeScript Support**: Full TypeScript definitions included + +## Supported Networks + +Works with EVM networks supported by velora (e.g., Ethereum, Polygon, Arbitrum, etc.). A working RPC provider is required. + +## Wallet Compatibility + +The swap service supports multiple EVM wallet types: + +- **Standard EVM Wallets**: `@tetherto/wdk-wallet-evm` accounts +- **ERC‑4337 Smart Accounts**: `@tetherto/wdk-wallet-evm-erc-4337` accounts with bundler/paymaster +- **Read‑Only Accounts**: For quoting swaps without sending transactions + +## Key Components + +- **velora Integration**: Uses velora aggregator for routing and quotes +- **Quote System**: Pre‑transaction fee and amount estimation via `quoteSwap` +- **AA Integration**: Optional paymaster, sponsorship, native-fee, and fee-cap overrides when using ERC‑4337 +- **Read-only quoting**: Quote swaps from read-only EVM and ERC‑4337 accounts + +## Next Steps + + + +Get started with WDK in a Node.js environment + + +Get started with WDK's velora Swap Protocol configuration + + +Get started with WDK's velora Swap Protocol API + + +Get started with WDK's velora Swap Protocol usage + + + +*** + +## Need Help? + + + +*** + +## API Reference +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/api-reference +Description: API Reference for @tetherto/wdk-protocol-swap-velora-evm + +## Class: VeloraProtocolEvm + +Main class for velora token swaps on EVM. + +### Constructor + +```javascript +new VeloraProtocolEvm(account, config?) +``` + +Parameters: +- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` +- `config` (optional): + - `swapMaxFee` (`bigint`): maximum total gas fee allowed (wei) + +Example: + +```javascript +const swap = new VeloraProtocolEvm(account, { swapMaxFee: 200000000000000n }) +``` + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `swap(options, config?)` | Perform a token swap | `Promise<{hash: string, fee: bigint, tokenInAmount: bigint, tokenOutAmount: bigint}>` | +| `quoteSwap(options, config?)` | Get estimated fee and amounts | `Promise<{fee: bigint, tokenInAmount: bigint, tokenOutAmount: bigint}>` | + +--- + +### `swap(options, config?)` +Execute a swap via velora. + +Options: +- `tokenIn` (`string`): Address of the ERC‑20 token to sell +- `tokenOut` (`string`): Address of the ERC‑20 token to buy +- `tokenInAmount` (`bigint`, optional): Exact input amount (base units) +- `tokenOutAmount` (`bigint`, optional): Exact output amount (base units) +- `to` (`string`, optional): Recipient address (defaults to account address) + +Config (ERC‑4337 only): +- `paymasterToken` (`{ address: string }`, optional): Paymaster token override for this swap +- `isSponsored` (`true`, optional): Use sponsorship mode for this swap +- `sponsorshipPolicyId` (`string`, optional): Sponsorship policy override +- `useNativeCoins` (`true`, optional): Pay fees in the chain's native token +- `swapMaxFee` (`bigint`, optional): Per‑swap fee cap (wei) + +Returns: +- Standard account: `{ hash, fee, tokenInAmount, tokenOutAmount }` +- ERC‑4337 account: `{ hash, fee, tokenInAmount, tokenOutAmount }` + +Notes: +- Approve the input token with the wallet account before swapping if the spender does not already have enough allowance. +- Requires a provider; requires a non read‑only account to send transactions. + +Example: + +```javascript +const tx = await swap.swap({ + tokenIn: '0xdAC17F...ec7', // USD₮ + tokenOut: '0xC02a...6Cc2', // WETH + tokenInAmount: 1000000n +}) +``` + +--- + +### `quoteSwap(options, config?)` +Get estimated fee and token in/out amounts. + +Options are the same as `swap`. + +Returns: `{ fee, tokenInAmount, tokenOutAmount }` + +Config (ERC‑4337 only): +- `paymasterToken` (`{ address: string }`, optional): Paymaster token override for fee estimation +- `isSponsored` (`true`, optional): Use sponsorship mode for fee estimation +- `sponsorshipPolicyId` (`string`, optional): Sponsorship policy override +- `useNativeCoins` (`true`, optional): Estimate fees in the chain's native token + +Works with read‑only accounts. + +Example: + +```javascript +const quote = await swap.quoteSwap({ + tokenIn: '0xdAC17F...ec7', // USD₮ + tokenOut: '0xC02a...6Cc2', // WETH + tokenOutAmount: 500000000000000000n // 0.5 WETH +}) +``` + +--- + +## Errors + +Common errors include: +- Insufficient liquidity / no route for pair +- Fee exceeds `swapMaxFee` +- Read‑only account cannot send swaps +- Provider/RPC errors (invalid endpoint, network mismatch) + +--- + +## Types + +- `swapMaxFee: bigint` — Upper bound for gas fees (wei) +- `tokenInAmount/tokenOutAmount: bigint` — ERC‑20 base units +- `paymasterToken: { address: string }` — ERC‑4337 paymaster token override + + + +Get started with WDK in a Node.js environment + + +Get started with WDK's Swap velora EVM Protocol configuration + + +Get started with WDK's Swap velora EVM Protocol usage + + + +*** + +## Need Help? + + + +*** + +## Configuration +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/configuration +Description: Configuration options and settings for @tetherto/wdk-protocol-swap-velora-evm + +## Swap Service Configuration + +The `VeloraProtocolEvm` accepts a configuration object that defines fee controls and behavior: + +```javascript +import VeloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' + +// Create wallet account first +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +// Create swap service with configuration +const swapProtocol = new VeloraProtocolEvm(account, { + swapMaxFee: 200000000000000n // Optional: Max swap fee in wei +}) +``` + +## Account Configuration + +The swap service uses the wallet account configuration for network access and signing: + +```javascript +import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' + +// Full access account +const account = new WalletAccountEvm( + seedPhrase, + "0'/0/0", + { + provider: 'https://ethereum-rpc.publicnode.com' + } +) + +// Read-only account (quotes only) +const readOnly = new WalletAccountReadOnlyEvm( + '0xYourAddress', + { + provider: 'https://ethereum-rpc.publicnode.com' + } +) + +// Create swap service +const swapProtocol = new VeloraProtocolEvm(account, { + swapMaxFee: 200000000000000n +}) +``` + +## Configuration Options + +### Swap Max Fee + +The `swapMaxFee` option sets an upper bound for total gas costs to prevent excessive fees. + +**Type:** `bigint` (optional) +**Unit:** Wei + +**Examples:** + +```javascript +const config = { + // Cap total gas fee to 0.0002 ETH (in wei) + swapMaxFee: 200000000000000n, +} + +// Usage example +try { + const result = await swapProtocol.swap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USD₮ (6 decimals) + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH (18 decimals) + tokenInAmount: 1000000n + }) +} catch (error) { + if (error.message.includes('max fee')) { + console.error('Swap stopped: Fee too high') + } +} +``` + +## ERC‑4337 (Account Abstraction) Configuration + +When using ERC‑4337 smart accounts (`@tetherto/wdk-wallet-evm-erc-4337`), you can override fee behavior per swap and specify a paymaster token: + +```javascript +import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' + +const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { + chainId: 1, + provider: 'https://ethereum-rpc.publicnode.com', + bundlerUrl: 'YOUR_BUNDLER_URL', + paymasterUrl: 'YOUR_PAYMASTER_URL', + paymasterAddress: 'YOUR_PAYMASTER_ADDRESS', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } +}) + +const swapAA = new VeloraProtocolEvm(aa, { swapMaxFee: 200000000000000n }) + +const result = await swapAA.swap({ + tokenIn: '0xTokenIn', + tokenOut: '0xTokenOut', + tokenInAmount: 1000000n +}, { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + swapMaxFee: 200000000000000n // Per‑swap override +}) +``` + +### Per-call ERC‑4337 Config + +The second argument to `swap()` and `quoteSwap()` accepts ERC‑4337 wallet config overrides. Use it to switch paymaster token, sponsorship policy, or native-coin fee behavior for a single call. `swap()` also accepts `swapMaxFee` as a per-swap fee cap. + +**Type:** partial ERC‑4337 wallet config (optional) + +**Example:** + +```javascript +const result = await swapAA.swap({ + tokenIn: '0xdAC17F...ec7', + tokenOut: '0xC02a...6Cc2', // WETH + tokenInAmount: 1000000n +}, { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + swapMaxFee: 200000000000000n +}) +``` + +## Network Support + +velora supports multiple EVM networks (e.g., Ethereum, Polygon, Arbitrum). Ensure your account is configured with a valid provider for the target network. + +```javascript +// Ethereum Mainnet +const eth = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +// Polygon +const polygon = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://polygon-bor-rpc.publicnode.com' +}) +``` + +## Swap Options + +When calling `swap`, provide the swap parameters: + +```javascript +const swapOptions = { + tokenIn: '0xTokenIn', // ERC‑20 to sell + tokenOut: '0xTokenOut', // ERC‑20 to buy + tokenInAmount: 1000000n, // exact input (base units) + // OR + // tokenOutAmount: 1000000n, // exact output (base units) + to: '0xRecipient' // optional recipient (defaults to your address) +} + +const result = await swapProtocol.swap(swapOptions) +``` + +### Parameters + +- `tokenIn` (`string`): ERC‑20 address to sell +- `tokenOut` (`string`): ERC‑20 address to buy +- `tokenInAmount` (`bigint`, optional): exact input amount in token base units +- `tokenOutAmount` (`bigint`, optional): exact output amount in token base units +- `to` (`string`, optional): recipient address (defaults to account address) + +> Note: Use either `tokenInAmount` OR `tokenOutAmount`, not both. + + + +Get started with WDK in a Node.js environment + + +Get started with WDK's velora Swap Protocol API + + +Get started with WDK's velora Swap Protocol usage + + + +*** + +## Need Help? + + + +*** + +## Execute Swaps +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/execute-swaps +Description: Run exact-input swaps, exact-output swaps, and swaps with ERC-4337 accounts. + +This guide explains how to run a [basic exact-input swap](#basic-exact-input-swap), an [exact-output swap](#exact-output-swap), and a [swap from an ERC-4337 smart account](#swap-with-erc-4337). You should already have a [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) instance. + + +Swaps spend tokens and gas on-chain. Use amounts you control and an RPC you trust. + + +## Basic exact-input swap + +You can sell an exact amount of the input token using [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): + +```javascript title="Exact input: USDT to WETH" +const result = await swapProtocol.swap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n +}) + +console.log('Swap transaction hash:', result.hash) +console.log('Total fee (wei):', result.fee) +console.log('Tokens sold (base units):', result.tokenInAmount) +console.log('Tokens bought (base units):', result.tokenOutAmount) +``` + +## Exact output swap + +You can receive an exact amount of the output token by passing `tokenOutAmount` to [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): + +```javascript title="Exact output amount" +const result = await swapProtocol.swap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenOutAmount: 500000000000000000n +}) + +console.log('Swap hash:', result.hash) +console.log('Tokens sold (base units):', result.tokenInAmount) +console.log('Tokens bought (base units):', result.tokenOutAmount) +``` + +## Swap with ERC-4337 + +You can perform a user-operation-backed swap by constructing [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) with [`WalletAccountEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and passing paymaster options to [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): + +```javascript title="Swap with smart account and paymaster" +import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' +import VeloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' + +const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { + chainId: 1, + provider: 'https://ethereum-rpc.publicnode.com', + bundlerUrl: process.env.BUNDLER_URL, + paymasterUrl: process.env.PAYMASTER_URL, + paymasterAddress: process.env.PAYMASTER_ADDRESS, + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } +}) + +const swapAA = new VeloraProtocolEvm(aa, { swapMaxFee: 200000000000000n }) + +const result = await swapAA.swap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n +}, { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + swapMaxFee: 200000000000000n +}) + +console.log('Swap hash:', result.hash) +console.log('Total fee (wei):', result.fee) +``` + + +Token addresses must match the chain your account uses (for example, mainnet USD₮ addresses differ from Arbitrum). + + +## Next Steps + +- [Get swap quotes](/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes) before sending +- [Handle errors](/sdk/swap-modules/swap-velora-evm/guides/handle-errors) +- [Get started](/sdk/swap-modules/swap-velora-evm/guides/get-started) if you still need setup + +*** + +## Get Started +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/get-started +Description: Install the package, create VeloraProtocolEvm, and learn supported networks. + +This guide covers [installation](#installation), [create the swap protocol](#create-the-swap-protocol), and [supported networks](#supported-networks). You need [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) to follow along. + +## Installation + +Run the following to install [@tetherto/wdk-protocol-swap-velora-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-swap-velora-evm): + +```bash title="Install with npm" +npm install @tetherto/wdk-protocol-swap-velora-evm +``` + + +You also need an EVM wallet account from [`@tetherto/wdk-wallet-evm`](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm) (or an ERC-4337 account from [`@tetherto/wdk-wallet-evm-erc-4337`](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm-erc-4337)) on the same chain as your RPC provider. + + +## Create the swap protocol + +You can construct a swap client with [`new VeloraProtocolEvm(account, config?)`](/sdk/swap-modules/swap-velora-evm/api-reference) on top of [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference): + +```javascript title="Create VeloraProtocolEvm" +import VeloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://ethereum-rpc.publicnode.com' +}) + +const swapProtocol = new VeloraProtocolEvm(account, { + swapMaxFee: 200000000000000n +}) +``` + +Optional `swapMaxFee` caps the total gas fee in wei for swaps. See [configuration](/sdk/swap-modules/swap-velora-evm/configuration) for environment-specific settings. + +## Supported networks + +Velora routing works on EVM networks the aggregator supports, including **Ethereum**, **Polygon**, **Arbitrum**, and other chains where Velora exposes liquidity. Use an RPC endpoint for the network your [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference) is configured for so [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) and [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) target the correct chain. + +## Next Steps + +- [Execute swaps](/sdk/swap-modules/swap-velora-evm/guides/execute-swaps) +- [Get swap quotes](/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes) +- [Handle errors](/sdk/swap-modules/swap-velora-evm/guides/handle-errors) + +*** + +## Get Swap Quotes +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes +Description: Estimate fees and amounts with quoteSwap before executing a swap. + +This guide shows how to [quote before swapping](#quote-before-swapping) and use quotes for [fee estimation](#fee-estimation). Quotes use [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference), which works with read-only accounts as well as signing accounts. + +## Quote before swapping + +You can preview fee and token amounts for the same parameters you would pass to [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) using [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference): + +```javascript title="Quote exact input swap" +const quote = await swapProtocol.quoteSwap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n +}) + +console.log('Estimated fee (wei):', quote.fee) +console.log('Tokens in (base units):', quote.tokenInAmount) +console.log('Tokens out (base units):', quote.tokenOutAmount) +``` + +You can quote an exact-output style trade the same way by passing `tokenOutAmount` instead of `tokenInAmount` to [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference): + +```javascript title="Quote exact output swap" +const quote = await swapProtocol.quoteSwap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenOutAmount: 500000000000000000n +}) + +console.log('Estimated fee (wei):', quote.fee) +console.log('Required token in (base units):', quote.tokenInAmount) +``` + +With an ERC‑4337 account, pass the optional second argument to preview the fee for a specific paymaster, sponsorship, or native-fee configuration: + +```javascript title="Quote with ERC-4337 fee config" +const quote = await swapAA.quoteSwap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n +}, { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } +}) +``` + +## Fee estimation + +You can read `quote.fee` from [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) as the estimated total swap fee in wei before calling [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): + +```javascript title="Quote fee before deciding" +const quote = await swapProtocol.quoteSwap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n +}) + +const maxFee = 200000000000000n +console.log('Quoted fee (wei):', quote.fee, 'cap:', maxFee) +``` + +You can compare that estimate to `swapMaxFee` on [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) and only then call [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) when the quote is within your cap: + +```javascript title="Swap when fee is under cap" +const maxFee = 200000000000000n +const quote = await swapProtocol.quoteSwap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n +}) + +if (quote.fee <= maxFee) { + const result = await swapProtocol.swap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n + }) + console.log('Swap hash:', result.hash) +} +``` + + +On-chain conditions can change between quote and execution. The executed [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) may still differ slightly from the last [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) result. + + +## Next Steps + +- [Execute swaps](/sdk/swap-modules/swap-velora-evm/guides/execute-swaps) +- [Handle errors](/sdk/swap-modules/swap-velora-evm/guides/handle-errors) +- [Get started](/sdk/swap-modules/swap-velora-evm/guides/get-started) + +*** + +## Handle Errors +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/handle-errors +Description: Catch swap failures, interpret common messages, and clean up sensitive state. + +This guide covers [swap errors](#swap-errors), [quote errors](#quote-errors), and [best practices](#best-practices) for clearing wallet material after use. + +## Swap errors + +You can detect failed swaps by wrapping [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) in `try/catch` and inspecting `error.message`: + +```javascript title="Handle swap failures" +try { + const result = await swapProtocol.swap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n + }) + console.log('Swap successful:', result.hash) +} catch (error) { + console.error('Swap failed:', error.message) + + if (error.message.includes('liquidity')) { + console.log('No route or insufficient liquidity for this pair') + } + if (error.message.includes('max fee')) { + console.log('Swap fee exceeds swapMaxFee') + } + if (error.message.includes('read-only')) { + console.log('Read-only account cannot swap') + } +} +``` + + +Match string fragments only as a convenience; production apps should prefer stable error codes from your runtime when available. + + +## Quote errors + +You can handle failures from [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) the same way, including provider or routing errors: + +```javascript title="Handle quote failures" +try { + const quote = await swapProtocol.quoteSwap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n + }) + console.log('Quoted fee (wei):', quote.fee) +} catch (error) { + console.error('Quote failed:', error.message) +} +``` + +Common causes are listed under [Errors](/sdk/swap-modules/swap-velora-evm/api-reference) in the API reference (liquidity, fee cap, read-only send attempts, RPC issues). + +## Best Practices + +You can clear signing material when a session ends by calling [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on each [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference), or [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference) if you use a manager: + +```javascript title="Dispose wallet accounts" +try { + await swapProtocol.swap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: 1000000n + }) +} finally { + account.dispose() +} +``` + +If you use an ERC-4337 account, call [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) on that account type per its API reference. Drop references to your [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) instance when you no longer need it. + +## Next Steps + +- [Get swap quotes](/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes) +- [Execute swaps](/sdk/swap-modules/swap-velora-evm/guides/execute-swaps) +- [API reference](/sdk/swap-modules/swap-velora-evm/api-reference) + +*** + +## Swap velora EVM Guides +URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/usage +Description: How to install and use @tetherto/wdk-protocol-swap-velora-evm for swapping tokens on EVM + +# Usage + +The [@tetherto/wdk-protocol-swap-velora-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-swap-velora-evm) module routes ERC-20 swaps on EVM chains through Velora. Use the guides below for setup, execution, quotes, and error handling. + + + + +Install the package, create VeloraProtocolEvm, and review supported networks. + + +Exact-input and exact-output swaps, including ERC-4337 smart accounts. + + +Quote before swapping and compare fees to your max fee cap. + + +Handle swap and quote failures and dispose wallet state safely. + + + + + + +Get started with WDK in a Node.js environment + + +RPC, fee limits, and environment settings for the Velora swap protocol + + +Methods, options, and error notes for VeloraProtocolEvm + + + + + +*** + +## Swap and Bridge Modules Overview +URL: https://docs.wdk.tether.io/sdk/swidge-modules +Description: Compare documented WDK Swidge providers and standalone swap and bridge modules. + +WDK supports documented Swidge providers alongside standalone swap and bridge modules. Use a Swidge provider for routes supplied by an external routing service, Velora for standalone EVM token swaps, or USDT0 for standalone cross-chain transfers. + + +Rows marked Community are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Swidge provider modules + +These modules implement the Swidge interface for routes returned by their providers. + +| Module | Provider | Ownership | Documentation | +|--------|----------|-----------|---------------| +| [`wdk-protocol-swidge-orchestra`](https://www.npmjs.com/package/wdk-protocol-swidge-orchestra) | Flashnet Orchestra | Community | [Documentation](/sdk/swidge-modules/swidge-orchestra/) | +| [`@rhino.fi/wdk-protocol-swidge-rhinofi`](https://www.npmjs.com/package/@rhino.fi/wdk-protocol-swidge-rhinofi) | Rhino.fi | Community | [Documentation](/sdk/swidge-modules/swidge-rhinofi/) | +| [`@symbiosis-finance/wdk-protocol-swidge-symbiosis`](https://www.npmjs.com/package/@symbiosis-finance/wdk-protocol-swidge-symbiosis) | Symbiosis | Community | [Documentation](/sdk/swidge-modules/swidge-symbiosis/) | +| [`@lifi/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi) | LI.FI | Community | [Documentation](/sdk/swidge-modules/swidge-lifi/) | + +## Standalone protocol modules + +These modules use dedicated swap or bridge interfaces rather than the Swidge interface. + +| Module | Integration | Documentation | +|--------|-------------|---------------| +| [`@tetherto/wdk-protocol-swap-velora-evm`](https://github.com/tetherto/wdk-protocol-swap-velora-evm) | Velora EVM swaps | [Documentation](/sdk/swap-modules/swap-velora-evm/) | +| [`@tetherto/wdk-protocol-bridge-usdt0-evm`](https://github.com/tetherto/wdk-protocol-bridge-usdt0-evm) | USDT0 cross-chain transfers | [Documentation](/sdk/bridge-modules/bridge-usdt0-evm/) | + +## Next steps + + + +Use the Flashnet Orchestra community provider for routes returned by Orchestra. + + +Use the Rhino.fi community provider for its supported cross-chain routes. + + +Use the Symbiosis community provider for dynamically discovered routes. + + +Use the LI.FI community provider for swap, bridge, and combined routes. + + +Use the Tether-maintained Velora module for standalone EVM token swaps. + + +Use the Tether-maintained USDT0 module for standalone cross-chain transfers. + + + +*** + +## Need Help? + + + +*** + +## LI.FI Swidge Overview +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-lifi +Description: Overview of the @lifi/wdk-protocol-swidge-lifi module for LI.FI swap and bridge routes. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +The LI.FI Swidge module lets WDK EVM accounts quote and execute swap, bridge, and combined swap-plus-bridge routes through LI.FI using the shared `SwidgeProtocol` interface. + +Use this module when an app needs live LI.FI route discovery, exact-input or exact-output quotes, execution through standard EVM accounts or ERC-4337 smart accounts, and status polling through the canonical WDK swidge status model. + +## Features + +- **Unified route interface**: Implements `quoteSwidge()`, `swidge()`, `getSwidgeStatus()`, `getSupportedChains()`, and `getSupportedTokens()`. +- **LI.FI routing**: Supports swap-only, bridge-only, and combined routes where source execution and recipient addressing are EVM-compatible. +- **EVM account support**: Works with `@tetherto/wdk-wallet-evm` and `@tetherto/wdk-wallet-evm-erc-4337`. +- **Quotes and discovery without signing**: Supports chain and token discovery without an account or provider. Accountless route quotes require `config.provider` to resolve the source chain. +- **Fee controls**: Applies optional `maxNetworkFeeBps` and `maxProtocolFeeBps` limits before execution. +- **Gasless-friendly**: Opt-in `denyBridges: NATIVE_VALUE_BRIDGE_DENY_LIST` and `allowNativeValue: false` filter native-value bridge routes and reject native-value quotes, so ERC-4337 sponsored execution can cover gas without the LI.FI route requiring separate native token value. +- **Reliability controls**: Provides request timeouts, retry handling, rate-limit classification, and typed LI.FI errors. +- **Transaction validation**: Validates quote transaction data before forwarding it to the wallet account. +- **Optional contract allowlist**: `trustedContracts` can require quote targets and approval addresses to match known LI.FI contracts. + +## Supported Routes + +LI.FI determines the live route set. Discovery can return chains from non-EVM ecosystems, while this module executes from a WDK EVM account and validates EVM recipient addresses. Call `getSupportedChains()` and `getSupportedTokens()` at runtime, then expose only routes compatible with the selected account and recipient. + +Common `toChain` aliases include: + +| Alias | Chain | +|-------|-------| +| `ethereum` | Ethereum | +| `arbitrum` | Arbitrum | +| `base` | Base | +| `optimism` | Optimism | +| `polygon` | Polygon | +| `bsc` | BNB Smart Chain | +| `avalanche` | Avalanche | +| `scroll` | Scroll | +| `zksync` | zkSync Era | + +Numeric LI.FI chain IDs are also accepted where the module accepts chain input. + +## Wallet Compatibility + +| Account type | Support | +|--------------|---------| +| `WalletAccountEvm` | Quotes and executes routes. | +| `WalletAccountEvmErc4337` | Quotes and executes routes with smart-account gas handling. | +| `WalletAccountReadOnlyEvm` | Quotes, status lookups, and discovery only. | +| No account | Chain and token discovery; quotes when `config.provider` is supplied. | + +Use a read-only EVM account when quotes should include its source address or when status lookups are needed. In no-account mode, `quoteSwidge()` uses the configured provider to resolve the source chain and omits `fromAddress` from the LI.FI request. + + +`swidge()` can approve and submit one or more EVM transactions. Show the quote, fee breakdown, destination token, destination chain, and recipient before calling it. + + +## Next Steps + + + +Configure LI.FI routing, API, fee, retry, and contract-validation options. + + +Install the package, discover supported assets, quote routes, execute routes, and poll status. + + +Review constructor options, methods, config fields, status mapping, fee mapping, and typed errors. + + + +--- + +## Need Help? + + + +*** + +## LI.FI Swidge API Reference +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-lifi/api-reference +Description: API reference for @lifi/wdk-protocol-swidge-lifi. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## LifiSwidgeProtocol + +`LifiSwidgeProtocol` extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols` and implements the shared WDK swidge methods. + +```javascript +import { + LifiSwidgeProtocol +} from '@lifi/wdk-protocol-swidge-lifi' + +const swidge = new LifiSwidgeProtocol(account, config) +``` + +## Constructor + +```typescript +new LifiSwidgeProtocol(account?, config?) +``` + +| Account | Available operations | +|---------|----------------------| +| `WalletAccountEvm` | Discovery, quote, status, and execution. | +| `WalletAccountEvmErc4337` | Discovery, quote, status, and execution through a smart account. | +| `WalletAccountReadOnlyEvm` | Discovery, quote, and status. | +| `undefined` | Chain and token discovery without a provider; quote when `config.provider` is supplied. | + +Use `WalletAccountReadOnlyEvm` for quote-only flows that should include the account address or support status lookups. In no-account mode, `quoteSwidge()` resolves the source chain through `config.provider` and omits `fromAddress` from the LI.FI request. + +## Methods + +| Method | Description | +|--------|-------------| +| `quoteSwidge(options)` | Returns a non-binding LI.FI route quote. | +| `swidge(options, config?)` | Executes a swap, bridge, or combined route. | +| `getSwidgeStatus(id, options?)` | Maps LI.FI status to WDK `SwidgeStatus`. | +| `getSupportedChains()` | Returns chains supported by LI.FI. | +| `getSupportedTokens(options?)` | Returns tokens supported by LI.FI, optionally filtered by chain context. | + +### `quoteSwidge(options)` + +```typescript +quoteSwidge(options: SwidgeOptions): Promise +``` + +Use this before execution to estimate output amounts, minimum output, and fees. + +### `swidge(options, config?)` + +```typescript +swidge( + options: SwidgeOptions, + config?: LifiSwidgeProtocolConfig +): Promise +``` + +Executes through the bound writable account. The module sends required approval transactions before the route transaction where needed. + +For quote-first flows, pass the `minAmountOut` field in `options`: set it to the `toTokenAmountMin` from a previously displayed `quoteSwidge()` result, and `swidge()` throws before any approval or transaction is sent if the fresh execution quote's minimum output falls below it. `minAmountOut` is not forwarded to LI.FI, and `quoteSwidge()` ignores it. + +Throws before execution when validation fails, a fee cap is exceeded, the quote falls below `minAmountOut`, `allowNativeValue: false` and the quote requires native value, or `trustedContracts` rejects the quote target or approval address. + +### `getSwidgeStatus(id, options?)` + +```typescript +getSwidgeStatus( + id: string, + options?: SwidgeStatusOptions +): Promise +``` + +Chain hints are optional: + +| Option | Type | Description | +|--------|------|-------------| +| `fromChain` | `string \| number` | Source chain name or LI.FI chain ID. | +| `toChain` | `string \| number` | Destination chain name or LI.FI chain ID. | + +## Config Type + +```typescript +type LifiRouteOrder = 'RECOMMENDED' | 'FASTEST' | 'CHEAPEST' + +type LifiSwidgeProtocolConfig = { + maxNetworkFeeBps?: number | bigint + maxProtocolFeeBps?: number | bigint + provider?: string | Eip1193Provider + integrator?: string + apiKey?: string + order?: LifiRouteOrder + allowBridges?: string[] + denyBridges?: string[] + allowDestinationCall?: boolean + allowNativeValue?: boolean + timeout?: number + retries?: number + retryDelay?: number + trustedContracts?: true | Record +} +``` + +## Status Mapping + +| LI.FI status | Substatus | WDK status | +|--------------|-----------|------------| +| `PENDING` | Any | `pending` | +| `DONE` | `COMPLETED` | `completed` | +| `DONE` | `PARTIAL` | `partial` | +| `DONE` | `REFUNDED` | `refunded` | +| `DONE` | `NOT_PROCESSABLE_REFUND_NEEDED` | `refund-pending` | +| `FAILED` | Any | `failed` | +| Any | Required actions present | `action-required` | + +## Fee Mapping + +| LI.FI cost | WDK fee type | Legacy field | +|------------|--------------|--------------| +| `gasCosts[].type === 'SEND'` | `network` | `fee` | +| `feeCosts[]` | `protocol` | `bridgeFee` | + +When LI.FI supplies cost-token metadata, `fee.chain` identifies that token's chain and may differ from the source or execution chain. The field is omitted when LI.FI does not supply a chain. + +## Error Types + +All LI.FI module errors extend `LifiProtocolError`. + +| Error | When thrown | +|-------|-------------| +| `LifiConfigurationError` | Required provider or configuration is missing or invalid. | +| `LifiQuoteError` | LI.FI quote or token API request fails. | +| `LifiExecutionError` | Execution cannot proceed, including fee-cap failures. | +| `LifiStatusError` | Status lookup fails. Inspect `lifiStatus`: `NOT_FOUND` can mean indexing is pending, while `INVALID` is terminal. | +| `LifiReadOnlyAccountError` | `swidge()` is called without a writable account. | +| `LifiUnsupportedChainError` | An unknown chain name is passed. | +| `LifiTimeoutError` | A LI.FI request exceeds the configured timeout. | +| `LifiNetworkError` | Network failures persist after retries. | +| `LifiRateLimitError` | LI.FI returns 429 after retries are exhausted. | +| `LifiSlippageError` | LI.FI returns 409 for a stale quote. | +| `LifiValidationError` | User input or API transaction data fails validation. | +| `LifiUntrustedContractError` | `trustedContracts` rejects a target or approval address. | + + + +Install, quote, execute, and track LI.FI routes. + + +Compare the available WDK swap and bridge integrations. + + + +*** + +## LI.FI Swidge Configuration +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-lifi/configuration +Description: Configuration options for @lifi/wdk-protocol-swidge-lifi. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +Configure `LifiSwidgeProtocol` with a WDK EVM account when you need execution, account-address-based quotes, or status lookups. Without an account, the module supports quotes and discovery when `config.provider` is supplied. + +```javascript +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +import { LifiSwidgeProtocol } from '@lifi/wdk-protocol-swidge-lifi' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://mainnet.infura.io/v3/YOUR_KEY' +}) + +const swidge = new LifiSwidgeProtocol(account, { + integrator: 'my-app', + order: 'RECOMMENDED', + maxNetworkFeeBps: 100, + maxProtocolFeeBps: 50 +}) +``` + +## Constructor + +```typescript +new LifiSwidgeProtocol(account?, config?) +``` + +| Parameter | Description | +|-----------|-------------| +| `account` | Optional WDK EVM account. Writable accounts can execute. Read-only accounts can quote, check status, and discover support. | +| `config` | Optional `LifiSwidgeProtocolConfig` for fee caps, provider setup, LI.FI route selection, retries, and contract validation. | + +When `account` is omitted, `config.provider` is required for `quoteSwidge()`. Accountless quote requests omit `fromAddress`. + +## Configuration Options + +| Option | Type | Description | +|--------|------|-------------| +| `provider` | `string \| Eip1193Provider` | RPC URL or EIP-1193 provider. Falls back to `account._config.provider` when omitted. | +| `integrator` | `string` | LI.FI integrator identifier sent with API requests. | +| `apiKey` | `string` | LI.FI API key for higher rate limits. Keep it server-side. | +| `order` | `'RECOMMENDED' \| 'FASTEST' \| 'CHEAPEST'` | Route selection strategy. Defaults to `RECOMMENDED`. | +| `allowBridges` | `string[]` | Bridge protocol allowlist, for example `['stargate']`. | +| `denyBridges` | `string[]` | Bridge protocol denylist, for example `['across']`. When omitted, no bridge filter is sent and LI.FI considers all bridges. For gasless integrations, pass the exported `NATIVE_VALUE_BRIDGE_DENY_LIST` to exclude bridges that require native token value. | +| `allowDestinationCall` | `boolean` | Allows LI.FI routes that execute a destination-chain call, such as a destination-chain swap. Forwarded only when set explicitly; when omitted, LI.FI's own default (`true`) applies. Set `false` to filter out routes that may leave the user with an intermediary token if the destination call fails. | +| `allowNativeValue` | `boolean` | Whether `swidge()` may execute quotes whose transaction requires native token value (`transactionRequest.value > 0`). Defaults to `true`. Set `false` for gasless setups (for example ERC-4337 with a paymaster): such quotes are rejected before any approval is sent. | +| `maxNetworkFeeBps` | `number \| bigint` | Rejects execution when LI.FI reports `SEND` gas costs above this many basis points of a positive `fromAmountUSD`. If input USD pricing is missing or zero, the check is skipped; missing gas-cost USD values count as zero. | +| `maxProtocolFeeBps` | `number \| bigint` | Rejects execution when protocol fees exceed this many basis points of the input amount. | +| `timeout` | `number` | Per-request timeout in milliseconds. Defaults to `30000`. | +| `retries` | `number` | Extra attempts for transient failures. Defaults to `1`; set `0` to disable retries. | +| `retryDelay` | `number` | Base retry delay in milliseconds. Defaults to `500` and backs off exponentially. | +| `trustedContracts` | `true \| Record` | Requires quote transaction targets and approval addresses to match known LI.FI contracts before execution. Off by default. | + +## Per-Call Overrides + +Pass a config object to `swidge(options, config)` to override fee caps or execution settings for one operation. + +```javascript +const route = { + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toChain: 'arbitrum', + fromTokenAmount: 10_000_000n +} + +await swidge.swidge(route, { + maxProtocolFeeBps: 20, + maxNetworkFeeBps: 75 +}) +``` + +## Route Filtering + +By default the module applies no extra route filtering: `denyBridges` is not set, `allowDestinationCall` is not forwarded (LI.FI's server-side default of `true` applies), and quotes that require native token value execute as-is. This gives the widest route coverage and matches LI.FI SDK behavior. + +For gasless integrations — for example ERC-4337 where a paymaster covers source-chain gas but the LI.FI route transaction itself must not require separate native token value — opt in explicitly: + +```javascript +import { + LifiSwidgeProtocol, + NATIVE_VALUE_BRIDGE_DENY_LIST +} from '@lifi/wdk-protocol-swidge-lifi' + +const swidge = new LifiSwidgeProtocol(account, { + denyBridges: NATIVE_VALUE_BRIDGE_DENY_LIST, // filter native-value bridges at quote time + allowNativeValue: false // reject any quote whose tx still needs native value +}) +``` + +`NATIVE_VALUE_BRIDGE_DENY_LIST` is the maintained list of bridges known to require native token value in the source transaction. `denyBridges` replaces (does not append to) any default, so `denyBridges: ['across']` denies only `across`. + +Set `allowDestinationCall: false` to exclude routes that require a destination-chain call, such as a swap after bridging, which can leave the user holding an intermediary token if the destination call fails. + +With `allowNativeValue: false`, execution rejects any quote whose transaction request requires native token value before sending approvals or the route transaction, even if a native-value bridge was not filtered out at quote time. + +## Contract Validation + +The module always validates returned transaction data before forwarding it to the wallet. Enable `trustedContracts` when the integration also needs an allowlist check against LI.FI Diamond deployments and Permit2. + +```javascript +const swidge = new LifiSwidgeProtocol(account, { + trustedContracts: true +}) +``` + +To extend the built-in allowlist for a chain, pass chain IDs mapped to one or more extra trusted addresses: + +```javascript +const swidge = new LifiSwidgeProtocol(account, { + trustedContracts: { + 137: [trustedPolygonContractAddress] + } +}) +``` + +`trustedPolygonContractAddress` must be a validated EVM address for the additional contract your integration trusts. + +## Security Notes + +- Do not expose LI.FI API keys in browser clients. +- Use explicit fee caps when routing user funds. +- Validate route choices with `getSupportedChains()` and `getSupportedTokens()` before quoting. +- Ask for user confirmation before calling `swidge()`. + + + +Quote, execute, and track LI.FI swidge routes. + + +Detailed method, type, status, fee, and error reference. + + + +*** + +## LI.FI Swidge Usage +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-lifi/usage +Description: Install and use @lifi/wdk-protocol-swidge-lifi for LI.FI swap and bridge routes. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Install + +```bash +npm install @lifi/wdk-protocol-swidge-lifi +``` + +Install the wallet module for the account type you plan to use: + +```bash +npm install @tetherto/wdk-wallet-evm +``` + +For an ERC-4337 smart account, install its wallet module instead: + +```bash +npm install @tetherto/wdk-wallet-evm-erc-4337 +``` + +## Create the Protocol + +```javascript +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +import { LifiSwidgeProtocol } from '@lifi/wdk-protocol-swidge-lifi' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://mainnet.infura.io/v3/YOUR_KEY' +}) + +const swidge = new LifiSwidgeProtocol(account, { + integrator: 'my-app', + order: 'RECOMMENDED' +}) +``` + +## Discover Chains and Tokens + +Discovery calls are read-only. They can be used before a wallet account is available. + +```javascript +const discovery = new LifiSwidgeProtocol(undefined, { + provider: 'https://mainnet.infura.io/v3/YOUR_KEY' +}) + +const chains = await discovery.getSupportedChains() +const ethereumTokens = await discovery.getSupportedTokens({ + fromChain: 1 +}) +``` + +Use returned chain and token identifiers when building route forms. + +## Quote a Route + +Call `quoteSwidge()` before execution so users can review the expected output and fees. + +```javascript +const route = { + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toChain: 'arbitrum', + fromTokenAmount: 10_000_000n, + slippage: 0.01 +} + +const quote = await swidge.quoteSwidge(route) + +console.log('Expected output:', quote.toTokenAmount) +console.log('Minimum output:', quote.toTokenAmountMin) +console.log('Fees:', quote.fees) +``` + +When sending to another account, set `recipient` to its complete EVM address. If omitted, the module uses the bound account address. + +For same-chain swaps, omit `toChain` and provide the destination token on the source chain. + +```javascript +const quote = await swidge.quoteSwidge({ + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + fromTokenAmount: 10_000_000n +}) +``` + +## Execute a Route + +After the user confirms the quote, call `swidge()` with the same route shape. The module handles required ERC-20 approvals, including reset-to-zero flows for tokens such as USDT on Ethereum. + +```javascript +const result = await swidge.swidge(route, { + maxNetworkFeeBps: 100, + maxProtocolFeeBps: 50 +}) + +console.log('Swidge ID:', result.id) +console.log('Transaction hash:', result.hash) +``` + +### Guard a quote-first flow with `minAmountOut` + +`swidge()` fetches a fresh quote at execution time, which can differ from the quote the user reviewed. Pass `minAmountOut` — the `toTokenAmountMin` from the displayed quote — to reject execution if the fresh quote's minimum output has dropped below what the user accepted. The guard runs before any approval or transaction is sent, and the value is never forwarded to LI.FI. + +```javascript +const quote = await swidge.quoteSwidge(route) +// ...user reviews and confirms the quote... + +const result = await swidge.swidge({ + ...route, + minAmountOut: quote.toTokenAmountMin +}) +``` + +## Track Status + +`swidge()` returns after the source transaction is broadcast. Use `getSwidgeStatus()` with the returned operation ID to follow the route to a terminal state. Chain hints can speed up indexing. LI.FI can return `NOT_FOUND` while a transaction is waiting to be indexed; keep polling only for that status error. + +```javascript +import { LifiStatusError } from '@lifi/wdk-protocol-swidge-lifi' + +const terminalStatuses = new Set([ + 'completed', + 'failed', + 'refunded', + 'partial', + 'cancelled', + 'expired' +]) +const maxStatusAttempts = 60 + +let status + +for (let attempt = 0; attempt < maxStatusAttempts; attempt += 1) { + if (attempt > 0) { + await new Promise(resolve => setTimeout(resolve, 10_000)) + } + + try { + const statusResult = await swidge.getSwidgeStatus(result.id, { + fromChain: 1, + toChain: 42161 + }) + status = statusResult.status + } catch (error) { + if (error instanceof LifiStatusError && error.lifiStatus === 'NOT_FOUND') { + continue + } + + throw error + } + + console.log('Route status:', status) + + if (terminalStatuses.has(status)) { + break + } +} + +if (!terminalStatuses.has(status)) { + throw new Error('Timed out waiting for a terminal LI.FI status') +} +``` + +## Handle Common Failures + +```javascript +import { + LifiProtocolError, + LifiRateLimitError, + LifiSlippageError, + LifiTimeoutError +} from '@lifi/wdk-protocol-swidge-lifi' + +try { + await swidge.swidge(route) +} catch (error) { + if (error instanceof LifiSlippageError) { + // Request a fresh quote before retrying. + } else if (error instanceof LifiRateLimitError || error instanceof LifiTimeoutError) { + // Retry later or use a configured API key. + } else if (error instanceof LifiProtocolError) { + // Handle another LI.FI module error. + } +} +``` + + + +Review route, API, fee, retry, and contract-validation options. + + +Detailed method, type, status, fee, and error reference. + + + +*** + +## Route with Orchestra +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra +Description: Use the Flashnet Orchestra community Swidge module for BTC and stablecoin routes from WDK wallet accounts. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +Use [`wdk-protocol-swidge-orchestra`](https://www.npmjs.com/package/wdk-protocol-swidge-orchestra) when your wallet needs a WDK `SwidgeProtocol` provider for BTC and stablecoin routes served by Flashnet Orchestra. The package connects WDK wallet accounts to the Flashnet Orchestra API for route discovery, quotes, source payments, order submission, and status tracking. + +The package is maintained by Flashnet at [`flashnetxyz/wdk-protocol-swidge-orchestra`](https://github.com/flashnetxyz/wdk-protocol-swidge-orchestra). + +For provider-maintained route support, integration patterns, and API concepts, see the [Flashnet Orchestra docs](https://docs.flashnet.xyz/products/orchestration/overview). + +## When to use it + +Use Orchestra when your application needs to route between BTC on Spark or Bitcoin L1 and stablecoin routes returned by Orchestra. + +| Use case | Module | +|---|---| +| BTC and stablecoin routes returned by Orchestra. Treat the live [Orchestra route matrix](https://orchestration.flashnet.xyz/v1/orchestration/routes) as provider-level data, then expose routes through `getSupportedChains()`, `getSupportedTokens(options?)`, registered WDK source accounts, and the package caveats below. | `wdk-protocol-swidge-orchestra` | +| Standalone EVM token swaps through Velora | [`@tetherto/wdk-protocol-swap-velora-evm`](/sdk/swap-modules/swap-velora-evm/) | +| Standalone USDT0 bridge routes | [`@tetherto/wdk-protocol-bridge-usdt0-evm`](/sdk/bridge-modules/bridge-usdt0-evm/) | + +For standard WDK execution through this package, do not expose Lightning as a source route. The package sends source payments from WDK accounts and submits source transaction identifiers to Orchestra; it does not implement a source Lightning receive-request flow. + +## Responsibility model + +| Area | Owner | +|---|---| +| Wallet accounts, key material, and source transaction signing | WDK wallet modules | +| Route quotes, deposit addresses, order state, and settlement | Flashnet Orchestra | +| Durable state storage and recovery policy | Host wallet application | + +`quoteSwidge()` is side-effect-free. `swidge()` and `executeSwapIntent()` can move funds from the source account. Production wallets should persist the full intent and state objects returned by the package before and after source payment. + +## Key capabilities + +- Discover route support with `getSupportedChains()` and `getSupportedTokens(options?)`. +- Quote routes with `quoteSwidge(options)` before showing a confirmation screen. +- Execute Swidge routes with `swidge(options, config?)` when the host app has recovery around the call. +- Use `prepareSwap()` and `executeSwapIntent()` when you need an explicit persistence boundary before source funds move. +- Recover or continue orders with `submitSourceTx()`, `resumeSwap()`, `getOrderStatus()`, `waitForCompletion()`, and `subscribeOrder()`. + +## Next steps + + + +Install the package, create a WDK account, and construct Orchestra. + + +Quote routes, show confirmation, and execute one-call Swidge operations. + + +Persist intents and resume orders after source payment or process failure. + + +Review provider-maintained route support and Orchestra API concepts. + + +Review methods, configuration fields, state objects, and errors. + + + +*** + +## Orchestra API Reference +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/api-reference +Description: API reference for the Flashnet Orchestra community Swidge module. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +# API Reference + +## Package + +```javascript +import Orchestra, { + OrchestraApiError, + OrchestraError, + OrchestraStateError, + OrchestraSubmitError, + OrchestraTimeoutError +} from 'wdk-protocol-swidge-orchestra' +``` + +The package exports `Orchestra` as both the default export and a named export. + +Use the [Flashnet Orchestra docs](https://docs.flashnet.xyz/products/orchestration/overview) for provider-maintained route support, API concepts, and integration patterns outside the WDK package interface. + +## Class: Orchestra + +`Orchestra` extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. + +### Constructor + +```javascript +new Orchestra(account, config?) +``` + +Parameters: + +- `account`: `IWalletAccount | IWalletAccountReadOnly | undefined` +- `config`: `OrchestraConfig` + +Use `undefined` only for discovery or status flows that do not send source payments. Write flows require a WDK account with source-payment methods. + +### Swidge methods + +| Method | Description | Returns | +|---|---|---| +| `quoteSwidge(options)` | Calls Orchestra estimate and returns a side-effect-free WDK Swidge quote. | `Promise` | +| `swidge(options, config?)` | Creates a quote, sends the source payment, submits the source transaction, and returns a WDK Swidge result. | `Promise` | +| `getSwidgeStatus(id, options?)` | Reads an Orchestra order and maps the order status to WDK Swidge status. | `Promise` | +| `getSupportedChains()` | Reads Orchestra's route matrix and returns supported chains. | `Promise` | +| `getSupportedTokens(options?)` | Reads supported tokens, optionally filtered by source chain, source token, or destination chain. | `Promise` | + +### Production flow methods + +| Method | Description | Returns | +|---|---|---| +| `prepareSwap(options, requestOptions?)` | Creates a durable Orchestra quote with deposit address and idempotency keys. Persist the returned intent before source payment. | `Promise` | +| `executeSwapIntent(intentOrState, options?)` | Sends the source payment and submits the transfer id to Orchestra. | `Promise` | +| `submitSourceTx(intentOrState, sourceTxHash, options?)` | Submits an already-sent source transaction without sending another source payment. | `Promise` | +| `resumeSwap(state, options?)` | Reads status, submits an existing source transaction, or resumes a fresh source payment only when explicitly allowed. | `Promise` | +| `getOrderStatus(target)` | Reads status by order id, quote id, or source transaction hash. | `Promise` | +| `waitForCompletion(target, options?)` | Polls status until a terminal Orchestra order status or timeout. | `Promise` | +| `subscribeOrder(target, callbacks, options?)` | Opens an SSE status subscription and returns a closable subscription. | `OrderSubscription` | + +### `quoteSwidge(options)` + +```javascript +const quote = await orchestra.quoteSwidge({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...', + slippage: 0.01 +}) +``` + +`quoteSwidge()` calls the estimate endpoint. It does not reserve a deposit address and does not move funds. + +### `swidge(options, config?)` + +```javascript +const result = await orchestra.swidge({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...' +}, { + maxNetworkFeeBps: 20n, + maxProtocolFeeBps: 100n +}) +``` + +`swidge()` can send a source payment. Show confirmation first and persist state through `onStateChange` when using this path. + +## Options + +### `OrchestraSwidgeOptions` + +| Field | Type | Description | +|---|---|---| +| `fromToken` | `string` | Source token, preferably chain-qualified such as `spark:BTC` or `bsc:USDT`. | +| `toToken` | `string` | Destination token, preferably chain-qualified. | +| `fromChain` | `string \| number` | Optional source-chain override. | +| `toChain` | `string \| number` | Optional destination-chain override. | +| `recipient` | `string` | Destination recipient. Required when the destination is not the source account. | +| `refundChain` | `string` | Refund chain for routes that need refund metadata. | +| `refundAddress` | `string` | Refund address for routes that need refund metadata. | +| `fromTokenAmount` | `number \| bigint \| string` | Exact source amount. Do not pass with `toTokenAmount`. | +| `toTokenAmount` | `number \| bigint \| string` | Exact destination amount. Do not pass with `fromTokenAmount`. | +| `slippage` | `number` | Decimal slippage, for example `0.01` for 1%. | +| `slippageBps` | `number` | Slippage in basis points. | +| `idempotencyKey` | `string` | Quote idempotency key for `prepareSwap()`. | +| `submitIdempotencyKey` | `string` | Submit idempotency key. | +| `sourceTxHash` | `string` | Existing source transaction id to submit instead of sending a new payment. | +| `sourceNetworkFee` | `bigint \| number \| string` | Source wallet fee for an existing source transaction. | +| `sourceAddress` | `string` | Source wallet address used for submit metadata. | +| `sourceSparkAddress` | `string` | Spark source address used for Spark submit metadata. | +| `sourceTokenIdentifier` | `string` | Per-call Spark token identifier override when the app should not rely only on constructor-level `sparkTokenIdentifiers`. | +| `sourceTokenAddress` | `string` | Per-call source token contract address override when the app should not rely only on constructor-level `sourceTokenAddresses`. | +| `sourceTxVout` | `number` | Bitcoin output index when needed for submit metadata. | +| `feeRate` | `number \| bigint` | Bitcoin source fee rate option. | +| `confirmationTarget` | `number` | Bitcoin source confirmation target option. | +| `broadcastTimeoutMs` | `number` | Bitcoin broadcast timeout. | +| `allowNewSourcePayment` | `boolean` | Allows `resumeSwap()` to send a fresh source payment. Use only after wallet-history recovery. | +| `ignoreQuoteExpiry` | `boolean` | Bypasses quote expiry protection. | +| `quoteExpirySafetyMs` | `number` | Per-call quote expiry safety window. | +| `appFees` | `AppFee[]` | App fee metadata passed to Orchestra. | +| `affiliateId` | `string` | Affiliate id metadata. | +| `affiliateIds` | `string[]` | Affiliate id metadata. | + +### `OrchestraConfig` + +See [Configuration](/sdk/swidge-modules/swidge-orchestra/configuration) for the full constructor config. Common fields are `apiKey`, `baseUrl`, `authMode`, `sourceChain`, `sourceTokenAddresses`, `sparkTokenIdentifiers`, `onStateChange`, timeout settings, and retry settings. + +### `OrchestraSwidgeStatusOptions` + +| Field | Type | Description | +|---|---|---| +| `readToken` | `string` | Scoped client-key status token returned on submitted Orchestra state. Pass it to `getSwidgeStatus(id, options?)` when status reads do not use an admin key. | + +## State objects + +### `OrchestraSwapIntent` + +Returned by `prepareSwap()`. Persist it before calling `executeSwapIntent()`. + +Key fields: + +- `version` +- `quoteId` +- `sourceChain` +- `sourceAsset` +- `destinationChain` +- `destinationAsset` +- `recipientAddress` +- `amountMode` +- `amountIn` +- `estimatedOut` +- `depositAddress` +- `expiresAt` +- `quoteIdempotencyKey` +- `submitIdempotencyKey` +- `createdAt` + +### `OrchestraSwapState` + +Returned after source payment, submit, or recovery steps. It extends `OrchestraSwapIntent`. + +Additional key fields: + +- `sourceTxHash` +- `sourceNetworkFee` +- `orderId` +- `status` +- `readToken` +- `sourcePaymentStartedAt` +- `fundedAt` +- `submittedAt` + +Persist the full object, not only `orderId`. Recovery may need the quote id, deposit address, source transaction hash, read token, source chain, and idempotency keys. + +## Status mapping + +`getSwidgeStatus()` maps Orchestra order statuses to WDK Swidge statuses: + +| Orchestra status | WDK Swidge status | +|---|---| +| `processing` or unknown in-flight state | `pending` | +| `completed` | `completed` | +| `failed` | `failed` | +| `unfulfilled` | `failed` | +| `expired` | `expired` | +| `refunded` | `refunded` | + +## Errors + +All package-specific errors extend `OrchestraError`. + +| Error | Description | Useful fields | +|---|---|---| +| `OrchestraError` | Base package error. | `code`, `details` | +| `OrchestraApiError` | Orchestra returned an API error or invalid API response. | `code`, `status`, `details` | +| `OrchestraStateError` | Input state is incomplete, unsafe to resume, expired, or incompatible with the requested source payment. | `code`, `details` | +| `OrchestraSubmitError` | Source payment was sent, but submit, post-submit validation, or post-submit state persistence failed. | `state`, `cause` | +| `OrchestraTimeoutError` | HTTP request or wait operation timed out. | `code`, `details` | + +## Source repository tooling + +The package repository includes a funded live-test harness. Those commands can move mainnet funds by default and are not required for normal WDK docs examples. Review the package repository before running them. + +*** + +## Orchestra Configuration +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/configuration +Description: Configure Flashnet Orchestra API access, source chains, asset identifiers, timeouts, and state callbacks. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +`Orchestra` accepts a WDK wallet account and an `OrchestraConfig` object. + +```javascript title="Create an Orchestra instance" +import Orchestra from 'wdk-protocol-swidge-orchestra' + +const orchestra = new Orchestra(account, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_API_KEY, + baseUrl: 'https://orchestration.flashnet.xyz' +}) +``` + +Create one `Orchestra` instance per source wallet account. Set `sourceChain` explicitly because WDK accounts do not always expose a canonical chain id to protocol constructors. + +## Install + +Install the Orchestra package and the WDK wallet base package: + +```bash title="Install Orchestra" +npm install wdk-protocol-swidge-orchestra @tetherto/wdk-wallet@1.0.0-beta.11 +``` + +Install the WDK wallet modules for the source accounts your application supports: + +```bash title="Install WDK wallet modules" +npm install @tetherto/wdk @tetherto/wdk-wallet-spark @tetherto/wdk-wallet-btc @tetherto/wdk-wallet-evm +``` + +## Constructor + +```javascript +new Orchestra(account, config?) +``` + +Parameters: + +- `account` (`IWalletAccount | IWalletAccountReadOnly | undefined`): WDK account used for source payments, read-only status access, or discovery-only use. +- `config` (`OrchestraConfig`, optional): API, source-chain, asset, timeout, and callback settings. + +## Core config + +| Field | Type | Description | +|---|---|---| +| `apiKey` | `string` | Flashnet Orchestra API key. Can be a backend key or scoped client key. | +| `baseUrl` | `string` | Orchestra API base URL. Defaults to the package client default when omitted. | +| `fetch` | `typeof fetch` | Custom fetch implementation. | +| `authMode` | `'admin' \| 'client' \| 'auto'` | Controls API key handling for status and SSE flows. | +| `sourceChain` | `string` | Default source chain for unqualified source assets. | +| `defaultSourceChain` | `string` | Alias for `sourceChain`. | +| `chain` | `string` | Alias for `sourceChain`. | +| `client` | `OrchestraClient` | Custom client instance. | + +## Authentication + +Use a backend key from a server or trusted runtime: + +```javascript title="Backend key" +const orchestra = new Orchestra(account, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_API_KEY +}) +``` + +Use `authMode: 'client'` for scoped client keys: + +```javascript title="Scoped client key" +const orchestra = new Orchestra(account, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_CLIENT_KEY, + authMode: 'client' +}) +``` + +Scoped client-key submissions can return `readToken`. Store that token with the submitted state and pass the full state back to status methods. + +Backend proxy integrations can provide headers per request: + +```javascript title="Backend proxy headers" +const orchestra = new Orchestra(account, { + sourceChain: 'spark', + baseUrl: 'https://your-api.example.com/orchestra', + getAuthHeaders: async () => ({ + Authorization: `Bearer ${await getSessionToken()}` + }) +}) +``` + +For direct browser SSE, provide a scoped SSE token with `sseToken` or `getSseToken`, or proxy SSE through your backend. + +## Asset config + +| Field | Type | Description | +|---|---|---| +| `sourceTokenAddresses` | `Record` | Source token contract addresses keyed by `':'`. | +| `tokenAddresses` | `Record` | Alias for `sourceTokenAddresses`. | +| `assetAddresses` | `Record` | Alias for `sourceTokenAddresses`. | +| `sparkTokenIdentifiers` | `Record` | Spark token identifiers keyed by Orchestra asset symbol. | +| `tokenIdentifiers` | `Record` | Alias for `sparkTokenIdentifiers`. | +| `nativeAssets` | `Record` | Native asset overrides by source chain. | +| `tokenDecimals` | `Record` | Token decimal overrides by chain-qualified asset key. | + +EVM token sources need token contract addresses. The package includes common USDT source addresses, but production wallets should pass their own allowlist. + +```javascript title="Configure EVM USDT source token" +const orchestra = new Orchestra(arbitrumAccount, { + sourceChain: 'arbitrum', + apiKey: process.env.FLASHNET_API_KEY, + sourceTokenAddresses: { + 'arbitrum:USDT': '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' + } +}) +``` + +Spark tokens other than BTC need Spark token identifiers: + +```javascript title="Configure Spark token identifiers" +const orchestra = new Orchestra(sparkAccount, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_API_KEY, + sparkTokenIdentifiers: { + USDB: 'btkn1...' + } +}) +``` + +## Safety and timeout config + +| Field | Type | Description | +|---|---|---| +| `slippageBps` | `number` | Default slippage in basis points. | +| `timeoutMs` | `number` | HTTP request timeout. | +| `maxRetries` | `number` | General request retry count. | +| `retryDelayMs` | `number` | General retry delay. | +| `submitMaxRetries` | `number` | Submit retry count. Bitcoin submit retries cover propagation delays for `tx_not_found` and `vout_not_found`. | +| `submitRetryDelayMs` | `number` | Submit retry delay. | +| `quoteExpirySafetyMs` | `number` | Safety window before quote expiry when sending source payments. | +| `pollIntervalMs` | `number` | Default polling interval for `waitForCompletion()`. | +| `waitTimeoutMs` | `number` | Default wait timeout for `waitForCompletion()`. | +| `idempotencyKeyFactory` | `() => string` | Custom idempotency key factory for quote and submit calls. | + +## State callbacks + +| Field | Type | Description | +|---|---|---| +| `onIntent` | `(intent) => void \| Promise` | Called after `prepareSwap()` creates an intent. | +| `onStateChange` | `(event, state) => void \| Promise` | Called for persisted state transitions. | +| `onOrderStatus` | `(status) => void \| Promise` | Called by `waitForCompletion()` after each status read. | + +Use `onStateChange` to persist state transitions that can affect funds: + +```javascript title="Persist state transitions" +const orchestra = new Orchestra(account, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_API_KEY, + onStateChange: async (event, state) => { + await saveSwapState(event, state) + } +}) +``` + +See [State and Recovery](/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery) before using this package with production funds. + +*** + +## Get Started with Orchestra +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/get-started +Description: Install the Flashnet Orchestra community Swidge module and create an Orchestra protocol instance. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide shows how to [install the package](#install-the-package), [create source accounts](#create-source-accounts), [create Orchestra](#create-orchestra), and [make a first quote](#make-a-first-quote). + +## Install the package + +Install Orchestra and the WDK wallet base package: + +```bash title="Install Orchestra" +npm install wdk-protocol-swidge-orchestra @tetherto/wdk-wallet@1.0.0-beta.11 +``` + +Install the WDK wallet modules for the chains you plan to support: + +```bash title="Install WDK wallet modules" +npm install @tetherto/wdk @tetherto/wdk-wallet-spark @tetherto/wdk-wallet-btc @tetherto/wdk-wallet-evm +``` + +## Create source accounts + +Create WDK accounts for the source chains your wallet supports. This example registers Spark, Bitcoin L1, and Arbitrum source accounts. + +```javascript title="Create WDK accounts" +import WDK from '@tetherto/wdk' +import WalletManagerBtc from '@tetherto/wdk-wallet-btc' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import WalletManagerSpark from '@tetherto/wdk-wallet-spark' + +const wdk = new WDK(seedPhrase) + .registerWallet('spark', WalletManagerSpark, { + network: 'MAINNET', + syncAndRetry: true + }) + .registerWallet('bitcoin', WalletManagerBtc, { + network: 'bitcoin', + client: { + type: 'electrum', + clientConfig: { + host: 'electrum.blockstream.info', + port: 50001 + } + } + }) + .registerWallet('arbitrum', WalletManagerEvm, { + chainId: 42161, + provider: process.env.ARBITRUM_RPC_URL + }) + +const spark = await wdk.getAccount('spark', 0) +const arbitrum = await wdk.getAccount('arbitrum', 0) +``` + +## Create Orchestra + +Create one `Orchestra` instance per source wallet account. Set `sourceChain` explicitly. + +```javascript title="Create Orchestra for Spark source routes" +import Orchestra from 'wdk-protocol-swidge-orchestra' + +const orchestra = new Orchestra(spark, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_API_KEY, + baseUrl: 'https://orchestration.flashnet.xyz' +}) +``` + +EVM token sources need token contract addresses. Common USDT addresses are built in, but production wallets should pass their own allowlist. + +```javascript title="Create Orchestra for Arbitrum USDT source routes" +const arbitrumOrchestra = new Orchestra(arbitrum, { + sourceChain: 'arbitrum', + apiKey: process.env.FLASHNET_API_KEY, + sourceTokenAddresses: { + 'arbitrum:USDT': '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' + } +}) +``` + +## Make a first quote + +Use `quoteSwidge()` to estimate a route before showing a confirmation screen. + +```javascript title="Quote Spark BTC to TRON USDT" +const quote = await orchestra.quoteSwidge({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...', + slippage: 0.01 +}) + +console.log(quote.toTokenAmount) +console.log(quote.toTokenAmountMin) +console.log(quote.fees) +``` + +`quoteSwidge()` does not reserve a deposit address or move funds. Call `swidge()` or the split `prepareSwap()` and `executeSwapIntent()` flow only after the user has reviewed the route, amount, fees, and recipient. + +## Next steps + + + +Show confirmation and execute Orchestra routes. + + +Use the production split flow and persist state. + + +Review auth, source-chain, token, timeout, and callback options. + + + +*** + +## Handle Orchestra Errors +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/handle-errors +Description: Recover from Orchestra API, state, submit, timeout, and status errors. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide explains how to [handle submit failures](#submit-failures), [handle state errors](#state-errors), [handle API and timeout errors](#api-and-timeout-errors), and [dispose wallet resources](#dispose-wallet-resources). + +## Error classes + +All package-specific errors extend `OrchestraError`. + +| Error | When it is thrown | Useful fields | +|---|---|---| +| `OrchestraError` | Base class for package-specific failures. | `code`, `details` | +| `OrchestraApiError` | Orchestra returns an API error or an invalid API response. | `code`, `status`, `details` | +| `OrchestraStateError` | Input state is incomplete, unsafe to resume, expired, or incompatible with the requested source payment. | `code`, `details` | +| `OrchestraSubmitError` | Source payment was sent, but submit, post-submit validation, or post-submit state persistence failed. | `state`, `cause` | +| `OrchestraTimeoutError` | HTTP request or wait operation exceeds its timeout. | `code`, `details` | + +## Submit failures + +`OrchestraSubmitError` is the most important error for funds-moving flows. It means a source payment may already have been sent. Persist `err.state` before retrying or resuming. + +```javascript title="Persist submit failure state" +try { + const submitted = await orchestra.executeSwapIntent(intent) + await saveSwap(submitted) + return submitted +} catch (err) { + if (err.name !== 'OrchestraSubmitError') throw err + + await saveSwap(err.state) + return await orchestra.resumeSwap(err.state) +} +``` + +Common submit-failure causes include: + +- Orchestra rejected or could not find a newly broadcast source transaction. +- Status validation failed after Orchestra accepted the source payment. +- Your `onStateChange` persistence callback failed after submit. +- Source network fee was unavailable while a fee cap required it. + +For Bitcoin source routes, the package retries `tx_not_found` and `vout_not_found` submit responses with the same idempotency key because a newly broadcast Bitcoin transaction may need time to propagate. + +## State errors + +`OrchestraStateError` is thrown before unsafe operations, including: + +- calling a write method without a writable WDK account +- passing both `fromTokenAmount` and `toTokenAmount` +- trying to resume an intent-only state without `allowNewSourcePayment: true` +- using an expired quote before source payment +- missing a source token address for an EVM token source +- missing a Spark token identifier for a non-BTC Spark token +- omitting a recipient when the destination is not the source account + +```javascript title="Handle unsafe resume" +try { + await orchestra.resumeSwap(savedIntent) +} catch (err) { + if (err.name === 'OrchestraStateError') { + console.error('Recovery needs a source transaction or wallet-history check:', err.message) + } +} +``` + +Use `allowNewSourcePayment: true` only after checking wallet history for a prior payment to the quote deposit address. + +## API and timeout errors + +Catch `OrchestraApiError` and `OrchestraTimeoutError` separately when you need to distinguish API failures from local state failures. + +```javascript title="Handle API and timeout failures" +try { + const status = await orchestra.getOrderStatus(submitted) + console.log(status.order?.status ?? status.status) +} catch (err) { + if (err.name === 'OrchestraApiError') { + console.error('Orchestra API failed:', err.code, err.status) + } else if (err.name === 'OrchestraTimeoutError') { + console.error('Timed out waiting for Orchestra:', err.message) + } else { + throw err + } +} +``` + +## Status errors + +`getOrderStatus()` requires an `orderId`, `quoteId`, or `sourceTxHash`. Scoped client-key status reads also need the `readToken` returned in the submitted state. + +```javascript title="Read status with submitted state" +const status = await orchestra.getOrderStatus(submitted) +``` + +When status polling runs too long, `waitForCompletion()` throws `OrchestraTimeoutError`. + +## Dispose wallet resources + +Dispose WDK wallet accounts after a route flow completes or fails. Keep the persisted Orchestra state until the order is terminal or your recovery policy has completed. + +```javascript title="Dispose wallet resources" +try { + const submitted = await orchestra.executeSwapIntent(intent) + await saveSwap(submitted) +} finally { + account.dispose?.() +} +``` + +For in-flight routes, do not delete persisted intent, submit, order id, read token, or source transaction data just because the account object was disposed. + +*** + +## Quote and Execute Orchestra Routes +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/quote-and-execute +Description: Quote Orchestra Swidge routes, show confirmation, and execute from WDK source accounts. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide covers [route discovery](#route-discovery), [quotes](#quotes), [one-call execution](#one-call-execution), and [source-chain examples](#source-chain-examples). + +## Route discovery + +Use discovery methods to build a wallet UI from the package-filtered route set. Treat Orchestra's live route matrix as provider-level data and still apply WDK account availability, source-chain support, and package caveats before exposing routes. + +```javascript title="Discover chains and tokens" +const chains = await orchestra.getSupportedChains() + +const tokens = await orchestra.getSupportedTokens({ + fromChain: 'spark', + toChain: 'tron' +}) +``` + +Use chain-qualified asset references such as `spark:BTC`, `bitcoin:BTC`, `bsc:USDT`, or `tron:USDT`. + +## Quotes + +`quoteSwidge()` is side-effect-free. It calls Orchestra's estimate endpoint and does not reserve a deposit address. + +```javascript title="Quote exact source amount" +const quote = await orchestra.quoteSwidge({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...', + slippage: 0.01 +}) +``` + +Show the user: + +- source amount and source asset +- expected destination amount +- minimum destination amount +- fees +- route and recipient +- expiry, if present + +## One-call execution + +Call `swidge()` only after user confirmation. The method creates a fresh Orchestra quote, sends the source payment from the WDK account, submits the source transaction id to Orchestra, and returns the Orchestra order id. + +```javascript title="Execute with fee caps" +const result = await orchestra.swidge({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...' +}, { + maxNetworkFeeBps: 20n, + maxProtocolFeeBps: 100n +}) + +console.log(result.id) +console.log(result.hash) +``` + + +There is a recovery gap after the source payment is sent and before Orchestra accepts the transaction id. Use the split flow in [State and Recovery](/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery) for production funds. + + +## Source-chain examples + +### Spark BTC to USDT + +Spark signs the BTC transfer. Orchestra settles USDT on the destination chain. + +```javascript title="Prepare Spark BTC to TRON USDT" +const spark = await wdk.getAccount('spark', 0) +const orchestra = new Orchestra(spark, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_API_KEY +}) + +const intent = await orchestra.prepareSwap({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...' +}) +``` + +### EVM USDT to Spark BTC + +EVM token sources use the WDK account's `transfer({ token, recipient, amount })` path. The source account needs native gas for its chain. + +```javascript title="Prepare BSC USDT to Spark BTC" +const bsc = await wdk.getAccount('bsc', 0) +const spark = await wdk.getAccount('spark', 0) + +const orchestra = new Orchestra(bsc, { + sourceChain: 'bsc', + apiKey: process.env.FLASHNET_API_KEY, + sourceTokenAddresses: { + 'bsc:USDT': '0x55d398326f99059ff775485246999027b3197955' + } +}) + +const intent = await orchestra.prepareSwap({ + fromToken: 'bsc:USDT', + toToken: 'spark:BTC', + fromTokenAmount: 5000000n, + recipient: await spark.getAddress() +}) +``` + +### Bitcoin L1 source + +Bitcoin L1 can be a source or destination. For Bitcoin source routes, the package submits `bitcoinTxid` to Orchestra and can retry `tx_not_found` or `vout_not_found` submit responses with the same idempotency key while the transaction propagates. + +```javascript title="Execute Bitcoin L1 to Spark BTC" +const bitcoin = await wdk.getAccount('bitcoin', 0) +const spark = await wdk.getAccount('spark', 0) + +const orchestra = new Orchestra(bitcoin, { + sourceChain: 'bitcoin', + apiKey: process.env.FLASHNET_API_KEY +}) + +const intent = await orchestra.prepareSwap({ + fromToken: 'bitcoin:BTC', + toToken: 'spark:BTC', + fromTokenAmount: 100000n, + recipient: await spark.getAddress() +}) + +await saveSwap(intent) + +const submitted = await orchestra.executeSwapIntent(intent, { + feeRate: 12n, + confirmationTarget: 2 +}) +``` + +### Destination Lightning + +Orchestra supports destination Lightning routes where the live route matrix exposes them. Pass a BOLT11 invoice or Lightning Address as `recipient`, and include refund metadata required by the route. + +```javascript title="Prepare USDT to destination Lightning" +const intent = await orchestra.prepareSwap({ + fromToken: 'bsc:USDT', + toToken: 'lightning:BTC', + fromTokenAmount: 5000000n, + recipient: bolt11Invoice, + refundChain: 'bsc', + refundAddress: await bsc.getAddress() +}) +``` + +Lightning as a source is not supported through this package's standard `swidge()` or `executeSwapIntent()` flow. + +*** + +## Orchestra State and Recovery +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery +Description: Persist Orchestra intents and resume routes after source payment, submit, or process failure. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide covers [the production split flow](#production-split-flow), [state callbacks](#state-callbacks), [resume rules](#resume-rules), and [status tracking](#status-tracking). + +## Production split flow + +Use `prepareSwap()` and `executeSwapIntent()` when funds are at risk. The split flow gives the host wallet a persistence boundary before the source payment is sent. + +1. `prepareSwap()` creates an Orchestra quote and reserves a deposit address. +2. The app persists the returned intent. +3. `executeSwapIntent()` sends the source payment and submits the transfer id. +4. The app persists the submitted state. +5. The app tracks status with `getOrderStatus()`, `getSwidgeStatus()`, `waitForCompletion()`, or `subscribeOrder()`. + +```javascript title="Split flow with persisted state" +const intent = await orchestra.prepareSwap({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...' +}) + +await saveSwap(intent) + +const submitted = await orchestra.executeSwapIntent(intent) +await saveSwap(submitted) + +const finalStatus = await orchestra.waitForCompletion(submitted, { + onStatus: async (status) => { + await saveOrderStatus(status) + } +}) +``` + +`saveSwap` and `saveOrderStatus` are your app code, not package exports. Back them with durable storage before moving real funds. + +## State callbacks + +Use `onStateChange` to persist every state transition that can affect funds. + +```javascript title="Persist state callbacks" +const orchestra = new Orchestra(account, { + sourceChain: 'spark', + apiKey: process.env.FLASHNET_API_KEY, + onStateChange: async (event, state) => { + await saveSwapState(event, state) + } +}) +``` + +State events: + +| Event | Meaning | +|---|---| +| `intent_created` | Quote exists and has a deposit address. No source funds moved. | +| `source_payment_started` | The package is about to broadcast or send the source payment. Persist before the callback returns. | +| `source_payment_sent` | Source payment returned a transaction id. | +| `submitted` | Orchestra accepted the source transaction and created or updated the order. | + +Persist the full state object. Do not store only the order id. Recovery may need the quote id, deposit address, source transaction hash, read token, source chain, and submit idempotency key. + +## Resume rules + +Call `resumeSwap(savedState, options?)` with the most complete saved state. + +```javascript title="Resume from saved state" +const next = await orchestra.resumeSwap(savedState) +await saveSwap(next) +``` + +`resumeSwap()` follows these rules: + +| Saved state | Behavior | +|---|---| +| Has `orderId` | Reads order status. | +| Has `sourceTxHash` | Submits or re-submits the source transaction id. | +| Has only the intent | Refuses to send a fresh source payment unless `allowNewSourcePayment: true` is set. | + +Use `allowNewSourcePayment: true` only after checking wallet history for a prior payment to the quote deposit address. + +```javascript title="Resume an intent only after wallet-history recovery" +await orchestra.resumeSwap(intentOnlyState, { + allowNewSourcePayment: true +}) +``` + +## Submit an existing source transaction + +Use `submitSourceTx()` when your app already has the source transaction hash and should not send another source payment. + +```javascript title="Submit an existing source transaction" +const submitted = await orchestra.submitSourceTx( + intent, + 'spark_transfer_existing', + { + sourceNetworkFee: 3n + } +) + +await saveSwap(submitted) +``` + +## Submit failure recovery + +If submit fails after source payment, the package throws `OrchestraSubmitError`. Persist `error.state` before retrying. + +```javascript title="Recover after submit failure" +try { + const submitted = await orchestra.executeSwapIntent(intent) + await saveSwap(submitted) + return submitted +} catch (err) { + if (err.name !== 'OrchestraSubmitError') throw err + + await saveSwap(err.state) + return await orchestra.resumeSwap(err.state) +} +``` + +## Status tracking + +Use `waitForCompletion()` for polling: + +```javascript title="Poll until terminal status" +const finalStatus = await orchestra.waitForCompletion(submitted, { + pollIntervalMs: 5000, + timeoutMs: 7200000, + onStatus: async (status) => { + await saveOrderStatus(status) + } +}) +``` + +Use `subscribeOrder()` for SSE status updates: + +```javascript title="Subscribe to order status" +const subscription = orchestra.subscribeOrder(submitted, { + onStatus: (status) => { + console.log(status) + }, + onError: (err) => { + console.error(err) + }, + onClose: () => { + console.log('Subscription closed') + } +}) + +subscription.close() +``` + +For direct browser SSE, provide `sseToken` or `getSseToken`, or proxy SSE from a backend. Admin keys should stay on trusted infrastructure. + +*** + +## Orchestra Usage +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/usage +Description: Discover, quote, execute, and track Orchestra Swidge routes from WDK wallet accounts. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +This guide explains how to [discover routes](#discover-routes), [quote before execution](#quote-before-execution), [execute routes](#execute-routes), and [track status](#track-status) with `wdk-protocol-swidge-orchestra`. + +## Discover routes + +Use `getSupportedChains()` and `getSupportedTokens(options?)` to build route selectors from the package-filtered route set. The live [route matrix](https://orchestration.flashnet.xyz/v1/orchestration/routes) is provider-level data; filter it through registered WDK source accounts and the package caveats before exposing routes in your UI. + +```javascript title="Discover supported routes" +const chains = await orchestra.getSupportedChains() + +const sparkToTronTokens = await orchestra.getSupportedTokens({ + fromChain: 'spark', + toChain: 'tron' +}) + +console.log(chains) +console.log(sparkToTronTokens) +``` + +The returned token identifiers use chain-qualified asset references such as `spark:BTC`, `bitcoin:BTC`, `bsc:USDT`, or `tron:USDT`. Use the values returned by discovery when building UI route options. + +## Quote before execution + +Call `quoteSwidge()` before execution. It calls the Orchestra estimate endpoint and does not reserve a deposit address. + +```javascript title="Quote Spark BTC to TRON USDT" +const quote = await orchestra.quoteSwidge({ + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...', + slippage: 0.01 +}) + +console.log(quote.fromTokenAmount) +console.log(quote.toTokenAmount) +console.log(quote.toTokenAmountMin) +console.log(quote.fees) +``` + +Treat quote output as indicative UI data. `swidge()` creates a fresh Orchestra quote through `prepareSwap()`, so do not assume a prior `quoteSwidge()` response locks rate, amount, fee, expiry, or deposit address. + +Use smallest units: + +| Asset | Unit | +|---|---| +| BTC | sats | +| USDT | 6-decimal token units | +| EVM native gas asset | wei | + +## Execute routes + +Use `swidge()` only after showing the quote details, route, recipient, fees, and expected output to the user. + +```javascript title="Execute after user confirmation" +const options = { + fromToken: 'spark:BTC', + toToken: 'tron:USDT', + fromTokenAmount: 7116n, + recipient: 'TRecipient...', + slippage: 0.01 +} + +const quote = await orchestra.quoteSwidge(options) +showConfirmation(quote) + +const result = await orchestra.swidge(options, { + maxNetworkFeeBps: 20n, + maxProtocolFeeBps: 100n +}) + +console.log(result.id) +console.log(result.hash) +console.log(result.transactions) +``` + + +`swidge()` can send the source payment from the WDK account. Production wallets should persist every state transition through `onStateChange` and persist `OrchestraSubmitError.state` before retrying after failures. + + +For production funds, prefer the split flow documented in [State and Recovery](/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery). + +## Track status + +Use `getSwidgeStatus()` when you have the Swidge result id: + +```javascript title="Read Swidge status" +const status = await orchestra.getSwidgeStatus(result.id) + +if (status.status === 'completed') { + console.log('Route completed') +} +``` + +For submitted Orchestra states, use `getOrderStatus()`, `waitForCompletion()`, or `subscribeOrder()`: + +```javascript title="Wait for final order status" +const finalStatus = await orchestra.waitForCompletion(submitted, { + pollIntervalMs: 5000, + timeoutMs: 7200000, + onStatus: async (status) => { + await saveOrderStatus(status) + } +}) + +console.log(finalStatus.order?.status ?? finalStatus.status) +``` + +Scoped client-key submissions can return a `readToken`. Preserve it with the submitted state so later status reads can authenticate without an admin key. Keep admin API keys on trusted infrastructure. + +```javascript title="Read status with a client read token" +const submitted = await loadSubmittedState(result.id) +const readToken = submitted.readToken + +const status = await orchestra.getSwidgeStatus(result.id, { + readToken +}) +``` + +## Next steps + + + +Install and configure the package. + + +Persist intents and resume in-flight orders. + + +Recover from API, state, submit, and timeout failures. + + + +*** + +## Rhino.fi Swidge Overview +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi +Description: Overview of the @rhino.fi/wdk-protocol-swidge-rhinofi module for Rhino.fi cross-chain routes. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +The Rhino.fi Swidge module lets WDK EVM accounts quote and execute cross-chain swaps and bridges through Rhino.fi using the shared `SwidgeProtocol` interface. + +Use this module when an app needs authenticated Rhino.fi quotes, EVM source-chain execution, live token discovery, status polling, and WDK-standard fee and status shapes. + +## Features + +- **Unified route interface**: Implements `quoteSwidge()`, `swidge()`, `getSwidgeStatus()`, `getSupportedChains()`, and `getSupportedTokens()`. +- **Rhino.fi routing**: Quotes and executes cross-chain swap and bridge routes supported by Rhino.fi. +- **EVM source support**: Signs source-chain deposits through `@tetherto/wdk-wallet-evm` accounts, including ERC-4337 accounts. +- **Authenticated API calls**: Uses a Rhino.fi API key for quotes, execution, discovery, and status. +- **Config caching**: Caches Rhino.fi chain and token config to reduce repeated API calls. +- **Fee controls**: Applies optional `maxNetworkFeeBps` and `maxProtocolFeeBps` limits before execution. +- **Status mapping**: Maps Rhino.fi operation states into canonical WDK `SwidgeStatus` values. +- **Typed errors**: Exposes module-specific errors for configuration, unsupported routes, fee limits, unknown operations, and execution failures. + +## Supported Routes + +Call `getSupportedChains()` and `getSupportedTokens()` at runtime because Rhino.fi controls the live route set. Use the provider-maintained [Supported Chains](https://docs.rhino.fi/get-started/supported-chains) page as route-support context before exposing routes in production UIs. + +| Ecosystem | Source-chain support | Notes | +|-----------|----------------------|-------| +| EVM | Supported | Uses WDK EVM accounts to sign deposits. | +| Solana | Planned | Destination support depends on Rhino.fi route availability. | +| TON | Planned | Destination support depends on Rhino.fi route availability. | +| Tron | Planned | Destination support depends on Rhino.fi route availability. | + +## Execution Model + +`swidge()` submits the source-chain deposit after any required ERC-20 approval. It resolves when the deposit transaction is broadcast, while cross-chain settlement continues asynchronously. + +Use `getSwidgeStatus(result.id)` to track the route to completion. + + +`swidge()` can approve tokens and submit an EVM deposit transaction. Show the quote, fee breakdown, recipient, source token, destination token, and destination chain before calling it. + + +## Next Steps + + + +Configure API authentication, fee caps, API base URL, and config caching. + + +Install the package, discover support, quote a route, execute a route, and poll status. + + +Review constructor options, methods, config fields, status mapping, fee mapping, and errors. + + + +--- + +## Need Help? + + + +*** + +## Rhino.fi Swidge API Reference +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi/api-reference +Description: API reference for @rhino.fi/wdk-protocol-swidge-rhinofi. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## RhinofiProtocol + +`RhinofiProtocol` extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols` and implements the shared WDK swidge methods. + +```javascript +import RhinofiProtocol, { + AccountRequiredError, + ConfigurationError +} from '@rhino.fi/wdk-protocol-swidge-rhinofi' + +const rhinofi = new RhinofiProtocol(account, config) +``` + +## Constructor + +```typescript +new RhinofiProtocol(account?, config) +``` + +| Account | Available operations | +|---------|----------------------| +| `WalletAccountEvm` | Discovery, quote, status, and execution. | +| `WalletAccountEvmErc4337` | Discovery, quote, status, and execution through a smart account. | +| `WalletAccountReadOnlyEvm` | Discovery and quotes for routes that do not need signing. | +| `undefined` | Discovery and account-independent setup when route context allows it. | + +## Methods + +| Method | Description | +|--------|-------------| +| `quoteSwidge(options)` | Returns a non-binding Rhino.fi route quote. | +| `swidge(options, config?)` | Executes a route and returns when the source deposit is broadcast. | +| `getSwidgeStatus(id, options?)` | Maps Rhino.fi operation state to WDK `SwidgeStatus`. | +| `getSupportedChains()` | Returns chains supported by Rhino.fi config. | +| `getSupportedTokens(options?)` | Returns tokens supported by Rhino.fi config, optionally filtered by chain context. | + +### `quoteSwidge(options)` + +```typescript +quoteSwidge(options: SwidgeOptions): Promise +``` + +The source chain is derived from the account when required by the route. + +### `swidge(options, config?)` + +```typescript +swidge( + options: SwidgeOptions, + config?: RhinofiProtocolConfig +): Promise +``` + +Requires a writable WDK EVM account. The method submits the source-chain deposit and returns the operation ID and source transaction hash. + +### `getSwidgeStatus(id, options?)` + +```typescript +getSwidgeStatus( + id: string, + options?: SwidgeStatusOptions +): Promise +``` + +Use the `id` returned by `swidge()`. + +## Config Type + +```typescript +type RhinofiProtocolConfig = { + apiKey: string + apiBaseUrl?: string + maxNetworkFeeBps?: number | bigint + maxProtocolFeeBps?: number | bigint + configTtlMs?: number +} +``` + +## Status Mapping + +| Rhino.fi state | WDK status | +|----------------|------------| +| `PENDING`, `PENDING_CONFIRMATION`, `DEPOSIT_ACCEPTED`, `ACCEPTED` | `pending` | +| `EXECUTED` | `completed` | +| `SWAP_FAILED` | `refund-pending` | +| `SWAP_FAILED_REFUNDED`, `DEPOSIT_RECEIVED_AFTER_GRACE_PERIOD_REFUNDED` | `refunded` | +| `DEPOSIT_RECEIVED_AFTER_GRACE_PERIOD` | `action-required` | +| `FAILED` | `failed` | +| `CANCELLED` | `cancelled` | + +## Fee Mapping + +| Rhino.fi quote fee | WDK fee type | Legacy field | +|--------------------|--------------|--------------| +| `gasFee` plus `sourceGasFee` | `network` | `fee` | +| Platform and percentage fee remainder | `protocol` | `bridgeFee` | + +The `network` and `protocol` fee amounts are itemized in `SwidgeFee[]` and denominated in the input token. + +## Error Types + +All Rhino.fi module errors extend `RhinofiProtocolError`. + +| Error | When thrown | +|-------|-------------| +| `AccountRequiredError` | `swidge()` is called without a writable account. | +| `ConfigurationError` | Required configuration is missing, such as `apiKey`. | +| `UnsupportedChainError` | A chain is unknown, unsupported, or invalid as a source chain. | +| `UnsupportedTokenError` | A token is unknown or unsupported on the selected chain. | +| `FeeLimitExceededError` | Quoted fees exceed configured fee caps. | +| `UnknownOperationError` | Status is requested for an unknown operation ID. | +| `SwidgeExecutionError` | Rhino.fi quote or execution fails. The `.code` field can carry provider failure codes. | + +## Legacy Delegations + +Inherited `swap`, `quoteSwap`, `bridge`, and `quoteBridge` calls delegate to `swidge()` and `quoteSwidge()`. Because those legacy option shapes do not carry source-chain context, the source chain must be derivable from the bound account. + + + +Install, quote, execute, and track Rhino.fi routes. + + +Compare released WDK Swidge provider modules. + + + +*** + +## Rhino.fi Swidge Configuration +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi/configuration +Description: Configuration options for @rhino.fi/wdk-protocol-swidge-rhinofi. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +`RhinofiProtocol` requires a Rhino.fi API key. The SDK authenticates every call, including quote and discovery calls. Create and manage API keys in the [Rhino.fi Console](https://console.rhino.fi/). + +```javascript +import RhinofiProtocol from '@rhino.fi/wdk-protocol-swidge-rhinofi' + +const rhinofi = new RhinofiProtocol(account, { + apiKey: process.env.RHINO_API_KEY, + maxNetworkFeeBps: 50, + maxProtocolFeeBps: 30 +}) +``` + +## Constructor + +```typescript +new RhinofiProtocol(account?, config) +``` + +| Parameter | Description | +|-----------|-------------| +| `account` | Optional WDK EVM account. Writable accounts can execute. Read-only accounts can quote and discover support. | +| `config` | Required `RhinofiProtocolConfig`. Must include `apiKey`. | + +## Configuration Options + +| Option | Type | Description | +|--------|------|-------------| +| `apiKey` | `string` | Rhino.fi API key. Required for every call. | +| `apiBaseUrl` | `string` | Optional Rhino.fi API base URL override. Use `https://`. | +| `maxNetworkFeeBps` | `number \| bigint` | Rejects execution when network fees exceed this many basis points of the input amount. | +| `maxProtocolFeeBps` | `number \| bigint` | Rejects execution when protocol fees exceed this many basis points of the input amount. | +| `configTtlMs` | `number` | Milliseconds to cache Rhino.fi config and swap-token lists. Defaults to `60000`; set `0` to always fetch fresh. | + +## Per-Call Overrides + +Pass config to `swidge(options, config)` to override fee caps for a single execution. + +```javascript +await rhinofi.swidge(options, { + maxNetworkFeeBps: 40, + maxProtocolFeeBps: 25 +}) +``` + +## API Base URL + +Use `https://` API URLs. `http://` URLs can redirect and break authenticated SDK requests. + +```javascript +const rhinofi = new RhinofiProtocol(account, { + apiKey: process.env.RHINO_API_KEY, + apiBaseUrl: 'https://api.rhino.fi' +}) +``` + +## Config Caching + +The module caches Rhino.fi chain config and swap-token lists for `configTtlMs`. + +```javascript +const rhinofi = new RhinofiProtocol(account, { + apiKey: process.env.RHINO_API_KEY, + configTtlMs: 60000 +}) +``` + +Set `configTtlMs: 0` when you need every call to fetch fresh provider configuration. + +## Security Notes + +- Store `apiKey` in server-side or secret-managed configuration. +- Use trusted RPC providers for WDK EVM accounts. +- Set fee caps for user-facing flows. +- Ask for user confirmation before calling `swidge()`. + + + +Quote, execute, and track Rhino.fi swidge routes. + + +Detailed method, type, status, fee, and error reference. + + + +*** + +## Rhino.fi Swidge Usage +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi/usage +Description: Install and use @rhino.fi/wdk-protocol-swidge-rhinofi for Rhino.fi cross-chain routes. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Install + +```bash +npm install @rhino.fi/wdk-protocol-swidge-rhinofi@1.0.0-beta.2 @tetherto/wdk-wallet-evm +``` + +Install ERC-4337 support when you need smart-account execution: + +```bash +npm install @tetherto/wdk-wallet-evm-erc-4337 +``` + +## Create the Protocol + +```javascript +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +import RhinofiProtocol from '@rhino.fi/wdk-protocol-swidge-rhinofi' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://arb1.arbitrum.io/rpc' +}) + +const rhinofi = new RhinofiProtocol(account, { + apiKey: process.env.RHINO_API_KEY, + maxNetworkFeeBps: 50, + maxProtocolFeeBps: 30 +}) +``` + +## Discover Chains and Tokens + +```javascript +const chains = await rhinofi.getSupportedChains() +const tokens = await rhinofi.getSupportedTokens({ + fromChain: 'ARBITRUM' +}) +``` + +The module reads live Rhino.fi chain and token config. The source chain must be derivable from the WDK account for execution. + +## Quote a Route + +```javascript +const quote = await rhinofi.quoteSwidge({ + fromToken: 'USDT', + toToken: 'USDC', + toChain: 'BASE', + recipient: '0xRecipient...', + fromTokenAmount: 1_000_000n +}) + +console.log('Expected output:', quote.toTokenAmount) +console.log('Minimum output:', quote.toTokenAmountMin) +console.log('Fees:', quote.fees) +``` + +Use `toTokenAmount` instead of `fromTokenAmount` for exact-output routes when supported by the provider route. + +## Execute a Route + +After the user confirms the quote, call `swidge()`. + +```javascript +const result = await rhinofi.swidge({ + fromToken: 'USDT', + toToken: 'USDC', + toChain: 'BASE', + recipient: '0xRecipient...', + fromTokenAmount: 1_000_000n +}) + +console.log('Operation ID:', result.id) +console.log('Source transaction:', result.hash) +``` + +`swidge()` resolves after the source deposit transaction is broadcast. The destination settlement can continue after the method returns. + +## Track Status + +```javascript +const status = await rhinofi.getSwidgeStatus(result.id) + +if (status.status === 'completed') { + console.log('Route completed') +} +``` + +## Handle Errors + +```javascript +import { + AccountRequiredError, + ConfigurationError, + FeeLimitExceededError, + RhinofiProtocolError +} from '@rhino.fi/wdk-protocol-swidge-rhinofi' + +try { + await rhinofi.swidge(options) +} catch (error) { + if (error instanceof AccountRequiredError) { + // Bind a writable WDK EVM account before execution. + } else if (error instanceof ConfigurationError) { + // Check apiKey and API configuration. + } else if (error instanceof FeeLimitExceededError) { + // Ask the user to approve the quoted fee or lower the amount. + } else if (error instanceof RhinofiProtocolError) { + // Handle another Rhino.fi module error. + } +} +``` + + + +Review API, fee, and config-cache settings. + + +Detailed method, type, status, fee, and error reference. + + + +*** + +## Symbiosis Swidge Overview +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-symbiosis +Description: Use the Symbiosis community Swidge module for same-chain and cross-chain asset routes. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +Use [`@symbiosis-finance/wdk-protocol-swidge-symbiosis@1.2.0`](https://www.npmjs.com/package/@symbiosis-finance/wdk-protocol-swidge-symbiosis/v/1.2.0) when your wallet needs a WDK `SwidgeProtocol` provider for routes served by Symbiosis. The module uses the public Symbiosis REST API for discovery, quotes, execution payloads, and cross-chain status. + +The released source is tagged [`v1.2.0`](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.2.0) and maintained by [Symbiosis](https://symbiosis.finance/). + +## When to use it + +Use this module when your application needs: + +- same-chain swaps, cross-chain bridges, or combined swap-and-bridge routes; +- runtime chain and token discovery; +- exact-input quotes; +- EVM or Bitcoin source execution through a compatible WDK wallet account; +- cross-chain settlement status mapped to WDK status values. + +The module can quote TON, Tron, and Solana source routes, but it cannot execute those source transaction formats through WDK wallet accounts in this release. + +## Responsibility model + +| Area | Owner | +|---|---| +| Wallet keys, source address, approval, signing, and transaction broadcast | WDK wallet account | +| Chain and token catalogs, route payloads, deposit addresses, and settlement status | Symbiosis API | +| Input validation, quote review, user confirmation, fee policy, retries, and status polling | Host application | + +## Discovery is not a route guarantee + +`getSupportedChains()` and `getSupportedTokens(options?)` read provider-maintained catalogs. `getSupportedTokens()` filters the token catalog by `toChain` when present, otherwise by `fromChain`. It does not prove that a specific token pair currently has liquidity. + +Call `quoteSwidge()` for the requested pair before presenting a route. Treat the returned quote as indicative because execution obtains a fresh response. + +## Quote and execution model + +`quoteSwidge()` calls the Symbiosis quote endpoint and does not write to the wallet. + +`swidge()` calls the Symbiosis swap endpoint again. The execution amounts, fees, spender, transaction payload, or Bitcoin deposit address can differ from the earlier quote. The method checks configured fee caps on this fresh response and then proceeds to the route-specific wallet writes. + + +`swidge()` does not expose the fresh execution response for a separate confirmation step. Show the indicative quote, recipient, destination chain, refund address, and selected slippage before calling it. Configure the applicable fee caps, and do not treat the earlier quote as reserved or bound to execution. + + +For a non-native EVM input token, `swidge()` can: + +1. Read the current allowance. +2. Reset a non-zero insufficient allowance to zero and, when the account supports receipt lookup, wait for that approval receipt. +3. Approve the spender returned by Symbiosis for the input amount and, when supported, wait for that receipt. +4. Broadcast the route transaction. + +The method returns after the source transaction is broadcast. Use `getSwidgeStatus(result.id)` to track destination settlement or a refund. + +## Source execution support + +| Source route type | Execution behavior | +|---|---| +| `evm` | Optionally approves the input ERC-20, then sends the API-provided calldata transaction. | +| `btc` | Transfers the input amount to the generated deposit address. Configure a suitable refund address. | +| `ton` | Quote only; `swidge()` throws `UnsupportedRouteError`. | +| `tron` | Quote only; `swidge()` throws `UnsupportedRouteError`. | +| `solana` | Quote only; `swidge()` throws `UnsupportedRouteError`. | + +Destination support is provider-controlled. Use runtime discovery and a successful quote instead of maintaining a static route list. + +## Integrator limitations + +- Only exact-input routes are supported. Passing `toTokenAmount` throws `ExactOutNotSupportedError`. +- `fromTokenAmount` must convert to a positive integer `bigint`; invalid, zero, and negative values throw `ValidationError` before an API request. +- The module does not validate slippage ranges or the formats of recipient, refund, and partner addresses. Validate those application inputs before calling the provider. +- Discovery responses are cached for ten minutes per protocol instance. The cache duration is not configurable. +- Monero and Zcash are excluded from discovery and chain resolution because their provider routes use third-party custodial integrations outside this module's scope. +- A status lookup returning HTTP `404` is mapped to `pending`. A newly submitted operation and a genuinely unknown ID are therefore indistinguishable through this method. +- API requests time out after `timeoutMs` (30 seconds by default). The module does not retry or back off automatically. +- The package documents `/v2/swap` as rate-limited to one request per second. Bitcoin execution also uses that endpoint to generate a deposit address. +- If allowance lookup fails, the module falls back to sending an approval without a reset. That direct approval can still fail for a token with an existing non-zero allowance, so ensure allowance reads work or manage the reset in the application. +- If the wallet does not expose transaction-receipt lookup, the module cannot wait for approval confirmation before submitting the route transaction. +- A fee whose description is exactly `Partner fee` maps to `affiliate`; every other fee maps to `protocol`. No fee maps to `network`, so `maxNetworkFeeBps` does not constrain a separately reported network cost and `maxProtocolFeeBps` does not constrain the affiliate fee. +- Quote-only construction without an account uses `recipient` as both the source sender and destination recipient. +- The package exposes ESM and Bare entrypoints but does not declare a Node.js `engines` range. + +## Next steps + + + +Install the released package, discover provider catalogs, quote a route, execute it, and track status. + + +Configure source-chain identity, slippage, refund handling, approval behavior, and fee caps. + + +Review the exported class, methods, options, result shapes, statuses, and typed errors. + + + +--- + +## Need Help? + + + +*** + +## Symbiosis Swidge API Reference +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-symbiosis/api-reference +Description: API reference for @symbiosis-finance/wdk-protocol-swidge-symbiosis 1.2.0. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Package exports + +```javascript +import SymbiosisProtocol, { + ApiError, + ConfigurationError, + ExactOutNotSupportedError, + FeeLimitExceededError, + ReadOnlyAccountError, + SymbiosisError, + TransactionError, + UnsupportedChainError, + UnsupportedRouteError, + UnsupportedTokenError, + ValidationError +} from '@symbiosis-finance/wdk-protocol-swidge-symbiosis' +``` + +The package exports `SymbiosisProtocol` as both its default export and a named export. It also re-exports `ISwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. + +This reference covers release [`1.2.0`](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.2.0). + +## `SymbiosisProtocol` + +`SymbiosisProtocol` extends `SwidgeProtocol`. + +### Constructor + +```typescript +new SymbiosisProtocol( + account?: IWalletAccount | IWalletAccountReadOnly, + config?: SymbiosisProtocolConfig +) +``` + +| Account | Available operations | +|---|---| +| Writable account with the route's required methods | Discovery, quote, status, and supported source execution | +| Read-only account | Discovery and status; quoting uses the account's address as the request sender | +| `undefined` | Discovery and status; quoting requires `recipient` to supply the request sender | + +`chain` is optional in the constructor type, but `quoteSwidge()` and `swidge()` throw `ConfigurationError` when it is absent. + +### Configuration type + +```typescript +type SymbiosisProtocolConfig = { + chain?: string | number + apiUrl?: string + timeoutMs?: number + partnerId?: string + defaultSlippage?: number + partnerAddress?: string + refundAddress?: string + skipApproval?: boolean + maxNetworkFeeBps?: number | bigint + maxProtocolFeeBps?: number | bigint +} +``` + +See [Configuration](/sdk/swidge-modules/swidge-symbiosis/configuration) for defaults, validation boundaries, and execution effects. + +## Methods + +| Method | Side effects | Description | +|---|---|---| +| `quoteSwidge(options)` | Provider API reads only | Returns an indicative exact-input quote. | +| `swidge(options, config?)` | Can approve and broadcast one or more source transactions | Requests a fresh execution response and submits its source route through the wallet account. | +| `getSwidgeStatus(id, options?)` | Provider API read | Maps Symbiosis settlement state to a WDK status. | +| `getSupportedChains()` | Provider API reads, cached | Returns provider-listed chains with WDK chain metadata. | +| `getSupportedTokens(options?)` | Provider API read, cached | Returns provider-listed tokens, optionally filtered to one chain. | + +### `quoteSwidge(options)` + +```typescript +quoteSwidge(options: SwidgeOptions): Promise +``` + +Builds an exact-input request and calls `/v2/quote`. The method does not reserve or bind the result for `swidge()`. + +Package-specific errors include: + +- `ConfigurationError` when `chain` is missing; +- `ValidationError` when `fromTokenAmount` is missing, is not an integer, or is not positive; a token identifier is not a string; or no account address or `recipient` supplies the request sender; +- `ExactOutNotSupportedError` when `toTokenAmount` is present; +- `UnsupportedChainError` or `UnsupportedTokenError` when discovery cannot resolve an identifier; +- `ApiError` when the API returns a non-2xx response, times out, or fails before a response is received. Network failures and timeouts carry `status: 0`. + +The method can also propagate an error from `account.getAddress()`. + +### `swidge(options, config?)` + +```typescript +swidge( + options: SwidgeOptions, + config?: SwidgeProtocolConfig +): Promise +``` + +Requires an account with `sendTransaction()`. It calls `/v2/swap`, checks the applicable fee caps, performs the route-specific approval or source-payment steps, and returns after source broadcast. + +The optional second argument overrides `maxNetworkFeeBps` and `maxProtocolFeeBps` for this execution. + + +`swidge()` does not consume the preceding `quoteSwidge()` response or expose its fresh `/v2/swap` response for a separate confirmation. After its fee checks, it proceeds internally to the required wallet writes. + + +### `getSwidgeStatus(id, options?)` + +```typescript +getSwidgeStatus( + id: string, + options?: SwidgeStatusOptions +): Promise +``` + +Pass the ID returned by `swidge()`: + +```text +: +``` + +For a bare transaction hash, pass `options.fromChain` or configure the instance source `chain`. The provider does not use other status hints. + +The resolved source-chain ID and transaction hash are URL-encoded before the provider builds the status endpoint path. + +HTTP `404` is returned as `pending`, not as `ApiError`. + +### `getSupportedChains()` + +```typescript +getSupportedChains(): Promise +``` + +Calls the Symbiosis chain and token endpoints and maps each in-scope chain to: + +| Field | Type | Description | +|---|---|---| +| `id` | `number` | Numeric Symbiosis chain ID | +| `name` | `string` | Provider chain name | +| `type` | `string` | `evm`, `utxo`, `tvm`, `tron`, or `svm` | +| `nativeToken` | `string` | Native token symbol when present in the token catalog | + +Monero and Zcash are filtered out because their provider routes use third-party custodial integrations outside this module's scope. + +### `getSupportedTokens(options?)` + +```typescript +getSupportedTokens( + options?: SwidgeSupportedTokensOptions +): Promise +``` + +The chain filter is resolved as `options.toChain ?? options.fromChain`. `fromToken` and other route context do not narrow the result. + +| Field | Type | Description | +|---|---|---| +| `token` | `string` | Native-format address when present, otherwise the token symbol | +| `chain` | `number` | Numeric Symbiosis chain ID | +| `symbol` | `string` | Provider token symbol | +| `decimals` | `number` | Base-unit precision | +| `address` | `string \| undefined` | Token address when it is not the native asset | +| `name` | `string \| undefined` | Provider token name when supplied | + +The response is a token catalog, not proof of pair liquidity. Request a quote for route availability. + +## Relevant `SwidgeOptions` + +| Field | Type | Provider behavior | +|---|---|---| +| `fromToken` | `string` | Required source token address, symbol, or native-token alias | +| `toToken` | `string` | Required destination token address, symbol, or native-token alias | +| `toChain` | `string \| number \| undefined` | Destination chain; defaults to the configured source chain | +| `recipient` | `string \| undefined` | Destination recipient; defaults to the bound account address. Without an account, it also supplies the request sender | +| `refundAddress` | `string \| undefined` | Per-call refund address; overrides the constructor default | +| `slippage` | `number \| undefined` | Decimal slippage; overrides `defaultSlippage` | +| `fromTokenAmount` | `number \| bigint` | Required exact input in source-token base units | +| `toTokenAmount` | `number \| bigint` | Unsupported; throws `ExactOutNotSupportedError` | + +The module does not validate slippage ranges or address formats. It converts `fromTokenAmount` with `BigInt` and throws `ValidationError` unless the result is greater than zero. + +## Quote and result fields + +### `SwidgeQuote` + +| Field | Type | Source | +|---|---|---| +| `fromTokenAmount` | `bigint` | Requested exact input | +| `toTokenAmount` | `bigint` | Provider-estimated output | +| `toTokenAmountMin` | `bigint` | Provider minimum output after slippage | +| `fees` | `SwidgeFee[]` | Mapped provider fee entries | +| `estimatedDuration` | `number \| undefined` | Provider estimate in seconds | +| `priceImpact` | `number \| undefined` | Provider percentage converted to a decimal | + +The provider does not map a quote expiry into `SwidgeQuote`. + +### `SwidgeResult` + +| Field | Public type | Provider behavior | +|---|---|---| +| `id` | `string` | `:` | +| `hash` | `string \| undefined` | Source transaction hash | +| `fees` | `SwidgeFee[]` | Fees from the fresh execution response | +| `transactions` | `SwidgeTransaction[] \| undefined` | Zero, one, or two EVM approval hashes followed by the source hash; a later status response returns its own source, destination, or refund transaction list | +| `fromTokenAmount` | `bigint` | Submitted exact input | +| `toTokenAmount` | `bigint` | Fresh provider-estimated output | +| `toTokenAmountMin` | `bigint \| undefined` | Fresh provider minimum output | + +## Status mapping + +| Symbiosis code or response | WDK status | +|---|---| +| `0` | `completed` | +| `1` | `pending` | +| `2` | `pending` | +| `3` | `refunded` | +| `-1` | `pending` | +| Unknown code | `pending` | +| HTTP `404` | `pending` with the known source transaction | + +When status is `refunded`, a returned settlement transaction is labeled `refund`; otherwise it is labeled `destination`. + +## Fee mapping and caps + +| Symbiosis fee rule | WDK fee type | Cap | +|---|---|---| +| `description` is exactly `Partner fee` | `affiliate` | None | +| Every other fee entry | `protocol` | `maxProtocolFeeBps` | + +Mapped fees include `amount`, `token`, `chain`, `description`, and `included: true`. + +The provider emits no `network` fee entry in this release, so `maxNetworkFeeBps` does not constrain the wallet transaction's chain fee. Inherited legacy `bridge()` results expose `0n` for their network `fee`; mapped protocol fees contribute to `bridgeFee`. + +Fee-cap fallback comparison uses decimal-normalized values when positive USD prices are unavailable. This is approximate when the fee token differs in unit value from the input token. + +## Error classes + +Every package-specific error extends `SymbiosisError`. + +| Error | When thrown | Useful fields | +|---|---|---| +| `SymbiosisError` | Base class for package-defined errors | Standard `Error` fields | +| `ConfigurationError` | Required source `chain` configuration is missing | — | +| `ValidationError` | A locally checked option, sender, token identifier, or status ID is invalid | — | +| `ExactOutNotSupportedError` | `toTokenAmount` requests exact-output execution | — | +| `UnsupportedChainError` | A chain ID or name is not in provider discovery | `identifier` | +| `UnsupportedTokenError` | A token is not in the selected chain's token catalog | `identifier` | +| `ReadOnlyAccountError` | Execution lacks a writable account or route-required capability | — | +| `UnsupportedRouteError` | The API returns a source transaction type not executable through WDK in this release | `type` | +| `FeeLimitExceededError` | A mapped `network` or `protocol` total exceeds its configured cap | `feeType`, `bps`, `cap` | +| `TransactionError` | Approval receipt polling detects a revert or reaches its 180-second timeout | `hash` | +| `ApiError` | The REST API returns a non-2xx response other than status lookup's special `404` handling, or a request fails or times out before a response | `status`, `response`, and `cause` for failures before a response | + +`ApiError.status` is `0` when no HTTP response was received. Errors thrown by wallet account methods are propagated and are not necessarily instances of `SymbiosisError`. + +## Inherited compatibility methods + +`SymbiosisProtocol` inherits: + +- `swap()` and `quoteSwap()`; +- `bridge()` and `quoteBridge()`. + +Those methods delegate to `swidge()` and `quoteSwidge()`. For the legacy bridge shape, the provider can resolve the destination token by matching the source token symbol on the destination chain. + +Prefer the Swidge methods when an application needs itemized fees, provider status, combined route semantics, or explicit destination-token selection. + + + +Install, discover, quote, confirm, execute, and track routes. + + +Review constructor defaults, approval behavior, fee caps, caching, and runtime constraints. + + + +*** + +## Symbiosis Swidge Configuration +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-symbiosis/configuration +Description: Configure source-chain identity, slippage, refunds, approval behavior, and fee caps for the Symbiosis community provider. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Constructor + +```typescript +new SymbiosisProtocol(account?, config?) +``` + +```javascript +import SymbiosisProtocol from '@symbiosis-finance/wdk-protocol-swidge-symbiosis' + +const symbiosis = new SymbiosisProtocol(account, { + chain: 'Ethereum', + timeoutMs: 30_000, + partnerId: 'my-app', + defaultSlippage: 0.02, + refundAddress: 'bc1qRefund...', + maxProtocolFeeBps: 100 +}) +``` + +| Parameter | Description | +|---|---| +| `account` | Optional WDK wallet account. Discovery and quote-only use can run without one; execution requires the capabilities used by the returned route. | +| `config` | Optional `SymbiosisProtocolConfig`. `chain` becomes required before quoting or execution. | + +## Configuration fields + +| Field | Type | Default | Behavior | +|---|---|---|---| +| `chain` | `string \| number` | None | Symbiosis chain name or numeric ID for the bound source account. Required by `quoteSwidge()` and `swidge()`. | +| `apiUrl` | `string` | `https://api.symbiosis.finance/crosschain` | Overrides the REST API base URL. Trailing slashes are removed. | +| `timeoutMs` | `number` | `30000` | Aborts a Symbiosis API request after this many milliseconds. Timeout failures become `ApiError` instances with `status: 0`. | +| `partnerId` | `string` | `'wdk'` | Sends the value in the `X-Partner-Id` header on every API request. Registered partners can receive higher API rate limits; pass `''` to omit the header. | +| `defaultSlippage` | `number` | `0.02` | Decimal slippage tolerance used when `options.slippage` is absent. `0.02` means 2%. | +| `partnerAddress` | `string` | None | Registered Symbiosis partner EVM address sent with quote and execution requests. | +| `refundAddress` | `string` | None | Default refund address for deposit-address routes. `options.refundAddress` overrides it. | +| `skipApproval` | `boolean` | `false` | Suppresses the module's automatic ERC-20 approval step. | +| `maxNetworkFeeBps` | `number \| bigint` | None | Shared network-fee cap. This release maps no provider fee to `network`, so the cap does not constrain a separate network cost. | +| `maxProtocolFeeBps` | `number \| bigint` | None | Rejects execution when fees mapped as `protocol` exceed this many basis points of the input amount. It does not constrain fees mapped as `affiliate`. | + +`partnerId` identifies the integrating application in an HTTP header. `partnerAddress` is a separate fee-share address included in quote and execution request bodies. + +## Source chain + +Use a numeric ID or the exact name returned by `getSupportedChains()`: + +```javascript +const byName = new SymbiosisProtocol(account, { + chain: 'Ethereum' +}) + +const byId = new SymbiosisProtocol(account, { + chain: 1 +}) +``` + +The configured chain must identify the bound account's source chain. The module does not derive or verify it from the wallet account. + +## Chain and token identifiers + +Chain identifiers can be numeric Symbiosis IDs or case-insensitive names from `getSupportedChains()`. + +Token identifiers can be: + +- a provider-listed contract or asset address; +- a token symbol on the selected chain; +- `''`, `'native'`, or the zero address for a native token. + +For TON and Solana assets, token discovery returns the provider's native-format address when available. + +Token symbols can be ambiguous. Prefer the exact address returned by `getSupportedTokens()` and confirm route availability with `quoteSwidge()`. + +## Slippage and amounts + +The per-call `slippage` option overrides `defaultSlippage`: + +```javascript +const quote = await symbiosis.quoteSwidge({ + fromToken, + toToken, + toChain, + recipient, + fromTokenAmount: 100_000_000n, + slippage: 0.01 +}) +``` + +The module converts the decimal slippage value to basis points with `Math.round(slippage * 10000)`. It does not validate the range of either slippage setting. + +Pass `fromTokenAmount` as a positive base-unit integer. Missing values, values that cannot be converted with `BigInt`, zero, and negative amounts throw `ValidationError` before the API request. + +## Recipient, partner, and refund addresses + +For a bound account, the source sender comes from `account.getAddress()`. `recipient` defaults to that address when omitted. + +Without an account, `recipient` supplies both the source sender and destination recipient: + +```javascript +const quoteOnly = new SymbiosisProtocol(undefined, { + chain: 'Ethereum' +}) + +const quote = await quoteOnly.quoteSwidge({ + fromToken: 'USDT', + toToken: 'USDC', + toChain: 'Arbitrum One', + recipient: '0xRecipient...', + fromTokenAmount: 100_000_000n +}) +``` + +Set a refund address suitable for a deposit-address route: + +```javascript +const symbiosis = new SymbiosisProtocol(bitcoinAccount, { + chain: 'Bitcoin', + refundAddress: 'bc1qRefund...' +}) +``` + +Override it for one request with `options.refundAddress`. + + +The module forwards `recipient`, `partnerAddress`, and `refundAddress` without validating their address formats or intended chains. Validate them in the host application. + + +## Approval behavior + +For a non-native EVM input token, the module uses the spender returned by the fresh execution response. + +By default it: + +1. calls `getAllowance(token, spender)` when available; +2. skips approval when allowance covers the input amount; +3. resets a non-zero insufficient allowance to zero and waits for its receipt when the account supports receipt lookup; +4. approves the exact input amount and waits for its receipt when supported; +5. approves without a reset when allowance lookup fails. + +When allowance lookup succeeds and the account returns transaction hashes, both approval hashes are included in `result.transactions` when a reset is required. This supports tokens such as USDT on Ethereum that reject a direct non-zero-to-non-zero allowance change. If allowance lookup fails while such a token already has a non-zero allowance, the fallback direct approval can still fail. + +Disable the automatic step only when the host application manages allowance: + +```javascript +const symbiosis = new SymbiosisProtocol(account, { + chain: 'Ethereum', + skipApproval: true +}) +``` + +`skipApproval` does not verify allowance. Insufficient allowance can cause the subsequent transaction to fail. + +## Fee caps + +Set protocol-level defaults on the instance: + +```javascript +const symbiosis = new SymbiosisProtocol(account, { + chain: 'Ethereum', + maxProtocolFeeBps: 100 +}) +``` + +Override shared fee caps for one execution: + +```javascript +await symbiosis.swidge(options, { + maxProtocolFeeBps: 75 +}) +``` + +The module checks the fresh `/v2/swap` response before calling a wallet write method. + +When both the input token and fee token have positive USD price data, the module compares USD values. Otherwise it compares decimal-normalized token amounts. That fallback is approximate when the fee token and input token have different unit values. + +| Symbiosis fee rule | Mapped type | Constrained by | +|---|---|---| +| `description` is exactly `Partner fee` | `affiliate` | Neither available cap | +| Every other fee entry | `protocol` | `maxProtocolFeeBps` | + +No returned fee maps to `network`, so `maxNetworkFeeBps` remains at zero in this provider's current fee calculation. The module does not estimate the wallet transaction's chain fee. + +## Discovery caching + +The provider caches chain and token discovery promises for ten minutes per instance. A failed request is removed from the cache and can be retried by a later call. + +The cache duration has no public configuration field. Construct a new provider instance when the application must bypass cached discovery. + +Monero and Zcash are filtered from chain discovery, token discovery, and chain resolution because their routes use third-party custodial integrations outside this module's scope. + +## API and runtime behavior + +- The default entrypoint is ESM. +- The `bare` export initializes `bare-node-runtime` globals before loading the provider. +- The package declares no Node.js `engines` range. +- Requests use the runtime's global `fetch`. +- Requests use an internal `AbortController` and time out after `timeoutMs`; network failures and timeouts throw `ApiError` with `status: 0`. +- The provider does not retry or back off automatically. +- `apiUrl` is normalized only by removing trailing slashes. + +Use a trusted `https://` endpoint for `apiUrl`. Choose a timeout appropriate for the runtime and retry cautiously, especially around execution and status polling. + + + +Discover, quote, confirm, execute, and track Symbiosis routes. + + +Review exported methods, option and result fields, statuses, fees, and errors. + + + +*** + +## Symbiosis Swidge Usage +URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-symbiosis/usage +Description: Install and use the released Symbiosis community Swidge provider with WDK wallet accounts. + + +Community modules are developed and maintained independently by third-party contributors. + +Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. + + +## Install + +The released Symbiosis package is `1.2.0`. The EVM example uses the WDK wallet version installed by that release's tests: + +```bash +npm install @symbiosis-finance/wdk-protocol-swidge-symbiosis@1.2.0 @tetherto/wdk-wallet-evm@1.0.0-beta.14 +``` + +The provider package includes `@tetherto/wdk-wallet` as a runtime dependency. Install the matching WDK wallet module separately for the source chain you intend to execute from. + +## Create the provider + +Configure `chain` as the Symbiosis ID or name for the bound source account. + +```javascript +import SymbiosisProtocol from '@symbiosis-finance/wdk-protocol-swidge-symbiosis' import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' -// Create wallet account first -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://eth.drpc.org' +}) + +const symbiosis = new SymbiosisProtocol(account, { + chain: 'Ethereum', + timeoutMs: 30_000, + partnerId: 'my-app', + maxProtocolFeeBps: 100 +}) +``` + +Keep the account alive through quote review, execution, and source broadcast. Dispose it in a `finally` block when the flow ends: + +```javascript +try { + // Discover, quote, and execute while the account is active. +} finally { + account.dispose() +} +``` + +## Discover chains and tokens + +```javascript +const chains = await symbiosis.getSupportedChains() + +const ethereumTokens = await symbiosis.getSupportedTokens({ + fromChain: 'Ethereum' +}) + +const arbitrumTokens = await symbiosis.getSupportedTokens({ + fromChain: 'Ethereum', + toChain: 'Arbitrum One' +}) +``` + +When both filters are present, `toChain` takes precedence. The method returns known tokens on the selected chain; it does not check whether a particular source and destination pair has a live route. + +Use the returned chain IDs, names, and token identifiers to build selectors, then request a quote to test the requested pair. Monero and Zcash do not appear because this release excludes their third-party custodial routes from the module. + +## Quote an exact-input route + +```javascript +const options = { + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + toChain: 'Arbitrum One', + recipient: '0xRecipient...', + fromTokenAmount: 100_000_000n, + slippage: 0.02 +} + +const quote = await symbiosis.quoteSwidge(options) + +console.log('Expected output:', quote.toTokenAmount) +console.log('Minimum output:', quote.toTokenAmountMin) +console.log('Estimated seconds:', quote.estimatedDuration) +console.log('Fees:', quote.fees) +``` + +`quoteSwidge()` performs no wallet write. It calls `/v2/quote` and returns an indicative result. + +Only exact-input operations are supported. Pass `fromTokenAmount`; passing `toTokenAmount` throws `ExactOutNotSupportedError`. + +## Review before execution + +Before calling `swidge()`, show the user: + +- source token and amount; +- destination token and chain; +- recipient and refund address, when applicable; +- expected and minimum output from the indicative quote; +- itemized quote fees; +- the selected slippage tolerance. + + +`swidge()` calls `/v2/swap` and then proceeds internally to fee checks, approval, and source broadcast. It does not expose that fresh response for a second application-level confirmation. Its amounts, fees, spender, transaction payload, or deposit address can differ from the preceding quote. + + +The provider rejects a missing, non-integer, zero, or negative `fromTokenAmount` with `ValidationError`. The host application must still validate its allowed slippage range and confirm that each user-supplied address belongs to the intended chain. + +## Execute an EVM route + +Call `swidge()` only after the user confirms the indicative quote and route inputs: + +```javascript +const result = await symbiosis.swidge(options, { + maxProtocolFeeBps: 100 +}) + +console.log('Operation ID:', result.id) +console.log('Source transaction:', result.hash) +console.log('Recorded transactions:', result.transactions) +``` + +The method uses this order: + +1. Resolve the source and destination chains and tokens. +2. Request a fresh response from `/v2/swap`. +3. Check applicable fee caps before a wallet write. +4. For a non-native EVM token, read allowance and reset a non-zero insufficient allowance to zero. +5. Approve the returned spender for the input amount. +6. Wait for each approval receipt when the account supports receipt lookup. +7. Broadcast the API-provided route transaction. +8. Return the source hash and operation ID without waiting for destination settlement. + +If allowance lookup fails, the module falls back to approval without a reset; that direct approval can still fail for a token with an existing non-zero allowance. When a reset is required and the account returns transaction hashes, both approval hashes are recorded in `result.transactions`. If the account cannot read receipts, it proceeds without waiting for approval confirmation. + +Set `skipApproval: true` only when the host application has already verified and managed allowance: + +```javascript +const symbiosis = new SymbiosisProtocol(account, { + chain: 'Ethereum', + skipApproval: true +}) +``` + +`maxProtocolFeeBps` constrains fees mapped as `protocol`; it does not constrain a fee whose description is exactly `Partner fee`, which maps as `affiliate`. This release maps no fee entry as `network`, so `maxNetworkFeeBps` does not constrain the wallet transaction's chain fee. + +## Execute a Bitcoin source route + +For Bitcoin, configure a refund address before requesting execution: + +```javascript +const symbiosis = new SymbiosisProtocol(bitcoinAccount, { + chain: 'Bitcoin', + refundAddress: 'bc1qRefund...' +}) + +const result = await symbiosis.swidge({ + fromToken: 'BTC', + toToken: 'USDC', + toChain: 'Arbitrum One', + recipient: '0xRecipient...', + fromTokenAmount: 50_000n +}) +``` + +`swidge()` requests a deposit address and sends the input amount to it without returning the deposit address for a separate confirmation. Confirm the refund address, recipient, amount, selected slippage, and indicative quote before calling the method. + +TON, Tron, and Solana source routes are quote-only in `1.2.0`; execution throws `UnsupportedRouteError`. TON execution was removed because WDK TON accounts treat the raw route payload as a text comment rather than the required BoC message body. + +## Track settlement + +The returned ID has the form `':'`: + +```javascript +const status = await symbiosis.getSwidgeStatus(result.id) + +console.log('Status:', status.status) +console.log('Transactions:', status.transactions) +``` + +Poll until the operation reaches the state your application handles as terminal. Symbiosis status code `2` is reported as `pending` because the provider resolves that state without a separate user action. + +The module also maps an HTTP `404` to `pending`. A newly submitted operation and a genuinely unknown ID produce the same result, so enforce a polling deadline and retain the source transaction hash. + +## Quote without a wallet account + +You can construct the provider without an account for a quote: + +```javascript +const symbiosis = new SymbiosisProtocol(undefined, { + chain: 'Ethereum' +}) + +const quote = await symbiosis.quoteSwidge({ + fromToken: 'USDT', + toToken: 'USDC', + toChain: 'Arbitrum One', + recipient: '0xRecipient...', + fromTokenAmount: 100_000_000n +}) +``` + +Without an account, the module sends `recipient` to Symbiosis as both the source sender and destination recipient. Bind an account when those addresses differ or use different address formats. + +## Handle errors + +```javascript +import { + ApiError, + ExactOutNotSupportedError, + FeeLimitExceededError, + ReadOnlyAccountError, + SymbiosisError, + UnsupportedRouteError +} from '@symbiosis-finance/wdk-protocol-swidge-symbiosis' + +try { + await symbiosis.swidge(options, { maxProtocolFeeBps: 100 }) +} catch (error) { + if (error instanceof FeeLimitExceededError) { + // Stop before wallet execution and review the fresh mapped fees. + } else if (error instanceof ExactOutNotSupportedError) { + // Rebuild the request with fromTokenAmount. + } else if (error instanceof ReadOnlyAccountError) { + // Bind an account that supports the required write capabilities. + } else if (error instanceof UnsupportedRouteError) { + // Keep the route quote-only or choose an executable source chain. + } else if (error instanceof ApiError) { + // Handle a non-2xx response, timeout, or network failure. + // status is 0 when no HTTP response was received. + } else if (error instanceof SymbiosisError) { + // Handle another package-specific error. + } else { + // Handle errors propagated by the wallet account. + } +} +``` + +API requests time out after 30 seconds by default; set `timeoutMs` on the provider to change that limit. The package does not retry automatically. Avoid retrying `swidge()` blindly after an uncertain API or wallet failure; first inspect wallet history and retained transaction state. + + + +Review constructor fields, per-call fee caps, identifiers, caching, and runtime behavior. + + +Review exact methods, result fields, statuses, fee mapping, and typed errors. + + + +*** + +## Wallet Modules Overview +URL: https://docs.wdk.tether.io/sdk/wallet-modules +Description: Explore WDK wallet modules for building self-custodial wallets across supported chains. + +The Wallet Development Kit (WDK) provides a set of modules that support multiple blockchain networks. All modules share a common interface, ensuring consistent behavior across different blockchain implementations. + +## Shared Fee Limits + +Wallet modules can expose fee caps through their configuration objects. Use the chain-specific docs for exact units and supported operations. + +| Option | Applies to | Description | +|--------|------------|-------------| +| `transferMaxFee` | `transfer()` | Caps token transfer fees in the module's base fee unit. | +| `transactionMaxFee` | `sendTransaction()` and `signTransaction()` | Caps native transaction send/sign flows separately from token transfers when the module supports that base wallet option. | + +## Shared Signer Interface + +The base `@tetherto/wdk-wallet` package defines a cross-chain `ISigner` interface for wallet modules that support external signing. Signer-aware modules can be constructed from a default signer instead of a seed, can register named signers with `addSigner(name, signer)`, and can resolve accounts by passing `signerName` to account retrieval methods. + +| Surface | Description | +| --- | --- | +| `ISigner.derive(relPath)` | Derives a child signer from a relative derivation path, when the signer supports derivation. | +| `ISigner.getAddress()` | Returns the address controlled by the signer. | +| `ISigner.dispose()` | Clears signer-held secret material or external resources. | +| `WalletManager.addSigner(name, signer)` | Registers a named signer. Blank names throw an error. | +| `WalletManager.getSigner(name?)` | Returns a named signer, or the default signer when called without a name. | +| `WalletManager.getSigners()` | Returns a shallow copy of the named signer map. | + +`signTransaction(tx)` is an account-level operation, not part of `ISigner`. Use the chain account's `IWalletAccount.signTransaction(tx)` when a module supports signing without broadcast. Modules can also type their signed transaction payloads so `sendTransaction(tx)` and `quoteSendTransaction(tx)` accept either unsigned transactions or module-specific signed payloads where that behavior is implemented. + +Module support depends on the chain-specific wallet implementation. Check each module's reference before relying on signer-based account creation. + +## Supported Networks + +This package works with multiple blockchain networks through wallet registration. + + + +Bitcoin Mainnet + + +Ethereum, Sepolia Testnet, L2s, etc. + + +Tron Mainnet + + +TON Mainnet + + +Solana Mainnet + + +Solana with paymaster-funded transactions + + +Spark Mainnet + + + +## Wallet Modules + +Wallet implementations for supported chains and asset systems: + +| Module | Blockchain | Status | Documentation | +|--------|------------|--------|---------------| +| [`@tetherto/wdk-wallet-evm`](https://github.com/tetherto/wdk-wallet-evm) | EVM | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-evm) | +| [`@tetherto/wdk-wallet-ton`](https://github.com/tetherto/wdk-wallet-ton) | TON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-ton) | +| [`@tetherto/wdk-wallet-btc`](https://github.com/tetherto/wdk-wallet-btc) | Bitcoin | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-btc) | +| [`@tetherto/wdk-wallet-spark`](https://github.com/tetherto/wdk-wallet-spark) | Spark | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-spark) | +| [`@tetherto/wdk-wallet-tron`](https://github.com/tetherto/wdk-wallet-tron) | TRON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-tron) | +| [`@tetherto/wdk-wallet-solana`](https://github.com/tetherto/wdk-wallet-solana) | Solana | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-solana) | +| [`@tetherto/wdk-wallet-aptos`](https://github.com/tetherto/wdk-wallet-aptos) | Aptos | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-aptos) | +| [`@utexo/wdk-wallet-rgb`](https://www.npmjs.com/package/@utexo/wdk-wallet-rgb) | Bitcoin (RGB) | ✅ Ready | [Documentation](/sdk/community-modules/wdk-wallet-rgb/) | +| [`@base58-io/wdk-wallet-cosmos`](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos) | Cosmos | ✅ Ready | [Documentation](/sdk/community-modules/wdk-wallet-cosmos/) | + +## Account Abstraction Wallet Modules + +Wallet implementations that support [Account Abstraction](/resources/concepts#account-abstraction) for gasless transactions using paymaster tokens like USD₮: + +| Module | Blockchain | Status | Documentation | +|--------|------------|--------|---------------| +| [`@tetherto/wdk-wallet-evm-erc-4337`](https://github.com/tetherto/wdk-wallet-evm-erc-4337) | EVM | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-evm-erc-4337) | +| [`@tetherto/wdk-wallet-evm-7702-gasless`](https://github.com/tetherto/wdk-wallet-evm-7702-gasless) | EVM | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-evm-7702-gasless) | +| [`@tetherto/wdk-wallet-ton-gasless`](https://github.com/tetherto/wdk-wallet-ton-gasless) | TON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-ton-gasless) | +| [`@tetherto/wdk-wallet-tron-gasfree`](https://github.com/tetherto/wdk-wallet-tron-gasfree) | TRON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-tron-gasfree) | +| [`@tetherto/wdk-wallet-solana-gasless`](https://github.com/tetherto/wdk-wallet-solana-gasless) | Solana | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-solana-gasless) | +| `@tetherto/wdk-wallet-solana-jupiterz` | Solana | In progress | - | + +## Next Steps + +To get started with WDK modules, follow these steps: + +1. Get up and running quickly with our [Quickstart Guide](/start-building/nodejs-bare-quickstart) +2. Choose the modules that best fit your needs from the tables above +3. Check specific documentation for modules you wish to use + +You can also: + +- Learn about key concepts like [Account Abstraction](/resources/concepts#account-abstraction) and other important definitions +- Use one of our ready-to-use examples to be production ready + +*** + +## Wallet Aptos Overview +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos +Description: Overview of the @tetherto/wdk-wallet-aptos module. + +The Aptos wallet module manages SLIP-0010 Ed25519 accounts for the Aptos blockchain through the shared WDK wallet interfaces. + +Use this module when an app needs Aptos account derivation, APT balances, fungible asset balances, native APT transfers, fungible asset transfers, fee quotes, message signing, and read-only account support. + +## Features + +- **BIP-39 seed support**: Accepts a mnemonic phrase or seed bytes. +- **SLIP-0010 Ed25519 derivation**: Uses Aptos coin type `637` and hardened path segments. +- **Aptos addresses**: Derives 32-byte Aptos addresses from the public key. +- **Native APT support**: Sends native APT through `0x1::aptos_account::transfer`. +- **Fungible asset support**: Reads and transfers Aptos fungible assets by metadata address. +- **Fee estimation**: Simulates transactions to estimate gas before signing and submitting. +- **Message signing**: Signs and verifies messages with Ed25519 keys. +- **Read-only accounts**: Address-only accounts read balances and receipts. Accounts created with the matching public key can also quote fees and verify signatures. +- **Bare runtime compatibility**: Uses the Aptos fullnode REST API over `fetch` instead of the Aptos SDK at runtime. + +## Supported Networks + +| Network | Chain ID | Fullnode example | +|---------|----------|------------------| +| Aptos Mainnet | `1` | `https://fullnode.mainnet.aptoslabs.com/v1` | +| Aptos Testnet | `2` | `https://fullnode.testnet.aptoslabs.com/v1` | + +## Aptos-Specific Behavior + +| Area | Behavior | +|------|----------| +| Default derivation path | `m/44'/637'/account'/0'/0'` | +| `getAccount(index)` mapping | Uses `index` as the `account` segment. | +| Token model | Uses Aptos fungible asset metadata addresses, not coin type tags. | +| APT units | APT balances and fees are returned in octas. | +| Token transfers | `transfer()` builds and submits `0x1::primary_fungible_store::transfer`. | +| Signing without broadcast | `signTransaction()` simulates and signs a native APT transfer without submitting it. It still requires a provider. | +| Fungible asset fee cap | `transferMaxFee` applies to `transfer()` only and rejects an estimated fee at or above the cap. | + + +All Aptos derivation path segments are hardened because the module uses Ed25519 with SLIP-0010 derivation. + + + +Native `sendTransaction()` and `signTransaction()` do not apply `transferMaxFee`. Quote native transfers and enforce an application-level limit before sending or signing when your product requires one. + + +## Next Steps + + + +Configure Aptos fullnode access, derivation paths, and fee limits. + + +Install the package, create accounts, read balances, send APT, transfer fungible assets, and sign messages. + + +Review manager, account, read-only account, config, transaction, and result types. + + + +--- + +## Need Help? + + + +*** + +## Wallet Aptos API Reference +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos/api-reference +Description: API reference for @tetherto/wdk-wallet-aptos. + +## Exports + +```javascript +import WalletManagerAptos, { + WalletAccountAptos, + WalletAccountReadOnlyAptos +} from '@tetherto/wdk-wallet-aptos' +``` + +## WalletManagerAptos + +Creates and manages seed-derived Aptos accounts. + +```typescript +new WalletManagerAptos( + seed: string | Uint8Array, + config?: AptosWalletConfig +) +``` + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `getAccount(index?)` | Returns the account at `m/44'/637'/index'/0'/0'`. | `Promise` | +| `getAccountByPath(path)` | Returns an account at a relative hardened path. | `Promise` | +| `getFeeRates()` | Returns Aptos fee rates in octas per gas unit. | `Promise` | +| `dispose()` | Clears managed account secret material. | `void` | + +## WalletAccountAptos + +Writable Aptos account with signing and transaction submission support. + +```typescript +new WalletAccountAptos( + seed: string | Uint8Array, + path: string, + config?: AptosWalletConfig +) +``` + +### Properties + +| Property | Description | +|----------|-------------| +| `index` | Index parsed from the derivation path. | +| `path` | Full derivation path, including the `m/44'/637'` prefix. | +| `keyPair` | Public/private key byte-array views. Treat them as read-only. The private key is unavailable after `dispose()`. | + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `getAddress()` | Returns the Aptos account address. | `Promise` | +| `getBalance()` | Returns the native APT balance in octas. | `Promise` | +| `getTokenBalance(tokenAddress)` | Returns a fungible asset balance by metadata address. | `Promise` | +| `quoteSendTransaction(tx)` | Estimates fee for a native APT transfer. | `Promise<{ fee: bigint }>` | +| `sendTransaction(tx)` | Signs and submits a native APT transfer. | `Promise` | +| `signTransaction(tx)` | Simulates and signs a native APT transfer without broadcasting. Requires a provider. | `Promise` | +| `quoteTransfer(options)` | Estimates fee for a fungible asset transfer. | `Promise<{ fee: bigint }>` | +| `transfer(options)` | Signs and submits a fungible asset transfer. | `Promise` | +| `getTransactionReceipt(hash)` | Looks up a pending or committed Aptos transaction. | `Promise<{ type: string; hash: string; success?: boolean; vm_status?: string } \| null>` | +| `sign(message)` | Signs a message with the account key. | `Promise` | +| `verify(message, signature)` | Verifies a message signature. | `Promise` | +| `toReadOnlyAccount()` | Returns a read-only account for the same address. | `Promise` | +| `dispose()` | Clears private key material from memory. | `void` | + +## WalletAccountReadOnlyAptos + +Read-only account for address-based reads and verification. + +```typescript +new WalletAccountReadOnlyAptos( + address: string, + config?: AptosWalletConfig, + publicKey?: Uint8Array +) +``` + +An address-only instance supports balance reads and receipt lookup. `quoteSendTransaction()`, `quoteTransfer()`, and `verify()` require the matching public key because an Aptos address cannot be reversed into an Ed25519 public key. `toReadOnlyAccount()` supplies that key automatically. + +| Method | Description | Returns | +|--------|-------------|---------| +| `getAddress()` | Returns the normalized Aptos address. | `Promise` | +| `getBalance()` | Returns the native APT balance in octas. | `Promise` | +| `getTokenBalance(tokenAddress)` | Returns a fungible asset balance by metadata address. | `Promise` | +| `quoteSendTransaction(tx)` | Estimates fee for a native APT transfer. | `Promise<{ fee: bigint }>` | +| `quoteTransfer(options)` | Estimates fee for a fungible asset transfer. | `Promise<{ fee: bigint }>` | +| `getTransactionReceipt(hash)` | Looks up a pending or committed Aptos transaction. | `Promise<{ type: string; hash: string; success?: boolean; vm_status?: string } \| null>` | +| `verify(message, signature)` | Verifies a message signature. Requires the matching public key. | `Promise` | + +## Config Type + +```typescript +type AptosWalletConfig = { + provider?: string | string[] + chainId?: number + retries?: number + txnExpirationSecs?: number + transferMaxFee?: number | bigint +} +``` + +| Field | Default and behavior | +|---|---| +| `provider` | No default. An array enables endpoint failover. | +| `chainId` | Fetched from ledger info on first use when omitted. A supplied value must match the provider network. | +| `retries` | `3` for a provider array. | +| `txnExpirationSecs` | `60`. | +| `transferMaxFee` | No default. Applies only to fungible asset `transfer()` and rejects fees at or above the cap. | + +## Transaction Types + +```typescript +type AptosTransaction = { + to: string + value: number | bigint +} + +type TransferOptions = { + token: string + recipient: string + amount: number | bigint +} +``` + +`token` is an Aptos fungible asset metadata address. + +## Result Types + +```typescript +type TransactionResult = { + hash: string + fee: bigint +} + +type TransferResult = { + hash: string + fee: bigint +} +``` + +`getTransactionReceipt()` has three observable states. The package root does not export an `AptosTransactionReceipt` type; the released declaration uses `type: string`. The following is the portable declared shape: + +```typescript +type AptosReceiptShape = { + type: string + hash: string + success?: boolean + vm_status?: string +} +``` + +- `null`: the fullnode does not know the hash. +- An observed `type` of `pending_transaction`: accepted into the mempool; `success` and `vm_status` are absent. +- An observed `type` of `user_transaction`: committed; inspect `success` and `vm_status` to determine execution outcome. + +## Signed Transaction + +`signTransaction(tx)` returns a JSON-form signed transaction accepted by the Aptos REST API. It includes sender, sequence number, gas fields, payload, and Ed25519 signature. Before signing, the module uses the configured provider to simulate the transaction and obtain sequence, gas, and chain data. A failed simulation prevents signing. + + +`signTransaction()` does not broadcast, but it is not an offline operation. It signs native APT transfers only. Use `transfer()` for fungible asset transfers. + + + +`transferMaxFee` does not protect native `sendTransaction()` or `signTransaction()` calls. Quote native transfers and enforce an application-level limit when required. + + + + +Create accounts, read balances, transfer funds, and sign messages. + + +Provider, network, fee, and derivation path options. + + + +*** + +## Wallet Aptos Configuration +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos/configuration +Description: Configuration options for @tetherto/wdk-wallet-aptos. + +Configure the Aptos wallet module with an Aptos fullnode REST endpoint. A provider is required for balance reads, fee quotes, transaction submission, and receipt polling. + +```javascript +import WalletManagerAptos from '@tetherto/wdk-wallet-aptos' + +const wallet = new WalletManagerAptos(seedPhrase, { + provider: [ + 'https://fullnode.mainnet.aptoslabs.com/v1', + process.env.APTOS_FAILOVER_FULLNODE_URL + ].filter(Boolean), + retries: 3, + txnExpirationSecs: 60, + transferMaxFee: 100000n +}) +``` + +## Wallet Configuration + +| Option | Type | Description | +|--------|------|-------------| +| `provider` | `string \| string[]` | Aptos fullnode REST API URL, or an ordered list for failover. Required for chain operations. | +| `chainId` | `number` | Optional chain ID. If omitted, the module fetches it from ledger info on first use. | +| `retries` | `number` | Failover attempts when `provider` is an array. Defaults to `3`. | +| `txnExpirationSecs` | `number` | Transaction expiration window measured from the current time. Defaults to `60`. | +| `transferMaxFee` | `number \| bigint` | Optional maximum estimated fee in octas for fungible asset `transfer()` calls. | + +An empty provider array behaves like no provider. Balance reads, quotes, receipt lookup, transaction signing, and transaction submission then throw a provider-required error. + +## Account Configuration + +You can construct accounts directly when you need a specific derivation path. + +```javascript +import { WalletAccountAptos } from '@tetherto/wdk-wallet-aptos' + +const account = new WalletAccountAptos(seedPhrase, "0'/0'/0'", { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1', + transferMaxFee: 100000n +}) +``` + +An address and provider are enough for balance and receipt reads: + +```javascript +import { WalletAccountReadOnlyAptos } from '@tetherto/wdk-wallet-aptos' + +const readOnlyAccount = new WalletAccountReadOnlyAptos('0x...', { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1' +}) +``` + +Fee quotes simulate a transaction and message verification uses Ed25519, so those operations also require the matching 32-byte public key: + +```javascript +const readOnlyWithPublicKey = new WalletAccountReadOnlyAptos( + '0x...', + { provider: 'https://fullnode.mainnet.aptoslabs.com/v1' }, + publicKey +) +``` + +The constructor rejects a public key that does not derive the supplied address. Prefer `account.toReadOnlyAccount()` when you already have a writable account; it carries the matching public key forward. + +## Network Selection + +Network selection is controlled by the fullnode URL and the transaction chain ID. If you provide `chainId`, it must match the configured fullnode. The module does not compare a supplied chain ID with ledger info before signing. + +| Network | Provider URL | +|---------|--------------| +| Mainnet | `https://fullnode.mainnet.aptoslabs.com/v1` | +| Testnet | `https://fullnode.testnet.aptoslabs.com/v1` | + +## Derivation Paths + +The module uses SLIP-0010 Ed25519 derivation. `getAccount(index)` derives: + +```text +m/44'/637'/index'/0'/0' +``` + +Use `getAccountByPath(path)` to provide a relative path after `m/44'/637'/`. + +```javascript +const account = await wallet.getAccountByPath("5'/0'/0'") +``` + + +Every Aptos path segment must be hardened. A path segment without an apostrophe is invalid for this module's Ed25519 derivation. + + +The relative path must contain exactly three hardened indexes without leading zeros, such as `"5'/0'/0'"`. + +## Fee Limit + +`transferMaxFee` caps the simulated fee for fungible asset `transfer()` calls. The transfer is rejected when the estimated fee is equal to or greater than the cap. + +```javascript +const wallet = new WalletManagerAptos(seedPhrase, { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1', + transferMaxFee: 100000n +}) +``` + +The cap does not apply to native APT `sendTransaction()` or `signTransaction()`. Use `quoteSendTransaction()` and enforce a separate application limit for native transfers when needed. + +## Security Notes + +- Keep seed phrases and seed bytes outside logs and client-visible error reporting. +- Use trusted fullnode endpoints for production wallets. +- Keep a supplied `chainId` consistent with every configured fullnode. +- Set a fee cap for user-facing fungible asset transfers and check native APT quotes separately. +- Call `dispose()` on accounts and managers when secret material is no longer needed. + + + +Create accounts, read balances, send transactions, and transfer fungible assets. + + +Detailed class, method, config, and type reference. + + + +*** + +## Wallet Aptos Usage +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos/usage +Description: Install and use @tetherto/wdk-wallet-aptos for Aptos accounts, balances, transfers, and signing. + +## Install + +```bash +npm install @tetherto/wdk-wallet-aptos +``` + +## Create a Wallet Manager + +```javascript +import WalletManagerAptos from '@tetherto/wdk-wallet-aptos' + +const wallet = new WalletManagerAptos(seedPhrase, { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1', + transferMaxFee: 100000n +}) + +const account = await wallet.getAccount(0) +const address = await account.getAddress() +``` + +## Manage Accounts + +```javascript +const first = await wallet.getAccount(0) +const second = await wallet.getAccount(1) + +const custom = await wallet.getAccountByPath("5'/0'/0'") + +console.log(await first.getAddress()) +console.log(await second.getAddress()) +console.log(await custom.getAddress()) +``` + +`getAccount(index)` maps to `m/44'/637'/index'/0'/0'`. + +## Read Balances + +```javascript +const aptBalance = await account.getBalance() +console.log('APT balance in octas:', aptBalance) + +const usdtMetadataAddress = + '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b' + +const usdtBalance = await account.getTokenBalance(usdtMetadataAddress) +console.log('USDT balance:', usdtBalance) +``` + +Read-only accounts support the same balance reads without a seed phrase. + +```javascript +import { WalletAccountReadOnlyAptos } from '@tetherto/wdk-wallet-aptos' + +const readOnlyAccount = new WalletAccountReadOnlyAptos('0x...', { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1' +}) + +const balance = await readOnlyAccount.getBalance() +``` + +An address-only account can read balances and receipts. Fee quotes and message verification also need the matching Ed25519 public key. Use `account.toReadOnlyAccount()` when possible; the returned account includes it. + +## Send Native APT + +```javascript +const nativeFeeLimit = 100000n + +const quote = await account.quoteSendTransaction({ + to: '0x...', + value: 100000000n +}) + +console.log('Estimated fee in octas:', quote.fee) + +if (quote.fee >= nativeFeeLimit) { + throw new Error('Native APT fee is at or above the application limit') +} + +const result = await account.sendTransaction({ + to: '0x...', + value: 100000000n +}) + +console.log('Transaction hash:', result.hash) +console.log('Fee in octas:', result.fee) +``` + +`sendTransaction()` submits a native APT transfer through `0x1::aptos_account::transfer`. + +## Transfer Fungible Assets + +Use the fungible asset metadata address as `token`. + +```javascript +const quote = await account.quoteTransfer({ + token: usdtMetadataAddress, + recipient: '0x...', + amount: 1000000n +}) + +console.log('Estimated fee in octas:', quote.fee) + +const result = await account.transfer({ + token: usdtMetadataAddress, + recipient: '0x...', + amount: 1000000n +}) + +console.log('Transfer hash:', result.hash) +console.log('Fee in octas:', result.fee) +``` + +`transfer()` submits `0x1::primary_fungible_store::transfer` and can auto-create the recipient primary store. + +When configured, `transferMaxFee` applies to this fungible asset flow only. `transfer()` rejects an estimated fee at or above the cap. + +## Sign and Verify Messages + +```javascript +const message = 'Hello, Aptos' +const signature = await account.sign(message) + +const readOnly = await account.toReadOnlyAccount() +const valid = await readOnly.verify(message, signature) + +console.log('Signature valid:', valid) +``` + +## Sign Native APT Transfers Without Broadcasting + +`signTransaction()` simulates and signs a native APT transfer without broadcasting it. It still calls the configured fullnode for account sequence, gas, chain, and simulation data. + +```javascript +const signed = await account.signTransaction({ + to: '0x...', + value: 100000000n +}) + +console.log('Signed Aptos transaction:', signed) +``` + + +This is not an offline operation. A failed simulation prevents signing. Fungible asset transfers are built and submitted through `transfer()`. + + +`transferMaxFee` does not apply to native APT signing or sending. Quote the transfer and enforce your own native-fee limit before signing or submitting: + +```javascript +const nativeFeeLimit = 100000n + +const quote = await account.quoteSendTransaction({ + to: '0x...', + value: 100000000n +}) + +if (quote.fee >= nativeFeeLimit) { + throw new Error('Native APT fee is at or above the application limit') +} +``` + +## Check Transaction Status + +```javascript +const receipt = await account.getTransactionReceipt(result.hash) + +if (!receipt) { + console.log('Transaction is not known to this fullnode') +} else if (receipt.type === 'pending_transaction') { + console.log('Transaction is pending') +} else if (!receipt.success) { + console.error('Transaction failed:', receipt.vm_status) +} +``` + +A non-null receipt is not necessarily final. Wait for `type === 'user_transaction'`, then inspect `success`. + +## Dispose Secret Material + +```javascript +account.dispose() +wallet.dispose() +``` + + + +Provider, fee, network, and derivation path configuration. + + +Detailed method and type reference. + + + +*** + +## Bitcoin wallet +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc +Description: Create and manage Bitcoin wallets with SegWit defaults, legacy path support, balances, UTXOs, and transactions. + +Use the Bitcoin wallet module to create SegWit wallets, manage accounts, read balances and UTXOs, sign messages, and send BTC transactions. + + +**Default Derivation Path Change in v1.0.0-beta.4+** + +The default derivation path was updated in v1.0.0-beta.4 to use BIP-84 (Native SegWit) instead of BIP-44 (Legacy): + +- **Previous path** (up to v1.0.0-beta.3): `m/44'/0'/0'/0/{index}` (Legacy addresses) +- **Current path** (v1.0.0-beta.4+): `m/84'/0'/0'/0/{index}` (Native SegWit addresses) + +If you're upgrading from an earlier version, existing wallets created with the old path will generate different addresses. Make sure to migrate any existing wallets or use the old path explicitly if needed for compatibility. + +Use [`getAccountByPath`](/sdk/wallet-modules/wallet-btc/api-reference#getaccountbypathpath) to supply an explicit derivation path when importing or recreating legacy wallets. + + +## Features + +- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases +- **Bitcoin Derivation Paths**: Support for BIP-84 (Native SegWit, default) and BIP-44 (Legacy) derivation paths +- **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase +- **Address Types Support**: Generate Native SegWit (P2WPKH) addresses by default, with Legacy (P2PKH) support via configuration +- **UTXO Management**: Track and manage unspent transaction outputs +- **Non-broadcasting Transaction Signing**: Build and sign Bitcoin transactions with `signTransaction()` without broadcasting them. The method still uses the configured client to fetch UTXOs and fee data. +- **Transaction Management**: Create, sign, and broadcast single-recipient Bitcoin transactions, or separately quote and broadcast signed raw transaction hex +- **Fee Estimation**: Dynamic fee calculation via mempool.space API +- **Provider Failover**: Configure ordered Electrum, WebSocket, Blockbook, or custom client fallbacks +- **TypeScript Support**: Full TypeScript definitions included +- **Memory Safety**: Secure private key management with memory-safe implementation +- **Network Flexibility**: Support for mainnet, testnet, and regtest + +## Supported Networks + +This package works with Bitcoin networks: + +- **Bitcoin Mainnet** (`"bitcoin"`) +- **Bitcoin Testnet** (`"testnet"`) +- **Bitcoin Regtest** (`"regtest"`) + +### Electrum Server Configuration + +**Important**: While the package defaults to `electrum.blockstream.info:50001` for convenience, **we strongly recommend configuring your own Electrum server** for production use. + +#### Recommended Approach: + +**For Production:** +- Set up your own Fulcrum server for optimal performance and reliability +- Use recent Fulcrum versions that support pagination for high-transaction addresses + +**For Development/Testing:** +- `fulcrum.frznode.com:50001` - Generally faster than default +- `electrum.blockstream.info:50001` - Default fallback + +## Next Steps + + + +Get started with WDK in a Node.js environment + + +Get started with WDK's Bitcoin Wallet configuration + + +Get started with WDK's Bitcoin Wallet API + + +Get started with WDK's Bitcoin Wallet usage + + + +*** + +### Need Help? + + + +*** + +## Wallet BTC API Reference +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/api-reference +Description: Complete API documentation for @tetherto/wdk-wallet-btc + +## Table of Contents + +| Class | Description | Methods | +|-------|-------------|---------| +| [WalletManagerBtc](#walletmanagerbtc) | Main class for managing Bitcoin wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. | [Constructor](#constructor), [Methods](#methods) | +| [WalletAccountBtc](#walletaccountbtc) | Individual Bitcoin wallet account implementation. Implements `IWalletAccount`. | [Constructor](#constructor-1), [Methods](#methods-1), [Properties](#properties) | +| [WalletAccountReadOnlyBtc](#walletaccountreadonlybtc) | Read-only Bitcoin wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-2), [Methods](#methods-2) | +| [ElectrumTcp](#electrumtcp) | Standard TCP Electrum client. Implements `IBtcClient`. | [Constructor](#constructor-3) | +| [ElectrumTls](#electrumtls) | TLS Electrum client. Implements `IBtcClient`. | [Constructor](#constructor-4) | +| [ElectrumSsl](#electrumssl) | SSL Electrum client. Implements `IBtcClient`. | [Constructor](#constructor-5) | +| [ElectrumWs](#electrumws) | WebSocket Electrum client for browser environments. Implements `IBtcClient`. | [Constructor](#constructor-6), [Methods](#methods-3) | + +## WalletManagerBtc + +The main class for managing Bitcoin wallets. +Extends `WalletManager` from `@tetherto/wdk-wallet`. + +#### Constructor + +```javascript +new WalletManagerBtc(seed, config) +``` +**Parameters:** +- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes +- `config` (BtcWalletConfig, optional): Configuration object + - `client` (`IBtcClient | BtcClientDescriptor | Array`, optional): Bitcoin client, descriptor, or ordered failover list + - `network` (string, optional): "bitcoin", "testnet", or "regtest" (default: "bitcoin") + - `bip` (number, optional): BIP address type - 44 (legacy) or 84 (native SegWit) (default: 84) + - `retries` (number, optional): Additional retry attempts when `client` is an array + - `transactionMaxFee` (number | bigint, optional): Maximum fee amount for `sendTransaction()` and `signTransaction()` operations (in satoshis) + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `getAccount(index)` | Returns a wallet account at the specified index | `Promise` | +| `getAccountByPath(path)` | Returns a wallet account at the specified derivation path | `Promise` | +| `getFeeRates()` | Returns current fee rates for transactions | `Promise<{normal: bigint, fast: bigint}>` | +| `dispose()` | Disposes all wallet accounts, clearing private keys from memory and closing internal Electrum connections | `void` | + +##### `getAccount(index)` +Returns a wallet account at the specified index using BIP-84 (default) or BIP-44 derivation. + +**Parameters:** +- `index` (number, optional): The index of the account to get (default: 0) + +**Returns:** `Promise` - The wallet account + +**Example:** +```javascript +// Returns the account with derivation path: +// For mainnet (bitcoin): m/84'/0'/0'/0/1 +// For testnet or regtest: m/84'/1'/0'/0/1 +const account = await wallet.getAccount(1) +``` + +##### `getAccountByPath(path)` +Returns a wallet account at the specified derivation path. + +**Parameters:** +- `path` (string): The derivation path (e.g., "0'/0/0") + +**Returns:** `Promise` - The wallet account + +**Example:** +```javascript +// Returns the account with derivation path: +// For mainnet (bitcoin): m/84'/0'/0'/0/1 +// For testnet or regtest: m/84'/1'/0'/0/1 +const account = await wallet.getAccountByPath("0'/0/1") +``` +##### `getFeeRates()` +Returns current fee rates from mempool.space API. + +**Returns:** `Promise<{normal: bigint, fast: bigint}>` - Object containing fee rates in sat/vB +- `normal`: Standard fee rate for confirmation within ~1 hour +- `fast`: Higher fee rate for faster confirmation + +**Example:** +```javascript +const feeRates = await wallet.getFeeRates() +console.log('Normal fee rate:', feeRates.normal, 'sat/vB') +console.log('Fast fee rate:', feeRates.fast, 'sat/vB') +``` + +##### `dispose()` +Disposes all wallet accounts, clears sensitive data from memory, and closes internal Electrum connections. + +**Returns:** `void` + +**Example:** +```javascript +wallet.dispose() +``` + +## WalletAccountBtc + +Represents an individual Bitcoin wallet account. Extends `WalletAccountReadOnlyBtc` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. + +#### Constructor + +```javascript +new WalletAccountBtc(seed, path, config) +``` + +**Parameters:** +- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes +- `path` (string): Derivation path suffix (e.g., "0'/0/0") +- `config` (BtcWalletConfig, optional): Configuration object + - `client` (`IBtcClient | BtcClientDescriptor | Array`, optional): Bitcoin client, descriptor, or ordered failover list + - `network` (string, optional): "bitcoin", "testnet", or "regtest" (default: "bitcoin") + - `bip` (number, optional): BIP address type - 44 (legacy) or 84 (native SegWit) (default: 84) + - `retries` (number, optional): Additional retry attempts when `client` is an array + - `transactionMaxFee` (number | bigint, optional): Maximum fee amount for `sendTransaction()` and `signTransaction()` operations (in satoshis) + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `getAddress()` | Returns the account's Bitcoin address | `Promise` | +| `getBalance()` | Returns the total account balance in satoshis, including unconfirmed funds when present | `Promise` | +| `sendTransaction(tx, timeoutMs?)` | Signs and sends transaction options, or broadcasts signed raw transaction hex, then optionally polls spent inputs | `Promise<{hash: string, fee: bigint}>` | +| `signTransaction(options)` | Signs a Bitcoin transaction without broadcasting it | `Promise` | +| `quoteSendTransaction(tx)` | Estimates an unsigned transaction fee or calculates the fee of signed raw transaction hex | `Promise<{fee: bigint}>` | +| `getTransfers(options?)` | Returns the account's transfer history | `Promise` | +| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | +| `getMaxSpendable()` | Returns the maximum spendable amount | `Promise` | +| `sign(message)` | Signs a message with the account's private key | `Promise` | +| `verify(message, signature)` | Verifies a message signature | `Promise` | +| `toReadOnlyAccount()` | Creates a read-only version of this account | `Promise` | +| `dispose()` | Disposes the wallet account, clearing private keys from memory | `void` | + +##### `getAddress()` +Returns the account's Bitcoin address (Native SegWit bech32 by default, or legacy if using BIP-44). + +**Returns:** `Promise` - The Bitcoin address + +**Example:** +```javascript +const address = await account.getAddress() +console.log('Address:', address) // bc1q... (BIP-84) or 1... (BIP-44) +``` +##### `getBalance()` +Returns the account's total balance in satoshis, including unconfirmed funds when present. + +**Returns:** `Promise` - Balance in satoshis + +**Example:** +```javascript +const balance = await account.getBalance() +console.log('Balance:', balance, 'satoshis') +``` + +##### `sendTransaction(options, timeoutMs?)` +Accepts either transaction options or signed raw Bitcoin transaction hex. With transaction options, the wallet builds, signs, and broadcasts a single-recipient transaction. With signed hex, it broadcasts the exact supplied transaction without rebuilding or signing it. Both paths can poll after broadcast until spent inputs disappear from the unspent-output set. + +**Parameters:** +- `options` (`BtcTransaction | string`): Transaction options or signed raw transaction hex + - `to` (string): Recipient's Bitcoin address + - `value` (number | bigint): Amount in satoshis + - `feeRate` (number | bigint, optional): Fee rate in sat/vB. If provided, overrides the fee rate estimated from the blockchain. + - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) +- `timeoutMs` (number, optional): Maximum milliseconds to poll after broadcast before returning (default: 10000) + +**Returns:** `Promise<{hash: string, fee: bigint}>` +- `hash`: Transaction hash +- `fee`: Transaction fee in satoshis + +**Throws:** Error if the transaction fee exceeds `transactionMaxFee` when configured. Invalid raw transaction data, unavailable previous transactions, and broadcast rejection errors propagate from the configured client. + +**Example:** +```javascript +const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 50000n +}) +console.log('Transaction hash:', result.hash) +console.log('Fee:', result.fee, 'satoshis') +``` + +##### `signTransaction(options)` +Signs a Bitcoin transaction and returns the signed raw transaction as a hex string. This method does not broadcast the transaction. + +**Parameters:** +- `options` (BtcTransaction): Transaction options + - `to` (string): Recipient's Bitcoin address + - `value` (number | bigint): Amount in satoshis + - `feeRate` (number | bigint, optional): Fee rate in sat/vB. If provided, overrides the fee rate estimated from the blockchain. + - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) + +**Returns:** `Promise` - Signed raw transaction hex string + +**Throws:** Error if the estimated transaction fee exceeds `transactionMaxFee` when configured. + +**Example:** +```javascript +const signedTransaction = await account.signTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 50000n, + feeRate: 10n +}) + +console.log('Signed transaction:', signedTransaction) +``` + +##### `quoteSendTransaction(options)` +Estimates the fee for transaction options or calculates the fee encoded by signed raw transaction hex, without broadcasting it. + +**Parameters:** +- `options` (`BtcTransaction | string`): Transaction options or signed raw transaction hex + - `to` (string): Recipient's Bitcoin address + - `value` (number | bigint): Amount in satoshis + - `feeRate` (number | bigint, optional): Fee rate in sat/vB. If provided, overrides the fee rate estimated from the blockchain. + - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) + +**Returns:** `Promise<{fee: bigint}>` +- `fee`: Estimated transaction fee in satoshis + +**Example:** +```javascript +const quote = await account.quoteSendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 50000n +}) +console.log('Estimated fee:', quote.fee, 'satoshis') +``` + +For signed hex, the wallet parses the transaction and fetches every referenced previous transaction through the configured client to calculate input value minus output value. This path requires network access, does not validate signatures before broadcast, does not broadcast, and does not apply `transactionMaxFee`. + +##### `getTransfers(options?)` +Returns the account's transfer history with detailed transaction information. + +**Parameters:** +- `options` (object, optional): Filter options + - `direction` (string, optional): 'incoming', 'outgoing', or 'all' (default: 'all') + - `limit` (number, optional): Maximum number of transfers (default: 10) + - `skip` (number, optional): Number of transfers to skip (default: 0) + +**Returns:** `Promise` - Array of transfer objects +- `txid`: Transaction ID +- `address`: Account's own address +- `vout`: Output index in the transaction +- `height`: Block height (0 if unconfirmed) +- `value`: Transfer value in satoshis (bigint) +- `direction`: 'incoming' or 'outgoing' +- `fee`: Transaction fee in satoshis (bigint, for outgoing transfers) +- `recipient`: Receiving address (for outgoing transfers) + +**Example:** +```javascript +const transfers = await account.getTransfers({ + direction: 'incoming', + limit: 5 +}) +console.log('Recent incoming transfers:', transfers) +``` + +##### `getTransactionReceipt(hash)` +Returns a transaction's receipt if it has been included in a block. + +**Parameters:** +- `hash` (string): The transaction hash (64 hex characters) + +**Returns:** `Promise` - The receipt, or null if the transaction has not been included in a block yet. + +**Example:** +```javascript +const receipt = await account.getTransactionReceipt('abc123...') +if (receipt) { + console.log('Transaction confirmed') +} +``` + +##### `getMaxSpendable()` +Returns the maximum spendable amount that can be sent in a single transaction. The maximum spendable amount can differ from the wallet's total balance for several reasons: +- **Transaction fees**: Fees are subtracted from the total balance +- **Uneconomic UTXOs**: Small UTXOs where the fee to spend them exceeds their value are excluded +- **UTXO limit**: A transaction can include at most 200 inputs. Wallets with more UTXOs cannot spend their full balance in a single transaction. +- **Dust limit**: Outputs below the dust threshold (294 sats for SegWit, 546 sats for legacy) cannot be created + +**Returns:** `Promise` - Maximum spendable result +- `amount`: Maximum spendable amount in satoshis (bigint) +- `fee`: Estimated network fee in satoshis (bigint) +- `changeValue`: Estimated change value in satoshis (bigint) + +**Example:** +```javascript +const { amount, fee } = await account.getMaxSpendable() +console.log('Max spendable:', amount, 'satoshis') +console.log('Estimated fee:', fee, 'satoshis') +``` + +##### `sign(message)` +Signs a message using the account's private key. + +**Parameters:** +- `message` (string): Message to sign + +**Returns:** `Promise` - Signature as base64 string + +**Example:** +```javascript +const signature = await account.sign('Hello Bitcoin!') +console.log('Signature:', signature) +``` + +##### `verify(message, signature)` +Verifies a message signature using the account's public key. + +**Parameters:** +- `message` (string): Original message +- `signature` (string): Signature as base64 string + +**Returns:** `Promise` - True if signature is valid + +**Example:** +```javascript +const isValid = await account.verify('Hello Bitcoin!', signature) +console.log('Signature valid:', isValid) +``` + +##### `toReadOnlyAccount()` +Creates a read-only version of this account that can query balances and transactions but cannot sign or send transactions. + +**Returns:** `Promise` - Read-only account instance + +**Example:** +```javascript +const readOnlyAccount = await account.toReadOnlyAccount() +const balance = await readOnlyAccount.getBalance() +``` + +##### `dispose()` +Disposes the wallet account, securely erasing the private key from memory and closing the Electrum connection. + +**Returns:** `void` + +**Example:** +```javascript +account.dispose() +// Private key is now securely wiped from memory +``` + +#### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `index` | `number` | The derivation path's index of this account | +| `path` | `string` | The full derivation path of this account | +| `keyPair` | `KeyPair` | The account's public and private key pair. Treat the returned `Uint8Array` values as read-only views because mutations affect the account's internal key material. | + +## WalletAccountReadOnlyBtc + +Represents a read-only Bitcoin wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. + +#### Constructor + +```javascript +new WalletAccountReadOnlyBtc(address, config) +``` + +**Parameters:** +- `address` (string): The account's Bitcoin address +- `config` (object, optional): Configuration object (same as BtcWalletConfig but without `bip` and `transactionMaxFee`) + - `client` (`IBtcClient | BtcClientDescriptor | Array`, optional): Bitcoin client, descriptor, or ordered failover list + - `network` (string, optional): "bitcoin", "testnet", or "regtest" (default: "bitcoin") + - `retries` (number, optional): Additional retry attempts when `client` is an array + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `getAddress()` | Returns the account's Bitcoin address | `Promise` | +| `getBalance()` | Returns the total account balance in satoshis, including unconfirmed funds when present | `Promise` | +| `quoteSendTransaction(options)` | Estimates the fee for a transaction | `Promise<{fee: bigint}>` | +| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | +| `getMaxSpendable()` | Returns the maximum spendable amount | `Promise` | +| `verify(message, signature)` | Verifies a message signature | `Promise` | +| `dispose()` | Closes any internal Electrum connection | `void` | + +##### `getAddress()` +Returns the account's Bitcoin address. + +**Returns:** `Promise` - The Bitcoin address + +**Example:** +```javascript +const address = await readOnlyAccount.getAddress() +console.log('Address:', address) +``` + +##### `getBalance()` +Returns the account's confirmed balance in satoshis. + +**Returns:** `Promise` - Balance in satoshis + +**Example:** +```javascript +const balance = await readOnlyAccount.getBalance() +console.log('Balance:', balance, 'satoshis') +``` + +##### `quoteSendTransaction(options)` +Estimates the fee for a transaction without broadcasting it. + +**Parameters:** +- `options` (BtcTransaction): Transaction options + - `to` (string): Recipient's Bitcoin address + - `value` (number | bigint): Amount in satoshis + - `feeRate` (number | bigint, optional): Fee rate in sat/vB + - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) + +**Returns:** `Promise<{fee: bigint}>` - Estimated fee in satoshis + +**Example:** +```javascript +const quote = await readOnlyAccount.quoteSendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 50000n +}) +console.log('Estimated fee:', quote.fee, 'satoshis') +``` + +##### `getTransactionReceipt(hash)` +Returns a transaction's receipt if it has been included in a block. + +**Parameters:** +- `hash` (string): The transaction hash + +**Returns:** `Promise` - The receipt, or null if not yet included + +**Example:** +```javascript +const receipt = await readOnlyAccount.getTransactionReceipt('abc123...') +if (receipt) { + console.log('Transaction confirmed') +} +``` + +##### `getMaxSpendable()` +Returns the maximum spendable amount that can be sent in a single transaction. + +**Returns:** `Promise` - Maximum spendable result +- `amount`: Maximum spendable amount in satoshis (bigint) +- `fee`: Estimated network fee in satoshis (bigint) +- `changeValue`: Estimated change value in satoshis (bigint) + +**Example:** +```javascript +const { amount, fee } = await readOnlyAccount.getMaxSpendable() +console.log('Max spendable:', amount, 'satoshis') +``` + +##### `verify(message, signature)` +Verifies a message signature using the account's public key. + +**Parameters:** +- `message` (string): Original message +- `signature` (string): Signature as base64 string + +**Returns:** `Promise` - True if signature is valid + +**Example:** +```javascript +const isValid = await readOnlyAccount.verify('Hello Bitcoin!', signature) +console.log('Signature valid:', isValid) +``` + +##### `dispose()` +Closes any internal Electrum connection owned by this account. If a [`client`](/sdk/wallet-modules/wallet-btc/configuration#client) was provided via config, the connection is left open (the caller manages its lifecycle). + +**Returns:** `void` + +**Example:** +```javascript +readOnlyAccount.dispose() +``` + +## ElectrumTcp + +Electrum client using TCP transport. Standard for command-line and server-side environments. +Implements `IBtcClient`. + +#### Constructor + +```javascript +new ElectrumTcp(config) +``` + +**Parameters:** +- `config` (`Omit`): Configuration options + - `host` (string): Electrum server hostname + - `port` (number): Electrum server port + +## ElectrumTls + +Electrum client using TLS transport. +Implements `IBtcClient`. + +#### Constructor + +```javascript +new ElectrumTls(config) +``` + +**Parameters:** +- `config` (`Omit`): Configuration options + - `host` (string): Electrum server hostname + - `port` (number): Electrum server port + +## ElectrumSsl + +Electrum client using SSL transport. +Implements `IBtcClient`. + +#### Constructor + +```javascript +new ElectrumSsl(config) +``` + +**Parameters:** +- `config` (`Omit`): Configuration options + - `host` (string): Electrum server hostname + - `port` (number): Electrum server port + +## ElectrumWs + +Electrum client using WebSocket transport. Compatible with browser environments where TCP sockets are not available. +Implements `IBtcClient`. + +#### Constructor + +```javascript +new ElectrumWs(config) +``` + +**Parameters:** +- `config` (ElectrumWsConfig): Configuration options + - `url` (string): The WebSocket URL (e.g., 'wss://electrum.example.com:50004') + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `connect()` | Establishes connection to Electrum server | `Promise` | +| `close()` | Closes the connection | `Promise` | +| `reconnect()` | Recreates the underlying socket and reinitializes the session | `Promise` | +| `getBalance(scripthash)` | Returns balance for a script hash | `Promise` | +| `listUnspent(scripthash)` | Returns UTXOs for a script hash | `Promise` | +| `getHistory(scripthash)` | Returns transaction history | `Promise` | +| `getTransaction(txHash)` | Returns raw transaction hex | `Promise` | +| `broadcast(rawTx)` | Broadcasts raw transaction | `Promise` | +| `estimateFee(blocks)` | Returns estimated fee rate | `Promise` | + +## Types + +### BtcWalletConfig + +```typescript +interface BtcWalletConfig { + client?: IBtcClient | BtcClientDescriptor | Array + network?: 'bitcoin' | 'testnet' | 'regtest' + bip?: 44 | 84 + retries?: number + transactionMaxFee?: number | bigint // Maximum send/sign fee in satoshis +} +``` + +### BtcTransaction + +```typescript +interface BtcTransaction { + to: string // The transaction's recipient + value: number | bigint // The amount of bitcoins to send to the recipient (in satoshis) + confirmationTarget?: number // Optional confirmation target in blocks (default: 1) + feeRate?: number | bigint // Optional fee rate in satoshis per virtual byte +} +``` + +### TransactionResult + +```typescript +interface TransactionResult { + hash: string // Transaction hash/ID + fee: bigint // Transaction fee in satoshis +} +``` + +### FeeRates + +```typescript +interface FeeRates { + normal: bigint // Standard fee rate (sat/vB) for ~1 hour confirmation + fast: bigint // Higher fee rate (sat/vB) for faster confirmation +} +``` + +### BtcTransfer + +```typescript +interface BtcTransfer { + txid: string // The transaction's ID + address: string // The user's own address + vout: number // The index of the output in the transaction + height: number // The block height (if unconfirmed, 0) + value: bigint // The value of the transfer (in satoshis) + direction: 'incoming' | 'outgoing' // The direction of the transfer + fee?: bigint // The fee paid for the full transaction (in satoshis) + recipient?: string // The receiving address for outgoing transfers +} +``` + +### BtcMaxSpendableResult + +```typescript +interface BtcMaxSpendableResult { + amount: bigint // The maximum spendable amount in satoshis + fee: bigint // The estimated network fee in satoshis + changeValue: bigint // The estimated change value in satoshis +} +``` + +### KeyPair + +```typescript +interface KeyPair { + publicKey: Uint8Array // Public key bytes. Treat as read-only. + privateKey: Uint8Array | null // Private key bytes. Treat as read-only. Null after dispose. +} +``` + +### BtcWalletConfig + +```typescript +interface BtcWalletConfig { + client?: IBtcClient | BtcClientDescriptor | Array // Client, descriptor, or failover list + network?: 'bitcoin' | 'testnet' | 'regtest' // Network to use (default: "bitcoin") + bip?: 44 | 84 // BIP address type - 44 (legacy) or 84 (native SegWit) (default: 84) + retries?: number // Additional retry attempts for client arrays +} +``` + +### BtcClientDescriptor + +```typescript +type BtcClientDescriptor = + | { type: 'electrum'; clientConfig: MempoolElectrumConfig } + | { type: 'electrum-ws'; clientConfig: ElectrumWsConfig } + | { type: 'blockbook-http'; clientConfig: BlockbookClientConfig } +``` + +### IBtcClient + +Interface for implementing custom Bitcoin network clients. +```typescript +interface IBtcClient { + connect(): Promise + close(): Promise + reconnect(): Promise + getBalance(scripthash: string): Promise + listUnspent(scripthash: string): Promise + getHistory(scripthash: string): Promise + getTransaction(txHash: string): Promise + broadcast(rawTx: string): Promise + estimateFee(blocks: number): Promise +} +``` + +### ElectrumBalance +```typescript +interface ElectrumBalance { + confirmed: number // Confirmed balance in satoshis + unconfirmed?: number // Unconfirmed balance in satoshis +} +``` + +### ElectrumUtxo +```typescript +interface ElectrumUtxo { + tx_hash: string // The transaction hash containing this UTXO + tx_pos: number // The output index within the transaction + value: number // The UTXO value in satoshis + height?: number // The block height (0 if unconfirmed) +} +``` + +### ElectrumHistoryItem +```typescript +interface ElectrumHistoryItem { + tx_hash: string // The transaction hash + height: number // The block height (0 or negative if unconfirmed) +} +``` + +### MempoolElectrumConfig + +```typescript +interface MempoolElectrumConfig { + host: string // Electrum server hostname + port: number // Electrum server port + protocol?: 'tcp' | 'ssl' | 'tls' // Transport protocol (default: 'tcp') + maxRetry?: number // Maximum reconnection attempts (default: 2) + retryPeriod?: number // Delay between reconnection attempts in ms (default: 1000) + pingPeriod?: number // Delay between keep-alive pings in ms (default: 120000) + callback?: (err: Error | null) => void // Called when all retries are exhausted +} +``` + + + +Get started with WDK in a Node.js environment + + +Build mobile wallets with React Native Expo + + +Get started with WDK's Bitcoin Wallet Usage + + +Get started with WDK's Bitcoin Wallet Configuration + + + +*** + +### Need Help? + + + +*** + +## Configuration +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/configuration +Description: Configuration options and settings for @tetherto/wdk-wallet-btc + +## Wallet Configuration + +```javascript +import WalletManagerBtc from '@tetherto/wdk-wallet-btc' + +const wallet = new WalletManagerBtc(seedPhrase, { + client: { + type: 'electrum', + clientConfig: { + host: 'electrum.blockstream.info', + port: 50002, + protocol: 'tls' + } + }, + network: 'bitcoin', + transactionMaxFee: 10000n // Optional: maximum send/sign fee in satoshis +}) +``` + +## Account Creation + +```javascript +// WalletAccountBtc is created by the WalletManagerBtc +// It takes the same configuration as the manager +const account = await wallet.getAccount(0) // Get account at index 0 +const customAccount = await wallet.getAccountByPath("0'/0/5") // Custom path +``` + +## Configuration Options + +### Client + +The `client` option specifies how the wallet connects to Bitcoin network data. It accepts a pre-built `IBtcClient`, a client descriptor, or an ordered array of clients and descriptors for automatic failover. + +**Type:** `IBtcClient | BtcClientDescriptor | Array` + +**Default:** Uses an Electrum descriptor for `electrum.blockstream.info:50001`. + +**Example:** +```javascript +const config = { + client: { + type: 'electrum', + clientConfig: { + host: 'fulcrum.frznode.com', + port: 50002, + protocol: 'tls' + } + } +} +``` + +`BtcClientDescriptor` supports: + +| Type | Description | +|------|-------------| +| `electrum` | Creates a TCP, TLS, or SSL Electrum client from `clientConfig`. | +| `electrum-ws` | Creates a WebSocket Electrum client from `clientConfig`. | +| `blockbook-http` | Creates a stateless Blockbook HTTP client from `clientConfig`. | + +#### Built-in Transport Clients + +The package still exports built-in transport clients when you want to instantiate the client yourself: + +```javascript +import { + ElectrumTcp, // TCP transport (default, port 50001) + ElectrumTls, // TLS transport (port 50002) + ElectrumSsl, // SSL transport (port 50002) + ElectrumWs // WebSocket transport +} from '@tetherto/wdk-wallet-btc' + +// TCP (default) +const tcpClient = new ElectrumTcp({ host: 'electrum.blockstream.info', port: 50001 }) + +// TLS +const tlsClient = new ElectrumTls({ host: 'electrum.blockstream.info', port: 50002 }) + +// SSL +const sslClient = new ElectrumSsl({ host: 'electrum.blockstream.info', port: 50002 }) + +// WebSocket +const wsClient = new ElectrumWs({ url: 'wss://electrum.example.com:50004' }) +``` + +#### Custom Bitcoin Client + +You can implement your own client by implementing `IBtcClient`: + +```typescript +import { IBtcClient } from '@tetherto/wdk-wallet-btc' + +class MyCustomBitcoinClient implements IBtcClient { + // Implement the required interface methods +} + +const wallet = new WalletManagerBtc(seedPhrase, { + client: new MyCustomBitcoinClient(params), + network: 'bitcoin' +}) +``` + +#### Client Failover + +Pass an ordered `client` array to retry connection failures against fallback clients. Set `retries` to control how many additional attempts can happen after the first failed call. + +```javascript +const wallet = new WalletManagerBtc(seedPhrase, { + client: [ + { + type: 'electrum', + clientConfig: { + host: 'primary-electrum.example', + port: 50002, + protocol: 'tls' + } + }, + { + type: 'electrum', + clientConfig: { + host: 'secondary-electrum.example', + port: 50002, + protocol: 'tls' + } + } + ], + retries: 1, + network: 'bitcoin' +}) +``` + +### Host + +The `host` value belongs inside an `electrum` descriptor's `clientConfig` or an `Electrum*` client constructor. It is not a top-level wallet config option. + +**Type:** `string` + +**Default:** `"electrum.blockstream.info"` + +**Recommended:** Configure your own Electrum server for production use. Public servers can be 10-300x slower and may fail for addresses with many transactions. + +**Example:** +```javascript +const config = { + client: { + type: 'electrum', + clientConfig: { + host: 'fulcrum.frznode.com', + port: 50002, + protocol: 'tls' + } + } +} +``` + +### Port + +The `port` value belongs inside an `electrum` descriptor's `clientConfig` or an `Electrum*` client constructor. It is not a top-level wallet config option. + +**Type:** `number` + +**Default:** `50001` + +**Common Ports:** +- `50001` - TCP (default) +- `50002` - TLS/SSL +- `50003` - WebSocket + +**Example:** +```javascript +const config = { + client: { + type: 'electrum', + clientConfig: { + host: 'electrum.blockstream.info', + port: 50002, + protocol: 'tls' + } + } +} +``` + +### Protocol + +The `protocol` value belongs inside an `electrum` descriptor's `clientConfig`. It is not a top-level wallet config option. + +**Type:** `string` + +**Values:** +- `"tcp"` - TCP transport (default) +- `"tls"` - TLS transport +- `"ssl"` - SSL transport + +**Default:** `"tcp"` + +**Example:** +```javascript +const config = { + client: { + type: 'electrum', + clientConfig: { + host: 'electrum.blockstream.info', + port: 50002, + protocol: 'tls' + } + } +} +``` + +### Retries + +The `retries` option controls failover retry attempts when `client` is an array. + +**Type:** `number` (optional) + +**Example:** +```javascript +const config = { + client: [ + { type: 'electrum', clientConfig: { host: 'primary.example', port: 50002, protocol: 'tls' } }, + { type: 'electrum', clientConfig: { host: 'secondary.example', port: 50002, protocol: 'tls' } } + ], + retries: 2 +} +``` + +### Transaction Max Fee + +The `transactionMaxFee` option sets the maximum fee, in satoshis, for BTC `sendTransaction()` and `signTransaction()` operations. It blocks building or signing a transaction above the cap and also blocks `sendTransaction(signedHex)` from broadcasting externally supplied signed hex when its calculated fee is above the cap. + +**Type:** `number | bigint` (optional) + +**Unit:** Satoshis + +**Example:** +```javascript +const wallet = new WalletManagerBtc(seedPhrase, { + network: 'bitcoin', + transactionMaxFee: 10000n // 10,000 satoshis +}) +``` + +Use [`quoteSendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#quotesendtransactionoptions) when you want to show the estimated fee before calling `sendTransaction()`. + +### Network + +The `network` option specifies which Bitcoin network to use. + +**Type:** `string` + +**Values:** +- `"bitcoin"` - Bitcoin [mainnet](/resources/concepts#mainnet) (production) +- `"testnet"` - Bitcoin [testnet](/resources/concepts#testnet) (development) +- `"regtest"` - Bitcoin [regtest](/resources/concepts#regtest) (local testing) + +**Default:** `"bitcoin"` + +**Example:** +```javascript +const config = { + network: 'testnet' // Use testnet for development +} +``` + +### BIP + +The `bip` option specifies the address type derivation standard to use. + +**Type:** `number` + +**Values:** +- `84` - [BIP-84](/resources/concepts#bip-84-native-segwit) (P2WPKH / Native SegWit) - addresses start with `bc1` (mainnet) or `tb1` (testnet) +- `44` - [BIP-44](/resources/concepts#bip-44-multi-account-hierarchy) (P2PKH / Legacy) - addresses start with `1` (mainnet) or `m`/`n` (testnet) + +**Default:** `84` + +**Example:** +```javascript +// Use legacy addresses +const config = { + bip: 44 +} +``` + +## Electrum Server Configuration + +**Important**: While the package defaults to `electrum.blockstream.info:50001` for convenience, **we strongly recommend configuring your own Electrum server** for production use. + +### Recommended Approach + +**For Production:** +- Set up your own Fulcrum server for optimal performance and reliability +- Use recent Fulcrum versions that support pagination for high-transaction addresses + +**For Development/Testing:** +- `fulcrum.frznode.com:50001` - Generally faster than default +- `electrum.blockstream.info:50001` - Default fallback + +### Configuration Examples + +```javascript +import { ElectrumTcp, ElectrumTls } from '@tetherto/wdk-wallet-btc' + +// Production with custom Fulcrum server +const productionClient = new ElectrumTls({ + host: 'your-fulcrum-server.com', + port: 50002 +}) + +const productionWallet = new WalletManagerBtc(seedPhrase, { + client: productionClient, + network: 'bitcoin' +}) + +// Development with alternative public server +const developmentClient = new ElectrumTcp({ + host: 'fulcrum.frznode.com', + port: 50001 }) -// Create swap service with configuration -const swapProtocol = new VeloraProtocolEvm(account, { - swapMaxFee: 200000000000000n // Optional: Max swap fee in wei +const developmentWallet = new WalletManagerBtc(seedPhrase, { + client: developmentClient, + network: 'bitcoin' }) ``` -## Account Configuration +### Network-Specific Configuration -The swap service uses the wallet account configuration for network access and signing: +#### Bitcoin Mainnet ```javascript -import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' - -// Full access account -const account = new WalletAccountEvm( - seedPhrase, - "0'/0/0", - { - provider: 'https://ethereum-rpc.publicnode.com' - } -) +import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' -// Read-only account (quotes only) -const readOnly = new WalletAccountReadOnlyEvm( - '0xYourAddress', - { - provider: 'https://ethereum-rpc.publicnode.com' - } -) +const client = new ElectrumTcp({ + host: 'electrum.blockstream.info', // Or your own server + port: 50001 +}) -// Create swap service -const swapProtocol = new VeloraProtocolEvm(account, { - swapMaxFee: 200000000000000n +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'bitcoin' }) ``` -## Configuration Options +#### Bitcoin Testnet -### Swap Max Fee +```javascript +import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' -The `swapMaxFee` option sets an upper bound for total gas costs to prevent excessive fees. +const client = new ElectrumTcp({ + host: 'testnet.hsmiths.com', // Example testnet server + port: 53011 +}) -**Type:** `bigint` (optional) -**Unit:** Wei +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'testnet' +}) +``` -**Examples:** +#### Bitcoin Regtest ```javascript -const config = { - // Cap total gas fee to 0.0002 ETH (in wei) - swapMaxFee: 200000000000000n, -} +import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' -// Usage example -try { - const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USD₮ (6 decimals) - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH (18 decimals) - tokenInAmount: 1000000n - }) -} catch (error) { - if (error.message.includes('max fee')) { - console.error('Swap stopped: Fee too high') - } -} +const client = new ElectrumTcp({ + host: 'localhost', // Local regtest node + port: 50001 +}) + +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'regtest' +}) ``` -## ERC‑4337 (Account Abstraction) Configuration +## Derivation Paths -When using ERC‑4337 smart accounts (`@tetherto/wdk-wallet-evm-erc-4337`), you can override fee behavior per swap and specify a paymaster token: +Bitcoin wallet addresses are derived using BIP-32 hierarchical deterministic paths: -```javascript -import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' +### BIP-84 (Native SegWit) - Default -const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { - chainId: 1, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: 'YOUR_BUNDLER_URL', - paymasterUrl: 'YOUR_PAYMASTER_URL' -}) +- `m/84'/0'/0'/0/0` for mainnet account 0, address 0 +- `m/84'/1'/0'/0/0` for testnet/regtest account 0, address 0 -const swapAA = new VeloraProtocolEvm(aa, { swapMaxFee: 200000000000000n }) +Addresses start with `bc1` (mainnet) or `tb1` (testnet). -const result = await swapAA.swap({ - tokenIn: '0xTokenIn', - tokenOut: '0xTokenOut', - tokenInAmount: 1000000n -}, { - paymasterToken: 'USDT', // Token used to pay for gas - swapMaxFee: 200000000000000n // Per‑swap override -}) -``` +### BIP-44 (Legacy) -### Paymaster Token (ERC‑4337) +- `m/44'/0'/0'/0/0` for mainnet account 0, address 0 +- `m/44'/1'/0'/0/0` for testnet/regtest account 0, address 0 -The `paymasterToken` option indicates which token the paymaster should use to sponsor gas. +Addresses start with `1` (mainnet) or `m`/`n` (testnet). -**Type:** `string` (optional) -**Format:** Token symbol or address + +**Default Derivation Path Change in v1.0.0-beta.4+** -**Example:** +The default derivation path was updated in v1.0.0-beta.4 to use BIP-84 (Native SegWit) instead of BIP-44 (Legacy): -```javascript -const result = await swapAA.swap({ - tokenIn: '0xdAC17F...ec7', - tokenOut: '0xC02a...6Cc2', // WETH - tokenInAmount: 1000000n -}, { - paymasterToken: 'USDT' -}) -``` +- **Previous path** (up to v1.0.0-beta.3): `m/44'/0'/0'/0/{index}` (Legacy addresses) +- **Current path** (v1.0.0-beta.4+): `m/84'/0'/0'/0/{index}` (Native SegWit addresses) -## Network Support +If you're upgrading from an earlier version, existing wallets created with the old path will generate different addresses. Make sure to migrate any existing wallets or use the old path explicitly if needed for compatibility. -velora supports multiple EVM networks (e.g., Ethereum, Polygon, Arbitrum). Ensure your account is configured with a valid provider for the target network. +Use [`getAccountByPath`](/sdk/wallet-modules/wallet-btc/api-reference#getaccountbypathpath) to supply an explicit derivation path when importing or recreating legacy wallets. + + +## Complete Configuration Example ```javascript -// Ethereum Mainnet -const eth = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +import WalletManagerBtc, { ElectrumTls } from '@tetherto/wdk-wallet-btc' -// Polygon -const polygon = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://polygon-bor-rpc.publicnode.com' +// Create Electrum client +const client = new ElectrumTls({ + host: 'your-electrum-server.com', // Replace with your server + port: 50002 }) -``` - -## Swap Options -When calling `swap`, provide the swap parameters: +// Create wallet manager with configuration +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'bitcoin', + bip: 84 // Native SegWit (default) +}) -```javascript -const swapOptions = { - tokenIn: '0xTokenIn', // ERC‑20 to sell - tokenOut: '0xTokenOut', // ERC‑20 to buy - tokenInAmount: 1000000n, // exact input (base units) - // OR - // tokenOutAmount: 1000000n, // exact output (base units) - to: '0xRecipient' // optional recipient (defaults to your address) -} +// Get accounts (inherit configuration from manager) +const account0 = await wallet.getAccount(0) +const account1 = await wallet.getAccount(1) +const customAccount = await wallet.getAccountByPath("0'/0/5") -const result = await swapProtocol.swap(swapOptions) +// Clean up when done +wallet.dispose() ``` -### Parameters +## Performance Considerations -- `tokenIn` (`string`): ERC‑20 address to sell -- `tokenOut` (`string`): ERC‑20 address to buy -- `tokenInAmount` (`bigint`, optional): exact input amount in token base units -- `tokenOutAmount` (`bigint`, optional): exact output amount in token base units -- `to` (`string`, optional): recipient address (defaults to account address) +**Electrum Server Performance:** +- Public servers like Blockstream's can be significantly slower +- Addresses with many transactions may cause timeouts +- Custom Fulcrum servers provide better performance and reliability +- Consider server location and network latency -> Note: Use either `tokenInAmount` OR `tokenOutAmount`, not both. +**Configuration Tips:** +- Use `fulcrum.frznode.com` for better development performance +- Set up your own Fulcrum server for production +- Monitor connection stability and implement retry logic +- Consider using multiple backup servers Get started with WDK in a Node.js environment - -Get started with WDK's velora Swap Protocol API + +Build mobile wallets with React Native Expo - -Get started with WDK's velora Swap Protocol usage + +Get started with WDK's BTC Wallet Usage + + +Get started with WDK's BTC Wallet API *** -## Need Help? +### Need Help? *** -## Execute Swaps -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/execute-swaps -Description: Run exact-input swaps, exact-output swaps, and swaps with ERC-4337 accounts. - -This guide explains how to run a [basic exact-input swap](#basic-exact-input-swap), an [exact-output swap](#exact-output-swap), and a [swap from an ERC-4337 smart account](#swap-with-erc-4337). You should already have a [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) instance. - - -Swaps spend tokens and gas on-chain. Use amounts you control and an RPC you trust. - +## Check Balances +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/check-balances +Description: Query native BTC balances for owned and read-only accounts. -## Basic exact-input swap +This guide explains how to check [native BTC balances](#native-btc-balance), [maximum spendable amounts](#maximum-spendable-amount), and [read-only account balances](#read-only-account-balances). -You can sell an exact amount of the input token using [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +## Native BTC Balance -```javascript title="Exact input: USDT to WETH" -const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}) +You can retrieve the confirmed balance in satoshis using [`account.getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference): -console.log('Swap transaction hash:', result.hash) -console.log('Total fee (wei):', result.fee) -console.log('Tokens sold (base units):', result.tokenInAmount) -console.log('Tokens bought (base units):', result.tokenOutAmount) +```javascript title="Get Native BTC Balance" +const balance = await account.getBalance() +console.log('Total balance:', balance, 'satoshis') ``` -## Exact output swap + +On Bitcoin, balances are expressed in satoshis (1 BTC = 100,000,000 satoshis). The [`getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference) method returns the total balance, including unconfirmed funds when present. + -You can receive an exact amount of the output token by passing `tokenOutAmount` to [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +## Maximum Spendable Amount -```javascript title="Exact output amount" -const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenOutAmount: 500000000000000000n -}) +You can check the maximum amount available to send in a single transaction using [`account.getMaxSpendable()`](/sdk/wallet-modules/wallet-btc/api-reference): -console.log('Swap hash:', result.hash) -console.log('Tokens sold (base units):', result.tokenInAmount) -console.log('Tokens bought (base units):', result.tokenOutAmount) +```javascript title="Get Maximum Spendable" +const { amount, fee } = await account.getMaxSpendable() +console.log('Max spendable:', amount, 'satoshis') +console.log('Estimated fee:', fee, 'satoshis') ``` -## Swap with ERC-4337 + +The maximum spendable amount can differ from the total balance due to transaction fees, uneconomic UTXOs, the 200-input limit per transaction, and the dust threshold (294 satoshis for SegWit, 546 for legacy). + -You can perform a user-operation-backed swap by constructing [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) with [`WalletAccountEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and passing paymaster options to [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +## Read-Only Account Balances -```javascript title="Swap with smart account and paymaster" -import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' -import VeloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +You can check balances for any Bitcoin address without a seed phrase using [`WalletAccountReadOnlyBtc`](/sdk/wallet-modules/wallet-btc/api-reference): -const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { - chainId: 42161, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: process.env.BUNDLER_URL, - paymasterUrl: process.env.PAYMASTER_URL -}) +```javascript title="Create Read-Only Account" +import { WalletAccountReadOnlyBtc, ElectrumTcp } from '@tetherto/wdk-wallet-btc' -const swapAA = new VeloraProtocolEvm(aa, { swapMaxFee: 200000000000000n }) +const client = new ElectrumTcp({ + host: 'electrum.blockstream.info', + port: 50001 +}) -const result = await swapAA.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}, { - paymasterToken: 'USDT', - swapMaxFee: 200000000000000n +const readOnlyAccount = new WalletAccountReadOnlyBtc('bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', { + client, + network: 'bitcoin' }) +``` -console.log('Swap hash:', result.hash) -console.log('Total fee (wei):', result.fee) +You can retrieve the balance from a read-only account using [`readOnlyAccount.getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference): + +```javascript title="Read-Only Balance" +const balance = await readOnlyAccount.getBalance() +console.log('Read-only account balance:', balance, 'satoshis') ``` -Token addresses must match the chain your account uses (for example, mainnet USD₮ addresses differ from Arbitrum). +Read-only accounts follow the same balance behavior as owned accounts: [`getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference) includes unconfirmed funds when present. + + + +You can also create a read-only account from an existing owned account using [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-btc/api-reference). ## Next Steps -- [Get swap quotes](/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes) before sending -- [Handle errors](/sdk/swap-modules/swap-velora-evm/guides/handle-errors) -- [Get started](/sdk/swap-modules/swap-velora-evm/guides/get-started) if you still need setup +With balance checks in place, learn how to [send BTC](/sdk/wallet-modules/wallet-btc/guides/send-transactions). *** ## Get Started -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/get-started -Description: Install the package, create VeloraProtocolEvm, and learn supported networks. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/get-started +Description: Install and create your first Bitcoin wallet. -This guide covers [installation](#installation), [create the swap protocol](#create-the-swap-protocol), and [supported networks](#supported-networks). You need [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) to follow along. +This guide explains how to [install the package](#1-install-the-package), [create a wallet](#2-create-a-wallet), [get your first account](#3-get-your-first-account), and optionally [convert to read-only](#4-optional-convert-to-read-only). -## Installation +## 1. Install the Package -Run the following to install [@tetherto/wdk-protocol-swap-velora-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-swap-velora-evm): +### Prerequisites -```bash title="Install with npm" -npm install @tetherto/wdk-protocol-swap-velora-evm +* **[Node.js](https://nodejs.org/)**: version 20.19.0 or higher. Since Wallet BTC beta.11, `@bitcoinerlab/descriptors` 3.1.7 sets this minimum; the requirement remains unchanged in beta.12. +* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. + +```bash title="Install @tetherto/wdk-wallet-btc" +npm install @tetherto/wdk-wallet-btc ``` - -You also need an EVM wallet account from [`@tetherto/wdk-wallet-evm`](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm) (or an ERC-4337 account from [`@tetherto/wdk-wallet-evm-erc-4337`](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm-erc-4337)) on the same chain as your RPC provider. - +## 2. Create a Wallet -## Create the swap protocol +You can create a new wallet instance using the [`WalletManagerBtc`](/sdk/wallet-modules/wallet-btc/api-reference) constructor with a BIP-39 seed phrase and an Electrum client: -You can construct a swap client with [`new VeloraProtocolEvm(account, config?)`](/sdk/swap-modules/swap-velora-evm/api-reference) on top of [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference): +```javascript title="Create Bitcoin Wallet" +import WalletManagerBtc, { ElectrumTcp } from '@tetherto/wdk-wallet-btc' -```javascript title="Create VeloraProtocolEvm" -import VeloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' +const client = new ElectrumTcp({ + host: 'electrum.blockstream.info', + port: 50001 }) -const swapProtocol = new VeloraProtocolEvm(account, { - swapMaxFee: 200000000000000n +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'bitcoin' }) ``` -Optional `swapMaxFee` caps the total gas fee in wei for swaps. See [configuration](/sdk/swap-modules/swap-velora-evm/configuration) for environment-specific settings. + +**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. + -## Supported networks + +**Electrum Server Performance:** Public servers like Blockstream's can be 10-300x slower than private servers. For production use, set up your own [Fulcrum](https://github.com/cculianu/Fulcrum) server. For development, consider `fulcrum.frznode.com` as a faster alternative. + -Velora routing works on EVM networks the aggregator supports, including **Ethereum**, **Polygon**, **Arbitrum**, and other chains where Velora exposes liquidity. Use an RPC endpoint for the network your [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference) is configured for so [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) and [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) target the correct chain. +## 3. Get Your First Account + +You can retrieve an account at a given index using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-btc/api-reference): + +```javascript title="Get Account" +const account = await wallet.getAccount(0) +const address = await account.getAddress() +console.log('Wallet address:', address) +``` + + +This implementation uses BIP-84 derivation paths and generates Native SegWit (bech32) addresses by default. Addresses start with `bc1` on mainnet. Set `bip: 44` in config for legacy (P2PKH) addresses. + + +## 4. (optional) Convert to Read-Only + +You can convert an owned account to a read-only account using [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-btc/api-reference): + +```javascript title="Convert to Read-Only" +const readOnlyAccount = await account.toReadOnlyAccount() +``` ## Next Steps -- [Execute swaps](/sdk/swap-modules/swap-velora-evm/guides/execute-swaps) -- [Get swap quotes](/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes) -- [Handle errors](/sdk/swap-modules/swap-velora-evm/guides/handle-errors) +With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-btc/guides/manage-accounts). *** -## Get Swap Quotes -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes -Description: Estimate fees and amounts with quoteSwap before executing a swap. +## Transaction History +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/get-transaction-history +Description: Retrieve and filter Bitcoin transfer history. -This guide shows how to [quote before swapping](#quote-before-swapping) and use quotes for [fee estimation](#fee-estimation). Quotes use [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference), which works with read-only accounts as well as signing accounts. +This guide explains how to [retrieve all transfers](#retrieve-all-transfers), [filter by direction](#filter-by-direction), [paginate results](#paginate-results), and [check transaction receipts](#check-transaction-receipts). -## Quote before swapping +## Retrieve All Transfers -You can preview fee and token amounts for the same parameters you would pass to [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) using [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +You can retrieve the account's transfer history using [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): -```javascript title="Quote exact input swap" -const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}) +```javascript title="Get All Transfers" +const transfers = await account.getTransfers() +console.log('Recent transfers:', transfers) +``` -console.log('Estimated fee (wei):', quote.fee) -console.log('Tokens in (base units):', quote.tokenInAmount) -console.log('Tokens out (base units):', quote.tokenOutAmount) + +The default limit is 10 transfers. Change outputs are automatically filtered out. Transfers are sorted by block height (newest first). + + +## Filter by Direction + +You can filter transfers by direction using the `direction` option in [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): + +```javascript title="Incoming Transfers" +const incoming = await account.getTransfers({ direction: 'incoming' }) +console.log('Incoming transfers:', incoming) ``` -You can quote an exact-output style trade the same way by passing `tokenOutAmount` instead of `tokenInAmount` to [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +You can retrieve outgoing transfers with a custom limit using [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): -```javascript title="Quote exact output swap" -const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenOutAmount: 500000000000000000n +```javascript title="Outgoing Transfers" +const outgoing = await account.getTransfers({ + direction: 'outgoing', + limit: 5 }) - -console.log('Estimated fee (wei):', quote.fee) -console.log('Required token in (base units):', quote.tokenInAmount) +console.log('Outgoing transfers:', outgoing) ``` -## Fee estimation +## Paginate Results -You can read `quote.fee` from [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) as the estimated total swap fee in wei before calling [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +You can paginate through transfer history using the `limit` and `skip` options in [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): -```javascript title="Quote fee before deciding" -const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n +```javascript title="Paginate Transfers" +const page = await account.getTransfers({ + direction: 'all', + limit: 20, + skip: 10 }) - -const maxFee = 200000000000000n -console.log('Quoted fee (wei):', quote.fee, 'cap:', maxFee) +console.log('Transfers 11-30:', page) ``` -You can compare that estimate to `swapMaxFee` on [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) and only then call [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) when the quote is within your cap: +## Check Transaction Receipts -```javascript title="Swap when fee is under cap" -const maxFee = 200000000000000n -const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}) +You can check whether a specific transaction has been confirmed using [`account.getTransactionReceipt()`](/sdk/wallet-modules/wallet-btc/api-reference): -if (quote.fee <= maxFee) { - const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n - }) - console.log('Swap hash:', result.hash) +```javascript title="Get Transaction Receipt" +const receipt = await account.getTransactionReceipt('abc123...') +if (receipt) { + console.log('Transaction confirmed') } ``` - -On-chain conditions can change between quote and execution. The executed [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) may still differ slightly from the last [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) result. - - ## Next Steps -- [Execute swaps](/sdk/swap-modules/swap-velora-evm/guides/execute-swaps) -- [Handle errors](/sdk/swap-modules/swap-velora-evm/guides/handle-errors) -- [Get started](/sdk/swap-modules/swap-velora-evm/guides/get-started) +Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-btc/guides/sign-verify-messages). *** ## Handle Errors -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/guides/handle-errors -Description: Catch swap failures, interpret common messages, and clean up sensitive state. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/handle-errors +Description: Handle errors, manage fees, and dispose of sensitive data. -This guide covers [swap errors](#swap-errors), [quote errors](#quote-errors), and [best practices](#best-practices) for clearing wallet material after use. +This guide explains how to [handle transaction errors](#transaction-errors), [handle connection errors](#connection-errors), and follow [best practices](#best-practices) for fee management and memory cleanup. -## Swap errors +## Transaction Errors -You can detect failed swaps by wrapping [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) in `try/catch` and inspecting `error.message`: +Transactions sent via [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference) can fail for several reasons. Wrap transaction calls in a `try/catch` block to handle specific error types: -```javascript title="Handle swap failures" +```javascript title="Handle Transaction Errors" try { - const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n + const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n }) - console.log('Swap successful:', result.hash) -} catch (error) { - console.error('Swap failed:', error.message) - - if (error.message.includes('liquidity')) { - console.log('No route or insufficient liquidity for this pair') - } - if (error.message.includes('max fee')) { - console.log('Swap fee exceeds swapMaxFee') - } - if (error.message.includes('read-only')) { - console.log('Read-only account cannot swap') + console.log('Transaction hash:', result.hash) +} catch (error) { + if (error.message.includes('Insufficient balance')) { + console.error('Not enough funds in wallet') + } else if (error.message.includes('Exceeded maximum fee')) { + console.error('Transaction fee exceeds transactionMaxFee') + } else if (error.message.includes('dust limit')) { + console.error('Amount is below the minimum dust limit') + } else if (error.message.includes('Invalid address')) { + console.error('Recipient address is invalid') + } else { + console.error('Transaction failed:', error.message) } } ``` - -Match string fragments only as a convenience; production apps should prefer stable error codes from your runtime when available. - - -## Quote errors +## Connection Errors -You can handle failures from [`quoteSwap()`](/sdk/swap-modules/swap-velora-evm/api-reference) the same way, including provider or routing errors: +Network issues with the Electrum server can cause failures across all operations. Handle connection errors at a higher level: -```javascript title="Handle quote failures" +```javascript title="Handle Connection Errors" try { - const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n - }) - console.log('Quoted fee (wei):', quote.fee) + const balance = await account.getBalance() + console.log('Balance:', balance, 'satoshis') } catch (error) { - console.error('Quote failed:', error.message) + if (error.message.includes('ECONNREFUSED') || error.message.includes('timeout')) { + console.error('Network error: check Electrum server connection') + } else if (error.message.includes('Invalid seed')) { + console.error('Invalid seed phrase provided') + } else { + console.error('Operation failed:', error.message) + } } ``` -Common causes are listed under [Errors](/sdk/swap-modules/swap-velora-evm/api-reference) in the API reference (liquidity, fee cap, read-only send attempts, RPC issues). - ## Best Practices -You can clear signing material when a session ends by calling [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on each [`WalletAccountEvm`](/sdk/wallet-modules/wallet-evm/api-reference), or [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference) if you use a manager: +### Fee Management -```javascript title="Dispose wallet accounts" +You can retrieve current network fee rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-btc/api-reference): + +```javascript title="Get Fee Rates" +const feeRates = await wallet.getFeeRates() +console.log('Normal fee rate:', feeRates.normal, 'sat/vB') +console.log('Fast fee rate:', feeRates.fast, 'sat/vB') +``` + +Set [`transactionMaxFee`](/sdk/wallet-modules/wallet-btc/configuration#transaction-max-fee) to stop `sendTransaction()` and `signTransaction()` when the estimated BTC network fee exceeds your limit. + + +[`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-btc/api-reference) fetches rates from the mempool.space API, while [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference) estimates fees from the connected Electrum server. Use [`getFeeRates()`](/sdk/wallet-modules/wallet-btc/api-reference) for display purposes. + + +### Dispose of Sensitive Data + +For security, clear sensitive data from memory when a session is complete. Use [`account.dispose()`](/sdk/wallet-modules/wallet-btc/api-reference) and [`wallet.dispose()`](/sdk/wallet-modules/wallet-btc/api-reference) to securely wipe private keys: + +```javascript title="Dispose Resources" try { - await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n + const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n }) + console.log('Transaction hash:', result.hash) } finally { account.dispose() + wallet.dispose() } ``` -If you use an ERC-4337 account, call [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) on that account type per its API reference. Drop references to your [`VeloraProtocolEvm`](/sdk/swap-modules/swap-velora-evm/api-reference) instance when you no longer need it. - -## Next Steps - -- [Get swap quotes](/sdk/swap-modules/swap-velora-evm/guides/get-swap-quotes) -- [Execute swaps](/sdk/swap-modules/swap-velora-evm/guides/execute-swaps) -- [API reference](/sdk/swap-modules/swap-velora-evm/api-reference) + +Always call [`dispose()`](/sdk/wallet-modules/wallet-btc/api-reference) when finished with accounts. Private keys are securely wiped from memory using `sodium_memzero`. Electrum connections are automatically closed. Disposal is irreversible. + *** -## Swap velora EVM Guides -URL: https://docs.wdk.tether.io/sdk/swap-modules/swap-velora-evm/usage -Description: How to install and use @tetherto/wdk-protocol-swap-velora-evm for swapping tokens on EVM +## Manage Accounts +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/manage-accounts +Description: Work with multiple accounts and custom derivation paths. -# Usage +This guide explains how to [retrieve accounts by index](#retrieve-accounts-by-index) and [use custom derivation paths](#retrieve-account-by-custom-derivation-path). -The [@tetherto/wdk-protocol-swap-velora-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-swap-velora-evm) module routes ERC-20 swaps on EVM chains through Velora. Use the guides below for setup, execution, quotes, and error handling. +## Retrieve Accounts by Index +You can retrieve multiple accounts using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-btc/api-reference) with different index values: - - -Install the package, create VeloraProtocolEvm, and review supported networks. - - -Exact-input and exact-output swaps, including ERC-4337 smart accounts. - - -Quote before swapping and compare fees to your max fee cap. - - -Handle swap and quote failures and dispose wallet state safely. - - +```javascript title="Retrieve Multiple Accounts" +const account0 = await wallet.getAccount(0) +const address0 = await account0.getAddress() +console.log('Account 0 address:', address0) +const account1 = await wallet.getAccount(1) +const address1 = await account1.getAddress() +console.log('Account 1 address:', address1) +``` - - -Get started with WDK in a Node.js environment - - -RPC, fee limits, and environment settings for the Velora swap protocol - - -Methods, options, and error notes for VeloraProtocolEvm - - +You can iterate through multiple accounts using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-btc/api-reference) to inspect addresses and balances in bulk: - +```javascript title="Iterate Over Accounts" +for (let i = 0; i < 5; i++) { + const account = await wallet.getAccount(i) + const address = await account.getAddress() + const balance = await account.getBalance() + console.log(`Account ${i}: ${address} (${balance} satoshis)`) +} +``` -*** +## Retrieve Account by Custom Derivation Path -## Swidge Protocol Interface -URL: https://docs.wdk.tether.io/sdk/swidge-modules -Description: Learn the preferred WDK swidge interface for swap-only, bridge-only, and combined asset routes. +You can retrieve an account at a specific derivation path using [`wallet.getAccountByPath()`](/sdk/wallet-modules/wallet-btc/api-reference): -The swidge interface is WDK's preferred route interface for protocol providers that can quote and execute asset movement. A swidge route moves from one token and chain to another token and chain. The provider may fulfill that route as a swap-only route, a bridge-only route, a combined swap and bridge route, an intent, a solver route, or an aggregator route. +```javascript title="Custom Derivation Path" +const customAccount = await wallet.getAccountByPath("0'/0/5") +const customAddress = await customAccount.getAddress() +console.log('Custom account address:', customAddress) +``` -Use swidge for new protocol integrations. Existing standalone swap and bridge interfaces remain supported for released modules, but WDK plans to deprecate those standalone interfaces in a future release after swidge provider coverage is available. +The default derivation scheme is BIP-84 (Native SegWit): `m/84'/0'/0'/0/{index}` on mainnet. Set `bip: 44` in the wallet configuration for legacy BIP-44 paths: `m/44'/0'/0'/0/{index}`. -## When to use swidge - -Use a swidge provider when a route matches any of these shapes: - -- Swap one token into another token on the same chain. -- Bridge the same token from one chain to another chain. -- Swap and bridge in one route. -- Route between different tokens, different chains, or both. +## Next Steps -Use the existing [swap modules](/sdk/swap-modules) and [bridge modules](/sdk/bridge-modules) docs when maintaining released modules that have not shipped a swidge implementation yet. For new provider modules, prefer the swidge interface. +With accounts set up, learn how to [check balances](/sdk/wallet-modules/wallet-btc/guides/check-balances). -## Released provider modules +*** -| Module | Provider | Routes | Documentation | -|--------|----------|--------|---------------| -| [`wdk-protocol-swidge-orchestra`](https://www.npmjs.com/package/wdk-protocol-swidge-orchestra) ([GitHub](https://github.com/flashnetxyz/wdk-protocol-swidge-orchestra)) | Flashnet Orchestra | BTC and stablecoin routes returned by Orchestra. Treat the live [Orchestra route matrix](https://orchestration.flashnet.xyz/v1/orchestration/routes) as provider-level data and filter through runtime discovery before exposing routes. | [Docs](/sdk/swidge-modules/swidge-orchestra/) | +## Send Transactions +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/send-transactions +Description: Send BTC and estimate transaction fees. -## Interface methods +This guide explains how to [send BTC](#send-btc), [extend post-broadcast polling](#extend-post-broadcast-polling), [sign without broadcasting](#sign-without-broadcasting), [quote and broadcast signed hex](#quote-and-broadcast-signed-hex), [estimate fees before sending](#estimate-fees), [cap transaction fees](#cap-transaction-fees), [use a custom fee rate](#send-with-custom-fee-rate), and [target a specific confirmation time](#send-with-confirmation-target). -Concrete swidge provider modules extend `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. The base interface exposes discovery, quote, execution, and status methods: +## Send BTC -| Method | Description | -|--------|-------------| -| `quoteSwidge(options)` | Quotes the route before execution. | -| `swidge(options, config?)` | Executes a swidge operation. This is a write method and must be user-confirmed. | -| `getSwidgeStatus(id, options?)` | Looks up the status of an in-flight swidge execution. | -| `getSupportedChains()` | Lists the provider's supported chains for swidge operations. | -| `getSupportedTokens(options?)` | Lists supported tokens, optionally filtered by chain or route context. | - -```typescript title="Swidge protocol shape" -interface ISwidgeProtocol { - quoteSwidge(options: SwidgeOptions): Promise - swidge( - options: SwidgeOptions, - config?: SwidgeProtocolConfig - ): Promise - getSwidgeStatus( - id: string, - options?: SwidgeStatusOptions - ): Promise - getSupportedChains(): Promise - getSupportedTokens( - options?: SwidgeSupportedTokensOptions - ): Promise -} -``` - -## Discover supported chains and tokens - -Use `getSupportedChains()` and `getSupportedTokens()` before building route pickers or validating a requested route. These methods are read-only discovery calls; they do not quote or execute a transaction. - -```typescript title="Discover swidge support" -const chains = await swidge.getSupportedChains() -const ethereumTokens = await swidge.getSupportedTokens({ - fromChain: 'ethereum' -}) +You can send Bitcoin to a recipient using [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms): -const routeTokens = await swidge.getSupportedTokens({ - fromChain: 'ethereum', - fromToken: '0xSourceToken...', - toChain: 'arbitrum' +```javascript title="Send BTC" +const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n // 0.001 BTC in satoshis }) +console.log('Transaction hash:', result.hash) +console.log('Transaction fee:', result.fee, 'satoshis') ``` -`getSupportedChains()` returns provider-specific chain identifiers plus display metadata. `getSupportedTokens(options?)` returns provider-specific token identifiers, chain identifiers, symbols, decimals, and optional token addresses. Use the returned `token` and `chain` values when calling `quoteSwidge()` unless the provider module documents a stricter identifier format. - -| Discovery type | Key fields | -|----------------|------------| -| `SwidgeSupportedChain` | `id`, `name`, `type`, `nativeToken` | -| `SwidgeSupportedToken` | `token`, `chain`, `symbol`, `decimals`, optional `address`, optional `name` | -| `SwidgeSupportedTokensOptions` | Optional `fromChain`, `fromToken`, and `toChain` filters | + +Bitcoin transactions support a single recipient only. Amounts and fees are always in satoshis (1 BTC = 100,000,000 satoshis). The minimum amount must be above the dust limit (294 satoshis for SegWit, 546 for legacy). + -## Quote a route +## Extend Post-Broadcast Polling -Call `quoteSwidge()` before execution so the user can review source amount, expected destination amount, minimum output, fees, and quote expiry. +If you want [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms) to keep polling after broadcast until spent inputs disappear from the unspent-output set, pass the optional `timeoutMs` argument: -```typescript title="Quote a swidge route" -const quote = await swidge.quoteSwidge({ - fromToken: '0xSourceToken...', - toToken: '0xDestinationToken...', - toChain: 'arbitrum', - recipient: '0xRecipient...', - fromTokenAmount: 1000000n, - slippage: 0.01 -}) +```javascript title="Send BTC With Extended Polling" +const result = await account.sendTransaction( + { + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + }, + 30000 +) -console.log('Expected output:', quote.toTokenAmount) -console.log('Minimum output:', quote.toTokenAmountMin) -console.log('Fees:', quote.fees) +console.log('Transaction hash:', result.hash) ``` -`SwidgeOptions` supports exact-in and exact-out routes: + +If you omit `timeoutMs`, the wallet uses the default post-broadcast polling window before returning. + -| Option | Type | Description | -|--------|------|-------------| -| `fromToken` | `string` | Source token address or provider-specific asset identifier. | -| `toToken` | `string` | Destination token address or provider-specific asset identifier. | -| `toChain` | `string \| number` | Optional destination chain identifier. If omitted, it defaults to the source chain for same-chain swaps. | -| `recipient` | `string` | Optional recipient for the output tokens. | -| `refundAddress` | `string` | Optional address that receives refunds if the transaction cannot complete. | -| `slippage` | `number` | Optional decimal slippage tolerance, for example `0.01` for 1%. | -| `fromTokenAmount` | `number \| bigint` | Exact source amount to spend. Do not pass with `toTokenAmount`. | -| `toTokenAmount` | `number \| bigint` | Exact destination amount to receive. Do not pass with `fromTokenAmount`. | +## Sign Without Broadcasting -## Execute a route +Use [`account.signTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#signtransactionoptions) when your app needs a signed raw Bitcoin transaction but does not want WDK to broadcast it immediately. -Call `quoteSwidge()` first, show the quote to the user, then call `swidge()` with the same route options after explicit confirmation. Apply fee caps with `SwidgeProtocolConfig` where supported by the provider. +```javascript title="Sign BTC Transaction" +const signedTransaction = await account.signTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + feeRate: 10n +}) -```typescript title="Execute a swidge route" -const options = { - fromToken: '0xSourceToken...', - toToken: '0xDestinationToken...', - toChain: 'arbitrum', - recipient: '0xRecipient...', - fromTokenAmount: 1000000n, - slippage: 0.01 -} +console.log('Signed transaction:', signedTransaction) +``` -const quote = await swidge.quoteSwidge(options) -// Show quote details and ask for confirmation before continuing. + +`signTransaction()` returns the signed transaction hex. Use `sendTransaction()` when WDK should sign, broadcast, and return the transaction hash. + -const result = await swidge.swidge(options, { - maxNetworkFeeBps: 50, - maxProtocolFeeBps: 25 +## Quote and Broadcast Signed Hex + +A writable Bitcoin account can quote and broadcast a previously signed raw transaction. The wallet broadcasts the supplied hex without rebuilding or signing it again. + +```javascript title="Quote And Broadcast Signed BTC" +const signedTransaction = await account.signTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + feeRate: 10n }) -console.log('Swidge ID:', result.id) -console.log('Primary transaction:', result.hash) -console.log('Transactions:', result.transactions) +const quote = await account.quoteSendTransaction(signedTransaction) +console.log('Signed transaction fee:', quote.fee, 'satoshis') + +const result = await account.sendTransaction(signedTransaction) +console.log('Transaction hash:', result.hash) ``` - -`swidge()` can submit one or more transactions. Ask for explicit user confirmation before calling it, and display the quote amounts, fee breakdown, route, and recipient first. - +Signed-hex quoting parses the transaction and fetches its referenced previous transactions through the configured client. It requires network access and does not broadcast. Broadcasting applies `transactionMaxFee` when configured, but signature validity is ultimately checked by the Bitcoin network rather than pre-validated by this method. -`SwidgeProtocolConfig` supports fee limits expressed in basis points of the input amount: +## Estimate Fees -| Config field | Type | Description | -|--------------|------|-------------| -| `maxNetworkFeeBps` | `number \| bigint` | Maximum acceptable network fee in basis points of the input amount. | -| `maxProtocolFeeBps` | `number \| bigint` | Maximum acceptable protocol fee in basis points of the input amount. | +You can estimate the fee for a transaction without broadcasting it using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#quotesendtransactionoptions): -## Track status +```javascript title="Estimate Fee" +const quote = await account.quoteSendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n +}) +console.log('Estimated fee:', quote.fee, 'satoshis') +``` -Use `getSwidgeStatus()` when the provider returns an execution ID and the final destination result may settle asynchronously. +## Cap Transaction Fees -```typescript title="Check swidge status" -const status = await swidge.getSwidgeStatus(result.id, { - fromChain: 'ethereum', - toChain: 'arbitrum' +Set [`transactionMaxFee`](/sdk/wallet-modules/wallet-btc/configuration#transaction-max-fee) when you create the wallet to stop `sendTransaction()` or `signTransaction()` if the estimated native BTC fee is too high. + +```javascript title="Cap BTC Transaction Fees" +const wallet = new WalletManagerBtc(seedPhrase, { + network: 'bitcoin', + transactionMaxFee: 10000n // satoshis }) +``` -if (status.status === 'completed') { - console.log('Swidge complete') -} +## Send with Custom Fee Rate + +You can override automatic fee estimation by providing a `feeRate` in sat/vB to [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms): + +```javascript title="Custom Fee Rate" +const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + feeRate: 10n // sat/vB +}) ``` -Supported statuses are: + +When `feeRate` is provided, the `confirmationTarget` parameter is ignored. + -| Status | Meaning | -|--------|---------| -| `pending` | The route has started and is waiting for progress. | -| `action-required` | The user or integrator must take another action. | -| `completed` | The route completed successfully. | -| `failed` | The route failed. | -| `refund-pending` | A refund has started but has not completed. | -| `refunded` | The route was refunded. | -| `cancelled` | The route was cancelled. | -| `expired` | The quote or route expired. | -| `partial` | The route partially completed. | +## Send with Confirmation Target -## Result fields +You can target a specific number of blocks for confirmation using the `confirmationTarget` parameter in [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms): -`SwidgeQuote` and `SwidgeResult` can include an itemized `fees` array. Prefer displaying the itemized fees when available. Public fee categories are `network`, `protocol`, `affiliate`, and `other`; providers can use the optional fee `description` for route-specific detail. +```javascript title="Confirmation Target" +const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + confirmationTarget: 6 // target 6 blocks (~1 hour) +}) +``` -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` | Execution identifier returned by `swidge()`. | -| `hash` | `string` | Primary transaction hash, if available immediately. | -| `transactions` | `SwidgeTransaction[]` | Source, destination, approval, refund, or other transaction hashes. | -| `fromTokenAmount` | `bigint` | Actual source amount spent. | -| `toTokenAmount` | `bigint` | Actual or expected destination amount. | -| `toTokenAmountMin` | `bigint` | Minimum destination amount after slippage. | -| `fees` | `SwidgeFee[]` | Itemized fee breakdown. | +## Next Steps -## Supported assets and chains +Learn how to [view transaction history](/sdk/wallet-modules/wallet-btc/guides/get-transaction-history). -Provider modules should still document route shapes, network caveats, token identifier formats, and provider-specific limits on their module pages. Use `getSupportedChains()` and `getSupportedTokens(options?)` for runtime discovery, then consult the provider module docs for behavior that cannot be represented in the shared discovery response. +*** -## Next steps +## Sign and Verify Messages +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/sign-verify-messages +Description: Sign messages and verify signatures with Bitcoin accounts. - - -Register and use protocol modules with WDK accounts - - -Use the Flashnet Orchestra community Swidge provider - - -Maintain existing standalone swap modules - - -Maintain existing standalone bridge modules - - +This guide explains how to [sign messages](#sign-a-message) and [verify signatures](#verify-a-signature). -## Route with Orchestra -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra -Description: Use the Flashnet Orchestra community Swidge module for BTC and stablecoin routes from WDK wallet accounts. +## Sign a Message - -Community modules are developed and maintained independently by third-party contributors. +You can sign a message with the account's private key using [`account.sign()`](/sdk/wallet-modules/wallet-btc/api-reference): -Tether and the WDK Team do not endorse or assume responsibility for their code, security, or maintenance. Use your own judgment and proceed at your own risk. - +```javascript title="Sign Message" +const message = 'Hello, Bitcoin!' +const signature = await account.sign(message) +console.log('Signature:', signature) +``` -Use [`wdk-protocol-swidge-orchestra`](https://www.npmjs.com/package/wdk-protocol-swidge-orchestra) when your wallet needs a WDK `SwidgeProtocol` provider for BTC and stablecoin routes served by Flashnet Orchestra. The package connects WDK wallet accounts to the Flashnet Orchestra API for route discovery, quotes, source payments, order submission, and status tracking. + +The signature is returned as a base64-encoded string. + -The package is maintained by Flashnet at [`flashnetxyz/wdk-protocol-swidge-orchestra`](https://github.com/flashnetxyz/wdk-protocol-swidge-orchestra). +## Verify a Signature -For provider-maintained route support, integration patterns, and API concepts, see the [Flashnet Orchestra docs](https://docs.flashnet.xyz/products/orchestration/overview). +You can verify that a signature was produced by the corresponding private key using [`account.verify()`](/sdk/wallet-modules/wallet-btc/api-reference): -## When to use it +```javascript title="Verify Signature" +const isValid = await account.verify(message, signature) +console.log('Signature valid:', isValid) +``` -Use Orchestra when your application needs to route between BTC on Spark or Bitcoin L1 and stablecoin routes returned by Orchestra. +You can also verify signatures using a [read-only account](/sdk/wallet-modules/wallet-btc/api-reference). Use [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-btc/api-reference) to create one from an owned account, then call [`readOnlyAccount.verify()`](/sdk/wallet-modules/wallet-btc/api-reference): -| Use case | Module | -|---|---| -| BTC and stablecoin routes returned by Orchestra. Treat the live [Orchestra route matrix](https://orchestration.flashnet.xyz/v1/orchestration/routes) as provider-level data, then expose routes through `getSupportedChains()`, `getSupportedTokens(options?)`, registered WDK source accounts, and the package caveats below. | `wdk-protocol-swidge-orchestra` | -| Standalone EVM token swaps through Velora | [`@tetherto/wdk-protocol-swap-velora-evm`](/sdk/swap-modules/swap-velora-evm/) | -| Standalone USDT0 bridge routes | [`@tetherto/wdk-protocol-bridge-usdt0-evm`](/sdk/bridge-modules/bridge-usdt0-evm/) | +```javascript title="Verify with Read-Only Account" +const readOnlyAccount = await account.toReadOnlyAccount() +const isValid = await readOnlyAccount.verify('Hello, Bitcoin!', signature) +console.log('Verified with read-only account:', isValid) +``` -For standard WDK execution through this package, do not expose Lightning as a source route. The package sends source payments from WDK accounts and submits source transaction identifiers to Orchestra; it does not implement a source Lightning receive-request flow. +## Next Steps -## Responsibility model +Learn how to [handle errors and manage resources](/sdk/wallet-modules/wallet-btc/guides/handle-errors). -| Area | Owner | -|---|---| -| Wallet accounts, key material, and source transaction signing | WDK wallet modules | -| Route quotes, deposit addresses, order state, and settlement | Flashnet Orchestra | -| Durable state storage and recovery policy | Host wallet application | +*** -`quoteSwidge()` is side-effect-free. `swidge()` and `executeSwapIntent()` can move funds from the source account. Production wallets should persist the full intent and state objects returned by the package before and after source payment. +## Wallet BTC Usage +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/usage +Description: Guide to using the @tetherto/wdk-wallet-btc module. -## Key capabilities +# Usage -- Discover route support with `getSupportedChains()` and `getSupportedTokens(options?)`. -- Quote routes with `quoteSwidge(options)` before showing a confirmation screen. -- Execute Swidge routes with `swidge(options, config?)` when the host app has recovery around the call. -- Use `prepareSwap()` and `executeSwapIntent()` when you need an explicit persistence boundary before source funds move. -- Recover or continue orders with `submitSourceTx()`, `resumeSwap()`, `getOrderStatus()`, `waitForCompletion()`, and `subscribeOrder()`. +The `@tetherto/wdk-wallet-btc` module provides wallet management for the Bitcoin blockchain. -## Next steps - -Install the package, create a WDK account, and construct Orchestra. + +Install the package and create your first wallet. - -Quote routes, show confirmation, and execute one-call Swidge operations. + +Work with multiple accounts and custom derivation paths. - -Persist intents and resume orders after source payment or process failure. + +Query native BTC balances for owned and read-only accounts. - -Review provider-maintained route support and Orchestra API concepts. + +Send Bitcoin and estimate transaction fees. - -Review methods, configuration fields, state objects, and errors. + +Retrieve and filter transfer history. + + +Sign messages and verify signatures. + + +Handle errors, manage fees, and dispose of sensitive data. -*** - -## Orchestra Usage -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/usage -Description: Discover, quote, execute, and track Orchestra Swidge routes from WDK wallet accounts. - -This guide explains how to [discover routes](#discover-routes), [quote before execution](#quote-before-execution), [execute routes](#execute-routes), and [track status](#track-status) with `wdk-protocol-swidge-orchestra`. - -## Discover routes - -Use `getSupportedChains()` and `getSupportedTokens(options?)` to build route selectors from the package-filtered route set. The live [route matrix](https://orchestration.flashnet.xyz/v1/orchestration/routes) is provider-level data; filter it through registered WDK source accounts and the package caveats before exposing routes in your UI. -```javascript title="Discover supported routes" -const chains = await orchestra.getSupportedChains() + + +Get started with WDK in a Node.js environment + + +Build mobile wallets with React Native Expo + + +Get started with WDK's Bitcoin Wallet Configuration + + +Get started with WDK's Bitcoin Wallet API + + -const sparkToTronTokens = await orchestra.getSupportedTokens({ - fromChain: 'spark', - toChain: 'tron' -}) +*** -console.log(chains) -console.log(sparkToTronTokens) -``` +### Need Help? -The returned token identifiers use chain-qualified asset references such as `spark:BTC`, `bitcoin:BTC`, `bsc:USDT`, or `tron:USDT`. Use the values returned by discovery when building UI route options. + -## Quote before execution +*** -Call `quoteSwidge()` before execution. It calls the Orchestra estimate endpoint and does not reserve a deposit address. +## Standard EVM wallet +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm +Description: Create and manage EVM wallets for native transfers, ERC-20 balances, token transfers, and signing. -```javascript title="Quote Spark BTC to TRON USDT" -const quote = await orchestra.quoteSwidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -}) +Use the EVM wallet module for standard Ethereum-compatible accounts where users pay gas with the chain native token. -console.log(quote.fromTokenAmount) -console.log(quote.toTokenAmount) -console.log(quote.toTokenAmountMin) -console.log(quote.fees) -``` +## Features -Treat quote output as indicative UI data. `swidge()` creates a fresh Orchestra quote through `prepareSwap()`, so do not assume a prior `quoteSwidge()` response locks rate, amount, fee, expiry, or deposit address. +- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases +- **EVM Derivation Paths**: Support for BIP-44 standard derivation paths for Ethereum (m/44'/60') +- **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase +- **EVM Address Support**: Generate and manage Ethereum-compatible addresses using ethers.js +- **Message Signing**: Sign and verify messages using EVM cryptography +- **Offline Transaction Signing**: Sign EVM transactions with `signTransaction()` without broadcasting them +- **Signer Abstraction**: Use seed-backed or private-key-backed signers from `@tetherto/wdk-wallet-evm/signers`, including named signer account retrieval. +- **Transaction Management**: Send transactions and get fee estimates with EIP-1559 support +- **Contract Deployment Transactions**: Send or sign contract-creation transactions by omitting `to` or passing `to: null`. +- **ERC20 Support**: Query native token and ERC20 token balances using smart contract interactions +- **Batch Token Balance Queries**: Fetch balances for multiple ERC20 tokens in one call with `getTokenBalances` +- **TypeScript Support**: Full TypeScript definitions included +- **Memory Safety**: Secure private key management with memory-safe HDNodeWallet implementation +- **Provider Flexibility**: Support for JSON-RPC URLs, EIP-1193 browser providers, and ordered failover provider lists +- **Gas Optimization**: Support for EIP-1559 maxFeePerGas and maxPriorityFeePerGas +- **Fee Estimation**: Dynamic fee calculation with normal (1.1x) and fast (2.0x) multipliers -Use smallest units: +## Supported Networks -| Asset | Unit | -|---|---| -| BTC | sats | -| USDT | 6-decimal token units | -| EVM native gas asset | wei | +This package works with any EVM-compatible blockchain, including: -## Execute routes +- **Ethereum**: Mainnet, Sepolia +- **Polygon**: Mainnet, Amoy +- **Binance Smart Chain (BSC)**: Mainnet, Testnet +- **Arbitrum**: One, Nova +- **Optimism**: Mainnet, Sepolia +- **Avalanche C-Chain**: Mainnet, Fuji +- **And many more...** -Use `swidge()` only after showing the quote details, route, recipient, fees, and expected output to the user. +## Next Steps -```javascript title="Execute after user confirmation" -const options = { - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -} + + +Get started with WDK in a Node.js environment + + +Get started with WDK's EVM Wallet configuration + + +Get started with WDK's EVM Wallet API + + +Get started with WDK's EVM Wallet usage + + -const quote = await orchestra.quoteSwidge(options) -showConfirmation(quote) +*** -const result = await orchestra.swidge(options, { - maxNetworkFeeBps: 20n, - maxProtocolFeeBps: 100n -}) +### Need Help? -console.log(result.id) -console.log(result.hash) -console.log(result.transactions) -``` + - -`swidge()` can send the source payment from the WDK account. Production wallets should persist every state transition through `onStateChange` and persist `OrchestraSubmitError.state` before retrying after failures. - +*** -For production funds, prefer the split flow documented in [State and Recovery](/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery). +## EIP-7702 accounts +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless +Description: Use EOA addresses with EIP-7702 delegation and ERC-4337 UserOperations for gasless EVM flows. -## Track status +Use the EIP-7702 wallet module when users should keep an EOA address while transactions run through bundler and paymaster infrastructure. -Use `getSwidgeStatus()` when you have the Swidge result id: +It handles EIP-7702 authorization, UserOperation construction, UserOperation signing, paymaster data, and receipt lookup behind the WDK wallet account interface. -```javascript title="Read Swidge status" -const status = await orchestra.getSwidgeStatus(result.id) +## What It Provides -if (status.status === 'completed') { - console.log('Route completed') -} -``` +- **EOA-based EIP-7702 accounts**: `getAddress()` returns the EOA address. The account delegates execution to the configured `delegationAddress` when needed. +- **Sponsored transactions**: Set `isSponsored: true` to use a sponsorship policy. Quotes return a zero fee in sponsored mode. +- **Paymaster-token transactions**: Configure `paymasterToken` to pay UserOperation costs in an ERC-20 token. +- **Provider failover**: Pass one provider or an ordered list of RPC URLs / EIP-1193 providers. Provider arrays support `retries`. +- **UserOperation receipts**: Resolve a UserOperation hash through `getUserOperationReceipt()` or map it to an EVM transaction receipt with `getTransactionReceipt()`. +- **Read-only and signing accounts**: Use `WalletAccountReadOnlyEvm7702Gasless` for balances, quotes, receipts, allowances, and verification. Use `WalletAccountEvm7702Gasless` when you need signing and sending. +- **EVM signing support**: Sign plain messages and EIP-712 typed data through the wrapped EVM account. -For submitted Orchestra states, use `getOrderStatus()`, `waitForCompletion()`, or `subscribeOrder()`: +## Requirements -```javascript title="Wait for final order status" -const finalStatus = await orchestra.waitForCompletion(submitted, { - pollIntervalMs: 5000, - timeoutMs: 7200000, - onStatus: async (status) => { - await saveOrderStatus(status) - } -}) +This module depends on EVM infrastructure that supports both EIP-7702 account delegation and ERC-4337 UserOperations. You need: -console.log(finalStatus.order?.status ?? finalStatus.status) -``` +- an RPC provider for a chain where the EIP-7702 flow is available; +- an ERC-4337 bundler URL; +- a paymaster endpoint, either the same as `bundlerUrl` or a separate `paymasterUrl`; +- a trusted smart-account implementation address for `delegationAddress`; +- either a sponsorship policy or an ERC-20 paymaster token configuration. -Scoped client-key submissions can return a `readToken`. Preserve it with the submitted state so later status reads can authenticate without an admin key. Keep admin API keys on trusted infrastructure. + +The EOA delegates execution to the configured `delegationAddress`. Treat that address as security-sensitive and verify it before using it with real funds. + -```javascript title="Read status with a client read token" -const submitted = await loadSubmittedState(result.id) -const readToken = submitted.readToken +## Fee Modes -const status = await orchestra.getSwidgeStatus(result.id, { - readToken -}) -``` +| Mode | Required Fields | Quote Behavior | +|------|-----------------|----------------| +| Sponsorship policy | `isSponsored: true`, optional `sponsorshipPolicyId` | `quoteSendTransaction()` and `quoteTransfer()` return `fee: 0n`. | +| Paymaster token | `paymasterToken.address`, optional `paymasterAddress`, optional `transferMaxFee` | Quotes return a fee in the paymaster token's base units. | -## Next steps +## Next Steps - -Install and configure the package. + +Install the package and create an EIP-7702 gasless account. - -Persist intents and resume in-flight orders. + +Review required fields, fee modes, provider failover, and per-call overrides. - -Recover from API, state, submit, and timeout failures. + +See the public classes, methods, config types, and error behavior. + + +Quote and submit EVM transactions through ERC-4337 UserOperations. *** -## Orchestra Configuration -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/configuration -Description: Configure Flashnet Orchestra API access, source chains, asset identifiers, timeouts, and state callbacks. - -`Orchestra` accepts a WDK wallet account and an `OrchestraConfig` object. +## Need Help? -```javascript title="Create an Orchestra instance" -import Orchestra from 'wdk-protocol-swidge-orchestra' + -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - baseUrl: 'https://orchestration.flashnet.xyz' -}) -``` +*** -Create one `Orchestra` instance per source wallet account. Set `sourceChain` explicitly because WDK accounts do not always expose a canonical chain id to protocol constructors. +## Wallet EVM 7702 Gasless API Reference +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/api-reference +Description: Complete API documentation for @tetherto/wdk-wallet-evm-7702-gasless. -## Install +## Table of Contents -Install the Orchestra package and the WDK wallet base package: +| Export | Description | +|--------|-------------| +| [WalletManagerEvm7702Gasless](#walletmanagerevm7702gasless) | Default export. Manages EIP-7702 gasless EVM wallet accounts. | +| [WalletAccountEvm7702Gasless](#walletaccountevm7702gasless) | Owned EIP-7702 gasless account with signing, approval, transfer, and send methods. | +| [WalletAccountReadOnlyEvm7702Gasless](#walletaccountreadonlyevm7702gasless) | Read-only account with balances, quotes, receipts, allowances, and verification. | +| [ConfigurationError](#configurationerror) | Error thrown for invalid wallet configuration. | +| [Config Types](#config-types) | `Evm7702GaslessWalletConfig` and related fee-mode types. | -```bash title="Install Orchestra" -npm install wdk-protocol-swidge-orchestra @tetherto/wdk-wallet@1.0.0-beta.11 -``` +## WalletManagerEvm7702Gasless -Install the WDK wallet modules for the source accounts your application supports: +Default export from `@tetherto/wdk-wallet-evm-7702-gasless`. -```bash title="Install WDK wallet modules" -npm install @tetherto/wdk @tetherto/wdk-wallet-spark @tetherto/wdk-wallet-btc @tetherto/wdk-wallet-evm +```javascript +import WalletManagerEvm7702Gasless from '@tetherto/wdk-wallet-evm-7702-gasless' ``` -## Constructor +### Constructor ```javascript -new Orchestra(account, config?) +new WalletManagerEvm7702Gasless(seed, config) ``` -Parameters: - -- `account` (`IWalletAccount | IWalletAccountReadOnly | undefined`): WDK account used for source payments, read-only status access, or discovery-only use. -- `config` (`OrchestraConfig`, optional): API, source-chain, asset, timeout, and callback settings. - -## Core config +| Parameter | Type | Description | +|-----------|------|-------------| +| `seed` | `string \| Uint8Array` | BIP-39 mnemonic seed phrase or seed bytes. | +| `config` | `Evm7702GaslessWalletConfig` | Wallet configuration with common fields and one fee mode. | -| Field | Type | Description | -|---|---|---| -| `apiKey` | `string` | Flashnet Orchestra API key. Can be a backend key or scoped client key. | -| `baseUrl` | `string` | Orchestra API base URL. Defaults to the package client default when omitted. | -| `fetch` | `typeof fetch` | Custom fetch implementation. | -| `authMode` | `'admin' \| 'client' \| 'auto'` | Controls API key handling for status and SSE flows. | -| `sourceChain` | `string` | Default source chain for unqualified source assets. | -| `defaultSourceChain` | `string` | Alias for `sourceChain`. | -| `chain` | `string` | Alias for `sourceChain`. | -| `client` | `OrchestraClient` | Custom client instance. | +### Methods -## Authentication +| Method | Parameters | Returns | Notes | +|--------|------------|---------|-------| +| `getAccount(index?)` | `index?: number` | `Promise\` | Defaults to index `0` and derives `0'/0/{index}`. | +| `getAccountByPath(path)` | `path: string` | `Promise\` | Returns a cached account for the derivation path suffix. | +| `getFeeRates()` | - | `Promise\<{ normal: bigint, fast: bigint }\>` | Uses provider fee data. Throws if no provider is connected. | +| `dispose()` | - | `void` | Inherited from `WalletManager`; disposes managed accounts. | -Use a backend key from a server or trusted runtime: +`getFeeRates()` returns fee rates in wei. The `normal` value applies the EVM wallet normal multiplier, and `fast` applies the EVM wallet fast multiplier to the provider fee. -```javascript title="Backend key" -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY -}) -``` +## WalletAccountEvm7702Gasless -Use `authMode: 'client'` for scoped client keys: +Named export for owned EIP-7702 gasless accounts. -```javascript title="Scoped client key" -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_CLIENT_KEY, - authMode: 'client' -}) +```javascript +import { WalletAccountEvm7702Gasless } from '@tetherto/wdk-wallet-evm-7702-gasless' ``` -Scoped client-key submissions can return `readToken`. Store that token with the submitted state and pass the full state back to status methods. - -Backend proxy integrations can provide headers per request: +### Constructors -```javascript title="Backend proxy headers" -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - baseUrl: 'https://your-api.example.com/orchestra', - getAuthHeaders: async () => ({ - Authorization: `Bearer ${await getSessionToken()}` - }) -}) +```javascript +new WalletAccountEvm7702Gasless(seed, path, config) +new WalletAccountEvm7702Gasless(walletAccountEvm, config) ``` -For direct browser SSE, provide a scoped SSE token with `sseToken` or `getSseToken`, or proxy SSE through your backend. - -## Asset config - -| Field | Type | Description | -|---|---|---| -| `sourceTokenAddresses` | `Record` | Source token contract addresses keyed by `':'`. | -| `tokenAddresses` | `Record` | Alias for `sourceTokenAddresses`. | -| `assetAddresses` | `Record` | Alias for `sourceTokenAddresses`. | -| `sparkTokenIdentifiers` | `Record` | Spark token identifiers keyed by Orchestra asset symbol. | -| `tokenIdentifiers` | `Record` | Alias for `sparkTokenIdentifiers`. | -| `nativeAssets` | `Record` | Native asset overrides by source chain. | -| `tokenDecimals` | `Record` | Token decimal overrides by chain-qualified asset key. | - -EVM token sources need token contract addresses. The package includes common USDT source addresses, but production wallets should pass their own allowlist. - -```javascript title="Configure EVM USDT source token" -const orchestra = new Orchestra(arbitrumAccount, { - sourceChain: 'arbitrum', - apiKey: process.env.FLASHNET_API_KEY, - sourceTokenAddresses: { - 'arbitrum:USDT': '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' - } -}) -``` +| Parameter | Type | Description | +|-----------|------|-------------| +| `seed` | `string \| Uint8Array` | BIP-39 mnemonic seed phrase or seed bytes. | +| `path` | `string` | EVM derivation path suffix, for example `"0'/0/0"`. | +| `walletAccountEvm` | `WalletAccountEvm` | Existing EVM account to wrap. | +| `config` | `Evm7702GaslessWalletConfig` | Wallet configuration. | -Spark tokens other than BTC need Spark token identifiers: +### Properties -```javascript title="Configure Spark token identifiers" -const orchestra = new Orchestra(sparkAccount, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - sparkTokenIdentifiers: { - USDB: 'btkn1...' - } -}) -``` +| Property | Type | Description | +|----------|------|-------------| +| `index` | `number` | Derivation path index from the wrapped EVM account. | +| `path` | `string` | Full derivation path from the wrapped EVM account. | +| `keyPair` | `KeyPair` | Read-only view of the wrapped EVM account key pair. | -## Safety and timeout config +Treat the arrays returned through `keyPair` as read-only. Call `dispose()` when the account is no longer needed. -| Field | Type | Description | -|---|---|---| -| `slippageBps` | `number` | Default slippage in basis points. | -| `timeoutMs` | `number` | HTTP request timeout. | -| `maxRetries` | `number` | General request retry count. | -| `retryDelayMs` | `number` | General retry delay. | -| `submitMaxRetries` | `number` | Submit retry count. Bitcoin submit retries cover propagation delays for `tx_not_found` and `vout_not_found`. | -| `submitRetryDelayMs` | `number` | Submit retry delay. | -| `quoteExpirySafetyMs` | `number` | Safety window before quote expiry when sending source payments. | -| `pollIntervalMs` | `number` | Default polling interval for `waitForCompletion()`. | -| `waitTimeoutMs` | `number` | Default wait timeout for `waitForCompletion()`. | -| `idempotencyKeyFactory` | `() => string` | Custom idempotency key factory for quote and submit calls. | +### Methods -## State callbacks +| Method | Parameters | Returns | Notes | +|--------|------------|---------|-------| +| `getAddress()` | - | `Promise\` | Inherited read-only method. Returns the EOA address. | +| `getBalance()` | - | `Promise\` | Native token balance in wei. | +| `getTokenBalance(tokenAddress)` | `tokenAddress: string` | `Promise\` | ERC-20 balance in base units. | +| `getTokenBalances(tokenAddresses)` | `tokenAddresses: string[]` | `Promise\\>` | Multiple ERC-20 balances in base units. | +| `getPaymasterTokenBalance()` | - | `Promise\` | Throws `ConfigurationError` when no `paymasterToken` is configured. | +| `sign(message)` | `message: string` | `Promise\` | Signs with the wrapped EVM account. | +| `signTypedData(typedData)` | `TypedData` | `Promise\` | Signs EIP-712 typed data. | +| `verify(message, signature)` | `message: string`, `signature: string` | `Promise\` | Verifies a plain message signature. | +| `verifyTypedData(typedData, signature)` | `TypedData`, `signature: string` | `Promise\` | Verifies EIP-712 typed data. | +| `approve(options)` | `ApproveOptions` | `Promise\` | Sends an ERC-20 approval as a UserOperation. | +| `quoteSendTransaction(tx, config?)` | `EvmTransaction \| EvmTransaction[]`, partial fee-mode config | `Promise\\>` | Returns `{ fee }`; owned accounts cache paymaster-token quotes for up to 2 minutes and validate the nonce before reuse. | +| `sendTransaction(tx, config?)` | `EvmTransaction \| EvmTransaction[]`, partial fee-mode config | `Promise\` | Returns a UserOperation hash and fee. | +| `quoteTransfer(options, config?)` | `EvmTransferOptions`, partial fee-mode config | `Promise\\>` | Quotes an ERC-20 transfer. | +| `transfer(options, config?)` | `EvmTransferOptions`, partial fee-mode config | `Promise\` | Sends an ERC-20 transfer as a UserOperation. | +| `getAllowance(token, spender)` | `token: string`, `spender: string` | `Promise\` | Reads ERC-20 allowance. | +| `getUserOperationReceipt(hash)` | `hash: string` | `Promise\` | Reads the raw bundler receipt. | +| `getTransactionReceipt(hash)` | `hash: string` | `Promise\` | Maps a UserOperation hash to an EVM receipt when included. | +| `toReadOnlyAccount()` | - | `Promise\` | Returns a cached read-only account. | +| `dispose()` | - | `void` | Clears quote cache and disposes the wrapped EVM account. | -| Field | Type | Description | -|---|---|---| -| `onIntent` | `(intent) => void \| Promise` | Called after `prepareSwap()` creates an intent. | -| `onStateChange` | `(event, state) => void \| Promise` | Called for persisted state transitions. | -| `onOrderStatus` | `(status) => void \| Promise` | Called by `waitForCompletion()` after each status read. | +### Quote and Send Behavior -Use `onStateChange` to persist state transitions that can affect funds: +- Sponsored mode returns `fee: 0n` from quote methods. +- Paymaster-token mode returns fees in the configured paymaster token's base units. +- Owned account paymaster-token quotes are cached for up to 2 minutes for the same serialized transaction. Before reusing a cached UserOperation, the account reads the current EntryPoint nonce and re-quotes if it has moved. +- The cache key contains the transaction but not the per-call fee-mode configuration. Use the same fee-mode and paymaster settings for a quote and its matching `sendTransaction()` or `transfer()` call. +- `sendTransaction()` signs the UserOperation typed data before submitting it to the bundler. +- If the account is already delegated to `delegationAddress`, the send path does not include a new EIP-7702 authorization. A send that needs a fresh authorization rebuilds the UserOperation instead of reusing the cached one. -```javascript title="Persist state transitions" -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - onStateChange: async (event, state) => { - await saveSwapState(event, state) - } -}) -``` +### Transfer Fee Cap -See [State and Recovery](/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery) before using this package with production funds. +`transfer()` checks `transferMaxFee` only in paymaster-token mode. It throws when the estimated fee is greater than or equal to the configured cap. -*** +### USDT Approval Rule -## Orchestra API Reference -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/api-reference -Description: API reference for the Flashnet Orchestra community Swidge module. +On Ethereum mainnet, `approve()` checks the USDT allowance rule. If the current allowance is non-zero and the requested amount is non-zero, the method throws before sending. Send an approval with amount `0` first, then send the new non-zero approval. -# API Reference +## WalletAccountReadOnlyEvm7702Gasless -## Package +Named export for read-only accounts. ```javascript -import Orchestra, { - OrchestraApiError, - OrchestraError, - OrchestraStateError, - OrchestraSubmitError, - OrchestraTimeoutError -} from 'wdk-protocol-swidge-orchestra' +import { WalletAccountReadOnlyEvm7702Gasless } from '@tetherto/wdk-wallet-evm-7702-gasless' ``` -The package exports `Orchestra` as both the default export and a named export. - -Use the [Flashnet Orchestra docs](https://docs.flashnet.xyz/products/orchestration/overview) for provider-maintained route support, API concepts, and integration patterns outside the WDK package interface. - -## Class: Orchestra - -`Orchestra` extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. - ### Constructor ```javascript -new Orchestra(account, config?) +new WalletAccountReadOnlyEvm7702Gasless(address, config) ``` -Parameters: - -- `account`: `IWalletAccount | IWalletAccountReadOnly | undefined` -- `config`: `OrchestraConfig` - -Use `undefined` only for discovery or status flows that do not send source payments. Write flows require a WDK account with source-payment methods. - -### Swidge methods +| Parameter | Type | Description | +|-----------|------|-------------| +| `address` | `string` | EOA address. | +| `config` | `Omit\` | Read-only configuration. | -| Method | Description | Returns | -|---|---|---| -| `quoteSwidge(options)` | Calls Orchestra estimate and returns a side-effect-free WDK Swidge quote. | `Promise` | -| `swidge(options, config?)` | Creates a quote, sends the source payment, submits the source transaction, and returns a WDK Swidge result. | `Promise` | -| `getSwidgeStatus(id, options?)` | Reads an Orchestra order and maps the order status to WDK Swidge status. | `Promise` | -| `getSupportedChains()` | Reads Orchestra's route matrix and returns supported chains. | `Promise` | -| `getSupportedTokens(options?)` | Reads supported tokens, optionally filtered by source chain, source token, or destination chain. | `Promise` | +### Methods -### Production flow methods +| Method | Parameters | Returns | Notes | +|--------|------------|---------|-------| +| `getAddress()` | - | `Promise\` | Returns the configured address. | +| `getBalance()` | - | `Promise\` | Native token balance in wei. | +| `getTokenBalance(tokenAddress)` | `tokenAddress: string` | `Promise\` | ERC-20 balance in base units. | +| `getTokenBalances(tokenAddresses)` | `tokenAddresses: string[]` | `Promise\\>` | Multiple ERC-20 balances. | +| `getPaymasterTokenBalance()` | - | `Promise\` | Reads the configured paymaster token balance. | +| `quoteSendTransaction(tx, config?)` | `EvmTransaction \| EvmTransaction[]`, partial fee-mode config | `Promise\\>` | Quotes without signing or sending. | +| `quoteTransfer(options, config?)` | `EvmTransferOptions`, partial fee-mode config | `Promise\\>` | Quotes an ERC-20 transfer. | +| `getAllowance(token, spender)` | `token: string`, `spender: string` | `Promise\` | Reads ERC-20 allowance. | +| `getUserOperationReceipt(hash)` | `hash: string` | `Promise\` | Reads the raw bundler receipt. | +| `getTransactionReceipt(hash)` | `hash: string` | `Promise\` | Returns `null` until the UserOperation maps to an EVM transaction. | +| `verify(message, signature)` | `message: string`, `signature: string` | `Promise\` | Verifies a plain message signature. | +| `verifyTypedData(typedData, signature)` | `TypedData`, `signature: string` | `Promise\` | Verifies EIP-712 typed data. | -| Method | Description | Returns | -|---|---|---| -| `prepareSwap(options, requestOptions?)` | Creates a durable Orchestra quote with deposit address and idempotency keys. Persist the returned intent before source payment. | `Promise` | -| `executeSwapIntent(intentOrState, options?)` | Sends the source payment and submits the transfer id to Orchestra. | `Promise` | -| `submitSourceTx(intentOrState, sourceTxHash, options?)` | Submits an already-sent source transaction without sending another source payment. | `Promise` | -| `resumeSwap(state, options?)` | Reads status, submits an existing source transaction, or resumes a fresh source payment only when explicitly allowed. | `Promise` | -| `getOrderStatus(target)` | Reads status by order id, quote id, or source transaction hash. | `Promise` | -| `waitForCompletion(target, options?)` | Polls status until a terminal Orchestra order status or timeout. | `Promise` | -| `subscribeOrder(target, callbacks, options?)` | Opens an SSE status subscription and returns a closable subscription. | `OrderSubscription` | +Read-only accounts cannot sign, approve, send, or transfer. -### `quoteSwidge(options)` +## ConfigurationError ```javascript -const quote = await orchestra.quoteSwidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -}) +import { ConfigurationError } from '@tetherto/wdk-wallet-evm-7702-gasless' ``` -`quoteSwidge()` calls the estimate endpoint. It does not reserve a deposit address and does not move funds. +`ConfigurationError` extends `Error` and is thrown when wallet configuration is invalid. -### `swidge(options, config?)` +Known configuration errors include: -```javascript -const result = await orchestra.swidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...' -}, { - maxNetworkFeeBps: 20n, - maxProtocolFeeBps: 100n -}) -``` +- missing `provider`; +- missing `bundlerUrl`; +- missing `delegationAddress`; +- missing `paymasterToken` when `isSponsored` is absent or `false`; +- empty provider array; +- configured `paymasterAddress` does not match the paymaster returned by the RPC. -`swidge()` can send a source payment. Show confirmation first and persist state through `onStateChange` when using this path. +## Config Types -## Options +### Evm7702GaslessWalletCommonConfig -### `OrchestraSwidgeOptions` +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `provider` | `string \| Eip1193Provider \| Array\` | Yes | RPC endpoint, EIP-1193 provider, or provider failover list. | +| `retries` | `number` | No | Additional retry attempts for provider arrays. Defaults to `3`. | +| `bundlerUrl` | `string` | Yes | ERC-4337 bundler endpoint. | +| `paymasterUrl` | `string` | No | Paymaster endpoint when different from `bundlerUrl`. | +| `delegationAddress` | `string` | Yes | Smart-account implementation address used for EIP-7702 delegation. | -| Field | Type | Description | -|---|---|---| -| `fromToken` | `string` | Source token, preferably chain-qualified such as `spark:BTC` or `bsc:USDT`. | -| `toToken` | `string` | Destination token, preferably chain-qualified. | -| `fromChain` | `string \| number` | Optional source-chain override. | -| `toChain` | `string \| number` | Optional destination-chain override. | -| `recipient` | `string` | Destination recipient. Required when the destination is not the source account. | -| `refundChain` | `string` | Refund chain for routes that need refund metadata. | -| `refundAddress` | `string` | Refund address for routes that need refund metadata. | -| `fromTokenAmount` | `number \| bigint \| string` | Exact source amount. Do not pass with `toTokenAmount`. | -| `toTokenAmount` | `number \| bigint \| string` | Exact destination amount. Do not pass with `fromTokenAmount`. | -| `slippage` | `number` | Decimal slippage, for example `0.01` for 1%. | -| `slippageBps` | `number` | Slippage in basis points. | -| `idempotencyKey` | `string` | Quote idempotency key for `prepareSwap()`. | -| `submitIdempotencyKey` | `string` | Submit idempotency key. | -| `sourceTxHash` | `string` | Existing source transaction id to submit instead of sending a new payment. | -| `sourceNetworkFee` | `bigint \| number \| string` | Source wallet fee for an existing source transaction. | -| `sourceAddress` | `string` | Source wallet address used for submit metadata. | -| `sourceSparkAddress` | `string` | Spark source address used for Spark submit metadata. | -| `sourceTokenIdentifier` | `string` | Per-call Spark token identifier override when the app should not rely only on constructor-level `sparkTokenIdentifiers`. | -| `sourceTokenAddress` | `string` | Per-call source token contract address override when the app should not rely only on constructor-level `sourceTokenAddresses`. | -| `sourceTxVout` | `number` | Bitcoin output index when needed for submit metadata. | -| `feeRate` | `number \| bigint` | Bitcoin source fee rate option. | -| `confirmationTarget` | `number` | Bitcoin source confirmation target option. | -| `broadcastTimeoutMs` | `number` | Bitcoin broadcast timeout. | -| `allowNewSourcePayment` | `boolean` | Allows `resumeSwap()` to send a fresh source payment. Use only after wallet-history recovery. | -| `ignoreQuoteExpiry` | `boolean` | Bypasses quote expiry protection. | -| `quoteExpirySafetyMs` | `number` | Per-call quote expiry safety window. | -| `appFees` | `AppFee[]` | App fee metadata passed to Orchestra. | -| `affiliateId` | `string` | Affiliate id metadata. | -| `affiliateIds` | `string[]` | Affiliate id metadata. | +### Evm7702GaslessSponsorshipPolicyConfig -### `OrchestraConfig` +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `isSponsored` | `true` | Yes | Enables sponsored fee mode. | +| `sponsorshipPolicyId` | `string` | No | Paymaster sponsorship policy ID. | -See [Configuration](/sdk/swidge-modules/swidge-orchestra/configuration) for the full constructor config. Common fields are `apiKey`, `baseUrl`, `authMode`, `sourceChain`, `sourceTokenAddresses`, `sparkTokenIdentifiers`, `onStateChange`, timeout settings, and retry settings. +### Evm7702GaslessPaymasterTokenConfig -### `OrchestraSwidgeStatusOptions` +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `isSponsored` | `false` | No | Omit or set to `false` for paymaster-token mode. | +| `paymasterAddress` | `string` | No | Expected paymaster contract address. | +| `paymasterToken.address` | `string` | Yes | ERC-20 token used for fee payment. | +| `transferMaxFee` | `number \| bigint` | No | Maximum fee for `transfer()` in token base units. | -| Field | Type | Description | -|---|---|---| -| `readToken` | `string` | Scoped client-key status token returned on submitted Orchestra state. Pass it to `getSwidgeStatus(id, options?)` when status reads do not use an admin key. | +### Evm7702GaslessWalletConfig -## State objects +```typescript +type Evm7702GaslessWalletConfig = + Evm7702GaslessWalletCommonConfig & + (Evm7702GaslessSponsorshipPolicyConfig | Evm7702GaslessPaymasterTokenConfig) +``` -### `OrchestraSwapIntent` +### Exported Types -Returned by `prepareSwap()`. Persist it before calling `executeSwapIntent()`. +The package also re-exports EVM wallet types from `@tetherto/wdk-wallet-evm`, including `FeeRates`, `KeyPair`, `EvmTransaction`, `TransactionResult`, `EvmTransferOptions`, `TransferResult`, `EvmTransactionReceipt`, `ApproveOptions`, `TypedData`, `TypedDataDomain`, and `TypedDataField`. -Key fields: +Module-specific exported types include `UserOperationReceipt`, `Eip7702AuthorizationOverride`, `BuildSponsoredUserOperationOverrides`, and `SponsoredUserOperation`. -- `version` -- `quoteId` -- `sourceChain` -- `sourceAsset` -- `destinationChain` -- `destinationAsset` -- `recipientAddress` -- `amountMode` -- `amountIn` -- `estimatedOut` -- `depositAddress` -- `expiresAt` -- `quoteIdempotencyKey` -- `submitIdempotencyKey` -- `createdAt` +*** -### `OrchestraSwapState` +## Configuration +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/configuration +Description: Configuration options for @tetherto/wdk-wallet-evm-7702-gasless. -Returned after source payment, submit, or recovery steps. It extends `OrchestraSwapIntent`. +## Wallet Configuration -Additional key fields: +`WalletManagerEvm7702Gasless`, `WalletAccountEvm7702Gasless`, and `WalletAccountReadOnlyEvm7702Gasless` use the same base configuration. The account must also choose one fee mode: sponsorship policy or paymaster token. -- `sourceTxHash` -- `sourceNetworkFee` -- `orderId` -- `status` -- `readToken` -- `sourcePaymentStartedAt` -- `fundedAt` -- `submittedAt` + +Replace `''` with a smart-account implementation address that you have verified for the target chain. The EOA delegates execution to this address. + -Persist the full object, not only `orderId`. Recovery may need the quote id, deposit address, source transaction hash, read token, source chain, and idempotency keys. +```javascript title="Sponsored wallet configuration" +import WalletManagerEvm7702Gasless from '@tetherto/wdk-wallet-evm-7702-gasless' -## Status mapping +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy' +}) +``` -`getSwidgeStatus()` maps Orchestra order statuses to WDK Swidge statuses: +```javascript title="Paymaster-token wallet configuration" +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + paymasterAddress: '0x888888888888Ec68A58AB8094Cc1AD20Ba3D2402', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + transferMaxFee: 100000n // 0.1 USDT when the token has 6 decimals +}) +``` -| Orchestra status | WDK Swidge status | -|---|---| -| `processing` or unknown in-flight state | `pending` | -| `completed` | `completed` | -| `failed` | `failed` | -| `unfulfilled` | `failed` | -| `expired` | `expired` | -| `refunded` | `refunded` | +## Required Common Fields -## Errors +| Field | Type | Description | +|-------|------|-------------| +| `provider` | `string \| Eip1193Provider \| Array\` | RPC endpoint, EIP-1193 provider, or ordered failover list. | +| `bundlerUrl` | `string` | ERC-4337 bundler endpoint used to build and submit UserOperations. | +| `delegationAddress` | `string` | Smart-account implementation address used for EIP-7702 delegation. | -All package-specific errors extend `OrchestraError`. +Account constructors and per-call config overrides throw `ConfigurationError` if any required common field is missing. -| Error | Description | Useful fields | -|---|---|---| -| `OrchestraError` | Base package error. | `code`, `details` | -| `OrchestraApiError` | Orchestra returned an API error or invalid API response. | `code`, `status`, `details` | -| `OrchestraStateError` | Input state is incomplete, unsafe to resume, expired, or incompatible with the requested source payment. | `code`, `details` | -| `OrchestraSubmitError` | Source payment was sent, but submit, post-submit validation, or post-submit state persistence failed. | `state`, `cause` | -| `OrchestraTimeoutError` | HTTP request or wait operation timed out. | `code`, `details` | +## Optional Common Fields -## Source repository tooling +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `paymasterUrl` | `string` | `bundlerUrl` | Paymaster endpoint when it differs from the bundler endpoint. | +| `retries` | `number` | `3` | Additional retry attempts when `provider` is an array. Total attempts are `1 + retries`. | -The package repository includes a funded live-test harness. Those commands can move mainnet funds by default and are not required for normal WDK docs examples. Review the package repository before running them. +### Provider Failover -*** +Pass an ordered array when you want read and quote calls to retry against multiple RPC providers: -## Get Started with Orchestra -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/get-started -Description: Install the Flashnet Orchestra community Swidge module and create an Orchestra protocol instance. +```javascript title="Provider failover" +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: [ + 'https://rpc.mevblocker.io/fast', + 'https://eth.llamarpc.com' + ], + retries: 3, + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + isSponsored: true +}) +``` -This guide shows how to [install the package](#install-the-package), [create source accounts](#create-source-accounts), [create Orchestra](#create-orchestra), and [make a first quote](#make-a-first-quote). + +An empty provider array throws `ConfigurationError`. Include at least one RPC URL or EIP-1193 provider. + -## Install the package +## Fee Mode Configuration -Install Orchestra and the WDK wallet base package: +### Sponsorship Policy -```bash title="Install Orchestra" -npm install wdk-protocol-swidge-orchestra @tetherto/wdk-wallet@1.0.0-beta.11 -``` +Use sponsorship mode when a paymaster sponsors UserOperation fees. -Install the WDK wallet modules for the chains you plan to support: +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `isSponsored` | `true` | Yes | Enables sponsorship mode. | +| `sponsorshipPolicyId` | `string` | No | Policy identifier passed to the paymaster context. | -```bash title="Install WDK wallet modules" -npm install @tetherto/wdk @tetherto/wdk-wallet-spark @tetherto/wdk-wallet-btc @tetherto/wdk-wallet-evm +```javascript title="Sponsorship mode" +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy' +}) ``` -## Create source accounts +In sponsorship mode, `quoteSendTransaction()` and `quoteTransfer()` return `{ fee: 0n }`. -Create WDK accounts for the source chains your wallet supports. This example registers Spark, Bitcoin L1, and Arbitrum source accounts. +### Paymaster Token -```javascript title="Create WDK accounts" -import WDK from '@tetherto/wdk' -import WalletManagerBtc from '@tetherto/wdk-wallet-btc' -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerSpark from '@tetherto/wdk-wallet-spark' +Use paymaster-token mode when the account pays UserOperation fees with an ERC-20 token. -const wdk = new WDK(seedPhrase) - .registerWallet('spark', WalletManagerSpark, { - network: 'MAINNET', - syncAndRetry: true - }) - .registerWallet('bitcoin', WalletManagerBtc, { - network: 'bitcoin', - client: { - type: 'electrum', - clientConfig: { - host: 'electrum.blockstream.info', - port: 50001 - } - } - }) - .registerWallet('arbitrum', WalletManagerEvm, { - chainId: 42161, - provider: process.env.ARBITRUM_RPC_URL - }) +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `isSponsored` | `false` | No | Omit this field or set it to `false`. | +| `paymasterToken.address` | `string` | Yes | ERC-20 token address used for fee payment. | +| `paymasterAddress` | `string` | No | Pins the expected paymaster contract address. | +| `transferMaxFee` | `number \| bigint` | No | Maximum fee for `transfer()` operations, in paymaster-token base units. Choose the value using the token's decimals. | -const spark = await wdk.getAccount('spark', 0) -const arbitrum = await wdk.getAccount('arbitrum', 0) -``` +If `isSponsored` is omitted or `false`, `paymasterToken` is required. -## Create Orchestra +```javascript title="Paymaster token mode" +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + transferMaxFee: 100000n // 0.1 USDT when the token has 6 decimals +}) +``` -Create one `Orchestra` instance per source wallet account. Set `sourceChain` explicitly. +When `paymasterAddress` is set, the account checks the paymaster address returned by the RPC response and throws `ConfigurationError` if it does not match. -```javascript title="Create Orchestra for Spark source routes" -import Orchestra from 'wdk-protocol-swidge-orchestra' +## Using Candide -const orchestra = new Orchestra(spark, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - baseUrl: 'https://orchestration.flashnet.xyz' -}) -``` +Candide serves the bundler and paymaster from a single unified URL, so you only need `bundlerUrl` — the paymaster is reached at the same endpoint. The chain is selected by its chain ID in the path. Use the public endpoint (rate-limited, no key required) or an authenticated endpoint with an API key from the [dashboard](https://dashboard.candide.dev): -EVM token sources need token contract addresses. Common USDT addresses are built in, but production wallets should pass their own allowlist. +- Public: `https://api.candide.dev/public/v3/{chainId}` +- Authenticated: `https://api.candide.dev/api/v3/{chainId}/{apiKey}` -```javascript title="Create Orchestra for Arbitrum USDT source routes" -const arbitrumOrchestra = new Orchestra(arbitrum, { - sourceChain: 'arbitrum', - apiKey: process.env.FLASHNET_API_KEY, - sourceTokenAddresses: { - 'arbitrum:USDT': '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' - } +```javascript title="Using Candide" +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.candide.dev/api/v3/1/YOUR_API_KEY', + isSponsored: true, + sponsorshipPolicyId: 'your_policy_id' }) ``` -## Make a first quote +If a provider serves its paymaster from a different URL than the bundler, set `paymasterUrl` to that endpoint; otherwise it defaults to `bundlerUrl`. -Use `quoteSwidge()` to estimate a route before showing a confirmation screen. +## Per-call Overrides -```javascript title="Quote Spark BTC to TRON USDT" -const quote = await orchestra.quoteSwidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -}) +`quoteSendTransaction()`, `sendTransaction()`, `quoteTransfer()`, and `transfer()` accept a partial fee-mode config override. Overrides are shallow-merged with the account config. Include `isSponsored: false` when switching a sponsored account to paymaster-token mode for one operation: -console.log(quote.toTokenAmount) -console.log(quote.toTokenAmountMin) -console.log(quote.fees) +```javascript title="Override the paymaster token" +const result = await account.sendTransaction({ + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + value: 0n, + data: '0x' +}, { + isSponsored: false, + paymasterToken: { + address: '0x68749665FF8D2d112Fa859AA293F07A622782F38' + } +}) ``` -`quoteSwidge()` does not reserve a deposit address or move funds. Call `swidge()` or the split `prepareSwap()` and `executeSwapIntent()` flow only after the user has reviewed the route, amount, fees, and recipient. - -## Next steps +When an override is provided, the merged config is validated before the operation runs. - - -Show confirmation and execute Orchestra routes. - - -Use the production split flow and persist state. - - -Review auth, source-chain, token, timeout, and callback options. - - + +The two-minute quote cache is keyed by the transaction, not by this override. Pass the same fee-mode and paymaster settings to a quote and its matching send or transfer. If those settings must change, do not execute the same transaction from that account until its earlier cached quote has expired. + *** -## Quote and Execute Orchestra Routes -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/quote-and-execute -Description: Quote Orchestra Swidge routes, show confirmation, and execute from WDK source accounts. - -This guide covers [route discovery](#route-discovery), [quotes](#quotes), [one-call execution](#one-call-execution), and [source-chain examples](#source-chain-examples). +## Check Balances +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/guides/check-balances +Description: Query native, ERC-20, and paymaster token balances. -## Route discovery +This guide explains how to read balances from an EIP-7702 gasless account. -Use discovery methods to build a wallet UI from the package-filtered route set. Treat Orchestra's live route matrix as provider-level data and still apply WDK account availability, source-chain support, and package caveats before exposing routes. +## Native Balance -```javascript title="Discover chains and tokens" -const chains = await orchestra.getSupportedChains() +Use `getBalance()` to read the native token balance in wei. -const tokens = await orchestra.getSupportedTokens({ - fromChain: 'spark', - toChain: 'tron' -}) +```javascript title="Get native balance" +const balance = await account.getBalance() +console.log('Native balance:', balance) ``` -Use chain-qualified asset references such as `spark:BTC`, `bitcoin:BTC`, `bsc:USDT`, or `tron:USDT`. - -## Quotes +## ERC-20 Balance -`quoteSwidge()` is side-effect-free. It calls Orchestra's estimate endpoint and does not reserve a deposit address. +Use `getTokenBalance(tokenAddress)` to read one ERC-20 token balance in the token's base units. -```javascript title="Quote exact source amount" -const quote = await orchestra.quoteSwidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -}) +```javascript title="Get token balance" +const usdtBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') +console.log('USDT balance:', usdtBalance) ``` -Show the user: +## Multiple ERC-20 Balances -- source amount and source asset -- expected destination amount -- minimum destination amount -- fees -- route and recipient -- expiry, if present +Use `getTokenBalances(tokenAddresses)` to read several token balances at once. -## One-call execution +```javascript title="Get multiple token balances" +const balances = await account.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' +]) -Call `swidge()` only after user confirmation. The method creates a fresh Orchestra quote, sends the source payment from the WDK account, submits the source transaction id to Orchestra, and returns the Orchestra order id. +console.log(balances) +``` -```javascript title="Execute with fee caps" -const result = await orchestra.swidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...' -}, { - maxNetworkFeeBps: 20n, - maxProtocolFeeBps: 100n -}) +The return value maps each token address to a `bigint` balance. -console.log(result.id) -console.log(result.hash) +## Paymaster Token Balance + +In paymaster-token mode, `getPaymasterTokenBalance()` reads the configured `paymasterToken.address`. + +```javascript title="Get paymaster token balance" +const feeTokenBalance = await account.getPaymasterTokenBalance() +console.log('Paymaster token balance:', feeTokenBalance) ``` -There is a recovery gap after the source payment is sent and before Orchestra accepts the transaction id. Use the split flow in [State and Recovery](/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery) for production funds. +`getPaymasterTokenBalance()` throws `ConfigurationError` when the account is configured for sponsorship mode because no paymaster token is present. -## Source-chain examples - -### Spark BTC to USDT - -Spark signs the BTC transfer. Orchestra settles USDT on the destination chain. +## Read-only Balance Checks -```javascript title="Prepare Spark BTC to TRON USDT" -const spark = await wdk.getAccount('spark', 0) -const orchestra = new Orchestra(spark, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY -}) +All balance methods are available on `WalletAccountReadOnlyEvm7702Gasless`. -const intent = await orchestra.prepareSwap({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...' -}) +```javascript title="Read-only balance checks" +const readOnlyAccount = await account.toReadOnlyAccount() +const balance = await readOnlyAccount.getBalance() +const tokenBalance = await readOnlyAccount.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') ``` -### EVM USDT to Spark BTC +*** -EVM token sources use the WDK account's `transfer({ token, recipient, amount })` path. The source account needs native gas for its chain. +## Get Started +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/guides/get-started +Description: Install @tetherto/wdk-wallet-evm-7702-gasless and create an EIP-7702 gasless account. -```javascript title="Prepare BSC USDT to Spark BTC" -const bsc = await wdk.getAccount('bsc', 0) -const spark = await wdk.getAccount('spark', 0) +This guide shows how to install the module, create a sponsored EIP-7702 gasless wallet, and get the account address. -const orchestra = new Orchestra(bsc, { - sourceChain: 'bsc', - apiKey: process.env.FLASHNET_API_KEY, - sourceTokenAddresses: { - 'bsc:USDT': '0x55d398326f99059ff775485246999027b3197955' - } -}) +## Install -const intent = await orchestra.prepareSwap({ - fromToken: 'bsc:USDT', - toToken: 'spark:BTC', - fromTokenAmount: 5000000n, - recipient: await spark.getAddress() -}) +```bash +npm install @tetherto/wdk-wallet-evm-7702-gasless ``` -### Bitcoin L1 source +## Create a Sponsored Wallet -Bitcoin L1 can be a source or destination. For Bitcoin source routes, the package submits `bitcoinTxid` to Orchestra and can retry `tx_not_found` or `vout_not_found` submit responses with the same idempotency key while the transaction propagates. + +Replace `''` with a smart-account implementation address that you have verified for the target chain. The EOA delegates execution to this address. + -```javascript title="Execute Bitcoin L1 to Spark BTC" -const bitcoin = await wdk.getAccount('bitcoin', 0) -const spark = await wdk.getAccount('spark', 0) +```javascript title="Create a sponsored EIP-7702 gasless wallet" +import WalletManagerEvm7702Gasless from '@tetherto/wdk-wallet-evm-7702-gasless' -const orchestra = new Orchestra(bitcoin, { - sourceChain: 'bitcoin', - apiKey: process.env.FLASHNET_API_KEY +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy' }) -const intent = await orchestra.prepareSwap({ - fromToken: 'bitcoin:BTC', - toToken: 'spark:BTC', - fromTokenAmount: 100000n, - recipient: await spark.getAddress() -}) +const account = await wallet.getAccount(0) +const address = await account.getAddress() -await saveSwap(intent) +console.log('EOA address:', address) +``` -const submitted = await orchestra.executeSwapIntent(intent, { - feeRate: 12n, - confirmationTarget: 2 +`getAddress()` returns the EOA address. The module signs EIP-7702 authorization only when the account is not already delegated to the configured `delegationAddress`. + +## Use Paymaster-token Mode + +If the user pays gas with an ERC-20 paymaster token, configure `paymasterToken` instead of `isSponsored: true`. + +```javascript title="Create a paymaster-token wallet" +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + transferMaxFee: 100000n // 0.1 USDT when the token has 6 decimals }) ``` -### Destination Lightning +## Clean Up -Orchestra supports destination Lightning routes where the live route matrix exposes them. Pass a BOLT11 invoice or Lightning Address as `recipient`, and include refund metadata required by the route. +Call `dispose()` when the account or wallet manager is no longer needed. -```javascript title="Prepare USDT to destination Lightning" -const intent = await orchestra.prepareSwap({ - fromToken: 'bsc:USDT', - toToken: 'lightning:BTC', - fromTokenAmount: 5000000n, - recipient: bolt11Invoice, - refundChain: 'bsc', - refundAddress: await bsc.getAddress() -}) +```javascript title="Dispose account state" +account.dispose() +wallet.dispose() ``` -Lightning as a source is not supported through this package's standard `swidge()` or `executeSwapIntent()` flow. +## Next Steps + +- Review [configuration](/sdk/wallet-modules/wallet-evm-7702-gasless/configuration) for fee modes and provider failover. +- Learn how to [send transactions](/sdk/wallet-modules/wallet-evm-7702-gasless/guides/send-transactions). +- Learn how to [transfer ERC-20 tokens](/sdk/wallet-modules/wallet-evm-7702-gasless/guides/transfer-tokens). *** -## Orchestra State and Recovery -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery -Description: Persist Orchestra intents and resume routes after source payment, submit, or process failure. +## Handle Errors +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/guides/handle-errors +Description: Handle EIP-7702 gasless wallet configuration, paymaster, fee-limit, and receipt states. -This guide covers [the production split flow](#production-split-flow), [state callbacks](#state-callbacks), [resume rules](#resume-rules), and [status tracking](#status-tracking). +This guide explains the main error cases exposed by `@tetherto/wdk-wallet-evm-7702-gasless`. -## Production split flow +## Configuration Errors -Use `prepareSwap()` and `executeSwapIntent()` when funds are at risk. The split flow gives the host wallet a persistence boundary before the source payment is sent. +Account creation and per-call config overrides can throw `ConfigurationError` when required fields are missing. The wallet manager stores the config, then account creation validates it before the account is used. -1. `prepareSwap()` creates an Orchestra quote and reserves a deposit address. -2. The app persists the returned intent. -3. `executeSwapIntent()` sends the source payment and submits the transfer id. -4. The app persists the submitted state. -5. The app tracks status with `getOrderStatus()`, `getSwidgeStatus()`, `waitForCompletion()`, or `subscribeOrder()`. +```javascript title="Handle configuration errors" +import WalletManagerEvm7702Gasless, { ConfigurationError } from '@tetherto/wdk-wallet-evm-7702-gasless' -```javascript title="Split flow with persisted state" -const intent = await orchestra.prepareSwap({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...' -}) +try { + const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + isSponsored: true + }) -await saveSwap(intent) + const account = await wallet.getAccount(0) + console.log(await account.getAddress()) +} catch (error) { + if (error instanceof ConfigurationError) { + console.error('Invalid wallet configuration:', error.message) + } +} +``` -const submitted = await orchestra.executeSwapIntent(intent) -await saveSwap(submitted) +Common configuration errors include missing `provider`, `bundlerUrl`, `delegationAddress`, or `paymasterToken` when the account is not sponsored. -const finalStatus = await orchestra.waitForCompletion(submitted, { - onStatus: async (status) => { - await saveOrderStatus(status) +## Paymaster Token Errors + +Token paymaster sends can fail when the account does not have enough paymaster-token balance to repay the paymaster. + +```javascript title="Handle paymaster balance errors" +try { + const result = await account.sendTransaction({ + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + value: 0n, + data: '0x' + }) +} catch (error) { + if (error.message.includes('not enough funds')) { + console.error('Insufficient paymaster token balance') + } else { + console.error('Transaction failed:', error.message) } -}) +} ``` -`saveSwap` and `saveOrderStatus` are your app code, not package exports. Back them with durable storage before moving real funds. +If a generic ERC-7677 paymaster does not support the configured token, the operation throws an error that includes `Token

is not supported by the paymaster.` -## State callbacks +## Paymaster Address Mismatch -Use `onStateChange` to persist every state transition that can affect funds. +When `paymasterAddress` is configured, the module checks it against the paymaster returned by the RPC. A mismatch throws `ConfigurationError`. -```javascript title="Persist state callbacks" -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - onStateChange: async (event, state) => { - await saveSwapState(event, state) +```javascript title="Paymaster address mismatch" +try { + await account.quoteSendTransaction({ + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + value: 0n, + data: '0x' + }) +} catch (error) { + if (error instanceof ConfigurationError && error.message.includes('paymasterAddress mismatch')) { + console.error('Unexpected paymaster address returned by RPC') } -}) +} ``` -State events: +## Transfer Fee Cap -| Event | Meaning | -|---|---| -| `intent_created` | Quote exists and has a deposit address. No source funds moved. | -| `source_payment_started` | The package is about to broadcast or send the source payment. Persist before the callback returns. | -| `source_payment_sent` | Source payment returned a transaction id. | -| `submitted` | Orchestra accepted the source transaction and created or updated the order. | +`transfer()` throws when paymaster-token fee estimation meets or exceeds `transferMaxFee`. -Persist the full state object. Do not store only the order id. Recovery may need the quote id, deposit address, source transaction hash, read token, source chain, and submit idempotency key. +```javascript title="Handle transfer fee cap" +try { + await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + amount: 1000000n + }) +} catch (error) { + if (error.message.includes('Exceeded maximum fee')) { + console.error('Transfer cancelled because the estimated fee exceeded transferMaxFee') + } +} +``` -## Resume rules +## Receipt Not Included Yet -Call `resumeSwap(savedState, options?)` with the most complete saved state. +Receipt methods return `null` before the UserOperation is included. -```javascript title="Resume from saved state" -const next = await orchestra.resumeSwap(savedState) -await saveSwap(next) +```javascript title="Poll receipt" +const txReceipt = await account.getTransactionReceipt(userOperationHash) + +if (txReceipt === null) { + console.log('UserOperation is not included yet') +} ``` -`resumeSwap()` follows these rules: +## Dispose of Sensitive State -| Saved state | Behavior | -|---|---| -| Has `orderId` | Reads order status. | -| Has `sourceTxHash` | Submits or re-submits the source transaction id. | -| Has only the intent | Refuses to send a fresh source payment unless `allowNewSourcePayment: true` is set. | +Use `dispose()` when the account is no longer needed. -Use `allowNewSourcePayment: true` only after checking wallet history for a prior payment to the quote deposit address. +```javascript title="Dispose state" +try { + const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + amount: 1000000n + }) +} finally { + account.dispose() + wallet.dispose() +} +``` -```javascript title="Resume an intent only after wallet-history recovery" -await orchestra.resumeSwap(intentOnlyState, { - allowNewSourcePayment: true -}) +*** + +## Manage Accounts +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/guides/manage-accounts +Description: Manage account indices, derivation paths, read-only accounts, and existing EVM accounts. + +This guide explains how to derive accounts, use custom paths, create read-only accounts, and wrap an existing `WalletAccountEvm`. + +## Get an Account by Index + +`getAccount(index)` derives accounts with the EVM BIP-44 path suffix `0'/0/{index}`. + +```javascript title="Get accounts by index" +const account0 = await wallet.getAccount() +const account1 = await wallet.getAccount(1) + +console.log(await account0.getAddress()) +console.log(await account1.getAddress()) ``` -## Submit an existing source transaction +`getAccount()` defaults to index `0`. -Use `submitSourceTx()` when your app already has the source transaction hash and should not send another source payment. +## Get an Account by Path -```javascript title="Submit an existing source transaction" -const submitted = await orchestra.submitSourceTx( - intent, - 'spark_transfer_existing', - { - sourceNetworkFee: 3n - } -) +Use `getAccountByPath(path)` when you need a specific derivation path suffix. -await saveSwap(submitted) +```javascript title="Get account by path" +const account = await wallet.getAccountByPath("0'/0/5") + +console.log(account.path) +console.log(account.index) ``` -## Submit failure recovery +The full derivation path is based on the EVM account path convention, for example `m/44'/60'/0'/0/5`. -If submit fails after source payment, the package throws `OrchestraSubmitError`. Persist `error.state` before retrying. +## Convert to a Read-only Account -```javascript title="Recover after submit failure" -try { - const submitted = await orchestra.executeSwapIntent(intent) - await saveSwap(submitted) - return submitted -} catch (err) { - if (err.name !== 'OrchestraSubmitError') throw err +Use `toReadOnlyAccount()` when a flow needs balances, quotes, receipts, allowances, or signature verification without signing access. - await saveSwap(err.state) - return await orchestra.resumeSwap(err.state) -} +```javascript title="Create a read-only account" +const account = await wallet.getAccount(0) +const readOnlyAccount = await account.toReadOnlyAccount() + +const balance = await readOnlyAccount.getBalance() ``` -## Status tracking +Read-only accounts do not expose `sign()`, `signTypedData()`, `sendTransaction()`, `transfer()`, or `approve()`. -Use `waitForCompletion()` for polling: +## Create a Read-only Account from an Address -```javascript title="Poll until terminal status" -const finalStatus = await orchestra.waitForCompletion(submitted, { - pollIntervalMs: 5000, - timeoutMs: 7200000, - onStatus: async (status) => { - await saveOrderStatus(status) - } +Use the verified EIP-7702 implementation address for the target chain. The placeholder below must be replaced before use. + +```javascript title="Read-only account from address" +import { WalletAccountReadOnlyEvm7702Gasless } from '@tetherto/wdk-wallet-evm-7702-gasless' + +const readOnlyAccount = new WalletAccountReadOnlyEvm7702Gasless('0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + isSponsored: true }) ``` -Use `subscribeOrder()` for SSE status updates: +## Wrap an Existing EVM Account -```javascript title="Subscribe to order status" -const subscription = orchestra.subscribeOrder(submitted, { - onStatus: (status) => { - console.log(status) - }, - onError: (err) => { - console.error(err) - }, - onClose: () => { - console.log('Subscription closed') - } +`WalletAccountEvm7702Gasless` can wrap a `WalletAccountEvm` instance. Use this when your application already manages the base EVM account. + +```javascript title="Wrap WalletAccountEvm" +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +import { WalletAccountEvm7702Gasless } from '@tetherto/wdk-wallet-evm-7702-gasless' + +const evmAccount = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://rpc.mevblocker.io/fast' }) -subscription.close() +const gaslessAccount = new WalletAccountEvm7702Gasless(evmAccount, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + isSponsored: true +}) ``` -For direct browser SSE, provide `sseToken` or `getSseToken`, or proxy SSE from a backend. Admin keys should stay on trusted infrastructure. +## Clean Up Account State + +```javascript title="Dispose accounts" +gaslessAccount.dispose() +wallet.dispose() +``` + +`dispose()` clears the account quote cache and disposes the wrapped EVM account. *** -## Handle Orchestra Errors -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-orchestra/guides/handle-errors -Description: Recover from Orchestra API, state, submit, timeout, and status errors. +## Send Transactions +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/guides/send-transactions +Description: Quote and send EVM transactions through EIP-7702 gasless UserOperations. -This guide explains how to [handle submit failures](#submit-failures), [handle state errors](#state-errors), [handle API and timeout errors](#api-and-timeout-errors), and [dispose wallet resources](#dispose-wallet-resources). +This guide explains how to quote and send EVM transactions through ERC-4337 UserOperations. -## Error classes +## Send a Transaction -All package-specific errors extend `OrchestraError`. +Use `sendTransaction(tx)` with a single transaction object or an array of transaction objects. -| Error | When it is thrown | Useful fields | -|---|---|---| -| `OrchestraError` | Base class for package-specific failures. | `code`, `details` | -| `OrchestraApiError` | Orchestra returns an API error or an invalid API response. | `code`, `status`, `details` | -| `OrchestraStateError` | Input state is incomplete, unsafe to resume, expired, or incompatible with the requested source payment. | `code`, `details` | -| `OrchestraSubmitError` | Source payment was sent, but submit, post-submit validation, or post-submit state persistence failed. | `state`, `cause` | -| `OrchestraTimeoutError` | HTTP request or wait operation exceeds its timeout. | `code`, `details` | +```javascript title="Send a transaction" +const result = await account.sendTransaction({ + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + value: 1000000000000000n, + data: '0x' +}) -## Submit failures +console.log('UserOperation hash:', result.hash) +console.log('Fee:', result.fee) +``` -`OrchestraSubmitError` is the most important error for funds-moving flows. It means a source payment may already have been sent. Persist `err.state` before retrying or resuming. +The returned `hash` is a UserOperation hash. Use `getUserOperationReceipt(hash)` or `getTransactionReceipt(hash)` to check inclusion. -```javascript title="Persist submit failure state" -try { - const submitted = await orchestra.executeSwapIntent(intent) - await saveSwap(submitted) - return submitted -} catch (err) { - if (err.name !== 'OrchestraSubmitError') throw err +## Send a Batch - await saveSwap(err.state) - return await orchestra.resumeSwap(err.state) +```javascript title="Send a batch" +const result = await account.sendTransaction([ + { + to: '0x1111111111111111111111111111111111111111', + value: 0n, + data: '0x' + }, + { + to: '0x2222222222222222222222222222222222222222', + value: 0n, + data: '0x' + } +]) +``` + +## Quote Before Sending + +Use `quoteSendTransaction(tx)` to estimate the fee without submitting the UserOperation. + +```javascript title="Quote then send" +const tx = { + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + value: 0n, + data: '0x' } + +const quote = await account.quoteSendTransaction(tx) +console.log('Estimated fee:', quote.fee) + +const result = await account.sendTransaction(tx) +console.log('UserOperation hash:', result.hash) ``` -Common submit-failure causes include: +For paymaster-token mode, the fee is returned in the paymaster token's base units. For sponsorship mode, the quote returns `fee: 0n`. -- Orchestra rejected or could not find a newly broadcast source transaction. -- Status validation failed after Orchestra accepted the source payment. -- Your `onStateChange` persistence callback failed after submit. -- Source network fee was unavailable while a fee cap required it. +## Quote Reuse -For Bitcoin source routes, the package retries `tx_not_found` and `vout_not_found` submit responses with the same idempotency key because a newly broadcast Bitcoin transaction may need time to propagate. +Owned accounts cache a recently quoted paymaster-token transaction for up to 2 minutes. If `sendTransaction()` receives the same transaction during that window, the account first reads the current EntryPoint nonce. It reuses the built UserOperation only when the nonce still matches; if the nonce moved, it quotes again. A send that needs a fresh EIP-7702 authorization also rebuilds the UserOperation before submission. -## State errors + +Cache identity does not include per-call fee-mode configuration. Use the same fee-mode and paymaster settings for `quoteSendTransaction()` and the matching `sendTransaction()`. Do not quote with one paymaster token or mode and send the same transaction with another while the quote is cached. + -`OrchestraStateError` is thrown before unsafe operations, including: +## Override Fee Mode for One Send -- calling a write method without a writable WDK account -- passing both `fromTokenAmount` and `toTokenAmount` -- trying to resume an intent-only state without `allowNewSourcePayment: true` -- using an expired quote before source payment -- missing a source token address for an EVM token source -- missing a Spark token identifier for a non-BTC Spark token -- omitting a recipient when the destination is not the source account +```javascript title="Per-call fee config" +const result = await account.sendTransaction({ + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + value: 0n, + data: '0x' +}, { + isSponsored: true, + sponsorshipPolicyId: 'sp_special_case' +}) +``` -```javascript title="Handle unsafe resume" -try { - await orchestra.resumeSwap(savedIntent) -} catch (err) { - if (err.name === 'OrchestraStateError') { - console.error('Recovery needs a source transaction or wallet-history check:', err.message) - } -} +## Read Receipts + +```javascript title="Read receipts" +const userOpReceipt = await account.getUserOperationReceipt(result.hash) +const txReceipt = await account.getTransactionReceipt(result.hash) ``` -Use `allowNewSourcePayment: true` only after checking wallet history for a prior payment to the quote deposit address. +`getTransactionReceipt()` returns `null` until the bundler receipt includes an EVM transaction hash. -## API and timeout errors +*** -Catch `OrchestraApiError` and `OrchestraTimeoutError` separately when you need to distinguish API failures from local state failures. +## Sign and Verify Messages +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/guides/sign-verify-messages +Description: Sign and verify EVM messages and EIP-712 typed data. -```javascript title="Handle API and timeout failures" -try { - const status = await orchestra.getOrderStatus(submitted) - console.log(status.order?.status ?? status.status) -} catch (err) { - if (err.name === 'OrchestraApiError') { - console.error('Orchestra API failed:', err.code, err.status) - } else if (err.name === 'OrchestraTimeoutError') { - console.error('Timed out waiting for Orchestra:', err.message) - } else { - throw err - } -} +This guide explains how to sign and verify messages with `WalletAccountEvm7702Gasless`. + +## Sign a Message + +```javascript title="Sign a message" +const signature = await account.sign('Hello, EIP-7702') +console.log(signature) ``` -## Status errors +`sign(message)` delegates to the wrapped EVM account. -`getOrderStatus()` requires an `orderId`, `quoteId`, or `sourceTxHash`. Scoped client-key status reads also need the `readToken` returned in the submitted state. +## Verify a Message -```javascript title="Read status with submitted state" -const status = await orchestra.getOrderStatus(submitted) +```javascript title="Verify a message" +const isValid = await account.verify('Hello, EIP-7702', signature) +console.log('Valid:', isValid) ``` -When status polling runs too long, `waitForCompletion()` throws `OrchestraTimeoutError`. +`verify(message, signature)` is available on both owned and read-only accounts. -## Dispose wallet resources +## Sign EIP-712 Typed Data -Dispose WDK wallet accounts after a route flow completes or fails. Keep the persisted Orchestra state until the order is terminal or your recovery policy has completed. +```javascript title="Sign typed data" +const signature = await account.signTypedData({ + domain: { + name: 'Example App', + version: '1', + chainId: 1, + verifyingContract: '0x0000000000000000000000000000000000000000' + }, + types: { + Transfer: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' } + ] + }, + message: { + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + amount: '1000000' + } +}) +``` -```javascript title="Dispose wallet resources" -try { - const submitted = await orchestra.executeSwapIntent(intent) - await saveSwap(submitted) -} finally { - account.dispose?.() -} +## Verify EIP-712 Typed Data + +```javascript title="Verify typed data" +const isValid = await account.verifyTypedData({ + domain: { + name: 'Example App', + version: '1', + chainId: 1, + verifyingContract: '0x0000000000000000000000000000000000000000' + }, + types: { + Transfer: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' } + ] + }, + message: { + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + amount: '1000000' + } +}, signature) ``` -For in-flight routes, do not delete persisted intent, submit, order id, read token, or source transaction data just because the account object was disposed. +Read-only accounts can verify signatures but cannot sign. *** -*** +## Transfer Tokens +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/guides/transfer-tokens +Description: Transfer ERC-20 tokens through EIP-7702 gasless UserOperations. -## Rhino.fi Swidge Overview -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi -Description: Overview of the @rhino.fi/wdk-protocol-swidge-rhinofi module for Rhino.fi cross-chain routes. +This guide explains how to quote and send ERC-20 transfers. -The Rhino.fi Swidge module lets WDK EVM accounts quote and execute cross-chain swaps and bridges through Rhino.fi using the shared `SwidgeProtocol` interface. +## Transfer an ERC-20 Token -Use this module when an app needs authenticated Rhino.fi quotes, EVM source-chain execution, live token discovery, status polling, and WDK-standard fee and status shapes. +Use `transfer(options)` with the token address, recipient, and amount in token base units. -## Features +```javascript title="Transfer ERC-20 tokens" +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + amount: 1000000n +}) -- **Unified route interface**: Implements `quoteSwidge()`, `swidge()`, `getSwidgeStatus()`, `getSupportedChains()`, and `getSupportedTokens()`. -- **Rhino.fi routing**: Quotes and executes cross-chain swap and bridge routes supported by Rhino.fi. -- **EVM source support**: Signs source-chain deposits through `@tetherto/wdk-wallet-evm` accounts, including ERC-4337 accounts. -- **Authenticated API calls**: Uses a Rhino.fi API key for quotes, execution, discovery, and status. -- **Config caching**: Caches Rhino.fi chain and token config to reduce repeated API calls. -- **Fee controls**: Applies optional `maxNetworkFeeBps` and `maxProtocolFeeBps` limits before execution. -- **Status mapping**: Maps Rhino.fi operation states into canonical WDK `SwidgeStatus` values. -- **Typed errors**: Exposes module-specific errors for configuration, unsupported routes, fee limits, unknown operations, and execution failures. +console.log('UserOperation hash:', result.hash) +console.log('Fee:', result.fee) +``` -## Supported Routes +## Quote a Transfer -Call `getSupportedChains()` and `getSupportedTokens()` at runtime because Rhino.fi controls the live route set. Use the provider-maintained [Supported Chains](https://docs.rhino.fi/get-started/supported-chains) page as route-support context before exposing routes in production UIs. +```javascript title="Quote a transfer" +const quote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + amount: 1000000n +}) -| Ecosystem | Source-chain support | Notes | -|-----------|----------------------|-------| -| EVM | Supported | Uses WDK EVM accounts to sign deposits. | -| Solana | Planned | Destination support depends on Rhino.fi route availability. | -| TON | Planned | Destination support depends on Rhino.fi route availability. | -| Tron | Planned | Destination support depends on Rhino.fi route availability. | +console.log('Estimated fee:', quote.fee) +``` -## Execution Model +`quoteTransfer()` builds the same ERC-20 transfer transaction shape that `transfer()` sends. -`swidge()` submits the source-chain deposit after any required ERC-20 approval. It resolves when the deposit transaction is broadcast, while cross-chain settlement continues asynchronously. +## Cap Transfer Fees -Use `getSwidgeStatus(result.id)` to track the route to completion. +In paymaster-token mode, set `transferMaxFee` to cancel `transfer()` when the estimated fee meets or exceeds the cap. -`swidge()` can approve tokens and submit an EVM deposit transaction. Show the quote, fee breakdown, recipient, source token, destination token, and destination chain before calling it. +Replace `''` with a smart-account implementation address that you have verified for the target chain. -## Next Steps +```javascript title="Cap transfer fee" +import WalletManagerEvm7702Gasless from '@tetherto/wdk-wallet-evm-7702-gasless' + +const wallet = new WalletManagerEvm7702Gasless(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + delegationAddress: '', + bundlerUrl: 'https://api.pimlico.io/v2/1/rpc?apikey=YOUR_KEY', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + transferMaxFee: 100000n // 0.1 USDT when the token has 6 decimals +}) +``` + + +`transferMaxFee` applies to `transfer()` in paymaster-token mode. Sponsored transfers return `fee: 0n`. + + +## Approve a Spender + +Use `approve(options)` when a dapp contract needs allowance for an ERC-20 token. + +```javascript title="Approve a spender" +const result = await account.approve({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + spender: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + amount: 1000000n +}) +``` + + +On Ethereum mainnet, USDT requires an existing non-zero allowance to be reset to `0` before setting a new non-zero allowance. The module throws before sending when this rule would be violated. + + +*** + +## Wallet EVM 7702 Gasless Usage +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/usage +Description: Guide to using the @tetherto/wdk-wallet-evm-7702-gasless module. + +# Usage + +The `@tetherto/wdk-wallet-evm-7702-gasless` module lets EVM EOAs submit gasless transactions through EIP-7702 delegation and ERC-4337 UserOperations. + + + +Install the package and create your first EIP-7702 gasless account. + + +Work with account indices, derivation paths, and existing EVM accounts. + + +Query native, ERC-20, and paymaster token balances. + + +Quote and send EVM transactions through UserOperations. + + +Transfer ERC-20 tokens and cap paymaster-token fees. + + +Sign messages and EIP-712 typed data with the underlying EVM account. + + +Handle configuration, paymaster, fee-limit, and receipt states. + + - -Configure API authentication, fee caps, API base URL, and config caching. + +Get started with WDK in a Node.js environment. - -Install the package, discover support, quote a route, execute a route, and poll status. + +Compare WDK wallet modules across supported chains. - -Review constructor options, methods, config fields, status mapping, fee mapping, and errors. + +Review EIP-7702 gasless wallet configuration. + + +Review classes, methods, config types, and return values. ---- +*** -## Need Help? +### Need Help? -*** -## Rhino.fi Swidge API Reference -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi/api-reference -Description: API reference for @rhino.fi/wdk-protocol-swidge-rhinofi. - -## RhinofiProtocol +*** -`RhinofiProtocol` extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols` and implements the shared WDK swidge methods. +## Smart accounts (ERC-4337) +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337 +Description: Create EVM smart accounts that submit UserOperations through ERC-4337 bundler and paymaster infrastructure. -```javascript -import RhinofiProtocol, { - AccountRequiredError, - ConfigurationError -} from '@rhino.fi/wdk-protocol-swidge-rhinofi' +Use the ERC-4337 wallet module when your app needs EVM smart accounts, UserOperations, or sponsored and token-paid transaction flows. -const rhinofi = new RhinofiProtocol(account, config) -``` +## Features -## Constructor +- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases +- **EVM Derivation Paths**: Support for BIP-44 standard derivation paths for Ethereum (m/44'/60') +- **Multi-Account Management**: Create and manage multiple account abstraction wallets from a single seed phrase +- **ERC-4337 Support**: Full implementation of ERC-4337 account abstraction standard +- **UserOperation Management**: Create and send UserOperations through bundlers +- **Separated Submission Flow**: Sign one UserOperation, review or quote it, then submit the same operation +- **Parallel Nonce Lanes**: Opt into independent ERC-4337 nonce keys for concurrent UserOperations +- **Message Signing**: Sign and verify messages using EVM cryptography +- **ERC20 Support**: Query native token and ERC20 token balances using smart contract interactions +- **TypeScript Support**: Full TypeScript definitions included +- **Memory Safety**: Secure private key management with memory-safe HDNodeWallet implementation +- **Bundler Integration**: Support for ERC-4337 bundler services +- **Gas Optimization**: Paymaster support and gas estimation for UserOperations +- **Fee Estimation**: Dynamic fee calculation with bundler-aware estimation +- **EIP-712 Typed Data Support**: Sign and verify EIP-712 structured typed data +- **Batch Token Balance Queries**: Query multiple ERC20 token balances in a single call -```typescript -new RhinofiProtocol(account?, config) -``` +## Supported Networks -| Account | Available operations | -|---------|----------------------| -| `WalletAccountEvm` | Discovery, quote, status, and execution. | -| `WalletAccountEvmErc4337` | Discovery, quote, status, and execution through a smart account. | -| `WalletAccountReadOnlyEvm` | Discovery and quotes for routes that do not need signing. | -| `undefined` | Discovery and account-independent setup when route context allows it. | +This package works with any EVM-compatible blockchain, including: -## Methods +- **Ethereum Mainnet** +- **Ethereum Testnets** (Sepolia) +- **Other EVM Chains** (Polygon, Arbitrum, Avalanche C-chain, Plasma etc.) -| Method | Description | -|--------|-------------| -| `quoteSwidge(options)` | Returns a non-binding Rhino.fi route quote. | -| `swidge(options, config?)` | Executes a route and returns when the source deposit is broadcast. | -| `getSwidgeStatus(id, options?)` | Maps Rhino.fi operation state to WDK `SwidgeStatus`. | -| `getSupportedChains()` | Returns chains supported by Rhino.fi config. | -| `getSupportedTokens(options?)` | Returns tokens supported by Rhino.fi config, optionally filtered by chain context. | +## Next Steps -### `quoteSwidge(options)` + + +Get started with WDK in a Node.js environment + + +Get started with WDK's EVM with ERC-4337 Wallet configuration + + +Get started with WDK's EVM with ERC-4337 Wallet API + + +Get started with WDK's EVM with ERC-4337 Wallet usage + + -```typescript -quoteSwidge(options: SwidgeOptions): Promise -``` +*** -The source chain is derived from the account when required by the route. +## Need Help? -### `swidge(options, config?)` + -```typescript -swidge( - options: SwidgeOptions, - config?: RhinofiProtocolConfig -): Promise -``` +*** -Requires a writable WDK EVM account. The method submits the source-chain deposit and returns the operation ID and source transaction hash. +## Wallet EVM ERC-4337 API Reference +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/api-reference +Description: Complete API documentation for @tetherto/wdk-wallet-evm-erc-4337 -### `getSwidgeStatus(id, options?)` +## Table of Contents -```typescript -getSwidgeStatus( - id: string, - options?: SwidgeStatusOptions -): Promise -``` +| Class | Description | Methods | +|-------|-------------|---------| +| [WalletManagerEvmErc4337](#walletmanagerevmerc4337) | Main class for managing ERC-4337 EVM wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. | [Constructor](#constructor), [Methods](#methods) | +| [WalletAccountEvmErc4337](#walletaccountevmerc4337) | Individual ERC-4337 wallet account implementation. Extends `WalletAccountReadOnlyEvmErc4337` and implements `IWalletAccount`. | [Constructor](#constructor-1), [Methods](#methods-1), [Properties](#properties) | +| [WalletAccountReadOnlyEvmErc4337](#walletaccountreadonlyevmerc4337) | Read-only ERC-4337 wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-2), [Methods](#methods-2) | +| [ConfigurationError](#configurationerror) | Error thrown when the wallet configuration is invalid or has missing required fields. | - | -Use the `id` returned by `swidge()`. +## WalletManagerEvmErc4337 -## Config Type +The main class for managing ERC-4337 EVM wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. -```typescript -type RhinofiProtocolConfig = { - apiKey: string - apiBaseUrl?: string - maxNetworkFeeBps?: number | bigint - maxProtocolFeeBps?: number | bigint - configTtlMs?: number -} -``` +### Fee Rate Behavior -## Status Mapping +Internally, `getFeeRates()` applies these multipliers to the base fee: -| Rhino.fi state | WDK status | -|----------------|------------| -| `PENDING`, `PENDING_CONFIRMATION`, `DEPOSIT_ACCEPTED`, `ACCEPTED` | `pending` | -| `EXECUTED` | `completed` | -| `SWAP_FAILED` | `refund-pending` | -| `SWAP_FAILED_REFUNDED`, `DEPOSIT_RECEIVED_AFTER_GRACE_PERIOD_REFUNDED` | `refunded` | -| `DEPOSIT_RECEIVED_AFTER_GRACE_PERIOD` | `action-required` | -| `FAILED` | `failed` | -| `CANCELLED` | `cancelled` | +- **Normal**: base fee × 110% +- **Fast**: base fee × 200% -## Fee Mapping +These multipliers are internal (`protected static`) and cannot be imported or overridden. -| Rhino.fi quote fee | WDK fee type | Legacy field | -|--------------------|--------------|--------------| -| `gasFee` plus `sourceGasFee` | `network` | `fee` | -| Platform and percentage fee remainder | `protocol` | `bridgeFee` | +### Constructor -The `network` and `protocol` fee amounts are itemized in `SwidgeFee[]` and denominated in the input token. +```javascript +new WalletManagerEvmErc4337(seed, config) +``` -## Error Types +**Parameters:** +- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes +- `config` (EvmErc4337WalletConfig): Configuration object with common fields and a gas payment mode -All Rhino.fi module errors extend `RhinofiProtocolError`. +**Common config fields (required for all modes):** + - `chainId` (number): The blockchain's ID (e.g., 1 for Ethereum mainnet) + - `provider` (string | Eip1193Provider | Array\): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list + - `bundlerUrl` (string): The URL of the bundler service + - `safeModulesVersion` (string): Must be `'0.3.0'`, the only Safe modules version supported by beta.14 -| Error | When thrown | -|-------|-------------| -| `AccountRequiredError` | `swidge()` is called without a writable account. | -| `ConfigurationError` | Required configuration is missing, such as `apiKey`. | -| `UnsupportedChainError` | A chain is unknown, unsupported, or invalid as a source chain. | -| `UnsupportedTokenError` | A token is unknown or unsupported on the selected chain. | -| `FeeLimitExceededError` | Quoted fees exceed configured fee caps. | -| `UnknownOperationError` | Status is requested for an unknown operation ID. | -| `SwidgeExecutionError` | Rhino.fi quote or execution fails. The `.code` field can carry provider failure codes. | +**Optional common config fields:** + - `parallel` (boolean): Put each send, sign, or transfer in a fresh random 192-bit nonce-key lane + - `nonceKey` (number | bigint | string): Reuse a raw uint192 key or a deterministic named lane; takes precedence over `parallel` -## Legacy Delegations +**Gas payment mode** (one of the following): -Inherited `swap`, `quoteSwap`, `bridge`, and `quoteBridge` calls delegate to `swidge()` and `quoteSwidge()`. Because those legacy option shapes do not carry source-chain context, the source chain must be derivable from the bound account. + + +Fees are paid using an ERC-20 token through a paymaster service. - - -Install, quote, execute, and track Rhino.fi routes. - - -Review the shared WDK swidge protocol shape. - - -*** +- `paymasterUrl` (string): The URL of the paymaster service +- `paymasterAddress` (string): The address of the paymaster smart contract +- `paymasterToken` (object): The paymaster token configuration + - `address` (string): The address of the ERC-20 token used for fees +- `transactionMaxFee` (number | bigint, optional): Maximum fee limit for `sendTransaction()` and `signTransaction()` in paymaster token units +- `transferMaxFee` (number | bigint, optional): Maximum fee limit in paymaster token units -## Rhino.fi Swidge Configuration -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi/configuration -Description: Configuration options for @rhino.fi/wdk-protocol-swidge-rhinofi. +```javascript +const wallet = new WalletManagerEvmErc4337(seedPhrase, { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + safeModulesVersion: '0.3.0', + // Paymaster token mode + paymasterUrl: 'https://api.candide.dev/public/v3/1', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USD₮ + }, + transactionMaxFee: 100000, // Optional: max send/sign fee in token units + transferMaxFee: 100000 // Optional: max fee in token units +}) +``` + + +Fees are sponsored by a third party via a sponsorship policy. -`RhinofiProtocol` requires a Rhino.fi API key. The SDK authenticates every call, including quote and discovery calls. Create and manage API keys in the [Rhino.fi Console](https://console.rhino.fi/). +- `isSponsored` (true): Enables sponsorship mode +- `paymasterUrl` (string): The URL of the paymaster service +- `sponsorshipPolicyId` (string, optional): The sponsorship policy ID ```javascript -import RhinofiProtocol from '@rhino.fi/wdk-protocol-swidge-rhinofi' - -const rhinofi = new RhinofiProtocol(account, { - apiKey: process.env.RHINO_API_KEY, - maxNetworkFeeBps: 50, - maxProtocolFeeBps: 30 +const wallet = new WalletManagerEvmErc4337(seedPhrase, { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + safeModulesVersion: '0.3.0', + // Sponsorship mode + isSponsored: true, + paymasterUrl: 'https://api.candide.dev/public/v3/1', + sponsorshipPolicyId: 'your-policy-id' // Optional }) ``` + + +Fees are paid using the chain's native token (e.g., ETH). -## Constructor +- `useNativeCoins` (true): Enables native coin fee payment +- `transactionMaxFee` (number | bigint, optional): Maximum fee limit for `sendTransaction()` and `signTransaction()` in native token units +- `transferMaxFee` (number | bigint, optional): Maximum fee limit in native token units -```typescript -new RhinofiProtocol(account?, config) +```javascript +const wallet = new WalletManagerEvmErc4337(seedPhrase, { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + safeModulesVersion: '0.3.0', + // Native coins mode + useNativeCoins: true, + transactionMaxFee: 100000000000000n, // Optional: max send/sign fee in wei + transferMaxFee: 100000000000000n // Optional: max fee in wei +}) ``` + + -| Parameter | Description | -|-----------|-------------| -| `account` | Optional WDK EVM account. Writable accounts can execute. Read-only accounts can quote and discover support. | -| `config` | Required `RhinofiProtocolConfig`. Must include `apiKey`. | - -## Configuration Options +### Methods -| Option | Type | Description | -|--------|------|-------------| -| `apiKey` | `string` | Rhino.fi API key. Required for every call. | -| `apiBaseUrl` | `string` | Optional Rhino.fi API base URL override. Use `https://`. | -| `maxNetworkFeeBps` | `number \| bigint` | Rejects execution when network fees exceed this many basis points of the input amount. | -| `maxProtocolFeeBps` | `number \| bigint` | Rejects execution when protocol fees exceed this many basis points of the input amount. | -| `configTtlMs` | `number` | Milliseconds to cache Rhino.fi config and swap-token lists. Defaults to `60000`; set `0` to always fetch fresh. | +| Method | Description | Returns | Throws | +|--------|-------------|---------|--------| +| `getRandomSeedPhrase(wordCount?)` | (static) Returns a random BIP-39 seed phrase | `string` | - | +| `isValidSeedPhrase(seedPhrase)` | (static) Checks if a seed phrase is valid | `boolean` | - | +| `getAccount(index?)` | Returns a wallet account at the specified index | `Promise\` | - | +| `getAccountByPath(path)` | Returns a wallet account at the specified BIP-44 derivation path | `Promise\` | - | +| `getFeeRates()` | Returns current fee rates for transactions | `Promise\<{normal: bigint, fast: bigint}\>` | If no provider | +| `dispose()` | Disposes all wallet accounts, clearing private keys from memory | `void` | - | -## Per-Call Overrides +### Properties -Pass config to `swidge(options, config)` to override fee caps for a single execution. +| Property | Type | Description | +|----------|------|-------------| +| `seed` | `Uint8Array` | The wallet's seed phrase as bytes | -```javascript -await rhinofi.swidge(options, { - maxNetworkFeeBps: 40, - maxProtocolFeeBps: 25 -}) -``` +#### `getRandomSeedPhrase(wordCount?)` (static) +Returns a random BIP-39 seed phrase. -## API Base URL +**Parameters:** +- `wordCount` (12 | 24, optional): The number of words in the seed phrase (default: 12) -Use `https://` API URLs. `http://` URLs can redirect and break authenticated SDK requests. +**Returns:** `string` - The seed phrase +**Example:** ```javascript -const rhinofi = new RhinofiProtocol(account, { - apiKey: process.env.RHINO_API_KEY, - apiBaseUrl: 'https://api.rhino.fi' -}) +const seedPhrase = WalletManagerEvmErc4337.getRandomSeedPhrase() +console.log('Seed phrase:', seedPhrase) // 12 words + +const longSeedPhrase = WalletManagerEvmErc4337.getRandomSeedPhrase(24) +console.log('Long seed phrase:', longSeedPhrase) // 24 words ``` -## Config Caching +#### `isValidSeedPhrase(seedPhrase)` (static) +Checks if a seed phrase is valid. -The module caches Rhino.fi chain config and swap-token lists for `configTtlMs`. +**Parameters:** +- `seedPhrase` (string): The seed phrase to validate + +**Returns:** `boolean` - True if the seed phrase is valid +**Example:** ```javascript -const rhinofi = new RhinofiProtocol(account, { - apiKey: process.env.RHINO_API_KEY, - configTtlMs: 60000 -}) +const isValid = WalletManagerEvmErc4337.isValidSeedPhrase('abandon abandon abandon ...') +console.log('Valid:', isValid) ``` -Set `configTtlMs: 0` when you need every call to fetch fresh provider configuration. - -## Security Notes - -- Store `apiKey` in server-side or secret-managed configuration. -- Use trusted RPC providers for WDK EVM accounts. -- Set fee caps for user-facing flows. -- Ask for user confirmation before calling `swidge()`. +#### `getAccount(index)` +Returns a wallet account at the specified index using BIP-44 derivation. - - -Quote, execute, and track Rhino.fi swidge routes. - - -Detailed method, type, status, fee, and error reference. - - -*** +**Parameters:** +- `index` (number, optional): The index of the account to get (default: 0) -## Rhino.fi Swidge Usage -URL: https://docs.wdk.tether.io/sdk/swidge-modules/swidge-rhinofi/usage -Description: Install and use @rhino.fi/wdk-protocol-swidge-rhinofi for Rhino.fi cross-chain routes. +**Returns:** `Promise\` - The wallet account -## Install +**Example:** +```javascript +// Get first account (index 0) +const account = await wallet.getAccount(0) -```bash -npm install @rhino.fi/wdk-protocol-swidge-rhinofi@1.0.0-beta.2 @tetherto/wdk-wallet-evm +// Get default account +const defaultAccount = await wallet.getAccount() ``` -Install ERC-4337 support when you need smart-account execution: +#### `getAccountByPath(path)` +Returns a wallet account at the specified BIP-44 derivation path. -```bash -npm install @tetherto/wdk-wallet-evm-erc-4337 -``` +**Parameters:** +- `path` (string): The derivation path (e.g., "0'/0/0") -## Create the Protocol +**Returns:** `Promise\` - The wallet account +**Example:** ```javascript -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' -import RhinofiProtocol from '@rhino.fi/wdk-protocol-swidge-rhinofi' +// Full derivation path: m/44'/60'/0'/0/1 +const account = await wallet.getAccountByPath("0'/0/1") +``` -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://arb1.arbitrum.io/rpc' -}) +#### `getFeeRates()` +Returns current fee rates with ERC-4337 specific multipliers. -const rhinofi = new RhinofiProtocol(account, { - apiKey: process.env.RHINO_API_KEY, - maxNetworkFeeBps: 50, - maxProtocolFeeBps: 30 -}) -``` +**Returns:** `Promise\<{normal: bigint, fast: bigint}\>` - Fee rates in wei -## Discover Chains and Tokens +**Throws:** Error if no provider is configured +**Example:** ```javascript -const chains = await rhinofi.getSupportedChains() -const tokens = await rhinofi.getSupportedTokens({ - fromChain: 'ARBITRUM' -}) +const feeRates = await wallet.getFeeRates() +console.log('Normal fee rate:', feeRates.normal, 'wei') // base fee × 1.1 +console.log('Fast fee rate:', feeRates.fast, 'wei') // base fee × 2.0 ``` -The module reads live Rhino.fi chain and token config. The source chain must be derivable from the WDK account for execution. - -## Quote a Route +#### `dispose()` +Disposes all wallet accounts, clearing private keys from memory. +**Example:** ```javascript -const quote = await rhinofi.quoteSwidge({ - fromToken: 'USDT', - toToken: 'USDC', - toChain: 'BASE', - recipient: '0xRecipient...', - fromTokenAmount: 1_000_000n -}) - -console.log('Expected output:', quote.toTokenAmount) -console.log('Minimum output:', quote.toTokenAmountMin) -console.log('Fees:', quote.fees) +// Clean up when done +wallet.dispose() ``` -Use `toTokenAmount` instead of `fromTokenAmount` for exact-output routes when supported by the provider route. +## WalletAccountEvmErc4337 -## Execute a Route +Represents an individual ERC-4337 wallet account. Extends `WalletAccountReadOnlyEvmErc4337` and implements `IWalletAccount`. -After the user confirms the quote, call `swidge()`. +### Constants -```javascript -const result = await rhinofi.swidge({ - fromToken: 'USDT', - toToken: 'USDC', - toChain: 'BASE', - recipient: '0xRecipient...', - fromTokenAmount: 1_000_000n -}) +The following constant is used internally for Safe account address derivation: -console.log('Operation ID:', result.id) -console.log('Source transaction:', result.hash) +```javascript +// Internal: used by predictSafeAddress() for deterministic address generation +const SALT_NONCE = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6' ``` -`swidge()` resolves after the source deposit transaction is broadcast. The destination settlement can continue after the method returns. +> **Note:** This constant is not re-exported from the package entry point. Use `predictSafeAddress()` instead of referencing it directly. -## Track Status +### Constructor ```javascript -const status = await rhinofi.getSwidgeStatus(result.id) - -if (status.status === 'completed') { - console.log('Route completed') -} +new WalletAccountEvmErc4337(seed, path, config) ``` -## Handle Errors +**Parameters:** +- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes +- `path` (string): BIP-44 derivation path (e.g., "0'/0/0") +- `config` (EvmErc4337WalletConfig): Configuration object (same as [WalletManagerEvmErc4337](#constructor)) +**Example:** ```javascript -import { - AccountRequiredError, - ConfigurationError, - FeeLimitExceededError, - RhinofiProtocolError -} from '@rhino.fi/wdk-protocol-swidge-rhinofi' - -try { - await rhinofi.swidge(options) -} catch (error) { - if (error instanceof AccountRequiredError) { - // Bind a writable WDK EVM account before execution. - } else if (error instanceof ConfigurationError) { - // Check apiKey and API configuration. - } else if (error instanceof FeeLimitExceededError) { - // Ask the user to approve the quoted fee or lower the amount. - } else if (error instanceof RhinofiProtocolError) { - // Handle another Rhino.fi module error. +const account = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + safeModulesVersion: '0.3.0', + paymasterUrl: 'https://api.candide.dev/public/v3/1', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' } -} +}) ``` - - -Review API, fee, and config-cache settings. - - -Detailed method, type, status, fee, and error reference. - - -*** +### Methods + +| Method | Description | Returns | Throws | +|--------|-------------|---------|--------| +| `predictSafeAddress(owner, config)` | (static) Predicts the Safe address for a given owner | `string` | - | +| `getAddress()` | Returns the Safe account's address | `Promise\` | - | +| `sign(message)` | Signs a message using the account's private key | `Promise\` | - | +| `verify(message, signature)` | Verifies a message signature | `Promise\` | - | +| `signTransaction(tx, config?)` | Builds and signs one UserOperation without submitting it | `Promise\` | If a non-sponsored fee exceeds max | +| `sendTransaction(tx, config?)` | Builds and sends transactions, or submits a signed UserOperation | `Promise\<{hash: string, fee: bigint}\>` | If a newly built operation exceeds max | +| `quoteSendTransaction(tx, config?)` | Estimates the fee for transactions or a signed UserOperation | `Promise\<{fee: bigint}\>` | - | +| `transfer(options, config?, txOverrides?)` | Transfers ERC20 tokens via UserOperation | `Promise\<{hash: string, fee: bigint}\>` | If fee meets or exceeds max | +| `quoteTransfer(options, config?, txOverrides?)` | Estimates the fee for an ERC20 transfer | `Promise\<{fee: bigint}\>` | - | +| `approve(options, txOverrides?)` | Approves a spender to spend ERC20 tokens | `Promise\<{hash: string, fee: bigint}\>` | - | +| `getBalance()` | Returns the native token balance (in wei) | `Promise\` | - | +| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise\` | - | +| `getPaymasterTokenBalance()` | Returns the paymaster token balance | `Promise\` | - | +| `getAllowance(token, spender)` | Returns current token allowance for a spender | `Promise\` | - | +| `getTransactionReceipt(hash)` | Returns a transaction receipt | `Promise\` | - | +| `getUserOperationReceipt(hash)` | Returns a UserOperation receipt | `Promise\` | - | +| `signTypedData(typedData)` | Signs EIP-712 typed structured data | `Promise\` | - | +| `verifyTypedData(typedData, signature)` | Verifies an EIP-712 typed data signature | `Promise\` | - | +| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise\\>` | - | +| `toReadOnlyAccount()` | Returns a read-only copy of the account | `Promise\` | - | +| `dispose()` | Disposes the wallet account, clearing private keys from memory | `void` | - | + +##### `getAddress()` +Returns the Safe smart contract wallet address (not the underlying EOA address). + +**Returns:** `Promise\` - The Safe account's address + +**Example:** +```javascript +const address = await account.getAddress() +console.log('Safe account address:', address) // 0x... (Smart contract address) +``` -## Wallet Modules Overview -URL: https://docs.wdk.tether.io/sdk/wallet-modules -Description: Explore WDK wallet modules for building self-custodial wallets across supported chains. +##### `sign(message)` +Signs a message using the underlying EOA private key. -The Wallet Development Kit (WDK) provides a set of modules that support multiple blockchain networks. All modules share a common interface, ensuring consistent behavior across different blockchain implementations. +**Parameters:** +- `message` (string): The message to sign -## Supported Networks +**Returns:** `Promise\` - The message signature -This package works with multiple blockchain networks through wallet registration. +**Example:** +```javascript +const message = 'Hello, ERC-4337!' +const signature = await account.sign(message) +console.log('Signature:', signature) +``` - - -Bitcoin Mainnet - - -Ethereum, Sepolia Testnet, L2s, etc. - - -Tron Mainnet - - -TON Mainnet - - -Solana Mainnet - - -Spark Mainnet - - +##### `verify(message, signature)` +Verifies a message signature against the underlying EOA address. -## Classic Wallet Modules +**Parameters:** +- `message` (string): The original message +- `signature` (string): The signature to verify -Standard wallet implementations that use native blockchain tokens for transaction fees: +**Returns:** `Promise\` - True if signature is valid -| Module | Blockchain | Status | Documentation | -|--------|------------|--------|---------------| -| [`@tetherto/wdk-wallet-evm`](https://github.com/tetherto/wdk-wallet-evm) | EVM | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-evm) | -| [`@tetherto/wdk-wallet-ton`](https://github.com/tetherto/wdk-wallet-ton) | TON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-ton) | -| [`@tetherto/wdk-wallet-btc`](https://github.com/tetherto/wdk-wallet-btc) | Bitcoin | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-btc) | -| [`@tetherto/wdk-wallet-spark`](https://github.com/tetherto/wdk-wallet-spark) | Spark | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-spark) | -| [`@tetherto/wdk-wallet-tron`](https://github.com/tetherto/wdk-wallet-tron) | TRON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-tron) | -| [`@tetherto/wdk-wallet-solana`](https://github.com/tetherto/wdk-wallet-solana) | Solana | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-solana) | -| `@tetherto/wdk-wallet-ark` | Ark | In progress | - | +**Example:** +```javascript +const isValid = await account.verify(message, signature) +console.log('Signature valid:', isValid) +``` -## Account Abstraction Wallet Modules +##### `signTransaction(tx, config?)` +Builds and signs one ERC-4337 v0.7 UserOperation without submitting it to the bundler. -Wallet implementations that support [Account Abstraction](/resources/concepts#account-abstraction) for gasless transactions using paymaster tokens like USD₮: +**Parameters:** +- `tx` (EvmErc4337Transaction): One transaction. Batch arrays are not accepted by this method. +- `config` (optional): Per-call configuration override (see [Config Override](#config-override)). -| Module | Blockchain | Status | Documentation | -|--------|------------|--------|---------------| -| [`@tetherto/wdk-wallet-evm-erc4337`](https://github.com/tetherto/wdk-wallet-evm-erc-4337) | EVM | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-evm-erc-4337) | -| [`@tetherto/wdk-wallet-ton-gasless`](https://github.com/tetherto/wdk-wallet-ton-gasless) | TON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-ton-gasless) | -| [`@tetherto/wdk-wallet-tron-gasfree`](https://github.com/tetherto/wdk-wallet-tron-gasfree) | TRON | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-tron-gasfree) | -| `@tetherto/wdk-wallet-solana-jupiterz` | Solana | In progress | - | +**Returns:** `Promise\` - A signed UserOperation that can be quoted or submitted later -## Community Wallet Modules +**Throws:** Error if a non-sponsored operation exceeds `transactionMaxFee`, or a raw `nonceKey` is outside `0..2^192-1` -Wallet modules developed by the community. See the [Community Modules](/sdk/community-modules/) page for more details. +```javascript title="Sign a UserOperation" +const signedUserOperation = await account.signTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +}) +``` - -Community modules are developed and maintained independently. Use your own judgment and proceed at your own risk. + +The returned operation contains the selected nonce lane and fee-mode configuration used while building it. Submit it promptly through the same account and do not mutate it. -| Module | Blockchain | Description | Repository | -|--------|------------|-------------|------------| -| [`@utexo/wdk-wallet-rgb`](https://www.npmjs.com/package/@utexo/wdk-wallet-rgb) | Bitcoin (RGB) | RGB protocol wallet integration for Bitcoin-based smart contracts | [Docs](/sdk/community-modules/wdk-wallet-rgb/) | -| [`@arkade-os/wdk`](https://www.npmjs.com/package/@arkade-os/wdk) | Arkade | Bitcoin wallet module built on the Arkade SDK | [README](https://github.com/arkade-os/arkade-wdk#readme) | -| [`@base58-io/wdk-wallet-cosmos`](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos) | Cosmos | Wallet module for Cosmos-compatible blockchains | [Docs](/sdk/community-modules/wdk-wallet-cosmos/) | +##### `sendTransaction(tx, config?)` +Sends a transaction via UserOperation through the bundler. -## Next Steps +**Parameters:** +- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[] | UserOperationV7): Transaction object, array for batch transactions, or a signed UserOperation + - `to` (string): Recipient address + - `value` (number | bigint): Amount in wei + - `data` (string, optional): Transaction data in hex format + - `callGasLimit`, `verificationGasLimit`, `preVerificationGas` (number | bigint, optional): Per-call overrides for the UserOperation gas limits + - `maxFeePerGas`, `maxPriorityFeePerGas` (number | bigint, optional): Per-call overrides for the EIP-1559 fee pair; set both together. In a batch, only the first transaction's gas overrides apply (see [EvmErc4337Transaction](#evmerc4337transaction)) +- `config` (optional): Per-call configuration override. Beta.14 runtime also reads `parallel` and `nonceKey`, subject to the [published typing limitation](#config-override). -To get started with WDK modules, follow these steps: +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - UserOperation hash and fee -1. Get up and running quickly with our [Quickstart Guide](/start-building/nodejs-bare-quickstart) -2. Choose the modules that best fit your needs from the tables above -3. Check specific documentation for modules you wish to use +**Throws:** Error if a non-sponsored, newly built operation exceeds `transactionMaxFee`, or a raw `nonceKey` is outside `0..2^192-1` -You can also: +When `tx` is a signed `UserOperationV7`, WDK forwards that exact operation to the bundler. It does not rebuild or re-sign it, and it does not apply the current `transactionMaxFee` again. Only submit an operation produced by the same account with the intended fee-mode configuration, and submit it before its nonce becomes stale. -- Learn about key concepts like [Account Abstraction](/resources/concepts#account-abstraction) and other important definitions -- Use one of our ready-to-use examples to be production ready +**Example:** +```javascript +// Single transaction +const result = await account.sendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n, // 1 ETH + data: '0x' +}) +console.log('UserOperation hash:', result.hash) +console.log('Fee paid:', result.fee) -*** +// Batch transactions +const batchResult = await account.sendTransaction([ + { to: '0x...', value: 100000000000000000n }, + { to: '0x...', value: 200000000000000000n } +]) -## Wallet BTC Overview -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc -Description: Overview of the @tetherto/wdk-wallet-btc module +// With per-call config override +const customResult = await account.sendTransaction({ + to: '0x...', + value: 1000000000000000000n +}, { + paymasterToken: { address: '0xNewToken...' } +}) -A simple and secure package to manage BIP-84 (SegWit) and BIP-44 (Legacy) wallets for the Bitcoin blockchain. This package provides a clean API for creating, managing, and interacting with Bitcoin wallets using BIP-39 seed phrases and Bitcoin-specific derivation paths. +// With per-call gas overrides (EIP-1559 fee pair set together) +const fastResult = await account.sendTransaction({ + to: '0x...', + value: 1000000000000000000n, + maxFeePerGas: 30000000000n, // 30 gwei + maxPriorityFeePerGas: 2000000000n // 2 gwei +}) +``` - -**Default Derivation Path Change in v1.0.0-beta.4+** +Beta.14 does not reserve sequential nonces locally. With no lane option, sends use the default key-0 lane and must not overlap. `parallel: true` creates a new random lane; `nonceKey` selects a reusable named or raw lane and takes precedence. Operations sharing a lane remain sequential, so wait for inclusion before reusing it. Distinct lanes are unordered; batch dependent transactions into one UserOperation. -The default derivation path was updated in v1.0.0-beta.4 to use BIP-84 (Native SegWit) instead of BIP-44 (Legacy): +##### `quoteSendTransaction(tx, config?)` +Estimates the fee for a UserOperation without sending it. -- **Previous path** (up to v1.0.0-beta.3): `m/44'/0'/0'/0/{index}` (Legacy addresses) -- **Current path** (v1.0.0-beta.4+): `m/84'/0'/0'/0/{index}` (Native SegWit addresses) +Default-lane, non-sponsored UserOperations built while quoting transaction inputs can be cached internally for up to 2 minutes. When a later default-lane `sendTransaction()` or `signTransaction()` matches, the account performs a lightweight on-chain nonce check before reuse and rebuilds if the nonce has moved. Sponsored quotes do not cache a built operation. A supplied signed UserOperation is evaluated directly rather than rebuilt. -If you're upgrading from an earlier version, existing wallets created with the old path will generate different addresses. Make sure to migrate any existing wallets or use the old path explicitly if needed for compatibility. +This quote method does not resolve or reserve a nonce lane. A later send or sign using `parallel` or `nonceKey` rebuilds in that lane instead of reusing the quote. -Use [`getAccountByPath`](/sdk/wallet-modules/wallet-btc/api-reference#getaccountbypathpath) to supply an explicit derivation path when importing or recreating legacy wallets. + +The cache key contains the transaction but not the per-call fee-mode configuration. Use the same fee-mode and paymaster settings for a quote and its matching send, sign, or transfer. Do not quote under one mode, token, paymaster, or sponsorship policy and execute the same transaction under another while the quote is cached. -## Features +**Parameters:** +- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[] | UserOperationV7): Transaction object, array, or signed UserOperation +- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) -- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases -- **Bitcoin Derivation Paths**: Support for BIP-84 (Native SegWit, default) and BIP-44 (Legacy) derivation paths -- **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase -- **Address Types Support**: Generate Native SegWit (P2WPKH) addresses by default, with Legacy (P2PKH) support via configuration -- **UTXO Management**: Track and manage unspent transaction outputs -- **Offline Transaction Signing**: Sign Bitcoin transactions with `signTransaction()` without broadcasting them -- **Transaction Management**: Create, sign, and broadcast Bitcoin transactions (single recipient per transaction) -- **Fee Estimation**: Dynamic fee calculation via mempool.space API -- **Provider Failover**: Configure ordered Electrum, WebSocket, Blockbook, or custom client fallbacks -- **TypeScript Support**: Full TypeScript definitions included -- **Memory Safety**: Secure private key management with memory-safe implementation -- **Network Flexibility**: Support for mainnet, testnet, and regtest +**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate -## Supported Networks +For a signed UserOperation, sponsored mode returns `0n`. Other modes return a 20% buffered native-gas ceiling in wei. In paymaster-token mode, this signed-operation quote is not a token-denominated paymaster charge. -This package works with Bitcoin networks: +**Example:** +```javascript +const quote = await account.quoteSendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n +}) +console.log('Estimated fee:', quote.fee) +``` -- **Bitcoin Mainnet** (`"bitcoin"`) -- **Bitcoin Testnet** (`"testnet"`) -- **Bitcoin Regtest** (`"regtest"`) +##### `transfer(options, config?, txOverrides?)` +Transfers ERC20 tokens via UserOperation. -### Electrum Server Configuration +**Parameters:** +- `options` (TransferOptions): Transfer options + - `token` (string): ERC20 token contract address + - `recipient` (string): Recipient address + - `amount` (number | bigint): Amount in token base units +- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) +- `txOverrides` (optional): UserOperation gas and fee overrides (see [EvmErc4337GasOverrides](#evmerc4337gasoverrides)) -**Important**: While the package defaults to `electrum.blockstream.info:50001` for convenience, **we strongly recommend configuring your own Electrum server** for production use. +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - UserOperation hash and fee -#### Recommended Approach: +**Throws:** +- Error if fee meets or exceeds `transferMaxFee` +- Error if insufficient token balance +- Error if a raw `nonceKey` is outside `0..2^192-1` -**For Production:** -- Set up your own Fulcrum server for optimal performance and reliability -- Use recent Fulcrum versions that support pagination for high-transaction addresses +**Example:** +```javascript +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USD₮ + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 // 1 USD₮ (6 decimals) +}, { + transferMaxFee: 50000 // Override max fee for this call +}, { + maxFeePerGas: 30000000000n, + maxPriorityFeePerGas: 2000000000n +}) +console.log('Transfer UserOperation hash:', result.hash) +console.log('Transfer fee:', result.fee) +``` -**For Development/Testing:** -- `fulcrum.frznode.com:50001` - Generally faster than default -- `electrum.blockstream.info:50001` - Default fallback +##### `quoteTransfer(options, config?, txOverrides?)` +Estimates the fee for an ERC20 token transfer. -## Next Steps +This method does not resolve or reserve a nonce lane. A later transfer using `parallel` or `nonceKey` rebuilds in that lane. - - -Get started with WDK in a Node.js environment - - -Get started with WDK's Bitcoin Wallet configuration - - -Get started with WDK's Bitcoin Wallet API - - -Get started with WDK's Bitcoin Wallet usage - - +**Parameters:** +- `options` (TransferOptions): Transfer options (same as transfer) +- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) +- `txOverrides` (optional): UserOperation gas and fee overrides (see [EvmErc4337GasOverrides](#evmerc4337gasoverrides)) -*** +**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate -### Need Help? +**Example:** +```javascript +const quote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}) +console.log('Transfer fee estimate:', quote.fee) +``` - +##### `getBalance()` +Returns the Safe account's native token balance. -*** +**Returns:** `Promise\` - Balance in wei -## Wallet BTC API Reference -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/api-reference -Description: Complete API documentation for @tetherto/wdk-wallet-btc +**Example:** +```javascript +const balance = await account.getBalance() +console.log('Native balance:', balance, 'wei') +``` -## Table of Contents +##### `getTokenBalance(tokenAddress)` +Returns the balance of a specific ERC20 token in the Safe account. -| Class | Description | Methods | -|-------|-------------|---------| -| [WalletManagerBtc](#walletmanagerbtc) | Main class for managing Bitcoin wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. | [Constructor](#constructor), [Methods](#methods) | -| [WalletAccountBtc](#walletaccountbtc) | Individual Bitcoin wallet account implementation. Implements `IWalletAccount`. | [Constructor](#constructor-1), [Methods](#methods-1), [Properties](#properties) | -| [WalletAccountReadOnlyBtc](#walletaccountreadonlybtc) | Read-only Bitcoin wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-2), [Methods](#methods-2) | -| [ElectrumTcp](#electrumtcp) | Standard TCP Electrum client. Implements `IBtcClient`. | [Constructor](#constructor-3) | -| [ElectrumTls](#electrumtls) | TLS Electrum client. Implements `IBtcClient`. | [Constructor](#constructor-4) | -| [ElectrumSsl](#electrumssl) | SSL Electrum client. Implements `IBtcClient`. | [Constructor](#constructor-5) | -| [ElectrumWs](#electrumws) | WebSocket Electrum client for browser environments. Implements `IBtcClient`. | [Constructor](#constructor-6), [Methods](#methods-3) | +**Parameters:** +- `tokenAddress` (string): The ERC20 token contract address -## WalletManagerBtc +**Returns:** `Promise\` - Token balance in base units -The main class for managing Bitcoin wallets. -Extends `WalletManager` from `@tetherto/wdk-wallet`. +**Example:** +```javascript +const tokenBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') +console.log('USDT balance:', tokenBalance) // In 6 decimal units +``` -#### Constructor +##### `getPaymasterTokenBalance()` +Returns the balance of the configured paymaster token used for paying fees. +**Returns:** `Promise\` - Paymaster token balance in base units + +**Example:** ```javascript -new WalletManagerBtc(seed, config) +const paymasterBalance = await account.getPaymasterTokenBalance() +console.log('Paymaster token balance:', paymasterBalance) + +// Check if sufficient for transaction +if (paymasterBalance < 10000n) { + console.warn('Low paymaster token balance - may not cover fees') +} ``` + +##### `approve(options, txOverrides?)` +Approves a spender to spend ERC20 tokens on behalf of the Safe account. + **Parameters:** -- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes -- `config` (BtcWalletConfig, optional): Configuration object - - `client` (`IBtcClient | BtcClientDescriptor | Array`, optional): Bitcoin client, descriptor, or ordered failover list - - `network` (string, optional): "bitcoin", "testnet", or "regtest" (default: "bitcoin") - - `bip` (number, optional): BIP address type - 44 (legacy) or 84 (native SegWit) (default: 84) - - `retries` (number, optional): Additional retry attempts when `client` is an array +- `options` (ApproveOptions): Approve options + - `token` (string): ERC20 token contract address + - `spender` (string): The address allowed to spend the tokens + - `amount` (number | bigint): Amount to approve in token base units +- `txOverrides` (optional): UserOperation gas and fee overrides (see [EvmErc4337GasOverrides](#evmerc4337gasoverrides)) -### Methods +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - Transaction result -| Method | Description | Returns | -|--------|-------------|---------| -| `getAccount(index)` | Returns a wallet account at the specified index | `Promise` | -| `getAccountByPath(path)` | Returns a wallet account at the specified derivation path | `Promise` | -| `getFeeRates()` | Returns current fee rates for transactions | `Promise<{normal: bigint, fast: bigint}>` | -| `dispose()` | Disposes all wallet accounts, clearing private keys from memory and closing internal Electrum connections | `void` | +**Example:** +```javascript +const result = await account.approve({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + spender: '0xSpenderContract...', + amount: 1000000n // 1 USD₮ +}, { + callGasLimit: 90000n, + verificationGasLimit: 120000n +}) +console.log('Approval hash:', result.hash) +``` -##### `getAccount(index)` -Returns a wallet account at the specified index using BIP-84 (default) or BIP-44 derivation. +##### `getAllowance(token, spender)` +Returns the current token allowance for the given spender. **Parameters:** -- `index` (number, optional): The index of the account to get (default: 0) +- `token` (string): ERC20 token contract address +- `spender` (string): The spender's address -**Returns:** `Promise` - The wallet account +**Returns:** `Promise\` - The current allowance **Example:** ```javascript -// Returns the account with derivation path: -// For mainnet (bitcoin): m/84'/0'/0'/0/1 -// For testnet or regtest: m/84'/1'/0'/0/1 -const account = await wallet.getAccount(1) +const allowance = await account.getAllowance( + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + '0xSpenderContract...' +) +console.log('Current allowance:', allowance) ``` -##### `getAccountByPath(path)` -Returns a wallet account at the specified derivation path. +##### `getTransactionReceipt(hash)` +Returns a transaction receipt by hash. **Parameters:** -- `path` (string): The derivation path (e.g., "0'/0/0") +- `hash` (string): The transaction hash -**Returns:** `Promise` - The wallet account +**Returns:** `Promise\` - Transaction receipt or null if not mined **Example:** ```javascript -// Returns the account with derivation path: -// For mainnet (bitcoin): m/84'/0'/0'/0/1 -// For testnet or regtest: m/84'/1'/0'/0/1 -const account = await wallet.getAccountByPath("0'/0/1") +const receipt = await account.getTransactionReceipt('0x...') +if (receipt) { + console.log('Confirmed in block:', receipt.blockNumber) +} ``` -##### `getFeeRates()` -Returns current fee rates from mempool.space API. -**Returns:** `Promise<{normal: bigint, fast: bigint}>` - Object containing fee rates in sat/vB -- `normal`: Standard fee rate for confirmation within ~1 hour -- `fast`: Higher fee rate for faster confirmation +##### `getUserOperationReceipt(hash)` +Returns a UserOperation receipt by hash. + +**Parameters:** +- `hash` (string): The UserOperation hash + +**Returns:** `Promise\` - UserOperation receipt or null if not processed **Example:** ```javascript -const feeRates = await wallet.getFeeRates() -console.log('Normal fee rate:', feeRates.normal, 'sat/vB') -console.log('Fast fee rate:', feeRates.fast, 'sat/vB') +const receipt = await account.getUserOperationReceipt('0x...') +if (receipt) { + console.log('UserOp receipt:', receipt) +} ``` -##### `dispose()` -Disposes all wallet accounts, clears sensitive data from memory, and closes internal Electrum connections. +##### `signTypedData(typedData)` +Signs EIP-712 typed structured data using the underlying EOA private key. -**Returns:** `void` +**Parameters:** +- `typedData` (TypedData): The typed data object containing domain, types, primaryType, and message + +**Returns:** `Promise\` - The typed data signature **Example:** ```javascript -wallet.dispose() +const typedData = { + domain: { + name: 'MyDApp', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + types: { + Transfer: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' } + ] + }, + primaryType: 'Transfer', + message: { + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000n + } +} + +const signature = await account.signTypedData(typedData) +console.log('Typed data signature:', signature) ``` -## WalletAccountBtc +##### `verifyTypedData(typedData, signature)` +Verifies an EIP-712 typed data signature against the underlying EOA address. -Represents an individual Bitcoin wallet account. Extends `WalletAccountReadOnlyBtc` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. +**Parameters:** +- `typedData` (TypedData): The original typed data object +- `signature` (string): The signature to verify -#### Constructor +**Returns:** `Promise\` - True if the signature is valid +**Example:** ```javascript -new WalletAccountBtc(seed, path, config) +const isValid = await account.verifyTypedData(typedData, signature) +console.log('Typed data signature valid:', isValid) ``` +##### `getTokenBalances(tokenAddresses)` +Returns balances for multiple ERC20 tokens in a single call. + **Parameters:** -- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes -- `path` (string): Derivation path suffix (e.g., "0'/0/0") -- `config` (BtcWalletConfig, optional): Configuration object - - `client` (`IBtcClient | BtcClientDescriptor | Array`, optional): Bitcoin client, descriptor, or ordered failover list - - `network` (string, optional): "bitcoin", "testnet", or "regtest" (default: "bitcoin") - - `bip` (number, optional): BIP address type - 44 (legacy) or 84 (native SegWit) (default: 84) - - `retries` (number, optional): Additional retry attempts when `client` is an array +- `tokenAddresses` (string[]): Array of ERC20 token contract addresses -### Methods +**Returns:** `Promise\\>` - Map of token address to balance in base units -| Method | Description | Returns | -|--------|-------------|---------| -| `getAddress()` | Returns the account's Bitcoin address | `Promise` | -| `getBalance()` | Returns the total account balance in satoshis, including unconfirmed funds when present | `Promise` | -| `sendTransaction(options, timeoutMs?)` | Sends a Bitcoin transaction and optionally polls until spent inputs disappear from unspent outputs | `Promise<{hash: string, fee: bigint}>` | -| `signTransaction(options)` | Signs a Bitcoin transaction without broadcasting it | `Promise` | -| `quoteSendTransaction(options)` | Estimates the fee for a transaction | `Promise<{fee: bigint}>` | -| `getTransfers(options?)` | Returns the account's transfer history | `Promise` | -| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | -| `getMaxSpendable()` | Returns the maximum spendable amount | `Promise` | -| `sign(message)` | Signs a message with the account's private key | `Promise` | -| `verify(message, signature)` | Verifies a message signature | `Promise` | -| `toReadOnlyAccount()` | Creates a read-only version of this account | `Promise` | -| `dispose()` | Disposes the wallet account, clearing private keys from memory | `void` | +**Example:** +```javascript +const balances = await account.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC +]) +for (const [address, balance] of balances) { + console.log(`Token ${address}: ${balance}`) +} +``` -##### `getAddress()` -Returns the account's Bitcoin address (Native SegWit bech32 by default, or legacy if using BIP-44). +##### `toReadOnlyAccount()` +Creates a read-only copy of the account with the same Safe address and configuration. -**Returns:** `Promise` - The Bitcoin address +**Returns:** `Promise\` - Read-only account instance **Example:** ```javascript -const address = await account.getAddress() -console.log('Address:', address) // bc1q... (BIP-84) or 1... (BIP-44) +const readOnlyAccount = await account.toReadOnlyAccount() + +// Can check balances but cannot send transactions +const balance = await readOnlyAccount.getBalance() +// readOnlyAccount.sendTransaction() // Would not be available ``` -##### `getBalance()` -Returns the account's total balance in satoshis, including unconfirmed funds when present. -**Returns:** `Promise` - Balance in satoshis +##### `dispose()` +Disposes the wallet account, clearing private keys from memory. **Example:** ```javascript -const balance = await account.getBalance() -console.log('Balance:', balance, 'satoshis') +account.dispose() ``` -##### `sendTransaction(options, timeoutMs?)` -Sends a Bitcoin transaction to a single recipient and optionally polls after broadcast until spent inputs disappear from the unspent-output set. - -**Parameters:** -- `options` (BtcTransaction): Transaction options - - `to` (string): Recipient's Bitcoin address - - `value` (number | bigint): Amount in satoshis - - `feeRate` (number | bigint, optional): Fee rate in sat/vB. If provided, overrides the fee rate estimated from the blockchain. - - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) -- `timeoutMs` (number, optional): Maximum milliseconds to poll after broadcast before returning (default: 10000) +### Properties -**Returns:** `Promise<{hash: string, fee: bigint}>` -- `hash`: Transaction hash -- `fee`: Transaction fee in satoshis +| Property | Type | Description | +|----------|------|-------------| +| `index` | `number` | The derivation path's index of this account | +| `path` | `string` | The full BIP-44 derivation path of this account | +| `keyPair` | `{privateKey: Uint8Array \| null, publicKey: Uint8Array}` | The account's key pair (⚠️ Contains sensitive data) | **Example:** ```javascript -const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 50000n -}) -console.log('Transaction hash:', result.hash) -console.log('Fee:', result.fee, 'satoshis') +console.log('Account index:', account.index) // 0, 1, 2, etc. +console.log('Account path:', account.path) // m/44'/60'/0'/0/0 + +// ⚠️ SENSITIVE: Handle with care +const { privateKey, publicKey } = account.keyPair +console.log('Public key length:', publicKey.length) // 65 bytes +console.log('Private key length:', privateKey?.length) // 32 bytes (null after dispose) ``` -##### `signTransaction(options)` -Signs a Bitcoin transaction and returns the signed raw transaction as a hex string. This method does not broadcast the transaction. +⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, or expose the private key. -**Parameters:** -- `options` (BtcTransaction): Transaction options - - `to` (string): Recipient's Bitcoin address - - `value` (number | bigint): Amount in satoshis - - `feeRate` (number | bigint, optional): Fee rate in sat/vB. If provided, overrides the fee rate estimated from the blockchain. - - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) +## WalletAccountReadOnlyEvmErc4337 +Represents a read-only ERC-4337 wallet account that can query balances and estimate fees but cannot send transactions. -**Returns:** `Promise` - Signed raw transaction hex string +### Constants -**Example:** -```javascript -const signedTransaction = await account.signTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 50000n, - feeRate: 10n -}) +The following constant is used internally for Safe account address derivation: -console.log('Signed transaction:', signedTransaction) +```javascript +// Internal: used by predictSafeAddress() for deterministic address generation +const SALT_NONCE = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6' ``` -##### `quoteSendTransaction(options)` -Estimates the fee for a transaction without broadcasting it. - -**Parameters:** -- `options` (BtcTransaction): Same as sendTransaction options - - `to` (string): Recipient's Bitcoin address - - `value` (number | bigint): Amount in satoshis - - `feeRate` (number | bigint, optional): Fee rate in sat/vB. If provided, overrides the fee rate estimated from the blockchain. - - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) +> **Note:** This constant is not re-exported from the package entry point. Use `predictSafeAddress()` instead of referencing it directly. -**Returns:** `Promise<{fee: bigint}>` -- `fee`: Estimated transaction fee in satoshis +### Constructor -**Example:** ```javascript -const quote = await account.quoteSendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 50000n -}) -console.log('Estimated fee:', quote.fee, 'satoshis') +new WalletAccountReadOnlyEvmErc4337(address, config) ``` -##### `getTransfers(options?)` -Returns the account's transfer history with detailed transaction information. - **Parameters:** -- `options` (object, optional): Filter options - - `direction` (string, optional): 'incoming', 'outgoing', or 'all' (default: 'all') - - `limit` (number, optional): Maximum number of transfers (default: 10) - - `skip` (number, optional): Number of transfers to skip (default: 0) - -**Returns:** `Promise` - Array of transfer objects -- `txid`: Transaction ID -- `address`: Account's own address -- `vout`: Output index in the transaction -- `height`: Block height (0 if unconfirmed) -- `value`: Transfer value in satoshis (bigint) -- `direction`: 'incoming' or 'outgoing' -- `fee`: Transaction fee in satoshis (bigint, for outgoing transfers) -- `recipient`: Receiving address (for outgoing transfers) +- `address` (string): The EOA address (owner address) +- `config` (`Omit`): Configuration object without send-only fee caps **Example:** ```javascript -const transfers = await account.getTransfers({ - direction: 'incoming', - limit: 5 +const readOnlyAccount = new WalletAccountReadOnlyEvmErc4337('0x...', { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + safeModulesVersion: '0.3.0', + paymasterUrl: 'https://api.candide.dev/public/v3/1', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } }) -console.log('Recent incoming transfers:', transfers) ``` -##### `getTransactionReceipt(hash)` -Returns a transaction's receipt if it has been included in a block. - -**Parameters:** -- `hash` (string): The transaction hash (64 hex characters) +### Static Methods -**Returns:** `Promise` - The receipt, or null if the transaction has not been included in a block yet. +| Method | Description | Returns | +|--------|-------------|---------| +| `predictSafeAddress(owner, config)` | Predicts the Safe address for a given owner without instantiating an account | `string` | -**Example:** -```javascript -const receipt = await account.getTransactionReceipt('abc123...') -if (receipt) { - console.log('Transaction confirmed') -} -``` +#### `predictSafeAddress(owner, config)` (static) +Predicts the address of a Safe account. -##### `getMaxSpendable()` -Returns the maximum spendable amount that can be sent in a single transaction. The maximum spendable amount can differ from the wallet's total balance for several reasons: -- **Transaction fees**: Fees are subtracted from the total balance -- **Uneconomic UTXOs**: Small UTXOs where the fee to spend them exceeds their value are excluded -- **UTXO limit**: A transaction can include at most 200 inputs. Wallets with more UTXOs cannot spend their full balance in a single transaction. -- **Dust limit**: Outputs below the dust threshold (294 sats for SegWit, 546 sats for legacy) cannot be created +**Parameters:** +- `owner` (string): The Safe owner's EOA address +- `config` (object): Configuration with: + - `chainId` (number): The blockchain ID + - `safeModulesVersion` (string): The Safe modules version -**Returns:** `Promise` - Maximum spendable result -- `amount`: Maximum spendable amount in satoshis (bigint) -- `fee`: Estimated network fee in satoshis (bigint) -- `changeValue`: Estimated change value in satoshis (bigint) +**Returns:** `string` - The predicted Safe address **Example:** ```javascript -const { amount, fee } = await account.getMaxSpendable() -console.log('Max spendable:', amount, 'satoshis') -console.log('Estimated fee:', fee, 'satoshis') +const safeAddress = WalletAccountReadOnlyEvmErc4337.predictSafeAddress( + '0xOwnerEOA...', + { chainId: 1, safeModulesVersion: '0.3.0' } +) +console.log('Predicted Safe address:', safeAddress) + +// Also available on WalletAccountEvmErc4337 (inherited) +const sameAddress = WalletAccountEvmErc4337.predictSafeAddress( + '0xOwnerEOA...', + { chainId: 1, safeModulesVersion: '0.3.0' } +) ``` -##### `sign(message)` -Signs a message using the account's private key. +### Methods -**Parameters:** -- `message` (string): Message to sign +| Method | Description | Returns | Throws | +|--------|-------------|---------|--------| +| `getAddress()` | Returns the Safe account's address | `Promise\` | - | +| `verify(message, signature)` | Verifies a message signature | `Promise\` | - | +| `getBalance()` | Returns the native token balance (in wei) | `Promise\` | - | +| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise\` | - | +| `getPaymasterTokenBalance()` | Returns the paymaster token balance | `Promise\` | - | +| `getAllowance(token, spender)` | Returns current token allowance for a spender | `Promise\` | - | +| `quoteSendTransaction(tx, config?)` | Estimates the fee for a UserOperation | `Promise\<{fee: bigint}\>` | If simulation fails | +| `quoteTransfer(options, config?, txOverrides?)` | Estimates the fee for an ERC20 transfer | `Promise\<{fee: bigint}\>` | If simulation fails | +| `getTransactionReceipt(hash)` | Returns a transaction receipt | `Promise\` | - | +| `getUserOperationReceipt(hash)` | Returns a UserOperation receipt | `Promise\` | - | +| `signTypedData(typedData)` | Signs EIP-712 typed structured data | `Promise\` | - | +| `verifyTypedData(typedData, signature)` | Verifies an EIP-712 typed data signature | `Promise\` | - | +| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise\\>` | - | -**Returns:** `Promise` - Signature as base64 string +##### `getAddress()` +Returns the Safe smart contract wallet address. + +**Returns:** `Promise\` - The Safe account's address **Example:** ```javascript -const signature = await account.sign('Hello Bitcoin!') -console.log('Signature:', signature) +const address = await readOnlyAccount.getAddress() +console.log('Safe address:', address) ``` ##### `verify(message, signature)` -Verifies a message signature using the account's public key. +Verifies a message signature against the underlying EOA address. **Parameters:** -- `message` (string): Original message -- `signature` (string): Signature as base64 string +- `message` (string): The original message +- `signature` (string): The signature to verify -**Returns:** `Promise` - True if signature is valid +**Returns:** `Promise\` - True if signature is valid **Example:** ```javascript -const isValid = await account.verify('Hello Bitcoin!', signature) +const isValid = await readOnlyAccount.verify(message, signature) console.log('Signature valid:', isValid) ``` -##### `toReadOnlyAccount()` -Creates a read-only version of this account that can query balances and transactions but cannot sign or send transactions. +##### `getBalance()` +Returns the Safe account's native token balance. -**Returns:** `Promise` - Read-only account instance +**Returns:** `Promise\` - Balance in wei **Example:** ```javascript -const readOnlyAccount = await account.toReadOnlyAccount() const balance = await readOnlyAccount.getBalance() +console.log('Balance:', balance, 'wei') ``` -##### `dispose()` -Disposes the wallet account, securely erasing the private key from memory and closing the Electrum connection. +##### `getTokenBalance(tokenAddress)` +Returns the balance of a specific ERC20 token. -**Returns:** `void` +**Parameters:** +- `tokenAddress` (string): The ERC20 token contract address + +**Returns:** `Promise\` - Token balance in base units **Example:** ```javascript -account.dispose() -// Private key is now securely wiped from memory +const tokenBalance = await readOnlyAccount.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') +console.log('USDT balance:', tokenBalance) ``` -#### Properties - -| Property | Type | Description | -|----------|------|-------------| -| `index` | `number` | The derivation path's index of this account | -| `path` | `string` | The full derivation path of this account | -| `keyPair` | `KeyPair` | The account's public and private key pair. Treat the returned `Uint8Array` values as read-only views because mutations affect the account's internal key material. | - -## WalletAccountReadOnlyBtc - -Represents a read-only Bitcoin wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. +##### `getPaymasterTokenBalance()` +Returns the balance of the configured paymaster token. -#### Constructor +**Returns:** `Promise\` - Paymaster token balance in base units +**Example:** ```javascript -new WalletAccountReadOnlyBtc(address, config) +const paymasterBalance = await readOnlyAccount.getPaymasterTokenBalance() +console.log('Paymaster token balance:', paymasterBalance) ``` -**Parameters:** -- `address` (string): The account's Bitcoin address -- `config` (object, optional): Configuration object (same as BtcWalletConfig but without `bip`) - - `client` (`IBtcClient | BtcClientDescriptor | Array`, optional): Bitcoin client, descriptor, or ordered failover list - - `network` (string, optional): "bitcoin", "testnet", or "regtest" (default: "bitcoin") - - `retries` (number, optional): Additional retry attempts when `client` is an array - -### Methods - -| Method | Description | Returns | -|--------|-------------|---------| -| `getAddress()` | Returns the account's Bitcoin address | `Promise` | -| `getBalance()` | Returns the total account balance in satoshis, including unconfirmed funds when present | `Promise` | -| `quoteSendTransaction(options)` | Estimates the fee for a transaction | `Promise<{fee: bigint}>` | -| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | -| `getMaxSpendable()` | Returns the maximum spendable amount | `Promise` | -| `verify(message, signature)` | Verifies a message signature | `Promise` | -| `dispose()` | Closes any internal Electrum connection | `void` | +##### `getAllowance(token, spender)` +Returns the current token allowance for the given spender. -##### `getAddress()` -Returns the account's Bitcoin address. +**Parameters:** +- `token` (string): ERC20 token contract address +- `spender` (string): The spender's address -**Returns:** `Promise` - The Bitcoin address +**Returns:** `Promise\` - The current allowance **Example:** ```javascript -const address = await readOnlyAccount.getAddress() -console.log('Address:', address) +const allowance = await readOnlyAccount.getAllowance( + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + '0xSpenderContract...' +) +console.log('Allowance:', allowance) ``` -##### `getBalance()` -Returns the account's confirmed balance in satoshis. +##### `quoteSendTransaction(tx, config?)` +Estimates the fee for a UserOperation. -**Returns:** `Promise` - Balance in satoshis +**Parameters:** +- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[]): Transaction object or array +- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) + +**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate + +**Throws:** Error if simulation fails **Example:** ```javascript -const balance = await readOnlyAccount.getBalance() -console.log('Balance:', balance, 'satoshis') +try { + const quote = await readOnlyAccount.quoteSendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n + }) + console.log('Estimated fee:', quote.fee) +} catch (error) { + if (error.message.includes('not enough funds')) { + console.error('Insufficient paymaster token balance') + } +} ``` -##### `quoteSendTransaction(options)` -Estimates the fee for a transaction without broadcasting it. +##### `quoteTransfer(options, config?, txOverrides?)` +Estimates the fee for an ERC20 token transfer. **Parameters:** -- `options` (BtcTransaction): Transaction options - - `to` (string): Recipient's Bitcoin address - - `value` (number | bigint): Amount in satoshis - - `feeRate` (number | bigint, optional): Fee rate in sat/vB - - `confirmationTarget` (number, optional): Target blocks for confirmation (default: 1) +- `options` (TransferOptions): Transfer options +- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) +- `txOverrides` (optional): UserOperation gas and fee overrides (see [EvmErc4337GasOverrides](#evmerc4337gasoverrides)) -**Returns:** `Promise<{fee: bigint}>` - Estimated fee in satoshis +**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate **Example:** ```javascript -const quote = await readOnlyAccount.quoteSendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 50000n +const quote = await readOnlyAccount.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 }) -console.log('Estimated fee:', quote.fee, 'satoshis') +console.log('Transfer fee estimate:', quote.fee) ``` ##### `getTransactionReceipt(hash)` -Returns a transaction's receipt if it has been included in a block. +Returns a transaction receipt by hash. **Parameters:** - `hash` (string): The transaction hash -**Returns:** `Promise` - The receipt, or null if not yet included +**Returns:** `Promise\` - Transaction receipt or null if not mined **Example:** ```javascript -const receipt = await readOnlyAccount.getTransactionReceipt('abc123...') +const receipt = await readOnlyAccount.getTransactionReceipt('0x...') if (receipt) { - console.log('Transaction confirmed') + console.log('Transaction confirmed in block:', receipt.blockNumber) + console.log('Status:', receipt.status) // 1 = success, 0 = failed +} else { + console.log('Transaction not yet mined') } ``` -##### `getMaxSpendable()` -Returns the maximum spendable amount that can be sent in a single transaction. +##### `getUserOperationReceipt(hash)` +Returns a UserOperation receipt by hash. -**Returns:** `Promise` - Maximum spendable result -- `amount`: Maximum spendable amount in satoshis (bigint) -- `fee`: Estimated network fee in satoshis (bigint) -- `changeValue`: Estimated change value in satoshis (bigint) +**Parameters:** +- `hash` (string): The UserOperation hash + +**Returns:** `Promise\` - UserOperation receipt or null if not processed **Example:** ```javascript -const { amount, fee } = await readOnlyAccount.getMaxSpendable() -console.log('Max spendable:', amount, 'satoshis') +const receipt = await readOnlyAccount.getUserOperationReceipt('0x...') +if (receipt) { + console.log('UserOp receipt:', receipt) +} ``` -##### `verify(message, signature)` -Verifies a message signature using the account's public key. +##### `signTypedData(typedData)` +Signs EIP-712 typed structured data using the underlying EOA address. **Parameters:** -- `message` (string): Original message -- `signature` (string): Signature as base64 string +- `typedData` (TypedData): The typed data object containing domain, types, primaryType, and message -**Returns:** `Promise` - True if signature is valid +**Returns:** `Promise\` - The typed data signature **Example:** ```javascript -const isValid = await readOnlyAccount.verify('Hello Bitcoin!', signature) -console.log('Signature valid:', isValid) +const typedData = { + domain: { + name: 'MyDApp', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + types: { + Transfer: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' } + ] + }, + primaryType: 'Transfer', + message: { + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000n + } +} + +const signature = await readOnlyAccount.signTypedData(typedData) +console.log('Typed data signature:', signature) ``` -##### `dispose()` -Closes any internal Electrum connection owned by this account. If a [`client`](/sdk/wallet-modules/wallet-btc/configuration#client) was provided via config, the connection is left open (the caller manages its lifecycle). +##### `verifyTypedData(typedData, signature)` +Verifies an EIP-712 typed data signature against the underlying EOA address. -**Returns:** `void` +**Parameters:** +- `typedData` (TypedData): The original typed data object +- `signature` (string): The signature to verify + +**Returns:** `Promise\` - True if the signature is valid **Example:** ```javascript -readOnlyAccount.dispose() +const isValid = await readOnlyAccount.verifyTypedData(typedData, signature) +console.log('Typed data signature valid:', isValid) ``` -## ElectrumTcp +##### `getTokenBalances(tokenAddresses)` +Returns balances for multiple ERC20 tokens in a single call. -Electrum client using TCP transport. Standard for command-line and server-side environments. -Implements `IBtcClient`. +**Parameters:** +- `tokenAddresses` (string[]): Array of ERC20 token contract addresses -#### Constructor +**Returns:** `Promise\\>` - Map of token address to balance in base units +**Example:** ```javascript -new ElectrumTcp(config) +const balances = await readOnlyAccount.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC +]) +for (const [address, balance] of balances) { + console.log(`Token ${address}: ${balance}`) +} ``` -**Parameters:** -- `config` (`Omit`): Configuration options - - `host` (string): Electrum server hostname - - `port` (number): Electrum server port +## Types -## ElectrumTls +### EvmErc4337WalletConfig -Electrum client using TLS transport. -Implements `IBtcClient`. +The configuration is a union type combining common fields with one of three gas payment modes: -#### Constructor +```typescript +// Common fields (required for all modes) +interface EvmErc4337WalletCommonConfig { + chainId: number; // Blockchain ID + provider: string | Eip1193Provider | Array; // RPC provider or failover list + bundlerUrl: string; // Bundler service URL + safeModulesVersion: string; // Must be '0.3.0' in beta.14 + parallel?: boolean; // Fresh random nonce-key lane per operation + nonceKey?: number | bigint | string; // Reusable raw or named uint192 lane key +} -```javascript -new ElectrumTls(config) -``` +// Mode 1: Paymaster Token +interface EvmErc4337WalletPaymasterTokenConfig { + isSponsored?: false; + useNativeCoins?: false; + paymasterUrl: string; // Paymaster service URL + paymasterAddress: string; // Paymaster contract address + paymasterToken: { address: string }; // ERC-20 token for fees + transferMaxFee?: number | bigint; // Maximum transfer fee limit + transactionMaxFee?: number | bigint; // Maximum send/sign fee limit +} -**Parameters:** -- `config` (`Omit`): Configuration options - - `host` (string): Electrum server hostname - - `port` (number): Electrum server port +// Mode 2: Sponsorship Policy +interface EvmErc4337WalletSponsorshipPolicyConfig { + isSponsored: true; + useNativeCoins?: false; + paymasterUrl: string; // Paymaster service URL + sponsorshipPolicyId?: string; // Sponsorship policy ID +} -## ElectrumSsl +// Mode 3: Native Coins +interface EvmErc4337WalletNativeCoinsConfig { + isSponsored?: false; + useNativeCoins: true; + transferMaxFee?: number | bigint; // Maximum transfer fee limit + transactionMaxFee?: number | bigint; // Maximum send/sign fee limit +} -Electrum client using SSL transport. -Implements `IBtcClient`. +// Full config type +type EvmErc4337WalletConfig = EvmErc4337WalletCommonConfig & + (EvmErc4337WalletPaymasterTokenConfig | + EvmErc4337WalletSponsorshipPolicyConfig | + EvmErc4337WalletNativeCoinsConfig); +``` -#### Constructor +### Config Override -```javascript -new ElectrumSsl(config) +The published beta.14 `config` parameter type on `sendTransaction`, `signTransaction`, `quoteSendTransaction`, `transfer`, and `quoteTransfer` allows per-call overrides of gas payment settings: + +```typescript +type ConfigOverride = Partial< + EvmErc4337WalletPaymasterTokenConfig | + EvmErc4337WalletSponsorshipPolicyConfig | + EvmErc4337WalletNativeCoinsConfig +>; ``` -**Parameters:** -- `config` (`Omit`): Configuration options - - `host` (string): Electrum server hostname - - `port` (number): Electrum server port +**Available override fields:** +- `isSponsored` (boolean): Enable or disable sponsorship mode +- `useNativeCoins` (boolean): Enable or disable native-coin mode +- `paymasterUrl` (string): Override paymaster URL +- `paymasterAddress` (string): Override paymaster contract +- `paymasterToken` (\{address: string\}): Override paymaster token +- `sponsorshipPolicyId` (string): Set sponsorship policy +- `transactionMaxFee` (number | bigint): Override maximum fee for `sendTransaction()` and `signTransaction()` +- `transferMaxFee` (number | bigint): Override maximum fee -## ElectrumWs +Overrides are shallow-merged with the account configuration, then validated. When switching modes, explicitly clear an inherited opposing flag: sponsorship requires `isSponsored: true` and `useNativeCoins: false`; native-coin mode requires `isSponsored: false` and `useNativeCoins: true`; paymaster-token mode requires both flags to be `false`. The merged configuration must also contain the URL, address, token, or policy fields required by the selected mode. -Electrum client using WebSocket transport. Compatible with browser environments where TCP sockets are not available. -Implements `IBtcClient`. +At runtime, beta.14 additionally honors `parallel` and `nonceKey` per call for `signTransaction()`, `sendTransaction()`, and `transfer()`. Those two fields are missing from the published per-call declarations, so object literals containing them fail TypeScript excess-property checks even though JavaScript calls work. Construction-level `EvmErc4337WalletCommonConfig` includes both fields and is the typed path in this release. `quoteSendTransaction()` and `quoteTransfer()` do not select a lane. -#### Constructor +The quote cache does not include fee-mode override fields in its key. Pass the same fee-mode and paymaster settings to a quote and its matching send, sign, or transfer. Lane sends, signs, and transfers bypass cached UserOperations and rebuild in the selected lane. -```javascript -new ElectrumWs(config) +### EvmErc4337Transaction + +The transaction shape accepted by `sendTransaction`, `quoteSendTransaction`, and `signTransaction`. Beyond the call fields (`to`, `value`, `data`), it accepts optional gas overrides that are applied to the resulting UserOperation. + +```typescript +interface EvmErc4337Transaction { + to: string; // Recipient address + value: number | bigint; // Amount of native coin in wei + data?: string; // Call data in hex format (optional) + callGasLimit?: number | bigint; // Override the UserOperation call gas limit (optional) + verificationGasLimit?: number | bigint; // Override the UserOperation verification gas limit (optional) + preVerificationGas?: number | bigint; // Override the UserOperation pre-verification gas (optional) + maxFeePerGas?: number | bigint; // Override the UserOperation max fee per gas — EIP-1559 cap (optional) + maxPriorityFeePerGas?: number | bigint; // Override the UserOperation max priority fee per gas (optional) +} ``` -**Parameters:** -- `config` (ElectrumWsConfig): Configuration options - - `url` (string): The WebSocket URL (e.g., 'wss://electrum.example.com:50004') +The gas-override fields are optional. When omitted, the gas limits fall back to AbstractionKit's estimation and the fee pair (`maxFeePerGas` / `maxPriorityFeePerGas`) falls back to the bundler-fetched gas price. Setting either fee field disables the bundler-fetched fee fallback for both, so set them together. In a batched call (`tx` passed as an array), only the gas overrides on the first transaction are honored — a UserOperation carries a single set of gas fields regardless of how many calls it batches. -### Methods +### UserOperationV7 -| Method | Description | Returns | -|--------|-------------|---------| -| `connect()` | Establishes connection to Electrum server | `Promise` | -| `close()` | Closes the connection | `Promise` | -| `reconnect()` | Recreates the underlying socket and reinitializes the session | `Promise` | -| `getBalance(scripthash)` | Returns balance for a script hash | `Promise` | -| `listUnspent(scripthash)` | Returns UTXOs for a script hash | `Promise` | -| `getHistory(scripthash)` | Returns transaction history | `Promise` | -| `getTransaction(txHash)` | Returns raw transaction hex | `Promise` | -| `broadcast(rawTx)` | Broadcasts raw transaction | `Promise` | -| `estimateFee(blocks)` | Returns estimated fee rate | `Promise` | +`UserOperationV7` is the ERC-4337 v0.7 operation type re-exported by `@tetherto/wdk-wallet-evm-erc-4337` from AbstractionKit. -## Types +```typescript +import type { UserOperationV7 } from '@tetherto/wdk-wallet-evm-erc-4337' +``` -### BtcTransaction +`signTransaction()` returns this type with its `signature` populated. Passing that signed value to `quoteSendTransaction()` estimates its submitted cost; passing it to `sendTransaction()` submits the same operation without rebuilding it. + +### EvmErc4337GasOverrides + +The helper methods `transfer()`, `quoteTransfer()`, and `approve()` accept these same UserOperation gas and fee override fields as a separate `txOverrides` argument. ```typescript -interface BtcTransaction { - to: string // The transaction's recipient - value: number | bigint // The amount of bitcoins to send to the recipient (in satoshis) - confirmationTarget?: number // Optional confirmation target in blocks (default: 1) - feeRate?: number | bigint // Optional fee rate in satoshis per virtual byte +interface EvmErc4337GasOverrides { + callGasLimit?: number | bigint; + verificationGasLimit?: number | bigint; + preVerificationGas?: number | bigint; + maxFeePerGas?: number | bigint; + maxPriorityFeePerGas?: number | bigint; } ``` -### TransactionResult +### TransferOptions ```typescript -interface TransactionResult { - hash: string // Transaction hash/ID - fee: bigint // Transaction fee in satoshis +interface TransferOptions { + token: string; // ERC20 token contract address + recipient: string; // Recipient address + amount: number | bigint; // Amount in token base units } ``` -### FeeRates +### ApproveOptions ```typescript -interface FeeRates { - normal: bigint // Standard fee rate (sat/vB) for ~1 hour confirmation - fast: bigint // Higher fee rate (sat/vB) for faster confirmation +interface ApproveOptions { + token: string; // ERC20 token contract address + spender: string; // Address allowed to spend tokens + amount: number | bigint; // Amount to approve in base units } ``` -### BtcTransfer +### TransactionResult ```typescript -interface BtcTransfer { - txid: string // The transaction's ID - address: string // The user's own address - vout: number // The index of the output in the transaction - height: number // The block height (if unconfirmed, 0) - value: bigint // The value of the transfer (in satoshis) - direction: 'incoming' | 'outgoing' // The direction of the transfer - fee?: bigint // The fee paid for the full transaction (in satoshis) - recipient?: string // The receiving address for outgoing transfers +interface TransactionResult { + hash: string; // UserOperation hash + fee: bigint; // Fee paid } ``` -### BtcMaxSpendableResult +### TransferResult ```typescript -interface BtcMaxSpendableResult { - amount: bigint // The maximum spendable amount in satoshis - fee: bigint // The estimated network fee in satoshis - changeValue: bigint // The estimated change value in satoshis +interface TransferResult { + hash: string; // UserOperation hash + fee: bigint; // Fee paid } ``` -### KeyPair +### TypedData ```typescript -interface KeyPair { - publicKey: Uint8Array // Public key bytes. Treat as read-only. - privateKey: Uint8Array | null // Private key bytes. Treat as read-only. Null after dispose. +interface TypedData { + domain: TypedDataDomain; // EIP-712 domain separator + types: Record; // Type definitions + primaryType: string; // Primary type name + message: Record; // Structured message data } ``` -### BtcWalletConfig +### TypedDataDomain ```typescript -interface BtcWalletConfig { - client?: IBtcClient | BtcClientDescriptor | Array // Client, descriptor, or failover list - network?: 'bitcoin' | 'testnet' | 'regtest' // Network to use (default: "bitcoin") - bip?: 44 | 84 // BIP address type - 44 (legacy) or 84 (native SegWit) (default: 84) - retries?: number // Additional retry attempts for client arrays +interface TypedDataDomain { + name?: string; // DApp or protocol name + version?: string; // Domain version + chainId?: number; // Blockchain ID + verifyingContract?: string; // Contract address + salt?: string; // Optional salt } ``` -### BtcClientDescriptor +### TypedDataField ```typescript -type BtcClientDescriptor = - | { type: 'electrum'; clientConfig: MempoolElectrumConfig } - | { type: 'electrum-ws'; clientConfig: ElectrumWsConfig } - | { type: 'blockbook-http'; clientConfig: BlockbookClientConfig } +interface TypedDataField { + name: string; // Field name + type: string; // Solidity type (e.g., 'address', 'uint256') +} ``` -### IBtcClient +### UserOperationReceipt -Interface for implementing custom Bitcoin network clients. ```typescript -interface IBtcClient { - connect(): Promise - close(): Promise - reconnect(): Promise - getBalance(scripthash: string): Promise - listUnspent(scripthash: string): Promise - getHistory(scripthash: string): Promise - getTransaction(txHash: string): Promise - broadcast(rawTx: string): Promise - estimateFee(blocks: number): Promise +interface UserOperationReceipt { + userOpHash: string; // UserOperation hash + sender: string; // Sender address + nonce: bigint; // Nonce + actualGasUsed: bigint; // Gas used + actualGasCost: bigint; // Gas cost + success: boolean; // Whether the operation succeeded + receipt: EvmTransactionReceipt; // The underlying transaction receipt } ``` -### ElectrumBalance +### ConfigurationError + ```typescript -interface ElectrumBalance { - confirmed: number // Confirmed balance in satoshis - unconfirmed?: number // Unconfirmed balance in satoshis +class ConfigurationError extends Error { + // Thrown when the wallet configuration is invalid + // e.g., missing required fields for the selected gas payment mode } ``` -### ElectrumUtxo +### FeeRates + ```typescript -interface ElectrumUtxo { - tx_hash: string // The transaction hash containing this UTXO - tx_pos: number // The output index within the transaction - value: number // The UTXO value in satoshis - height?: number // The block height (0 if unconfirmed) +interface FeeRates { + normal: bigint; // Fee rate for normal priority + fast: bigint; // Fee rate for fast priority } ``` -### ElectrumHistoryItem +### KeyPair + ```typescript -interface ElectrumHistoryItem { - tx_hash: string // The transaction hash - height: number // The block height (0 or negative if unconfirmed) +interface KeyPair { + publicKey: Uint8Array; // The public key + privateKey: Uint8Array | null; // The private key (null after dispose) } ``` -### MempoolElectrumConfig +### Internal Constants + +The following constants are used internally by the SDK and are **not importable** from the package entry point. ```typescript -interface MempoolElectrumConfig { - host: string // Electrum server hostname - port: number // Electrum server port - protocol?: 'tcp' | 'ssl' | 'tls' // Transport protocol (default: 'tcp') - maxRetry?: number // Maximum reconnection attempts (default: 2) - retryPeriod?: number // Delay between reconnection attempts in ms (default: 1000) - pingPeriod?: number // Delay between keep-alive pings in ms (default: 120000) - callback?: (err: Error | null) => void // Called when all retries are exhausted -} +// Used by predictSafeAddress() for deterministic address generation +// Not re-exported from '@tetherto/wdk-wallet-evm-erc-4337' +const SALT_NONCE: string = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6'; + +// Fee rate multipliers (protected static on WalletManagerEvm) +// Applied internally by getFeeRates() +const _FEE_RATE_NORMAL_MULTIPLIER: bigint; // ~110% +const _FEE_RATE_FAST_MULTIPLIER: bigint; // ~200% ``` @@ -13680,454 +23615,537 @@ Get started with WDK in a Node.js environment Build mobile wallets with React Native Expo - -Get started with WDK's Bitcoin Wallet Usage + +Get started with WDK's EVM with ERC-4337 Wallet Usage - -Get started with WDK's Bitcoin Wallet Configuration + +Get started with WDK's EVM with ERC-4337 Wallet Configuration *** -### Need Help? +## Need Help? *** ## Configuration -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/configuration -Description: Configuration options and settings for @tetherto/wdk-wallet-btc +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/configuration +Description: Configuration options and settings for @tetherto/wdk-wallet-evm-erc-4337 ## Wallet Configuration +The `WalletManagerEvmErc4337` requires a complete ERC-4337 configuration object with all required parameters: + ```javascript -import WalletManagerBtc from '@tetherto/wdk-wallet-btc' +import WalletManagerEvmErc4337 from '@tetherto/wdk-wallet-evm-erc-4337' -const wallet = new WalletManagerBtc(seedPhrase, { - client: { - type: 'electrum', - clientConfig: { - host: 'electrum.blockstream.info', - port: 50002, - protocol: 'tls' - } +const config = { + // Required parameters + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + safeModulesVersion: '0.3.0', + bundlerUrl: `https://api.pimlico.io/v1/ethereum/rpc?apikey=${PIMLICO_API_KEY}`, + paymasterUrl: `https://api.pimlico.io/v2/ethereum/rpc?apikey=${PIMLICO_API_KEY}`, + paymasterAddress: '0x777777777777AeC03fd955926DbF81597e66834C', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }, - network: 'bitcoin' -}) -``` - -## Account Creation + transactionMaxFee: 100000, + transferMaxFee: 100000 +} -```javascript -// WalletAccountBtc is created by the WalletManagerBtc -// It takes the same configuration as the manager -const account = await wallet.getAccount(0) // Get account at index 0 -const customAccount = await wallet.getAccountByPath("0'/0/5") // Custom path +const wallet = new WalletManagerEvmErc4337(seedPhrase, config) ``` -## Configuration Options - -### Client +## Account Configuration -The `client` option specifies how the wallet connects to Bitcoin network data. It accepts a pre-built `IBtcClient`, a client descriptor, or an ordered array of clients and descriptors for automatic failover. +Both `WalletAccountEvmErc4337` and `WalletAccountReadOnlyEvmErc4337` use the same configuration structure: -**Type:** `IBtcClient | BtcClientDescriptor | Array` +```javascript +import { WalletAccountEvmErc4337, WalletAccountReadOnlyEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' -**Default:** Uses an Electrum descriptor for `electrum.blockstream.info:50001`. +// Full access account +const account = new WalletAccountEvmErc4337( + seedPhrase, + "0'/0/0", // BIP-44 derivation path + config // Same config as wallet manager +) -**Example:** -```javascript -const config = { - client: { - type: 'electrum', - clientConfig: { - host: 'fulcrum.frznode.com', - port: 50002, - protocol: 'tls' +// Read-only account (fee caps for sending are not needed) +const readOnlyAccount = new WalletAccountReadOnlyEvmErc4337( + '0x...', // Owner EOA address; the module predicts the Safe address + { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + paymasterUrl: 'https://api.candide.dev/public/v3/1', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' } + // Note: transferMaxFee and transactionMaxFee omitted for read-only accounts } -} +) ``` -`BtcClientDescriptor` supports: +## Configuration Options -| Type | Description | -|------|-------------| -| `electrum` | Creates a TCP, TLS, or SSL Electrum client from `clientConfig`. | -| `electrum-ws` | Creates a WebSocket Electrum client from `clientConfig`. | -| `blockbook-http` | Creates a stateless Blockbook HTTP client from `clientConfig`. | +### Chain ID -#### Built-in Transport Clients +The `chainId` option specifies the blockchain network ID. **Required** for fee estimation and smart account initialization. -The package still exports built-in transport clients when you want to instantiate the client yourself: +**Type:** `number` +**Required:** Yes +**Examples:** ```javascript -import { - ElectrumTcp, // TCP transport (default, port 50001) - ElectrumTls, // TLS transport (port 50002) - ElectrumSsl, // SSL transport (port 50002) - ElectrumWs // WebSocket transport -} from '@tetherto/wdk-wallet-btc' +// Ethereum Mainnet +const config = { chainId: 1 } -// TCP (default) -const tcpClient = new ElectrumTcp({ host: 'electrum.blockstream.info', port: 50001 }) +// Polygon Mainnet +const config = { chainId: 137 } -// TLS -const tlsClient = new ElectrumTls({ host: 'electrum.blockstream.info', port: 50002 }) +// Arbitrum One +const config = { chainId: 42161 } -// SSL -const sslClient = new ElectrumSsl({ host: 'electrum.blockstream.info', port: 50002 }) +// Avalanche C-Chain +const config = { chainId: 43114 } -// WebSocket -const wsClient = new ElectrumWs({ url: 'wss://electrum.example.com:50004' }) ``` -#### Custom Bitcoin Client +### Provider -You can implement your own client by implementing `IBtcClient`: +The `provider` option specifies the RPC endpoint or EIP-1193 provider instance for blockchain interactions. **Required** for all operations. You can also pass an array of endpoints or providers to enable automatic failover: when a request to one provider fails, the wallet retries the next provider in the list. -```typescript -import { IBtcClient } from '@tetherto/wdk-wallet-btc' +**Type:** `string | Eip1193Provider | Array` +**Required:** Yes -class MyCustomBitcoinClient implements IBtcClient { - // Implement the required interface methods +**Examples:** +```javascript +// Using RPC URL +const config = { + provider: 'https://rpc.mevblocker.io/fast' } -const wallet = new WalletManagerBtc(seedPhrase, { - client: new MyCustomBitcoinClient(params), - network: 'bitcoin' -}) +// Using browser provider (MetaMask) +const config = { + provider: window.ethereum +} + +// Using custom ethers provider +import { JsonRpcProvider } from 'ethers' +const config = { + provider: new JsonRpcProvider('https://rpc.mevblocker.io/fast') +} + +// Using multiple providers for automatic failover +const config = { + provider: [ + 'https://rpc.mevblocker.io/fast', + 'https://eth.llamarpc.com' + ], + retries: 3 // Optional: additional retry attempts after the initial call fails +} ``` -#### Client Failover +### Retries -Pass an ordered `client` array to retry connection failures against fallback clients. Set `retries` to control how many additional attempts can happen after the first failed call. +The `retries` option sets the number of additional retry attempts after the initial call fails. It only applies when `provider` is an array of endpoints or providers. Total attempts equal `1 + retries`. If `retries` exceeds the number of providers, the failover loops back and retries already-failed providers in round-robin order. + +**Type:** `number` +**Required:** No (optional) +**Default:** `3` ```javascript -const wallet = new WalletManagerBtc(seedPhrase, { - client: [ - { - type: 'electrum', - clientConfig: { - host: 'primary-electrum.example', - port: 50002, - protocol: 'tls' - } - }, - { - type: 'electrum', - clientConfig: { - host: 'secondary-electrum.example', - port: 50002, - protocol: 'tls' - } - } +const config = { + provider: [ + 'https://rpc.mevblocker.io/fast', + 'https://eth.llamarpc.com' ], - retries: 1, - network: 'bitcoin' -}) + retries: 5 +} ``` -### Host +### Bundler URL -The `host` value belongs inside an `electrum` descriptor's `clientConfig` or an `Electrum*` client constructor. It is not a top-level wallet config option. +The `bundlerUrl` option specifies the URL of the ERC-4337 bundler service that handles UserOperation bundling and submission to the mempool. **Required** for transaction processing. **Type:** `string` - -**Default:** `"electrum.blockstream.info"` - -**Recommended:** Configure your own Electrum server for production use. Public servers can be 10-300x slower and may fail for addresses with many transactions. +**Required:** Yes **Example:** ```javascript const config = { - client: { - type: 'electrum', - clientConfig: { - host: 'fulcrum.frznode.com', - port: 50002, - protocol: 'tls' - } - } + bundlerUrl: 'https://api.candide.dev/public/v3/1' } ``` -### Port - -The `port` value belongs inside an `electrum` descriptor's `clientConfig` or an `Electrum*` client constructor. It is not a top-level wallet config option. - -**Type:** `number` +### Paymaster URL -**Default:** `50001` +The `paymasterUrl` option specifies the URL of the paymaster service that sponsors transaction fees using ERC-20 tokens or sponsorship policies. -**Common Ports:** -- `50001` - TCP (default) -- `50002` - TLS/SSL -- `50003` - WebSocket +**Type:** `string` +**Required:** Yes, for Paymaster Token mode and Sponsorship Policy mode. Not used in Native Coins mode. **Example:** ```javascript const config = { - client: { - type: 'electrum', - clientConfig: { - host: 'electrum.blockstream.info', - port: 50002, - protocol: 'tls' - } - } + paymasterUrl: 'https://api.candide.dev/public/v3/1' } ``` -### Protocol +### Paymaster Address -The `protocol` value belongs inside an `electrum` descriptor's `clientConfig`. It is not a top-level wallet config option. +The `paymasterAddress` option specifies the address of the paymaster smart contract. **Type:** `string` - -**Values:** -- `"tcp"` - TCP transport (default) -- `"tls"` - TLS transport -- `"ssl"` - SSL transport - -**Default:** `"tcp"` +**Required:** Yes, for Paymaster Token mode only. Not used in Sponsorship Policy or Native Coins modes. **Example:** ```javascript const config = { - client: { - type: 'electrum', - clientConfig: { - host: 'electrum.blockstream.info', - port: 50002, - protocol: 'tls' - } - } + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba' } ``` -### Retries +### On-Chain Identifier -The `retries` option controls failover retry attempts when `client` is an array. +The `onChainIdentifier` option appends a 50-byte project marker to every UserOperation's call data. Pass a string to use it as the project name, or pass an object for more control over the platform and tool fields. -**Type:** `number` (optional) +**Type:** `string | OnChainIdentifier` +**Required:** No (optional) + +**Properties (object form):** +- `project` (string): The project name included in the marker +- `platform` (`'Web' | 'Mobile' | 'Safe App' | 'Widget'`, optional): The platform type (default: `'Web'`) +- `tool` (string, optional): The tool name used to create the UserOperation +- `toolVersion` (string, optional): Semver-style tool version string (e.g., `'1.0.0'`) -**Example:** ```javascript +// String form const config = { - client: [ - { type: 'electrum', clientConfig: { host: 'primary.example', port: 50002, protocol: 'tls' } }, - { type: 'electrum', clientConfig: { host: 'secondary.example', port: 50002, protocol: 'tls' } } - ], - retries: 2 + onChainIdentifier: 'my-project' +} + +// Object form +const config = { + onChainIdentifier: { + project: 'my-project', + platform: 'Mobile', + tool: 'my-wallet', + toolVersion: '1.0.0' + } } ``` -### Network +### Parallel Nonce Lanes -The `network` option specifies which Bitcoin network to use. +ERC-4337 supports two-dimensional nonces: a 192-bit key selects a lane, and each lane has its own sequence. By default this module uses key `0`, so do not fire another default-lane operation until the previous one has been included. -**Type:** `string` +Use these optional common configuration fields when the account needs independent lanes: -**Values:** -- `"bitcoin"` - Bitcoin [mainnet](/resources/concepts#mainnet) (production) -- `"testnet"` - Bitcoin [testnet](/resources/concepts#testnet) (development) -- `"regtest"` - Bitcoin [regtest](/resources/concepts#regtest) (local testing) +- `parallel` (`boolean`, default `false`): Give each send, sign, or transfer operation a fresh random lane at sequence `0`. Ordering across those lanes is not guaranteed. Each successfully included UserOperation in a fresh lane creates or advances a distinct EntryPoint nonce slot; signing alone and unincluded submissions do not advance on-chain nonce state. +- `nonceKey` (`number | bigint | string`): Reuse an explicit lane. A string is hashed into a deterministic named lane. A number or `bigint` is used as the raw uint192 key and must be in `0..2^192-1`; use `bigint` for raw keys above `Number.MAX_SAFE_INTEGER`. -**Default:** `"bitcoin"` +`nonceKey` takes precedence over `parallel`; when neither is set, the module uses the default key-0 lane. -**Example:** -```javascript +Before enabling either option, configure the account without a lane, submit one default-lane operation, and wait for its receipt so the Safe is deployed. Confirm that the configured bundler accepts nonzero nonce keys, then create the account with the lane setting. + +```javascript title="Configure A Named Lane After Safe Deployment" const config = { - network: 'testnet' // Use testnet for development + // ...required network and gas-payment fields + nonceKey: 'scheduled-payments' } + +const wallet = new WalletManagerEvmErc4337(seedPhrase, config) ``` -### BIP +Reusing one named lane remains sequential: wait for inclusion before submitting its next operation, or batch dependent calls into one UserOperation. -The `bip` option specifies the address type derivation standard to use. + +The beta.14 runtime also honors `parallel` and `nonceKey` in the per-call config for `signTransaction()`, `sendTransaction()`, and `transfer()`, but the published per-call TypeScript declarations omit both fields. Construction-level configuration is typed; JavaScript per-call usage works at runtime. + -**Type:** `number` +### Safe Modules Version -**Values:** -- `84` - [BIP-84](/resources/concepts#bip-84-native-segwit) (P2WPKH / Native SegWit) - addresses start with `bc1` (mainnet) or `tb1` (testnet) -- `44` - [BIP-44](/resources/concepts#bip-44-multi-account-hierarchy) (P2PKH / Legacy) - addresses start with `1` (mainnet) or `m`/`n` (testnet) +The `safeModulesVersion` option specifies the Safe modules version for smart contract wallet implementation. Beta.14 supports only `0.3.0`; other values, including `0.2.0`, throw `ConfigurationError`. **Required** for smart account initialization. -**Default:** `84` +**Type:** `string` +**Required:** Yes **Example:** ```javascript -// Use legacy addresses const config = { - bip: 44 + safeModulesVersion: '0.3.0' } ``` -## Electrum Server Configuration - -**Important**: While the package defaults to `electrum.blockstream.info:50001` for convenience, **we strongly recommend configuring your own Electrum server** for production use. - -### Recommended Approach +### Paymaster Token -**For Production:** -- Set up your own Fulcrum server for optimal performance and reliability -- Use recent Fulcrum versions that support pagination for high-transaction addresses +The `paymasterToken` option specifies the ERC-20 token used for paying transaction fees through the paymaster. -**For Development/Testing:** -- `fulcrum.frznode.com:50001` - Generally faster than default -- `electrum.blockstream.info:50001` - Default fallback +**Type:** `object` +**Required:** Yes, for Paymaster Token mode only. Not used in Sponsorship Policy or Native Coins modes. -### Configuration Examples +**Properties:** +- `address` (string): The ERC-20 token contract address +**Example:** ```javascript -import { ElectrumTcp, ElectrumTls } from '@tetherto/wdk-wallet-btc' +const config = { + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USD₮ + } +} +``` -// Production with custom Fulcrum server -const productionClient = new ElectrumTls({ - host: 'your-fulcrum-server.com', - port: 50002 -}) +### Sponsorship Policy ID -const productionWallet = new WalletManagerBtc(seedPhrase, { - client: productionClient, - network: 'bitcoin' -}) +The `sponsorshipPolicyId` option specifies the sponsorship policy identifier for sponsored transactions. -// Development with alternative public server -const developmentClient = new ElectrumTcp({ - host: 'fulcrum.frznode.com', - port: 50001 -}) +**Type:** `string` +**Required:** No (optional), only used in Sponsorship Policy mode. -const developmentWallet = new WalletManagerBtc(seedPhrase, { - client: developmentClient, - network: 'bitcoin' -}) +**Example:** +```javascript +const config = { + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy_id' +} ``` -### Network-Specific Configuration +### Gas Payment Mode Flags -#### Bitcoin Mainnet +These boolean flags control which gas payment mode is used. Only one mode should be active at a time. + +#### `isSponsored` + +Enables Sponsorship Policy mode, where a sponsor covers transaction fees. + +**Type:** `boolean` +**Default:** `false` ```javascript -import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' +const config = { + isSponsored: true, + paymasterUrl: 'https://api.candide.dev/public/v3/1' +} +``` -const client = new ElectrumTcp({ - host: 'electrum.blockstream.info', // Or your own server - port: 50001 -}) +#### `useNativeCoins` -const wallet = new WalletManagerBtc(seedPhrase, { - client, - network: 'bitcoin' -}) +Enables Native Coins mode, where the user pays fees in the chain's native currency (ETH, MATIC, etc.). + +**Type:** `boolean` +**Default:** `false` + +```javascript +const config = { + useNativeCoins: true, + transactionMaxFee: 100000000000000n, // Optional: max send/sign fee in wei + transferMaxFee: 100000000000000n // Optional: max transfer fee in wei +} ``` -#### Bitcoin Testnet +### Transaction Max Fee -```javascript -import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' +The `transactionMaxFee` option sets the maximum fee amount for non-sponsored `sendTransaction()` and `signTransaction()` operations. It applies in Paymaster Token and Native Coins modes and is separate from `transferMaxFee`, which only caps token transfer operations. **Optional** parameter. -const client = new ElectrumTcp({ - host: 'testnet.hsmiths.com', // Example testnet server - port: 53011 -}) +**Type:** `number | bigint` +**Required:** No (optional) +**Unit:** Paymaster token base units in Paymaster Token mode; native token base units in Native Coins mode -const wallet = new WalletManagerBtc(seedPhrase, { - client, - network: 'testnet' -}) +**Example:** +```javascript +const config = { + transactionMaxFee: 100000, // 100,000 paymaster token units + transferMaxFee: 100000 +} + +try { + const result = await account.sendTransaction({ + to: '0x...', + value: 1000000000000000n + }) +} catch (error) { + if (error.message.includes('Exceeded maximum fee')) { + console.error('Transaction cancelled: Fee too high') + } +} ``` -#### Bitcoin Regtest +### Transfer Max Fee -```javascript -import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' +The `transferMaxFee` option sets the maximum fee amount for transfer operations. In Paymaster Token mode, the cap uses paymaster token base units. In Native Coins mode, the cap uses native token base units. This prevents transactions with unexpectedly high fees. **Optional** parameter. -const client = new ElectrumTcp({ - host: 'localhost', // Local regtest node - port: 50001 -}) +**Type:** `number | bigint` +**Required:** No (optional) +**Unit:** Paymaster token base units in Paymaster Token mode; native token base units in Native Coins mode -const wallet = new WalletManagerBtc(seedPhrase, { - client, - network: 'regtest' -}) -``` +**Example:** +```javascript +const config = { + transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USD₮ if 6 decimals) +} -## Derivation Paths +// Usage with error handling +try { + const result = await account.transfer({ + token: '0x...', + recipient: '0x...', + amount: 1000000 + }) +} catch (error) { + if (error.message.includes('Exceeded maximum fee')) { + console.error('Transfer cancelled: Fee too high') + } +} +``` -Bitcoin wallet addresses are derived using BIP-32 hierarchical deterministic paths: +## Network-Specific Configurations -### BIP-84 (Native SegWit) - Default +### Ethereum Mainnet -- `m/84'/0'/0'/0/0` for mainnet account 0, address 0 -- `m/84'/1'/0'/0/0` for testnet/regtest account 0, address 0 + +**Supported Paymaster Tokens** -Addresses start with `bc1` (mainnet) or `tb1` (testnet). +The following tokens are supported for gas payments on Ethereum Mainnet: -### BIP-44 (Legacy) +* **USD₮**: `0xdAC17F958D2ee523a2206206994597C13D831ec7` +* **USA₮**: `0x07041776f5007aca2a54844f50503a18a72a8b68` +* **XAU₮**: `0x68749665ff8d2d112fa859aa293f07a622782f38` + -- `m/44'/0'/0'/0/0` for mainnet account 0, address 0 -- `m/44'/1'/0'/0/0` for testnet/regtest account 0, address 0 +```javascript +const ethereumConfig = { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + paymasterUrl: 'https://api.candide.dev/public/v3/1', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT + }, + transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USDT if 6 decimals) +} +``` -Addresses start with `1` (mainnet) or `m`/`n` (testnet). +### Polygon Mainnet - -**Default Derivation Path Change in v1.0.0-beta.4+** +```javascript +const polygonConfig = { + chainId: 137, + provider: 'https://polygon-rpc.com', + bundlerUrl: 'https://api.candide.dev/public/v3/137', + paymasterUrl: 'https://api.candide.dev/public/v3/137', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' // USDT on Polygon + }, + transferMaxFee: 100000 +} +``` -The default derivation path was updated in v1.0.0-beta.4 to use BIP-84 (Native SegWit) instead of BIP-44 (Legacy): +### Arbitrum One -- **Previous path** (up to v1.0.0-beta.3): `m/44'/0'/0'/0/{index}` (Legacy addresses) -- **Current path** (v1.0.0-beta.4+): `m/84'/0'/0'/0/{index}` (Native SegWit addresses) +```javascript +const arbitrumConfig = { + chainId: 42161, + provider: 'https://arb1.arbitrum.io/rpc', + bundlerUrl: 'https://public.pimlico.io/v2/42161/rpc', + paymasterUrl: 'https://public.pimlico.io/v2/42161/rpc', + paymasterAddress: '0x777777777777AeC03fd955926DbF81597e66834C', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' // USDT on Arbitrum + }, + transferMaxFee: 100000 +} +``` -If you're upgrading from an earlier version, existing wallets created with the old path will generate different addresses. Make sure to migrate any existing wallets or use the old path explicitly if needed for compatibility. +### Avalanche C-Chain -Use [`getAccountByPath`](/sdk/wallet-modules/wallet-btc/api-reference#getaccountbypathpath) to supply an explicit derivation path when importing or recreating legacy wallets. - +``` javascript +const avalancheConfig = { + chainId: 43114, + provider: 'https://avalanche-c-chain-rpc.publicnode.com', + bundlerUrl: "https://public.pimlico.io/v2/43114/rpc", + paymasterUrl: "https://public.pimlico.io/v2/43114/rpc", + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7' // USDT + }, + transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USDT if 6 decimals) +} +``` -## Complete Configuration Example +### Plasma ```javascript -import WalletManagerBtc, { ElectrumTls } from '@tetherto/wdk-wallet-btc' +// Plasma (example Layer 2) +const plasmaConfig = { + chainId: 9745, + provider: 'https://plasma.drpc.org', + // For ERC-4337 support, optional fields: + bundlerUrl: 'https://api.candide.dev/public/v3/9745', + paymasterUrl: 'https://api.candide.dev/public/v3/9745', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb' // USDT + }, + transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USDT if 6 decimals) +} +``` -// Create Electrum client -const client = new ElectrumTls({ - host: 'your-electrum-server.com', // Replace with your server - port: 50002 -}) +### Sepolia Testnet (USD₮ ERC-20 mock/testnet only) -// Create wallet manager with configuration -const wallet = new WalletManagerBtc(seedPhrase, { - client, - network: 'bitcoin', - bip: 84 // Native SegWit (default) -}) +````javascript -// Get accounts (inherit configuration from manager) -const account0 = await wallet.getAccount(0) -const account1 = await wallet.getAccount(1) -const customAccount = await wallet.getAccountByPath("0'/0/5") +// Pimlico +const sepoliaConfigPimlico = { + chainId: 11155111, + provider: 'https://sepolia.drpc.org', + bundlerUrl: 'https://public.pimlico.io/v2/11155111/rpc', + paymasterUrl: 'https://public.pimlico.io/v2/11155111/rpc', + paymasterAddress: '0x777777777777AeC03fd955926DbF81597e66834C', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xd077a400968890eacc75cdc901f0356c943e4fdb' // USDT Sepolia + }, + transferMaxFee: 100000 // 0.1 USDT (6 decimals) +} -// Clean up when done -wallet.dispose() -``` +// Candide +const sepoliaConfigCandide = { + chainId: 11155111, + provider: 'https://sepolia.drpc.org', + bundlerUrl: 'https://api.candide.dev/public/v3/11155111', + paymasterUrl: 'https://api.candide.dev/public/v3/11155111', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xd077a400968890eacc75cdc901f0356c943e4fdb' // USDT Sepolia + }, + transferMaxFee: 100000 +} +```` -## Performance Considerations +**Important** +Ethereum Sepolia is a testnet. The USD₮ tokens available at the links below are not real and do not entitle the holder to anything. In particular, they cannot be redeemed with Tether International, S.A. de C.V. ("Tether International") and are not Tether Tokens as described in [Tether International's Terms of Service](https://tether.to/en/legal). The USD₮ tokens available at the links below on this testnet are intended for testing WDK on Ethereum Sepolia. The links below are links to third-party websites and are Third-Party Information as described in Tether Operations, S.A. de [C.V.'s Website Terms](https://tether.io/terms/) -**Electrum Server Performance:** -- Public servers like Blockstream's can be significantly slower -- Addresses with many transactions may cause timeouts -- Custom Fulcrum servers provide better performance and reliability -- Consider server location and network latency +**USD₮ on Sepolia contract:** [0xd077a400968890eacc75cdc901f0356c943e4fdb](https://sepolia.etherscan.io/address/0xd077a400968890eacc75cdc901f0356c943e4fdb) -**Configuration Tips:** -- Use `fulcrum.frznode.com` for better development performance -- Set up your own Fulcrum server for production -- Monitor connection stability and implement retry logic -- Consider using multiple backup servers +**Get test USD₮:** +- [Pimlico faucet](https://dashboard.pimlico.io/test-erc20-faucet) +- [Candide faucet](https://dashboard.candide.dev/faucet) @@ -14136,97 +24154,103 @@ Get started with WDK in a Node.js environment Build mobile wallets with React Native Expo - -Get started with WDK's BTC Wallet Usage + +Get started with WDK's EVM with ERC-4337 Wallet Usage - -Get started with WDK's BTC Wallet API + +Get started with WDK's EVM with ERC-4337 Wallet API *** -### Need Help? +## Need Help? *** ## Check Balances -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/check-balances -Description: Query native BTC balances for owned and read-only accounts. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/check-balances +Description: Query native, ERC-20, and paymaster token balances. -This guide explains how to check [native BTC balances](#native-btc-balance), [maximum spendable amounts](#maximum-spendable-amount), and [read-only account balances](#read-only-account-balances). +This guide explains how to check [native token balances](#native-token-balance), [ERC-20 token balances](#erc-20-token-balance), [multiple token balances](#multiple-token-balances), [paymaster token balances](#paymaster-token-balance), and [read-only account balances](#read-only-account-balances). -## Native BTC Balance +## Native Token Balance -You can retrieve the confirmed balance in satoshis using [`account.getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference): +You can retrieve the native token balance (e.g., ETH) using [`account.getBalance()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): -```javascript title="Get Native BTC Balance" +```javascript title="Get Native Balance" const balance = await account.getBalance() -console.log('Total balance:', balance, 'satoshis') +console.log('Native balance:', balance, 'wei') ``` - -On Bitcoin, balances are expressed in satoshis (1 BTC = 100,000,000 satoshis). The [`getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference) method returns the total balance, including unconfirmed funds when present. - +## ERC-20 Token Balance -## Maximum Spendable Amount +You can check the balance of a specific ERC-20 token using [`account.getTokenBalance()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): -You can check the maximum amount available to send in a single transaction using [`account.getMaxSpendable()`](/sdk/wallet-modules/wallet-btc/api-reference): +```javascript title="Get ERC-20 Balance" +const tokenBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') // USDT +console.log('USDT balance:', tokenBalance) +``` -```javascript title="Get Maximum Spendable" -const { amount, fee } = await account.getMaxSpendable() -console.log('Max spendable:', amount, 'satoshis') -console.log('Estimated fee:', fee, 'satoshis') +## Multiple Token Balances + +You can check balances for multiple ERC-20 tokens in a single call using [`account.getTokenBalances()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): + +```javascript title="Get Multiple Token Balances" +const tokenBalances = await account.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT +]) +console.log('Multi-token balances:', tokenBalances) +``` + +## Paymaster Token Balance + +You can check the paymaster token balance used for paying gas fees using [`account.getPaymasterTokenBalance()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): + +```javascript title="Get Paymaster Token Balance" +const paymasterBalance = await account.getPaymasterTokenBalance() +console.log('Paymaster token balance:', paymasterBalance) ``` -The maximum spendable amount can differ from the total balance due to transaction fees, uneconomic UTXOs, the 200-input limit per transaction, and the dust threshold (294 satoshis for SegWit, 546 for legacy). +The paymaster token balance determines how many gasless transactions you can execute. Ensure the paymaster has sufficient token balance before initiating gasless operations. ## Read-Only Account Balances -You can check balances for any Bitcoin address without a seed phrase using [`WalletAccountReadOnlyBtc`](/sdk/wallet-modules/wallet-btc/api-reference): - -```javascript title="Create Read-Only Account" -import { WalletAccountReadOnlyBtc, ElectrumTcp } from '@tetherto/wdk-wallet-btc' +You can check balances for any smart account address without a seed phrase using [`WalletAccountReadOnlyEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): -const client = new ElectrumTcp({ - host: 'electrum.blockstream.info', - port: 50001 -}) +```javascript title="Read-Only Balance" +import { WalletAccountReadOnlyEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' -const readOnlyAccount = new WalletAccountReadOnlyBtc('bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', { - client, - network: 'bitcoin' +const readOnlyAccount = new WalletAccountReadOnlyEvmErc4337('0x...', { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + paymasterUrl: 'https://api.candide.dev/public/v3/1', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + } }) -``` - -You can retrieve the balance from a read-only account using [`readOnlyAccount.getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference): -```javascript title="Read-Only Balance" const balance = await readOnlyAccount.getBalance() -console.log('Read-only account balance:', balance, 'satoshis') +console.log('Read-only account balance:', balance, 'wei') ``` - -Read-only accounts follow the same balance behavior as owned accounts: [`getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference) includes unconfirmed funds when present. - - - -You can also create a read-only account from an existing owned account using [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-btc/api-reference). - - ## Next Steps -With balance checks in place, learn how to [send BTC](/sdk/wallet-modules/wallet-btc/guides/send-transactions). +With balance checks in place, learn how to [send gasless transactions](/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions). *** ## Get Started -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/get-started -Description: Install and create your first Bitcoin wallet. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/get-started +Description: Install and create your first ERC-4337 smart account wallet. This guide explains how to [install the package](#1-install-the-package), [create a wallet](#2-create-a-wallet), [get your first account](#3-get-your-first-account), and optionally [convert to read-only](#4-optional-convert-to-read-only). @@ -14237,27 +24261,29 @@ This guide explains how to [install the package](#1-install-the-package), [creat * **[Node.js](https://nodejs.org/)**: version 18 or higher. * **[npm](https://www.npmjs.com/)**: usually comes with Node.js. -```bash title="Install @tetherto/wdk-wallet-btc" -npm install @tetherto/wdk-wallet-btc +```bash title="Install @tetherto/wdk-wallet-evm-erc-4337" +npm install @tetherto/wdk-wallet-evm-erc-4337 ``` ## 2. Create a Wallet -You can create a new wallet instance using the [`WalletManagerBtc`](/sdk/wallet-modules/wallet-btc/api-reference) constructor with a BIP-39 seed phrase and an Electrum client: +You can create a new wallet instance using the [`WalletManagerEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) constructor with a BIP-39 seed phrase and ERC-4337 configuration: -```javascript title="Create Bitcoin Wallet" -import WalletManagerBtc, { ElectrumTcp } from '@tetherto/wdk-wallet-btc' +```javascript title="Create ERC-4337 Wallet" +import WalletManagerEvmErc4337 from '@tetherto/wdk-wallet-evm-erc-4337' const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' -const client = new ElectrumTcp({ - host: 'electrum.blockstream.info', - port: 50001 -}) - -const wallet = new WalletManagerBtc(seedPhrase, { - client, - network: 'bitcoin' +const wallet = new WalletManagerEvmErc4337(seedPhrase, { + chainId: 1, + provider: 'https://rpc.mevblocker.io/fast', + bundlerUrl: 'https://api.candide.dev/public/v3/1', + paymasterUrl: 'https://api.candide.dev/public/v3/1', + paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', + safeModulesVersion: '0.3.0', + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT + } }) ``` @@ -14265,27 +24291,23 @@ const wallet = new WalletManagerBtc(seedPhrase, { **Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. - -**Electrum Server Performance:** Public servers like Blockstream's can be 10-300x slower than private servers. For production use, set up your own [Fulcrum](https://github.com/cculianu/Fulcrum) server. For development, consider `fulcrum.frznode.com` as a faster alternative. + +To use test/mock tokens instead of real funds, see the [testnet configuration section](/sdk/wallet-modules/wallet-evm-erc-4337/configuration#network-specific-configurations). ## 3. Get Your First Account -You can retrieve an account at a given index using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-btc/api-reference): +You can retrieve a smart account at a given index using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): ```javascript title="Get Account" const account = await wallet.getAccount(0) const address = await account.getAddress() -console.log('Wallet address:', address) +console.log('Smart account address:', address) ``` - -This implementation uses BIP-84 derivation paths and generates Native SegWit (bech32) addresses by default. Addresses start with `bc1` on mainnet. Set `bip: 44` in config for legacy (P2PKH) addresses. - - ## 4. (optional) Convert to Read-Only -You can convert an owned account to a read-only account using [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-btc/api-reference): +You can convert an owned account to a read-only account using [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): ```javascript title="Convert to Read-Only" const readOnlyAccount = await account.toReadOnlyAccount() @@ -14293,154 +24315,92 @@ const readOnlyAccount = await account.toReadOnlyAccount() ## Next Steps -With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-btc/guides/manage-accounts). - -*** - -## Transaction History -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/get-transaction-history -Description: Retrieve and filter Bitcoin transfer history. - -This guide explains how to [retrieve all transfers](#retrieve-all-transfers), [filter by direction](#filter-by-direction), [paginate results](#paginate-results), and [check transaction receipts](#check-transaction-receipts). - -## Retrieve All Transfers - -You can retrieve the account's transfer history using [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): - -```javascript title="Get All Transfers" -const transfers = await account.getTransfers() -console.log('Recent transfers:', transfers) -``` - - -The default limit is 10 transfers. Change outputs are automatically filtered out. Transfers are sorted by block height (newest first). - - -## Filter by Direction - -You can filter transfers by direction using the `direction` option in [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): - -```javascript title="Incoming Transfers" -const incoming = await account.getTransfers({ direction: 'incoming' }) -console.log('Incoming transfers:', incoming) -``` - -You can retrieve outgoing transfers with a custom limit using [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): - -```javascript title="Outgoing Transfers" -const outgoing = await account.getTransfers({ - direction: 'outgoing', - limit: 5 -}) -console.log('Outgoing transfers:', outgoing) -``` - -## Paginate Results - -You can paginate through transfer history using the `limit` and `skip` options in [`account.getTransfers()`](/sdk/wallet-modules/wallet-btc/api-reference): - -```javascript title="Paginate Transfers" -const page = await account.getTransfers({ - direction: 'all', - limit: 20, - skip: 10 -}) -console.log('Transfers 11-30:', page) -``` - -## Check Transaction Receipts - -You can check whether a specific transaction has been confirmed using [`account.getTransactionReceipt()`](/sdk/wallet-modules/wallet-btc/api-reference): - -```javascript title="Get Transaction Receipt" -const receipt = await account.getTransactionReceipt('abc123...') -if (receipt) { - console.log('Transaction confirmed') -} -``` - -## Next Steps - -Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-btc/guides/sign-verify-messages). +With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-evm-erc-4337/guides/manage-accounts). *** ## Handle Errors -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/handle-errors +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/handle-errors Description: Handle errors, manage fees, and dispose of sensitive data. -This guide explains how to [handle transaction errors](#transaction-errors), [handle connection errors](#connection-errors), and follow [best practices](#best-practices) for fee management and memory cleanup. +This guide explains how to [handle transaction errors](#transaction-errors), [handle transfer errors](#transfer-errors), [diagnose nonce-lane failures](#nonce-lane-failures), and follow [best practices](#best-practices) for fee management and memory cleanup. ## Transaction Errors -Transactions sent via [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference) can fail for several reasons. Wrap transaction calls in a `try/catch` block to handle specific error types: +Transactions sent via [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) can fail when the paymaster token balance is insufficient. Wrap calls in a `try/catch` block: ```javascript title="Handle Transaction Errors" try { const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n }) - console.log('Transaction hash:', result.hash) + console.log('UserOperation hash:', result.hash) } catch (error) { - if (error.message.includes('Insufficient balance')) { - console.error('Not enough funds in wallet') - } else if (error.message.includes('dust limit')) { - console.error('Amount is below the minimum dust limit') - } else if (error.message.includes('Invalid address')) { - console.error('Recipient address is invalid') + if (error.message.includes('not enough funds')) { + console.error('Insufficient paymaster token balance') } else { console.error('Transaction failed:', error.message) } } ``` -## Connection Errors +## Transfer Errors -Network issues with the Electrum server can cause failures across all operations. Handle connection errors at a higher level: +Token transfers via [`account.transfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) can fail due to insufficient balance or a fee at or above the maximum limit: -```javascript title="Handle Connection Errors" +```javascript title="Handle Transfer Errors" try { - const balance = await account.getBalance() - console.log('Balance:', balance, 'satoshis') + const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 + }) + console.log('Transfer UserOperation hash:', result.hash) } catch (error) { - if (error.message.includes('ECONNREFUSED') || error.message.includes('timeout')) { - console.error('Network error: check Electrum server connection') - } else if (error.message.includes('Invalid seed')) { - console.error('Invalid seed phrase provided') + if (error.message.includes('Exceeded maximum fee')) { + console.error('Transfer cancelled: fee meets or exceeds the configured limit') + } else if (error.message.includes('not enough funds')) { + console.error('Insufficient paymaster token balance') } else { - console.error('Operation failed:', error.message) + console.error('Transfer failed:', error.message) } } ``` +## Nonce-Lane Failures + +Lane configuration and bundler behavior can fail in several ways: + +- A raw `nonceKey` below `0` or above `2^192 - 1` throws `nonceKey must be within the uint192 range (0 to 2^192 - 1).` +- Parallel sends from an undeployed Safe or a bundler that does not support nonzero keys can be rejected by the RPC or bundler. Deploy the account with one completed operation first and verify bundler support. +- Two operations submitted concurrently in the same named or default lane can select the same sequence. Both calls can return a UserOperation hash even though one never receives a receipt. + +Treat a returned hash as submission evidence, not inclusion. Poll `getUserOperationReceipt(hash)` with an application timeout. If one same-lane hash never resolves, inspect the bundler response and on-chain lane sequence before retrying; use a fresh lane for independent work or batch dependent calls rather than blindly resubmitting the same operation. + ## Best Practices ### Fee Management -You can retrieve current network fee rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-btc/api-reference): +You can retrieve current network fee rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): ```javascript title="Get Fee Rates" const feeRates = await wallet.getFeeRates() -console.log('Normal fee rate:', feeRates.normal, 'sat/vB') -console.log('Fast fee rate:', feeRates.fast, 'sat/vB') +console.log('Normal fee rate:', feeRates.normal) +console.log('Fast fee rate:', feeRates.fast) ``` - -[`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-btc/api-reference) fetches rates from the mempool.space API, while [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference) estimates fees from the connected Electrum server. Use [`getFeeRates()`](/sdk/wallet-modules/wallet-btc/api-reference) for display purposes. - - ### Dispose of Sensitive Data -For security, clear sensitive data from memory when a session is complete. Use [`account.dispose()`](/sdk/wallet-modules/wallet-btc/api-reference) and [`wallet.dispose()`](/sdk/wallet-modules/wallet-btc/api-reference) to securely wipe private keys: +For security, clear sensitive data from memory when a session is complete. Use [`account.dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and [`wallet.dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) to securely wipe private keys: ```javascript title="Dispose Resources" try { const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n }) - console.log('Transaction hash:', result.hash) + console.log('UserOperation hash:', result.hash) } finally { account.dispose() wallet.dispose() @@ -14448,20 +24408,20 @@ try { ``` -Always call [`dispose()`](/sdk/wallet-modules/wallet-btc/api-reference) when finished with accounts. Private keys are securely wiped from memory using `sodium_memzero`. Electrum connections are automatically closed. Disposal is irreversible. +Always call [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) when finished with accounts. Private keys are securely wiped from memory. Disposal is irreversible. *** ## Manage Accounts -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/manage-accounts -Description: Work with multiple accounts and custom derivation paths. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/manage-accounts +Description: Work with multiple smart accounts and custom derivation paths. This guide explains how to [retrieve accounts by index](#retrieve-accounts-by-index) and [use custom derivation paths](#retrieve-account-by-custom-derivation-path). ## Retrieve Accounts by Index -You can retrieve multiple accounts using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-btc/api-reference) with different index values: +You can retrieve multiple smart accounts using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) with different index values: ```javascript title="Retrieve Multiple Accounts" const account0 = await wallet.getAccount(0) @@ -14473,20 +24433,9 @@ const address1 = await account1.getAddress() console.log('Account 1 address:', address1) ``` -You can iterate through multiple accounts using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-btc/api-reference) to inspect addresses and balances in bulk: - -```javascript title="Iterate Over Accounts" -for (let i = 0; i < 5; i++) { - const account = await wallet.getAccount(i) - const address = await account.getAddress() - const balance = await account.getBalance() - console.log(`Account ${i}: ${address} (${balance} satoshis)`) -} -``` - ## Retrieve Account by Custom Derivation Path -You can retrieve an account at a specific derivation path using [`wallet.getAccountByPath()`](/sdk/wallet-modules/wallet-btc/api-reference): +You can retrieve an account at a specific derivation path using [`wallet.getAccountByPath()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): ```javascript title="Custom Derivation Path" const customAccount = await wallet.getAccountByPath("0'/0/5") @@ -14494,448 +24443,439 @@ const customAddress = await customAccount.getAddress() console.log('Custom account address:', customAddress) ``` - -The default derivation scheme is BIP-84 (Native SegWit): `m/84'/0'/0'/0/{index}` on mainnet. Set `bip: 44` in the wallet configuration for legacy BIP-44 paths: `m/44'/0'/0'/0/{index}`. - - ## Next Steps -With accounts set up, learn how to [check balances](/sdk/wallet-modules/wallet-btc/guides/check-balances). +With accounts set up, learn how to [check balances](/sdk/wallet-modules/wallet-evm-erc-4337/guides/check-balances). *** ## Send Transactions -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/send-transactions -Description: Send BTC and estimate transaction fees. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions +Description: Send gasless transactions and estimate fees. -This guide explains how to [send BTC](#send-btc), [extend post-broadcast polling](#extend-post-broadcast-polling), [sign without broadcasting](#sign-without-broadcasting), [estimate fees before sending](#estimate-fees), [use a custom fee rate](#send-with-custom-fee-rate), and [target a specific confirmation time](#send-with-confirmation-target). +This guide explains how to [send a gasless transaction](#send-a-gasless-transaction), [estimate fees](#estimate-fees), [sign, quote, and submit a UserOperation](#sign-quote-and-submit-a-useroperation), [cap transaction fees](#cap-transaction-fees), [reuse a recent quote](#reuse-a-recent-quote), [use parallel nonce lanes](#use-parallel-nonce-lanes), and [use a custom paymaster token](#send-with-custom-paymaster-token). -## Send BTC +## Send a Gasless Transaction -You can send Bitcoin to a recipient using [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms): +You can send a transaction with gas fees paid in the paymaster token using [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): -```javascript title="Send BTC" +```javascript title="Send Gasless Transaction" const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n // 0.001 BTC in satoshis + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n // 0.001 ETH in wei }) -console.log('Transaction hash:', result.hash) -console.log('Transaction fee:', result.fee, 'satoshis') +console.log('UserOperation hash:', result.hash) +console.log('Fee paid in paymaster token:', result.fee) ``` -Bitcoin transactions support a single recipient only. Amounts and fees are always in satoshis (1 BTC = 100,000,000 satoshis). The minimum amount must be above the dust limit (294 satoshis for SegWit, 546 for legacy). +ERC-4337 transactions are gasless for the end user. Gas fees are paid through the configured paymaster using the specified paymaster token (e.g., USD₮). -## Extend Post-Broadcast Polling +## Estimate Fees -If you want [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms) to keep polling after broadcast until spent inputs disappear from the unspent-output set, pass the optional `timeoutMs` argument: +You can estimate the fee for a transaction without broadcasting it using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): -```javascript title="Send BTC With Extended Polling" -const result = await account.sendTransaction( - { - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n, - }, - 30000 -) +```javascript title="Estimate Fee" +const quote = await account.quoteSendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +}) +console.log('Estimated fee:', quote.fee) +``` -console.log('Transaction hash:', result.hash) +## Sign, Quote, and Submit a UserOperation + +Use `signTransaction()` when a separate review or relay step needs a signed ERC-4337 v0.7 UserOperation. The method accepts one transaction, not a batch. + +```javascript title="Sign, Quote, and Submit" +const tx = { + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +} + +const signedUserOperation = await account.signTransaction(tx) +const quote = await account.quoteSendTransaction(signedUserOperation) +console.log('Signed operation fee ceiling:', quote.fee) + +const result = await account.sendTransaction(signedUserOperation) +console.log('UserOperation hash:', result.hash) ``` - -If you omit `timeoutMs`, the wallet uses the default post-broadcast polling window before returning. + +Only submit an operation produced by the same account with the intended fee-mode configuration. WDK preserves the signed nonce and configuration, does not rebuild or re-sign the operation, and does not reapply `transactionMaxFee` during signed submission. Do not mutate the operation, and submit it before its nonce becomes stale. -## Sign Without Broadcasting +For signed UserOperations, sponsored mode quotes `0n`. Other modes quote a 20% buffered native-gas ceiling in wei. In paymaster-token mode, that signed-operation quote is not a token-denominated paymaster charge. -Use [`account.signTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#signtransactionoptions) when your app needs a signed raw Bitcoin transaction but does not want WDK to broadcast it immediately. +## Cap Transaction Fees -```javascript title="Sign BTC Transaction" -const signedTransaction = await account.signTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n, - feeRate: 10n -}) +Set `transactionMaxFee` to reject non-sponsored operations newly built by `sendTransaction()` or `signTransaction()` when the estimated UserOperation fee is above your cap. A signed UserOperation passed to `sendTransaction()` is not checked against the cap again. Use `transferMaxFee` separately for token transfers. -console.log('Signed transaction:', signedTransaction) +```javascript title="Cap sendTransaction fees" +const result = await account.sendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +}, { + transactionMaxFee: 100000 +}) ``` -`signTransaction()` returns the signed transaction hex. Use `sendTransaction()` when WDK should sign, broadcast, and return the transaction hash. +`transactionMaxFee` applies to Paymaster Token and Native Coins modes. Sponsored operations return a zero fee to the caller and do not use this cap. -## Estimate Fees +## Reuse a Recent Quote -You can estimate the fee for a transaction without broadcasting it using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#quotesendtransactionoptions): +Default-lane, non-sponsored UserOperations built while quoting can be cached for up to 2 minutes. If you call `sendTransaction()` with the same transaction during that window, the account checks the current on-chain nonce before reusing a cached UserOperation. If the nonce has moved, it rebuilds before sending. Sponsored quotes do not cache a built UserOperation. -```javascript title="Estimate Fee" -const quote = await account.quoteSendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n -}) -console.log('Estimated fee:', quote.fee, 'satoshis') +Quote methods do not resolve or reserve a nonce lane. A later send or sign using `parallel` or `nonceKey` rebuilds in that selected lane instead of reusing the quoted UserOperation. + + +Cache identity does not include per-call fee-mode configuration. Use the same fee-mode and paymaster settings for `quoteSendTransaction()` and the matching `sendTransaction()` or `signTransaction()`. Do not quote with one mode, paymaster token, or sponsorship policy and execute the same transaction with another while the quote is cached. + + +```javascript title="Quote Then Send" +const tx = { + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +} + +const quote = await account.quoteSendTransaction(tx) +console.log('Estimated fee:', quote.fee) + +const result = await account.sendTransaction(tx) +console.log('UserOperation hash:', result.hash) ``` -## Send with Custom Fee Rate +## Use Parallel Nonce Lanes -You can override automatic fee estimation by providing a `feeRate` in sat/vB to [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms): +Beta.14 no longer reserves sequential nonces locally. By default, operations use ERC-4337 nonce key `0`; two default-lane sends started before the first is included can select the same sequence and collide. -```javascript title="Custom Fee Rate" -const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n, - feeRate: 10n // sat/vB -}) +Before using nonzero lanes, deploy the Safe with one operation and wait for its UserOperation receipt. Your bundler must support parallel nonce keys, and its per-sender mempool limits still apply. + +Use `parallel: true` to give each independent operation a fresh random lane: + +```javascript title="Send Independent Operations Concurrently" +const [first, second] = await Promise.all([ + account.sendTransaction({ to: recipientA, value: 0n }, { parallel: true }), + account.sendTransaction({ to: recipientB, value: 0n }, { parallel: true }) +]) + +console.log(first.hash, second.hash) // UserOperation hashes ``` - -When `feeRate` is provided, the `confirmationTarget` parameter is ignored. +Use distinct named lanes for stable independent work streams. Strings are hashed as labels; they are not parsed as numeric keys. + +```javascript title="Use Named Nonce Lanes" +const payroll = await account.sendTransaction(txA, { nonceKey: 'payroll' }) +const refunds = await account.sendTransaction(txB, { nonceKey: 'refunds' }) +``` + +`nonceKey` takes precedence over `parallel`. A raw numeric key must fit uint192; use a `bigint` above `Number.MAX_SAFE_INTEGER`. + + +One lane is still sequential. Two concurrent sends using the same named or default lane can return UserOperation hashes even though one never receives a receipt. Wait for inclusion before reusing a lane. If calls depend on one another, batch them with `sendTransaction([tx1, tx2])` so they execute in order under one nonce. Different lanes are independent and can be included in either order. -## Send with Confirmation Target +`signTransaction()` preserves the lane chosen by the same configuration rules. In beta.14, per-call `parallel` and `nonceKey` work at runtime but are missing from the published per-call TypeScript declarations; construction-level lane configuration is typed. -You can target a specific number of blocks for confirmation using the `confirmationTarget` parameter in [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms): +## Send with Custom Paymaster Token -```javascript title="Confirmation Target" +You can override the default paymaster token for a specific transaction by passing a config object to [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference). This example assumes the account is already in paymaster-token mode: + +```javascript title="Custom Paymaster Token" const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n, - confirmationTarget: 6 // target 6 blocks (~1 hour) + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +}, { + paymasterToken: { + address: '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT + } }) ``` +When switching from sponsored or native-coin mode, also set `isSponsored: false` and `useNativeCoins: false`, and provide any paymaster fields absent from the account configuration. + ## Next Steps -Learn how to [view transaction history](/sdk/wallet-modules/wallet-btc/guides/get-transaction-history). +Learn how to [transfer ERC-20 tokens](/sdk/wallet-modules/wallet-evm-erc-4337/guides/transfer-tokens). *** ## Sign and Verify Messages -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/sign-verify-messages -Description: Sign messages and verify signatures with Bitcoin accounts. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/sign-verify-messages +Description: Sign messages and EIP-712 typed data with smart accounts. -This guide explains how to [sign messages](#sign-a-message) and [verify signatures](#verify-a-signature). +This guide explains how to [sign messages](#sign-a-message), [verify signatures](#verify-a-signature), and [sign EIP-712 typed data](#sign-typed-data-eip-712). ## Sign a Message -You can sign a message with the account's private key using [`account.sign()`](/sdk/wallet-modules/wallet-btc/api-reference): +You can sign a message with the account's private key using [`account.sign()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): ```javascript title="Sign Message" -const message = 'Hello, Bitcoin!' -const signature = await account.sign(message) +const signature = await account.sign('Hello, ERC-4337!') console.log('Signature:', signature) ``` - -The signature is returned as a base64-encoded string. - - ## Verify a Signature -You can verify that a signature was produced by the corresponding private key using [`account.verify()`](/sdk/wallet-modules/wallet-btc/api-reference): +You can verify a signature using a read-only account. Use [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) to create one, then call [`readOnlyAccount.verify()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): ```javascript title="Verify Signature" -const isValid = await account.verify(message, signature) +const readOnlyAccount = await account.toReadOnlyAccount() +const isValid = await readOnlyAccount.verify('Hello, ERC-4337!', signature) console.log('Signature valid:', isValid) ``` -You can also verify signatures using a [read-only account](/sdk/wallet-modules/wallet-btc/api-reference). Use [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-btc/api-reference) to create one from an owned account, then call [`readOnlyAccount.verify()`](/sdk/wallet-modules/wallet-btc/api-reference): +## Sign Typed Data (EIP-712) -```javascript title="Verify with Read-Only Account" +You can sign EIP-712 structured data using [`account.signTypedData()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): + +```javascript title="Sign Typed Data" +const typedData = { + domain: { + name: 'MyDApp', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + types: { + Mail: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'contents', type: 'string' } + ] + }, + message: { + from: '0x1234567890abcdef1234567890abcdef12345678', + to: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', + contents: 'Hello!' + } +} + +const typedDataSignature = await account.signTypedData(typedData) +console.log('Typed data signature:', typedDataSignature) +``` + +You can verify typed data signatures using [`readOnlyAccount.verifyTypedData()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): + +```javascript title="Verify Typed Data" const readOnlyAccount = await account.toReadOnlyAccount() -const isValid = await readOnlyAccount.verify('Hello, Bitcoin!', signature) -console.log('Verified with read-only account:', isValid) +const isValid = await readOnlyAccount.verifyTypedData(typedData, typedDataSignature) +console.log('Typed data signature valid:', isValid) ``` ## Next Steps -Learn how to [handle errors and manage resources](/sdk/wallet-modules/wallet-btc/guides/handle-errors). +Learn how to [handle errors and manage resources](/sdk/wallet-modules/wallet-evm-erc-4337/guides/handle-errors). *** -## Wallet BTC Usage -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/usage -Description: Guide to using the @tetherto/wdk-wallet-btc module. +## Transfer Tokens +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/transfer-tokens +Description: Transfer ERC-20 tokens with gasless transactions. -# Usage +This guide explains how to [transfer ERC-20 tokens](#transfer-erc-20-tokens), [estimate transfer fees](#estimate-transfer-fees), [set a maximum fee limit](#transfer-with-maximum-fee-limit), [select a nonce lane](#transfer-in-a-nonce-lane), and [apply UserOperation gas overrides](#transfer-with-useroperation-gas-overrides). -The `@tetherto/wdk-wallet-btc` module provides wallet management for the Bitcoin blockchain. +## Transfer ERC-20 Tokens +You can transfer ERC-20 tokens using gasless transactions with [`account.transfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - - -Install the package and create your first wallet. - - -Work with multiple accounts and custom derivation paths. - - -Query native BTC balances for owned and read-only accounts. - - -Send Bitcoin and estimate transaction fees. - - -Retrieve and filter transfer history. - - -Sign messages and verify signatures. - - -Handle errors, manage fees, and dispose of sensitive data. - - +```javascript title="Transfer ERC-20 Tokens" +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 // 1 USDT (6 decimals) +}) +console.log('Transfer UserOperation hash:', result.hash) +console.log('Transfer fee:', result.fee) +``` +## Estimate Transfer Fees - - -Get started with WDK in a Node.js environment - - -Build mobile wallets with React Native Expo - - -Get started with WDK's Bitcoin Wallet Configuration - - -Get started with WDK's Bitcoin Wallet API - - +You can estimate the fee for a token transfer without executing it using [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): -*** +```javascript title="Estimate Transfer Fee" +const quote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}) +console.log('Estimated transfer fee:', quote.fee) +``` -### Need Help? +## Transfer with Maximum Fee Limit - +You can set a maximum fee for a specific transfer by passing a `transferMaxFee` in the config object to [`account.transfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): -*** +```javascript title="Transfer with Fee Limit" +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}, { + transferMaxFee: 100000 +}) +``` -## Wallet EVM Overview -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm -Description: Overview of the @tetherto/wdk-wallet-evm module + +If the estimated fee meets or exceeds `transferMaxFee`, the transfer is cancelled with an "Exceeded maximum fee" error. + -A simple and secure package to manage BIP-44 wallets for EVM (Ethereum Virtual Machine) blockchains. This package provides a clean API for creating, managing, and interacting with Ethereum-compatible wallets using BIP-39 seed phrases and BIP-44 derivation paths. +## Transfer in a Nonce Lane -## Features +`transfer()` follows the same beta.14 nonce-lane rules as sends. Use a stable named lane for an independent transfer stream, and wait for inclusion before reusing that lane: -- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases -- **EVM Derivation Paths**: Support for BIP-44 standard derivation paths for Ethereum (m/44'/60') -- **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase -- **EVM Address Support**: Generate and manage Ethereum-compatible addresses using ethers.js -- **Message Signing**: Sign and verify messages using EVM cryptography -- **Offline Transaction Signing**: Sign EVM transactions with `signTransaction()` without broadcasting them -- **Transaction Management**: Send transactions and get fee estimates with EIP-1559 support -- **ERC20 Support**: Query native token and ERC20 token balances using smart contract interactions -- **Batch Token Balance Queries**: Fetch balances for multiple ERC20 tokens in one call with `getTokenBalances` -- **TypeScript Support**: Full TypeScript definitions included -- **Memory Safety**: Secure private key management with memory-safe HDNodeWallet implementation -- **Provider Flexibility**: Support for JSON-RPC URLs, EIP-1193 browser providers, and ordered failover provider lists -- **Gas Optimization**: Support for EIP-1559 maxFeePerGas and maxPriorityFeePerGas -- **Fee Estimation**: Dynamic fee calculation with normal (1.1x) and fast (2.0x) multipliers +```javascript title="Transfer In A Named Lane" +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}, { + nonceKey: 'token-payouts' +}) +``` -## Supported Networks +`quoteTransfer()` estimates the transfer but does not select or reserve a lane. A lane transfer rebuilds its UserOperation. The beta.14 runtime accepts lane fields per call, while the published per-call TypeScript declaration omits them; see [Use Parallel Nonce Lanes](/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions#use-parallel-nonce-lanes). -This package works with any EVM-compatible blockchain, including: +## Transfer with UserOperation Gas Overrides -- **Ethereum**: Mainnet, Sepolia -- **Polygon**: Mainnet, Amoy -- **Binance Smart Chain (BSC)**: Mainnet, Testnet -- **Arbitrum**: One, Nova -- **Optimism**: Mainnet, Sepolia -- **Avalanche C-Chain**: Mainnet, Fuji -- **And many more...** +Use the third argument to pass UserOperation gas or EIP-1559 fee overrides through `transfer()` or `quoteTransfer()`. Set `maxFeePerGas` and `maxPriorityFeePerGas` together. -## Next Steps +```javascript title="Transfer with Gas Overrides" +const txOverrides = { + callGasLimit: 90000n, + verificationGasLimit: 120000n, + maxFeePerGas: 30000000000n, + maxPriorityFeePerGas: 2000000000n +} - - -Get started with WDK in a Node.js environment - - -Get started with WDK's EVM Wallet configuration - - -Get started with WDK's EVM Wallet API - - -Get started with WDK's EVM Wallet usage - - +const quote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}, undefined, txOverrides) -*** +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}, undefined, txOverrides) +``` -### Need Help? +## Next Steps - +Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-evm-erc-4337/guides/sign-verify-messages). *** -## Wallet EVM ERC-4337 Overview -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337 -Description: Overview of the @tetherto/wdk-wallet-evm-erc-4337 module - -A simple and secure package to manage ERC-4337 compliant wallets for EVM-compatible blockchains. This package provides a clean API for creating, managing, and interacting with account abstraction wallets using BIP-39 seed phrases and EVM-specific derivation paths. - -## Features +## Wallet EVM ERC-4337 Usage +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/usage +Description: Guide to using the @tetherto/wdk-wallet-evm-erc-4337 module. -- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases -- **EVM Derivation Paths**: Support for BIP-44 standard derivation paths for Ethereum (m/44'/60') -- **Multi-Account Management**: Create and manage multiple account abstraction wallets from a single seed phrase -- **ERC-4337 Support**: Full implementation of ERC-4337 account abstraction standard -- **UserOperation Management**: Create and send UserOperations through bundlers -- **Message Signing**: Sign and verify messages using EVM cryptography -- **ERC20 Support**: Query native token and ERC20 token balances using smart contract interactions -- **TypeScript Support**: Full TypeScript definitions included -- **Memory Safety**: Secure private key management with memory-safe HDNodeWallet implementation -- **Bundler Integration**: Support for ERC-4337 bundler services -- **Gas Optimization**: Paymaster support and gas estimation for UserOperations -- **Fee Estimation**: Dynamic fee calculation with bundler-aware estimation -- **EIP-712 Typed Data Support**: Sign and verify EIP-712 structured typed data -- **Batch Token Balance Queries**: Query multiple ERC20 token balances in a single call +# Usage -## Supported Networks +The `@tetherto/wdk-wallet-evm-erc-4337` module provides account abstraction wallet management for EVM-compatible blockchains using the ERC-4337 standard. -This package works with any EVM-compatible blockchain, including: -- **Ethereum Mainnet** -- **Ethereum Testnets** (Sepolia) -- **Other EVM Chains** (Polygon, Arbitrum, Avalanche C-chain, Plasma etc.) + + +Install the package and create your first smart account. + + +Work with multiple smart accounts and custom derivation paths. + + +Query native, ERC-20, and paymaster token balances. + + +Send gasless transactions and estimate fees. + + +Transfer ERC-20 tokens with gasless transactions. + + +Sign messages and EIP-712 typed data. + + +Handle errors, manage fees, and dispose of sensitive data. + + -## Next Steps Get started with WDK in a Node.js environment - -Get started with WDK's EVM with ERC-4337 Wallet configuration + +Build mobile wallets with React Native Expo - -Get started with WDK's EVM with ERC-4337 Wallet API + +Get started with WDK's EVM ERC-4337 Wallet Configuration - -Get started with WDK's EVM with ERC-4337 Wallet usage + +Get started with WDK's EVM ERC-4337 Wallet API *** -## Need Help? +### Need Help? *** -## Wallet EVM ERC-4337 API Reference -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/api-reference -Description: Complete API documentation for @tetherto/wdk-wallet-evm-erc-4337 +## Wallet EVM API Reference +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/api-reference +Description: Complete API documentation for @tetherto/wdk-wallet-evm ## Table of Contents | Class | Description | Methods | |-------|-------------|---------| -| [WalletManagerEvmErc4337](#walletmanagerevmerc4337) | Main class for managing ERC-4337 EVM wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. | [Constructor](#constructor), [Methods](#methods) | -| [WalletAccountEvmErc4337](#walletaccountevmerc4337) | Individual ERC-4337 wallet account implementation. Extends `WalletAccountReadOnlyEvmErc4337` and implements `IWalletAccount`. | [Constructor](#constructor-1), [Methods](#methods-1), [Properties](#properties) | -| [WalletAccountReadOnlyEvmErc4337](#walletaccountreadonlyevmerc4337) | Read-only ERC-4337 wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-2), [Methods](#methods-2) | -| [ConfigurationError](#configurationerror) | Error thrown when the wallet configuration is invalid or has missing required fields. | - | - -## WalletManagerEvmErc4337 - -The main class for managing ERC-4337 EVM wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. - -### Fee Rate Behavior - -Internally, `getFeeRates()` applies these multipliers to the base fee: +| [WalletManagerEvm](#walletmanagerevm) | Main class for managing EVM wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. | [Constructor](#constructor), [Methods](#methods) | +| [WalletAccountEvm](#walletaccountevm) | Individual EVM wallet account implementation. Extends `WalletAccountReadOnlyEvm` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-1), [Methods](#methods-1), [Properties](#properties) | +| [WalletAccountReadOnlyEvm](#walletaccountreadonlyevm) | Read-only EVM wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-2), [Methods](#methods-2) | -- **Normal**: base fee × 110% -- **Fast**: base fee × 200% +## WalletManagerEvm -These multipliers are internal (`protected static`) and cannot be imported or overridden. +The main class for managing EVM wallets. +Extends `WalletManager` from `@tetherto/wdk-wallet`. ### Constructor ```javascript -new WalletManagerEvmErc4337(seed, config) +new WalletManagerEvm(seedOrSigner, config?) ``` **Parameters:** -- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes -- `config` (EvmErc4337WalletConfig): Configuration object with common fields and a gas payment mode - -**Common config fields (required for all modes):** - - `chainId` (number): The blockchain's ID (e.g., 1 for Ethereum mainnet) - - `provider` (string | Eip1193Provider | Array\): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list - - `bundlerUrl` (string): The URL of the bundler service - - `safeModulesVersion` (string): The Safe modules version (e.g., `'0.3.0'`) - -**Gas payment mode** (one of the following): - - - -Fees are paid using an ERC-20 token through a paymaster service. - -- `paymasterUrl` (string): The URL of the paymaster service -- `paymasterAddress` (string): The address of the paymaster smart contract -- `paymasterToken` (object): The paymaster token configuration - - `address` (string): The address of the ERC-20 token used for fees -- `transferMaxFee` (number | bigint, optional): Maximum fee limit in paymaster token units - -```javascript -const wallet = new WalletManagerEvmErc4337(seedPhrase, { - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - safeModulesVersion: '0.3.0', - // Paymaster token mode - paymasterUrl: 'https://api.candide.dev/public/v3/1', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USD₮ - }, - transferMaxFee: 100000 // Optional: max fee in token units -}) -``` - - -Fees are sponsored by a third party via a sponsorship policy. - -- `isSponsored` (true): Enables sponsorship mode -- `paymasterUrl` (string): The URL of the paymaster service -- `sponsorshipPolicyId` (string, optional): The sponsorship policy ID - -```javascript -const wallet = new WalletManagerEvmErc4337(seedPhrase, { - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - safeModulesVersion: '0.3.0', - // Sponsorship mode - isSponsored: true, - paymasterUrl: 'https://api.candide.dev/public/v3/1', - sponsorshipPolicyId: 'your-policy-id' // Optional -}) -``` - - -Fees are paid using the chain's native token (e.g., ETH). +- `seedOrSigner` (`string | Uint8Array | ISigner`): BIP-39 mnemonic seed phrase, seed bytes, or a derivable root signer +- `config` (object, optional): Configuration object + - `provider` (`string | Eip1193Provider | Array`, optional): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list + - `retries` (number, optional): Additional retry attempts when `provider` is an array + - `chainId` (number, optional): Network chain ID. When provided, skips automatic chain ID detection. + - `transferMaxFee` (number | bigint, optional): Maximum fee amount for transfer operations (in wei) + - `transactionMaxFee` (number | bigint, optional): Maximum fee amount for native `sendTransaction()` and provider-backed `signTransaction()` operations (in wei) -- `useNativeCoins` (true): Enables native coin fee payment -- `transferMaxFee` (number | bigint, optional): Maximum fee limit in native token units +The default signer must support derivation. Register non-derivable signers, such as private-key signers, with `addSigner()` and retrieve them by name. +**Example:** ```javascript -const wallet = new WalletManagerEvmErc4337(seedPhrase, { - chainId: 1, +const wallet = new WalletManagerEvm(seedPhrase, { provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - safeModulesVersion: '0.3.0', - // Native coins mode - useNativeCoins: true, - transferMaxFee: 100000000000000n // Optional: max fee in wei + transferMaxFee: 100000000000000, // Maximum ERC-20 transfer fee in wei + transactionMaxFee: 100000000000000 // Maximum native send/sign fee in wei +}) + +import { SeedSignerEvm } from '@tetherto/wdk-wallet-evm/signers' + +const signer = new SeedSignerEvm(seedPhrase) +const signerWallet = new WalletManagerEvm(signer, { + provider: 'https://rpc.mevblocker.io/fast' }) ``` - - ### Methods @@ -14943,16 +24883,20 @@ const wallet = new WalletManagerEvmErc4337(seedPhrase, { |--------|-------------|---------|--------| | `getRandomSeedPhrase(wordCount?)` | (static) Returns a random BIP-39 seed phrase | `string` | - | | `isValidSeedPhrase(seedPhrase)` | (static) Checks if a seed phrase is valid | `boolean` | - | -| `getAccount(index?)` | Returns a wallet account at the specified index | `Promise\` | - | -| `getAccountByPath(path)` | Returns a wallet account at the specified BIP-44 derivation path | `Promise\` | - | -| `getFeeRates()` | Returns current fee rates for transactions | `Promise\<{normal: bigint, fast: bigint}\>` | If no provider | +| `addSigner(signerName, signer)` | Registers a named signer on the manager | `WalletManagerEvm` | If `signerName` is empty | +| `getSigner(signerName?)` | Returns the default signer or a named signer | `ISigner` | If the requested signer is unavailable | +| `getSigners()` | Returns registered named signers | `Record` | - | +| `getAccount(index?, options?)` | Returns a wallet account at the specified index, optionally from a named signer | `Promise` | If the requested signer is unavailable or cannot derive | +| `getAccount(signerName)` | Returns the account associated with a registered signer | `Promise` | If the named signer is unavailable | +| `getAccountByPath(path, options?)` | Returns a wallet account at the specified BIP-44 derivation path, optionally from a named signer | `Promise` | If the requested signer is unavailable or cannot derive | +| `getFeeRates()` | Returns current fee rates for transactions | `Promise<{normal: bigint, fast: bigint}>` | If no provider is set | | `dispose()` | Disposes all wallet accounts, clearing private keys from memory | `void` | - | ### Properties | Property | Type | Description | |----------|------|-------------| -| `seed` | `Uint8Array` | The wallet's seed phrase as bytes | +| `seed` | `Uint8Array` | The wallet's seed bytes | #### `getRandomSeedPhrase(wordCount?)` (static) Returns a random BIP-39 seed phrase. @@ -14964,10 +24908,10 @@ Returns a random BIP-39 seed phrase. **Example:** ```javascript -const seedPhrase = WalletManagerEvmErc4337.getRandomSeedPhrase() +const seedPhrase = WalletManagerEvm.getRandomSeedPhrase() console.log('Seed phrase:', seedPhrase) // 12 words -const longSeedPhrase = WalletManagerEvmErc4337.getRandomSeedPhrase(24) +const longSeedPhrase = WalletManagerEvm.getRandomSeedPhrase(24) console.log('Long seed phrase:', longSeedPhrase) // 24 words ``` @@ -14981,53 +24925,119 @@ Checks if a seed phrase is valid. **Example:** ```javascript -const isValid = WalletManagerEvmErc4337.isValidSeedPhrase('abandon abandon abandon ...') +const isValid = WalletManagerEvm.isValidSeedPhrase('abandon abandon abandon ...') console.log('Valid:', isValid) ``` -#### `getAccount(index)` -Returns a wallet account at the specified index using BIP-44 derivation. +#### `addSigner(signerName, signer)` +Registers a signer under a name. Use this for external or non-default signers that should be retrieved explicitly. + +**Parameters:** +- `signerName` (string): Name used for lookup +- `signer` (ISigner): Signer instance + +**Returns:** `WalletManagerEvm` - The wallet manager + +**Example:** +```javascript +import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers' + +wallet.addSigner('treasury', new PrivateKeySignerEvm(privateKey)) +``` + +#### `getSigner(signerName?)` +Returns the default signer when called with no argument, or a registered named signer. + +**Parameters:** +- `signerName` (string, optional): Name registered with `addSigner()` + +**Returns:** `ISigner` - The signer + +#### `getSigners()` +Returns a shallow copy of the named signers registered with `addSigner()`. The default signer is not included. + +**Returns:** `Record` - Registered named signers + +#### `getAccount(index?)` +Returns a wallet account at the specified index following BIP-44 standard. Pass `options.signerName` to derive the account from a registered derivable signer. **Parameters:** - `index` (number, optional): The index of the account to get (default: 0) +- `options.signerName` (string, optional): Registered signer name -**Returns:** `Promise\` - The wallet account +**Returns:** `Promise` - The wallet account **Example:** ```javascript // Get first account (index 0) const account = await wallet.getAccount(0) -// Get default account +// Get second account (index 1) +const account1 = await wallet.getAccount(1) + +// Get first account (default) const defaultAccount = await wallet.getAccount() + +// Derive account 2 from a registered derivable signer +const signerAccount = await wallet.getAccount(2, { signerName: 'hardware-root' }) +``` + +#### `getAccount(signerName)` +Returns the wallet account associated with a registered signer. Non-derivable signers return their single account. + +**Parameters:** +- `signerName` (string): Name registered with `addSigner()` + +**Returns:** `Promise` - The signer-backed wallet account + +**Example:** +```javascript +const treasuryAccount = await wallet.getAccount('treasury') ``` #### `getAccountByPath(path)` -Returns a wallet account at the specified BIP-44 derivation path. +Returns a wallet account at the specified BIP-44 derivation path. Pass `options.signerName` to derive from a registered derivable signer. **Parameters:** - `path` (string): The derivation path (e.g., "0'/0/0") +- `options.signerName` (string, optional): Registered signer name -**Returns:** `Promise\` - The wallet account +**Returns:** `Promise` - The wallet account **Example:** ```javascript -// Full derivation path: m/44'/60'/0'/0/1 +// Full path: m/44'/60'/0'/0/1 const account = await wallet.getAccountByPath("0'/0/1") + +// Custom path: m/44'/60'/0'/0/5 +const customAccount = await wallet.getAccountByPath("0'/0/5") + +const customSignerAccount = await wallet.getAccountByPath("0'/0/5", { + signerName: 'hardware-root' +}) ``` #### `getFeeRates()` -Returns current fee rates with ERC-4337 specific multipliers. +Returns current fee rates based on network conditions with predefined multipliers. -**Returns:** `Promise\<{normal: bigint, fast: bigint}\>` - Fee rates in wei +**Returns:** `Promise<{normal: bigint, fast: bigint}>` - Fee rates in wei +- `normal`: Base fee × 1.1 (10% above base) +- `fast`: Base fee × 2.0 (100% above base) **Throws:** Error if no provider is configured **Example:** ```javascript const feeRates = await wallet.getFeeRates() -console.log('Normal fee rate:', feeRates.normal, 'wei') // base fee × 1.1 -console.log('Fast fee rate:', feeRates.fast, 'wei') // base fee × 2.0 +console.log('Normal fee rate:', feeRates.normal, 'wei') +console.log('Fast fee rate:', feeRates.fast, 'wei') + +// Use in transaction +const result = await account.sendTransaction({ + to: '0x...', + value: 1000000000000000000n, + maxFeePerGas: feeRates.fast +}) ``` #### `dispose()` @@ -15039,218 +25049,327 @@ Disposes all wallet accounts, clearing private keys from memory. wallet.dispose() ``` -## WalletAccountEvmErc4337 - -Represents an individual ERC-4337 wallet account. Extends `WalletAccountReadOnlyEvmErc4337` and implements `IWalletAccount`. - -### Constants - -The following constant is used internally for Safe account address derivation: - -```javascript -// Internal: used by predictSafeAddress() for deterministic address generation -const SALT_NONCE = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6' -``` +## WalletAccountEvm -> **Note:** This constant is not re-exported from the package entry point. Use `predictSafeAddress()` instead of referencing it directly. +Represents an individual wallet account. Extends `WalletAccountReadOnlyEvm` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. ### Constructor ```javascript -new WalletAccountEvmErc4337(seed, path, config) +new WalletAccountEvm(seed, path, config?) +new WalletAccountEvm(signer, config?) ``` **Parameters:** - `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes - `path` (string): BIP-44 derivation path (e.g., "0'/0/0") -- `config` (EvmErc4337WalletConfig): Configuration object (same as [WalletManagerEvmErc4337](#constructor)) +- `signer`: Object implementing the EVM signer shape +- `config` (object, optional): Configuration object + - `provider` (`string | Eip1193Provider | Array`, optional): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list + - `retries` (number, optional): Additional retry attempts when `provider` is an array + - `chainId` (number, optional): Network chain ID. When provided, skips automatic chain ID detection. + - `transferMaxFee` (number | bigint, optional): Maximum fee amount for transfer operations (in wei) + - `transactionMaxFee` (number | bigint, optional): Maximum fee amount for native `sendTransaction()` and `signTransaction()` operations (in wei) + +**Throws:** +- Error if seed phrase is invalid (BIP-39 validation fails) **Example:** ```javascript -const account = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { - chainId: 1, +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - safeModulesVersion: '0.3.0', - paymasterUrl: 'https://api.candide.dev/public/v3/1', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } + transferMaxFee: 100000000000000, + transactionMaxFee: 100000000000000 }) + +import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers' + +const signerAccount = new WalletAccountEvm( + new PrivateKeySignerEvm(privateKey), + { provider: 'https://rpc.mevblocker.io/fast' } +) ``` +### Static Methods + +| Method | Description | Returns | Throws | +|--------|-------------|---------|--------| +| `fromPrivateKey(privateKey, config?)` | Creates a standalone account from a raw private key | `WalletAccountEvm` | If the private key is invalid | + ### Methods | Method | Description | Returns | Throws | |--------|-------------|---------|--------| -| `predictSafeAddress(owner, config)` | (static) Predicts the Safe address for a given owner | `string` | - | -| `getAddress()` | Returns the Safe account's address | `Promise\` | - | -| `sign(message)` | Signs a message using the account's private key | `Promise\` | - | -| `verify(message, signature)` | Verifies a message signature | `Promise\` | - | -| `sendTransaction(tx, config?)` | Sends a transaction via UserOperation | `Promise\<{hash: string, fee: bigint}\>` | If fee exceeds max | -| `quoteSendTransaction(tx, config?)` | Estimates the fee for a UserOperation | `Promise\<{fee: bigint}\>` | - | -| `transfer(options, config?)` | Transfers ERC20 tokens via UserOperation | `Promise\<{hash: string, fee: bigint}\>` | If fee exceeds max | -| `quoteTransfer(options, config?)` | Estimates the fee for an ERC20 transfer | `Promise\<{fee: bigint}\>` | - | -| `approve(options)` | Approves a spender to spend ERC20 tokens | `Promise\<{hash: string, fee: bigint}\>` | - | -| `getBalance()` | Returns the native token balance (in wei) | `Promise\` | - | -| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise\` | - | -| `getPaymasterTokenBalance()` | Returns the paymaster token balance | `Promise\` | - | -| `getAllowance(token, spender)` | Returns current token allowance for a spender | `Promise\` | - | -| `getTransactionReceipt(hash)` | Returns a transaction receipt | `Promise\` | - | -| `getUserOperationReceipt(hash)` | Returns a UserOperation receipt | `Promise\` | - | -| `signTypedData(typedData)` | Signs EIP-712 typed structured data | `Promise\` | - | -| `verifyTypedData(typedData, signature)` | Verifies an EIP-712 typed data signature | `Promise\` | - | -| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise\\>` | - | -| `toReadOnlyAccount()` | Returns a read-only copy of the account | `Promise\` | - | +| `getAddress()` | Returns the account's address | `Promise` | - | +| `sign(message)` | Signs a message using the account's private key | `Promise` | - | +| `signTypedData(typedData)` | Signs typed data according to EIP-712 | `Promise` | - | +| `signTransaction(tx)` | Signs an EVM transaction without broadcasting it | `Promise` | If transaction signing fails | +| `verify(message, signature)` | Verifies a message signature | `Promise` | - | +| `verifyTypedData(typedData, signature)` | Verifies a typed data signature (EIP-712) | `Promise` | - | +| `sendTransaction(tx)` | Sends an EVM transaction object | `Promise<{hash: string, fee: bigint}>` | If no provider | +| `quoteSendTransaction(tx)` | Estimates the fee for an EVM transaction object or serialized transaction | `Promise<{fee: bigint}>` | If no provider | +| `transfer(options)` | Transfers ERC20 tokens to another address | `Promise<{hash: string, fee: bigint}>` | If no provider or fee exceeds max | +| `quoteTransfer(options)` | Estimates the fee for an ERC20 transfer | `Promise<{fee: bigint}>` | If no provider | +| `getBalance()` | Returns the native token balance (in wei) | `Promise` | If no provider | +| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise` | If no provider | +| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise>` | If no provider | +| `approve(options)` | Approves a spender to spend tokens | `Promise<{hash: string, fee: bigint}>` | If no provider | +| `getAllowance(token, spender)` | Returns current allowance for a spender | `Promise` | If no provider | +| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | If no provider | +| `toReadOnlyAccount()` | Returns a read-only copy of the account | `Promise` | - | +| `signAuthorization(auth)` | Signs an ERC-7702 authorization tuple | `Promise` | If signing fails | +| `delegate(delegateAddress)` | Delegates the EOA to a contract through an ERC-7702 type 4 transaction | `Promise<{hash: string, fee: bigint}>` | If no provider | +| `revokeDelegation()` | Revokes active ERC-7702 delegation by delegating to the zero address | `Promise<{hash: string, fee: bigint}>` | If no provider | | `dispose()` | Disposes the wallet account, clearing private keys from memory | `void` | - | -##### `getAddress()` -Returns the Safe smart contract wallet address (not the underlying EOA address). +#### `fromPrivateKey(privateKey, config?)` (static) +Creates a standalone EVM account from a raw private key. -**Returns:** `Promise\` - The Safe account's address +**Parameters:** +- `privateKey` (`string | Uint8Array`): Raw private key, as a hex string with or without `0x`, or 32 bytes +- `config` (object, optional): EVM wallet configuration + +**Returns:** `WalletAccountEvm` - The wallet account + +**Example:** +```javascript +const account = WalletAccountEvm.fromPrivateKey(privateKey, { + provider: 'https://rpc.mevblocker.io/fast' +}) +``` + +#### `getAddress()` +Returns the account's Ethereum address. + +**Returns:** `Promise` - Checksummed Ethereum address **Example:** ```javascript const address = await account.getAddress() -console.log('Safe account address:', address) // 0x... (Smart contract address) +console.log('Account address:', address) // 0x... ``` -##### `sign(message)` -Signs a message using the underlying EOA private key. +#### `sign(message)` +Signs a message using the account's private key. **Parameters:** - `message` (string): The message to sign -**Returns:** `Promise\` - The message signature +**Returns:** `Promise` - The message signature **Example:** ```javascript -const message = 'Hello, ERC-4337!' +const message = 'Hello, Ethereum!' const signature = await account.sign(message) console.log('Signature:', signature) ``` -##### `verify(message, signature)` -Verifies a message signature against the underlying EOA address. +#### `signTypedData(typedData)` +Signs typed data according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). + +**Parameters:** +- `typedData` (TypedData): The typed data to sign + - `domain` (TypedDataDomain): The domain separator (name, version, chainId, verifyingContract) + - `types` (Record\): The type definitions + - `message` (Record\): The message data + +**Returns:** `Promise` - The typed data signature + +**Example:** +```javascript +const typedData = { + domain: { + name: 'MyDApp', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + types: { + Mail: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'contents', type: 'string' } + ] + }, + message: { + from: '0xAlice...', + to: '0xBob...', + contents: 'Hello Bob!' + } +} +const signature = await account.signTypedData(typedData) +console.log('EIP-712 Signature:', signature) +``` + +#### `signTransaction(tx)` +Signs an EVM transaction and returns the signed raw transaction as a hex string. This method does not broadcast the transaction. + +**Parameters:** +- `tx` (EvmTransaction): The transaction object + - `to` (string | null, optional): Recipient address; omit or pass `null` for contract creation + - `value` (number | bigint): Amount in wei + - `data` (string, optional): Transaction data in hex format + - `gasLimit` (number | bigint, optional): Maximum gas units + - `gasPrice` (number | bigint, optional): Legacy gas price in wei + - `maxFeePerGas` (number | bigint, optional): EIP-1559 max fee per gas in wei + - `maxPriorityFeePerGas` (number | bigint, optional): EIP-1559 max priority fee per gas in wei + - `type` (number, optional): Transaction type, such as `4` for ERC-7702 + - `nonce` (number, optional): Transaction nonce + - `chainId` (number | bigint, optional): Network chain ID + - `authorizationList` (AuthorizationLike[], optional): ERC-7702 authorization list for type 4 transactions + +**Returns:** `Promise` - Signed raw transaction hex string + +**Throws:** Error if a provider is configured and the estimated transaction fee exceeds `transactionMaxFee`. + +**Example:** +```javascript +const signedTransaction = await account.signTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n, + chainId: 1 +}) + +console.log('Signed transaction:', signedTransaction) +``` + +#### `verify(message, signature)` +Verifies a message signature against the account's address. **Parameters:** - `message` (string): The original message - `signature` (string): The signature to verify -**Returns:** `Promise\` - True if signature is valid +**Returns:** `Promise` - True if signature is valid **Example:** ```javascript +const message = 'Hello, Ethereum!' +const signature = await account.sign(message) const isValid = await account.verify(message, signature) -console.log('Signature valid:', isValid) +console.log('Signature valid:', isValid) // true ``` -##### `sendTransaction(tx, config?)` -Sends a transaction via UserOperation through the bundler. +#### `verifyTypedData(typedData, signature)` +Verifies a typed data signature according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). **Parameters:** -- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[]): Transaction object or array for batch transactions - - `to` (string): Recipient address +- `typedData` (TypedData): The typed data that was signed +- `signature` (string): The signature to verify + +**Returns:** `Promise` - True if signature is valid + +**Example:** +```javascript +const isValid = await account.verifyTypedData(typedData, signature) +console.log('Typed data signature valid:', isValid) // true +``` + +#### `sendTransaction(tx)` +Sends an EVM transaction and returns the result with hash and fee. + + +In `1.0.0-beta.16`, the TypeScript declaration also accepts a serialized transaction string, but the send path does not broadcast those supplied bytes. It repopulates a transaction from the value passed to the method and can therefore broadcast a different transaction. Pass an `EvmTransaction` object here. Submit signed raw transactions through a separate relay or provider until this runtime mismatch is resolved. + + +**Parameters:** +- `tx` (EvmTransaction | string): The declared input type. Use the `EvmTransaction` object form for sending in `1.0.0-beta.16`. + - `to` (string | null, optional): Recipient address; omit or pass `null` for contract creation - `value` (number | bigint): Amount in wei - `data` (string, optional): Transaction data in hex format - - `callGasLimit`, `verificationGasLimit`, `preVerificationGas` (number | bigint, optional): Per-call overrides for the UserOperation gas limits - - `maxFeePerGas`, `maxPriorityFeePerGas` (number | bigint, optional): Per-call overrides for the EIP-1559 fee pair; set both together. In a batch, only the first transaction's gas overrides apply (see [EvmErc4337Transaction](#evmerc4337transaction)) -- `config` (optional): Per-call configuration override. Accepts a partial version of the gas payment mode fields (see [Config Override](#config-override)). + - `gasLimit` (number | bigint, optional): Maximum gas units + - `gasPrice` (number | bigint, optional): Legacy gas price in wei + - `maxFeePerGas` (number | bigint, optional): EIP-1559 max fee per gas in wei + - `maxPriorityFeePerGas` (number | bigint, optional): EIP-1559 max priority fee per gas in wei + - `type` (number, optional): Transaction type, such as `4` for ERC-7702 + - `nonce` (number, optional): Transaction nonce + - `chainId` (number | bigint, optional): Network chain ID + - `authorizationList` (AuthorizationLike[], optional): ERC-7702 authorization list for type 4 transactions -**Returns:** `Promise\<{hash: string, fee: bigint}\>` - UserOperation hash and fee +**Returns:** `Promise<{hash: string, fee: bigint}>` - Transaction result -**Throws:** Error if fee exceeds `transferMaxFee` +**Throws:** +- Error if no provider is configured +- Error if fee exceeds `transactionMaxFee` when configured **Example:** ```javascript -// Single transaction +// EIP-1559 transaction const result = await account.sendTransaction({ to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n, // 1 ETH - data: '0x' + value: 1000000000000000000, // 1 ETH in wei + maxFeePerGas: 30000000000, + maxPriorityFeePerGas: 2000000000 }) -console.log('UserOperation hash:', result.hash) -console.log('Fee paid:', result.fee) - -// Batch transactions -const batchResult = await account.sendTransaction([ - { to: '0x...', value: 100000000000000000n }, - { to: '0x...', value: 200000000000000000n } -]) -// With per-call config override -const customResult = await account.sendTransaction({ - to: '0x...', - value: 1000000000000000000n -}, { - paymasterToken: { address: '0xNewToken...' } +// Legacy transaction +const legacyResult = await account.sendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000, + gasPrice: 20000000000, + gasLimit: 21000 }) -// With per-call gas overrides (EIP-1559 fee pair set together) -const fastResult = await account.sendTransaction({ - to: '0x...', - value: 1000000000000000000n, - maxFeePerGas: 30000000000n, // 30 gwei - maxPriorityFeePerGas: 2000000000n // 2 gwei -}) +console.log('Transaction hash:', result.hash) +console.log('Transaction fee:', result.fee, 'wei') ``` -##### `quoteSendTransaction(tx, config?)` -Estimates the fee for a UserOperation without sending it. +#### `quoteSendTransaction(tx)` +Estimates the fee for an EVM transaction without sending it. **Parameters:** -- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[]): Transaction object or array (same as sendTransaction) -- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) +- `tx` (EvmTransaction | string): A transaction object or serialized transaction string -**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate +For a serialized transaction, the method parses the transaction fields for gas estimation but uses the provider's current fee data to calculate the quote. The result is a current network estimate and does not necessarily reproduce the fee settings embedded in the serialized transaction. + +**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei + +**Throws:** Error if no provider is configured **Example:** ```javascript const quote = await account.quoteSendTransaction({ to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n + value: 1000000000000000000 }) -console.log('Estimated fee:', quote.fee) +console.log('Estimated fee:', quote.fee, 'wei') ``` -##### `transfer(options, config?)` -Transfers ERC20 tokens via UserOperation. +#### `transfer(options)` +Transfers ERC20 tokens to another address using the standard transfer function. **Parameters:** - `options` (TransferOptions): Transfer options - - `token` (string): ERC20 token contract address + - `token` (string): Token contract address - `recipient` (string): Recipient address - `amount` (number | bigint): Amount in token base units -- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) -**Returns:** `Promise\<{hash: string, fee: bigint}\>` - UserOperation hash and fee +**Returns:** `Promise<{hash: string, fee: bigint}>` - Transfer result **Throws:** -- Error if fee exceeds `transferMaxFee` -- Error if insufficient token balance +- Error if no provider is configured +- Error if fee exceeds `transferMaxFee` (if configured) **Example:** ```javascript const result = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USD₮ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 // 1 USD₮ (6 decimals) -}, { - transferMaxFee: 50000 // Override max fee for this call + amount: 1000000 // 1 USDT (6 decimals) }) console.log('Transfer hash:', result.hash) -console.log('Transfer fee:', result.fee) +console.log('Transfer fee:', result.fee, 'wei') ``` -##### `quoteTransfer(options, config?)` +#### `quoteTransfer(options)` Estimates the fee for an ERC20 token transfer. **Parameters:** - `options` (TransferOptions): Transfer options (same as transfer) -- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) -**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate +**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei + +**Throws:** Error if no provider is configured **Example:** ```javascript @@ -15259,79 +25378,97 @@ const quote = await account.quoteTransfer({ recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', amount: 1000000 }) -console.log('Transfer fee estimate:', quote.fee) +console.log('Transfer fee estimate:', quote.fee, 'wei') ``` -##### `getBalance()` -Returns the Safe account's native token balance. +#### `getBalance()` +Returns the native token balance (ETH, MATIC, BNB, etc.). -**Returns:** `Promise\` - Balance in wei +**Returns:** `Promise` - Balance in wei + +**Throws:** Error if no provider is configured **Example:** ```javascript const balance = await account.getBalance() -console.log('Native balance:', balance, 'wei') +console.log('Balance:', balance, 'wei') +console.log('Balance in ETH:', balance / 1000000000000000000) ``` -##### `getTokenBalance(tokenAddress)` -Returns the balance of a specific ERC20 token in the Safe account. +#### `getTokenBalance(tokenAddress)` +Returns the balance of a specific ERC20 token using the balanceOf function. **Parameters:** - `tokenAddress` (string): The ERC20 token contract address -**Returns:** `Promise\` - Token balance in base units +**Returns:** `Promise` - Token balance in base units + +**Throws:** Error if no provider is configured **Example:** ```javascript -const tokenBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') -console.log('USDT balance:', tokenBalance) // In 6 decimal units +// Get USDT balance +const usdtBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') +console.log('USDT balance:', usdtBalance) // In 6 decimal places +console.log('USDT balance formatted:', usdtBalance / 1000000, 'USDT') ``` -##### `getPaymasterTokenBalance()` -Returns the balance of the configured paymaster token used for paying fees. +#### `getTokenBalances(tokenAddresses)` +Returns balances for multiple ERC20 tokens in one call. -**Returns:** `Promise\` - Paymaster token balance in base units +**Parameters:** +- `tokenAddresses` (string[]): List of ERC20 token contract addresses + +**Returns:** `Promise>` - Object mapping each token address to its balance in base units + +**Throws:** Error if no provider is configured **Example:** ```javascript -const paymasterBalance = await account.getPaymasterTokenBalance() -console.log('Paymaster token balance:', paymasterBalance) +const balances = await account.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT +]) -// Check if sufficient for transaction -if (paymasterBalance < 10000n) { - console.warn('Low paymaster token balance - may not cover fees') -} +console.log('USDT:', balances['0xdAC17F958D2ee523a2206206994597C13D831ec7']) +console.log('XAUT:', balances['0x68749665FF8D2d112Fa859AA293F07A622782F38']) ``` -##### `approve(options)` -Approves a spender to spend ERC20 tokens on behalf of the Safe account. +#### `approve(options)` +Approves a specific amount of tokens to a spender. **Parameters:** - `options` (ApproveOptions): Approve options - - `token` (string): ERC20 token contract address - - `spender` (string): The address allowed to spend the tokens - - `amount` (number | bigint): Amount to approve in token base units + - `token` (string): Token contract address + - `spender` (string): Spender address + - `amount` (number | bigint): Amount to approve -**Returns:** `Promise\<{hash: string, fee: bigint}\>` - Transaction result +**Returns:** `Promise<{hash: string, fee: bigint}>` - Transaction result + +**Throws:** +- Error if no provider is configured +- Error if trying to re-approve USDT on Ethereum without resetting to 0 first **Example:** ```javascript const result = await account.approve({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - spender: '0xSpenderContract...', - amount: 1000000n // 1 USD₮ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + spender: '0xSpenderAddress...', + amount: 1000000n }) -console.log('Approval hash:', result.hash) +console.log('Approve hash:', result.hash) ``` -##### `getAllowance(token, spender)` +#### `getAllowance(token, spender)` Returns the current token allowance for the given spender. **Parameters:** - `token` (string): ERC20 token contract address - `spender` (string): The spender's address -**Returns:** `Promise\` - The current allowance +**Returns:** `Promise` - The current allowance + +**Throws:** Error if no provider is configured **Example:** ```javascript @@ -15342,125 +25479,75 @@ const allowance = await account.getAllowance( console.log('Current allowance:', allowance) ``` -##### `getTransactionReceipt(hash)` +#### `getTransactionReceipt(hash)` Returns a transaction receipt by hash. **Parameters:** - `hash` (string): The transaction hash -**Returns:** `Promise\` - Transaction receipt or null if not mined +**Returns:** `Promise` - Transaction receipt or null if not mined + +**Throws:** Error if no provider is configured **Example:** ```javascript const receipt = await account.getTransactionReceipt('0x...') if (receipt) { console.log('Confirmed in block:', receipt.blockNumber) + console.log('Status:', receipt.status) // 1 = success, 0 = failed } ``` -##### `getUserOperationReceipt(hash)` -Returns a UserOperation receipt by hash. - -**Parameters:** -- `hash` (string): The UserOperation hash - -**Returns:** `Promise\` - UserOperation receipt or null if not processed - -**Example:** -```javascript -const receipt = await account.getUserOperationReceipt('0x...') -if (receipt) { - console.log('UserOp receipt:', receipt) -} -``` - -##### `signTypedData(typedData)` -Signs EIP-712 typed structured data using the underlying EOA private key. - -**Parameters:** -- `typedData` (TypedData): The typed data object containing domain, types, primaryType, and message - -**Returns:** `Promise\` - The typed data signature - -**Example:** -```javascript -const typedData = { - domain: { - name: 'MyDApp', - version: '1', - chainId: 1, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' - }, - types: { - Transfer: [ - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' } - ] - }, - primaryType: 'Transfer', - message: { - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000n - } -} - -const signature = await account.signTypedData(typedData) -console.log('Typed data signature:', signature) -``` - -##### `verifyTypedData(typedData, signature)` -Verifies an EIP-712 typed data signature against the underlying EOA address. - -**Parameters:** -- `typedData` (TypedData): The original typed data object -- `signature` (string): The signature to verify +#### `toReadOnlyAccount()` +Creates a read-only copy of the account with the same configuration. -**Returns:** `Promise\` - True if the signature is valid +**Returns:** `Promise` - Read-only account instance **Example:** ```javascript -const isValid = await account.verifyTypedData(typedData, signature) -console.log('Typed data signature valid:', isValid) +const readOnlyAccount = await account.toReadOnlyAccount() + +// Can check balances but cannot send transactions +const balance = await readOnlyAccount.getBalance() +// readOnlyAccount.sendTransaction() // Would throw error ``` -##### `getTokenBalances(tokenAddresses)` -Returns balances for multiple ERC20 tokens in a single call. +#### `signAuthorization(auth)` +Signs an ERC-7702 authorization tuple. **Parameters:** -- `tokenAddresses` (string[]): Array of ERC20 token contract addresses +- `auth` (AuthorizationRequest): ERC-7702 authorization request -**Returns:** `Promise\\>` - Map of token address to balance in base units +**Returns:** `Promise` - The signed authorization **Example:** ```javascript -const balances = await account.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC -]) -for (const [address, balance] of balances) { - console.log(`Token ${address}: ${balance}`) -} +const authorization = await account.signAuthorization({ + chainId: 1, + address: delegateContract, + nonce: 0 +}) ``` -##### `toReadOnlyAccount()` -Creates a read-only copy of the account with the same Safe address and configuration. +#### `delegate(delegateAddress)` +Delegates the EOA to a smart contract through an ERC-7702 type 4 transaction. -**Returns:** `Promise\` - Read-only account instance +**Parameters:** +- `delegateAddress` (string): Contract address to delegate to -**Example:** -```javascript -const readOnlyAccount = await account.toReadOnlyAccount() +**Returns:** `Promise<{hash: string, fee: bigint}>` - Transaction result -// Can check balances but cannot send transactions -const balance = await readOnlyAccount.getBalance() -// readOnlyAccount.sendTransaction() // Would not be available -``` +#### `revokeDelegation()` +Revokes active ERC-7702 delegation by delegating to the zero address. -##### `dispose()` -Disposes the wallet account, clearing private keys from memory. +**Returns:** `Promise<{hash: string, fee: bigint}>` - Transaction result + +#### `dispose()` +Disposes the wallet account, erasing the private key from memory. **Example:** ```javascript +// Clean up when done account.dispose() ``` @@ -15470,7 +25557,8 @@ account.dispose() |----------|------|-------------| | `index` | `number` | The derivation path's index of this account | | `path` | `string` | The full BIP-44 derivation path of this account | -| `keyPair` | `{privateKey: Uint8Array \| null, publicKey: Uint8Array}` | The account's key pair (⚠️ Contains sensitive data) | +| `keyPair` | `{privateKey: Uint8Array \| null, publicKey: Uint8Array}` | The account's key pair (⚠️ Contains sensitive data). The returned arrays are bound to the account — treat them as a read-only view and do not modify their contents. `privateKey` is `null` after `dispose()` is called. | +| `address` | `string` | The account's Ethereum address (inherited from `WalletAccountReadOnlyEvm`) | **Example:** ```javascript @@ -15480,130 +25568,76 @@ console.log('Account path:', account.path) // m/44'/60'/0'/0/0 // ⚠️ SENSITIVE: Handle with care const { privateKey, publicKey } = account.keyPair console.log('Public key length:', publicKey.length) // 65 bytes -console.log('Private key length:', privateKey?.length) // 32 bytes (null after dispose) +if (privateKey !== null) { + console.log('Private key length:', privateKey.length) // 32 bytes +} ``` -⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, or expose the private key. - -## WalletAccountReadOnlyEvmErc4337 -Represents a read-only ERC-4337 wallet account that can query balances and estimate fees but cannot send transactions. - -### Constants - -The following constant is used internally for Safe account address derivation: +⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, or expose the private key. The byte arrays are bound to the wallet account — do not modify their contents. -```javascript -// Internal: used by predictSafeAddress() for deterministic address generation -const SALT_NONCE = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6' -``` +## WalletAccountReadOnlyEvm -> **Note:** This constant is not re-exported from the package entry point. Use `predictSafeAddress()` instead of referencing it directly. +Represents a read-only wallet account that can query balances and estimate fees but cannot send transactions. ### Constructor ```javascript -new WalletAccountReadOnlyEvmErc4337(address, config) +new WalletAccountReadOnlyEvm(address, config?) ``` **Parameters:** -- `address` (string): The EOA address (owner address) -- `config` (`Omit`): Configuration object without `transferMaxFee` +- `address` (string): The account's Ethereum address +- `config` (`Omit`, optional): Configuration object (same as `EvmWalletConfig` but without send-only fee caps, since read-only accounts cannot send transactions) + - `provider` (`string | Eip1193Provider | Array`, optional): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list + - `retries` (number, optional): Additional retry attempts when `provider` is an array + - `chainId` (number, optional): Network chain ID. When provided, skips automatic chain ID detection. **Example:** ```javascript -const readOnlyAccount = new WalletAccountReadOnlyEvmErc4337('0x...', { - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - safeModulesVersion: '0.3.0', - paymasterUrl: 'https://api.candide.dev/public/v3/1', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } +const readOnlyAccount = new WalletAccountReadOnlyEvm('0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', { + provider: 'https://rpc.mevblocker.io/fast' }) ``` -### Static Methods - -| Method | Description | Returns | -|--------|-------------|---------| -| `predictSafeAddress(owner, config)` | Predicts the Safe address for a given owner without instantiating an account | `string` | - -#### `predictSafeAddress(owner, config)` (static) -Predicts the address of a Safe account. - -**Parameters:** -- `owner` (string): The Safe owner's EOA address -- `config` (object): Configuration with: - - `chainId` (number): The blockchain ID - - `safeModulesVersion` (string): The Safe modules version - -**Returns:** `string` - The predicted Safe address - -**Example:** -```javascript -const safeAddress = WalletAccountReadOnlyEvmErc4337.predictSafeAddress( - '0xOwnerEOA...', - { chainId: 1, safeModulesVersion: '0.3.0' } -) -console.log('Predicted Safe address:', safeAddress) +### Properties -// Also available on WalletAccountEvmErc4337 (inherited) -const sameAddress = WalletAccountEvmErc4337.predictSafeAddress( - '0xOwnerEOA...', - { chainId: 1, safeModulesVersion: '0.3.0' } -) -``` +| Property | Type | Description | +|----------|------|-------------| +| `address` | `string` | The account's Ethereum address | ### Methods | Method | Description | Returns | Throws | |--------|-------------|---------|--------| -| `getAddress()` | Returns the Safe account's address | `Promise\` | - | -| `verify(message, signature)` | Verifies a message signature | `Promise\` | - | -| `getBalance()` | Returns the native token balance (in wei) | `Promise\` | - | -| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise\` | - | -| `getPaymasterTokenBalance()` | Returns the paymaster token balance | `Promise\` | - | -| `getAllowance(token, spender)` | Returns current token allowance for a spender | `Promise\` | - | -| `quoteSendTransaction(tx, config?)` | Estimates the fee for a UserOperation | `Promise\<{fee: bigint}\>` | If simulation fails | -| `quoteTransfer(options, config?)` | Estimates the fee for an ERC20 transfer | `Promise\<{fee: bigint}\>` | If simulation fails | -| `getTransactionReceipt(hash)` | Returns a transaction receipt | `Promise\` | - | -| `getUserOperationReceipt(hash)` | Returns a UserOperation receipt | `Promise\` | - | -| `signTypedData(typedData)` | Signs EIP-712 typed structured data | `Promise\` | - | -| `verifyTypedData(typedData, signature)` | Verifies an EIP-712 typed data signature | `Promise\` | - | -| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise\\>` | - | +| `getAddress()` | Returns the account's address | `Promise` | - | +| `getBalance()` | Returns the native token balance (in wei) | `Promise` | If no provider | +| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise` | If no provider | +| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise>` | If no provider | +| `quoteSendTransaction(tx)` | Estimates the fee for an EVM transaction | `Promise<{fee: bigint}>` | If no provider | +| `quoteTransfer(options)` | Estimates the fee for an ERC20 transfer | `Promise<{fee: bigint}>` | If no provider | +| `verify(message, signature)` | Verifies a message signature | `Promise` | - | +| `verifyTypedData(typedData, signature)` | Verifies a typed data signature (EIP-712) | `Promise` | - | +| `getDelegation()` | Checks active ERC-7702 delegation status | `Promise` | If no provider | +| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | If no provider | +| `getAllowance(token, spender)` | Returns current allowance for a spender | `Promise` | If no provider | -##### `getAddress()` -Returns the Safe smart contract wallet address. +#### `getAddress()` +Returns the account's Ethereum address. -**Returns:** `Promise\` - The Safe account's address +**Returns:** `Promise` - Checksummed Ethereum address **Example:** ```javascript const address = await readOnlyAccount.getAddress() -console.log('Safe address:', address) +console.log('Account address:', address) // 0x... ``` -##### `verify(message, signature)` -Verifies a message signature against the underlying EOA address. - -**Parameters:** -- `message` (string): The original message -- `signature` (string): The signature to verify - -**Returns:** `Promise\` - True if signature is valid - -**Example:** -```javascript -const isValid = await readOnlyAccount.verify(message, signature) -console.log('Signature valid:', isValid) -``` +#### `getBalance()` +Returns the account's native token balance. -##### `getBalance()` -Returns the Safe account's native token balance. +**Returns:** `Promise` - Balance in wei -**Returns:** `Promise\` - Balance in wei +**Throws:** Error if no provider is configured **Example:** ```javascript @@ -15611,13 +25645,15 @@ const balance = await readOnlyAccount.getBalance() console.log('Balance:', balance, 'wei') ``` -##### `getTokenBalance(tokenAddress)` +#### `getTokenBalance(tokenAddress)` Returns the balance of a specific ERC20 token. **Parameters:** - `tokenAddress` (string): The ERC20 token contract address -**Returns:** `Promise\` - Token balance in base units +**Returns:** `Promise` - Token balance in base units + +**Throws:** Error if no provider is configured **Example:** ```javascript @@ -15625,69 +25661,53 @@ const tokenBalance = await readOnlyAccount.getTokenBalance('0xdAC17F958D2ee523a2 console.log('USDT balance:', tokenBalance) ``` -##### `getPaymasterTokenBalance()` -Returns the balance of the configured paymaster token. - -**Returns:** `Promise\` - Paymaster token balance in base units - -**Example:** -```javascript -const paymasterBalance = await readOnlyAccount.getPaymasterTokenBalance() -console.log('Paymaster token balance:', paymasterBalance) -``` - -##### `getAllowance(token, spender)` -Returns the current token allowance for the given spender. +#### `getTokenBalances(tokenAddresses)` +Returns balances for multiple ERC20 tokens. **Parameters:** -- `token` (string): ERC20 token contract address -- `spender` (string): The spender's address +- `tokenAddresses` (string[]): List of ERC20 token contract addresses -**Returns:** `Promise\` - The current allowance +**Returns:** `Promise>` - Object mapping each token address to its balance in base units + +**Throws:** Error if no provider is configured **Example:** ```javascript -const allowance = await readOnlyAccount.getAllowance( - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - '0xSpenderContract...' -) -console.log('Allowance:', allowance) +const balances = await readOnlyAccount.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT +]) +console.log('Balances:', balances) ``` -##### `quoteSendTransaction(tx, config?)` -Estimates the fee for a UserOperation. +#### `quoteSendTransaction(tx)` +Estimates the fee for an EVM transaction. **Parameters:** -- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[]): Transaction object or array -- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) +- `tx` (EvmTransaction): The transaction object -**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate +**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei -**Throws:** Error if simulation fails +**Throws:** Error if no provider is configured **Example:** ```javascript -try { - const quote = await readOnlyAccount.quoteSendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n - }) - console.log('Estimated fee:', quote.fee) -} catch (error) { - if (error.message.includes('not enough funds')) { - console.error('Insufficient paymaster token balance') - } -} +const quote = await readOnlyAccount.quoteSendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000 +}) +console.log('Estimated fee:', quote.fee, 'wei') ``` -##### `quoteTransfer(options, config?)` +#### `quoteTransfer(options)` Estimates the fee for an ERC20 token transfer. **Parameters:** - `options` (TransferOptions): Transfer options -- `config` (optional): Per-call configuration override (see [Config Override](#config-override)) -**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate +**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei + +**Throws:** Error if no provider is configured **Example:** ```javascript @@ -15696,234 +25716,202 @@ const quote = await readOnlyAccount.quoteTransfer({ recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', amount: 1000000 }) -console.log('Transfer fee estimate:', quote.fee) +console.log('Transfer fee estimate:', quote.fee, 'wei') ``` -##### `getTransactionReceipt(hash)` -Returns a transaction receipt by hash. +#### `verify(message, signature)` +Verifies a message signature against the account's address. **Parameters:** -- `hash` (string): The transaction hash +- `message` (string): The original message +- `signature` (string): The signature to verify -**Returns:** `Promise\` - Transaction receipt or null if not mined +**Returns:** `Promise` - True if signature is valid **Example:** ```javascript -const receipt = await readOnlyAccount.getTransactionReceipt('0x...') -if (receipt) { - console.log('Transaction confirmed in block:', receipt.blockNumber) - console.log('Status:', receipt.status) // 1 = success, 0 = failed -} else { - console.log('Transaction not yet mined') -} +const message = 'Hello, Ethereum!' +const signature = await account.sign(message) + +const readOnlyAccount = new WalletAccountReadOnlyEvm('0x...', { provider: '...' }) +const isValid = await readOnlyAccount.verify(message, signature) +console.log('Signature valid:', isValid) // true ``` -##### `getUserOperationReceipt(hash)` -Returns a UserOperation receipt by hash. +#### `verifyTypedData(typedData, signature)` +Verifies a typed data signature according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). **Parameters:** -- `hash` (string): The UserOperation hash +- `typedData` (TypedData): The typed data that was signed +- `signature` (string): The signature to verify -**Returns:** `Promise\` - UserOperation receipt or null if not processed +**Returns:** `Promise` - True if signature is valid **Example:** ```javascript -const receipt = await readOnlyAccount.getUserOperationReceipt('0x...') -if (receipt) { - console.log('UserOp receipt:', receipt) -} +const isValid = await readOnlyAccount.verifyTypedData(typedData, signature) +console.log('Typed data signature valid:', isValid) // true ``` -##### `signTypedData(typedData)` -Signs EIP-712 typed structured data using the underlying EOA address. - -**Parameters:** -- `typedData` (TypedData): The typed data object containing domain, types, primaryType, and message +#### `getDelegation()` +Checks whether the account currently has an active ERC-7702 delegation. -**Returns:** `Promise\` - The typed data signature +**Returns:** `Promise` - Delegation status and delegate address **Example:** ```javascript -const typedData = { - domain: { - name: 'MyDApp', - version: '1', - chainId: 1, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' - }, - types: { - Transfer: [ - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' } - ] - }, - primaryType: 'Transfer', - message: { - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000n - } -} - -const signature = await readOnlyAccount.signTypedData(typedData) -console.log('Typed data signature:', signature) +const delegation = await readOnlyAccount.getDelegation() +console.log('Delegated:', delegation.isDelegated) +console.log('Delegate:', delegation.delegateAddress) ``` -##### `verifyTypedData(typedData, signature)` -Verifies an EIP-712 typed data signature against the underlying EOA address. +#### `getTransactionReceipt(hash)` +Returns a transaction's receipt if it has been mined. **Parameters:** -- `typedData` (TypedData): The original typed data object -- `signature` (string): The signature to verify +- `hash` (string): The transaction hash -**Returns:** `Promise\` - True if the signature is valid +**Returns:** `Promise` - Transaction receipt or null if not yet mined + +**Throws:** Error if no provider is configured **Example:** ```javascript -const isValid = await readOnlyAccount.verifyTypedData(typedData, signature) -console.log('Typed data signature valid:', isValid) +const receipt = await readOnlyAccount.getTransactionReceipt('0x...') +if (receipt) { + console.log('Transaction confirmed in block:', receipt.blockNumber) + console.log('Gas used:', receipt.gasUsed) + console.log('Status:', receipt.status) // 1 = success, 0 = failed +} else { + console.log('Transaction not yet mined') +} ``` -##### `getTokenBalances(tokenAddresses)` -Returns balances for multiple ERC20 tokens in a single call. +#### `getAllowance(token, spender)` +Returns the current allowance for the given token and spender. **Parameters:** -- `tokenAddresses` (string[]): Array of ERC20 token contract addresses +- `token` (string): The token's address +- `spender` (string): The spender's address -**Returns:** `Promise\\>` - Map of token address to balance in base units +**Returns:** `Promise` - The allowance **Example:** ```javascript -const balances = await readOnlyAccount.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC -]) -for (const [address, balance] of balances) { - console.log(`Token ${address}: ${balance}`) -} +const allowance = await readOnlyAccount.getAllowance( + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + '0xSpenderAddress...' +) +console.log('Allowance:', allowance) ``` ## Types -### EvmErc4337WalletConfig - -The configuration is a union type combining common fields with one of three gas payment modes: +### EvmTransaction ```typescript -// Common fields (required for all modes) -interface EvmErc4337WalletCommonConfig { - chainId: number; // Blockchain ID - provider: string | Eip1193Provider | Array; // RPC provider or failover list - bundlerUrl: string; // Bundler service URL - safeModulesVersion: string; // Safe modules version (e.g., '0.3.0') -} - -// Mode 1: Paymaster Token -interface EvmErc4337WalletPaymasterTokenConfig { - isSponsored?: false; - useNativeCoins?: false; - paymasterUrl: string; // Paymaster service URL - paymasterAddress: string; // Paymaster contract address - paymasterToken: { address: string }; // ERC-20 token for fees - transferMaxFee?: number | bigint; // Maximum fee limit +interface EvmTransaction { + to?: string | null; // Recipient address; omit or pass null for contract creation + value: number | bigint; // The amount of ethers to send (in wei) + data?: string; // The transaction's data in hex format (optional) + gasLimit?: number | bigint; // Maximum amount of gas this transaction can use (optional) + gasPrice?: number | bigint; // Legacy gas price in wei (optional) + maxFeePerGas?: number | bigint; // EIP-1559 max fee per gas in wei (optional) + maxPriorityFeePerGas?: number | bigint; // EIP-1559 priority fee in wei (optional) + type?: number; // Transaction type, such as 4 for ERC-7702 (optional) + nonce?: number; // Transaction nonce (optional) + chainId?: number | bigint; // Network chain ID (optional) + authorizationList?: AuthorizationLike[]; // ERC-7702 authorization list for type 4 transactions (optional) } +``` -// Mode 2: Sponsorship Policy -interface EvmErc4337WalletSponsorshipPolicyConfig { - isSponsored: true; - useNativeCoins?: false; - paymasterUrl: string; // Paymaster service URL - sponsorshipPolicyId?: string; // Sponsorship policy ID -} +### TransferOptions -// Mode 3: Native Coins -interface EvmErc4337WalletNativeCoinsConfig { - isSponsored?: false; - useNativeCoins: true; - transferMaxFee?: number | bigint; // Maximum fee limit +```typescript +interface TransferOptions { + token: string; // ERC20 token contract address + recipient: string; // Recipient's Ethereum address + amount: number | bigint; // Amount in token's base units } - -// Full config type -type EvmErc4337WalletConfig = EvmErc4337WalletCommonConfig & - (EvmErc4337WalletPaymasterTokenConfig | - EvmErc4337WalletSponsorshipPolicyConfig | - EvmErc4337WalletNativeCoinsConfig); ``` -### Config Override - -The `config` parameter on `sendTransaction`, `quoteSendTransaction`, `transfer`, and `quoteTransfer` allows per-call overrides of gas payment settings: +### TransactionResult ```typescript -type ConfigOverride = Partial< - EvmErc4337WalletPaymasterTokenConfig | - EvmErc4337WalletSponsorshipPolicyConfig | - EvmErc4337WalletNativeCoinsConfig ->; +interface TransactionResult { + hash: string; // Transaction hash + fee: bigint; // Transaction fee paid in wei +} ``` -**Available override fields:** -- `isSponsored` (boolean): Switch to sponsorship mode -- `useNativeCoins` (boolean): Switch to native coin mode -- `paymasterUrl` (string): Override paymaster URL -- `paymasterAddress` (string): Override paymaster contract -- `paymasterToken` (\{address: string\}): Override paymaster token -- `sponsorshipPolicyId` (string): Set sponsorship policy -- `transferMaxFee` (number | bigint): Override maximum fee - -### EvmErc4337Transaction - -The transaction shape accepted by `sendTransaction`, `quoteSendTransaction`, and `signTransaction`. Beyond the call fields (`to`, `value`, `data`), it accepts optional gas overrides that are applied to the resulting UserOperation. +### TransferResult ```typescript -interface EvmErc4337Transaction { - to: string; // Recipient address - value: number | bigint; // Amount of native coin in wei - data?: string; // Call data in hex format (optional) - callGasLimit?: number | bigint; // Override the UserOperation call gas limit (optional) - verificationGasLimit?: number | bigint; // Override the UserOperation verification gas limit (optional) - preVerificationGas?: number | bigint; // Override the UserOperation pre-verification gas (optional) - maxFeePerGas?: number | bigint; // Override the UserOperation max fee per gas — EIP-1559 cap (optional) - maxPriorityFeePerGas?: number | bigint; // Override the UserOperation max priority fee per gas (optional) +interface TransferResult { + hash: string; // Transfer transaction hash + fee: bigint; // Transfer fee paid in wei } ``` -The gas-override fields are optional. When omitted, the gas limits fall back to AbstractionKit's estimation and the fee pair (`maxFeePerGas` / `maxPriorityFeePerGas`) falls back to the bundler-fetched gas price. Setting either fee field disables the bundler-fetched fee fallback for both, so set them together. In a batched call (`tx` passed as an array), only the gas overrides on the first transaction are honored — a UserOperation carries a single set of gas fields regardless of how many calls it batches. - -### TransferOptions +### FeeRates ```typescript -interface TransferOptions { - token: string; // ERC20 token contract address - recipient: string; // Recipient address - amount: number | bigint; // Amount in token base units +interface FeeRates { + normal: bigint; // Normal priority fee rate (base fee × 1.1) + fast: bigint; // Fast priority fee rate (base fee × 2.0) } ``` -### ApproveOptions +### KeyPair ```typescript -interface ApproveOptions { - token: string; // ERC20 token contract address - spender: string; // Address allowed to spend tokens - amount: number | bigint; // Amount to approve in base units +interface KeyPair { + privateKey: Uint8Array | null; // Private key as Uint8Array (32 bytes, null after dispose) + publicKey: Uint8Array; // Public key as Uint8Array (65 bytes) } ``` -### TransactionResult +### EVM signer structural shape ```typescript -interface TransactionResult { - hash: string; // UserOperation hash - fee: bigint; // Fee paid +interface EvmSignerLike extends ISigner { + readonly isDerivable: boolean; + readonly index?: number; + readonly path?: string; + readonly address?: string; + readonly keyPair: KeyPair; + derive(relPath: string): Promise; + getAddress(): Promise; + sign(message: string): Promise; + signTransaction(unsignedTx: UnsignedEvmTransaction): Promise; + signTypedData(typedData: TypedData): Promise; + signAuthorization(auth: AuthorizationRequest): Promise; + dispose(): void; } ``` -### TransferResult +`SeedSignerEvm` and `PrivateKeySignerEvm` are exported from `@tetherto/wdk-wallet-evm/signers`. `SeedSignerEvm` supports derivation and can be used as a manager default signer. `PrivateKeySignerEvm` represents one private-key account, does not support derivation, and should be registered by name or used directly with `WalletAccountEvm`. + +### UnsignedEvmTransaction ```typescript -interface TransferResult { - hash: string; // UserOperation hash - fee: bigint; // Fee paid +interface UnsignedEvmTransaction { + chainId: number; + nonce: number; + from: string; + to: string | null; + data: string; + value: number | bigint; + type: number; + gasLimit: number | bigint; + gasPrice?: number | bigint; + maxFeePerGas?: number | bigint; + maxPriorityFeePerGas?: number | bigint; + accessList?: any[]; + maxFeePerBlobGas?: number | bigint; + blobs?: any[]; + blobVersionedHashes?: string[]; + authorizationList?: AuthorizationLike[]; } ``` @@ -15931,10 +25919,9 @@ interface TransferResult { ```typescript interface TypedData { - domain: TypedDataDomain; // EIP-712 domain separator - types: Record; // Type definitions - primaryType: string; // Primary type name - message: Record; // Structured message data + domain: TypedDataDomain; // The domain separator + types: Record; // The type definitions + message: Record; // The message data } ``` @@ -15942,77 +25929,74 @@ interface TypedData { ```typescript interface TypedDataDomain { - name?: string; // DApp or protocol name - version?: string; // Domain version - chainId?: number; // Blockchain ID - verifyingContract?: string; // Contract address - salt?: string; // Optional salt + name?: string; // The domain name (e.g., the DApp name) + version?: string; // The domain version + chainId?: number | bigint; // The chain ID + verifyingContract?: string; // The verifying contract address + salt?: string; // An optional salt } ``` ### TypedDataField -```typescript -interface TypedDataField { - name: string; // Field name - type: string; // Solidity type (e.g., 'address', 'uint256') -} -``` - -### UserOperationReceipt - -```typescript -interface UserOperationReceipt { - userOpHash: string; // UserOperation hash - sender: string; // Sender address - nonce: bigint; // Nonce - actualGasUsed: bigint; // Gas used - actualGasCost: bigint; // Gas cost - success: boolean; // Whether the operation succeeded - receipt: EvmTransactionReceipt; // The underlying transaction receipt +```typescript +interface TypedDataField { + name: string; // The field name + type: string; // The field type (e.g., 'address', 'uint256', 'string') } ``` -### ConfigurationError +### EvmWalletConfig ```typescript -class ConfigurationError extends Error { - // Thrown when the wallet configuration is invalid - // e.g., missing required fields for the selected gas payment mode +interface EvmWalletConfig { + provider?: string | Eip1193Provider | Array; // RPC URL, EIP-1193 provider, or ordered failover list + retries?: number; // Additional retry attempts for provider arrays + chainId?: number; // Network chain ID. Skips automatic detection when provided. + transferMaxFee?: number | bigint; // Maximum ERC-20 transfer fee in wei + transactionMaxFee?: number | bigint; // Maximum native send/sign fee in wei } ``` -### FeeRates +### DelegationInfo ```typescript -interface FeeRates { - normal: bigint; // Fee rate for normal priority - fast: bigint; // Fee rate for fast priority +interface DelegationInfo { + isDelegated: boolean; // Whether the account has an active ERC-7702 delegation + delegateAddress: string | null; // Delegate contract address, or null when not delegated } ``` -### KeyPair +### ApproveOptions ```typescript -interface KeyPair { - publicKey: Uint8Array; // The public key - privateKey: Uint8Array | null; // The private key (null after dispose) +interface ApproveOptions { + token: string; // ERC20 token contract address + spender: string; // Address allowed to spend tokens + amount: number | bigint; // Amount to approve in base units } ``` -### Internal Constants - -The following constants are used internally by the SDK and are **not importable** from the package entry point. +### EvmTransactionReceipt ```typescript -// Used by predictSafeAddress() for deterministic address generation -// Not re-exported from '@tetherto/wdk-wallet-evm-erc-4337' -const SALT_NONCE: string = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6'; - -// Fee rate multipliers (protected static on WalletManagerEvm) -// Applied internally by getFeeRates() -const _FEE_RATE_NORMAL_MULTIPLIER: bigint; // ~110% -const _FEE_RATE_FAST_MULTIPLIER: bigint; // ~200% +interface EvmTransactionReceipt { + to: string; // Recipient address + from: string; // Sender address + contractAddress: string | null; // Contract address if contract creation + transactionIndex: number; // Transaction index in block + gasUsed: bigint; // Gas actually used + logsBloom: string; // Bloom filter for logs + blockHash: string; // Block hash containing transaction + transactionHash: string; // Transaction hash + logs: Array; // Event logs + blockNumber: number; // Block number + confirmations: number; // Number of confirmations + cumulativeGasUsed: bigint; // Cumulative gas used in block + effectiveGasPrice: bigint; // Effective gas price paid + status: number; // Transaction status (1 = success, 0 = failed) + type: number; // Transaction type (0 = legacy, 2 = EIP-1559) +} ``` @@ -16022,332 +26006,234 @@ Get started with WDK in a Node.js environment Build mobile wallets with React Native Expo - -Get started with WDK's EVM with ERC-4337 Wallet Usage + +Get started with WDK's EVM Wallet Usage - -Get started with WDK's EVM with ERC-4337 Wallet Configuration + +Get started with WDK's EVM Wallet Configuration *** -## Need Help? +### Need Help? *** ## Configuration -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/configuration -Description: Configuration options and settings for @tetherto/wdk-wallet-evm-erc-4337 +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/configuration +Description: Configuration options and settings for @tetherto/wdk-wallet-evm ## Wallet Configuration -The `WalletManagerEvmErc4337` requires a complete ERC-4337 configuration object with all required parameters: +The `WalletManagerEvm` accepts a configuration object that defines how the wallet interacts with the blockchain: ```javascript -import WalletManagerEvmErc4337 from '@tetherto/wdk-wallet-evm-erc-4337' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' const config = { - // Required parameters - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - safeModulesVersion: '0.3.0', // '0.2.0' and '0.3.0' are valid - bundlerUrl: `https://api.pimlico.io/v1/ethereum/rpc?apikey=${PIMLICO_API_KEY}`, - paymasterUrl: `https://api.pimlico.io/v2/ethereum/rpc?apikey=${PIMLICO_API_KEY}`, - paymasterAddress: '0x777777777777AeC03fd955926DbF81597e66834C', - transferMaxFee: 100000000000000, - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } -} - -const wallet = new WalletManagerEvmErc4337(seedPhrase, config) -``` + // Recommended: RPC endpoint URL, EIP-1193 provider, or ordered failover list + provider: 'https://eth.drpc.org', -## Account Configuration + // Optional: Skip automatic chain ID detection when the network is known + chainId: 1, -Both `WalletAccountEvmErc4337` and `WalletAccountReadOnlyEvmErc4337` use the same configuration structure: + // Optional: Additional failover attempts when provider is an array + retries: 2, -```javascript -import { WalletAccountEvmErc4337, WalletAccountReadOnlyEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' + // Optional: Maximum fee for ERC-20 transfer operations (in wei) + transferMaxFee: 100000000000000, // 0.0001 ETH -// Full access account -const account = new WalletAccountEvmErc4337( - seedPhrase, - "0'/0/0", // BIP-44 derivation path - config // Same config as wallet manager -) + // Optional: Maximum fee for native send/provider-backed sign operations (in wei) + transactionMaxFee: 100000000000000 +} -// Read-only account (transferMaxFee not needed) -const readOnlyAccount = new WalletAccountReadOnlyEvmErc4337( - '0x...', // Smart contract wallet address - { - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - paymasterUrl: 'https://api.candide.dev/public/v3/1', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - // Note: transferMaxFee omitted for read-only accounts - } -) +const wallet = new WalletManagerEvm(seedPhrase, config) ``` -## Configuration Options - -### Chain ID - -The `chainId` option specifies the blockchain network ID. **Required** for fee estimation and smart account initialization. - -**Type:** `number` -**Required:** Yes - -**Examples:** -```javascript -// Ethereum Mainnet -const config = { chainId: 1 } +## Signer Configuration -// Polygon Mainnet -const config = { chainId: 137 } +`WalletManagerEvm` accepts either a BIP-39 seed phrase/seed bytes or a derivable EVM signer as its first argument. Use the `@tetherto/wdk-wallet-evm/signers` entrypoint when you need explicit signer objects: -// Arbitrum One -const config = { chainId: 42161 } +```javascript title="Create A Manager From A Seed Signer" +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import { SeedSignerEvm } from '@tetherto/wdk-wallet-evm/signers' -// Avalanche C-Chain -const config = { chainId: 43114 } +const signer = new SeedSignerEvm(seedPhrase) +const wallet = new WalletManagerEvm(signer, { + provider: 'https://eth.drpc.org' +}) ``` -### Provider - -The `provider` option specifies the RPC endpoint or EIP-1193 provider instance for blockchain interactions. **Required** for all operations. You can also pass an array of endpoints or providers to enable automatic failover: when a request to one provider fails, the wallet retries the next provider in the list. - -**Type:** `string | Eip1193Provider | Array` -**Required:** Yes +The manager's default signer must support derivation. Non-derivable signers, such as private-key signers, can be registered by name and used to retrieve signer-backed accounts: -**Examples:** -```javascript -// Using RPC URL -const config = { - provider: 'https://rpc.mevblocker.io/fast' -} +```javascript title="Register A Private-Key Signer" +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers' -// Using browser provider (MetaMask) -const config = { - provider: window.ethereum -} +const wallet = new WalletManagerEvm(seedPhrase, { + provider: 'https://eth.drpc.org' +}) -// Using custom ethers provider -import { JsonRpcProvider } from 'ethers' -const config = { - provider: new JsonRpcProvider('https://rpc.mevblocker.io/fast') -} +wallet.addSigner('treasury', new PrivateKeySignerEvm(privateKey)) -// Using multiple providers for automatic failover -const config = { - provider: [ - 'https://rpc.mevblocker.io/fast', - 'https://eth.llamarpc.com' - ], - retries: 3 // Optional: additional retry attempts after the initial call fails -} +const treasury = await wallet.getAccount('treasury') ``` -### Retries - -The `retries` option sets the number of additional retry attempts after the initial call fails. It only applies when `provider` is an array of endpoints or providers. Total attempts equal `1 + retries`. If `retries` exceeds the number of providers, the failover loops back and retries already-failed providers in round-robin order. +For a standalone account backed by one private key, construct the account directly: -**Type:** `number` -**Required:** No (optional) -**Default:** `3` +```javascript title="Standalone Private-Key Account" +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' -```javascript -const config = { - provider: [ - 'https://rpc.mevblocker.io/fast', - 'https://eth.llamarpc.com' - ], - retries: 5 -} +const account = WalletAccountEvm.fromPrivateKey(privateKey, { + provider: 'https://eth.drpc.org' +}) ``` -### Bundler URL - -The `bundlerUrl` option specifies the URL of the ERC-4337 bundler service that handles UserOperation bundling and submission to the mempool. **Required** for transaction processing. +## Account Configuration -**Type:** `string` -**Required:** Yes +Both `WalletAccountEvm` and `WalletAccountReadOnlyEvm` share similar configuration options: -**Example:** ```javascript -const config = { - bundlerUrl: 'https://api.candide.dev/public/v3/1' -} -``` - -### Paymaster URL - -The `paymasterUrl` option specifies the URL of the paymaster service that sponsors transaction fees using ERC-20 tokens or sponsorship policies. +import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' -**Type:** `string` -**Required:** Yes, for Paymaster Token mode and Sponsorship Policy mode. Not used in Native Coins mode. +// Full access account +const account = new WalletAccountEvm( + seedPhrase, + "0'/0/0", // BIP-44 derivation path + { + provider: 'https://eth.drpc.org', + transferMaxFee: 100000000000000, + transactionMaxFee: 100000000000000 + } +) -**Example:** -```javascript -const config = { - paymasterUrl: 'https://api.candide.dev/public/v3/1' -} +// Read-only account +const readOnlyAccount = new WalletAccountReadOnlyEvm( + '0x...', // Ethereum address + { + provider: 'https://eth.drpc.org' + } +) ``` -### Paymaster Address - -The `paymasterAddress` option specifies the address of the paymaster smart contract. - -**Type:** `string` -**Required:** Yes, for Paymaster Token mode only. Not used in Sponsorship Policy or Native Coins modes. - -**Example:** -```javascript -const config = { - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba' -} -``` +## Configuration Options -### On-Chain Identifier +### Provider -The `onChainIdentifier` option appends a 50-byte project marker to every UserOperation's call data. Pass a string to use it as the project name, or pass an object for more control over the platform and tool fields. +The `provider` option specifies how to connect to the blockchain. It can be a URL string, an EIP-1193 compatible provider instance, or an ordered array of URL strings and EIP-1193 providers for automatic failover. -**Type:** `string | OnChainIdentifier` -**Required:** No (optional) +**Type:** `string | Eip1193Provider | Array` -**Properties (object form):** -- `project` (string): The project name included in the marker -- `platform` (`'Web' | 'Mobile' | 'Safe App' | 'Widget'`, optional): The platform type (default: `'Web'`) -- `tool` (string, optional): The tool name used to create the UserOperation -- `toolVersion` (string, optional): Semver-style tool version string (e.g., `'1.0.0'`) +**Examples:** ```javascript -// String form +// Option 1: Using RPC URL const config = { - onChainIdentifier: 'my-project' + provider: 'https://eth.drpc.org' } -// Object form +// Option 2: Using browser provider (e.g., MetaMask) const config = { - onChainIdentifier: { - project: 'my-project', - platform: 'Mobile', - tool: 'my-wallet', - toolVersion: '1.0.0' - } + provider: window.ethereum } -``` - -### Safe Modules Version -The `safeModulesVersion` option specifies the Safe modules version for smart contract wallet implementation. **Required** for smart account initialization. - -**Type:** `string` -**Required:** Yes - -**Example:** -```javascript -const config = { - safeModulesVersion: '0.3.0' +// Option 3: Using a custom EIP-1193 provider +// Works in Node.js, Bare, and browsers - zero external dependencies +function createFetchProvider(rpcUrl) { + let requestId = 0 + return { + request: async ({ method, params }) => { + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: ++requestId, + method, + params: params || [] + }) + }) + const data = await response.json() + if (data.error) throw new Error(data.error.message) + return data.result + } + } } -``` - -### Paymaster Token -The `paymasterToken` option specifies the ERC-20 token used for paying transaction fees through the paymaster. - -**Type:** `object` -**Required:** Yes, for Paymaster Token mode only. Not used in Sponsorship Policy or Native Coins modes. - -**Properties:** -- `address` (string): The ERC-20 token contract address - -**Example:** -```javascript const config = { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USD₮ - } + provider: createFetchProvider('https://eth.drpc.org') } -``` - -### Sponsorship Policy ID - -The `sponsorshipPolicyId` option specifies the sponsorship policy identifier for sponsored transactions. - -**Type:** `string` -**Required:** No (optional), only used in Sponsorship Policy mode. -**Example:** -```javascript +// Option 4: Using ordered provider failover const config = { - isSponsored: true, - sponsorshipPolicyId: 'sp_my_policy_id' + provider: [ + 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY', + 'https://eth.drpc.org', + createFetchProvider('https://ethereum.publicnode.com') + ], + retries: 2 } ``` -### Gas Payment Mode Flags - -These boolean flags control which gas payment mode is used. Only one mode should be active at a time. +When `provider` is an array, the wallet uses the candidates in order and retries connection failures against the next provider. If `retries` is greater than the number of providers, the failover loop wraps around in round-robin order. -#### `isSponsored` +### Retries -Enables Sponsorship Policy mode, where a sponsor covers transaction fees. +The `retries` option controls how many additional attempts can happen after the first provider call fails. It only applies when `provider` is an array. -**Type:** `boolean` -**Default:** `false` +**Type:** `number` (optional) +**Default:** `3` +**Example:** ```javascript const config = { - isSponsored: true, - paymasterUrl: 'https://api.candide.dev/public/v3/1' + provider: [ + 'https://primary.example', + 'https://secondary.example' + ], + retries: 1 } ``` -#### `useNativeCoins` +### Chain ID -Enables Native Coins mode, where the user pays fees in the chain's native currency (ETH, MATIC, etc.). +The `chainId` option pins the provider to a known EVM chain ID. Use it when you already know the target network and want to skip automatic chain ID detection during provider setup. -**Type:** `boolean` -**Default:** `false` +**Type:** `number` (optional) +**Example:** ```javascript const config = { - useNativeCoins: true, - transferMaxFee: 100000000000000n // Optional: max fee in wei + provider: 'https://polygon-rpc.com', + chainId: 137 } ``` ### Transfer Max Fee -The `transferMaxFee` option sets the maximum fee amount **in paymaster token units** for transfer operations. This prevents transactions with unexpectedly high fees. **Optional** parameter. +The `transferMaxFee` option sets a maximum fee limit for ERC-20 `transfer()` operations. -**Type:** `number | bigint` -**Required:** No (optional) -**Unit:** Paymaster token base units +**Type:** `number | bigint` (optional) +**Unit:** Wei (1 ETH = 1000000000000000000 Wei) + +**Examples:** -**Example:** ```javascript const config = { - transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USD₮ if 6 decimals) + // Set maximum fee to 0.0001 ETH + transferMaxFee: 100000000000000n, } -// Usage with error handling +// Usage example try { const result = await account.transfer({ - token: '0x...', + token: '0x...', // ERC20 address recipient: '0x...', - amount: 1000000 + amount: 1000000n }) } catch (error) { if (error.message.includes('Exceeded maximum fee')) { @@ -16356,146 +26242,84 @@ try { } ``` -## Network-Specific Configurations - -### Ethereum Mainnet - - -**Supported Paymaster Tokens** +### Transaction Max Fee -The following tokens are supported for gas payments on Ethereum Mainnet: +The `transactionMaxFee` option sets a maximum fee limit for native EVM `sendTransaction()` operations and provider-backed `signTransaction()` operations. This is separate from `transferMaxFee`, which applies to ERC-20 token transfers. Offline signing without a provider cannot estimate fees, so the fee-cap check does not run there. -* **USD₮**: `0xdAC17F958D2ee523a2206206994597C13D831ec7` -* **USA₮**: `0x07041776f5007aca2a54844f50503a18a72a8b68` -* **XAU₮**: `0x68749665ff8d2d112fa859aa293f07a622782f38` - +**Type:** `number | bigint` (optional) +**Unit:** Wei (1 ETH = 1000000000000000000 Wei) +**Example:** ```javascript -const ethereumConfig = { - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - paymasterUrl: 'https://api.candide.dev/public/v3/1', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT - }, - transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USDT if 6 decimals) +const config = { + provider: 'https://eth.drpc.org', + transactionMaxFee: 100000000000000n } ``` -### Polygon Mainnet +### Fee Rate Multipliers + +The wallet manager uses predefined multipliers for fee calculations: ```javascript -const polygonConfig = { - chainId: 137, - provider: 'https://polygon-rpc.com', - bundlerUrl: 'https://api.candide.dev/public/v3/137', - paymasterUrl: 'https://api.candide.dev/public/v3/137', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' // USDT on Polygon - }, - transferMaxFee: 100000 -} +// Normal fee rate = base fee × 1.1 +const normalFee = await wallet.getFeeRates() +console.log('Normal fee:', normalFee.normal) + +// Fast fee rate = base fee × 2.0 +const fastFee = await wallet.getFeeRates() +console.log('Fast fee:', fastFee.fast) ``` -### Arbitrum One +## Network Support + +The configuration works with any EVM-compatible network. Just change the provider URL: ```javascript -const arbitrumConfig = { - chainId: 42161, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: 'https://public.pimlico.io/v2/42161/rpc', - paymasterUrl: 'https://public.pimlico.io/v2/42161/rpc', - paymasterAddress: '0x777777777777AeC03fd955926DbF81597e66834C', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' // USDT on Arbitrum - }, - transferMaxFee: 100000 +// Ethereum Mainnet +const mainnetConfig = { + provider: 'https://eth.drpc.org' } -``` - -### Avalanche C-Chain -``` javascript -const avalancheConfig = { - chainId: 43114, - provider: 'https://avalanche-c-chain-rpc.publicnode.com', - bundlerUrl: "https://public.pimlico.io/v2/43114/rpc", - paymasterUrl: "https://public.pimlico.io/v2/43114/rpc", - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7' // USDT - }, - transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USDT if 6 decimals) +// Polygon (Matic) +const polygonConfig = { + provider: 'https://polygon-rpc.com' } -``` -### Plasma - -```javascript -// Plasma (example Layer 2) -const plasmaConfig = { - chainId: 9745, - provider: 'https://plasma.drpc.org', - // For ERC-4337 support, optional fields: - bundlerUrl: 'https://api.candide.dev/public/v3/9745', - paymasterUrl: 'https://api.candide.dev/public/v3/9745', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb' // USDT - }, - transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USDT if 6 decimals) +// Arbitrum +const arbitrumConfig = { + provider: 'https://arb1.arbitrum.io/rpc' } -``` -### Sepolia Testnet (USD₮ ERC-20 mock/testnet only) - -````javascript +// BSC (Binance Smart Chain) +const bscConfig = { + provider: 'https://bsc-dataseed.binance.org' +} -// Pimlico -const sepoliaConfigPimlico = { - chainId: 11155111, - provider: 'https://sepolia.drpc.org', - bundlerUrl: 'https://public.pimlico.io/v2/11155111/rpc', - paymasterUrl: 'https://public.pimlico.io/v2/11155111/rpc', - paymasterAddress: '0x777777777777AeC03fd955926DbF81597e66834C', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xd077a400968890eacc75cdc901f0356c943e4fdb' // USDT Sepolia - }, - transferMaxFee: 100000 // 0.1 USDT (6 decimals) +// Avalanche C-Chain +const avalancheConfig = { + provider: 'https://avalanche-c-chain-rpc.publicnode.com', } -// Candide -const sepoliaConfigCandide = { - chainId: 11155111, - provider: 'https://sepolia.drpc.org', - bundlerUrl: 'https://api.candide.dev/public/v3/11155111', - paymasterUrl: 'https://api.candide.dev/public/v3/11155111', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xd077a400968890eacc75cdc901f0356c943e4fdb' // USDT Sepolia - }, - transferMaxFee: 100000 +// Plasma +const plasmaConfig = { + provider: 'https://plasma.drpc.org', } -```` -**Important** -Ethereum Sepolia is a testnet. The USD₮ tokens available at the links below are not real and do not entitle the holder to anything. In particular, they cannot be redeemed with Tether International, S.A. de C.V. ("Tether International") and are not Tether Tokens as described in [Tether International's Terms of Service](https://tether.to/en/legal). The USD₮ tokens available at the links below on this testnet are intended for testing WDK on Ethereum Sepolia. The links below are links to third-party websites and are Third-Party Information as described in Tether Operations, S.A. de [C.V.'s Website Terms](https://tether.io/terms/) +// Stable (uses USD₮ as native gas token) +// No need for ERC-4337 paymaster/bundler setup. +const stableConfig = { + provider: 'https://rpc.stable.xyz', +} -**USD₮ on Sepolia contract:** [0xd077a400968890eacc75cdc901f0356c943e4fdb](https://sepolia.etherscan.io/address/0xd077a400968890eacc75cdc901f0356c943e4fdb) +// Sepolia Testnet +const sepoliaConfig = { + provider: 'https://sepolia.drpc.org', +} -**Get test USD₮:** -- [Pimlico faucet](https://dashboard.pimlico.io/test-erc20-faucet) -- [Candide faucet](https://dashboard.candide.dev/faucet) +``` + +## Next Steps @@ -16504,49 +26328,54 @@ Get started with WDK in a Node.js environment Build mobile wallets with React Native Expo - -Get started with WDK's EVM with ERC-4337 Wallet Usage + +Get started with WDK's EVM Wallet Usage - -Get started with WDK's EVM with ERC-4337 Wallet API + +Get started with WDK's EVM Wallet API *** -## Need Help? +### Need Help? *** ## Check Balances -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/check-balances -Description: Query native, ERC-20, and paymaster token balances. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/check-balances +Description: Query native and ERC-20 token balances on EVM chains. -This guide explains how to check [native token balances](#native-token-balance), [ERC-20 token balances](#erc-20-token-balance), [multiple token balances](#multiple-token-balances), [paymaster token balances](#paymaster-token-balance), and [read-only account balances](#read-only-account-balances). +This guide explains how to check native token and ERC-20 token balances for both owned and read-only accounts. -## Native Token Balance +## Owned Account Balances -You can retrieve the native token balance (e.g., ETH) using [`account.getBalance()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +Use an account retrieved from [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference) to query balances. + +### Native Token Balance + +You can retrieve the native token balance from an `Account` object using [`account.getBalance()`](/sdk/wallet-modules/wallet-evm/api-reference): ```javascript title="Get Native Balance" const balance = await account.getBalance() console.log('Native balance:', balance, 'wei') ``` -## ERC-20 Token Balance +### Single ERC-20 Token Balance -You can check the balance of a specific ERC-20 token using [`account.getTokenBalance()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +You can retrieve a single ERC-20 token balance from an `Account` object using [`account.getTokenBalance(tokenAddress)`](/sdk/wallet-modules/wallet-evm/api-reference#gettokenbalancetokenaddress): ```javascript title="Get ERC-20 Balance" -const tokenBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') // USDT -console.log('USDT balance:', tokenBalance) +const tokenAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT +const tokenBalance = await account.getTokenBalance(tokenAddress) +console.log('Token balance:', tokenBalance) ``` -## Multiple Token Balances +### Multiple ERC-20 Token Balances -You can check balances for multiple ERC-20 tokens in a single call using [`account.getTokenBalances()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +You can retrieve multiple ERC-20 token balances from an `Account` object using [`account.getTokenBalances(tokenAddresses)`](/sdk/wallet-modules/wallet-evm/api-reference#gettokenbalancestokenaddresses), where `tokenAddresses` is an array of ERC-20 tokens: ```javascript title="Get Multiple Token Balances" const tokenBalances = await account.getTokenBalances([ @@ -16556,2373 +26385,2023 @@ const tokenBalances = await account.getTokenBalances([ console.log('Multi-token balances:', tokenBalances) ``` -## Paymaster Token Balance - -You can check the paymaster token balance used for paying gas fees using [`account.getPaymasterTokenBalance()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Get Paymaster Token Balance" -const paymasterBalance = await account.getPaymasterTokenBalance() -console.log('Paymaster token balance:', paymasterBalance) -``` - - -The paymaster token balance determines how many gasless transactions you can execute. Ensure the paymaster has sufficient token balance before initiating gasless operations. - - ## Read-Only Account Balances -You can check balances for any smart account address without a seed phrase using [`WalletAccountReadOnlyEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +Use [`WalletAccountReadOnlyEvm`](/sdk/wallet-modules/wallet-evm/api-reference) to check balances for any public address without a seed phrase. -```javascript title="Read-Only Balance" -import { WalletAccountReadOnlyEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' +### Native Balance -const readOnlyAccount = new WalletAccountReadOnlyEvmErc4337('0x...', { - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - paymasterUrl: 'https://api.candide.dev/public/v3/1', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } +```javascript title="Read-Only Native Balance" +import { WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' + +const readOnlyAccount = new WalletAccountReadOnlyEvm('0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', { + provider: 'https://rpc.mevblocker.io/fast', }) const balance = await readOnlyAccount.getBalance() -console.log('Read-only account balance:', balance, 'wei') +console.log('Native balance:', balance, 'wei') ``` -## Next Steps - -With balance checks in place, learn how to [send gasless transactions](/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions). - -*** - -## Get Started -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/get-started -Description: Install and create your first ERC-4337 smart account wallet. - -This guide explains how to [install the package](#1-install-the-package), [create a wallet](#2-create-a-wallet), [get your first account](#3-get-your-first-account), and optionally [convert to read-only](#4-optional-convert-to-read-only). - -## 1. Install the Package - -### Prerequisites - -* **[Node.js](https://nodejs.org/)**: version 18 or higher. -* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. +### Single Token Balance -```bash title="Install @tetherto/wdk-wallet-evm-erc-4337" -npm install @tetherto/wdk-wallet-evm-erc-4337 +```javascript title="Read-Only Token Balance" +const tokenBalance = await readOnlyAccount.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') +console.log('Token balance:', tokenBalance) ``` -## 2. Create a Wallet - -You can create a new wallet instance using the [`WalletManagerEvmErc4337`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) constructor with a BIP-39 seed phrase and ERC-4337 configuration: - -```javascript title="Create ERC-4337 Wallet" -import WalletManagerEvmErc4337 from '@tetherto/wdk-wallet-evm-erc-4337' - -const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' +### Multiple Token Balances -const wallet = new WalletManagerEvmErc4337(seedPhrase, { - chainId: 1, - provider: 'https://rpc.mevblocker.io/fast', - bundlerUrl: 'https://api.candide.dev/public/v3/1', - paymasterUrl: 'https://api.candide.dev/public/v3/1', - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - safeModulesVersion: '0.3.0', - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT - } -}) +```javascript title="Read-Only Multiple Token Balances" +const tokenBalances = await readOnlyAccount.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT +]) +console.log('Multi-token balances:', tokenBalances) ``` - -**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. - - -To use test/mock tokens instead of real funds, see the [testnet configuration section](/sdk/wallet-modules/wallet-evm-erc-4337/configuration#network-specific-configurations). +You can also create a read-only account from an existing owned account using [`await account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm/api-reference). -## 3. Get Your First Account - -You can retrieve a smart account at a given index using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Get Account" -const account = await wallet.getAccount(0) -const address = await account.getAddress() -console.log('Smart account address:', address) -``` - -## 4. (optional) Convert to Read-Only - -You can convert an owned account to a read-only account using [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Convert to Read-Only" -const readOnlyAccount = await account.toReadOnlyAccount() -``` - ## Next Steps -With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-evm-erc-4337/guides/manage-accounts). +With balance checks in place, learn how to [send transactions](/sdk/wallet-modules/wallet-evm/guides/send-transactions). *** -## Handle Errors -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/handle-errors -Description: Handle errors, manage fees, and dispose of sensitive data. +## Error Handling +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/error-handling +Description: Handle errors, manage fees, and dispose of sensitive data in EVM wallets. -This guide explains how to [handle transaction errors](#transaction-errors), [handle transfer errors](#transfer-errors), and follow [best practices](#best-practices) for fee management and memory cleanup. +This guide covers best practices for handling transaction errors, managing fee limits, and cleaning up sensitive data from memory. -## Transaction Errors +## Handle Transaction Errors -Transactions sent via [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) can fail when the paymaster token balance is insufficient. Wrap calls in a `try/catch` block: +Wrap transactions in `try/catch` blocks to handle common failure scenarios such as insufficient funds or exceeded fee limits. -```javascript title="Handle Transaction Errors" +```javascript title="Transaction Error Handling" try { const result = await account.sendTransaction({ to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000n + value: 1000000000000000000n }) - console.log('Transaction hash:', result.hash) + console.log('Transaction successful:', result.hash) } catch (error) { - if (error.message.includes('not enough funds')) { - console.error('Insufficient paymaster token balance') - } else { - console.error('Transaction failed:', error.message) + console.error('Transaction failed:', error.message) + if (error.message.includes('insufficient funds')) { + console.log('Please add more funds to your wallet') + } + if (error.message.includes('Exceeded maximum fee')) { + console.log('Transaction fee too high') } } ``` -## Transfer Errors +## Handle Token Transfer Errors -Token transfers via [`account.transfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) can fail due to insufficient balance or exceeding the maximum fee limit: +Token transfers can fail for additional reasons such as invalid addresses or insufficient token balances. -```javascript title="Handle Transfer Errors" +```javascript title="Token Transfer Error Handling" try { const result = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 + amount: 1000000000000000000n }) - console.log('Transfer hash:', result.hash) + console.log('Transfer completed:', result.hash) } catch (error) { + console.error('Transfer failed:', error.message) if (error.message.includes('Exceeded maximum fee')) { - console.error('Transfer cancelled: fee exceeds the configured limit') - } else if (error.message.includes('not enough funds')) { - console.error('Insufficient paymaster token balance') - } else { - console.error('Transfer failed:', error.message) + console.log('Transfer fee too high') } } ``` -## Best Practices - -### Fee Management +## Manage Fee Limits -You can retrieve current network fee rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +Set `transactionMaxFee` to cap native `sendTransaction()` costs and provider-backed `signTransaction()` costs. Offline signing without a provider cannot estimate fees, so the fee-cap check does not run there. Set `transferMaxFee` separately to cap ERC-20 `transfer()` costs. Retrieve current network rates with [`getFeeRates()`](/sdk/wallet-modules/wallet-evm/api-reference) to make informed decisions. -```javascript title="Get Fee Rates" +```javascript title="Fee Management" const feeRates = await wallet.getFeeRates() -console.log('Normal fee rate:', feeRates.normal) -console.log('Fast fee rate:', feeRates.fast) +console.log('Normal fee rate:', feeRates.normal, 'wei') +console.log('Fast fee rate:', feeRates.fast, 'wei') ``` -### Dispose of Sensitive Data +## Dispose of Sensitive Data -For security, clear sensitive data from memory when a session is complete. Use [`account.dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) and [`wallet.dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) to securely wipe private keys: +Call [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on accounts and wallet managers to clear private keys and sensitive data from memory when they are no longer needed. -```javascript title="Dispose Resources" -try { - const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000n - }) - console.log('Transaction hash:', result.hash) -} finally { - account.dispose() - wallet.dispose() -} +```javascript title="Memory Cleanup" +account.dispose() + +wallet.dispose() ``` -Always call [`dispose()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) when finished with accounts. Private keys are securely wiped from memory. Disposal is irreversible. +Always call [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) in a `finally` block or cleanup handler to ensure sensitive data is cleared even if an error occurs. *** -## Manage Accounts -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/manage-accounts -Description: Work with multiple smart accounts and custom derivation paths. - -This guide explains how to [retrieve accounts by index](#retrieve-accounts-by-index) and [use custom derivation paths](#retrieve-account-by-custom-derivation-path). +## Getting Started +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/getting-started +Description: Install and create your first EVM wallet. -## Retrieve Accounts by Index +This guide explains how to install the [`@tetherto/wdk-wallet-evm`](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm) package and create a new wallet instance. -You can retrieve multiple smart accounts using [`wallet.getAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) with different index values: +## 1. Installation -```javascript title="Retrieve Multiple Accounts" -const account0 = await wallet.getAccount(0) -const address0 = await account0.getAddress() -console.log('Account 0 address:', address0) +### Prerequisites -const account1 = await wallet.getAccount(1) -const address1 = await account1.getAddress() -console.log('Account 1 address:', address1) -``` +Before you begin, ensure you have the following installed: -## Retrieve Account by Custom Derivation Path +* **[Node.js](https://nodejs.org/)**: version 18 or higher. +* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. -You can retrieve an account at a specific derivation path using [`wallet.getAccountByPath()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +### Install Package -```javascript title="Custom Derivation Path" -const customAccount = await wallet.getAccountByPath("0'/0/5") -const customAddress = await customAccount.getAddress() -console.log('Custom account address:', customAddress) +```bash title="Install @tetherto/wdk-wallet-evm" +npm install @tetherto/wdk-wallet-evm ``` -## Next Steps - -With accounts set up, learn how to [check balances](/sdk/wallet-modules/wallet-evm-erc-4337/guides/check-balances). - -*** - -## Send Transactions -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions -Description: Send gasless transactions and estimate fees. +## 2. Create a Wallet -This guide explains how to [send a gasless transaction](#send-a-gasless-transaction), [estimate fees](#estimate-fees), and [use a custom paymaster token](#send-with-custom-paymaster-token). +Import the module and create a [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference) instance with a BIP-39 seed phrase and an RPC provider. -## Send a Gasless Transaction +```javascript title="Create EVM Wallet" +import WalletManagerEvm, { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' -You can send a transaction with gas fees paid in the paymaster token using [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +const seedPhrase = 'your twelve word seed phrase here' -```javascript title="Send Gasless Transaction" -const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000n // 0.001 ETH in wei +const wallet = new WalletManagerEvm(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + transferMaxFee: 100000000000000, // Optional: maximum ERC-20 transfer fee in wei + transactionMaxFee: 100000000000000 // Optional: maximum native send/provider-backed sign fee in wei }) -console.log('Transaction hash:', result.hash) -console.log('Fee paid in paymaster token:', result.fee) ``` - -ERC-4337 transactions are gasless for the end user. Gas fees are paid through the configured paymaster using the specified paymaster token (e.g., USD₮). + +**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. -## Estimate Fees - -You can estimate the fee for a transaction without broadcasting it using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Estimate Fee" -const quote = await account.quoteSendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000n -}) -console.log('Estimated fee:', quote.fee) -``` - -## Send with Custom Paymaster Token - -You can override the default paymaster token for a specific transaction by passing a config object to [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +You can also pass an EIP-1193 provider (e.g., from a browser wallet) instead of an RPC URL: -```javascript title="Custom Paymaster Token" -const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000n -}, { - paymasterToken: { - address: '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT - } +```javascript title="Use EIP-1193 Provider" +const wallet = new WalletManagerEvm(seedPhrase, { + provider: window.ethereum, + transferMaxFee: 100000000000000, + transactionMaxFee: 100000000000000 }) ``` -## Next Steps - -Learn how to [transfer ERC-20 tokens](/sdk/wallet-modules/wallet-evm-erc-4337/guides/transfer-tokens). - -*** - -## Sign and Verify Messages -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/sign-verify-messages -Description: Sign messages and EIP-712 typed data with smart accounts. - -This guide explains how to [sign messages](#sign-a-message), [verify signatures](#verify-a-signature), and [sign EIP-712 typed data](#sign-typed-data-eip-712). - -## Sign a Message - -You can sign a message with the account's private key using [`account.sign()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Sign Message" -const signature = await account.sign('Hello, ERC-4337!') -console.log('Signature:', signature) -``` - -## Verify a Signature - -You can verify a signature using a read-only account. Use [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference) to create one, then call [`readOnlyAccount.verify()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Verify Signature" -const readOnlyAccount = await account.toReadOnlyAccount() -const isValid = await readOnlyAccount.verify('Hello, ERC-4337!', signature) -console.log('Signature valid:', isValid) -``` - -## Sign Typed Data (EIP-712) - -You can sign EIP-712 structured data using [`account.signTypedData()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Sign Typed Data" -const typedData = { - domain: { - name: 'MyDApp', - version: '1', - chainId: 1, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' - }, - types: { - Mail: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'contents', type: 'string' } - ] - }, - message: { - from: '0x1234567890abcdef1234567890abcdef12345678', - to: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', - contents: 'Hello!' - } -} +## 3. Get Your First Account -const typedDataSignature = await account.signTypedData(typedData) -console.log('Typed data signature:', typedDataSignature) -``` +Retrieve an account from the wallet and inspect its address. -You can verify typed data signatures using [`readOnlyAccount.verifyTypedData()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +```javascript title="Get Account" +const account = await wallet.getAccount(0) +const address = await account.getAddress() +console.log('Wallet address:', address) -```javascript title="Verify Typed Data" const readOnlyAccount = await account.toReadOnlyAccount() -const isValid = await readOnlyAccount.verifyTypedData(typedData, typedDataSignature) -console.log('Typed data signature valid:', isValid) -``` - -## Next Steps - -Learn how to [handle errors and manage resources](/sdk/wallet-modules/wallet-evm-erc-4337/guides/handle-errors). - -*** - -## Transfer Tokens -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/guides/transfer-tokens -Description: Transfer ERC-20 tokens with gasless transactions. - -This guide explains how to [transfer ERC-20 tokens](#transfer-erc-20-tokens), [estimate transfer fees](#estimate-transfer-fees), and [set a maximum fee limit](#transfer-with-maximum-fee-limit). - -## Transfer ERC-20 Tokens - -You can transfer ERC-20 tokens using gasless transactions with [`account.transfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Transfer ERC-20 Tokens" -const result = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 // 1 USDT (6 decimals) -}) -console.log('Transfer hash:', result.hash) -console.log('Transfer fee:', result.fee) -``` - -## Estimate Transfer Fees - -You can estimate the fee for a token transfer without executing it using [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Estimate Transfer Fee" -const quote = await account.quoteTransfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}) -console.log('Estimated transfer fee:', quote.fee) -``` - -## Transfer with Maximum Fee Limit - -You can set a maximum fee for a specific transfer by passing a `transferMaxFee` in the config object to [`account.transfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): - -```javascript title="Transfer with Fee Limit" -const result = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}, { - transferMaxFee: 100000 -}) ``` - -If the estimated fee exceeds `transferMaxFee`, the transfer is cancelled with an "Exceeded maximum fee" error. + +**RPC Providers:** The examples use public RPC endpoints for demonstration. We do not endorse any specific provider. +* **Testnets:** You can find public RPCs for Ethereum and other EVM chains on [Chainlist](https://chainlist.org). +* **Mainnet:** For production environments, we recommend using reliable, paid RPC providers to ensure stability. -## Next Steps - -Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-evm-erc-4337/guides/sign-verify-messages). - -*** - -## Wallet EVM ERC-4337 Usage -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/usage -Description: Guide to using the @tetherto/wdk-wallet-evm-erc-4337 module. - -# Usage - -The `@tetherto/wdk-wallet-evm-erc-4337` module provides account abstraction wallet management for EVM-compatible blockchains using the ERC-4337 standard. - - - - -Install the package and create your first smart account. - - -Work with multiple smart accounts and custom derivation paths. - - -Query native, ERC-20, and paymaster token balances. - - -Send gasless transactions and estimate fees. - - -Transfer ERC-20 tokens with gasless transactions. - - -Sign messages and EIP-712 typed data. - - -Handle errors, manage fees, and dispose of sensitive data. - - + +To use test/mock tokens instead of real funds, see the [configuration section](/sdk/wallet-modules/wallet-evm/configuration#network-support). + +## Next Steps - - -Get started with WDK in a Node.js environment - - -Build mobile wallets with React Native Expo - - -Get started with WDK's EVM ERC-4337 Wallet Configuration - - -Get started with WDK's EVM ERC-4337 Wallet API - - +With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-evm/guides/manage-accounts). *** -### Need Help? - - +## Manage Accounts +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/manage-accounts +Description: Work with multiple EVM accounts and custom derivation paths. -*** +This guide explains how to retrieve multiple accounts from your EVM wallet and use custom derivation paths. -## Wallet EVM API Reference -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/api-reference -Description: Complete API documentation for @tetherto/wdk-wallet-evm +## Retrieve Accounts by Index -## Table of Contents +Use `getAccount()` with a zero-based index to access accounts derived from the default BIP-44 path (`m/44'/60'/0'/0/{index}`). -| Class | Description | Methods | -|-------|-------------|---------| -| [WalletManagerEvm](#walletmanagerevm) | Main class for managing EVM wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. | [Constructor](#constructor), [Methods](#methods) | -| [WalletAccountEvm](#walletaccountevm) | Individual EVM wallet account implementation. Extends `WalletAccountReadOnlyEvm` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-1), [Methods](#methods-1), [Properties](#properties) | -| [WalletAccountReadOnlyEvm](#walletaccountreadonlyevm) | Read-only EVM wallet account. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. | [Constructor](#constructor-2), [Methods](#methods-2) | +```javascript title="Get Accounts by Index" +const account = await wallet.getAccount(0) +const address = await account.getAddress() +console.log('Account 0 address:', address) -## WalletManagerEvm +const account1 = await wallet.getAccount(1) +const address1 = await account1.getAddress() +console.log('Account 1 address:', address1) +``` -The main class for managing EVM wallets. -Extends `WalletManager` from `@tetherto/wdk-wallet`. +## Retrieve Account by Custom Derivation Path -### Constructor +Use `getAccountByPath()` when you need a specific hierarchy beyond the default sequential index. -```javascript -new WalletManagerEvm(seed, config?) +```javascript title="Custom Derivation Path" +const customAccount = await wallet.getAccountByPath("0'/0/5") +const customAddress = await customAccount.getAddress() +console.log('Custom account address:', customAddress) ``` -**Parameters:** -- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes -- `config` (object, optional): Configuration object - - `provider` (`string | Eip1193Provider | Array`, optional): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list - - `retries` (number, optional): Additional retry attempts when `provider` is an array - - `chainId` (number, optional): Network chain ID. When provided, skips automatic chain ID detection. - - `transferMaxFee` (number | bigint, optional): Maximum fee amount for transfer operations (in wei) +## Retrieve Accounts from Named Signers + +Register named signers when an account should use signing material outside the manager's default seed. The default manager signer must be derivable; non-derivable private-key signers can be registered by name. + +```javascript title="Register A Named Signer" +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers' -**Example:** -```javascript const wallet = new WalletManagerEvm(seedPhrase, { - provider: 'https://rpc.mevblocker.io/fast', - transferMaxFee: 100000000000000 // Maximum fee in wei + provider: 'https://eth.drpc.org' }) + +wallet.addSigner('treasury', new PrivateKeySignerEvm(privateKey)) + +const treasuryAccount = await wallet.getAccount('treasury') +console.log('Treasury address:', await treasuryAccount.getAddress()) ``` -### Methods +If a registered signer supports derivation, you can derive accounts from that signer by passing `signerName`: -| Method | Description | Returns | Throws | -|--------|-------------|---------|--------| -| `getRandomSeedPhrase(wordCount?)` | (static) Returns a random BIP-39 seed phrase | `string` | - | -| `isValidSeedPhrase(seedPhrase)` | (static) Checks if a seed phrase is valid | `boolean` | - | -| `getAccount(index?)` | Returns a wallet account at the specified index | `Promise` | - | -| `getAccountByPath(path)` | Returns a wallet account at the specified BIP-44 derivation path | `Promise` | - | -| `getFeeRates()` | Returns current fee rates for transactions | `Promise<{normal: bigint, fast: bigint}>` | If no provider is set | -| `dispose()` | Disposes all wallet accounts, clearing private keys from memory | `void` | - | +```javascript title="Derive From A Named Signer" +const account = await wallet.getAccount(2, { signerName: 'hardware-root' }) +const customAccount = await wallet.getAccountByPath("0'/0/5", { signerName: 'hardware-root' }) +``` -### Properties +## Iterate Over Multiple Accounts -| Property | Type | Description | -|----------|------|-------------| -| `seed` | `Uint8Array` | The wallet's seed bytes | +You can loop through accounts to inspect addresses and balances in bulk. -#### `getRandomSeedPhrase(wordCount?)` (static) -Returns a random BIP-39 seed phrase. +```javascript title="Multi-Account Iteration" +async function listAccounts(wallet) { + const accounts = [] -**Parameters:** -- `wordCount` (12 | 24, optional): The number of words in the seed phrase (default: 12) + for (let i = 0; i < 5; i++) { + const account = await wallet.getAccount(i) + const address = await account.getAddress() + const balance = await account.getBalance() -**Returns:** `string` - The seed phrase + accounts.push({ + index: i, + path: `m/44'/60'/0'/0/${i}`, + address, + balance + }) -**Example:** -```javascript -const seedPhrase = WalletManagerEvm.getRandomSeedPhrase() -console.log('Seed phrase:', seedPhrase) // 12 words + console.log(`Account ${i}:`, { address, balance: balance.toString() }) + } -const longSeedPhrase = WalletManagerEvm.getRandomSeedPhrase(24) -console.log('Long seed phrase:', longSeedPhrase) // 24 words + return accounts +} ``` -#### `isValidSeedPhrase(seedPhrase)` (static) -Checks if a seed phrase is valid. - -**Parameters:** -- `seedPhrase` (string): The seed phrase to validate +## Next Steps -**Returns:** `boolean` - True if the seed phrase is valid +Now that you can access your accounts, learn how to [check balances](/sdk/wallet-modules/wallet-evm/guides/check-balances). -**Example:** -```javascript -const isValid = WalletManagerEvm.isValidSeedPhrase('abandon abandon abandon ...') -console.log('Valid:', isValid) -``` +*** -#### `getAccount(index?)` -Returns a wallet account at the specified index following BIP-44 standard. +## Send Transactions +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/send-transactions +Description: Send native tokens on EVM chains with EIP-1559 or legacy gas settings. -**Parameters:** -- `index` (number, optional): The index of the account to get (default: 0) +This guide explains how to [send EVM transactions with EIP-1559 gas parameters](#send-with-eip-1559-gas-parameters), [send legacy gas transactions](#send-with-legacy-gas-parameters), [deploy contracts](#deploy-a-contract), [sign without broadcasting](#sign-without-broadcasting), [estimate fees](#estimate-transaction-fees), [cap transaction fees](#cap-transaction-fees), and [use dynamic fee rates](#use-dynamic-fee-rates). -**Returns:** `Promise` - The wallet account + +**BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. + -**Example:** -```javascript -// Get first account (index 0) -const account = await wallet.getAccount(0) +## Send with EIP-1559 Gas Parameters -// Get second account (index 1) -const account1 = await wallet.getAccount(1) +You can use [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#sendtransactiontx) to send an EIP-1559 transaction. EIP-1559 transactions provide more predictable gas fees and faster inclusion times. -// Get first account (default) -const defaultAccount = await wallet.getAccount() +```javascript title="EIP-1559 Transaction" +const result = await account.sendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n, // 1 ETH in wei + maxFeePerGas: 30000000000, + maxPriorityFeePerGas: 2000000000 +}) +console.log('Transaction hash:', result.hash) +console.log('Transaction fee:', result.fee, 'wei') ``` -#### `getAccountByPath(path)` -Returns a wallet account at the specified BIP-44 derivation path. +## Send with Legacy Gas Parameters -**Parameters:** -- `path` (string): The derivation path (e.g., "0'/0/0") +You can also use [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#sendtransactiontx) with legacy gas settings for chains that do not support EIP-1559. -**Returns:** `Promise` - The wallet account +```javascript title="Legacy Transaction" +const legacyResult = await account.sendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n, + gasPrice: 20000000000n, + gasLimit: 21000 +}) +console.log('Transaction hash:', legacyResult.hash) +``` -**Example:** -```javascript -// Full path: m/44'/60'/0'/0/1 -const account = await wallet.getAccountByPath("0'/0/1") +## Deploy a Contract -// Custom path: m/44'/60'/0'/0/5 -const customAccount = await wallet.getAccountByPath("0'/0/5") -``` +For contract-creation transactions, omit `to` or pass `to: null` and provide the deployment bytecode in `data`. -#### `getFeeRates()` -Returns current fee rates based on network conditions with predefined multipliers. +```javascript title="Contract Creation" +const result = await account.sendTransaction({ + to: null, + value: 0n, + data: contractBytecode, + maxFeePerGas: 30000000000n, + maxPriorityFeePerGas: 2000000000n +}) -**Returns:** `Promise<{normal: bigint, fast: bigint}>` - Fee rates in wei -- `normal`: Base fee × 1.1 (10% above base) -- `fast`: Base fee × 2.0 (100% above base) +console.log('Deployment transaction:', result.hash) +``` -**Throws:** Error if no provider is configured +## Sign Without Broadcasting -**Example:** -```javascript -const feeRates = await wallet.getFeeRates() -console.log('Normal fee rate:', feeRates.normal, 'wei') -console.log('Fast fee rate:', feeRates.fast, 'wei') +Use [`account.signTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#signtransactiontx) when you need a signed raw transaction but want to submit it through a separate relay, service, or review flow. -// Use in transaction -const result = await account.sendTransaction({ - to: '0x...', +```javascript title="Sign EVM Transaction" +const signedTransaction = await account.signTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', value: 1000000000000000000n, - maxFeePerGas: feeRates.fast + chainId: 1, + maxFeePerGas: 30000000000n, + maxPriorityFeePerGas: 2000000000n }) + +console.log('Signed transaction:', signedTransaction) ``` -#### `dispose()` -Disposes all wallet accounts, clearing private keys from memory. + +`signTransaction()` returns the signed transaction payload and does not broadcast it. In `1.0.0-beta.16`, do not pass that serialized payload back to `sendTransaction()`: the send path does not broadcast the supplied bytes and can populate a different transaction. Submit signed raw transactions through a separate relay or provider. Use `sendTransaction()` with an `EvmTransaction` object when WDK should populate, sign, broadcast, and return the transaction hash. + -**Example:** -```javascript -// Clean up when done -wallet.dispose() -``` +## Estimate Transaction Fees -## WalletAccountEvm +Use [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#quotesendtransactiontx) to get a fee estimate before sending. -Represents an individual wallet account. Extends `WalletAccountReadOnlyEvm` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. +```javascript title="Quote Transaction Fee" +const quote = await account.quoteSendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n +}) +console.log('Estimated fee:', quote.fee, 'wei') +``` -### Constructor +## Cap Transaction Fees -```javascript -new WalletAccountEvm(seed, path, config?) +Set [`transactionMaxFee`](/sdk/wallet-modules/wallet-evm/configuration#transaction-max-fee) when you create the wallet to stop native `sendTransaction()` calls and provider-backed `signTransaction()` calls if the estimated fee exceeds your limit. Offline signing without a provider cannot estimate fees, so the fee-cap check does not run there. + +```javascript title="Cap Native Transaction Fees" +const wallet = new WalletManagerEvm(seedPhrase, { + provider: 'https://eth.drpc.org', + transactionMaxFee: 100000000000000n +}) ``` -**Parameters:** -- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes -- `path` (string): BIP-44 derivation path (e.g., "0'/0/0") -- `config` (object, optional): Configuration object - - `provider` (`string | Eip1193Provider | Array`, optional): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list - - `retries` (number, optional): Additional retry attempts when `provider` is an array - - `chainId` (number, optional): Network chain ID. When provided, skips automatic chain ID detection. - - `transferMaxFee` (number | bigint, optional): Maximum fee amount for transfer operations (in wei) +## Use Dynamic Fee Rates -**Throws:** -- Error if seed phrase is invalid (BIP-39 validation fails) +Retrieve current fee rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-evm/api-reference#getfeerates) and apply them to your transaction. -**Example:** -```javascript -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://rpc.mevblocker.io/fast', - transferMaxFee: 100000000000000 +```javascript title="Dynamic Fee Rates" +const feeRates = await wallet.getFeeRates() +console.log('Normal fee rate:', feeRates.normal, 'wei') +console.log('Fast fee rate:', feeRates.fast, 'wei') + +const result = await account.sendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n, + data: '0x', + gasLimit: 21000, + maxFeePerGas: feeRates.fast, + maxPriorityFeePerGas: 2000000000n }) +console.log('Transaction sent:', result.hash) +console.log('Fee paid:', result.fee, 'wei') ``` -### Methods + +**Gas Estimation:** The `maxFeePerGas` and `maxPriorityFeePerGas` fields enable EIP-1559 transactions, ensuring more predictable gas fees and faster inclusion times. + -| Method | Description | Returns | Throws | -|--------|-------------|---------|--------| -| `getAddress()` | Returns the account's address | `Promise` | - | -| `sign(message)` | Signs a message using the account's private key | `Promise` | - | -| `signTypedData(typedData)` | Signs typed data according to EIP-712 | `Promise` | - | -| `signTransaction(tx)` | Signs an EVM transaction without broadcasting it | `Promise` | If transaction signing fails | -| `verify(message, signature)` | Verifies a message signature | `Promise` | - | -| `verifyTypedData(typedData, signature)` | Verifies a typed data signature (EIP-712) | `Promise` | - | -| `sendTransaction(tx)` | Sends an EVM transaction | `Promise<{hash: string, fee: bigint}>` | If no provider | -| `quoteSendTransaction(tx)` | Estimates the fee for an EVM transaction | `Promise<{fee: bigint}>` | If no provider | -| `transfer(options)` | Transfers ERC20 tokens to another address | `Promise<{hash: string, fee: bigint}>` | If no provider or fee exceeds max | -| `quoteTransfer(options)` | Estimates the fee for an ERC20 transfer | `Promise<{fee: bigint}>` | If no provider | -| `getBalance()` | Returns the native token balance (in wei) | `Promise` | If no provider | -| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise` | If no provider | -| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise>` | If no provider | -| `approve(options)` | Approves a spender to spend tokens | `Promise<{hash: string, fee: bigint}>` | If no provider | -| `getAllowance(token, spender)` | Returns current allowance for a spender | `Promise` | If no provider | -| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | If no provider | -| `toReadOnlyAccount()` | Returns a read-only copy of the account | `Promise` | - | -| `dispose()` | Disposes the wallet account, clearing private keys from memory | `void` | - | +## Next Steps -#### `getAddress()` -Returns the account's Ethereum address. +To transfer ERC-20 tokens instead of native tokens, see [Transfer ERC-20 Tokens](/sdk/wallet-modules/wallet-evm/guides/transfer-tokens). -**Returns:** `Promise` - Checksummed Ethereum address +*** -**Example:** -```javascript -const address = await account.getAddress() -console.log('Account address:', address) // 0x... -``` +## Sign and Verify Messages +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/sign-verify-messages +Description: Sign messages and verify signatures with EVM accounts. -#### `sign(message)` -Signs a message using the account's private key. +This guide explains how to sign arbitrary messages with an owned account and verify signatures using a read-only account. -**Parameters:** -- `message` (string): The message to sign +## Sign a Message -**Returns:** `Promise` - The message signature +Use [`account.sign()`](/sdk/wallet-modules/wallet-evm/api-reference#signmessage) to produce a cryptographic signature for any string message. -**Example:** -```javascript +```javascript title="Sign a Message" const message = 'Hello, Ethereum!' const signature = await account.sign(message) console.log('Signature:', signature) ``` -#### `signTypedData(typedData)` -Signs typed data according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). - -**Parameters:** -- `typedData` (TypedData): The typed data to sign - - `domain` (TypedDataDomain): The domain separator (name, version, chainId, verifyingContract) - - `types` (Record\): The type definitions - - `message` (Record\): The message data +## Verify a Signature -**Returns:** `Promise` - The typed data signature +You can get a [read-only account](/sdk/wallet-modules/wallet-evm/api-reference#walletaccountreadonlyevm) from any `Account` object by calling [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm/api-reference#toreadonlyaccount). Use a read-only account to [`verify()`](/sdk/wallet-modules/wallet-evm/api-reference#verifymessage-signature-1) that a signature was produced by the corresponding private key. -**Example:** -```javascript -const typedData = { - domain: { - name: 'MyDApp', - version: '1', - chainId: 1, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' - }, - types: { - Mail: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'contents', type: 'string' } - ] - }, - message: { - from: '0xAlice...', - to: '0xBob...', - contents: 'Hello Bob!' - } -} -const signature = await account.signTypedData(typedData) -console.log('EIP-712 Signature:', signature) +```javascript title="Verify a Signature" +const readOnlyAccount = await account.toReadOnlyAccount() +const isValid = await readOnlyAccount.verify(message, signature) +console.log('Signature valid:', isValid) ``` -#### `signTransaction(tx)` -Signs an EVM transaction and returns the signed raw transaction as a hex string. This method does not broadcast the transaction. + +You can also create a [`WalletAccountReadOnlyEvm`](/sdk/wallet-modules/wallet-evm/api-reference) from any public address to verify signatures without access to the private key. + -**Parameters:** -- `tx` (EvmTransaction): The transaction object - - `to` (string): Recipient address - - `value` (number | bigint): Amount in wei - - `data` (string, optional): Transaction data in hex format - - `gasLimit` (number | bigint, optional): Maximum gas units - - `gasPrice` (number | bigint, optional): Legacy gas price in wei - - `maxFeePerGas` (number | bigint, optional): EIP-1559 max fee per gas in wei - - `maxPriorityFeePerGas` (number | bigint, optional): EIP-1559 max priority fee per gas in wei - - `type` (number, optional): Transaction type, such as `4` for ERC-7702 - - `nonce` (number, optional): Transaction nonce - - `chainId` (number | bigint, optional): Network chain ID - - `authorizationList` (AuthorizationLike[], optional): ERC-7702 authorization list for type 4 transactions +## Next Steps -**Returns:** `Promise` - Signed raw transaction hex string +For best practices on handling errors, managing fees, and cleaning up memory, see [Error Handling](/sdk/wallet-modules/wallet-evm/guides/error-handling). -**Example:** -```javascript -const signedTransaction = await account.signTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n, - chainId: 1 -}) +*** -console.log('Signed transaction:', signedTransaction) -``` +## Transfer ERC-20 Tokens +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/transfer-tokens +Description: Transfer ERC-20 tokens and estimate transfer fees on EVM chains. -#### `verify(message, signature)` -Verifies a message signature against the account's address. +This guide explains how to transfer ERC-20 tokens (such as USD₮ or XAU₮), estimate fees, and validate inputs before executing. -**Parameters:** -- `message` (string): The original message -- `signature` (string): The signature to verify +## Transfer Tokens -**Returns:** `Promise` - True if signature is valid +Use [`account.transfer()`](/sdk/wallet-modules/wallet-evm/api-reference#transferoptions) to send ERC-20 tokens to a recipient address. -**Example:** -```javascript -const message = 'Hello, Ethereum!' -const signature = await account.sign(message) -const isValid = await account.verify(message, signature) -console.log('Signature valid:', isValid) // true +```javascript title="Transfer ERC-20 Tokens" +const transferResult = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000000000000000n // 1 token in base units +}) +console.log('Transfer hash:', transferResult.hash) +console.log('Transfer fee:', transferResult.fee, 'wei') ``` -#### `verifyTypedData(typedData, signature)` -Verifies a typed data signature according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). - -**Parameters:** -- `typedData` (TypedData): The typed data that was signed -- `signature` (string): The signature to verify +## Estimate Transfer Fees -**Returns:** `Promise` - True if signature is valid +Use [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm/api-reference#quotetransferoptions) to get a fee estimate before executing the transfer. -**Example:** -```javascript -const isValid = await account.verifyTypedData(typedData, signature) -console.log('Typed data signature valid:', isValid) // true +```javascript title="Quote Token Transfer" +const transferQuote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000000000000000n +}) +console.log('Transfer fee estimate:', transferQuote.fee, 'wei') ``` -#### `sendTransaction(tx)` -Sends an EVM transaction and returns the result with hash and fee. +## Transfer with Validation -**Parameters:** -- `tx` (EvmTransaction): The transaction object - - `to` (string): Recipient address - - `value` (number | bigint): Amount in wei - - `data` (string, optional): Transaction data in hex format - - `gasLimit` (number | bigint, optional): Maximum gas units - - `gasPrice` (number | bigint, optional): Legacy gas price in wei - - `maxFeePerGas` (number | bigint, optional): EIP-1559 max fee per gas in wei - - `maxPriorityFeePerGas` (number | bigint, optional): EIP-1559 max priority fee per gas in wei - - `type` (number, optional): Transaction type, such as `4` for ERC-7702 - - `nonce` (number, optional): Transaction nonce - - `chainId` (number | bigint, optional): Network chain ID - - `authorizationList` (AuthorizationLike[], optional): ERC-7702 authorization list for type 4 transactions +You can use `transferTokenWithValidation()` to validate addresses and check balances before transferring to catch errors early. -**Returns:** `Promise<{hash: string, fee: bigint}>` - Transaction result +### 1. Validate Addresses -**Throws:** Error if no provider is configured +```javascript title="Address Validation" +if (!tokenAddress.startsWith('0x') || tokenAddress.length !== 42) { + throw new Error('Invalid token address') +} -**Example:** -```javascript -// EIP-1559 transaction -const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000, // 1 ETH in wei - maxFeePerGas: 30000000000, - maxPriorityFeePerGas: 2000000000 -}) +if (!recipient.startsWith('0x') || recipient.length !== 42) { + throw new Error('Invalid recipient address') +} +``` -// Legacy transaction -const legacyResult = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000, - gasPrice: 20000000000, - gasLimit: 21000 -}) +### 2. Check Balances -console.log('Transaction hash:', result.hash) -console.log('Transaction fee:', result.fee, 'wei') -``` +Use [`account.getTokenBalance()`](/sdk/wallet-modules/wallet-evm/api-reference#gettokenbalancetokenaddress) and [`account.getBalance()`](/sdk/wallet-modules/wallet-evm/api-reference#getbalance) to verify sufficient funds: -#### `quoteSendTransaction(tx)` -Estimates the fee for an EVM transaction without sending it. +```javascript title="Balance Check" +const balance = await account.getTokenBalance(tokenAddress) +if (balance < amount) { + throw new Error('Insufficient token balance') +} -**Parameters:** -- `tx` (EvmTransaction): The transaction object (same format as sendTransaction) +const nativeBalance = await account.getBalance() +if (nativeBalance === 0n) { + throw new Error('Need ETH for gas fees') +} +``` -**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei +### 3. Quote and Execute Transfer -**Throws:** Error if no provider is configured +Use [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm/api-reference#quotetransferoptions) to estimate fees, then [`account.transfer()`](/sdk/wallet-modules/wallet-evm/api-reference#transferoptions) to execute: -**Example:** -```javascript -const quote = await account.quoteSendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000 +```javascript title="Quote and Execute" +const quote = await account.quoteTransfer({ + token: tokenAddress, + recipient, + amount +}) +console.log('Transfer fee estimate:', quote.fee, 'wei') + +const result = await account.transfer({ + token: tokenAddress, + recipient, + amount }) -console.log('Estimated fee:', quote.fee, 'wei') +console.log('Transfer completed:', result.hash) +console.log('Fee paid:', result.fee, 'wei') ``` -#### `transfer(options)` -Transfers ERC20 tokens to another address using the standard transfer function. +## Next Steps -**Parameters:** -- `options` (TransferOptions): Transfer options - - `token` (string): Token contract address - - `recipient` (string): Recipient address - - `amount` (number | bigint): Amount in token base units +Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-evm/guides/sign-verify-messages) with your EVM account. -**Returns:** `Promise<{hash: string, fee: bigint}>` - Transfer result +*** -**Throws:** -- Error if no provider is configured -- Error if fee exceeds `transferMaxFee` (if configured) +## Usage +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/usage +Description: Guide to using the @tetherto/wdk-wallet-evm module. -**Example:** -```javascript -const result = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 // 1 USDT (6 decimals) -}) -console.log('Transfer hash:', result.hash) -console.log('Transfer fee:', result.fee, 'wei') -``` +The `@tetherto/wdk-wallet-evm` module provides wallet management for Ethereum and EVM-compatible blockchains. -#### `quoteTransfer(options)` -Estimates the fee for an ERC20 token transfer. + + +Install the package and create your first wallet. + + +Work with multiple accounts and custom derivation paths. + + +Query native and ERC-20 token balances. + + +Send native tokens with EIP-1559 or legacy gas settings. + + +Transfer ERC-20 tokens and estimate fees. + + +Sign messages and verify signatures. + + +Handle errors, manage fees, and dispose of sensitive data. + + -**Parameters:** -- `options` (TransferOptions): Transfer options (same as transfer) + + +Get started with WDK in a Node.js environment + + +Build mobile wallets with React Native Expo + + +Get started with WDK's EVM Wallet Configuration + + +Get started with WDK's EVM Wallet API + + -**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei +--- -**Throws:** Error if no provider is configured +### Need Help? -**Example:** -```javascript -const quote = await account.quoteTransfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}) -console.log('Transfer fee estimate:', quote.fee, 'wei') -``` + -#### `getBalance()` -Returns the native token balance (ETH, MATIC, BNB, etc.). +*** -**Returns:** `Promise` - Balance in wei +## Solana wallet +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana +Description: Create and manage Solana wallets with SOL transfers, SPL token balances, message signing, and program transactions. -**Throws:** Error if no provider is configured +Use the Solana wallet module to create SLIP-0010 accounts, read SOL and SPL token balances, sign messages, and send Solana transactions. -**Example:** -```javascript -const balance = await account.getBalance() -console.log('Balance:', balance, 'wei') -console.log('Balance in ETH:', balance / 1000000000000000000) -``` + +**Default Derivation Path Change in v1.0.0-beta.4+** -#### `getTokenBalance(tokenAddress)` -Returns the balance of a specific ERC20 token using the balanceOf function. +The default derivation path was updated in v1.0.0-beta.4 to match ecosystem conventions: -**Parameters:** -- `tokenAddress` (string): The ERC20 token contract address +- **Before** (up to v1.0.0-beta.3): `m/44'/501'/0'/0/{index}` +- **After** (v1.0.0-beta.4+): `m/44'/501'/{index}'/0'` -**Returns:** `Promise` - Token balance in base units +If you're upgrading from an earlier version, existing wallets created with the old path will generate different addresses. Make sure to migrate any existing wallets or use the old path explicitly if needed for compatibility. -**Throws:** Error if no provider is configured +Use [`getAccountByPath`](/sdk/wallet-modules/wallet-solana/api-reference#getaccountbypathpath) to supply an explicit derivation path when importing or recreating legacy wallets. On Solana, every child segment in a custom path must be hardened. + -**Example:** -```javascript -// Get USDT balance -const usdtBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') -console.log('USDT balance:', usdtBalance) // In 6 decimal places -console.log('USDT balance formatted:', usdtBalance / 1000000, 'USDT') -``` +## Features -#### `getTokenBalances(tokenAddresses)` -Returns balances for multiple ERC20 tokens in one call. +- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases +- **Solana Derivation Paths**: Support for SLIP-0010 derivation paths for Solana (m/44'/501') +- **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase +- **Solana Address Support**: Generate and manage Solana public keys and addresses +- **Message Signing**: Sign and verify messages using Ed25519 cryptography +- **Transaction Management**: Sign, send, and quote Solana transactions +- **Signed Transaction Relay**: Quote and broadcast the exact `FullySignedTransaction` returned by `signTransaction()` +- **SPL Token Support**: Query native SOL plus single or batch SPL token balances +- **TypeScript Support**: Full TypeScript definitions included +- **Memory Safety**: Secure private key management with memory-safe implementation +- **Provider Flexibility**: Support for a single Solana RPC endpoint, plus runtime failover support for ordered `provider` lists +- **Transaction Message Support**: Quote or send prebuilt `TransactionMessage` flows with recent blockhash or durable nonce lifetimes +- **Fee Estimation**: Dynamic fee calculation with recent blockhash +- **Program Interaction**: Support for interacting with Solana programs -**Parameters:** -- `tokenAddresses` (string[]): List of ERC20 token contract addresses +## Supported Networks -**Returns:** `Promise>` - Object mapping each token address to its balance in base units +This package works with the Solana blockchain, including: -**Throws:** Error if no provider is configured +- **Solana Mainnet** +- **Solana Devnet** +- **Solana Testnet** +- **Localnet** -**Example:** -```javascript -const balances = await account.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT -]) +## Next Steps -console.log('USDT:', balances['0xdAC17F958D2ee523a2206206994597C13D831ec7']) -console.log('XAUT:', balances['0x68749665FF8D2d112Fa859AA293F07A622782F38']) -``` + + +Get started with WDK in a Node.js environment + + +Get started with WDK's Solana Wallet configuration + + +Get started with WDK's Solana Wallet API + + +Get started with WDK's with Solana Wallet usage + + -#### `approve(options)` -Approves a specific amount of tokens to a spender. +*** -**Parameters:** -- `options` (ApproveOptions): Approve options - - `token` (string): Token contract address - - `spender` (string): Spender address - - `amount` (number | bigint): Amount to approve +### Need Help? -**Returns:** `Promise<{hash: string, fee: bigint}>` - Transaction result + -**Throws:** -- Error if no provider is configured -- Error if trying to re-approve USDT on Ethereum without resetting to 0 first +*** -**Example:** -```javascript -const result = await account.approve({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - spender: '0xSpenderAddress...', - amount: 1000000n -}) -console.log('Approve hash:', result.hash) -``` +## Gasless Solana wallet +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless +Description: Overview of the @tetherto/wdk-wallet-solana-gasless module. -#### `getAllowance(token, spender)` -Returns the current token allowance for the given spender. +The `@tetherto/wdk-wallet-solana-gasless` module manages Solana accounts that send transactions through a Kora-compatible paymaster. It wraps the standard Solana wallet module and adds paymaster-funded native SOL sends, SPL token transfers, fee quotes, message signing, and read-only account support. -**Parameters:** -- `token` (string): ERC20 token contract address -- `spender` (string): The spender's address + +These pages document the published `@tetherto/wdk-wallet-solana-gasless@1.0.0-beta.2` package. Test your Solana RPC and Kora-compatible paymaster configuration on the target network before production use. + -**Returns:** `Promise` - The current allowance + +This module requires a Solana RPC endpoint and a Kora-compatible paymaster endpoint. The paymaster address becomes the transaction fee payer, and fees are quoted in the configured paymaster token's base units. + -**Throws:** Error if no provider is configured +## Features -**Example:** -```javascript -const allowance = await account.getAllowance( - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - '0xSpenderContract...' -) -console.log('Current allowance:', allowance) -``` +- **Gasless Solana Transactions**: Quote, sign, and send native SOL transfers through a paymaster. +- **Signed Transaction Handoff**: Inspect a fully signed transaction, quote its embedded payment fee, then send it through the configured Solana RPC without contacting the paymaster again. +- **SPL Token Transfers**: Transfer SPL tokens and create the recipient associated token account when needed. +- **Paymaster Fee Quotes**: Estimate paymaster token fees before sending. +- **Per-Operation Overrides**: Override the paymaster token for unsigned quote, sign, send, or transfer calls; use `transactionMaxFee` for send/sign caps and `transferMaxFee` for transfer caps. +- **SLIP-0010 Derivation Paths**: Use the same Solana derivation path behavior as `@tetherto/wdk-wallet-solana`. +- **Read-Only Accounts**: Check balances, quote fees, read receipts, and verify signatures for an address without a private key. +- **Message Signing**: Sign and verify messages with Ed25519 account keys. +- **Provider Failover**: Pass ordered RPC or paymaster endpoint lists with retry behavior. +- **TypeScript Support**: Use bundled type declarations for the module classes and config types. -#### `getTransactionReceipt(hash)` -Returns a transaction receipt by hash. +## Supported Networks -**Parameters:** -- `hash` (string): The transaction hash +The module works with Solana RPC providers and Kora-compatible paymasters on networks where your paymaster is deployed and funded: -**Returns:** `Promise` - Transaction receipt or null if not mined +- Solana Mainnet Beta +- Solana Devnet +- Solana Testnet -**Throws:** Error if no provider is configured +## Key Exports -**Example:** -```javascript -const receipt = await account.getTransactionReceipt('0x...') -if (receipt) { - console.log('Confirmed in block:', receipt.blockNumber) - console.log('Status:', receipt.status) // 1 = success, 0 = failed -} -``` +| Export | Purpose | +|--------|---------| +| `WalletManagerSolanaGasless` | Default export for deriving and caching gasless Solana accounts from a seed. | +| `WalletAccountSolanaGasless` | Owned account with signing, transfer, quote, and paymaster send methods. | +| `WalletAccountReadOnlySolanaGasless` | Read-only account for balances, quotes, receipts, and signature verification. | -#### `toReadOnlyAccount()` -Creates a read-only copy of the account with the same configuration. +## Next Steps -**Returns:** `Promise` - Read-only account instance + + +Install the package and create a gasless Solana account. + + +Configure Solana RPC, paymaster endpoints, fee tokens, and failover. + + +Quote, sign, and send paymaster-funded Solana transactions. + + +Review exported classes, methods, and configuration types. + + -**Example:** -```javascript -const readOnlyAccount = await account.toReadOnlyAccount() +*** -// Can check balances but cannot send transactions -const balance = await readOnlyAccount.getBalance() -// readOnlyAccount.sendTransaction() // Would throw error -``` +## Need Help? -#### `dispose()` -Disposes the wallet account, erasing the private key from memory. + -**Example:** -```javascript -// Clean up when done -account.dispose() -``` +*** -### Properties +## API Reference +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/api-reference +Description: API documentation for @tetherto/wdk-wallet-solana-gasless. -| Property | Type | Description | -|----------|------|-------------| -| `index` | `number` | The derivation path's index of this account | -| `path` | `string` | The full BIP-44 derivation path of this account | -| `keyPair` | `{privateKey: Uint8Array \| null, publicKey: Uint8Array}` | The account's key pair (⚠️ Contains sensitive data). The returned arrays are bound to the account — treat them as a read-only view and do not modify their contents. `privateKey` is `null` after `dispose()` is called. | -| `address` | `string` | The account's Ethereum address (inherited from `WalletAccountReadOnlyEvm`) | + +This page documents the published `@tetherto/wdk-wallet-solana-gasless@1.0.0-beta.2` type declarations and runtime behavior. + -**Example:** -```javascript -console.log('Account index:', account.index) // 0, 1, 2, etc. -console.log('Account path:', account.path) // m/44'/60'/0'/0/0 +## Imports -// ⚠️ SENSITIVE: Handle with care -const { privateKey, publicKey } = account.keyPair -console.log('Public key length:', publicKey.length) // 65 bytes -if (privateKey !== null) { - console.log('Private key length:', privateKey.length) // 32 bytes -} +```javascript title="Default import" +import WalletManagerSolanaGasless from '@tetherto/wdk-wallet-solana-gasless' ``` -⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, or expose the private key. The byte arrays are bound to the wallet account — do not modify their contents. +```javascript title="Named imports" +import { + WalletAccountReadOnlySolanaGasless, + WalletAccountSolanaGasless +} from '@tetherto/wdk-wallet-solana-gasless' +``` -## WalletAccountReadOnlyEvm +## Exports -Represents a read-only wallet account that can query balances and estimate fees but cannot send transactions. +| Export | Kind | Description | +|--------|------|-------------| +| `WalletManagerSolanaGasless` | Class, default export | Derives Solana gasless accounts from a seed. | +| `WalletAccountSolanaGasless` | Class | Owned account with signing, sending, SPL transfer, quote, and read methods. | +| `WalletAccountReadOnlySolanaGasless` | Class | Read-only account for balances, quotes, receipts, and signature verification. | +| `KeyPair` | Type | Raw account key pair shape inherited from `@tetherto/wdk-wallet`. | +| `SolanaGaslessWalletConfig` | Type | Solana wallet config plus required paymaster options. | +| `SolanaGaslessWalletPaymasterConfig` | Type | Paymaster endpoint, address, and token configuration. | +| `SolanaGaslessWalletPaymasterConfigOverrides` | Type | Per-call overrides for paymaster token and fee caps. | +| `PaymasterTokenConfig` | Type | Paymaster fee token configuration. | +| `SolanaTransaction` | Type | Simple Solana transaction input or transaction message input inherited from the Solana wallet module. | +| `SolanaTransactionReceipt` | Type | Return type for `getTransactionReceipt()`. | +| `FullySignedTransaction` | Type | Fully signed Solana transaction returned by `signTransaction()`. | +| `TransactionResult` | Type | Result shape for send operations. | +| `TransferOptions` | Type | SPL transfer input options. | +| `TransferResult` | Type | Result shape for SPL token transfers. | + +## WalletManagerSolanaGasless + +Derives and returns owned Solana gasless accounts from a BIP-39 seed phrase or seed bytes. Extends `WalletManager` from `@tetherto/wdk-wallet`. ### Constructor -```javascript -new WalletAccountReadOnlyEvm(address, config?) +```typescript +new WalletManagerSolanaGasless( + seed: string | Uint8Array, + config?: SolanaGaslessWalletConfig +) ``` **Parameters:** -- `address` (string): The account's Ethereum address -- `config` (`Omit`, optional): Configuration object (same as `EvmWalletConfig` but without `transferMaxFee`, since read-only accounts cannot send transactions) - - `provider` (`string | Eip1193Provider | Array`, optional): RPC endpoint URL, EIP-1193 provider instance, or ordered failover list - - `retries` (number, optional): Additional retry attempts when `provider` is an array - - `chainId` (number, optional): Network chain ID. When provided, skips automatic chain ID detection. - -**Example:** -```javascript -const readOnlyAccount = new WalletAccountReadOnlyEvm('0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', { - provider: 'https://rpc.mevblocker.io/fast' -}) -``` -### Properties - -| Property | Type | Description | -|----------|------|-------------| -| `address` | `string` | The account's Ethereum address | +- `seed`: BIP-39 mnemonic seed phrase or seed bytes. +- `config`: Solana RPC and Kora-compatible paymaster configuration. ### Methods -| Method | Description | Returns | Throws | -|--------|-------------|---------|--------| -| `getAddress()` | Returns the account's address | `Promise` | - | -| `getBalance()` | Returns the native token balance (in wei) | `Promise` | If no provider | -| `getTokenBalance(tokenAddress)` | Returns the balance of a specific ERC20 token | `Promise` | If no provider | -| `getTokenBalances(tokenAddresses)` | Returns balances for multiple ERC20 tokens | `Promise>` | If no provider | -| `quoteSendTransaction(tx)` | Estimates the fee for an EVM transaction | `Promise<{fee: bigint}>` | If no provider | -| `quoteTransfer(options)` | Estimates the fee for an ERC20 transfer | `Promise<{fee: bigint}>` | If no provider | -| `verify(message, signature)` | Verifies a message signature | `Promise` | - | -| `verifyTypedData(typedData, signature)` | Verifies a typed data signature (EIP-712) | `Promise` | - | -| `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise` | If no provider | -| `getAllowance(token, spender)` | Returns current allowance for a spender | `Promise` | If no provider | - -#### `getAddress()` -Returns the account's Ethereum address. +| Method | Description | Returns | +|--------|-------------|---------| +| `getAccount(index?)` | Returns the account at the default Solana derivation path for the given index. | `Promise` | +| `getAccountByPath(path)` | Returns the account at a specific SLIP-0010 derivation path. | `Promise` | -**Returns:** `Promise` - Checksummed Ethereum address +#### getAccount -**Example:** -```javascript -const address = await readOnlyAccount.getAddress() -console.log('Account address:', address) // 0x... +```typescript +getAccount(index?: number): Promise ``` -#### `getBalance()` -Returns the account's native token balance. - -**Returns:** `Promise` - Balance in wei +Returns the account for `m/44'/501'/index'/0'`. If `index` is omitted, the module uses `0`. -**Throws:** Error if no provider is configured - -**Example:** -```javascript -const balance = await readOnlyAccount.getBalance() -console.log('Balance:', balance, 'wei') +```javascript title="Get the first account" +const wallet = new WalletManagerSolanaGasless(seedPhrase, config) +const account = await wallet.getAccount(0) ``` -#### `getTokenBalance(tokenAddress)` -Returns the balance of a specific ERC20 token. - -**Parameters:** -- `tokenAddress` (string): The ERC20 token contract address +#### getAccountByPath -**Returns:** `Promise` - Token balance in base units +```typescript +getAccountByPath(path: string): Promise +``` -**Throws:** Error if no provider is configured +Returns the account at a specific Solana SLIP-0010 derivation path. -**Example:** -```javascript -const tokenBalance = await readOnlyAccount.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') -console.log('USDT balance:', tokenBalance) +```javascript title="Get an account by path" +const account = await wallet.getAccountByPath("0'/0'/1'") ``` -#### `getTokenBalances(tokenAddresses)` -Returns balances for multiple ERC20 tokens. - -**Parameters:** -- `tokenAddresses` (string[]): List of ERC20 token contract addresses +## WalletAccountSolanaGasless -**Returns:** `Promise>` - Object mapping each token address to its balance in base units +Owned Solana gasless account. Extends `WalletAccountReadOnlySolanaGasless` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. -**Throws:** Error if no provider is configured +### Constructor -**Example:** -```javascript -const balances = await readOnlyAccount.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT -]) -console.log('Balances:', balances) +```typescript +new WalletAccountSolanaGasless( + seed: string | Uint8Array, + path: string, + config: SolanaGaslessWalletConfig +) ``` -#### `quoteSendTransaction(tx)` -Estimates the fee for an EVM transaction. - **Parameters:** -- `tx` (EvmTransaction): The transaction object - -**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei -**Throws:** Error if no provider is configured - -**Example:** -```javascript -const quote = await readOnlyAccount.quoteSendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000 -}) -console.log('Estimated fee:', quote.fee, 'wei') -``` +- `seed`: BIP-39 mnemonic seed phrase or seed bytes. +- `path`: SLIP-0010 derivation path, for example `"0'/0'/0'"`. +- `config`: Solana RPC and Kora-compatible paymaster configuration. -#### `quoteTransfer(options)` -Estimates the fee for an ERC20 token transfer. +### Properties -**Parameters:** -- `options` (TransferOptions): Transfer options +| Property | Description | Type | +|----------|-------------|------| +| `index` | Derivation path index for this account. | `number` | +| `path` | Derivation path for this account. | `string` | +| `keyPair` | Raw Solana Ed25519 key pair bytes. | `KeyPair` | -**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei +### Methods -**Throws:** Error if no provider is configured +| Method | Description | Returns | +|--------|-------------|---------| +| `getAddress()` | Returns the account address. | `Promise` | +| `sign(message)` | Signs a message with the account private key. | `Promise` | +| `verify(message, signature)` | Verifies a message signature against the account address. | `Promise` | +| `getBalance()` | Returns the native SOL balance in lamports. | `Promise` | +| `getTokenBalance(tokenAddress)` | Returns one SPL token balance in base units. | `Promise` | +| `getTokenBalances(tokenAddresses)` | Returns multiple SPL token balances in base units. | `Promise>` | +| `getPaymasterTokenBalance()` | Returns the configured paymaster token balance in base units. | `Promise` | +| `quoteSendTransaction(tx, config?)` | Quotes an unsigned send, or decodes the embedded payment fee from a fully signed transaction. | `Promise>` | +| `signTransaction(tx, config?)` | Returns a fully signed paymaster-funded transaction without broadcasting it. | `Promise` | +| `sendTransaction(tx, config?)` | Sends an unsigned paymaster-funded transaction, or directly broadcasts a fully signed transaction through Solana RPC. | `Promise` | +| `quoteTransfer(options, config?)` | Quotes the paymaster fee for an SPL transfer. | `Promise>` | +| `transfer(options, config?)` | Transfers SPL tokens through the configured paymaster. | `Promise` | +| `getTransactionReceipt(hash)` | Reads a Solana transaction receipt by signature. | `Promise` | +| `toReadOnlyAccount()` | Returns a read-only copy of the account. | `Promise` | +| `dispose()` | Clears private key material held by the account. | `void` | + +#### getAddress -**Example:** -```javascript -const quote = await readOnlyAccount.quoteTransfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}) -console.log('Transfer fee estimate:', quote.fee, 'wei') +```typescript +getAddress(): Promise ``` -#### `verify(message, signature)` -Verifies a message signature against the account's address. - -**Parameters:** -- `message` (string): The original message -- `signature` (string): The signature to verify - -**Returns:** `Promise` - True if signature is valid +Returns the account's base58-encoded Solana address. -**Example:** -```javascript -const message = 'Hello, Ethereum!' -const signature = await account.sign(message) +#### sign -const readOnlyAccount = new WalletAccountReadOnlyEvm('0x...', { provider: '...' }) -const isValid = await readOnlyAccount.verify(message, signature) -console.log('Signature valid:', isValid) // true +```typescript +sign(message: string): Promise ``` -#### `verifyTypedData(typedData, signature)` -Verifies a typed data signature according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). - -**Parameters:** -- `typedData` (TypedData): The typed data that was signed -- `signature` (string): The signature to verify +Signs a message and returns its signature. -**Returns:** `Promise` - True if signature is valid +#### signTransaction -**Example:** -```javascript -const isValid = await readOnlyAccount.verifyTypedData(typedData, signature) -console.log('Typed data signature valid:', isValid) // true +```typescript +signTransaction( + tx: SolanaTransaction, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise ``` -#### `getTransactionReceipt(hash)` -Returns a transaction's receipt if it has been mined. - -**Parameters:** -- `hash` (string): The transaction hash +Signs a paymaster-funded transaction without broadcasting it. The module adds the paymaster payment instruction, checks the quoted payment against `transactionMaxFee`, signs with the account owner, asks the paymaster to sign, and returns the fully signed transaction. -**Returns:** `Promise` - Transaction receipt or null if not yet mined +The method throws when the quoted paymaster fee is greater than `transactionMaxFee`. -**Throws:** Error if no provider is configured +#### sendTransaction -**Example:** -```javascript -const receipt = await readOnlyAccount.getTransactionReceipt('0x...') -if (receipt) { - console.log('Transaction confirmed in block:', receipt.blockNumber) - console.log('Gas used:', receipt.gasUsed) - console.log('Status:', receipt.status) // 1 = success, 0 = failed -} else { - console.log('Transaction not yet mined') -} +```typescript +sendTransaction( + tx: SolanaTransaction | FullySignedTransaction, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise ``` -#### `getAllowance(token, spender)` -Returns the current allowance for the given token and spender. - -**Parameters:** -- `token` (string): The token's address -- `spender` (string): The spender's address +For an unsigned input, sends a paymaster-funded native transfer or prebuilt transaction message. For a `FullySignedTransaction` returned by `signTransaction()`, the module does not contact the paymaster: it decodes the embedded payment fee, applies `transactionMaxFee`, base64-encodes the signed wire transaction, and sends it through the configured Solana RPC with `encoding: 'base64'`. -**Returns:** `Promise` - The allowance +```javascript title="Send native SOL" +const result = await account.sendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + transactionMaxFee: 500000n +}) -**Example:** -```javascript -const allowance = await readOnlyAccount.getAllowance( - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - '0xSpenderAddress...' -) -console.log('Allowance:', allowance) +console.log(result.hash) +console.log(result.fee) ``` -## Types +The method throws when the payment fee is greater than `transactionMaxFee`; a fee equal to the cap is allowed. A signed transaction retains its existing blockhash or durable nonce lifetime, payment instruction, and signatures. The method does not refresh or re-sign it. -### EvmTransaction +#### transfer ```typescript -interface EvmTransaction { - to: string; // The transaction's recipient address - value: number | bigint; // The amount of ethers to send (in wei) - data?: string; // The transaction's data in hex format (optional) - gasLimit?: number | bigint; // Maximum amount of gas this transaction can use (optional) - gasPrice?: number | bigint; // Legacy gas price in wei (optional) - maxFeePerGas?: number | bigint; // EIP-1559 max fee per gas in wei (optional) - maxPriorityFeePerGas?: number | bigint; // EIP-1559 priority fee in wei (optional) - type?: number; // Transaction type, such as 4 for ERC-7702 (optional) - nonce?: number; // Transaction nonce (optional) - chainId?: number | bigint; // Network chain ID (optional) - authorizationList?: AuthorizationLike[]; // ERC-7702 authorization list for type 4 transactions (optional) -} +transfer( + options: TransferOptions, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise ``` -### TransferOptions +Transfers SPL tokens through the paymaster. Native SOL transfers are handled by `sendTransaction()` instead. -```typescript -interface TransferOptions { - token: string; // ERC20 token contract address - recipient: string; // Recipient's Ethereum address - amount: number | bigint; // Amount in token's base units -} +```javascript title="Transfer an SPL token" +const result = await account.transfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n +}, { + transferMaxFee: 500000n +}) ``` -### TransactionResult +The method throws when the quoted paymaster fee is greater than `transferMaxFee`. + +#### quoteSendTransaction ```typescript -interface TransactionResult { - hash: string; // Transaction hash - fee: bigint; // Transaction fee paid in wei -} +quoteSendTransaction( + tx: SolanaTransaction | FullySignedTransaction, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise> ``` -### TransferResult +For an unsigned input, requests a paymaster fee quote for `sendTransaction()` or `signTransaction()` inputs. For a `FullySignedTransaction`, it does not call the paymaster or broadcast: it locates the signed SPL token payment to the configured paymaster token account and decodes its `u64` amount. Both forms return a `bigint` fee in the configured paymaster token's base units and do not enforce `transactionMaxFee`. + +#### quoteTransfer ```typescript -interface TransferResult { - hash: string; // Transfer transaction hash - fee: bigint; // Transfer fee paid in wei -} +quoteTransfer( + options: TransferOptions, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise> ``` -### FeeRates +Quotes the paymaster fee for `transfer()` inputs. Quote methods return estimates and do not enforce `transferMaxFee`. + +#### getTransactionReceipt ```typescript -interface FeeRates { - normal: bigint; // Normal priority fee rate (base fee × 1.1) - fast: bigint; // Fast priority fee rate (base fee × 2.0) -} +getTransactionReceipt(hash: string): Promise ``` -### KeyPair +Returns the Solana transaction receipt for a submitted signature, or `null` if the transaction has not been included in a block yet. + +#### toReadOnlyAccount ```typescript -interface KeyPair { - privateKey: Uint8Array | null; // Private key as Uint8Array (32 bytes, null after dispose) - publicKey: Uint8Array; // Public key as Uint8Array (65 bytes) -} +toReadOnlyAccount(): Promise ``` -### TypedData +Returns a read-only account for the same address. + +## WalletAccountReadOnlySolanaGasless + +Read-only Solana gasless account for an address. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. + +### Constructor ```typescript -interface TypedData { - domain: TypedDataDomain; // The domain separator - types: Record; // The type definitions - message: Record; // The message data -} +new WalletAccountReadOnlySolanaGasless( + addr: string, + config: Omit +) ``` -### TypedDataDomain +**Parameters:** + +- `addr`: Solana account address. +- `config`: Solana RPC and paymaster configuration. Read-only accounts do not accept `transferMaxFee` or `transactionMaxFee`. + +### Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `getBalance()` | Returns the native SOL balance in lamports. | `Promise` | +| `getTokenBalance(tokenAddress)` | Returns one SPL token balance in base units. | `Promise` | +| `getTokenBalances(tokenAddresses)` | Returns multiple SPL token balances in base units. | `Promise>` | +| `getPaymasterTokenBalance()` | Returns the configured paymaster token balance in base units. | `Promise` | +| `quoteSendTransaction(tx, config?)` | Quotes the paymaster fee for a native send or transaction message. | `Promise>` | +| `quoteTransfer(options, config?)` | Quotes the paymaster fee for an SPL transfer. | `Promise>` | +| `getTransactionReceipt(hash)` | Reads a Solana transaction receipt by signature. | `Promise` | +| `verify(message, signature)` | Verifies a message signature against the account address. | `Promise` | + +## Configuration Types + +### SolanaGaslessWalletConfig ```typescript -interface TypedDataDomain { - name?: string; // The domain name (e.g., the DApp name) - version?: string; // The domain version - chainId?: number | bigint; // The chain ID - verifyingContract?: string; // The verifying contract address - salt?: string; // An optional salt -} +type SolanaGaslessWalletConfig = + SolanaWalletConfig & SolanaGaslessWalletPaymasterConfig ``` -### TypedDataField +Combines the base Solana wallet configuration with the required paymaster configuration. + +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `provider` | `string \| string[]` | No | Solana RPC endpoint or ordered failover list. RPC-backed reads, quotes, signing, sending, and transfers require a usable `provider` or `rpcUrl`. | +| `rpcUrl` | `string \| string[]` | No | Deprecated alias inherited from the base Solana wallet module. Use `provider`. | +| `commitment` | `'processed' \| 'confirmed' \| 'finalized'` | No | Solana commitment level for reads and receipts. | +| `retries` | `number` | No | Additional retry attempts for ordered Solana RPC or paymaster failover lists. Default: `3`. | +| `paymasterUrl` | `string \| KoraClientOptions \| Array` | Yes | Kora-compatible paymaster endpoint, client options, or ordered failover list. | +| `paymasterAddress` | `string` | Yes | Solana address used as the transaction fee payer. | +| `paymasterToken` | `PaymasterTokenConfig` | Yes | Token used by the paymaster to quote and charge fees. | +| `transferMaxFee` | `number \| bigint` | No | Fee cap for `transfer()` calls, in the paymaster token's base units. | +| `transactionMaxFee` | `number \| bigint` | No | Fee cap for `sendTransaction()` and `signTransaction()` calls, in the paymaster token's base units. | + +### SolanaGaslessWalletPaymasterConfig ```typescript -interface TypedDataField { - name: string; // The field name - type: string; // The field type (e.g., 'address', 'uint256', 'string') +type SolanaGaslessWalletPaymasterConfig = { + paymasterUrl: string | KoraClientOptions | (string | KoraClientOptions)[] + paymasterAddress: string + paymasterToken: PaymasterTokenConfig } ``` -### EvmWalletConfig +### PaymasterTokenConfig ```typescript -interface EvmWalletConfig { - provider?: string | Eip1193Provider | Array; // RPC URL, EIP-1193 provider, or ordered failover list - retries?: number; // Additional retry attempts for provider arrays - chainId?: number; // Network chain ID. Skips automatic detection when provided. - transferMaxFee?: number | bigint; // Maximum fee for transfers in wei +type PaymasterTokenConfig = { + address: string } ``` -### ApproveOptions +### SolanaGaslessWalletPaymasterConfigOverrides ```typescript -interface ApproveOptions { - token: string; // ERC20 token contract address - spender: string; // Address allowed to spend tokens - amount: number | bigint; // Amount to approve in base units -} +type SolanaGaslessWalletPaymasterConfigOverrides = Partial< + Pick & + Pick +> ``` -### EvmTransactionReceipt +Pass overrides as the second argument to quote, sign, send, or transfer methods. + +| Override | Applies to | Description | +|----------|------------|-------------| +| `paymasterToken` | Quotes, signing, sends, transfers | Overrides the fee token for one call. | +| `transactionMaxFee` | `sendTransaction()`, `signTransaction()` | Cancels the operation when the quoted transaction fee is above the cap. | +| `transferMaxFee` | `transfer()` | Cancels the operation when the quoted transfer fee is above the cap. | + +## ConfigurationError + +The beta.2 TypeScript declarations include `ConfigurationError`, and the runtime uses that error name for missing required paymaster fields. The JavaScript root entrypoint does not re-export the runtime class, so JavaScript code should not import `ConfigurationError` from `@tetherto/wdk-wallet-solana-gasless`. ```typescript -interface EvmTransactionReceipt { - to: string; // Recipient address - from: string; // Sender address - contractAddress: string | null; // Contract address if contract creation - transactionIndex: number; // Transaction index in block - gasUsed: bigint; // Gas actually used - logsBloom: string; // Bloom filter for logs - blockHash: string; // Block hash containing transaction - transactionHash: string; // Transaction hash - logs: Array; // Event logs - blockNumber: number; // Block number - confirmations: number; // Number of confirmations - cumulativeGasUsed: bigint; // Cumulative gas used in block - effectiveGasPrice: bigint; // Effective gas price paid - status: number; // Transaction status (1 = success, 0 = failed) - type: number; // Transaction type (0 = legacy, 2 = EIP-1559) +class ConfigurationError extends Error { + constructor(message: string) } ``` +Missing `paymasterUrl`, `paymasterAddress`, or `paymasterToken` throws an error named `ConfigurationError`. An empty `paymasterUrl` failover list throws a plain `Error`. + +## Next Steps + - -Get started with WDK in a Node.js environment - - -Build mobile wallets with React Native Expo + +Configure Solana RPC, paymaster endpoints, fee tokens, and fee caps. - -Get started with WDK's EVM Wallet Usage + +Quote, sign, and send paymaster-funded transactions. - -Get started with WDK's EVM Wallet Configuration + +Transfer SPL tokens through the paymaster. *** -### Need Help? +## Need Help? *** ## Configuration -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/configuration -Description: Configuration options and settings for @tetherto/wdk-wallet-evm +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/configuration +Description: Configuration options for @tetherto/wdk-wallet-solana-gasless. ## Wallet Configuration -The `WalletManagerEvm` accepts a configuration object that defines how the wallet interacts with the blockchain: +`WalletManagerSolanaGasless` accepts a seed phrase or seed bytes plus a Solana gasless wallet configuration: -```javascript -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +```javascript title="Create a gasless Solana wallet" +import WalletManagerSolanaGasless from '@tetherto/wdk-wallet-solana-gasless' const config = { - // Recommended: RPC endpoint URL, EIP-1193 provider, or ordered failover list - provider: 'https://eth.drpc.org', - - // Optional: Skip automatic chain ID detection when the network is known - chainId: 1, - - // Optional: Additional failover attempts when provider is an array - retries: 2, - - // Optional: Maximum fee for transfer operations (in wei) - transferMaxFee: 100000000000000 // 0.0001 ETH + provider: 'https://api.devnet.solana.com', + commitment: 'confirmed', + paymasterUrl: 'https://your-kora-paymaster.example', + paymasterAddress: 'Paymaster111111111111111111111111111111111', + paymasterToken: { + address: 'TokenMint111111111111111111111111111111111' + }, + transferMaxFee: 1000000n, + transactionMaxFee: 1000000n } -const wallet = new WalletManagerEvm(seedPhrase, config) +const wallet = new WalletManagerSolanaGasless(seedPhrase, config) +const account = await wallet.getAccount(0) ``` ## Account Configuration -Both `WalletAccountEvm` and `WalletAccountReadOnlyEvm` share similar configuration options: +You can also construct an owned or read-only account directly: -```javascript -import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' +```javascript title="Create accounts directly" +import { + WalletAccountReadOnlySolanaGasless, + WalletAccountSolanaGasless +} from '@tetherto/wdk-wallet-solana-gasless' -// Full access account -const account = new WalletAccountEvm( - seedPhrase, - "0'/0/0", // BIP-44 derivation path - { - provider: 'https://eth.drpc.org', - transferMaxFee: 100000000000000 - } -) +const account = new WalletAccountSolanaGasless(seedPhrase, "0'/0'", config) -// Read-only account -const readOnlyAccount = new WalletAccountReadOnlyEvm( - '0x...', // Ethereum address - { - provider: 'https://eth.drpc.org' +const readOnlyAccount = new WalletAccountReadOnlySolanaGasless('SolanaAddress...', { + provider: 'https://api.devnet.solana.com', + commitment: 'confirmed', + paymasterUrl: 'https://your-kora-paymaster.example', + paymasterAddress: 'Paymaster111111111111111111111111111111111', + paymasterToken: { + address: 'TokenMint111111111111111111111111111111111' } -) +}) ``` +Read-only accounts do not accept `transferMaxFee` or `transactionMaxFee` in their public type because they cannot send transfers or sign transactions. + ## Configuration Options -### Provider +### provider -The `provider` option specifies how to connect to the blockchain. It can be a URL string, an EIP-1193 compatible provider instance, or an ordered array of URL strings and EIP-1193 providers for automatic failover. +Solana RPC endpoint, or an ordered list of RPC endpoints for failover. This is required for balance reads, blockhash lookup, quotes, signing transactions, sending transactions, and transfers. -**Type:** `string | Eip1193Provider | Array` +**Type:** `string | string[]` -**Examples:** +**Example:** ```javascript -// Option 1: Using RPC URL const config = { - provider: 'https://eth.drpc.org' + provider: [ + 'https://api.devnet.solana.com', + 'https://backup-solana-rpc.example' + ] } +``` -// Option 2: Using browser provider (e.g., MetaMask) -const config = { - provider: window.ethereum -} +### rpcUrl -// Option 3: Using a custom EIP-1193 provider -// Works in Node.js, Bare, and browsers - zero external dependencies -function createFetchProvider(rpcUrl) { - let requestId = 0 - return { - request: async ({ method, params }) => { - const response = await fetch(rpcUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: ++requestId, - method, - params: params || [] - }) - }) - const data = await response.json() - if (data.error) throw new Error(data.error.message) - return data.result - } - } -} +Deprecated alias for `provider`, inherited from the base Solana wallet module. New code should use `provider`. -const config = { - provider: createFetchProvider('https://eth.drpc.org') -} +**Type:** `string | string[]` -// Option 4: Using ordered provider failover -const config = { - provider: [ - 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY', - 'https://eth.drpc.org', - createFetchProvider('https://ethereum.publicnode.com') - ], - retries: 2 -} -``` +### commitment -When `provider` is an array, the wallet uses the candidates in order and retries connection failures against the next provider. If `retries` is greater than the number of providers, the failover loop wraps around in round-robin order. +Solana commitment level used for RPC reads such as balances, blockhashes, and receipts. -### Retries +**Type:** `'processed' | 'confirmed' | 'finalized'` -The `retries` option controls how many additional attempts can happen after the first provider call fails. It only applies when `provider` is an array. +### paymasterUrl -**Type:** `number` (optional) -**Default:** `3` +Kora-compatible paymaster RPC endpoint, Kora client options object, or ordered failover list. + +**Type:** `string | KoraClientOptions | Array` + +**Required:** Yes **Example:** + ```javascript const config = { - provider: [ - 'https://primary.example', - 'https://secondary.example' - ], - retries: 1 + paymasterUrl: [ + 'https://primary-paymaster.example', + { rpcUrl: 'https://backup-paymaster.example' } + ] } ``` -### Chain ID - -The `chainId` option pins the provider to a known EVM chain ID. Use it when you already know the target network and want to skip automatic chain ID detection during provider setup. +An empty `paymasterUrl` array throws an error. -**Type:** `number` (optional) +### paymasterAddress -**Example:** -```javascript -const config = { - provider: 'https://polygon-rpc.com', - chainId: 137 -} -``` +Solana address used as the transaction fee payer. For prebuilt `TransactionMessage` inputs, an explicit `feePayer` must be absent or equal to this address. -### Transfer Max Fee +**Type:** `string` -The `transferMaxFee` option sets a maximum limit for transaction fees to prevent unexpectedly high costs. +**Required:** Yes -**Type:** `number | bigint` (optional) -**Unit:** Wei (1 ETH = 1000000000000000000 Wei) +### paymasterToken -**Examples:** +Token used by the paymaster to quote and charge fees. -```javascript -const config = { - // Set maximum fee to 0.0001 ETH - transferMaxFee: 100000000000000n, -} +**Type:** -// Usage example -try { - const result = await account.transfer({ - token: '0x...', // ERC20 address - recipient: '0x...', - amount: 1000000n - }) -} catch (error) { - if (error.message.includes('Exceeded maximum fee')) { - console.error('Transfer cancelled: Fee too high') - } +```typescript +type PaymasterTokenConfig = { + address: string } ``` -### Fee Rate Multipliers +**Required:** Yes -The wallet manager uses predefined multipliers for fee calculations: +The module passes `paymasterToken.address` to the paymaster as the fee token and returns fees in that token's base units. -```javascript -// Normal fee rate = base fee × 1.1 -const normalFee = await wallet.getFeeRates() -console.log('Normal fee:', normalFee.normal) +### retries -// Fast fee rate = base fee × 2.0 -const fastFee = await wallet.getFeeRates() -console.log('Fast fee:', fastFee.fast) -``` +Additional retry attempts used by failover providers when `provider` or `paymasterUrl` is an ordered list. -## Network Support +**Type:** `number` -The configuration works with any EVM-compatible network. Just change the provider URL: +**Default:** `3` + +### transferMaxFee + +Maximum allowed paymaster fee for `transfer()` calls, in the configured paymaster token's base units. `transfer()` throws when the quoted fee is greater than this cap. + +**Type:** `number | bigint` + +**Required:** No + +**Example:** ```javascript -// Ethereum Mainnet -const mainnetConfig = { - provider: 'https://eth.drpc.org' -} +const result = await account.transfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n +}, { + transferMaxFee: 500000n +}) +``` -// Polygon (Matic) -const polygonConfig = { - provider: 'https://polygon-rpc.com' -} +### transactionMaxFee -// Arbitrum -const arbitrumConfig = { - provider: 'https://arb1.arbitrum.io/rpc' -} +Maximum allowed paymaster fee for `sendTransaction()` and `signTransaction()` calls, in the configured paymaster token's base units. For unsigned inputs, the module obtains the payment instruction and checks this cap before either the account or paymaster signs. For a `FullySignedTransaction` passed to `sendTransaction()`, it decodes the embedded payment amount and checks this cap before broadcasting through Solana RPC. A fee equal to the cap is allowed; only a greater fee is rejected. -// BSC (Binance Smart Chain) -const bscConfig = { - provider: 'https://bsc-dataseed.binance.org' -} +**Type:** `number | bigint` -// Avalanche C-Chain -const avalancheConfig = { - provider: 'https://avalanche-c-chain-rpc.publicnode.com', -} +**Required:** No -// Plasma -const plasmaConfig = { - provider: 'https://plasma.drpc.org', -} +**Example:** -// Stable (uses USD₮ as native gas token) -// No need for ERC-4337 paymaster/bundler setup. -const stableConfig = { - provider: 'https://rpc.stable.xyz', -} +```javascript +const result = await account.sendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + transactionMaxFee: 500000n +}) +``` -// Sepolia Testnet -const sepoliaConfig = { - provider: 'https://sepolia.drpc.org', -} +## Paymaster Overrides +Quote, send, sign, and transfer methods accept a second configuration object for per-call fee-token and fee-cap overrides: + +```javascript title="Override paymaster token for one call" +const quote = await account.quoteTransfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n +}, { + paymasterToken: { + address: 'AlternateFeeMint11111111111111111111111111' + }, + transferMaxFee: 500000n +}) ``` -## Next Steps +In `1.0.0-beta.2`, overrides support `paymasterToken`, `transferMaxFee`, and `transactionMaxFee`. Use `transferMaxFee` for `transfer()` fee protection and `transactionMaxFee` for `sendTransaction()` or `signTransaction()` fee protection. `quoteTransfer()` and `quoteSendTransaction()` return estimates without enforcing either cap. + +For an already signed transaction, its payment token and amount are fixed in the signed message. `quoteSendTransaction(signedTransaction)` decodes that embedded payment and does not use the per-call override. On `sendTransaction(signedTransaction, { transactionMaxFee })`, only the fee cap is relevant before direct RPC broadcast; an override cannot replace the signed payment token. + +## Security Considerations + +- Use HTTPS Solana RPC and paymaster endpoints. +- Use RPC endpoints that serve the same Solana network when enabling failover. +- Keep paymaster tokens funded for the accounts that need sponsored transactions. +- Set `transferMaxFee` for token transfers and `transactionMaxFee` for send/sign flows when your application needs fee protection. +- Validate the paymaster address and paymaster token address before using them in production configuration. +- Call `dispose()` on owned accounts and wallet managers when private keys are no longer needed. - -Get started with WDK in a Node.js environment - - -Build mobile wallets with React Native Expo - - -Get started with WDK's EVM Wallet Usage + +Create a gasless Solana account. - -Get started with WDK's EVM Wallet API + +Choose a guide for common gasless Solana flows. *** -### Need Help? +## Need Help? *** ## Check Balances -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/check-balances -Description: Query native and ERC-20 token balances on EVM chains. - -This guide explains how to check native token and ERC-20 token balances for both owned and read-only accounts. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/guides/check-balances +Description: Query SOL, SPL token, and paymaster token balances with the Solana gasless wallet. -## Owned Account Balances - -Use an account retrieved from [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference) to query balances. +This guide explains how to check native SOL, SPL token, batch SPL token, and paymaster token balances. -### Native Token Balance +## Native SOL Balance -You can retrieve the native token balance from an `Account` object using [`account.getBalance()`](/sdk/wallet-modules/wallet-evm/api-reference): +Use `getBalance()` to read the native SOL balance in lamports: -```javascript title="Get Native Balance" +```javascript title="Get SOL balance" const balance = await account.getBalance() -console.log('Native balance:', balance, 'wei') +console.log('SOL balance:', balance, 'lamports') ``` -### Single ERC-20 Token Balance +## SPL Token Balance -You can retrieve a single ERC-20 token balance from an `Account` object using [`account.getTokenBalance(tokenAddress)`](/sdk/wallet-modules/wallet-evm/api-reference#gettokenbalancetokenaddress): +Use `getTokenBalance(tokenAddress)` to read one SPL token balance: -```javascript title="Get ERC-20 Balance" -const tokenAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT -const tokenBalance = await account.getTokenBalance(tokenAddress) +```javascript title="Get SPL token balance" +const tokenBalance = await account.getTokenBalance('TokenMint111111111111111111111111111111111') console.log('Token balance:', tokenBalance) ``` -### Multiple ERC-20 Token Balances +Balances are returned in the token's base units. -You can retrieve multiple ERC-20 token balances from an `Account` object using [`account.getTokenBalances(tokenAddresses)`](/sdk/wallet-modules/wallet-evm/api-reference#gettokenbalancestokenaddresses), where `tokenAddresses` is an array of ERC-20 tokens: +## Batch SPL Token Balances -```javascript title="Get Multiple Token Balances" -const tokenBalances = await account.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT -]) -console.log('Multi-token balances:', tokenBalances) -``` +Use `getTokenBalances(tokenAddresses)` to read multiple SPL balances: -## Read-Only Account Balances +```javascript title="Get batch token balances" +const balances = await account.getTokenBalances([ + 'TokenMint111111111111111111111111111111111', + 'OtherMint1111111111111111111111111111111111' +]) -Use [`WalletAccountReadOnlyEvm`](/sdk/wallet-modules/wallet-evm/api-reference) to check balances for any public address without a seed phrase. +console.log(balances) +``` -### Native Balance +Missing associated token accounts return `0n`. -```javascript title="Read-Only Native Balance" -import { WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' +## Paymaster Token Balance -const readOnlyAccount = new WalletAccountReadOnlyEvm('0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', { - provider: 'https://rpc.mevblocker.io/fast', -}) +Use `getPaymasterTokenBalance()` to read the balance of the configured paymaster fee token: -const balance = await readOnlyAccount.getBalance() -console.log('Native balance:', balance, 'wei') +```javascript title="Get paymaster token balance" +const feeTokenBalance = await account.getPaymasterTokenBalance() +console.log('Paymaster token balance:', feeTokenBalance) ``` -### Single Token Balance +The method reads `paymasterToken.address` from the account configuration and delegates to `getTokenBalance()`. -```javascript title="Read-Only Token Balance" -const tokenBalance = await readOnlyAccount.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') -console.log('Token balance:', tokenBalance) -``` +## Read-Only Balances -### Multiple Token Balances +Read-only accounts support the same balance methods: -```javascript title="Read-Only Multiple Token Balances" -const tokenBalances = await readOnlyAccount.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT -]) -console.log('Multi-token balances:', tokenBalances) -``` +```javascript title="Read-only balance checks" +const readOnlyAccount = await account.toReadOnlyAccount() - -You can also create a read-only account from an existing owned account using [`await account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm/api-reference). - +console.log(await readOnlyAccount.getBalance()) +console.log(await readOnlyAccount.getPaymasterTokenBalance()) +``` ## Next Steps -With balance checks in place, learn how to [send transactions](/sdk/wallet-modules/wallet-evm/guides/send-transactions). +With balances in place, learn how to [send transactions](/sdk/wallet-modules/wallet-solana-gasless/guides/send-transactions). *** -## Error Handling -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/error-handling -Description: Handle errors, manage fees, and dispose of sensitive data in EVM wallets. - -This guide covers best practices for handling transaction errors, managing fee limits, and cleaning up sensitive data from memory. +## Get Started +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/guides/get-started +Description: Install @tetherto/wdk-wallet-solana-gasless and create a paymaster-funded Solana account. -## Handle Transaction Errors +This guide creates a gasless Solana account, reads its address, and checks the configured paymaster token balance. -Wrap transactions in `try/catch` blocks to handle common failure scenarios such as insufficient funds or exceeded fee limits. +## Install -```javascript title="Transaction Error Handling" -try { - const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n - }) - console.log('Transaction successful:', result.hash) -} catch (error) { - console.error('Transaction failed:', error.message) - if (error.message.includes('insufficient funds')) { - console.log('Please add more funds to your wallet') - } - if (error.message.includes('Exceeded maximum fee')) { - console.log('Transfer fee too high') - } -} +```bash title="Install @tetherto/wdk-wallet-solana-gasless beta.2" +npm install @tetherto/wdk-wallet-solana-gasless@1.0.0-beta.2 ``` -## Handle Token Transfer Errors - -Token transfers can fail for additional reasons such as invalid addresses or insufficient token balances. +## Create a Wallet -```javascript title="Token Transfer Error Handling" -try { - const result = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000000000000000n - }) - console.log('Transfer completed:', result.hash) -} catch (error) { - console.error('Transfer failed:', error.message) - if (error.message.includes('Exceeded maximum fee')) { - console.log('Transfer fee too high') - } -} -``` +```javascript title="Create a gasless Solana wallet" +import WalletManagerSolanaGasless from '@tetherto/wdk-wallet-solana-gasless' -## Manage Fee Limits +const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' -Set `transferMaxFee` when creating the wallet to prevent transactions from exceeding a maximum gas cost. Retrieve current network rates with [`getFeeRates()`](/sdk/wallet-modules/wallet-evm/api-reference) to make informed decisions. +const wallet = new WalletManagerSolanaGasless(seedPhrase, { + provider: 'https://api.devnet.solana.com', + commitment: 'confirmed', + paymasterUrl: 'https://your-kora-paymaster.example', + paymasterAddress: 'Paymaster111111111111111111111111111111111', + paymasterToken: { + address: 'TokenMint111111111111111111111111111111111' + }, + transferMaxFee: 1000000n, + transactionMaxFee: 1000000n +}) -```javascript title="Fee Management" -const feeRates = await wallet.getFeeRates() -console.log('Normal fee rate:', feeRates.normal, 'wei') -console.log('Fast fee rate:', feeRates.fast, 'wei') +const account = await wallet.getAccount(0) +console.log('Address:', await account.getAddress()) +console.log('Paymaster token balance:', await account.getPaymasterTokenBalance()) ``` -## Dispose of Sensitive Data +## Clean Up -Call [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) on accounts and wallet managers to clear private keys and sensitive data from memory when they are no longer needed. +Call `dispose()` when you no longer need the account or wallet manager: -```javascript title="Memory Cleanup" +```javascript title="Dispose sensitive data" account.dispose() - wallet.dispose() ``` - -Always call [`dispose()`](/sdk/wallet-modules/wallet-evm/api-reference) in a `finally` block or cleanup handler to ensure sensitive data is cleared even if an error occurs. - +## Next Steps + +- Review [configuration](/sdk/wallet-modules/wallet-solana-gasless/configuration) for paymaster and failover options. +- Learn how to [send transactions](/sdk/wallet-modules/wallet-solana-gasless/guides/send-transactions). +- Learn how to [transfer SPL tokens](/sdk/wallet-modules/wallet-solana-gasless/guides/transfer-tokens). *** -## Getting Started -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/getting-started -Description: Install and create your first EVM wallet. +## Handle Errors +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/guides/handle-errors +Description: Handle paymaster, fee, transaction, and cleanup errors in Solana gasless wallets. -This guide explains how to install the [`@tetherto/wdk-wallet-evm`](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm) package and create a new wallet instance. +This guide covers configuration errors, paymaster failures, fee caps, transaction message fee payer checks, and memory cleanup. -## 1. Installation +## Handle Configuration Errors -### Prerequisites +The module validates required paymaster fields when you create an account: -Before you begin, ensure you have the following installed: +```javascript title="Configuration error handling" +try { + const account = await wallet.getAccount(0) +} catch (error) { + if (error.name === 'ConfigurationError') { + console.error('Invalid paymaster config:', error.message) + } +} +``` -* **[Node.js](https://nodejs.org/)**: version 18 or higher. -* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. +Required paymaster fields are `paymasterUrl`, `paymasterAddress`, and `paymasterToken`. -### Install Package + +In `1.0.0-beta.2`, the published TypeScript declarations name `ConfigurationError`, but the JavaScript root entrypoint does not re-export the runtime class. Check `error.name` or `error.message` instead of importing the class from the package root. + -```bash title="Install @tetherto/wdk-wallet-evm" -npm install @tetherto/wdk-wallet-evm +## Handle Transaction Fee Caps + +`sendTransaction()` and `signTransaction()` throw when the quoted fee is greater than `transactionMaxFee`: + +```javascript title="Transaction fee cap handling" +try { + const result = await account.sendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n + }, { + transactionMaxFee: 500000n + }) + console.log('Transaction submitted:', result.hash) +} catch (error) { + if (error.message.includes('Exceeded maximum fee cost for transaction operation')) { + console.error('Transaction cancelled because the paymaster fee exceeded the cap.') + } +} ``` -## 2. Create a Wallet +## Handle Transfer Fee Caps -Import the module and create a [`WalletManagerEvm`](/sdk/wallet-modules/wallet-evm/api-reference) instance with a BIP-39 seed phrase and an RPC provider. +`transfer()` throws when its quoted fee is greater than `transferMaxFee`: -```javascript title="Create EVM Wallet" -import WalletManagerEvm, { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' +```javascript title="Transfer fee cap handling" +try { + const result = await account.transfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n + }, { + transferMaxFee: 500000n + }) + console.log('Transfer submitted:', result.hash) +} catch (error) { + if (error.message.includes('Exceeded maximum fee cost')) { + console.error('Transfer cancelled because the paymaster fee exceeded the cap.') + } +} +``` -const seedPhrase = 'your twelve word seed phrase here' +## Handle Fee Payer Mismatches -const wallet = new WalletManagerEvm(seedPhrase, { - provider: 'https://rpc.mevblocker.io/fast', - transferMaxFee: 100000000000000 // Optional: maximum fee in wei -}) +When you pass a prebuilt `TransactionMessage`, the explicit fee payer must match `paymasterAddress`: + +```javascript title="Fee payer mismatch handling" +try { + await account.sendTransaction(transactionMessage) +} catch (error) { + if (error.message.includes('does not match paymaster address')) { + console.error('Set the TransactionMessage fee payer to the configured paymaster address.') + } +} ``` - -**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. - +## Handle Paymaster Failures -You can also pass an EIP-1193 provider (e.g., from a browser wallet) instead of an RPC URL: +Paymaster calls can fail when the endpoint is unavailable, the paymaster cannot quote the transaction, or the paymaster token is not funded for the requested flow. Wrap quote and send calls in `try/catch` blocks: -```javascript title="Use EIP-1193 Provider" -const wallet = new WalletManagerEvm(seedPhrase, { - provider: window.ethereum, - transferMaxFee: 100000000000000 -}) +```javascript title="Paymaster error handling" +try { + const quote = await account.quoteSendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n + }) + console.log('Paymaster fee estimate:', quote.fee) +} catch (error) { + console.error('Unable to quote paymaster fee:', error.message) +} ``` -## 3. Get Your First Account +Use ordered `paymasterUrl` arrays and `retries` when you need endpoint failover. -Retrieve an account from the wallet and inspect its address. +## Handle Signed-Transaction Broadcasts -```javascript title="Get Account" -const account = await wallet.getAccount(0) -const address = await account.getAddress() -console.log('Wallet address:', address) +`sendTransaction(signedTransaction)` bypasses the paymaster and sends the existing signed wire transaction directly through the configured Solana RPC. It still decodes the embedded payment fee and enforces `transactionMaxFee` before broadcast. Keep the original signed payload so that a timeout can be investigated without creating a second transaction: -const readOnlyAccount = await account.toReadOnlyAccount() +```javascript title="Broadcast one signed transaction safely" +const signedTransaction = await account.signTransaction(transactionMessage, { + transactionMaxFee: 500000n +}) + +try { + const result = await account.sendTransaction(signedTransaction, { + transactionMaxFee: 500000n + }) + console.log('Transaction submitted:', result.hash) +} catch (error) { + // Do not create and sign a replacement transaction until you have checked + // the original transaction's status and lifetime. + console.error('Signed transaction was not broadcast:', error.message) +} ``` - -**RPC Providers:** The examples use public RPC endpoints for demonstration. We do not endorse any specific provider. -* **Testnets:** You can find public RPCs for Ethereum and other EVM chains on [Chainlist](https://chainlist.org). -* **Mainnet:** For production environments, we recommend using reliable, paid RPC providers to ensure stability. + +The package does not refresh a signed transaction's blockhash, durable nonce, payment instruction, or signatures. Broadcast it while its existing transaction lifetime is valid, and avoid concurrent retries of the same signed payload. If the RPC outcome is uncertain, determine whether the existing transaction reached the network before deciding whether to rebuild and re-sign. - -To use test/mock tokens instead of real funds, see the [configuration section](/sdk/wallet-modules/wallet-evm/configuration#network-support). - +## Dispose of Sensitive Data -## Next Steps +Call `dispose()` on owned accounts and wallet managers when private keys are no longer needed: -With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-evm/guides/manage-accounts). +```javascript title="Memory cleanup" +try { + await account.sendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n + }) +} finally { + account.dispose() + wallet.dispose() +} +``` + + +Read-only accounts do not hold private keys, but owned accounts wrap a standard Solana account and should be disposed after use. + *** ## Manage Accounts -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/manage-accounts -Description: Work with multiple EVM accounts and custom derivation paths. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/guides/manage-accounts +Description: Work with gasless Solana accounts and custom derivation paths. -This guide explains how to retrieve multiple accounts from your EVM wallet and use custom derivation paths. +`WalletManagerSolanaGasless` derives accounts using Solana SLIP-0010 paths and caches accounts by path. ## Retrieve Accounts by Index -Use `getAccount()` with a zero-based index to access accounts derived from the default BIP-44 path (`m/44'/60'/0'/0/{index}`). +Use `getAccount(index)` to access accounts derived from the default path. -```javascript title="Get Accounts by Index" -const account = await wallet.getAccount(0) -const address = await account.getAddress() -console.log('Account 0 address:', address) +```javascript title="Get accounts by index" +const account0 = await wallet.getAccount(0) +console.log('Account 0:', await account0.getAddress()) const account1 = await wallet.getAccount(1) -const address1 = await account1.getAddress() -console.log('Account 1 address:', address1) +console.log('Account 1:', await account1.getAddress()) ``` -## Retrieve Account by Custom Derivation Path +Index `n` derives `m/44'/501'/{n}'/0'`. -Use `getAccountByPath()` when you need a specific hierarchy beyond the default sequential index. +## Retrieve Account by Custom Path -```javascript title="Custom Derivation Path" -const customAccount = await wallet.getAccountByPath("0'/0/5") -const customAddress = await customAccount.getAddress() -console.log('Custom account address:', customAddress) +Use `getAccountByPath(path)` when you need an explicit derivation path suffix: + +```javascript title="Get account by path" +const account = await wallet.getAccountByPath("0'/0'/5'") +console.log('Custom account:', await account.getAddress()) ``` -## Iterate Over Multiple Accounts + +Solana custom child segments should be hardened, for example `0'/0'/5'`. Gasless accounts inherit the wallet manager's RPC and paymaster configuration. + -You can loop through accounts to inspect addresses and balances in bulk. +## Create a Read-Only Account -```javascript title="Multi-Account Iteration" -async function listAccounts(wallet) { - const accounts = [] +Convert an owned account to a read-only account when you only need balances, quotes, receipts, or signature verification: - for (let i = 0; i < 5; i++) { - const account = await wallet.getAccount(i) - const address = await account.getAddress() - const balance = await account.getBalance() +```javascript title="Convert to read-only" +const readOnlyAccount = await account.toReadOnlyAccount() +console.log(await readOnlyAccount.getBalance()) +``` - accounts.push({ - index: i, - path: `m/44'/60'/0'/0/${i}`, - address, - balance - }) +You can also construct a read-only account for any Solana address: - console.log(`Account ${i}:`, { address, balance: balance.toString() }) - } +```javascript title="Construct read-only account" +import { WalletAccountReadOnlySolanaGasless } from '@tetherto/wdk-wallet-solana-gasless' - return accounts -} +const readOnlyAccount = new WalletAccountReadOnlySolanaGasless('SolanaAddress...', { + provider: 'https://api.devnet.solana.com', + paymasterUrl: 'https://your-kora-paymaster.example', + paymasterAddress: 'Paymaster111111111111111111111111111111111', + paymasterToken: { + address: 'TokenMint111111111111111111111111111111111' + } +}) ``` ## Next Steps -Now that you can access your accounts, learn how to [check balances](/sdk/wallet-modules/wallet-evm/guides/check-balances). +Now that you can access accounts, learn how to [check balances](/sdk/wallet-modules/wallet-solana-gasless/guides/check-balances). *** ## Send Transactions -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/send-transactions -Description: Send native tokens on EVM chains with EIP-1559 or legacy gas settings. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/guides/send-transactions +Description: Quote, sign, and send paymaster-funded Solana transactions. -This guide explains how to [send EVM transactions with EIP-1559 gas parameters](#send-with-eip-1559-gas-parameters), [send legacy gas transactions](#send-with-legacy-gas-parameters), [sign without broadcasting](#sign-without-broadcasting), [estimate fees](#estimate-transaction-fees), and [use dynamic fee rates](#use-dynamic-fee-rates). +This guide explains how to send native SOL, sign without broadcasting, quote paymaster fees, send an already signed transaction, and use prebuilt transaction messages. -**BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. +Use `BigInt` values for token and lamport amounts to avoid precision loss. -## Send with EIP-1559 Gas Parameters +## Send Native SOL -You can use [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#sendtransactiontx) to send an EIP-1559 transaction. EIP-1559 transactions provide more predictable gas fees and faster inclusion times. +Use `sendTransaction({ to, value })` to send native SOL through the configured paymaster: -```javascript title="EIP-1559 Transaction" +```javascript title="Send SOL through the paymaster" const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n, // 1 ETH in wei - maxFeePerGas: 30000000000, - maxPriorityFeePerGas: 2000000000 + to: 'Recipient1111111111111111111111111111111', + value: 1000000n }) + console.log('Transaction hash:', result.hash) -console.log('Transaction fee:', result.fee, 'wei') +console.log('Paymaster fee:', result.fee) ``` -## Send with Legacy Gas Parameters +The returned `fee` is denominated in the configured paymaster token's base units, not lamports. -You can also use [`account.sendTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#sendtransactiontx) with legacy gas settings for chains that do not support EIP-1559. +## Quote Before Sending -```javascript title="Legacy Transaction" -const legacyResult = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n, - gasPrice: 20000000000n, - gasLimit: 21000 +Use `quoteSendTransaction()` to get the paymaster fee before submitting: + +```javascript title="Quote SOL send" +const quote = await account.quoteSendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}) + +console.log('Paymaster fee estimate:', quote.fee) +``` + +## Set a Transaction Fee Cap + +Set `transactionMaxFee` in the wallet config or as a per-call override to cancel `sendTransaction()` or `signTransaction()` when the paymaster fee is higher than your cap: + +```javascript title="Send with a transaction fee cap" +const result = await account.sendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + transactionMaxFee: 500000n }) -console.log('Transaction hash:', legacyResult.hash) ``` +`quoteSendTransaction()` returns the estimated fee without enforcing `transactionMaxFee`. `sendTransaction()` and `signTransaction()` reject only when the fee is greater than the cap, so a fee equal to the cap is allowed. + ## Sign Without Broadcasting -Use [`account.signTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#signtransactiontx) when you need a signed raw transaction but want to submit it through a separate relay, service, or review flow. +Use `signTransaction()` when another process will inspect or submit the fully signed transaction: -```javascript title="Sign EVM Transaction" +```javascript title="Sign paymaster-funded transaction" const signedTransaction = await account.signTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n, - chainId: 1, - maxFeePerGas: 30000000000n, - maxPriorityFeePerGas: 2000000000n + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + transactionMaxFee: 500000n }) console.log('Signed transaction:', signedTransaction) ``` - -`signTransaction()` returns the signed transaction payload and does not broadcast it. Use `sendTransaction()` when WDK should sign, broadcast, and return the transaction hash. - +The module adds the payment instruction, checks `transactionMaxFee`, signs with the owner account, asks the paymaster to sign, and returns a `FullySignedTransaction`. It does not broadcast from `signTransaction()`. -## Estimate Transaction Fees +## Quote and Send a Signed Transaction -Use [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#quotesendtransactiontx) to get a fee estimate before sending. +In `1.0.0-beta.2`, owned accounts accept the `FullySignedTransaction` returned by `signTransaction()` in both `quoteSendTransaction()` and `sendTransaction()`: -```javascript title="Quote Transaction Fee" -const quote = await account.quoteSendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n +```javascript title="Inspect then broadcast a signed transaction" +const signedTransaction = await account.signTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + transactionMaxFee: 500000n }) -console.log('Estimated fee:', quote.fee, 'wei') + +const { fee } = await account.quoteSendTransaction(signedTransaction) +console.log('Embedded paymaster fee:', fee) + +const result = await account.sendTransaction(signedTransaction, { + transactionMaxFee: 500000n +}) +console.log('Transaction hash:', result.hash) ``` -## Use Dynamic Fee Rates +For a signed input, `quoteSendTransaction()` does not request a new paymaster quote or broadcast anything. It decodes the payment amount from the signed message's SPL token payment instruction; the result is a `bigint` in the configured paymaster token's base units. -Retrieve current fee rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-evm/api-reference#getfeerates) and apply them to your transaction. +`sendTransaction(signedTransaction)` decodes that same fee, enforces `transactionMaxFee`, base64-encodes the fully signed wire transaction, and submits it through the configured Solana RPC with `encoding: 'base64'`. It does not call the paymaster's `signAndSendTransaction()` again. -```javascript title="Dynamic Fee Rates" -const feeRates = await wallet.getFeeRates() -console.log('Normal fee rate:', feeRates.normal, 'wei') -console.log('Fast fee rate:', feeRates.fast, 'wei') + +A signed transaction is immutable. This flow does not obtain a fresh blockhash, durable nonce, payment instruction, or paymaster signature. Submit the exact signed payload while its existing lifetime is valid, and check its original signature before retrying after an uncertain RPC result. + + +## Use a TransactionMessage + +Pass a prebuilt `TransactionMessage` when your app needs custom instructions. + +```javascript title="Quote and send a TransactionMessage" +const quote = await account.quoteSendTransaction(transactionMessage) +console.log('Paymaster fee estimate:', quote.fee) + +const result = await account.sendTransaction(transactionMessage) +console.log('Transaction hash:', result.hash) +``` + +If the message already includes a recent blockhash or durable nonce lifetime, the module preserves it. If it does not, the module adds a blockhash lifetime before it requests payment instructions and signatures. A signed result keeps that lifetime; `sendTransaction(signedTransaction)` does not refresh it. + + +If a prebuilt message sets `feePayer`, it must equal `paymasterAddress`. The module sets the fee payer to the paymaster address before asking the paymaster for fee instructions. + + +## Read a Transaction Receipt + +Use `getTransactionReceipt(hash)` to check whether a submitted transaction has been included in a block: + +```javascript title="Read transaction receipt" +const receipt = await account.getTransactionReceipt(result.hash) +if (receipt === null) { + console.log('Transaction is not included in a block yet.') +} else { + console.log('Transaction receipt:', receipt) +} +``` + +The method returns `null` while the transaction is still pending. + +## Override Paymaster Token + +You can override the paymaster token for one quote, sign, or send call: + +```javascript title="Override fee token for one send" const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n, - data: '0x', - gasLimit: 21000, - maxFeePerGas: feeRates.fast, - maxPriorityFeePerGas: 2000000000n + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + paymasterToken: { + address: 'AlternateFeeMint11111111111111111111111111' + } }) -console.log('Transaction sent:', result.hash) -console.log('Fee paid:', result.fee, 'wei') ``` - -**Gas Estimation:** The `maxFeePerGas` and `maxPriorityFeePerGas` fields enable EIP-1559 transactions, ensuring more predictable gas fees and faster inclusion times. - +For an already signed transaction, the payment token is already embedded in the signed message. Do not use `paymasterToken` as an override to try to change it; only `transactionMaxFee` is applied before direct broadcast. ## Next Steps -To transfer ERC-20 tokens instead of native tokens, see [Transfer ERC-20 Tokens](/sdk/wallet-modules/wallet-evm/guides/transfer-tokens). +To transfer SPL tokens instead of native SOL, see [Transfer SPL Tokens](/sdk/wallet-modules/wallet-solana-gasless/guides/transfer-tokens). *** ## Sign and Verify Messages -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/sign-verify-messages -Description: Sign messages and verify signatures with EVM accounts. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/guides/sign-verify-messages +Description: Sign and verify messages with Solana gasless accounts. -This guide explains how to sign arbitrary messages with an owned account and verify signatures using a read-only account. +Owned gasless Solana accounts delegate message signing and verification to the wrapped Solana account. ## Sign a Message -Use [`account.sign()`](/sdk/wallet-modules/wallet-evm/api-reference#signmessage) to produce a cryptographic signature for any string message. +Use `sign(message)` to create an Ed25519 signature: -```javascript title="Sign a Message" -const message = 'Hello, Ethereum!' +```javascript title="Sign message" +const message = 'Hello, Solana!' const signature = await account.sign(message) + console.log('Signature:', signature) ``` ## Verify a Signature -You can get a [read-only account](/sdk/wallet-modules/wallet-evm/api-reference#walletaccountreadonlyevm) from any `Account` object by calling [`account.toReadOnlyAccount()`](/sdk/wallet-modules/wallet-evm/api-reference#toreadonlyaccount). Use a read-only account to [`verify()`](/sdk/wallet-modules/wallet-evm/api-reference#verifymessage-signature-1) that a signature was produced by the corresponding private key. +Use `verify(message, signature)` to check whether a signature matches the account address: -```javascript title="Verify a Signature" -const readOnlyAccount = await account.toReadOnlyAccount() -const isValid = await readOnlyAccount.verify(message, signature) +```javascript title="Verify signature" +const isValid = await account.verify(message, signature) console.log('Signature valid:', isValid) ``` - -You can also create a [`WalletAccountReadOnlyEvm`](/sdk/wallet-modules/wallet-evm/api-reference) from any public address to verify signatures without access to the private key. - +## Verify from a Read-Only Account + +Read-only accounts can verify signatures without access to the private key: + +```javascript title="Verify with read-only account" +import { WalletAccountReadOnlySolanaGasless } from '@tetherto/wdk-wallet-solana-gasless' + +const readOnlyAccount = new WalletAccountReadOnlySolanaGasless('SolanaAddress...', { + provider: 'https://api.devnet.solana.com', + paymasterUrl: 'https://your-kora-paymaster.example', + paymasterAddress: 'Paymaster111111111111111111111111111111111', + paymasterToken: { + address: 'TokenMint111111111111111111111111111111111' + } +}) + +const isValid = await readOnlyAccount.verify(message, signature) +``` ## Next Steps -For best practices on handling errors, managing fees, and cleaning up memory, see [Error Handling](/sdk/wallet-modules/wallet-evm/guides/error-handling). +For paymaster and cleanup failures, see [Handle Errors](/sdk/wallet-modules/wallet-solana-gasless/guides/handle-errors). *** -## Transfer ERC-20 Tokens -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/guides/transfer-tokens -Description: Transfer ERC-20 tokens and estimate transfer fees on EVM chains. +## Transfer SPL Tokens +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/guides/transfer-tokens +Description: Transfer SPL tokens through a Kora-compatible paymaster. -This guide explains how to transfer ERC-20 tokens (such as USD₮ or XAU₮), estimate fees, and validate inputs before executing. +This guide explains how to quote and transfer SPL tokens with paymaster-funded fees. ## Transfer Tokens -Use [`account.transfer()`](/sdk/wallet-modules/wallet-evm/api-reference#transferoptions) to send ERC-20 tokens to a recipient address. +Use `transfer(options)` to send SPL tokens through the configured paymaster: -```javascript title="Transfer ERC-20 Tokens" -const transferResult = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000000000000000n // 1 token in base units +```javascript title="Transfer SPL tokens" +const result = await account.transfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n }) -console.log('Transfer hash:', transferResult.hash) -console.log('Transfer fee:', transferResult.fee, 'wei') + +console.log('Transaction hash:', result.hash) +console.log('Paymaster fee:', result.fee) ``` -## Estimate Transfer Fees +If the recipient associated token account does not exist, the module adds an idempotent create-associated-token-account instruction with the paymaster as payer. -Use [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm/api-reference#quotetransferoptions) to get a fee estimate before executing the transfer. +## Quote Transfer Fees -```javascript title="Quote Token Transfer" -const transferQuote = await account.quoteTransfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000000000000000n +Use `quoteTransfer()` to estimate the paymaster fee before submitting: + +```javascript title="Quote SPL transfer" +const quote = await account.quoteTransfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n }) -console.log('Transfer fee estimate:', transferQuote.fee, 'wei') -``` -## Transfer with Validation +console.log('Estimated paymaster fee:', quote.fee) +``` -You can use `transferTokenWithValidation()` to validate addresses and check balances before transferring to catch errors early. +The returned `fee` is in the configured paymaster token's base units. -### 1. Validate Addresses +## Set a Transfer Fee Cap -```javascript title="Address Validation" -if (!tokenAddress.startsWith('0x') || tokenAddress.length !== 42) { - throw new Error('Invalid token address') -} +Set `transferMaxFee` in the wallet config or as a per-call override: -if (!recipient.startsWith('0x') || recipient.length !== 42) { - throw new Error('Invalid recipient address') -} +```javascript title="Transfer with fee cap" +const result = await account.transfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n +}, { + transferMaxFee: 500000n +}) ``` -### 2. Check Balances +The module throws when the quoted transfer fee is greater than `transferMaxFee`. -Use [`account.getTokenBalance()`](/sdk/wallet-modules/wallet-evm/api-reference#gettokenbalancetokenaddress) and [`account.getBalance()`](/sdk/wallet-modules/wallet-evm/api-reference#getbalance) to verify sufficient funds: +## Override Paymaster Token -```javascript title="Balance Check" -const balance = await account.getTokenBalance(tokenAddress) -if (balance < amount) { - throw new Error('Insufficient token balance') -} +Use a different paymaster token for one transfer: -const nativeBalance = await account.getBalance() -if (nativeBalance === 0n) { - throw new Error('Need ETH for gas fees') -} +```javascript title="Transfer with alternate fee token" +const result = await account.transfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n +}, { + paymasterToken: { + address: 'AlternateFeeMint11111111111111111111111111' + }, + transferMaxFee: 500000n +}) ``` -### 3. Quote and Execute Transfer +## Validate Before Sending -Use [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm/api-reference#quotetransferoptions) to estimate fees, then [`account.transfer()`](/sdk/wallet-modules/wallet-evm/api-reference#transferoptions) to execute: +Check token balances and quote fees before sending: -```javascript title="Quote and Execute" -const quote = await account.quoteTransfer({ - token: tokenAddress, - recipient, - amount -}) -console.log('Transfer fee estimate:', quote.fee, 'wei') +```javascript title="Validated SPL transfer" +async function transferWithValidation(account, token, recipient, amount) { + const balance = await account.getTokenBalance(token) + if (balance < amount) { + throw new Error('Insufficient SPL token balance') + } -const result = await account.transfer({ - token: tokenAddress, - recipient, - amount -}) -console.log('Transfer completed:', result.hash) -console.log('Fee paid:', result.fee, 'wei') + const quote = await account.quoteTransfer({ token, recipient, amount }) + console.log('Paymaster fee estimate:', quote.fee) + + return await account.transfer({ token, recipient, amount }) +} ``` +## Limitations + +- `amount` must fit in an unsigned 64-bit integer. +- JavaScript `number` amounts must fit within `Number.MAX_SAFE_INTEGER`; use `bigint` for large token amounts. +- Token-2022 extensions and token-transfer memos are not supported by this module. + ## Next Steps -Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-evm/guides/sign-verify-messages) with your EVM account. +Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-solana-gasless/guides/sign-verify-messages). *** ## Usage -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/usage -Description: Guide to using the @tetherto/wdk-wallet-evm module. +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana-gasless/usage +Description: Guide to using the @tetherto/wdk-wallet-solana-gasless module. -The `@tetherto/wdk-wallet-evm` module provides wallet management for Ethereum and EVM-compatible blockchains. +The `@tetherto/wdk-wallet-solana-gasless` module provides Solana wallet management for paymaster-funded transaction flows. - -Install the package and create your first wallet. - - -Work with multiple accounts and custom derivation paths. - - -Query native and ERC-20 token balances. + +Install the package and create your first gasless Solana account. - -Send native tokens with EIP-1559 or legacy gas settings. - - -Transfer ERC-20 tokens and estimate fees. - - -Sign messages and verify signatures. + +Work with derived accounts and custom Solana derivation paths. - -Handle errors, manage fees, and dispose of sensitive data. + +Query SOL, SPL token, and paymaster token balances. - - - - -Get started with WDK in a Node.js environment + +Quote, sign, and send native SOL or custom transaction messages. - -Build mobile wallets with React Native Expo + +Transfer SPL tokens with paymaster-funded fees. - -Get started with WDK's EVM Wallet Configuration + +Sign messages and verify Ed25519 signatures. - -Get started with WDK's EVM Wallet API + +Handle paymaster, fee, transaction, and cleanup failures. ---- - -### Need Help? - - - -*** - -## Wallet Solana Overview -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana -Description: Overview of the @tetherto/wdk-wallet-solana module - -A simple and secure package to manage SLIP-0010 wallets for the Solana blockchain. This package provides a clean API for creating, managing, and interacting with Solana wallets using BIP-39 seed phrases and Solana-specific derivation paths. - - -**Default Derivation Path Change in v1.0.0-beta.4+** - -The default derivation path was updated in v1.0.0-beta.4 to match ecosystem conventions: - -- **Before** (up to v1.0.0-beta.3): `m/44'/501'/0'/0/{index}` -- **After** (v1.0.0-beta.4+): `m/44'/501'/{index}'/0'` - -If you're upgrading from an earlier version, existing wallets created with the old path will generate different addresses. Make sure to migrate any existing wallets or use the old path explicitly if needed for compatibility. - -Use [`getAccountByPath`](/sdk/wallet-modules/wallet-solana/api-reference#getaccountbypathpath) to supply an explicit derivation path when importing or recreating legacy wallets. On Solana, every child segment in a custom path must be hardened. - - -## Features - -- **BIP-39 Seed Phrase Support**: Generate and validate BIP-39 mnemonic seed phrases -- **Solana Derivation Paths**: Support for SLIP-0010 derivation paths for Solana (m/44'/501') -- **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase -- **Solana Address Support**: Generate and manage Solana public keys and addresses -- **Message Signing**: Sign and verify messages using Ed25519 cryptography -- **Transaction Management**: Sign, send, and quote Solana transactions -- **SPL Token Support**: Query native SOL plus single or batch SPL token balances -- **TypeScript Support**: Full TypeScript definitions included -- **Memory Safety**: Secure private key management with memory-safe implementation -- **Provider Flexibility**: Support for a single Solana RPC endpoint, plus runtime failover support for ordered `provider` lists -- **Transaction Message Support**: Quote or send prebuilt `TransactionMessage` flows with recent blockhash or durable nonce lifetimes -- **Fee Estimation**: Dynamic fee calculation with recent blockhash -- **Program Interaction**: Support for interacting with Solana programs - -## Supported Networks - -This package works with the Solana blockchain, including: - -- **Solana Mainnet** -- **Solana Devnet** -- **Solana Testnet** -- **Localnet** - -## Next Steps - -Get started with WDK in a Node.js environment - - -Get started with WDK's Solana Wallet configuration +Get started with WDK in a Node.js environment. - -Get started with WDK's Solana Wallet API + +Review required paymaster and Solana RPC options. - -Get started with WDK's with Solana Wallet usage + +Review exported classes, methods, and configuration types. *** -### Need Help? +## Need Help? @@ -18958,14 +28437,16 @@ new WalletManagerSolana(seed, config) - `rpcUrl` (string | string[], optional): Deprecated alias for `provider`. If both are set, `provider` takes precedence - `commitment` (string, optional): Commitment level ('processed', 'confirmed', or 'finalized') - `retries` (number, optional): Additional retry attempts for ordered provider failover (default: 3) - - `transferMaxFee` (number, optional): Maximum fee amount for transfer operations (in lamports) + - `transferMaxFee` (number | bigint, optional): Maximum fee amount for SPL token transfer operations (in lamports) + - `transactionMaxFee` (number | bigint, optional): Maximum fee amount for native SOL send/sign operations (in lamports) **Example:** ```javascript const wallet = new WalletManagerSolana(seedPhrase, { provider: 'https://api.mainnet-beta.solana.com', commitment: 'confirmed', - transferMaxFee: 5000 // Maximum fee in lamports + transferMaxFee: 5000, // Maximum SPL transfer fee in lamports + transactionMaxFee: 5000 // Maximum native send/sign fee in lamports }) ``` @@ -19053,8 +28534,8 @@ In v1.0.0-beta.9 the constructor was made public. The static factory method `Wal | `sign(message)` | Signs a message using the account's private key | `Promise` | | `signTransaction(tx)` | Signs a Solana transaction without broadcasting it | `Promise` | | `verify(message, signature)` | Verifies a message signature | `Promise` | -| `sendTransaction(tx)` | Sends a Solana transaction | `Promise<{hash: string, fee: bigint}>` | -| `quoteSendTransaction(tx)` | Estimates the fee for a transaction | `Promise<{fee: bigint}>` | +| `sendTransaction(tx)` | Builds and sends a transaction, or broadcasts a fully signed transaction | `Promise<{hash: string, fee: bigint}>` | +| `quoteSendTransaction(tx)` | Estimates the fee for a transaction or fully signed transaction | `Promise<{fee: bigint}>` | | `transfer(options)` | Transfers SPL tokens to another address | `Promise<{hash: string, fee: bigint}>` | | `quoteTransfer(options)` | Estimates the fee for an SPL token transfer | `Promise<{fee: bigint}>` | | `getBalance()` | Returns the native SOL balance (in lamports) | `Promise` | @@ -19099,7 +28580,7 @@ When `tx` is a `TransactionMessage`, WDK preserves an existing recent blockhash **Returns:** `Promise` - The signed transaction -**Throws:** Error if wallet is not connected to a provider +**Throws:** Error if wallet is not connected to a provider or if the estimated fee is greater than `transactionMaxFee` **Example:** ```javascript @@ -19129,15 +28610,17 @@ console.log('Signature valid:', isValid) Sends a Solana transaction. **Parameters:** -- `tx` (SolanaTransaction): A simple transfer object or a prebuilt `TransactionMessage` +- `tx` (SolanaTransaction | FullySignedTransaction): A simple transfer object, prebuilt `TransactionMessage`, or fully signed transaction - `to` (string): Recipient's Solana address (base58-encoded) - `value` (number | bigint): Amount in lamports When `tx` is a `TransactionMessage`, WDK preserves an existing recent blockhash or durable nonce lifetime. If no lifetime is present, WDK fetches the latest blockhash before quoting or sending. If you set an explicit `feePayer`, it must match the wallet address. +When `tx` is a `FullySignedTransaction`, WDK quotes it again, enforces `transactionMaxFee`, and broadcasts its exact wire bytes. It does not refresh the recent blockhash or durable nonce and does not re-sign the transaction. + **Returns:** `Promise<{hash: string, fee: bigint}>` - Object containing transaction hash and fee (in lamports) -**Throws:** Error if wallet is not connected to a provider +**Throws:** Error if wallet is not connected to a provider or if the estimated fee is greater than `transactionMaxFee` **Example:** ```javascript @@ -19152,7 +28635,7 @@ console.log('Transaction fee:', result.fee, 'lamports') Estimates the fee for a Solana transaction. **Parameters:** -- `tx` (SolanaTransaction): The transaction object (same as `sendTransaction`) +- `tx` (SolanaTransaction | FullySignedTransaction): A transaction input or fully signed transaction (same forms as `sendTransaction`) **Returns:** `Promise<{fee: bigint}>` - Object containing fee estimate (in lamports) @@ -19286,9 +28769,9 @@ account.dispose() |----------|------|-------------| | `index` | `number` | The derivation path's index of this account | | `path` | `string` | The full derivation path of this account | -| `keyPair` | `{publicKey: Uint8Array, privateKey: Uint8Array \| null}` | The account's Ed25519 key pair. `privateKey` is `null` after `dispose()` is called. | +| `keyPair` | `{publicKey: Uint8Array, privateKey: Uint8Array \| null}` | The account's Ed25519 key pair. The returned arrays are bound to the account and should be treated as read-only. `privateKey` is `null` after `dispose()` is called. | -⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, or expose the private key. +⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, mutate, or expose the private key. ### WalletAccountReadOnlySolana @@ -19435,9 +28918,20 @@ interface SolanaWalletConfig { commitment?: 'processed' | 'confirmed' | 'finalized'; retries?: number; transferMaxFee?: number | bigint; + transactionMaxFee?: number | bigint; } ``` +### FullySignedTransaction + +`signTransaction()` returns the `FullySignedTransaction` type defined by `@solana/transactions`. The WDK package does not re-export this type; import it from the Solana package when an explicit annotation is needed. + +```typescript +import type { FullySignedTransaction } from '@solana/transactions' +``` + +The signed value contains the compiled message bytes and all required signatures. Its transaction lifetime is sealed at signing time. + ### TransferOptions ```typescript @@ -19453,7 +28947,7 @@ interface TransferOptions { ```typescript interface KeyPair { publicKey: Uint8Array - privateKey: Uint8Array + privateKey: Uint8Array | null } ``` @@ -19493,7 +28987,8 @@ import WalletManagerSolana from '@tetherto/wdk-wallet-solana' const config = { provider: 'https://api.mainnet-beta.solana.com', // Recommended: Solana RPC endpoint - transferMaxFee: 10000000 // Optional: Maximum fee in lamports + transferMaxFee: 10000000, // Optional: Maximum SPL transfer fee in lamports + transactionMaxFee: 10000000 // Optional: Maximum native send/sign fee in lamports } const wallet = new WalletManagerSolana(seedPhrase, config) @@ -19508,7 +29003,8 @@ import WalletManagerSolana from '@tetherto/wdk-wallet-solana' const accountConfig = { provider: 'https://api.mainnet-beta.solana.com', - transferMaxFee: 10000000 // Optional: Maximum fee in lamports + transferMaxFee: 10000000, // Optional: Maximum SPL transfer fee in lamports + transactionMaxFee: 10000000 // Optional: Maximum native send/sign fee in lamports } const wallet = new WalletManagerSolana(seedPhrase, accountConfig) @@ -19585,9 +29081,9 @@ const config = { ### transferMaxFee -The `transferMaxFee` option sets the maximum allowed fee (in lamports) for transfer operations. This helps prevent unexpectedly high transaction fees. +The `transferMaxFee` option sets the maximum allowed fee, in lamports, for SPL token `transfer()` operations. This helps prevent unexpectedly high token-transfer fees. -**Type:** `number` (optional) +**Type:** `number | bigint` (optional) **Unit:** Lamports (1 SOL = 1,000,000,000 lamports) @@ -19598,6 +29094,23 @@ const config = { } ``` +### transactionMaxFee + +The `transactionMaxFee` option sets the maximum allowed fee, in lamports, for native SOL `sendTransaction()` and `signTransaction()` operations. This is separate from `transferMaxFee`, which applies to SPL token transfers. + +**Type:** `number | bigint` (optional) + +**Unit:** Lamports (1 SOL = 1,000,000,000 lamports) + +WDK rejects native send/sign operations when the estimated fee is greater than `transactionMaxFee`. A fee equal to the configured cap is allowed. + +**Example:** +```javascript +const config = { + transactionMaxFee: 10000000 // 0.01 SOL in lamports +} +``` + ## Complete Configuration Example ```javascript @@ -19608,7 +29121,8 @@ const config = { provider: 'https://api.mainnet-beta.solana.com', // Optional: Fee protection - transferMaxFee: 10000000 // 0.01 SOL maximum fee + transferMaxFee: 10000000, // 0.01 SOL maximum SPL transfer fee + transactionMaxFee: 10000000 // 0.01 SOL maximum native send/sign fee } const wallet = new WalletManagerSolana(seedPhrase, config) @@ -19652,7 +29166,7 @@ Use [`getAccountByPath`](/sdk/wallet-modules/wallet-solana/api-reference#getacco - Always use HTTPS URLs for RPC endpoints - Use RPC endpoints that serve the same Solana network when enabling failover -- Set appropriate `transferMaxFee` limits for your use case +- Set appropriate `transferMaxFee` and `transactionMaxFee` limits for your use case - Consider using environment variables for configuration in production - Use trusted RPC providers or run your own Solana validator for production applications @@ -19895,8 +29409,8 @@ async function safeTransfer(account, wallet) { console.error('Please add more SOL to your wallet') } else if (error.message.includes('invalid address')) { console.error('The recipient address is invalid') - } else if (error.message.includes('max fee')) { - console.error('The transfer fee exceeds your configured maximum') + } else if (error.message.toLowerCase().includes('fee')) { + console.error('The transaction fee exceeds your configured maximum') } else { console.error('Transaction failed:', error.message) } @@ -19918,12 +29432,18 @@ Durable nonce flows still need a valid nonce account and signer setup in the mes ## Manage Fee Limits -Set `transferMaxFee` when creating the wallet to prevent transactions from exceeding a maximum cost. Retrieve current network rates with [`getFeeRates()`](/sdk/wallet-modules/wallet-solana/api-reference) to make informed decisions. +Set `transactionMaxFee` when creating the wallet to cap native SOL `sendTransaction()` and `signTransaction()` costs. Set `transferMaxFee` separately for SPL token `transfer()` costs. Fee caps reject estimates greater than the configured limit, so an estimate equal to the cap is allowed. Retrieve current network rates with [`getFeeRates()`](/sdk/wallet-modules/wallet-solana/api-reference#getfeerates) to make informed decisions. ```javascript title="Fee Management" const feeRates = await wallet.getFeeRates() console.log('Normal fee rate:', feeRates.normal, 'lamports') console.log('Fast fee rate:', feeRates.fast, 'lamports') + +const walletWithCaps = new WalletManagerSolana(seedPhrase, { + provider: 'https://api.mainnet-beta.solana.com', + transactionMaxFee: 10000000n, + transferMaxFee: 10000000n +}) ``` ## Dispose of Sensitive Data @@ -20015,7 +29535,7 @@ With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modu URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana/guides/send-transactions Description: Send native SOL and estimate transaction fees on Solana. -This guide explains how to [send native SOL](#send-native-sol), [sign a transaction without broadcasting](#sign-a-transaction-without-broadcasting), [estimate transaction fees](#estimate-transaction-fees), [quote or send a TransactionMessage](#quote-or-send-a-transactionmessage), [use dynamic fee rates](#use-dynamic-fee-rates), and [run a complete SOL transfer flow](#complete-example). +This guide explains how to [send native SOL](#send-native-sol), [sign a transaction without broadcasting](#sign-a-transaction-without-broadcasting), [quote and send a signed transaction](#quote-and-send-a-signed-transaction), [estimate transaction fees](#estimate-transaction-fees), [cap native transaction fees](#cap-native-transaction-fees), [quote or send a TransactionMessage](#quote-or-send-a-transactionmessage), [use dynamic fee rates](#use-dynamic-fee-rates), and [run a complete SOL transfer flow](#complete-example). **BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. @@ -20050,6 +29570,27 @@ const signedTransaction = await account.signTransaction({ console.log('Signed transaction:', signedTransaction) ``` +## Quote and Send a Signed Transaction + +Pass the `FullySignedTransaction` returned by `signTransaction()` to the quote and send methods when review and submission are separate steps. + +```javascript title="Quote and Send Signed Bytes" +const signedTransaction = await account.signTransaction({ + to: '11111111111111111111111111111112', + value: 1000000000n +}) + +const quote = await account.quoteSendTransaction(signedTransaction) +console.log('Estimated fee:', quote.fee, 'lamports') + +const result = await account.sendTransaction(signedTransaction) +console.log('Transaction signature:', result.hash) +``` + + +Signing seals the recent blockhash or durable nonce into the message. WDK broadcasts the signed bytes unchanged and does not refresh the transaction lifetime or re-sign it. Submit the transaction before that lifetime becomes invalid. `sendTransaction()` quotes it again and enforces `transactionMaxFee`. + + ## Estimate Transaction Fees Use [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-solana/api-reference#quotesendtransactiontx) to get a fee estimate before sending. @@ -20062,6 +29603,17 @@ const quote = await account.quoteSendTransaction({ console.log('Estimated fee:', quote.fee, 'lamports') ``` +## Cap Native Transaction Fees + +Set [`transactionMaxFee`](/sdk/wallet-modules/wallet-solana/configuration#transactionmaxfee) when you create the wallet to stop native `sendTransaction()` and `signTransaction()` calls if the estimated fee is greater than your limit. A fee equal to the configured cap is allowed. Use `transferMaxFee` separately for SPL token transfers. + +```javascript title="Set a Native Transaction Fee Cap" +const wallet = new WalletManagerSolana(seedPhrase, { + provider: 'https://api.mainnet-beta.solana.com', + transactionMaxFee: 10000000n // 0.01 SOL in lamports +}) +``` + ## Quote or Send a TransactionMessage Use a prebuilt `TransactionMessage` when you need custom instructions or a durable nonce flow. @@ -20299,13 +29851,11 @@ Get started with WDK's Solana Wallet API *** -## Wallet Spark Overview +## Lightning (Spark) wallet URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-spark -Description: Overview of the @tetherto/wdk-wallet-spark module +Description: Create Spark wallets for Lightning payments, Spark transfers, deposits, withdrawals, and token balances. -# @tetherto/wdk-wallet-spark Overview - -A simple and secure package to manage BIP-32 wallets for the Spark blockchain. This package provides a clean API for creating, managing, and interacting with Spark wallets using [BIP-39 seed phrases](/resources/concepts#bip-39-mnemonic-seed-phrases), [BIP-44 derivation paths](/resources/concepts#bip-44-multi-account-hierarchy), and the Spark SDK for Bitcoin [layer 2](/resources/concepts#layer-2-solutions) functionality including [Lightning Network](/resources/concepts#lightning-network) integration. +Use the Spark wallet module for Spark network wallets, Lightning invoices, Spark invoices, token transfers, and Bitcoin layer 1 deposits or withdrawals. ## Features @@ -20314,6 +29864,7 @@ A simple and secure package to manage BIP-32 wallets for the Spark blockchain. T - **Spark Invoices**: Create and pay Spark invoices for receiving sats and tokens directly on the Spark network - **SparkScan Balance Polling**: Use SparkScan-backed balance polling in [`getBalance()`](/sdk/wallet-modules/wallet-spark/api-reference) when `sparkscan` is configured - **Sync and Retry**: Optionally sync wallet state and retry failed [`sendTransaction()`](/sdk/wallet-modules/wallet-spark/api-reference) and [`payLightningInvoice()`](/sdk/wallet-modules/wallet-spark/api-reference) calls once +- **Spark SDK Logging**: Enable Spark SDK logs with the `enableLogging` configuration option when debugging wallet setup or runtime behavior - **Token Transfers**: Transfer tokens to other Spark addresses - **Bitcoin Layer 1 Bridge**: Deposit and withdraw Bitcoin between layer 1 and Spark - **Static Deposit Addresses**: Reusable deposit addresses for Bitcoin layer 1 deposits @@ -20397,6 +29948,7 @@ new WalletManagerSpark(seed, config) - `network` (string, optional): 'MAINNET', 'SIGNET', or 'REGTEST' (default: 'MAINNET') - `sparkscan` (`SparkScanConfig`, optional): SparkScan configuration for balance polling - `syncAndRetry` (boolean, optional): When true, failed sends and Lightning payments sync wallet state and retry once + - `enableLogging` (boolean, optional): When true, forwards logging to the underlying Spark SDK (default: false) ### Methods @@ -21609,7 +31161,8 @@ const config = { sparkscan: { apiKey: 'your-api-key-here' }, - syncAndRetry: true + syncAndRetry: true, + enableLogging: false } const wallet = new WalletManagerSpark(seedPhrase, config) @@ -21686,6 +31239,22 @@ const config = { You can also call [`syncWalletBalance()`](/sdk/wallet-modules/wallet-spark/api-reference) directly when you want to reconcile wallet state before retrying an operation. +### Spark SDK Logging + +The `enableLogging` option forwards logging to the underlying Spark SDK. + +**Type:** `boolean` (optional) + +**Default:** `false` + +**Example:** +```javascript +const config = { + network: 'REGTEST', + enableLogging: true +} +``` + ## Network Configuration The wallet can be configured for different Spark networks: @@ -21731,7 +31300,8 @@ const wallet = new WalletManagerSpark(seedPhrase, { sparkscan: { apiKey: 'your-api-key-here' }, - syncAndRetry: true + syncAndRetry: true, + enableLogging: false }) // Get accounts (no additional configuration needed) @@ -22460,11 +32030,11 @@ Get started with WDK's Spark Wallet API *** -## Wallet TON Overview +## Standard TON wallet URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton -Description: Overview of the @tetherto/wdk-wallet-ton module +Description: Create and manage TON wallets with TON transfers, Jetton balances, message signing, and configurable TON providers. -A simple and secure package to manage BIP-44 wallets for the TON blockchain. This package provides a clean API for creating, managing, and interacting with TON wallets using BIP-39 seed phrases and TON-specific derivation paths. +Use the TON wallet module for standard TON accounts where users can pay network fees with TON. **Default Derivation Path Change in v1.0.0-beta.6+** @@ -22487,9 +32057,10 @@ Use [`getAccountByPath`](/sdk/wallet-modules/wallet-ton/api-reference) to supply - **TON Address Support**: Generate and manage TON addresses using V5R1 wallet contracts - **Message Signing**: Sign and verify messages using TON cryptography - **Transaction Management**: Send transactions and get fee estimates +- **Signed Body Submission**: Quote and send the signed transfer-body `Cell` returned by `signTransaction()` - **Jetton Support**: Query native TON and Jetton token balances - **TypeScript Support**: Full TypeScript definitions included -- **Memory Safety**: Secure private key management with automatic memory cleanup using sodium-universal +- **Derived Key Cleanup**: Account disposal zeroes derived private key bytes with sodium-universal - **Provider Flexibility**: Support for custom TON RPC endpoints and TON Center API ## Supported Networks @@ -22524,11 +32095,11 @@ Get started with WDK's TON Wallet usage *** -## Wallet TON Gasless Overview +## Gasless TON wallet URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless -Description: Overview of the @tetherto/wdk-wallet-ton-gasless module +Description: Create TON wallets that support gasless Jetton transfers through paymaster-backed flows. -A simple and secure package to manage gasless transactions on the TON blockchain. This package provides a clean API for creating, managing, and interacting with TON wallets using BIP-39 seed phrases and TON-specific derivation paths, with support for gasless transactions through a paymaster system. +Use the gasless TON wallet module when your app needs Jetton transfers without requiring users to hold TON for fees. ## Features @@ -22537,11 +32108,11 @@ A simple and secure package to manage gasless transactions on the TON blockchain - **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase - **TON Address Support**: Generate and manage TON addresses using V5R1 wallet contracts - **Message Signing**: Sign and verify messages using TON cryptography -- **Gasless Transactions**: Execute transactions without requiring TON for gas fees +- **Gasless Jetton Transfers**: Transfer Jettons with fees paid in a supported paymaster Jetton - **Paymaster Integration**: Built-in support for paymaster-based fee delegation - **Jetton Support**: Query native TON and Jetton token balances - **TypeScript Support**: Full TypeScript definitions included -- **Memory Safety**: Secure private key management with automatic memory cleanup using sodium-universal +- **Derived Key Cleanup**: Account disposal zeroes derived private key bytes with sodium-universal - **Provider Flexibility**: Support for both TON Center and TON API endpoints ## Supported Networks @@ -22564,7 +32135,7 @@ Get started with WDK's TON Gasless Wallet configuration Get started with WDK's TON Gasless Wallet API -Get started with WDK's with TON Gasless Wallet usage +Get started with WDK's TON Gasless Wallet usage @@ -22602,25 +32173,26 @@ new WalletManagerTonGasless(seed, config) - `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes - `config` (TonGaslessWalletConfig): Configuration object - `tonClient` (object | TonClient | array): TON client configuration, instance, or an array of configurations or instances for failover - - `url` (string): TON Center API URL (e.g., 'https://toncenter.com/api/v3') + - `url` (string): TON Center v2 JSON-RPC URL (e.g., 'https://toncenter.com/api/v2/jsonRPC') - `secretKey` (string, optional): API key for TON Center - `tonApiClient` (object | TonApiClient | array): TON API client configuration, instance, or an array of configurations or instances for failover - - `url` (string): TON API URL (e.g., 'https://tonapi.io/v2') + - `url` (string): TON API base URL (e.g., 'https://tonapi.io') - `secretKey` (string, optional): API key for TON API - `paymasterToken` (object): Paymaster token configuration - - `address` (string): Paymaster Jetton master contract address + - `address` (string): Supported paymaster Jetton master contract address - `retries` (number, optional): Failover retries used when `tonClient` and `tonApiClient` are arrays (default: 3) - - `transferMaxFee` (number | bigint, optional): Maximum fee for transfer operations + - `transferMaxFee` (number | bigint, optional): Maximum fee for gasless transfer operations + - `transactionMaxFee` (number | bigint, optional): Shared wallet config option; native `sendTransaction()`, `quoteSendTransaction()`, and `signTransaction()` are unsupported on this module **Example:** ```javascript const wallet = new WalletManagerTonGasless(seedPhrase, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, tonApiClient: { - url: 'https://tonapi.io/v2', + url: 'https://tonapi.io', secretKey: 'your-tonapi-key' }, paymasterToken: { @@ -22637,8 +32209,8 @@ const wallet = new WalletManagerTonGasless(seedPhrase, { |--------|-------------|---------| | `getAccount(index)` | Returns a gasless wallet account at the specified index | `Promise\` | | `getAccountByPath(path)` | Returns a gasless wallet account at the specified BIP-44 derivation path | `Promise\` | -| `getFeeRates()` | Returns current fee rates for transactions | `Promise\<{normal: number, fast: number}\>` | -| `dispose()` | Disposes all wallet accounts, clearing private keys from memory | `void` | +| `getFeeRates()` | Returns fee rates from the mainnet TON API configuration | `Promise\<{normal: bigint, fast: bigint}\>` | +| `dispose()` | Disposes cached accounts and signers; the manager seed remains in memory | `void` | ##### `getAccount(index)` Returns a gasless wallet account at the specified index. Index `n` derives the account at `m/44'/607'/n'`. @@ -22669,9 +32241,9 @@ const account = await wallet.getAccountByPath("1'") ``` ##### `getFeeRates()` -Returns current fee rates for transactions based on blockchain config. +Returns fee rates from the mainnet TON API configuration. Through `1.0.0-beta.8`, this method always requests `https://tonapi.io/v2`, does not follow the configured client network, and returns the same calculated value for both fields. -**Returns:** `Promise\<{normal: number, fast: number}\>` - Object containing fee rates +**Returns:** `Promise\<{normal: bigint, fast: bigint}\>` - Object containing fee rates **Example:** ```javascript @@ -22681,7 +32253,7 @@ console.log('Fast fee rate:', feeRates.fast) ``` ##### `dispose()` -Disposes all wallet accounts, clearing private keys from memory. +Disposes cached wallet accounts and signers, clearing their derived private keys. In the current beta, this method does not zero or unset the wallet manager's seed bytes. **Example:** ```javascript @@ -22710,10 +32282,11 @@ new WalletAccountTonGasless(seed, path, config) | `getAddress()` | Returns the account's TON address | `Promise\` | | `sign(message)` | Signs a message using the account's private key | `Promise\` | | `signTransaction(tx)` | Not supported on gasless; always throws | `Promise\` | -| `sendTransaction(tx)` | Not supported on gasless; always throws | `Promise\` | +| `sendTransaction(tx)` | Not supported on gasless; always rejects | `Promise\` | +| `quoteSendTransaction(tx)` | Not supported on gasless; always rejects | `Promise\\>` | | `verify(message, signature)` | Verifies a message signature | `Promise\` | -| `transfer(options, config?)` | Transfers tokens using gasless transactions | `Promise\<{hash: string, fee: number}\>` | -| `quoteTransfer(options, config?)` | Estimates the fee for a token transfer | `Promise\<{fee: number}\>` | +| `transfer(options, config?)` | Transfers tokens using gasless transactions | `Promise\<{hash: string, fee: bigint}\>` | +| `quoteTransfer(options, config?)` | Estimates the fee for a token transfer | `Promise\<{fee: bigint}\>` | | `getBalance()` | Returns the native TON balance (in nanotons) | `Promise\` | | `getTokenBalance(tokenAddress)` | Returns the balance of a specific token | `Promise\` | | `getPaymasterTokenBalance()` | Returns the balance of the paymaster token | `Promise\` | @@ -22746,7 +32319,7 @@ console.log('Signature:', signature) ``` ##### `signTransaction(tx)` -Not supported on the gasless module. This method always throws. The gasless module only supports paymaster-funded Jetton transfers through [`transfer()`](#transferoptions-config). Use the standard `@tetherto/wdk-wallet-ton` module for offline transaction signing. +Not supported on the gasless module. This method always throws. The gasless module only supports paymaster-funded Jetton transfers through [`transfer()`](#transferoptions-config). Use the standard `@tetherto/wdk-wallet-ton` module to build signed native transaction bodies. **Parameters:** - `tx` (TonTransaction): The transaction @@ -22760,12 +32333,12 @@ await account.signTransaction({ to: 'EQ...', value: 1000000000n }) ``` ##### `sendTransaction(tx)` -Not supported on the gasless module. This method always throws. To move funds, use [`transfer()`](#transferoptions-config), which relays a paymaster-funded Jetton transfer. +Not supported on the gasless module. This method is typed as `Promise` for wallet-interface compatibility, but it always rejects at runtime. To move funds, use [`transfer()`](#transferoptions-config), which relays a paymaster-funded Jetton transfer. **Parameters:** - `tx` (TonTransaction): The transaction -**Returns:** `Promise\` - Never resolves; always throws +**Returns:** `Promise\` - Interface-compatible return type; never resolves successfully on this module **Example:** ```javascript @@ -22773,6 +32346,20 @@ Not supported on the gasless module. This method always throws. To move funds, u await account.sendTransaction({ to: 'EQ...', value: 1000000000n }) ``` +##### `quoteSendTransaction(tx)` +Not supported on the gasless module. This method is inherited for wallet-interface compatibility and always rejects at runtime. Use [`quoteTransfer()`](#quotetransferoptions-config) to estimate gasless Jetton transfer fees. + +**Parameters:** +- `tx` (TonTransaction): The transaction + +**Returns:** `Promise\\>` - Interface-compatible return type; never resolves successfully on this module + +**Example:** +```javascript +// Throws: "Method 'quoteSendTransaction(tx)' not supported on ton gasless." +await account.quoteSendTransaction({ to: 'EQ...', value: 1000000000n }) +``` + ##### `verify(message, signature)` Verifies a message signature. @@ -22789,19 +32376,19 @@ console.log('Signature valid:', isValid) ``` ##### `transfer(options, config?)` -Transfers a Jetton using a gasless transaction, paying the fee with the configured paymaster token. This is the only way to move funds on the gasless module: `sendTransaction()` and `signTransaction()` are not supported and throw. +Transfers a Jetton using a gasless transaction, paying the fee with the configured paymaster token. This is the only way to move funds on the gasless module: `sendTransaction()`, `quoteSendTransaction()`, and `signTransaction()` are not supported and reject or throw. **Parameters:** - `options` (TransferOptions): Transfer options - `token` (string): Token contract address - `recipient` (string): Recipient TON address - - `amount` (number): Amount in token's base units -- `config` (object, optional): Override configuration - - `paymasterToken` (object, optional): Override default paymaster token + - `amount` (number | bigint): Amount in token's base units +- `config` (object, optional): Per-call configuration. When supplied, it replaces the wallet-level transfer configuration for this call. + - `paymasterToken` (object, required when `config` is supplied): Paymaster token for this transfer - `address` (string): Paymaster token address - - `transferMaxFee` (number, optional): Override maximum fee + - `transferMaxFee` (number | bigint, optional): Override maximum fee. Transfers throw only when the estimated fee is greater than this cap, so an equal estimate is allowed. -**Returns:** `Promise\<{hash: string, fee: number}\>` - Transfer result +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - The signed transfer body hash as lowercase hex and the fee in paymaster Jetton base units **Example:** ```javascript @@ -22815,18 +32402,21 @@ const result = await account.transfer({ }) ``` -##### `quoteTransfer(options)` +##### `quoteTransfer(options, config?)` Estimates the fee for a Jetton (TON token) transfer. **Parameters:** - `options` (TransferOptions): Transfer options - `token` (string): Token contract address - `recipient` (string): Recipient TON address - - `amount` (number): Amount in token's base units -- `config` (object, optional): Override configuration - - `paymasterToken` (object, optional): Override default paymaster token + - `amount` (number | bigint): Amount in token's base units +- `config` (object, optional): Per-call configuration. When supplied, it replaces the wallet-level quote configuration for this call. + - `paymasterToken` (object, required when `config` is supplied): Paymaster token for this quote + - `address` (string): Paymaster token address + +**Returns:** `Promise\<{fee: bigint}\>` - Object containing fee estimate (in paymaster token base units) -**Returns:** `Promise\<{fee: number}\>` - Object containing fee estimate (in paymaster token base units) +`quoteTransfer()` returns a fee estimate only. It does not submit a transfer or return a transaction hash. **Example:** ```javascript @@ -22920,8 +32510,8 @@ new WalletAccountReadOnlyTonGasless(publicKey, config) ``` **Parameters:** -- `publicKey` (string | Uint8Array): The account's public key -- `config` (TonGaslessWalletConfig): Configuration object +- `publicKey` (string | Uint8Array): The account's public key. String values must be hex encoded. +- `config` (object): Client, retry, and paymaster configuration. `transferMaxFee` and `transactionMaxFee` are not accepted by the read-only constructor. #### Methods @@ -22931,7 +32521,8 @@ new WalletAccountReadOnlyTonGasless(publicKey, config) | `getBalance()` | Returns the native TON balance | `Promise\` | | `getTokenBalance(tokenAddress)` | Returns the balance of a specific token | `Promise\` | | `getPaymasterTokenBalance()` | Returns the balance of the paymaster token | `Promise\` | -| `quoteTransfer(options, config?)` | Estimates the fee for a token transfer | `Promise\<{fee: number}\>` | +| `quoteSendTransaction(tx)` | Not supported on gasless; always rejects | `Promise\\>` | +| `quoteTransfer(options, config?)` | Estimates the fee for a token transfer | `Promise\<{fee: bigint}\>` | | `verify(message, signature)` | Verifies a message signature | `Promise\` | | `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise\` | @@ -22982,6 +32573,20 @@ const paymasterBalance = await readOnlyAccount.getPaymasterTokenBalance() console.log('Paymaster token balance:', paymasterBalance) ``` +##### `quoteSendTransaction(tx)` +Not supported on the gasless module. This method is present for wallet-interface compatibility and always rejects. Use [`quoteTransfer()`](#quotetransferoptions-config) to estimate gasless Jetton transfer fees. + +**Parameters:** +- `tx` (TonTransaction): The transaction + +**Returns:** `Promise\\>` - Interface-compatible return type; never resolves successfully on this module + +**Example:** +```javascript +// Throws: "Method 'quoteSendTransaction(tx)' not supported on ton gasless." +await readOnlyAccount.quoteSendTransaction({ to: 'EQ...', value: 1000000000n }) +``` + ##### `quoteTransfer(options, config?)` Estimates the fee for a token transfer. @@ -22989,11 +32594,14 @@ Estimates the fee for a token transfer. - `options` (TransferOptions): Transfer options - `token` (string): Token contract address - `recipient` (string): Recipient TON address - - `amount` (number): Amount in token's base units -- `config` (object, optional): Override configuration - - `paymasterToken` (object, optional): Override default paymaster token + - `amount` (number | bigint): Amount in token's base units +- `config` (object, optional): Per-call configuration. When supplied, it replaces the account's wallet-level quote configuration for this call. + - `paymasterToken` (object, required when `config` is supplied): Paymaster token for this quote + - `address` (string): Paymaster token address -**Returns:** `Promise\<{fee: number}\>` - Object containing fee estimate (in paymaster token base units) +**Returns:** `Promise\<{fee: bigint}\>` - Object containing fee estimate (in paymaster token base units) + +`quoteTransfer()` returns a fee estimate only. It does not submit a transfer or return a transaction hash. **Example:** ```javascript @@ -23023,8 +32631,10 @@ console.log('Signature valid:', isValid) ##### `getTransactionReceipt(hash)` Returns a transaction's receipt. +Through `1.0.0-beta.8`, the inherited initial receipt lookup always queries mainnet TON Center v3. Do not rely on this method for testnet receipts; see [Network Selection](/sdk/wallet-modules/wallet-ton-gasless/configuration#network-selection). + **Parameters:** -- `hash` (string): The transaction's hash +- `hash` (string): The signed transfer body hash returned by `transfer()` **Returns:** `Promise\` - The receipt, or null if the transaction has not been included in a block yet @@ -23044,14 +32654,14 @@ if (receipt) { ```typescript type TonClientConfig = { - /** TON Center API URL @example 'https://toncenter.com/api/v3' */ + /** TON Center v2 JSON-RPC URL @example 'https://toncenter.com/api/v2/jsonRPC' */ url: string; /** Optional API key for TON Center */ secretKey?: string; }; type TonApiClientConfig = { - /** TON API URL @example 'https://tonapi.io/v2' */ + /** TON API base URL @example 'https://tonapi.io' */ url: string; /** Optional API key for TON API */ secretKey?: string; @@ -23089,6 +32699,13 @@ type TonGaslessWalletConfig = { * Maximum fee for transfer operations (in paymaster Jetton base units) */ transferMaxFee?: number | bigint; + + /** + * Shared wallet config option. Native sendTransaction(), quoteSendTransaction(), + * and signTransaction() are unsupported on this gasless module; use transferMaxFee + * for gasless transfers. + */ + transactionMaxFee?: number | bigint; }; ``` @@ -23111,7 +32728,7 @@ interface TransferOptions { /** * Amount in token's base units */ - amount: number; + amount: number | bigint; } ``` @@ -23120,14 +32737,15 @@ interface TransferOptions { ```typescript interface TransferResult { /** - * Transaction hash + * Signed transfer body hash as a lowercase hex string; pass it to getTransactionReceipt() + * @example '7f83b1657ff1fc53b92dc18148a1d65dfa13501404a55e63ddfde593f4f5f9d8' */ hash: string; /** * Fee paid in paymaster token units */ - fee: number; + fee: bigint; } ``` @@ -23182,11 +32800,11 @@ import WalletManagerTonGasless from '@tetherto/wdk-wallet-ton-gasless' const config = { // Required parameters tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional }, tonApiClient: { - url: 'https://tonapi.io/v2', + url: 'https://tonapi.io', secretKey: 'your-ton-api-key' // Optional }, paymasterToken: { @@ -23201,6 +32819,10 @@ const config = { const wallet = new WalletManagerTonGasless(seedPhrase, config) ``` + +`tonClient.url` must be a TON Center v2 JSON-RPC endpoint because the module passes it to `@ton/ton`'s `TonClient`. Set `tonApiClient.url` to the TON API base URL without `/v2`; the generated TON API client appends `/v2/gasless/...` to the base URL. + + ## Account Configuration ```javascript @@ -23209,11 +32831,11 @@ import { WalletAccountTonGasless } from '@tetherto/wdk-wallet-ton-gasless' const accountConfig = { // Required parameters tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional }, tonApiClient: { - url: 'https://tonapi.io/v2', + url: 'https://tonapi.io', secretKey: 'your-ton-api-key' // Optional }, paymasterToken: { @@ -23232,14 +32854,14 @@ const account = new WalletAccountTonGasless(seedPhrase, "0'", accountConfig) ### tonClient -The `tonClient` option configures the TON Center API client for blockchain interactions. You can pass a single configuration object, a `TonClient` instance, or an array of configurations or instances to enable failover. +The `tonClient` option configures the TON Center v2 JSON-RPC client for blockchain interactions. You can pass a single configuration object, a `TonClient` instance, or an array of configurations or instances to enable failover. **Type:** ```typescript type TonClientConfig = { /** - * TON Center API endpoint URL - * @example 'https://toncenter.com/api/v3' + * TON Center v2 JSON-RPC endpoint URL + * @example 'https://toncenter.com/api/v2/jsonRPC' */ url: string; @@ -23256,18 +32878,18 @@ type TonClient = TonClientConfig | TonClientInstance | Array +Do not include `/v2` in `tonApiClient.url`. Use `https://tonapi.io`, not `https://tonapi.io/v2`; otherwise the generated client requests `/v2/v2/gasless/...`. + + ### paymasterToken -The `paymasterToken` option specifies the Jetton used to pay gasless transfer fees instead of native TON. +The `paymasterToken` option specifies the Jetton used to pay gasless transfer fees instead of native TON. Its address must match a `gas_jettons[].master_id` value returned by the configured TON API service's raw `/v2/gasless/config` endpoint. The generated `@ton-api/client` exposes the same values as `gasJettons[].masterId`. **Type:** ```typescript @@ -23297,6 +32923,10 @@ type PaymasterToken = { **Required:** Yes + +`paymasterToken` must be an object with an `address` field, not a raw address string. + + **Example:** ```javascript const config = { @@ -23318,12 +32948,12 @@ The `retries` option sets the number of additional failover attempts after the i ```javascript const config = { tonClient: [ - { url: 'https://toncenter.com/api/v3' }, - { url: 'https://backup.toncenter.com/api/v3', secretKey: 'your-api-key' } + { url: 'https://toncenter.com/api/v2/jsonRPC' }, + { url: 'https://your-secondary-toncenter.example/api/v2/jsonRPC', secretKey: 'your-secondary-api-key' } // Replace with a real independent provider ], tonApiClient: [ - { url: 'https://tonapi.io/v2' }, - { url: 'https://backup.tonapi.io/v2' } + { url: 'https://tonapi.io' }, + { url: 'https://your-secondary-tonapi.example' } // Replace with a real independent provider ], retries: 3 } @@ -23331,7 +32961,7 @@ const config = { ### transferMaxFee -The `transferMaxFee` option sets the maximum allowed fee in paymaster Jetton base units for transfer operations. A transfer throws if its estimated fee reaches this limit. +The `transferMaxFee` option sets the maximum allowed fee in paymaster Jetton base units for transfer operations. A transfer throws if its estimated fee is greater than this limit, so an estimate equal to the configured cap is allowed. **Type:** `number | bigint` @@ -23344,22 +32974,30 @@ const config = { } ``` +### transactionMaxFee + +`TonGaslessWalletConfig` includes `transactionMaxFee` for alignment with the shared wallet config shape. The gasless module does not support `sendTransaction()`, `quoteSendTransaction()`, or `signTransaction()`, so this option does not cap gasless Jetton transfers. Use `transferMaxFee` for `transfer()` fee enforcement, and use `quoteTransfer()` to inspect estimated fees before transferring. + +**Type:** `number | bigint` + +**Required:** No + ## Complete Configuration Example -Here's a complete configuration example with all options, including failover: +Here's a complete configuration example with required clients, failover, and gasless transfer fee protection: ```javascript const config = { // TON Client (Required) - array enables failover tonClient: [ - { url: 'https://toncenter.com/api/v3', secretKey: 'your-api-key' }, - { url: 'https://backup.toncenter.com/api/v3' } + { url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, + { url: 'https://your-secondary-toncenter.example/api/v2/jsonRPC' } // Replace with a real independent provider ], // TON API Client (Required) - array enables failover tonApiClient: [ - { url: 'https://tonapi.io/v2', secretKey: 'your-ton-api-key' }, - { url: 'https://backup.tonapi.io/v2' } + { url: 'https://tonapi.io', secretKey: 'your-ton-api-key' }, + { url: 'https://your-secondary-tonapi.example' } // Replace with a real independent provider ], // Paymaster Token (Required) @@ -23371,10 +33009,23 @@ const config = { retries: 3, // Fee Limits (Optional) - transferMaxFee: 10000000 // Maximum fee in paymaster Jetton base units + transferMaxFee: 10000000 // Maximum gasless transfer fee in paymaster Jetton base units } ``` +## Network Selection + +Use TON Center and TON API endpoints for the same network: + +- Mainnet: `https://toncenter.com/api/v2/jsonRPC` and `https://tonapi.io` +- Testnet: `https://testnet.toncenter.com/api/v2/jsonRPC` and `https://testnet.tonapi.io` + +Do not mix mainnet and testnet clients in one wallet configuration or failover list. + + +Through `@tetherto/wdk-wallet-ton-gasless` `1.0.0-beta.8`, the inherited `getTransactionReceipt()` implementation starts its lookup against a hard-coded mainnet TON Center v3 endpoint. `getFeeRates()` likewise always reads mainnet TON API configuration. Do not rely on these methods for testnet-specific receipts or fee rates. + + The default derivation path changed in `1.0.0-beta.5`. Accounts now derive at `m/44'/607'/{index}'` to align with `@tetherto/wdk-wallet-ton`. Wallets created with `1.0.0-beta.4` or earlier derived `getAccount(index)` at `m/44'/607'/0'/0/{index}`, so the same seed produces different addresses after upgrading. Migrate existing accounts using [`getAccountByPath()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#getaccountbypathpath) with the old path if you need to keep prior addresses. @@ -23384,7 +33035,8 @@ The default derivation path changed in `1.0.0-beta.5`. Accounts now derive at `m - Keep API keys and secrets secure and never expose them in client-side code - Use environment variables for sensitive configuration values - Always use HTTPS URLs for API endpoints -- Set appropriate `transferMaxFee` limits to prevent excessive fees +- Set appropriate `transferMaxFee` limits to prevent excessive gasless transfer fees +- Do not rely on `transactionMaxFee` for gasless transfers; native send, quote, and sign methods are unsupported in this module - Validate the paymaster token address before using it in configuration @@ -23445,7 +33097,7 @@ console.log('Paymaster Jetton balance:', paymasterBalance) ``` -The paymaster token balance determines how many gasless transfers you can execute. Ensure the paymaster has sufficient token balance before initiating gasless transfers. +The account pays gasless fees from its balance of the configured paymaster Jetton. If that is also the Jetton being transferred, the same balance must cover both the transfer amount and the final fee. ## Read-Only Account Balances @@ -23457,11 +33109,11 @@ import { WalletAccountReadOnlyTonGasless } from '@tetherto/wdk-wallet-ton-gasles const readOnlyAccount = new WalletAccountReadOnlyTonGasless(publicKey, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional }, tonApiClient: { - url: 'https://tonapi.io/v2', + url: 'https://tonapi.io', secretKey: 'your-ton-api-key' // Optional }, paymasterToken: { @@ -23482,7 +33134,7 @@ console.log('Paymaster token balance:', paymasterBalance) ## Next Steps -With balance checks in place, learn how to [send TON](/sdk/wallet-modules/wallet-ton-gasless/guides/send-transactions). +With balance checks in place, learn how to [transfer Jetton tokens gaslessly](/sdk/wallet-modules/wallet-ton-gasless/guides/transfer-tokens). *** @@ -23513,15 +33165,16 @@ import WalletManagerTonGasless, { WalletAccountReadOnlyTonGasless } from '@tetherto/wdk-wallet-ton-gasless' -const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') const wallet = new WalletManagerTonGasless(seedPhrase, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional }, tonApiClient: { - url: 'https://tonapi.io/v2', + url: 'https://tonapi.io', secretKey: 'your-ton-api-key' // Optional }, paymasterToken: { @@ -23532,7 +33185,7 @@ const wallet = new WalletManagerTonGasless(seedPhrase, { ``` -**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. +**Secure the Seed Phrase:** Load seed phrases from secure storage; never hardcode or log them. This server-side example uses an environment variable. If the seed phrase is lost, the user will permanently lose access to their funds. ## 3. Get Your First Account @@ -23561,9 +33214,9 @@ With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modu ## Handle Errors URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless/guides/handle-errors -Description: Handle errors, manage fees, and dispose of sensitive data in gasless TON wallets. +Description: Handle errors, manage fees, and clean up derived account keys in gasless TON wallets. -This guide covers how to [handle gasless transfer errors](#handle-gasless-transfer-errors) and [handle unsupported method errors](#handle-unsupported-method-errors), plus [best practices](#best-practices) for fee management and memory cleanup. +This guide covers how to [handle gasless transfer errors](#handle-gasless-transfer-errors) and [handle unsupported method errors](#handle-unsupported-method-errors), plus [best practices](#best-practices) for fee management and derived key cleanup. ## Handle Gasless Transfer Errors @@ -23576,7 +33229,7 @@ try { recipient: 'EQ...', amount: 1000000000 }) - console.log('Transfer hash:', result.hash) + console.log('Signed transfer body hash:', result.hash) } catch (error) { if (error.message.includes('insufficient jetton balance')) { console.error('Please add more Jetton tokens to your wallet') @@ -23584,7 +33237,7 @@ try { console.error('Please add more paymaster tokens for gas fees') } else if (error.message.includes('invalid address')) { console.error('The recipient address is invalid') - } else if (error.message.includes('max fee')) { + } else if (error.message === 'The transfer operation exceeds the transfer max fee.') { console.error('The transfer fee exceeds your configured maximum') } else { console.error('Transfer failed:', error.message) @@ -23594,7 +33247,7 @@ try { ## Handle Unsupported Method Errors -The gasless module supports only paymaster-funded Jetton transfers. Both [`account.sendTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#sendtransactiontx) and [`account.signTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#signtransactiontx) throw when called. Use [`account.transfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#transferoptions-config) instead, and guard any code path that might reach these methods: +The gasless module supports only paymaster-funded Jetton transfers. [`account.sendTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#sendtransactiontx), [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#quotesendtransactiontx), and [`account.signTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#signtransactiontx) reject or throw when called. Use [`account.transfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#transferoptions-config) instead, and guard any code path that might reach these methods: ```javascript title="Unsupported Method Handling" try { @@ -23612,7 +33265,7 @@ try { ### Manage Fee Limits -Set `transferMaxFee` when creating the wallet to prevent gasless transfers from exceeding a maximum cost. You can retrieve current network rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#getfeerates): +Set `transferMaxFee` when creating the wallet to prevent gasless transfers from exceeding a maximum cost. Fee caps reject estimates greater than the configured limit, so an estimate equal to the cap is allowed. Native `sendTransaction()`, `quoteSendTransaction()`, and `signTransaction()` are unsupported on this module, so `transactionMaxFee` does not cap gasless transfers. You can retrieve mainnet TON API rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#getfeerates). Through `1.0.0-beta.8`, this method does not follow configured testnet clients and returns the same calculated value for `normal` and `fast`: ```javascript title="Fee Management" const feeRates = await wallet.getFeeRates() @@ -23620,9 +33273,9 @@ console.log('Normal fee rate:', feeRates.normal, 'nanotons') console.log('Fast fee rate:', feeRates.fast, 'nanotons') ``` -### Dispose of Sensitive Data +### Dispose Derived Account Keys -Call [`dispose()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#dispose-1) on accounts and wallet managers to clear private keys and sensitive data from memory when they are no longer needed: +Call [`dispose()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#dispose-1) on accounts and wallet managers to clear cached accounts' derived private keys when they are no longer needed: ```javascript title="Memory Cleanup" account.dispose() @@ -23631,7 +33284,7 @@ wallet.dispose() ``` -Always call [`dispose()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#dispose-1) in a `finally` block or cleanup handler to ensure sensitive data is cleared even if an error occurs. +Call [`dispose()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#dispose-1) in a `finally` block or cleanup handler so derived account keys are cleared even if an error occurs. In the current beta, `wallet.dispose()` does not zero or unset `wallet.seed`; manage the seed lifecycle separately and release all manager references when finished. *** @@ -23697,15 +33350,15 @@ Now that you can access your accounts, learn how to [check balances](/sdk/wallet *** -## Send Transactions +## Native Sends Unsupported URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless/guides/send-transactions Description: Why native TON sends are unsupported on the gasless module and what to use instead. -This guide explains why [native TON sends are not supported](#native-ton-sends-are-not-supported) on the gasless module and how to [check current fee rates](#check-current-fee-rates). +This guide explains why [native TON sends are not supported](#native-ton-sends-are-not-supported) on the gasless module and how to [check mainnet fee rates](#check-mainnet-fee-rates). ## Native TON Sends Are Not Supported -The gasless module does not send native TON. Both [`account.sendTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#sendtransactiontx) and [`account.signTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#signtransactiontx) throw on this module. The module's purpose is to relay paymaster-funded Jetton transfers, so the only way to move funds is [`account.transfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#transferoptions-config). +The gasless module does not send native TON. [`account.sendTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#sendtransactiontx), [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#quotesendtransactiontx), and [`account.signTransaction()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#signtransactiontx) reject or throw on this module. The module's purpose is to relay paymaster-funded Jetton transfers, so the only way to move funds is [`account.transfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#transferoptions-config). Calling `sendTransaction()` throws an error: @@ -23719,9 +33372,9 @@ await account.sendTransaction({ To send native TON, use the standard `@tetherto/wdk-wallet-ton` module. For gasless Jetton transfers, see [Transfer Jetton Tokens](/sdk/wallet-modules/wallet-ton-gasless/guides/transfer-tokens). -## Check Current Fee Rates +## Check Mainnet Fee Rates -You can retrieve current fee rates from the wallet manager using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#getfeerates): +You can retrieve mainnet TON API fee rates from the wallet manager using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#getfeerates). Through `1.0.0-beta.8`, this method does not follow configured testnet clients and returns the same calculated value for `normal` and `fast`: ```javascript title="Get Fee Rates" const feeRates = await wallet.getFeeRates() @@ -23767,11 +33420,11 @@ import { WalletAccountReadOnlyTonGasless } from '@tetherto/wdk-wallet-ton-gasles const readOnlyAccount = new WalletAccountReadOnlyTonGasless(publicKey, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, tonApiClient: { - url: 'https://tonapi.io/v2', + url: 'https://tonapi.io', secretKey: 'your-ton-api-key' }, paymasterToken: { @@ -23793,7 +33446,7 @@ For best practices on handling errors, managing fees, and cleaning up memory, se URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless/guides/transfer-tokens Description: Transfer Jetton tokens gaslessly with fees paid in paymaster tokens. -This guide explains how to [transfer Jetton tokens gaslessly](#transfer-tokens-gasless), [override paymaster configuration](#override-paymaster-configuration), [estimate transfer fees](#estimate-transfer-fees), and [validate inputs before executing](#transfer-with-validation). +This guide explains how to [transfer Jetton tokens gaslessly](#transfer-tokens-gasless), [override paymaster configuration](#override-paymaster-configuration), [estimate transfer fees](#estimate-transfer-fees), and [run preflight checks](#preflight-transfer-checks). ## Transfer Tokens (Gasless) @@ -23805,7 +33458,7 @@ const result = await account.transfer({ recipient: 'EQ...', // Recipient's TON address amount: 1000000000 // Amount in Jetton's base units }) -console.log('Transfer hash:', result.hash) +console.log('Signed transfer body hash:', result.hash) console.log('Transfer fee:', result.fee, 'paymaster token units') ``` @@ -23824,13 +33477,15 @@ const result = await account.transfer({ }, transferMaxFee: 2000000000 // Override maximum allowed fee }) -console.log('Transfer hash:', result.hash) +console.log('Signed transfer body hash:', result.hash) console.log('Transfer fee:', result.fee, 'paymaster token units') ``` +`transferMaxFee` rejects estimates greater than the configured cap. A fee estimate equal to the cap is allowed. + ## Estimate Transfer Fees -You can get a fee estimate before executing the transfer using [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#quotetransferoptions): +You can get a fee estimate before executing the transfer using [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#quotetransferoptions-config): ```javascript title="Quote Gasless Transfer" const quote = await account.quoteTransfer({ @@ -23841,46 +33496,70 @@ const quote = await account.quoteTransfer({ console.log('Transfer fee estimate:', quote.fee, 'paymaster token units') ``` -## Transfer with Validation +## Preflight Transfer Checks -Validate balances before transferring: +Inspect balances and fees before transferring: 1. Use [`account.getTokenBalance()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#gettokenbalancetokenaddress) to check Jetton balance. -2. Use [`account.getPaymasterTokenBalance()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#getpaymastertokenbalance) to verify sufficient paymaster funds. -3. Use [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#quotetransferoptions) to estimate fees. -4. Execute the transfer with [`account.transfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#transferoptions-config): +2. Use [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#quotetransferoptions-config) with the intended paymaster to estimate the fee. +3. Query that paymaster Jetton explicitly with `getTokenBalance(paymasterJettonAddress)`. `getPaymasterTokenBalance()` always reads the wallet-level paymaster, so do not use it to preflight a per-call override. +4. If the transferred Jetton is also the paymaster Jetton, require one balance to cover the transfer amount plus the fee. Compare parsed TON addresses because different string encodings can identify the same Jetton master. +5. Reject a quote above your application's fee policy, then execute [`account.transfer()`](/sdk/wallet-modules/wallet-ton-gasless/api-reference#transferoptions-config) with `transferMaxFee` set to that quote. The method obtains a fresh estimate and aborts before relay if it has risen. This explicit policy check matters because a per-call configuration replaces, rather than merges with, the wallet-level transfer configuration: -```javascript title="Validated Gasless Transfer" -async function transferWithValidation(account, jettonAddress, recipient, amount) { +This example imports `Address` from `@ton/ton` for canonical address comparison. Add `@ton/ton` as a direct dependency in your application before using it. + +```javascript title="Gasless Transfer with Preflight Checks" +import { Address } from '@ton/ton' + +async function transferWithChecks(account, jettonAddress, paymasterJettonAddress, recipient, amount, maxFee) { if (typeof jettonAddress !== 'string' || jettonAddress.length === 0) { throw new Error('Invalid Jetton address format') } + if (typeof paymasterJettonAddress !== 'string' || paymasterJettonAddress.length === 0) { + throw new Error('Invalid paymaster Jetton address format') + } + if (typeof recipient !== 'string' || recipient.length === 0) { throw new Error('Invalid recipient address format') } - const balance = await account.getTokenBalance(jettonAddress) - if (balance < amount) { - throw new Error('Insufficient Jetton balance') + const amountBaseUnits = BigInt(amount) + const maxFeeBaseUnits = BigInt(maxFee) + const transferOptions = { + token: jettonAddress, + recipient, + amount: amountBaseUnits } + const paymasterToken = { address: paymasterJettonAddress } - const paymasterBalance = await account.getPaymasterTokenBalance() - console.log('Paymaster token balance:', paymasterBalance) + const transferBalance = await account.getTokenBalance(jettonAddress) + if (transferBalance < amountBaseUnits) { + throw new Error('Insufficient Jetton balance') + } - const quote = await account.quoteTransfer({ - token: jettonAddress, - recipient, - amount - }) + const quote = await account.quoteTransfer(transferOptions, { paymasterToken }) console.log('Estimated fee (paymaster token):', quote.fee) - const result = await account.transfer({ - token: jettonAddress, - recipient, - amount + if (quote.fee > maxFeeBaseUnits) { + throw new Error('Quoted fee exceeds application policy') + } + + const paymasterBalance = await account.getTokenBalance(paymasterJettonAddress) + const sameJetton = Address.parse(jettonAddress).equals(Address.parse(paymasterJettonAddress)) + const requiredPaymasterBalance = sameJetton + ? amountBaseUnits + quote.fee + : quote.fee + + if (paymasterBalance < requiredPaymasterBalance) { + throw new Error('Insufficient paymaster Jetton balance') + } + + const result = await account.transfer(transferOptions, { + paymasterToken, + transferMaxFee: quote.fee }) - console.log('Transfer hash:', result.hash) + console.log('Signed transfer body hash:', result.hash) console.log('Actual fee (paymaster token):', result.fee) return result @@ -23897,7 +33576,7 @@ Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-ton-gasless/g URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless/usage Description: Guide to using the @tetherto/wdk-wallet-ton-gasless module. -The `@tetherto/wdk-wallet-ton-gasless` module provides wallet management for the TON blockchain with gasless transaction support, where transfer fees are paid using a configured paymaster token instead of native TON. +The `@tetherto/wdk-wallet-ton-gasless` module provides wallet management for the TON blockchain with gasless Jetton transfer support, where transfer fees are paid using a configured paymaster token instead of native TON. @@ -23909,8 +33588,8 @@ Work with multiple accounts and custom derivation paths. Query native TON, Jetton, and paymaster token balances. - -Send native TON transactions. + +Understand why native TON sends throw and when to use the standard TON module. Transfer Jetton tokens gaslessly with paymaster fees. @@ -23919,7 +33598,7 @@ Transfer Jetton tokens gaslessly with paymaster fees. Sign messages and verify signatures. -Handle errors, manage fees, and dispose of sensitive data. +Handle errors, manage fees, and clean up derived account keys. @@ -23977,19 +33656,21 @@ new WalletManagerTon(seed, config) - `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes - `config` (object): Configuration object - `tonClient` (object | TonClient): TON client configuration or instance - - `url` (string): TON Center API URL (e.g., 'https://toncenter.com/api/v3') + - `url` (string): TON Center v2 JSON-RPC URL (e.g., 'https://toncenter.com/api/v2/jsonRPC') - `secretKey` (string, optional): API key for TON Center - - `transferMaxFee` (number, optional): Maximum fee amount for transfer operations (in nanotons) + - `transferMaxFee` (number | bigint, optional): Maximum fee amount for transfer operations (in nanotons) + - `transactionMaxFee` (number | bigint, optional): Maximum fee amount for native `sendTransaction()` and `signTransaction()` operations (in nanotons) **Example:** ```javascript const wallet = new WalletManagerTon(seedPhrase, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, - transferMaxFee: 1000000000 // Maximum fee in nanotons (1 TON) + transferMaxFee: 1000000000, // Maximum Jetton transfer fee in nanotons + transactionMaxFee: 1000000000 // Maximum native send/sign fee in nanotons }) ``` @@ -24000,8 +33681,8 @@ const wallet = new WalletManagerTon(seedPhrase, { |--------|-------------|---------| | `getAccount(index)` | Returns a wallet account at the specified index | `Promise\` | | `getAccountByPath(path)` | Returns a wallet account at the specified BIP-44 derivation path | `Promise\` | -| `getFeeRates()` | Returns current fee rates for transactions | `Promise\<{normal: number, fast: number}\>` | -| `dispose()` | Disposes all wallet accounts, clearing private keys from memory | `void` | +| `getFeeRates()` | Returns fee rates from the mainnet TON API configuration | `Promise\<{normal: bigint, fast: bigint}\>` | +| `dispose()` | Disposes cached accounts and signers; the manager seed remains in memory | `void` | ##### `getAccount(index)` @@ -24031,7 +33712,7 @@ const account = await wallet.getAccountByPath("0'/0/1") ``` ##### `getFeeRates()` -Returns current fee rates for normal and fast transactions. +Returns normal and fast fee rates from the mainnet TON API configuration. Through `1.0.0-beta.12`, this method always requests `https://tonapi.io/v2`, does not follow the configured `tonClient` network, and returns the same calculated value for both fields. **Returns:** `Promise\` - Object containing normal and fast fee rates @@ -24043,7 +33724,7 @@ console.log('Fast fee rate:', feeRates.fast, 'nanotons') ``` ##### `dispose()` -Disposes all wallet accounts, clearing private keys from memory. +Disposes cached wallet accounts and signers, clearing their derived private keys. In the current beta, this method does not zero or unset the wallet manager's seed bytes. **Example:** ```javascript @@ -24053,14 +33734,13 @@ wallet.dispose() #### Properties ##### `seed` -The wallet's seed phrase. +The wallet manager's sensitive raw seed bytes. A manager created with a seed phrase converts it to bytes before storing it; a signer-backed manager returns `undefined`. -**Type:** `string | Uint8Array` +**Type:** `Uint8Array | undefined` -**Example:** -```javascript -console.log('Seed phrase:', wallet.seed) -``` + +Do not log, serialize, or expose this property. In the current beta, [`wallet.dispose()`](#dispose) does not zero or unset these bytes; release all manager references and manage the original seed lifecycle separately. + ### WalletAccountTon @@ -24078,19 +33758,21 @@ new WalletAccountTon(seed, path, config) - `path` (string): BIP-44 derivation path (e.g., "0'/0/0") - `config` (object): Configuration object - `tonClient` (object | TonClient): TON client configuration or instance - - `url` (string): TON Center API URL + - `url` (string): TON Center v2 JSON-RPC URL - `secretKey` (string, optional): API key for TON Center - - `transferMaxFee` (number, optional): Maximum fee amount for transfer operations + - `transferMaxFee` (number | bigint, optional): Maximum fee amount for transfer operations + - `transactionMaxFee` (number | bigint, optional): Maximum fee amount for native `sendTransaction()` and `signTransaction()` operations **Example:** ```javascript const account = new WalletAccountTon(seedPhrase, "0'/0/0", { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, - transferMaxFee: 10000000 // Maximum fee in nanotons (e.g., 0.01 TON) + transferMaxFee: 10000000, // Maximum Jetton transfer fee in nanotons + transactionMaxFee: 10000000 // Maximum native send/sign fee in nanotons }) ``` @@ -24101,11 +33783,11 @@ const account = new WalletAccountTon(seedPhrase, "0'/0/0", { | `getAddress()` | Returns the account's TON address | `Promise\` | | `sign(message)` | Signs a message using the account's private key | `Promise\` | | `verify(message, signature)` | Verifies a message signature | `Promise\` | -| `signTransaction(tx)` | Signs a transaction offline without broadcasting it | `Promise\` | -| `sendTransaction(tx)` | Sends a TON transaction | `Promise\<{hash: string, fee: number}\>` | -| `quoteSendTransaction(tx)` | Estimates the fee for a TON transaction | `Promise\<{fee: number}\>` | -| `transfer(options)` | Transfers Jetton tokens to another address | `Promise\<{hash: string, fee: number}\>` | -| `quoteTransfer(options)` | Estimates the fee for a Jetton transfer | `Promise\<{fee: number}\>` | +| `signTransaction(tx)` | Builds a signed external-message body using current chain state, without broadcasting it | `Promise\` | +| `sendTransaction(tx)` | Builds and sends a transaction, or sends a signed transfer-body `Cell` | `Promise\<{hash: string, fee: bigint}\>` | +| `quoteSendTransaction(tx)` | Estimates the fee for a transaction or signed transfer-body `Cell` | `Promise\<{fee: bigint}\>` | +| `transfer(options)` | Transfers Jetton tokens to another address | `Promise\<{hash: string, fee: bigint}\>` | +| `quoteTransfer(options)` | Estimates the fee for a Jetton transfer | `Promise\<{fee: bigint}\>` | | `getBalance()` | Returns the native TON balance (in nanotons) | `Promise\` | | `getTokenBalance(tokenAddress)` | Returns the balance of a specific Jetton token | `Promise\` | | `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise\` | @@ -24154,7 +33836,7 @@ console.log('Signature:', signature) ``` ##### `signTransaction(tx)` -Signs a transaction offline and returns the signed external-message body without broadcasting it. Use this to build a transaction on a signing device and broadcast it elsewhere. Added in v1.0.0-beta.8. +Builds and signs an external-message body without broadcasting it. This is not an offline operation: it requires a configured TON client to read the wallet's current sequence number and, when `transactionMaxFee` is set, to estimate the fee. Added in v1.0.0-beta.8. **Parameters:** - `tx` (object): The transaction object (same shape as `sendTransaction`) @@ -24163,7 +33845,9 @@ Signs a transaction offline and returns the signed external-message body without - `bounceable` (boolean, optional): Whether the destination address is bounceable - `body` (string | Cell, optional): Optional message body -**Returns:** `Promise\` - The signed external-message body as a TON `Cell`. Call `cell.toBoc().toString('base64')` to obtain a wire-format payload ready for broadcast. +**Returns:** `Promise\` - The signed body as a TON `Cell`. It is the body accepted by the matching opened `WalletContractV5R1.send()` call, not a complete external-message BOC that can be posted directly to TON Center. + +**Throws:** Error if the estimated transaction fee exceeds `transactionMaxFee` when configured. **Example:** ```javascript @@ -24171,21 +33855,23 @@ const cell = await account.signTransaction({ to: 'EQ...', // TON address value: 1000000000 // 1 TON in nanotons }); -const boc = cell.toBoc().toString('base64'); -console.log('Signed payload:', boc); +// `cell` is not broadcast by signTransaction(). ``` ##### `sendTransaction(tx)` -Sends a TON transaction and returns the result with hash and fee. +Sends a TON transaction and returns its signed transfer body hash and fee. **Parameters:** -- `tx` (object): The transaction object +- `tx` (TonTransaction | Cell): A transaction object or signed transfer-body `Cell` - `to` (string): Recipient TON address (e.g., 'EQ...') - - `value` (number): Amount in nanotons (1 TON = 1,000,000,000 nanotons) + - `value` (number | bigint): Amount in nanotons (1 TON = 1,000,000,000 nanotons) - `bounceable` (boolean, optional): Whether the address is bounceable (TON-specific, optional) +When `tx` is a `Cell`, WDK estimates its fee, enforces `transactionMaxFee`, and passes that exact body to the matching opened `WalletContractV5R1.send()` call. It does not rebuild the body, refresh its sequence number, or re-sign it. + +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - Object containing the signed transfer body hash as lowercase hex and the fee in nanotons -**Returns:** `Promise\<{hash: string, fee: number}\>` - Object containing hash and fee (in nanotons) +**Throws:** Error if the estimated transaction fee exceeds `transactionMaxFee` when configured. **Example:** ```javascript @@ -24193,7 +33879,7 @@ const result = await account.sendTransaction({ to: 'EQ...', // TON address value: 1000000000 // 1 TON in nanotons }); -console.log('Transaction hash:', result.hash); +console.log('Signed transfer body hash:', result.hash); console.log('Transaction fee:', result.fee, 'nanotons'); ``` @@ -24201,12 +33887,12 @@ console.log('Transaction fee:', result.fee, 'nanotons'); Estimates the fee for a transaction. **Parameters:** -- `tx` (object): The transaction object (same as sendTransaction) +- `tx` (TonTransaction | Cell): A transaction object or signed transfer-body `Cell` - `to` (string): Recipient TON address (e.g., 'EQ...') - - `value` (number): Amount in nanotons (1 TON = 1,000,000,000 nanotons) + - `value` (number | bigint): Amount in nanotons (1 TON = 1,000,000,000 nanotons) - `bounceable` (boolean, optional): Whether the address is bounceable (TON-specific, optional) -**Returns:** `Promise\<{fee: number}\>` - Object containing fee estimate (in nanotons) +**Returns:** `Promise\<{fee: bigint}\>` - Object containing fee estimate (in nanotons) **Example:** ```javascript @@ -24224,9 +33910,9 @@ Transfers Jettons (TON tokens) to another address. - `options` (object): Transfer options - `token` (string): Jetton master contract address (TON format, e.g., 'EQ...') - `recipient` (string): Recipient TON address (e.g., 'EQ...') - - `amount` (number): Amount in Jetton's base units + - `amount` (number | bigint): Amount in Jetton's base units -**Returns:** `Promise\<{hash: string, fee: number}\>` - Object containing hash and fee (in nanotons) +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - Object containing the signed transfer body hash as lowercase hex and the fee in nanotons **Example:** ```javascript @@ -24235,7 +33921,7 @@ const result = await account.transfer({ recipient: 'EQ...', // Recipient's TON address amount: 1000000000 // Amount in Jetton's base units }); -console.log('Transfer hash:', result.hash); +console.log('Signed transfer body hash:', result.hash); console.log('Transfer fee:', result.fee, 'nanotons'); ``` @@ -24246,9 +33932,9 @@ Estimates the fee for a Jetton (TON token) transfer. - `options` (object): Transfer options (same as transfer) - `token` (string): Jetton master contract address (TON format, e.g., 'EQ...') - `recipient` (string): Recipient TON address (e.g., 'EQ...') - - `amount` (number): Amount in Jetton's base units + - `amount` (number | bigint): Amount in Jetton's base units -**Returns:** `Promise\<{fee: number}\>` - Object containing fee estimate (in nanotons) +**Returns:** `Promise\<{fee: bigint}\>` - Object containing fee estimate (in nanotons) **Example:** ```javascript @@ -24282,21 +33968,32 @@ Returns the balance of a specific Jetton (TON token). **Example:** ```javascript const tokenBalance = await account.getTokenBalance('EQ...'); -console.log('Token balance:', tokenBalance, 'nanotons'); +console.log('Token balance:', tokenBalance, 'Jetton base units'); ``` ##### `getTransactionReceipt(hash)` Returns a transaction's receipt if it has been mined. +Through `1.0.0-beta.12`, the initial receipt lookup always queries mainnet TON Center v3. Do not rely on this method for testnet receipts; see [Network Selection](/sdk/wallet-modules/wallet-ton/configuration#network-selection). + **Parameters:** -- `hash` (string): The transaction hash +- `hash` (string): The signed transfer body hash returned by `sendTransaction()` or `transfer()` **Returns:** `Promise\` - Transaction receipt or null if not yet mined **Example:** ```javascript -const receipt = await account.getTransactionReceipt('EQ...') -console.log('Transaction confirmed:', receipt.success) +const result = await account.sendTransaction({ + to: 'EQ...', + value: 1000000000n +}) +const receipt = await account.getTransactionReceipt(result.hash) + +if (receipt) { + console.log('Transaction receipt:', receipt) +} else { + console.log('Transaction not yet included in a block') +} ``` ##### `toReadOnlyAccount()` @@ -24346,8 +34043,8 @@ new WalletAccountReadOnlyTon(publicKey, config) ``` **Parameters:** -- `publicKey` (string): The account's public key (hex or base64) -- `config` (object): Configuration object (same as WalletManagerTon) +- `publicKey` (string | Uint8Array): The account's public key. String values must be hex encoded. +- `config` (object): TON client and retry configuration without send-only fee caps #### Methods @@ -24395,15 +34092,24 @@ console.log('Signature valid:', isValid) ##### `getTransactionReceipt(hash)` Returns a transaction's receipt if it has been mined. +Through `1.0.0-beta.12`, the initial receipt lookup always queries mainnet TON Center v3. Do not rely on this method for testnet receipts; see [Network Selection](/sdk/wallet-modules/wallet-ton/configuration#network-selection). + **Parameters:** -- `hash` (string): The transaction hash +- `hash` (string): The signed transfer body hash returned by `sendTransaction()` or `transfer()` **Returns:** `Promise\` - Transaction receipt or null if not yet mined **Example:** ```javascript -const receipt = await readOnlyAccount.getTransactionReceipt('EQ...') -console.log('Transaction confirmed:', receipt.success) +async function logTransactionReceipt(readOnlyAccount, transactionHash) { + const receipt = await readOnlyAccount.getTransactionReceipt(transactionHash) + + if (receipt) { + console.log('Transaction receipt:', receipt) + } else { + console.log('Transaction not yet included in a block') + } +} ``` ## Types @@ -24436,6 +34142,16 @@ interface TonTransaction { } ``` +### Cell + +The signed transaction body returned by `signTransaction()` is a `Cell` from `@ton/core`. It is not re-exported by `@tetherto/wdk-wallet-ton`. + +```typescript +import type { Cell } from '@ton/core' +``` + +This value is the signed transfer body accepted by the matching opened `WalletContractV5R1.send()` call. It is not a complete external-message BOC. + ### TransferOptions ```typescript @@ -24456,7 +34172,7 @@ interface TransferOptions { * Amount in Jetton's base units * @example 1000000000 // Amount depends on token decimals */ - amount: number; + amount: number | bigint; } ``` @@ -24465,16 +34181,16 @@ interface TransferOptions { ```typescript interface TransactionResult { /** - * Transaction hash in base64 format - * @example 'EQD4FPq...' + * Signed transfer body hash as a lowercase hex string; pass it to getTransactionReceipt() + * @example '7f83b1657ff1fc53b92dc18148a1d65dfa13501404a55e63ddfde593f4f5f9d8' */ hash: string; /** * Transaction fee in nanotons - * @example 100000 // 0.0001 TON + * @example 100000n // 0.0001 TON */ - fee: number; + fee: bigint; } ``` @@ -24483,16 +34199,16 @@ interface TransactionResult { ```typescript interface FeeRates { /** - * Fee rate for normal priority transactions (in nanotons) - * @example 100000000 // 0.1 TON + * Mainnet-derived fee rate in nanotons + * @example 100000000n // 0.1 TON */ - normal: number; + normal: bigint; /** - * Fee rate for high priority transactions (in nanotons) - * @example 200000000 // 0.2 TON + * Same mainnet-derived fee rate as `normal` through v1.0.0-beta.12 + * @example 100000000n // 0.1 TON */ - fast: number; + fast: bigint; } ``` @@ -24519,8 +34235,8 @@ interface KeyPair { interface TonWalletConfig { /** * TON Center client configuration, a TonClient instance, or an array of - * either. When an array is provided, connection errors cause the wallet to - * fail over to the next client in the list. + * either. When an array is provided, any thrown Error causes the wallet to + * retry on the next client by default. */ tonClient?: TonClientConfig | TonClient | Array; @@ -24536,12 +34252,18 @@ interface TonWalletConfig { * @example 1000000000 // 1 TON */ transferMaxFee?: number | bigint; + + /** + * Maximum allowed fee for native send/sign operations (in nanotons) + * @example 1000000000 // 1 TON + */ + transactionMaxFee?: number | bigint; } interface TonClientConfig { /** * TON Center API endpoint - * @example 'https://toncenter.com/api/v3' + * @example 'https://toncenter.com/api/v2/jsonRPC' */ url: string; @@ -24590,10 +34312,11 @@ import WalletManagerTon from '@tetherto/wdk-wallet-ton' const config = { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional }, - transferMaxFee: 1000000000 // Optional: Maximum fee in nanotons (1 TON) + transferMaxFee: 1000000000, // Optional: Maximum Jetton transfer fee in nanotons + transactionMaxFee: 1000000000 // Optional: Maximum native send/sign fee in nanotons } const wallet = new WalletManagerTon(seedPhrase, config) @@ -24606,10 +34329,11 @@ import { WalletAccountTon } from '@tetherto/wdk-wallet-ton' const accountConfig = { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional }, - transferMaxFee: 1000000000 // Optional: Maximum fee in nanotons + transferMaxFee: 1000000000, // Optional: Maximum Jetton transfer fee in nanotons + transactionMaxFee: 1000000000 // Optional: Maximum native send/sign fee in nanotons } const account = new WalletAccountTon(seedPhrase, "0'/0/0", accountConfig) @@ -24619,13 +34343,13 @@ const account = new WalletAccountTon(seedPhrase, "0'/0/0", accountConfig) ### tonClient -The `tonClient` option configures the TON Center API client for blockchain interactions. It accepts a single client configuration, a `TonClient` instance, or an array of either for endpoint failover. +The `tonClient` option configures the TON Center v2 JSON-RPC client for blockchain interactions. It accepts a single client configuration, a `TonClient` instance, or an array of either for endpoint failover. **Type:** ```typescript interface TonClientConfig { /** - * TON Center API endpoint URL + * TON Center v2 JSON-RPC endpoint URL */ url: string; @@ -24648,22 +34372,26 @@ type TonClientOption = // Basic configuration const config = { tonClient: { - url: 'https://toncenter.com/api/v3' + url: 'https://toncenter.com/api/v2/jsonRPC' } } // With API key for higher rate limits const config = { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' } } ``` + +Provide the full v2 JSON-RPC endpoint. The module passes this URL to `@ton/ton`'s `TonClient`, which posts JSON-RPC requests directly to it. + + ### tonClient (array) and retries -Since v1.0.0-beta.8, `tonClient` also accepts an array of configurations or instances. When a connection error occurs, the wallet automatically fails over to the next client in the list. The `retries` option sets the number of additional retry attempts after the initial call fails. +Since v1.0.0-beta.8, `tonClient` also accepts an array of configurations or instances. When a client call throws an `Error`, the wallet automatically retries on the next client. By default, this includes application errors as well as connection errors. The `retries` option sets the number of additional attempts after the initial call fails. **Type:** `retries` is `number` (optional) @@ -24674,8 +34402,8 @@ The total number of attempts is `1 + retries`. For example, `retries: 3` with fo ```javascript const config = { tonClient: [ - { url: 'https://toncenter.com/api/v3', secretKey: 'your-api-key' }, - { url: 'https://toncenter.com/api/v3' } + { url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, + { url: 'https://your-secondary-toncenter.example/api/v2/jsonRPC' } // Replace with a real independent provider ], retries: 3 // Optional: additional retry attempts after the first failure } @@ -24683,9 +34411,9 @@ const config = { ### transferMaxFee -The `transferMaxFee` option sets the maximum allowed fee (in nanotons) for transfer operations. This helps prevent unexpectedly high transaction fees. +The `transferMaxFee` option sets the maximum allowed fee, in nanotons, for Jetton `transfer()` operations. -**Type:** `number` (nanotons) +**Type:** `number | bigint` (nanotons) **Default:** No maximum (undefined) @@ -24698,13 +34426,32 @@ const config = { // Example with both options const config = { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, transferMaxFee: 1000000000 } ``` +### Transaction Max Fee + +The `transactionMaxFee` option sets the maximum allowed fee, in nanotons, for native TON `sendTransaction()` and `signTransaction()` operations. This is separate from `transferMaxFee`, which applies to Jetton transfers. + +**Type:** `number | bigint` (nanotons) + +**Default:** No maximum (undefined) + +**Example:** +```javascript +const config = { + tonClient: { + url: 'https://toncenter.com/api/v2/jsonRPC', + secretKey: 'your-api-key' + }, + transactionMaxFee: 1000000000 // 1 TON in nanotons +} +``` + ## Read-Only Account Configuration For read-only accounts, you only need the TON client configuration: @@ -24714,7 +34461,7 @@ import { WalletAccountReadOnlyTon } from '@tetherto/wdk-wallet-ton' const readOnlyConfig = { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional } } @@ -24724,10 +34471,14 @@ const readOnlyAccount = new WalletAccountReadOnlyTon(publicKey, readOnlyConfig) ## Network Selection -The TON network (mainnet or testnet) is determined by the TON Center API endpoint URL: +For client-backed balance and transaction operations, the TON network is determined by the TON Center API endpoint URL: -- Mainnet: `https://toncenter.com/api/v3` -- Testnet: `https://testnet.toncenter.com/api/v3` +- Mainnet: `https://toncenter.com/api/v2/jsonRPC` +- Testnet: `https://testnet.toncenter.com/api/v2/jsonRPC` + + +Through `@tetherto/wdk-wallet-ton` `1.0.0-beta.12`, `getTransactionReceipt()` starts its lookup against a hard-coded mainnet TON Center v3 endpoint, even when `tonClient` points to testnet. `getFeeRates()` likewise always reads mainnet TON API configuration. Do not rely on these methods for testnet-specific receipts or fee rates. + ## Derivation Paths @@ -24753,7 +34504,7 @@ Use [`getAccountByPath`](/sdk/wallet-modules/wallet-ton/api-reference) to supply - Always use HTTPS URLs for TON Center API endpoints - Keep API keys secure and never expose them in client-side code - Consider using environment variables for API keys -- Set appropriate `transferMaxFee` limits for your use case +- Set appropriate `transactionMaxFee` and `transferMaxFee` limits for your use case @@ -24816,7 +34567,7 @@ import { WalletAccountReadOnlyTon } from '@tetherto/wdk-wallet-ton' const readOnlyAccount = new WalletAccountReadOnlyTon(publicKey, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional } }) @@ -24863,18 +34614,19 @@ You can create a new wallet instance using the [`WalletManagerTon`](/sdk/wallet- ```javascript title="Create TON Wallet" import WalletManagerTon, { WalletAccountTon, WalletAccountReadOnlyTon } from '@tetherto/wdk-wallet-ton' -const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') const wallet = new WalletManagerTon(seedPhrase, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional } }) ``` -**Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. +**Secure the Seed Phrase:** Load seed phrases from secure storage; never hardcode or log them. This server-side example uses an environment variable. If the seed phrase is lost, the user will permanently lose access to their funds. ## 3. Get Your First Account @@ -24903,9 +34655,9 @@ With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modu ## Handle Errors URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton/guides/handle-errors -Description: Handle errors, manage fees, and dispose of sensitive data in TON wallets. +Description: Handle errors, manage fees, and clean up derived account keys in TON wallets. -This guide covers how to [handle transaction errors](#handle-transaction-errors) and [handle token transfer errors](#handle-token-transfer-errors), plus [best practices](#best-practices) for fee management and memory cleanup. +This guide covers how to [handle transaction errors](#handle-transaction-errors) and [handle token transfer errors](#handle-token-transfer-errors), plus [best practices](#best-practices) for fee management and derived key cleanup. ## Handle Transaction Errors @@ -24918,11 +34670,13 @@ try { value: 1000000000, bounceable: true }) - console.log('Transaction hash:', result.hash) + console.log('Signed transfer body hash:', result.hash) console.log('Fee paid:', result.fee, 'nanotons') } catch (error) { if (error.message.includes('insufficient balance')) { console.error('Not enough TON to complete transaction') + } else if (error.message === 'Exceeded maximum fee cost for transaction operation.') { + console.error('Transaction fee exceeds transactionMaxFee') } else if (error.message.includes('invalid address')) { console.error('Invalid recipient address') } else if (error.message.includes('timeout')) { @@ -24944,12 +34698,12 @@ try { recipient: 'EQ...', amount: 1000000 }) - console.log('Transfer submitted:', result.hash) + console.log('Signed transfer body hash:', result.hash) } catch (error) { console.error('Transfer failed:', error.message) if (error.message.toLowerCase().includes('insufficient')) { console.log('Please add more tokens to your wallet') - } else if (error.message.toLowerCase().includes('fee')) { + } else if (error.message === 'Exceeded maximum fee cost for transfer operations.') { console.log('The transfer fee exceeds your configured maximum') } } @@ -24959,7 +34713,7 @@ try { ### Manage Fee Limits -Set `transferMaxFee` when creating the wallet to prevent transactions from exceeding a maximum cost. You can retrieve current network rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton/api-reference): +Set `transactionMaxFee` when creating the wallet to cap native `sendTransaction()` and `signTransaction()` costs. Set `transferMaxFee` separately for Jetton `transfer()` costs. You can retrieve mainnet TON API rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton/api-reference). Through `1.0.0-beta.12`, this method does not follow a configured testnet client and returns the same calculated value for `normal` and `fast`: ```javascript title="Fee Management" const feeRates = await wallet.getFeeRates() @@ -24967,9 +34721,9 @@ console.log('Normal fee rate:', feeRates.normal, 'nanotons') console.log('Fast fee rate:', feeRates.fast, 'nanotons') ``` -### Dispose of Sensitive Data +### Dispose Derived Account Keys -Call [`dispose()`](/sdk/wallet-modules/wallet-ton/api-reference) on accounts and wallet managers to clear private keys and sensitive data from memory when they are no longer needed: +Call [`dispose()`](/sdk/wallet-modules/wallet-ton/api-reference) on accounts and wallet managers to clear cached accounts' derived private keys when they are no longer needed: ```javascript title="Memory Cleanup" account.dispose() @@ -24978,7 +34732,7 @@ wallet.dispose() ``` -Always call [`dispose()`](/sdk/wallet-modules/wallet-ton/api-reference) in a `finally` block or cleanup handler to ensure sensitive data is cleared even if an error occurs. +Call [`dispose()`](/sdk/wallet-modules/wallet-ton/api-reference) in a `finally` block or cleanup handler so derived account keys are cleared even if an error occurs. In the current beta, `wallet.dispose()` does not zero or unset `wallet.seed`; manage the seed lifecycle separately and release all manager references when finished. *** @@ -25043,7 +34797,7 @@ Now that you can access your accounts, learn how to [check balances](/sdk/wallet URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton/guides/send-transactions Description: Send native TON and estimate transaction fees. -This guide explains how to [send native TON](#send-native-ton), [estimate transaction fees](#estimate-transaction-fees), [use dynamic fee rates](#use-dynamic-fee-rates), and [sign a transaction offline](#sign-a-transaction-offline). +This guide explains how to [send native TON](#send-native-ton), [estimate transaction fees](#estimate-transaction-fees), [cap transaction fees](#cap-transaction-fees), [read mainnet fee rates](#read-mainnet-fee-rates), [prepare a signed transaction body](#prepare-a-signed-transaction-body), and [quote and send a signed transaction body](#quote-and-send-a-signed-transaction-body). On TON, values are expressed in nanotons (1 TON = 10^9 nanotons). Transactions support an optional `bounceable` parameter specific to the TON network. @@ -25059,7 +34813,7 @@ const result = await account.sendTransaction({ value: 1000000000, // 1 TON in nanotons bounceable: true // Optional: specify if the address is bounceable }) -console.log('Transaction hash:', result.hash) +console.log('Signed transfer body hash:', result.hash) console.log('Transaction fee:', result.fee, 'nanotons') ``` @@ -25076,9 +34830,20 @@ const quote = await account.quoteSendTransaction({ console.log('Estimated fee:', quote.fee, 'nanotons') ``` -## Use Dynamic Fee Rates +## Cap Transaction Fees + +Set [`transactionMaxFee`](/sdk/wallet-modules/wallet-ton/configuration#transaction-max-fee) when you create the wallet to stop native `sendTransaction()` and `signTransaction()` calls if the estimated fee exceeds your limit. + +```javascript title="Cap Native Transaction Fees" +const wallet = new WalletManagerTon(seedPhrase, { + tonClient: { url: 'https://toncenter.com/api/v2/jsonRPC' }, + transactionMaxFee: 1000000000n +}) +``` + +## Read Mainnet Fee Rates -You can retrieve current fee rates from the wallet manager using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton/api-reference): +You can retrieve mainnet TON API fee rates from the wallet manager using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton/api-reference#getfeerates). Through `1.0.0-beta.12`, this method does not follow a configured testnet client and returns the same calculated value for `normal` and `fast`: ```javascript title="Get Fee Rates" const feeRates = await wallet.getFeeRates() @@ -25086,21 +34851,40 @@ console.log('Normal fee rate:', feeRates.normal, 'nanotons') console.log('Fast fee rate:', feeRates.fast, 'nanotons') ``` -## Sign a Transaction Offline +## Prepare a Signed Transaction Body -You can sign a transaction without broadcasting it using [`account.signTransaction()`](/sdk/wallet-modules/wallet-ton/api-reference#signtransactiontx). This returns the signed external-message body as a TON `Cell`, which you can serialize and broadcast from another environment. +You can build a signed transaction body without broadcasting it using [`account.signTransaction()`](/sdk/wallet-modules/wallet-ton/api-reference#signtransactiontx). The method still requires the configured TON client to read the current sequence number and, when a fee cap is configured, estimate the fee. It returns the body `Cell` accepted by the matching opened `WalletContractV5R1.send()` call, not a complete external-message BOC. -```javascript title="Sign Transaction Offline" +```javascript title="Prepare Signed Transaction Body" const cell = await account.signTransaction({ to: 'EQ...', // TON address value: 1000000000 // 1 TON in nanotons }) -// Serialize to a wire-format payload for broadcast elsewhere -const boc = cell.toBoc().toString('base64') -console.log('Signed payload:', boc) +// `cell` is not broadcast by signTransaction(). ``` +## Quote and Send a Signed Transaction Body + +Pass the `Cell` returned by `signTransaction()` to the quote and send methods when review and submission are separate steps. + +```javascript title="Quote and Send a Signed Body" +const cell = await account.signTransaction({ + to: 'EQ...', + value: 1000000000n +}) + +const quote = await account.quoteSendTransaction(cell) +console.log('Estimated fee:', quote.fee, 'nanotons') + +const result = await account.sendTransaction(cell) +console.log('Signed transfer body hash:', result.hash) +``` + + +The signed body contains the wallet sequence number read during signing. Submit it through the same matching account before that sequence number changes. WDK sends the `Cell` unchanged and does not rebuild or re-sign it; `sendTransaction()` estimates its fee again and enforces `transactionMaxFee`. The returned hash identifies the signed transfer body, not a network transaction hash. + + ## Next Steps To transfer Jetton tokens instead of native TON, see [Transfer Jetton Tokens](/sdk/wallet-modules/wallet-ton/guides/transfer-tokens). @@ -25159,7 +34943,7 @@ const transferResult = await account.transfer({ recipient: 'EQ...', // Recipient's TON address amount: 1000000 // Amount in Jetton's base units }) -console.log('Transfer hash:', transferResult.hash) +console.log('Signed transfer body hash:', transferResult.hash) console.log('Transfer fee:', transferResult.fee, 'nanotons') ``` @@ -25211,7 +34995,7 @@ async function transferJettonWithValidation(account, jettonAddress, recipient, a recipient, amount }) - console.log('Transfer hash:', result.hash) + console.log('Signed transfer body hash:', result.hash) console.log('Actual fee:', result.fee, 'nanotons') return result @@ -25250,7 +35034,7 @@ Transfer Jetton tokens and estimate fees. Sign messages and verify signatures. -Handle errors, manage fees, and dispose of sensitive data. +Handle errors, manage fees, and clean up derived account keys. @@ -25277,11 +35061,11 @@ Get started with WDK's TON Wallet API *** -## Wallet Tron Overview +## Standard TRON wallet URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron -Description: Overview of the @tetherto/wdk-wallet-tron module +Description: Create and manage TRON wallets with TRX transfers, TRC20 balances, signing, and provider failover. -A simple and secure package to manage BIP-44 wallets for the Tron blockchain. This package provides a clean API for creating, managing, and interacting with Tron wallets using BIP-39 seed phrases and Tron-specific derivation paths. +Use the TRON wallet module for standard TRON accounts where users can handle regular TRON fees and resources. ## Features @@ -25290,11 +35074,13 @@ A simple and secure package to manage BIP-44 wallets for the Tron blockchain. Th - **Multi-Account Management**: Create and manage multiple accounts from a single seed phrase - **Tron Address Support:** Generate and manage Tron addresses - **Message Signing:** Sign and verify messages using Tron cryptography -- **Transaction Management**: Send transactions and get fee estimates +- **Transaction Management**: Send transactions and get fee estimates, including activation fee details for native TRX sends +- **Arbitrary Transactions**: Quote, sign, and send smart-contract call descriptors or pre-built TronWeb transactions in addition to native TRX transfers +- **Signed Transaction Relay**: Quote and broadcast the exact `TronSignedTransaction` returned by `signTransaction()` - **TRC20 Support:** Query native TRX and TRC20 token balances. - **TypeScript Support**: Full TypeScript definitions included - **Memory Safety**: Secure private key management with automatic memory cleanup -- **Provider Flexibility:** Support for custom Tron RPC endpoints +- **Provider Flexibility:** Support for custom Tron RPC endpoints, TronWeb instances, and ordered failover provider lists ## Supported Networks @@ -25328,11 +35114,11 @@ Get started with WDK's with Tron Wallet usage *** -## Wallet Tron Gas-Free Overview +## Gasfree TRON wallet URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron-gasfree -Description: Overview of the @tetherto/wdk-wallet-tron-gasfree module +Description: Create TRON wallets that support gasfree TRC20 transfers through provider-backed flows. -A simple and secure package to manage BIP-44 wallets for the Tron blockchain with **gas-free TRC20 token transfers**. This package provides a clean API for creating, managing, and interacting with Tron wallets using BIP-39 seed phrases and Tron-specific derivation paths, with support for gas-free operations via a service provider. +Use the gasfree TRON wallet module when your app needs TRC20 transfers without making users manage TRON gas or resources directly. ## Features @@ -25412,7 +35198,8 @@ new WalletManagerTronGasfree(seed, config) - `verifyingContract` (string): Gas-free verifying contract address - `gasFreeApiKey` (string, optional): API key for signed GasFree provider requests - `gasFreeApiSecret` (string, optional): API secret for signed GasFree provider requests. Provide together with `gasFreeApiKey`. - - `transferMaxFee` (number, optional): Maximum fee for transfer operations + - `transferMaxFee` (number | bigint, optional): Shared config field; the current runtime does not use it as a default transfer cap + - `transactionMaxFee` (number | bigint, optional): Shared config field; native transaction methods remain unsupported, so it has no runtime effect **Example:** ```javascript @@ -25423,8 +35210,7 @@ const wallet = new WalletManagerTronGasfree(seedPhrase, { serviceProvider: 'T...', verifyingContract: 'T...', gasFreeApiKey: 'your-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-api-secret', - transferMaxFee: 10000000 // Optional + gasFreeApiSecret: 'your-api-secret' }) ``` @@ -25434,7 +35220,7 @@ const wallet = new WalletManagerTronGasfree(seedPhrase, { |--------|-------------|---------| | `getAccount(index)` | Returns a wallet account at the specified index | `Promise\` | | `getAccountByPath(path)` | Returns a wallet account at the specified BIP-44 derivation path | `Promise\` | -| `getFeeRates()` | Returns current fee rates for normal and fast transactions | `Promise\<{normal: number, fast: number}\>` | +| `getFeeRates()` | Returns current fee rates for normal and fast transactions | `Promise\<{normal: bigint, fast: bigint}\>` | | `dispose()` | Disposes all wallet accounts, clearing private keys from memory | `void` | ##### `getAccount(index)` @@ -25466,7 +35252,7 @@ const account = await wallet.getAccountByPath("0'/0/1") ##### `getFeeRates()` Returns current fee rates for normal and fast transactions. -**Returns:** `Promise\<{normal: number, fast: number}\>` - Object containing fee rates in sun +**Returns:** `Promise\<{normal: bigint, fast: bigint}\>` - Object containing fee rates in sun - `normal`: Fee rate for normal priority transactions - `fast`: Fee rate for high priority transactions @@ -25507,8 +35293,8 @@ new WalletAccountTronGasfree(seed, path, config) | `getAddress()` | Returns the account's address | `Promise\` | | `getBalance()` | Returns the native TRX balance (in sun) | `Promise\` | | `getTokenBalance(tokenAddress)` | Returns the balance of a specific TRC20 token | `Promise\` | -| `transfer(options)` | Transfers TRC20 tokens to another address | `Promise\<{hash: string, fee: bigint}\>` | -| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: bigint}\>` | +| `transfer(options, config?)` | Transfers TRC20 tokens with an optional per-call fee cap | `Promise\<{hash: string, fee: bigint, activationFee: bigint}\>` | +| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: bigint, activationFee: bigint}\>` | | `sign(message)` | Signs a message using the account's private key | `Promise\` | | `signTransaction(tx)` | Unsupported on Tron GasFree; always throws | `Promise\` | | `sendTransaction(tx)` | Unsupported on Tron GasFree; always throws | `Promise\` | @@ -25559,19 +35345,24 @@ Transfers TRC20 tokens to another address using the gas-free service. - `options` (TransferOptions): Transfer options - `token` (string): TRC20 contract address - `recipient` (string): Recipient's Tron address - - `amount` (number): Amount in token base units + - `amount` (number | bigint): Amount in token base units +- `config` (object, optional): Per-call transfer configuration + - `transferMaxFee` (number | bigint, optional): Reject when the quoted total fee is greater than this token-base-unit cap. A fee equal to the cap is allowed. -**Returns:** `Promise\<{hash: string, fee: bigint}\>` - Object containing transaction hash and fee paid in token base units. The returned fee includes the estimated transfer fee plus any activation fee returned by the GasFree service. +**Returns:** `Promise\<{hash: string, fee: bigint, activationFee: bigint}\>` - Object containing transaction hash, total fee paid in token base units, and the activation-fee portion. `activationFee` is `0n` when no account activation fee applies. **Example:** ```javascript const result = await account.transfer({ token: 'T...', // TRC20 contract address recipient: 'T...', // Recipient's address - amount: 1000000 // Amount in token base units + amount: 1000000n // Amount in token base units +}, { + transferMaxFee: 5000n }) console.log('Transaction hash:', result.hash) console.log('Fee paid:', result.fee, 'token base units') +console.log('Activation fee:', result.activationFee, 'token base units') ``` ##### `quoteTransfer(options)` @@ -25580,7 +35371,7 @@ Estimates the fee for a TRC20 token transfer. **Parameters:** - `options` (TransferOptions): Transfer options (same as transfer method) -**Returns:** `Promise\<{fee: bigint}\>` - Estimated fee in token base units. The estimate includes the token's transfer fee and, when the GasFree account is inactive, the token activation fee. +**Returns:** `Promise\<{fee: bigint, activationFee: bigint}\>` - Estimated total fee in token base units, plus the activation-fee portion. `activationFee` is `0n` when no account activation fee applies. **Example:** ```javascript @@ -25590,6 +35381,7 @@ const quote = await account.quoteTransfer({ amount: 1000000 }) console.log('Estimated fee:', quote.fee, 'token base units') +console.log('Activation fee:', quote.activationFee, 'token base units') ``` ##### `sign(message)` @@ -25681,7 +35473,7 @@ new WalletAccountReadOnlyTronGasfree(address, config) | `getAddress()` | Returns the account's address | `Promise\` | | `getBalance()` | Returns the native TRX balance (in sun) | `Promise\` | | `getTokenBalance(tokenAddress)` | Returns the balance of a specific TRC20 token | `Promise\` | -| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: bigint}\>` | +| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: bigint, activationFee: bigint}\>` | | `quoteSendTransaction(tx)` | Unsupported on Tron GasFree; always throws | `Promise\\>` | | `verify(message, signature)` | Verifies a message signature | `Promise\` | @@ -25730,7 +35522,7 @@ Estimates the fee for a TRC20 token transfer without requiring private keys. - `recipient` (string): Recipient's Tron address - `amount` (number): Amount in token base units -**Returns:** `Promise\<{fee: bigint}\>` - Estimated fee in token base units. The estimate includes the token's transfer fee and, when the GasFree account is inactive, the token activation fee. +**Returns:** `Promise\<{fee: bigint, activationFee: bigint}\>` - Estimated total fee in token base units, plus the activation-fee portion. `activationFee` is `0n` when no account activation fee applies. **Example:** ```javascript @@ -25740,6 +35532,7 @@ const quote = await readOnlyAccount.quoteTransfer({ amount: 1000000 }) console.log('Estimated fee:', quote.fee, 'token base units') +console.log('Activation fee:', quote.activationFee, 'token base units') ``` ##### `quoteSendTransaction(tx)` @@ -25788,26 +35581,29 @@ interface TransferOptions { * Amount to transfer in token base units * @example 1000000 // 1 USDT (6 decimals) */ - amount: number; + amount: number | bigint; } ``` -### TransferResult -Result object returned by transfer operations. +### Transfer Results + +The GasFree package re-exports `TransferResult` and `TronActivationFee` from `@tetherto/wdk-wallet-tron`. GasFree transfer and quote methods return intersections of those types: ```typescript -interface TransferResult { - /** - * The transaction hash - * @example '0x123...' - */ - hash: string; +import type { + TransferResult, + TronActivationFee +} from '@tetherto/wdk-wallet-tron-gasfree' - /** - * Fee paid in token base units - * @example 1000 // Fee in token base units - */ - fee: bigint; +type TronGasfreeTransferResult = TransferResult & TronActivationFee +type TronGasfreeTransferQuote = Omit & TronActivationFee +``` + +`TransferResult` supplies `hash: string` and `fee: bigint`. `TronActivationFee` supplies the activation-fee portion separately: + +```typescript +interface TronActivationFee { + activationFee: bigint; } ``` @@ -25820,13 +35616,13 @@ interface FeeRates { * Fee rate for normal priority transactions (in sun) * @example 1000 */ - normal: number; + normal: bigint; /** * Fee rate for high priority transactions (in sun) * @example 2000 */ - fast: number; + fast: bigint; } ``` @@ -25960,9 +35756,20 @@ interface TronGasfreeWalletConfig { * @example 10000000 */ transferMaxFee?: number | bigint; + + /** + * Shared wallet config field for native transaction methods. + * The GasFree module does not support quoteSendTransaction(), + * signTransaction(), or sendTransaction(), so this field has no + * runtime effect in the current release. + * @optional + */ + transactionMaxFee?: number | bigint; } ``` +The current runtime also does not use constructor-level `transferMaxFee` as a default. Enforce a GasFree transfer cap with the second argument to `account.transfer()`. + ### KeyPair Account key pair information. @@ -26001,11 +35808,6 @@ Get started with WDK's Tron Gasfree Wallet Configuration ## Need Help? - -*** - -## Need Help? - *** @@ -26035,13 +35837,8 @@ const config = { chainId: 728126428, // Blockchain ID provider: 'https://api.trongrid.io', // Tron RPC endpoint gasFreeProvider: 'https://open.gasfree.io/tron/', // Gas-free service URL - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', // Service provider address - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', // Verifying contract address - - // Optional parameters - gasFreeApiKey: 'your-api-key', // Required only for signed GasFree API requests - gasFreeApiSecret: 'your-api-secret', // Provide together with gasFreeApiKey - transferMaxFee: 10000000 // Maximum fee in token base units + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } const wallet = new WalletManagerTronGasfree(seedPhrase, config) @@ -26052,15 +35849,23 @@ const config2 = { chainId: 728126428, provider: tronWeb, gasFreeProvider: 'https://open.gasfree.io/tron/', - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - gasFreeApiKey: 'your-api-key', - gasFreeApiSecret: 'your-api-secret' + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } ``` `gasFreeApiKey` and `gasFreeApiSecret` are optional. Omit both when your GasFree provider accepts unsigned requests. If you configure signed GasFree API requests, provide both values together; the constructor rejects partial credentials. + +The module performs HMAC request signing in the application process. Never embed `gasFreeApiSecret` in browser, mobile, or distributed desktop code. For client applications, use unsigned access only when the selected provider explicitly permits it, or route GasFree API requests through an authenticated backend that stores the secret. + + +Retrieve the current service-provider address from the provider's [`GET /api/v1/config/provider/all`](https://docs.gasfree.io/#get-apiv1configproviderall) response. Do not substitute the verifying-contract address for `serviceProvider`. + + +`TronGasfreeWalletConfig` includes `transferMaxFee` and `transactionMaxFee` for shared wallet type compatibility. The current GasFree runtime does not use either constructor field as a fee cap. Pass `transferMaxFee` in the second argument to `account.transfer()`; native transaction methods are unsupported, so `transactionMaxFee` has no effect. + + ## Account Configuration Both `WalletAccountTronGasfree` and `WalletAccountReadOnlyTronGasfree` share similar configuration requirements: @@ -26076,25 +35881,20 @@ const account = new WalletAccountTronGasfree( chainId: 728126428, provider: 'https://api.trongrid.io', gasFreeProvider: 'https://open.gasfree.io/tron/', - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - gasFreeApiKey: 'your-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-api-secret', - transferMaxFee: 10000000 // Optional + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } ) -// Read-only account (transferMaxFee not needed) +// Read-only account (fee-cap fields are omitted) const readOnlyAccount = new WalletAccountReadOnlyTronGasfree( 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', // Tron address { chainId: 728126428, provider: 'https://api.trongrid.io', gasFreeProvider: 'https://open.gasfree.io/tron/', - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - gasFreeApiKey: 'your-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-api-secret' + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } ) @@ -26171,14 +35971,15 @@ The `gasFreeApiKey` option is your API key for signed requests to the gas-free s **Example:** ```javascript +// Trusted server runtime only const config = { - gasFreeApiKey: 'your-api-key', - gasFreeApiSecret: 'your-api-secret' + gasFreeApiKey: process.env.GASFREE_API_KEY, + gasFreeApiSecret: process.env.GASFREE_API_SECRET } ``` -Provide `gasFreeApiKey` and `gasFreeApiSecret` together. Passing only one of them throws during account construction. +Provide `gasFreeApiKey` and `gasFreeApiSecret` together. Passing only one of them throws during account construction. Because the module uses the secret to sign requests locally, configure these fields only in a trusted server runtime. ### Gas-Free API Secret @@ -26191,14 +35992,15 @@ The `gasFreeApiSecret` option is your API secret for signed requests to the gas- **Example:** ```javascript +// Trusted server runtime only const config = { - gasFreeApiKey: 'your-api-key', - gasFreeApiSecret: 'your-api-secret' + gasFreeApiKey: process.env.GASFREE_API_KEY, + gasFreeApiSecret: process.env.GASFREE_API_SECRET } ``` -Provide `gasFreeApiSecret` and `gasFreeApiKey` together. Passing only one of them throws during account construction. +Provide `gasFreeApiSecret` and `gasFreeApiKey` together. Passing only one of them throws during account construction. Never ship the secret in a browser, mobile, or desktop application bundle. ### Service Provider @@ -26212,11 +36014,13 @@ The `serviceProvider` option is the Tron address of the gas-free service provide ```javascript const config = { - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH' + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS' } ``` +Retrieve the current address from the provider's [`GET /api/v1/config/provider/all`](https://docs.gasfree.io/#get-apiv1configproviderall) response. + ### Verifying Contract The `verifyingContract` option is the Tron address of the contract that verifies gas-free transactions. @@ -26229,14 +36033,14 @@ The `verifyingContract` option is the Tron address of the contract that verifies ```javascript const config = { - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH' + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } ``` ### Transfer Max Fee -The `transferMaxFee` option sets a maximum limit for transaction fees to prevent unexpectedly high costs. +Pass `transferMaxFee` in the optional second argument to `account.transfer()` to cap one GasFree TRC20 transfer. The constructor-level field exists in `TronGasfreeWalletConfig` but is not used as a default by the current runtime. **Type:** `number | bigint` @@ -26248,26 +36052,29 @@ The `transferMaxFee` option sets a maximum limit for transaction fees to prevent ```javascript const config = { - transferMaxFee: 10000000 // Maximum fee in token base units + transferMaxFee: 5000n } -// Usage with error handling -try { - const result = await account.transfer({ - token: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', - recipient: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - amount: 1000000 - }, { - transferMaxFee: 5000 // Override default max fee - }) -} catch (error) { - if (error.message.includes('exceeds the transfer max fee')) { - console.error('Transfer cancelled: Fee too high') - } -} +const result = await account.transfer({ + token: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', + recipient: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', + amount: 1000000n +}, config) ``` +The transfer is rejected only when the quoted total fee is greater than the cap. A fee equal to `transferMaxFee` is allowed. Use an integer or `bigint` value because the runtime converts the cap with `BigInt()`. + +### Transaction Max Fee + +`transactionMaxFee` is present in the public config type for base-wallet compatibility. + +**Type:** `number | bigint` + +**Required:** No (optional) + +The GasFree module does not support `quoteSendTransaction()`, `signTransaction()`, or `sendTransaction()`. Those methods always throw, so this field does not control a runtime operation in the current release. Use the base `@tetherto/wdk-wallet-tron` package for native TRX transactions. + ## Network-Specific Configurations ### Tron Mainnet @@ -26277,10 +36084,8 @@ const mainnetConfig = { chainId: 728126428, provider: 'https://api.trongrid.io', gasFreeProvider: 'https://open.gasfree.io/tron/', - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - verifyingContract: 'TFFAMLQZybALab4uxHA9RBE7pxhUAjfF3U', // Official Mainnet Contract - gasFreeApiKey: 'your-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-api-secret' + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' // Official mainnet contract } ``` @@ -26292,10 +36097,8 @@ const nileConfig = { chainId: 3448148188, // Nile Testnet (Specific ID required for GasFree) provider: 'https://nile.trongrid.io', gasFreeProvider: 'https://open-test.gasfree.io/nile/', - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - verifyingContract: 'THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc', // Official Testnet Contract - gasFreeApiKey: 'your-testnet-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-testnet-api-secret' + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc' // Official Nile testnet contract } ``` @@ -26318,11 +36121,6 @@ Get started with WDK's Tron Gasfree Wallet API ## Need Help? - -*** - -## Need Help? - *** @@ -26366,11 +36164,9 @@ import { WalletAccountReadOnlyTronGasfree } from '@tetherto/wdk-wallet-tron-gasf const readOnlyAccount = new WalletAccountReadOnlyTronGasfree('TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', { chainId: 728126428, provider: 'https://api.trongrid.io', - gasFreeProvider: 'https://gasfree.provider.url', - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - gasFreeApiKey: 'your-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-api-secret' + gasFreeProvider: 'https://open.gasfree.io/tron/', + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' }) ``` @@ -26387,7 +36183,8 @@ You can also create a read-only account from an existing owned account using [`a ## Next Steps -With balance checks in place, learn how to [send TRX](/sdk/wallet-modules/wallet-tron-gasfree/guides/send-transactions). +With balance checks in place, learn how to [transfer TRC20 tokens](/sdk/wallet-modules/wallet-tron-gasfree/guides/transfer-tokens). + *** ## Get Started @@ -26422,17 +36219,20 @@ const seedPhrase = 'your twelve word seed phrase here' const wallet = new WalletManagerTronGasfree(seedPhrase, { chainId: 728126428, provider: 'https://api.trongrid.io', - gasFreeProvider: 'https://gasfree.provider.url', - serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', - gasFreeApiKey: 'your-gasfree-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-gasfree-api-secret', - transferMaxFee: 10000000 // Optional: maximum fee in token base units + gasFreeProvider: 'https://open.gasfree.io/tron/', + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' }) ``` If your GasFree provider does not require signed API requests, omit both `gasFreeApiKey` and `gasFreeApiSecret`. If you provide one, provide both. + +Never put `gasFreeApiSecret` in a browser, mobile, or distributed desktop application. The module signs requests in-process. If your provider requires authenticated requests, keep the credentials in a trusted backend and proxy the GasFree API calls. + + +Retrieve `serviceProvider` from the provider's [`GET /api/v1/config/provider/all`](https://docs.gasfree.io/#get-apiv1configproviderall) response. Pass any transfer fee cap to `account.transfer()` for that transfer; the current runtime does not use a constructor-level `transferMaxFee` default. + **Secure the Seed Phrase:** You must securely store this seed phrase immediately. If it is lost, the user will permanently lose access to their funds. @@ -26462,6 +36262,7 @@ All Tron addresses start with `T` and are 34 characters long. ## Next Steps With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-tron-gasfree/guides/manage-accounts). + *** ## Handle Errors @@ -26480,6 +36281,8 @@ try { token: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT recipient: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', amount: 1000000 + }, { + transferMaxFee: 1000n }) console.log('Transfer successful:', result.hash) console.log('Fee paid:', result.fee, 'token units') @@ -26514,7 +36317,7 @@ try { ### Manage Fee Limits -Set `transferMaxFee` when creating the wallet or per-transfer to prevent gas-free transfers from exceeding a maximum cost. You can retrieve current network rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-tron-gasfree/api-reference): +Pass `transferMaxFee` in the second argument to each `account.transfer()` call that needs a cap. The constructor-level field is not used as a default by the current runtime. You can retrieve current network rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-tron-gasfree/api-reference): ```javascript title="Fee Management" const feeRates = await wallet.getFeeRates() @@ -26535,6 +36338,7 @@ wallet.dispose() Always call [`dispose()`](/sdk/wallet-modules/wallet-tron-gasfree/api-reference) in a `finally` block or cleanup handler to ensure sensitive data is cleared even if an error occurs. + *** ## Manage Accounts @@ -26600,6 +36404,8 @@ Description: Understand native TRX transaction limitations in the gas-free Tron The Tron GasFree wallet module is for gas-free TRC20 token transfers. It does not support native TRX transaction execution or native transaction fee quotes. +Although `transactionMaxFee` is present in the shared config type, it has no effect here because the native quote, sign, and send methods remain unsupported. + For gas-free TRC20 transfers, use [`transfer(options)`](/sdk/wallet-modules/wallet-tron-gasfree/api-reference#transferoptions) and [`quoteTransfer(options)`](/sdk/wallet-modules/wallet-tron-gasfree/api-reference#quotetransferoptions). If you need native TRX transactions, use the base `@tetherto/wdk-wallet-tron` module instead. ## Unsupported Methods @@ -26621,13 +36427,15 @@ const quote = await account.quoteTransfer({ amount: 1000000 }) console.log('Gas-free transfer fee estimate:', quote.fee, 'token units') +console.log('Activation fee estimate:', quote.activationFee, 'token units') ``` -The fee estimate includes the token transfer fee and, when the GasFree account is inactive, the token activation fee returned by the provider. +The fee estimate includes the token transfer fee and, when the GasFree account is inactive, the token activation fee returned by the provider. The activation portion is exposed separately as `activationFee`. ## Next Steps To transfer TRC20 tokens with gas-free fees, see [Transfer TRC20 Tokens](/sdk/wallet-modules/wallet-tron-gasfree/guides/transfer-tokens). + *** ## Sign and Verify Messages @@ -26666,6 +36474,7 @@ You can also create a [`WalletAccountReadOnlyTronGasfree`](/sdk/wallet-modules/w ## Next Steps For best practices on handling errors, managing fees, and cleaning up memory, see [Handle Errors](/sdk/wallet-modules/wallet-tron-gasfree/guides/handle-errors). + *** ## Transfer TRC20 Tokens @@ -26686,9 +36495,10 @@ const transferResult = await account.transfer({ }) console.log('Transfer hash:', transferResult.hash) console.log('Transfer fee:', transferResult.fee, 'token units') +console.log('Activation fee:', transferResult.activationFee, 'token units') ``` -The returned `fee` includes the GasFree transfer fee and, when the GasFree account is not active yet, the token activation fee returned by the provider. +The returned `fee` includes the GasFree transfer fee and, when the GasFree account is not active yet, the token activation fee returned by the provider. Both `fee` and `activationFee` are returned as `bigint`; `activationFee` is `0n` when no activation fee applies. ## Override Fee Limit @@ -26704,8 +36514,11 @@ const result = await account.transfer({ }) console.log('Transfer hash:', result.hash) console.log('Transfer fee:', result.fee, 'token units') +console.log('Activation fee:', result.activationFee, 'token units') ``` +The call rejects only when the quoted total fee is greater than `transferMaxFee`; a fee equal to the cap is allowed. The constructor-level `transferMaxFee` field is not used as a default by the current runtime, so pass the cap on every transfer that needs one. + ## Estimate Transfer Fees You can get a fee estimate before executing the transfer using [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-tron-gasfree/api-reference#quotetransferoptions): @@ -26717,9 +36530,10 @@ const transferQuote = await account.quoteTransfer({ amount: 1000000 }) console.log('Transfer fee estimate:', transferQuote.fee, 'token units') +console.log('Activation fee estimate:', transferQuote.activationFee, 'token units') ``` -If the GasFree account is inactive, the quote includes the token's activation fee in addition to the transfer fee. +If the GasFree account is inactive, the quote includes the token's activation fee in addition to the transfer fee and exposes that portion as `activationFee`. ## Transfer with Validation @@ -26750,6 +36564,7 @@ async function transferWithValidation(account, tokenAddress, recipient, amount) amount }) console.log('Transfer fee estimate:', quote.fee, 'token units') + console.log('Activation fee estimate:', quote.activationFee, 'token units') const result = await account.transfer({ token: tokenAddress, @@ -26758,6 +36573,7 @@ async function transferWithValidation(account, tokenAddress, recipient, amount) }) console.log('Transfer completed:', result.hash) console.log('Fee paid:', result.fee, 'token units') + console.log('Activation fee paid:', result.activationFee, 'token units') return result } @@ -26766,6 +36582,7 @@ async function transferWithValidation(account, tokenAddress, recipient, amount) ## Next Steps Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-tron-gasfree/guides/sign-verify-messages) with your gas-free Tron account. + *** ## Usage @@ -26818,6 +36635,7 @@ Get started with WDK's Tron Gasfree Wallet API ## Need Help? + *** ## API Reference @@ -26839,8 +36657,8 @@ The main class for managing Tron wallets. Extends `WalletManager` from `@tethert ### Fee Rate Constants ```javascript -const FEE_RATE_NORMAL_MULTIPLIER = 1.1 -const FEE_RATE_FAST_MULTIPLIER = 2.0 +const FEE_RATE_NORMAL_MULTIPLIER = 110n +const FEE_RATE_FAST_MULTIPLIER = 200n ``` ### Constructor @@ -26852,21 +36670,34 @@ new WalletManagerTron(seed, config?) **Parameters:** - `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes - `config` (TronWalletConfig, optional): Configuration object - - `provider` (string | TronWeb, optional): Tron RPC endpoint URL or TronWeb instance - - `transferMaxFee` (number, optional): Maximum fee amount for transfer operations (in sun) + - `provider` (`string | TronWeb | Array`, optional): Tron RPC endpoint URL, TronWeb instance, or ordered failover list + - `retries` (number, optional): Additional failover attempts when `provider` is an array (default: 3) + - `transferMaxFee` (`number | bigint`, optional): Maximum fee amount for TRC20 transfer operations (in sun) + - `transactionMaxFee` (`number | bigint`, optional): Maximum fee amount for `sendTransaction()` and `signTransaction()` operations (in sun) **Example:** ```javascript const wallet = new WalletManagerTron(seedPhrase, { provider: 'https://api.trongrid.io', // Tron RPC endpoint - transferMaxFee: 10000000 // 10 TRX in sun + transferMaxFee: 10000000n, // Maximum TRC20 transfer fee in sun + transactionMaxFee: 10000000n // Maximum send/sign transaction fee in sun }) // Or with TronWeb instance const tronWeb = new TronWeb({ fullHost: 'https://api.trongrid.io' }) const wallet2 = new WalletManagerTron(seedPhrase, { provider: tronWeb, - transferMaxFee: 10000000 + transferMaxFee: 10000000n, + transactionMaxFee: 10000000n +}) + +// Or with ordered provider failover +const wallet3 = new WalletManagerTron(seedPhrase, { + provider: [ + 'https://api.trongrid.io', + 'https://secondary-tron-rpc.example' + ], + retries: 3 }) ``` @@ -26876,7 +36707,7 @@ const wallet2 = new WalletManagerTron(seedPhrase, { |--------|-------------|---------|--------| | `getAccount(index?)` | Returns a wallet account at the specified index | `Promise\` | - | | `getAccountByPath(path)` | Returns a wallet account at the specified BIP-44 derivation path | `Promise\` | - | -| `getFeeRates()` | Returns current fee rates from Tron network | `Promise\<{normal: number, fast: number}\>` | If no provider | +| `getFeeRates()` | Returns current fee rates from Tron network | `Promise\<{normal: bigint, fast: bigint}\>` | If no provider | | `dispose()` | Disposes all wallet accounts, clearing private keys from memory | `void` | - | ##### `getAccount(index?)` @@ -26913,7 +36744,7 @@ const account = await wallet.getAccountByPath("0'/0/1") ##### `getFeeRates()` Returns current fee rates from Tron network chain parameters. -**Returns:** `Promise\<{normal: number, fast: number}\>` - Fee rates in sun +**Returns:** `Promise\<{normal: bigint, fast: bigint}\>` - Fee rates in sun - `normal`: Base fee × 1.1 - `fast`: Base fee × 2.0 @@ -26942,7 +36773,7 @@ Represents an individual Tron wallet account. Extends `WalletAccountReadOnlyTron ```javascript const BIP_44_TRON_DERIVATION_PATH_PREFIX = "m/44'/195'" -const BANDWIDTH_PRICE = 1_000 +const BANDWIDTH_PRICE = 1_000n ``` ### Constructor @@ -26962,7 +36793,8 @@ new WalletAccountTron(seed, path, config?) ```javascript const account = new WalletAccountTron(seedPhrase, "0'/0/0", { provider: 'https://api.trongrid.io', - transferMaxFee: 10000000 // 10 TRX in sun + transferMaxFee: 10000000n, // Maximum TRC20 transfer fee in sun + transactionMaxFee: 10000000n // Maximum send/sign transaction fee in sun }) ``` @@ -26973,10 +36805,11 @@ const account = new WalletAccountTron(seedPhrase, "0'/0/0", { | `getAddress()` | Returns the account's Tron address | `Promise\` | - | | `sign(message)` | Signs a message using the account's private key | `Promise\` | - | | `verify(message, signature)` | Verifies a message signature | `Promise\` | - | -| `sendTransaction(tx)` | Sends a Tron transaction | `Promise\<{hash: string, fee: number}\>` | If no provider or fee exceeds max | -| `quoteSendTransaction(tx)` | Estimates the fee for a Tron transaction | `Promise\<{fee: number}\>` | If no provider | -| `transfer(options)` | Transfers TRC20 tokens to another address | `Promise\<{hash: string, fee: number}\>` | If no provider or fee exceeds max | -| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: number}\>` | If no provider | +| `signTransaction(tx)` | Signs a Tron transaction without broadcasting it | `Promise\` | If no provider, fee exceeds `transactionMaxFee`, or an unsigned pre-built transaction owner does not match the account | +| `sendTransaction(tx)` | Builds and sends a transaction, or broadcasts a signed transaction | `Promise\<{hash: string, fee: bigint, activationFee: bigint}\>` | If no provider or fee exceeds `transactionMaxFee`; unsigned pre-built inputs also require a matching owner | +| `quoteSendTransaction(tx)` | Estimates the fee for an unsigned or signed Tron transaction | `Promise\<{fee: bigint, activationFee: bigint}\>` | If no provider | +| `transfer(options)` | Transfers TRC20 tokens to another address | `Promise\<{hash: string, fee: bigint}\>` | If no provider or fee exceeds `transferMaxFee` | +| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: bigint}\>` | If no provider | | `getBalance()` | Returns the native TRX balance (in sun) | `Promise\` | If no provider | | `getTokenBalance(tokenAddress)` | Returns the balance of a specific TRC20 token | `Promise\` | If no provider | | `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise\` | If no provider | @@ -27024,19 +36857,43 @@ const isValid = await account.verify('Hello, Tron!', signature) console.log('Signature valid:', isValid) ``` +##### `signTransaction(tx)` +Signs a Tron transaction and returns the signed transaction object. This method does not broadcast the transaction. + +**Parameters:** +- `tx` (TronTransaction): Native TRX transfer, smart-contract call descriptor, or pre-built TronWeb transaction + +**Returns:** `Promise\` - Signed Tron transaction object with a `signature` array + +**Throws:** +- Error if no TronWeb provider is configured +- Error if fee exceeds `transactionMaxFee` when configured +- Error if a pre-built transaction is owned by a different account + +**Example:** +```javascript +const signedTransaction = await account.signTransaction({ + to: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', + value: 1000000 +}) + +console.log('Signed transaction:', signedTransaction) +``` + ##### `sendTransaction(tx)` -Sends a TRX transaction and returns the result with hash and fee. +Sends a Tron transaction and returns the result with hash, fee, and activation fee details. **Parameters:** -- `tx` (TronTransaction): The transaction object - - `to` (string): Recipient Tron address (e.g., 'T...') - - `value` (number): Amount in sun (1 TRX = 1,000,000 sun) +- `tx` (TronTransaction | TronSignedTransaction): Native TRX transfer, smart-contract call descriptor, pre-built unsigned TronWeb transaction, or signed transaction -**Returns:** `Promise\<{hash: string, fee: number}\>` - Transaction hash and fee in sun +When `tx` has a signature, WDK quotes it again, enforces `transactionMaxFee`, and forwards the exact object to `tronWeb.trx.sendRawTransaction()`. It does not rebuild the transaction, refresh its reference block or expiration, re-sign it, or repeat the unsigned pre-built owner check. + +**Returns:** `Promise\<{hash: string, fee: bigint, activationFee: bigint}\>` - Transaction hash, total fee in sun, and the portion used for account activation **Throws:** - Error if no TronWeb provider is configured -- Error if fee exceeds `transferMaxFee` +- Error if fee exceeds `transactionMaxFee` when configured +- Error if an unsigned pre-built transaction is owned by a different account **Example:** ```javascript @@ -27046,15 +36903,16 @@ const result = await account.sendTransaction({ }) console.log('Transaction hash:', result.hash) console.log('Transaction fee:', result.fee, 'sun') +console.log('Activation fee:', result.activationFee, 'sun') ``` ##### `quoteSendTransaction(tx)` -Estimates the bandwidth cost for a TRX transaction. +Estimates the cost for a Tron transaction. Quotes include bandwidth for every transaction, energy for smart-contract execution, and activation fee for native transfers to inactive recipients. **Parameters:** -- `tx` (TronTransaction): The transaction object (same as sendTransaction) +- `tx` (TronTransaction | TronSignedTransaction): An unsigned transaction input or signed transaction -**Returns:** `Promise\<{fee: number}\>` - Fee estimate in sun +**Returns:** `Promise\<{fee: bigint, activationFee: bigint}\>` - Fee estimate in sun and the portion used for account activation **Throws:** Error if no TronWeb provider is configured @@ -27065,6 +36923,7 @@ const quote = await account.quoteSendTransaction({ value: 1000000 }) console.log('Estimated fee:', quote.fee, 'sun') +console.log('Activation fee:', quote.activationFee, 'sun') ``` ##### `transfer(options)` @@ -27076,7 +36935,7 @@ Transfers TRC20 tokens using smart contract call. - `recipient` (string): Recipient Tron address (e.g., 'T...') - `amount` (number | bigint): Amount in token's base units -**Returns:** `Promise\<{hash: string, fee: number}\>` - Transaction hash and fee in sun +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - Transaction hash and fee in sun **Throws:** - Error if no TronWeb provider is configured @@ -27094,12 +36953,12 @@ console.log('Transfer fee:', result.fee, 'sun') ``` ##### `quoteTransfer(options)` -Estimates the energy and bandwidth cost for a TRC20 token transfer. +Estimates the TRC20 token transfer cost from current chain parameters, account resources, contract energy use, and bandwidth use. **Parameters:** - `options` (TransferOptions): Transfer options (same as transfer) -**Returns:** `Promise\<{fee: number}\>` - Fee estimate in sun (energy + bandwidth costs) +**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate in sun (energy + bandwidth costs) **Throws:** Error if no TronWeb provider is configured @@ -27185,20 +37044,21 @@ account.dispose() |----------|------|-------------| | `index` | `number` | The derivation path's index of this account | | `path` | `string` | The full BIP-44 derivation path of this account | -| `keyPair` | `{privateKey: Buffer, publicKey: Buffer}` | The account's key pair (⚠️ Contains sensitive data) | +| `keyPair` | `{privateKey: Uint8Array \| null, publicKey: Uint8Array}` | Read-only view of the account's key pair. `privateKey` is `null` after `dispose()` | **Example:** ```javascript console.log('Account index:', account.index) // 0, 1, 2, etc. console.log('Account path:', account.path) // m/44'/195'/0'/0/0 -// ⚠️ SENSITIVE: Handle with care const { privateKey, publicKey } = account.keyPair console.log('Public key length:', publicKey.length) // 33 bytes (compressed) -console.log('Private key length:', privateKey.length) // 32 bytes +console.log('Private key length:', privateKey?.length) // 32 bytes before dispose() ``` -⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, or expose the private key. + +The `keyPair` byte arrays are bound to the wallet account. Treat them as a read-only view: do not mutate, log, display, or expose the private key. + ## WalletAccountReadOnlyTron @@ -27212,7 +37072,7 @@ new WalletAccountReadOnlyTron(address, config?) **Parameters:** - `address` (string): The account's Tron address -- `config` (`Omit`, optional): Configuration object without transferMaxFee +- `config` (`Omit`, optional): Configuration object without send-only fee caps **Example:** ```javascript @@ -27227,8 +37087,8 @@ const readOnlyAccount = new WalletAccountReadOnlyTron('TLyqzVGLV1srkB7dToTAEqgDS |--------|-------------|---------|--------| | `getBalance()` | Returns the native TRX balance (in sun) | `Promise\` | If no provider | | `getTokenBalance(tokenAddress)` | Returns the balance of a specific TRC20 token | `Promise\` | If no provider | -| `quoteSendTransaction(tx)` | Estimates the fee for a TRX transaction | `Promise\<{fee: number}\>` | If no provider | -| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: number}\>` | If no provider | +| `quoteSendTransaction(tx)` | Estimates the fee for a Tron transaction | `Promise\<{fee: bigint, activationFee: bigint}\>` | If no provider | +| `quoteTransfer(options)` | Estimates the fee for a TRC20 transfer | `Promise\<{fee: bigint}\>` | If no provider | | `verify(message, signature)` | Verifies a message signature | `Promise\` | - | | `getTransactionReceipt(hash)` | Returns a transaction's receipt | `Promise\` | If no provider | @@ -27262,22 +37122,22 @@ console.log('USDT balance:', tokenBalance) ``` ##### `quoteSendTransaction(tx)` -Estimates the bandwidth cost for a TRX transaction. +Estimates the cost for a Tron transaction. Quotes include bandwidth for every transaction, energy for smart-contract execution, and activation fee for native transfers to inactive recipients. **Parameters:** - `tx` (TronTransaction): The transaction object -**Returns:** `Promise\<{fee: number}\>` - Fee estimate in sun +**Returns:** `Promise\<{fee: bigint, activationFee: bigint}\>` - Fee estimate in sun and the portion used for account activation **Throws:** Error if no TronWeb provider is configured ##### `quoteTransfer(options)` -Estimates the energy and bandwidth cost for a TRC20 transfer. +Estimates the TRC20 token transfer cost from current chain parameters, account resources, contract energy use, and bandwidth use. **Parameters:** - `options` (TransferOptions): Transfer options -**Returns:** `Promise\<{fee: number}\>` - Fee estimate in sun +**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate in sun **Throws:** Error if no TronWeb provider is configured @@ -27313,20 +37173,35 @@ Returns a transaction's receipt if it has been processed. ```typescript interface TronWalletConfig { - provider?: string | TronWeb; // Tron RPC URL or TronWeb instance - transferMaxFee?: number; // Maximum fee in sun + provider?: string | TronWeb | Array; // RPC, TronWeb, or failover list + retries?: number; // Additional failover attempts, default 3 + transferMaxFee?: number | bigint; // Maximum TRC20 transfer fee in sun + transactionMaxFee?: number | bigint; // Maximum sendTransaction/signTransaction fee in sun } ``` ### TronTransaction ```typescript -interface TronTransaction { +type TronTransaction = TronTrxTransfer | TronSmartContractCall | Transaction; + +interface TronTrxTransfer { to: string; // Recipient Tron address - value: number; // Amount in sun (1 TRX = 1,000,000 sun) + value: number | bigint; // Amount in sun (1 TRX = 1,000,000 sun) +} + +interface TronSmartContractCall { + contractAddress: string; // Smart contract address to call + functionSelector: string; // Function selector, e.g. 'transfer(address,uint256)' + parameters?: ContractFunctionParameter[]; + options?: TriggerSmartContractOptions; } + +type Transaction = import('tronweb').Types.Transaction; ``` +`ContractFunctionParameter` and `TriggerSmartContractOptions` are TronWeb types used by `transactionBuilder.triggerSmartContract()`. Pre-built `Transaction` values are the unsigned transaction objects returned by TronWeb transaction-builder methods. + ### TransferOptions ```typescript @@ -27342,7 +37217,23 @@ interface TransferOptions { ```typescript interface TransactionResult { hash: string; // Transaction hash - fee: number; // Fee paid in sun + fee: bigint; // Fee paid in sun +} +``` + +### TronSignedTransaction + +```typescript +type TronSignedTransaction = import('tronweb').Types.SignedTransaction +``` + +The WDK package re-exports this TronWeb type under the exact name `TronSignedTransaction`. + +### TronActivationFee + +```typescript +interface TronActivationFee { + activationFee: bigint; // Portion of the fee used for account activation } ``` @@ -27351,7 +37242,7 @@ interface TransactionResult { ```typescript interface TransferResult { hash: string; // Transaction hash - fee: number; // Fee paid in sun + fee: bigint; // Fee paid in sun } ``` @@ -27360,11 +37251,11 @@ interface TransferResult { ```typescript // Tron-specific constants const BIP_44_TRON_DERIVATION_PATH_PREFIX: string = "m/44'/195'"; -const BANDWIDTH_PRICE: number = 1_000; +const BANDWIDTH_PRICE: bigint = 1_000n; // Fee rate multipliers -const FEE_RATE_NORMAL_MULTIPLIER: number = 1.1; -const FEE_RATE_FAST_MULTIPLIER: number = 2.0; +const FEE_RATE_NORMAL_MULTIPLIER: bigint = 110n; +const FEE_RATE_FAST_MULTIPLIER: bigint = 200n; ``` @@ -27400,8 +37291,13 @@ Description: Configuration options and settings for @tetherto/wdk-wallet-tron import WalletManagerTron from '@tetherto/wdk-wallet-tron' const config = { - provider: 'https://api.trongrid.io', // Tron RPC endpoint - transferMaxFee: 10000000 // Maximum fee in sun (optional) + provider: [ + 'https://api.trongrid.io', + 'https://secondary-tron-rpc.example' + ], // Tron RPC endpoints; array enables failover + retries: 3, // Additional failover attempts when provider is an array + transferMaxFee: 10000000n, // Maximum TRC20 transfer fee in sun (optional) + transactionMaxFee: 10000000n // Maximum sendTransaction/signTransaction fee in sun (optional) } const wallet = new WalletManagerTron(seedPhrase, config) @@ -27414,7 +37310,8 @@ import { WalletAccountTron } from '@tetherto/wdk-wallet-tron' const accountConfig = { provider: 'https://api.trongrid.io', - transferMaxFee: 10000000 // Maximum fee in sun (optional) + transferMaxFee: 10000000n, // Maximum TRC20 transfer fee in sun (optional) + transactionMaxFee: 10000000n // Maximum sendTransaction/signTransaction fee in sun (optional) } const account = new WalletAccountTron(seedPhrase, "0'/0/0", accountConfig) @@ -27424,31 +37321,84 @@ const account = new WalletAccountTron(seedPhrase, "0'/0/0", accountConfig) ### Provider -The `provider` option specifies the Tron RPC endpoint or TronWeb instance for blockchain interactions. +The `provider` option specifies how the wallet connects to Tron. It accepts a Tron RPC endpoint URL, a TronWeb instance, or an ordered list of URLs and TronWeb instances for automatic failover. -**Type:** `string | TronWeb` +**Type:** `string | TronWeb | Array` -**Example:** +**Examples:** ```javascript +// Single RPC endpoint const config = { provider: 'https://api.trongrid.io' } + +// TronWeb instance +const config = { + provider: tronWeb +} + +// Ordered failover list +const config = { + provider: [ + 'https://api.trongrid.io', + 'https://secondary-tron-rpc.example' + ], + retries: 3 +} ``` -### Transfer Max Fee +When `provider` is an array, connection errors fail over to the next provider in the list. Empty arrays throw during provider initialization. Use endpoints that serve the same Tron network. + +If the array contains TronWeb instances, the first instance becomes the wallet's primary client. Additional instances contribute their `fullNode`, `solidityNode`, and `eventServer` connections to the failover pool. + +### Retries -The `transferMaxFee` option sets the maximum fee amount (in sun) for transfer operations. This helps prevent transactions from being sent with unexpectedly high fees. +The `retries` option controls how many additional failover attempts can happen after the initial provider call fails. It only applies when `provider` is an array. **Type:** `number` (optional) +**Default:** `3` + +**Example:** +```javascript +const config = { + provider: [ + 'https://api.trongrid.io', + 'https://secondary-tron-rpc.example' + ], + retries: 1 +} +``` + +### Transfer Max Fee + +The `transferMaxFee` option sets the maximum fee amount (in sun) for TRC20 `transfer()` operations. This helps prevent transfers from being sent with unexpectedly high fees. + +**Type:** `number | bigint` (optional) +**Unit:** Sun (1 TRX = 1,000,000 Sun) + +**Example:** +```javascript +const config = { + transferMaxFee: 10000000n // 10 TRX in sun +} +``` + +### Transaction Max Fee + +The `transactionMaxFee` option sets the maximum fee amount, in sun, for `sendTransaction()` and `signTransaction()` operations. This includes native TRX transfers, smart-contract call descriptors, and pre-built TronWeb transactions. This is separate from `transferMaxFee`, which applies to the dedicated TRC20 `transfer()` API. + +**Type:** `number | bigint` (optional) **Unit:** Sun (1 TRX = 1,000,000 Sun) **Example:** ```javascript const config = { - transferMaxFee: 10000000 // 10 TRX in sun + transactionMaxFee: 10000000n // 10 TRX in sun } ``` +Native TRX send quotes and results include `activationFee` when the recipient account must be activated. Smart-contract and pre-built transaction quotes include bandwidth and energy costs where applicable. + Get started with WDK in a Node.js environment @@ -27605,17 +37555,22 @@ Wrap transactions in `try/catch` blocks to handle common failure scenarios. Use ```javascript title="Transaction Error Handling" try { - const result = await account.sendTransaction({ + const tx = { to: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', value: 1000000 // 1 TRX in sun - }) + } + + const result = await account.sendTransaction(tx) console.log('Transaction hash:', result.hash) console.log('Fee paid:', result.fee, 'sun') + console.log('Activation fee:', result.activationFee, 'sun') } catch (error) { if (error.message.toLowerCase().includes('insufficient')) { console.error('Not enough TRX to complete transaction') - } else if (error.message.toLowerCase().includes('max fee')) { - console.error('The transfer fee exceeds your configured maximum') + } else if (error.message.includes('Exceeded maximum fee')) { + console.error('The transaction fee exceeds transactionMaxFee') + } else if (error.message.toLowerCase().includes('transaction owner')) { + console.error('The pre-built transaction is owned by a different account') } else { console.error('Transaction failed:', error.message) } @@ -27649,7 +37604,11 @@ try { ### Manage Fee Limits -Set `transferMaxFee` when creating the wallet to prevent transactions from exceeding a maximum cost. You can retrieve current network rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-tron/api-reference): +Set `transactionMaxFee` when creating the wallet to cap `sendTransaction()` and `signTransaction()` costs for native TRX transfers, smart-contract call descriptors, and pre-built TronWeb transactions. Set `transferMaxFee` separately for TRC20 `transfer()` costs. Native TRX quotes and results include `activationFee` when the recipient account must be activated. + +For pre-built TronWeb transactions, WDK verifies that the transaction owner address matches the wallet account address before signing or broadcasting. + +You can retrieve current network rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-tron/api-reference): ```javascript title="Fee Management" const feeRates = await wallet.getFeeRates() @@ -27729,11 +37688,11 @@ Now that you can access your accounts, learn how to [check balances](/sdk/wallet *** -## Send TRX +## Send TRX and Transactions URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron/guides/send-transactions -Description: Send native TRX and estimate transaction fees on Tron. +Description: Send native TRX, smart-contract calls, and pre-built TronWeb transactions on Tron. -This guide explains how to [send native TRX](#send-native-trx), [estimate transaction fees](#estimate-transaction-fees), and [use dynamic fee rates](#use-dynamic-fee-rates). +This guide explains how to [send native TRX](#send-native-trx), [send smart-contract calls](#send-smart-contract-calls), [send pre-built TronWeb transactions](#send-pre-built-tronweb-transactions), [estimate transaction fees](#estimate-transaction-fees), [cap transaction fees](#cap-transaction-fees), [sign without broadcasting](#sign-without-broadcasting), [quote and send a signed transaction](#quote-and-send-a-signed-transaction), and [use dynamic fee rates](#use-dynamic-fee-rates). On Tron, values are expressed in sun (1 TRX = 1,000,000 sun). @@ -27750,11 +37709,54 @@ const result = await account.sendTransaction({ }) console.log('Transaction hash:', result.hash) console.log('Transaction fee:', result.fee, 'sun') +console.log('Activation fee:', result.activationFee, 'sun') +``` + +## Send Smart-Contract Calls + +`sendTransaction()`, `quoteSendTransaction()`, and `signTransaction()` also accept smart-contract call descriptors. WDK builds a TronWeb `TriggerSmartContract` transaction from the contract address, function selector, parameters, and optional trigger options. + +```javascript title="Send A Smart-Contract Call" +const result = await account.sendTransaction({ + contractAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', + functionSelector: 'transfer(address,uint256)', + parameters: [ + { type: 'address', value: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH' }, + { type: 'uint256', value: 1000000 } + ], + options: { + feeLimit: 20_000_000 + } +}) + +console.log('Transaction hash:', result.hash) +console.log('Total fee:', result.fee, 'sun') +``` + +Smart-contract quotes include bandwidth plus the net energy cost after the sender's available resources are considered. + +## Send Pre-built TronWeb Transactions + +Pass a pre-built TronWeb transaction when you need a transaction type that WDK does not model directly, such as staking or voting. This example uses `sendTrx()` for brevity; the same pattern works with other TronWeb transaction-builder methods that return an unsigned transaction owned by the account. + +```javascript title="Send A Pre-built TronWeb Transaction" +const address = await account.getAddress() + +const transaction = await tronWeb.transactionBuilder.sendTrx( + 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', + 1_000_000, + address +) + +const result = await account.sendTransaction(transaction) +console.log('Transaction hash:', result.hash) ``` +Before signing or sending an unsigned pre-built transaction, WDK checks that the transaction owner address matches the wallet account address. + ## Estimate Transaction Fees -You can get a fee estimate before sending using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-tron/api-reference#quotesendtransactiontx): +You can get a fee estimate before sending using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-tron/api-reference#quotesendtransactiontx). For native TRX transfers, the quote includes `activationFee`, which is non-zero when the recipient account must be activated: ```javascript title="Quote Transaction Fee" const quote = await account.quoteSendTransaction({ @@ -27762,8 +37764,54 @@ const quote = await account.quoteSendTransaction({ value: 1000000 }) console.log('Estimated fee:', quote.fee, 'sun') +console.log('Activation fee:', quote.activationFee, 'sun') ``` +## Cap Transaction Fees + +Set [`transactionMaxFee`](/sdk/wallet-modules/wallet-tron/configuration#transaction-max-fee) when you create the wallet to stop `sendTransaction()` and `signTransaction()` calls if the estimated fee exceeds your limit. + +```javascript title="Cap Native TRX Fees" +const wallet = new WalletManagerTron(seedPhrase, { + provider: 'https://api.trongrid.io', + transactionMaxFee: 10000000n +}) +``` + +## Sign Without Broadcasting + +Use [`account.signTransaction()`](/sdk/wallet-modules/wallet-tron/api-reference#signtransactiontx) when you need a signed Tron transaction but do not want WDK to broadcast it. + +```javascript title="Sign TRX Transaction" +const signedTransaction = await account.signTransaction({ + to: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', + value: 1000000 +}) + +console.log('Signed transaction:', signedTransaction) +``` + +## Quote and Send a Signed Transaction + +Pass the `TronSignedTransaction` returned by `signTransaction()` to the quote and send methods when review and submission are separate steps. + +```javascript title="Quote and Send a Signed Transaction" +const signedTransaction = await account.signTransaction({ + to: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', + value: 1000000 +}) + +const quote = await account.quoteSendTransaction(signedTransaction) +console.log('Estimated fee:', quote.fee, 'sun') + +const result = await account.sendTransaction(signedTransaction) +console.log('Transaction hash:', result.hash) +``` + + +WDK forwards a signed object unchanged and does not refresh its reference block or expiration, re-sign it, or repeat the unsigned owner-address check. Validate that the signed transaction belongs to the intended account and submit it while it remains valid. `sendTransaction()` quotes it again and enforces `transactionMaxFee`. + + ## Use Dynamic Fee Rates You can retrieve current fee rates from the wallet manager using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-tron/api-reference): @@ -27849,6 +37897,8 @@ const transferQuote = await account.quoteTransfer({ console.log('Transfer fee estimate:', transferQuote.fee, 'sun') ``` +The quote uses current chain parameters, the sender's available resources, and the TRC20 contract simulation result. It charges only the missing energy after available energy is considered, then adds any bandwidth cost. + ## Transfer with Validation Validate addresses and check balances before transferring to catch errors early: @@ -27950,6 +38000,176 @@ Get started with WDK's Tron Wallet API *** +## Which wallet module do I need? +URL: https://docs.wdk.tether.io/sdk/wallet-modules/which-wallet-module +Description: Choose the right WDK wallet module by chain, account model, and transaction requirements. + +WDK wallet modules are split by chain and account model. Use this chooser when you know what you want to build but do not know which package maps to that intent. + + + +## How to choose + +Start with the chain your app needs to support. Then choose the account model: + +| If you need | Start with | +|---|---| +| Standard EVM accounts | [Standard EVM](/sdk/wallet-modules/wallet-evm) | +| EVM smart accounts with ERC-4337 | [Smart accounts (ERC-4337)](/sdk/wallet-modules/wallet-evm-erc-4337) | +| EVM EOA addresses with EIP-7702 delegation | [EIP-7702 accounts](/sdk/wallet-modules/wallet-evm-7702-gasless) | +| Bitcoin base-layer wallets | [Bitcoin](/sdk/wallet-modules/wallet-btc) | +| Lightning payments through Spark | [Lightning (Spark)](/sdk/wallet-modules/wallet-spark) | +| Standard TON wallets | [Standard TON](/sdk/wallet-modules/wallet-ton) | +| Gasless TON Jetton transfers | [Gasless TON](/sdk/wallet-modules/wallet-ton-gasless) | +| Standard TRON wallets | [Standard TRON](/sdk/wallet-modules/wallet-tron) | +| Gasfree TRON TRC20 transfers | [Gasfree TRON](/sdk/wallet-modules/wallet-tron-gasfree) | +| Standard Solana wallets | [Standard Solana](/sdk/wallet-modules/wallet-solana) | +| Gasless Solana transactions | [Gasless Solana](/sdk/wallet-modules/wallet-solana-gasless) | +| Aptos wallets | [Aptos](/sdk/wallet-modules/wallet-aptos) | +| RGB assets on Bitcoin | [RGB](/sdk/community-modules/wdk-wallet-rgb) | +| Cosmos-compatible chains | [Cosmos](/sdk/community-modules/wdk-wallet-cosmos) | + +## Keep the reference list + +The full package-oriented catalog remains available in [Wallet module reference](/sdk/wallet-modules). Use the reference list when you already know the package name or need to compare every released wallet module. + +*** + +## Build with AI +URL: https://docs.wdk.tether.io/start-building/build-with-ai +Description: Connect AI assistants to WDK docs, project rules, and wallet tools for faster app development. + +WDK documentation is optimized for AI coding assistants. Give your AI tool context about WDK to get accurate code generation, architecture guidance, and debugging help. + +Use this page as the starting point for building WDK apps with AI assistance. + + + +Give AI agents access to WDK wallet tools through MCP + + +Use WDK instructions with agentic coding tools + + +Install the community WDK skill in OpenClaw + + +Build AI payment flows with WDK wallets + + + +There are two practical ways to provide WDK context to your AI: + +1. **[Connect via Markdown](#connect-wdk-docs-via-markdown)** - Works with any AI tool. Feed documentation directly into the context window. +2. **[Add WDK project rules](#add-wdk-project-rules-optional)** - Give your AI assistant persistent context about package names, architecture, and coding patterns. + + +**Want to give AI agents wallet access?** The [MCP Toolkit](/ai/mcp-toolkit) creates an MCP server that exposes WDK wallets as tools, letting AI agents check balances, send transactions, swap tokens, bridge assets, and more. + + +--- + +## Connect WDK Docs via Markdown + +If your AI tool does not support a docs-specific connector, you can feed WDK documentation directly into the context window using these endpoints: + +| Endpoint | URL | Description | +|---|---|---| +| Page index | [docs.wdk.tether.io/llms.txt](/llms.txt) | Index of all page URLs and titles | +| Full docs | [docs.wdk.tether.io/llms-full.txt](/llms-full.txt) | Complete documentation in one file | + +You can also append `.md` to any documentation page URL to get raw Markdown, ready to paste into a chat context window. + +--- + +## Add WDK Project Rules (Optional) + +Project rules give your AI assistant persistent context about WDK conventions, package naming, and common patterns. This is optional, but recommended for teams working extensively with WDK. + +Copy the rules content below and save it at the file path for your tool. + +### Rules Content + +````markdown +# WDK Development Rules + +## Package Structure +- All WDK packages are published under the `@tetherto` scope on npm. +- Core module: `@tetherto/wdk`. +- Wallet modules follow the pattern: `@tetherto/wdk-wallet-`. + - Examples: `@tetherto/wdk-wallet-evm`, `@tetherto/wdk-wallet-btc`, `@tetherto/wdk-wallet-solana`, `@tetherto/wdk-wallet-ton`, `@tetherto/wdk-wallet-tron`, `@tetherto/wdk-wallet-spark`. +- Specialized wallet modules: `@tetherto/wdk-wallet-evm-erc-4337`, `@tetherto/wdk-wallet-ton-gasless`, `@tetherto/wdk-wallet-tron-gasfree`. +- Protocol modules follow the pattern: `@tetherto/wdk-protocol---`. + - Examples: `@tetherto/wdk-protocol-swap-velora-evm`, `@tetherto/wdk-protocol-bridge-usdt0-evm`, `@tetherto/wdk-protocol-lending-aave-evm`. + +## Platform Notes +- For Node.js or Bare runtime: use `@tetherto/wdk` as the orchestrator, then register individual wallet modules. +- For React Native: use the React Native provider package for convenience, or use WDK packages directly in the Hermes runtime. + +## Architecture +- WDK is modular: each blockchain and protocol is a separate npm package. +- Wallet modules expose `WalletManager`, `WalletAccount`, and `WalletAccountReadOnly` classes. +- `WalletAccount` extends `WalletAccountReadOnly`, so it has all read-only methods plus write methods such as sign and send. +- All modules follow a consistent pattern: configuration, initialization, usage. + +## Documentation +- Official docs: [docs.wdk.tether.io](/) +- For any WDK question, consult the official documentation before making assumptions. +- API references, configuration guides, and usage examples are available for every module. +```` + +### Where to Save + +| AI Coding Assistant | File Path | Notes | +|---|---|---| +| Cursor | `.cursor/rules/wdk.mdc` | Project-level, auto-attached | +| Claude Code | `CLAUDE.md` | Place in project root | +| Windsurf | `.windsurf/rules/wdk.md` | Project-level rules | +| GitHub Copilot | `.github/copilot-instructions.md` | Project-level instructions | +| Cline | `.clinerules` | Place in project root | +| Continue | `.continuerules` | Place in project root | + +--- + +## Agent Guidelines in WDK Repos + +Each WDK package repository can include an `AGENTS.md` file in its root. This file gives AI agents context about project structure, coding conventions, testing patterns, and linting rules. + +If your AI tool has access to WDK source repositories through a local clone, it can use `AGENTS.md` for additional context beyond the documentation. + +--- + +## Example Prompt + +Use a prompt like this to generate a multi-chain wallet with WDK. Include `/llms-full.txt` or the relevant quickstart pages in context for best results: + +```txt +Create a Node.js app using WDK (@tetherto/wdk) that: +1. Creates a multi-chain wallet supporting Bitcoin and Polygon. +2. Uses @tetherto/wdk-wallet-btc for Bitcoin and @tetherto/wdk-wallet-evm for Polygon. +3. Generates wallet addresses for both chains. +4. Retrieves the balance for each address. +5. Uses a mnemonic from environment variables. + +Check the WDK documentation for the correct configuration and initialization pattern. +``` + +--- + +## Tips for Effective AI-Assisted Development + +- **Be specific about the chain.** Tell the AI which blockchain you are targeting, such as "I am building on Ethereum using `@tetherto/wdk-wallet-evm`." +- **Reference the exact package name.** Mention the full `@tetherto/wdk-*` package name in your prompt for more accurate code generation. +- **Ask the AI to check docs first.** Prompt with "Check the WDK documentation before answering" to make it use the docs context instead of older training data. +- **Start with a quickstart.** Point the AI at the [Node.js & Bare Quickstart](/start-building/nodejs-bare-quickstart) or [React Native Quickstart](/start-building/react-native-quickstart) as a working reference before building custom features. +- **Iterate in steps.** Use the AI to scaffold your WDK integration first, then refine module configuration and error handling in follow-up prompts. + +## Need Help? + + + +*** + ## Node.js & Bare Runtime Quickstart URL: https://docs.wdk.tether.io/start-building/nodejs-bare-quickstart Description: Get started with WDK in Node.js or Bare runtime environments in 3 minutes @@ -28043,26 +38263,20 @@ import WalletManagerBtc from '@tetherto/wdk-wallet-btc' console.log('Starting WDK App...') -try { - // Your code will go here -} catch (error) { - console.error('Application error:', error.message) - process.exit(1) -} +// Your code will go here ``` Now, add the following code to generate a seed phrase: ```typescript title="app.js" -try { - const seedPhrase = WDK.getRandomSeedPhrase() - console.log('Generated seed phrase:', seedPhrase) -} catch (error) { - console.error('Application error:', error.message) - process.exit(1) -} +const seedPhrase = WDK.getRandomSeedPhrase() +console.log('Generated seed phrase:', seedPhrase) ``` + +For production apps, never log a real user seed phrase. This quickstart prints a generated phrase only so you can see the demo flow. For cleanup expectations, see [Seed Lifecycle](/sdk/core-module/guides/error-handling#seed-lifecycle). + + Now, let's register wallets for different blockchains: ```typescript title="app.js" @@ -28138,53 +38352,50 @@ import WalletManagerBtc from '@tetherto/wdk-wallet-btc' console.log('Starting WDK App...') -try { - const seedPhrase = WDK.getRandomSeedPhrase() - console.log('Generated seed phrase:', seedPhrase) +const seedPhrase = WDK.getRandomSeedPhrase() +console.log('Generated seed phrase:', seedPhrase) - console.log('Registering wallets...') - - const wdkWithWallets = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, { - provider: 'https://eth.drpc.org' - }) - .registerWallet('tron', WalletManagerTron, { - provider: 'https://api.trongrid.io' - }) - .registerWallet('bitcoin', WalletManagerBtc, { - network: 'mainnet', - host: 'electrum.blockstream.info', - port: 50001 - }) +console.log('Registering wallets...') - console.log('Wallets registered for Ethereum, TRON, and Bitcoin') +const wdkWithWallets = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' + }) + .registerWallet('tron', WalletManagerTron, { + provider: 'https://api.trongrid.io' + }) + .registerWallet('bitcoin', WalletManagerBtc, { + network: 'mainnet', + host: 'electrum.blockstream.info', + port: 50001 + }) - const accounts = { - ethereum: await wdkWithWallets.getAccount('ethereum', 0), - tron: await wdkWithWallets.getAccount('tron', 0), - bitcoin: await wdkWithWallets.getAccount('bitcoin', 0) - } +console.log('Wallets registered for Ethereum, TRON, and Bitcoin') - console.log('Resolving addresses:') +const accounts = { + ethereum: await wdkWithWallets.getAccount('ethereum', 0), + tron: await wdkWithWallets.getAccount('tron', 0), + bitcoin: await wdkWithWallets.getAccount('bitcoin', 0) +} - for (const [chain, account] of Object.entries(accounts)) { - const address = await account.getAddress() - console.log(` ${chain.toUpperCase()}: ${address}`) - } +console.log('Resolving addresses:') - console.log('Checking balances...') +for (const [chain, account] of Object.entries(accounts)) { + const address = await account.getAddress() + console.log(` ${chain.toUpperCase()}: ${address}`) +} - for (const [chain, account] of Object.entries(accounts)) { - const balance = await account.getBalance() - console.log(` ${chain.toUpperCase()}: ${balance.toString()} units`) - } +console.log('Checking balances...') - console.log('Application completed successfully!') - process.exit(0) -} catch (error) { - console.error('Application error:', error.message) - process.exit(1) +for (const [chain, account] of Object.entries(accounts)) { + const balance = await account.getBalance() + console.log(` ${chain.toUpperCase()}: ${balance.toString()} units`) } + +console.log('Application completed successfully!') + +// Close all wallet connections so the program can exit +wdkWithWallets.dispose() ``` *** @@ -28267,15 +38478,11 @@ wdk.registerWallet('solana', WalletManagerSolana, { ```typescript for (const [chain, account] of Object.entries(accounts)) { - try { - const quote = await account.quoteSendTransaction({ - to: await account.getAddress(), - value: chain === 'bitcoin' ? 100000000n : chain === 'tron' ? 1000000n : 1000000000000000000n - }) - console.log(` ${chain.toUpperCase()}: ${quote.fee.toString()} units`) - } catch (error) { - console.log(` ${chain.toUpperCase()}: Unable to estimate`) - } + const quote = await account.quoteSendTransaction({ + to: await account.getAddress(), + value: chain === 'bitcoin' ? 100000000n : chain === 'tron' ? 1000000n : 1000000000000000000n + }) + console.log(` ${chain.toUpperCase()}: ${quote.fee.toString()} units`) } ``` @@ -28454,9 +38661,15 @@ Integrate WDK into your existing React Native or Expo project using `@tetherto/w ### Step 1: Install ```bash -npm install @tetherto/wdk-react-native-core +npm install @tetherto/wdk-react-native-core react-native-bare-kit ``` + +The `v1.0.0-beta.15` npm artifact includes the React Native source entry at `src/index.ts`, but it does not include the declared default JavaScript or type declaration files under `dist/`. Use a React Native resolver that selects the package's `react-native` condition. Resolvers that select `default` or `types` target files that are not present in this artifact. + + +Starting in beta.14, `react-native-bare-kit` is a peer dependency of React Native Core and must be installed explicitly. + ### Step 2: Configure Android minSdkVersion The library requires **Android API 29** or higher to support `react-native-bare-kit`. @@ -28504,11 +38717,7 @@ buildscript { ### Step 3: Configure the Bundle -The WDK engine runs inside a Bare worklet. You need to provide a bundle - choose one of two approaches: - - - -Use the `@tetherto/wdk-worklet-bundler` CLI to generate a bundle with only the modules you need: +The WDK engine runs inside a Bare worklet. Use the `@tetherto/wdk-worklet-bundler` CLI to generate an HRPC bundle with only the modules you need: ```bash # 1. Install the bundler CLI @@ -28535,23 +38744,8 @@ import { bundle } from './.wdk' **Which WDK modules do I need?** Each blockchain requires its own wallet module (e.g., `wdk-wallet-evm-erc-4337` for Ethereum/Polygon, `wdk-wallet-btc` for Bitcoin). See the full list of available modules in the [wdk-worklet-bundler documentation](https://github.com/tetherto/wdk-worklet-bundler). - - -For quick prototyping, install and import the ready-made bundle from `@tetherto/pear-wrk-wdk`: - -```bash -npm install @tetherto/pear-wrk-wdk -``` - -```typescript -import { bundle } from '@tetherto/pear-wrk-wdk' -``` - -The pre-built bundle includes all blockchain modules, resulting in a larger bundle size. For production apps, generate a custom bundle with only the modules you need. - - - +`@tetherto/pear-wrk-wdk` provides worklet transport and runtime handlers; it does not export a pre-built WDK bundle. ### Step 4: Configure WDK Settings @@ -28830,7 +39024,7 @@ Some native dependencies may lack type definitions. Add to your `tsconfig.json`: **For Expo projects:** -- Install `@tetherto/wdk-react-native-core` +- Install `@tetherto/wdk-react-native-core` and its `react-native-bare-kit` peer dependency - Configure Android minSdkVersion to 29 in `app.json` - Set up bundle (custom or pre-built) - Create `WdkConfigs` configuration @@ -28840,7 +39034,7 @@ Some native dependencies may lack type definitions. Add to your `tsconfig.json`: **For bare React Native:** -- Install package +- Install `@tetherto/wdk-react-native-core` and its `react-native-bare-kit` peer dependency - Set minSdkVersion to 29 in `android/build.gradle` - Set up bundle (custom or pre-built) - Create `WdkConfigs` configuration @@ -28870,6 +39064,369 @@ Ready to dive deeper? Check out these resources: *** +## Asset Registry +URL: https://docs.wdk.tether.io/tools/asset-registry +Description: Standardized asset metadata and lookup helpers for @tetherto/wdk-asset-registry + +Asset Registry provides in-memory registries for standardized asset metadata. Use `@tetherto/wdk-asset-registry` when a wallet, protocol, UI, or indexing flow needs consistent asset identifiers, symbols, decimals, chain IDs, and token contract addresses without adding an RPC dependency. + +Powered by [`@tetherto/wdk-asset-registry`](https://github.com/tetherto/wdk-asset-registry). + +## Features + +- **Base asset registry**: Register and look up assets by `id` or by partial metadata filters. +- **Token asset registry**: Work with token metadata including `address`, `symbol`, `name`, `decimals`, and `isNative`. +- **Bundled asset lists**: Import predefined JSON metadata from `@tetherto/wdk-asset-registry/assets/common-tokens`. +- **Zod-backed schemas**: Validate base and token asset records before registration. +- **Uniswap token-list helpers**: Normalize Uniswap-style token entries into WDK `TokenAsset` records. +- **No RPC dependency**: The package stores and searches metadata locally. It does not read balances, prices, or chain state. + +## Why this matters + +- Share one asset identity model across WDK wallet modules, pricing flows, protocol integrations, and UI surfaces. +- Normalize token metadata before it reaches send, swap, bridge, or display logic. +- Keep static metadata lookup separate from live providers that fetch balances, prices, quotes, or contract state. + + + +Install the package, preload common tokens, and register local metadata. + + +Review registry classes, schemas, helper functions, and result types. + + + +*** + +## Need Help? + + + +*** + +## Asset Registry API Reference +URL: https://docs.wdk.tether.io/tools/asset-registry/api-reference +Description: Registry classes, schemas, helpers, and types for @tetherto/wdk-asset-registry + +## Package: `@tetherto/wdk-asset-registry` + +### Exports + +| Export | Description | +| --- | --- | +| `default` | Base registry class for assets that include `id` and `chainId`; import it as `WdkBaseAssetRegistry` | +| `WdkTokenAssetRegistry` | Token registry class with token-specific lookup helpers | +| `BaseAssetSchema` | Zod schema for base asset records | +| `BaseAssetJsonSchema` | JSON schema for base asset records | +| `TokenAssetSchema` | Zod schema for token asset records | +| `TokenAssetJsonSchema` | JSON schema for token asset records | +| `fromUniswapToken(token)` | Convert one Uniswap token-list entry into a `TokenAsset` | +| `fromUniswapTokenList(tokens)` | Convert a list of Uniswap token-list entries into `TokenAsset[]` | +| `AssetRegistryError` | Error class for registry-level failures such as duplicate asset IDs | + +## Class: `WdkBaseAssetRegistry` + +In-memory registry for `BaseAsset`-compatible records. + +### Constructor + +```javascript title="Create A Base Registry" +import WdkBaseAssetRegistry from '@tetherto/wdk-asset-registry' + +const registry = new WdkBaseAssetRegistry(initialAssets) +``` + +Pass zero or more asset arrays to preload records. + +### Methods + +| Method | Description | Returns | +| --- | --- | --- | +| `registerAsset(asset, upsert?)` | Validate and register one asset by `asset.id` | `void` | +| `registerAssets(assets, upsert?)` | Register a list of assets | `void` | +| `getAssets()` | Return all registered assets | `BaseAsset[]` | +| `getAssetById(id)` | Return one asset by exact ID | `BaseAsset \| null` | +| `getAsset(filter, options?)` | Return all assets matching one or more partial filters | `BaseAsset[]` | + +#### `registerAsset(asset, upsert?)` + +Validates the asset before registration. Duplicate IDs throw `AssetRegistryError` unless `upsert` is `true`. + +```javascript title="Register A Base Asset" +registry.registerAsset({ + id: 'eip155:1', + chainId: 'eip155:1' +}) +``` + +#### `registerAssets(assets, upsert?)` + +Registers each asset by calling `registerAsset()`. + +```javascript title="Register Multiple Assets" +registry.registerAssets([ + { id: 'eip155:1', chainId: 'eip155:1' }, + { id: 'eip155:137', chainId: 'eip155:137' } +]) +``` + +#### `getAssetById(id)` + +Looks up an asset by exact `id`. + +```javascript title="Get Asset By ID" +const asset = registry.getAssetById('eip155:1') +``` + +#### `getAsset(filter, options?)` + +Finds assets that match one or more partial filters. Pass an array of filter objects. Keys inside one filter object are AND conditions. Multiple filter objects are OR conditions. + +```javascript title="Filter Assets" +const byChain = registry.getAsset([ + { chainId: 'eip155:1' } +]) + +const byOneOfMany = registry.getAsset([ + { chainId: 'eip155:1' }, + { chainId: 'eip155:137' } +]) +``` + +String comparisons in `getAsset()` are case-insensitive by default. Pass `{ caseSensitive: true }` to require exact casing. + +## Class: `WdkTokenAssetRegistry` + +Token-specific registry built on `WdkBaseAssetRegistry`. + +```javascript title="Create A Token Registry" +import { WdkTokenAssetRegistry } from '@tetherto/wdk-asset-registry' +import commonTokens from '@tetherto/wdk-asset-registry/assets/common-tokens' with { type: 'json' } + +const registry = new WdkTokenAssetRegistry(commonTokens) +``` + +### Methods + +| Method | Description | Returns | +| --- | --- | --- | +| `getTokens()` | Return all registered token assets | `TokenAsset[]` | +| `getTokenById(id)` | Return one token by exact ID | `TokenAsset \| null` | +| `getTokenByAddress(address, options?)` | Return all tokens matching an address; exact casing is required by default | `TokenAsset[]` | +| `getTokenBySymbol(symbol, options?)` | Return all tokens matching a symbol | `TokenAsset[]` | +| `getTokenByChain(chainId, options?)` | Return all tokens matching a chain ID | `TokenAsset[]` | + +```javascript title="Token Lookups" +const usdtTokens = registry.getTokenBySymbol('USDT') +const tokensByAddress = registry.getTokenByAddress('0xdAC17F958D2ee523a2206206994597C13D831ec7') +const tokensByAddressIgnoringCase = registry.getTokenByAddress( + '0xdac17f958d2ee523a2206206994597c13d831ec7', + { caseSensitive: false } +) +const chainTokens = registry.getTokenByChain('eip155:1') +``` + +`getTokenByAddress()` is case-sensitive by default. Pass `{ caseSensitive: false }` when the registry and lookup input may use different casing. Symbol and chain lookups remain case-insensitive by default. + +## Helper Functions + +### `fromUniswapToken(token)` + +Converts one Uniswap token-list entry into a `TokenAsset`. + +```javascript title="Normalize One Token" +import { fromUniswapToken } from '@tetherto/wdk-asset-registry' + +const token = fromUniswapToken({ + chainId: 1, + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + symbol: 'USDT', + name: 'Tether USD', + decimals: 6 +}) +``` + +### `fromUniswapTokenList(tokens)` + +Converts a token-list array into `TokenAsset[]`. + +```javascript title="Normalize A Token List" +import { fromUniswapTokenList } from '@tetherto/wdk-asset-registry' + +const tokens = fromUniswapTokenList(uniswapTokenList.tokens) +``` + +## Types + +### `BaseAsset` + +```typescript +type BaseAsset = { + id: string + chainId: string | number +} +``` + +### `TokenAsset` + +```typescript +type TokenAsset = BaseAsset & { + address: string + symbol: string + name: string + decimals: number + isNative: boolean +} +``` + +### `BaseAssetOptions` + +```typescript +type BaseAssetOptions = { + caseSensitive?: boolean +} +``` + +The address helper accepts the same `{ caseSensitive?: boolean }` shape at runtime. The `TokenAddressLookupOptions` declaration is not re-exported from the package's top-level type entrypoint in `v1.0.0-beta.2`, so use an inline object when importing from the package root. + +### `BaseAssetFilter` + +```typescript +type BaseAssetFilter = Partial +``` + +## Bundled Assets + +`@tetherto/wdk-asset-registry/assets/common-tokens` exports bundled token metadata. In `v1.0.0-beta.2`, the published list contains USDT, USDT0, and XAUt token records across supported chains. + +## Errors + +| Error | Cause | +| --- | --- | +| `AssetRegistryError` | Registry-level failure, such as duplicate asset registration without `upsert` | +| `ZodError` | Invalid asset shape passed to registration or normalization helpers | + +## Notes + +- Exact ID lookups return `null` when no asset matches. Filter and token-helper lookups return an empty array. +- `registerAssets()` validates and registers each asset sequentially. It is not an atomic batch transaction. +- The token registry validates records with `TokenAssetSchema`. +- Asset registry metadata is static and local; use live providers for balances, prices, quotes, chain state, or contract validation. + +*** + +## Need Help? + + + +*** + +## Asset Registry Configuration +URL: https://docs.wdk.tether.io/tools/asset-registry/configuration +Description: Install and configure @tetherto/wdk-asset-registry for local asset metadata lookup + +`@tetherto/wdk-asset-registry` has no API keys, environment variables, RPC providers, or runtime service configuration. Create an in-memory registry, preload optional asset lists, and register any project-specific asset metadata your app needs. + +## Install the package + +```bash title="Install @tetherto/wdk-asset-registry" +npm install @tetherto/wdk-asset-registry +``` + +## Create a token registry + +Import the token registry and preload the bundled common-token metadata. + +```javascript title="Create A Token Registry" +import { WdkTokenAssetRegistry } from '@tetherto/wdk-asset-registry' +import commonTokens from '@tetherto/wdk-asset-registry/assets/common-tokens' with { type: 'json' } + +const registry = new WdkTokenAssetRegistry(commonTokens) + +const usdt = registry.getTokenBySymbol('USDT') +``` + + +Plain Node.js ESM requires the JSON import attribute. Some bundlers can load the same asset JSON without `with { type: 'json' }`. + + +## Register project assets + +Use `registerAsset()` for one token or `registerAssets()` for a list. Duplicate IDs throw `AssetRegistryError` unless you pass `true` as the `upsert` argument. + +```javascript title="Register A Token" +registry.registerAsset({ + id: 'eip155:1/0xdAC17F958D2ee523a2206206994597C13D831ec7', + chainId: 'eip155:1', + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + symbol: 'USDT', + name: 'Tether USD', + decimals: 6, + isNative: false +}) + +registry.registerAsset( + { + id: 'eip155:1/0xdAC17F958D2ee523a2206206994597C13D831ec7', + chainId: 'eip155:1', + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + symbol: 'USDT', + name: 'Tether USD', + decimals: 6, + isNative: false + }, + true +) +``` + +## Look up tokens + +Symbol and chain lookups are case-insensitive by default. Address lookups are case-sensitive by default in `v1.0.0-beta.2`; pass `{ caseSensitive: false }` only when the registry and lookup input may use different casing. + +```javascript title="Lookup Tokens" +const bySymbol = registry.getTokenBySymbol('usdt') +const byAddress = registry.getTokenByAddress('0xdAC17F958D2ee523a2206206994597C13D831ec7') +const byAddressIgnoringCase = registry.getTokenByAddress( + '0xdac17f958d2ee523a2206206994597c13d831ec7', + { caseSensitive: false } +) +const byChain = registry.getTokenByChain('eip155:1') +const exactSymbol = registry.getTokenBySymbol('USDT', { caseSensitive: true }) +``` + +## Normalize token lists + +Use the Uniswap helpers when importing token metadata from a token-list compatible source. + +```javascript title="Normalize Uniswap Tokens" +import { + WdkTokenAssetRegistry, + fromUniswapTokenList +} from '@tetherto/wdk-asset-registry' + +const normalizedTokens = fromUniswapTokenList(uniswapTokenList.tokens) +const registry = new WdkTokenAssetRegistry(normalizedTokens) +``` + +`fromUniswapToken()` maps numeric EVM chain IDs to `eip155:`, builds token IDs as `/
`, sets `isNative` to `false`, and validates the result as a `TokenAsset`. + +## Runtime notes + +- Registries are in-memory. Persist custom asset lists in your application storage if they must survive process restarts. +- `common-tokens` is static bundled metadata. It is not a freshness guarantee. +- The package does not fetch balances, prices, quotes, token images, chain state, or contract verification data. +- `TokenAsset` requires `id`, `chainId`, `address`, `symbol`, `name`, `decimals`, and `isNative`. +- `decimals` must be an integer between `0` and `255`. +- Uniswap helpers normalize only fields required by `TokenAsset`; fields such as `logoURI`, `tags`, and `extensions` are not preserved. + +*** + +## Need Help? + + + +*** + ## Create WDK Module URL: https://docs.wdk.tether.io/tools/create-wdk-module Description: CLI scaffolding tool to generate new WDK wallet and protocol modules with a single command. @@ -28912,7 +39469,7 @@ npm test ## Module Types -The CLI supports all six WDK module categories. The generated package name follows WDK naming conventions automatically. +The CLI supports seven WDK module categories. The generated package name follows WDK naming conventions automatically. | Type | Description | Generated Package Example | |------|-------------|---------------------------| @@ -28922,6 +39479,7 @@ The CLI supports all six WDK module categories. The generated package name follo | `lending` | DeFi lending protocol | `wdk-protocol-lending-compound-evm` | | `fiat` | Fiat on/off-ramp provider | `wdk-protocol-fiat-moonpay` | | `swidge` | Cross-chain swap (swap + bridge combined) | `wdk-protocol-swidge-lifi` | +| `sda` | Smart Deposit Address provider | `wdk-protocol-sda-rhino` | --- @@ -28929,16 +39487,20 @@ The CLI supports all six WDK module categories. The generated package name follo | Option | Alias | Description | Default | |--------|-------|-------------|---------| -| `[type]` | | Module type (wallet, swap, bridge, lending, fiat, swidge) | (interactive prompt) | +| `[type]` | | Module type (wallet, swap, bridge, lending, fiat, swidge, sda) | (interactive prompt) | | `[name]` | | Module or protocol name | (interactive prompt) | | `[blockchain]` | | Target blockchain | (interactive prompt) | | `--scope \` | `-s` | npm scope (e.g., `@myorg`) | none | | `--git` | | Initialize a git repository | `true` | | `--no-git` | | Skip git initialization | | | `--yes` | `-y` | Skip prompts, use defaults | `false` | -| `--version` | `-v` | Show version | | +| `--version` | `-V` | Show version | | | `--help` | `-h` | Show help | | + +In `1.0.0-beta.3`, `--version` still prints the hard-coded beta.1 value. Use npm or the installed package metadata when you need the actual installed version. + + ### Examples ```bash title="Common CLI usage patterns" @@ -28948,6 +39510,9 @@ npx @tetherto/create-wdk-module@latest wallet stellar --scope @myorg # Create a swap protocol module npx @tetherto/create-wdk-module@latest swap jupiter solana +# Create an SDA provider module (provider name only; no blockchain argument) +npx @tetherto/create-wdk-module@latest sda rhino + # Create with all defaults, no prompts npx @tetherto/create-wdk-module@latest wallet stellar --yes ``` @@ -28970,6 +39535,7 @@ $ npx @tetherto/create-wdk-module@latest Lending Module (DeFi lending protocol) Fiat Module (fiat on/off-ramp) Swidge Module (cross-chain swap (swidge = swap + bridge)) + SDA Module (smart deposit address) ? What is the blockchain name? (e.g., "stellar", "solana") > stellar @@ -29048,7 +39614,7 @@ Wallet modules generate three core files that correspond to the WDK wallet archi - + ```text title="Protocol module project tree" wdk-protocol-swap-jupiter-solana/ @@ -29078,7 +39644,11 @@ wdk-protocol-swap-jupiter-solana/ └── tsconfig.json ``` -Protocol modules (swap, bridge, lending, fiat, swidge) generate a single provider file rather than the three-file wallet pattern, since protocols interact with external services through a unified interface. +Protocol modules (swap, bridge, lending, fiat, swidge, sda) generate a single provider file rather than the three-file wallet pattern, since protocols interact with external services through a unified interface. An SDA invocation such as `sda rhino` generates `wdk-protocol-sda-rhino` with `src/rhino-protocol.js`; it asks for a provider name rather than a blockchain. + + +The beta.3 SDA scaffold is a starting point, not an operational provider. Implement the required `getSupportedRoutes()` and `createDepositAddress()` methods, replace the generated `test.todo` cases, and either implement or delete every optional method stub before publishing. Empty optional stubs override the base class and return `undefined`; deleting an unsupported optional override restores the base `UnsupportedOperationError` behavior. The generated `apiUrl` and `apiKey` fields are placeholders, so replace them with the provider's actual configuration and document custody, authentication, routes, output assets, expiry, and recovery behavior. + @@ -29121,7 +39691,7 @@ Powered by [`@tetherto/wdk-failover-provider`](https://github.com/tetherto/wdk-f - **Generic provider wrapper**: `FailoverProvider\` accepts any provider-like object type and returns a proxied `T` from `initialize()`. - **Configurable retries**: Set `retries` to control how many additional attempts happen after the first failure. - **Retry predicate**: Use `shouldRetryOn(error)` to decide which errors should advance to the next provider. -- **Sync and async failover**: The runtime retries both synchronous throws and rejected promises. +- **Sync and async failover**: The runtime retries synchronous throws and rejected promise-like results whose `then` property is a function. - **Zero runtime dependencies**: The published package ships a single default export with no runtime dependency tree. ## Why this matters @@ -29130,6 +39700,10 @@ Powered by [`@tetherto/wdk-failover-provider`](https://github.com/tetherto/wdk-f - You can narrow failover behavior to transient errors instead of retrying every exception. - You can add redundancy to read-heavy or submission-heavy flows without building your own retry proxy. + +The returned proxy forwards property reads and method calls. It does not forward writes, enumeration, or property-descriptor operations to the active provider. + + Install the package, add provider candidates, and tune retry behavior. @@ -29171,7 +39745,7 @@ new FailoverProvider({ ``` - `retries` (`number`, optional): Number of additional attempts after the first failure. Total attempts are `1 + retries`. -- `shouldRetryOn` (`(error: Error) =\> boolean`, optional): Predicate that decides whether the proxy should switch to the next provider after a failure. +- `shouldRetryOn` (`(error: unknown) =\> boolean`, optional): Predicate that decides whether the proxy should switch to the next provider after a failure. #### Methods @@ -29219,13 +39793,19 @@ The package exports the `FailoverProviderConfig` type through its top-level type ## Runtime behavior -- `initialize()` returns a JavaScript `Proxy` over the first added provider. +- `initialize()` returns a JavaScript `Proxy` with a neutral target whose property reads are delegated to the active provider. - Non-function properties are forwarded from the currently active provider. +- Getters and methods run with the underlying provider as `this`, so instance state and private fields remain available. - If a property getter throws and `shouldRetryOn(error)` returns `true`, the runtime advances to the next provider before retrying the property access. - If a synchronous method throws and `shouldRetryOn(error)` returns `true`, the runtime advances to the next provider and retries the method call. - If an asynchronous method rejects and `shouldRetryOn(error)` returns `true`, the runtime advances to the next provider and retries the method call. +- A result is treated as promise-like only when its `then` property is a function. An object with a non-function `then` property is returned synchronously. - Provider switching is round-robin. If the active provider already changed while another call was failing, the runtime keeps the newer active provider instead of advancing twice. + +The proxy implements property reads only. Writes, enumeration, and property-descriptor operations are not forwarded to the active provider. + + ## Example This example shows a transient failure on the first provider and a successful retry on the second provider: @@ -29247,7 +39827,8 @@ const secondary = { const provider = new FailoverProvider({ retries: 1, - shouldRetryOn: (error) => error.message.includes('temporary') + shouldRetryOn: (error) => + error instanceof Error && error.message.includes('temporary') }) .addProvider(primary) .addProvider(secondary) @@ -29301,7 +39882,8 @@ const secondary = { const provider = new FailoverProvider({ retries: 1, - shouldRetryOn: (error) => error.message.includes('temporary') + shouldRetryOn: (error) => + error instanceof Error && error.message.includes('temporary') }) .addProvider(primary) .addProvider(secondary) @@ -29323,7 +39905,8 @@ Use a narrow predicate when you only want to retry transient failures: ```javascript title="Retry Only On Transient Errors" const provider = new FailoverProvider({ retries: 2, - shouldRetryOn: (error) => /timeout|temporary|429/.test(error.message) + shouldRetryOn: (error) => + error instanceof Error && /timeout|temporary|429/.test(error.message) }) ``` @@ -29362,10 +39945,16 @@ factory.initialize() ## Runtime notes - Non-function properties are read from the currently active provider. +- Property getters and methods run with the underlying provider as `this`, including providers that use private fields or instance state. - When a synchronous method throws and `shouldRetryOn(error)` returns `true`, the proxy switches to the next provider and retries. - When an asynchronous method rejects and `shouldRetryOn(error)` returns `true`, the proxy switches to the next provider and retries. +- A method result is treated as asynchronous only when its `then` property is a function. Other values, including objects with a non-function `then` property, are returned synchronously. - Provider selection advances in round-robin order. If `retries` is larger than the number of providers, the runtime loops back through the list. + +Use the initialized proxy for property reads and method calls. Writes, enumeration, and property-descriptor operations apply to the proxy target rather than the active provider. + + *** ## Need Help? @@ -29449,7 +40038,7 @@ x-api-key: your-api-key-here ``` -**Don't have an API key yet?** Request one by following the steps in our [Get Started](get-started) guide. +**Don't have an API key yet?** Request one by following the steps in our [Get Started](/tools/indexer-api/get-started/) guide. *** @@ -29530,7 +40119,7 @@ The API returns standard HTTP error codes: ## Next Steps -* [**Get Started**](get-started) - Quick start guide with setup instructions +* [**Get Started**](/tools/indexer-api/get-started/) - Quick start guide with setup instructions * [**React Native Starter**](/examples-and-starters/react-native-starter) - See it in action *** @@ -29603,14 +40192,14 @@ console.log(`Balance: ${balance.amount} ${balance.token.toUpperCase()}`); -**Want more info?** Check out the complete [API Reference](api-reference) for detailed method documentation, parameters, and response formats. +**Want more info?** Check out the complete [API Reference](/tools/indexer-api/api-reference/) for detailed method documentation, parameters, and response formats. *** ## Next Steps -* [**API Reference**](api-reference) - Complete method documentation with examples and response formats +* [**API Reference**](/tools/indexer-api/api-reference/) - Complete method documentation with examples and response formats *** @@ -29620,11 +40209,485 @@ console.log(`Balance: ${balance.amount} ${balance.token.toUpperCase()}`); *** +## P2P Address Book +URL: https://docs.wdk.tether.io/tools/p2p-address-book +Description: Store and synchronize wallet contacts across a user's devices with encrypted peer-to-peer replication + +`@tetherto/wdk-p2p-address-book` gives a wallet application a user-specific directory for named contacts and payment destinations. It is an optional WDK utility, not a wallet implementation or a required part of every WDK application. + +Devices that use the same wallet seed and application namespace can open the same logical address book and enroll their own writers. Updates replicate between those devices through Holepunch networking. This is same-user device synchronization—not a public contact directory or a protocol for sharing contacts with other users. + +The recommended `fromSeed()` workflow encrypts replicated address-book blocks before they leave the device. A blind peer can retain those blocks for recovery without receiving the content key, but the wallet operator must still plan for peer availability, local storage protection, and key recovery. + +## When to use it + +Use the P2P Address Book when: + +- A wallet needs named recipients instead of repeated address entry. +- The same user needs contacts on multiple devices. +- The product wants cross-device sync without a central plaintext contacts database. +- The team can operate or depend on the P2P availability needed for recovery. + +Choose a conventional contacts service when the product requires server-side plaintext search, moderation, analytics, or account-based recovery. This package is also a poor fit when its beta status, native dependencies, or eventual-synchronization model do not meet the product's requirements. + +The package stores destinations; it does not validate chain syntax, resolve identifiers, prove address ownership, or send payments. Validate a selected destination before using it in a wallet flow. See [WDK Utils](/tools/wdk-utils/) for available validators and parsers. + +## How it works + +1. The wallet opens a persistent Corestore and calls `AddressBook.fromSeed()` with wallet seed bytes and a stable namespace. +2. The first device calls `create()` to enroll its device-specific writer. +3. The wallet can register a blind-peer public key with `addMirror()` for offline availability. +4. Another device uses the same seed and namespace, but its own Corestore, and calls `addMirror()` to restore the existing history and enroll its writer. +5. Authorized writers append operations. Autobase produces the shared operation order and HyperDB exposes the current contacts and addresses. + +derived on each device"] + A["Device A
Corestore + writer"] + B["Device B
Corestore + writer"] + M["Blind peer
encrypted replicated blocks
no content key"] + U["Unrelated wallet
different identity"] + + I --> A + I --> B + A <-->|"Direct Hyperswarm replication"| B + A -->|"Mirror blocks"| M + M -->|"Restore history"| B + U -. "Cannot derive this book or content key" .-> M`} +/> + +Random peers do not automatically store every address book. A blind peer is an explicitly configured availability peer. + +## Builder path + + + +Install the package and create a local address book with explicit resource cleanup. + + +Enroll devices, configure blind peers, and understand recovery guarantees. + + +Review public methods, record shapes, configuration, and data semantics. + + +Plan availability, storage, privacy, deletion, and beta-version operations. + + + +## Related resources + + + +Review the released implementation, declarations, and tests. + + +Validate supported destinations before storing or using them. + + +Build advanced Bare worklet integrations for supported host applications. + + +Set up the wider WDK runtime before adding optional tools. + + + +*** + +## Need Help? + + + +*** + +## P2P Address Book API Reference +URL: https://docs.wdk.tether.io/tools/p2p-address-book/api-reference +Description: Public API, records, configuration, and data semantics for @tetherto/wdk-p2p-address-book. + +## Exports + +```javascript +import AddressBook, { + AddressBook as NamedAddressBook, + ADDRESS_TYPES +} from '@tetherto/wdk-p2p-address-book' +``` + +The default and named `AddressBook` exports refer to the same class. + +## Construction + +| API | Description | +|---|---| +| `AddressBook.fromSeed(seed, corestore, options)` | Derives the book identity and encryption material from seed bytes and a namespace, then opens the local book. | +| `new AddressBook(corestore, options?)` | Low-level constructor for advanced integrations. | +| `AddressBook.createWorkletModule({ seed, config })` | Creates a worklet-compatible module with seed-isolated storage. | +| `AddressBook.deriveAutobaseKey(keyPair, options?)` | Derives the public Autobase key from a bootstrap public key or key pair. | +| `AddressBook.selectMirrors(autobaseKey, pool, n?)` | Deterministically selects up to `n` mirror keys from a pool. | + +Prefer `fromSeed()`. The low-level constructor permits replication without an encryption key; advanced callers must supply `encryptionKey` before opening or set `replicate: false`. + +### `fromSeed()` options + +| Option | Required | Behavior | +|---|---|---| +| `namespace` | Yes | Stable application scope for book identity, encryption derivation, and Corestore isolation. | +| `replicate` | No | Defaults to `true`; set `false` for local-only use. | +| `mirrors` | No | Blind-peer keys used for peering. Does not replace `addMirror()` during restoration. | +| `timeout` | No | Bootstrap and enrollment wait. Defaults to 20 seconds. | +| `name` | No | Label stored for the enrolling device writer. | +| `bootstrap`, `swarm`, `relayThrough` | No | Advanced networking hooks whose concrete types are not declared stable. `close()` destroys a supplied swarm. | + +The declared low-level `optimistic` option is ignored in `1.0.0-beta.2`; Autobase optimistic mode is always enabled. + +## Lifecycle and identity + +| Surface | Description | +|---|---| +| `ready()` | Opens the local book. In beta.2, initial swarm flush and peering continue in the background. | +| `create()` | Enrolls the local writer for a new book without attempting restoration. | +| `close()` | Closes book-owned Autobase, peering, and swarm resources. | +| `suspend()` / `resume()` | Suspends or resumes networking resources. | +| `getInfo()` | Returns `{ autobaseKey, writable }`. | +| `writerKey` | Current device writer key. | +| `key` | Public Autobase key. | +| `discoveryKey` | Discovery key used for replication. | +| `writable` | Whether the current device writer is enrolled. | +| `encryptionKey` | Content-encryption key. Treat it as secret. | + +## Contact and address records + +| Record | Required input | Optional input | Generated fields | +|---|---|---|---| +| Contact | `name` | `username` | `id`, `createdAt`, `updatedAt` | +| Address | `address`, `type`, `network` | `label` | `id`, `contactId`, `createdAt`, `updatedAt` | + +Supported address types are: + +```javascript +ADDRESS_TYPES.BITCOIN +ADDRESS_TYPES.EVM +ADDRESS_TYPES.TRON +ADDRESS_TYPES.UMA +ADDRESS_TYPES.LIGHTNING_ADDRESS +ADDRESS_TYPES.LNURL +ADDRESS_TYPES.SPARK +``` + +The package trims text fields and lowercases `network`, but it does not validate destination syntax. An address and normalized network pair must be unique; UMA values must also be unique across UMA records. Address comparisons are case-sensitive. + +Contact names and usernames do not need to be unique. The schema has no first-class email or custom-field support. + +## Contact and address methods + +| Area | Methods | +|---|---| +| Contacts | `addContact()`, `editContact()`, `deleteContact()`, `getContact()`, `listContacts()` | +| Addresses | `addAddress()`, `editAddress()`, `deleteAddress()`, `listAddresses()` | +| Query and events | `search()`, `on('update')`, `off('update')` | + +- `listContacts()` sorts by name. `listAddresses()` does not promise an order. +- `search()` matches contact names, addresses, and address labels without case sensitivity. It does not search usernames, networks, or types. +- Deleting a contact removes it and its currently visible addresses from the materialized view. +- `update` events have no record payload. Refetch the required view. + + +Conflicts follow Autobase's accepted operation order, not `updatedAt`. A later accepted contact update can recreate a deleted contact, and a delayed address operation can appear after its parent contact was deleted. The beta release has no tombstone or revision mechanism for preventing these outcomes. + + +## Writer methods + +| Method | Description | +|---|---| +| `addWriter(data)` | Adds a writer key or writer record. | +| `removeWriter(key)` | Removes current write membership. | +| `listWriters(query?)` | Returns a HyperDB query for writer records. | +| `getWriter(key)` | Returns a matching writer or `null`. | + +Writer-management methods are not included in the exported worklet bridge interface. + +## Mirror methods + +| Method | Description | +|---|---| +| `addMirror(key)` | Registers one mirror, peers with it, and enrolls a deferred local writer. Returns the selected key in an array. | +| `addMirror(pool, n?)` | Selects up to `n` entries, then registers and peers with them. | +| `listMirrors()` | Returns persisted mirror records. | +| `removeMirror(key)` | Removes a persisted mirror record. See the beta limitation below. | + + +In `1.0.0-beta.2`, `removeMirror()` leaves the key active in the current in-memory peering instance. Close and reopen the book without that key before treating peering as stopped. The method does not delete blocks already held by the server. + + +## Worklet API + +The worklet bridge exposes `getInfo()`, `create()`, contact and address CRUD, `search()`, and mirror methods. Byte-array mirror keys are normalized to strings by the bridge. Writer-management methods and raw key properties are direct-API surfaces only. + +## Related guides + +- [Get Started](/tools/p2p-address-book/get-started) +- [Sync and Recovery](/tools/p2p-address-book/sync-and-recovery) +- [Production and Privacy](/tools/p2p-address-book/production-and-privacy) + +*** + +## P2P Address Book Get Started +URL: https://docs.wdk.tether.io/tools/p2p-address-book/get-started +Description: Install @tetherto/wdk-p2p-address-book and create a local wallet contact directory. + +This guide creates a local address book without peer replication. Start here before enrolling other devices or configuring blind peers. + +## Prerequisites + +You need: + +- Wallet seed bytes as a `Uint8Array`, not mnemonic text. +- A stable application namespace. +- A persistent writable Corestore directory dedicated to that wallet seed and device. +- A Node.js ESM or tested Bare environment with native-module support. +- Network access when replication is enabled. + +Install the Address Book and declare Corestore because application code constructs it directly: + +```bash title="Install P2P Address Book" +npm install @tetherto/wdk-p2p-address-book corestore +``` + +## Create a local address book + +The following example disables networking, creates a new book, stores a caller-validated destination, reads the records, and closes both package-owned and caller-owned resources: + +```javascript title="local-address-book.js" +import Corestore from 'corestore' +import AddressBook, { + ADDRESS_TYPES +} from '@tetherto/wdk-p2p-address-book' + +export async function createLocalBook(seed, storagePath, recipientAddress) { + const store = new Corestore(storagePath) + let book + + try { + book = await AddressBook.fromSeed(seed, store, { + namespace: 'example-wallet', + replicate: false + }) + await book.create() + + const contact = await book.addContact({ + name: 'Alice', + username: 'alice' + }) + + await book.addAddress(contact.id, { + address: recipientAddress, + type: ADDRESS_TYPES.EVM, + network: 'ethereum', + label: 'Primary wallet' + }) + + return { + contact: await book.getContact(contact.id), + addresses: await book.listAddresses(contact.id) + } + } finally { + try { + if (book) await book.close() + } finally { + await store.close() + } + } +} +``` + +Use `create()` only when the book is known to be new. To join an existing book, follow [Sync and Recovery](/tools/p2p-address-book/sync-and-recovery) and call `addMirror()` instead. + +## Understand readiness + +`fromSeed()` opens the local Autobase before resolving. With replication enabled, it also joins the Hyperswarm discovery topic. + + +In `1.0.0-beta.2`, the initial swarm flush and blind-peer setup continue in the background after `ready()` and `fromSeed()` resolve. Errors from that background startup are not surfaced to the caller. Do not treat either promise as proof that a peer is reachable or that replication has completed. + + +`addMirror()` has stronger semantics for a device joining an existing book: it awaits peering, waits for existing genesis, and enrolls the local writer. It still does not prove that every record has been restored or retained remotely. + +## Close resources + +`book.close()` closes the book's networking and replication resources. The direct API does not close the root Corestore supplied by the application, so close that store separately as shown above. + +If you supply a custom swarm through configuration, `book.close()` destroys it. Do not share that swarm with another owner that expects to keep using it. + +## Next steps + +Continue with [Sync and Recovery](/tools/p2p-address-book/sync-and-recovery), or review exact record and method behavior in the [API Reference](/tools/p2p-address-book/api-reference). + +*** + +## P2P Address Book Production and Privacy +URL: https://docs.wdk.tether.io/tools/p2p-address-book/production-and-privacy +Description: Plan blind-peer availability, storage, privacy, deletion, and upgrades for the P2P Address Book. + +P2P replication reduces central readable data custody. It does not eliminate infrastructure, operational responsibilities, or privacy risk. + +## Compare it with a CRUD backend + +| Area | P2P Address Book | Conventional CRUD backend | +|---|---|---| +| Primary storage | User devices and configured replicas | Application database | +| Cross-device sync | Peer replication | Server API | +| Blind-peer plaintext access | Blind-peer process is not given the content key; wallet process can read records | Usually available to the server unless separately encrypted | +| Offline writes | Enrolled devices can append locally | Requires an application-specific offline queue | +| Recovery | Correct keys plus an available history | Account authentication plus retained server data | +| Server-side processing | Plaintext indexing is unavailable to a blind peer | Straightforward | +| Operations | Peer availability, keys, local storage, and recovery | API, database, authentication, and backups | + +## Operate blind peers + +The package includes the blind-peer client integration, not the server. Deploy the separate [`blind-peer`](https://github.com/holepunchto/blind-peer) package and distribute its public key through trusted application configuration. + +- One blind-peer server can accept multiple cryptographically distinct address books, but they share its storage and garbage-collection capacity. The release tests recovery through one peer. +- `addMirror(pool, n)` registers selected keys, but the underlying client contacts at most two configured peers for an Autobase request by default. Neither `n` nor `listMirrors()` is a confirmed replica count. +- Pin and configure the server version you operate. In `blind-peer@3.12.1`, the GC-managed store defaults to 100 GB; address-book cores use priority 2 but can still be collected under storage pressure. +- Monitor storage, garbage collection, requests, database health, and transports. These signals do not prove per-book durability or recovery completion. + + +In `1.0.0-beta.2`, `removeMirror()` removes the persisted record but leaves the key active in the current in-memory instance. Close and reopen the book without that key before treating peering as stopped. The method also does not erase blocks already held by the blind peer. + + +## Understand encryption boundaries + +`fromSeed()` supplies Autobase with a seed-derived content key before replication starts. Blind peers receive encrypted blocks, not that key. The wallet process and any device retaining the key can decrypt the book. + +Replication encryption is not local encryption at rest. Protect the Corestore directory as sensitive wallet data. + +A blind peer can withhold blocks or become unavailable, but signed Hypercore feeds prevent it from forging writer-authenticated history that clients accept. + +Blind-peer operators can still observe connection public keys and IP addresses, feed identifiers, timing, requested lengths, and approximate data volume. Logs, analytics, crash reports, screenshots, and backups can expose contact data independently of replication. + +## Plan deletion and recovery + +Contact deletion changes the current view; it does not erase append-only history or copies already held by devices and blind peers. The address-book API has no cross-replica physical-erasure or cryptographic-erasure workflow. Legal and privacy obligations depend on the complete product and deployment, not this package alone. + +The seed and namespace reproduce the book identity and keys, not the history. If every device and retained replica becomes unavailable, the contacts cannot be recovered from the seed. + +## Production checklist + +Before production: + +- Persist and protect each device's Corestore and wallet seed. +- Pin compatible Address Book, Holepunch, and blind-peer versions. +- Supervise blind-peer processes and monitor storage pressure, garbage collection, errors, and reachability. +- Define replica count, retention, deletion, and backup procedures outside this package. +- Test restoration with all original devices offline. +- Test concurrent offline edits and upgrades against existing histories. +- Validate destinations before using them in payment flows. + +The beta release publishes no package-level migration, downgrade, or cross-version replication guarantee. + +## Holepunch components + +| Component | Responsibility | +|---|---| +| [Corestore](https://github.com/holepunchto/corestore) | Persists device writers, replicated cores, the current view, and networking keys. | +| [Hypercore](https://github.com/holepunchto/hypercore) | Provides signed append-only feeds for replicated operations. | +| [Autobase](https://github.com/holepunchto/autobase) | Orders operations from authorized writers and encrypts replicated blocks when given the content key. | +| [HyperDB](https://github.com/holepunchto/hyperdb) | Materializes contacts, addresses, writers, and mirror configuration for queries. | +| [Hyperswarm](https://github.com/holepunchto/hyperswarm) | Discovers peers and carries direct replication connections. | +| [`blind-peering`](https://github.com/holepunchto/blind-peering) | Requests background replication from configured blind peers. | + +## Next steps + +Return to [Sync and Recovery](/tools/p2p-address-book/sync-and-recovery) for device enrollment, or use the [API Reference](/tools/p2p-address-book/api-reference) while implementing lifecycle and storage handling. + +*** + +## P2P Address Book Sync and Recovery +URL: https://docs.wdk.tether.io/tools/p2p-address-book/sync-and-recovery +Description: Enroll devices, configure blind peers, and recover an existing P2P Address Book. + +Devices that use the same seed and namespace derive the same book identity, content key, and bootstrap signing key. Each device stores a separate writer key in its own Corestore. + +## Choose the correct enrollment flow + +- Call `create()` only for a book that is known to be new. +- Call `addMirror()` when an existing book may need to be restored. + +`addMirror()` waits for existing genesis before enrolling the local writer and does not create a fallback book on timeout. This avoids silently forking a missing book. + +## Register availability on the first device + +A blind peer is optional while two enrolled devices can reach each other directly. It becomes important when a new device must restore while every existing user device is offline. + +Create the new book before registering the mirror: + +```javascript +const first = await AddressBook.fromSeed(seed, firstDeviceStore, { + namespace: 'example-wallet' +}) + +await first.create() +await first.addMirror(mirrorKey) +``` + +The package contains the blind-peer client integration, not the server. See [Production and Privacy](/tools/p2p-address-book/production-and-privacy) before relying on a blind peer for recovery. + +## Restore another device + +Use the same seed and namespace with a different persistent Corestore. Do not call `create()`: + +```javascript +const restored = await AddressBook.fromSeed(seed, secondDeviceStore, { + namespace: 'example-wallet', + timeout: 20_000 +}) + +await restored.addMirror(mirrorKey) + +const refresh = () => restored + .listContacts() + .then(renderContacts) + .catch(reportError) + +restored.on('update', refresh) +await refresh() +``` + +The `mirrors` constructor option configures peering but does not enroll a restored writer by itself. Call `addMirror()`. + +When `addMirror()` resolves, the device has observed existing genesis and enrolled its writer. Resolution does not confirm that every record has arrived or that the blind peer will retain the history. Refetch on payload-free `update` events, unregister listeners during cleanup, and close both the book and root Corestore. + + +`ready()` and `fromSeed()` are not connectivity signals in `1.0.0-beta.2`. Initial swarm flush and blind-peer setup run in the background. Use the explicit `addMirror()` restore path and application-level recovery checks rather than assuming that local readiness means synchronization is complete. + + +The seed and namespace can reproduce the book identity and keys, but not missing history. If all devices and retained replicas are unavailable, the contacts cannot be rebuilt from the seed alone. + +## Understand writer permissions + +Writer permissions are flat. Every enrolled writer can change contacts and addresses, and the direct API allows writers to add or remove other writers. There is no administrator role, read-only role, or invitation protocol. + + +`removeWriter()` removes current write membership only. It does not rotate the seed-derived authority or content key, revoke read access, erase the removed device's data, or prevent a device holding the seed from enrolling again. Autobase also rejects removal of the final writer. Do not use this method as a complete compromised-device control. + + +## Select mirrors from a pool + +`AddressBook.selectMirrors(autobaseKey, pool, n)` deterministically ranks mirror keys for a book. Passing the same pool to `addMirror(pool, n)` selects and registers the top entries. + +The returned selection is not proof of storage or availability. Test restoration with the original devices offline. + +## Next steps + +Review mirror and writer methods in the [API Reference](/tools/p2p-address-book/api-reference), then plan deployment and retention in [Production and Privacy](/tools/p2p-address-book/production-and-privacy). + +*** + ## Pear Worklet WDK URL: https://docs.wdk.tether.io/tools/pear-wrk-wdk -Description: Low-level HRPC infrastructure for running WDK inside a Bare worklet +Description: HRPC and framed JSON-RPC infrastructure for running WDK inside a Bare worklet -Pear Worklet WDK is the low-level transport and handler layer for running WDK inside a Bare worklet. It provides the `HRPC` client, the `registerRpcHandlers()` server helper, and the typed request payloads needed to initialize WDK, call wallet methods, and reset selected wallet modules from the host app. +Pear Worklet WDK is the low-level transport and handler layer for running WDK inside a Bare worklet. It provides an `HRPC` client and server helper, a separate framed JSON-RPC server entrypoint for native hosts, and typed request payloads for WDK, wallet, protocol, and HRPC-only generic-module operations. Powered by `@tetherto/pear-wrk-wdk`. @@ -29633,7 +40696,9 @@ Powered by `@tetherto/pear-wrk-wdk`. - **Bare worklet bridge**: Connect a host app to a worklet through the shipped `HRPC` client and generated HRPC schema - **Typed lifecycle requests**: Initialize WDK with `initializeWDK({ config, encryptionKey, encryptedSeed })` and tear it down with `dispose()` - **Selective wallet resets**: Re-register only the wallet modules listed in a new worklet `networks` config using `resetWdkWallets({ config })` -- **Generic wallet method calls**: Call account methods through `callMethod({ methodName, network, accountIndex, args, options })` +- **Generic wallet and protocol calls**: Call account methods through `callMethod({ methodName, network, accountIndex, args, options })`, including Swidge protocols by name +- **HRPC generic modules**: Construct named modules during WDK initialization, call their methods with `callModule()`, and forward declared events to the host +- **Framed JSON-RPC server**: Register a length-prefixed JSON-RPC 2.0 server through `@tetherto/pear-wrk-wdk/jsonrpc` - **Dynamic registration hooks**: Register additional wallets or protocols with `registerWallet()` and `registerProtocol()` ## Why this matters @@ -29641,14 +40706,23 @@ Powered by `@tetherto/pear-wrk-wdk`. - You can keep WDK state and signing operations off the main thread in Bare-based apps - You can reconfigure selected wallet modules without fully disposing the worklet - You can use one transport layer across custom mobile, desktop, or embedded Bare integrations +- You can host the same wallet and protocol handlers behind HRPC or a native JSON-RPC bridge, within each transport's supported method set Use this package when you need direct control over the worklet host and RPC layer. If you want generated worklet entry files instead, start with [`@tetherto/wdk-worklet-bundler`](https://github.com/tetherto/wdk-worklet-bundler). + +The top-level package exports the `HRPC` runtime value. Import `registerRpcHandlers()` from `@tetherto/pear-wrk-wdk/worklet` and `registerJsonRpcHandlers()` from `@tetherto/pear-wrk-wdk/jsonrpc`. + + + +Generic-module calls and events are HRPC-only in `v1.0.0-beta.10`; the JSON-RPC handler does not expose them. This package also does not export a pre-built WDK bundle. + + -Build the worklet context and pass the JSON config payloads used by initialize and reset requests +Build HRPC or JSON-RPC contexts and configure WDK, wallet, protocol, and generic-module initialization Review the exported class, server helper, and request shapes @@ -29669,7 +40743,7 @@ Description: API reference for the HRPC client, registerRpcHandlers helper, and ## Package: `@tetherto/pear-wrk-wdk` -### Export: `HRPC` +### Top-level export: `HRPC` #### Command Methods @@ -29682,10 +40756,12 @@ Description: API reference for the HRPC client, registerRpcHandlers helper, and | `generateEntropyAndEncrypt()` | `generateEntropyAndEncrypt(args: WdkGenerateEntropyParams): Promise\` | Generates encrypted seed and entropy buffers inside the worklet. | | `getMnemonicFromEntropy()` | `getMnemonicFromEntropy(args: WdkGetMnemonicParams): Promise\<{ mnemonic: string }\>` | Decrypts an encrypted entropy payload and returns the mnemonic. | | `getSeedAndEntropyFromMnemonic()` | `getSeedAndEntropyFromMnemonic(args: { mnemonic: string }): Promise\` | Converts a mnemonic into encrypted seed and entropy buffers. | -| `dispose()` | `dispose(args: DisposeRequest): void` | Disposes the worklet WDK instance. | +| `dispose()` | `dispose(args: DisposeRequest): void` | Disposes the full worklet WDK instance or only selected blockchains. | | `callMethod()` | `callMethod(args: CallMethodRequest): Promise\` | Looks up the target account and invokes one wallet or protocol method by name. | | `registerWallet()` | `registerWallet(args: { config: string }): Promise\<{ status: string, blockchains: string }\>` | Dynamically registers additional wallets from a JSON config string. | | `registerProtocol()` | `registerProtocol(args: { config: string }): Promise\<{ status: string }\>` | Dynamically registers additional protocols from a JSON config string. | +| `callModule()` | `callModule(args: CallModuleRequest): Promise\` | Calls a method on a configured generic module over HRPC. | +| `moduleEvent()` | `moduleEvent(args: ModuleEventRequest): void` | Sends a generic-module event from the worklet to the HRPC host. | #### Handler Registration Methods @@ -29702,6 +40778,8 @@ Description: API reference for the HRPC client, registerRpcHandlers helper, and | `onCallMethod()` | `onCallMethod(responseFn): void` | Registers the server-side handler for `callMethod()`. | | `onRegisterWallet()` | `onRegisterWallet(responseFn): void` | Registers the server-side handler for `registerWallet()`. | | `onRegisterProtocol()` | `onRegisterProtocol(responseFn): void` | Registers the server-side handler for `registerProtocol()`. | +| `onCallModule()` | `onCallModule(responseFn): void` | Registers the worklet-side handler for generic-module calls. | +| `onModuleEvent()` | `onModuleEvent(responseFn): void` | Registers the host-side handler for generic-module events. | #### `log` @@ -29727,13 +40805,15 @@ Returns: - `encryptedSeed?` (`string`): Base64-encoded encrypted seed buffer. - `config` (`string`): JSON stringified `WdkWorkletConfig`. -The handler requires `encryptionKey` and `encryptedSeed` to be passed together or omitted together. When a seeded WDK instance already exists, the runtime disposes it before re-registering the wallets and optional protocols in `config`. +The handler requires `encryptionKey` and `encryptedSeed` to be passed together or omitted together. When a seeded WDK instance already exists, the runtime disposes it and closes its generic modules before re-registering the wallets and optional protocols in `config`. + +In beta.10, HRPC generic modules are constructed from `context.moduleManagers` and `config.modules` only when that request includes the encrypted seed pair. A seedless reinitialization closes existing module instances without reconstructing them. Supply both seed fields on every initialization that must construct or reconstruct modules. #### `resetWdkWallets` - `config` (`string`): JSON stringified object containing a `networks` map. -The runtime validates `config.networks`, extracts each target `blockchain`, calls `wdk.dispose(targetChains)`, and re-registers only those wallet managers. This method does not re-register protocols. +The runtime validates `config.networks`, extracts each target `blockchain`, calls `wdk.dispose(targetChains)`, and re-registers only those wallet managers. This method does not re-register protocols or close generic modules; existing module instances keep running. #### `generateEntropyAndEncrypt` @@ -29766,7 +40846,9 @@ Returns: #### `dispose` -- `args` (`DisposeRequest`): Empty request object. +- `args` (`DisposeRequest`): Optional `blockchains` array. Omit it or pass an empty array for a full disposal. + +A full disposal closes all generic modules and clears the WDK instance. A non-empty `blockchains` array disposes only those wallets and leaves generic modules running. #### `callMethod` @@ -29776,7 +40858,7 @@ Returns: - `args?` (`string`): JSON string of the method arguments. - `options?` (`string`): JSON string of `CallMethodOptions`. -`options.protocolType` may be `swap`, `bridge`, `lending`, or `fiat`. When present, the runtime resolves the protocol-specific account wrapper before invoking `methodName`. +`options.protocolType` may be `swap`, `swidge`, `bridge`, `lending`, or `fiat`. When present, the runtime requires a non-empty `options.protocolName` and resolves the protocol-specific account wrapper before invoking `methodName`. Swidge calls resolve the wrapper with `account.getSwidgeProtocol(protocolName)`. #### `registerWallet` @@ -29795,6 +40877,24 @@ Returns: - `status` (`string`) +#### `callModule` + +Call one method on a configured generic module. This command is available on HRPC only. + +- `module` (`string`): Module name shared by `RpcContext.moduleManagers` and `WdkWorkletConfig.modules`. +- `method` (`string`): Non-empty method name on the constructed module instance. +- `args?` (`string`): Optional JSON string of arguments. Arrays are spread as positional arguments; a non-array value is passed as one argument. + +Returns `CallModuleResponse` with optional `result`, a JSON string. The runtime awaits promises, materializes values with `.toArray()`, and recursively converts `Uint8Array` values to hex before serialization. + +#### `moduleEvent` + +Send an HRPC module event to the host. + +- `module` (`string`): Module name. +- `event` (`string`): Event name. +- `payload?` (`string | null`): Optional JSON string payload. + #### `onLog` Registers the server-side handler used to service `log()` requests. @@ -29839,12 +40939,20 @@ Registers the server-side handler used to service `registerWallet()` requests. Registers the server-side handler used to service `registerProtocol()` requests. -### Export: `registerRpcHandlers(rpc, context)` +#### `onCallModule` + +Registers the worklet-side handler used to service `callModule()` requests. + +#### `onModuleEvent` + +Registers the host-side handler used to receive `moduleEvent()` messages. + +### Worklet export: `registerRpcHandlers(rpc, context)` -Registers the package's server-side handlers on the provided RPC instance. +Import this helper from `@tetherto/pear-wrk-wdk/worklet`. It registers the package's server-side handlers on the provided RPC instance. - `rpc` (`any`): RPC server instance that supports the generated handler registration methods. -- `context` (`RpcContext`): Runtime context containing `wdk`, `WDK`, `walletManagers`, `protocolManagers`, and `wdkLoadError`. +- `context` (`RpcContext`): Runtime context containing `wdk`, `WDK`, `walletManagers`, `protocolManagers`, and `wdkLoadError`. HRPC generic modules can additionally supply `moduleManagers` and `capabilities`; the runtime manages `moduleRuntime` and `moduleInstances`. ### Types @@ -29865,6 +40973,47 @@ interface WdkWorkletConfig { config: unknown } } + modules?: { + [moduleName: string]: Record + } +} +``` + +The `modules` map contains runtime module configuration. Its names must match the module managers generated by Worklet Bundler or supplied manually in `RpcContext`. + +#### `WdkModuleManager` + +```ts +interface WdkModuleManager { + events?: string[] + createModule: (context: { + seed: any + config: any + capabilities: Record + emit: (event: string, payload?: any) => void + }) => any | Promise +} +``` + +The factory must consume `seed` synchronously rather than retain it. Module instances can optionally implement `close()`, `suspend()`, and `resume()`. The runtime calls `close()` during full disposal or reinitialization; targeted blockchain disposal and `resetWdkWallets()` leave generic modules running. Worklet Bundler-generated HRPC entrypoints forward Bare lifecycle events to `suspend()` and `resume()`; manual Pear integrations must wire those events themselves. Declared `events` are forwarded from the instance, and the injected `emit()` function can emit events directly. + +#### Module request types + +```ts +interface CallModuleRequest { + module: string + method: string + args?: string +} + +interface CallModuleResponse { + result?: string | null +} + +interface ModuleEventRequest { + module: string + event: string + payload?: string | null } ``` @@ -29879,14 +41028,60 @@ interface WdkResetWalletParams { #### `CallMethodOptions` ```ts +enum ProtocolType { + SWAP = 'swap', + SWIDGE = 'swidge', + BRIDGE = 'bridge', + LENDING = 'lending', + FIAT = 'fiat' +} + interface CallMethodOptions { transformResult: Function defaultValue: any - protocolType: 'swap' | 'bridge' | 'lending' | 'fiat' + protocolType: ProtocolType protocolName: string } ``` +The published declarations include `ProtocolType`, but the top-level JavaScript entry does not export that enum value at runtime. Pass the corresponding string literal, such as `'swidge'`, in serialized request options. + +The published `CallMethodOptions` declaration marks every field as required. The request's `options` string remains optional at runtime, and the handler reads fields only when their behavior is used. + +## JSON-RPC Transport + +Import `registerJsonRpcHandlers()` from the separate JSON-RPC entrypoint: + +```javascript +const { registerJsonRpcHandlers } = require('@tetherto/pear-wrk-wdk/jsonrpc') + +registerJsonRpcHandlers(ipc, context) +``` + +The server reads UTF-8 JSON-RPC 2.0 messages framed with a four-byte unsigned big-endian payload length. Every request requires an ID, and an ID cannot be reused while its earlier request is still in flight. Malformed frames are dropped without a response. The package does not export a JSON-RPC client or native-host helper. + +Beta.10 supports these JSON-RPC method names: + +- `workletStart` +- `generateEntropyAndEncrypt` +- `getMnemonicFromEntropy` +- `getSeedAndEntropyFromMnemonic` +- `initializeWDK` +- `callMethod` +- `registerWallet` +- `registerProtocol` +- `dispose` + +JSON-RPC does not support `resetWdkWallets`, generic-module calls, or module events in this release. Use HRPC for those operations. + +The HRPC and JSON-RPC transports share the `callMethod` handler, so both support the `swidge` protocol type in beta.10. + + +INFO-level logging can include wallet-call arguments and JSON-RPC parameters or results. Production defaults to ERROR logging; do not enable more verbose logging for requests that may contain seeds, mnemonics, keys, or other sensitive values. + + +Mnemonic strings, encryption-key strings, and encrypted payload strings cannot be zeroed in JavaScript. Discard references promptly and never log them. The runtime validates imported mnemonics for 12 or 24 English BIP-39 words and clears temporary byte buffers where possible. + *** ## Need Help? @@ -29897,33 +41092,69 @@ interface CallMethodOptions { ## Pear Worklet WDK Configuration URL: https://docs.wdk.tether.io/tools/pear-wrk-wdk/configuration -Description: Configure the Bare worklet context and the JSON payloads passed to initializeWDK and resetWdkWallets +Description: Configure Pear Worklet HRPC and JSON-RPC contexts, WDK payloads, and generic modules -This page explains how to [build the worklet context](#worklet-context), [shape the worklet config payload](#worklet-config-payload), [initialize-wdk](#initialize-wdk), and [reset-selected-wallets](#reset-selected-wallets). +This page explains how to [build the worklet context](#worklet-context), [shape the worklet config payload](#worklet-config-payload), [initialize WDK](#initialize-wdk), [call generic modules](#call-generic-module-methods), and [choose a transport](#json-rpc-transport). ## Worklet Context You can bind the shipped RPC handlers to your Bare worklet using `registerRpcHandlers()`: ```javascript title="Register RPC Handlers" +require('bare-node-runtime/global') + const { registerRpcHandlers } = require('@tetherto/pear-wrk-wdk/worklet') -const { WDK } = require('@tetherto/wdk') -const EvmWalletManager = require('@tetherto/wdk-wallet-evm') -const SparkWalletManager = require('@tetherto/wdk-wallet-spark') +const wdkModule = require('@tetherto/wdk', { with: { imports: 'bare-node-runtime/imports' } }) +const { createModule: createPreferencesModule } = require('@your-org/wdk-module-preferences') + +const WDK = wdkModule.default || wdkModule.WDK || wdkModule +const walletCache = {} + +function loadWalletManager(network) { + if (walletCache[network]) return walletCache[network] + + let walletModule + if (network === 'ethereum') { + walletModule = require('@tetherto/wdk-wallet-evm', { with: { imports: 'bare-node-runtime/imports' } }) + } + if (network === 'spark') { + walletModule = require('@tetherto/wdk-wallet-spark', { with: { imports: 'bare-node-runtime/imports' } }) + } + + if (walletModule) walletCache[network] = walletModule.default || walletModule + return walletCache[network] || null +} + +const walletManagers = new Proxy({}, { + get: (_, network) => loadWalletManager(network), + has: (_, network) => ['ethereum', 'spark'].includes(network) +}) const context = { wdk: null, WDK, - walletManagers: { - ethereum: EvmWalletManager, - spark: SparkWalletManager - }, + walletManagers, protocolManagers: {}, + moduleManagers: { + preferences: { + createModule: createPreferencesModule, + events: ['changed'] + } + }, + capabilities: {}, wdkLoadError: null } module.exports = (rpc) => { registerRpcHandlers(rpc, context) + + Bare.on('suspend', async () => { + await context.moduleRuntime?.suspendAll() + }) + + Bare.on('resume', async () => { + await context.moduleRuntime?.resumeAll() + }) } ``` @@ -29935,11 +41166,13 @@ module.exports = (rpc) => { - `protocolManagers`: A map from protocol name to protocol manager implementation. - `wdkLoadError`: Any startup error captured while loading WDK. Use `null` when there is no load failure. +For HRPC generic modules, `moduleManagers` optionally maps module names to `{ createModule, events? }`. The factory receives `{ seed, config, capabilities, emit }` and can return an instance or a promise. `capabilities` is an optional host-supplied object and is empty by default. The runtime manages `moduleRuntime` and `moduleInstances`; do not initialize those fields yourself. Manual integrations must forward Bare `suspend` and `resume` events as shown if module instances should receive those lifecycle calls. Worklet Bundler-generated HRPC entrypoints wire them automatically. + ## Worklet Config Payload Both [`initializeWDK()`](/tools/pear-wrk-wdk/api-reference) and [`resetWdkWallets()`](/tools/pear-wrk-wdk/api-reference) expect `config` to be a JSON string. The decoded object must contain at least one entry under `networks`. -```javascript title="Minimal Worklet Config JSON" +```javascript title="Worklet Config JSON" const workletConfig = { networks: { ethereum: { @@ -29957,6 +41190,11 @@ const workletConfig = { environment: 'sandbox' } } + }, + modules: { + preferences: { + storagePath: '/app-data/preferences' + } } } ``` @@ -29966,8 +41204,13 @@ const workletConfig = { - `networks` is required and must contain at least one network entry. - Each network entry must include `blockchain` and an object `config`. - `protocols` is optional during initialization. +- `modules` is optional and contains runtime config for named generic modules. Each key must match a `moduleManagers` key in the HRPC context and the corresponding build-time Worklet Bundler module name. - `resetWdkWallets()` reads only the `networks` portion of the decoded config. + +Generic modules are HRPC-only in beta.10. The JSON-RPC handler does not construct modules or expose module calls and events. + + ## Initialize WDK You can create and register the WDK instance inside the worklet using [`initializeWDK()`](/tools/pear-wrk-wdk/api-reference): @@ -29988,7 +41231,9 @@ await hrpc.initializeWDK({ - Pass both `encryptionKey` and `encryptedSeed`, or omit both together. - On first initialization, the worklet must receive an encrypted seed pair so it can create `context.wdk`. -- If `context.wdk` already exists, a later `initializeWDK()` call disposes the existing instance before re-registering wallets and protocols from the new config. +- If `context.wdk` already exists, a later `initializeWDK()` call disposes the existing instance and closes its generic modules before re-registering wallets and protocols from the new config. +- In beta.10, generic modules are constructed only when that `initializeWDK()` request includes both `encryptionKey` and `encryptedSeed`. A seedless reinitialization closes existing module instances but does not rebuild them, even when `config.modules` is present. Supply the seed pair on every initialization that must construct or reconstruct modules. +- Module `close()` is called during full disposal or reinitialization. Targeted blockchain disposal leaves generic modules running. Optional `suspend()` and `resume()` methods run only when the host forwards Bare lifecycle events; the manual context above and Worklet Bundler-generated HRPC entrypoints do so. ## Reset Selected Wallets @@ -30015,8 +41260,9 @@ await hrpc.resetWdkWallets({ - The handler calls `wdk.dispose(targetChains)` with the blockchains extracted from `config.networks`. - Only wallets listed in the request `networks` object are re-registered. - The reset flow does not re-register protocols. +- The reset flow does not close or reconstruct generic modules; existing module instances keep running. -## Call Wallet Methods +## Call Wallet and Protocol Methods You can execute wallet account methods through [`callMethod()`](/tools/pear-wrk-wdk/api-reference): @@ -30034,6 +41280,54 @@ const result = await hrpc.callMethod({ - `options` is optional and must be a JSON string when provided. - When `args` decodes to an array, the handler spreads the values as positional method arguments. - When `args` decodes to an object or primitive, the handler passes it as a single argument. +- Set `options.protocolType` to `swap`, `swidge`, `bridge`, `lending`, or `fiat` to call a protocol wrapper. Every protocol call requires a non-empty `options.protocolName`. + +## Call Generic Module Methods + +On an HRPC worklet configured with matching `moduleManagers` and runtime `modules`, call a module method by name: + +```javascript title="Call A Generic Module" +const response = await hrpc.callModule({ + module: 'preferences', + method: 'getTheme', + args: JSON.stringify([]) +}) + +const theme = response.result ? JSON.parse(response.result) : undefined +``` + +`args` is an optional JSON string. Arrays are spread into positional arguments; a non-array value is passed as one argument. Promise results are awaited, `.toArray()` results are materialized, and `Uint8Array` values are normalized to hex before the response is serialized. + +Subscribe to events declared by the module manager: + +```javascript title="Receive A Module Event" +hrpc.onModuleEvent(({ module, event, payload }) => { + if (module === 'preferences' && event === 'changed') { + const value = payload ? JSON.parse(payload) : undefined + console.log('Preferences changed:', value) + } +}) +``` + +## JSON-RPC Transport + +Native hosts can register the separate framed JSON-RPC server entrypoint: + +```javascript title="Register JSON-RPC Handlers" +const { registerJsonRpcHandlers } = require('@tetherto/pear-wrk-wdk/jsonrpc') + +module.exports = (ipc) => { + registerJsonRpcHandlers(ipc, context) +} +``` + +Messages are UTF-8 JSON-RPC 2.0 objects prefixed by a four-byte unsigned big-endian payload length. Requests require an ID, and IDs must be unique while a request is in flight. The package exports no JSON-RPC host/client helper; the native host must implement framing and correlation. + +JSON-RPC beta.10 supports WDK initialization and disposal, secret/mnemonic operations, wallet and protocol calls, and dynamic wallet/protocol registration. Its shared `callMethod` handler supports the `swidge` protocol type. It does not support `resetWdkWallets`, `callModule`, or module events. See the [API reference](/tools/pear-wrk-wdk/api-reference#json-rpc-transport) for the exact method list. + + +INFO-level logs can contain wallet-call arguments and JSON-RPC parameters or results. Keep production logging at its default ERROR level when requests may contain sensitive values. + *** @@ -30049,19 +41343,22 @@ Description: Unified pricing tools for fetching current and historical asset pri Pricing utilities for WDK apps. Includes HTTP clients and providers to retrieve current and historical prices from supported services. -Powered by [`@tetherto/wdk-pricing-bitfinex-http`](https://github.com/tetherto/wdk-pricing-bitfinex-http) and compatible with [`@tetherto/wdk-pricing-provider`](https://github.com/tetherto/wdk-pricing-provider). +Powered by [`@tetherto/wdk-pricing-bitfinex-http`](https://github.com/tetherto/wdk-pricing-bitfinex-http), compatible with [`@tetherto/wdk-pricing-provider`](https://github.com/tetherto/wdk-pricing-provider), and usable with [`@tetherto/wdk-pricing-coingecko-http`](/sdk/pricing-modules/pricing-coingecko-http/) as a CoinGecko fallback client. ## Features -- **Current Prices**: Fetch latest price for a base/quote pair (e.g., BTC/USD) +- **Current Prices**: Fetch latest price for one pair or many pairs in a batch (e.g., BTC/USD) +- **Direct Bitfinex Quotes**: Return prices only for pairs Bitfinex can quote directly; unsupported pairs return `null` - **Historical Series**: Retrieve historical price series; long histories optionally downscaled to ≤ 100 points - **Provider Compatibility**: Works with `@tetherto/wdk-pricing-provider` +- **CoinGecko Fallback**: Use `@tetherto/wdk-pricing-coingecko-http` when you want a CoinGecko-backed pricing client - **Lightweight HTTP Client**: Minimal dependencies; easy to integrate ## Why this matters - Consistent pricing is required for balance valuations, charts, and quotes - A unified client reduces integration time and data handling errors +- Unsupported pairs return `null` from the pricing client so applications can show a clear unavailable-price state @@ -30070,6 +41367,9 @@ Get started with WDK price clients Check the API Reference and examples + +Use CoinGecko for current prices and historical series + *** @@ -30102,13 +41402,40 @@ new BitfinexPricingClient(options?) | Method | Description | Returns | |--------|-------------|---------| -| `getCurrentPrice(base, quote)` | Fetch latest price for base/quote pair | `Promise\` | +| `getCurrentPrice(base, quote)` | Fetch latest price for base/quote pair | `Promise\` | +| `getMultiCurrentPrices(pairs)` | Fetch latest prices for multiple pairs in one batch | `Promise\\>` | +| `getMultiPriceData(pairs)` | Fetch last price and 24h change data for multiple directly quoted pairs | `Promise\\>` | | `getHistoricalPrice(from, to, opts?)` | Fetch historical series (downscaled to ≤ 100 points if needed) | `Promise\` | ##### `getCurrentPrice(base, quote)` +Uses Bitfinex's `/calc/fx/batch` endpoint. Returns `null` when Bitfinex cannot quote the pair directly. The client does not try a two-leg USD pivot. + ```javascript title="Current Price" const price = await client.getCurrentPrice('BTC', 'USD') +const unsupported = await client.getCurrentPrice('BTC', 'BRL') // null when unsupported +``` + +##### `getMultiCurrentPrices(pairs)` + +Returns current prices in the same order as the input pairs. Each unresolved pair returns `null`. + +```javascript title="Batch Current Prices" +const prices = await client.getMultiCurrentPrices([ + { from: 'BTC', to: 'USD' }, + { from: 'ETH', to: 'USD' }, + { from: 'BTC', to: 'BRL' } +]) +``` + +##### `getMultiPriceData(pairs)` + +Returns last price plus 24-hour absolute and relative change. This method reads Bitfinex ticker data and does not use the USD-pivot fallback, so unsupported entries return `null`. + +```javascript title="Batch Price Data" +const data = await client.getMultiPriceData([ + { from: 'BTC', to: 'USD' } +]) ``` ##### `getHistoricalPrice(from, to, opts?)` @@ -30147,6 +41474,9 @@ new PricingProvider({ | Method | Description | Returns | |--------|-------------|---------| | `getLastPrice(base, quote)` | Returns cached last price; refreshes when TTL expires | `Promise\` | +| `getMultiLastPrices(pairs)` | Returns cached last prices for multiple pairs | `Promise\` | +| `getLastPriceData(base, quote)` | Returns cached last price plus daily change data | `Promise\` | +| `getMultiLastPriceData(pairs)` | Returns cached price data for multiple pairs | `Promise\` | | `getHistoricalPrice(from, to, opts?)` | Delegates to client for historical data | `Promise\` | ##### `getLastPrice(base, quote)` @@ -30156,6 +41486,30 @@ const provider = new PricingProvider({ client }) const last = await provider.getLastPrice('BTC', 'USD') ``` +##### `getMultiLastPrices(pairs)` + +```javascript title="Cached Batch Last Prices" +const prices = await provider.getMultiLastPrices([ + { from: 'BTC', to: 'USD' }, + { from: 'ETH', to: 'BRL' } +]) +``` + +##### `getLastPriceData(base, quote)` + +```javascript title="Cached Price Data" +const data = await provider.getLastPriceData('BTC', 'USD') +console.log(data.lastPrice, data.dailyChange, data.dailyChangeRelative) +``` + +##### `getMultiLastPriceData(pairs)` + +```javascript title="Cached Batch Price Data" +const data = await provider.getMultiLastPriceData([ + { from: 'BTC', to: 'USD' } +]) +``` + ##### `getHistoricalPrice(from, to, opts?)` ```javascript title="Historical via Provider" @@ -30171,12 +41525,16 @@ Implement this interface to plug your data source into `PricingProvider`. | Method | Signature | Notes | |--------|-----------|-------| -| `getCurrentPrice` | `(from: string, to: string) =\> Promise\` | Should return spot price | +| `getCurrentPrice` | `(from: string, to: string) =\> Promise\` | Return spot price or `null` when the pair cannot be resolved | +| `getMultiCurrentPrices` | `(list: PricePair[]) =\> Promise\\>` | Return one result per pair; unresolved entries are `null` | +| `getMultiPriceData` | `(list: PricePair[]) =\> Promise\\>` | Return last price and daily change data; unresolved entries are `null` | | `getHistoricalPrice` | `(from: string, to: string, opts?: HistoricalPriceOptions) =\> Promise\` | Return series for charting | ## Notes -- Uses Bitfinex Public HTTP API (`/v2/ticker` and `/v2/candles`) under the hood for the Bitfinex client +- Uses Bitfinex Public HTTP API (`/v2/calc/fx/batch`, `/v2/tickers`, and `/v2/tickers/hist`) under the hood for the Bitfinex client +- Bitfinex lookups support only pairs Bitfinex quotes directly. Unsupported current-price and price-data pairs return `null`; unsupported historical pairs return an empty series. +- Use [`@tetherto/wdk-pricing-coingecko-http`](/sdk/pricing-modules/pricing-coingecko-http/) when you need a CoinGecko-backed `PricingClient` - Provider caches last price per pair using in-memory store and TTL *** @@ -30202,8 +41560,37 @@ const client = new BitfinexPricingClient() ### Current Price +Current-price lookups use Bitfinex's FX conversion endpoint. The client returns a price only when Bitfinex can quote the pair directly. Unsupported pairs return `null`; the client does not try a USD-pivot fallback. + ```javascript title="Get Current Price" const price = await client.getCurrentPrice('BTC', 'USD') +const unsupported = await client.getCurrentPrice('BTC', 'BRL') // null when Bitfinex has no direct quote + +if (unsupported === null) { + // Show an unavailable-price state in your UI +} +``` + +### Batch Current Prices + +Batch lookups return results in the same order as the input list. Entries that cannot be resolved are `null`. + +```javascript title="Get Batch Current Prices" +const prices = await client.getMultiCurrentPrices([ + { from: 'BTC', to: 'USD' }, + { from: 'ETH', to: 'USD' }, + { from: 'BTC', to: 'BRL' } // null when unsupported +]) +``` + +### Batch Price Data + +Use `getMultiPriceData()` when you need the last price plus 24-hour absolute and relative change. This method uses Bitfinex ticker data and only supports pairs Bitfinex quotes directly. + +```javascript title="Get Batch Price Data" +const priceData = await client.getMultiPriceData([ + { from: 'BTC', to: 'USD' } +]) ``` ### Historical Series @@ -30232,6 +41619,10 @@ const provider = new PricingProvider({ }) const last = await provider.getLastPrice('BTC', 'USD') +const prices = await provider.getMultiLastPrices([ + { from: 'BTC', to: 'USD' }, + { from: 'ETH', to: 'USD' } +]) const hist = await provider.getHistoricalPrice('BTC', 'USD', { start: 1709906400000, end: 1709913600000 @@ -30251,6 +41642,22 @@ const provider = new PricingProvider({ const last = await provider.getLastPrice('BTC', 'USD') ``` +Use [`@tetherto/wdk-pricing-coingecko-http`](/sdk/pricing-modules/pricing-coingecko-http/) as another `PricingClient` when you want CoinGecko as a fallback or primary data source: + +```javascript title="CoinGecko fallback client" +import { PricingProvider } from '@tetherto/wdk-pricing-provider' +import { BitfinexPricingClient } from '@tetherto/wdk-pricing-bitfinex-http' +import { CoingeckoPricingClient } from '@tetherto/wdk-pricing-coingecko-http' + +const provider = new PricingProvider({ + client: [ + new BitfinexPricingClient(), + new CoingeckoPricingClient({ apiKey: process.env.COINGECKO_API_KEY }) + ], + retries: 1 +}) +``` + *** ## Need Help? @@ -30267,22 +41674,33 @@ Description: Hooks-based React Native library for building multi-chain wallet ap ## Features -- **Hooks-based architecture** - `useWdkApp`, `useWalletManager`, `useAccount`, `useBalance`, and more -- **TanStack Query caching** - automatic balance fetching, per-token fallback for modules without batch balance support, cache invalidation, and optimistic updates +- **Hooks-based architecture** - `useWdkApp`, `useWalletManager`, `useAccount`, `useProtocol`, `useBalance`, `useBalancesForWallets`, and more +- **Generic module API** - `useModule` and `ModuleService` expose named worklet-module calls and events +- **TanStack Query caching** - automatic balance fetching across one or many account indices, per-token fallback for modules without batch balance support, cache invalidation, and optimistic updates - **Zustand state management** - persisted wallet state with MMKV storage - **Worklet runtime** - runs WDK in an isolated Bare worklet - **Biometric authentication** - secure storage with device biometrics - **Multi-wallet support** - create, restore, switch, lock, unlock, and delete wallets -- **TypeScript-first** - full type safety with exported types and interfaces +- **Typed React Native source API** - exported hook and service types are available through the package's React Native source condition + + +Starting in `v1.0.0-beta.14`, React Native Core pins a Pear Worklet version with the HRPC methods required by `useModule()` and `ModuleService`. Beta.15 pins Pear Worklet beta.10. + + + +The `v1.0.0-beta.15` npm artifact includes the React Native source entry at `src/index.ts`, but it does not include the declared default JavaScript or type declaration files under `dist/`. Use a React Native resolver that selects the package's `react-native` condition. Resolvers that select `default` or `types` target files that are not present in this artifact. + ## Quick Start ### 1. Install ```bash -npm install @tetherto/wdk-react-native-core +npm install @tetherto/wdk-react-native-core react-native-bare-kit ``` +Starting in beta.14, `react-native-bare-kit` is a peer dependency with a supported range of `>=0.14.5`, so the app must install it explicitly. + ### 2. Wrap Your App ```tsx @@ -30327,9 +41745,7 @@ For a full integration guide, see the [React Native Quickstart](/start-building/ ## Bundle Configuration -The WDK engine runs inside a Bare worklet. You need to provide a bundle - there are two approaches: - -### Custom Bundle (Recommended) +The WDK engine runs inside a Bare worklet. Generate an HRPC bundle with `@tetherto/wdk-worklet-bundler` and import it from the generated `./.wdk` entrypoint. Use the `@tetherto/wdk-worklet-bundler` CLI to generate a bundle with only the blockchain modules you need: @@ -30353,27 +41769,20 @@ Example `wdk.config.js`: ```javascript module.exports = { - modules: { - core: '@tetherto/wdk', - erc4337: '@tetherto/wdk-wallet-evm-erc-4337', - }, networks: { ethereum: { - module: 'erc4337', - chainId: 1, - blockchain: 'ethereum', - provider: 'https://eth.drpc.org', + package: '@tetherto/wdk-wallet-evm-erc-4337' }, polygon: { - module: 'erc4337', - chainId: 137, - blockchain: 'polygon', - provider: 'https://polygon.drpc.org', - }, + package: '@tetherto/wdk-wallet-evm-erc-4337' + } }, + transport: 'hrpc' } ``` +This file selects packages at build time. Pass chain IDs, providers, protocol settings, and any per-module runtime config through `wdkConfigs` on `WdkAppProvider`. The optional build-time `modules` map in the bundler is distinct from runtime `wdkConfigs.modules`; their names must match when generic-module support is available. + After running `wdk-worklet-bundler generate`, import and use the bundle: ```typescript @@ -30384,23 +41793,7 @@ import { bundle } from './.wdk' ``` -For full bundler documentation, see [wdk-worklet-bundler](https://github.com/tetherto/wdk-worklet-bundler). - -### Pre-built Bundle - -For quick prototyping, use the pre-built bundle from `@tetherto/pear-wrk-wdk` which includes all blockchain modules: - -```typescript -import { bundle } from '@tetherto/pear-wrk-wdk' - - - - -``` - - -The pre-built bundle includes all blockchain modules, resulting in a larger bundle size. For production apps, generate a custom bundle with only the modules you need. - +For the complete config and transport rules, see the [Worklet Bundler configuration guide](/tools/worklet-bundler/configuration). `@tetherto/pear-wrk-wdk` provides the worklet transport/runtime layer; it does not export a pre-built WDK bundle. ## Architecture @@ -30416,10 +41809,13 @@ WdkAppProvider +-- useWdkApp() - app state (INITIALIZING, NO_WALLET, LOCKED, READY, ERROR) +-- useWalletManager() - create, restore, lock, unlock, delete wallets +-- useAccount() - address, send, sign, verify, estimateFee + +-- useProtocol() - call bridge, swap, Swidge, lending, and fiat protocol methods + +-- useModule() - call named generic modules and subscribe to events +-- useAddresses() - load and query addresses - +-- useBalance() - single balance with TanStack Query - +-- useBalancesForWallet() - bulk balance fetch with per-token fallback - +-- useRefreshBalance() - invalidate and refetch balances + +-- useBalance() - single balance with TanStack Query + +-- useBalancesForWallet() - bulk balance fetch for one account index + +-- useBalancesForWallets() - bulk balance fetch across account indices + +-- useRefreshBalance() - invalidate and refetch balances ``` --- @@ -30451,9 +41847,13 @@ Description: Complete API reference for @tetherto/wdk-react-native-core | [`useWdkApp`](#usewdkapp) | Hook | App-level state (discriminated union) | | [`useWalletManager`](#usewalletmanager) | Hook | Wallet lifecycle (create, restore, lock, unlock) | | [`useAccount`](#useaccount) | Hook | Account operations (send, sign, verify, estimateFee) | +| [`useProtocol`](#useprotocol) | Hook | Protocol method calls through the active worklet account | +| [`useModule`](#usemodule) | Hook | Named generic-module calls and event subscriptions | +| [`ModuleService`](#moduleservice) | Class | Low-level static gateway for generic-module calls and events | | [`useAddresses`](#useaddresses) | Hook | Load and query wallet addresses | | [`useBalance`](#usebalance) | Hook | Single asset balance with TanStack Query | | [`useBalancesForWallet`](#usebalancesforwallet) | Hook | Bulk balance fetch for multiple assets | +| [`useBalancesForWallets`](#usebalancesforwallets) | Hook | Bulk balance fetch for multiple assets across account indices | | [`useRefreshBalance`](#userefreshbalance) | Hook | Invalidate and refetch balances | | [`BaseAsset`](#baseasset) | Class | Default `IAsset` implementation | | [`validateMnemonic`](#validatemnemonic) | Utility | Validate BIP39 mnemonic phrases | @@ -30462,6 +41862,9 @@ Description: Complete API reference for @tetherto/wdk-react-native-core | `WdkAppContextValue` | Type | Return type of `useWdkApp` | | `UseAccountParams` | Type | Parameters for `useAccount` | | `UseAccountReturn` | Type | Return type of `useAccount` | +| `UseProtocolParams` | Type | Parameters for `useProtocol` | +| `UseModuleProxy` | Type | Typed asynchronous module proxy returned by `useModule` | +| `ModuleEventListener` | Type | Listener for parsed module-event payloads | | `UseAddressesReturn` | Type | Return type of `useAddresses` | | `UseWalletManagerResult` | Type | Return type of `useWalletManager` | @@ -30762,6 +42165,134 @@ const transfers = await btcApi.getTransfers() --- +## useProtocol + +Hook to call configured protocol modules through the active WDK worklet account. It returns a typed proxy, so each method you access is forwarded to the protocol registered for the requested account, network, type, and name. + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `params.network` | `string` | Network identifier configured in `wdkConfigs` | +| `params.accountIndex` | `number` | Account index to use for the protocol call | +| `params.protocolType` | `'bridge' \| 'swap' \| 'swidge' \| 'lending' \| 'fiat'` | Protocol category | +| `params.protocolName` | `string` | Registered protocol name | + +### Returns + +Returns `T`, a proxy for the protocol methods you type with the hook generic. Each method returns a promise from the worklet call. + +If no active account is available, protocol method calls log an error and return `undefined`. + +The `swidge` protocol type is available in `v1.0.0-beta.15`. It requires a non-empty `protocolName` and forwards calls through the matching Swidge protocol wrapper on the selected account. + +### Example + +```tsx +import { useProtocol } from '@tetherto/wdk-react-native-core' + +type SwapProtocol = { + quoteSwap: (params: { + tokenIn: string + tokenOut: string + tokenInAmount: string + }) => Promise<{ tokenOutAmount: string; fee: string }> +} + +function SwapQuoteScreen() { + const swap = useProtocol({ + network: 'ethereum', + accountIndex: 0, + protocolType: 'swap', + protocolName: 'velora', + }) + + const loadQuote = async () => { + const quote = await swap.quoteSwap({ + tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + tokenInAmount: '1000000', + }) + + console.log('Quoted output:', quote?.tokenOutAmount) + } + + return