From 365437bdb5865a395a96fb5d28a27d4ddf10dba3 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 7 Jul 2026 13:40:52 +0530 Subject: [PATCH 01/38] add lifi swidge release docs --- content/docs/overview/changelog.mdx | 7 + content/docs/sdk/all-modules.mdx | 5 +- content/docs/sdk/community-modules/index.mdx | 1 + .../swidge-lifi/api-reference.mdx | 147 +++++++++++++++++ .../swidge-lifi/configuration.mdx | 100 ++++++++++++ .../sdk/swidge-modules/swidge-lifi/index.mdx | 74 +++++++++ .../sdk/swidge-modules/swidge-lifi/usage.mdx | 151 ++++++++++++++++++ 7 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx create mode 100644 content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx create mode 100644 content/docs/sdk/swidge-modules/swidge-lifi/index.mdx create mode 100644 content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx diff --git a/content/docs/overview/changelog.mdx b/content/docs/overview/changelog.mdx index 0b80a5e8..15b07a88 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -87,6 +87,13 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### June 11, 2026 + +**What's New** +- **swidge-lifi** ([v0.2.0](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi/v/0.2.0)): Add generated TypeScript declarations, request timeouts and retries, typed LI.FI error classes, input and transaction validation, optional trusted-contract checks, and fee-cap handling for LI.FI swidge routes. + +--- + ### June 10, 2026 **What's New** diff --git a/content/docs/sdk/all-modules.mdx b/content/docs/sdk/all-modules.mdx index f0dc417c..4fc7bbad 100644 --- a/content/docs/sdk/all-modules.mdx +++ b/content/docs/sdk/all-modules.mdx @@ -36,7 +36,9 @@ Wallet modules provide blockchain-specific wallet functionality for managing add 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. -No released swidge provider module is listed yet. See the [swidge protocol interface](/sdk/swidge-modules/) for the shared API shape that new provider modules should implement. +| Module | Provider | Description | Documentation | +|--------|----------|-------------|---------------| +| [`@kenny_io/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi) | LI.FI | Swidge routes for LI.FI swap, bridge, and combined swap-plus-bridge operations | [Docs](/sdk/swidge-modules/swidge-lifi/) | ## Pricing Modules @@ -88,3 +90,4 @@ Modules built by the WDK community. See the [Community Modules](/sdk/community-m |--------|------------|-------------|---------------| | [`@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/) | | [`@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/) | +| [`@kenny_io/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi) | LI.FI | Swidge swap and bridge route integration | [Docs](/sdk/swidge-modules/swidge-lifi/) | diff --git a/content/docs/sdk/community-modules/index.mdx b/content/docs/sdk/community-modules/index.mdx index 025575ad..1ee031a9 100644 --- a/content/docs/sdk/community-modules/index.mdx +++ b/content/docs/sdk/community-modules/index.mdx @@ -21,6 +21,7 @@ Tether and the WDK Team do not endorse or assume responsibility for their code, | [@arkade-os/wdk](https://www.npmjs.com/package/@arkade-os/wdk) ([GitHub](https://github.com/arkade-os/arkade-wdk)) | Wallet Module | Bitcoin wallet module built on the Arkade SDK with Arkade addresses, boarding addresses, BIP21/LNURL/Lightning send routing, and optional Boltz swaps | [README](https://github.com/arkade-os/arkade-wdk#readme) | [Arkade](https://github.com/arkade-os) | | [@base58-io/wdk-wallet-cosmos](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos) ([GitHub](https://github.com/base58-io/wdk-wallet-cosmos)) | Wallet Module | Wallet module for Cosmos-compatible blockchains | [Docs](/sdk/community-modules/wdk-wallet-cosmos/) | [Base58](https://base58.io/) | | [@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/) | +| [@kenny_io/wdk-protocol-swidge-lifi](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi) ([GitHub](https://github.com/kenny-io/wdk-lifi-swidge-protocol)) | Swidge Module | LI.FI swap and bridge routes through the WDK swidge interface | [Docs](/sdk/swidge-modules/swidge-lifi/) | [Kenny](https://github.com/kenny-io) | --- diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx new file mode 100644 index 00000000..410048a2 --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx @@ -0,0 +1,147 @@ +--- +title: LI.FI Swidge API Reference +description: API reference for @kenny_io/wdk-protocol-swidge-lifi. +docType: reference +schemaType: APIReference +icon: Code +--- + +## LifiSwidgeProtocol + +`LifiSwidgeProtocol` extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols` and implements the shared WDK swidge methods. + +```javascript +import { + LifiSwidgeProtocol +} from '@kenny_io/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` | Discovery and account-independent quote setup. | + +## 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. + +Throws before execution when validation fails, a configured fee cap is exceeded, 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[] + 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` | + +## 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 or the operation ID is invalid. | +| `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. + + +Review the shared WDK swidge protocol shape. + + diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx new file mode 100644 index 00000000..a206c3cf --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx @@ -0,0 +1,100 @@ +--- +title: LI.FI Swidge Configuration +description: Configuration options for @kenny_io/wdk-protocol-swidge-lifi. +docType: reference +schemaType: TechArticle +icon: Settings +--- + +Configure `LifiSwidgeProtocol` with a WDK EVM account when you need execution, or without an account when you only need discovery. + +```javascript +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +import { LifiSwidgeProtocol } from '@kenny_io/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. | + +## 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']`. | +| `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. | +| `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 +await swidge.swidge(options, { + maxProtocolFeeBps: 20, + maxNetworkFeeBps: 75 +}) +``` + +## 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: ['0x...'] + } +}) +``` + +## 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. + + diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx new file mode 100644 index 00000000..9ada03e7 --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx @@ -0,0 +1,74 @@ +--- +title: LI.FI Swidge Overview +description: Overview of the @kenny_io/wdk-protocol-swidge-lifi module for LI.FI swap and bridge routes. +docType: explanation +schemaType: TechArticle +--- + +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 across chains supported by LI.FI. +- **EVM account support**: Works with `@tetherto/wdk-wallet-evm` and `@tetherto/wdk-wallet-evm-erc-4337`. +- **Discovery without signing**: Supports chain and token discovery without a bound wallet account. +- **Fee controls**: Applies optional `maxNetworkFeeBps` and `maxProtocolFeeBps` limits before execution. +- **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. Call `getSupportedChains()` and `getSupportedTokens()` at runtime before showing route choices or validating user input. + +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 | Discovery and account-independent quote setup only. | + + +`swidge()` can approve and submit one or more EVM transactions. Show the quote, fee breakdown, destination token, destination chain, recipient, and refund address 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? + + diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx new file mode 100644 index 00000000..5527ac6e --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx @@ -0,0 +1,151 @@ +--- +title: LI.FI Swidge Usage +description: Install and use @kenny_io/wdk-protocol-swidge-lifi for LI.FI swap and bridge routes. +docType: how-to +schemaType: TechArticle +icon: BookOpen +--- + +## Install + +```bash +npm install @kenny_io/wdk-protocol-swidge-lifi +``` + +Install the wallet module for the account type you plan to use: + +```bash +npm install @tetherto/wdk-wallet-evm +npm install @tetherto/wdk-wallet-evm-erc-4337 +``` + +## Create the Protocol + +```javascript +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +import { LifiSwidgeProtocol } from '@kenny_io/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 quote = await swidge.quoteSwidge({ + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toChain: 'arbitrum', + recipient: '0xRecipient...', + refundAddress: '0xRefund...', + fromTokenAmount: 10_000_000n, + slippage: 0.01 +}) + +console.log('Expected output:', quote.toTokenAmount) +console.log('Minimum output:', quote.toTokenAmountMin) +console.log('Fees:', quote.fees) +``` + +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({ + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toChain: 'arbitrum', + recipient: '0xRecipient...', + refundAddress: '0xRefund...', + fromTokenAmount: 10_000_000n +}, { + maxNetworkFeeBps: 100, + maxProtocolFeeBps: 50 +}) + +console.log('Swidge ID:', result.id) +console.log('Transaction hash:', result.hash) +``` + +## Track Status + +Use `getSwidgeStatus()` with the returned operation ID. Chain hints can speed up indexing. + +```javascript +const status = await swidge.getSwidgeStatus(result.id, { + fromChain: 1, + toChain: 42161 +}) + +if (status.status === 'completed') { + console.log('Route completed') +} +``` + +## Handle Common Failures + +```javascript +import { + LifiProtocolError, + LifiRateLimitError, + LifiSlippageError, + LifiTimeoutError +} from '@kenny_io/wdk-protocol-swidge-lifi' + +try { + await swidge.swidge(options) +} 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. + + From 707b515de9dc7afae08ae23d2bfaa73d59ca5abd Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 7 Jul 2026 18:34:17 +0530 Subject: [PATCH 02/38] Refine Swidge docs navigation --- src/lib/custom-tree.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/custom-tree.ts b/src/lib/custom-tree.ts index 2652608c..60ec4f8e 100644 --- a/src/lib/custom-tree.ts +++ b/src/lib/custom-tree.ts @@ -233,7 +233,12 @@ export const customTree: Node[] = [ ]), separator('Swap and Bridge'), - page('Swap module reference', '/sdk/swap-modules', 'ArrowLeftRight'), + page('Swidge protocol interface', '/sdk/swidge-modules', 'Route'), + folder('LI.FI', '/sdk/swidge-modules/swidge-lifi', 'Waypoints', [ + usage('/sdk/swidge-modules/swidge-lifi/usage'), + configuration('/sdk/swidge-modules/swidge-lifi/configuration'), + apiReference('/sdk/swidge-modules/swidge-lifi/api-reference'), + ]), folder('Velora', '/sdk/swap-modules/swap-velora-evm', 'ArrowLeftRight', [ usage('/sdk/swap-modules/swap-velora-evm/usage'), guides([ @@ -245,7 +250,6 @@ export const customTree: Node[] = [ configuration('/sdk/swap-modules/swap-velora-evm/configuration'), apiReference('/sdk/swap-modules/swap-velora-evm/api-reference'), ]), - page('Bridge module reference', '/sdk/bridge-modules', 'Link'), folder('USDT0 bridge', '/sdk/bridge-modules/bridge-usdt0-evm', 'Waypoints', [ usage('/sdk/bridge-modules/bridge-usdt0-evm/usage'), guides([ From caa1d5f3b583dc334d8d5a10552b11f6f0d3690d Mon Sep 17 00:00:00 2001 From: Bruce Xu Date: Wed, 8 Jul 2026 13:50:36 +0800 Subject: [PATCH 03/38] Fix LI.FI swidge docs metadata --- content/docs/overview/changelog.mdx | 2 +- content/docs/sdk/all-modules.mdx | 4 ++-- content/docs/sdk/community-modules/index.mdx | 2 +- .../swidge-modules/swidge-lifi/api-reference.mdx | 9 ++++++--- .../swidge-modules/swidge-lifi/configuration.mdx | 15 ++++++++++++--- .../docs/sdk/swidge-modules/swidge-lifi/index.mdx | 9 ++++++--- .../docs/sdk/swidge-modules/swidge-lifi/usage.mdx | 10 ++++------ 7 files changed, 32 insertions(+), 19 deletions(-) diff --git a/content/docs/overview/changelog.mdx b/content/docs/overview/changelog.mdx index 15b07a88..e42218a6 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -90,7 +90,7 @@ Stay up to date with the latest improvements, new features, and bug fixes across ### June 11, 2026 **What's New** -- **swidge-lifi** ([v0.2.0](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi/v/0.2.0)): Add generated TypeScript declarations, request timeouts and retries, typed LI.FI error classes, input and transaction validation, optional trusted-contract checks, and fee-cap handling for LI.FI swidge routes. +- **@lifi/wdk-protocol-swidge-lifi** ([v0.3.0](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi/v/0.3.0)): Add generated TypeScript declarations, request timeouts and retries, typed LI.FI error classes, input and transaction validation, optional trusted-contract checks, fee-cap handling, and default gasless route filtering for LI.FI swidge routes. --- diff --git a/content/docs/sdk/all-modules.mdx b/content/docs/sdk/all-modules.mdx index 4fc7bbad..131454de 100644 --- a/content/docs/sdk/all-modules.mdx +++ b/content/docs/sdk/all-modules.mdx @@ -38,7 +38,7 @@ Swidge is the preferred interface for new protocol providers that can quote and | Module | Provider | Description | Documentation | |--------|----------|-------------|---------------| -| [`@kenny_io/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi) | LI.FI | Swidge routes for LI.FI swap, bridge, and combined swap-plus-bridge operations | [Docs](/sdk/swidge-modules/swidge-lifi/) | +| [`@lifi/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi) | LI.FI | Swidge routes for LI.FI swap, bridge, and combined swap-plus-bridge operations | [Docs](/sdk/swidge-modules/swidge-lifi/) | ## Pricing Modules @@ -90,4 +90,4 @@ Modules built by the WDK community. See the [Community Modules](/sdk/community-m |--------|------------|-------------|---------------| | [`@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/) | | [`@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/) | -| [`@kenny_io/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi) | LI.FI | Swidge swap and bridge route integration | [Docs](/sdk/swidge-modules/swidge-lifi/) | +| [`@lifi/wdk-protocol-swidge-lifi`](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi) | LI.FI | Swidge swap and bridge route integration | [Docs](/sdk/swidge-modules/swidge-lifi/) | diff --git a/content/docs/sdk/community-modules/index.mdx b/content/docs/sdk/community-modules/index.mdx index 1ee031a9..2a1aa58f 100644 --- a/content/docs/sdk/community-modules/index.mdx +++ b/content/docs/sdk/community-modules/index.mdx @@ -21,7 +21,7 @@ Tether and the WDK Team do not endorse or assume responsibility for their code, | [@arkade-os/wdk](https://www.npmjs.com/package/@arkade-os/wdk) ([GitHub](https://github.com/arkade-os/arkade-wdk)) | Wallet Module | Bitcoin wallet module built on the Arkade SDK with Arkade addresses, boarding addresses, BIP21/LNURL/Lightning send routing, and optional Boltz swaps | [README](https://github.com/arkade-os/arkade-wdk#readme) | [Arkade](https://github.com/arkade-os) | | [@base58-io/wdk-wallet-cosmos](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos) ([GitHub](https://github.com/base58-io/wdk-wallet-cosmos)) | Wallet Module | Wallet module for Cosmos-compatible blockchains | [Docs](/sdk/community-modules/wdk-wallet-cosmos/) | [Base58](https://base58.io/) | | [@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/) | -| [@kenny_io/wdk-protocol-swidge-lifi](https://www.npmjs.com/package/@kenny_io/wdk-protocol-swidge-lifi) ([GitHub](https://github.com/kenny-io/wdk-lifi-swidge-protocol)) | Swidge Module | LI.FI swap and bridge routes through the WDK swidge interface | [Docs](/sdk/swidge-modules/swidge-lifi/) | [Kenny](https://github.com/kenny-io) | +| [@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/) | --- diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx index 410048a2..2b78f180 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx @@ -1,6 +1,6 @@ --- title: LI.FI Swidge API Reference -description: API reference for @kenny_io/wdk-protocol-swidge-lifi. +description: API reference for @lifi/wdk-protocol-swidge-lifi. docType: reference schemaType: APIReference icon: Code @@ -13,7 +13,7 @@ icon: Code ```javascript import { LifiSwidgeProtocol -} from '@kenny_io/wdk-protocol-swidge-lifi' +} from '@lifi/wdk-protocol-swidge-lifi' const swidge = new LifiSwidgeProtocol(account, config) ``` @@ -29,7 +29,9 @@ new LifiSwidgeProtocol(account?, config?) | `WalletAccountEvm` | Discovery, quote, status, and execution. | | `WalletAccountEvmErc4337` | Discovery, quote, status, and execution through a smart account. | | `WalletAccountReadOnlyEvm` | Discovery, quote, and status. | -| `undefined` | Discovery and account-independent quote setup. | +| `undefined` | Chain and token discovery only. | + +Use `WalletAccountReadOnlyEvm` for quote-only flows that should not sign transactions. No-account mode cannot provide the source address required by LI.FI quotes. ## Methods @@ -92,6 +94,7 @@ type LifiSwidgeProtocolConfig = { order?: LifiRouteOrder allowBridges?: string[] denyBridges?: string[] + allowDestinationCall?: boolean timeout?: number retries?: number retryDelay?: number diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx index a206c3cf..5a5fe1b6 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx @@ -1,6 +1,6 @@ --- title: LI.FI Swidge Configuration -description: Configuration options for @kenny_io/wdk-protocol-swidge-lifi. +description: Configuration options for @lifi/wdk-protocol-swidge-lifi. docType: reference schemaType: TechArticle icon: Settings @@ -10,7 +10,7 @@ Configure `LifiSwidgeProtocol` with a WDK EVM account when you need execution, o ```javascript import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' -import { LifiSwidgeProtocol } from '@kenny_io/wdk-protocol-swidge-lifi' +import { LifiSwidgeProtocol } from '@lifi/wdk-protocol-swidge-lifi' const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { provider: 'https://mainnet.infura.io/v3/YOUR_KEY' @@ -44,7 +44,8 @@ new LifiSwidgeProtocol(account?, config?) | `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']`. | +| `denyBridges` | `string[]` | Bridge protocol denylist, for example `['across']`. When omitted, native-value bridges are denied by default. Passing any array overrides that default. | +| `allowDestinationCall` | `boolean` | Allows LI.FI routes that execute a destination-chain call, such as a destination-chain swap. Defaults to `false`. | | `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. | | `timeout` | `number` | Per-request timeout in milliseconds. Defaults to `30000`. | @@ -63,6 +64,14 @@ await swidge.swidge(options, { }) ``` +## Route Filtering + +The module is gasless-oriented by default for ERC-4337 sponsored execution. In that flow, the paymaster or sponsor can cover source-chain gas, but the LI.FI route transaction itself must not require separate native token value. When `denyBridges` is omitted, the module sends a built-in native-value bridge denylist to LI.FI. Passing `denyBridges` replaces that default list, so `denyBridges: ['across']` denies only `across`, and `denyBridges: []` clears the default list. + +The module also passes `allowDestinationCall=false` by default. This excludes routes that require a destination-chain call, such as a swap after bridging. Set `allowDestinationCall: true` only when broader route coverage is more important than filtering out routes that can finish partially. + +Execution still rejects any quote whose transaction request requires native token value before sending approvals or the route transaction. + ## 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. diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx index 9ada03e7..aa51cf4e 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx @@ -1,6 +1,6 @@ --- title: LI.FI Swidge Overview -description: Overview of the @kenny_io/wdk-protocol-swidge-lifi module for LI.FI swap and bridge routes. +description: Overview of the @lifi/wdk-protocol-swidge-lifi module for LI.FI swap and bridge routes. docType: explanation schemaType: TechArticle --- @@ -16,6 +16,7 @@ Use this module when an app needs live LI.FI route discovery, exact-input or exa - **EVM account support**: Works with `@tetherto/wdk-wallet-evm` and `@tetherto/wdk-wallet-evm-erc-4337`. - **Discovery without signing**: Supports chain and token discovery without a bound wallet account. - **Fee controls**: Applies optional `maxNetworkFeeBps` and `maxProtocolFeeBps` limits before execution. +- **Gasless route defaults**: Filters native-value bridge routes by default 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. @@ -47,10 +48,12 @@ Numeric LI.FI chain IDs are also accepted where the module accepts chain input. | `WalletAccountEvm` | Quotes and executes routes. | | `WalletAccountEvmErc4337` | Quotes and executes routes with smart-account gas handling. | | `WalletAccountReadOnlyEvm` | Quotes, status lookups, and discovery only. | -| No account | Discovery and account-independent quote setup only. | +| No account | Chain and token discovery only. | + +Use a read-only EVM account when you need quotes without signing. LI.FI quotes require a source address, so no-account mode is limited to chain and token discovery. -`swidge()` can approve and submit one or more EVM transactions. Show the quote, fee breakdown, destination token, destination chain, recipient, and refund address before calling it. +`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 diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx index 5527ac6e..7ae41d4f 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx @@ -1,6 +1,6 @@ --- title: LI.FI Swidge Usage -description: Install and use @kenny_io/wdk-protocol-swidge-lifi for LI.FI swap and bridge routes. +description: Install and use @lifi/wdk-protocol-swidge-lifi for LI.FI swap and bridge routes. docType: how-to schemaType: TechArticle icon: BookOpen @@ -9,7 +9,7 @@ icon: BookOpen ## Install ```bash -npm install @kenny_io/wdk-protocol-swidge-lifi +npm install @lifi/wdk-protocol-swidge-lifi ``` Install the wallet module for the account type you plan to use: @@ -23,7 +23,7 @@ npm install @tetherto/wdk-wallet-evm-erc-4337 ```javascript import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' -import { LifiSwidgeProtocol } from '@kenny_io/wdk-protocol-swidge-lifi' +import { LifiSwidgeProtocol } from '@lifi/wdk-protocol-swidge-lifi' const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { provider: 'https://mainnet.infura.io/v3/YOUR_KEY' @@ -62,7 +62,6 @@ const quote = await swidge.quoteSwidge({ toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', toChain: 'arbitrum', recipient: '0xRecipient...', - refundAddress: '0xRefund...', fromTokenAmount: 10_000_000n, slippage: 0.01 }) @@ -92,7 +91,6 @@ const result = await swidge.swidge({ toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', toChain: 'arbitrum', recipient: '0xRecipient...', - refundAddress: '0xRefund...', fromTokenAmount: 10_000_000n }, { maxNetworkFeeBps: 100, @@ -126,7 +124,7 @@ import { LifiRateLimitError, LifiSlippageError, LifiTimeoutError -} from '@kenny_io/wdk-protocol-swidge-lifi' +} from '@lifi/wdk-protocol-swidge-lifi' try { await swidge.swidge(options) From 7c028c18149676bea6ab30c93f63fc37fd71f1b4 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Wed, 8 Jul 2026 16:01:27 +0530 Subject: [PATCH 04/38] remove unsupported lifi refund option docs --- content/docs/sdk/swidge-modules/swidge-lifi/index.mdx | 2 +- content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx index 9ada03e7..938a3a3c 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx @@ -50,7 +50,7 @@ Numeric LI.FI chain IDs are also accepted where the module accepts chain input. | No account | Discovery and account-independent quote setup only. | -`swidge()` can approve and submit one or more EVM transactions. Show the quote, fee breakdown, destination token, destination chain, recipient, and refund address before calling it. +`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 diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx index 5527ac6e..13351628 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx @@ -62,7 +62,6 @@ const quote = await swidge.quoteSwidge({ toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', toChain: 'arbitrum', recipient: '0xRecipient...', - refundAddress: '0xRefund...', fromTokenAmount: 10_000_000n, slippage: 0.01 }) @@ -92,7 +91,6 @@ const result = await swidge.swidge({ toToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', toChain: 'arbitrum', recipient: '0xRecipient...', - refundAddress: '0xRefund...', fromTokenAmount: 10_000_000n }, { maxNetworkFeeBps: 100, From a9c41d697df5644c22c6a5f679c59a3831853497 Mon Sep 17 00:00:00 2001 From: Bruce Xu Date: Tue, 21 Jul 2026 21:06:23 +0800 Subject: [PATCH 05/38] docs: align LI.FI swidge docs with @lifi/wdk-protocol-swidge-lifi v0.5.1 The 0.4.0 breaking change made gasless behavior opt-in, reversing the defaults the docs described. Correct the docs to match shipped v0.5.1: - denyBridges: omitting sends no filter (LI.FI considers all bridges); document NATIVE_VALUE_BRIDGE_DENY_LIST for gasless opt-in - allowDestinationCall: forwarded only when set; LI.FI default (true) applies when omitted (was documented as default false) - Add allowNativeValue config option (default true) to tables and type - Rewrite the Route Filtering section around explicit gasless opt-in - Reword the "Gasless route defaults" feature bullet to opt-in - Document the minAmountOut execution guard for quote-first flows - Update changelog entry to v0.5.1 and correct the feature description Co-Authored-By: Claude Opus 4.8 (1M context) --- content/docs/overview/changelog.mdx | 2 +- .../swidge-lifi/api-reference.mdx | 5 +++- .../swidge-lifi/configuration.mdx | 27 +++++++++++++++---- .../sdk/swidge-modules/swidge-lifi/index.mdx | 2 +- .../sdk/swidge-modules/swidge-lifi/usage.mdx | 14 ++++++++++ 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/content/docs/overview/changelog.mdx b/content/docs/overview/changelog.mdx index e42218a6..7e8cb1c7 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -90,7 +90,7 @@ Stay up to date with the latest improvements, new features, and bug fixes across ### June 11, 2026 **What's New** -- **@lifi/wdk-protocol-swidge-lifi** ([v0.3.0](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi/v/0.3.0)): Add generated TypeScript declarations, request timeouts and retries, typed LI.FI error classes, input and transaction validation, optional trusted-contract checks, fee-cap handling, and default gasless route filtering for LI.FI swidge routes. +- **@lifi/wdk-protocol-swidge-lifi** ([v0.5.1](https://www.npmjs.com/package/@lifi/wdk-protocol-swidge-lifi/v/0.5.1)): Add generated TypeScript declarations, request timeouts and retries, typed LI.FI error classes, input and transaction validation, optional trusted-contract checks, fee-cap handling, an optional `minAmountOut` execution guard for quote-first flows, opt-in gasless route filtering (`NATIVE_VALUE_BRIDGE_DENY_LIST` and `allowNativeValue`), and per-fee-token chain attribution for LI.FI swidge routes. --- diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx index 2b78f180..3c9da48a 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/api-reference.mdx @@ -62,7 +62,9 @@ swidge( Executes through the bound writable account. The module sends required approval transactions before the route transaction where needed. -Throws before execution when validation fails, a configured fee cap is exceeded, or `trustedContracts` rejects the quote target or approval address. +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?)` @@ -95,6 +97,7 @@ type LifiSwidgeProtocolConfig = { allowBridges?: string[] denyBridges?: string[] allowDestinationCall?: boolean + allowNativeValue?: boolean timeout?: number retries?: number retryDelay?: number diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx index 5a5fe1b6..26dec0f8 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/configuration.mdx @@ -44,8 +44,9 @@ new LifiSwidgeProtocol(account?, config?) | `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, native-value bridges are denied by default. Passing any array overrides that default. | -| `allowDestinationCall` | `boolean` | Allows LI.FI routes that execute a destination-chain call, such as a destination-chain swap. Defaults to `false`. | +| `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 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. | | `timeout` | `number` | Per-request timeout in milliseconds. Defaults to `30000`. | @@ -66,11 +67,27 @@ await swidge.swidge(options, { ## Route Filtering -The module is gasless-oriented by default for ERC-4337 sponsored execution. In that flow, the paymaster or sponsor can cover source-chain gas, but the LI.FI route transaction itself must not require separate native token value. When `denyBridges` is omitted, the module sends a built-in native-value bridge denylist to LI.FI. Passing `denyBridges` replaces that default list, so `denyBridges: ['across']` denies only `across`, and `denyBridges: []` clears the default list. +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. -The module also passes `allowDestinationCall=false` by default. This excludes routes that require a destination-chain call, such as a swap after bridging. Set `allowDestinationCall: true` only when broader route coverage is more important than filtering out routes that can finish partially. +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: -Execution still rejects any quote whose transaction request requires native token value before sending approvals or the route transaction. +```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 diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx index aa51cf4e..76b56326 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/index.mdx @@ -16,7 +16,7 @@ Use this module when an app needs live LI.FI route discovery, exact-input or exa - **EVM account support**: Works with `@tetherto/wdk-wallet-evm` and `@tetherto/wdk-wallet-evm-erc-4337`. - **Discovery without signing**: Supports chain and token discovery without a bound wallet account. - **Fee controls**: Applies optional `maxNetworkFeeBps` and `maxProtocolFeeBps` limits before execution. -- **Gasless route defaults**: Filters native-value bridge routes by default so ERC-4337 sponsored execution can cover gas without the LI.FI route requiring separate native token value. +- **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. diff --git a/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx index 7ae41d4f..906f2c06 100644 --- a/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx +++ b/content/docs/sdk/swidge-modules/swidge-lifi/usage.mdx @@ -101,6 +101,20 @@ 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(options) +// ...user reviews and confirms the quote... + +const result = await swidge.swidge({ + ...options, + minAmountOut: quote.toTokenAmountMin +}) +``` + ## Track Status Use `getSwidgeStatus()` with the returned operation ID. Chain hints can speed up indexing. From 78d08fa12d98981876f527695f52c36b4d0ba7a8 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Mon, 13 Jul 2026 11:10:37 +0530 Subject: [PATCH 06/38] docs: cover July 8 and 9 WDK releases --- content/docs/overview/changelog.mdx | 24 ++++ .../docs/sdk/core-module/api-reference.mdx | 28 +++- content/docs/sdk/swidge-modules/index.mdx | 5 + .../wallet-btc/api-reference.mdx | 16 ++- .../wallet-btc/configuration.mdx | 2 +- .../wallet-btc/guides/get-started.mdx | 2 +- .../wallet-btc/guides/send-transactions.mdx | 22 +++- .../sdk/wallet-modules/wallet-btc/index.mdx | 2 +- .../wallet-tron-gasfree/api-reference.mdx | 66 ++++++---- .../wallet-tron-gasfree/configuration.mdx | 47 ++++--- .../guides/handle-errors.mdx | 4 +- .../guides/send-transactions.mdx | 2 + .../guides/transfer-tokens.mdx | 4 +- .../react-native-quickstart.mdx | 27 +--- .../docs/tools/pear-wrk-wdk/api-reference.mdx | 117 ++++++++++++++++- .../docs/tools/pear-wrk-wdk/configuration.mdx | 120 ++++++++++++++++-- content/docs/tools/pear-wrk-wdk/index.mdx | 13 +- .../tools/react-native-core/api-reference.mdx | 79 ++++++++++++ .../docs/tools/react-native-core/index.mdx | 53 +++----- .../docs/tools/wdk-utils/api-reference.mdx | 55 ++++++++ .../docs/tools/wdk-utils/configuration.mdx | 36 +++++- content/docs/tools/wdk-utils/index.mdx | 6 +- .../tools/worklet-bundler/api-reference.mdx | 61 ++++++++- .../tools/worklet-bundler/configuration.mdx | 100 ++++++++++++--- content/docs/tools/worklet-bundler/index.mdx | 20 ++- 25 files changed, 744 insertions(+), 167 deletions(-) diff --git a/content/docs/overview/changelog.mdx b/content/docs/overview/changelog.mdx index a6efd257..a405cf96 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -10,6 +10,30 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### 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** diff --git a/content/docs/sdk/core-module/api-reference.mdx b/content/docs/sdk/core-module/api-reference.mdx index e5aaded8..4953c100 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` | - | @@ -259,7 +260,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 +290,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 +420,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 @@ -666,6 +667,20 @@ wdk.dispose() ## Types +### WdkAccount + +`WdkAccount` is the exported type returned by `getAccount()` and `getAccountByPath()`. It combines the writable wallet methods with the protocol registration and lookup surface. This is a type-only export; there is no runtime `WdkAccount` value. + +```typescript title="Use WdkAccount" +import WDK, { type WdkAccount } from '@tetherto/wdk' + +async function getBitcoinAccount(wdk: WDK): Promise { + return wdk.getAccount('bitcoin') +} +``` + +The published definition is `IWalletAccount & IWalletAccountWithProtocols`. Concrete wallet packages can expose additional methods beyond this shared shape. The declared return type of `IWalletAccountWithProtocols.registerProtocol()` remains `IWalletAccountWithProtocols`, so assigning the result of that chained call can narrow away the writable-account methods in TypeScript. + ### FeeRates ```typescript title="Type: FeeRates" @@ -832,6 +847,7 @@ type SwidgeOptions = { 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 diff --git a/content/docs/sdk/swidge-modules/index.mdx b/content/docs/sdk/swidge-modules/index.mdx index c30b1db2..7779b044 100644 --- a/content/docs/sdk/swidge-modules/index.mdx +++ b/content/docs/sdk/swidge-modules/index.mdx @@ -112,9 +112,14 @@ console.log('Fees:', quote.fees) | `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%. | +| `minAmountOut` | `number \| bigint` | Optional minimum destination amount, in destination-token base units, passed to the provider. | | `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`. | + +The shared WDK wallet package passes `minAmountOut` to the concrete provider but does not validate or enforce it. Confirm provider-specific support before relying on this field as an execution guard. + + ## Execute a route 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. diff --git a/content/docs/sdk/wallet-modules/wallet-btc/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-btc/api-reference.mdx index c5b98935..72901fdf 100644 --- a/content/docs/sdk/wallet-modules/wallet-btc/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-btc/api-reference.mdx @@ -127,9 +127,9 @@ new WalletAccountBtc(seed, path, config) |--------|-------------|---------| | `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}>` | +| `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(options)` | Estimates the fee for a transaction | `Promise<{fee: bigint}>` | +| `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` | @@ -160,10 +160,10 @@ console.log('Balance:', balance, 'satoshis') ``` ##### `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. +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): Transaction options +- `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. @@ -174,7 +174,7 @@ Sends a Bitcoin transaction to a single recipient and optionally polls after bro - `hash`: Transaction hash - `fee`: Transaction fee in satoshis -**Throws:** Error if the estimated transaction fee exceeds `transactionMaxFee` when configured. +**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 @@ -212,10 +212,10 @@ console.log('Signed transaction:', signedTransaction) ``` ##### `quoteSendTransaction(options)` -Estimates the fee for a transaction without broadcasting it. +Estimates the fee for transaction options or calculates the fee encoded by signed raw transaction hex, without broadcasting it. **Parameters:** -- `options` (BtcTransaction): Same as sendTransaction options +- `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. @@ -233,6 +233,8 @@ const quote = await account.quoteSendTransaction({ 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. diff --git a/content/docs/sdk/wallet-modules/wallet-btc/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-btc/configuration.mdx index 42add2d4..be14821b 100644 --- a/content/docs/sdk/wallet-modules/wallet-btc/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-btc/configuration.mdx @@ -234,7 +234,7 @@ const config = { ### Transaction Max Fee -The `transactionMaxFee` option sets the maximum fee, in satoshis, for BTC `sendTransaction()` and `signTransaction()` operations. Use it to stop a native BTC send or signed transaction from being created when the estimated fee is higher than your app allows. +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) diff --git a/content/docs/sdk/wallet-modules/wallet-btc/guides/get-started.mdx b/content/docs/sdk/wallet-modules/wallet-btc/guides/get-started.mdx index 3aaf87d4..766555fa 100644 --- a/content/docs/sdk/wallet-modules/wallet-btc/guides/get-started.mdx +++ b/content/docs/sdk/wallet-modules/wallet-btc/guides/get-started.mdx @@ -9,7 +9,7 @@ This guide explains how to [install the package](#1-install-the-package), [creat ### Prerequisites -* **[Node.js](https://nodejs.org/)**: version 18 or higher. +* **[Node.js](https://nodejs.org/)**: version 20.19.0 or higher. Wallet BTC beta.11 depends on `@bitcoinerlab/descriptors` 3.1.7, which sets this minimum. * **[npm](https://www.npmjs.com/)**: usually comes with Node.js. ```bash title="Install @tetherto/wdk-wallet-btc" diff --git a/content/docs/sdk/wallet-modules/wallet-btc/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-btc/guides/send-transactions.mdx index 6f134564..406a9a88 100644 --- a/content/docs/sdk/wallet-modules/wallet-btc/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-btc/guides/send-transactions.mdx @@ -3,7 +3,7 @@ title: Send Transactions description: Send BTC and estimate transaction 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), [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). +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). ## Send BTC @@ -60,6 +60,26 @@ console.log('Signed transaction:', signedTransaction) `signTransaction()` returns the signed transaction hex. Use `sendTransaction()` when WDK should sign, broadcast, and return the transaction hash. +## 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 +}) + +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) +``` + +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. + ## Estimate Fees You can estimate the fee for a transaction without broadcasting it using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#quotesendtransactionoptions): diff --git a/content/docs/sdk/wallet-modules/wallet-btc/index.mdx b/content/docs/sdk/wallet-modules/wallet-btc/index.mdx index 06876bf5..a298cde8 100644 --- a/content/docs/sdk/wallet-modules/wallet-btc/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-btc/index.mdx @@ -28,7 +28,7 @@ Use [`getAccountByPath`](/sdk/wallet-modules/wallet-btc/api-reference#getaccount - **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) +- **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 diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/api-reference.mdx index d172d99e..2650bd3f 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/api-reference.mdx @@ -34,7 +34,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 @@ -45,8 +46,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' }) ``` @@ -56,7 +56,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)` @@ -88,7 +88,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 @@ -129,7 +129,7 @@ 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, activationFee: 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\` | @@ -181,7 +181,9 @@ 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, 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. @@ -190,7 +192,9 @@ Transfers TRC20 tokens to another address using the gas-free service. 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') @@ -413,26 +417,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; } ``` @@ -445,13 +452,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; } ``` @@ -585,9 +592,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. diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx index 575cd323..5b89ad1d 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx @@ -32,8 +32,7 @@ const config = { // 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 + gasFreeApiSecret: 'your-api-secret' // Provide together with gasFreeApiKey } const wallet = new WalletManagerTronGasfree(seedPhrase, config) @@ -53,6 +52,10 @@ const config2 = { `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. + +`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: @@ -71,12 +74,11 @@ const account = new WalletAccountTronGasfree( serviceProvider: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', gasFreeApiKey: 'your-api-key', // Optional: provide with gasFreeApiSecret - gasFreeApiSecret: 'your-api-secret', - transferMaxFee: 10000000 // Optional + gasFreeApiSecret: 'your-api-secret' } ) -// Read-only account (transferMaxFee not needed) +// Read-only account (fee-cap fields are omitted) const readOnlyAccount = new WalletAccountReadOnlyTronGasfree( 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH', // Tron address { @@ -228,7 +230,7 @@ const config = { ### 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` @@ -240,26 +242,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 diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/handle-errors.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/handle-errors.mdx index d942e817..2561b760 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/handle-errors.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/handle-errors.mdx @@ -17,6 +17,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') @@ -51,7 +53,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() diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/send-transactions.mdx index 68538b53..d6e2f57d 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/send-transactions.mdx @@ -7,6 +7,8 @@ schemaType: TechArticle 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 diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/transfer-tokens.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/transfer-tokens.mdx index 21f312cf..3bbecac2 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/transfer-tokens.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/transfer-tokens.mdx @@ -22,7 +22,7 @@ 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. Use `activationFee` to show that activation portion separately; it is `0n` when no activation fee applies. +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 @@ -41,6 +41,8 @@ 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): diff --git a/content/docs/start-building/react-native-quickstart.mdx b/content/docs/start-building/react-native-quickstart.mdx index 83b0561c..9ebb47a8 100644 --- a/content/docs/start-building/react-native-quickstart.mdx +++ b/content/docs/start-building/react-native-quickstart.mdx @@ -128,6 +128,10 @@ Integrate WDK into your existing React Native or Expo project using `@tetherto/w npm install @tetherto/wdk-react-native-core ``` + +`@tetherto/wdk-react-native-core` `v1.0.0-beta.13` includes the React Native source entry used by Metro, but its npm artifact omits the declared `dist/index.js` and `dist/index.d.ts` files. Default Node resolution and package declaration lookup fail for this tag. Use it only in a Metro flow you have verified; if your build requires the missing outputs, wait for a corrected artifact. + + ### Step 2: Configure Android minSdkVersion The library requires **Android API 29** or higher to support `react-native-bare-kit`. @@ -175,11 +179,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 @@ -206,23 +206,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 diff --git a/content/docs/tools/pear-wrk-wdk/api-reference.mdx b/content/docs/tools/pear-wrk-wdk/api-reference.mdx index 95783a77..516b193e 100644 --- a/content/docs/tools/pear-wrk-wdk/api-reference.mdx +++ b/content/docs/tools/pear-wrk-wdk/api-reference.mdx @@ -20,10 +20,12 @@ schemaType: APIReference | `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 @@ -40,6 +42,8 @@ schemaType: APIReference | `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` @@ -65,13 +69,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.9, 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` @@ -104,7 +110,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` @@ -133,6 +141,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. @@ -177,12 +203,20 @@ Registers the server-side handler used to service `registerWallet()` requests. Registers the server-side handler used to service `registerProtocol()` requests. +#### `onCallModule` + +Registers the worklet-side handler used to service `callModule()` requests. + +#### `onModuleEvent` + +Registers the host-side handler used to receive `moduleEvent()` messages. + ### Export: `registerRpcHandlers(rpc, context)` 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 @@ -203,6 +237,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 } ``` @@ -225,6 +300,38 @@ interface CallMethodOptions { } ``` +## 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.9 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. + + +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? diff --git a/content/docs/tools/pear-wrk-wdk/configuration.mdx b/content/docs/tools/pear-wrk-wdk/configuration.mdx index e453986f..29bd6dc5 100644 --- a/content/docs/tools/pear-wrk-wdk/configuration.mdx +++ b/content/docs/tools/pear-wrk-wdk/configuration.mdx @@ -1,33 +1,69 @@ --- title: Pear Worklet 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() + }) } ``` @@ -39,11 +75,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: { @@ -61,6 +99,11 @@ const workletConfig = { environment: 'sandbox' } } + }, + modules: { + preferences: { + storagePath: '/app-data/preferences' + } } } ``` @@ -70,8 +113,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.9. 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): @@ -92,7 +140,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.9, 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 @@ -119,6 +169,7 @@ 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 @@ -139,6 +190,53 @@ const result = await hrpc.callMethod({ - 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. +## 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.9 supports WDK initialization and disposal, secret/mnemonic operations, wallet-account calls, and dynamic wallet/protocol registration. 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. + + *** ## Need Help? diff --git a/content/docs/tools/pear-wrk-wdk/index.mdx b/content/docs/tools/pear-wrk-wdk/index.mdx index b88842e1..0aa628c0 100644 --- a/content/docs/tools/pear-wrk-wdk/index.mdx +++ b/content/docs/tools/pear-wrk-wdk/index.mdx @@ -1,9 +1,9 @@ --- title: Pear Worklet 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`. @@ -13,6 +13,8 @@ Powered by `@tetherto/pear-wrk-wdk`. - **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 })` +- **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 @@ -20,14 +22,19 @@ 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). + +Generic-module calls and events are HRPC-only in `v1.0.0-beta.9`; 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 diff --git a/content/docs/tools/react-native-core/api-reference.mdx b/content/docs/tools/react-native-core/api-reference.mdx index c1d42709..ce3d8a87 100644 --- a/content/docs/tools/react-native-core/api-reference.mdx +++ b/content/docs/tools/react-native-core/api-reference.mdx @@ -12,6 +12,8 @@ schemaType: APIReference | [`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 | @@ -25,6 +27,8 @@ schemaType: APIReference | `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` | @@ -381,6 +385,76 @@ function SwapQuoteScreen() { --- +## useModule + +Hook that creates a typed proxy for one named generic module in the active HRPC worklet. Every method access is forwarded through `ModuleService.callModule()`. The proxy also exposes `on(event, listener)` for module events. + + +React Native Core `v1.0.0-beta.13` exposes this API but pins Pear Worklet `v1.0.0-beta.8`, which does not contain the required module HRPC methods. The default dependency graph therefore cannot run these calls. Installing Pear Worklet beta.9 separately does not replace the nested beta.8 dependency; wait for a dependency-aligned React Native Core release before adopting this API. + + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `moduleName` | `string` | Name shared by the bundler's build-time `modules` map and runtime `wdkConfigs.modules` map | + +### Returns + +Returns `UseModuleProxy`. Define module methods as asynchronous in `T` because every proxy call crosses the worklet boundary and returns a promise. + +```typescript title="UseModuleProxy" +type UseModuleProxy = T & { + on: (event: string, listener: ModuleEventListener) => () => void +} + +type ModuleEventListener = (payload: unknown) => void +``` + +### Example + +```tsx title="Call A Named Module" +import { useEffect } from 'react' +import { useModule } from '@tetherto/wdk-react-native-core' + +type PreferencesModule = { + getTheme: () => Promise<'light' | 'dark'> + setTheme: (theme: 'light' | 'dark') => Promise +} + +function PreferencesScreen() { + const preferences = useModule('preferences') + + useEffect(() => { + return preferences.on('changed', payload => { + console.log('Preferences changed:', payload) + }) + }, [preferences]) + + // Module calls are asynchronous. + // const theme = await preferences.getTheme() +} +``` + +Subscriptions are removed automatically on unmount or when `moduleName` changes. Calling the returned unsubscribe function removes a subscription earlier. + +--- + +## ModuleService + +Low-level static service used by `useModule()`. Use it when a hook is not appropriate but the React Native worklet has already initialized. + +| Method | Signature | Description | +|--------|-----------|-------------| +| `callModule()` | `callModule(moduleName: string, method: string, ...args: unknown[]): Promise` | Serialize positional arguments, call the named module method, and parse the JSON response | +| `onModuleEvent()` | `onModuleEvent(moduleName: string, event: string, listener: ModuleEventListener): () => void` | Subscribe to one named event and return an unsubscribe function | + +`callModule()` requires non-empty module and method names plus an initialized worklet. It returns `undefined` when the worklet response is absent or empty. Arguments and results must be safely JSON-serializable; `bigint` arguments are serialized as strings, while circular and non-standard-prototype objects are rejected. + +`onModuleEvent()` parses JSON event payloads when possible and otherwise delivers the raw string. Listener failures do not stop delivery to other listeners. + +--- + ## useAddresses Hook to load and query wallet addresses across all networks. @@ -732,9 +806,14 @@ interface WdkConfigs, TProtocol = Record } + modules?: { + [moduleName: string]: Record + } } ``` +`modules` contains runtime configuration for generic modules. It is distinct from the Worklet Bundler's build-time `modules` map, which selects package names, factories, and forwarded events. Use the same module name in both maps. + #### WdkNetworkConfig Wrapper around `NetworkConfig` with typed `config` field. diff --git a/content/docs/tools/react-native-core/index.mdx b/content/docs/tools/react-native-core/index.mdx index bd31c587..54d7b7d6 100644 --- a/content/docs/tools/react-native-core/index.mdx +++ b/content/docs/tools/react-native-core/index.mdx @@ -8,12 +8,21 @@ description: Hooks-based React Native library for building multi-chain wallet ap ## Features - **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 + + +In `v1.0.0-beta.13`, the generic module API is not runnable through the default dependency graph: React Native Core pins Pear Worklet `v1.0.0-beta.8`, while module calls and events require the HRPC methods added in Pear Worklet `v1.0.0-beta.9`. Installing beta.9 separately does not replace the nested beta.8 dependency. Wait for a dependency-aligned React Native Core release before using `useModule()` or `ModuleService` in an end-to-end integration. + + + +The beta.13 npm artifact ships its `react-native` source entry but omits the declared default JavaScript and declaration outputs under `dist/`. Consume this release through React Native/Metro; the tag does not provide a working default Node entrypoint. + ## Quick Start @@ -67,9 +76,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: @@ -93,27 +100,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 @@ -124,23 +124,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 @@ -157,6 +141,7 @@ WdkAppProvider +-- useWalletManager() - create, restore, lock, unlock, delete wallets +-- useAccount() - address, send, sign, verify, estimateFee +-- useProtocol() - call bridge, swap, lending, and fiat protocol methods + +-- useModule() - call named generic modules and subscribe to events (dependency-aligned HRPC builds only) +-- useAddresses() - load and query addresses +-- useBalance() - single balance with TanStack Query +-- useBalancesForWallet() - bulk balance fetch for one account index diff --git a/content/docs/tools/wdk-utils/api-reference.mdx b/content/docs/tools/wdk-utils/api-reference.mdx index 20825c1f..4510ae06 100644 --- a/content/docs/tools/wdk-utils/api-reference.mdx +++ b/content/docs/tools/wdk-utils/api-reference.mdx @@ -361,6 +361,61 @@ import { decryptWithKey } from '@tetherto/wdk-utils' const plaintext = decryptWithKey(encrypted, key) ``` +### Seed Key Derivation Helpers + +Derive deterministic, domain-separated key material from high-entropy seed bytes. + +| Function | Description | Returns | +| --- | --- | --- | +| `deriveSeedKey(seed, options)` | Derive a byte key with HKDF-SHA256. | `Uint8Array` | +| `deriveSeedKeyPair(seed, options)` | Derive a deterministic Ed25519 keypair. | `KeyPair` | + +```typescript title="Seed Key Derivation Types" +type SeedKeyOptions = { + salt: string | Uint8Array + info: string | Uint8Array + length?: number +} + +type KeyPair = { + publicKey: Uint8Array + secretKey: Uint8Array +} +``` + +#### `deriveSeedKey(seed, options)` + +Derive a key using HKDF-SHA256. `salt` and `info` are required caller-controlled domain-separation values, and `length` defaults to 32 bytes. + +```javascript title="Derive A Seed Key" +import { deriveSeedKey } from '@tetherto/wdk-utils' + +const key = deriveSeedKey(seedBytes, { + salt: 'com.example.wallet/v1', + info: 'local-encryption', + length: 32 +}) +``` + +The function throws `salt is required` or `info is required` when either field is absent. String inputs are consumed as raw UTF-8 bytes; a mnemonic string is not converted into BIP-39 seed bytes. + +#### `deriveSeedKeyPair(seed, options)` + +Derive a deterministic Ed25519 keypair from the same seed, salt, and info inputs. The function always uses a 32-byte derived seed, even if `options.length` is supplied, and returns a 32-byte public key plus a 64-byte secret key laid out as the derived seed followed by the public key. + +```javascript title="Derive An Ed25519 Keypair" +import { deriveSeedKeyPair } from '@tetherto/wdk-utils' + +const keyPair = deriveSeedKeyPair(seedBytes, { + salt: 'com.example.wallet/v1', + info: 'device-signing' +}) +``` + + +Treat the returned `secretKey` as sensitive and clear it after its final use. The implementation clears its intermediate derived seed, but the returned secret key remains in caller-managed memory. + + ### BIP-21 Bitcoin Payment URI Helpers | Function | Description | Returns | diff --git a/content/docs/tools/wdk-utils/configuration.mdx b/content/docs/tools/wdk-utils/configuration.mdx index 3a115c60..2a6227a0 100644 --- a/content/docs/tools/wdk-utils/configuration.mdx +++ b/content/docs/tools/wdk-utils/configuration.mdx @@ -1,6 +1,6 @@ --- title: WDK Utils Configuration -description: Install and import validation, encryption, BIP-21, BOLT11, and EIP-681 helpers from @tetherto/wdk-utils +description: Install and import validation, encryption, key-derivation, and payment-request helpers from @tetherto/wdk-utils --- This package does not have constructor options or runtime configuration. This page shows how to install `@tetherto/wdk-utils`, import the helpers you need, and understand the published runtime surface. @@ -68,6 +68,17 @@ import { } from '@tetherto/wdk-utils' ``` +## Import seed key derivation helpers + +Derive domain-separated byte keys or deterministic Ed25519 keypairs from high-entropy seed bytes: + +```javascript title="Import Seed Key Derivation Helpers" +import { + deriveSeedKey, + deriveSeedKeyPair +} from '@tetherto/wdk-utils' +``` + ## Import BOLT11 helpers You can validate, decode, sign, and encode BOLT11 Lightning invoices from the package entrypoint: @@ -91,6 +102,9 @@ import { - `encrypt()` returns a versioned payload with hex-encoded `salt`, `iv`, `tag`, and `ciphertext` fields plus the scrypt cost parameters used for key derivation. - `decrypt()` reads the scrypt cost parameters from the encrypted payload when they are present. - `deriveKey()` returns a 32-byte `Uint8Array` key, and `decryptWithKey()` can reuse that key for decrypting a payload. +- `deriveSeedKey()` uses HKDF-SHA256 and defaults to a 32-byte output. Both `salt` and `info` are required and are chosen by the caller; the package provides no built-in domain labels. +- Pass high-entropy BIP-39 seed bytes to the seed key derivation helpers, not a mnemonic phrase. Accepted string inputs are consumed literally as UTF-8 and are not converted from mnemonic words into seed bytes. +- `deriveSeedKeyPair()` always derives a 32-byte Ed25519 seed and returns a 32-byte `publicKey` plus a sensitive 64-byte `secretKey`, regardless of an `options.length` value. - BIP-21 amounts are decimal BTC strings with up to eight decimal places and a maximum value of `21000000`. - BOLT11 helpers support invoices for `bitcoin`, `testnet`, `regtest`, and `signet` networks. - `decode()` returns user-provided invoice descriptions when they are present. Sanitize descriptions before rendering them in HTML or storing them. @@ -161,6 +175,26 @@ const encrypted = encrypt(seedPhrase, passphrase) const restoredSeedPhrase = decrypt(encrypted, passphrase) ``` +You can derive independent keys for application-specific purposes by using distinct domain labels: + +```javascript title="Derive Domain-Separated Keys" +import { deriveSeedKey, deriveSeedKeyPair } from '@tetherto/wdk-utils' + +const syncKey = deriveSeedKey(seedBytes, { + salt: 'com.example.wallet/v1', + info: 'sync-encryption' +}) + +const signingKeyPair = deriveSeedKeyPair(seedBytes, { + salt: 'com.example.wallet/v1', + info: 'device-signing' +}) + +// Clear sensitive outputs after their final use. +syncKey.fill(0) +signingKeyPair.secretKey.fill(0) +``` + *** ## Need Help? diff --git a/content/docs/tools/wdk-utils/index.mdx b/content/docs/tools/wdk-utils/index.mdx index 3bc2f552..44c57982 100644 --- a/content/docs/tools/wdk-utils/index.mdx +++ b/content/docs/tools/wdk-utils/index.mdx @@ -1,15 +1,16 @@ --- title: WDK Utils -description: Address validation, seed encryption, BIP-21, BOLT11 invoice, and EIP-681 request helpers for @tetherto/wdk-utils +description: Address validation, seed encryption, deterministic key derivation, and payment-request helpers for @tetherto/wdk-utils --- -WDK Utils provides validation helpers for Bitcoin, EVM, Lightning, Spark, Tron, and UMA identifiers, passphrase-based seed encryption helpers, BIP-21 Bitcoin payment URI helpers, BOLT11 invoice helpers, and EIP-681 request parsing helpers for token transfer deep links. Powered by [`@tetherto/wdk-utils`](https://github.com/tetherto/wdk-utils). +WDK Utils provides validation helpers for Bitcoin, EVM, Lightning, Spark, Tron, and UMA identifiers, passphrase-based seed encryption, deterministic HKDF-SHA256 and Ed25519 key derivation, BIP-21 Bitcoin payment URI helpers, BOLT11 invoice helpers, and EIP-681 request parsing for token transfer deep links. Powered by [`@tetherto/wdk-utils`](https://github.com/tetherto/wdk-utils). ## Features - **Address validation helpers**: Validate Bitcoin, EVM, Lightning invoice, LNURL, Lightning address, Spark, Tron, and UMA inputs before you hand them to a wallet flow. - **BIP-21 payment URI helpers**: Detect, parse, and encode `bitcoin:` payment URIs with optional amount, label, and message fields. - **Seed encryption helpers**: Encrypt and decrypt seed phrases or other strings with AES-256-GCM and scrypt-derived keys. +- **Seed key derivation**: Derive domain-separated byte keys or deterministic Ed25519 keypairs from high-entropy seed bytes. - **Lightning payment parsing**: Decode LNURL strings and BOLT11 invoices before you display or route payment details. - **BOLT11 invoice helpers**: Validate, decode, hash, sign, and encode BOLT11 invoices for Bitcoin, testnet, regtest, and signet flows. - **EIP-681 request parsing**: Detect request-shaped EIP-681 strings and parse transfer payloads into `recipient`, `tokenAddress`, `chainId`, and `amountSmallest`. @@ -21,6 +22,7 @@ WDK Utils provides validation helpers for Bitcoin, EVM, Lightning, Spark, Tron, - Validate user input early and return machine-readable failure reasons before you attempt a transaction or resolution flow. - Protect seed phrases or other local strings with passphrase-based encryption before storing them in app-managed persistence. +- Derive separate application keys from one high-entropy seed without reusing the same key material across purposes. - Parse and encode BIP-21 Bitcoin payment links before pre-filling send forms. - Inspect BOLT11 invoice metadata, fallback addresses, routing information, and feature bits before presenting payment details. - Normalize EIP-681 payment links into structured transfer data that wallet UIs can inspect before execution. diff --git a/content/docs/tools/worklet-bundler/api-reference.mdx b/content/docs/tools/worklet-bundler/api-reference.mdx index 68524dc4..70b45312 100644 --- a/content/docs/tools/worklet-bundler/api-reference.mdx +++ b/content/docs/tools/worklet-bundler/api-reference.mdx @@ -18,20 +18,42 @@ schemaType: APIReference ```typescript title="WdkBundleConfig Shape" interface WdkBundleConfig { networks: Record - protocols?: Record + protocols?: Record + modules?: Record preloadModules?: string[] + transport?: 'hrpc' | 'jsonrpc' output?: { bundle?: string types?: string + addons?: { + ios?: string + macos?: string + android?: string + } + addonsYml?: string } options?: { minify?: boolean sourceMaps?: boolean targets?: string[] + linkAddons?: boolean + platforms?: Array<'ios' | 'macos' | 'android'> + swiftTarget?: string + convertEsmToCjs?: boolean } } ``` +`modules` is generated only for HRPC in beta.5. JSON-RPC entry generation ignores that map. JSON-RPC otherwise defaults addon linking and ESM-to-CJS conversion to `true`; HRPC defaults both to `false`. + + +Beta.5 declares `output.types`, `options.minify`, and `options.sourceMaps`, but its bundle generator does not honor those fields. Declarations are always written to `./.wdk/index.d.ts`; `minify` and `sourceMaps` do not control `bare-pack` output. JSON-RPC ESM-to-CJS conversion minifies independently. + + #### `ResolvedConfig` `ResolvedConfig` extends `WdkBundleConfig` with absolute filesystem paths produced by `loadConfig()`. @@ -43,6 +65,12 @@ interface ResolvedConfig extends WdkBundleConfig { resolvedOutput: { bundle: string types: string + addons: { + ios: string + macos: string + android: string + } + addonsYml: string } } ``` @@ -119,7 +147,10 @@ Run the uninstall flow from code and receive a structured `UninstallResult`. | `loadConfig(configPath?)` | Load, validate, and resolve a `wdk.config.js` file into absolute paths. | `Promise\` | | `generateBundle(config, options?)` | Generate the entrypoint, imports, bundle, and optional type output. | `Promise\` | | `generateSourceFiles(config, options?)` | Generate the source entrypoint and related artifacts without bundling. | `Promise\<{ entryPath: string }\>` | -| `generateEntryPoint(config, outputDir)` | Generate only the Bare worklet entrypoint file. | `Promise\` | +| `generateEntryPoint(config, outputDir)` | Generate an HRPC Bare worklet entrypoint file. | `Promise\` | +| `generateJsonRpcEntryPoint(config, outputDir)` | Generate a JSON-RPC Bare worklet entrypoint file. | `Promise\` | +| `linkAddons(config, options?)` | Link native addons for selected platforms with `bare-link`. | `Promise\` | +| `generateAddonsYml(iosAddonsDir, swiftTarget, outputPath)` | Generate the BareKit Swift addon dependency file. | `void` | | `generateWalletModulesCode(config)` | Generate the wallet-module section inserted into the entrypoint. | `string` | #### `loadConfig(configPath?)` @@ -154,6 +185,8 @@ Use `generateBundle()` when you want the same bundle workflow that powers the CL - `error?` - `missingModule?` +In beta.5, `typesPath` can reflect a configured `output.types` path even though the declaration file is still written to `./.wdk/index.d.ts`. + ```typescript title="Generate A Bundle Programmatically" import { generateBundle, loadConfig } from '@tetherto/wdk-worklet-bundler' @@ -167,10 +200,24 @@ Use `generateSourceFiles()` when you want the generated entrypoint without the f #### `generateEntryPoint(config, outputDir)` -Use `generateEntryPoint()` when you need the exact generated Bare entrypoint string written to a chosen output directory. +Use `generateEntryPoint()` when you need the generated HRPC Bare entrypoint written to a chosen output directory. This path includes configured generic modules and their lifecycle/event wiring. In `beta.3`, the generated entrypoint suspends and resumes both the `bare-http1` and `bare-https` global agents when the Bare runtime emits `suspend` and `resume`. +#### `generateJsonRpcEntryPoint(config, outputDir)` + +Generate the framed JSON-RPC entrypoint used by native hosts. In beta.5 this path includes wallet and protocol managers but not generic `modules`. + +#### `linkAddons(config, options?)` + +Link required Bare addons for iOS, macOS, Android, or a selected subset. `LinkAddonsOptions` supports `platforms`, `verbose`, and `silent`. The result contains `success`, `duration`, `platforms`, and optional `error`. + +When iOS is selected, addon linking also calls `generateAddonsYml()` with `config.options.swiftTarget` or the default target name `app`. + +#### `generateAddonsYml(iosAddonsDir, swiftTarget, outputPath)` + +Generate the `addons.yml` dependency list expected by BareKit Swift from linked iOS XCFrameworks. + #### `generateWalletModulesCode(config)` Use `generateWalletModulesCode()` when you only need the generated wallet-module section for inspection or custom generator flows. @@ -181,13 +228,17 @@ The published CLI exposes these commands through `wdk-worklet-bundler`: | Command | Description | Key Options | | --- | --- | --- | -| `generate` | Generate a WDK bundle from configuration. | `--config`, `--install`, `--keep-artifacts`, `--dry-run`, `--no-types`, `--source-only`, `--skip-generation`, `--verbose` | +| `generate` | Generate a WDK bundle from configuration. | `--config`, `--install`, `--keep-artifacts`, `--dry-run`, `--no-types`, `--source-only`, `--skip-generation`, `--transport`, `--link-addons`, `--skip-link-addons`, `--platforms`, `--no-esm-to-cjs`, `--verbose` | | `init` | Create a new `wdk.config.js` file. | `--yes` | | `validate` | Validate configuration without building. | `--config` | | `list-modules` | List available WDK modules. | `--json` | | `clean` | Remove the generated `.wdk` folder. | `--yes` | -For the end-to-end config workflow, see the Worklet Bundler configuration guide when it is available. +For the end-to-end config workflow and transport defaults, see the [Worklet Bundler configuration guide](/tools/worklet-bundler/configuration). + + +Beta.5 applies `--transport` after `loadConfig()` resolves output paths. The flag changes entrypoint generation but does not recompute the default bundle filename. Set `transport` in `wdk.config.js`, or configure `output.bundle` explicitly when using the flag. + *** diff --git a/content/docs/tools/worklet-bundler/configuration.mdx b/content/docs/tools/worklet-bundler/configuration.mdx index afa044c4..0631a79f 100644 --- a/content/docs/tools/worklet-bundler/configuration.mdx +++ b/content/docs/tools/worklet-bundler/configuration.mdx @@ -3,16 +3,19 @@ title: Worklet Bundler Configuration description: Configure wdk.config.js and generate Bare worklet bundles with @tetherto/wdk-worklet-bundler --- -This page shows how to install `@tetherto/wdk-worklet-bundler`, shape `wdk.config.js`, and generate a Bare worklet bundle for your WDK modules. +This page shows how to install `@tetherto/wdk-worklet-bundler` and its Pear runtime, shape `wdk.config.js`, and generate a Bare worklet bundle for your WDK modules. -## Install the package +## Install the packages -Install the bundler as a development dependency in the host project: +Install the bundler as a development dependency and Pear Worklet as a runtime dependency in the host project: -```bash title="Install @tetherto/wdk-worklet-bundler" -npm install --save-dev @tetherto/wdk-worklet-bundler +```bash title="Install The Bundler And Runtime" +npm install @tetherto/pear-wrk-wdk@1.0.0-beta.9 +npm install --save-dev @tetherto/wdk-worklet-bundler@1.0.0-beta.5 ``` +Worklet Bundler beta.5 generates entrypoints that import Pear Worklet, but `generate --install` does not add that package. Beta.9 is also required for the generated JSON-RPC entrypoint and HRPC generic-module methods documented here. + ## Create `wdk.config.js` Use `init` to create a starter config, or write the file yourself: @@ -21,7 +24,7 @@ Use `init` to create a starter config, or write the file yourself: npx wdk-worklet-bundler init ``` -The published config surface accepts `networks`, optional `protocols`, optional `preloadModules`, optional `output`, and optional `options`: +The published config surface accepts `networks`, optional `protocols`, HRPC-only generic `modules`, `transport`, `preloadModules`, `output`, and `options`: ```javascript title="Example wdk.config.js" module.exports = { @@ -40,18 +43,25 @@ module.exports = { } }, + modules: { + preferences: { + package: '@your-org/wdk-module-preferences', + factory: 'createModule', + events: ['changed'] + } + }, + + transport: 'hrpc', + preloadModules: [ 'spark-frost-bare-addon' ], output: { - bundle: './.wdk-bundle/wdk-worklet.bundle.js', - types: './.wdk/index.d.ts' + bundle: './.wdk-bundle/wdk-worklet.bundle.js' }, options: { - minify: false, - sourceMaps: false, targets: ['ios-arm64', 'android-arm64'] } } @@ -81,21 +91,77 @@ module.exports = { Use `protocols` when the worklet should preload WDK protocol packages alongside wallet modules. +### `modules` (optional, HRPC only) + +Use `modules` for named generic packages that expose a module factory. Each entry supports: + +- `package` (`string`, required): Package name or local path. +- `factory` (`string`, optional): Named factory export. When omitted, the package's callable default export is used. +- `events` (`string[]`, optional): Event names forwarded from the worklet to the host. + +The factory receives `{ seed, config, capabilities, emit }` and can return the module instance or a promise for it. A module that needs the seed must consume it synchronously rather than retain it. The build-time name, such as `preferences`, must match the key in the host's runtime module config. + + +Beta.5 generic modules are included only in generated HRPC entrypoints. Do not configure `modules` with `transport: 'jsonrpc'`; the current schema accepts the field, but JSON-RPC generation ignores it. + + +### `transport` (optional) + +Choose `hrpc` or `jsonrpc`. HRPC is the default and is the transport used by React Native Core. JSON-RPC produces a length-prefixed bundle for a native host and enables addon linking and ESM-to-CJS conversion by default. + ### `preloadModules` (optional) Use `preloadModules` for native addons or other modules that must be required before the generated worklet starts. ### `output` (optional) -If you omit `output.bundle`, the loader resolves the bundle path to `./.wdk-bundle/wdk-worklet.bundle.js`. If you omit `output.types`, the loader resolves the type path to `./.wdk/index.d.ts`. +Supported output fields are: + +- `bundle`: Bundle path. Defaults to `./.wdk-bundle/wdk-worklet.bundle.js` for HRPC and `./.wdk-bundle/wdk-worklet.bundle` for JSON-RPC. +- `types`: Declared in the beta.5 config type, but the generator does not honor a custom value and always writes declarations to `./.wdk/index.d.ts`. +- `addons.ios`, `addons.macos`, `addons.android`: Platform addon directories. Defaults are `./ios-addons`, `./mac-addons`, and `./android-addons`. +- `addonsYml`: BareKit Swift dependency file. Defaults to `./ios-addons/addons.yml` and is generated when iOS addons are linked. ### `options` (optional) The published config type supports these build options: -- `minify` (`boolean`): Minify the generated bundle. -- `sourceMaps` (`boolean`): Request source map output. +- `minify` (`boolean`): Declared in the beta.5 config type but not read by the bundle path. JSON-RPC ESM-to-CJS conversion minifies independently of this field. +- `sourceMaps` (`boolean`): Declared in the beta.5 config type but not read by the bundle path, so it does not produce source maps. - `targets` (`string[]`): Override the default Bare build hosts. The shipped defaults cover iOS arm64 and simulator targets plus Android arm, arm64, ia32, and x64 hosts. +- `linkAddons` (`boolean`): Link native addons with `bare-link`. Defaults to `true` for JSON-RPC and `false` for HRPC. +- `platforms` (`('ios' | 'macos' | 'android')[]`): Addon platforms. Defaults to all three when addon linking is active. +- `swiftTarget` (`string`): Xcode target written to `addons.yml`. Defaults to `app`. +- `convertEsmToCjs` (`boolean`): Convert the bundle for JSC runtimes. Defaults to `true` for JSON-RPC and `false` for HRPC. Android uses V8, so Android-only JSON-RPC builds can set this to `false`. + +## Configure JSON-RPC and native addons + +Use JSON-RPC when a native host will implement Pear Worklet's length-prefixed JSON-RPC protocol: + +```javascript title="JSON-RPC wdk.config.js" +module.exports = { + networks: { + ethereum: { + package: '@tetherto/wdk-wallet-evm' + } + }, + transport: 'jsonrpc', + output: { + addons: { + ios: './ios-addons', + macos: './mac-addons', + android: './android-addons' + }, + addonsYml: './ios-addons/addons.yml' + }, + options: { + platforms: ['ios', 'android'], + swiftTarget: 'app' + } +} +``` + +JSON-RPC generation defaults to a bundle without a `.js` suffix, ESM-to-CJS conversion, and addon linking. It links `bare-posix` with the other required Bare modules automatically. See the [Pear Worklet API reference](/tools/pear-wrk-wdk/api-reference#json-rpc-transport) for framing and supported methods. ## Validate and generate the bundle @@ -111,13 +177,15 @@ Use `generate` to build the worklet artifact: npx wdk-worklet-bundler generate --install ``` -`generate --install` can auto-install missing configured modules after the package manager is detected from the project root. Use `--source-only` when you want the generated `.wdk/wdk-worklet.generated.js` entrypoint and related artifacts without running `bare-pack`. +`generate --install` can auto-install missing configured wallet, protocol, generic, and preload modules after the package manager is detected from the project root. In beta.5 it does not install the Pear Worklet runtime imported by generated entrypoints; install Pear explicitly as shown above. Use `--source-only` when you want the generated `.wdk/wdk-worklet.generated.js` entrypoint and related artifacts without running `bare-pack`. + +The `generate` command also accepts `--transport hrpc|jsonrpc`, `--link-addons`, `--skip-link-addons`, `--platforms ios,macos,android`, and `--no-esm-to-cjs`. Set `transport` in `wdk.config.js` when you rely on its transport-specific default bundle path. In beta.5, `--transport` changes the generated transport after output paths have already been resolved, so it does not switch `.js` HRPC output to the extensionless JSON-RPC default, or vice versa. If you use the flag, set `output.bundle` explicitly to the intended path. ## Suspend and resume behavior -Generated `beta.3` worklet entrypoints register Bare lifecycle handlers for `suspend` and `resume`, then apply those handlers to both the `bare-http1` and `bare-https` global agents. No config flag is required for this behavior. +Generated HRPC worklet entrypoints register Bare lifecycle handlers for `suspend` and `resume`, then apply those handlers to both the `bare-http1` and `bare-https` global agents. No config flag is required for this behavior. -This matters when a generated worklet performs HTTPS-backed fetches. In `beta.2`, only the `bare-http1` agent was suspended and resumed. In `beta.3`, generated worklets suspend and resume both agents together. +This matters when a generated HRPC worklet performs HTTPS-backed fetches. Starting in beta.3, those generated entrypoints suspend and resume both agents together. ## Troubleshooting diff --git a/content/docs/tools/worklet-bundler/index.mdx b/content/docs/tools/worklet-bundler/index.mdx index 084863a1..af15abf2 100644 --- a/content/docs/tools/worklet-bundler/index.mdx +++ b/content/docs/tools/worklet-bundler/index.mdx @@ -3,21 +3,31 @@ title: Worklet Bundler description: CLI tool for generating WDK Bare worklet bundles --- -Worklet Bundler packages selected WDK wallet and protocol modules into a Bare runtime bundle that runs outside your main React Native application thread. Powered by [`@tetherto/wdk-worklet-bundler`](https://github.com/tetherto/wdk-worklet-bundler). +Worklet Bundler packages selected WDK wallet, protocol, and optional generic modules into a Bare runtime bundle that runs outside the host application thread. It can generate the default HRPC worklet used by React Native Core or a framed JSON-RPC bundle for a native host. Powered by [`@tetherto/wdk-worklet-bundler`](https://github.com/tetherto/wdk-worklet-bundler). ## Features -- **Config-driven bundle generation**: Map logical network names to wallet packages and optional protocol names to protocol packages in `wdk.config.js`. -- **Generated worklet artifact**: Produce a bundle for the Bare runtime plus generated TypeScript types for the host app. +- **Config-driven bundle generation**: Map logical network names to wallet packages, protocol names to protocol packages, and HRPC-only generic-module names to package factories. +- **HRPC and JSON-RPC transports**: Generate an HRPC JavaScript bundle by default or a length-prefixed JSON-RPC bundle for native hosts. +- **Generated worklet artifacts**: Produce a Bare bundle, a stable `./.wdk` host import, and generated TypeScript types. +- **Native addon outputs**: Link iOS, macOS, and Android addons and generate `addons.yml` for JSON-RPC/native integrations. - **Dependency validation helpers**: Validate configured modules, detect the active package manager, and generate install or uninstall commands when dependencies are missing. - **CLI workflow**: Use `init`, `validate`, `generate`, `list-modules`, and `clean` without building your own wrapper. -- **Bare suspend and resume handling**: `beta.3` generated entrypoints suspend and resume both the `bare-http1` and `bare-https` global agents when the Bare thread lifecycle changes. +- **Bare suspend and resume handling**: Generated HRPC entrypoints suspend and resume both the `bare-http1` and `bare-https` global agents when the Bare thread lifecycle changes. + + +`v1.0.0-beta.5` is the first published artifact that contains the built CLI and API files for the JSON-RPC, generic-module, and addon-linking surface introduced in beta.4. It also links `bare-posix` automatically for supported native platforms. + ## Why this matters - Bundle generation keeps wallet and protocol code in a separate Bare worklet instead of the UI thread. - The generated type output gives the host app a stable import for the bundle surface. -- The `beta.3` runtime fix reduces the chance that HTTPS-backed worklet fetches keep running after a Bare thread suspension. +- Transport-specific defaults produce a JavaScript HRPC bundle for React Native Core or native addon artifacts for JSON-RPC hosts. + + +Generic `modules` are generated only for HRPC in beta.5. A JSON-RPC config can currently accept a `modules` field but silently omits those modules from the generated entrypoint. + From f74589a10b43a7d4c85ea669084c77c9b2fd1a12 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Mon, 13 Jul 2026 12:23:58 +0530 Subject: [PATCH 07/38] docs: correct TON wallet guidance --- content/docs/resources/concepts.mdx | 2 +- content/docs/sdk/all-modules.mdx | 2 +- .../wallet-ton-gasless/api-reference.mdx | 81 ++++++----- .../wallet-ton-gasless/configuration.mdx | 65 ++++++--- .../guides/check-balances.mdx | 8 +- .../wallet-ton-gasless/guides/get-started.mdx | 9 +- .../guides/handle-errors.mdx | 16 +-- .../guides/send-transactions.mdx | 8 +- .../guides/sign-verify-messages.mdx | 4 +- .../guides/transfer-tokens.mdx | 76 ++++++---- .../wallet-ton-gasless/index.mdx | 6 +- .../wallet-ton-gasless/usage.mdx | 4 +- .../wallet-ton/api-reference.mdx | 134 ++++++++++-------- .../wallet-ton/configuration.mdx | 40 +++--- .../wallet-ton/guides/check-balances.mdx | 2 +- .../wallet-ton/guides/get-started.mdx | 7 +- .../wallet-ton/guides/handle-errors.mdx | 20 +-- .../wallet-ton/guides/send-transactions.mdx | 20 ++- .../wallet-ton/guides/transfer-tokens.mdx | 4 +- .../sdk/wallet-modules/wallet-ton/index.mdx | 2 +- .../sdk/wallet-modules/wallet-ton/usage.mdx | 2 +- skills/wdk/references/wallet-ton.md | 36 ++--- 22 files changed, 316 insertions(+), 232 deletions(-) diff --git a/content/docs/resources/concepts.mdx b/content/docs/resources/concepts.mdx index cdb06bcd..bfb8ff93 100644 --- a/content/docs/resources/concepts.mdx +++ b/content/docs/resources/concepts.mdx @@ -15,7 +15,7 @@ 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-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..e8f4c0a5 100644 --- a/content/docs/sdk/all-modules.mdx +++ b/content/docs/sdk/all-modules.mdx @@ -26,7 +26,7 @@ Wallet modules provide blockchain-specific wallet functionality for managing add | [`@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-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/) | diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/api-reference.mdx index f9f6e043..31492249 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/api-reference.mdx @@ -28,13 +28,13 @@ 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 gasless transfer operations - `transactionMaxFee` (number | bigint, optional): Shared wallet config option; native `sendTransaction()`, `quoteSendTransaction()`, and `signTransaction()` are unsupported on this module @@ -43,11 +43,11 @@ new WalletManagerTonGasless(seed, config) ```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: { @@ -64,8 +64,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'`. @@ -96,9 +96,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. In `1.0.0-beta.7`, 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 @@ -108,7 +108,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 @@ -140,8 +140,8 @@ new WalletAccountTonGasless(seed, path, config) | `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\` | @@ -174,7 +174,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 @@ -202,7 +202,7 @@ 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) to estimate gasless Jetton transfer fees. +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 @@ -237,13 +237,13 @@ Transfers a Jetton using a gasless transaction, paying the fee with the configur - `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 | 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 @@ -257,18 +257,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: 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 @@ -362,8 +365,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 @@ -374,7 +377,7 @@ new WalletAccountReadOnlyTonGasless(publicKey, config) | `getTokenBalance(tokenAddress)` | Returns the balance of a specific token | `Promise\` | | `getPaymasterTokenBalance()` | Returns the balance of the paymaster token | `Promise\` | | `quoteSendTransaction(tx)` | Not supported on gasless; always rejects | `Promise\\>` | -| `quoteTransfer(options, config?)` | Estimates the fee for a token transfer | `Promise\<{fee: number}\>` | +| `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\` | @@ -446,11 +449,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: 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 @@ -480,8 +486,10 @@ console.log('Signature valid:', isValid) ##### `getTransactionReceipt(hash)` Returns a transaction's receipt. +In `1.0.0-beta.7`, 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 @@ -501,14 +509,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; @@ -575,7 +583,7 @@ interface TransferOptions { /** * Amount in token's base units */ - amount: number; + amount: number | bigint; } ``` @@ -584,14 +592,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; } ``` diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/configuration.mdx index 27273c4c..d2bc0826 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/configuration.mdx @@ -14,11 +14,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: { @@ -33,6 +33,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 @@ -41,11 +45,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: { @@ -64,14 +68,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; @@ -88,18 +92,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 @@ -129,6 +137,10 @@ type PaymasterToken = { **Required:** Yes + +`paymasterToken` must be an object with an `address` field, not a raw address string. + + **Example:** ```javascript const config = { @@ -150,12 +162,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 } @@ -192,14 +204,14 @@ Here's a complete configuration example with required clients, failover, and gas 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) @@ -215,6 +227,19 @@ const config = { } ``` +## 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. + + +In `@tetherto/wdk-wallet-ton-gasless` `1.0.0-beta.7`, 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. diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/check-balances.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/check-balances.mdx index b906bf4b..5a244413 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/check-balances.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/check-balances.mdx @@ -36,7 +36,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 @@ -48,11 +48,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: { @@ -73,4 +73,4 @@ 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). diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/get-started.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/get-started.mdx index 0473db1c..efbf7d27 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/get-started.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/get-started.mdx @@ -28,15 +28,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: { @@ -47,7 +48,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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/handle-errors.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/handle-errors.mdx index fdbdbeaa..2345aebc 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/handle-errors.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/handle-errors.mdx @@ -1,11 +1,11 @@ --- title: 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. docType: how-to schemaType: TechArticle --- -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 @@ -18,7 +18,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') @@ -26,7 +26,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.toLowerCase().includes('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) @@ -54,7 +54,7 @@ try { ### Manage Fee Limits -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 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). In `1.0.0-beta.7`, 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() @@ -62,9 +62,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() @@ -73,5 +73,5 @@ 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. diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/send-transactions.mdx index fca1d4e0..a295013b 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/send-transactions.mdx @@ -1,11 +1,11 @@ --- -title: Send Transactions +title: Native Sends Unsupported description: Why native TON sends are unsupported on the gasless module and what to use instead. docType: how-to schemaType: TechArticle --- -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 @@ -23,9 +23,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). In `1.0.0-beta.7`, 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() diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/sign-verify-messages.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/sign-verify-messages.mdx index a7c406cc..4af049b4 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/sign-verify-messages.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/sign-verify-messages.mdx @@ -33,11 +33,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: { diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/transfer-tokens.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/transfer-tokens.mdx index 32d976f1..231a7a0c 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/transfer-tokens.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/guides/transfer-tokens.mdx @@ -5,7 +5,7 @@ docType: how-to schemaType: TechArticle --- -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) @@ -17,7 +17,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') ``` @@ -36,7 +36,7 @@ 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') ``` @@ -44,7 +44,7 @@ console.log('Transfer fee:', result.fee, 'paymaster token units') ## 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({ @@ -55,46 +55,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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/index.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/index.mdx index d42b410f..7a7d736a 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/index.mdx @@ -14,11 +14,11 @@ Use the gasless TON wallet module when your app needs Jetton transfers without r - **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 @@ -41,7 +41,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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton-gasless/usage.mdx b/content/docs/sdk/wallet-modules/wallet-ton-gasless/usage.mdx index f8348c1d..de8b9134 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton-gasless/usage.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton-gasless/usage.mdx @@ -6,7 +6,7 @@ schemaType: TechArticle icon: BookOpen --- -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. @@ -28,7 +28,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. diff --git a/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx index 8befa2e0..5d4d18ea 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx @@ -33,9 +33,9 @@ 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) @@ -43,7 +43,7 @@ new WalletManagerTon(seed, config) ```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 Jetton transfer fee in nanotons @@ -58,8 +58,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)` @@ -89,7 +89,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. In `1.0.0-beta.10`, 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 @@ -101,7 +101,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 @@ -111,14 +111,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 @@ -136,9 +135,9 @@ 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 @@ -146,7 +145,7 @@ new WalletAccountTon(seed, path, config) ```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 Jetton transfer fee in nanotons @@ -161,11 +160,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)` | Sends a TON transaction | `Promise\<{hash: string, fee: bigint}\>` | +| `quoteSendTransaction(tx)` | Estimates the fee for a TON transaction | `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\` | @@ -214,7 +213,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`) @@ -223,7 +222,7 @@ 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. @@ -233,21 +232,20 @@ 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 - `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\<{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 **Throws:** Error if the estimated transaction fee exceeds `transactionMaxFee` when configured. @@ -257,7 +255,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'); ``` @@ -267,10 +265,10 @@ Estimates the fee for a transaction. **Parameters:** - `tx` (object): The transaction object (same as sendTransaction) - `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 @@ -288,9 +286,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 @@ -299,7 +297,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'); ``` @@ -310,9 +308,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 @@ -346,21 +344,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. +In `1.0.0-beta.10`, 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()` @@ -410,7 +419,7 @@ new WalletAccountReadOnlyTon(publicKey, config) ``` **Parameters:** -- `publicKey` (string): The account's public key (hex or base64) +- `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 @@ -459,15 +468,24 @@ console.log('Signature valid:', isValid) ##### `getTransactionReceipt(hash)` Returns a transaction's receipt if it has been mined. +In `1.0.0-beta.10`, 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 @@ -520,7 +538,7 @@ interface TransferOptions { * Amount in Jetton's base units * @example 1000000000 // Amount depends on token decimals */ - amount: number; + amount: number | bigint; } ``` @@ -529,16 +547,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; } ``` @@ -547,16 +565,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` in v1.0.0-beta.10 + * @example 100000000n // 0.1 TON */ - fast: number; + fast: bigint; } ``` @@ -583,8 +601,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; @@ -611,7 +629,7 @@ interface TonWalletConfig { interface TonClientConfig { /** * TON Center API endpoint - * @example 'https://toncenter.com/api/v3' + * @example 'https://toncenter.com/api/v2/jsonRPC' */ url: string; diff --git a/content/docs/sdk/wallet-modules/wallet-ton/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-ton/configuration.mdx index 3f309c59..909715bb 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/configuration.mdx @@ -15,7 +15,7 @@ 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 Jetton transfer fee in nanotons @@ -32,7 +32,7 @@ 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 Jetton transfer fee in nanotons @@ -46,13 +46,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; @@ -75,22 +75,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) @@ -101,8 +105,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 } @@ -112,7 +116,7 @@ const config = { 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) @@ -125,7 +129,7 @@ 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 @@ -144,7 +148,7 @@ The `transactionMaxFee` option sets the maximum allowed fee, in nanotons, for na ```javascript const config = { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' }, transactionMaxFee: 1000000000 // 1 TON in nanotons @@ -160,7 +164,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 } } @@ -170,10 +174,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` + + +In `@tetherto/wdk-wallet-ton` `1.0.0-beta.10`, `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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton/guides/check-balances.mdx b/content/docs/sdk/wallet-modules/wallet-ton/guides/check-balances.mdx index 16d80a76..97be9c9b 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/guides/check-balances.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/guides/check-balances.mdx @@ -39,7 +39,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 } }) diff --git a/content/docs/sdk/wallet-modules/wallet-ton/guides/get-started.mdx b/content/docs/sdk/wallet-modules/wallet-ton/guides/get-started.mdx index 39408225..64508958 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/guides/get-started.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/guides/get-started.mdx @@ -25,18 +25,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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton/guides/handle-errors.mdx b/content/docs/sdk/wallet-modules/wallet-ton/guides/handle-errors.mdx index 157721a1..62b00a7f 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/guides/handle-errors.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/guides/handle-errors.mdx @@ -1,11 +1,11 @@ --- title: 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. docType: how-to schemaType: TechArticle --- -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 @@ -18,12 +18,12 @@ 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.includes('Exceeded maximum fee')) { + } 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') @@ -46,12 +46,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') } } @@ -61,7 +61,7 @@ try { ### Manage Fee Limits -Set `transactionMaxFee` when creating the wallet to cap native `sendTransaction()` and `signTransaction()` costs. Set `transferMaxFee` separately for Jetton `transfer()` costs. 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). In `1.0.0-beta.10`, 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() @@ -69,9 +69,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() @@ -80,5 +80,5 @@ 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. diff --git a/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx index 8dbc0652..deed1281 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx @@ -5,7 +5,7 @@ docType: how-to schemaType: TechArticle --- -This guide explains how to [send native TON](#send-native-ton), [estimate transaction fees](#estimate-transaction-fees), [cap transaction fees](#cap-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), and [prepare a signed transaction body](#prepare-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. @@ -21,7 +21,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') ``` @@ -44,14 +44,14 @@ Set [`transactionMaxFee`](/sdk/wallet-modules/wallet-ton/configuration#transacti ```javascript title="Cap Native Transaction Fees" const wallet = new WalletManagerTon(seedPhrase, { - tonClient: { url: 'https://toncenter.com/api/v3' }, + tonClient: { url: 'https://toncenter.com/api/v2/jsonRPC' }, transactionMaxFee: 1000000000n }) ``` -## Use Dynamic Fee Rates +## 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). In `1.0.0-beta.10`, 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() @@ -59,19 +59,17 @@ 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(). ``` ## Next Steps diff --git a/content/docs/sdk/wallet-modules/wallet-ton/guides/transfer-tokens.mdx b/content/docs/sdk/wallet-modules/wallet-ton/guides/transfer-tokens.mdx index 9c891c59..ada6de07 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/guides/transfer-tokens.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/guides/transfer-tokens.mdx @@ -17,7 +17,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') ``` @@ -69,7 +69,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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton/index.mdx b/content/docs/sdk/wallet-modules/wallet-ton/index.mdx index b0a1b747..762e114d 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/index.mdx @@ -30,7 +30,7 @@ Use [`getAccountByPath`](/sdk/wallet-modules/wallet-ton/api-reference) to supply - **Transaction Management**: Send transactions and get fee estimates - **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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton/usage.mdx b/content/docs/sdk/wallet-modules/wallet-ton/usage.mdx index 449d6024..d058c2c0 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/usage.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/usage.mdx @@ -28,7 +28,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. diff --git a/skills/wdk/references/wallet-ton.md b/skills/wdk/references/wallet-ton.md index ec180e7a..a620389a 100644 --- a/skills/wdk/references/wallet-ton.md +++ b/skills/wdk/references/wallet-ton.md @@ -6,10 +6,10 @@ |----------|-----| | **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-ton | | **GitHub** | https://github.com/tetherto/wdk-wallet-ton | -| **Docs — Overview** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton | -| **Docs — Usage** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton/usage | -| **Docs — Configuration** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton/configuration | -| **Docs — API Reference** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton/api-reference | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton/api-reference | ## Links — wallet-ton-gasless @@ -17,10 +17,10 @@ |----------|-----| | **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-ton-gasless | | **GitHub** | https://github.com/tetherto/wdk-wallet-ton-gasless | -| **Docs — Overview** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton-gasless | -| **Docs — Usage** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton-gasless/usage | -| **Docs — Configuration** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton-gasless/configuration | -| **Docs — API Reference** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-ton-gasless/api-reference | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-ton-gasless/api-reference | ## Packages @@ -36,13 +36,13 @@ import WalletManagerTonGasless from '@tetherto/wdk-wallet-ton-gasless' ## Key Details — wallet-ton -- **Derivation**: BIP-44 (`m/44'/607'/0'/0/{index}`) +- **Derivation**: BIP-44 (`m/44'/607'/{index}'` in v1.0.0-beta.6+) - **Key type**: Ed25519 - **Wallet contract**: V5R1 - **Fee unit**: nanotons (1 TON = 1,000,000,000 nanotons) - **Token standard**: Jettons via `transfer()` - **USDT Jetton master**: `EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs` -- `sendTransaction` accepts a `payload` field for arbitrary contract calls — treat with caution. +- `sendTransaction` accepts a `body` field for arbitrary contract calls — treat with caution. > **Derivation path change in v1.0.0-beta.6+**: Previous default was `m/44'/607'/0'/0/{index}`, updated to match ecosystem conventions. Existing wallets created with old path will generate different addresses. Use `getAccountByPath` for legacy wallet recovery. @@ -51,7 +51,7 @@ import WalletManagerTonGasless from '@tetherto/wdk-wallet-ton-gasless' ```javascript const wallet = new WalletManagerTon(seedPhrase, { tonClient: { - url: 'https://toncenter.com/api/v3', + url: 'https://toncenter.com/api/v2/jsonRPC', secretKey: 'your-api-key' // Optional but recommended for production }, transferMaxFee: 1000000000n // Optional: max fee in nanotons @@ -60,22 +60,22 @@ const wallet = new WalletManagerTon(seedPhrase, { ## Key Details — wallet-ton-gasless -- Same derivation and key type as wallet-ton -- **Gasless**: Paymaster covers transaction fees; user pays in Jettons -- Requires `tonApiClient` and `paymasterToken` config -- **Jetton-to-Jetton only**: `sendTransaction()` **throws** — use `transfer()` only -- Fee is typically 0 or covered by paymaster +- Same default derivation and key type as wallet-ton in gasless v1.0.0-beta.5+ +- **Gasless Jetton transfers**: fees are paid in a supported paymaster Jetton instead of native TON +- Requires a `tonApiClient` base URL and `paymasterToken.address` configuration +- **Jetton transfers only**: `sendTransaction()`, `quoteSendTransaction()`, and `signTransaction()` are unsupported — use `transfer()` and `quoteTransfer()` +- Transfer fees are returned in paymaster Jetton base units. The account pays them from its configured paymaster Jetton balance; if that Jetton is also being transferred, the same balance must cover both the transfer amount and the final fee. ## Configuration — wallet-ton-gasless ```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/v3', + url: 'https://tonapi.io', secretKey: 'your-ton-api-key' }, paymasterToken: { From 18655824d98e56020bc57b7bd4f2fb5f624ca382 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Mon, 13 Jul 2026 10:20:29 +0530 Subject: [PATCH 08/38] docs: fix published package names --- content/docs/resources/concepts.mdx | 2 +- content/docs/sdk/all-modules.mdx | 4 ++-- content/docs/sdk/wallet-modules/index.mdx | 2 +- content/docs/start-building/build-with-ai.mdx | 2 +- public/llms-full.txt | 10 +++++----- skills/wdk/SKILL.md | 4 +--- src/components/wallet-module-chooser.tsx | 2 +- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/content/docs/resources/concepts.mdx b/content/docs/resources/concepts.mdx index bfb8ff93..e426b99a 100644 --- a/content/docs/resources/concepts.mdx +++ b/content/docs/resources/concepts.mdx @@ -14,7 +14,7 @@ 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-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 diff --git a/content/docs/sdk/all-modules.mdx b/content/docs/sdk/all-modules.mdx index e8f4c0a5..de0428ab 100644 --- a/content/docs/sdk/all-modules.mdx +++ b/content/docs/sdk/all-modules.mdx @@ -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,7 +23,7 @@ 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 Jetton transfers on TON | [Docs](/sdk/wallet-modules/wallet-ton-gasless/) | diff --git a/content/docs/sdk/wallet-modules/index.mdx b/content/docs/sdk/wallet-modules/index.mdx index ef306d0d..8feafa63 100644 --- a/content/docs/sdk/wallet-modules/index.mdx +++ b/content/docs/sdk/wallet-modules/index.mdx @@ -81,7 +81,7 @@ Wallet implementations that support [Account Abstraction](/resources/concepts#ac | 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-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) | diff --git a/content/docs/start-building/build-with-ai.mdx b/content/docs/start-building/build-with-ai.mdx index 9639f017..b4f2350e 100644 --- a/content/docs/start-building/build-with-ai.mdx +++ b/content/docs/start-building/build-with-ai.mdx @@ -65,7 +65,7 @@ Copy the rules content below and save it at the file path for your tool. - 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-erc4337`, `@tetherto/wdk-wallet-ton-gasless`, `@tetherto/wdk-wallet-tron-gasfree`. +- 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`. diff --git a/public/llms-full.txt b/public/llms-full.txt index eb1270f2..18e39979 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -3912,7 +3912,7 @@ 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-evm-erc-4337` - EVM chains with ERC-4337 standard - `@tetherto/wdk-wallet-ton-gasless` - TON blockchain with gasless transactions - `@tetherto/wdk-wallet-tron-gasfree` - TRON blockchain with gas-free transactions @@ -4084,7 +4084,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 @@ -4094,7 +4094,7 @@ 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/) | @@ -5671,7 +5671,7 @@ Community modules should: ## WDK Core URL: https://docs.wdk.tether.io/sdk/core-module -Description: Overview of the @tetherto/wdk-core module +Description: Overview of the @tetherto/wdk module This package serves as the main entry point and **orchestrator for all WDK wallet and protocol modules**, allowing you to register and manage different blockchain wallets through a single, unified interface. @@ -12865,7 +12865,7 @@ Wallet implementations that support [Account Abstraction](/resources/concepts#ac | 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-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-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 | - | diff --git a/skills/wdk/SKILL.md b/skills/wdk/SKILL.md index 4d2b9578..a4e36e44 100644 --- a/skills/wdk/SKILL.md +++ b/skills/wdk/SKILL.md @@ -1,6 +1,6 @@ --- name: wdk -description: Tether Wallet Development Kit (WDK) for building non-custodial multi-chain wallets. Use when working with @tetherto/wdk-core, wallet modules (wdk-wallet-btc, wdk-wallet-evm, wdk-wallet-evm-erc-4337, wdk-wallet-solana, wdk-wallet-spark, wdk-wallet-ton, wdk-wallet-tron, ton-gasless, tron-gasfree), and protocol modules including swidge, swap (wdk-protocol-swap-velora-evm), bridge (wdk-protocol-bridge-usdt0-evm), lending (wdk-protocol-lending-aave-evm), and fiat (wdk-protocol-fiat-moonpay). Covers wallet creation, transactions, token transfers, swidge asset routes, DEX swaps, cross-chain bridges, DeFi lending/borrowing, and fiat on/off ramps. +description: Tether Wallet Development Kit (WDK) for building non-custodial multi-chain wallets. Use when working with @tetherto/wdk, wallet modules (wdk-wallet-btc, wdk-wallet-evm, wdk-wallet-evm-erc-4337, wdk-wallet-solana, wdk-wallet-spark, wdk-wallet-ton, wdk-wallet-tron, ton-gasless, tron-gasfree), and protocol modules including swidge, swap (wdk-protocol-swap-velora-evm), bridge (wdk-protocol-bridge-usdt0-evm), lending (wdk-protocol-lending-aave-evm), and fiat (wdk-protocol-fiat-moonpay). Covers wallet creation, transactions, token transfers, swidge asset routes, DEX swaps, cross-chain bridges, DeFi lending/borrowing, and fiat on/off ramps. --- # Tether WDK @@ -64,8 +64,6 @@ When a task targets a specific chain or protocol, read the relevant reference fi └── wdk-protocol-fiat-moonpay # Fiat on/off ramp ``` -> **Note:** `@tetherto/wdk-core` appears in the architecture tree but the npm package is `@tetherto/wdk` — import as `import WDK from '@tetherto/wdk'`. - ## npm Packages All packages are under the `@tetherto` scope. **Always** `npm view version` before adding to `package.json` — never hardcode versions. diff --git a/src/components/wallet-module-chooser.tsx b/src/components/wallet-module-chooser.tsx index 3a1ed7b8..f7bfcd64 100644 --- a/src/components/wallet-module-chooser.tsx +++ b/src/components/wallet-module-chooser.tsx @@ -82,7 +82,7 @@ const walletModules: WalletModule[] = [ label: "Smart accounts (ERC-4337)", chain: "evm", goals: ["gasless"], - packageName: "@tetherto/wdk-wallet-evm-erc4337", + packageName: "@tetherto/wdk-wallet-evm-erc-4337", docsHref: "/sdk/wallet-modules/wallet-evm-erc-4337", apiHref: "/sdk/wallet-modules/wallet-evm-erc-4337/api-reference", bestFor: "Account abstraction flows that submit UserOperations through a bundler.", From 771e8a43ecc591ce671c978366617ab06ee83c56 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 7 Jul 2026 13:40:52 +0530 Subject: [PATCH 09/38] add aptos wallet release docs --- content/docs/sdk/all-modules.mdx | 1 + content/docs/sdk/wallet-modules/index.mdx | 1 + .../wallet-aptos/api-reference.mdx | 152 +++++++++++++++++ .../wallet-aptos/configuration.mdx | 102 +++++++++++ .../sdk/wallet-modules/wallet-aptos/index.mdx | 64 +++++++ .../sdk/wallet-modules/wallet-aptos/usage.mdx | 158 ++++++++++++++++++ 6 files changed, 478 insertions(+) create mode 100644 content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx create mode 100644 content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx create mode 100644 content/docs/sdk/wallet-modules/wallet-aptos/index.mdx create mode 100644 content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx diff --git a/content/docs/sdk/all-modules.mdx b/content/docs/sdk/all-modules.mdx index de0428ab..e8aa958d 100644 --- a/content/docs/sdk/all-modules.mdx +++ b/content/docs/sdk/all-modules.mdx @@ -31,6 +31,7 @@ Wallet modules provide blockchain-specific wallet functionality for managing add | [`@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 diff --git a/content/docs/sdk/wallet-modules/index.mdx b/content/docs/sdk/wallet-modules/index.mdx index 8feafa63..0939da61 100644 --- a/content/docs/sdk/wallet-modules/index.mdx +++ b/content/docs/sdk/wallet-modules/index.mdx @@ -73,6 +73,7 @@ Standard wallet implementations that use native blockchain tokens for transactio | [`@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 | In review | [Documentation](/sdk/wallet-modules/wallet-aptos) | | [`@arkade-os/wdk`](https://github.com/arkade-os/arkade-wdk) | Arkade | ✅ Ready | [README](https://github.com/arkade-os/arkade-wdk#readme) | ## Account Abstraction Wallet Modules diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx new file mode 100644 index 00000000..bee3976c --- /dev/null +++ b/content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx @@ -0,0 +1,152 @@ +--- +title: Wallet Aptos API Reference +description: API reference for @tetherto/wdk-wallet-aptos. +docType: reference +schemaType: APIReference +icon: Code +--- + +## 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` | Relative derivation path. | +| `keyPair` | Public/private key pair view. 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)` | Signs a native APT transfer without broadcasting. | `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 transaction receipt. | `Promise` | +| `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 +) +``` + +| 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 transaction receipt. | `Promise` | +| `verify(message, signature)` | Verifies a message signature. | `Promise` | + +## Config Type + +```typescript +type AptosWalletConfig = { + provider?: string + transferMaxFee?: number | bigint +} +``` + +## 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 +} +``` + +## 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. + + +`signTransaction()` signs native APT transfers only. Use `transfer()` for fungible asset transfers. + + + + +Create accounts, read balances, transfer funds, and sign messages. + + +Provider, network, fee, and derivation path options. + + diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx new file mode 100644 index 00000000..ca123b71 --- /dev/null +++ b/content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx @@ -0,0 +1,102 @@ +--- +title: Wallet Aptos Configuration +description: Configuration options for @tetherto/wdk-wallet-aptos. +docType: reference +schemaType: TechArticle +icon: Settings +--- + +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', + transferMaxFee: 100000n +}) +``` + +## Wallet Configuration + +| Option | Type | Description | +|--------|------|-------------| +| `provider` | `string` | Aptos fullnode REST API URL. Required for chain operations. | +| `transferMaxFee` | `number \| bigint` | Optional maximum fee in octas for transfer operations. | + +## 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 +}) +``` + +Read-only accounts require only an address and provider configuration. + +```javascript +import { WalletAccountReadOnlyAptos } from '@tetherto/wdk-wallet-aptos' + +const readOnlyAccount = new WalletAccountReadOnlyAptos('0x...', { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1' +}) +``` + +## Network Selection + +Network selection is controlled by the fullnode URL. + +| 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. + + +## Fee Limit + +`transferMaxFee` caps estimated transaction fees before signing and submitting transfer operations. + +```javascript +const wallet = new WalletManagerAptos(seedPhrase, { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1', + transferMaxFee: 100000n +}) +``` + +## Security Notes + +- Keep seed phrases and seed bytes outside logs and client-visible error reporting. +- Use trusted fullnode endpoints for production wallets. +- Set a fee cap for user-facing transfers. +- 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. + + diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/index.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/index.mdx new file mode 100644 index 00000000..d382c1cf --- /dev/null +++ b/content/docs/sdk/wallet-modules/wallet-aptos/index.mdx @@ -0,0 +1,64 @@ +--- +title: Wallet Aptos Overview +description: Overview of the @tetherto/wdk-wallet-aptos module. +docType: explanation +schemaType: TechArticle +--- + +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**: Reads balances, quotes fees, verifies signatures, and checks receipts without private keys. +- **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`. | +| Offline signing | `signTransaction()` supports native APT transfers. Use `transfer()` for fungible asset transfers. | + + +All Aptos derivation path segments are hardened because the module uses Ed25519 with SLIP-0010 derivation. + + +## 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? + + diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx new file mode 100644 index 00000000..9698a0df --- /dev/null +++ b/content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx @@ -0,0 +1,158 @@ +--- +title: Wallet Aptos Usage +description: Install and use @tetherto/wdk-wallet-aptos for Aptos accounts, balances, transfers, and signing. +docType: how-to +schemaType: TechArticle +icon: BookOpen +--- + +## 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() +``` + +## Send Native APT + +```javascript +const quote = await account.quoteSendTransaction({ + to: '0x...', + value: 100000000n +}) + +console.log('Estimated fee in octas:', quote.fee) + +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. + +## 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 Offline + +`signTransaction()` signs a native APT transfer without broadcasting it. + +```javascript +const signed = await account.signTransaction({ + to: '0x...', + value: 100000000n +}) + +console.log('Signed Aptos transaction:', signed) +``` + + +Offline signing is for native APT transfers. Fungible asset transfers are built and submitted through `transfer()`. + + +## Dispose Secret Material + +```javascript +account.dispose() +wallet.dispose() +``` + + + +Provider, fee, network, and derivation path configuration. + + +Detailed method and type reference. + + From 9a03976b38c1550b3a53a2d6acff0be07a11cf6e Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 14 Jul 2026 17:24:55 +0530 Subject: [PATCH 10/38] Document P2P Address Book --- content/docs/tools/p2p-address-book/index.mdx | 311 ++++++++++++++++++ content/docs/tools/wdk-utils/index.mdx | 1 + src/lib/custom-tree.ts | 1 + 3 files changed, 313 insertions(+) create mode 100644 content/docs/tools/p2p-address-book/index.mdx diff --git a/content/docs/tools/p2p-address-book/index.mdx b/content/docs/tools/p2p-address-book/index.mdx new file mode 100644 index 00000000..ff68fb84 --- /dev/null +++ b/content/docs/tools/p2p-address-book/index.mdx @@ -0,0 +1,311 @@ +--- +title: P2P Address Book +description: Store and synchronize wallet contacts across a user's devices with encrypted peer-to-peer replication +docType: explanation +schemaType: TechArticle +icon: BookUser +--- + +`@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. + + +The current release is `1.0.0-beta.1`. Use Node.js 20.19 or newer with its current dependency set. The package does not publish an official runtime or platform support matrix, so treat Linux as the only verified operating system. The Bare entrypoint loads in its package test; direct browser and React Native support are not established. + + +## 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. The blind peer is an explicitly configured availability peer. + +### Identity and permissions + +The seed and namespace derive the book identity, content key, and bootstrap signing key. Each device keeps a separate writer key in its Corestore. 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. + +Use `create()` only when the book is known to be new. Use `addMirror()` when joining an existing book; it waits until the existing genesis is visible before enrolling the device and does not create a fallback book on timeout. + + +`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. + + + + + +| Component | Responsibility | +| --- | --- | +| Corestore | Persists device writers, replicated cores, the current view, and networking keys. | +| Hypercore | Provides signed append-only feeds for replicated operations. | +| Autobase | Orders operations from authorized writers and encrypts replicated blocks when given the derived content key. | +| HyperDB | Materializes contacts, addresses, writers, and mirror configuration for queries. | +| Hyperswarm | Discovers peers for the book and carries direct replication connections. | +| `blind-peering` | Requests background replication from configured blind peers. | + +Replication encryption is not local encryption at rest. Protect the Corestore directory as sensitive wallet data. + + + + +## Install + +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 + +This example disables networking and creates a new book. It expects a caller-validated destination, reads the current 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() + } + } +} +``` + +`fromSeed()` opens the book before resolving. `book.close()` closes the book's networking and replication resources, but the direct API does not close the root Corestore supplied by the application. + +## Synchronize another 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. + +On the first device, 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) +``` + +On 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()`. Its resolution confirms that existing genesis was observed and the local writer enrolled; it does not confirm that every record is restored or retained remotely. Refetch on payload-free `update` events, unregister listeners during cleanup, and close the book and root Corestore. + +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. + +## Data and operations + +| Record | Required input | Optional input | Generated fields | +| --- | --- | --- | --- | +| Contact | `name` | `username` | `id`, `createdAt`, `updatedAt` | +| Address | `address`, `type`, `network` | `label` | `id`, `contactId`, `createdAt`, `updatedAt` | + +Contact names and usernames do not need to be unique. The schema has no first-class email or custom-field support. Supported address types are `bitcoin`, `evm`, `tron`, `uma`, `lightning-address`, `lnurl`, and `spark`. + +The package trims text fields and lowercases `network`, but 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. + +- `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. +- Refetch after an `update` event rather than treating a locally returned record as the converged state. + + +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. + + +## P2P Address Book or 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 | + +P2P reduces central readable data custody; it does not eliminate infrastructure. It exchanges a conventional contacts database for device storage, key management, replication availability, and recovery responsibilities. + +## Prepare for production + +### Blind peers and availability + +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 to clients 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 its storage, garbage collection, request, database, and transport metrics. These do not prove per-book durability or recovery completion. + +In `1.0.0-beta.1`, `removeMirror()` deletes the persisted mirror 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 delete blocks already held by the server. + +### Privacy and deletion 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. + +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. + +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. + +### Operational 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. + +The beta release publishes no package-level migration, downgrade, or cross-version replication guarantee. + + + + +| Area | Public surface | +| --- | --- | +| Construction | `AddressBook.fromSeed()`, advanced `new AddressBook()`, `AddressBook.createWorkletModule()` | +| Lifecycle | `ready()`, `create()`, `close()`, `suspend()`, `resume()` | +| Contacts | `addContact()`, `editContact()`, `deleteContact()`, `getContact()`, `listContacts()` | +| Addresses | `addAddress()`, `editAddress()`, `deleteAddress()`, `listAddresses()` | +| Query and events | `search()`, `on('update')`, `off('update')`, `getInfo()` | +| Writers | `addWriter()`, `removeWriter()`, `listWriters()`, `getWriter()` | +| Mirrors | `addMirror()`, `removeMirror()`, `listMirrors()`, `AddressBook.selectMirrors()` | +| Identifiers | `writerKey`, `key`, `discoveryKey`, `writable`; treat `encryptionKey` as secret | + +The main `fromSeed()` options are: + +| Option | Required | Purpose | +| --- | --- | --- | +| `namespace` | Yes | Stable application scope for book identity, encryption, 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, defaulting 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 as stable. `close()` destroys a supplied swarm. | + +The low-level constructor does not require an encryption key while replication defaults to enabled. Prefer `fromSeed()`. Advanced callers must supply `encryptionKey` before `ready()` or disable replication. The declared `optimistic` constructor option is ignored in `1.0.0-beta.1`; Autobase optimistic mode is always enabled. + +Writer-management methods are not included in the exported worklet bridge interface. + + + + +## 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? + + diff --git a/content/docs/tools/wdk-utils/index.mdx b/content/docs/tools/wdk-utils/index.mdx index 44c57982..16838d3e 100644 --- a/content/docs/tools/wdk-utils/index.mdx +++ b/content/docs/tools/wdk-utils/index.mdx @@ -21,6 +21,7 @@ WDK Utils provides validation helpers for Bitcoin, EVM, Lightning, Spark, Tron, ## Why this matters - Validate user input early and return machine-readable failure reasons before you attempt a transaction or resolution flow. +- Pair these helpers with the [P2P Address Book](/tools/p2p-address-book/) when a wallet stores user-selected destinations for later use. - Protect seed phrases or other local strings with passphrase-based encryption before storing them in app-managed persistence. - Derive separate application keys from one high-entropy seed without reusing the same key material across purposes. - Parse and encode BIP-21 Bitcoin payment links before pre-filling send forms. diff --git a/src/lib/custom-tree.ts b/src/lib/custom-tree.ts index aa7f6529..005a0040 100644 --- a/src/lib/custom-tree.ts +++ b/src/lib/custom-tree.ts @@ -363,6 +363,7 @@ export const customTree: Node[] = [ configuration('/tools/secret-manager/configuration'), apiReference('/tools/secret-manager/api-reference'), ]), + page('Sync wallet contacts across devices', '/tools/p2p-address-book', 'BookUser'), folder('Fetch token prices', '/tools/price-rates', 'TrendingUp', [ configuration('/tools/price-rates/configuration'), apiReference('/tools/price-rates/api-reference'), From 809b2ca6538a7b230b6fcb2377743b180126fea2 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Wed, 15 Jul 2026 09:52:33 +0530 Subject: [PATCH 11/38] Remove runtime support callout --- content/docs/tools/p2p-address-book/index.mdx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/docs/tools/p2p-address-book/index.mdx b/content/docs/tools/p2p-address-book/index.mdx index ff68fb84..ec0533d7 100644 --- a/content/docs/tools/p2p-address-book/index.mdx +++ b/content/docs/tools/p2p-address-book/index.mdx @@ -12,10 +12,6 @@ Devices that use the same wallet seed and application namespace can open the sam 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. - -The current release is `1.0.0-beta.1`. Use Node.js 20.19 or newer with its current dependency set. The package does not publish an official runtime or platform support matrix, so treat Linux as the only verified operating system. The Bare entrypoint loads in its package test; direct browser and React Native support are not established. - - ## When to use it Use the P2P Address Book when: From 30d41ff23dc01a0a5df6b1fe30f878c2d623811c Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Wed, 15 Jul 2026 09:56:27 +0530 Subject: [PATCH 12/38] Link Holepunch component repositories --- content/docs/tools/p2p-address-book/index.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/docs/tools/p2p-address-book/index.mdx b/content/docs/tools/p2p-address-book/index.mdx index ec0533d7..f3e0d675 100644 --- a/content/docs/tools/p2p-address-book/index.mdx +++ b/content/docs/tools/p2p-address-book/index.mdx @@ -66,12 +66,12 @@ Use `create()` only when the book is known to be new. Use `addMirror()` when joi | Component | Responsibility | | --- | --- | -| Corestore | Persists device writers, replicated cores, the current view, and networking keys. | -| Hypercore | Provides signed append-only feeds for replicated operations. | -| Autobase | Orders operations from authorized writers and encrypts replicated blocks when given the derived content key. | -| HyperDB | Materializes contacts, addresses, writers, and mirror configuration for queries. | -| Hyperswarm | Discovers peers for the book and carries direct replication connections. | -| `blind-peering` | Requests background replication from configured blind peers. | +| [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 derived 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 for the book and carries direct replication connections. | +| [`blind-peering`](https://github.com/holepunchto/blind-peering) | Requests background replication from configured blind peers. | Replication encryption is not local encryption at rest. Protect the Corestore directory as sensitive wallet data. From 7becbf2753e4d219420b57ea0d5d256042334c4a Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Mon, 27 Jul 2026 14:07:17 +0530 Subject: [PATCH 13/38] docs: document July WDK releases --- content/docs/ai/mcp-toolkit/configuration.mdx | 4 +- content/docs/ai/mcp-toolkit/get-started.mdx | 2 - content/docs/ai/mcp-toolkit/langchain.mdx | 10 +- content/docs/overview/about.mdx | 1 + content/docs/overview/changelog.mdx | 66 +++++ content/docs/sdk/all-modules.mdx | 10 +- .../bridge-usdt0-evm/api-reference.mdx | 175 ++++++++---- .../bridge-usdt0-evm/configuration.mdx | 56 ++-- .../guides/bridge-cross-ecosystem.mdx | 15 +- .../bridge-usdt0-evm/guides/bridge-tokens.mdx | 16 +- .../guides/bridge-with-4337.mdx | 26 +- .../bridge-usdt0-evm/guides/get-started.mdx | 6 +- .../bridge-usdt0-evm/guides/handle-errors.mdx | 4 +- .../bridge-modules/bridge-usdt0-evm/index.mdx | 27 +- .../fiat-moonpay/api-reference.mdx | 5 +- .../fiat-moonpay/configuration.mdx | 4 + .../fiat-moonpay/guides/buy-and-sell.mdx | 2 +- .../fiat-moonpay/guides/get-started.mdx | 6 +- .../guides/manage-transactions.mdx | 5 +- .../sdk/fiat-modules/fiat-moonpay/index.mdx | 19 +- .../sdk/fiat-modules/fiat-moonpay/usage.mdx | 2 +- content/docs/sdk/sda-modules/index.mdx | 239 +++++++++++++++++ content/docs/sdk/wallet-modules/index.mdx | 2 +- .../wallet-aptos/api-reference.mdx | 53 +++- .../wallet-aptos/configuration.mdx | 41 ++- .../sdk/wallet-modules/wallet-aptos/index.mdx | 9 +- .../sdk/wallet-modules/wallet-aptos/usage.mdx | 47 +++- .../sdk/wallet-modules/wallet-btc/index.mdx | 2 +- .../wallet-evm-7702-gasless/api-reference.mdx | 7 +- .../wallet-evm-7702-gasless/configuration.mdx | 4 + .../guides/send-transactions.mdx | 6 +- .../wallet-evm-erc-4337/api-reference.mdx | 61 ++++- .../guides/send-transactions.mdx | 36 ++- .../wallet-evm-erc-4337/index.mdx | 1 + .../wallet-evm/api-reference.mdx | 14 +- .../wallet-evm/guides/send-transactions.mdx | 2 +- .../wallet-solana/api-reference.mdx | 20 +- .../guides/send-transactions.mdx | 23 +- .../wallet-modules/wallet-solana/index.mdx | 1 + .../wallet-ton/api-reference.mdx | 19 +- .../wallet-ton/guides/send-transactions.mdx | 23 +- .../sdk/wallet-modules/wallet-ton/index.mdx | 1 + .../wallet-tron-gasfree/configuration.mdx | 64 +++-- .../guides/check-balances.mdx | 10 +- .../guides/get-started.mdx | 15 +- .../wallet-tron/api-reference.mdx | 26 +- .../wallet-tron/guides/send-transactions.mdx | 25 +- .../sdk/wallet-modules/wallet-tron/index.mdx | 1 + .../wallet-modules/which-wallet-module.mdx | 1 + .../react-native-quickstart.mdx | 10 +- .../tools/asset-registry/api-reference.mdx | 14 +- .../tools/asset-registry/configuration.mdx | 6 +- .../tools/failover-provider/api-reference.mdx | 13 +- .../tools/failover-provider/configuration.mdx | 12 +- .../docs/tools/failover-provider/index.mdx | 6 +- .../tools/p2p-address-book/api-reference.mdx | 132 ++++++++++ .../tools/p2p-address-book/get-started.mdx | 94 +++++++ content/docs/tools/p2p-address-book/index.mdx | 249 ++---------------- .../production-and-privacy.mdx | 79 ++++++ .../p2p-address-book/sync-and-recovery.mdx | 82 ++++++ .../docs/tools/pear-wrk-wdk/api-reference.mdx | 28 +- .../docs/tools/pear-wrk-wdk/configuration.mdx | 9 +- content/docs/tools/pear-wrk-wdk/index.mdx | 8 +- .../tools/react-native-core/api-reference.mdx | 8 +- .../docs/tools/react-native-core/index.mdx | 16 +- .../configuration.mdx | 6 +- .../react-native-secure-storage/index.mdx | 4 + .../docs/tools/wdk-utils/api-reference.mdx | 68 ++++- .../docs/tools/wdk-utils/configuration.mdx | 48 +++- content/docs/tools/wdk-utils/index.mdx | 8 +- .../tools/worklet-bundler/api-reference.mdx | 15 +- .../tools/worklet-bundler/configuration.mdx | 42 ++- content/docs/tools/worklet-bundler/index.mdx | 5 +- skills/wdk/SKILL.md | 19 +- skills/wdk/references/protocol-bridge.md | 105 +++++--- skills/wdk/references/protocol-fiat.md | 31 ++- skills/wdk/references/protocol-sda.md | 65 +++++ skills/wdk/references/wallet-aptos.md | 60 +++++ skills/wdk/references/wallet-evm.md | 47 +++- skills/wdk/references/wallet-solana.md | 16 +- skills/wdk/references/wallet-ton.md | 4 + skills/wdk/references/wallet-tron.md | 22 +- src/components/wallet-module-chooser.tsx | 17 ++ src/lib/custom-tree.ts | 15 +- 84 files changed, 1949 insertions(+), 628 deletions(-) create mode 100644 content/docs/sdk/sda-modules/index.mdx create mode 100644 content/docs/tools/p2p-address-book/api-reference.mdx create mode 100644 content/docs/tools/p2p-address-book/get-started.mdx create mode 100644 content/docs/tools/p2p-address-book/production-and-privacy.mdx create mode 100644 content/docs/tools/p2p-address-book/sync-and-recovery.mdx create mode 100644 skills/wdk/references/protocol-sda.md create mode 100644 skills/wdk/references/wallet-aptos.md 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 a405cf96..7eea72d4 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -10,6 +10,72 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### 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. + +--- + +### 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)): Add the provider-facing `SdaProtocol` and `ISdaProtocol` interface for supported-route discovery and deposit-address creation, with optional quoting, address derivation and lifecycle, transfer history and status, and recovery methods. No concrete SDA provider or WDK Core registration/accessor ships in the current releases, so this is an implementation contract rather than a runnable end-user flow. The package also exports `UnsupportedOperationError`, `ValueError`, and `NoSuchElementError`. + +--- + +### 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. + +**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** diff --git a/content/docs/sdk/all-modules.mdx b/content/docs/sdk/all-modules.mdx index e8aa958d..e407f826 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, Smart Deposit Address, pricing, swidge, swap, bridge, lending, and fiat interfaces and modules. docType: reference schemaType: TechArticle --- @@ -34,6 +34,14 @@ Wallet modules provide blockchain-specific wallet functionality for managing add | [`@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/) | +## Smart Deposit Address Interface + +The released `@tetherto/wdk-wallet` package includes the provider contract for Smart Deposit Address routes. It defines discovery, deposit-address creation, and optional quote, monitoring, renewal, recovery, and disable operations; it does not include a concrete SDA provider. + +| Interface | Package | Description | Documentation | +|-----------|---------|-------------|---------------| +| `SdaProtocol` | [`@tetherto/wdk-wallet`](https://www.npmjs.com/package/@tetherto/wdk-wallet) | Abstract provider interface for route-bound deposit addresses | [Docs](/sdk/sda-modules/) | + ## Swidge Interface 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. 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/fiat-modules/fiat-moonpay/api-reference.mdx b/content/docs/sdk/fiat-modules/fiat-moonpay/api-reference.mdx index 4c0f85f7..fa196315 100644 --- a/content/docs/sdk/fiat-modules/fiat-moonpay/api-reference.mdx +++ b/content/docs/sdk/fiat-modules/fiat-moonpay/api-reference.mdx @@ -39,11 +39,12 @@ import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; const moonpay = new MoonPayProtocol(walletAccount, { apiKey: 'pk_live_xxxxx', - signUrl: async (urlForSignature) => urlForSignature, 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. + --- ## Methods @@ -218,6 +219,8 @@ Retrieves details of a specific transaction. } ``` +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 diff --git a/content/docs/sdk/fiat-modules/fiat-moonpay/configuration.mdx b/content/docs/sdk/fiat-modules/fiat-moonpay/configuration.mdx index 2afc069f..c9983822 100644 --- a/content/docs/sdk/fiat-modules/fiat-moonpay/configuration.mdx +++ b/content/docs/sdk/fiat-modules/fiat-moonpay/configuration.mdx @@ -61,6 +61,10 @@ const moonpay = new MoonPayProtocol(walletAccount, { | `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: diff --git a/content/docs/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell.mdx b/content/docs/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell.mdx index e816df37..3eb93786 100644 --- a/content/docs/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell.mdx +++ b/content/docs/sdk/fiat-modules/fiat-moonpay/guides/buy-and-sell.mdx @@ -11,7 +11,7 @@ Amounts use smallest units: fiat in minor units (cents), crypto in on-chain base ## 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: +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({ diff --git a/content/docs/sdk/fiat-modules/fiat-moonpay/guides/get-started.mdx b/content/docs/sdk/fiat-modules/fiat-moonpay/guides/get-started.mdx index e0ae4ab8..915d9b48 100644 --- a/content/docs/sdk/fiat-modules/fiat-moonpay/guides/get-started.mdx +++ b/content/docs/sdk/fiat-modules/fiat-moonpay/guides/get-started.mdx @@ -22,15 +22,15 @@ import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay' const moonpay = new MoonPayProtocol(walletAccount, { apiKey: process.env.MOONPAY_PUBLISHABLE_KEY, - secretKey: process.env.MOONPAY_SECRET_KEY + environment: 'sandbox' }) ``` -Never ship a secret key to browsers. Run server-side signing where your architecture allows, and rotate keys if they leak. +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 `cacheTime` and related options. +See [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) for secure URL signing, `cacheTime`, and related options. ## Next Steps diff --git a/content/docs/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions.mdx b/content/docs/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions.mdx index 5d283dd2..2910a20d 100644 --- a/content/docs/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions.mdx +++ b/content/docs/sdk/fiat-modules/fiat-moonpay/guides/manage-transactions.mdx @@ -26,9 +26,12 @@ const buyTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'buy') console.log('Crypto asset:', buyTx.cryptoAsset) console.log('Fiat currency:', buyTx.fiatCurrency) -console.log('Metadata:', buyTx.metadata) ``` + +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" diff --git a/content/docs/sdk/fiat-modules/fiat-moonpay/index.mdx b/content/docs/sdk/fiat-modules/fiat-moonpay/index.mdx index b25e3e06..1fdacc2f 100644 --- a/content/docs/sdk/fiat-modules/fiat-moonpay/index.mdx +++ b/content/docs/sdk/fiat-modules/fiat-moonpay/index.mdx @@ -22,24 +22,11 @@ This module requires a MoonPay developer account. [Create your account here](htt - **Currency Support**: Query supported cryptocurrencies, fiat currencies, and countries - **Customizable Widget**: Configure colors, themes, language, and behavior -## Supported Payment Methods +## Check Current Availability -- Credit and debit cards (Visa, Mastercard, etc.) -- Bank transfers (ACH, SEPA, etc.) -- Apple Pay and Google Pay -- Local payment methods (varies by region) +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. -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). - -## Supported Cryptocurrencies - -This module supports purchasing and selling cryptocurrencies on networks compatible with WDK wallet modules, including: - -- Ethereum and EVM-compatible chains (ETH, USD₮, etc.) -- Bitcoin (BTC) -- TRON (TRX, USD₮) -- TON -- Solana (SOL, USD₮) +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 diff --git a/content/docs/sdk/fiat-modules/fiat-moonpay/usage.mdx b/content/docs/sdk/fiat-modules/fiat-moonpay/usage.mdx index 53c88c4e..79e9e09c 100644 --- a/content/docs/sdk/fiat-modules/fiat-moonpay/usage.mdx +++ b/content/docs/sdk/fiat-modules/fiat-moonpay/usage.mdx @@ -8,7 +8,7 @@ icon: BookOpen # Usage -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. +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. diff --git a/content/docs/sdk/sda-modules/index.mdx b/content/docs/sdk/sda-modules/index.mdx new file mode 100644 index 00000000..25ffcceb --- /dev/null +++ b/content/docs/sdk/sda-modules/index.mdx @@ -0,0 +1,239 @@ +--- +title: Smart Deposit Address interface +description: Implement or integrate provider-defined deposit routes with the WDK Smart Deposit Address protocol contract. +docType: reference +schemaType: TechArticle +--- + +The Smart Deposit Address (SDA) interface in `@tetherto/wdk-wallet/protocols` defines how a provider exposes deposit routes, creates route-bound deposit addresses, and optionally reports their lifecycle and transfer status. + + +`@tetherto/wdk-wallet` provides the SDA interface and abstract base class, not a concrete SDA provider. The currently released `@tetherto/wdk` package does not expose SDA-specific orchestration. Instantiate a concrete provider directly according to that provider's documentation. + + +## Imports + +```typescript +import { + SdaProtocol, + type ISdaProtocol, + type SdaCreateDepositAddressOptions, + type SdaDepositAddress, + type SdaDepositOptions, + type SdaDepositQuote, + type SdaRecoveryOptions, + type SdaRecoveryResult, + type SdaRoute, + type SdaRoutesOptions, + type SdaTransfer, + type SdaTransfersOptions +} from '@tetherto/wdk-wallet/protocols' + +import { + NoSuchElementError, + UnsupportedOperationError, + ValueError +} from '@tetherto/wdk-wallet' +``` + +A provider subclass of `SdaProtocol` may be constructed with a writable account, a read-only account, or no account. A bound account supplies the default `destinationAddress` for address creation. + +## Provider Contract + +A concrete provider that extends `SdaProtocol` must implement route discovery and address creation. The remaining methods have default implementations that throw `UnsupportedOperationError`. + +| Method | Support | Purpose | +|--------|---------|---------| +| `getSupportedRoutes(options?)` | Required | List supported source chains, input tokens, destinations, and route metadata. | +| `createDepositAddress(options)` | Required | Create and, when the provider has an activation lifecycle, activate deposit addresses. | +| `quoteDeposit(options)` | Optional | Return a non-binding estimate for a deposit. | +| `deriveDepositAddress(options)` | Optional | Derive an address client-side without activating or monitoring it. | +| `getDepositAddress(id)` | Optional | Look up an address descriptor by its provider identifier. | +| `renewDepositAddress(id)` | Optional | Refresh a time-limited address activation. | +| `getTransfers(address, options?)` | Optional | List transfers observed for an address. | +| `getTransfersByRecipient(destinationChain, recipient, options?)` | Optional | List transfers routed to a destination recipient. | +| `getTransfer(id)` | Optional | Read one transfer's current status. | +| `recoverDepositAddress(options)` | Optional | Ask the provider to reindex a deposit by SDA identifier or address. | +| `disableDepositAddress(id)` | Optional | Stop a provider from accepting deposits at an address. | + +Check provider capabilities before building a flow around an optional method. An `UnsupportedOperationError` is a capability result, not a transient network failure. + +## Deposit Flow + +1. Call `getSupportedRoutes()` and select an advertised source chain, input token, destination chain, and output asset. +2. If the provider supports it, call `quoteDeposit()` for a non-binding estimate. Treat `expiry` as quote metadata, not a delivery guarantee. +3. Call `createDepositAddress()` with the selected route. Set `destinationAddress` explicitly, or bind an account whose address should be used as the destination. +4. Present the returned descriptor and its limits to the depositor before funds are sent. +5. If supported, monitor with `getTransfers()`, `getTransfersByRecipient()`, or `getTransfer()`. +6. Use renew, recovery, or disable operations only when the provider implements the corresponding lifecycle. + + +Address creation does not itself transfer funds. Validate that the returned descriptor matches the intended source chains, tokens, destination chain, output asset, and recipient before displaying or funding an address. + + +`createDepositAddress()` returns one descriptor per distinct address. A provider that uses one address across a chain family can return one descriptor covering every requested source chain; a provider that issues one address per chain returns multiple descriptors. + +## Core Types + +### Routes and tokens + +```typescript +type Blockchain = string | number + +type SdaRoutesOptions = { + sourceChain?: Blockchain + sourceToken?: string + destinationChain?: Blockchain + outputAsset?: string +} + +type SdaToken = { + token: string + chain: Blockchain + symbol: string + decimals: number + address?: string + name?: string +} + +type SdaRoute = { + sourceChains: Blockchain[] + inputTokens: SdaToken[] + destinationChain: Blockchain + outputAsset?: SdaToken + limits?: { + min?: number | bigint + max?: number | bigint + } + reusable?: boolean + estimatedDuration?: number +} +``` + +Route limits are denominated in the input token's base unit. A provider that only has limits in another denomination omits `limits` instead of converting them. + +### Quotes + +```typescript +type SdaDepositOptions = { + sourceChain: Blockchain + inputToken: string + destinationChain: Blockchain + outputAsset?: string + inputAmount: number | bigint +} + +type SdaDepositQuote = { + inputChain: Blockchain + inputToken: string + inputAmount: bigint + destinationChain: Blockchain + outputAsset: string + outputAmount: bigint + fees: Array<{ + type: 'network' | 'protocol' | 'affiliate' | 'other' + amount: bigint + token: string + chain?: Blockchain + included?: boolean + description?: string + }> + rate?: string + expiry?: number + id?: string +} +``` + +Quote amounts and itemized fees use base units. `rate` is a string to avoid precision loss, and the quote is non-binding. + +### Deposit addresses + +```typescript +type SdaCreateDepositAddressOptions = { + sourceChains: Blockchain[] + destinationChain: Blockchain + outputAsset?: string + destinationAddress?: string +} + +type SdaDepositAddress = { + address: string + id: string + sourceChains: Blockchain[] + supportedInputTokens: SdaToken[] + destinationChain: Blockchain + outputAsset?: SdaToken + destinationAddress: string + limits?: { + min?: number | bigint + max?: number | bigint + } + reusable: boolean + expiry?: number +} +``` + +Omitting `destinationAddress` uses the bound account's address. If no account is bound, the provider contract throws `ValueError`. + +`deriveDepositAddress()` uses the same options but only computes an address client-side. It does not activate the address or cause the provider to monitor it. + +### Transfer status + +```typescript +type SdaTransferStatus = + | 'pending' + | 'detected' + | 'processing' + | 'completed' + | 'failed' + | 'refund-pending' + | 'refunded' + | 'expired' + +type SdaTransfersOptions = { + sourceChain?: Blockchain + limit?: number + skip?: number + status?: SdaTransferStatus +} + +type SdaTransfer = { + id: string + status: SdaTransferStatus +} +``` + +The base contract guarantees only an identifier and status for a transfer. Provider-specific implementations may expose more data, but portable integrations should not require amounts, hashes, or timestamps that are absent from `SdaTransfer`. + +`sourceChain` can be required by a provider that keys an address by its address-and-chain pair. `limit`, `skip`, and `status` provide optional history pagination and filtering. + +### Recovery + +```typescript +type SdaRecoveryOptions = + | { id: string } + | { + address: string + sourceChain?: Blockchain + } + +type SdaRecoveryResult = { + status: 'reindexed' | 'pending' | 'failed' + address?: string + id?: string + transfer?: SdaTransfer + message?: string +} +``` + +Recover by the `SdaDepositAddress.id`, or by address plus `sourceChain` when the provider requires chain context. A recovery result reports whether reindexing completed, remains pending, or failed; a recovered transfer is optional. + +## Errors and Capability Checks + +| Error | Meaning | +|-------|---------| +| `UnsupportedOperationError` | The provider does not implement an optional operation. | +| `ValueError` | Required route data is missing or invalid, or `destinationAddress` was omitted without a bound account. | +| `NoSuchElementError` | A requested address or transfer does not exist. | + +Provider documentation remains authoritative for supported routes, activation windows, monitoring behavior, recovery semantics, and any additional option fields. diff --git a/content/docs/sdk/wallet-modules/index.mdx b/content/docs/sdk/wallet-modules/index.mdx index 0939da61..91ee17b0 100644 --- a/content/docs/sdk/wallet-modules/index.mdx +++ b/content/docs/sdk/wallet-modules/index.mdx @@ -73,7 +73,7 @@ Standard wallet implementations that use native blockchain tokens for transactio | [`@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 | In review | [Documentation](/sdk/wallet-modules/wallet-aptos) | +| [`@tetherto/wdk-wallet-aptos`](https://github.com/tetherto/wdk-wallet-aptos) | Aptos | ✅ Ready | [Documentation](/sdk/wallet-modules/wallet-aptos) | | [`@arkade-os/wdk`](https://github.com/arkade-os/arkade-wdk) | Arkade | ✅ Ready | [README](https://github.com/arkade-os/arkade-wdk#readme) | ## Account Abstraction Wallet Modules diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx index bee3976c..c66dbe38 100644 --- a/content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-aptos/api-reference.mdx @@ -52,8 +52,8 @@ new WalletAccountAptos( | Property | Description | |----------|-------------| | `index` | Index parsed from the derivation path. | -| `path` | Relative derivation path. | -| `keyPair` | Public/private key pair view. The private key is unavailable after `dispose()`. | +| `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 @@ -64,10 +64,10 @@ new WalletAccountAptos( | `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)` | Signs a native APT transfer without broadcasting. | `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 transaction receipt. | `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` | @@ -80,10 +80,13 @@ Read-only account for address-based reads and verification. ```typescript new WalletAccountReadOnlyAptos( address: string, - config?: AptosWalletConfig + 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` | @@ -91,18 +94,29 @@ new WalletAccountReadOnlyAptos( | `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 transaction receipt. | `Promise` | -| `verify(message, signature)` | Verifies a message signature. | `Promise` | +| `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 + 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 @@ -134,12 +148,31 @@ type TransferResult = { } ``` +`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. +`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. + -`signTransaction()` 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. diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx index ca123b71..13dc2fbf 100644 --- a/content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-aptos/configuration.mdx @@ -12,7 +12,12 @@ Configure the Aptos wallet module with an Aptos fullnode REST endpoint. A provid import WalletManagerAptos from '@tetherto/wdk-wallet-aptos' const wallet = new WalletManagerAptos(seedPhrase, { - provider: 'https://fullnode.mainnet.aptoslabs.com/v1', + provider: [ + 'https://fullnode.mainnet.aptoslabs.com/v1', + process.env.APTOS_FAILOVER_FULLNODE_URL + ].filter(Boolean), + retries: 3, + txnExpirationSecs: 60, transferMaxFee: 100000n }) ``` @@ -21,8 +26,13 @@ const wallet = new WalletManagerAptos(seedPhrase, { | Option | Type | Description | |--------|------|-------------| -| `provider` | `string` | Aptos fullnode REST API URL. Required for chain operations. | -| `transferMaxFee` | `number \| bigint` | Optional maximum fee in octas for transfer operations. | +| `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 @@ -37,7 +47,7 @@ const account = new WalletAccountAptos(seedPhrase, "0'/0'/0'", { }) ``` -Read-only accounts require only an address and provider configuration. +An address and provider are enough for balance and receipt reads: ```javascript import { WalletAccountReadOnlyAptos } from '@tetherto/wdk-wallet-aptos' @@ -47,9 +57,21 @@ const readOnlyAccount = new WalletAccountReadOnlyAptos('0x...', { }) ``` +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. +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 | |---------|--------------| @@ -74,9 +96,11 @@ 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 estimated transaction fees before signing and submitting transfer operations. +`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, { @@ -85,11 +109,14 @@ const wallet = new WalletManagerAptos(seedPhrase, { }) ``` +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. -- Set a fee cap for user-facing transfers. +- 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. diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/index.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/index.mdx index d382c1cf..e65856c7 100644 --- a/content/docs/sdk/wallet-modules/wallet-aptos/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-aptos/index.mdx @@ -18,7 +18,7 @@ Use this module when an app needs Aptos account derivation, APT balances, fungib - **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**: Reads balances, quotes fees, verifies signatures, and checks receipts without private 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 @@ -37,12 +37,17 @@ Use this module when an app needs Aptos account derivation, APT balances, fungib | 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`. | -| Offline signing | `signTransaction()` supports native APT transfers. Use `transfer()` for fungible asset transfers. | +| 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 diff --git a/content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx b/content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx index 9698a0df..95af9a9e 100644 --- a/content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx +++ b/content/docs/sdk/wallet-modules/wallet-aptos/usage.mdx @@ -66,9 +66,13 @@ const readOnlyAccount = new WalletAccountReadOnlyAptos('0x...', { 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 @@ -76,6 +80,10 @@ const quote = await account.quoteSendTransaction({ 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 @@ -112,6 +120,8 @@ 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 @@ -124,9 +134,9 @@ const valid = await readOnly.verify(message, signature) console.log('Signature valid:', valid) ``` -## Sign Native APT Transfers Offline +## Sign Native APT Transfers Without Broadcasting -`signTransaction()` signs a native APT transfer without broadcasting it. +`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({ @@ -138,9 +148,40 @@ console.log('Signed Aptos transaction:', signed) ``` -Offline signing is for native APT transfers. Fungible asset transfers are built and submitted through `transfer()`. +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 diff --git a/content/docs/sdk/wallet-modules/wallet-btc/index.mdx b/content/docs/sdk/wallet-modules/wallet-btc/index.mdx index a298cde8..afa2fd08 100644 --- a/content/docs/sdk/wallet-modules/wallet-btc/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-btc/index.mdx @@ -27,7 +27,7 @@ Use [`getAccountByPath`](/sdk/wallet-modules/wallet-btc/api-reference#getaccount - **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 +- **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 diff --git a/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/api-reference.mdx index 2e5994bc..472bd871 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/api-reference.mdx @@ -92,7 +92,7 @@ Treat the arrays returned through `keyPair` as read-only. Call `dispose()` when | `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. | +| `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. | @@ -106,9 +106,10 @@ Treat the arrays returned through `keyPair` as read-only. Call `dispose()` when - Sponsored mode returns `fee: 0n` from quote methods. - Paymaster-token mode returns fees in the configured paymaster token's base units. -- Owned account quotes are cached for up to 2 minutes for the same serialized transaction. +- 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. +- 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. ### Transfer Fee Cap diff --git a/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/configuration.mdx index a9fd91dc..24b14882 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/configuration.mdx @@ -164,3 +164,7 @@ const result = await account.sendTransaction({ ``` When an override is provided, the merged config is validated before the operation runs. + + +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. + diff --git a/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/guides/send-transactions.mdx index 3f16cefc..eee5d4cb 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm-7702-gasless/guides/send-transactions.mdx @@ -62,7 +62,11 @@ For paymaster-token mode, the fee is returned in the paymaster token's base unit ## Quote Reuse -Owned accounts cache a recently quoted transaction for up to 2 minutes. If `sendTransaction()` receives the same transaction during that window, the account can reuse the built UserOperation. If the account must include a fresh EIP-7702 authorization, it rebuilds the UserOperation before sending. +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. + + +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. + ## Override Fee Mode for One Send diff --git a/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/api-reference.mdx index 781ae33d..675542b7 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/api-reference.mdx @@ -267,8 +267,9 @@ const account = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { | `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}\>` | - | +| `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 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}\>` | - | @@ -325,11 +326,33 @@ const isValid = await account.verify(message, signature) console.log('Signature valid:', isValid) ``` +##### `signTransaction(tx, config?)` +Builds and signs one ERC-4337 v0.7 UserOperation without submitting it to the bundler. + +**Parameters:** +- `tx` (EvmErc4337Transaction): One transaction. Batch arrays are not accepted by this method. +- `config` (optional): Per-call configuration override (see [Config Override](#config-override)). + +**Returns:** `Promise\` - A signed UserOperation that can be quoted or submitted later + +**Throws:** Error if a non-sponsored operation exceeds `transactionMaxFee` + +```javascript title="Sign a UserOperation" +const signedUserOperation = await account.signTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +}) +``` + + +The returned operation contains the current nonce and the fee-mode configuration used while building it. Submit it promptly through the same account and do not mutate it. + + ##### `sendTransaction(tx, config?)` Sends a transaction via UserOperation through the bundler. **Parameters:** -- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[]): Transaction object or array for batch transactions +- `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 @@ -339,7 +362,9 @@ Sends a transaction via UserOperation through the bundler. **Returns:** `Promise\<{hash: string, fee: bigint}\>` - UserOperation hash and fee -**Throws:** Error if fee exceeds `transactionMaxFee` +**Throws:** Error if a non-sponsored, newly built operation exceeds `transactionMaxFee` + +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. **Example:** ```javascript @@ -380,14 +405,20 @@ The account reserves a local nonce for each pending `sendTransaction()` or `tran ##### `quoteSendTransaction(tx, config?)` Estimates the fee for a UserOperation without sending it. -Quoted UserOperations are cached internally for up to 2 minutes. When a later `sendTransaction()` matches the quoted transaction, the account performs a lightweight on-chain nonce check before reuse and re-quotes if the nonce has moved. +UserOperations built while quoting transaction inputs are cached internally for up to 2 minutes. When a later `sendTransaction()` or `signTransaction()` matches the quoted transaction, the account performs a lightweight on-chain nonce check before reuse and re-quotes if the nonce has moved. A supplied signed UserOperation is evaluated directly rather than rebuilt. + + +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. + **Parameters:** -- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[]): Transaction object or array (same as sendTransaction) +- `tx` (EvmErc4337Transaction | EvmErc4337Transaction[] | UserOperationV7): Transaction object, array, or signed UserOperation - `config` (optional): Per-call configuration override (see [Config Override](#config-override)) **Returns:** `Promise\<{fee: bigint}\>` - Fee estimate +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. + **Example:** ```javascript const quote = await account.quoteSendTransaction({ @@ -1057,8 +1088,8 @@ type ConfigOverride = Partial< ``` **Available override fields:** -- `isSponsored` (boolean): Switch to sponsorship mode -- `useNativeCoins` (boolean): Switch to native coin mode +- `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 @@ -1066,6 +1097,10 @@ type ConfigOverride = Partial< - `transactionMaxFee` (number | bigint): Override maximum fee for `sendTransaction()` and `signTransaction()` - `transferMaxFee` (number | bigint): Override maximum fee +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. + +The quote cache does not include these override fields in its key. Pass the same fee-mode and paymaster settings to a quote and its matching send, sign, or transfer. + ### 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. @@ -1085,6 +1120,16 @@ interface EvmErc4337Transaction { 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. +### UserOperationV7 + +`UserOperationV7` is the ERC-4337 v0.7 operation type re-exported by `@tetherto/wdk-wallet-evm-erc-4337` from AbstractionKit. + +```typescript +import type { UserOperationV7 } from '@tetherto/wdk-wallet-evm-erc-4337' +``` + +`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. diff --git a/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions.mdx index 063ae29e..8a7e82e7 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/guides/send-transactions.mdx @@ -3,7 +3,7 @@ title: Send Transactions description: Send gasless transactions and estimate fees. --- -This guide explains how to [send a gasless transaction](#send-a-gasless-transaction), [estimate fees](#estimate-fees), [cap transaction fees](#cap-transaction-fees), [reuse a recent quote](#reuse-a-recent-quote), and [use a custom paymaster token](#send-with-custom-paymaster-token). +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), and [use a custom paymaster token](#send-with-custom-paymaster-token). ## Send a Gasless Transaction @@ -34,9 +34,33 @@ const quote = await account.quoteSendTransaction({ console.log('Estimated fee:', quote.fee) ``` +## 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) +``` + + +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. + + +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. + ## Cap Transaction Fees -Set `transactionMaxFee` to reject non-sponsored `sendTransaction()` and `signTransaction()` operations when the estimated UserOperation fee is above your cap. Use `transferMaxFee` separately for token transfers. +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. ```javascript title="Cap sendTransaction fees" const result = await account.sendTransaction({ @@ -55,6 +79,10 @@ const result = await account.sendTransaction({ Quotes are 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 the cached UserOperation. If the nonce has moved, it builds a fresh quote before sending. + +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', @@ -78,7 +106,7 @@ The wallet reads the on-chain nonce before reserving locally. If that read does ## 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 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({ @@ -91,6 +119,8 @@ const result = await account.sendTransaction({ }) ``` +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 [transfer ERC-20 tokens](/sdk/wallet-modules/wallet-evm-erc-4337/guides/transfer-tokens). diff --git a/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/index.mdx b/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/index.mdx index 3136210a..b6579d04 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm-erc-4337/index.mdx @@ -14,6 +14,7 @@ Use the ERC-4337 wallet module when your app needs EVM smart accounts, UserOpera - **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 - **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 diff --git a/content/docs/sdk/wallet-modules/wallet-evm/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-evm/api-reference.mdx index a3e47806..1d1ca5d4 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm/api-reference.mdx @@ -281,8 +281,8 @@ const signerAccount = new WalletAccountEvm( | `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 | +| `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 | @@ -443,8 +443,12 @@ 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): The transaction object +- `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 @@ -489,7 +493,9 @@ console.log('Transaction fee:', result.fee, 'wei') Estimates the fee for an EVM transaction without sending it. **Parameters:** -- `tx` (EvmTransaction): The transaction object (same format as sendTransaction) +- `tx` (EvmTransaction | string): A transaction object or serialized transaction string + +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 diff --git a/content/docs/sdk/wallet-modules/wallet-evm/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-evm/guides/send-transactions.mdx index 7c8307ab..4cac24a6 100644 --- a/content/docs/sdk/wallet-modules/wallet-evm/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-evm/guides/send-transactions.mdx @@ -73,7 +73,7 @@ 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. +`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. ## Estimate Transaction Fees diff --git a/content/docs/sdk/wallet-modules/wallet-solana/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-solana/api-reference.mdx index da49681b..8549722d 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana/api-reference.mdx @@ -129,8 +129,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` | @@ -205,12 +205,14 @@ 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 or if the estimated fee is greater than `transactionMaxFee` @@ -228,7 +230,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) @@ -515,6 +517,16 @@ interface SolanaWalletConfig { } ``` +### 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 diff --git a/content/docs/sdk/wallet-modules/wallet-solana/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-solana/guides/send-transactions.mdx index 98592994..dd227f61 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana/guides/send-transactions.mdx @@ -5,7 +5,7 @@ docType: how-to schemaType: TechArticle --- -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), [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). +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. @@ -40,6 +40,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. diff --git a/content/docs/sdk/wallet-modules/wallet-solana/index.mdx b/content/docs/sdk/wallet-modules/wallet-solana/index.mdx index 7f909faa..c721af37 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana/index.mdx @@ -28,6 +28,7 @@ Use [`getAccountByPath`](/sdk/wallet-modules/wallet-solana/api-reference#getacco - **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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx index 5d4d18ea..acef4b09 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/api-reference.mdx @@ -161,8 +161,8 @@ const account = new WalletAccountTon(seedPhrase, "0'/0/0", { | `sign(message)` | Signs a message using the account's private key | `Promise\` | | `verify(message, signature)` | Verifies a message signature | `Promise\` | | `signTransaction(tx)` | Builds a signed external-message body using current chain state, without broadcasting it | `Promise\` | -| `sendTransaction(tx)` | Sends a TON transaction | `Promise\<{hash: string, fee: bigint}\>` | -| `quoteSendTransaction(tx)` | Estimates the fee for a TON transaction | `Promise\<{fee: bigint}\>` | +| `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\` | @@ -239,11 +239,12 @@ const cell = await account.signTransaction({ 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 | 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 @@ -263,7 +264,7 @@ 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 | bigint): Amount in nanotons (1 TON = 1,000,000,000 nanotons) - `bounceable` (boolean, optional): Whether the address is bounceable (TON-specific, optional) @@ -518,6 +519,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 diff --git a/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx index deed1281..29180fa0 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/guides/send-transactions.mdx @@ -5,7 +5,7 @@ docType: how-to schemaType: TechArticle --- -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), and [prepare a signed transaction body](#prepare-a-signed-transaction-body). +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. @@ -72,6 +72,27 @@ const cell = await account.signTransaction({ // `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). diff --git a/content/docs/sdk/wallet-modules/wallet-ton/index.mdx b/content/docs/sdk/wallet-modules/wallet-ton/index.mdx index 762e114d..a89ab12e 100644 --- a/content/docs/sdk/wallet-modules/wallet-ton/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-ton/index.mdx @@ -28,6 +28,7 @@ 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 - **Derived Key Cleanup**: Account disposal zeroes derived private key bytes with sodium-universal diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx index 5b89ad1d..1e3f1576 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/configuration.mdx @@ -27,12 +27,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 + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } const wallet = new WalletManagerTronGasfree(seedPhrase, config) @@ -43,15 +39,19 @@ 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. @@ -71,10 +71,8 @@ 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' + serviceProvider: 'REPLACE_WITH_PROVIDER_ADDRESS', + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } ) @@ -85,10 +83,8 @@ const readOnlyAccount = new WalletAccountReadOnlyTronGasfree( 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' } ) @@ -165,14 +161,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 @@ -185,14 +182,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 @@ -206,11 +204,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. @@ -223,7 +223,7 @@ The `verifyingContract` option is the Tron address of the contract that verifies ```javascript const config = { - verifyingContract: 'TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH' + verifyingContract: 'TFFAMQLZybALaLb4uxHA9RBE7pxhUAjF3U' } ``` @@ -274,10 +274,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 } ``` @@ -289,10 +287,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 } ``` diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/check-balances.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/check-balances.mdx index 65eddf5b..241b90a1 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/check-balances.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/check-balances.mdx @@ -40,11 +40,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' }) ``` @@ -61,4 +59,4 @@ 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). diff --git a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/get-started.mdx b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/get-started.mdx index 0a246972..d04e52b7 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/get-started.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron-gasfree/guides/get-started.mdx @@ -33,17 +33,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. diff --git a/content/docs/sdk/wallet-modules/wallet-tron/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-tron/api-reference.mdx index 5e2b8346..e1fe3fa1 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron/api-reference.mdx @@ -169,9 +169,9 @@ 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\` | - | -| `signTransaction(tx)` | Signs a Tron transaction without broadcasting it | `Promise\` | If no provider, fee exceeds `transactionMaxFee`, or a pre-built transaction owner does not match the account | -| `sendTransaction(tx)` | Sends a Tron transaction | `Promise\<{hash: string, fee: bigint, activationFee: bigint}\>` | If no provider, fee exceeds `transactionMaxFee`, or a pre-built transaction owner does not match the account | -| `quoteSendTransaction(tx)` | Estimates the fee for a Tron transaction | `Promise\<{fee: bigint, activationFee: bigint}\>` | 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 | @@ -227,7 +227,7 @@ Signs a Tron transaction and returns the signed transaction object. This method **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 +**Returns:** `Promise\` - Signed Tron transaction object with a `signature` array **Throws:** - Error if no TronWeb provider is configured @@ -248,14 +248,16 @@ console.log('Signed transaction:', signedTransaction) Sends a Tron transaction and returns the result with hash, fee, and activation fee details. **Parameters:** -- `tx` (TronTransaction): Native TRX transfer, smart-contract call descriptor, or pre-built TronWeb transaction +- `tx` (TronTransaction | TronSignedTransaction): Native TRX transfer, smart-contract call descriptor, pre-built unsigned TronWeb transaction, or signed transaction + +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 `transactionMaxFee` when configured -- Error if a pre-built transaction is owned by a different account +- Error if an unsigned pre-built transaction is owned by a different account **Example:** ```javascript @@ -272,7 +274,7 @@ console.log('Activation fee:', result.activationFee, 'sun') 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: bigint, activationFee: bigint}\>` - Fee estimate in sun and the portion used for account activation @@ -583,16 +585,14 @@ interface TransactionResult { } ``` -### SignedTransaction +### TronSignedTransaction ```typescript -interface SignedTransaction { - txID: string; // Tron transaction ID - signature: string[]; // Serialized secp256k1 signature - [key: string]: unknown; // Other fields from the unsigned Tron transaction -} +type TronSignedTransaction = import('tronweb').Types.SignedTransaction ``` +The WDK package re-exports this TronWeb type under the exact name `TronSignedTransaction`. + ### TronActivationFee ```typescript diff --git a/content/docs/sdk/wallet-modules/wallet-tron/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-tron/guides/send-transactions.mdx index 09980e2e..cf7cf9e7 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron/guides/send-transactions.mdx @@ -5,7 +5,7 @@ docType: how-to schemaType: TechArticle --- -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), 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). @@ -65,7 +65,7 @@ const result = await account.sendTransaction(transaction) console.log('Transaction hash:', result.hash) ``` -Before signing or sending a pre-built transaction, WDK checks that the transaction owner address matches the wallet account address. +Before signing or sending an unsigned pre-built transaction, WDK checks that the transaction owner address matches the wallet account address. ## Estimate Transaction Fees @@ -104,6 +104,27 @@ const signedTransaction = await account.signTransaction({ 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): diff --git a/content/docs/sdk/wallet-modules/wallet-tron/index.mdx b/content/docs/sdk/wallet-modules/wallet-tron/index.mdx index a7bfc647..54b57490 100644 --- a/content/docs/sdk/wallet-modules/wallet-tron/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-tron/index.mdx @@ -16,6 +16,7 @@ Use the TRON wallet module for standard TRON accounts where users can handle reg - **Message Signing:** Sign and verify messages using Tron cryptography - **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 diff --git a/content/docs/sdk/wallet-modules/which-wallet-module.mdx b/content/docs/sdk/wallet-modules/which-wallet-module.mdx index f34b44e6..e6a5cb4b 100644 --- a/content/docs/sdk/wallet-modules/which-wallet-module.mdx +++ b/content/docs/sdk/wallet-modules/which-wallet-module.mdx @@ -26,6 +26,7 @@ Start with the chain your app needs to support. Then choose the account model: | 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) | diff --git a/content/docs/start-building/react-native-quickstart.mdx b/content/docs/start-building/react-native-quickstart.mdx index 9ebb47a8..87a2cba1 100644 --- a/content/docs/start-building/react-native-quickstart.mdx +++ b/content/docs/start-building/react-native-quickstart.mdx @@ -125,13 +125,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 ``` -`@tetherto/wdk-react-native-core` `v1.0.0-beta.13` includes the React Native source entry used by Metro, but its npm artifact omits the declared `dist/index.js` and `dist/index.d.ts` files. Default Node resolution and package declaration lookup fail for this tag. Use it only in a Metro flow you have verified; if your build requires the missing outputs, wait for a corrected artifact. +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`. @@ -486,7 +488,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 @@ -496,7 +498,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 diff --git a/content/docs/tools/asset-registry/api-reference.mdx b/content/docs/tools/asset-registry/api-reference.mdx index 6c7aac33..d6601921 100644 --- a/content/docs/tools/asset-registry/api-reference.mdx +++ b/content/docs/tools/asset-registry/api-reference.mdx @@ -91,7 +91,7 @@ const byOneOfMany = registry.getAsset([ ]) ``` -String comparisons are case-insensitive by default. Pass `{ caseSensitive: true }` to require exact casing. +String comparisons in `getAsset()` are case-insensitive by default. Pass `{ caseSensitive: true }` to require exact casing. ## Class: `WdkTokenAssetRegistry` @@ -110,16 +110,22 @@ const registry = new WdkTokenAssetRegistry(commonTokens) | --- | --- | --- | | `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 | `TokenAsset[]` | +| `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)` @@ -179,6 +185,8 @@ type BaseAssetOptions = { } ``` +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 @@ -187,7 +195,7 @@ type BaseAssetFilter = Partial ## Bundled Assets -`@tetherto/wdk-asset-registry/assets/common-tokens` exports bundled token metadata. In `v1.0.0-beta.1`, the published list contains USDT, USDT0, and XAUt token records across supported chains. +`@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 diff --git a/content/docs/tools/asset-registry/configuration.mdx b/content/docs/tools/asset-registry/configuration.mdx index 63e278be..a82095d0 100644 --- a/content/docs/tools/asset-registry/configuration.mdx +++ b/content/docs/tools/asset-registry/configuration.mdx @@ -62,11 +62,15 @@ registry.registerAsset( ## Look up tokens -String lookups are case-insensitive by default. Pass `{ caseSensitive: true }` when casing must match exactly. +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 }) ``` diff --git a/content/docs/tools/failover-provider/api-reference.mdx b/content/docs/tools/failover-provider/api-reference.mdx index 6d4f3c6f..e37061fd 100644 --- a/content/docs/tools/failover-provider/api-reference.mdx +++ b/content/docs/tools/failover-provider/api-reference.mdx @@ -25,7 +25,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 @@ -73,13 +73,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: @@ -101,7 +107,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) diff --git a/content/docs/tools/failover-provider/configuration.mdx b/content/docs/tools/failover-provider/configuration.mdx index a30cffaf..ef515a4d 100644 --- a/content/docs/tools/failover-provider/configuration.mdx +++ b/content/docs/tools/failover-provider/configuration.mdx @@ -36,7 +36,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) @@ -58,7 +59,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) }) ``` @@ -97,10 +99,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? diff --git a/content/docs/tools/failover-provider/index.mdx b/content/docs/tools/failover-provider/index.mdx index 10419562..f8e6a069 100644 --- a/content/docs/tools/failover-provider/index.mdx +++ b/content/docs/tools/failover-provider/index.mdx @@ -12,7 +12,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 @@ -21,6 +21,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. diff --git a/content/docs/tools/p2p-address-book/api-reference.mdx b/content/docs/tools/p2p-address-book/api-reference.mdx new file mode 100644 index 00000000..9b53262a --- /dev/null +++ b/content/docs/tools/p2p-address-book/api-reference.mdx @@ -0,0 +1,132 @@ +--- +title: P2P Address Book API Reference +description: Public API, records, configuration, and data semantics for @tetherto/wdk-p2p-address-book. +docType: reference +schemaType: APIReference +icon: Code +--- + +## 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) diff --git a/content/docs/tools/p2p-address-book/get-started.mdx b/content/docs/tools/p2p-address-book/get-started.mdx new file mode 100644 index 00000000..dea28e04 --- /dev/null +++ b/content/docs/tools/p2p-address-book/get-started.mdx @@ -0,0 +1,94 @@ +--- +title: P2P Address Book Get Started +description: Install @tetherto/wdk-p2p-address-book and create a local wallet contact directory. +docType: how-to +schemaType: TechArticle +icon: Rocket +--- + +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). diff --git a/content/docs/tools/p2p-address-book/index.mdx b/content/docs/tools/p2p-address-book/index.mdx index f3e0d675..131a5ed6 100644 --- a/content/docs/tools/p2p-address-book/index.mdx +++ b/content/docs/tools/p2p-address-book/index.mdx @@ -49,244 +49,29 @@ The package stores destinations; it does not validate chain syntax, resolve iden U -. "Cannot derive this book or content key" .-> M`} /> -Random peers do not automatically store every address book. The blind peer is an explicitly configured availability peer. +Random peers do not automatically store every address book. A blind peer is an explicitly configured availability peer. -### Identity and permissions +## Builder path -The seed and namespace derive the book identity, content key, and bootstrap signing key. Each device keeps a separate writer key in its Corestore. 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. - -Use `create()` only when the book is known to be new. Use `addMirror()` when joining an existing book; it waits until the existing genesis is visible before enrolling the device and does not create a fallback book on timeout. - - -`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. - - - - - -| 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 derived 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 for the book and carries direct replication connections. | -| [`blind-peering`](https://github.com/holepunchto/blind-peering) | Requests background replication from configured blind peers. | - -Replication encryption is not local encryption at rest. Protect the Corestore directory as sensitive wallet data. - - - - -## Install - -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 - -This example disables networking and creates a new book. It expects a caller-validated destination, reads the current 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() - } - } -} -``` - -`fromSeed()` opens the book before resolving. `book.close()` closes the book's networking and replication resources, but the direct API does not close the root Corestore supplied by the application. - -## Synchronize another 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. - -On the first device, 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) -``` - -On 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()`. Its resolution confirms that existing genesis was observed and the local writer enrolled; it does not confirm that every record is restored or retained remotely. Refetch on payload-free `update` events, unregister listeners during cleanup, and close the book and root Corestore. - -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. - -## Data and operations - -| Record | Required input | Optional input | Generated fields | -| --- | --- | --- | --- | -| Contact | `name` | `username` | `id`, `createdAt`, `updatedAt` | -| Address | `address`, `type`, `network` | `label` | `id`, `contactId`, `createdAt`, `updatedAt` | - -Contact names and usernames do not need to be unique. The schema has no first-class email or custom-field support. Supported address types are `bitcoin`, `evm`, `tron`, `uma`, `lightning-address`, `lnurl`, and `spark`. - -The package trims text fields and lowercases `network`, but 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. - -- `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. -- Refetch after an `update` event rather than treating a locally returned record as the converged state. - - -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. - - -## P2P Address Book or 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 | - -P2P reduces central readable data custody; it does not eliminate infrastructure. It exchanges a conventional contacts database for device storage, key management, replication availability, and recovery responsibilities. - -## Prepare for production - -### Blind peers and availability - -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 to clients 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 its storage, garbage collection, request, database, and transport metrics. These do not prove per-book durability or recovery completion. - -In `1.0.0-beta.1`, `removeMirror()` deletes the persisted mirror 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 delete blocks already held by the server. - -### Privacy and deletion 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. - -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. - -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. - -### Operational 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. - -The beta release publishes no package-level migration, downgrade, or cross-version replication guarantee. - - - - -| Area | Public surface | -| --- | --- | -| Construction | `AddressBook.fromSeed()`, advanced `new AddressBook()`, `AddressBook.createWorkletModule()` | -| Lifecycle | `ready()`, `create()`, `close()`, `suspend()`, `resume()` | -| Contacts | `addContact()`, `editContact()`, `deleteContact()`, `getContact()`, `listContacts()` | -| Addresses | `addAddress()`, `editAddress()`, `deleteAddress()`, `listAddresses()` | -| Query and events | `search()`, `on('update')`, `off('update')`, `getInfo()` | -| Writers | `addWriter()`, `removeWriter()`, `listWriters()`, `getWriter()` | -| Mirrors | `addMirror()`, `removeMirror()`, `listMirrors()`, `AddressBook.selectMirrors()` | -| Identifiers | `writerKey`, `key`, `discoveryKey`, `writable`; treat `encryptionKey` as secret | - -The main `fromSeed()` options are: - -| Option | Required | Purpose | -| --- | --- | --- | -| `namespace` | Yes | Stable application scope for book identity, encryption, 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, defaulting 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 as stable. `close()` destroys a supplied swarm. | - -The low-level constructor does not require an encryption key while replication defaults to enabled. Prefer `fromSeed()`. Advanced callers must supply `encryptionKey` before `ready()` or disable replication. The declared `optimistic` constructor option is ignored in `1.0.0-beta.1`; Autobase optimistic mode is always enabled. - -Writer-management methods are not included in the exported worklet bridge interface. - - - + + +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. diff --git a/content/docs/tools/p2p-address-book/production-and-privacy.mdx b/content/docs/tools/p2p-address-book/production-and-privacy.mdx new file mode 100644 index 00000000..fc1bd3d9 --- /dev/null +++ b/content/docs/tools/p2p-address-book/production-and-privacy.mdx @@ -0,0 +1,79 @@ +--- +title: P2P Address Book Production and Privacy +description: Plan blind-peer availability, storage, privacy, deletion, and upgrades for the P2P Address Book. +docType: explanation +schemaType: TechArticle +icon: ShieldCheck +--- + +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. diff --git a/content/docs/tools/p2p-address-book/sync-and-recovery.mdx b/content/docs/tools/p2p-address-book/sync-and-recovery.mdx new file mode 100644 index 00000000..85989f51 --- /dev/null +++ b/content/docs/tools/p2p-address-book/sync-and-recovery.mdx @@ -0,0 +1,82 @@ +--- +title: P2P Address Book Sync and Recovery +description: Enroll devices, configure blind peers, and recover an existing P2P Address Book. +docType: how-to +schemaType: TechArticle +icon: RefreshCw +--- + +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). diff --git a/content/docs/tools/pear-wrk-wdk/api-reference.mdx b/content/docs/tools/pear-wrk-wdk/api-reference.mdx index 516b193e..9f2d9b1f 100644 --- a/content/docs/tools/pear-wrk-wdk/api-reference.mdx +++ b/content/docs/tools/pear-wrk-wdk/api-reference.mdx @@ -7,7 +7,7 @@ schemaType: APIReference ## Package: `@tetherto/pear-wrk-wdk` -### Export: `HRPC` +### Top-level export: `HRPC` #### Command Methods @@ -71,7 +71,7 @@ Returns: 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.9, 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. +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` @@ -122,7 +122,7 @@ A full disposal closes all generic modules and clears the WDK instance. A non-em - `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` @@ -211,9 +211,9 @@ Registers the worklet-side handler used to service `callModule()` requests. Registers the host-side handler used to receive `moduleEvent()` messages. -### Export: `registerRpcHandlers(rpc, context)` +### 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`. HRPC generic modules can additionally supply `moduleManagers` and `capabilities`; the runtime manages `moduleRuntime` and `moduleInstances`. @@ -292,14 +292,26 @@ 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: @@ -312,7 +324,7 @@ 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.9 supports these JSON-RPC method names: +Beta.10 supports these JSON-RPC method names: - `workletStart` - `generateEntropyAndEncrypt` @@ -326,6 +338,8 @@ Beta.9 supports these JSON-RPC method names: 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. diff --git a/content/docs/tools/pear-wrk-wdk/configuration.mdx b/content/docs/tools/pear-wrk-wdk/configuration.mdx index 29bd6dc5..7d6b44f3 100644 --- a/content/docs/tools/pear-wrk-wdk/configuration.mdx +++ b/content/docs/tools/pear-wrk-wdk/configuration.mdx @@ -117,7 +117,7 @@ const workletConfig = { - `resetWdkWallets()` reads only the `networks` portion of the decoded config. -Generic modules are HRPC-only in beta.9. The JSON-RPC handler does not construct modules or expose module calls and events. +Generic modules are HRPC-only in beta.10. The JSON-RPC handler does not construct modules or expose module calls and events. ## Initialize WDK @@ -141,7 +141,7 @@ 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 and closes its generic modules before re-registering wallets and protocols from the new config. -- In beta.9, 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. +- 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 @@ -171,7 +171,7 @@ await hrpc.resetWdkWallets({ - 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): @@ -189,6 +189,7 @@ 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 @@ -231,7 +232,7 @@ module.exports = (ipc) => { 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.9 supports WDK initialization and disposal, secret/mnemonic operations, wallet-account calls, and dynamic wallet/protocol registration. 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. +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. diff --git a/content/docs/tools/pear-wrk-wdk/index.mdx b/content/docs/tools/pear-wrk-wdk/index.mdx index 0aa628c0..be68773d 100644 --- a/content/docs/tools/pear-wrk-wdk/index.mdx +++ b/content/docs/tools/pear-wrk-wdk/index.mdx @@ -12,7 +12,7 @@ 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()` @@ -28,8 +28,12 @@ Powered by `@tetherto/pear-wrk-wdk`. 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.9`; the JSON-RPC handler does not expose them. This package also does not export a pre-built WDK bundle. +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. diff --git a/content/docs/tools/react-native-core/api-reference.mdx b/content/docs/tools/react-native-core/api-reference.mdx index ce3d8a87..bda64376 100644 --- a/content/docs/tools/react-native-core/api-reference.mdx +++ b/content/docs/tools/react-native-core/api-reference.mdx @@ -339,7 +339,7 @@ Hook to call configured protocol modules through the active WDK worklet account. |-----------|------|-------------| | `params.network` | `string` | Network identifier configured in `wdkConfigs` | | `params.accountIndex` | `number` | Account index to use for the protocol call | -| `params.protocolType` | `'bridge' \| 'swap' \| 'lending' \| 'fiat'` | Protocol category | +| `params.protocolType` | `'bridge' \| 'swap' \| 'swidge' \| 'lending' \| 'fiat'` | Protocol category | | `params.protocolName` | `string` | Registered protocol name | ### Returns @@ -348,6 +348,8 @@ Returns `T`, a proxy for the protocol methods you type with the hook generic. Ea 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 @@ -389,8 +391,8 @@ function SwapQuoteScreen() { Hook that creates a typed proxy for one named generic module in the active HRPC worklet. Every method access is forwarded through `ModuleService.callModule()`. The proxy also exposes `on(event, listener)` for module events. - -React Native Core `v1.0.0-beta.13` exposes this API but pins Pear Worklet `v1.0.0-beta.8`, which does not contain the required module HRPC methods. The default dependency graph therefore cannot run these calls. Installing Pear Worklet beta.9 separately does not replace the nested beta.8 dependency; wait for a dependency-aligned React Native Core release before adopting this API. + +React Native Core `v1.0.0-beta.14` and later pin a Pear Worklet version with the HRPC methods required by this API. ### Parameters diff --git a/content/docs/tools/react-native-core/index.mdx b/content/docs/tools/react-native-core/index.mdx index 54d7b7d6..d2c6d648 100644 --- a/content/docs/tools/react-native-core/index.mdx +++ b/content/docs/tools/react-native-core/index.mdx @@ -16,12 +16,12 @@ description: Hooks-based React Native library for building multi-chain wallet ap - **Multi-wallet support** - create, restore, switch, lock, unlock, and delete wallets - **Typed React Native source API** - exported hook and service types are available through the package's React Native source condition - -In `v1.0.0-beta.13`, the generic module API is not runnable through the default dependency graph: React Native Core pins Pear Worklet `v1.0.0-beta.8`, while module calls and events require the HRPC methods added in Pear Worklet `v1.0.0-beta.9`. Installing beta.9 separately does not replace the nested beta.8 dependency. Wait for a dependency-aligned React Native Core release before using `useModule()` or `ModuleService` in an end-to-end integration. + +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 beta.13 npm artifact ships its `react-native` source entry but omits the declared default JavaScript and declaration outputs under `dist/`. Consume this release through React Native/Metro; the tag does not provide a working default Node entrypoint. + +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 @@ -29,9 +29,11 @@ The beta.13 npm artifact ships its `react-native` source entry but omits the dec ### 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 @@ -140,8 +142,8 @@ 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, lending, and fiat protocol methods - +-- useModule() - call named generic modules and subscribe to events (dependency-aligned HRPC builds only) + +-- 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 for one account index diff --git a/content/docs/tools/react-native-secure-storage/configuration.mdx b/content/docs/tools/react-native-secure-storage/configuration.mdx index 74a1b3dd..a2a17215 100644 --- a/content/docs/tools/react-native-secure-storage/configuration.mdx +++ b/content/docs/tools/react-native-secure-storage/configuration.mdx @@ -10,9 +10,13 @@ This page shows how to install `@tetherto/wdk-react-native-secure-storage`, crea ## Install the package ```bash title="Install React Native Secure Storage" -npm install @tetherto/wdk-react-native-secure-storage react-native-keychain expo-crypto expo-local-authentication +npm install @tetherto/wdk-react-native-secure-storage@1.0.0-beta.4 react-native-keychain expo-crypto expo-local-authentication ``` + +The `v1.0.0-beta.5` npm artifact includes the React Native source entry at `src/index.ts`, but it does not include the `dist/` files declared by `main`, `types`, and `exports.default`. The install command pins beta.4, the latest artifact with those compiled JavaScript and declaration files. Use beta.5 only with a React Native resolver configuration you have verified against its source entry. + + The package declares `react-native` as a peer dependency and uses native modules through `react-native-keychain`. diff --git a/content/docs/tools/react-native-secure-storage/index.mdx b/content/docs/tools/react-native-secure-storage/index.mdx index 5c2b7dc6..7af94f7b 100644 --- a/content/docs/tools/react-native-secure-storage/index.mdx +++ b/content/docs/tools/react-native-secure-storage/index.mdx @@ -9,6 +9,10 @@ React Native Secure Storage provides a typed wrapper for storing encrypted walle Powered by [`@tetherto/wdk-react-native-secure-storage`](https://www.npmjs.com/package/@tetherto/wdk-react-native-secure-storage). + +The `v1.0.0-beta.5` npm artifact includes the React Native source entry at `src/index.ts`, but it does not include the `dist/` files declared by `main`, `types`, and `exports.default`. Pin beta.4 when the consumer needs those compiled JavaScript and declaration files; use beta.5 only with a React Native resolver configuration you have verified against its source entry. + + ## Features - **Wallet credential helpers**: Store and retrieve encrypted seeds, encrypted entropy, and encryption keys. diff --git a/content/docs/tools/wdk-utils/api-reference.mdx b/content/docs/tools/wdk-utils/api-reference.mdx index 4510ae06..365f85fc 100644 --- a/content/docs/tools/wdk-utils/api-reference.mdx +++ b/content/docs/tools/wdk-utils/api-reference.mdx @@ -1,6 +1,6 @@ --- title: WDK Utils API Reference -description: Validation, encryption, BIP-21, BOLT11, and EIP-681 APIs for @tetherto/wdk-utils +description: Validation, encryption, mnemonic sharing, BIP-21, BOLT11, and EIP-681 APIs for @tetherto/wdk-utils docType: reference schemaType: APIReference --- @@ -16,6 +16,7 @@ schemaType: APIReference | `validateBech32m(address)` | Validate a SegWit v1+ Bitcoin address. | `BtcAddressValidationResult` | | `validateBitcoinAddress(address)` | Validate a Bitcoin address across Base58Check, Bech32, and Bech32m formats. | `BtcAddressValidationResult` | | `validateEVMAddress(address)` | Validate an EVM address, including EIP-55 checksum rules for mixed-case inputs. | `EvmAddressValidationResult` | +| `validateSolanaAddress(address)` | Validate a base58-encoded 32-byte Solana public key, including off-curve program-derived addresses. | `SolanaAddressValidationResult` | | `stripLightningPrefix(input)` | Remove a leading `lightning:` prefix before other Lightning validation steps. | `string` | | `validateLightningInvoice(address)` | Validate a Lightning invoice string. | `LightningInvoiceValidationResult` | | `decode(invoice)` | Decode a BOLT11 Lightning invoice into structured invoice data. | `LightningInvoiceDecodingResult` | @@ -92,6 +93,25 @@ const result = validateEVMAddress('0x742d35Cc6634C0532925a3b844Bc454e4438f44e') - `{ success: true, type: 'evm' }` - `{ success: false, reason: string }` +#### `validateSolanaAddress(address)` + +Validate a Solana address as a base58-encoded 32-byte public key. The validator trims surrounding whitespace and accepts off-curve program-derived addresses because they can be valid recipients. + +```javascript title="Validate A Solana Address" +import { validateSolanaAddress } from '@tetherto/wdk-utils' + +const result = validateSolanaAddress( + 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' +) +``` + +`SolanaAddressValidationResult` is one of these shapes: + +- `{ success: true, type: 'solana' }` +- `{ success: false, reason: 'EMPTY_ADDRESS' | 'INVALID_FORMAT' | 'INVALID_LENGTH' }` + +Solana addresses do not include a checksum. This helper verifies structure and decoded length, not whether an account exists, is on curve, or belongs to the intended recipient. + #### `stripLightningPrefix(input)` Remove a `lightning:` URI prefix before validating or parsing Lightning inputs. @@ -416,6 +436,52 @@ const keyPair = deriveSeedKeyPair(seedBytes, { Treat the returned `secretKey` as sensitive and clear it after its final use. The implementation clears its intermediate derived seed, but the returned secret key remains in caller-managed memory. +### BIP-39 Mnemonic Sharing Helpers + +| Function | Description | Returns | +| --- | --- | --- | +| `splitMnemonic(mnemonic, options)` | Split valid BIP-39 mnemonic entropy into hex-encoded Shamir shares. | `Promise` | +| `combineMnemonic(shares)` | Reconstruct and validate a BIP-39 mnemonic from a threshold-sized share set. | `Promise` | + +#### `splitMnemonic(mnemonic, options)` + +Decode a valid 12-, 15-, 18-, 21-, or 24-word English BIP-39 mnemonic to entropy and split it into Shamir shares. + +```javascript title="Split A Mnemonic" +import { splitMnemonic } from '@tetherto/wdk-utils' + +const shares = await splitMnemonic(seedPhrase, { + shares: 5, + threshold: 3 +}) +``` + +- `shares` must be an integer from `2` through `255`. +- `threshold` must be an integer from `2` through `shares`. +- The returned shares are unencrypted lowercase hex strings. +- Invalid BIP-39 words, word counts, and checksums are rejected. +- The runtime must provide secure `crypto.getRandomValues`; React Native apps may need to load `react-native-get-random-values` before importing WDK Utils. + +#### `combineMnemonic(shares)` + +Reconstruct a mnemonic from enough shares produced by `splitMnemonic()`. + +```javascript title="Reconstruct A Mnemonic" +import { combineMnemonic } from '@tetherto/wdk-utils' + +const restored = await combineMnemonic([ + shares[0], + shares[2], + shares[4] +]) +``` + +The helper verifies the embedded four-byte integrity checksum and rejects malformed, corrupted, duplicate, mixed-length, or insufficient share sets. + + +Each share is sensitive recovery material. Store shares in separate trusted locations and never log or transmit them through analytics. The embedded checksum detects accidental corruption; it does not authenticate shares or protect against maliciously crafted input. + + ### BIP-21 Bitcoin Payment URI Helpers | Function | Description | Returns | diff --git a/content/docs/tools/wdk-utils/configuration.mdx b/content/docs/tools/wdk-utils/configuration.mdx index 2a6227a0..c012f964 100644 --- a/content/docs/tools/wdk-utils/configuration.mdx +++ b/content/docs/tools/wdk-utils/configuration.mdx @@ -1,6 +1,6 @@ --- title: WDK Utils Configuration -description: Install and import validation, encryption, key-derivation, and payment-request helpers from @tetherto/wdk-utils +description: Install and import validation, encryption, mnemonic-sharing, key-derivation, and payment-request helpers from @tetherto/wdk-utils --- This package does not have constructor options or runtime configuration. This page shows how to install `@tetherto/wdk-utils`, import the helpers you need, and understand the published runtime surface. @@ -13,6 +13,14 @@ You can install `@tetherto/wdk-utils` from npm: npm install @tetherto/wdk-utils ``` +React Native runtimes without `globalThis.crypto.getRandomValues` also need a secure random-values polyfill: + +```bash title="Install React Native Random Values" +npm install react-native-get-random-values +``` + +Load the polyfill before importing WDK Utils in the application entrypoint. + ## Import address validation helpers You can import only the validators your flow needs from the package entrypoint: @@ -25,6 +33,7 @@ import { validateLnurl, decodeLnurl, validateLightningAddress, + validateSolanaAddress, validateSparkAddress, validateTronAddress, validateUmaAddress, @@ -32,6 +41,17 @@ import { } from '@tetherto/wdk-utils' ``` +## Import mnemonic sharing helpers + +Split a valid English BIP-39 mnemonic into threshold shares and reconstruct it from enough shares: + +```javascript title="Import Mnemonic Sharing Helpers" +import { + combineMnemonic, + splitMnemonic +} from '@tetherto/wdk-utils' +``` + ## Import EIP-681 helpers You can detect and parse token transfer requests using the EIP-681 helpers: @@ -97,6 +117,10 @@ import { - `@tetherto/wdk-utils` exports plain functions. There is no client object to initialize. - The package publishes a default module entrypoint through `index.js` and a bare runtime entrypoint through `bare.js`. +- `validateSolanaAddress()` accepts base58-encoded 32-byte public keys, including off-curve program-derived addresses. Solana has no address checksum, so the helper cannot detect every mistyped address. +- `splitMnemonic()` accepts valid 12-, 15-, 18-, 21-, or 24-word English BIP-39 phrases. It returns hex-encoded Shamir shares and supports threshold schemes from 2-of-2 through 255-of-255. +- `combineMnemonic()` verifies an embedded integrity checksum before returning the reconstructed phrase. The checksum detects corruption but does not authenticate shares. +- `encrypt()` and `splitMnemonic()` require `globalThis.crypto.getRandomValues`. In React Native, load `react-native-get-random-values` before importing `@tetherto/wdk-utils` when the runtime does not provide secure random bytes. - `parseBip21Request()` accepts `bitcoin:` URIs with a validated Bitcoin address and optional `amount`, `label`, and `message` parameters. - `encodeBip21Request()` validates the Bitcoin address and amount before returning a `bitcoin:` URI. - `encrypt()` returns a versioned payload with hex-encoded `salt`, `iv`, `tag`, and `ciphertext` fields plus the scrypt cost parameters used for key derivation. @@ -120,12 +144,14 @@ You can validate common wallet inputs before handing them to a module: import { validateBitcoinAddress, validateLightningAddress, + validateSolanaAddress, validateTronAddress, validateUmaAddress } from '@tetherto/wdk-utils' const btc = validateBitcoinAddress('bc1qu9yqnhc6wjj6s62s9x0shnl5l2r7gq5cudm94r7mvwv0uw4s7acq0hn9g6') const lightning = validateLightningAddress('sprycomfort92@waletofsatoshi.com') +const solana = validateSolanaAddress('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') const tron = validateTronAddress('TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH') const uma = validateUmaAddress('$you@uma.money') ``` @@ -175,6 +201,26 @@ const encrypted = encrypt(seedPhrase, passphrase) const restoredSeedPhrase = decrypt(encrypted, passphrase) ``` +You can split a BIP-39 mnemonic into shares that require a threshold to recover: + +```javascript title="Split And Recover A Mnemonic" +import 'react-native-get-random-values' // React Native only; load before WDK Utils +import { combineMnemonic, splitMnemonic } from '@tetherto/wdk-utils' + +const shares = await splitMnemonic(seedPhrase, { + shares: 5, + threshold: 3 +}) + +const restored = await combineMnemonic([ + shares[0], + shares[2], + shares[4] +]) +``` + +Treat every unencrypted share as sensitive recovery material. Store shares separately and do not send them to logs, analytics, or untrusted services. + You can derive independent keys for application-specific purposes by using distinct domain labels: ```javascript title="Derive Domain-Separated Keys" diff --git a/content/docs/tools/wdk-utils/index.mdx b/content/docs/tools/wdk-utils/index.mdx index 16838d3e..ef8a1196 100644 --- a/content/docs/tools/wdk-utils/index.mdx +++ b/content/docs/tools/wdk-utils/index.mdx @@ -1,13 +1,14 @@ --- title: WDK Utils -description: Address validation, seed encryption, deterministic key derivation, and payment-request helpers for @tetherto/wdk-utils +description: Address validation, mnemonic sharing, seed encryption, deterministic key derivation, and payment-request helpers for @tetherto/wdk-utils --- -WDK Utils provides validation helpers for Bitcoin, EVM, Lightning, Spark, Tron, and UMA identifiers, passphrase-based seed encryption, deterministic HKDF-SHA256 and Ed25519 key derivation, BIP-21 Bitcoin payment URI helpers, BOLT11 invoice helpers, and EIP-681 request parsing for token transfer deep links. Powered by [`@tetherto/wdk-utils`](https://github.com/tetherto/wdk-utils). +WDK Utils provides validation helpers for Bitcoin, EVM, Lightning, Solana, Spark, Tron, and UMA identifiers, Shamir-based mnemonic sharing, passphrase-based seed encryption, deterministic HKDF-SHA256 and Ed25519 key derivation, BIP-21 Bitcoin payment URI helpers, BOLT11 invoice helpers, and EIP-681 request parsing for token transfer deep links. Powered by [`@tetherto/wdk-utils`](https://github.com/tetherto/wdk-utils). ## Features -- **Address validation helpers**: Validate Bitcoin, EVM, Lightning invoice, LNURL, Lightning address, Spark, Tron, and UMA inputs before you hand them to a wallet flow. +- **Address validation helpers**: Validate Bitcoin, EVM, Lightning invoice, LNURL, Lightning address, Solana, Spark, Tron, and UMA inputs before you hand them to a wallet flow. +- **Mnemonic sharing helpers**: Split an English BIP-39 mnemonic into threshold-based Shamir shares and combine enough shares to recover it. - **BIP-21 payment URI helpers**: Detect, parse, and encode `bitcoin:` payment URIs with optional amount, label, and message fields. - **Seed encryption helpers**: Encrypt and decrypt seed phrases or other strings with AES-256-GCM and scrypt-derived keys. - **Seed key derivation**: Derive domain-separated byte keys or deterministic Ed25519 keypairs from high-entropy seed bytes. @@ -21,6 +22,7 @@ WDK Utils provides validation helpers for Bitcoin, EVM, Lightning, Spark, Tron, ## Why this matters - Validate user input early and return machine-readable failure reasons before you attempt a transaction or resolution flow. +- Split a mnemonic across separate storage or recovery locations without making every share sufficient on its own. - Pair these helpers with the [P2P Address Book](/tools/p2p-address-book/) when a wallet stores user-selected destinations for later use. - Protect seed phrases or other local strings with passphrase-based encryption before storing them in app-managed persistence. - Derive separate application keys from one high-entropy seed without reusing the same key material across purposes. diff --git a/content/docs/tools/worklet-bundler/api-reference.mdx b/content/docs/tools/worklet-bundler/api-reference.mdx index 70b45312..45cba0be 100644 --- a/content/docs/tools/worklet-bundler/api-reference.mdx +++ b/content/docs/tools/worklet-bundler/api-reference.mdx @@ -48,10 +48,10 @@ interface WdkBundleConfig { } ``` -`modules` is generated only for HRPC in beta.5. JSON-RPC entry generation ignores that map. JSON-RPC otherwise defaults addon linking and ESM-to-CJS conversion to `true`; HRPC defaults both to `false`. +`modules` is generated only for HRPC in beta.6. JSON-RPC entry generation ignores that map. JSON-RPC otherwise defaults addon linking and ESM-to-CJS conversion to `true`; HRPC defaults both to `false`. -Beta.5 declares `output.types`, `options.minify`, and `options.sourceMaps`, but its bundle generator does not honor those fields. Declarations are always written to `./.wdk/index.d.ts`; `minify` and `sourceMaps` do not control `bare-pack` output. JSON-RPC ESM-to-CJS conversion minifies independently. +Beta.6 declares `output.types`, `options.minify`, and `options.sourceMaps`, but its bundle generator does not honor those fields. Declarations are always written to `./.wdk/index.d.ts`; `minify` and `sourceMaps` do not control `bare-pack` output. JSON-RPC ESM-to-CJS conversion minifies independently. #### `ResolvedConfig` @@ -174,6 +174,9 @@ Use `generateBundle()` when you want the same bundle workflow that powers the CL - `silent` - `skipTypes` - `skipGeneration` +- `deferOptionalPeers?` (`boolean`): When omitted or `true`, pass missing peers marked optional through `bare-pack --defer`. Set it to `false` to require those imports at build time. + +`deferOptionalPeers` is a per-run API option. It is not a `WdkBundleConfig` field and cannot be set in `wdk.config.js`. `GenerateBundleResult` contains: @@ -185,7 +188,7 @@ Use `generateBundle()` when you want the same bundle workflow that powers the CL - `error?` - `missingModule?` -In beta.5, `typesPath` can reflect a configured `output.types` path even though the declaration file is still written to `./.wdk/index.d.ts`. +In beta.6, `typesPath` can reflect a configured `output.types` path even though the declaration file is still written to `./.wdk/index.d.ts`. ```typescript title="Generate A Bundle Programmatically" import { generateBundle, loadConfig } from '@tetherto/wdk-worklet-bundler' @@ -206,7 +209,7 @@ In `beta.3`, the generated entrypoint suspends and resumes both the `bare-http1` #### `generateJsonRpcEntryPoint(config, outputDir)` -Generate the framed JSON-RPC entrypoint used by native hosts. In beta.5 this path includes wallet and protocol managers but not generic `modules`. +Generate the framed JSON-RPC entrypoint used by native hosts. In beta.6 this path includes wallet and protocol managers but not generic `modules`. #### `linkAddons(config, options?)` @@ -228,7 +231,7 @@ The published CLI exposes these commands through `wdk-worklet-bundler`: | Command | Description | Key Options | | --- | --- | --- | -| `generate` | Generate a WDK bundle from configuration. | `--config`, `--install`, `--keep-artifacts`, `--dry-run`, `--no-types`, `--source-only`, `--skip-generation`, `--transport`, `--link-addons`, `--skip-link-addons`, `--platforms`, `--no-esm-to-cjs`, `--verbose` | +| `generate` | Generate a WDK bundle from configuration. | `--config`, `--install`, `--keep-artifacts`, `--dry-run`, `--no-types`, `--source-only`, `--skip-generation`, `--transport`, `--link-addons`, `--skip-link-addons`, `--platforms`, `--no-esm-to-cjs`, `--no-defer-optional-peers`, `--verbose` | | `init` | Create a new `wdk.config.js` file. | `--yes` | | `validate` | Validate configuration without building. | `--config` | | `list-modules` | List available WDK modules. | `--json` | @@ -237,7 +240,7 @@ The published CLI exposes these commands through `wdk-worklet-bundler`: For the end-to-end config workflow and transport defaults, see the [Worklet Bundler configuration guide](/tools/worklet-bundler/configuration). -Beta.5 applies `--transport` after `loadConfig()` resolves output paths. The flag changes entrypoint generation but does not recompute the default bundle filename. Set `transport` in `wdk.config.js`, or configure `output.bundle` explicitly when using the flag. +Beta.6 applies `--transport` after `loadConfig()` resolves output paths. The flag changes entrypoint generation but does not recompute the default bundle filename. Set `transport` in `wdk.config.js`, or configure `output.bundle` explicitly when using the flag. *** diff --git a/content/docs/tools/worklet-bundler/configuration.mdx b/content/docs/tools/worklet-bundler/configuration.mdx index 0631a79f..9079e6ac 100644 --- a/content/docs/tools/worklet-bundler/configuration.mdx +++ b/content/docs/tools/worklet-bundler/configuration.mdx @@ -10,11 +10,11 @@ This page shows how to install `@tetherto/wdk-worklet-bundler` and its Pear runt Install the bundler as a development dependency and Pear Worklet as a runtime dependency in the host project: ```bash title="Install The Bundler And Runtime" -npm install @tetherto/pear-wrk-wdk@1.0.0-beta.9 -npm install --save-dev @tetherto/wdk-worklet-bundler@1.0.0-beta.5 +npm install @tetherto/pear-wrk-wdk@1.0.0-beta.10 +npm install --save-dev @tetherto/wdk-worklet-bundler@1.0.0-beta.6 ``` -Worklet Bundler beta.5 generates entrypoints that import Pear Worklet, but `generate --install` does not add that package. Beta.9 is also required for the generated JSON-RPC entrypoint and HRPC generic-module methods documented here. +Worklet Bundler beta.6 generates entrypoints that import Pear Worklet, but `generate --install` does not add that package. Pear Worklet beta.10 provides the JSON-RPC, HRPC generic-module, and Swidge protocol behavior documented here. ## Create `wdk.config.js` @@ -102,7 +102,7 @@ Use `modules` for named generic packages that expose a module factory. Each entr The factory receives `{ seed, config, capabilities, emit }` and can return the module instance or a promise for it. A module that needs the seed must consume it synchronously rather than retain it. The build-time name, such as `preferences`, must match the key in the host's runtime module config. -Beta.5 generic modules are included only in generated HRPC entrypoints. Do not configure `modules` with `transport: 'jsonrpc'`; the current schema accepts the field, but JSON-RPC generation ignores it. +Beta.6 generic modules are included only in generated HRPC entrypoints. Do not configure `modules` with `transport: 'jsonrpc'`; the current schema accepts the field, but JSON-RPC generation ignores it. ### `transport` (optional) @@ -118,7 +118,7 @@ Use `preloadModules` for native addons or other modules that must be required be Supported output fields are: - `bundle`: Bundle path. Defaults to `./.wdk-bundle/wdk-worklet.bundle.js` for HRPC and `./.wdk-bundle/wdk-worklet.bundle` for JSON-RPC. -- `types`: Declared in the beta.5 config type, but the generator does not honor a custom value and always writes declarations to `./.wdk/index.d.ts`. +- `types`: Declared in the beta.6 config type, but the generator does not honor a custom value and always writes declarations to `./.wdk/index.d.ts`. - `addons.ios`, `addons.macos`, `addons.android`: Platform addon directories. Defaults are `./ios-addons`, `./mac-addons`, and `./android-addons`. - `addonsYml`: BareKit Swift dependency file. Defaults to `./ios-addons/addons.yml` and is generated when iOS addons are linked. @@ -126,8 +126,8 @@ Supported output fields are: The published config type supports these build options: -- `minify` (`boolean`): Declared in the beta.5 config type but not read by the bundle path. JSON-RPC ESM-to-CJS conversion minifies independently of this field. -- `sourceMaps` (`boolean`): Declared in the beta.5 config type but not read by the bundle path, so it does not produce source maps. +- `minify` (`boolean`): Declared in the beta.6 config type but not read by the bundle path. JSON-RPC ESM-to-CJS conversion minifies independently of this field. +- `sourceMaps` (`boolean`): Declared in the beta.6 config type but not read by the bundle path, so it does not produce source maps. - `targets` (`string[]`): Override the default Bare build hosts. The shipped defaults cover iOS arm64 and simulator targets plus Android arm, arm64, ia32, and x64 hosts. - `linkAddons` (`boolean`): Link native addons with `bare-link`. Defaults to `true` for JSON-RPC and `false` for HRPC. - `platforms` (`('ios' | 'macos' | 'android')[]`): Addon platforms. Defaults to all three when addon linking is active. @@ -163,7 +163,7 @@ module.exports = { JSON-RPC generation defaults to a bundle without a `.js` suffix, ESM-to-CJS conversion, and addon linking. It links `bare-posix` with the other required Bare modules automatically. See the [Pear Worklet API reference](/tools/pear-wrk-wdk/api-reference#json-rpc-transport) for framing and supported methods. -## Validate and generate the bundle +## Validate dependencies Use `validate` to check the config and dependency resolution before you generate the bundle: @@ -171,15 +171,37 @@ Use `validate` to check the config and dependency resolution before you generate npx wdk-worklet-bundler validate ``` +## Handle peer dependencies + +Worklet Bundler beta.6 separates required peers from peers explicitly marked optional through `peerDependenciesMeta`: + +- During bundle generation, missing required peers are offered for installation. In a non-interactive environment without `--install`, the CLI prints the exact manual install command and continues; `bare-pack` can still fail if the bundle needs that peer. The peer scan is skipped by `--source-only`. +- Missing optional peers are not prompted for or installed. By default, the bundler passes each one to `bare-pack --defer`, so an unused optional feature does not block the build. +- Installed optional peers are bundled normally. A peer that is required anywhere else in the scanned dependency tree is treated as required and is not deferred. + +Use `--no-defer-optional-peers` when the app uses an optional feature and you want a missing peer to fail during the build: + +```bash title="Require Optional Peers At Build Time" +npx wdk-worklet-bundler generate --no-defer-optional-peers +``` + +For example, a bundle can build while a missing `@ledgerhq/ledger-bitcoin` peer is deferred, then fail at runtime if the app uses the corresponding Ledger feature. Install the peer explicitly, or use `--no-defer-optional-peers` to surface the missing import during bundling. + +This behavior is a per-run setting. `wdk.config.js` has no `deferOptionalPeers` field. Programmatic callers can set `GenerateBundleOptions.deferOptionalPeers` to `false`; omitting it or setting it to `true` keeps the default deferral behavior. + +If a core configured dependency is missing, a non-interactive `generate` run that will invoke `bare-pack` exits with status `1` and tells you to install it manually or rerun with `--install`. A `--source-only` run can still emit the generated source files. + +## Generate the bundle + Use `generate` to build the worklet artifact: ```bash title="Generate The Worklet Bundle" npx wdk-worklet-bundler generate --install ``` -`generate --install` can auto-install missing configured wallet, protocol, generic, and preload modules after the package manager is detected from the project root. In beta.5 it does not install the Pear Worklet runtime imported by generated entrypoints; install Pear explicitly as shown above. Use `--source-only` when you want the generated `.wdk/wdk-worklet.generated.js` entrypoint and related artifacts without running `bare-pack`. +`generate --install` can auto-install missing configured wallet, protocol, generic, and preload modules after the package manager is detected from the project root. In beta.6 it does not install the Pear Worklet runtime imported by generated entrypoints; install Pear explicitly as shown above. Use `--source-only` when you want the generated `.wdk/wdk-worklet.generated.js` entrypoint and related artifacts without running `bare-pack`. -The `generate` command also accepts `--transport hrpc|jsonrpc`, `--link-addons`, `--skip-link-addons`, `--platforms ios,macos,android`, and `--no-esm-to-cjs`. Set `transport` in `wdk.config.js` when you rely on its transport-specific default bundle path. In beta.5, `--transport` changes the generated transport after output paths have already been resolved, so it does not switch `.js` HRPC output to the extensionless JSON-RPC default, or vice versa. If you use the flag, set `output.bundle` explicitly to the intended path. +The `generate` command also accepts `--transport hrpc|jsonrpc`, `--link-addons`, `--skip-link-addons`, `--platforms ios,macos,android`, `--no-esm-to-cjs`, and `--no-defer-optional-peers`. Set `transport` in `wdk.config.js` when you rely on its transport-specific default bundle path. In beta.6, `--transport` changes the generated transport after output paths have already been resolved, so it does not switch `.js` HRPC output to the extensionless JSON-RPC default, or vice versa. If you use the flag, set `output.bundle` explicitly to the intended path. ## Suspend and resume behavior diff --git a/content/docs/tools/worklet-bundler/index.mdx b/content/docs/tools/worklet-bundler/index.mdx index af15abf2..0d481e4f 100644 --- a/content/docs/tools/worklet-bundler/index.mdx +++ b/content/docs/tools/worklet-bundler/index.mdx @@ -12,11 +12,12 @@ Worklet Bundler packages selected WDK wallet, protocol, and optional generic mod - **Generated worklet artifacts**: Produce a Bare bundle, a stable `./.wdk` host import, and generated TypeScript types. - **Native addon outputs**: Link iOS, macOS, and Android addons and generate `addons.yml` for JSON-RPC/native integrations. - **Dependency validation helpers**: Validate configured modules, detect the active package manager, and generate install or uninstall commands when dependencies are missing. +- **Optional peer deferral**: Defer missing peers marked optional by their package metadata, or require them at build time with `--no-defer-optional-peers`. - **CLI workflow**: Use `init`, `validate`, `generate`, `list-modules`, and `clean` without building your own wrapper. - **Bare suspend and resume handling**: Generated HRPC entrypoints suspend and resume both the `bare-http1` and `bare-https` global agents when the Bare thread lifecycle changes. -`v1.0.0-beta.5` is the first published artifact that contains the built CLI and API files for the JSON-RPC, generic-module, and addon-linking surface introduced in beta.4. It also links `bare-posix` automatically for supported native platforms. +`v1.0.0-beta.6` includes the built CLI and API files for the JSON-RPC, generic-module, and addon-linking surface published in beta.5. It also defers missing optional peers during bundling by default and links `bare-posix` automatically for supported native platforms. ## Why this matters @@ -26,7 +27,7 @@ Worklet Bundler packages selected WDK wallet, protocol, and optional generic mod - Transport-specific defaults produce a JavaScript HRPC bundle for React Native Core or native addon artifacts for JSON-RPC hosts. -Generic `modules` are generated only for HRPC in beta.5. A JSON-RPC config can currently accept a `modules` field but silently omits those modules from the generated entrypoint. +Generic `modules` are generated only for HRPC in beta.6. A JSON-RPC config can currently accept a `modules` field but silently omits those modules from the generated entrypoint. diff --git a/skills/wdk/SKILL.md b/skills/wdk/SKILL.md index a4e36e44..92c732cb 100644 --- a/skills/wdk/SKILL.md +++ b/skills/wdk/SKILL.md @@ -1,6 +1,6 @@ --- name: wdk -description: Tether Wallet Development Kit (WDK) for building non-custodial multi-chain wallets. Use when working with @tetherto/wdk, wallet modules (wdk-wallet-btc, wdk-wallet-evm, wdk-wallet-evm-erc-4337, wdk-wallet-solana, wdk-wallet-spark, wdk-wallet-ton, wdk-wallet-tron, ton-gasless, tron-gasfree), and protocol modules including swidge, swap (wdk-protocol-swap-velora-evm), bridge (wdk-protocol-bridge-usdt0-evm), lending (wdk-protocol-lending-aave-evm), and fiat (wdk-protocol-fiat-moonpay). Covers wallet creation, transactions, token transfers, swidge asset routes, DEX swaps, cross-chain bridges, DeFi lending/borrowing, and fiat on/off ramps. +description: Tether Wallet Development Kit (WDK) for building non-custodial multi-chain wallets. Use when working with @tetherto/wdk, wallet modules (wdk-wallet-aptos, wdk-wallet-btc, wdk-wallet-evm, wdk-wallet-evm-erc-4337, wdk-wallet-solana, wdk-wallet-spark, wdk-wallet-ton, wdk-wallet-tron, ton-gasless, tron-gasfree), Smart Deposit Address provider interfaces, and protocol modules including swidge, swap (wdk-protocol-swap-velora-evm), bridge (wdk-protocol-bridge-usdt0-evm), lending (wdk-protocol-lending-aave-evm), and fiat (wdk-protocol-fiat-moonpay). Covers wallet creation, transactions, token transfers, deposit routing, swidge asset routes, DEX swaps, cross-chain bridges, DeFi lending/borrowing, and fiat on/off ramps. --- # Tether WDK @@ -28,12 +28,14 @@ This skill is organized into reference files for chain-specific and protocol-spe |------|---------| | `references/chains.md` | Chain IDs, native tokens, units, decimals, public RPC endpoints, dust thresholds, address formats, EIP-3009 support, bridge routes | | `references/deployments.md` | USDT native addresses, USDT0 omnichain addresses | +| `references/wallet-aptos.md` | Aptos: SLIP-0010 Ed25519, APT, fungible assets, octas, fullnode REST | | `references/wallet-btc.md` | Bitcoin wallet: BIP-84, Electrum, PSBT, fee rates | | `references/wallet-evm.md` | EVM + ERC-4337: BIP-44, EIP-1559, ERC20, batch txs, paymaster | | `references/wallet-solana.md` | Solana: Ed25519, SPL tokens, lamports | | `references/wallet-spark.md` | Spark: Lightning, key tree, deposits, withdrawals | | `references/wallet-ton.md` | TON + TON Gasless: Jettons, nanotons, paymaster | | `references/wallet-tron.md` | TRON + TRON Gasfree: TRC20, energy/bandwidth, gasFreeProvider | +| `references/protocol-sda.md` | Smart Deposit Address: provider interface, route discovery, address lifecycle, transfer status | | `references/protocol-swidge.md` | Swidge: preferred route interface for new swap, bridge, and combined providers | | `references/protocol-swap.md` | Velora EVM swap protocol | | `references/protocol-bridge.md` | USDT0 cross-chain bridge via LayerZero | @@ -47,6 +49,7 @@ When a task targets a specific chain or protocol, read the relevant reference fi ``` @tetherto/wdk # Orchestrator - registers wallets + protocols ├── @tetherto/wdk-wallet # Base classes (WalletManager, IWalletAccount) + │ ├── wdk-wallet-aptos # Aptos (SLIP-0010 Ed25519) │ ├── wdk-wallet-btc # Bitcoin (BIP-84, SegWit) │ ├── wdk-wallet-evm # Ethereum & EVM chains │ ├── wdk-wallet-evm-erc-4337 # EVM with Account Abstraction @@ -55,7 +58,8 @@ When a task targets a specific chain or protocol, read the relevant reference fi │ ├── wdk-wallet-ton # TON │ ├── wdk-wallet-ton-gasless # TON gasless │ ├── wdk-wallet-tron # TRON - │ └── wdk-wallet-tron-gasfree # TRON gas-free + │ ├── wdk-wallet-tron-gasfree # TRON gas-free + │ └── protocols/SdaProtocol # Smart Deposit Address provider interface; no concrete provider └── Protocol Modules ├── swidge protocol interface # Preferred route interface for new swap, bridge, or combined providers ├── wdk-protocol-swap-velora-evm # DEX swaps on EVM @@ -79,6 +83,7 @@ All packages are under the `@tetherto` scope. **Always** `npm view version | Package | npm | |---------|-----| +| `@tetherto/wdk-wallet-aptos` | [npmjs.com/package/@tetherto/wdk-wallet-aptos](https://www.npmjs.com/package/@tetherto/wdk-wallet-aptos) | | `@tetherto/wdk-wallet-btc` | [npmjs.com/package/@tetherto/wdk-wallet-btc](https://www.npmjs.com/package/@tetherto/wdk-wallet-btc) | | `@tetherto/wdk-wallet-evm` | [npmjs.com/package/@tetherto/wdk-wallet-evm](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm) | | `@tetherto/wdk-wallet-evm-erc-4337` | [npmjs.com/package/@tetherto/wdk-wallet-evm-erc-4337](https://www.npmjs.com/package/@tetherto/wdk-wallet-evm-erc-4337) | @@ -170,12 +175,13 @@ Before making any transaction, first use the corresponding quote method to estim #### Common wallet write methods (deduplicated) -- **`sendTransaction`** — Sends native tokens. Present on: btc, evm, evm-erc-4337, solana, spark, ton, tron. **Throws** on ton-gasless and tron-gasfree. -- **`transfer`** — Transfers tokens (ERC20/SPL/Jetton/TRC20). Present on: evm, evm-erc-4337, solana, spark, ton, ton-gasless, tron, tron-gasfree. **Throws** on btc. +- **`sendTransaction`** — Sends native tokens. Present on: aptos, btc, evm, evm-erc-4337, solana, spark, ton, tron. **Throws** on ton-gasless and tron-gasfree. +- **`transfer`** — Transfers tokens (Aptos fungible assets/ERC20/SPL/Jetton/TRC20). Present on: aptos, evm, evm-erc-4337, solana, spark, ton, ton-gasless, tron, tron-gasfree. **Throws** on btc. - **`sign`** — Signs an arbitrary message with the private key. Present on **all** wallet modules. Can authorize off-chain actions — treat as dangerous. #### Module-specific warnings +- **wallet-aptos**: `signTransaction()` is provider-backed and signs native APT transfers only; it is not an offline operation. `transferMaxFee` protects fungible-asset `transfer()` only, so enforce a separate application limit for native sends and signing. - **wallet-evm**: `sendTransaction` accepts a `data` field (arbitrary hex calldata). Can execute **any** contract function — `approve()`, `transferFrom()`, `setApprovalForAll()`, etc. Extra scrutiny for non-empty `data`. - **wallet-evm-erc-4337**: Same `data` risk. Also accepts an **array** of transactions for batch execution — multiple operations in one call. - **wallet-ton**: `sendTransaction` accepts a `payload` field for arbitrary contract calls. @@ -188,9 +194,10 @@ All require human confirmation: `claimDeposit`, `claimStaticDeposit`, `refundSta - **Swidge**: `getSupportedChains` and `getSupportedTokens` discover available routes; `swidge` executes a swap-only, bridge-only, or combined route. Quote first with `quoteSwidge` and require human confirmation before execution. - **Swap**: `swap` (velora-evm) — quote first and require human confirmation. May internally approve + reset allowance. -- **Bridge**: `bridge` (usdt0-evm) — quote first and require human confirmation. Requires prior token approval for the source-chain OFT or bridge spender. +- **Bridge**: `bridge` (usdt0-evm) — quote first and require human confirmation. Standard EVM accounts require prior token approval for the source-chain spender; supported ERC-4337 helper routes bundle approval and bridging into one UserOperation. - **Lending (Aave)**: `supply`, `withdraw`, `borrow`, `repay`, `setUseReserveAsCollateral`, `setUserEMode` -- **Fiat (MoonPay)**: `buy`, `sell` (generate signed widget URLs) +- **Fiat (MoonPay)**: `buy`, `sell` (generate widget URLs; signed only when `signUrl` is configured) +- **Smart Deposit Address**: `getSupportedRoutes`, `quoteDeposit`, lookups, and transfer-history methods are reads. `createDepositAddress`, `renewDepositAddress`, `recoverDepositAddress`, and `disableDepositAddress` can change provider-side lifecycle state and require human confirmation. Address creation does not itself move funds; any later deposit is a separate wallet transaction. ### Pre-Transaction Validation diff --git a/skills/wdk/references/protocol-bridge.md b/skills/wdk/references/protocol-bridge.md index c09dc2c0..b0a0d623 100644 --- a/skills/wdk/references/protocol-bridge.md +++ b/skills/wdk/references/protocol-bridge.md @@ -23,55 +23,98 @@ npm install @tetherto/wdk-protocol-bridge-usdt0-evm import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' ``` -## Quick Reference +## Choose the account flow + +| Account | Approval behavior | Submission | +|---|---|---| +| Standard `WalletAccountEvm` | Call `account.approve()` for the source-chain OFT or bridge spender before `bridge()`. | Approval and bridge are separate EVM transactions. | +| `WalletAccountEvmErc4337` | Do not call `account.approve()` separately. The protocol builds an approval to the transaction-value helper. | Approval and helper bridge call are submitted in one UserOperation. | + +ERC-4337 helper bridging is available from Ethereum, Arbitrum, Plasma, and Polygon. Other supported EVM source chains require a standard account. + +## Standard account quick reference ```javascript const bridge = new Usdt0ProtocolEvm(evmAccount, { - bridgeMaxFee: 1000000000000000n // Optional max fee in wei + bridgeMaxFee: 1000000000000000n }) -// Quote first -const quote = await bridge.quoteBridge({ +const options = { targetChain: 'arbitrum', recipient: '0x...', - token: '0x...', // USDT0 token address on source chain - amount: 1000000n // 1 USDT0 (6 decimals) -}) + token: '0x...', + amount: 1000000n, + oftContractAddress: process.env.USDT0_OFT_ADDRESS +} + +const quote = await bridge.quoteBridge(options) -// Then bridge (requires human confirmation) await evmAccount.approve({ - token: '0x...', // USDT0 token address on source chain - spender: '0x...', // OFT or bridge spender address - amount: 1000000n + token: options.token, + spender: options.oftContractAddress, + amount: options.amount }) -await bridge.bridge({ - targetChain: 'arbitrum', - recipient: '0x...', - token: '0x...', - amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender -}) +const result = await bridge.bridge(options) ``` -## Supported Routes +For a standard account, `fee` and `bridgeFee` are in source-chain native base units. `bridge()` rejects when `fee + bridgeFee` is equal to or greater than `bridgeMaxFee`. -**Source chains** (EVM only): ethereum, arbitrum, polygon, berachain, ink -**Destination chains**: ethereum, arbitrum, polygon, berachain, ink, ton, tron +## ERC-4337 quick reference -Works with both wallet-evm and wallet-evm-erc-4337 accounts. +```javascript +const bridge = new Usdt0ProtocolEvm(erc4337Account) + +const result = await bridge.bridge( + { + targetChain: 'polygon', + recipient: '0x...', + token: process.env.USDT_SOURCE_TOKEN_ADDRESS, + amount: 1000000n, + oftContractAddress: process.env.USDT0_OFT_ADDRESS + }, + { + paymasterToken: { + address: process.env.PAYMASTER_TOKEN_ADDRESS + } + } +) +``` + +The returned hash identifies the single UserOperation containing the approval and helper bridge call. + +### ERC-4337 fee-unit limitation + +In `1.0.0-beta.7`: + +- `bridgeFee` is in bridged-token base units. +- `fee` is in source-native base units for native gas, paymaster-token base units for token-paid gas, or zero for sponsored gas. +- The protocol numerically adds `fee + bridgeFee` when enforcing `bridgeMaxFee`. + +Do not interpret that sum as one currency or configure an ERC-4337 cap until the selected payment mode is known to produce compatible units. Equality with the cap is rejected. -## How It Works +## Supported routes -- Uses **LayerZero OFT** (Omnichain Fungible Token) protocol for cross-chain messaging -- Requires prior token approval for the source-chain OFT or bridge spender -- Bridge fees include LayerZero messaging fees (paid in native token of source chain) -- Token addresses differ per chain — see `references/deployments.md` for the full table +**EVM source and destination keys:** `ethereum`, `arbitrum`, `optimism`, `polygon`, `berachain`, `ink`, `plasma`, `conflux`, `corn`, `avalanche`, `celo`, `flare`, `hyperevm`, `mantle`, `megaeth`, `monad`, `morph`, `rootstock`, `sei`, `stable`, `unichain`, `xlayer` -## Common Interface +**Additional destination keys:** `solana`, `ton`, `tron` + +For Solana, TON, and TRON targets, beta.7 skips a source chain's ordinary `oftContract` during auto-resolution. The bundled source-side candidates are: + +- USD₮0 legacy mesh: Ethereum, Arbitrum, Celo. +- XAU₮0 OFT: Ethereum, Arbitrum, Avalanche, Celo, HyperEVM, Ink, Monad, Plasma, Polygon, Stable. + +These are candidate source contracts, not a Cartesian route guarantee. A contract must have the selected destination peer configured on-chain. `getSupportedChains()` and `getSupportedTokens()` expose static configuration and do not prove a source-token-destination pair. Verify the exact route with `quoteBridge()`, or supply a verified route-specific `oftContractAddress` and optional `dstEid`. + +Route availability also depends on a matching USD₮0 or XAU₮0 deployment. Verify current contract addresses against the USDT0 deployment documentation. + +## Common interface | Method | Description | |--------|-------------| -| `bridge({targetChain, recipient, token, amount})` | Execute bridge (⚠️ write method) | -| `quoteBridge({targetChain, recipient, token, amount})` | Get bridge fee estimate | -| `getConfig()` | Get protocol configuration | +| `bridge({ targetChain, recipient, token, amount, oftContractAddress?, dstEid? }, config?)` | Execute a bridge operation. Requires human confirmation before the write. | +| `quoteBridge({ targetChain, recipient, token, amount, oftContractAddress?, dstEid? }, config?)` | Estimate account and bridge fee fields without submitting. | +| `getSupportedChains()` | Return the configured chain descriptors. | +| `getSupportedTokens(options?)` | Return configured USD₮0 or XAU₮0 token descriptors, optionally filtered by chain or token symbol. | + +Validate the destination address for its target ecosystem, verify route contracts and endpoint overrides, and keep approvals bounded to the intended standard-account transfer. diff --git a/skills/wdk/references/protocol-fiat.md b/skills/wdk/references/protocol-fiat.md index d3736dc8..af6cf78e 100644 --- a/skills/wdk/references/protocol-fiat.md +++ b/skills/wdk/references/protocol-fiat.md @@ -24,18 +24,17 @@ import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay' ```javascript const moonpay = new MoonPayProtocol(account, { - apiKey: 'pk_...', // MoonPay publishable key - secretKey: 'sk_...' // MoonPay secret key (for URL signing) + apiKey: 'pk_...' // MoonPay publishable key }) -// Buy crypto with fiat (generates signed widget URL) +// Buy crypto with fiat (generates an unsigned widget URL unless signUrl is configured) const { buyUrl } = await moonpay.buy({ cryptoAsset: 'eth', fiatCurrency: 'usd', fiatAmount: 10000n }) -// Sell crypto for fiat (generates signed widget URL) +// Sell crypto for fiat (generates an unsigned widget URL unless signUrl is configured) const { sellUrl } = await moonpay.sell({ cryptoAsset: 'eth', fiatCurrency: 'usd', @@ -45,24 +44,36 @@ const { sellUrl } = await moonpay.sell({ ## How It Works -- `buy()` and `sell()` generate **signed MoonPay widget URLs** — they do not directly move funds +- `buy()` and `sell()` generate MoonPay widget URLs — they do not directly move funds - The URL is presented to the user who completes the transaction in MoonPay's widget - The wallet address from the connected account is automatically included -- URLs are signed with `secretKey` for security +- Configure `signUrl` to send URLs to an authenticated backend that keeps the MoonPay signing secret server-side ## Configuration ```javascript const moonpay = new MoonPayProtocol(account, { apiKey: 'pk_...', // Required: MoonPay publishable API key - secretKey: 'sk_...' // Required: MoonPay secret key for URL signing + 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 + } }) ``` +The backend must authenticate the caller and validate or reconstruct the MoonPay URL before signing. Omit `signUrl` when unsigned widget URLs are acceptable. + ## Methods | Method | Description | |--------|-------------| -| `buy({cryptoAsset, fiatCurrency, fiatAmount})` | Generate signed buy URL (⚠️ write method) | -| `sell({cryptoAsset, fiatCurrency, cryptoAmount})` | Generate signed sell URL (⚠️ write method) | -| `getConfig()` | Get protocol configuration | +| `buy({cryptoAsset, fiatCurrency, fiatAmount})` | Generate a buy URL (⚠️ write method) | +| `sell({cryptoAsset, fiatCurrency, cryptoAmount})` | Generate a sell URL (⚠️ write method) | +| `getSupportedCryptoAssets()` | List currently supported crypto assets | +| `getSupportedFiatCurrencies()` | List currently supported fiat currencies | +| `getSupportedCountries()` | List currently supported countries | diff --git a/skills/wdk/references/protocol-sda.md b/skills/wdk/references/protocol-sda.md new file mode 100644 index 00000000..805ed3c1 --- /dev/null +++ b/skills/wdk/references/protocol-sda.md @@ -0,0 +1,65 @@ +# Smart Deposit Address (SDA) Provider Interface + +## Links + +| Resource | URL | +|----------|-----| +| **npm — base package** | https://www.npmjs.com/package/@tetherto/wdk-wallet | +| **GitHub** | https://github.com/tetherto/wdk-wallet | +| **Docs** | https://docs.wdk.tether.io/sdk/sda-modules | + +## Release Boundary + +- `SdaProtocol`, `ISdaProtocol`, and the SDA types are exported from `@tetherto/wdk-wallet/protocols`. +- `@tetherto/wdk-wallet` supplies the interface and abstract base class, not a concrete SDA provider. +- The currently released `@tetherto/wdk` orchestrator has no SDA-specific protocol bucket, getter, or documented registration flow. Use a concrete provider directly according to its documentation. + +```typescript +import { + SdaProtocol, + type ISdaProtocol, + type SdaRoute, + type SdaDepositAddress, + type SdaTransfer +} from '@tetherto/wdk-wallet/protocols' +``` + +## Required Provider Methods + +- `getSupportedRoutes(options?)` discovers supported route combinations. +- `createDepositAddress(options)` creates one descriptor per distinct address and activates it when the provider has an activation lifecycle. + +All other base methods throw `UnsupportedOperationError` until a provider implements them: + +- `quoteDeposit` +- `deriveDepositAddress` +- `getDepositAddress` +- `renewDepositAddress` +- `getTransfers` +- `getTransfersByRecipient` +- `getTransfer` +- `recoverDepositAddress` +- `disableDepositAddress` + +## Key Type Facts + +- `Blockchain` is `string | number`. +- Route discovery filters can include `sourceChain`, `sourceToken`, `destinationChain`, and `outputAsset`. +- A route contains `sourceChains`, `inputTokens`, and `destinationChain`; output asset, limits, reuse, and estimated duration are optional. +- Quote input and output amounts are base-unit values. Quote fees are itemized and the rate is a string. Quotes are non-binding. +- `createDepositAddress()` accepts `sourceChains` and `destinationChain`, plus optional `outputAsset` and `destinationAddress`. +- If `destinationAddress` is omitted, the bound account address is used. Without a bound account, the contract throws `ValueError`. +- `deriveDepositAddress()` is client-side only. It does not activate the address or cause provider monitoring. +- `SdaTransfer` guarantees only `{ id, status }`. Do not assume portable amount, hash, or timestamp fields. +- Status values are `pending`, `detected`, `processing`, `completed`, `failed`, `refund-pending`, `refunded`, and `expired`. +- Transfer-history options can filter by `sourceChain` or status and paginate with `limit` and `skip`. +- Recovery accepts either `{ id }` or `{ address, sourceChain? }`. Its result status is `reindexed`, `pending`, or `failed`, with optional address, SDA id, transfer, and message fields. + +## Lifecycle and Safety + +- Validate every returned descriptor against the intended route and recipient before showing or funding the address. +- Address creation does not move funds. +- Treat `UnsupportedOperationError` as an unsupported provider capability. +- Treat `NoSuchElementError` as a missing address or transfer, not a retryable transport error. +- `createDepositAddress`, `renewDepositAddress`, `recoverDepositAddress`, and `disableDepositAddress` can alter provider-side state and require explicit user confirmation before invocation. +- Read-only discovery, quoting, lookup, and history methods do not move funds. A later deposit is a separate wallet action and requires the normal transaction confirmation checks. diff --git a/skills/wdk/references/wallet-aptos.md b/skills/wdk/references/wallet-aptos.md new file mode 100644 index 00000000..684db838 --- /dev/null +++ b/skills/wdk/references/wallet-aptos.md @@ -0,0 +1,60 @@ +# wallet-aptos — Aptos + +## Links + +| Resource | URL | +|----------|-----| +| **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-aptos | +| **GitHub** | https://github.com/tetherto/wdk-wallet-aptos | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-aptos/api-reference | + +## Package + +```bash +npm install @tetherto/wdk-wallet-aptos +``` + +```javascript +import WalletManagerAptos from '@tetherto/wdk-wallet-aptos' +``` + +## Key Details + +- **Derivation**: SLIP-0010 Ed25519 at `m/44'/637'/{index}'/0'/0'`; every path segment is hardened. +- **Native unit**: APT balances and fees are returned in octas. +- **Token model**: `getTokenBalance()` and `transfer()` use Aptos fungible-asset metadata addresses. +- **Native transfers**: `quoteSendTransaction()`, `sendTransaction()`, and `signTransaction()` accept `{ to, value }`. +- **Fungible-asset transfers**: `quoteTransfer()` and `transfer()` accept `{ token, recipient, amount }`. +- `signTransaction()` simulates and signs a native APT transfer without broadcasting. It still needs a configured fullnode and is not an offline operation. +- `transferMaxFee` applies only to fungible-asset `transfer()` and rejects a fee equal to or greater than the cap. It does not protect native send or sign operations. +- The package root does not export `AptosTransactionReceipt`. Receipt `type` is declared as `string`; observed values include `pending_transaction` and `user_transaction`. + +## Configuration + +```javascript +const wallet = new WalletManagerAptos(seedPhrase, { + provider: 'https://fullnode.mainnet.aptoslabs.com/v1', + txnExpirationSecs: 60, + transferMaxFee: 100000n +}) +``` + +- `provider` accepts one fullnode REST URL or an ordered URL array for failover. +- `retries` defaults to `3` for provider arrays. +- `chainId` is optional and is fetched from ledger info when omitted. A supplied value is not checked against ledger info before signing, so keep it consistent with every configured provider. +- Mainnet uses chain ID `1`; testnet uses chain ID `2`. + +## Read-Only Accounts + +An address-only `WalletAccountReadOnlyAptos` can read balances and receipts. Fee quotes and Ed25519 message verification also require the matching 32-byte public key. Prefer `account.toReadOnlyAccount()` when starting from a writable account because it carries that public key forward. + +## Transaction Safety + +- Require explicit human confirmation before `sendTransaction()`, `transfer()`, or `signTransaction()`. +- Quote native APT transfers and enforce an application-level fee limit before sending or signing because `transferMaxFee` does not apply. +- Validate the destination as an Aptos address and treat the fungible-asset metadata address as a separate trust boundary. +- A non-null receipt can still be pending. Wait for an observed `type === 'user_transaction'`, then inspect `success` and `vm_status`. +- Treat `keyPair` and seed material as sensitive, never log signed transactions, and call `dispose()` when secret material is no longer needed. diff --git a/skills/wdk/references/wallet-evm.md b/skills/wdk/references/wallet-evm.md index f589abdd..a403beac 100644 --- a/skills/wdk/references/wallet-evm.md +++ b/skills/wdk/references/wallet-evm.md @@ -1,4 +1,4 @@ -# wallet-evm & wallet-evm-erc-4337 — EVM Chains +# wallet-evm, wallet-evm-erc-4337 & wallet-evm-7702-gasless — EVM Chains ## Links — wallet-evm @@ -6,10 +6,10 @@ |----------|-----| | **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-evm | | **GitHub** | https://github.com/tetherto/wdk-wallet-evm | -| **Docs — Overview** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm | -| **Docs — Usage** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm/usage | -| **Docs — Configuration** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm/configuration | -| **Docs — API Reference** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm/api-reference | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/api-reference | ## Links — wallet-evm-erc-4337 @@ -17,21 +17,34 @@ |----------|-----| | **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-evm-erc-4337 | | **GitHub** | https://github.com/tetherto/wdk-wallet-evm-erc-4337 | -| **Docs — Overview** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm-erc-4337 | -| **Docs — Usage** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/usage | -| **Docs — Configuration** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/configuration | -| **Docs — API Reference** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/api-reference | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337 | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-erc-4337/api-reference | + +## Links — wallet-evm-7702-gasless + +| Resource | URL | +|----------|-----| +| **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-evm-7702-gasless | +| **GitHub** | https://github.com/tetherto/wdk-wallet-evm-7702-gasless | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm-7702-gasless/api-reference | ## Packages ```bash npm install @tetherto/wdk-wallet-evm npm install @tetherto/wdk-wallet-evm-erc-4337 # for Account Abstraction +npm install @tetherto/wdk-wallet-evm-7702-gasless # for EIP-7702 gasless EOAs ``` ```javascript import WalletManagerEvm from '@tetherto/wdk-wallet-evm' import WalletManagerEvmErc4337 from '@tetherto/wdk-wallet-evm-erc-4337' +import WalletManagerEvm7702Gasless from '@tetherto/wdk-wallet-evm-7702-gasless' ``` ## Key Details — wallet-evm @@ -42,6 +55,8 @@ import WalletManagerEvmErc4337 from '@tetherto/wdk-wallet-evm-erc-4337' - **Supports**: ERC20 via `transfer()`, arbitrary calldata via `sendTransaction({data})` - ⚠️ **Ethereum USDT** uses non-standard ERC20 (no bool return on `transfer()`). Use SafeERC20 in custom contracts. - ⚠️ `sendTransaction` accepts a `data` field (arbitrary hex calldata) — can execute **any** contract function. Extra scrutiny for non-empty `data`. +- ⚠️ In wallet-evm `1.0.0-beta.16`, do not pass a serialized signed transaction to `sendTransaction()`. The declared string input is not broadcast as supplied and can produce a different populated transaction. Use a separate relay or provider for signed raw bytes. +- `quoteSendTransaction(serializedTx)` is non-broadcasting, but calculates with current provider fee data rather than reproducing the serialized fee settings. ## Configuration — wallet-evm @@ -59,6 +74,10 @@ const wallet = new WalletManagerEvm(seedPhrase, { - Fees paid in **paymaster token** (e.g., USDT) instead of native ETH - `getPaymasterTokenBalance()` for fee balance - **Batch transactions**: `sendTransaction([tx1, tx2])` — multiple operations in one call +- `signTransaction(tx)` signs one `UserOperationV7`; the signed result can be quoted and submitted through `sendTransaction()`. +- Signed UserOperations preserve their nonce and fee-mode configuration. Submit promptly through the same account and do not mutate them. +- Signed UserOperation submission does not reapply `transactionMaxFee`. In paymaster-token mode, a signed-operation quote is a buffered native-gas ceiling in wei, not a token-denominated charge. +- Quote-cache keys omit fee-mode configuration. Use the same mode, paymaster token, paymaster endpoints, and sponsorship policy for a quote and its matching send, sign, or transfer. - Same `data` risk as wallet-evm, plus batch execution risk ## Configuration — wallet-evm-erc-4337 @@ -70,10 +89,18 @@ const wallet = new WalletManagerEvmErc4337(seedPhrase, { bundlerUrl: 'https://api.candide.dev/public/v3/42161', paymasterUrl: 'https://api.candide.dev/public/v3/42161', paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba', - entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', paymasterToken: { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' // USDT on Arbitrum }, transferMaxFee: 5000000 // in paymaster token units }) ``` + +## Key Details — wallet-evm-7702-gasless + +- Uses EIP-7702 delegation and ERC-4337 UserOperations while retaining the EOA address. +- Supports sponsored mode and paymaster-token mode. +- Owned account paymaster-token quotes are cached for up to 2 minutes. +- Before reusing a cached UserOperation, the account validates its EntryPoint nonce and re-quotes if the nonce moved. +- A send that needs a fresh EIP-7702 authorization rebuilds the UserOperation instead of reusing the cached one. +- Quote-cache keys omit fee-mode configuration. Use the same mode, paymaster token, paymaster endpoints, and sponsorship policy for a quote and its matching send or transfer. diff --git a/skills/wdk/references/wallet-solana.md b/skills/wdk/references/wallet-solana.md index 190950c3..43dfd606 100644 --- a/skills/wdk/references/wallet-solana.md +++ b/skills/wdk/references/wallet-solana.md @@ -6,10 +6,10 @@ |----------|-----| | **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-solana | | **GitHub** | https://github.com/tetherto/wdk-wallet-solana | -| **Docs — Overview** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-solana | -| **Docs — Usage** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-solana/usage | -| **Docs — Configuration** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-solana/configuration | -| **Docs — API Reference** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-solana/api-reference | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-solana/api-reference | ## Package @@ -29,13 +29,17 @@ import WalletManagerSolana from '@tetherto/wdk-wallet-solana' - **Token standard**: SPL tokens via `transfer()` - **Rent-exempt minimum**: ~890,880 lamports for new accounts - **USDT mint**: `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` +- `signTransaction()` returns a `FullySignedTransaction` from `@solana/transactions`; WDK does not re-export that type. +- `quoteSendTransaction()` and `sendTransaction()` accept that signed value. Send broadcasts the exact wire bytes and rechecks `transactionMaxFee`. +- ⚠️ Signing seals the recent blockhash or durable nonce. WDK does not refresh or re-sign a supplied signed transaction; submit it before its lifetime becomes invalid. ## Configuration ```javascript const wallet = new WalletManagerSolana(seedPhrase, { - rpcUrl: 'https://api.mainnet-beta.solana.com', - transferMaxFee: 10000000 // Optional max fee in lamports + provider: 'https://api.mainnet-beta.solana.com', + transferMaxFee: 10000000n, // Optional max SPL transfer fee in lamports + transactionMaxFee: 10000000n // Optional max send/sign fee in lamports }) ``` diff --git a/skills/wdk/references/wallet-ton.md b/skills/wdk/references/wallet-ton.md index a620389a..94566cc3 100644 --- a/skills/wdk/references/wallet-ton.md +++ b/skills/wdk/references/wallet-ton.md @@ -43,6 +43,10 @@ import WalletManagerTonGasless from '@tetherto/wdk-wallet-ton-gasless' - **Token standard**: Jettons via `transfer()` - **USDT Jetton master**: `EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs` - `sendTransaction` accepts a `body` field for arbitrary contract calls — treat with caution. +- `signTransaction()` returns a signed transfer-body `Cell` from `@ton/core`, not a complete external-message BOC. +- `quoteSendTransaction()` and `sendTransaction()` accept that `Cell`; send passes the exact body to the matching `WalletContractV5R1.send()` call and rechecks `transactionMaxFee`. +- ⚠️ The signed body contains the current wallet sequence number. Submit it through the same account before the sequence number changes; WDK does not rebuild or re-sign it. +- The returned send hash is the signed transfer-body hash, not a network transaction hash. > **Derivation path change in v1.0.0-beta.6+**: Previous default was `m/44'/607'/0'/0/{index}`, updated to match ecosystem conventions. Existing wallets created with old path will generate different addresses. Use `getAccountByPath` for legacy wallet recovery. diff --git a/skills/wdk/references/wallet-tron.md b/skills/wdk/references/wallet-tron.md index 9c464a10..6747a1e9 100644 --- a/skills/wdk/references/wallet-tron.md +++ b/skills/wdk/references/wallet-tron.md @@ -6,10 +6,10 @@ |----------|-----| | **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-tron | | **GitHub** | https://github.com/tetherto/wdk-wallet-tron | -| **Docs — Overview** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron | -| **Docs — Usage** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron/usage | -| **Docs — Configuration** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron/configuration | -| **Docs — API Reference** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron/api-reference | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron/api-reference | ## Links — wallet-tron-gasfree @@ -17,10 +17,10 @@ |----------|-----| | **npm** | https://www.npmjs.com/package/@tetherto/wdk-wallet-tron-gasfree | | **GitHub** | https://github.com/tetherto/wdk-wallet-tron-gasfree | -| **Docs — Overview** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron-gasfree | -| **Docs — Usage** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron-gasfree/usage | -| **Docs — Configuration** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron-gasfree/configuration | -| **Docs — API Reference** | https://docs.wallet.tether.io/sdk/wallet-modules/wallet-tron-gasfree/api-reference | +| **Docs — Overview** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron-gasfree | +| **Docs — Usage** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron-gasfree/usage | +| **Docs — Configuration** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron-gasfree/configuration | +| **Docs — API Reference** | https://docs.wdk.tether.io/sdk/wallet-modules/wallet-tron-gasfree/api-reference | ## Packages @@ -43,13 +43,17 @@ import WalletManagerTronGasfree from '@tetherto/wdk-wallet-tron-gasfree' - **USDT address**: `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` - ⚠️ TRON USDT has the same non-standard `transfer()` as Ethereum USDT (no bool return) - **Fee model**: Energy + Bandwidth costs (not simple gas like EVM) +- `signTransaction()` returns the exported `TronSignedTransaction` type. +- `quoteSendTransaction()` and `sendTransaction()` accept that signed object; send forwards it unchanged to `tronWeb.trx.sendRawTransaction()` and rechecks `transactionMaxFee`. +- ⚠️ WDK does not refresh the reference block or expiration, re-sign a supplied signed transaction, or repeat the unsigned owner-address check. Validate ownership and submit while it remains valid. ## Configuration — wallet-tron ```javascript const wallet = new WalletManagerTron(seedPhrase, { provider: 'https://api.trongrid.io', - transferMaxFee: 10000000n // Optional: max fee in sun + transferMaxFee: 10000000n, // Optional: max TRC20 transfer fee in sun + transactionMaxFee: 10000000n // Optional: max send/sign fee in sun }) ``` diff --git a/src/components/wallet-module-chooser.tsx b/src/components/wallet-module-chooser.tsx index f7bfcd64..8391a82b 100644 --- a/src/components/wallet-module-chooser.tsx +++ b/src/components/wallet-module-chooser.tsx @@ -12,6 +12,7 @@ type ChainFilter = | "ton" | "tron" | "solana" + | "aptos" | "rgb" | "cosmos"; @@ -45,6 +46,7 @@ const chainOptions: Array<{ value: ChainFilter; label: string }> = [ { value: "ton", label: "TON" }, { value: "tron", label: "TRON" }, { value: "solana", label: "Solana" }, + { value: "aptos", label: "Aptos" }, { value: "rgb", label: "RGB" }, { value: "cosmos", label: "Cosmos" }, ]; @@ -227,6 +229,21 @@ const walletModules: WalletModule[] = [ "You want Solana support without requiring users to fund fees directly.", ], }, + { + id: "aptos", + label: "Aptos", + chain: "aptos", + goals: ["standard"], + packageName: "@tetherto/wdk-wallet-aptos", + docsHref: "/sdk/wallet-modules/wallet-aptos", + apiHref: "/sdk/wallet-modules/wallet-aptos/api-reference", + bestFor: "Aptos wallets with native APT and fungible asset support.", + chooseWhen: [ + "You need Aptos accounts derived with SLIP-0010 Ed25519 paths.", + "You need APT balances, transfers, transaction signing, or receipts.", + "You need Aptos fungible asset balances and transfers.", + ], + }, { id: "rgb", label: "RGB", diff --git a/src/lib/custom-tree.ts b/src/lib/custom-tree.ts index 005a0040..fcadd72a 100644 --- a/src/lib/custom-tree.ts +++ b/src/lib/custom-tree.ts @@ -241,6 +241,11 @@ export const customTree: Node[] = [ apiReference('/sdk/wallet-modules/wallet-solana-gasless/api-reference'), ]), ]), + folder('Aptos', '/sdk/wallet-modules/wallet-aptos', 'Wallet', [ + usage('/sdk/wallet-modules/wallet-aptos/usage'), + configuration('/sdk/wallet-modules/wallet-aptos/configuration'), + apiReference('/sdk/wallet-modules/wallet-aptos/api-reference'), + ]), folder('RGB', '/sdk/community-modules/wdk-wallet-rgb', 'Palette', [ apiReference('/sdk/community-modules/wdk-wallet-rgb/api-reference'), ]), @@ -248,6 +253,9 @@ export const customTree: Node[] = [ apiReference('/sdk/community-modules/wdk-wallet-cosmos/api-reference'), ]), + separator('Deposit Routing'), + page('Smart Deposit Address interface', '/sdk/sda-modules', 'Route'), + separator('Swap and Bridge'), page('Swidge protocol interface', '/sdk/swidge-modules', 'Route'), folder('Orchestra', '/sdk/swidge-modules/swidge-orchestra', 'Waypoints', [ @@ -363,7 +371,12 @@ export const customTree: Node[] = [ configuration('/tools/secret-manager/configuration'), apiReference('/tools/secret-manager/api-reference'), ]), - page('Sync wallet contacts across devices', '/tools/p2p-address-book', 'BookUser'), + folder('P2P Address Book', '/tools/p2p-address-book', 'BookUser', [ + page('Get Started', '/tools/p2p-address-book/get-started', 'Rocket'), + page('Sync and Recovery', '/tools/p2p-address-book/sync-and-recovery', 'RefreshCw'), + apiReference('/tools/p2p-address-book/api-reference'), + page('Production and Privacy', '/tools/p2p-address-book/production-and-privacy', 'ShieldCheck'), + ]), folder('Fetch token prices', '/tools/price-rates', 'TrendingUp', [ configuration('/tools/price-rates/configuration'), apiReference('/tools/price-rates/api-reference'), From 49c4535a8b10bedf943962ba1b054468b3c4ca36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 03:17:35 +0000 Subject: [PATCH 14/38] build(deps): bump next from 16.2.6 to 16.2.12 Bumps [next](https://github.com/vercel/next.js) from 16.2.6 to 16.2.12. - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](https://github.com/vercel/next.js/compare/v16.2.6...v16.2.12) --- updated-dependencies: - dependency-name: next dependency-version: 16.2.12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 121 ++++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 53 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3958580d..e0602835 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "katex": "^0.16.27", "lucide-react": "^0.562.0", "mermaid": "^11.15.0", - "next": "^16.2.6", + "next": "^16.2.12", "next-themes": "^0.4.6", "react": "^19.2.3", "react-dom": "^19.2.3", @@ -1933,17 +1933,6 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, - "node_modules/@inkeep/cxkit-primitives/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@inkeep/cxkit-react": { "version": "0.5.111", "resolved": "https://registry.npmjs.org/@inkeep/cxkit-react/-/cxkit-react-0.5.111.tgz", @@ -2116,15 +2105,15 @@ } }, "node_modules/@next/env": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", - "integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.12.tgz", + "integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", - "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.12.tgz", + "integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==", "cpu": [ "arm64" ], @@ -2138,9 +2127,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", - "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.12.tgz", + "integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==", "cpu": [ "x64" ], @@ -2154,12 +2143,15 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", - "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.12.tgz", + "integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2170,12 +2162,15 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", - "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.12.tgz", + "integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2186,12 +2181,15 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz", - "integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.12.tgz", + "integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2202,12 +2200,15 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz", - "integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.12.tgz", + "integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2218,9 +2219,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", - "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.12.tgz", + "integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==", "cpu": [ "arm64" ], @@ -2234,9 +2235,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", - "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.12.tgz", + "integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==", "cpu": [ "x64" ], @@ -4349,7 +4350,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4366,7 +4366,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4383,7 +4382,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4400,7 +4398,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4417,7 +4414,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4434,7 +4430,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4451,7 +4446,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4468,7 +4462,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4485,7 +4478,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4510,7 +4502,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4527,7 +4518,6 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { "version": "1.7.1", - "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4538,7 +4528,6 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { "version": "1.7.1", - "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4548,7 +4537,6 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "1.1.0", - "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4558,7 +4546,6 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { "version": "1.1.0", - "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4570,7 +4557,6 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { "version": "0.10.1", - "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4580,7 +4566,6 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { "version": "2.8.1", - "dev": true, "inBundle": true, "license": "0BSD", "optional": true @@ -4592,7 +4577,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4609,7 +4593,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10317,12 +10300,12 @@ } }, "node_modules/next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", - "integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.12.tgz", + "integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==", "license": "MIT", "dependencies": { - "@next/env": "16.2.6", + "@next/env": "16.2.12", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -10336,14 +10319,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.6", - "@next/swc-darwin-x64": "16.2.6", - "@next/swc-linux-arm64-gnu": "16.2.6", - "@next/swc-linux-arm64-musl": "16.2.6", - "@next/swc-linux-x64-gnu": "16.2.6", - "@next/swc-linux-x64-musl": "16.2.6", - "@next/swc-win32-arm64-msvc": "16.2.6", - "@next/swc-win32-x64-msvc": "16.2.6", + "@next/swc-darwin-arm64": "16.2.12", + "@next/swc-darwin-x64": "16.2.12", + "@next/swc-linux-arm64-gnu": "16.2.12", + "@next/swc-linux-arm64-musl": "16.2.12", + "@next/swc-linux-x64-gnu": "16.2.12", + "@next/swc-linux-x64-musl": "16.2.12", + "@next/swc-win32-arm64-msvc": "16.2.12", + "@next/swc-win32-x64-msvc": "16.2.12", "sharp": "^0.34.5" }, "peerDependencies": { diff --git a/package.json b/package.json index 5338f72e..2a0137aa 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "katex": "^0.16.27", "lucide-react": "^0.562.0", "mermaid": "^11.15.0", - "next": "^16.2.6", + "next": "^16.2.12", "next-themes": "^0.4.6", "react": "^19.2.3", "react-dom": "^19.2.3", From 78926f2f661e02fab1345b97c0e45c07b420a53b Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 28 Jul 2026 18:56:16 +0530 Subject: [PATCH 15/38] build(deps): apply remaining Dependabot updates --- package-lock.json | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0602835..7806ab56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1933,6 +1933,17 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/@inkeep/cxkit-primitives/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@inkeep/cxkit-react": { "version": "0.5.111", "resolved": "https://registry.npmjs.org/@inkeep/cxkit-react/-/cxkit-react-0.5.111.tgz", @@ -4350,6 +4361,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4366,6 +4378,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4382,6 +4395,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4398,6 +4412,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4414,6 +4429,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4430,6 +4446,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4446,6 +4463,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4462,6 +4480,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4478,6 +4497,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4502,6 +4522,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4518,6 +4539,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { "version": "1.7.1", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4528,6 +4550,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { "version": "1.7.1", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4537,6 +4560,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4546,6 +4570,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4557,6 +4582,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { "version": "0.10.1", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -4566,6 +4592,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { "version": "2.8.1", + "dev": true, "inBundle": true, "license": "0BSD", "optional": true @@ -4577,6 +4604,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4593,6 +4621,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6835,9 +6864,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -7271,9 +7300,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { From 5ed79f7fe30950ba2d55fa54da1aca188365868c Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 30 Jun 2026 11:48:54 +0530 Subject: [PATCH 16/38] docs: add WDK CLI documentation --- content/docs/ai/agent-skills.mdx | 6 +- content/docs/ai/mcp-toolkit/index.mdx | 3 + content/docs/ai/openclaw.mdx | 5 + content/docs/ai/wdk-cli/commands.mdx | 192 +++++++++++++++++++ content/docs/ai/wdk-cli/configuration.mdx | 219 ++++++++++++++++++++++ content/docs/ai/wdk-cli/get-started.mdx | 139 ++++++++++++++ content/docs/ai/wdk-cli/index.mdx | 82 ++++++++ content/docs/ai/wdk-cli/mcp.mdx | 135 +++++++++++++ src/lib/custom-tree.ts | 6 + 9 files changed, 785 insertions(+), 2 deletions(-) create mode 100644 content/docs/ai/wdk-cli/commands.mdx create mode 100644 content/docs/ai/wdk-cli/configuration.mdx create mode 100644 content/docs/ai/wdk-cli/get-started.mdx create mode 100644 content/docs/ai/wdk-cli/index.mdx create mode 100644 content/docs/ai/wdk-cli/mcp.mdx diff --git a/content/docs/ai/agent-skills.mdx b/content/docs/ai/agent-skills.mdx index 2044834c..1ba0fbeb 100644 --- a/content/docs/ai/agent-skills.mdx +++ b/content/docs/ai/agent-skills.mdx @@ -9,7 +9,7 @@ icon: Brain WDK provides agent skills: structured instruction sets that teach AI agents how to create wallets, send transactions, swap tokens, bridge assets, and interact with DeFi protocols across 20+ blockchains. All operations are self-custodial. Keys stay on your machine, with no third-party custody dependency. -**Skill vs MCP Toolkit**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](/ai/mcp-toolkit/) when your agent supports the Model Context Protocol natively (e.g., Claude, Cursor). Use both for maximum coverage. +**Skill vs MCP Toolkit vs WDK CLI**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. Use [WDK CLI](/ai/wdk-cli/) when you want a ready-made local wallet CLI, daemon, and MCP server. ## What Are Agent Skills? @@ -66,7 +66,8 @@ WDK's agent skills use a self-custodial model where your agent controls its own | **OpenClaw** | Install from [ClawHub](/ai/openclaw/) or clone to workspace. See [OpenClaw Integration](/ai/openclaw/) | | **Claude** | Upload `SKILL.md` as project knowledge, or paste into conversation | | **Cursor / Windsurf** | Clone to `.cursor/skills/wdk` or `.windsurf/skills/wdk` | -| **Any MCP-compatible agent** | Use the [MCP Toolkit](/ai/mcp-toolkit/) for structured tool calling | +| **Any MCP-compatible agent** | Use [WDK CLI](/ai/wdk-cli/mcp/) for a ready-made local daemon, or the [MCP Toolkit](/ai/mcp-toolkit/) for a custom MCP server | +| **Any local CLI-capable agent** | Use [WDK CLI](/ai/wdk-cli/) with `--json` output | | **Any other agent** | Copy `SKILL.md` into system prompt or conversation context | ## Community Projects @@ -84,6 +85,7 @@ Community-built projects using WDK's agentic capabilities: - [WDK SKILL.md on GitHub](https://github.com/tetherto/wdk-docs/blob/main/skills/wdk/SKILL.md) - The full skill file agents consume - [WDK Skill on ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit) - Install the skill - [AgentSkills Specification](https://agentskills.io/specification) - The skill format standard +- [WDK CLI](/ai/wdk-cli/) - Local CLI, daemon, and MCP server for AI agents - [WDK MCP Toolkit](https://github.com/tetherto/wdk-mcp-toolkit) - MCP server for structured tool calling - [WDK Core](https://github.com/tetherto/wdk-core) - The core SDK diff --git a/content/docs/ai/mcp-toolkit/index.mdx b/content/docs/ai/mcp-toolkit/index.mdx index 74526976..ffc1019a 100644 --- a/content/docs/ai/mcp-toolkit/index.mdx +++ b/content/docs/ai/mcp-toolkit/index.mdx @@ -60,6 +60,9 @@ All 35 built-in MCP tools and the WdkMcpServer class Use WDK tools in LangChain agents via the serve CLI + +Use a ready-made local wallet CLI, daemon, and MCP server when you do not need to build a custom MCP server + *** diff --git a/content/docs/ai/openclaw.mdx b/content/docs/ai/openclaw.mdx index a09b74be..968a4a4c 100644 --- a/content/docs/ai/openclaw.mdx +++ b/content/docs/ai/openclaw.mdx @@ -18,6 +18,10 @@ Tether and the WDK Team do not endorse or assume responsibility for its code, se The WDK community skill follows the [AgentSkills specification](https://agentskills.io/specification), so it works with any compatible agent platform. This page covers the OpenClaw-specific setup. + +If you want OpenClaw to call a ready-made local wallet daemon through MCP instead of loading a file-based skill, see [WDK CLI MCP setup](/ai/wdk-cli/mcp/). + + ## Install the WDK Community Skill Install from [ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit): @@ -113,6 +117,7 @@ OpenClaw makes use of artificial intelligence and machine learning technologies. ## Next Steps - [Agent Skills](/ai/agent-skills/) - Full capabilities, how skills work, and a comparison with other agentic wallet solutions +- [WDK CLI MCP](/ai/wdk-cli/mcp/) - Configure OpenClaw with the bundled WDK CLI MCP server - [MCP Toolkit](/ai/mcp-toolkit/) - Programmatic wallet access for MCP-compatible agents - [OpenClaw Skills Documentation](https://docs.openclaw.ai/tools/skills) - How OpenClaw discovers and loads skills diff --git a/content/docs/ai/wdk-cli/commands.mdx b/content/docs/ai/wdk-cli/commands.mdx new file mode 100644 index 00000000..11705fe4 --- /dev/null +++ b/content/docs/ai/wdk-cli/commands.mdx @@ -0,0 +1,192 @@ +--- +title: Commands +description: WDK CLI command reference for wallets, reads, sends, config, networks, tokens, ramps, and MCP +docType: reference +schemaType: TechArticle +icon: ListChecks +--- + +WDK CLI exposes one root command, `wdk`, plus the `wdk-mcp` and `wdk-daemon` binaries used by the MCP server and daemon. + +## Global Flags + +| Flag | Applies to | Description | +| --- | --- | --- | +| `--json` | All commands | Print machine-readable JSON output where supported | +| `--verbose` | All commands | Print debug output and full errors | +| `--wallet NAME` | Wallet-dependent commands | Use a named wallet instead of the default wallet | +| `--index N` | Address, balance, history, send, buy, sell | Use a BIP account index instead of the configured default | + +## Wallets + +| Command | Purpose | +| --- | --- | +| `wdk wallet create --name NAME --words WORD_COUNT` | Create a new named wallet. `WORD_COUNT` is `12` or `24` | +| `wdk wallet import --name NAME` | Import an existing seed phrase | +| `wdk wallet export --name NAME` | Display the seed phrase for an existing wallet | +| `wdk wallet list` | List wallets and their lock/default status | +| `wdk wallet unlock --name NAME --ttl MINUTES` | Unlock a wallet and start the daemon if needed | +| `wdk wallet lock --name NAME` | Lock one wallet | +| `wdk wallet lock --all` | Lock every wallet and stop the daemon when none remain unlocked | +| `wdk wallet delete --name NAME` | Delete a wallet after passphrase confirmation | +| `wdk wallet default --name NAME` | Set the default wallet | +| `wdk wallet rename --name OLD --new-name NEW` | Rename a wallet | + +Example flow: + +```bash title="Terminal" +wdk wallet create --name dev --words 24 +wdk wallet unlock --name dev --ttl 5 +wdk wallet default --name dev +wdk wallet list +``` + +Commands that need a passphrase prompt can use `WDK_PASSPHRASE` for non-interactive local automation: + +```bash title="Terminal" +WDK_PASSPHRASE= wdk wallet unlock --name dev --json +``` + +## Read Commands + +| Command | Purpose | +| --- | --- | +| `wdk get address --network NETWORK` | Derive an address for one network | +| `wdk get address --all` | Derive addresses for all mainnet networks | +| `wdk get address --all --testnet` | Include testnet networks | +| `wdk get balance --network NETWORK` | Read native balance for one network | +| `wdk get balance --network NETWORK --token TICKER` | Read a registered token balance | +| `wdk get balance --all` | Read balances across mainnet networks | +| `wdk get history --network NETWORK` | Read transaction history through the indexer | +| `wdk get history --network NETWORK --token TICKER --limit 50` | Filter history by registered token and limit | + +Examples: + +```bash title="Terminal" +wdk get address --network ethereum --wallet dev +wdk get balance --network ethereum --token usdt --wallet dev +wdk get history --network ethereum --from-date 2026-01-01 --to-date 2026-03-31 +``` + +`get history` requires indexer support for the network and an indexer API key when the endpoint requires one. + +## Send + +`wdk send` supports native and registered token transfers. Amounts are decimal by default. + +| Option | Required | Description | +| --- | --- | --- | +| `--network NETWORK` | Yes | Network to send on | +| `--to ADDRESS` | Yes | Recipient address | +| `--amount AMOUNT` | Yes | Decimal amount unless `--base-units` is set | +| `--token TICKER` | No | Registered token ticker. Omit for native transfers | +| `--base-units` | No | Treat `--amount` as raw base units | +| `--dry-run` | No | Preview the transfer without broadcasting | + +Preview first: + +```bash title="Terminal" +wdk send \ + --network ethereum \ + --to 0x000000000000000000000000000000000000dEaD \ + --amount 0.001 \ + --dry-run +``` + +Send a token transfer after checking the preview: + +```bash title="Terminal" +wdk send \ + --network ethereum \ + --token usdt \ + --to 0x000000000000000000000000000000000000dEaD \ + --amount 1.5 +``` + +Use `--base-units` only when your script already has wei, satoshi, lamports, or another token base-unit value. + +## Config + +| Command | Purpose | +| --- | --- | +| `wdk config get --all` | Print all config | +| `wdk config get --key KEY` | Print a single key | +| `wdk config get --network NETWORK` | Print network-scoped config | +| `wdk config set --key KEY --value VALUE` | Set a global key | +| `wdk config set --network NETWORK --key KEY --value VALUE` | Set a network key | +| `wdk config reset --key KEY` | Reset one key | +| `wdk config reset --all` | Reset stored config while preserving custom networks, custom tokens, and default wallet | +| `wdk config path` | Print the config file path | + +Network config updates lock wallets so new SDK settings take effect on the next unlock. + +## Networks + +| Command | Purpose | +| --- | --- | +| `wdk network list` | List built-in and custom networks | +| `wdk network list --mainnet` | Show only mainnets | +| `wdk network list --testnet` | Show only testnets | +| `wdk network info --network NETWORK` | Show one network's details | +| `wdk network create JSON_OR_FILE` | Add a custom network | +| `wdk network delete --name NAME` | Delete a custom network | + +Custom network data can be an inline JSON string or a path to a JSON file: + +```bash title="Terminal" +wdk network create ./optimism.json +``` + +## Tokens + +| Command | Purpose | +| --- | --- | +| `wdk token list` | List all registered tokens | +| `wdk token list --network NETWORK` | List tokens for one network | +| `wdk token info --network NETWORK --token TICKER` | Show one token entry | +| `wdk token add JSON_OR_FILE` | Add or override a custom token | +| `wdk token delete --network NETWORK --token TICKER` | Delete a custom token | + +The `--token` flag on `get balance`, `get history`, `send`, `buy`, and `sell` resolves through this registry. + +## Buy And Sell + +| Command | Purpose | +| --- | --- | +| `wdk buy --network NETWORK --token TICKER` | Create a MoonPay buy URL | +| `wdk sell --network NETWORK --token TICKER` | Create a MoonPay sell URL | + +Options: + +| Option | Description | +| --- | --- | +| `--module MODULE` | Fiat provider. The current source defaults to `moonpay` | +| `--fiat-currency CURRENCY` | Fiat currency code, default `usd` | +| `--fiat-amount VALUE` | Fiat amount. Mutually exclusive with `--crypto-amount` | +| `--crypto-amount VALUE` | Crypto amount. Mutually exclusive with `--fiat-amount` | + +Examples: + +```bash title="Terminal" +wdk buy --network ethereum --token usdt --fiat-amount 100 +wdk sell --network polygon --token usdt --crypto-amount 50 +``` + +## MCP + +| Command | Purpose | +| --- | --- | +| `wdk mcp setup --ai-tool claude-desktop` | Register `wdk-mcp` with Claude Desktop | +| `wdk mcp setup --ai-tool claude-code` | Register `wdk-mcp` with Claude Code | +| `wdk mcp setup --ai-tool openclaw` | Register `wdk-mcp` with OpenClaw | +| `wdk mcp verify-setup --ai-tool TOOL` | Verify the MCP setup for one tool | +| `wdk mcp remove --ai-tool TOOL` | Remove the WDK MCP server from one tool | +| `wdk mcp list` | Show setup status for supported AI tools | + +See [MCP](/ai/wdk-cli/mcp/) for the MCP tool list and the required two-step `send_token` flow. + +*** + +## Need Help? + + diff --git a/content/docs/ai/wdk-cli/configuration.mdx b/content/docs/ai/wdk-cli/configuration.mdx new file mode 100644 index 00000000..9e8f6dde --- /dev/null +++ b/content/docs/ai/wdk-cli/configuration.mdx @@ -0,0 +1,219 @@ +--- +title: Configuration +description: Configure WDK CLI wallets, sessions, networks, tokens, indexer access, and ramps +docType: reference +schemaType: TechArticle +icon: Settings +--- + +WDK CLI stores wallet files and user configuration locally. Read commands can inspect configuration without an unlocked wallet. Write commands such as `config set`, `config reset`, `network create`, `network delete`, `token add`, and `token delete` require passphrase confirmation or an unlocked wallet depending on the operation. + +## Local Paths + +| Data | Default path | +| --- | --- | +| Config directory | `~/.config/wdk-cli` | +| Wallet seed file | `~/.config/wdk-cli/wallets/NAME/seed.enc` | +| Daemon socket | `~/.config/wdk-cli/daemon.sock` on Unix-like systems | +| Daemon PID file | `~/.config/wdk-cli/daemon.pid` | + +When `XDG_CONFIG_HOME` is set, the config directory becomes `$XDG_CONFIG_HOME/wdk-cli`. + +Use the CLI to print the exact config file path for your system: + +```bash title="Terminal" +wdk config path +``` + +## Environment Variables + +| Variable | Applies to | Notes | +| --- | --- | --- | +| `WDK_PASSPHRASE` | Passphrase-protected commands | Skips the interactive passphrase prompt for automation | +| `WDK_INDEXER_API_KEY` | `indexer.apiKey` | Overrides the stored indexer API key at read time | + + +The current CLI source maps only `WDK_INDEXER_API_KEY` as an indexer environment override. Configure the indexer base URL with `wdk config set --key indexer.baseUrl --value ...` if you need to change it. + + +## Wallet Sessions + +Unlocking a wallet starts the daemon if it is not already running: + +```bash title="Terminal" +wdk wallet unlock --name dev --ttl 5 +``` + +Each wallet has its own TTL. The default is 5 minutes. + +| TTL | Behavior | +| --- | --- | +| omitted | Uses the default 5 minute session | +| `--ttl 60` | Keeps that wallet unlocked for 60 minutes | +| `--ttl 0` | Keeps that wallet unlocked until you lock it or the daemon exits | + +Lock a wallet after use: + +```bash title="Terminal" +wdk wallet lock --name dev +``` + +## Global Config + +Read all config: + +```bash title="Terminal" +wdk config get --all +``` + +Read a single key: + +```bash title="Terminal" +wdk config get --key indexer.apiKey +``` + +Set a single key: + +```bash title="Terminal" +wdk config set --key indexer.apiKey --value +``` + +Set structured values by passing JSON: + +```bash title="Terminal" +wdk config set \ + --key ramp.moonpay \ + --value '{"apiKey":"pk_test_...","signUrl":"https://example.com/sign","environment":"sandbox"}' +``` + +Reset a key: + +```bash title="Terminal" +wdk config reset --key ramp.moonpay.apiKey +``` + +Reset all config: + +```bash title="Terminal" +wdk config reset --all +``` + +`config reset --all` preserves default wallet, custom network, and custom token state. + +## Network Config + +Read one network's config: + +```bash title="Terminal" +wdk config get --network ethereum +``` + +Set a network provider: + +```bash title="Terminal" +wdk config set \ + --network ethereum \ + --key provider \ + --value https://eth-mainnet.g.alchemy.com/v2/ +``` + +Set the full network config with JSON: + +```bash title="Terminal" +wdk config set \ + --network ethereum \ + --value '{"provider":"https://eth-mainnet.g.alchemy.com/v2/","chainId":1}' +``` + +Changing `networks.*` config locks all wallets so stale wallet managers are dropped. Unlock the wallet again before continuing: + +```bash title="Terminal" +wdk wallet unlock --name dev +``` + +## Indexer + +Transaction history uses the WDK Indexer API. Set the API key in config or use the environment variable: + +```bash title="Terminal" +wdk config set --key indexer.apiKey --value +``` + +```bash title="Terminal" +WDK_INDEXER_API_KEY= wdk get history --network ethereum --wallet dev +``` + +If you need a custom indexer endpoint, store it in config: + +```bash title="Terminal" +wdk config set --key indexer.baseUrl --value https://indexer.example.com +``` + +## Custom Networks + +Create a custom network from inline JSON: + +```bash title="Terminal" +wdk network create '{"network":"optimism","module":"@tetherto/wdk-wallet-evm","indexerSlug":"ethereum","config":{"provider":"https://mainnet.optimism.io","chainId":10}}' +``` + +Or create it from a file: + +```bash title="Terminal" +wdk network create ./optimism.json +``` + +Custom network specs can include `tokens` so the network and token registry update together. If a token entry fails validation, the network is not created. + +Delete only custom networks: + +```bash title="Terminal" +wdk network delete --name optimism +``` + +## Custom Tokens + +Add a token from inline JSON: + +```bash title="Terminal" +wdk token add '{"network":"ethereum","token":"dai","symbol":"DAI","decimals":18,"isNative":false,"address":"0x6B175474E89094C44Da98b954EedeAC495271d0F"}' +``` + +Or add it from a file: + +```bash title="Terminal" +wdk token add ./dai-on-ethereum.json +``` + +List tokens: + +```bash title="Terminal" +wdk token list --network ethereum +wdk token info --network ethereum --token usdt +``` + +Delete only custom tokens: + +```bash title="Terminal" +wdk token delete --network ethereum --token dai +``` + +Built-in tokens can be overridden by a custom token with the same ticker. Removing the custom token restores the built-in entry. + +## MoonPay Buy/Sell + +`wdk buy` and `wdk sell` use MoonPay as the fiat provider in the current source. Configure all three values: + +```bash title="Terminal" +wdk config set --key ramp.moonpay.apiKey --value +wdk config set --key ramp.moonpay.signUrl --value https://example.com/moonpay/sign +wdk config set --key ramp.moonpay.environment --value sandbox +``` + +The environment must be `sandbox` or `production`. The CLI rejects testnet purchases with a production MoonPay environment, and rejects mainnet purchases with a sandbox environment. + +*** + +## Need Help? + + diff --git a/content/docs/ai/wdk-cli/get-started.mdx b/content/docs/ai/wdk-cli/get-started.mdx new file mode 100644 index 00000000..967a49b3 --- /dev/null +++ b/content/docs/ai/wdk-cli/get-started.mdx @@ -0,0 +1,139 @@ +--- +title: Get Started +description: Install WDK CLI from source and run a local development wallet +docType: getting-started +schemaType: TechArticle +icon: Rocket +--- + + +WDK CLI is not release-installable yet. The implementation is under review in [tetherto/wdk-cli#1](https://github.com/tetherto/wdk-cli/pull/1), and the public `wdk-cli` npm package is still a placeholder. Install from source until a real package is published. + + +## Prerequisites + +- Node.js `>=22.18.0` +- npm +- GitHub CLI, if you want to check out the pull request directly +- A dedicated development wallet seed with limited or no funds + +Check your Node.js version: + +```bash title="Terminal" +node --version +``` + +## Install From The Source PR + +Clone the repository and check out the active CLI pull request: + +```bash title="Terminal" +gh repo clone tetherto/wdk-cli +cd wdk-cli +gh pr checkout 1 +npm install +npm link +``` + +`npm link` makes the `wdk`, `wdk-mcp`, and `wdk-daemon` binaries available on your local PATH. + +Verify the CLI is available: + +```bash title="Terminal" +wdk --help +wdk wallet --help +``` + + +If you cannot use GitHub CLI, clone the pull request source branch from GitHub and run the same `npm install` and `npm link` commands from that checkout. + + +## Create A Development Wallet + +Create a named wallet. The CLI prompts for a passphrase interactively. + +```bash title="Terminal" +wdk wallet create --name dev --words 24 +wdk wallet default --name dev +``` + +Unlock the wallet before reading balances or sending transactions: + +```bash title="Terminal" +wdk wallet unlock --name dev --ttl 5 +``` + +The daemon keeps the unlocked wallet in memory for the requested TTL. The default TTL is 5 minutes. Use `--ttl 0` only when a local agent session needs a wallet to stay unlocked and you accept that risk. + +## Read An Address And Balance + +Get an address for a single network: + +```bash title="Terminal" +wdk get address --network ethereum --wallet dev +``` + +Check the native balance: + +```bash title="Terminal" +wdk get balance --network ethereum --wallet dev +``` + +Use `--json` when another program or AI agent needs structured output: + +```bash title="Terminal" +wdk get balance --network ethereum --wallet dev --json +``` + +## Preview A Send + +Always start with a dry run. This estimates the transfer and fee without broadcasting a transaction. + +```bash title="Terminal" +wdk send \ + --network ethereum \ + --to 0x000000000000000000000000000000000000dEaD \ + --amount 0.001 \ + --wallet dev \ + --dry-run +``` + +Token transfers use registered token tickers: + +```bash title="Terminal" +wdk send \ + --network ethereum \ + --token usdt \ + --to 0x000000000000000000000000000000000000dEaD \ + --amount 1.5 \ + --wallet dev \ + --dry-run +``` + +Only remove `--dry-run` after you have checked the preview and intend to broadcast the transaction. + +## Lock The Wallet + +Lock the wallet when the session is finished: + +```bash title="Terminal" +wdk wallet lock --name dev +``` + +Lock every unlocked wallet: + +```bash title="Terminal" +wdk wallet lock --all +``` + +## Next Steps + +- [Configuration](/ai/wdk-cli/configuration/) - Configure indexer access, providers, custom networks, tokens, and MoonPay +- [Commands](/ai/wdk-cli/commands/) - Review every command group +- [MCP](/ai/wdk-cli/mcp/) - Connect the same wallet daemon to Claude Desktop, Claude Code, or OpenClaw + +*** + +## Need Help? + + diff --git a/content/docs/ai/wdk-cli/index.mdx b/content/docs/ai/wdk-cli/index.mdx new file mode 100644 index 00000000..30d07854 --- /dev/null +++ b/content/docs/ai/wdk-cli/index.mdx @@ -0,0 +1,82 @@ +--- +title: WDK CLI +description: Run a local WDK wallet CLI, daemon, and MCP server for AI agents +docType: explanation +schemaType: TechArticle +icon: Terminal +--- + +WDK CLI is a local wallet command line interface for humans, scripts, and AI agents. It wraps WDK wallet modules in three local processes: + +| Process | What it does | +| --- | --- | +| `wdk` | Parses commands, prints human-readable or JSON output, and sends wallet requests to the daemon | +| `wdk-daemon` | Holds unlocked WDK wallet instances in memory, derives addresses, signs transactions, and locks wallets after their TTL | +| `wdk-mcp` | Exposes wallet operations as Model Context Protocol tools for MCP-compatible AI clients | + + +**Draft source install only** - The WDK CLI implementation is currently under review in [tetherto/wdk-cli#1](https://github.com/tetherto/wdk-cli/pull/1). Use the source-install flow until the module is merged and a non-placeholder package is published. + + +Use WDK CLI when you want a ready-made local wallet and daemon. Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building your own MCP server or embedding WDK tools in an application. + +## What You Can Do + +- Create, import, rename, unlock, lock, and delete named wallets +- Derive addresses, read balances, and query transaction history +- Preview transfers with `--dry-run`, then send native or registered token transfers after confirmation +- Configure networks, tokens, indexer access, and MoonPay on/off-ramp settings +- Run all commands with `--json` for agent or script consumption +- Register `wdk-mcp` with Claude Desktop, Claude Code, or OpenClaw + +## Architecture + +The CLI and MCP server are thin clients. They do not keep seed phrases or private keys themselves. Wallet operations go through the daemon over local IPC. + +```text +wdk CLI -> local IPC -> wdk-daemon -> WDK wallet modules -> blockchain RPC/indexer +wdk-mcp MCP -> local IPC -> wdk-daemon -> WDK wallet modules -> blockchain RPC/indexer +``` + +The daemon starts when a wallet is unlocked. It keeps each unlocked wallet in memory until the wallet is locked, the TTL expires, or the last wallet is locked and the daemon exits. + +## CLI vs MCP + +| Interface | Use it when | Example | +| --- | --- | --- | +| CLI | Your agent or script can run local shell commands | `wdk get balance --network ethereum --json` | +| MCP | Your AI client supports MCP tools and should call structured wallet actions | `wdk mcp setup --ai-tool claude-desktop` | +| MCP Toolkit | You need to build and customize your own MCP server in code | `new WdkMcpServer(...)` | + +The CLI and MCP server share the same daemon and wallet store. You can create and unlock a wallet with `wdk`, then use the MCP tools from your AI client. + +## Safety Model + + +Use a dedicated development wallet with limited funds while these docs and the CLI module are still in draft. Do not use a production seed phrase to test unmerged code. + + +Wallet seed files are stored under `~/.config/wdk-cli/wallets/NAME/seed.enc` by default, or under `$XDG_CONFIG_HOME/wdk-cli/wallets/NAME/seed.enc` when `XDG_CONFIG_HOME` is set. If you provide a passphrase, the seed is encrypted before it is written to disk. + +Unlocking a wallet sends the passphrase to the daemon over local IPC, initializes WDK wallet instances in memory, and starts a per-wallet TTL. The default TTL is 5 minutes. Use `--ttl 0` only for controlled local development sessions where you accept the longer-lived unlocked wallet. + + + +Install from the source PR, create a development wallet, and run the first read and dry-run send commands + + +Configure wallets, sessions, networks, tokens, indexer access, and MoonPay settings + + +Review the CLI command surface for wallets, reads, sends, config, networks, tokens, buy/sell, and MCP + + +Connect WDK CLI to Claude Desktop, Claude Code, or OpenClaw through the bundled MCP server + + + +*** + +## Need Help? + + diff --git a/content/docs/ai/wdk-cli/mcp.mdx b/content/docs/ai/wdk-cli/mcp.mdx new file mode 100644 index 00000000..e28179a5 --- /dev/null +++ b/content/docs/ai/wdk-cli/mcp.mdx @@ -0,0 +1,135 @@ +--- +title: MCP +description: Connect WDK CLI to Claude Desktop, Claude Code, or OpenClaw with the bundled MCP server +docType: how-to +schemaType: TechArticle +icon: Wand +--- + +WDK CLI includes `wdk-mcp`, a Model Context Protocol server that exposes local wallet actions as structured tools. The MCP server talks to the same `wdk-daemon` used by the CLI. + + +Create and unlock a dedicated development wallet before connecting an AI client. Do not test unmerged wallet code with a production seed phrase or meaningful funds. + + +## Prepare The Wallet + +Install WDK CLI from source first. See [Get Started](/ai/wdk-cli/get-started/). + +Create and unlock a wallet: + +```bash title="Terminal" +wdk wallet create --name agent-dev --words 24 +wdk wallet unlock --name agent-dev --ttl 5 +wdk wallet default --name agent-dev +``` + +Use a short TTL while you are testing. Increase the TTL only when your local agent workflow needs it. + +## Set Up An AI Tool + +WDK CLI supports three setup targets in the current source: + +```bash title="Terminal" +wdk mcp setup --ai-tool claude-desktop +wdk mcp setup --ai-tool claude-code +wdk mcp setup --ai-tool openclaw +``` + +Setup resolves the local Node.js executable, points the AI tool at `wdk-mcp`, verifies the MCP server when possible, and writes or delegates the tool config: + +| AI tool | Setup behavior | +| --- | --- | +| Claude Desktop | Writes the `wdk-wallet` server entry into Claude Desktop's MCP config file | +| Claude Code | Runs `claude mcp add -s user wdk-wallet -- ...` | +| OpenClaw | Runs `openclaw mcp set wdk-wallet ...` | + +Verify setup: + +```bash title="Terminal" +wdk mcp verify-setup --ai-tool claude-desktop +wdk mcp list +``` + +Remove setup: + +```bash title="Terminal" +wdk mcp remove --ai-tool claude-desktop +``` + +## MCP Tools + +| Tool | Purpose | +| --- | --- | +| `get_networks` | List supported networks, optionally filtered by mainnet or testnet | +| `list_tokens` | List registered tokens for one network or every network | +| `get_token` | Read one token registry entry | +| `get_address` | Get one address or all addresses | +| `get_balance` | Get one balance or all balances with USD values | +| `get_history` | Get transaction history through the indexer | +| `send_token` | Preview or execute a native or token transfer | +| `buy_crypto` | Create a signed MoonPay buy URL | +| `sell_crypto` | Create a signed MoonPay sell URL | + +Wallet-dependent tools accept an optional `wallet` parameter. When omitted, the daemon uses the default wallet. + +## Send Token Confirmation Flow + +`send_token` defaults to `dryRun: true`. Agents must use a two-step flow: + +1. Call `send_token` with the transfer details and no `dryRun` value, or with `dryRun: true`. +2. Show the preview, amount, recipient, network, token, and fee information to the user. +3. Call `send_token` again with `dryRun: false` only after the user confirms. + +Example dry-run request: + +```json +{ + "network": "ethereum", + "token": "usdt", + "to": "0x000000000000000000000000000000000000dEaD", + "amount": "1.5" +} +``` + +Example execution request after confirmation: + +```json +{ + "network": "ethereum", + "token": "usdt", + "to": "0x000000000000000000000000000000000000dEaD", + "amount": "1.5", + "dryRun": false +} +``` + +## Security Notes + +- The MCP server does not receive seed phrases or private keys from the AI client. +- The MCP server routes wallet actions to the local daemon over IPC. +- The daemon holds unlocked wallet instances in memory until the wallet is locked or its TTL expires. +- `--ttl 0` disables automatic expiry for that wallet. Use it only for controlled local development sessions. +- Use `WDK_PASSPHRASE` only in trusted local automation. Do not commit it to shell profiles, logs, scripts, or MCP config files. +- Lock the wallet after the agent session: + +```bash title="Terminal" +wdk wallet lock --name agent-dev +``` + +## When To Use The MCP Toolkit Instead + +Use [MCP Toolkit](/ai/mcp-toolkit/) instead of WDK CLI when you need to: + +- Build a custom MCP server in application code +- Choose exactly which WDK tools are registered +- Add custom MCP tools beside WDK tools +- Embed wallet behavior in a larger service instead of using a local daemon + +Use WDK CLI when you want a local wallet daemon and ready-made MCP setup for supported AI tools. + +*** + +## Need Help? + + diff --git a/src/lib/custom-tree.ts b/src/lib/custom-tree.ts index aa7f6529..76296a66 100644 --- a/src/lib/custom-tree.ts +++ b/src/lib/custom-tree.ts @@ -334,6 +334,12 @@ export const customTree: Node[] = [ apiReference('/ai/mcp-toolkit/api-reference'), page('LangChain Integration', '/ai/mcp-toolkit/langchain', 'Link'), ]), + folder('WDK CLI', '/ai/wdk-cli', 'Terminal', [ + page('Get Started', '/ai/wdk-cli/get-started', 'Rocket'), + configuration('/ai/wdk-cli/configuration'), + page('Commands', '/ai/wdk-cli/commands', 'ListChecks'), + page('MCP', '/ai/wdk-cli/mcp', 'Wand'), + ]), page('Agent Skills', '/ai/agent-skills', 'Brain'), page('OpenClaw', '/ai/openclaw', 'Cat'), page('x402', '/ai/x402', 'CreditCard'), From 50da1aaf0e5aa64330cd1c844b1fbb7d66ada75f Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 30 Jun 2026 12:31:07 +0530 Subject: [PATCH 17/38] docs: adjust AI navigation order --- src/lib/custom-tree.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/custom-tree.ts b/src/lib/custom-tree.ts index 76296a66..bab8ca22 100644 --- a/src/lib/custom-tree.ts +++ b/src/lib/custom-tree.ts @@ -328,18 +328,18 @@ export const customTree: Node[] = [ separator('AI'), page('Build with AI', '/start-building/build-with-ai', 'Bot'), - folder('MCP Toolkit', '/ai/mcp-toolkit', 'Wand', [ - page('Get Started', '/ai/mcp-toolkit/get-started', 'Rocket'), - configuration('/ai/mcp-toolkit/configuration'), - apiReference('/ai/mcp-toolkit/api-reference'), - page('LangChain Integration', '/ai/mcp-toolkit/langchain', 'Link'), - ]), folder('WDK CLI', '/ai/wdk-cli', 'Terminal', [ page('Get Started', '/ai/wdk-cli/get-started', 'Rocket'), configuration('/ai/wdk-cli/configuration'), page('Commands', '/ai/wdk-cli/commands', 'ListChecks'), page('MCP', '/ai/wdk-cli/mcp', 'Wand'), ]), + folder('MCP Toolkit', '/ai/mcp-toolkit', 'Wand', [ + page('Get Started', '/ai/mcp-toolkit/get-started', 'Rocket'), + configuration('/ai/mcp-toolkit/configuration'), + apiReference('/ai/mcp-toolkit/api-reference'), + page('LangChain Integration', '/ai/mcp-toolkit/langchain', 'Link'), + ]), page('Agent Skills', '/ai/agent-skills', 'Brain'), page('OpenClaw', '/ai/openclaw', 'Cat'), page('x402', '/ai/x402', 'CreditCard'), From ee5e3faa4589a5e4cd2cd1bde2c8e031fcd2c134 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Wed, 8 Jul 2026 19:43:03 +0530 Subject: [PATCH 18/38] docs: refresh WDK CLI draft against merged source --- content/docs/ai/wdk-cli/commands.mdx | 6 +++++- content/docs/ai/wdk-cli/configuration.mdx | 4 ++-- content/docs/ai/wdk-cli/get-started.mdx | 11 +++++------ content/docs/ai/wdk-cli/index.mdx | 10 +++++----- content/docs/ai/wdk-cli/mcp.mdx | 4 ++-- content/docs/start-building/build-with-ai.mdx | 7 +++++-- public/llms.txt | 12 +++++++++--- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/content/docs/ai/wdk-cli/commands.mdx b/content/docs/ai/wdk-cli/commands.mdx index 11705fe4..3543f6f0 100644 --- a/content/docs/ai/wdk-cli/commands.mdx +++ b/content/docs/ai/wdk-cli/commands.mdx @@ -17,6 +17,10 @@ WDK CLI exposes one root command, `wdk`, plus the `wdk-mcp` and `wdk-daemon` bin | `--wallet NAME` | Wallet-dependent commands | Use a named wallet instead of the default wallet | | `--index N` | Address, balance, history, send, buy, sell | Use a BIP account index instead of the configured default | + +`wdk wallet create --json` and `wdk wallet export --json` can print seed phrases. Treat that output as secret material. Do not log it, paste it into agent transcripts, store it in CI output, or use it with production funds. + + ## Wallets | Command | Purpose | @@ -160,7 +164,7 @@ Options: | Option | Description | | --- | --- | -| `--module MODULE` | Fiat provider. The current source defaults to `moonpay` | +| `--module MODULE` | Fiat provider. The CLI defaults to `moonpay` | | `--fiat-currency CURRENCY` | Fiat currency code, default `usd` | | `--fiat-amount VALUE` | Fiat amount. Mutually exclusive with `--crypto-amount` | | `--crypto-amount VALUE` | Crypto amount. Mutually exclusive with `--fiat-amount` | diff --git a/content/docs/ai/wdk-cli/configuration.mdx b/content/docs/ai/wdk-cli/configuration.mdx index 9e8f6dde..49a0cbbb 100644 --- a/content/docs/ai/wdk-cli/configuration.mdx +++ b/content/docs/ai/wdk-cli/configuration.mdx @@ -33,7 +33,7 @@ wdk config path | `WDK_INDEXER_API_KEY` | `indexer.apiKey` | Overrides the stored indexer API key at read time | -The current CLI source maps only `WDK_INDEXER_API_KEY` as an indexer environment override. Configure the indexer base URL with `wdk config set --key indexer.baseUrl --value ...` if you need to change it. +The CLI currently maps only `WDK_INDEXER_API_KEY` as an indexer environment override. Configure the indexer base URL with `wdk config set --key indexer.baseUrl --value ...` if you need to change it. ## Wallet Sessions @@ -202,7 +202,7 @@ Built-in tokens can be overridden by a custom token with the same ticker. Removi ## MoonPay Buy/Sell -`wdk buy` and `wdk sell` use MoonPay as the fiat provider in the current source. Configure all three values: +`wdk buy` and `wdk sell` use MoonPay as the fiat provider. Configure all three values: ```bash title="Terminal" wdk config set --key ramp.moonpay.apiKey --value diff --git a/content/docs/ai/wdk-cli/get-started.mdx b/content/docs/ai/wdk-cli/get-started.mdx index 967a49b3..17beef32 100644 --- a/content/docs/ai/wdk-cli/get-started.mdx +++ b/content/docs/ai/wdk-cli/get-started.mdx @@ -7,14 +7,14 @@ icon: Rocket --- -WDK CLI is not release-installable yet. The implementation is under review in [tetherto/wdk-cli#1](https://github.com/tetherto/wdk-cli/pull/1), and the public `wdk-cli` npm package is still a placeholder. Install from source until a real package is published. +WDK CLI is not release-installable yet. The source has been merged into [tetherto/wdk-cli](https://github.com/tetherto/wdk-cli), but the public `wdk-cli` npm package still resolves to a placeholder `0.0.0` release. Install from source until a real package is published. ## Prerequisites - Node.js `>=22.18.0` - npm -- GitHub CLI, if you want to check out the pull request directly +- GitHub CLI or Git for cloning the source repository - A dedicated development wallet seed with limited or no funds Check your Node.js version: @@ -23,14 +23,13 @@ Check your Node.js version: node --version ``` -## Install From The Source PR +## Install From GitHub Source -Clone the repository and check out the active CLI pull request: +Clone the repository and install the merged source: ```bash title="Terminal" gh repo clone tetherto/wdk-cli cd wdk-cli -gh pr checkout 1 npm install npm link ``` @@ -45,7 +44,7 @@ wdk wallet --help ``` -If you cannot use GitHub CLI, clone the pull request source branch from GitHub and run the same `npm install` and `npm link` commands from that checkout. +If you cannot use GitHub CLI, run `git clone https://github.com/tetherto/wdk-cli.git` and then run the same `npm install` and `npm link` commands from that checkout. ## Create A Development Wallet diff --git a/content/docs/ai/wdk-cli/index.mdx b/content/docs/ai/wdk-cli/index.mdx index 30d07854..a6c7f977 100644 --- a/content/docs/ai/wdk-cli/index.mdx +++ b/content/docs/ai/wdk-cli/index.mdx @@ -15,7 +15,7 @@ WDK CLI is a local wallet command line interface for humans, scripts, and AI age | `wdk-mcp` | Exposes wallet operations as Model Context Protocol tools for MCP-compatible AI clients | -**Draft source install only** - The WDK CLI implementation is currently under review in [tetherto/wdk-cli#1](https://github.com/tetherto/wdk-cli/pull/1). Use the source-install flow until the module is merged and a non-placeholder package is published. +**Source install until package release** - WDK CLI has been merged into [tetherto/wdk-cli](https://github.com/tetherto/wdk-cli), but the public `wdk-cli` npm package still resolves to a placeholder `0.0.0` release. Install from source until a real package release is published. Use WDK CLI when you want a ready-made local wallet and daemon. Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building your own MCP server or embedding WDK tools in an application. @@ -26,12 +26,12 @@ Use WDK CLI when you want a ready-made local wallet and daemon. Use the [MCP Too - Derive addresses, read balances, and query transaction history - Preview transfers with `--dry-run`, then send native or registered token transfers after confirmation - Configure networks, tokens, indexer access, and MoonPay on/off-ramp settings -- Run all commands with `--json` for agent or script consumption +- Run commands with `--json` for agent or script consumption, while treating wallet create/export output as secret material - Register `wdk-mcp` with Claude Desktop, Claude Code, or OpenClaw ## Architecture -The CLI and MCP server are thin clients. They do not keep seed phrases or private keys themselves. Wallet operations go through the daemon over local IPC. +For daemon-backed wallet operations, the CLI and MCP server are thin clients. They send wallet requests to the daemon over local IPC instead of keeping unlocked wallet instances themselves. ```text wdk CLI -> local IPC -> wdk-daemon -> WDK wallet modules -> blockchain RPC/indexer @@ -53,7 +53,7 @@ The CLI and MCP server share the same daemon and wallet store. You can create an ## Safety Model -Use a dedicated development wallet with limited funds while these docs and the CLI module are still in draft. Do not use a production seed phrase to test unmerged code. +Use a dedicated development wallet with limited funds while installing WDK CLI from source. Do not use a production seed phrase to test a local source checkout. Wallet seed files are stored under `~/.config/wdk-cli/wallets/NAME/seed.enc` by default, or under `$XDG_CONFIG_HOME/wdk-cli/wallets/NAME/seed.enc` when `XDG_CONFIG_HOME` is set. If you provide a passphrase, the seed is encrypted before it is written to disk. @@ -62,7 +62,7 @@ Unlocking a wallet sends the passphrase to the daemon over local IPC, initialize -Install from the source PR, create a development wallet, and run the first read and dry-run send commands +Install from source, create a development wallet, and run the first read and dry-run send commands Configure wallets, sessions, networks, tokens, indexer access, and MoonPay settings diff --git a/content/docs/ai/wdk-cli/mcp.mdx b/content/docs/ai/wdk-cli/mcp.mdx index e28179a5..3b4b1e24 100644 --- a/content/docs/ai/wdk-cli/mcp.mdx +++ b/content/docs/ai/wdk-cli/mcp.mdx @@ -9,7 +9,7 @@ icon: Wand WDK CLI includes `wdk-mcp`, a Model Context Protocol server that exposes local wallet actions as structured tools. The MCP server talks to the same `wdk-daemon` used by the CLI. -Create and unlock a dedicated development wallet before connecting an AI client. Do not test unmerged wallet code with a production seed phrase or meaningful funds. +Create and unlock a dedicated development wallet before connecting an AI client. Do not connect a production seed phrase or meaningful funds to a local source checkout. ## Prepare The Wallet @@ -28,7 +28,7 @@ Use a short TTL while you are testing. Increase the TTL only when your local age ## Set Up An AI Tool -WDK CLI supports three setup targets in the current source: +WDK CLI supports three setup targets: ```bash title="Terminal" wdk mcp setup --ai-tool claude-desktop diff --git a/content/docs/start-building/build-with-ai.mdx b/content/docs/start-building/build-with-ai.mdx index 9639f017..987116b9 100644 --- a/content/docs/start-building/build-with-ai.mdx +++ b/content/docs/start-building/build-with-ai.mdx @@ -11,8 +11,11 @@ WDK documentation is optimized for AI coding assistants. Give your AI tool conte Use this page as the starting point for building WDK apps with AI assistance. + +Run a ready-made local wallet CLI, daemon, and MCP server for AI agents + -Give AI agents access to WDK wallet tools through MCP +Build a custom MCP server that exposes WDK wallet tools Use WDK instructions with agentic coding tools @@ -31,7 +34,7 @@ There are two practical ways to provide WDK context to your AI: 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. +**Want to give AI agents wallet access?** Use [WDK CLI](/ai/wdk-cli/) for a ready-made local wallet CLI, daemon, and MCP server. Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. --- diff --git a/public/llms.txt b/public/llms.txt index 945fe95b..b6915244 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -4,12 +4,18 @@ Base URL: https://docs.wdk.tether.io Raw markdown is available by appending `.md` to any documentation page URL. - Welcome to WDK: https://docs.wdk.tether.io/ -- Agent Skills: https://docs.wdk.tether.io/ai/agent-skills +- Build with AI: https://docs.wdk.tether.io/start-building/build-with-ai +- WDK CLI: https://docs.wdk.tether.io/ai/wdk-cli +- Get Started: https://docs.wdk.tether.io/ai/wdk-cli/get-started +- Configuration: https://docs.wdk.tether.io/ai/wdk-cli/configuration +- Commands: https://docs.wdk.tether.io/ai/wdk-cli/commands +- MCP: https://docs.wdk.tether.io/ai/wdk-cli/mcp - MCP Toolkit: https://docs.wdk.tether.io/ai/mcp-toolkit -- API Reference: https://docs.wdk.tether.io/ai/mcp-toolkit/api-reference -- Configuration: https://docs.wdk.tether.io/ai/mcp-toolkit/configuration - Get Started: https://docs.wdk.tether.io/ai/mcp-toolkit/get-started +- Configuration: https://docs.wdk.tether.io/ai/mcp-toolkit/configuration +- API Reference: https://docs.wdk.tether.io/ai/mcp-toolkit/api-reference - LangChain Integration: https://docs.wdk.tether.io/ai/mcp-toolkit/langchain +- Agent Skills: https://docs.wdk.tether.io/ai/agent-skills - OpenClaw (Community Skill): https://docs.wdk.tether.io/ai/openclaw - x402: https://docs.wdk.tether.io/ai/x402 - React Native Starter (Alpha): https://docs.wdk.tether.io/examples-and-starters/react-native-starter From 1eaed45bc57cf4957f2da6d924fb9eb0995365c4 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Tue, 28 Jul 2026 19:36:17 +0530 Subject: [PATCH 19/38] docs: complete WDK CLI documentation --- content/docs/ai/agent-skills.mdx | 8 +- content/docs/ai/mcp-toolkit/index.mdx | 2 +- content/docs/ai/openclaw.mdx | 4 +- content/docs/ai/wdk-cli/commands.mdx | 196 -- content/docs/ai/wdk-cli/configuration.mdx | 219 -- content/docs/ai/wdk-cli/get-started.mdx | 138 - content/docs/ai/wdk-cli/index.mdx | 82 - content/docs/ai/wdk-cli/mcp.mdx | 135 - content/docs/cli/api-reference.mdx | 366 +++ content/docs/cli/configuration.mdx | 234 ++ content/docs/cli/guides/custom-networks.mdx | 155 + content/docs/cli/guides/get-started.mdx | 134 + content/docs/cli/guides/handle-errors.mdx | 138 + content/docs/cli/guides/manage-tokens.mdx | 138 + content/docs/cli/guides/manage-wallets.mdx | 232 ++ content/docs/cli/guides/use-mcp-server.mdx | 158 ++ content/docs/cli/index.mdx | 98 + content/docs/cli/reference/architecture.mdx | 131 + content/docs/cli/reference/security-model.mdx | 168 ++ content/docs/cli/reference/storage-format.mdx | 295 ++ content/docs/overview/changelog.mdx | 7 + content/docs/start-building/build-with-ai.mdx | 4 +- public/llms-full.txt | 2523 ++++++++++++++++- public/llms.txt | 21 +- src/lib/custom-tree.ts | 25 +- 25 files changed, 4754 insertions(+), 857 deletions(-) delete mode 100644 content/docs/ai/wdk-cli/commands.mdx delete mode 100644 content/docs/ai/wdk-cli/configuration.mdx delete mode 100644 content/docs/ai/wdk-cli/get-started.mdx delete mode 100644 content/docs/ai/wdk-cli/index.mdx delete mode 100644 content/docs/ai/wdk-cli/mcp.mdx create mode 100644 content/docs/cli/api-reference.mdx create mode 100644 content/docs/cli/configuration.mdx create mode 100644 content/docs/cli/guides/custom-networks.mdx create mode 100644 content/docs/cli/guides/get-started.mdx create mode 100644 content/docs/cli/guides/handle-errors.mdx create mode 100644 content/docs/cli/guides/manage-tokens.mdx create mode 100644 content/docs/cli/guides/manage-wallets.mdx create mode 100644 content/docs/cli/guides/use-mcp-server.mdx create mode 100644 content/docs/cli/index.mdx create mode 100644 content/docs/cli/reference/architecture.mdx create mode 100644 content/docs/cli/reference/security-model.mdx create mode 100644 content/docs/cli/reference/storage-format.mdx diff --git a/content/docs/ai/agent-skills.mdx b/content/docs/ai/agent-skills.mdx index 1ba0fbeb..87e895dd 100644 --- a/content/docs/ai/agent-skills.mdx +++ b/content/docs/ai/agent-skills.mdx @@ -9,7 +9,7 @@ icon: Brain WDK provides agent skills: structured instruction sets that teach AI agents how to create wallets, send transactions, swap tokens, bridge assets, and interact with DeFi protocols across 20+ blockchains. All operations are self-custodial. Keys stay on your machine, with no third-party custody dependency. -**Skill vs MCP Toolkit vs WDK CLI**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. Use [WDK CLI](/ai/wdk-cli/) when you want a ready-made local wallet CLI, daemon, and MCP server. +**Skill vs MCP Toolkit vs WDK CLI**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. Use [WDK CLI](/cli/) when you want a ready-made local wallet CLI, daemon, and MCP server. ## What Are Agent Skills? @@ -66,8 +66,8 @@ WDK's agent skills use a self-custodial model where your agent controls its own | **OpenClaw** | Install from [ClawHub](/ai/openclaw/) or clone to workspace. See [OpenClaw Integration](/ai/openclaw/) | | **Claude** | Upload `SKILL.md` as project knowledge, or paste into conversation | | **Cursor / Windsurf** | Clone to `.cursor/skills/wdk` or `.windsurf/skills/wdk` | -| **Any MCP-compatible agent** | Use [WDK CLI](/ai/wdk-cli/mcp/) for a ready-made local daemon, or the [MCP Toolkit](/ai/mcp-toolkit/) for a custom MCP server | -| **Any local CLI-capable agent** | Use [WDK CLI](/ai/wdk-cli/) with `--json` output | +| **Any MCP-compatible agent** | Use [WDK CLI](/cli/guides/use-mcp-server/) for a ready-made local daemon, or the [MCP Toolkit](/ai/mcp-toolkit/) for a custom MCP server | +| **Any local CLI-capable agent** | Use [WDK CLI](/cli/) with `--json` output | | **Any other agent** | Copy `SKILL.md` into system prompt or conversation context | ## Community Projects @@ -85,7 +85,7 @@ Community-built projects using WDK's agentic capabilities: - [WDK SKILL.md on GitHub](https://github.com/tetherto/wdk-docs/blob/main/skills/wdk/SKILL.md) - The full skill file agents consume - [WDK Skill on ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit) - Install the skill - [AgentSkills Specification](https://agentskills.io/specification) - The skill format standard -- [WDK CLI](/ai/wdk-cli/) - Local CLI, daemon, and MCP server for AI agents +- [WDK CLI](/cli/) - Local CLI, daemon, and MCP server for AI agents - [WDK MCP Toolkit](https://github.com/tetherto/wdk-mcp-toolkit) - MCP server for structured tool calling - [WDK Core](https://github.com/tetherto/wdk-core) - The core SDK diff --git a/content/docs/ai/mcp-toolkit/index.mdx b/content/docs/ai/mcp-toolkit/index.mdx index ffc1019a..9cc19622 100644 --- a/content/docs/ai/mcp-toolkit/index.mdx +++ b/content/docs/ai/mcp-toolkit/index.mdx @@ -60,7 +60,7 @@ All 35 built-in MCP tools and the WdkMcpServer class Use WDK tools in LangChain agents via the serve CLI - + Use a ready-made local wallet CLI, daemon, and MCP server when you do not need to build a custom MCP server diff --git a/content/docs/ai/openclaw.mdx b/content/docs/ai/openclaw.mdx index 968a4a4c..924a9c3e 100644 --- a/content/docs/ai/openclaw.mdx +++ b/content/docs/ai/openclaw.mdx @@ -19,7 +19,7 @@ The WDK community skill follows the [AgentSkills specification](https://agentski -If you want OpenClaw to call a ready-made local wallet daemon through MCP instead of loading a file-based skill, see [WDK CLI MCP setup](/ai/wdk-cli/mcp/). +If you want OpenClaw to call a ready-made local wallet daemon through MCP instead of loading a file-based skill, see [WDK CLI MCP setup](/cli/guides/use-mcp-server/). ## Install the WDK Community Skill @@ -117,7 +117,7 @@ OpenClaw makes use of artificial intelligence and machine learning technologies. ## Next Steps - [Agent Skills](/ai/agent-skills/) - Full capabilities, how skills work, and a comparison with other agentic wallet solutions -- [WDK CLI MCP](/ai/wdk-cli/mcp/) - Configure OpenClaw with the bundled WDK CLI MCP server +- [WDK CLI MCP](/cli/guides/use-mcp-server/) - Configure OpenClaw with the bundled WDK CLI MCP server - [MCP Toolkit](/ai/mcp-toolkit/) - Programmatic wallet access for MCP-compatible agents - [OpenClaw Skills Documentation](https://docs.openclaw.ai/tools/skills) - How OpenClaw discovers and loads skills diff --git a/content/docs/ai/wdk-cli/commands.mdx b/content/docs/ai/wdk-cli/commands.mdx deleted file mode 100644 index 3543f6f0..00000000 --- a/content/docs/ai/wdk-cli/commands.mdx +++ /dev/null @@ -1,196 +0,0 @@ ---- -title: Commands -description: WDK CLI command reference for wallets, reads, sends, config, networks, tokens, ramps, and MCP -docType: reference -schemaType: TechArticle -icon: ListChecks ---- - -WDK CLI exposes one root command, `wdk`, plus the `wdk-mcp` and `wdk-daemon` binaries used by the MCP server and daemon. - -## Global Flags - -| Flag | Applies to | Description | -| --- | --- | --- | -| `--json` | All commands | Print machine-readable JSON output where supported | -| `--verbose` | All commands | Print debug output and full errors | -| `--wallet NAME` | Wallet-dependent commands | Use a named wallet instead of the default wallet | -| `--index N` | Address, balance, history, send, buy, sell | Use a BIP account index instead of the configured default | - - -`wdk wallet create --json` and `wdk wallet export --json` can print seed phrases. Treat that output as secret material. Do not log it, paste it into agent transcripts, store it in CI output, or use it with production funds. - - -## Wallets - -| Command | Purpose | -| --- | --- | -| `wdk wallet create --name NAME --words WORD_COUNT` | Create a new named wallet. `WORD_COUNT` is `12` or `24` | -| `wdk wallet import --name NAME` | Import an existing seed phrase | -| `wdk wallet export --name NAME` | Display the seed phrase for an existing wallet | -| `wdk wallet list` | List wallets and their lock/default status | -| `wdk wallet unlock --name NAME --ttl MINUTES` | Unlock a wallet and start the daemon if needed | -| `wdk wallet lock --name NAME` | Lock one wallet | -| `wdk wallet lock --all` | Lock every wallet and stop the daemon when none remain unlocked | -| `wdk wallet delete --name NAME` | Delete a wallet after passphrase confirmation | -| `wdk wallet default --name NAME` | Set the default wallet | -| `wdk wallet rename --name OLD --new-name NEW` | Rename a wallet | - -Example flow: - -```bash title="Terminal" -wdk wallet create --name dev --words 24 -wdk wallet unlock --name dev --ttl 5 -wdk wallet default --name dev -wdk wallet list -``` - -Commands that need a passphrase prompt can use `WDK_PASSPHRASE` for non-interactive local automation: - -```bash title="Terminal" -WDK_PASSPHRASE= wdk wallet unlock --name dev --json -``` - -## Read Commands - -| Command | Purpose | -| --- | --- | -| `wdk get address --network NETWORK` | Derive an address for one network | -| `wdk get address --all` | Derive addresses for all mainnet networks | -| `wdk get address --all --testnet` | Include testnet networks | -| `wdk get balance --network NETWORK` | Read native balance for one network | -| `wdk get balance --network NETWORK --token TICKER` | Read a registered token balance | -| `wdk get balance --all` | Read balances across mainnet networks | -| `wdk get history --network NETWORK` | Read transaction history through the indexer | -| `wdk get history --network NETWORK --token TICKER --limit 50` | Filter history by registered token and limit | - -Examples: - -```bash title="Terminal" -wdk get address --network ethereum --wallet dev -wdk get balance --network ethereum --token usdt --wallet dev -wdk get history --network ethereum --from-date 2026-01-01 --to-date 2026-03-31 -``` - -`get history` requires indexer support for the network and an indexer API key when the endpoint requires one. - -## Send - -`wdk send` supports native and registered token transfers. Amounts are decimal by default. - -| Option | Required | Description | -| --- | --- | --- | -| `--network NETWORK` | Yes | Network to send on | -| `--to ADDRESS` | Yes | Recipient address | -| `--amount AMOUNT` | Yes | Decimal amount unless `--base-units` is set | -| `--token TICKER` | No | Registered token ticker. Omit for native transfers | -| `--base-units` | No | Treat `--amount` as raw base units | -| `--dry-run` | No | Preview the transfer without broadcasting | - -Preview first: - -```bash title="Terminal" -wdk send \ - --network ethereum \ - --to 0x000000000000000000000000000000000000dEaD \ - --amount 0.001 \ - --dry-run -``` - -Send a token transfer after checking the preview: - -```bash title="Terminal" -wdk send \ - --network ethereum \ - --token usdt \ - --to 0x000000000000000000000000000000000000dEaD \ - --amount 1.5 -``` - -Use `--base-units` only when your script already has wei, satoshi, lamports, or another token base-unit value. - -## Config - -| Command | Purpose | -| --- | --- | -| `wdk config get --all` | Print all config | -| `wdk config get --key KEY` | Print a single key | -| `wdk config get --network NETWORK` | Print network-scoped config | -| `wdk config set --key KEY --value VALUE` | Set a global key | -| `wdk config set --network NETWORK --key KEY --value VALUE` | Set a network key | -| `wdk config reset --key KEY` | Reset one key | -| `wdk config reset --all` | Reset stored config while preserving custom networks, custom tokens, and default wallet | -| `wdk config path` | Print the config file path | - -Network config updates lock wallets so new SDK settings take effect on the next unlock. - -## Networks - -| Command | Purpose | -| --- | --- | -| `wdk network list` | List built-in and custom networks | -| `wdk network list --mainnet` | Show only mainnets | -| `wdk network list --testnet` | Show only testnets | -| `wdk network info --network NETWORK` | Show one network's details | -| `wdk network create JSON_OR_FILE` | Add a custom network | -| `wdk network delete --name NAME` | Delete a custom network | - -Custom network data can be an inline JSON string or a path to a JSON file: - -```bash title="Terminal" -wdk network create ./optimism.json -``` - -## Tokens - -| Command | Purpose | -| --- | --- | -| `wdk token list` | List all registered tokens | -| `wdk token list --network NETWORK` | List tokens for one network | -| `wdk token info --network NETWORK --token TICKER` | Show one token entry | -| `wdk token add JSON_OR_FILE` | Add or override a custom token | -| `wdk token delete --network NETWORK --token TICKER` | Delete a custom token | - -The `--token` flag on `get balance`, `get history`, `send`, `buy`, and `sell` resolves through this registry. - -## Buy And Sell - -| Command | Purpose | -| --- | --- | -| `wdk buy --network NETWORK --token TICKER` | Create a MoonPay buy URL | -| `wdk sell --network NETWORK --token TICKER` | Create a MoonPay sell URL | - -Options: - -| Option | Description | -| --- | --- | -| `--module MODULE` | Fiat provider. The CLI defaults to `moonpay` | -| `--fiat-currency CURRENCY` | Fiat currency code, default `usd` | -| `--fiat-amount VALUE` | Fiat amount. Mutually exclusive with `--crypto-amount` | -| `--crypto-amount VALUE` | Crypto amount. Mutually exclusive with `--fiat-amount` | - -Examples: - -```bash title="Terminal" -wdk buy --network ethereum --token usdt --fiat-amount 100 -wdk sell --network polygon --token usdt --crypto-amount 50 -``` - -## MCP - -| Command | Purpose | -| --- | --- | -| `wdk mcp setup --ai-tool claude-desktop` | Register `wdk-mcp` with Claude Desktop | -| `wdk mcp setup --ai-tool claude-code` | Register `wdk-mcp` with Claude Code | -| `wdk mcp setup --ai-tool openclaw` | Register `wdk-mcp` with OpenClaw | -| `wdk mcp verify-setup --ai-tool TOOL` | Verify the MCP setup for one tool | -| `wdk mcp remove --ai-tool TOOL` | Remove the WDK MCP server from one tool | -| `wdk mcp list` | Show setup status for supported AI tools | - -See [MCP](/ai/wdk-cli/mcp/) for the MCP tool list and the required two-step `send_token` flow. - -*** - -## Need Help? - - diff --git a/content/docs/ai/wdk-cli/configuration.mdx b/content/docs/ai/wdk-cli/configuration.mdx deleted file mode 100644 index 49a0cbbb..00000000 --- a/content/docs/ai/wdk-cli/configuration.mdx +++ /dev/null @@ -1,219 +0,0 @@ ---- -title: Configuration -description: Configure WDK CLI wallets, sessions, networks, tokens, indexer access, and ramps -docType: reference -schemaType: TechArticle -icon: Settings ---- - -WDK CLI stores wallet files and user configuration locally. Read commands can inspect configuration without an unlocked wallet. Write commands such as `config set`, `config reset`, `network create`, `network delete`, `token add`, and `token delete` require passphrase confirmation or an unlocked wallet depending on the operation. - -## Local Paths - -| Data | Default path | -| --- | --- | -| Config directory | `~/.config/wdk-cli` | -| Wallet seed file | `~/.config/wdk-cli/wallets/NAME/seed.enc` | -| Daemon socket | `~/.config/wdk-cli/daemon.sock` on Unix-like systems | -| Daemon PID file | `~/.config/wdk-cli/daemon.pid` | - -When `XDG_CONFIG_HOME` is set, the config directory becomes `$XDG_CONFIG_HOME/wdk-cli`. - -Use the CLI to print the exact config file path for your system: - -```bash title="Terminal" -wdk config path -``` - -## Environment Variables - -| Variable | Applies to | Notes | -| --- | --- | --- | -| `WDK_PASSPHRASE` | Passphrase-protected commands | Skips the interactive passphrase prompt for automation | -| `WDK_INDEXER_API_KEY` | `indexer.apiKey` | Overrides the stored indexer API key at read time | - - -The CLI currently maps only `WDK_INDEXER_API_KEY` as an indexer environment override. Configure the indexer base URL with `wdk config set --key indexer.baseUrl --value ...` if you need to change it. - - -## Wallet Sessions - -Unlocking a wallet starts the daemon if it is not already running: - -```bash title="Terminal" -wdk wallet unlock --name dev --ttl 5 -``` - -Each wallet has its own TTL. The default is 5 minutes. - -| TTL | Behavior | -| --- | --- | -| omitted | Uses the default 5 minute session | -| `--ttl 60` | Keeps that wallet unlocked for 60 minutes | -| `--ttl 0` | Keeps that wallet unlocked until you lock it or the daemon exits | - -Lock a wallet after use: - -```bash title="Terminal" -wdk wallet lock --name dev -``` - -## Global Config - -Read all config: - -```bash title="Terminal" -wdk config get --all -``` - -Read a single key: - -```bash title="Terminal" -wdk config get --key indexer.apiKey -``` - -Set a single key: - -```bash title="Terminal" -wdk config set --key indexer.apiKey --value -``` - -Set structured values by passing JSON: - -```bash title="Terminal" -wdk config set \ - --key ramp.moonpay \ - --value '{"apiKey":"pk_test_...","signUrl":"https://example.com/sign","environment":"sandbox"}' -``` - -Reset a key: - -```bash title="Terminal" -wdk config reset --key ramp.moonpay.apiKey -``` - -Reset all config: - -```bash title="Terminal" -wdk config reset --all -``` - -`config reset --all` preserves default wallet, custom network, and custom token state. - -## Network Config - -Read one network's config: - -```bash title="Terminal" -wdk config get --network ethereum -``` - -Set a network provider: - -```bash title="Terminal" -wdk config set \ - --network ethereum \ - --key provider \ - --value https://eth-mainnet.g.alchemy.com/v2/ -``` - -Set the full network config with JSON: - -```bash title="Terminal" -wdk config set \ - --network ethereum \ - --value '{"provider":"https://eth-mainnet.g.alchemy.com/v2/","chainId":1}' -``` - -Changing `networks.*` config locks all wallets so stale wallet managers are dropped. Unlock the wallet again before continuing: - -```bash title="Terminal" -wdk wallet unlock --name dev -``` - -## Indexer - -Transaction history uses the WDK Indexer API. Set the API key in config or use the environment variable: - -```bash title="Terminal" -wdk config set --key indexer.apiKey --value -``` - -```bash title="Terminal" -WDK_INDEXER_API_KEY= wdk get history --network ethereum --wallet dev -``` - -If you need a custom indexer endpoint, store it in config: - -```bash title="Terminal" -wdk config set --key indexer.baseUrl --value https://indexer.example.com -``` - -## Custom Networks - -Create a custom network from inline JSON: - -```bash title="Terminal" -wdk network create '{"network":"optimism","module":"@tetherto/wdk-wallet-evm","indexerSlug":"ethereum","config":{"provider":"https://mainnet.optimism.io","chainId":10}}' -``` - -Or create it from a file: - -```bash title="Terminal" -wdk network create ./optimism.json -``` - -Custom network specs can include `tokens` so the network and token registry update together. If a token entry fails validation, the network is not created. - -Delete only custom networks: - -```bash title="Terminal" -wdk network delete --name optimism -``` - -## Custom Tokens - -Add a token from inline JSON: - -```bash title="Terminal" -wdk token add '{"network":"ethereum","token":"dai","symbol":"DAI","decimals":18,"isNative":false,"address":"0x6B175474E89094C44Da98b954EedeAC495271d0F"}' -``` - -Or add it from a file: - -```bash title="Terminal" -wdk token add ./dai-on-ethereum.json -``` - -List tokens: - -```bash title="Terminal" -wdk token list --network ethereum -wdk token info --network ethereum --token usdt -``` - -Delete only custom tokens: - -```bash title="Terminal" -wdk token delete --network ethereum --token dai -``` - -Built-in tokens can be overridden by a custom token with the same ticker. Removing the custom token restores the built-in entry. - -## MoonPay Buy/Sell - -`wdk buy` and `wdk sell` use MoonPay as the fiat provider. Configure all three values: - -```bash title="Terminal" -wdk config set --key ramp.moonpay.apiKey --value -wdk config set --key ramp.moonpay.signUrl --value https://example.com/moonpay/sign -wdk config set --key ramp.moonpay.environment --value sandbox -``` - -The environment must be `sandbox` or `production`. The CLI rejects testnet purchases with a production MoonPay environment, and rejects mainnet purchases with a sandbox environment. - -*** - -## Need Help? - - diff --git a/content/docs/ai/wdk-cli/get-started.mdx b/content/docs/ai/wdk-cli/get-started.mdx deleted file mode 100644 index 17beef32..00000000 --- a/content/docs/ai/wdk-cli/get-started.mdx +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Get Started -description: Install WDK CLI from source and run a local development wallet -docType: getting-started -schemaType: TechArticle -icon: Rocket ---- - - -WDK CLI is not release-installable yet. The source has been merged into [tetherto/wdk-cli](https://github.com/tetherto/wdk-cli), but the public `wdk-cli` npm package still resolves to a placeholder `0.0.0` release. Install from source until a real package is published. - - -## Prerequisites - -- Node.js `>=22.18.0` -- npm -- GitHub CLI or Git for cloning the source repository -- A dedicated development wallet seed with limited or no funds - -Check your Node.js version: - -```bash title="Terminal" -node --version -``` - -## Install From GitHub Source - -Clone the repository and install the merged source: - -```bash title="Terminal" -gh repo clone tetherto/wdk-cli -cd wdk-cli -npm install -npm link -``` - -`npm link` makes the `wdk`, `wdk-mcp`, and `wdk-daemon` binaries available on your local PATH. - -Verify the CLI is available: - -```bash title="Terminal" -wdk --help -wdk wallet --help -``` - - -If you cannot use GitHub CLI, run `git clone https://github.com/tetherto/wdk-cli.git` and then run the same `npm install` and `npm link` commands from that checkout. - - -## Create A Development Wallet - -Create a named wallet. The CLI prompts for a passphrase interactively. - -```bash title="Terminal" -wdk wallet create --name dev --words 24 -wdk wallet default --name dev -``` - -Unlock the wallet before reading balances or sending transactions: - -```bash title="Terminal" -wdk wallet unlock --name dev --ttl 5 -``` - -The daemon keeps the unlocked wallet in memory for the requested TTL. The default TTL is 5 minutes. Use `--ttl 0` only when a local agent session needs a wallet to stay unlocked and you accept that risk. - -## Read An Address And Balance - -Get an address for a single network: - -```bash title="Terminal" -wdk get address --network ethereum --wallet dev -``` - -Check the native balance: - -```bash title="Terminal" -wdk get balance --network ethereum --wallet dev -``` - -Use `--json` when another program or AI agent needs structured output: - -```bash title="Terminal" -wdk get balance --network ethereum --wallet dev --json -``` - -## Preview A Send - -Always start with a dry run. This estimates the transfer and fee without broadcasting a transaction. - -```bash title="Terminal" -wdk send \ - --network ethereum \ - --to 0x000000000000000000000000000000000000dEaD \ - --amount 0.001 \ - --wallet dev \ - --dry-run -``` - -Token transfers use registered token tickers: - -```bash title="Terminal" -wdk send \ - --network ethereum \ - --token usdt \ - --to 0x000000000000000000000000000000000000dEaD \ - --amount 1.5 \ - --wallet dev \ - --dry-run -``` - -Only remove `--dry-run` after you have checked the preview and intend to broadcast the transaction. - -## Lock The Wallet - -Lock the wallet when the session is finished: - -```bash title="Terminal" -wdk wallet lock --name dev -``` - -Lock every unlocked wallet: - -```bash title="Terminal" -wdk wallet lock --all -``` - -## Next Steps - -- [Configuration](/ai/wdk-cli/configuration/) - Configure indexer access, providers, custom networks, tokens, and MoonPay -- [Commands](/ai/wdk-cli/commands/) - Review every command group -- [MCP](/ai/wdk-cli/mcp/) - Connect the same wallet daemon to Claude Desktop, Claude Code, or OpenClaw - -*** - -## Need Help? - - diff --git a/content/docs/ai/wdk-cli/index.mdx b/content/docs/ai/wdk-cli/index.mdx deleted file mode 100644 index a6c7f977..00000000 --- a/content/docs/ai/wdk-cli/index.mdx +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: WDK CLI -description: Run a local WDK wallet CLI, daemon, and MCP server for AI agents -docType: explanation -schemaType: TechArticle -icon: Terminal ---- - -WDK CLI is a local wallet command line interface for humans, scripts, and AI agents. It wraps WDK wallet modules in three local processes: - -| Process | What it does | -| --- | --- | -| `wdk` | Parses commands, prints human-readable or JSON output, and sends wallet requests to the daemon | -| `wdk-daemon` | Holds unlocked WDK wallet instances in memory, derives addresses, signs transactions, and locks wallets after their TTL | -| `wdk-mcp` | Exposes wallet operations as Model Context Protocol tools for MCP-compatible AI clients | - - -**Source install until package release** - WDK CLI has been merged into [tetherto/wdk-cli](https://github.com/tetherto/wdk-cli), but the public `wdk-cli` npm package still resolves to a placeholder `0.0.0` release. Install from source until a real package release is published. - - -Use WDK CLI when you want a ready-made local wallet and daemon. Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building your own MCP server or embedding WDK tools in an application. - -## What You Can Do - -- Create, import, rename, unlock, lock, and delete named wallets -- Derive addresses, read balances, and query transaction history -- Preview transfers with `--dry-run`, then send native or registered token transfers after confirmation -- Configure networks, tokens, indexer access, and MoonPay on/off-ramp settings -- Run commands with `--json` for agent or script consumption, while treating wallet create/export output as secret material -- Register `wdk-mcp` with Claude Desktop, Claude Code, or OpenClaw - -## Architecture - -For daemon-backed wallet operations, the CLI and MCP server are thin clients. They send wallet requests to the daemon over local IPC instead of keeping unlocked wallet instances themselves. - -```text -wdk CLI -> local IPC -> wdk-daemon -> WDK wallet modules -> blockchain RPC/indexer -wdk-mcp MCP -> local IPC -> wdk-daemon -> WDK wallet modules -> blockchain RPC/indexer -``` - -The daemon starts when a wallet is unlocked. It keeps each unlocked wallet in memory until the wallet is locked, the TTL expires, or the last wallet is locked and the daemon exits. - -## CLI vs MCP - -| Interface | Use it when | Example | -| --- | --- | --- | -| CLI | Your agent or script can run local shell commands | `wdk get balance --network ethereum --json` | -| MCP | Your AI client supports MCP tools and should call structured wallet actions | `wdk mcp setup --ai-tool claude-desktop` | -| MCP Toolkit | You need to build and customize your own MCP server in code | `new WdkMcpServer(...)` | - -The CLI and MCP server share the same daemon and wallet store. You can create and unlock a wallet with `wdk`, then use the MCP tools from your AI client. - -## Safety Model - - -Use a dedicated development wallet with limited funds while installing WDK CLI from source. Do not use a production seed phrase to test a local source checkout. - - -Wallet seed files are stored under `~/.config/wdk-cli/wallets/NAME/seed.enc` by default, or under `$XDG_CONFIG_HOME/wdk-cli/wallets/NAME/seed.enc` when `XDG_CONFIG_HOME` is set. If you provide a passphrase, the seed is encrypted before it is written to disk. - -Unlocking a wallet sends the passphrase to the daemon over local IPC, initializes WDK wallet instances in memory, and starts a per-wallet TTL. The default TTL is 5 minutes. Use `--ttl 0` only for controlled local development sessions where you accept the longer-lived unlocked wallet. - - - -Install from source, create a development wallet, and run the first read and dry-run send commands - - -Configure wallets, sessions, networks, tokens, indexer access, and MoonPay settings - - -Review the CLI command surface for wallets, reads, sends, config, networks, tokens, buy/sell, and MCP - - -Connect WDK CLI to Claude Desktop, Claude Code, or OpenClaw through the bundled MCP server - - - -*** - -## Need Help? - - diff --git a/content/docs/ai/wdk-cli/mcp.mdx b/content/docs/ai/wdk-cli/mcp.mdx deleted file mode 100644 index 3b4b1e24..00000000 --- a/content/docs/ai/wdk-cli/mcp.mdx +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: MCP -description: Connect WDK CLI to Claude Desktop, Claude Code, or OpenClaw with the bundled MCP server -docType: how-to -schemaType: TechArticle -icon: Wand ---- - -WDK CLI includes `wdk-mcp`, a Model Context Protocol server that exposes local wallet actions as structured tools. The MCP server talks to the same `wdk-daemon` used by the CLI. - - -Create and unlock a dedicated development wallet before connecting an AI client. Do not connect a production seed phrase or meaningful funds to a local source checkout. - - -## Prepare The Wallet - -Install WDK CLI from source first. See [Get Started](/ai/wdk-cli/get-started/). - -Create and unlock a wallet: - -```bash title="Terminal" -wdk wallet create --name agent-dev --words 24 -wdk wallet unlock --name agent-dev --ttl 5 -wdk wallet default --name agent-dev -``` - -Use a short TTL while you are testing. Increase the TTL only when your local agent workflow needs it. - -## Set Up An AI Tool - -WDK CLI supports three setup targets: - -```bash title="Terminal" -wdk mcp setup --ai-tool claude-desktop -wdk mcp setup --ai-tool claude-code -wdk mcp setup --ai-tool openclaw -``` - -Setup resolves the local Node.js executable, points the AI tool at `wdk-mcp`, verifies the MCP server when possible, and writes or delegates the tool config: - -| AI tool | Setup behavior | -| --- | --- | -| Claude Desktop | Writes the `wdk-wallet` server entry into Claude Desktop's MCP config file | -| Claude Code | Runs `claude mcp add -s user wdk-wallet -- ...` | -| OpenClaw | Runs `openclaw mcp set wdk-wallet ...` | - -Verify setup: - -```bash title="Terminal" -wdk mcp verify-setup --ai-tool claude-desktop -wdk mcp list -``` - -Remove setup: - -```bash title="Terminal" -wdk mcp remove --ai-tool claude-desktop -``` - -## MCP Tools - -| Tool | Purpose | -| --- | --- | -| `get_networks` | List supported networks, optionally filtered by mainnet or testnet | -| `list_tokens` | List registered tokens for one network or every network | -| `get_token` | Read one token registry entry | -| `get_address` | Get one address or all addresses | -| `get_balance` | Get one balance or all balances with USD values | -| `get_history` | Get transaction history through the indexer | -| `send_token` | Preview or execute a native or token transfer | -| `buy_crypto` | Create a signed MoonPay buy URL | -| `sell_crypto` | Create a signed MoonPay sell URL | - -Wallet-dependent tools accept an optional `wallet` parameter. When omitted, the daemon uses the default wallet. - -## Send Token Confirmation Flow - -`send_token` defaults to `dryRun: true`. Agents must use a two-step flow: - -1. Call `send_token` with the transfer details and no `dryRun` value, or with `dryRun: true`. -2. Show the preview, amount, recipient, network, token, and fee information to the user. -3. Call `send_token` again with `dryRun: false` only after the user confirms. - -Example dry-run request: - -```json -{ - "network": "ethereum", - "token": "usdt", - "to": "0x000000000000000000000000000000000000dEaD", - "amount": "1.5" -} -``` - -Example execution request after confirmation: - -```json -{ - "network": "ethereum", - "token": "usdt", - "to": "0x000000000000000000000000000000000000dEaD", - "amount": "1.5", - "dryRun": false -} -``` - -## Security Notes - -- The MCP server does not receive seed phrases or private keys from the AI client. -- The MCP server routes wallet actions to the local daemon over IPC. -- The daemon holds unlocked wallet instances in memory until the wallet is locked or its TTL expires. -- `--ttl 0` disables automatic expiry for that wallet. Use it only for controlled local development sessions. -- Use `WDK_PASSPHRASE` only in trusted local automation. Do not commit it to shell profiles, logs, scripts, or MCP config files. -- Lock the wallet after the agent session: - -```bash title="Terminal" -wdk wallet lock --name agent-dev -``` - -## When To Use The MCP Toolkit Instead - -Use [MCP Toolkit](/ai/mcp-toolkit/) instead of WDK CLI when you need to: - -- Build a custom MCP server in application code -- Choose exactly which WDK tools are registered -- Add custom MCP tools beside WDK tools -- Embed wallet behavior in a larger service instead of using a local daemon - -Use WDK CLI when you want a local wallet daemon and ready-made MCP setup for supported AI tools. - -*** - -## Need Help? - - diff --git a/content/docs/cli/api-reference.mdx b/content/docs/cli/api-reference.mdx new file mode 100644 index 00000000..63fc09f3 --- /dev/null +++ b/content/docs/cli/api-reference.mdx @@ -0,0 +1,366 @@ +--- +title: API Reference +description: Complete WDK CLI beta.1 command and option reference +docType: reference +schemaType: TechArticle +icon: ListChecks +--- + +This page documents the 31 leaf commands in `@tetherto/wdk-cli@1.0.0-beta.1`. Run `wdk COMMAND --help` to inspect the installed command surface. + +## Root Options + +| Option | Behavior | +| --- | --- | +| `--json` | Requests machine-readable output from the selected command; see [JSON and exit behavior](#json-and-exit-behavior) for exceptions | +| `--verbose` | Adds a stack trace to handled errors; it does not enable general debug logging | +| `-V`, `--version` | Prints the CLI version followed by the installed WDK dependency versions | +| `-h`, `--help` | Prints help for the selected command | + +The WDK-specific global flags are `--json` and `--verbose`; version and help are also root options. Options such as `--wallet` and `--index` belong to individual commands. + +## Shared Wallet Selection + +Wallet-dependent read, send, buy, and sell commands use: + +| Option | Behavior | +| --- | --- | +| `--wallet ` | Uses the named wallet; otherwise uses `defaultWallet` | +| `--index ` | Uses a non-negative account index; otherwise uses `defaultIndex`, initially `0` | + +The selected wallet must be unlocked before daemon-backed operations. See [Manage Wallets](/cli/guides/manage-wallets). + +## Wallet Commands + +### `wdk wallet create` + +Creates a named wallet from a newly generated BIP-39 seed phrase. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--name ` | Yes | — | Wallet name | +| `--words ` | No | `12` | Seed length; accepts `12` or `24` | + +The first created wallet becomes the default. The command prompts for a passphrase and prints the seed phrase. With `--json`, the success object also contains `seedPhrase`. + +### `wdk wallet import` + +Imports an existing 12-word or 24-word BIP-39 seed phrase. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Wallet name | + +The command prompts for the seed phrase and a new storage passphrase. `WDK_PASSPHRASE` supplies only the passphrase; it does not supply the seed phrase. + +### `wdk wallet export` + +Decrypts and prints a wallet's seed phrase. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Wallet name | + +With `--json`, the success object contains `seedPhrase`. + + +The output from `wallet create` and `wallet export` is secret material in both text and JSON modes. Do not log it, paste it into an agent transcript, or store it in CI output. + + +### `wdk wallet list` + +Lists local wallets with their default, lock, and TTL state. This command has no command-specific options. + +### `wdk wallet delete` + +Deletes a named wallet after verifying its passphrase. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Wallet name | + +If the deleted wallet was the default, the CLI selects the first remaining wallet as the new default. See [Manage Wallets](/cli/guides/manage-wallets) for the deletion and backup implications. + +### `wdk wallet unlock` + +Unlocks a wallet and starts the daemon when needed. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--name ` | Yes | — | Wallet name | +| `--ttl ` | No | `5` | Non-negative session duration in minutes; `0` disables automatic expiry | + +Unlocking an already unlocked wallet resets that wallet's timer. The timer is absolute from unlock or reset; wallet activity does not extend it. + +### `wdk wallet lock` + +Locks one wallet or every wallet. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | One selector required | Wallet to lock | +| `--all` | One selector required | Lock every wallet | + +If both selectors are present, beta.1 applies `--all`. + +### `wdk wallet default` + +Sets the default wallet after passphrase confirmation. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Existing wallet name | + +### `wdk wallet rename` + +Renames a wallet after verifying its passphrase. An unlocked source wallet is locked first. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Current wallet name | +| `--new-name ` | Yes | New wallet name | + +## Read Commands + +### `wdk get address` + +Derives an address for one network or for a network group. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | One selector required | — | Derive one network address | +| `--all` | One selector required | — | Derive addresses for all mainnets by default | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | +| `--testnet` | No | Off | With `--all`, select testnets instead of mainnets | + +When both `--network` and `--all` are supplied, beta.1 runs the single-network path. In aggregate mode, networks that fail address derivation are omitted from the result. + +### `wdk get balance` + +Reads one registered asset balance or native balances across a network group. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | One selector required | — | Query one network | +| `--all` | One selector required | — | Query native balances on all mainnets by default | +| `--token ` | No | Native asset | Registered ticker for a single-network query | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | +| `--testnet` | No | Off | With `--all`, select testnets instead of mainnets | + +`--token` is ignored by the aggregate path, which queries native assets. Networks that fail in aggregate mode are omitted. A missing price produces a USD value of `0` rather than failing the balance lookup. + +### `wdk get history` + +Reads token-transfer history through the configured indexer. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network to query | +| `--token ` | No | All indexer-supported tokens | Exact `metadata.indexerSlug` code; the installed registry yields `btc`, `usdt`, and `xaut`, while custom entries can add other codes | +| `--limit ` | No | `30` | Positive maximum number of transfers | +| `--from-date ` | No | — | ISO 8601 start date | +| `--to-date ` | No | — | ISO 8601 end date | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | + +When `--token` is omitted, beta.1 batches the network's supported token requests, ignores failed batch items, merges successful transfers by timestamp, and then applies `--limit`. + +## Send Command + +### `wdk send` + +Previews or broadcasts a native or registered-token transfer. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network to send on | +| `--to
` | Yes | — | Recipient address | +| `--amount ` | Yes | — | Positive decimal amount, or an integer when `--base-units` is set | +| `--token ` | No | Native asset | Registered token ticker | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | +| `--base-units` | No | Off | Treat `--amount` as raw base units | +| `--dry-run` | No | Off | Estimate fees and return a preview without broadcasting | + +Use `--dry-run` before broadcasting: + +```bash title="Terminal" +wdk send \ + --network ethereum \ + --to 0x000000000000000000000000000000000000dEaD \ + --amount 0.001 \ + --dry-run +``` + +Without `--dry-run`, the command broadcasts immediately. There is no additional interactive confirmation. + +## Fiat Ramp Commands + +`wdk buy` and `wdk sell` derive the selected wallet address and create a provider URL. Both require an unlocked wallet and valid [MoonPay configuration](/cli/configuration#moonpay). + +### `wdk buy` + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network to receive the asset on | +| `--token ` | Yes | — | Registered asset code | +| `--fiat-amount ` | One amount required | — | Fiat amount to spend | +| `--crypto-amount ` | One amount required | — | Crypto amount to buy | +| `--fiat-currency ` | No | `usd` | Fiat currency code | +| `--module ` | No | `moonpay` | Fiat provider module | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | + +### `wdk sell` + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network holding the asset | +| `--token ` | Yes | — | Registered asset code | +| `--fiat-amount ` | One amount required | — | Target fiat amount | +| `--crypto-amount ` | One amount required | — | Crypto amount to sell | +| `--fiat-currency ` | No | `usd` | Fiat currency code | +| `--module ` | No | `moonpay` | Fiat provider module | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | + +For each command, provide exactly one of `--fiat-amount` and `--crypto-amount`. Beta.1 supports only the `moonpay` module. + +## Configuration Commands + +See [Configuration](/cli/configuration) for keys, types, precedence, and storage considerations. + +### `wdk config get` + +| Option | Required | Description | +| --- | --- | --- | +| `--key ` | One selector required | Read one dot-separated key | +| `--network ` | One selector required | Read a network object, or scope `--key` to a network | +| `--all` | One selector required | Read the configuration view | + +`--all` cannot be combined with `--key` or `--network`. `--network` and `--key` can be combined. + +### `wdk config set` + +| Option | Required | Description | +| --- | --- | --- | +| `--value ` | Yes | JSON value when parseable; otherwise a string | +| `--key ` | Without `--network` | Dot-separated key | +| `--network ` | No | Scope `--key`, or replace the network's entire configuration object | + +### `wdk config reset` + +| Option | Required | Description | +| --- | --- | --- | +| `--key ` | One selector required | Reset or remove one key | +| `--network ` | No | Scope `--key` to a network | +| `--all` | One selector required | Reset configuration while preserving the default wallet and custom network/token records | + +`--key` and `--all` are mutually exclusive. `--network` can be combined only with `--key`. + +### `wdk config path` + +Prints the resolved `config.json` path. This command has no command-specific options. + +## Network Commands + +### `wdk network list` + +| Option | Default | Description | +| --- | --- | --- | +| `--testnet` | Off | Show only testnets | +| `--mainnet` | Off | Show only mainnets | + +With neither option, the command shows every registered network. If both are provided, beta.1 applies `--testnet`. + +### `wdk network create ` + +Creates a custom network from an inline JSON object or a JSON file path. The `` positional argument is required. + +See [Custom Networks](/cli/guides/custom-networks) for the network schema and validation rules. + +### `wdk network delete` + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Custom network to delete | + +Built-in networks cannot be deleted. Deleting a custom network also removes its network configuration and custom token entries. + +### `wdk network info` + +| Option | Required | Description | +| --- | --- | --- | +| `--network ` | Yes | Registered network to inspect | + +## Token Commands + +### `wdk token list` + +| Option | Default | Description | +| --- | --- | --- | +| `--network ` | All networks | Filter to one registered network | + +### `wdk token info` + +| Option | Required | Description | +| --- | --- | --- | +| `--network ` | Yes | Registered network | +| `--token ` | Yes | Registered token ticker | + +### `wdk token add ` + +Adds or overrides a token from an inline JSON object or a JSON file path. The `` positional argument is required. + +See [Manage Tokens](/cli/guides/manage-tokens) for the token schema and built-in override behavior. + +### `wdk token delete` + +| Option | Required | Description | +| --- | --- | --- | +| `--network ` | Yes | Registered network | +| `--token ` | Yes | Custom token ticker to delete | + +The command removes only a custom entry. If that entry overrides a built-in token, the built-in entry becomes effective again. + +## MCP Setup Commands + +The accepted `--ai-tool` values are `claude-desktop`, `claude-code`, and `openclaw`. + +### `wdk mcp setup` + +Adds the bundled MCP server to the selected client. `--ai-tool ` is required. + +### `wdk mcp remove` + +Removes the bundled MCP server from the selected client. `--ai-tool ` is required. + +### `wdk mcp verify-setup` + +Checks the selected client's configuration and tests the MCP server. `--ai-tool ` is required. + +### `wdk mcp list` + +Shows setup status for all supported clients. This command has no command-specific options. + +See [Use the MCP Server](/cli/guides/use-mcp-server) for client-specific setup and the exposed tool surface. + +## JSON and Exit Behavior + +Most command handlers print one JSON value to stdout when `--json` is set. The current contract has exceptions: + +- `wdk mcp setup`, `remove`, `verify-setup`, and `list` print human-readable success output even with `--json`. +- Help, version, unknown-command, unknown-option, and missing-required-option output remains text. +- Interactive wallet prompts render on stdout. If a wallet command opens a prompt, prompt text and terminal-control bytes can precede any JSON result; `wallet import` always prompts for the seed phrase. +- `wdk send` can write spinner or completion text to stderr while emitting JSON on stdout. +- A non-empty `WDK_PASSPHRASE` produces a notice on stderr. + +Parse stdout separately from stderr and always check the exit status. See [Handle Errors](/cli/guides/handle-errors) for the error envelope and exit-status contract. + +*** + +## Need Help? + + diff --git a/content/docs/cli/configuration.mdx b/content/docs/cli/configuration.mdx new file mode 100644 index 00000000..8cafc38e --- /dev/null +++ b/content/docs/cli/configuration.mdx @@ -0,0 +1,234 @@ +--- +title: Configuration +description: Configure WDK CLI paths, defaults, indexer access, wallet modules, and fiat ramps +docType: reference +schemaType: TechArticle +icon: Settings +--- + +WDK CLI stores its configuration and wallet data under one local configuration directory. Use `wdk config` for supported changes instead of editing `config.json` directly. + +## Local Paths + +The default configuration directory is `~/.config/wdk-cli`. If `XDG_CONFIG_HOME` is a non-empty environment variable, WDK CLI uses `$XDG_CONFIG_HOME/wdk-cli` instead. + +| Data | Path | +| --- | --- | +| User configuration | `CONFIG_DIR/config.json` | +| Wallet seed | `CONFIG_DIR/wallets/NAME/seed.enc` | +| Daemon PID | `CONFIG_DIR/daemon.pid` | +| Daemon socket on Unix-like systems | `CONFIG_DIR/daemon.sock` | +| Daemon endpoint on Windows | `\\.\pipe\wdk-cli-daemon` | + +Print the resolved `config.json` path: + +```bash title="Terminal" +wdk config path +``` + +With JSON output: + +```bash title="Terminal" +wdk config path --json +``` + +```json +{"path":"/home/user/.config/wdk-cli/config.json"} +``` + +See [Storage Format](/cli/reference/storage-format) for seed-file and daemon-file permissions. + +## Configuration Precedence + +WDK CLI resolves runtime values in this order: + +| Value | Highest to lowest precedence | +| --- | --- | +| Configuration directory | Non-empty `XDG_CONFIG_HOME`, then `~/.config` | +| Wallet | Command `--wallet`, then `defaultWallet` | +| Account index | Command `--index`, then `defaultIndex`, then `0` | +| Indexer API key | Non-empty `WDK_INDEXER_API_KEY`, then stored `indexer.apiKey`, then an empty value | +| Wallet passphrase | Non-empty `WDK_PASSPHRASE`, then a hidden interactive prompt | + +`--wallet` and `--index` are options on wallet-dependent commands; they are not root flags. + +An empty `WDK_PASSPHRASE` value does not override the prompt. To use an empty passphrase, enter it interactively. See [Manage Wallets](/cli/guides/manage-wallets) before choosing an empty passphrase. + +## Environment Variables + +| Variable | Effect | +| --- | --- | +| `XDG_CONFIG_HOME` | Changes the parent directory used for WDK CLI data | +| `WDK_INDEXER_API_KEY` | Overrides `indexer.apiKey` for the current process | +| `WDK_PASSPHRASE` | Supplies a non-empty passphrase instead of opening a prompt | + +There are no beta.1 environment-variable mappings for `indexer.baseUrl`, wallet defaults, account defaults, network configuration, or MoonPay configuration. + + +Environment variables can be inherited by child processes and may be visible to other processes running as the same OS user. Limit their lifetime and do not print them in shell history, CI logs, or agent transcripts. + + +## Supported Keys + +| Key | Expected value | Default or behavior | +| --- | --- | --- | +| `defaultWallet` | Wallet name | The first created or imported wallet becomes the default; use `wdk wallet default` to change it | +| `defaultIndex` | Non-negative integer | `0` | +| `indexer.baseUrl` | Indexer base URL | `https://wdk-api.tether.io` | +| `indexer.apiKey` | Indexer API key | Empty; `WDK_INDEXER_API_KEY` overrides it | +| `ramp.moonpay.apiKey` | MoonPay API key | Empty | +| `ramp.moonpay.signUrl` | URL of your MoonPay signing service | Empty | +| `ramp.moonpay.environment` | `sandbox` or `production` | Empty; required by `wdk buy` and `wdk sell` | +| `networks.NETWORK` | Wallet-module configuration object | Defaults come from the installed `wdk.config.json` | +| `networks.NETWORK.KEY` | One wallet-module configuration value | Depends on the selected wallet module | + +Custom-network records and custom-token records also live in `config.json`. Manage them with `wdk network` and `wdk token` so the CLI can validate their shape and related state. + +## Read Configuration + +Read one global key: + +```bash title="Terminal" +wdk config get --key defaultIndex +``` + +Read one network configuration: + +```bash title="Terminal" +wdk config get --network ethereum +``` + +Read a key inside one network configuration: + +```bash title="Terminal" +wdk config get --network ethereum --key provider +``` + +Read the full configuration view: + +```bash title="Terminal" +wdk config get --all +``` + +`config get --all` excludes the custom-token registry. Use `wdk token list` to read tokens. + + +`config.json` is plaintext. The CLI does not request an owner-only mode for this file, so its effective permissions follow the operating system and runtime defaults and may be `0644`. `config get --all` can reveal stored API keys, signing URLs, and credentials embedded in provider URLs. Do not publish the file or command output. + + +Prefer `WDK_INDEXER_API_KEY` when you do not want to persist the indexer key. No environment override is available for the other supported secrets in beta.1. + +## Set Configuration + +Set a string: + +```bash title="Terminal" +wdk config set --key indexer.baseUrl --value https://indexer.example.com +``` + +Set a number: + +```bash title="Terminal" +wdk config set --key defaultIndex --value 1 +``` + +Set a JSON object: + +```bash title="Terminal" +wdk config set \ + --key ramp.moonpay \ + --value '{"apiKey":"pk_test_...","signUrl":"https://example.com/sign","environment":"sandbox"}' +``` + +`config set` parses a valid JSON value into its JSON type. If parsing fails, it stores the value as a string. Quote objects and arrays so the shell passes them as one argument. + +Set a network-specific key: + +```bash title="Terminal" +wdk config set \ + --network ethereum \ + --key provider \ + --value https://ethereum-rpc.publicnode.com +``` + +Replace a network's complete wallet-module configuration: + +```bash title="Terminal" +wdk config set \ + --network ethereum \ + --value '{"chainId":1,"provider":"https://ethereum-rpc.publicnode.com","transferMaxFee":5000000000000000}' +``` + +Network configuration is passed to the selected WDK wallet module. Use only keys supported by that module. + +## Reset Configuration + +Reset one global key: + +```bash title="Terminal" +wdk config reset --key indexer.baseUrl +``` + +Reset one network key: + +```bash title="Terminal" +wdk config reset --network ethereum --key provider +``` + +Reset configuration defaults: + +```bash title="Terminal" +wdk config reset --all +``` + +`config reset --all` preserves the default-wallet selection, custom networks, and custom tokens. It resets the remaining values to their installed defaults. + +## Authorization and Wallet Locking + +When at least one wallet exists, these configuration mutations verify the current default wallet's passphrase: + +- `wdk config set` +- `wdk config reset` +- `wdk network create` +- `wdk network delete` +- `wdk token add` +- `wdk token delete` + +Set `WDK_PASSPHRASE` for non-interactive local automation or enter the passphrase at the prompt. + +Changing or resetting a key under `networks` locks all wallets so the next unlock initializes WDK with the new wallet-module configuration. `config reset --all` also locks all wallets. + +## Indexer + +Transfer history uses the WDK Indexer API: + +```bash title="Terminal" +WDK_INDEXER_API_KEY=YOUR_INDEXER_API_KEY \ + wdk get history --network ethereum --wallet dev +``` + +Set a custom endpoint when needed: + +```bash title="Terminal" +wdk config set --key indexer.baseUrl --value https://indexer.example.com +``` + +`WDK_INDEXER_BASE_URL` is not read by beta.1. + +## MoonPay + +`wdk buy` and `wdk sell` require all three MoonPay values: + +```bash title="Terminal" +wdk config set --key ramp.moonpay.apiKey --value YOUR_MOONPAY_API_KEY +wdk config set --key ramp.moonpay.signUrl --value https://example.com/moonpay/sign +wdk config set --key ramp.moonpay.environment --value sandbox +``` + +Use `sandbox` for testnets and `production` for mainnets. The CLI rejects a sandbox/mainnet or production/testnet mismatch. + +*** + +## Need Help? + + diff --git a/content/docs/cli/guides/custom-networks.mdx b/content/docs/cli/guides/custom-networks.mdx new file mode 100644 index 00000000..446201f4 --- /dev/null +++ b/content/docs/cli/guides/custom-networks.mdx @@ -0,0 +1,155 @@ +--- +title: Custom Networks +description: Add and remove blockchain networks in WDK CLI +docType: how-to +schemaType: TechArticle +--- + +Use a custom network when WDK CLI already supports the network's wallet-module type but does not include the specific chain in its built-in registry. + +`wdk network create` cannot introduce a new wallet-module implementation. Its `module` field must match a wallet-module type already used by a built-in network. + +## Inspect Available Networks + +List built-in and custom networks: + +```bash title="Terminal" +wdk network list +``` + +Use JSON output to inspect both the versioned `module` and its unversioned `type`: + +```bash title="Terminal" +wdk --json network list +``` + +Use a returned `type`, such as `@tetherto/wdk-wallet-evm`, as the `module` value in a custom network spec. + +Inspect the effective metadata and SDK configuration for one network: + +```bash title="Terminal" +wdk network info --network ethereum +``` + +## Network Spec + +`wdk network create ` accepts either an inline JSON object or the path to a JSON file. + +| Field | Required | Rules and effect | +| --- | --- | --- | +| `network` | Yes | Unique identifier containing lowercase letters, numbers, and hyphens. The first character must be a letter or number. | +| `module` | Yes | Unversioned wallet-module type already used by a built-in network. | +| `displayName` | No | Non-empty display label. Defaults to the `network` value. | +| `testnet` | No | Boolean. Defaults to `false`. | +| `indexerSlug` | No | Non-empty WDK Indexer chain identifier. Without it, `get history` is unavailable for the custom network. | +| `config` | No | Object passed to the selected wallet module, such as a provider URL and chain ID. The wallet module validates these values when used. | +| `tokens` | No | Array of token specs to store with the network. Token keys must be unique, and at most one entry can be native. | + +Each item in `tokens` uses the fields documented in [Manage Tokens](/cli/guides/manage-tokens/#token-spec), with `network` omitted because the parent network supplies it. + +## Create A Custom Network + +Create `optimism.json`: + +```json title="optimism.json" +{ + "network": "optimism", + "module": "@tetherto/wdk-wallet-evm", + "displayName": "Optimism", + "testnet": false, + "config": { + "provider": "https://mainnet.optimism.io", + "chainId": 10 + }, + "tokens": [ + { + "token": "eth", + "symbol": "ETH", + "decimals": 18, + "isNative": true + } + ] +} +``` + +Create the network and its native-token entry: + +```bash title="Terminal" +wdk network create ./optimism.json +``` + +Verify the result: + +```bash title="Terminal" +wdk network info --network optimism +wdk token list --network optimism +``` + + +The example uses a public mainnet RPC endpoint. Verify the chain ID, provider, token addresses, and provider-specific limits before using a custom network with funds. Omit `indexerSlug` unless you know the WDK Indexer identifier for that chain. + + +## Creation Side Effects + +The CLI validates the complete network and token spec before storing it. On success, it writes: + +- The custom network metadata +- The network's SDK `config` +- Every entry in `tokens` + +If storing a token fails, the command rolls back the custom network, its SDK config, and token entries already written by that command. + +If at least one wallet exists, `network create` prompts for the current default wallet's passphrase. It does not require the wallet to be unlocked. With no wallets, it does not prompt. + +`network create` and `network delete` do not unlock, extend, or lock an existing daemon wallet session. `wdk config reset --all` preserves custom network entries. + +## Update Network Configuration + +Use `config set` to replace the complete SDK config object: + +```bash title="Terminal" +wdk config set \ + --network optimism \ + --value '{"provider":"https://mainnet.optimism.io","chainId":10}' +``` + +Or change one nested key: + +```bash title="Terminal" +wdk config set \ + --network optimism \ + --key provider \ + --value https://mainnet.optimism.io +``` + + +Changing `networks.*` configuration locks every unlocked wallet so the daemon drops cached wallet managers. Unlock the wallet again before reading balances or sending. + + +## Delete A Custom Network + + +Deleting a custom network also deletes its SDK configuration and every custom token under that network. Export or record the spec first if you may need to recreate it. + + +Delete the network: + +```bash title="Terminal" +wdk network delete --name optimism +``` + +Deletion prompts for the default wallet's passphrase when wallets exist. Built-in networks cannot be deleted. + +Deleting registry configuration does not move blockchain assets or delete a wallet seed, but the CLI can no longer access that network until you recreate the registry entry. + +## Next Steps + +- [Manage Tokens](/cli/guides/manage-tokens/) - Add token contracts and provider mappings +- [Configuration](/cli/configuration/) - Review paths, environment variables, and precedence +- [API Reference](/cli/api-reference/) - Review all network command flags + +*** + +## Need Help? + + diff --git a/content/docs/cli/guides/get-started.mdx b/content/docs/cli/guides/get-started.mdx new file mode 100644 index 00000000..f7c775f2 --- /dev/null +++ b/content/docs/cli/guides/get-started.mdx @@ -0,0 +1,134 @@ +--- +title: Get Started +description: Install WDK CLI and complete a testnet wallet flow +docType: getting-started +schemaType: TechArticle +icon: Rocket +--- + +Install WDK CLI globally: + +```bash title="Terminal" +npm install -g @tetherto/wdk-cli@1.0.0-beta.1 +``` + +The current release is `1.0.0-beta.1`. Beta releases can change before the stable release. + + +Use a dedicated development wallet with testnet funds for this guide. Do not import a production seed phrase or fund this wallet with mainnet assets. + + +## Prerequisites + +- Node.js `22.18.0` or later +- npm +- A trusted source of Sepolia test ETH + +Verify the installation: + +```bash title="Terminal" +wdk --version +wdk --help +``` + +The package installs the `wdk`, `wdk-mcp`, and `wdk-daemon` binaries. Use `wdk` for the steps below. + +## 1. Create A Development Wallet + +Create a named 24-word wallet: + +```bash title="Terminal" +wdk wallet create --name quickstart-dev --words 24 +``` + +Enter a non-empty, unique passphrase when prompted. The command displays the generated seed phrase. Record it offline before continuing and do not put it in a terminal command, source file, or screenshot. + +The first wallet you create or import becomes the default wallet automatically. You do not need to run `wdk wallet default` for this flow. + +Unlock the wallet for five minutes: + +```bash title="Terminal" +wdk wallet unlock --name quickstart-dev --ttl 5 +``` + +The TTL starts at unlock and does not restart when you use the wallet. Unlock it again if the session expires while you complete this guide. See [Manage Wallets](/cli/guides/manage-wallets/) for session details. + +## 2. Get A Receiving Address + +Derive the wallet's Sepolia address: + +```bash title="Terminal" +wdk get address --network sepolia --wallet quickstart-dev +``` + +Copy the returned address into a trusted Sepolia faucet. Fund it with only enough test ETH to complete the example. + + +Receiving funds is an on-chain operation outside WDK CLI. Wait for the faucet transaction to confirm before checking the balance. + + +## 3. Check The Balance + +Read the wallet's native Sepolia balance: + +```bash title="Terminal" +wdk get balance --network sepolia --wallet quickstart-dev +``` + +If the command reports that the wallet is locked, unlock it again and repeat the balance check. + +## 4. Preview A Send + +Use an address you control as the recipient. A dry run estimates the fee and returns a transaction summary without broadcasting: + +```bash title="Terminal" +wdk send \ + --network sepolia \ + --to YOUR_SEPOLIA_ADDRESS \ + --amount 0.0001 \ + --wallet quickstart-dev \ + --dry-run +``` + +Check the network, recipient, amount, and estimated fee in the preview. + + +Removing `--dry-run` broadcasts the transaction. WDK CLI does not ask for another confirmation after the wallet is unlocked. Replace the placeholder with an address you control and review every value first. + + +When you intend to send the testnet transaction, run the same command without `--dry-run`: + +```bash title="Terminal" +wdk send \ + --network sepolia \ + --to YOUR_SEPOLIA_ADDRESS \ + --amount 0.0001 \ + --wallet quickstart-dev +``` + +## 5. Lock The Wallet + +End the wallet session when you finish: + +```bash title="Terminal" +wdk wallet lock --name quickstart-dev +``` + +Lock every unlocked wallet with: + +```bash title="Terminal" +wdk wallet lock --all +``` + +## Next Steps + +- [Manage Wallets](/cli/guides/manage-wallets/) - Import, export, rename, unlock, and delete wallets +- [Manage Tokens](/cli/guides/manage-tokens/) - Inspect and extend the token registry +- [Use the MCP Server](/cli/guides/use-mcp-server/) - Connect a supported AI client +- [Security Model](/cli/reference/security-model/) - Understand the daemon trust boundary and session trade-offs + +*** + +## Need Help? + + diff --git a/content/docs/cli/guides/handle-errors.mdx b/content/docs/cli/guides/handle-errors.mdx new file mode 100644 index 00000000..0285e1aa --- /dev/null +++ b/content/docs/cli/guides/handle-errors.mdx @@ -0,0 +1,138 @@ +--- +title: Handle Errors +description: Handle WDK CLI exit statuses, JSON output, stderr, and error codes +docType: how-to +schemaType: TechArticle +icon: CircleQuestionMark +--- + +Use a command's exit status as the primary success signal. When you request `--json`, parse stdout separately from stderr and allow for commands that still produce text. + +This page describes `@tetherto/wdk-cli@1.0.0-beta.1`. + +## Handled Error Envelope + +Errors that reach the WDK CLI command handler use this JSON shape: + +```json +{ + "error": "Wallet 'dev' is not unlocked.", + "code": "WALLET_NOT_UNLOCKED", + "suggestion": "Run: wdk wallet unlock --name dev" +} +``` + +| Field | Present | Description | +| --- | --- | --- | +| `error` | Always | Human-readable message | +| `code` | Always | Machine-readable code | +| `suggestion` | Sometimes | Recovery guidance | +| `stack` | Only with `--verbose` when available | JavaScript stack trace | + +`--verbose` adds error stacks. It does not enable general debug logging. + +## Exit Statuses + +| Status | Meaning in beta.1 | +| --- | --- | +| `0` | Command, help, or version output completed | +| `1` | A handled CLI/runtime error or a command-line parsing error occurred | +| `2` | An unexpected non-`Error` value or an error outside the normal command handler reached the top level | + +Do not treat the presence of stdout as success. Some handled failures emit a JSON object to stdout and exit with status `1`. + +## JSON Output Contract + +For most successful commands, `--json` emits one JSON value followed by a newline on stdout. Handled WDK CLI errors also emit one JSON object on stdout. + +The current exceptions are: + +| Case | Stdout | Stderr | Status | +| --- | --- | --- | --- | +| Most successful commands with `--json` | JSON | Usually empty | `0` | +| Handled command error with `--json` | JSON error envelope | Usually empty | `1`, or `2` for a non-`Error` value | +| Unknown command, unknown option, or missing required option | Empty | Human-readable Commander error and help | `1` | +| Help or version, even with `--json` | Human-readable text | Empty | `0` | +| Successful `wdk mcp setup`, `remove`, `verify-setup`, or `list` with `--json` | Human-readable text | Empty | `0` | +| Wallet command that opens an interactive prompt with `--json` | Prompt text and terminal-control bytes, followed by JSON if the command completes | Normal notices or errors can still appear | Depends on result | +| `wdk send --json` | JSON on completion or a handled error | May contain spinner, success, or failure text | Depends on result | +| A command using non-empty `WDK_PASSPHRASE` | Normal command output | Includes a passphrase-source notice | Depends on result | + + +Do not combine stdout and stderr before parsing JSON. Spinner output and notices can make a combined stream invalid JSON. + + +`--json` changes output formatting; it does not make every command non-interactive or guarantee JSON-only stdout when a prompt opens. Wallet create, import, export, unlock, delete, default, and rename flows can still request secret input. Set a non-empty `WDK_PASSPHRASE` only when you accept the environment-variable exposure described in [Configuration](/cli/configuration#environment-variables). `wallet import` still prompts for the seed phrase, so its stdout is not a clean JSON stream even when the environment variable supplies the passphrase. + +## Handle Output in a Shell Script + +Capture the streams separately and inspect the exit status before parsing. This example requires `jq`: + +```bash title="check-balance.sh" +#!/usr/bin/env bash +set -euo pipefail + +stdout_file=$(mktemp) +stderr_file=$(mktemp) +trap 'rm -f "$stdout_file" "$stderr_file"' EXIT + +if wdk get balance \ + --network ethereum \ + --wallet dev \ + --json >"$stdout_file" 2>"$stderr_file"; then + jq . "$stdout_file" +elif jq -e 'type == "object" and has("code")' "$stdout_file" >/dev/null 2>&1; then + jq . "$stdout_file" >&2 + exit 1 +else + cat "$stderr_file" >&2 + exit 1 +fi +``` + +This pattern handles both JSON command errors and text-only argument parsing errors. + +## Error Codes + +The following names are defined by beta.1. A code can appear only on commands that reach the corresponding behavior. + +| Area | Codes | +| --- | --- | +| Wallet and key state | `KEY_NOT_FOUND`, `INVALID_SEED_PHRASE`, `WRONG_PASSPHRASE`, `WALLET_NOT_UNLOCKED`, `WALLET_EXISTS`, `WALLET_LOCKED`, `PASSPHRASE_MISMATCH` | +| Arguments and configuration | `INVALID_ARGUMENT`, `INVALID_INDEX`, `INVALID_CONFIG`, `MISSING_CONFIG`, `INVALID_AMOUNT`, `INVALID_TOKEN` | +| Networks and tokens | `NETWORK_NOT_SUPPORTED`, `TOKEN_NOT_SUPPORTED`, `NETWORK_ERROR` | +| Transactions and providers | `INSUFFICIENT_BALANCE`, `TRANSACTION_FAILED`, `UNSUPPORTED_MODULE`, `ENVIRONMENT_MISMATCH`, `SIGN_FAILED`, `PROVIDER_UNAVAILABLE`, `QUOTE_REJECTED` | +| Fallbacks | `UNKNOWN_ERROR`, `UNEXPECTED_ERROR` | + +The code set is not a closed protocol enum. The daemon and WDK dependencies can pass through additional codes. Beta.1 also recognizes and formats several dependency codes, including `INSUFFICIENT_FUNDS`, `SERVER_ERROR`, and `TIMEOUT`. Consumers should preserve unknown code strings instead of rejecting the response. + +## Common Recovery Paths + +| Code or symptom | Check | +| --- | --- | +| `WALLET_NOT_UNLOCKED` | Run `wdk wallet unlock --name NAME`; confirm that its TTL has not expired | +| `KEY_NOT_FOUND` | Run `wdk wallet list`; verify the wallet name or default wallet | +| `WRONG_PASSPHRASE` | Retry through the hidden prompt; do not print or log the passphrase | +| `NETWORK_NOT_SUPPORTED` | Run `wdk network list`; check spelling and custom-network state | +| `TOKEN_NOT_SUPPORTED` | Run `wdk token list --network NETWORK`; use the registered ticker | +| `MISSING_CONFIG` for history | Configure `indexer.baseUrl` and, when required, `WDK_INDEXER_API_KEY` | +| `MISSING_CONFIG` for buy or sell | Configure all `ramp.moonpay` values | +| `ENVIRONMENT_MISMATCH` | Use MoonPay `sandbox` with a testnet or `production` with a mainnet | +| `NETWORK_ERROR`, `TIMEOUT`, or `SERVER_ERROR` | Check the RPC/indexer endpoint and retry only when repeating the operation is safe | +| Text error with empty stdout | Treat it as an argument/help parsing failure; inspect stderr | + +## Partial Results + +Some successful aggregate operations omit failed items: + +- `wdk get address --all` skips networks that fail address derivation. +- `wdk get balance --all` skips networks that fail address or balance lookup. +- `wdk get history` without `--token` ignores failed token batch items and returns successful transfers. + +These commands can exit `0` with an incomplete aggregate result. If completeness matters, query each required network or token separately and track failures in your application. + +*** + +## Need Help? + + diff --git a/content/docs/cli/guides/manage-tokens.mdx b/content/docs/cli/guides/manage-tokens.mdx new file mode 100644 index 00000000..283f8f4e --- /dev/null +++ b/content/docs/cli/guides/manage-tokens.mdx @@ -0,0 +1,138 @@ +--- +title: Manage Tokens +description: Inspect, add, override, and remove WDK CLI token registry entries +docType: how-to +schemaType: TechArticle +--- + +WDK CLI resolves token names such as `usdt` through a local registry. Each entry defines how the CLI formats amounts, selects a native or contract transfer, and connects the token to optional indexer, MoonPay, and price-provider features. + +The effective registry combines built-in entries with your custom entries. A custom entry with the same network and token key replaces the built-in entry until you remove the override. + +## Inspect The Registry + +List tokens across every network: + +```bash title="Terminal" +wdk token list +``` + +Filter the list to one network or inspect one entry: + +```bash title="Terminal" +wdk token list --network ethereum +wdk token info --network ethereum --token usdt +``` + +Use the lowercase registry key shown by these commands with `--token` on `get balance`, `send`, `buy`, and `sell`. + +`get history --token` is different: pass the exact `metadata.indexerSlug` value, which may differ from the registry key. + +## Token Spec + +`wdk token add ` accepts either an inline JSON object or the path to a JSON file. + +| Field | Required | Rules and effect | +| --- | --- | --- | +| `network` | Yes | Must name an existing built-in or custom network. | +| `token` | Yes | Registry key. Use lowercase letters, numbers, and hyphens; the first character must be a letter or number. | +| `symbol` | Yes | Non-empty display symbol, such as `DAI`. | +| `decimals` | Yes | Integer from `0` through `24`; used to convert decimal amounts to base units. | +| `isNative` | Yes | `true` uses the network's native transfer path; `false` uses a token contract or mint. | +| `address` | For non-native tokens | Non-empty contract or mint address. Optional for a native token. | +| `metadata` | No | Object containing supported provider mappings. | + +A network can have at most one effective entry with `isNative: true`. + +### Provider Metadata + +| Field | Used by | Effect when omitted | +| --- | --- | --- | +| `metadata.indexerSlug` | `wdk get history` | Pass this exact value to `--token`. Without it, a token-specific history request cannot use the token and an all-token history request skips it. The network also needs its own `indexerSlug`. | +| `metadata.moonpaySlug` | `wdk buy` and `wdk sell` | MoonPay operations do not support that token. | +| `metadata.bitfinexSlug` | USD price conversion | Balance and preview operations can continue without that provider's USD estimate. | + +Only `indexerSlug`, `moonpaySlug`, and `bitfinexSlug` are retained inside `metadata`. + +## Add A Custom Token + +Create a file named `dai-on-ethereum.json`: + +```json title="dai-on-ethereum.json" +{ + "network": "ethereum", + "token": "dai", + "symbol": "DAI", + "decimals": 18, + "isNative": false, + "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F" +} +``` + +Add the entry: + +```bash title="Terminal" +wdk token add ./dai-on-ethereum.json +``` + +You can also pass the JSON inline: + +```bash title="Terminal" +wdk token add '{"network":"ethereum","token":"dai","symbol":"DAI","decimals":18,"isNative":false,"address":"0x6B175474E89094C44Da98b954EedeAC495271d0F"}' +``` + +Verify the stored entry: + +```bash title="Terminal" +wdk token info --network ethereum --token dai +``` + + +Adding a registry entry does not transfer tokens or interact with the blockchain. Verify contract addresses, decimals, and provider identifiers independently before using the entry to query or send assets. + + +## Understand Mutation Confirmation + +`token add` and `token delete` protect registry changes with passphrase confirmation: + +- If at least one wallet exists, the CLI prompts for the current default wallet's passphrase. +- If no wallet exists, the registry command does not prompt for a passphrase. +- Confirmation does not unlock, extend, or lock an existing daemon wallet session. + +Set a valid default wallet before changing the registry if wallets already exist. + +## Override A Built-In Entry + +Adding a custom entry with the same `network` and `token` as a built-in entry replaces the complete effective entry; it does not merge individual fields. The CLI reports that the custom entry overrides the built-in entry. + +Use overrides carefully. An incorrect address, decimal count, or native-token flag can route later balance or send operations incorrectly. + +Remove the custom override to restore the built-in entry: + +```bash title="Terminal" +wdk token delete --network ethereum --token usdt +``` + +## Delete A Custom Entry + +Delete the DAI entry created above: + +```bash title="Terminal" +wdk token delete --network ethereum --token dai +``` + +`token delete` removes only custom entries. It rejects deletion of a built-in entry when no custom override exists. Deleting a custom network also removes every custom token stored under that network. + +`wdk config reset --all` preserves custom token entries. + +## Next Steps + +- [Custom Networks](/cli/guides/custom-networks/) - Create a network and its initial token entries together +- [Configuration](/cli/configuration/) - Configure provider and indexer access +- [API Reference](/cli/api-reference/) - Review token command flags and JSON results + +*** + +## Need Help? + + diff --git a/content/docs/cli/guides/manage-wallets.mdx b/content/docs/cli/guides/manage-wallets.mdx new file mode 100644 index 00000000..80f51792 --- /dev/null +++ b/content/docs/cli/guides/manage-wallets.mdx @@ -0,0 +1,232 @@ +--- +title: Manage Wallets +description: Create, import, select, unlock, lock, export, rename, and delete WDK CLI wallets safely. +docType: how-to +schemaType: TechArticle +icon: WalletCards +--- + +WDK CLI stores independent named wallets and keeps only explicitly unlocked wallets in the daemon. This guide covers the complete wallet lifecycle. + + +Wallet create, import, and export handle a BIP-39 seed phrase. Use a private terminal with logging, screen sharing, and AI assistants disabled. Anyone who obtains the phrase can control the wallet. + + +## Wallet names + +Wallet names may contain letters, numbers, hyphens, and underscores. Other characters are rejected. + +Examples in this guide use a wallet named `dev`: + +```bash title="Terminal" +wdk wallet list +``` + +## Create a wallet + +Create a 12-word wallet: + +```bash title="Terminal" +wdk wallet create --name dev +``` + +Create a 24-word wallet: + +```bash title="Terminal" +wdk wallet create --name dev --words 24 +``` + +The CLI asks for a passphrase twice, writes the encrypted mnemonic to `wallets/dev/seed.enc`, and displays the generated phrase once. Record both the mnemonic and passphrase in separate recoverable locations before continuing. + +The first wallet becomes the default automatically. You do not need to run `wdk wallet default` after creating the first wallet. + + +The current CLI permits an empty passphrase. Although `seed.enc` remains AES-GCM ciphertext, an empty passphrase provides no meaningful confidentiality. Use a strong, unique passphrase. + + +## Import a wallet + +Import an existing 12- or 24-word BIP-39 phrase: + +```bash title="Terminal" +wdk wallet import --name recovered +``` + +The CLI prompts for the phrase and then for a new local encryption passphrase. The seed-phrase input is interactive but is not masked, so import only in a private terminal. + +Import creates another encrypted local copy. It does not remove or change the source wallet or any existing backup. + +## List wallets and sessions + +Show stored wallets, the default wallet, lock status, and TTL remaining: + +```bash title="Terminal" +wdk wallet list +``` + +When a wallet is unlocked, the table reports either the approximate remaining time or `unlimited` for a `--ttl 0` session. + +In JSON mode, an unlocked entry includes `ttlMs` and `ttlRemaining` in milliseconds: + +```bash title="Terminal" +wdk --json wallet list +``` + +## Select the default wallet + +Commands use the default wallet when their `--wallet` option is omitted. + +```bash title="Terminal" +wdk wallet default --name dev +``` + +If a default wallet already exists, changing it requires that wallet's passphrase. This prevents an unconfirmed configuration change, but it does not unlock either wallet. + +To target another wallet for one supported operation without changing the default, pass its command-level option: + +```bash title="Terminal" +wdk get balance --network ethereum --wallet recovered +``` + +## Unlock a wallet + +Unlock with the default five-minute TTL: + +```bash title="Terminal" +wdk wallet unlock --name dev +``` + +Specify an absolute TTL in minutes: + +```bash title="Terminal" +wdk wallet unlock --name dev --ttl 15 +``` + +Unlock starts the daemon if needed. The CLI verifies the passphrase, then the daemon decrypts the wallet, creates its WDK instance, and starts the timer. + + +After unlock, any process running as the same operating-system user that can connect to the daemon endpoint can request signing or sending without entering the passphrase. Use a short TTL, avoid running untrusted code, and lock immediately after use. + + +### Control the unlock lifetime + +TTL is per wallet and starts at unlock: + +| Command or event | Result | +| --- | --- | +| Omit `--ttl` | Wallet locks after five minutes | +| `--ttl 15` | Wallet locks 15 minutes after unlock | +| Use the wallet | Timer continues; activity does not restart it | +| Unlock the wallet again | Timer resets to the newly requested TTL | +| `--ttl 0` | No automatic expiry | +| TTL expires | That wallet is disposed and locked | +| Last wallet locks | Daemon exits | + +`--ttl 0` accepts the risk of a session that remains unlocked until explicit lock, daemon shutdown, process failure, or machine restart: + +```bash title="Terminal" +wdk wallet unlock --name dev --ttl 0 +``` + +Use it only for a controlled local workflow. Normal wallet operations do not refresh any TTL, so a finite session can expire during a long task. + +## Lock wallets + +Lock one wallet: + +```bash title="Terminal" +wdk wallet lock --name dev +``` + +Lock every wallet: + +```bash title="Terminal" +wdk wallet lock --all +``` + +Locking disposes the wallet's WDK instance and removes the session from the daemon. The daemon exits after the last wallet locks. + +Verify the result: + +```bash title="Terminal" +wdk wallet list +``` + +Cleanup is best effort. The CLI zeroes retained mutable key and seed buffers on normal disposal, but JavaScript strings, dependency-internal copies, swap, core dumps, and abrupt termination cannot be guaranteed to be erased. See the [security model](/cli/reference/security-model#seed-and-passphrase-lifetime). + +## Export a seed phrase + + +Export prints the decrypted mnemonic. Do not run this command in CI, a recorded terminal, an agent session, or any environment that captures stdout. + + +Export a wallet: + +```bash title="Terminal" +wdk wallet export --name dev +``` + +The CLI asks for the wallet passphrase and prints the phrase. `--json` also includes the phrase in stdout; JSON does not make secret output safe to log. + +Use export to create or verify an offline recovery backup. If WDK CLI itself is unavailable, use the standalone [version 1 manual-recovery procedure](/cli/reference/storage-format#recover-without-wdk-cli). + +## Rename a wallet + +Rename a stored wallet: + +```bash title="Terminal" +wdk wallet rename --name dev --new-name development +``` + +The command verifies the old wallet's passphrase, locks that wallet if it is unlocked, and moves its storage directory. If it was the default, the new name becomes the default. Unlock it again under the new name before using it: + +```bash title="Terminal" +wdk wallet unlock --name development +``` + +Renaming does not decrypt or re-encrypt `seed.enc`. + +## Delete a wallet + + +Deletion is irreversible through WDK CLI. Confirm that you have tested the mnemonic and passphrase backup before deleting the only local copy. + + +Delete a wallet: + +```bash title="Terminal" +wdk wallet delete --name development +``` + +The command: + +1. verifies the wallet passphrase +2. attempts to lock an active session +3. recursively removes the wallet directory +4. chooses another stored wallet as the default when necessary + +Deletion is ordinary filesystem removal, not secure erase. Copies can remain in backups, snapshots, journals, swap, or recoverable storage blocks. + +## Passphrases in automation + +Commands that prompt for a passphrase read a non-empty `WDK_PASSPHRASE` value when it is set: + +```bash title="Terminal" +WDK_PASSPHRASE='YOUR_PASSPHRASE' wdk --json wallet unlock --name dev --ttl 5 +``` + +Environment variables can leak through process inspection, child-process inheritance, command tracing, crash reports, or automation logs. Prefer the hidden interactive prompt. If automation is necessary: + +- inject the value from a secret manager for one process +- disable command tracing and output capture +- do not commit the value to a script or configuration file +- lock the wallet and remove the environment variable immediately after use + +Never pass the passphrase or mnemonic as a CLI argument. + +## Related pages + +- [API reference](/cli/api-reference#wallet-commands) +- [Architecture](/cli/reference/architecture) +- [Storage format and manual recovery](/cli/reference/storage-format) +- [Security model](/cli/reference/security-model) diff --git a/content/docs/cli/guides/use-mcp-server.mdx b/content/docs/cli/guides/use-mcp-server.mdx new file mode 100644 index 00000000..795e12db --- /dev/null +++ b/content/docs/cli/guides/use-mcp-server.mdx @@ -0,0 +1,158 @@ +--- +title: Use the MCP Server +description: Connect WDK CLI to Claude Desktop, Claude Code, or OpenClaw +docType: how-to +schemaType: TechArticle +icon: Wand +--- + +WDK CLI includes the `wdk-mcp` Model Context Protocol server. It exposes wallet operations as structured tools over stdio and routes wallet-dependent operations to the same local daemon used by the CLI. + + +Use a dedicated development wallet with limited funds. On Unix-like systems, while a wallet is unlocked, any process running as the same OS user that can connect to the owner-only daemon socket can request signing or sending without another passphrase. + + +## Prepare A Wallet + +Install WDK CLI and create a wallet as described in [Get Started](/cli/guides/get-started/). + +If `agent-dev` is your first wallet, it becomes the default automatically: + +```bash title="Terminal" +wdk wallet create --name agent-dev --words 24 +wdk wallet unlock --name agent-dev --ttl 5 +``` + +If another wallet is already the default, select `agent-dev` explicitly: + +```bash title="Terminal" +wdk wallet default --name agent-dev +``` + +Wallet-dependent MCP tools use the default wallet unless the request includes `wallet`. The wallet must be unlocked before those tools run. + +The TTL is absolute from unlock; MCP activity does not extend it. Use a short TTL during development and lock the wallet when the session ends. + +## Configure A Supported AI Client + +Choose one setup target: + +```bash title="Claude Desktop" +wdk mcp setup --ai-tool claude-desktop +``` + +```bash title="Claude Code" +wdk mcp setup --ai-tool claude-code +``` + +```bash title="OpenClaw" +wdk mcp setup --ai-tool openclaw +``` + +| Target | Setup behavior | +| --- | --- | +| Claude Desktop | Adds `wdk-wallet` under `mcpServers` in the platform-specific Claude Desktop JSON config. | +| Claude Code | Runs `claude mcp add -s user wdk-wallet -- ...` with the current Node.js executable and installed MCP script. | +| OpenClaw | Runs `openclaw mcp set wdk-wallet ...` with the current Node.js executable and installed MCP script. | + +Setup checks whether the target appears to be installed, tests the MCP server when possible, and reports the restart action required by that client. + +Verify one target: + +```bash title="Terminal" +wdk mcp verify-setup --ai-tool claude-desktop +``` + +List the configuration status of all three targets: + +```bash title="Terminal" +wdk mcp list +``` + +Remove a target configuration with: + +```bash title="Terminal" +wdk mcp remove --ai-tool claude-desktop +``` + +Replace `claude-desktop` with `claude-code` or `openclaw` as needed. + +## Available MCP Tools + +| Tool | Wallet required | Purpose | +| --- | --- | --- | +| `get_networks` | No | List networks, optionally filtered to mainnets or testnets. | +| `list_tokens` | No | List every registered token or filter by network. | +| `get_token` | No | Read one network and token registry entry. | +| `get_address` | Yes | Derive one address or addresses across networks. | +| `get_balance` | Yes | Read one balance or aggregate native balances with USD estimates. | +| `get_history` | Yes | Query indexer-backed token-transfer history for one network. | +| `send_token` | Yes | Preview or execute a native or registered-token transfer. | +| `buy_crypto` | Yes | Create a signed MoonPay buy URL. | +| `sell_crypto` | Yes | Create a signed MoonPay sell URL. | + +`get_history` needs indexer configuration. `buy_crypto` and `sell_crypto` need MoonPay configuration. See [Configuration](/cli/configuration/). + +## Preview And Confirm Transfers + +`send_token` defaults to `dryRun: true`. The recommended client workflow is: + +1. Call `send_token` without `dryRun`, or with `dryRun: true`. +2. Show the network, token, recipient, amount, and estimated fee to the user. +3. Ask the user to confirm those exact values. +4. Call `send_token` with `dryRun: false` only after confirmation. + +Example preview request: + +```json +{ + "network": "sepolia", + "to": "", + "amount": "0.0001", + "wallet": "agent-dev" +} +``` + +Example execution request after confirmation: + +```json +{ + "network": "sepolia", + "to": "", + "amount": "0.0001", + "wallet": "agent-dev", + "dryRun": false +} +``` + + +The preview-and-confirm sequence is guidance for the AI client; the daemon does not enforce it. A direct `send_token` call with `dryRun: false` broadcasts when the selected wallet is unlocked and the request is otherwise valid. + + +## Security And Session Boundaries + +- The AI client sends tool parameters to `wdk-mcp`; it does not send the wallet seed or passphrase as tool input. +- Wallet operations cross the local daemon endpoint. On Unix-like systems, owner-only socket permissions block other OS users, not other processes running as the wallet owner. +- An unlocked wallet behaves like a local hot wallet for the session. Read, signing, and send operations do not ask for the passphrase again. +- `--ttl 0` disables automatic expiry for that wallet. Prefer a finite TTL. +- Prompts or client-side confirmation dialogs are not daemon authorization controls. + +Read [Security Model](/cli/reference/security-model/) before exposing an unlocked wallet to an AI client or local automation. + +Lock the wallet when the session is finished: + +```bash title="Terminal" +wdk wallet lock --name agent-dev +``` + +## When To Use The MCP Toolkit + +Use [MCP Toolkit](/ai/mcp-toolkit/) when you need to build and control an MCP server in application code, select individual WDK tools, or add custom tools. + +Use the WDK CLI MCP server when you want the bundled tool set, local wallet daemon, and setup commands for Claude Desktop, Claude Code, or OpenClaw. + +*** + +## Need Help? + + diff --git a/content/docs/cli/index.mdx b/content/docs/cli/index.mdx new file mode 100644 index 00000000..e3cf2b9b --- /dev/null +++ b/content/docs/cli/index.mdx @@ -0,0 +1,98 @@ +--- +title: WDK CLI +description: Install and use the local WDK wallet CLI, daemon, and MCP server +docType: explanation +schemaType: TechArticle +icon: Terminal +--- + +WDK CLI provides a local command-line wallet built with WDK. Use it to manage named wallets, derive addresses, read balances and history, send registered assets, and connect an MCP-compatible AI client to the same local wallet daemon. + + +**Beta** - This documentation describes `@tetherto/wdk-cli@1.0.0-beta.1`. Test integrations with a dedicated wallet and limited funds before relying on them. + + +## Install + +WDK CLI requires Node.js 22.18.0 or later. + +```bash title="Terminal" +npm install -g @tetherto/wdk-cli@1.0.0-beta.1 +``` + +Verify the installation: + +```bash title="Terminal" +wdk --version +``` + +Install the scoped `@tetherto/wdk-cli` package. The unscoped `wdk-cli` package is not this CLI. + +## Installed Binaries + +| Binary | Purpose | +| --- | --- | +| `wdk` | Runs wallet, read, send, configuration, network, token, fiat-ramp, and MCP setup commands | +| `wdk-daemon` | Holds unlocked wallet instances and serves local wallet requests; `wdk` starts it when needed | +| `wdk-mcp` | Exposes WDK CLI wallet operations to supported MCP clients | + +Most users invoke `wdk`. The CLI and MCP integrations manage the other two binaries. + +## Command Map + +| Command | What it does | +| --- | --- | +| `wdk wallet` | Create, import, export, list, rename, delete, unlock, lock, and select wallets | +| `wdk get` | Derive addresses and read balances or transfer history | +| `wdk send` | Preview or broadcast native and registered-token transfers | +| `wdk buy`, `wdk sell` | Create MoonPay on-ramp or off-ramp URLs | +| `wdk config` | Read, update, reset, or locate local configuration | +| `wdk network` | Inspect built-in networks and manage custom networks | +| `wdk token` | Inspect built-in tokens and manage custom token entries | +| `wdk mcp` | Configure the bundled MCP server for a supported AI client | + +Run `wdk --help` for the command map or append `--help` to a command: + +```bash title="Terminal" +wdk wallet create --help +wdk get balance --help +``` + +## Choose an Interface + +| Interface | Use it when | +| --- | --- | +| WDK CLI | A person, shell script, or local agent can run `wdk` commands | +| Bundled MCP server | Claude Desktop, Claude Code, or OpenClaw should call the CLI's structured wallet tools | +| [MCP Toolkit](/ai/mcp-toolkit/) | You are building and customizing an MCP server in code | + +The CLI and bundled MCP server use the same local wallet store and daemon. Review the [security model](/cli/reference/security-model) before unlocking a funded wallet or giving another local process access to the MCP server. + +## Next Steps + + + +Create a wallet, derive an address, read its balance, and preview a transfer + + +Configure paths, defaults, indexer access, wallet modules, and MoonPay + + +Review all commands, required parameters, options, and defaults + + +Create, import, unlock, lock, rename, export, and delete wallets + + +Connect the bundled MCP server to a supported AI client + + +Use exit statuses and understand the current `--json` contract + + + +*** + +## Need Help? + + diff --git a/content/docs/cli/reference/architecture.mdx b/content/docs/cli/reference/architecture.mdx new file mode 100644 index 00000000..4299eb58 --- /dev/null +++ b/content/docs/cli/reference/architecture.mdx @@ -0,0 +1,131 @@ +--- +title: Architecture +description: Understand how the WDK CLI, MCP server, daemon, wallet store, and WDK modules work together. +docType: explanation +schemaType: TechArticle +icon: Network +--- + +WDK CLI separates short-lived command and MCP processes from a background wallet daemon. The daemon owns unlocked WDK instances; the command and MCP processes ask it to derive addresses, read balances, estimate fees, and sign transactions over local inter-process communication (IPC). + +```text +wdk command ─┐ + ├─ local IPC ─> wdk-daemon ─> WDK wallet modules ─> RPC provider +wdk-mcp ─────┘ │ + └─ in-memory unlocked wallets + +wdk command ─> WDK Indexer API (history, after deriving the address through the daemon) +``` + +## Components + +| Component | Lifetime | Responsibility | +| --- | --- | --- | +| `wdk` | One command | Parses arguments, prompts for secrets, reads and writes local configuration, formats output, and calls the daemon | +| `wdk-mcp` | MCP client session | Exposes structured wallet tools and calls the same command actions and daemon used by `wdk` | +| `wdk-daemon` | While at least one wallet is unlocked | Holds unlocked WDK instances, derives accounts, estimates fees, signs and sends transactions, and enforces wallet TTLs | +| `seed.enc` | Until the wallet is deleted | Stores one encrypted BIP-39 mnemonic for each named wallet | +| `config.json` | Until configuration is reset or removed | Stores non-seed CLI configuration, including the default wallet, networks, tokens, and provider settings | + +The CLI and MCP server share the same wallet store, configuration, and daemon. A wallet unlocked with `wdk wallet unlock` is therefore also available to `wdk-mcp` running as the same operating-system user. + +## Wallet creation and import + +Creating or importing a wallet does not require the daemon: + +```text +generated or entered mnemonic + │ + ├─ scrypt(passphrase, random salt) ─> AES-256-GCM + │ + └─> wallets/NAME/seed.enc +``` + +The command process validates that the mnemonic is a 12- or 24-word BIP-39 phrase. It then encrypts the phrase and writes [`seed.enc`](/cli/reference/storage-format). The first wallet becomes the default wallet automatically. + +The generated or imported mnemonic and passphrase are JavaScript strings in the command process during this flow. JavaScript strings cannot be reliably overwritten after use. See the [security model](/cli/reference/security-model#seed-and-passphrase-lifetime) for the resulting memory limitations. + +## Wallet unlock + +Unlocking crosses both the command and daemon processes: + +1. `wdk` reads the passphrase from a hidden prompt or `WDK_PASSPHRASE`. +2. The command process decrypts `seed.enc` to verify the passphrase. +3. The command process starts `wdk-daemon` if it is not already running. +4. It sends the wallet name, passphrase, and TTL to the daemon over local IPC. +5. The daemon decrypts `seed.enc`, derives the BIP-39 master-seed buffer, creates a WDK instance, and starts that wallet's TTL. + +The passphrase and mnemonic therefore do not exist only inside the daemon. They are briefly present in the command process during normal unlock, and the daemon decrypts the mnemonic again to create the long-lived wallet session. + + +An unlocked wallet is a local hot wallet for the operating-system user that owns the daemon. Any process running as that user that can connect to the daemon endpoint can request wallet operations without entering the passphrase again. Socket permissions separate operating-system users; they do not authenticate individual programs running as the same user. + + +## Requests while unlocked + +The daemon creates wallet accounts lazily. The first request for a network loads the configured wallet module and obtains the requested account index; later requests reuse cached accounts for that wallet session. + +| Operation | Where it runs | +| --- | --- | +| Address derivation | Daemon | +| Native and token balance reads | Daemon, followed by optional price lookup in the caller | +| Fee estimation | Daemon | +| Transaction signing and broadcast | Daemon | +| Transaction history | Caller queries the Indexer API after obtaining the address from the daemon | +| Wallet file creation, import, export, rename, and deletion | `wdk` command process | +| Network, token, and general configuration | `wdk` command process | + +CLI and MCP callers use the same signing path. A dry run estimates fees through the daemon but does not broadcast. A later request that executes a send goes directly to the daemon; the daemon does not implement a second confirmation or passphrase challenge. + +## IPC and operating systems + +On macOS and Linux, the daemon listens on a Unix domain socket: + +```text +~/.config/wdk-cli/daemon.sock +``` + +If `XDG_CONFIG_HOME` is set, the socket is under `$XDG_CONFIG_HOME/wdk-cli`. The daemon creates it under an owner-only `0077` umask; the current socket mode is `0700`. + +On Windows, the daemon uses the named pipe: + +```text +\\.\pipe\wdk-cli-daemon +``` + +POSIX file modes do not apply to the Windows named pipe. The current implementation relies on the platform's default named-pipe access control rather than creating an explicit security descriptor. + +IPC messages are newline-delimited JSON and are limited to 64 KiB. The protocol is internal to the CLI package and is not a versioned public API. Use `wdk` or `wdk-mcp` instead of building another client against it. + +## Session and shutdown lifecycle + +Each unlocked wallet has an independent TTL. The default is five minutes. + +```text +unlock or explicit re-unlock ─> start absolute TTL + │ + normal wallet use does not refresh it + │ + TTL expires or wallet is locked + │ + dispose wallet and remove its session + │ + last wallet locked ─> daemon exits +``` + +An explicit `wdk wallet unlock` for an already unlocked wallet resets that wallet's timer to the requested value. `--ttl 0` creates a session without automatic expiry. See [Manage wallets](/cli/guides/manage-wallets#control-the-unlock-lifetime) for commands and [Security model](/cli/reference/security-model#session-lifecycle) for the accepted trade-offs. + +On a normal lock, TTL expiry, `SIGINT`, or `SIGTERM`, the daemon disposes the affected WDK instances. When the last wallet is locked, it closes the IPC server, removes the socket and PID file, and exits. Abrupt process termination, system crashes, swap, and core dumps are outside this graceful-cleanup path. + +## Configuration changes + +Network configuration is captured by WDK instances when they are created. `wdk config set` and `wdk config reset` changes under `networks.*` lock the current wallet sessions so the next unlock creates fresh instances with the new settings. `wdk network create` and `wdk network delete` change the registry without automatically locking current sessions. + +Configuration and storage locations are covered in [Configuration](/cli/configuration) and [Storage format](/cli/reference/storage-format). + +## Next steps + +- [Manage wallets](/cli/guides/manage-wallets) +- [Storage format and manual recovery](/cli/reference/storage-format) +- [Security model](/cli/reference/security-model) +- [API reference](/cli/api-reference) diff --git a/content/docs/cli/reference/security-model.mdx b/content/docs/cli/reference/security-model.mdx new file mode 100644 index 00000000..63d0cbb0 --- /dev/null +++ b/content/docs/cli/reference/security-model.mdx @@ -0,0 +1,168 @@ +--- +title: Security Model +description: Understand seed protection, daemon trust boundaries, memory lifetime, TTL auto-locking, and operational trade-offs. +docType: explanation +schemaType: TechArticle +icon: Shield +--- + +WDK CLI protects a locked wallet's mnemonic with passphrase-based encryption and restricts security-critical local artifacts to the owning operating-system user on macOS and Linux. After unlock, it deliberately trusts processes running as that user. + + +Treat an unlocked WDK CLI wallet as a local hot wallet. Use a dedicated wallet with limited funds, keep the unlock TTL short, and lock it before running untrusted code. + + +## Security boundaries + +| Boundary | Current protection | Not protected | +| --- | --- | --- | +| Locked seed at rest | AES-256-GCM with a scrypt-derived key; `seed.enc` mode `0600` on macOS and Linux | Weak or empty passphrases, compromised owner account, root/administrator, backups, or storage capture while unlocked | +| Daemon endpoint | Unix socket mode `0700` under an owner-only umask | Another process running as the same owner; there is no per-program daemon credential | +| PID file | Mode `0600` on macOS and Linux | Process discovery through other operating-system interfaces | +| Wallet session | Per-wallet absolute TTL and explicit lock | Activity does not shorten or refresh exposure; `--ttl 0` has no automatic expiry | +| In-memory cleanup | WDK disposal plus best-effort zeroing of retained mutable buffers | Immutable JavaScript strings, copies inside dependencies, swap, core dumps, crashes, or abrupt termination | +| CLI and MCP sends | Dry-run support and caller-level guidance | The daemon does not enforce a second confirmation or passphrase check for an unlocked wallet | +| General configuration | Separate from `seed.enc` | `config.json` has no owner-only guarantee and may contain user-added credentials | + +These controls reduce accidental exposure and cross-user access. They do not make a general-purpose computer a hardware wallet or isolate an unlocked wallet from malware running under the same account. + +## Seed encryption at rest + +Each named wallet stores its BIP-39 mnemonic in `wallets/NAME/seed.enc`. Version 1 uses: + +- AES-256-GCM authenticated encryption +- scrypt with `N=65536`, `r=8`, and `p=1` +- a random 32-byte salt and 12-byte IV +- a 16-byte authentication tag +- an owner read/write `0600` file mode on macOS and Linux + +See [Storage format](/cli/reference/storage-format#seedenc-version-1) for field encodings and the manual-recovery contract. + +A wrong passphrase or modified encrypted payload fails GCM authentication. Encryption does not protect a mnemonic after a process has decrypted it. + +### Passphrase handling + +The interactive passphrase prompt hides input. For automation, `WDK_PASSPHRASE` overrides the prompt when it contains a non-empty value. + +Environment variables can be inherited by child processes and may be visible through process inspection, crash reports, shell tooling, or automation logs. Prefer the interactive prompt for manual use. If automation requires `WDK_PASSPHRASE`, scope it to one trusted process, prevent command tracing, and remove it immediately after use. + +The current CLI accepts an empty passphrase. The file is still AES-GCM ciphertext, but an empty passphrase provides no meaningful confidentiality because anyone who obtains the file knows the value required to derive its key. + +## Same-user daemon access + +On macOS and Linux, `daemon.sock` is available only to the owning operating-system user. This stops a different local user from connecting through the socket. It does not identify or authorize individual programs owned by that user. + +Once a wallet is unlocked: + +- a same-user process that can connect to `daemon.sock` can derive addresses, read balances, estimate fees, and request signed transactions +- it does not need to know or re-enter the wallet passphrase +- it can speak directly to the internal daemon endpoint instead of using the normal CLI or MCP user flow +- CLI dry runs and MCP instructions to preview and confirm are caller behavior, not daemon authorization controls + +This same-user signing capability is an accepted design trade-off: the daemon provides a reusable local wallet session, and the operating-system user is the session's trust boundary. + + +Do not run downloaded scripts, unreviewed packages, browser automation, plugins, or AI agents under the wallet owner's account while a valuable wallet is unlocked. Owner-only socket permissions do not protect against code that you run as the owner. + + +For stronger practical separation, run WDK CLI under a dedicated non-administrator operating-system account and do not run unrelated tools under that account. This does not protect against root/administrator compromise, but it narrows which processes can reach the owner-only endpoint. + +## Session lifecycle + +Each wallet has its own absolute unlock timer: + +| Action | Timer effect | +| --- | --- | +| `wdk wallet unlock --name NAME` | Starts the requested TTL; default is five minutes | +| Normal address, balance, history, fee, or send request | Does not refresh the TTL | +| Explicitly unlock an already unlocked wallet | Resets its timer to the new TTL | +| `wdk wallet unlock --name NAME --ttl 0` | Disables automatic expiry for that wallet | +| `wdk wallet lock --name NAME` | Immediately disposes that wallet session | +| `wdk wallet lock --all` | Disposes all wallet sessions | +| TTL expires | Disposes that wallet session | +| Last wallet locks or expires | Shuts down the daemon | + +An absolute timer limits the maximum duration from unlock without silently extending the session on every operation. It can also expire during a longer workflow because activity does not refresh it. Re-unlock explicitly when more time is needed. + +Use `--ttl 0` only in a controlled environment where you accept an unlocked session that lasts until explicit lock, daemon shutdown, process failure, or machine restart. It is not appropriate as a convenience default. + +## Seed and passphrase lifetime + +Normal unlock handles sensitive values in more than one place: + +1. The `wdk` command process receives the passphrase as a JavaScript string. +2. It decrypts `seed.enc` and receives the mnemonic as a JavaScript string to verify the passphrase. +3. It sends the passphrase through owner-restricted local IPC to the daemon. +4. The daemon decrypts the mnemonic as a JavaScript string. +5. The daemon derives a mutable BIP-39 master-seed `Buffer` and retains it with the WDK instance until lock. + +The encryption key buffers are zeroed after encryption or decryption. On normal lock or graceful daemon shutdown, the CLI disposes the WDK instance and zeroes the retained master-seed buffer. + +These are best-effort language-level controls, not a guarantee that every copy is erased: + +- JavaScript strings are immutable and garbage-collected +- WDK or wallet modules may hold internal copies while in use +- operating-system swap, hibernation, crash dumps, and debugger access can capture process memory +- `SIGKILL`, a power loss, or a runtime crash can bypass normal cleanup + +Use full-disk encryption, restrict crash dumps and debugger access, and keep the host patched and free of untrusted software. Locking promptly reduces exposure but cannot retroactively erase copies outside the CLI's control. + +## Wallet export, logs, and automation + +`wdk wallet create` displays the generated mnemonic, and `wdk wallet export` displays the decrypted mnemonic. With `--json`, that secret appears in structured stdout. + +Do not: + +- run create or export in CI +- capture their output in logs or agent transcripts +- paste output into tickets, chat, or AI tools +- include `WDK_PASSPHRASE` in a committed script +- pass a mnemonic or passphrase as a shell argument + +Use a private terminal, create an offline backup, and clear terminal scrollback after handling a mnemonic. + +## Configuration is not secret storage + +`config.json` is a normal plaintext file. It primarily contains public WDK-style configuration, but user-supplied values can include indexer keys or credentials embedded in provider and signing URLs. + +- Prefer `WDK_INDEXER_API_KEY` over storing the indexer key when your environment can protect it. +- Protect any credentials embedded in custom provider URLs separately. +- Review `wdk config get --all` before copying its output. +- Do not assume `config.json` has the same `0600` mode as `seed.enc`. + +See [Configuration](/cli/configuration) for supported settings and precedence. + +## Deletion and recovery + +Wallet deletion performs ordinary recursive filesystem removal after passphrase verification. It is not secure erase, and it cannot remove copies from backups, snapshots, swap, journals, or previously copied files. + +Maintain an independently tested backup and the passphrase. The documented [`seed.enc` version 1 recovery procedure](/cli/reference/storage-format#recover-without-wdk-cli) remains available even if a future format does not provide automated migration. + +## Operational checklist + +Before unlock: + +- use a dedicated wallet with only the funds needed for the task +- stop untrusted same-user processes +- confirm the wallet name and requested TTL +- prefer a hidden interactive passphrase prompt + +While unlocked: + +- preview recipient, amount, network, token, and fees +- remember that normal use does not refresh the timer +- do not install packages or run unreviewed scripts under the same user +- treat MCP clients and agents as capable of requesting real sends + +After use: + +- run `wdk wallet lock --name NAME` or `wdk wallet lock --all` +- verify `wdk wallet list` reports the wallet as locked +- clear terminals or files that displayed the mnemonic + +## Related pages + +- [Architecture](/cli/reference/architecture) +- [Storage format and manual recovery](/cli/reference/storage-format) +- [Manage wallets](/cli/guides/manage-wallets) +- [Use the MCP server](/cli/guides/use-mcp-server) diff --git a/content/docs/cli/reference/storage-format.mdx b/content/docs/cli/reference/storage-format.mdx new file mode 100644 index 00000000..93aa526c --- /dev/null +++ b/content/docs/cli/reference/storage-format.mdx @@ -0,0 +1,295 @@ +--- +title: Storage Format +description: Inspect WDK CLI storage, seed.enc version 1, file permissions, and the standalone manual-recovery procedure. +docType: reference +schemaType: TechArticle +icon: Database +--- + +WDK CLI stores each named wallet as an encrypted `seed.enc` file. This page defines the current version 1 format and provides a recovery path that uses only Node.js built-in modules if the CLI is unavailable. + + +Possession of a recovered seed phrase gives control of the wallet. Perform recovery on a trusted, offline computer. Do not upload `seed.enc` to a website, paste it into an AI assistant, or use an online decryption tool. + + +## Storage layout + +The default storage root is `~/.config/wdk-cli`. If `XDG_CONFIG_HOME` is non-empty, the root is `$XDG_CONFIG_HOME/wdk-cli`. + +```text +wdk-cli/ +├── config.json +├── daemon.pid +├── daemon.sock # macOS and Linux only +└── wallets/ + └── WALLET_NAME/ + └── seed.enc +``` + +Windows uses the named pipe `\\.\pipe\wdk-cli-daemon` instead of `daemon.sock`. + +Run the following command to print the exact `config.json` path: + +```bash title="Terminal" +wdk config path +``` + +## Current file permissions + +The current permissions are intentional and protect the artifacts that enforce seed-at-rest and daemon access controls. + +| Artifact | macOS and Linux | Purpose | +| --- | --- | --- | +| `wallets/NAME/seed.enc` | `0600` | Owner read and write only | +| `daemon.pid` | `0600` | Owner read and write only | +| `daemon.sock` | `0700` | Owner-only daemon endpoint | +| `config.json` | No owner-only mode is set by WDK CLI | Ordinary plaintext configuration; its resulting mode follows the config library and process environment and may be `0644` | +| Storage and wallet directories | No explicit mode is set by WDK CLI | Resulting modes depend on the process umask | + +POSIX modes do not apply on Windows. The CLI uses a named pipe for daemon IPC and relies on Windows access controls. + +`config.json` is not seed storage. However, values that you add can still be sensitive, including API keys and provider URLs containing credentials. Do not treat the absence of seed material as permission to publish the file. Prefer supported environment-variable overrides for secrets, and review output from `wdk config get --all` before sharing it. + +The Unix socket's owner-only mode excludes other operating-system users. It does not distinguish between programs running as the owner. See the [same-user trust boundary](/cli/reference/security-model#same-user-daemon-access). + +## `seed.enc` version 1 + +`seed.enc` is a UTF-8 JSON object with five fields: + +```json title="seed.enc shape" +{ + "version": 1, + "salt": "64 hexadecimal characters", + "iv": "24 hexadecimal characters", + "tag": "32 hexadecimal characters", + "ciphertext": "variable-length hexadecimal ciphertext" +} +``` + +All binary fields use hexadecimal encoding, not Base64. + +| Property | Version 1 value | +| --- | --- | +| Cipher | AES-256-GCM | +| Password KDF | scrypt | +| scrypt `N` | `65536` (`2^16`) | +| scrypt `r` | `8` | +| scrypt `p` | `1` | +| scrypt maximum memory | `134217728` bytes (128 MiB) | +| Derived key | 32 bytes | +| Salt | 32 random bytes | +| IV | 12 random bytes | +| Authentication tag | 16 bytes | +| Ciphertext encoding | Hexadecimal | +| Additional authenticated data | None | +| Plaintext | UTF-8 BIP-39 mnemonic | + +Only `version` and the four binary fields are stored. The algorithm and scrypt parameters are implicit in version 1. + +Each write generates a new random salt and IV. AES-GCM authenticates the ciphertext: a wrong passphrase or a modified salt, IV, tag, or ciphertext causes decryption to fail. + + +Users can rely on the documented version 1 recovery procedure. If a future format is introduced, an automated migration is not guaranteed, but the version 1 recovery procedure will remain documented so existing files can be recovered. + + +## Empty passphrases + +The current CLI accepts an empty passphrase. It still runs scrypt and writes AES-256-GCM ciphertext, so the mnemonic is not stored as plaintext. An empty passphrase is nevertheless known to any reader of the file and provides no meaningful confidentiality. File permissions become the only practical at-rest barrier. + +Use a strong, unique passphrase and keep a recoverable record separate from `seed.enc`. + +## Recover without WDK CLI + +This procedure requires Node.js `22.18.0` or later but does not import WDK CLI or any third-party package. + +Before recovery: + +1. Copy `seed.enc` and its backup to a trusted, offline computer. +2. Preserve the original file; do not edit it in place. +3. Close screen-sharing, logging, terminal recording, clipboard managers, and AI assistants. +4. Use a private terminal. The recovered mnemonic will appear in terminal output and may remain in scrollback. +5. Do not put the passphrase in a command argument or exported environment variable. + +Save the following as `recover-wdk-seed.mjs`: + +```javascript title="recover-wdk-seed.mjs" +import { createDecipheriv, scryptSync } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { emitKeypressEvents } from 'node:readline' +import process from 'node:process' + +const SCRYPT = { + N: 2 ** 16, + r: 8, + p: 1, + maxmem: 128 * 1024 * 1024 +} + +function decodeHex(field, value, expectedBytes) { + if (typeof value !== 'string' || value.length === 0) { + throw new Error(`${field} must be a non-empty hexadecimal string`) + } + if (value.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(value)) { + throw new Error(`${field} is not valid hexadecimal`) + } + + const decoded = Buffer.from(value, 'hex') + if (decoded.length * 2 !== value.length) { + throw new Error(`${field} is not valid hexadecimal`) + } + if (expectedBytes !== undefined && decoded.length !== expectedBytes) { + throw new Error(`${field} must decode to ${expectedBytes} bytes`) + } + return decoded +} + +function validatePayload(payload) { + if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('seed.enc must contain a JSON object') + } + if (payload.version !== 1) { + throw new Error(`unsupported seed.enc version: ${String(payload.version)}`) + } + + return { + salt: decodeHex('salt', payload.salt, 32), + iv: decodeHex('iv', payload.iv, 12), + tag: decodeHex('tag', payload.tag, 16), + ciphertext: decodeHex('ciphertext', payload.ciphertext) + } +} + +function promptHidden(message) { + if (!process.stdin.isTTY || !process.stdout.isTTY || + typeof process.stdin.setRawMode !== 'function') { + throw new Error('run this script directly in a private interactive terminal') + } + + emitKeypressEvents(process.stdin) + const wasRaw = process.stdin.isRaw + let secret = '' + + process.stdin.setRawMode(true) + process.stdin.resume() + + return new Promise((resolve, reject) => { + function finish(error) { + process.stdin.removeListener('keypress', onKeypress) + process.stdin.setRawMode(Boolean(wasRaw)) + if (!wasRaw) process.stdin.pause() + process.stdout.write('\n') + + if (error) reject(error) + else resolve(secret) + } + + function onKeypress(text, key = {}) { + if (key.ctrl && key.name === 'c') { + finish(new Error('recovery cancelled')) + return + } + if (key.name === 'return' || key.name === 'enter') { + finish() + return + } + if (key.name === 'backspace') { + secret = secret.slice(0, -1) + return + } + if (typeof text === 'string' && !key.ctrl && !key.meta && + !/[\u0000-\u001f\u007f]/.test(text)) { + secret += text + } + } + + process.stdin.on('keypress', onKeypress) + process.stdout.write(message) + }) +} + +function writeOutput(chunk) { + return new Promise((resolve, reject) => { + process.stdout.write(chunk, (error) => { + if (error) reject(error) + else resolve() + }) + }) +} + +async function main() { + const args = process.argv.slice(2) + if (args.length !== 1) { + throw new Error('usage: node recover-wdk-seed.mjs /path/to/seed.enc') + } + + const file = await readFile(args[0], 'utf8') + let payload + try { + payload = JSON.parse(file) + } catch { + throw new Error('seed.enc is not valid JSON') + } + + const { salt, iv, tag, ciphertext } = validatePayload(payload) + const passphrase = await promptHidden('Passphrase: ') + const key = scryptSync(passphrase, salt, 32, SCRYPT) + let decryptedChunk + let authenticatedTail + let plaintext + + try { + const decipher = createDecipheriv('aes-256-gcm', key, iv) + decipher.setAuthTag(tag) + decryptedChunk = decipher.update(ciphertext) + authenticatedTail = decipher.final() + plaintext = Buffer.concat([decryptedChunk, authenticatedTail]) + + await writeOutput('Recovered seed phrase:\n') + await writeOutput(plaintext) + await writeOutput('\n') + } finally { + key.fill(0) + decryptedChunk?.fill(0) + authenticatedTail?.fill(0) + plaintext?.fill(0) + } +} + +main().catch((error) => { + console.error(`Recovery failed: ${error.message}`) + process.exitCode = 1 +}) +``` + +Run it with only the `seed.enc` path as an argument: + +```bash title="Terminal" +chmod 700 recover-wdk-seed.mjs +node recover-wdk-seed.mjs "$HOME/.config/wdk-cli/wallets/WALLET_NAME/seed.enc" +``` + +Enter the passphrase at the hidden prompt. The script intentionally refuses non-interactive input so the passphrase is not supplied through a pipe, argument, or environment variable. + +The script validates the version, field encodings, and fixed field lengths before deriving the key. It makes a best-effort attempt to clear the derived key, decrypted chunks, and concatenated plaintext buffer after success or failure. The passphrase and displayed mnemonic still pass through JavaScript, OpenSSL internals, and terminal-managed memory, where reliable zeroization is not possible. + +After recovery: + +1. Verify the phrase by importing it into trusted wallet software while still offline. +2. Clear the terminal and close it to reduce scrollback exposure. +3. If the original computer or passphrase may be compromised, move funds to a newly generated seed. +4. Securely remove any temporary copies according to the storage medium and backup system you used. + +## Rename and deletion behavior + +Renaming a wallet moves its directory; it does not decrypt or re-encrypt `seed.enc`. + +Deleting a wallet removes its wallet directory after passphrase verification and attempts to lock its active daemon session first. This is ordinary filesystem deletion, not cryptographic erasure. Copies may remain in backups, snapshots, filesystem journals, swap, or recoverable storage blocks. + +Always keep an independently tested recovery backup before deleting or changing the only working copy. + +## Related pages + +- [Manage wallets](/cli/guides/manage-wallets) +- [Architecture](/cli/reference/architecture) +- [Security model](/cli/reference/security-model) +- [Configuration](/cli/configuration) diff --git a/content/docs/overview/changelog.mdx b/content/docs/overview/changelog.mdx index a6efd257..fd379acc 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -10,6 +10,13 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### July 27, 2026 + +**What's New** +- **WDK CLI** ([v1.0.0-beta.1](https://github.com/tetherto/wdk-cli/releases/tag/v1.0.0-beta.1)): Introduce the public `@tetherto/wdk-cli` beta with the `wdk` command-line interface, a local wallet daemon, and a bundled MCP server. The release supports encrypted named wallets, multi-network reads and sends, custom networks and tokens, indexer history, MoonPay buy/sell links, and MCP setup for supported AI tools. + +--- + ### July 07, 2026 **What's New** diff --git a/content/docs/start-building/build-with-ai.mdx b/content/docs/start-building/build-with-ai.mdx index 987116b9..edbf9d10 100644 --- a/content/docs/start-building/build-with-ai.mdx +++ b/content/docs/start-building/build-with-ai.mdx @@ -11,7 +11,7 @@ WDK documentation is optimized for AI coding assistants. Give your AI tool conte Use this page as the starting point for building WDK apps with AI assistance. - + Run a ready-made local wallet CLI, daemon, and MCP server for AI agents @@ -34,7 +34,7 @@ There are two practical ways to provide WDK context to your AI: 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?** Use [WDK CLI](/ai/wdk-cli/) for a ready-made local wallet CLI, daemon, and MCP server. Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. +**Want to give AI agents wallet access?** Use [WDK CLI](/cli/) for a ready-made local wallet CLI, daemon, and MCP server. Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. --- diff --git a/public/llms-full.txt b/public/llms-full.txt index eb1270f2..bd793910 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -71,96 +71,2378 @@ Explore our React Native UI Kit with pre-built components *** -## Agent Skills -URL: https://docs.wdk.tether.io/ai/agent-skills -Description: Give any AI agent self-custodial wallet capabilities with WDK agent skills +## 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. + + + +Run a ready-made local wallet CLI, daemon, and MCP server for AI agents + + +Build a custom MCP server that exposes WDK wallet tools + + +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?** Use [WDK CLI](/cli/) for a ready-made local wallet CLI, daemon, and MCP server. Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. + + +--- + +## 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-erc4337`, `@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? + + + +*** + +## WDK CLI +URL: https://docs.wdk.tether.io/cli +Description: Install and use the local WDK wallet CLI, daemon, and MCP server + +WDK CLI provides a local command-line wallet built with WDK. Use it to manage named wallets, derive addresses, read balances and history, send registered assets, and connect an MCP-compatible AI client to the same local wallet daemon. + + +**Beta** - This documentation describes `@tetherto/wdk-cli@1.0.0-beta.1`. Test integrations with a dedicated wallet and limited funds before relying on them. + + +## Install + +WDK CLI requires Node.js 22.18.0 or later. + +```bash title="Terminal" +npm install -g @tetherto/wdk-cli@1.0.0-beta.1 +``` + +Verify the installation: + +```bash title="Terminal" +wdk --version +``` + +Install the scoped `@tetherto/wdk-cli` package. The unscoped `wdk-cli` package is not this CLI. + +## Installed Binaries + +| Binary | Purpose | +| --- | --- | +| `wdk` | Runs wallet, read, send, configuration, network, token, fiat-ramp, and MCP setup commands | +| `wdk-daemon` | Holds unlocked wallet instances and serves local wallet requests; `wdk` starts it when needed | +| `wdk-mcp` | Exposes WDK CLI wallet operations to supported MCP clients | + +Most users invoke `wdk`. The CLI and MCP integrations manage the other two binaries. + +## Command Map + +| Command | What it does | +| --- | --- | +| `wdk wallet` | Create, import, export, list, rename, delete, unlock, lock, and select wallets | +| `wdk get` | Derive addresses and read balances or transfer history | +| `wdk send` | Preview or broadcast native and registered-token transfers | +| `wdk buy`, `wdk sell` | Create MoonPay on-ramp or off-ramp URLs | +| `wdk config` | Read, update, reset, or locate local configuration | +| `wdk network` | Inspect built-in networks and manage custom networks | +| `wdk token` | Inspect built-in tokens and manage custom token entries | +| `wdk mcp` | Configure the bundled MCP server for a supported AI client | + +Run `wdk --help` for the command map or append `--help` to a command: + +```bash title="Terminal" +wdk wallet create --help +wdk get balance --help +``` + +## Choose an Interface + +| Interface | Use it when | +| --- | --- | +| WDK CLI | A person, shell script, or local agent can run `wdk` commands | +| Bundled MCP server | Claude Desktop, Claude Code, or OpenClaw should call the CLI's structured wallet tools | +| [MCP Toolkit](/ai/mcp-toolkit/) | You are building and customizing an MCP server in code | + +The CLI and bundled MCP server use the same local wallet store and daemon. Review the [security model](/cli/reference/security-model) before unlocking a funded wallet or giving another local process access to the MCP server. + +## Next Steps + + + +Create a wallet, derive an address, read its balance, and preview a transfer + + +Configure paths, defaults, indexer access, wallet modules, and MoonPay + + +Review all commands, required parameters, options, and defaults + + +Create, import, unlock, lock, rename, export, and delete wallets + + +Connect the bundled MCP server to a supported AI client + + +Use exit statuses and understand the current `--json` contract + + + +*** + +## Need Help? + + + +*** + +## Configuration +URL: https://docs.wdk.tether.io/cli/configuration +Description: Configure WDK CLI paths, defaults, indexer access, wallet modules, and fiat ramps + +WDK CLI stores its configuration and wallet data under one local configuration directory. Use `wdk config` for supported changes instead of editing `config.json` directly. + +## Local Paths + +The default configuration directory is `~/.config/wdk-cli`. If `XDG_CONFIG_HOME` is a non-empty environment variable, WDK CLI uses `$XDG_CONFIG_HOME/wdk-cli` instead. + +| Data | Path | +| --- | --- | +| User configuration | `CONFIG_DIR/config.json` | +| Wallet seed | `CONFIG_DIR/wallets/NAME/seed.enc` | +| Daemon PID | `CONFIG_DIR/daemon.pid` | +| Daemon socket on Unix-like systems | `CONFIG_DIR/daemon.sock` | +| Daemon endpoint on Windows | `\\.\pipe\wdk-cli-daemon` | + +Print the resolved `config.json` path: + +```bash title="Terminal" +wdk config path +``` + +With JSON output: + +```bash title="Terminal" +wdk config path --json +``` + +```json +{"path":"/home/user/.config/wdk-cli/config.json"} +``` + +See [Storage Format](/cli/reference/storage-format) for seed-file and daemon-file permissions. + +## Configuration Precedence + +WDK CLI resolves runtime values in this order: + +| Value | Highest to lowest precedence | +| --- | --- | +| Configuration directory | Non-empty `XDG_CONFIG_HOME`, then `~/.config` | +| Wallet | Command `--wallet`, then `defaultWallet` | +| Account index | Command `--index`, then `defaultIndex`, then `0` | +| Indexer API key | Non-empty `WDK_INDEXER_API_KEY`, then stored `indexer.apiKey`, then an empty value | +| Wallet passphrase | Non-empty `WDK_PASSPHRASE`, then a hidden interactive prompt | + +`--wallet` and `--index` are options on wallet-dependent commands; they are not root flags. + +An empty `WDK_PASSPHRASE` value does not override the prompt. To use an empty passphrase, enter it interactively. See [Manage Wallets](/cli/guides/manage-wallets) before choosing an empty passphrase. + +## Environment Variables + +| Variable | Effect | +| --- | --- | +| `XDG_CONFIG_HOME` | Changes the parent directory used for WDK CLI data | +| `WDK_INDEXER_API_KEY` | Overrides `indexer.apiKey` for the current process | +| `WDK_PASSPHRASE` | Supplies a non-empty passphrase instead of opening a prompt | + +There are no beta.1 environment-variable mappings for `indexer.baseUrl`, wallet defaults, account defaults, network configuration, or MoonPay configuration. + + +Environment variables can be inherited by child processes and may be visible to other processes running as the same OS user. Limit their lifetime and do not print them in shell history, CI logs, or agent transcripts. + + +## Supported Keys + +| Key | Expected value | Default or behavior | +| --- | --- | --- | +| `defaultWallet` | Wallet name | The first created or imported wallet becomes the default; use `wdk wallet default` to change it | +| `defaultIndex` | Non-negative integer | `0` | +| `indexer.baseUrl` | Indexer base URL | `https://wdk-api.tether.io` | +| `indexer.apiKey` | Indexer API key | Empty; `WDK_INDEXER_API_KEY` overrides it | +| `ramp.moonpay.apiKey` | MoonPay API key | Empty | +| `ramp.moonpay.signUrl` | URL of your MoonPay signing service | Empty | +| `ramp.moonpay.environment` | `sandbox` or `production` | Empty; required by `wdk buy` and `wdk sell` | +| `networks.NETWORK` | Wallet-module configuration object | Defaults come from the installed `wdk.config.json` | +| `networks.NETWORK.KEY` | One wallet-module configuration value | Depends on the selected wallet module | + +Custom-network records and custom-token records also live in `config.json`. Manage them with `wdk network` and `wdk token` so the CLI can validate their shape and related state. + +## Read Configuration + +Read one global key: + +```bash title="Terminal" +wdk config get --key defaultIndex +``` + +Read one network configuration: + +```bash title="Terminal" +wdk config get --network ethereum +``` + +Read a key inside one network configuration: + +```bash title="Terminal" +wdk config get --network ethereum --key provider +``` + +Read the full configuration view: + +```bash title="Terminal" +wdk config get --all +``` + +`config get --all` excludes the custom-token registry. Use `wdk token list` to read tokens. + + +`config.json` is plaintext. The CLI does not request an owner-only mode for this file, so its effective permissions follow the operating system and runtime defaults and may be `0644`. `config get --all` can reveal stored API keys, signing URLs, and credentials embedded in provider URLs. Do not publish the file or command output. + + +Prefer `WDK_INDEXER_API_KEY` when you do not want to persist the indexer key. No environment override is available for the other supported secrets in beta.1. + +## Set Configuration + +Set a string: + +```bash title="Terminal" +wdk config set --key indexer.baseUrl --value https://indexer.example.com +``` + +Set a number: + +```bash title="Terminal" +wdk config set --key defaultIndex --value 1 +``` + +Set a JSON object: + +```bash title="Terminal" +wdk config set \ + --key ramp.moonpay \ + --value '{"apiKey":"pk_test_...","signUrl":"https://example.com/sign","environment":"sandbox"}' +``` + +`config set` parses a valid JSON value into its JSON type. If parsing fails, it stores the value as a string. Quote objects and arrays so the shell passes them as one argument. + +Set a network-specific key: + +```bash title="Terminal" +wdk config set \ + --network ethereum \ + --key provider \ + --value https://ethereum-rpc.publicnode.com +``` + +Replace a network's complete wallet-module configuration: + +```bash title="Terminal" +wdk config set \ + --network ethereum \ + --value '{"chainId":1,"provider":"https://ethereum-rpc.publicnode.com","transferMaxFee":5000000000000000}' +``` + +Network configuration is passed to the selected WDK wallet module. Use only keys supported by that module. + +## Reset Configuration + +Reset one global key: + +```bash title="Terminal" +wdk config reset --key indexer.baseUrl +``` + +Reset one network key: + +```bash title="Terminal" +wdk config reset --network ethereum --key provider +``` + +Reset configuration defaults: + +```bash title="Terminal" +wdk config reset --all +``` + +`config reset --all` preserves the default-wallet selection, custom networks, and custom tokens. It resets the remaining values to their installed defaults. + +## Authorization and Wallet Locking + +When at least one wallet exists, these configuration mutations verify the current default wallet's passphrase: + +- `wdk config set` +- `wdk config reset` +- `wdk network create` +- `wdk network delete` +- `wdk token add` +- `wdk token delete` + +Set `WDK_PASSPHRASE` for non-interactive local automation or enter the passphrase at the prompt. + +Changing or resetting a key under `networks` locks all wallets so the next unlock initializes WDK with the new wallet-module configuration. `config reset --all` also locks all wallets. + +## Indexer + +Transfer history uses the WDK Indexer API: + +```bash title="Terminal" +WDK_INDEXER_API_KEY=YOUR_INDEXER_API_KEY \ + wdk get history --network ethereum --wallet dev +``` + +Set a custom endpoint when needed: + +```bash title="Terminal" +wdk config set --key indexer.baseUrl --value https://indexer.example.com +``` + +`WDK_INDEXER_BASE_URL` is not read by beta.1. + +## MoonPay + +`wdk buy` and `wdk sell` require all three MoonPay values: + +```bash title="Terminal" +wdk config set --key ramp.moonpay.apiKey --value YOUR_MOONPAY_API_KEY +wdk config set --key ramp.moonpay.signUrl --value https://example.com/moonpay/sign +wdk config set --key ramp.moonpay.environment --value sandbox +``` + +Use `sandbox` for testnets and `production` for mainnets. The CLI rejects a sandbox/mainnet or production/testnet mismatch. + +*** + +## Need Help? + + + +*** + +## API Reference +URL: https://docs.wdk.tether.io/cli/api-reference +Description: Complete WDK CLI beta.1 command and option reference + +This page documents the 31 leaf commands in `@tetherto/wdk-cli@1.0.0-beta.1`. Run `wdk COMMAND --help` to inspect the installed command surface. + +## Root Options + +| Option | Behavior | +| --- | --- | +| `--json` | Requests machine-readable output from the selected command; see [JSON and exit behavior](#json-and-exit-behavior) for exceptions | +| `--verbose` | Adds a stack trace to handled errors; it does not enable general debug logging | +| `-V`, `--version` | Prints the CLI version followed by the installed WDK dependency versions | +| `-h`, `--help` | Prints help for the selected command | + +The WDK-specific global flags are `--json` and `--verbose`; version and help are also root options. Options such as `--wallet` and `--index` belong to individual commands. + +## Shared Wallet Selection + +Wallet-dependent read, send, buy, and sell commands use: + +| Option | Behavior | +| --- | --- | +| `--wallet ` | Uses the named wallet; otherwise uses `defaultWallet` | +| `--index ` | Uses a non-negative account index; otherwise uses `defaultIndex`, initially `0` | + +The selected wallet must be unlocked before daemon-backed operations. See [Manage Wallets](/cli/guides/manage-wallets). + +## Wallet Commands + +### `wdk wallet create` + +Creates a named wallet from a newly generated BIP-39 seed phrase. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--name ` | Yes | — | Wallet name | +| `--words ` | No | `12` | Seed length; accepts `12` or `24` | + +The first created wallet becomes the default. The command prompts for a passphrase and prints the seed phrase. With `--json`, the success object also contains `seedPhrase`. + +### `wdk wallet import` + +Imports an existing 12-word or 24-word BIP-39 seed phrase. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Wallet name | + +The command prompts for the seed phrase and a new storage passphrase. `WDK_PASSPHRASE` supplies only the passphrase; it does not supply the seed phrase. + +### `wdk wallet export` + +Decrypts and prints a wallet's seed phrase. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Wallet name | + +With `--json`, the success object contains `seedPhrase`. + + +The output from `wallet create` and `wallet export` is secret material in both text and JSON modes. Do not log it, paste it into an agent transcript, or store it in CI output. + + +### `wdk wallet list` + +Lists local wallets with their default, lock, and TTL state. This command has no command-specific options. + +### `wdk wallet delete` + +Deletes a named wallet after verifying its passphrase. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Wallet name | + +If the deleted wallet was the default, the CLI selects the first remaining wallet as the new default. See [Manage Wallets](/cli/guides/manage-wallets) for the deletion and backup implications. + +### `wdk wallet unlock` + +Unlocks a wallet and starts the daemon when needed. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--name ` | Yes | — | Wallet name | +| `--ttl ` | No | `5` | Non-negative session duration in minutes; `0` disables automatic expiry | + +Unlocking an already unlocked wallet resets that wallet's timer. The timer is absolute from unlock or reset; wallet activity does not extend it. + +### `wdk wallet lock` + +Locks one wallet or every wallet. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | One selector required | Wallet to lock | +| `--all` | One selector required | Lock every wallet | + +If both selectors are present, beta.1 applies `--all`. + +### `wdk wallet default` + +Sets the default wallet after passphrase confirmation. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Existing wallet name | + +### `wdk wallet rename` + +Renames a wallet after verifying its passphrase. An unlocked source wallet is locked first. + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Current wallet name | +| `--new-name ` | Yes | New wallet name | + +## Read Commands + +### `wdk get address` + +Derives an address for one network or for a network group. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | One selector required | — | Derive one network address | +| `--all` | One selector required | — | Derive addresses for all mainnets by default | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | +| `--testnet` | No | Off | With `--all`, select testnets instead of mainnets | + +When both `--network` and `--all` are supplied, beta.1 runs the single-network path. In aggregate mode, networks that fail address derivation are omitted from the result. + +### `wdk get balance` + +Reads one registered asset balance or native balances across a network group. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | One selector required | — | Query one network | +| `--all` | One selector required | — | Query native balances on all mainnets by default | +| `--token ` | No | Native asset | Registered ticker for a single-network query | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | +| `--testnet` | No | Off | With `--all`, select testnets instead of mainnets | + +`--token` is ignored by the aggregate path, which queries native assets. Networks that fail in aggregate mode are omitted. A missing price produces a USD value of `0` rather than failing the balance lookup. + +### `wdk get history` + +Reads token-transfer history through the configured indexer. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network to query | +| `--token ` | No | All indexer-supported tokens | Exact `metadata.indexerSlug` code; the installed registry yields `btc`, `usdt`, and `xaut`, while custom entries can add other codes | +| `--limit ` | No | `30` | Positive maximum number of transfers | +| `--from-date ` | No | — | ISO 8601 start date | +| `--to-date ` | No | — | ISO 8601 end date | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | + +When `--token` is omitted, beta.1 batches the network's supported token requests, ignores failed batch items, merges successful transfers by timestamp, and then applies `--limit`. + +## Send Command + +### `wdk send` + +Previews or broadcasts a native or registered-token transfer. + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network to send on | +| `--to
` | Yes | — | Recipient address | +| `--amount ` | Yes | — | Positive decimal amount, or an integer when `--base-units` is set | +| `--token ` | No | Native asset | Registered token ticker | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | +| `--base-units` | No | Off | Treat `--amount` as raw base units | +| `--dry-run` | No | Off | Estimate fees and return a preview without broadcasting | + +Use `--dry-run` before broadcasting: + +```bash title="Terminal" +wdk send \ + --network ethereum \ + --to 0x000000000000000000000000000000000000dEaD \ + --amount 0.001 \ + --dry-run +``` + +Without `--dry-run`, the command broadcasts immediately. There is no additional interactive confirmation. + +## Fiat Ramp Commands + +`wdk buy` and `wdk sell` derive the selected wallet address and create a provider URL. Both require an unlocked wallet and valid [MoonPay configuration](/cli/configuration#moonpay). + +### `wdk buy` + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network to receive the asset on | +| `--token ` | Yes | — | Registered asset code | +| `--fiat-amount ` | One amount required | — | Fiat amount to spend | +| `--crypto-amount ` | One amount required | — | Crypto amount to buy | +| `--fiat-currency ` | No | `usd` | Fiat currency code | +| `--module ` | No | `moonpay` | Fiat provider module | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | + +### `wdk sell` + +| Option | Required | Default | Description | +| --- | --- | --- | --- | +| `--network ` | Yes | — | Network holding the asset | +| `--token ` | Yes | — | Registered asset code | +| `--fiat-amount ` | One amount required | — | Target fiat amount | +| `--crypto-amount ` | One amount required | — | Crypto amount to sell | +| `--fiat-currency ` | No | `usd` | Fiat currency code | +| `--module ` | No | `moonpay` | Fiat provider module | +| `--wallet ` | No | Default wallet | Wallet selection | +| `--index ` | No | Configured index, initially `0` | Non-negative account index | + +For each command, provide exactly one of `--fiat-amount` and `--crypto-amount`. Beta.1 supports only the `moonpay` module. + +## Configuration Commands + +See [Configuration](/cli/configuration) for keys, types, precedence, and storage considerations. + +### `wdk config get` + +| Option | Required | Description | +| --- | --- | --- | +| `--key ` | One selector required | Read one dot-separated key | +| `--network ` | One selector required | Read a network object, or scope `--key` to a network | +| `--all` | One selector required | Read the configuration view | + +`--all` cannot be combined with `--key` or `--network`. `--network` and `--key` can be combined. + +### `wdk config set` + +| Option | Required | Description | +| --- | --- | --- | +| `--value ` | Yes | JSON value when parseable; otherwise a string | +| `--key ` | Without `--network` | Dot-separated key | +| `--network ` | No | Scope `--key`, or replace the network's entire configuration object | + +### `wdk config reset` + +| Option | Required | Description | +| --- | --- | --- | +| `--key ` | One selector required | Reset or remove one key | +| `--network ` | No | Scope `--key` to a network | +| `--all` | One selector required | Reset configuration while preserving the default wallet and custom network/token records | + +`--key` and `--all` are mutually exclusive. `--network` can be combined only with `--key`. + +### `wdk config path` + +Prints the resolved `config.json` path. This command has no command-specific options. + +## Network Commands + +### `wdk network list` + +| Option | Default | Description | +| --- | --- | --- | +| `--testnet` | Off | Show only testnets | +| `--mainnet` | Off | Show only mainnets | + +With neither option, the command shows every registered network. If both are provided, beta.1 applies `--testnet`. + +### `wdk network create ` + +Creates a custom network from an inline JSON object or a JSON file path. The `` positional argument is required. + +See [Custom Networks](/cli/guides/custom-networks) for the network schema and validation rules. + +### `wdk network delete` + +| Option | Required | Description | +| --- | --- | --- | +| `--name ` | Yes | Custom network to delete | + +Built-in networks cannot be deleted. Deleting a custom network also removes its network configuration and custom token entries. + +### `wdk network info` + +| Option | Required | Description | +| --- | --- | --- | +| `--network ` | Yes | Registered network to inspect | + +## Token Commands + +### `wdk token list` + +| Option | Default | Description | +| --- | --- | --- | +| `--network ` | All networks | Filter to one registered network | + +### `wdk token info` + +| Option | Required | Description | +| --- | --- | --- | +| `--network ` | Yes | Registered network | +| `--token ` | Yes | Registered token ticker | + +### `wdk token add ` + +Adds or overrides a token from an inline JSON object or a JSON file path. The `` positional argument is required. + +See [Manage Tokens](/cli/guides/manage-tokens) for the token schema and built-in override behavior. + +### `wdk token delete` + +| Option | Required | Description | +| --- | --- | --- | +| `--network ` | Yes | Registered network | +| `--token ` | Yes | Custom token ticker to delete | + +The command removes only a custom entry. If that entry overrides a built-in token, the built-in entry becomes effective again. + +## MCP Setup Commands + +The accepted `--ai-tool` values are `claude-desktop`, `claude-code`, and `openclaw`. + +### `wdk mcp setup` + +Adds the bundled MCP server to the selected client. `--ai-tool ` is required. + +### `wdk mcp remove` + +Removes the bundled MCP server from the selected client. `--ai-tool ` is required. + +### `wdk mcp verify-setup` + +Checks the selected client's configuration and tests the MCP server. `--ai-tool ` is required. + +### `wdk mcp list` + +Shows setup status for all supported clients. This command has no command-specific options. + +See [Use the MCP Server](/cli/guides/use-mcp-server) for client-specific setup and the exposed tool surface. + +## JSON and Exit Behavior + +Most command handlers print one JSON value to stdout when `--json` is set. The current contract has exceptions: + +- `wdk mcp setup`, `remove`, `verify-setup`, and `list` print human-readable success output even with `--json`. +- Help, version, unknown-command, unknown-option, and missing-required-option output remains text. +- Interactive wallet prompts render on stdout. If a wallet command opens a prompt, prompt text and terminal-control bytes can precede any JSON result; `wallet import` always prompts for the seed phrase. +- `wdk send` can write spinner or completion text to stderr while emitting JSON on stdout. +- A non-empty `WDK_PASSPHRASE` produces a notice on stderr. + +Parse stdout separately from stderr and always check the exit status. See [Handle Errors](/cli/guides/handle-errors) for the error envelope and exit-status contract. + +*** + +## Need Help? + + + +*** + +## Get Started +URL: https://docs.wdk.tether.io/cli/guides/get-started +Description: Install WDK CLI and complete a testnet wallet flow + +Install WDK CLI globally: + +```bash title="Terminal" +npm install -g @tetherto/wdk-cli@1.0.0-beta.1 +``` + +The current release is `1.0.0-beta.1`. Beta releases can change before the stable release. + + +Use a dedicated development wallet with testnet funds for this guide. Do not import a production seed phrase or fund this wallet with mainnet assets. + + +## Prerequisites + +- Node.js `22.18.0` or later +- npm +- A trusted source of Sepolia test ETH + +Verify the installation: + +```bash title="Terminal" +wdk --version +wdk --help +``` + +The package installs the `wdk`, `wdk-mcp`, and `wdk-daemon` binaries. Use `wdk` for the steps below. + +## 1. Create A Development Wallet + +Create a named 24-word wallet: + +```bash title="Terminal" +wdk wallet create --name quickstart-dev --words 24 +``` + +Enter a non-empty, unique passphrase when prompted. The command displays the generated seed phrase. Record it offline before continuing and do not put it in a terminal command, source file, or screenshot. + +The first wallet you create or import becomes the default wallet automatically. You do not need to run `wdk wallet default` for this flow. + +Unlock the wallet for five minutes: + +```bash title="Terminal" +wdk wallet unlock --name quickstart-dev --ttl 5 +``` + +The TTL starts at unlock and does not restart when you use the wallet. Unlock it again if the session expires while you complete this guide. See [Manage Wallets](/cli/guides/manage-wallets/) for session details. + +## 2. Get A Receiving Address + +Derive the wallet's Sepolia address: + +```bash title="Terminal" +wdk get address --network sepolia --wallet quickstart-dev +``` + +Copy the returned address into a trusted Sepolia faucet. Fund it with only enough test ETH to complete the example. + + +Receiving funds is an on-chain operation outside WDK CLI. Wait for the faucet transaction to confirm before checking the balance. + + +## 3. Check The Balance + +Read the wallet's native Sepolia balance: + +```bash title="Terminal" +wdk get balance --network sepolia --wallet quickstart-dev +``` + +If the command reports that the wallet is locked, unlock it again and repeat the balance check. + +## 4. Preview A Send + +Use an address you control as the recipient. A dry run estimates the fee and returns a transaction summary without broadcasting: + +```bash title="Terminal" +wdk send \ + --network sepolia \ + --to YOUR_SEPOLIA_ADDRESS \ + --amount 0.0001 \ + --wallet quickstart-dev \ + --dry-run +``` + +Check the network, recipient, amount, and estimated fee in the preview. + + +Removing `--dry-run` broadcasts the transaction. WDK CLI does not ask for another confirmation after the wallet is unlocked. Replace the placeholder with an address you control and review every value first. + + +When you intend to send the testnet transaction, run the same command without `--dry-run`: + +```bash title="Terminal" +wdk send \ + --network sepolia \ + --to YOUR_SEPOLIA_ADDRESS \ + --amount 0.0001 \ + --wallet quickstart-dev +``` + +## 5. Lock The Wallet + +End the wallet session when you finish: + +```bash title="Terminal" +wdk wallet lock --name quickstart-dev +``` + +Lock every unlocked wallet with: + +```bash title="Terminal" +wdk wallet lock --all +``` + +## Next Steps + +- [Manage Wallets](/cli/guides/manage-wallets/) - Import, export, rename, unlock, and delete wallets +- [Manage Tokens](/cli/guides/manage-tokens/) - Inspect and extend the token registry +- [Use the MCP Server](/cli/guides/use-mcp-server/) - Connect a supported AI client +- [Security Model](/cli/reference/security-model/) - Understand the daemon trust boundary and session trade-offs + +*** + +## Need Help? + + + +*** + +## Manage Wallets +URL: https://docs.wdk.tether.io/cli/guides/manage-wallets +Description: Create, import, select, unlock, lock, export, rename, and delete WDK CLI wallets safely. + +WDK CLI stores independent named wallets and keeps only explicitly unlocked wallets in the daemon. This guide covers the complete wallet lifecycle. + + +Wallet create, import, and export handle a BIP-39 seed phrase. Use a private terminal with logging, screen sharing, and AI assistants disabled. Anyone who obtains the phrase can control the wallet. + + +## Wallet names + +Wallet names may contain letters, numbers, hyphens, and underscores. Other characters are rejected. + +Examples in this guide use a wallet named `dev`: + +```bash title="Terminal" +wdk wallet list +``` + +## Create a wallet + +Create a 12-word wallet: + +```bash title="Terminal" +wdk wallet create --name dev +``` + +Create a 24-word wallet: + +```bash title="Terminal" +wdk wallet create --name dev --words 24 +``` + +The CLI asks for a passphrase twice, writes the encrypted mnemonic to `wallets/dev/seed.enc`, and displays the generated phrase once. Record both the mnemonic and passphrase in separate recoverable locations before continuing. + +The first wallet becomes the default automatically. You do not need to run `wdk wallet default` after creating the first wallet. + + +The current CLI permits an empty passphrase. Although `seed.enc` remains AES-GCM ciphertext, an empty passphrase provides no meaningful confidentiality. Use a strong, unique passphrase. + + +## Import a wallet + +Import an existing 12- or 24-word BIP-39 phrase: + +```bash title="Terminal" +wdk wallet import --name recovered +``` + +The CLI prompts for the phrase and then for a new local encryption passphrase. The seed-phrase input is interactive but is not masked, so import only in a private terminal. + +Import creates another encrypted local copy. It does not remove or change the source wallet or any existing backup. + +## List wallets and sessions + +Show stored wallets, the default wallet, lock status, and TTL remaining: + +```bash title="Terminal" +wdk wallet list +``` + +When a wallet is unlocked, the table reports either the approximate remaining time or `unlimited` for a `--ttl 0` session. + +In JSON mode, an unlocked entry includes `ttlMs` and `ttlRemaining` in milliseconds: + +```bash title="Terminal" +wdk --json wallet list +``` + +## Select the default wallet + +Commands use the default wallet when their `--wallet` option is omitted. + +```bash title="Terminal" +wdk wallet default --name dev +``` + +If a default wallet already exists, changing it requires that wallet's passphrase. This prevents an unconfirmed configuration change, but it does not unlock either wallet. + +To target another wallet for one supported operation without changing the default, pass its command-level option: + +```bash title="Terminal" +wdk get balance --network ethereum --wallet recovered +``` + +## Unlock a wallet + +Unlock with the default five-minute TTL: + +```bash title="Terminal" +wdk wallet unlock --name dev +``` + +Specify an absolute TTL in minutes: + +```bash title="Terminal" +wdk wallet unlock --name dev --ttl 15 +``` + +Unlock starts the daemon if needed. The CLI verifies the passphrase, then the daemon decrypts the wallet, creates its WDK instance, and starts the timer. + + +After unlock, any process running as the same operating-system user that can connect to the daemon endpoint can request signing or sending without entering the passphrase. Use a short TTL, avoid running untrusted code, and lock immediately after use. + + +### Control the unlock lifetime + +TTL is per wallet and starts at unlock: + +| Command or event | Result | +| --- | --- | +| Omit `--ttl` | Wallet locks after five minutes | +| `--ttl 15` | Wallet locks 15 minutes after unlock | +| Use the wallet | Timer continues; activity does not restart it | +| Unlock the wallet again | Timer resets to the newly requested TTL | +| `--ttl 0` | No automatic expiry | +| TTL expires | That wallet is disposed and locked | +| Last wallet locks | Daemon exits | + +`--ttl 0` accepts the risk of a session that remains unlocked until explicit lock, daemon shutdown, process failure, or machine restart: + +```bash title="Terminal" +wdk wallet unlock --name dev --ttl 0 +``` + +Use it only for a controlled local workflow. Normal wallet operations do not refresh any TTL, so a finite session can expire during a long task. + +## Lock wallets + +Lock one wallet: + +```bash title="Terminal" +wdk wallet lock --name dev +``` + +Lock every wallet: + +```bash title="Terminal" +wdk wallet lock --all +``` + +Locking disposes the wallet's WDK instance and removes the session from the daemon. The daemon exits after the last wallet locks. + +Verify the result: + +```bash title="Terminal" +wdk wallet list +``` + +Cleanup is best effort. The CLI zeroes retained mutable key and seed buffers on normal disposal, but JavaScript strings, dependency-internal copies, swap, core dumps, and abrupt termination cannot be guaranteed to be erased. See the [security model](/cli/reference/security-model#seed-and-passphrase-lifetime). + +## Export a seed phrase + + +Export prints the decrypted mnemonic. Do not run this command in CI, a recorded terminal, an agent session, or any environment that captures stdout. + + +Export a wallet: + +```bash title="Terminal" +wdk wallet export --name dev +``` + +The CLI asks for the wallet passphrase and prints the phrase. `--json` also includes the phrase in stdout; JSON does not make secret output safe to log. + +Use export to create or verify an offline recovery backup. If WDK CLI itself is unavailable, use the standalone [version 1 manual-recovery procedure](/cli/reference/storage-format#recover-without-wdk-cli). + +## Rename a wallet + +Rename a stored wallet: + +```bash title="Terminal" +wdk wallet rename --name dev --new-name development +``` + +The command verifies the old wallet's passphrase, locks that wallet if it is unlocked, and moves its storage directory. If it was the default, the new name becomes the default. Unlock it again under the new name before using it: + +```bash title="Terminal" +wdk wallet unlock --name development +``` + +Renaming does not decrypt or re-encrypt `seed.enc`. + +## Delete a wallet + + +Deletion is irreversible through WDK CLI. Confirm that you have tested the mnemonic and passphrase backup before deleting the only local copy. + + +Delete a wallet: + +```bash title="Terminal" +wdk wallet delete --name development +``` + +The command: + +1. verifies the wallet passphrase +2. attempts to lock an active session +3. recursively removes the wallet directory +4. chooses another stored wallet as the default when necessary + +Deletion is ordinary filesystem removal, not secure erase. Copies can remain in backups, snapshots, journals, swap, or recoverable storage blocks. + +## Passphrases in automation + +Commands that prompt for a passphrase read a non-empty `WDK_PASSPHRASE` value when it is set: + +```bash title="Terminal" +WDK_PASSPHRASE='YOUR_PASSPHRASE' wdk --json wallet unlock --name dev --ttl 5 +``` + +Environment variables can leak through process inspection, child-process inheritance, command tracing, crash reports, or automation logs. Prefer the hidden interactive prompt. If automation is necessary: + +- inject the value from a secret manager for one process +- disable command tracing and output capture +- do not commit the value to a script or configuration file +- lock the wallet and remove the environment variable immediately after use + +Never pass the passphrase or mnemonic as a CLI argument. + +## Related pages + +- [API reference](/cli/api-reference#wallet-commands) +- [Architecture](/cli/reference/architecture) +- [Storage format and manual recovery](/cli/reference/storage-format) +- [Security model](/cli/reference/security-model) + +*** + +## Manage Tokens +URL: https://docs.wdk.tether.io/cli/guides/manage-tokens +Description: Inspect, add, override, and remove WDK CLI token registry entries + +WDK CLI resolves token names such as `usdt` through a local registry. Each entry defines how the CLI formats amounts, selects a native or contract transfer, and connects the token to optional indexer, MoonPay, and price-provider features. + +The effective registry combines built-in entries with your custom entries. A custom entry with the same network and token key replaces the built-in entry until you remove the override. + +## Inspect The Registry + +List tokens across every network: + +```bash title="Terminal" +wdk token list +``` + +Filter the list to one network or inspect one entry: + +```bash title="Terminal" +wdk token list --network ethereum +wdk token info --network ethereum --token usdt +``` + +Use the lowercase registry key shown by these commands with `--token` on `get balance`, `send`, `buy`, and `sell`. + +`get history --token` is different: pass the exact `metadata.indexerSlug` value, which may differ from the registry key. + +## Token Spec + +`wdk token add ` accepts either an inline JSON object or the path to a JSON file. + +| Field | Required | Rules and effect | +| --- | --- | --- | +| `network` | Yes | Must name an existing built-in or custom network. | +| `token` | Yes | Registry key. Use lowercase letters, numbers, and hyphens; the first character must be a letter or number. | +| `symbol` | Yes | Non-empty display symbol, such as `DAI`. | +| `decimals` | Yes | Integer from `0` through `24`; used to convert decimal amounts to base units. | +| `isNative` | Yes | `true` uses the network's native transfer path; `false` uses a token contract or mint. | +| `address` | For non-native tokens | Non-empty contract or mint address. Optional for a native token. | +| `metadata` | No | Object containing supported provider mappings. | + +A network can have at most one effective entry with `isNative: true`. + +### Provider Metadata + +| Field | Used by | Effect when omitted | +| --- | --- | --- | +| `metadata.indexerSlug` | `wdk get history` | Pass this exact value to `--token`. Without it, a token-specific history request cannot use the token and an all-token history request skips it. The network also needs its own `indexerSlug`. | +| `metadata.moonpaySlug` | `wdk buy` and `wdk sell` | MoonPay operations do not support that token. | +| `metadata.bitfinexSlug` | USD price conversion | Balance and preview operations can continue without that provider's USD estimate. | + +Only `indexerSlug`, `moonpaySlug`, and `bitfinexSlug` are retained inside `metadata`. + +## Add A Custom Token + +Create a file named `dai-on-ethereum.json`: + +```json title="dai-on-ethereum.json" +{ + "network": "ethereum", + "token": "dai", + "symbol": "DAI", + "decimals": 18, + "isNative": false, + "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F" +} +``` + +Add the entry: + +```bash title="Terminal" +wdk token add ./dai-on-ethereum.json +``` + +You can also pass the JSON inline: + +```bash title="Terminal" +wdk token add '{"network":"ethereum","token":"dai","symbol":"DAI","decimals":18,"isNative":false,"address":"0x6B175474E89094C44Da98b954EedeAC495271d0F"}' +``` + +Verify the stored entry: + +```bash title="Terminal" +wdk token info --network ethereum --token dai +``` + + +Adding a registry entry does not transfer tokens or interact with the blockchain. Verify contract addresses, decimals, and provider identifiers independently before using the entry to query or send assets. + + +## Understand Mutation Confirmation + +`token add` and `token delete` protect registry changes with passphrase confirmation: + +- If at least one wallet exists, the CLI prompts for the current default wallet's passphrase. +- If no wallet exists, the registry command does not prompt for a passphrase. +- Confirmation does not unlock, extend, or lock an existing daemon wallet session. + +Set a valid default wallet before changing the registry if wallets already exist. + +## Override A Built-In Entry + +Adding a custom entry with the same `network` and `token` as a built-in entry replaces the complete effective entry; it does not merge individual fields. The CLI reports that the custom entry overrides the built-in entry. + +Use overrides carefully. An incorrect address, decimal count, or native-token flag can route later balance or send operations incorrectly. + +Remove the custom override to restore the built-in entry: + +```bash title="Terminal" +wdk token delete --network ethereum --token usdt +``` + +## Delete A Custom Entry + +Delete the DAI entry created above: + +```bash title="Terminal" +wdk token delete --network ethereum --token dai +``` + +`token delete` removes only custom entries. It rejects deletion of a built-in entry when no custom override exists. Deleting a custom network also removes every custom token stored under that network. + +`wdk config reset --all` preserves custom token entries. + +## Next Steps + +- [Custom Networks](/cli/guides/custom-networks/) - Create a network and its initial token entries together +- [Configuration](/cli/configuration/) - Configure provider and indexer access +- [API Reference](/cli/api-reference/) - Review token command flags and JSON results + +*** + +## Need Help? + + + +*** + +## Custom Networks +URL: https://docs.wdk.tether.io/cli/guides/custom-networks +Description: Add and remove blockchain networks in WDK CLI + +Use a custom network when WDK CLI already supports the network's wallet-module type but does not include the specific chain in its built-in registry. + +`wdk network create` cannot introduce a new wallet-module implementation. Its `module` field must match a wallet-module type already used by a built-in network. + +## Inspect Available Networks + +List built-in and custom networks: + +```bash title="Terminal" +wdk network list +``` + +Use JSON output to inspect both the versioned `module` and its unversioned `type`: + +```bash title="Terminal" +wdk --json network list +``` + +Use a returned `type`, such as `@tetherto/wdk-wallet-evm`, as the `module` value in a custom network spec. + +Inspect the effective metadata and SDK configuration for one network: + +```bash title="Terminal" +wdk network info --network ethereum +``` + +## Network Spec + +`wdk network create ` accepts either an inline JSON object or the path to a JSON file. + +| Field | Required | Rules and effect | +| --- | --- | --- | +| `network` | Yes | Unique identifier containing lowercase letters, numbers, and hyphens. The first character must be a letter or number. | +| `module` | Yes | Unversioned wallet-module type already used by a built-in network. | +| `displayName` | No | Non-empty display label. Defaults to the `network` value. | +| `testnet` | No | Boolean. Defaults to `false`. | +| `indexerSlug` | No | Non-empty WDK Indexer chain identifier. Without it, `get history` is unavailable for the custom network. | +| `config` | No | Object passed to the selected wallet module, such as a provider URL and chain ID. The wallet module validates these values when used. | +| `tokens` | No | Array of token specs to store with the network. Token keys must be unique, and at most one entry can be native. | + +Each item in `tokens` uses the fields documented in [Manage Tokens](/cli/guides/manage-tokens/#token-spec), with `network` omitted because the parent network supplies it. + +## Create A Custom Network + +Create `optimism.json`: + +```json title="optimism.json" +{ + "network": "optimism", + "module": "@tetherto/wdk-wallet-evm", + "displayName": "Optimism", + "testnet": false, + "config": { + "provider": "https://mainnet.optimism.io", + "chainId": 10 + }, + "tokens": [ + { + "token": "eth", + "symbol": "ETH", + "decimals": 18, + "isNative": true + } + ] +} +``` + +Create the network and its native-token entry: + +```bash title="Terminal" +wdk network create ./optimism.json +``` + +Verify the result: + +```bash title="Terminal" +wdk network info --network optimism +wdk token list --network optimism +``` + + +The example uses a public mainnet RPC endpoint. Verify the chain ID, provider, token addresses, and provider-specific limits before using a custom network with funds. Omit `indexerSlug` unless you know the WDK Indexer identifier for that chain. + + +## Creation Side Effects + +The CLI validates the complete network and token spec before storing it. On success, it writes: + +- The custom network metadata +- The network's SDK `config` +- Every entry in `tokens` + +If storing a token fails, the command rolls back the custom network, its SDK config, and token entries already written by that command. + +If at least one wallet exists, `network create` prompts for the current default wallet's passphrase. It does not require the wallet to be unlocked. With no wallets, it does not prompt. + +`network create` and `network delete` do not unlock, extend, or lock an existing daemon wallet session. `wdk config reset --all` preserves custom network entries. + +## Update Network Configuration + +Use `config set` to replace the complete SDK config object: + +```bash title="Terminal" +wdk config set \ + --network optimism \ + --value '{"provider":"https://mainnet.optimism.io","chainId":10}' +``` + +Or change one nested key: + +```bash title="Terminal" +wdk config set \ + --network optimism \ + --key provider \ + --value https://mainnet.optimism.io +``` + + +Changing `networks.*` configuration locks every unlocked wallet so the daemon drops cached wallet managers. Unlock the wallet again before reading balances or sending. + + +## Delete A Custom Network + + +Deleting a custom network also deletes its SDK configuration and every custom token under that network. Export or record the spec first if you may need to recreate it. + + +Delete the network: + +```bash title="Terminal" +wdk network delete --name optimism +``` + +Deletion prompts for the default wallet's passphrase when wallets exist. Built-in networks cannot be deleted. + +Deleting registry configuration does not move blockchain assets or delete a wallet seed, but the CLI can no longer access that network until you recreate the registry entry. + +## Next Steps + +- [Manage Tokens](/cli/guides/manage-tokens/) - Add token contracts and provider mappings +- [Configuration](/cli/configuration/) - Review paths, environment variables, and precedence +- [API Reference](/cli/api-reference/) - Review all network command flags + +*** + +## Need Help? + + + +*** + +## Use the MCP Server +URL: https://docs.wdk.tether.io/cli/guides/use-mcp-server +Description: Connect WDK CLI to Claude Desktop, Claude Code, or OpenClaw + +WDK CLI includes the `wdk-mcp` Model Context Protocol server. It exposes wallet operations as structured tools over stdio and routes wallet-dependent operations to the same local daemon used by the CLI. + + +Use a dedicated development wallet with limited funds. On Unix-like systems, while a wallet is unlocked, any process running as the same OS user that can connect to the owner-only daemon socket can request signing or sending without another passphrase. + + +## Prepare A Wallet + +Install WDK CLI and create a wallet as described in [Get Started](/cli/guides/get-started/). + +If `agent-dev` is your first wallet, it becomes the default automatically: + +```bash title="Terminal" +wdk wallet create --name agent-dev --words 24 +wdk wallet unlock --name agent-dev --ttl 5 +``` + +If another wallet is already the default, select `agent-dev` explicitly: + +```bash title="Terminal" +wdk wallet default --name agent-dev +``` + +Wallet-dependent MCP tools use the default wallet unless the request includes `wallet`. The wallet must be unlocked before those tools run. + +The TTL is absolute from unlock; MCP activity does not extend it. Use a short TTL during development and lock the wallet when the session ends. + +## Configure A Supported AI Client + +Choose one setup target: + +```bash title="Claude Desktop" +wdk mcp setup --ai-tool claude-desktop +``` + +```bash title="Claude Code" +wdk mcp setup --ai-tool claude-code +``` + +```bash title="OpenClaw" +wdk mcp setup --ai-tool openclaw +``` + +| Target | Setup behavior | +| --- | --- | +| Claude Desktop | Adds `wdk-wallet` under `mcpServers` in the platform-specific Claude Desktop JSON config. | +| Claude Code | Runs `claude mcp add -s user wdk-wallet -- ...` with the current Node.js executable and installed MCP script. | +| OpenClaw | Runs `openclaw mcp set wdk-wallet ...` with the current Node.js executable and installed MCP script. | + +Setup checks whether the target appears to be installed, tests the MCP server when possible, and reports the restart action required by that client. + +Verify one target: + +```bash title="Terminal" +wdk mcp verify-setup --ai-tool claude-desktop +``` + +List the configuration status of all three targets: + +```bash title="Terminal" +wdk mcp list +``` + +Remove a target configuration with: + +```bash title="Terminal" +wdk mcp remove --ai-tool claude-desktop +``` + +Replace `claude-desktop` with `claude-code` or `openclaw` as needed. + +## Available MCP Tools + +| Tool | Wallet required | Purpose | +| --- | --- | --- | +| `get_networks` | No | List networks, optionally filtered to mainnets or testnets. | +| `list_tokens` | No | List every registered token or filter by network. | +| `get_token` | No | Read one network and token registry entry. | +| `get_address` | Yes | Derive one address or addresses across networks. | +| `get_balance` | Yes | Read one balance or aggregate native balances with USD estimates. | +| `get_history` | Yes | Query indexer-backed token-transfer history for one network. | +| `send_token` | Yes | Preview or execute a native or registered-token transfer. | +| `buy_crypto` | Yes | Create a signed MoonPay buy URL. | +| `sell_crypto` | Yes | Create a signed MoonPay sell URL. | + +`get_history` needs indexer configuration. `buy_crypto` and `sell_crypto` need MoonPay configuration. See [Configuration](/cli/configuration/). + +## Preview And Confirm Transfers + +`send_token` defaults to `dryRun: true`. The recommended client workflow is: + +1. Call `send_token` without `dryRun`, or with `dryRun: true`. +2. Show the network, token, recipient, amount, and estimated fee to the user. +3. Ask the user to confirm those exact values. +4. Call `send_token` with `dryRun: false` only after confirmation. + +Example preview request: + +```json +{ + "network": "sepolia", + "to": "", + "amount": "0.0001", + "wallet": "agent-dev" +} +``` + +Example execution request after confirmation: + +```json +{ + "network": "sepolia", + "to": "", + "amount": "0.0001", + "wallet": "agent-dev", + "dryRun": false +} +``` + + +The preview-and-confirm sequence is guidance for the AI client; the daemon does not enforce it. A direct `send_token` call with `dryRun: false` broadcasts when the selected wallet is unlocked and the request is otherwise valid. + + +## Security And Session Boundaries + +- The AI client sends tool parameters to `wdk-mcp`; it does not send the wallet seed or passphrase as tool input. +- Wallet operations cross the local daemon endpoint. On Unix-like systems, owner-only socket permissions block other OS users, not other processes running as the wallet owner. +- An unlocked wallet behaves like a local hot wallet for the session. Read, signing, and send operations do not ask for the passphrase again. +- `--ttl 0` disables automatic expiry for that wallet. Prefer a finite TTL. +- Prompts or client-side confirmation dialogs are not daemon authorization controls. + +Read [Security Model](/cli/reference/security-model/) before exposing an unlocked wallet to an AI client or local automation. + +Lock the wallet when the session is finished: + +```bash title="Terminal" +wdk wallet lock --name agent-dev +``` + +## When To Use The MCP Toolkit + +Use [MCP Toolkit](/ai/mcp-toolkit/) when you need to build and control an MCP server in application code, select individual WDK tools, or add custom tools. + +Use the WDK CLI MCP server when you want the bundled tool set, local wallet daemon, and setup commands for Claude Desktop, Claude Code, or OpenClaw. + +*** + +## Need Help? + + + +*** + +## Handle Errors +URL: https://docs.wdk.tether.io/cli/guides/handle-errors +Description: Handle WDK CLI exit statuses, JSON output, stderr, and error codes + +Use a command's exit status as the primary success signal. When you request `--json`, parse stdout separately from stderr and allow for commands that still produce text. + +This page describes `@tetherto/wdk-cli@1.0.0-beta.1`. + +## Handled Error Envelope + +Errors that reach the WDK CLI command handler use this JSON shape: + +```json +{ + "error": "Wallet 'dev' is not unlocked.", + "code": "WALLET_NOT_UNLOCKED", + "suggestion": "Run: wdk wallet unlock --name dev" +} +``` + +| Field | Present | Description | +| --- | --- | --- | +| `error` | Always | Human-readable message | +| `code` | Always | Machine-readable code | +| `suggestion` | Sometimes | Recovery guidance | +| `stack` | Only with `--verbose` when available | JavaScript stack trace | + +`--verbose` adds error stacks. It does not enable general debug logging. + +## Exit Statuses + +| Status | Meaning in beta.1 | +| --- | --- | +| `0` | Command, help, or version output completed | +| `1` | A handled CLI/runtime error or a command-line parsing error occurred | +| `2` | An unexpected non-`Error` value or an error outside the normal command handler reached the top level | + +Do not treat the presence of stdout as success. Some handled failures emit a JSON object to stdout and exit with status `1`. + +## JSON Output Contract + +For most successful commands, `--json` emits one JSON value followed by a newline on stdout. Handled WDK CLI errors also emit one JSON object on stdout. + +The current exceptions are: + +| Case | Stdout | Stderr | Status | +| --- | --- | --- | --- | +| Most successful commands with `--json` | JSON | Usually empty | `0` | +| Handled command error with `--json` | JSON error envelope | Usually empty | `1`, or `2` for a non-`Error` value | +| Unknown command, unknown option, or missing required option | Empty | Human-readable Commander error and help | `1` | +| Help or version, even with `--json` | Human-readable text | Empty | `0` | +| Successful `wdk mcp setup`, `remove`, `verify-setup`, or `list` with `--json` | Human-readable text | Empty | `0` | +| Wallet command that opens an interactive prompt with `--json` | Prompt text and terminal-control bytes, followed by JSON if the command completes | Normal notices or errors can still appear | Depends on result | +| `wdk send --json` | JSON on completion or a handled error | May contain spinner, success, or failure text | Depends on result | +| A command using non-empty `WDK_PASSPHRASE` | Normal command output | Includes a passphrase-source notice | Depends on result | + + +Do not combine stdout and stderr before parsing JSON. Spinner output and notices can make a combined stream invalid JSON. + + +`--json` changes output formatting; it does not make every command non-interactive or guarantee JSON-only stdout when a prompt opens. Wallet create, import, export, unlock, delete, default, and rename flows can still request secret input. Set a non-empty `WDK_PASSPHRASE` only when you accept the environment-variable exposure described in [Configuration](/cli/configuration#environment-variables). `wallet import` still prompts for the seed phrase, so its stdout is not a clean JSON stream even when the environment variable supplies the passphrase. + +## Handle Output in a Shell Script + +Capture the streams separately and inspect the exit status before parsing. This example requires `jq`: + +```bash title="check-balance.sh" +#!/usr/bin/env bash +set -euo pipefail + +stdout_file=$(mktemp) +stderr_file=$(mktemp) +trap 'rm -f "$stdout_file" "$stderr_file"' EXIT + +if wdk get balance \ + --network ethereum \ + --wallet dev \ + --json >"$stdout_file" 2>"$stderr_file"; then + jq . "$stdout_file" +elif jq -e 'type == "object" and has("code")' "$stdout_file" >/dev/null 2>&1; then + jq . "$stdout_file" >&2 + exit 1 +else + cat "$stderr_file" >&2 + exit 1 +fi +``` + +This pattern handles both JSON command errors and text-only argument parsing errors. + +## Error Codes + +The following names are defined by beta.1. A code can appear only on commands that reach the corresponding behavior. + +| Area | Codes | +| --- | --- | +| Wallet and key state | `KEY_NOT_FOUND`, `INVALID_SEED_PHRASE`, `WRONG_PASSPHRASE`, `WALLET_NOT_UNLOCKED`, `WALLET_EXISTS`, `WALLET_LOCKED`, `PASSPHRASE_MISMATCH` | +| Arguments and configuration | `INVALID_ARGUMENT`, `INVALID_INDEX`, `INVALID_CONFIG`, `MISSING_CONFIG`, `INVALID_AMOUNT`, `INVALID_TOKEN` | +| Networks and tokens | `NETWORK_NOT_SUPPORTED`, `TOKEN_NOT_SUPPORTED`, `NETWORK_ERROR` | +| Transactions and providers | `INSUFFICIENT_BALANCE`, `TRANSACTION_FAILED`, `UNSUPPORTED_MODULE`, `ENVIRONMENT_MISMATCH`, `SIGN_FAILED`, `PROVIDER_UNAVAILABLE`, `QUOTE_REJECTED` | +| Fallbacks | `UNKNOWN_ERROR`, `UNEXPECTED_ERROR` | + +The code set is not a closed protocol enum. The daemon and WDK dependencies can pass through additional codes. Beta.1 also recognizes and formats several dependency codes, including `INSUFFICIENT_FUNDS`, `SERVER_ERROR`, and `TIMEOUT`. Consumers should preserve unknown code strings instead of rejecting the response. + +## Common Recovery Paths + +| Code or symptom | Check | +| --- | --- | +| `WALLET_NOT_UNLOCKED` | Run `wdk wallet unlock --name NAME`; confirm that its TTL has not expired | +| `KEY_NOT_FOUND` | Run `wdk wallet list`; verify the wallet name or default wallet | +| `WRONG_PASSPHRASE` | Retry through the hidden prompt; do not print or log the passphrase | +| `NETWORK_NOT_SUPPORTED` | Run `wdk network list`; check spelling and custom-network state | +| `TOKEN_NOT_SUPPORTED` | Run `wdk token list --network NETWORK`; use the registered ticker | +| `MISSING_CONFIG` for history | Configure `indexer.baseUrl` and, when required, `WDK_INDEXER_API_KEY` | +| `MISSING_CONFIG` for buy or sell | Configure all `ramp.moonpay` values | +| `ENVIRONMENT_MISMATCH` | Use MoonPay `sandbox` with a testnet or `production` with a mainnet | +| `NETWORK_ERROR`, `TIMEOUT`, or `SERVER_ERROR` | Check the RPC/indexer endpoint and retry only when repeating the operation is safe | +| Text error with empty stdout | Treat it as an argument/help parsing failure; inspect stderr | + +## Partial Results + +Some successful aggregate operations omit failed items: + +- `wdk get address --all` skips networks that fail address derivation. +- `wdk get balance --all` skips networks that fail address or balance lookup. +- `wdk get history` without `--token` ignores failed token batch items and returns successful transfers. + +These commands can exit `0` with an incomplete aggregate result. If completeness matters, query each required network or token separately and track failures in your application. + +*** + +## Need Help? + + + +*** + +## Architecture +URL: https://docs.wdk.tether.io/cli/reference/architecture +Description: Understand how the WDK CLI, MCP server, daemon, wallet store, and WDK modules work together. + +WDK CLI separates short-lived command and MCP processes from a background wallet daemon. The daemon owns unlocked WDK instances; the command and MCP processes ask it to derive addresses, read balances, estimate fees, and sign transactions over local inter-process communication (IPC). + +```text +wdk command ─┐ + ├─ local IPC ─> wdk-daemon ─> WDK wallet modules ─> RPC provider +wdk-mcp ─────┘ │ + └─ in-memory unlocked wallets + +wdk command ─> WDK Indexer API (history, after deriving the address through the daemon) +``` + +## Components + +| Component | Lifetime | Responsibility | +| --- | --- | --- | +| `wdk` | One command | Parses arguments, prompts for secrets, reads and writes local configuration, formats output, and calls the daemon | +| `wdk-mcp` | MCP client session | Exposes structured wallet tools and calls the same command actions and daemon used by `wdk` | +| `wdk-daemon` | While at least one wallet is unlocked | Holds unlocked WDK instances, derives accounts, estimates fees, signs and sends transactions, and enforces wallet TTLs | +| `seed.enc` | Until the wallet is deleted | Stores one encrypted BIP-39 mnemonic for each named wallet | +| `config.json` | Until configuration is reset or removed | Stores non-seed CLI configuration, including the default wallet, networks, tokens, and provider settings | + +The CLI and MCP server share the same wallet store, configuration, and daemon. A wallet unlocked with `wdk wallet unlock` is therefore also available to `wdk-mcp` running as the same operating-system user. + +## Wallet creation and import + +Creating or importing a wallet does not require the daemon: + +```text +generated or entered mnemonic + │ + ├─ scrypt(passphrase, random salt) ─> AES-256-GCM + │ + └─> wallets/NAME/seed.enc +``` + +The command process validates that the mnemonic is a 12- or 24-word BIP-39 phrase. It then encrypts the phrase and writes [`seed.enc`](/cli/reference/storage-format). The first wallet becomes the default wallet automatically. + +The generated or imported mnemonic and passphrase are JavaScript strings in the command process during this flow. JavaScript strings cannot be reliably overwritten after use. See the [security model](/cli/reference/security-model#seed-and-passphrase-lifetime) for the resulting memory limitations. + +## Wallet unlock + +Unlocking crosses both the command and daemon processes: + +1. `wdk` reads the passphrase from a hidden prompt or `WDK_PASSPHRASE`. +2. The command process decrypts `seed.enc` to verify the passphrase. +3. The command process starts `wdk-daemon` if it is not already running. +4. It sends the wallet name, passphrase, and TTL to the daemon over local IPC. +5. The daemon decrypts `seed.enc`, derives the BIP-39 master-seed buffer, creates a WDK instance, and starts that wallet's TTL. + +The passphrase and mnemonic therefore do not exist only inside the daemon. They are briefly present in the command process during normal unlock, and the daemon decrypts the mnemonic again to create the long-lived wallet session. + + +An unlocked wallet is a local hot wallet for the operating-system user that owns the daemon. Any process running as that user that can connect to the daemon endpoint can request wallet operations without entering the passphrase again. Socket permissions separate operating-system users; they do not authenticate individual programs running as the same user. + + +## Requests while unlocked + +The daemon creates wallet accounts lazily. The first request for a network loads the configured wallet module and obtains the requested account index; later requests reuse cached accounts for that wallet session. + +| Operation | Where it runs | +| --- | --- | +| Address derivation | Daemon | +| Native and token balance reads | Daemon, followed by optional price lookup in the caller | +| Fee estimation | Daemon | +| Transaction signing and broadcast | Daemon | +| Transaction history | Caller queries the Indexer API after obtaining the address from the daemon | +| Wallet file creation, import, export, rename, and deletion | `wdk` command process | +| Network, token, and general configuration | `wdk` command process | + +CLI and MCP callers use the same signing path. A dry run estimates fees through the daemon but does not broadcast. A later request that executes a send goes directly to the daemon; the daemon does not implement a second confirmation or passphrase challenge. + +## IPC and operating systems + +On macOS and Linux, the daemon listens on a Unix domain socket: + +```text +~/.config/wdk-cli/daemon.sock +``` + +If `XDG_CONFIG_HOME` is set, the socket is under `$XDG_CONFIG_HOME/wdk-cli`. The daemon creates it under an owner-only `0077` umask; the current socket mode is `0700`. + +On Windows, the daemon uses the named pipe: + +```text +\\.\pipe\wdk-cli-daemon +``` + +POSIX file modes do not apply to the Windows named pipe. The current implementation relies on the platform's default named-pipe access control rather than creating an explicit security descriptor. + +IPC messages are newline-delimited JSON and are limited to 64 KiB. The protocol is internal to the CLI package and is not a versioned public API. Use `wdk` or `wdk-mcp` instead of building another client against it. + +## Session and shutdown lifecycle + +Each unlocked wallet has an independent TTL. The default is five minutes. + +```text +unlock or explicit re-unlock ─> start absolute TTL + │ + normal wallet use does not refresh it + │ + TTL expires or wallet is locked + │ + dispose wallet and remove its session + │ + last wallet locked ─> daemon exits +``` + +An explicit `wdk wallet unlock` for an already unlocked wallet resets that wallet's timer to the requested value. `--ttl 0` creates a session without automatic expiry. See [Manage wallets](/cli/guides/manage-wallets#control-the-unlock-lifetime) for commands and [Security model](/cli/reference/security-model#session-lifecycle) for the accepted trade-offs. + +On a normal lock, TTL expiry, `SIGINT`, or `SIGTERM`, the daemon disposes the affected WDK instances. When the last wallet is locked, it closes the IPC server, removes the socket and PID file, and exits. Abrupt process termination, system crashes, swap, and core dumps are outside this graceful-cleanup path. + +## Configuration changes + +Network configuration is captured by WDK instances when they are created. `wdk config set` and `wdk config reset` changes under `networks.*` lock the current wallet sessions so the next unlock creates fresh instances with the new settings. `wdk network create` and `wdk network delete` change the registry without automatically locking current sessions. + +Configuration and storage locations are covered in [Configuration](/cli/configuration) and [Storage format](/cli/reference/storage-format). + +## Next steps + +- [Manage wallets](/cli/guides/manage-wallets) +- [Storage format and manual recovery](/cli/reference/storage-format) +- [Security model](/cli/reference/security-model) +- [API reference](/cli/api-reference) + +*** + +## Storage Format +URL: https://docs.wdk.tether.io/cli/reference/storage-format +Description: Inspect WDK CLI storage, seed.enc version 1, file permissions, and the standalone manual-recovery procedure. + +WDK CLI stores each named wallet as an encrypted `seed.enc` file. This page defines the current version 1 format and provides a recovery path that uses only Node.js built-in modules if the CLI is unavailable. + + +Possession of a recovered seed phrase gives control of the wallet. Perform recovery on a trusted, offline computer. Do not upload `seed.enc` to a website, paste it into an AI assistant, or use an online decryption tool. + + +## Storage layout + +The default storage root is `~/.config/wdk-cli`. If `XDG_CONFIG_HOME` is non-empty, the root is `$XDG_CONFIG_HOME/wdk-cli`. + +```text +wdk-cli/ +├── config.json +├── daemon.pid +├── daemon.sock # macOS and Linux only +└── wallets/ + └── WALLET_NAME/ + └── seed.enc +``` + +Windows uses the named pipe `\\.\pipe\wdk-cli-daemon` instead of `daemon.sock`. + +Run the following command to print the exact `config.json` path: + +```bash title="Terminal" +wdk config path +``` + +## Current file permissions + +The current permissions are intentional and protect the artifacts that enforce seed-at-rest and daemon access controls. + +| Artifact | macOS and Linux | Purpose | +| --- | --- | --- | +| `wallets/NAME/seed.enc` | `0600` | Owner read and write only | +| `daemon.pid` | `0600` | Owner read and write only | +| `daemon.sock` | `0700` | Owner-only daemon endpoint | +| `config.json` | No owner-only mode is set by WDK CLI | Ordinary plaintext configuration; its resulting mode follows the config library and process environment and may be `0644` | +| Storage and wallet directories | No explicit mode is set by WDK CLI | Resulting modes depend on the process umask | + +POSIX modes do not apply on Windows. The CLI uses a named pipe for daemon IPC and relies on Windows access controls. + +`config.json` is not seed storage. However, values that you add can still be sensitive, including API keys and provider URLs containing credentials. Do not treat the absence of seed material as permission to publish the file. Prefer supported environment-variable overrides for secrets, and review output from `wdk config get --all` before sharing it. + +The Unix socket's owner-only mode excludes other operating-system users. It does not distinguish between programs running as the owner. See the [same-user trust boundary](/cli/reference/security-model#same-user-daemon-access). + +## `seed.enc` version 1 + +`seed.enc` is a UTF-8 JSON object with five fields: + +```json title="seed.enc shape" +{ + "version": 1, + "salt": "64 hexadecimal characters", + "iv": "24 hexadecimal characters", + "tag": "32 hexadecimal characters", + "ciphertext": "variable-length hexadecimal ciphertext" +} +``` + +All binary fields use hexadecimal encoding, not Base64. + +| Property | Version 1 value | +| --- | --- | +| Cipher | AES-256-GCM | +| Password KDF | scrypt | +| scrypt `N` | `65536` (`2^16`) | +| scrypt `r` | `8` | +| scrypt `p` | `1` | +| scrypt maximum memory | `134217728` bytes (128 MiB) | +| Derived key | 32 bytes | +| Salt | 32 random bytes | +| IV | 12 random bytes | +| Authentication tag | 16 bytes | +| Ciphertext encoding | Hexadecimal | +| Additional authenticated data | None | +| Plaintext | UTF-8 BIP-39 mnemonic | + +Only `version` and the four binary fields are stored. The algorithm and scrypt parameters are implicit in version 1. + +Each write generates a new random salt and IV. AES-GCM authenticates the ciphertext: a wrong passphrase or a modified salt, IV, tag, or ciphertext causes decryption to fail. + + +Users can rely on the documented version 1 recovery procedure. If a future format is introduced, an automated migration is not guaranteed, but the version 1 recovery procedure will remain documented so existing files can be recovered. + + +## Empty passphrases + +The current CLI accepts an empty passphrase. It still runs scrypt and writes AES-256-GCM ciphertext, so the mnemonic is not stored as plaintext. An empty passphrase is nevertheless known to any reader of the file and provides no meaningful confidentiality. File permissions become the only practical at-rest barrier. + +Use a strong, unique passphrase and keep a recoverable record separate from `seed.enc`. + +## Recover without WDK CLI + +This procedure requires Node.js `22.18.0` or later but does not import WDK CLI or any third-party package. + +Before recovery: -WDK provides agent skills: structured instruction sets that teach AI agents how to create wallets, send transactions, swap tokens, bridge assets, and interact with DeFi protocols across 20+ blockchains. All operations are self-custodial. Keys stay on your machine, with no third-party custody dependency. +1. Copy `seed.enc` and its backup to a trusted, offline computer. +2. Preserve the original file; do not edit it in place. +3. Close screen-sharing, logging, terminal recording, clipboard managers, and AI assistants. +4. Use a private terminal. The recovered mnemonic will appear in terminal output and may remain in scrollback. +5. Do not put the passphrase in a command argument or exported environment variable. - -**Skill vs MCP Toolkit**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](mcp-toolkit/) when your agent supports the Model Context Protocol natively (e.g., Claude, Cursor). Use both for maximum coverage. - +Save the following as `recover-wdk-seed.mjs`: -## What Are Agent Skills? +```javascript title="recover-wdk-seed.mjs" +import { createDecipheriv, scryptSync } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { emitKeypressEvents } from 'node:readline' +import process from 'node:process' -An agent skill is a structured set of instructions and reference documentation that teaches an AI agent to use a specific tool or SDK. Skills follow the [AgentSkills specification](https://agentskills.io/specification). Each skill is a `SKILL.md` file with frontmatter metadata and detailed instructions that any compatible agent can load and execute. +const SCRYPT = { + N: 2 ** 16, + r: 8, + p: 1, + maxmem: 128 * 1024 * 1024 +} -WDK publishes a skill that covers the full SDK surface: wallet modules, swidge, swap, bridge, lending, fiat on/off-ramps, and the indexer. When an agent loads the skill, it learns WDK's APIs so you don't need blockchain expertise to get started. You can view the full skill file on [GitHub](https://github.com/tetherto/wdk-docs/blob/main/skills/wdk/SKILL.md). +function decodeHex(field, value, expectedBytes) { + if (typeof value !== 'string' || value.length === 0) { + throw new Error(`${field} must be a non-empty hexadecimal string`) + } + if (value.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(value)) { + throw new Error(`${field} is not valid hexadecimal`) + } -## Capabilities + const decoded = Buffer.from(value, 'hex') + if (decoded.length * 2 !== value.length) { + throw new Error(`${field} is not valid hexadecimal`) + } + if (expectedBytes !== undefined && decoded.length !== expectedBytes) { + throw new Error(`${field} must decode to ${expectedBytes} bytes`) + } + return decoded +} -Once an agent loads the WDK skill, it can: +function validatePayload(payload) { + if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('seed.enc must contain a JSON object') + } + if (payload.version !== 1) { + throw new Error(`unsupported seed.enc version: ${String(payload.version)}`) + } -| Category | Operations | -| --- | --- | -| **Wallets** | Create and recover wallets across EVM chains, Bitcoin, Solana, Spark, TON, and Tron | -| **Transactions** | Send native tokens and token transfers (ERC-20, SPL, Jetton, TRC-20) | -| **Swaps and routes** | DEX swaps via Velora and Swidge routes through providers such as Orchestra | -| **Bridges** | Cross-chain bridges with USDT0 via LayerZero | -| **Lending** | Supply, borrow, repay, and withdraw via Aave V3 | -| **Fiat** | Buy and sell crypto via MoonPay on/off-ramps | -| **Gasless** | Fee-free transfers on TON (via paymaster) and Tron (via gas-free service), and ERC-4337 account abstraction on EVM | + return { + salt: decodeHex('salt', payload.salt, 32), + iv: decodeHex('iv', payload.iv, 12), + tag: decodeHex('tag', payload.tag, 16), + ciphertext: decodeHex('ciphertext', payload.ciphertext) + } +} + +function promptHidden(message) { + if (!process.stdin.isTTY || !process.stdout.isTTY || + typeof process.stdin.setRawMode !== 'function') { + throw new Error('run this script directly in a private interactive terminal') + } + + emitKeypressEvents(process.stdin) + const wasRaw = process.stdin.isRaw + let secret = '' + + process.stdin.setRawMode(true) + process.stdin.resume() + + return new Promise((resolve, reject) => { + function finish(error) { + process.stdin.removeListener('keypress', onKeypress) + process.stdin.setRawMode(Boolean(wasRaw)) + if (!wasRaw) process.stdin.pause() + process.stdout.write('\n') + + if (error) reject(error) + else resolve(secret) + } + + function onKeypress(text, key = {}) { + if (key.ctrl && key.name === 'c') { + finish(new Error('recovery cancelled')) + return + } + if (key.name === 'return' || key.name === 'enter') { + finish() + return + } + if (key.name === 'backspace') { + secret = secret.slice(0, -1) + return + } + if (typeof text === 'string' && !key.ctrl && !key.meta && + !/[\u0000-\u001f\u007f]/.test(text)) { + secret += text + } + } + + process.stdin.on('keypress', onKeypress) + process.stdout.write(message) + }) +} + +function writeOutput(chunk) { + return new Promise((resolve, reject) => { + process.stdout.write(chunk, (error) => { + if (error) reject(error) + else resolve() + }) + }) +} + +async function main() { + const args = process.argv.slice(2) + if (args.length !== 1) { + throw new Error('usage: node recover-wdk-seed.mjs /path/to/seed.enc') + } + + const file = await readFile(args[0], 'utf8') + let payload + try { + payload = JSON.parse(file) + } catch { + throw new Error('seed.enc is not valid JSON') + } + + const { salt, iv, tag, ciphertext } = validatePayload(payload) + const passphrase = await promptHidden('Passphrase: ') + const key = scryptSync(passphrase, salt, 32, SCRYPT) + let decryptedChunk + let authenticatedTail + let plaintext + + try { + const decipher = createDecipheriv('aes-256-gcm', key, iv) + decipher.setAuthTag(tag) + decryptedChunk = decipher.update(ciphertext) + authenticatedTail = decipher.final() + plaintext = Buffer.concat([decryptedChunk, authenticatedTail]) + + await writeOutput('Recovered seed phrase:\n') + await writeOutput(plaintext) + await writeOutput('\n') + } finally { + key.fill(0) + decryptedChunk?.fill(0) + authenticatedTail?.fill(0) + plaintext?.fill(0) + } +} + +main().catch((error) => { + console.error(`Recovery failed: ${error.message}`) + process.exitCode = 1 +}) +``` + +Run it with only the `seed.enc` path as an argument: + +```bash title="Terminal" +chmod 700 recover-wdk-seed.mjs +node recover-wdk-seed.mjs "$HOME/.config/wdk-cli/wallets/WALLET_NAME/seed.enc" +``` + +Enter the passphrase at the hidden prompt. The script intentionally refuses non-interactive input so the passphrase is not supplied through a pipe, argument, or environment variable. + +The script validates the version, field encodings, and fixed field lengths before deriving the key. It makes a best-effort attempt to clear the derived key, decrypted chunks, and concatenated plaintext buffer after success or failure. The passphrase and displayed mnemonic still pass through JavaScript, OpenSSL internals, and terminal-managed memory, where reliable zeroization is not possible. + +After recovery: + +1. Verify the phrase by importing it into trusted wallet software while still offline. +2. Clear the terminal and close it to reduce scrollback exposure. +3. If the original computer or passphrase may be compromised, move funds to a newly generated seed. +4. Securely remove any temporary copies according to the storage medium and backup system you used. + +## Rename and deletion behavior + +Renaming a wallet moves its directory; it does not decrypt or re-encrypt `seed.enc`. + +Deleting a wallet removes its wallet directory after passphrase verification and attempts to lock its active daemon session first. This is ordinary filesystem deletion, not cryptographic erasure. Copies may remain in backups, snapshots, filesystem journals, swap, or recoverable storage blocks. + +Always keep an independently tested recovery backup before deleting or changing the only working copy. + +## Related pages + +- [Manage wallets](/cli/guides/manage-wallets) +- [Architecture](/cli/reference/architecture) +- [Security model](/cli/reference/security-model) +- [Configuration](/cli/configuration) + +*** + +## Security Model +URL: https://docs.wdk.tether.io/cli/reference/security-model +Description: Understand seed protection, daemon trust boundaries, memory lifetime, TTL auto-locking, and operational trade-offs. + +WDK CLI protects a locked wallet's mnemonic with passphrase-based encryption and restricts security-critical local artifacts to the owning operating-system user on macOS and Linux. After unlock, it deliberately trusts processes running as that user. -All write operations require explicit human confirmation. The skill instructs agents to estimate fees before sending and includes prompt injection protection guidance. +Treat an unlocked WDK CLI wallet as a local hot wallet. Use a dedicated wallet with limited funds, keep the unlock TTL short, and lock it before running untrusted code. -## How It Works +## Security boundaries -1. **Install the skill** by cloning the skill repository or installing from a skill registry like [ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit) -2. **Agent loads the skill** and reads `SKILL.md` along with per-module reference files to learn WDK's API surface -3. **Agent executes operations** when you ask it to create a wallet or send a transaction, generating the correct WDK code -4. **You confirm** before any write operation (transactions, swaps, bridges) goes through +| Boundary | Current protection | Not protected | +| --- | --- | --- | +| Locked seed at rest | AES-256-GCM with a scrypt-derived key; `seed.enc` mode `0600` on macOS and Linux | Weak or empty passphrases, compromised owner account, root/administrator, backups, or storage capture while unlocked | +| Daemon endpoint | Unix socket mode `0700` under an owner-only umask | Another process running as the same owner; there is no per-program daemon credential | +| PID file | Mode `0600` on macOS and Linux | Process discovery through other operating-system interfaces | +| Wallet session | Per-wallet absolute TTL and explicit lock | Activity does not shorten or refresh exposure; `--ttl 0` has no automatic expiry | +| In-memory cleanup | WDK disposal plus best-effort zeroing of retained mutable buffers | Immutable JavaScript strings, copies inside dependencies, swap, core dumps, crashes, or abrupt termination | +| CLI and MCP sends | Dry-run support and caller-level guidance | The daemon does not enforce a second confirmation or passphrase check for an unlocked wallet | +| General configuration | Separate from `seed.enc` | `config.json` has no owner-only guarantee and may contain user-added credentials | -The skill includes security guidance: pre-transaction validation checklists, prompt injection detection rules, and mandatory key cleanup patterns. +These controls reduce accidental exposure and cross-user access. They do not make a general-purpose computer a hardware wallet or isolate an unlocked wallet from malware running under the same account. -## Self-Custodial vs Hosted +## Seed encryption at rest -WDK's agent skills use a self-custodial model where your agent controls its own keys locally. This differs from hosted solutions where a third party manages your keys. +Each named wallet stores its BIP-39 mnemonic in `wallets/NAME/seed.enc`. Version 1 uses: -| Feature | WDK | Coinbase Agentic Wallet | Privy Server Wallets | -| --- | --- | --- | --- | -| Custody model | Self-custodial | Coinbase-hosted | Privy-hosted (server) | -| Multi-chain | Yes (EVM, Bitcoin, Solana, TON, Tron, Spark + more) | EVM + Solana | EVM + Solana + Bitcoin + more | -| Open source | Yes (SDK + skills) | CLI/skills open, infra closed | Skills open, API closed | -| MCP support | Yes ([MCP Toolkit](mcp-toolkit/)) | Via skills | Via skills | -| OpenClaw support | Yes ([ClawHub skill](https://clawhub.ai/HumanRupert/tether-wallet-development-kit)) | Yes (npx skills) | Yes (ClawHub skill) | -| x402 payments | Via [community extensions](#community-projects) | Yes (native) | No | -| Key management | Local / self-managed | Coinbase infrastructure | Privy infrastructure | +- AES-256-GCM authenticated encryption +- scrypt with `N=65536`, `r=8`, and `p=1` +- a random 32-byte salt and 12-byte IV +- a 16-byte authentication tag +- an owner read/write `0600` file mode on macOS and Linux -## Use With Agent Platforms +See [Storage format](/cli/reference/storage-format#seedenc-version-1) for field encodings and the manual-recovery contract. -| Platform | How to Use | -| --- | --- | -| **OpenClaw** | Install from [ClawHub](openclaw) or clone to workspace. See [OpenClaw Integration](openclaw) | -| **Claude** | Upload `SKILL.md` as project knowledge, or paste into conversation | -| **Cursor / Windsurf** | Clone to `.cursor/skills/wdk` or `.windsurf/skills/wdk` | -| **Any MCP-compatible agent** | Use the [MCP Toolkit](mcp-toolkit/) for structured tool calling | -| **Any other agent** | Copy `SKILL.md` into system prompt or conversation context | +A wrong passphrase or modified encrypted payload fails GCM authentication. Encryption does not protect a mnemonic after a process has decrypted it. -## Community Projects +### Passphrase handling -Community-built projects using WDK's agentic capabilities: +The interactive passphrase prompt hides input. For automation, `WDK_PASSPHRASE` overrides the prompt when it contains a non-empty value. -| Project | Description | +Environment variables can be inherited by child processes and may be visible through process inspection, crash reports, shell tooling, or automation logs. Prefer the interactive prompt for manual use. If automation requires `WDK_PASSPHRASE`, scope it to one trusted process, prevent command tracing, and remove it immediately after use. + +The current CLI accepts an empty passphrase. The file is still AES-GCM ciphertext, but an empty passphrase provides no meaningful confidentiality because anyone who obtains the file knows the value required to derive its key. + +## Same-user daemon access + +On macOS and Linux, `daemon.sock` is available only to the owning operating-system user. This stops a different local user from connecting through the socket. It does not identify or authorize individual programs owned by that user. + +Once a wallet is unlocked: + +- a same-user process that can connect to `daemon.sock` can derive addresses, read balances, estimate fees, and request signed transactions +- it does not need to know or re-enter the wallet passphrase +- it can speak directly to the internal daemon endpoint instead of using the normal CLI or MCP user flow +- CLI dry runs and MCP instructions to preview and confirm are caller behavior, not daemon authorization controls + +This same-user signing capability is an accepted design trade-off: the daemon provides a reusable local wallet session, and the operating-system user is the session's trust boundary. + + +Do not run downloaded scripts, unreviewed packages, browser automation, plugins, or AI agents under the wallet owner's account while a valuable wallet is unlocked. Owner-only socket permissions do not protect against code that you run as the owner. + + +For stronger practical separation, run WDK CLI under a dedicated non-administrator operating-system account and do not run unrelated tools under that account. This does not protect against root/administrator compromise, but it narrows which processes can reach the owner-only endpoint. + +## Session lifecycle + +Each wallet has its own absolute unlock timer: + +| Action | Timer effect | | --- | --- | -| [wdk-wallet-evm-x402-facilitator](https://github.com/SemanticPay/wdk-wallet-evm-x402-facilitator) | Agent-to-agent payments using the x402 HTTP payment protocol | -| [x402-usdt0](https://github.com/baghdadgherras/x402-usdt0) | Reference implementation of x402 on Plasma with USDT0 | -| [Novanet zkML Guardrails](https://github.com/hshadab/tether) | Zero-knowledge ML safety checks for wallet operations | +| `wdk wallet unlock --name NAME` | Starts the requested TTL; default is five minutes | +| Normal address, balance, history, fee, or send request | Does not refresh the TTL | +| Explicitly unlock an already unlocked wallet | Resets its timer to the new TTL | +| `wdk wallet unlock --name NAME --ttl 0` | Disables automatic expiry for that wallet | +| `wdk wallet lock --name NAME` | Immediately disposes that wallet session | +| `wdk wallet lock --all` | Disposes all wallet sessions | +| TTL expires | Disposes that wallet session | +| Last wallet locks or expires | Shuts down the daemon | -## Resources +An absolute timer limits the maximum duration from unlock without silently extending the session on every operation. It can also expire during a longer workflow because activity does not refresh it. Re-unlock explicitly when more time is needed. -- [WDK SKILL.md on GitHub](https://github.com/tetherto/wdk-docs/blob/main/skills/wdk/SKILL.md) - The full skill file agents consume -- [WDK Skill on ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit) - Install the skill -- [AgentSkills Specification](https://agentskills.io/specification) - The skill format standard -- [WDK MCP Toolkit](https://github.com/tetherto/wdk-mcp-toolkit) - MCP server for structured tool calling -- [WDK Core](https://github.com/tetherto/wdk-core) - The core SDK +Use `--ttl 0` only in a controlled environment where you accept an unlocked session that lasts until explicit lock, daemon shutdown, process failure, or machine restart. It is not appropriate as a convenience default. -*** +## Seed and passphrase lifetime -## Need Help? +Normal unlock handles sensitive values in more than one place: - +1. The `wdk` command process receives the passphrase as a JavaScript string. +2. It decrypts `seed.enc` and receives the mnemonic as a JavaScript string to verify the passphrase. +3. It sends the passphrase through owner-restricted local IPC to the daemon. +4. The daemon decrypts the mnemonic as a JavaScript string. +5. The daemon derives a mutable BIP-39 master-seed `Buffer` and retains it with the WDK instance until lock. + +The encryption key buffers are zeroed after encryption or decryption. On normal lock or graceful daemon shutdown, the CLI disposes the WDK instance and zeroes the retained master-seed buffer. + +These are best-effort language-level controls, not a guarantee that every copy is erased: + +- JavaScript strings are immutable and garbage-collected +- WDK or wallet modules may hold internal copies while in use +- operating-system swap, hibernation, crash dumps, and debugger access can capture process memory +- `SIGKILL`, a power loss, or a runtime crash can bypass normal cleanup + +Use full-disk encryption, restrict crash dumps and debugger access, and keep the host patched and free of untrusted software. Locking promptly reduces exposure but cannot retroactively erase copies outside the CLI's control. + +## Wallet export, logs, and automation + +`wdk wallet create` displays the generated mnemonic, and `wdk wallet export` displays the decrypted mnemonic. With `--json`, that secret appears in structured stdout. + +Do not: + +- run create or export in CI +- capture their output in logs or agent transcripts +- paste output into tickets, chat, or AI tools +- include `WDK_PASSPHRASE` in a committed script +- pass a mnemonic or passphrase as a shell argument + +Use a private terminal, create an offline backup, and clear terminal scrollback after handling a mnemonic. + +## Configuration is not secret storage + +`config.json` is a normal plaintext file. It primarily contains public WDK-style configuration, but user-supplied values can include indexer keys or credentials embedded in provider and signing URLs. + +- Prefer `WDK_INDEXER_API_KEY` over storing the indexer key when your environment can protect it. +- Protect any credentials embedded in custom provider URLs separately. +- Review `wdk config get --all` before copying its output. +- Do not assume `config.json` has the same `0600` mode as `seed.enc`. + +See [Configuration](/cli/configuration) for supported settings and precedence. + +## Deletion and recovery + +Wallet deletion performs ordinary recursive filesystem removal after passphrase verification. It is not secure erase, and it cannot remove copies from backups, snapshots, swap, journals, or previously copied files. + +Maintain an independently tested backup and the passphrase. The documented [`seed.enc` version 1 recovery procedure](/cli/reference/storage-format#recover-without-wdk-cli) remains available even if a future format does not provide automated migration. + +## Operational checklist + +Before unlock: + +- use a dedicated wallet with only the funds needed for the task +- stop untrusted same-user processes +- confirm the wallet name and requested TTL +- prefer a hidden interactive passphrase prompt + +While unlocked: + +- preview recipient, amount, network, token, and fees +- remember that normal use does not refresh the timer +- do not install packages or run unreviewed scripts under the same user +- treat MCP clients and agents as capable of requesting real sends + +After use: + +- run `wdk wallet lock --name NAME` or `wdk wallet lock --all` +- verify `wdk wallet list` reports the wallet as locked +- clear terminals or files that displayed the mnemonic + +## Related pages + +- [Architecture](/cli/reference/architecture) +- [Storage format and manual recovery](/cli/reference/storage-format) +- [Manage wallets](/cli/guides/manage-wallets) +- [Use the MCP server](/cli/guides/use-mcp-server) *** @@ -222,6 +2504,9 @@ All 35 built-in MCP tools and the WdkMcpServer class Use WDK tools in LangChain agents via the serve CLI + +Use a ready-made local wallet CLI, daemon, and MCP server when you do not need to build a custom MCP server + *** @@ -1997,6 +4282,101 @@ The examples auto-detect which provider to use based on which API key is set. If *** +## Agent Skills +URL: https://docs.wdk.tether.io/ai/agent-skills +Description: Give any AI agent self-custodial wallet capabilities with WDK agent skills + +WDK provides agent skills: structured instruction sets that teach AI agents how to create wallets, send transactions, swap tokens, bridge assets, and interact with DeFi protocols across 20+ blockchains. All operations are self-custodial. Keys stay on your machine, with no third-party custody dependency. + + +**Skill vs MCP Toolkit vs WDK CLI**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](/ai/mcp-toolkit/) when you are building a custom MCP server in code. Use [WDK CLI](/cli/) when you want a ready-made local wallet CLI, daemon, and MCP server. + + +## What Are Agent Skills? + +An agent skill is a structured set of instructions and reference documentation that teaches an AI agent to use a specific tool or SDK. Skills follow the [AgentSkills specification](https://agentskills.io/specification). Each skill is a `SKILL.md` file with frontmatter metadata and detailed instructions that any compatible agent can load and execute. + +WDK publishes a skill that covers the full SDK surface: wallet modules, swidge, swap, bridge, lending, fiat on/off-ramps, and the indexer. When an agent loads the skill, it learns WDK's APIs so you don't need blockchain expertise to get started. You can view the full skill file on [GitHub](https://github.com/tetherto/wdk-docs/blob/main/skills/wdk/SKILL.md). + +## Capabilities + +Once an agent loads the WDK skill, it can: + +| Category | Operations | +| --- | --- | +| **Wallets** | Create and recover wallets across EVM chains, Bitcoin, Solana, Spark, TON, and Tron | +| **Transactions** | Send native tokens and token transfers (ERC-20, SPL, Jetton, TRC-20) | +| **Swaps and routes** | DEX swaps via Velora and Swidge routes through providers such as Orchestra | +| **Bridges** | Cross-chain bridges with USDT0 via LayerZero | +| **Lending** | Supply, borrow, repay, and withdraw via Aave V3 | +| **Fiat** | Buy and sell crypto via MoonPay on/off-ramps | +| **Gasless** | Fee-free transfers on TON (via paymaster) and Tron (via gas-free service), and ERC-4337 account abstraction on EVM | + + +All write operations require explicit human confirmation. The skill instructs agents to estimate fees before sending and includes prompt injection protection guidance. + + +## How It Works + +1. **Install the skill** by cloning the skill repository or installing from a skill registry like [ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit) +2. **Agent loads the skill** and reads `SKILL.md` along with per-module reference files to learn WDK's API surface +3. **Agent executes operations** when you ask it to create a wallet or send a transaction, generating the correct WDK code +4. **You confirm** before any write operation (transactions, swaps, bridges) goes through + +The skill includes security guidance: pre-transaction validation checklists, prompt injection detection rules, and mandatory key cleanup patterns. + +## Self-Custodial vs Hosted + +WDK's agent skills use a self-custodial model where your agent controls its own keys locally. This differs from hosted solutions where a third party manages your keys. + +| Feature | WDK | Coinbase Agentic Wallet | Privy Server Wallets | +| --- | --- | --- | --- | +| Custody model | Self-custodial | Coinbase-hosted | Privy-hosted (server) | +| Multi-chain | Yes (EVM, Bitcoin, Solana, TON, Tron, Spark + more) | EVM + Solana | EVM + Solana + Bitcoin + more | +| Open source | Yes (SDK + skills) | CLI/skills open, infra closed | Skills open, API closed | +| MCP support | Yes ([MCP Toolkit](/ai/mcp-toolkit/)) | Via skills | Via skills | +| OpenClaw support | Yes ([ClawHub skill](https://clawhub.ai/HumanRupert/tether-wallet-development-kit)) | Yes (npx skills) | Yes (ClawHub skill) | +| x402 payments | Via [community extensions](#community-projects) | Yes (native) | No | +| Key management | Local / self-managed | Coinbase infrastructure | Privy infrastructure | + +## Use With Agent Platforms + +| Platform | How to Use | +| --- | --- | +| **OpenClaw** | Install from [ClawHub](/ai/openclaw/) or clone to workspace. See [OpenClaw Integration](/ai/openclaw/) | +| **Claude** | Upload `SKILL.md` as project knowledge, or paste into conversation | +| **Cursor / Windsurf** | Clone to `.cursor/skills/wdk` or `.windsurf/skills/wdk` | +| **Any MCP-compatible agent** | Use [WDK CLI](/cli/guides/use-mcp-server/) for a ready-made local daemon, or the [MCP Toolkit](/ai/mcp-toolkit/) for a custom MCP server | +| **Any local CLI-capable agent** | Use [WDK CLI](/cli/) with `--json` output | +| **Any other agent** | Copy `SKILL.md` into system prompt or conversation context | + +## Community Projects + +Community-built projects using WDK's agentic capabilities: + +| Project | Description | +| --- | --- | +| [wdk-wallet-evm-x402-facilitator](https://github.com/SemanticPay/wdk-wallet-evm-x402-facilitator) | Agent-to-agent payments using the x402 HTTP payment protocol | +| [x402-usdt0](https://github.com/baghdadgherras/x402-usdt0) | Reference implementation of x402 on Plasma with USDT0 | +| [Novanet zkML Guardrails](https://github.com/hshadab/tether) | Zero-knowledge ML safety checks for wallet operations | + +## Resources + +- [WDK SKILL.md on GitHub](https://github.com/tetherto/wdk-docs/blob/main/skills/wdk/SKILL.md) - The full skill file agents consume +- [WDK Skill on ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit) - Install the skill +- [AgentSkills Specification](https://agentskills.io/specification) - The skill format standard +- [WDK CLI](/cli/) - Local CLI, daemon, and MCP server for AI agents +- [WDK MCP Toolkit](https://github.com/tetherto/wdk-mcp-toolkit) - MCP server for structured tool calling +- [WDK Core](https://github.com/tetherto/wdk-core) - The core SDK + +*** + +## Need Help? + + + +*** + ## OpenClaw (Community Skill) URL: https://docs.wdk.tether.io/ai/openclaw Description: Give your OpenClaw AI agent a self-custodial WDK wallet in minutes @@ -2013,6 +4393,10 @@ Tether and the WDK Team do not endorse or assume responsibility for its code, se The WDK community skill follows the [AgentSkills specification](https://agentskills.io/specification), so it works with any compatible agent platform. This page covers the OpenClaw-specific setup. + +If you want OpenClaw to call a ready-made local wallet daemon through MCP instead of loading a file-based skill, see [WDK CLI MCP setup](/cli/guides/use-mcp-server/). + + ## Install the WDK Community Skill Install from [ClawHub](https://clawhub.ai/HumanRupert/tether-wallet-development-kit): @@ -2064,7 +4448,7 @@ Once the skill is loaded, your agent can: - **Lend and borrow** through Aave V3 - **Buy and sell crypto** via MoonPay fiat on/off-ramps -For the full list of capabilities and how skills work, see [Agent Skills](agent-skills). +For the full list of capabilities and how skills work, see [Agent Skills](/ai/agent-skills/). ## Security Risks and Safety Precautions @@ -2107,8 +4491,9 @@ OpenClaw makes use of artificial intelligence and machine learning technologies. ## Next Steps -- [Agent Skills](agent-skills) - Full capabilities, how skills work, and a comparison with other agentic wallet solutions -- [MCP Toolkit](mcp-toolkit/) - Programmatic wallet access for MCP-compatible agents +- [Agent Skills](/ai/agent-skills/) - Full capabilities, how skills work, and a comparison with other agentic wallet solutions +- [WDK CLI MCP](/cli/guides/use-mcp-server/) - Configure OpenClaw with the bundled WDK CLI MCP server +- [MCP Toolkit](/ai/mcp-toolkit/) - Programmatic wallet access for MCP-compatible agents - [OpenClaw Skills Documentation](https://docs.openclaw.ai/tools/skills) - How OpenClaw discovers and loads skills *** @@ -2995,6 +5380,13 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### July 27, 2026 + +**What's New** +- **WDK CLI** ([v1.0.0-beta.1](https://github.com/tetherto/wdk-cli/releases/tag/v1.0.0-beta.1)): Introduce the public `@tetherto/wdk-cli` beta with the `wdk` command-line interface, a local wallet daemon, and a bundled MCP server. The release supports encrypted named wallets, multi-network reads and sends, custom networks and tokens, indexer history, MoonPay buy/sell links, and MCP setup for supported AI tools. + +--- + ### July 07, 2026 **What's New** @@ -3639,6 +6031,7 @@ Stay up to date with the latest improvements, new features, and bug fixes across - Check this page for the latest updates - Join our [Discord community](https://discord.gg/arYXDhHB2w) for real-time announcements - Star and follow the [GitHub repositories](https://github.com/orgs/tetherto/repositories?q=wdk) for detailed release notes + *** ## Partner with WDK diff --git a/public/llms.txt b/public/llms.txt index b6915244..eed87347 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -5,15 +5,22 @@ Raw markdown is available by appending `.md` to any documentation page URL. - Welcome to WDK: https://docs.wdk.tether.io/ - Build with AI: https://docs.wdk.tether.io/start-building/build-with-ai -- WDK CLI: https://docs.wdk.tether.io/ai/wdk-cli -- Get Started: https://docs.wdk.tether.io/ai/wdk-cli/get-started -- Configuration: https://docs.wdk.tether.io/ai/wdk-cli/configuration -- Commands: https://docs.wdk.tether.io/ai/wdk-cli/commands -- MCP: https://docs.wdk.tether.io/ai/wdk-cli/mcp +- WDK CLI: https://docs.wdk.tether.io/cli +- WDK CLI Configuration: https://docs.wdk.tether.io/cli/configuration +- WDK CLI API Reference: https://docs.wdk.tether.io/cli/api-reference +- WDK CLI Get Started: https://docs.wdk.tether.io/cli/guides/get-started +- WDK CLI Manage Wallets: https://docs.wdk.tether.io/cli/guides/manage-wallets +- WDK CLI Manage Tokens: https://docs.wdk.tether.io/cli/guides/manage-tokens +- WDK CLI Custom Networks: https://docs.wdk.tether.io/cli/guides/custom-networks +- WDK CLI MCP Server: https://docs.wdk.tether.io/cli/guides/use-mcp-server +- WDK CLI Error Handling: https://docs.wdk.tether.io/cli/guides/handle-errors +- WDK CLI Architecture: https://docs.wdk.tether.io/cli/reference/architecture +- WDK CLI Storage Format: https://docs.wdk.tether.io/cli/reference/storage-format +- WDK CLI Security Model: https://docs.wdk.tether.io/cli/reference/security-model - MCP Toolkit: https://docs.wdk.tether.io/ai/mcp-toolkit -- Get Started: https://docs.wdk.tether.io/ai/mcp-toolkit/get-started -- Configuration: https://docs.wdk.tether.io/ai/mcp-toolkit/configuration - API Reference: https://docs.wdk.tether.io/ai/mcp-toolkit/api-reference +- Configuration: https://docs.wdk.tether.io/ai/mcp-toolkit/configuration +- Get Started: https://docs.wdk.tether.io/ai/mcp-toolkit/get-started - LangChain Integration: https://docs.wdk.tether.io/ai/mcp-toolkit/langchain - Agent Skills: https://docs.wdk.tether.io/ai/agent-skills - OpenClaw (Community Skill): https://docs.wdk.tether.io/ai/openclaw diff --git a/src/lib/custom-tree.ts b/src/lib/custom-tree.ts index bab8ca22..601b1ae1 100644 --- a/src/lib/custom-tree.ts +++ b/src/lib/custom-tree.ts @@ -326,14 +326,27 @@ export const customTree: Node[] = [ apiReference('/sdk/fiat-modules/fiat-moonpay/api-reference'), ]), + separator('CLI'), + folder('WDK CLI', '/cli', 'Terminal', [ + configuration('/cli/configuration'), + apiReference('/cli/api-reference'), + guides([ + page('Get Started', '/cli/guides/get-started', 'Rocket'), + page('Manage Wallets', '/cli/guides/manage-wallets', 'WalletCards'), + page('Manage Tokens', '/cli/guides/manage-tokens', 'Coins'), + page('Custom Networks', '/cli/guides/custom-networks', 'Network'), + page('Use the MCP Server', '/cli/guides/use-mcp-server', 'Wand'), + page('Handle Errors', '/cli/guides/handle-errors', 'CircleAlert'), + ]), + group('Reference', 'BookOpen', [ + page('Architecture', '/cli/reference/architecture', 'Network'), + page('Storage Format', '/cli/reference/storage-format', 'Database'), + page('Security Model', '/cli/reference/security-model', 'ShieldCheck'), + ]), + ]), + separator('AI'), page('Build with AI', '/start-building/build-with-ai', 'Bot'), - folder('WDK CLI', '/ai/wdk-cli', 'Terminal', [ - page('Get Started', '/ai/wdk-cli/get-started', 'Rocket'), - configuration('/ai/wdk-cli/configuration'), - page('Commands', '/ai/wdk-cli/commands', 'ListChecks'), - page('MCP', '/ai/wdk-cli/mcp', 'Wand'), - ]), folder('MCP Toolkit', '/ai/mcp-toolkit', 'Wand', [ page('Get Started', '/ai/mcp-toolkit/get-started', 'Rocket'), configuration('/ai/mcp-toolkit/configuration'), From 4e632ee4896433d7fcee1d519e20cd6319bd8865 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Thu, 30 Jul 2026 12:24:30 +0530 Subject: [PATCH 20/38] docs: expand released module coverage --- content/docs/overview/changelog.mdx | 29 +- content/docs/sdk/all-modules.mdx | 67 +- content/docs/sdk/bridge-modules/index.mdx | 2 +- content/docs/sdk/community-modules/index.mdx | 6 + .../wdk-rgb-lightning/api-reference.mdx | 209 + .../wdk-rgb-lightning/configuration.mdx | 150 + .../guides/balances-history.mdx | 100 + .../wdk-rgb-lightning/guides/get-started.mdx | 105 + .../guides/handle-errors-cleanup.mdx | 132 + .../guides/lightning-payments.mdx | 105 + .../guides/lsp-lightning-address-apay.mdx | 127 + .../guides/manage-node-account.mdx | 92 + .../guides/peers-channels.mdx | 108 + .../wdk-rgb-lightning/guides/rgb-assets.mdx | 116 + .../guides/send-btc-utxos.mdx | 86 + .../guides/sign-verify-messages.mdx | 74 + .../guides/vss-backup-recovery.mdx | 110 + .../wdk-rgb-lightning/index.mdx | 87 + .../wdk-rgb-lightning/usage.mdx | 57 + .../wdk-wallet-cosmos/api-reference.mdx | 330 +- .../wdk-wallet-cosmos/configuration.mdx | 157 + .../guides/check-balances.mdx | 79 + .../wdk-wallet-cosmos/guides/get-started.mdx | 84 + .../guides/handle-errors.mdx | 135 + .../guides/manage-accounts.mdx | 82 + .../guides/send-transactions.mdx | 124 + .../guides/sign-verify-messages.mdx | 86 + .../guides/transfer-tokens.mdx | 120 + .../wdk-wallet-cosmos/index.mdx | 90 +- .../wdk-wallet-cosmos/usage.mdx | 45 + .../wdk-wallet-rgb/api-reference.mdx | 219 +- .../wdk-wallet-rgb/configuration.mdx | 115 + .../guides/backup-restore-migrate.mdx | 111 + .../guides/balances-history.mdx | 105 + .../wdk-wallet-rgb/guides/btc-utxos.mdx | 89 + .../wdk-wallet-rgb/guides/get-started.mdx | 83 + .../wdk-wallet-rgb/guides/handle-errors.mdx | 109 + .../guides/issue-receive-assets.mdx | 90 + .../guides/manage-account-storage.mdx | 97 + .../guides/sign-verify-messages.mdx | 66 + .../wdk-wallet-rgb/guides/transfer-assets.mdx | 100 + .../wdk-wallet-rgb/index.mdx | 91 +- .../wdk-wallet-rgb/usage.mdx | 51 + .../docs/sdk/core-module/api-reference.mdx | 2 +- .../guides/protocol-integration.mdx | 2 +- content/docs/sdk/lending-modules/index.mdx | 12 +- .../lending-aave-evm/configuration.mdx | 12 +- .../guides/lending-operations.mdx | 12 +- .../lending-morpho-evm/api-reference.mdx | 16 +- .../lending-morpho-evm/configuration.mdx | 6 + .../lending-morpho-evm/guides/get-started.mdx | 6 + .../guides/handle-errors.mdx | 6 + .../guides/lending-operations.mdx | 18 +- .../lending-morpho-evm/usage.mdx | 6 + content/docs/sdk/sda-modules/index.mdx | 239 - content/docs/sdk/swap-modules/index.mdx | 2 +- content/docs/sdk/swidge-modules/index.mdx | 237 +- .../swidge-orchestra/api-reference.mdx | 6 + .../swidge-orchestra/configuration.mdx | 6 + .../swidge-orchestra/guides/get-started.mdx | 6 + .../swidge-orchestra/guides/handle-errors.mdx | 6 + .../guides/quote-and-execute.mdx | 6 + .../guides/state-and-recovery.mdx | 6 + .../swidge-modules/swidge-orchestra/usage.mdx | 6 + .../swidge-rhinofi/api-reference.mdx | 10 +- .../swidge-rhinofi/configuration.mdx | 6 + .../swidge-modules/swidge-rhinofi/index.mdx | 6 + .../swidge-modules/swidge-rhinofi/usage.mdx | 6 + .../swidge-symbiosis/api-reference.mdx | 280 + .../swidge-symbiosis/configuration.mdx | 196 + .../swidge-modules/swidge-symbiosis/index.mdx | 108 + .../swidge-modules/swidge-symbiosis/usage.mdx | 249 + content/docs/sdk/wallet-modules/index.mdx | 6 +- .../wallet-solana-gasless/api-reference.mdx | 20 +- .../wallet-solana-gasless/configuration.mdx | 6 +- .../guides/get-started.mdx | 4 +- .../guides/handle-errors.mdx | 27 +- .../guides/send-transactions.mdx | 39 +- .../wallet-solana-gasless/index.mdx | 5 +- .../wallet-modules/which-wallet-module.mdx | 39 +- public/llms-full.txt | 32589 +++++++++++----- public/llms.txt | 123 +- skills/wdk/SKILL.md | 6 +- skills/wdk/references/protocol-sda.md | 65 - skills/wdk/references/protocol-swidge.md | 2 +- src/components/wallet-module-chooser.tsx | 62 +- src/lib/custom-tree.ts | 62 +- 87 files changed, 28296 insertions(+), 10658 deletions(-) create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/api-reference.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/configuration.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/balances-history.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/get-started.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/send-btc-utxos.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/sign-verify-messages.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/index.mdx create mode 100644 content/docs/sdk/community-modules/wdk-rgb-lightning/usage.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/configuration.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/check-balances.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/get-started.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/manage-accounts.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/send-transactions.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/sign-verify-messages.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-cosmos/usage.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/configuration.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/backup-restore-migrate.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/balances-history.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/btc-utxos.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/get-started.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/handle-errors.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/issue-receive-assets.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/manage-account-storage.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/sign-verify-messages.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/guides/transfer-assets.mdx create mode 100644 content/docs/sdk/community-modules/wdk-wallet-rgb/usage.mdx delete mode 100644 content/docs/sdk/sda-modules/index.mdx create mode 100644 content/docs/sdk/swidge-modules/swidge-symbiosis/api-reference.mdx create mode 100644 content/docs/sdk/swidge-modules/swidge-symbiosis/configuration.mdx create mode 100644 content/docs/sdk/swidge-modules/swidge-symbiosis/index.mdx create mode 100644 content/docs/sdk/swidge-modules/swidge-symbiosis/usage.mdx delete mode 100644 skills/wdk/references/protocol-sda.md diff --git a/content/docs/overview/changelog.mdx b/content/docs/overview/changelog.mdx index 7eea72d4..7314d2c8 100644 --- a/content/docs/overview/changelog.mdx +++ b/content/docs/overview/changelog.mdx @@ -10,6 +10,30 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### July 29, 2026 + +**What's New** +- **rgb-lightning** ([v0.1.0-beta.15](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15)): Add the community-maintained RGB Lightning wallet and node module for BTC and RGB balances, peers and channels, Lightning and RGB invoices and payments, LSP and Lightning Address flows, APay, and VSS backup. The beta requires one platform-specific native peer, persistent node state, and an explicit unlock step before account operations. + +**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 the community-maintained Symbiosis Swidge provider for dynamic chain and token discovery, indicative cross-chain quotes, source-chain approval and execution, and status polling. Execution obtains a fresh route rather than consuming the earlier quote, so applications must reconfirm the current response and enforce their own output policy before broadcast. + +--- + +### 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** @@ -26,6 +50,7 @@ Stay up to date with the latest improvements, new features, and bug fixes across **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. --- @@ -47,7 +72,7 @@ Stay up to date with the latest improvements, new features, and bug fixes across ### 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)): Add the provider-facing `SdaProtocol` and `ISdaProtocol` interface for supported-route discovery and deposit-address creation, with optional quoting, address derivation and lifecycle, transfer history and status, and recovery methods. No concrete SDA provider or WDK Core registration/accessor ships in the current releases, so this is an implementation contract rather than a runnable end-user flow. The package also exports `UnsupportedOperationError`, `ValueError`, and `NoSuchElementError`. +- **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. --- @@ -301,7 +326,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/sdk/all-modules.mdx b/content/docs/sdk/all-modules.mdx index e407f826..84645e70 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 available WDK wallet, Smart Deposit Address, pricing, swidge, swap, bridge, lending, and fiat interfaces and modules. +description: Complete list of available WDK wallet, pricing, swidge, swap, bridge, lending, and fiat interfaces and modules. docType: reference schemaType: TechArticle --- @@ -34,22 +34,25 @@ Wallet modules provide blockchain-specific wallet functionality for managing add | [`@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/) | -## Smart Deposit Address Interface +## Swidge Modules -The released `@tetherto/wdk-wallet` package includes the provider contract for Smart Deposit Address routes. It defines discovery, deposit-address creation, and optional quote, monitoring, renewal, recovery, and disable operations; it does not include a concrete SDA provider. +Swidge providers can quote and execute asset routes. A route can be swap-only, bridge-only, or a combined swap and bridge route. -| Interface | Package | Description | Documentation | -|-----------|---------|-------------|---------------| -| `SdaProtocol` | [`@tetherto/wdk-wallet`](https://www.npmjs.com/package/@tetherto/wdk-wallet) | Abstract provider interface for route-bound deposit addresses | [Docs](/sdk/sda-modules/) | + +Rows marked Community are developed and maintained independently by third-party contributors. -## Swidge Interface +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 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. - -| 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/) | +| 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 | Cross-chain routes discovered and executed 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 through LI.FI | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | +| [`@moonpay/wdk-protocol-swidge-moonpay-trade`](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade) | MoonPay Trade | Community | Swidge routes through MoonPay Trade | [README](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade#readme) | +| [`@swapdk/wdk-protocol-swidge-swapdk`](https://www.npmjs.com/package/@swapdk/wdk-protocol-swidge-swapdk) | SwapDK | Community | Swidge routes through SwapDK | [README](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk#readme) | +| [`@gobob/wdk-protocol-swidge-gateway`](https://www.npmjs.com/package/@gobob/wdk-protocol-swidge-gateway) | BOB Gateway | Community | Swidge routes through the BOB Gateway | [README](https://github.com/bob-collective/wdk-protocol-swidge-gateway#readme) | ## Pricing Modules @@ -80,9 +83,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 @@ -97,11 +106,23 @@ 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/) | +| [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) | Wallet | RGB Lightning node and wallet integration | [Docs](/sdk/community-modules/wdk-rgb-lightning/) | +| [`@arkade-os/wdk`](https://github.com/arkade-os/arkade-wdk) | Wallet | 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) | 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 | Cross-chain 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 | Routes through LI.FI | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | +| [`@moonpay/wdk-protocol-swidge-moonpay-trade`](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade) | Swidge | Routes through MoonPay Trade | [README](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade#readme) | +| [`@swapdk/wdk-protocol-swidge-swapdk`](https://www.npmjs.com/package/@swapdk/wdk-protocol-swidge-swapdk) | Swidge | Routes through SwapDK | [README](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk#readme) | +| [`@gobob/wdk-protocol-swidge-gateway`](https://www.npmjs.com/package/@gobob/wdk-protocol-swidge-gateway) | Swidge | Routes through the BOB Gateway | [README](https://github.com/bob-collective/wdk-protocol-swidge-gateway#readme) | 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..ed6b9660 100644 --- a/content/docs/sdk/community-modules/index.mdx +++ b/content/docs/sdk/community-modules/index.mdx @@ -18,11 +18,17 @@ Tether and the WDK Team do not endorse or assume responsibility for their code, | Module | Type | Description | Documentation | Author | |--------|------|-------------|---------------|--------| | [@utexo/wdk-wallet-rgb](https://www.npmjs.com/package/@utexo/wdk-wallet-rgb) ([GitHub](https://github.com/UTEXO-Protocol/wdk-wallet-rgb)) | Wallet Module | Wallet module for RGB, Bitcoin-based smart contracts | [Docs](/sdk/community-modules/wdk-wallet-rgb/) | [UTEXO](https://github.com/UTEXO-Protocol) | +| [@utexo/wdk-rgb-lightning](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) ([GitHub](https://github.com/UTEXO-Protocol/wdk-rgb-lightning)) | Wallet Module | RGB Lightning node, channel, payment, and asset integration | [Docs](/sdk/community-modules/wdk-rgb-lightning/) | [UTEXO](https://github.com/UTEXO-Protocol) | | [@arkade-os/wdk](https://www.npmjs.com/package/@arkade-os/wdk) ([GitHub](https://github.com/arkade-os/arkade-wdk)) | Wallet Module | Bitcoin wallet module built on the Arkade SDK with Arkade addresses, boarding addresses, BIP21/LNURL/Lightning send routing, and optional Boltz swaps | [README](https://github.com/arkade-os/arkade-wdk#readme) | [Arkade](https://github.com/arkade-os) | | [@base58-io/wdk-wallet-cosmos](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos) ([GitHub](https://github.com/base58-io/wdk-wallet-cosmos)) | Wallet Module | Wallet module for Cosmos-compatible blockchains | [Docs](/sdk/community-modules/wdk-wallet-cosmos/) | [Base58](https://base58.io/) | | [@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/) | +| [@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 | Cross-chain routes discovered and executed through the Symbiosis API | [Docs](/sdk/swidge-modules/swidge-symbiosis/) | [Symbiosis](https://symbiosis.finance/) | +| [@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 | Routes through LI.FI | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | [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/) | --- diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/api-reference.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/api-reference.mdx new file mode 100644 index 00000000..5416483f --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/api-reference.mdx @@ -0,0 +1,209 @@ +--- +title: RGB Lightning wallet API reference +description: Public API reference for @utexo/wdk-rgb-lightning 0.1.0-beta.15. +docType: reference +schemaType: TechArticle +--- + +This page covers the package-root declarations and released runtime behavior of [`@utexo/wdk-rgb-lightning@0.1.0-beta.15`](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15). + + +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-rgb-lightning@0.1.0-beta.15` | +| Repository | [UTEXO-Protocol/wdk-rgb-lightning](https://github.com/UTEXO-Protocol/wdk-rgb-lightning) | +| Module format | ESM | +| Node/default entry | `index.js` → `index-node.js` | +| Bare entry | `bare.js` → `index-bare.js` | +| Declarations | `index.d.ts` | + +Install the matching optional native peer. Node and Bare expose the same JavaScript API but load different native bindings. + +## Root exports + +| Group | Exports | +|---|---| +| Manager and accounts | Default `WalletManagerRgbLightning`, `WalletAccountRgbLightning`, `WalletAccountReadOnlyRgbLightning` | +| Low-level bindings | `NodeRgbLightningBinding`, `BareRgbLightningBinding`, `IRgbLightningBinding` type | +| Wallet errors | `RgbLightningError`, `UnlockError`, `AccountLockedError`, `VssError`, `VssNotConfiguredError`, `ApayError`, `NotImplementedError` | +| LSP | `LspClient`, `LspError`, `UtexoLsp`, LSP result/config types, timeout and settlement errors | +| LNURL / address | `isUmaAddress`, `normalizeLightningAddress`, `parseLightningAddress`, `fetchDiscovery`, `resolveAddressToInvoice`, `LnurlPayError` | +| Account-bound helpers | `payLightningAddress`, `requestLspRgbDeposit`, `payRgbViaLsp` | + +Low-level binding classes are advanced escape hatches. Prefer the manager because it owns external-signer attachment, fallback identity handling, shutdown, and secret cleanup. + +## `WalletManagerRgbLightning` + +| Member | Returns | Behavior | +|---|---|---| +| `constructor(seed, config)` | Manager | Requires BIP-39 mnemonic or seed bytes, `network`, and persistent `dataDir`. | +| `getAccount(index = 0)` | `Promise` | Returns the only account. Nonzero indexes and registered WDK signer names are rejected. | +| `getAccountByPath(path)` | `Promise` | Accepts only `m`. | +| `getFeeRates()` | `Promise` | Fetches mempool.space recommendations without selecting the configured network. | +| `dispose()` | `void` | Terminal manager-owned binding shutdown, signer destruction, seed cleanup, and account invalidation. | +| `static Binding` | Binding constructor | Runtime-selected Node or Bare binding. | + +## `WalletAccountReadOnlyRgbLightning` + +The read-only adapter exposes queries without signing, broadcasting, channel mutation, VSS recovery, or LSP credentials. + +| Group | Methods | +|---|---| +| Bootstrap and node | `getBootstrap()`, `getNodeInfo()`, `getNetworkInfo()` | +| Address | `getAddress()`, `getAddressState()` | +| Channels and peers | `listChannels()`, `getChannelId(tempId)`, `listPeers()` | +| Lightning | `decodeInvoice()`, `getInvoiceStatus()`, `listPayments()`, `getPayment(hash, type)` | +| RGB | `listAssets(filter?)`, `getAssetBalance()`, `getAssetMetadata()`, `listTransfers()`, `listTransfersByTxid()`, `decodeRgbInvoice()`, `getAssetMedia()` | +| Bitcoin | `getBalance(skipSync?)`, `getBalanceDetails(skipSync?)`, `getTransactions(skipSync?)`, `getTransactionsByTxid()`, `listUnspents()`, `estimateFee()` | +| WDK | `getTokenBalance()`, `verify()`, `quoteTransfer()`, `quoteSendTransaction()`, `getTransactionReceipt()` | +| Diagnostics | `checkIndexerUrl()`, `checkProxyEndpoint()`, `vssStatus()` | + +`getAddress()` throws `AccountLockedError` before unlock. `getAddressState()` returns `{status:'locked', address:null}` without throwing. + + +Before unlock, `getBalance()` returns `0n`. Use `getAddressState()` to distinguish a locked account from a ready account with a real zero balance. + + +`getTransactionReceipt()` returns only terminal confirmed Bitcoin, settled RGB, or non-pending Lightning records; otherwise it returns `null`. + +## `WalletAccountRgbLightning` + +The full account has fixed identity fields: + +| Member | Value | +|---|---| +| `index` | `0` | +| `path` | `m` | +| `keyPair.publicKey` | 33-byte compressed Lightning node public key | +| `keyPair.privateKey` | Always `null`; VLS holds signing material | + +### Lifecycle and node + +| Method | Behavior | +|---|---| +| `unlock(nativeRequest)` | Unlocks the node with native snake_case RPC, indexer, proxy, and announce fields; wraps failures as `UnlockError`. | +| `getBootstrap()` | Returns public signer/bootstrap metadata. | +| `getNodeInfo()` / `getNetworkInfo()` | Query node and chain information. | +| `sync()` | Synchronizes node state. | +| `getAddress()` / `getAddressState()` | Read current stable address or lock state. | +| `rotateAddress()` | Explicitly advances the Bitcoin address. | +| `shutdown()` | Idempotently shuts down the account binding. | +| `dispose()` | Account no-op; the manager owns terminal cleanup. | + +### Peers and channels + +| Method | Behavior | +|---|---| +| `connectPeer(pubkeyAndAddress)` | Connects a `pubkey@host:port` peer. | +| `disconnectPeer(request)` | Forwards the native disconnect request. | +| `listPeers()` | Returns native peer records. | +| `openChannel(request)` | Accepts `OpenChannelRequest` or a native object. | +| `closeChannel(request)` | Forwards the native close request. | +| `listChannels()` | Returns native channel records. | +| `getChannelId(temporaryId)` | Resolves a temporary channel ID. | + +### Invoices and payments + +| Method | Behavior | +|---|---| +| `createInvoice(request)` | Native BOLT11 invoice request. | +| `createLightningInvoice(request)` | Accepts native snake_case or released camelCase convenience fields. | +| `decodeInvoice(invoice)` / `getInvoiceStatus(invoice)` | Query invoice data and status. | +| `createHodlInvoice(params)` | Creates a HODL invoice for a caller-supplied payment hash. | +| `cancelHodlInvoice(request)` / `claimHodlInvoice(request)` | Native HODL lifecycle calls. | +| `sendPayment(request)` / `keysend(request)` | Native Lightning payment calls. | +| `listPayments()` / `getPayment(hash, type)` | Payment history. `type` is `Outbound`, `InboundAutoClaim`, or `InboundHodl`. | + +### RGB assets + +| Method | Behavior | +|---|---| +| `listAssets(filter?)`, `getAssetBalance()`, `getAssetMetadata()` | Query held RGB assets. | +| `listTransfers(assetId)`, `listTransfersByTxid(txid)` | Query transfer records. | +| `refreshTransfers(request)`, `failTransfers(request)` | Native transfer-state mutation. | +| `createRgbInvoice(request)`, `decodeRgbInvoice(invoice)` | Create or decode an RGB receive invoice. | +| `sendRgbAsset(request)` | Sends native grouped RGB recipients. | +| `getAssetMedia(digest)`, `postAssetMedia(request)` | Reads or uploads asset media. | + +Runtime JavaScript includes issuance forwarders that beta.15's public declaration omits. They are intentionally excluded here. Use [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb) for released, documented issuance. + +### Bitcoin and WDK operations + +| Method | Behavior | +|---|---| +| `sendTransaction({to, value, feeRate?, confirmationTarget?})` | WDK Bitcoin send; VLS signs internally and returns `{hash, fee}`. | +| `sendBtc(request)` | Low-level native Bitcoin send. | +| `createUtxos(request)` | Native UTXO-creation request. | +| `quoteSendTransaction(tx)` | Approximate standard-send quote based on 141 vbytes. | +| `transfer(options)` | Routes BOLT11, node ID, Bitcoin address, or RGB invoice recipients. | +| `quoteTransfer(options)` | Routes to an approximate flow-specific quote. | +| `sign(message)` / `verify(message, signature)` | Lightning message signing and verification. | +| `signTransaction()` | Always throws `NotImplementedError`; use operation-specific send methods. | +| `toReadOnlyAccount()` | Returns the cached query-only adapter. | + +## Transfer routing and units + +`transfer(options)` classifies `recipient`: + +| Recipient | Route | `amount` unit | Returned `fee` unit | +|---|---|---|---| +| BOLT11 invoice | `sendPayment` | millisatoshis | millisatoshis | +| 66-character hex node ID | `keysend` | millisatoshis | millisatoshis | +| `rgb:` or `utxob:` invoice | RGB send | RGB asset base units | `0n` because the native fee is not exposed | +| Other valid recipient | Bitcoin send | satoshis | satoshis | + +For RGB routing, `token` is the asset ID. The generic router assumes `Fungible`, `donation: false`, and one confirmation. Use `sendRgbAsset()` with the exact native request for other assignment kinds or grouped recipients. + + +An RGB transfer result of `fee: 0n` does not prove the operation was fee-free. It means beta.15 does not expose the native fee through the WDK result. + + +The Lightning quote uses a 50-basis-point allowance, not a live route fee. An RGB-routed HTLC has a hard minimum of 3,000,000 msat. + +## LSP, Lightning Address, and VSS + +| Surface | Key methods | +|---|---| +| `LspClient` | `health`, `getInfo`, LNURL discovery/callback, address resolution, on-chain send bridge, Lightning receive bridge | +| `UtexoLsp` | `connect`, `waitForChannel`, `receiveAsset`, settlement/liquidity waits, `sendAsset`, `payAddress`, `enableLightningAddress`, `claimPendingPayments` | +| APay | `apayNew`, `bootstrapLsp`, `getLspConfig`, `createLsp` | +| VSS | `vssStatus`, `vssBackup`, `clearVssFence` | + +Public HTTP is rejected for LSP and VSS by default, except loopback where supported. LNURL callbacks remain on the discovery host unless `allowCrossHostCallback` is explicitly enabled. `$user@host` is normalized as UMA-style address syntax only; the package does not implement UMA signing, compliance, or currency negotiation. + +## Errors + +Selected boundaries use typed errors: + +| Error | Boundary | +|---|---| +| `UnlockError` | Node unlock | +| `AccountLockedError` | Locked address/signature operations | +| `VssError`, `VssNotConfiguredError` | VSS operations | +| `ApayError` | APay/bootstrap | +| `NotImplementedError` | Unsupported transaction signing | +| `LspError`, `LnurlPayError` | HTTP LSP and LNURL helpers | +| `LspChannelTimeoutError`, `LspLiquidityTimeoutError`, `LspSettlementError` | Composed LSP waits | + +Many native methods still throw raw `Rln(): ` errors. Preserve the original error and operation context. + +## Declaration boundary + +Many native requests and responses are deliberately typed as `object` or `Record` because RLN owns their shape. Do not invent stable fields from demos, another beta, or an unreleased branch. Validate the exact beta.15 payloads your application consumes. + +Atomic swap methods are available only on the native binding, not on the released WDK account. They are outside this reference. + +## Guides + +- [Configure the node](/sdk/community-modules/wdk-rgb-lightning/configuration) +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Use peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/configuration.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/configuration.mdx new file mode 100644 index 00000000..bf1951a9 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/configuration.mdx @@ -0,0 +1,150 @@ +--- +title: RGB Lightning wallet configuration +description: Configure the beta.15 node, persistent state, native unlock services, VSS, LSP, and signer policy. +docType: reference +schemaType: TechArticle +icon: Settings +--- + +RGB Lightning has two configuration phases: construct the node with stable local settings, then unlock it with live Bitcoin and RGB service settings. + + +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 configuration + +```js +import WalletManagerRgbLightning from '@utexo/wdk-rgb-lightning' + +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-lightning', + daemonListeningPort: 0, + ldkPeerListeningPort: 0, + maxMediaUploadSizeMb: 5, + permissiveSignerPolicy: true, + nodeSeedDerivation: 'auto', +}) +``` + +## Consumed constructor fields + +| Field | Type | Default | Behavior | +|---|---|---|---| +| `network` | `'mainnet' \| 'testnet' \| 'regtest' \| 'signet'` | None | Required. | +| `dataDir` | `string` | None | Required persistent, app-private RLN/LDK/RGB state path. | +| `daemonListeningPort` | `number` | `0` | RLN daemon port; `0` selects an ephemeral port. | +| `ldkPeerListeningPort` | `number` | `0` | LDK peer port; `0` selects an ephemeral port. | +| `maxMediaUploadSizeMb` | `number` | `5` | Maximum RGB media upload size. | +| `enableVirtualChannelsV0` | `boolean` | `false` | Enables trusted non-broadcast virtual channels; required for production APay. | +| `virtualPeerPubkeys` | `string[]` | None | Node-ID allowlist for virtual channel peers. | +| `permissiveSignerPolicy` | `boolean` | `true` | Relaxes VLS policy checks for the in-process single-user integration. | +| `nodeSeedDerivation` | `'auto' \| 'wdk-seed-v2' \| 'legacy-v1'` | `'auto'` | Selects corrected or legacy beta node-identity derivation. | +| `vssUrl` | `string` | None | Enables remote encrypted VSS snapshots. | +| `vssAllowHttp` | `boolean` | `false` | Allows non-HTTPS VSS; use only for explicitly accepted development risk. | +| `vssAllowEmptyRestore` | `boolean` | `false` | Allows startup without remote state when the VSS store is empty. | +| `lspBaseUrl` | `string` | None | LSP base URL used by APay and no-argument `createLsp()`. | +| `lspBearerToken` | `string` | None | Optional bearer credential for internal LSP endpoints. | + +Assess `permissiveSignerPolicy: true` against your threat model. Tightening it can reject operations the integration expects, so test policy changes with real channel lifecycle flows. + +## Native unlock request + +After `getAccount(0)`, pass live services to `unlock()` in native snake_case: + +```js +const account = await manager.getAccount(0) + +await account.unlock({ + bitcoind_rpc_username: 'rpc-user', + bitcoind_rpc_password: await loadRpcPassword(), + bitcoind_rpc_host: '127.0.0.1', + bitcoind_rpc_port: 18443, + indexer_url: 'tcp://127.0.0.1:50001', + proxy_endpoint: 'rpc://127.0.0.1:3000/json-rpc', + announce_addresses: [], + announce_alias: 'example-node', +}) +``` + +Do not log the unlock request. It can contain RPC credentials and network-identifying values. + + +The beta.15 `RgbLightningWalletConfig` declaration also lists camelCase Bitcoin RPC, indexer, proxy, and announce fields. `WalletManagerRgbLightning` does not consume or forward those constructor fields. Pass the corresponding native snake_case values to `account.unlock()` instead. + + +`proxyEndpoint` in constructor config is also not forwarded to native binding configuration. Well-formed RGB invoices carry consignment transport endpoints; explicit native operations should use their released request shapes. + +## Persistent state and wallet identity + +Use a different `dataDir` from [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb): + +```text +/app-private/wdk/rgb-onchain +/app-private/wdk/rgb-lightning +``` + +The two modules derive different wallet fingerprints and do not share RGB records. A shared seed and matching asset ID do not create a shared balance. + +Only one live owner should use a Lightning `dataDir` or VSS namespace. LDK state has strict consistency and anti-rollback requirements. + +## Node seed derivation + +| Value | Use | +|---|---| +| `auto` | Corrected WDK seed derivation for new nodes; retries the beta.14-and-earlier legacy identity only on an exact persisted signer-identity mismatch. | +| `wdk-seed-v2` | Always use corrected derivation. | +| `legacy-v1` | Always use legacy beta derivation for an already-persisted compatible node. | + +Do not switch derivation modes casually. A changed node identity can make persisted channel state unusable. Back up and test the exact upgrade path before changing a funded node. + +## VSS + +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + vssUrl: 'https://vss.example.com', +}) +``` + +Non-loopback HTTP is rejected unless `vssAllowHttp` is true. The original seed is required to decrypt recovery state. `vssStatus()` is a local configuration view, not a VSS server health check. + +## LSP and APay + +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + lspBaseUrl: 'https://lsp.example.com', + lspBearerToken: await loadLspToken(), + enableVirtualChannelsV0: true, + virtualPeerPubkeys: [lspNodeId], +}) +``` + +Production APay requires both virtual-channel fields and an LSP that trusts the wallet node. Treat `lspBearerToken` as a secret. The standalone `LspClient` rejects public HTTP by default. + +## Runtime binding + +Install exactly one optional native peer for the host: + +```bash +# Node host +npm install @utexo/rgb-lightning-node-nodejs@0.1.0-beta.11 + +# Bare/mobile host +npm install @utexo/rgb-lightning-node-bare@0.1.0-beta.15 +``` + +The declared compatible ranges are wider than these verified versions. Pin and validate a known pair rather than allowing an unreviewed beta upgrade. + +## Next steps + +- [Get started](/sdk/community-modules/wdk-rgb-lightning/guides/get-started) +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [VSS backup and recovery](/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/balances-history.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/balances-history.mdx new file mode 100644 index 00000000..d2e47ff7 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/balances-history.mdx @@ -0,0 +1,100 @@ +--- +title: Read RGB Lightning balances and history +description: Query Bitcoin, RGB, Lightning payment, transaction, and terminal receipt state. +docType: how-to +schemaType: TechArticle +--- + +Use the account or its read-only adapter to query state owned by the RGB Lightning node. + + +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. + + +## Confirm the account is unlocked + +```js +const state = await account.getAddressState() +if (state.status !== 'ready') { + throw new Error('Unlock the account before interpreting balances') +} + +await account.sync() +``` + +Before unlock, `getBalance()` returns `0n`; that value is not distinguishable from a real zero without `getAddressState()`. + +## Read Bitcoin balances + +```js +const spendableSats = await account.getBalance() +const balanceDetails = await account.getBalanceDetails() + +console.log({ + spendableSats: spendableSats.toString(), + balanceDetails, +}) +``` + +`getBalance()` returns spendable vanilla Bitcoin satoshis. `getBalanceDetails()` returns the native breakdown as an opaque object. + +## Read RGB balances and assets + +```js +const assets = await account.listAssets() +const balance = await account.getAssetBalance(assetId) +const spendableUnits = await account.getTokenBalance(assetId) +const metadata = await account.getAssetMetadata(assetId) +``` + +`getTokenBalance()` returns spendable asset base units and falls back to settled units when needed. Format values using validated asset metadata. + +The Lightning node and on-chain RGB wallet do not share records. Query the module that actually received or holds the asset. + +## Read Bitcoin and RGB history + +```js +const [transactions, unspents, rgbTransfers] = await Promise.all([ + account.getTransactions(), + account.listUnspents(), + account.listTransfers(assetId), +]) +``` + +You can narrow Bitcoin records with `getTransactionsByTxid(txid)` and RGB records with `listTransfersByTxid(txid)`. + +## Read Lightning payments + +```js +const payments = await account.listPayments() +const outbound = await account.getPayment(paymentHash, 'Outbound') +``` + +The accepted payment discriminants are `Outbound`, `InboundAutoClaim`, and `InboundHodl`. Pre-1.0 HTTP names such as `sent` and `received` are not accepted. + +## Read a terminal receipt + +```js +const receipt = await account.getTransactionReceipt(hash) +``` + +The method returns: + +- a confirmed Bitcoin record; +- a settled RGB transfer; +- a non-pending Lightning payment; +- or `null`. + +Treat `null` as pending, unknown, or not yet indexed—not proof that a write failed. + +## Validate native payloads + +Many responses are intentionally declared as `object` because RLN owns their shape. Validate the fields your application consumes at runtime and pin them to beta.15. Do not copy response fields from an unreleased branch or different RLN beta. + +## Next steps + +- [Send Bitcoin and manage UTXOs](/sdk/community-modules/wdk-rgb-lightning/guides/send-btc-utxos) +- [Use Lightning payments](/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/get-started.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/get-started.mdx new file mode 100644 index 00000000..e69182dd --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/get-started.mdx @@ -0,0 +1,105 @@ +--- +title: Get started with RGB Lightning +description: Install @utexo/wdk-rgb-lightning beta.15 with a matching native peer and unlock the node. +docType: how-to +schemaType: TechArticle +--- + +This guide creates the single RGB Lightning node account and unlocks it against development services. + + +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 module and one native peer + +For a Node.js 18-or-newer host: + +```bash +npm install @utexo/wdk-rgb-lightning@0.1.0-beta.15 @utexo/rgb-lightning-node-nodejs@0.1.0-beta.11 +``` + +For a Bare/mobile host: + +```bash +npm install @utexo/wdk-rgb-lightning@0.1.0-beta.15 @utexo/rgb-lightning-node-bare@0.1.0-beta.15 +``` + +Install only the peer for the target runtime. No Windows native artifact was published in this release set. + +## 2. Construct the manager + +```js +import WalletManagerRgbLightning from '@utexo/wdk-rgb-lightning' + +const seedPhrase = await loadSeedFromSecretStorage() + +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-lightning', + nodeSeedDerivation: 'auto', +}) +``` + +Use a persistent directory that is not shared with the on-chain RGB wallet or another live node instance. + +## 3. Get and unlock account 0 + +```js +const account = await manager.getAccount(0) + +await account.unlock({ + bitcoind_rpc_username: 'rpc-user', + bitcoind_rpc_password: await loadRpcPassword(), + bitcoind_rpc_host: '127.0.0.1', + bitcoind_rpc_port: 18443, + indexer_url: 'tcp://127.0.0.1:50001', + proxy_endpoint: 'rpc://127.0.0.1:3000/json-rpc', + announce_addresses: [], + announce_alias: 'example-node', +}) +``` + +Constructor camelCase RPC, indexer, proxy, and announce fields are not consumed in beta.15. Pass native snake_case fields to `unlock()`. + +## 4. Confirm readiness + +```js +const addressState = await account.getAddressState() +if (addressState.status !== 'ready') { + throw new Error('RGB Lightning account is still locked') +} + +const [nodeInfo, networkInfo] = await Promise.all([ + account.getNodeInfo(), + account.getNetworkInfo(), +]) + +console.log({ + address: addressState.address, + nodeInfo, + networkInfo, +}) +``` + +The current address remains stable until `rotateAddress()` is called. + +## 5. Dispose at shutdown + +```js +try { + await runNodeFlows(account) +} finally { + manager.dispose() +} +``` + +`manager.dispose()` is terminal. It owns binding shutdown, VLS signer destruction, retained-seed cleanup, and account invalidation. `account.dispose()` is a no-op in this release. + +## Next steps + +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Connect peers and open channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup.mdx new file mode 100644 index 00000000..7e4e4c73 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup.mdx @@ -0,0 +1,132 @@ +--- +title: Handle errors and clean up the node +description: Handle typed RGB Lightning boundaries, raw RLN failures, ambiguous writes, and terminal manager cleanup. +docType: how-to +schemaType: TechArticle +--- + +Beta.15 wraps selected lifecycle boundaries in typed errors, while many native calls still return raw `Rln(): ` failures. + + +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. + + +## Branch on typed boundaries + +```js +import { + AccountLockedError, + ApayError, + UnlockError, + VssError, + VssNotConfiguredError, +} from '@utexo/wdk-rgb-lightning' + +try { + await account.unlock(unlockRequest) +} catch (error) { + if (error instanceof UnlockError) { + reportUnlockFailure(error.code, error.cause) + } + throw error +} +``` + +Typed `RgbLightningError` subclasses preserve the original error as `cause` and expose `code` plus `toJSON()`. + +| Error | Handle | +|---|---| +| `UnlockError` | RPC credentials, host reachability, indexer, proxy, VSS initialization, or persisted identity mismatch. | +| `AccountLockedError` | Prompt for unlock or use `getAddressState()` before address/signature operations. | +| `VssNotConfiguredError` | Disable the VSS action or construct with `vssUrl`. | +| `VssError` | Preserve the fence/snapshot state and investigate before retrying. | +| `ApayError` | Inspect peer visibility, LSP auth, virtual-channel config, and partial bootstrap state. | +| `NotImplementedError` | Route to an operation-specific send method. | + +`LspError`, `LnurlPayError`, and the composed LSP timeout/settlement errors are separate hierarchies. + +## Preserve raw native errors + +Many node, channel, payment, Bitcoin, and RGB methods still throw version-specific `Rln(...)` strings. + +```js +try { + await account.openChannel(openRequest) +} catch (error) { + reportWalletFailure({ + operation: 'open_channel', + message: error instanceof Error ? error.message : String(error), + }) + throw error +} +``` + +Do not branch on raw message text unless the behavior is pinned and covered by tests. Never log the mnemonic, unlock credentials, LSP token, VSS fence password, complete private invoice context, or native requests containing secrets. + +## Reconcile writes before retrying + +A timeout does not prove failure. After a channel, payment, Bitcoin, RGB, or APay write: + +1. Preserve returned IDs and the original error. +2. Query the relevant channel, payment, transaction, transfer, peer, or APay state. +3. Synchronize when appropriate. +4. Retry only after an explicit idempotency rule proves it safe. + +`bootstrapLsp()` can leave the LSP peer connected when later APay registration fails. Inspect `listPeers()` before retrying. + +## Distinguish lock from zero balance + +Before unlock, `getBalance()` returns `0n`: + +```js +const state = await account.getAddressState() + +if (state.status === 'locked') { + showUnlockRequired() +} else { + showBalance(await account.getBalance()) +} +``` + +## Preserve the primary failure during cleanup + +```js +let operationError + +try { + await runWalletFlow(account) +} catch (error) { + operationError = error + throw error +} finally { + try { + manager.dispose() + } catch (cleanupError) { + reportCleanupFailure(cleanupError, { operationError }) + } +} +``` + +`account.shutdown()` is idempotent, but `manager.dispose()` is the terminal owner-level cleanup. It shuts down the binding, destroys the VLS signer, wipes retained seed buffers, and invalidates read-only adapters. `account.dispose()` is a no-op. + +Do not reuse a manager, account, read-only adapter, or LSP object after manager disposal. + +## Investigate common failures + +| Symptom | Evidence to collect | +|---|---| +| Native binding fails to load | Host OS/architecture, installed peer version, downloaded artifact, Node/Bare entry selected. | +| Persisted identity mismatch | `nodeSeedDerivation`, prior beta version, node public key, isolated backup of `dataDir`. | +| Locked address | `getAddressState()`, unlock request and service reachability without secret values. | +| Channel/payment failure | Peer and channel state, route amount units, RGB 3,000,000-msat minimum, native error cause. | +| VSS fence held | Previous owner lifecycle, VSS namespace, last checkpoint, proof no other live writer exists. | +| LSP/APay failure | HTTPS/auth, peer visibility, virtual-channel flags, trusted LSP node ID, partial bootstrap state. | + +## Next steps + +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) +- [VSS backup and recovery](/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery) +- [LSP, Lightning Address, and APay](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments.mdx new file mode 100644 index 00000000..b740076c --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments.mdx @@ -0,0 +1,105 @@ +--- +title: Create and send Lightning payments +description: Create BOLT11 and HODL invoices, send payments or keysend, and inspect Lightning payment state. +docType: how-to +schemaType: TechArticle +--- + +Use the released invoice and payment methods after the node is unlocked and has a ready channel. + + +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 invoice + +The convenience method accepts released camelCase fields: + +```js +const created = await account.createLightningInvoice({ + amountMsat: 5_000_000, + expirySec: 3_600, +}) +``` + +It also accepts native snake_case objects. The return is intentionally typed as `object`; validate and extract the BOLT11 field according to the pinned beta.15 response. + +For an RGB-routed invoice, include `assetId` and `assetAmount`. Such an HTLC must be at least 3,000,000 msat. + +## Decode and inspect an invoice + +```js +const decoded = await account.decodeInvoice(bolt11) +const status = await account.getInvoiceStatus(bolt11) +``` + +Before paying, validate network, destination, amount, expiry, description or description hash, asset fields, and any application-specific approval. + +## Send a BOLT11 payment + +```js +const sent = await account.sendPayment({ invoice: bolt11 }) +``` + +`sendPayment()` forwards the native request and returns a native object. Reconcile with `getPayment()` or `listPayments()` after ambiguous failure. + +The generic WDK router also recognizes a BOLT11 recipient: + +```js +const result = await account.transfer({ + recipient: bolt11, + amount: 5_000_000n, +}) +``` + +Lightning amounts and returned fees are millisatoshis. + +## Keysend + +`keysend(request)` accepts a native request object. The generic router treats a 66-character hexadecimal recipient as a node public key: + +```js +const result = await account.transfer({ + recipient: destinationNodeId, + amount: 5_000_000n, +}) +``` + +Validate that the recipient is exactly the intended compressed node ID. A malformed recipient can fall through to a different transfer route and fail later. + +## Quote limitations + +`quoteTransfer()` uses a 50-basis-point Lightning allowance. It is not a live route probe and does not guarantee the route, liquidity, final fee, or success. + +Apply an application policy, then inspect the actual payment record rather than presenting the quote as final. + +## HODL invoices + +```js +const hodl = await account.createHodlInvoice({ + paymentHash, + amtMsat: 5_000_000, + expirySec: 3_600, +}) +``` + +The caller owns preimage generation and custody. Use `claimHodlInvoice(validatedNativeClaimRequest)` only after the intended condition is satisfied, or `cancelHodlInvoice(validatedNativeCancelRequest)` according to a documented timeout policy. + +Losing, disclosing, or reusing a preimage can violate the payment contract. + +## Inspect payment history + +```js +const payments = await account.listPayments() +const payment = await account.getPayment(paymentHash, 'Outbound') +``` + +Valid payment types are `Outbound`, `InboundAutoClaim`, and `InboundHodl`. + +## Next steps + +- [Connect peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) +- [Use Lightning Address and APay](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay.mdx new file mode 100644 index 00000000..7bd22c8f --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay.mdx @@ -0,0 +1,127 @@ +--- +title: Use an LSP, Lightning Address, and APay +description: Use the beta.15 LSP client, composed RGB flows, LNURL-pay helpers, Lightning Address, and asynchronous payments. +docType: how-to +schemaType: TechArticle +--- + +The package exposes a standalone HTTP client, account-bound helpers, and a composed `UtexoLsp` flow object. + + +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 a secure LSP endpoint + +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + lspBaseUrl: 'https://lsp.example.com', + lspBearerToken: await loadLspToken(), +}) +``` + +Treat the bearer token as a secret. Public HTTP is rejected by default. Enable HTTP only for an explicitly accepted loopback/development environment. + +## Use the standalone client + +```js +import { LspClient } from '@utexo/wdk-rgb-lightning' + +const client = new LspClient({ + baseUrl: 'https://lsp.example.com', +}) + +const [health, info] = await Promise.all([ + client.health(), + client.getInfo(), +]) +``` + +The client also exposes LNURL discovery/callback, Lightning Address resolution, RGB on-chain send bridging, and Lightning receive bridging. Validate native response objects. + +## Use composed RGB flows + +With `lspBaseUrl` configured, the no-argument form discovers the peer: + +```js +const lsp = await account.createLsp() + +await lsp.connect() + +const receive = await lsp.receiveAsset({ + assetId, + amountRgb: 100, +}) + +const settlement = await lsp.awaitReceiveSettlement(receive.lnInvoice) +``` + +`UtexoLsp` also exposes `waitForChannel()`, `waitForOutboundLiquidity()`, `sendAsset()`, `payAddress()`, `enableLightningAddress()`, and `claimPendingPayments()`. + +Bound every wait with a timeout or `AbortSignal`. Handle `LspChannelTimeoutError`, `LspLiquidityTimeoutError`, and `LspSettlementError` separately. + +## Pay a Lightning Address + +```js +const paid = await lsp.payAddress({ + address: 'alice@example.com', + amtMsat: 5_000_000, +}) +``` + +The lower-level `payLightningAddress()` and LNURL helpers are also exported from the package root. + +LNURL callbacks must remain on the discovery host by default. Set `allowCrossHostCallback: true` only after reviewing the delegated host and redirect threat model. + +Inputs such as `$alice@example.com` are normalized as UMA-style address syntax. This release does not implement UMA signing, compliance, currency negotiation, or exchange-rate semantics. + +## Enable a Lightning Address + +```js +const address = await lsp.enableLightningAddress() +console.log(address.address) +``` + +The LSP owns availability and name assignment. Do not present the address as durable until the LSP confirms it and your application stores the returned mapping. + +## Configure production APay + +APay receives payments while the wallet is offline. Construct the manager with: + +```js +{ + lspBaseUrl: 'https://lsp.example.com', + lspBearerToken, + enableVirtualChannelsV0: true, + virtualPeerPubkeys: [lspNodeId], +} +``` + +Then bootstrap the authenticated LSP peer: + +```js +const result = await account.bootstrapLsp({ + peerPubkeyAndAddr: `${lspNodeId}@${lspHost}:${lspPort}`, + hostNodeId: lspNodeId, + waitForPeerMs: 15_000, + pollIntervalMs: 250, +}) +``` + +`bootstrapLsp()` connects the peer, waits for visibility, then calls `apayNew()` when `hostNodeId` is supplied. + + +A failed bootstrap can leave a connected peer even when APay registration did not finish. Inspect `listPeers()` and APay state before retrying; do not assume rollback. + + +Production APay requires mutual trust for `trusted_no_broadcast` virtual channels. Authenticate the LSP node ID independently. + +## Next steps + +- [Connect peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account.mdx new file mode 100644 index 00000000..d64580c6 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account.mdx @@ -0,0 +1,92 @@ +--- +title: Manage the RGB Lightning node account +description: Manage node identity, lock state, addresses, read-only access, seed derivation, and lifecycle cleanup. +docType: how-to +schemaType: TechArticle +--- + +One manager owns one RLN/LDK node, one account at index `0`, and one persistent state directory. + + +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 fixed account identity + +```js +const account = await manager.getAccount(0) + +console.log(account.index) // 0 +console.log(account.path) // m +``` + +`getAccountByPath('m')` returns the same account. Other paths and nonzero indexes are rejected. The manager's signer-name overload does not enable registered WDK signers; this module requires its attached VLS signer. + +`account.keyPair.publicKey` is the compressed Lightning node public key. `account.keyPair.privateKey` is always `null`. + +## Handle locked state explicitly + +```js +const state = await account.getAddressState() + +if (state.status === 'locked') { + showUnlockRequired() +} else { + showAddress(state.address) +} +``` + +`getAddress()` throws `AccountLockedError` before unlock. `getBalance()` instead returns `0n`, so never infer readiness from balance alone. + +## Keep or rotate the address deliberately + +```js +const current = await account.getAddress() +const next = await account.rotateAddress() +``` + +The binding enables address reuse, so repeated `getAddress()` calls return the stable current address. `rotateAddress()` is an explicit state-changing operation; update deposit records and UI only after it succeeds. + +## Create a read-only adapter + +```js +const readOnly = await account.toReadOnlyAccount() + +const [channels, peers, assets] = await Promise.all([ + readOnly.listChannels(), + readOnly.listPeers(), + readOnly.listAssets(), +]) +``` + +The adapter can query node, channel, payment, RGB, Bitcoin, fee, receipt, signature-verification, and diagnostic state. It cannot sign, broadcast, mutate channels, recover VSS, or expose LSP credentials. + +Do not retain it after `manager.dispose()`, because it uses the manager-owned native query transport. + +## Preserve node identity across upgrades + +The default `nodeSeedDerivation: 'auto'` uses corrected seed derivation for new nodes and retries legacy beta derivation only on an exact persisted identity mismatch. + +Pin `legacy-v1` only for a verified pre-beta.15 node that requires it. Pin `wdk-seed-v2` only after proving the persisted node uses the corrected identity. Changing identity against funded channel state can make the node unusable. + +## Shut down safely + +`account.shutdown()` is idempotent and stops the binding. `manager.dispose()` is the terminal owner-level cleanup and should still be your application shutdown boundary. + +```js +try { + await runNode(account) +} finally { + manager.dispose() +} +``` + +Cleanup can throw. Preserve the primary operation error and report cleanup failure separately. + +## Next steps + +- [Read balances and history](/sdk/community-modules/wdk-rgb-lightning/guides/balances-history) +- [Back up with VSS](/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels.mdx new file mode 100644 index 00000000..9e9441a4 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels.mdx @@ -0,0 +1,108 @@ +--- +title: Connect peers and manage channels +description: Connect Lightning peers and open, inspect, or close standard, RGB, and trusted virtual channels. +docType: how-to +schemaType: TechArticle +--- + +Unlock and synchronize the node before changing peer or channel 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. + + +## Connect a peer + +```js +const peerUri = `${peerNodeId}@${peerHost}:${peerPort}` + +await account.connectPeer(peerUri) +const peers = await account.listPeers() +``` + +Validate the node ID, host, port, network, and intended counterparty out of band. A successful connection does not prove channel readiness. + +## Open a standard channel + +```js +const opened = await account.openChannel({ + peer_pubkey_and_opt_addr: peerUri, + capacity_sat: 1_000_000, + push_msat: 0, + public: true, + with_anchors: true, +}) +``` + +Values are forwarded to RLN. Confirm funding, reserves, feerate, public/private policy, anchor support, and counterparty compatibility before opening. + +## Open an RGB channel + +The released `OpenChannelRequest` also declares `asset_id` and `asset_amount`: + +```js +const opened = await account.openChannel({ + peer_pubkey_and_opt_addr: peerUri, + capacity_sat: 1_000_000, + asset_id: assetId, + asset_amount: 10_000, + public: false, + with_anchors: true, +}) +``` + +`asset_amount` is in RGB asset base units. The asset must already exist in this Lightning node's independent wallet state. + + +An RGB-routed HTLC has a hard minimum of 3,000,000 millisatoshis in this release. Smaller RGB-channel invoices or payments fail to route. + + +## Inspect channel state + +```js +const channels = await account.listChannels() +const permanentId = await account.getChannelId(temporaryChannelIdHex) +``` + +Responses are native objects. Validate status, confirmations, channel IDs, balances, and counterparty before enabling payments. + +## Close or disconnect + +```js +await account.closeChannel(validatedNativeCloseRequest) +await account.disconnectPeer(validatedNativeDisconnectRequest) +``` + +The public declaration leaves both request shapes as `object`. Use the exact matching beta.15/RLN schema and validate it at the application boundary. + +Do not treat peer disconnection as channel closure. Confirm final channel and on-chain state. + +## Trusted virtual channels + +Production APay uses non-broadcast trusted channels. Construct the manager with: + +```js +{ + enableVirtualChannelsV0: true, + virtualPeerPubkeys: [lspNodeId], +} +``` + +Then the native open request can use: + +```js +{ + virtual_open_mode: 'trusted_no_broadcast', + // Other validated OpenChannelRequest fields. +} +``` + +Only trust an authenticated LSP node ID. Both sides must configure mutual trust; virtual channels change the normal broadcast and counterparty assumptions. + +## Next steps + +- [Use Lightning payments](/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments) +- [Use LSP and APay](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets.mdx new file mode 100644 index 00000000..34a1980a --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets.mdx @@ -0,0 +1,116 @@ +--- +title: Receive and send RGB assets +description: Query existing RGB assets, create receive invoices, and transfer assets through the RGB Lightning node. +docType: how-to +schemaType: TechArticle +--- + +The Lightning node holds and transfers its own RGB asset records. It does not share balances with the on-chain 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. + + +## Inspect an existing asset + +```js +const assets = await account.listAssets() +const balance = await account.getAssetBalance(assetId) +const metadata = await account.getAssetMetadata(assetId) +``` + +`getTokenBalance(assetId)` returns spendable base units and falls back to settled units when needed. + + +Use [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb) for released, documented NIA issuance. The beta.15 runtime contains issuance forwarders, but its public TypeScript declarations omit them; they are not documented here as supported WDK account APIs. + + +## Create an RGB invoice + +```js +const created = await account.createRgbInvoice({ + min_confirmations: 1, + witness: false, + asset_id: assetId, + assignment_kind: 'Fungible', + assignment_amount: 100, + duration_seconds: 3_600, +}) +``` + +`min_confirmations` and `witness` are required. The result is a native object; validate and extract the complete invoice using the pinned beta.15 response shape. + +Treat the invoice as single-use and send it over an authenticated channel. + +## Send with the WDK router + +```js +const transfer = await account.transfer({ + recipient: rgbInvoice, + token: assetId, + amount: 100n, + feeRate: 2, +}) + +console.log(transfer.hash) +``` + +The router recognizes `rgb:` and `utxob:` invoices. It decodes the invoice and sends one `Fungible` recipient with `donation: false` and `min_confirmations: 1`. + + +The WDK result returns `fee: 0n` for RGB because beta.15 does not expose the native fee through this route. It does not mean the transfer was fee-free. + + +## Use the native grouped send + +For non-fungible assignment kinds, donation behavior, witness data, multiple recipients, or explicit confirmation policy, use `sendRgbAsset()` with a validated `SendRgbAssetRequest`: + +```js +const result = await account.sendRgbAsset({ + donation: false, + fee_rate: 2, + min_confirmations: 1, + recipient_groups: validatedRecipientGroups, +}) +``` + +Each recipient group contains an `asset_id` and recipients with a decoded `recipient_id`, assignment kind and amount, transport endpoints, and optional witness data. Do not construct those fields from unvalidated display text. + +## Refresh and inspect transfer state + +```js +await account.refreshTransfers(validatedNativeRefreshRequest) + +const transfers = await account.listTransfers(assetId) +const byTransaction = await account.listTransfersByTxid(txid) +``` + +`failTransfers(request)` mutates transfer state. Call it only with the exact matching native request after proving the transfer should be failed. + +## Media + +```js +const media = await account.getAssetMedia(digest) +const posted = await account.postAssetMedia(validatedNativeMediaRequest) +``` + +Media is untrusted content. Validate digest, size, MIME type, storage, and rendering. `maxMediaUploadSizeMb` defaults to `5`. + +## State boundary + +Use separate persistent paths: + +```text +rgb-onchain/ # @utexo/wdk-wallet-rgb +rgb-lightning/ # @utexo/wdk-rgb-lightning +``` + +Moving an asset between them requires a real receive/send flow. Copying files or reusing one path is not a migration. + +## Next steps + +- [Read balances and history](/sdk/community-modules/wdk-rgb-lightning/guides/balances-history) +- [Use peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use LSP flows](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/send-btc-utxos.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/send-btc-utxos.mdx new file mode 100644 index 00000000..55b31ce8 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/send-btc-utxos.mdx @@ -0,0 +1,86 @@ +--- +title: Send Bitcoin and manage UTXOs +description: Quote and send Bitcoin, inspect history, and use native UTXO operations through the RGB Lightning account. +docType: how-to +schemaType: TechArticle +--- + +The RGB Lightning account exposes a WDK-shaped Bitcoin send and lower-level RLN Bitcoin/UTXO methods. + + +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. + + +## Check readiness and balance + +```js +const state = await account.getAddressState() +if (state.status !== 'ready') { + throw new Error('Unlock the account before sending Bitcoin') +} + +await account.sync() +const balance = await account.getBalance() +const unspents = await account.listUnspents() +``` + +Validate spendable balance, UTXO status, confirmations, and any channel reserve requirements. + +## Quote a standard send + +```js +const transaction = { + to: bitcoinAddress, + value: 50_000n, + feeRate: 2, + confirmationTarget: 6, +} + +const quote = await account.quoteSendTransaction(transaction) +``` + +The beta.15 quote approximates a standard transaction as 141 vbytes multiplied by a fee rate. It does not construct the final transaction or guarantee its exact size or fee. + +`manager.getFeeRates()` reads main mempool.space recommendations without selecting `testnet`, `signet`, or `regtest`. Use a network-appropriate estimator for policy decisions. + +## Send through the WDK shape + +```js +const maximumFee = 2_000n +if (quote.fee > maximumFee) { + throw new Error('Quoted Bitcoin fee exceeds the application limit') +} + +const result = await account.sendTransaction(transaction) +console.log({ txid: result.hash, feeSats: result.fee.toString() }) +``` + +VLS signs internally. The result fee is in satoshis. + +## Use native methods only with pinned shapes + +`sendBtc(request)` and `createUtxos(request)` forward native RLN request objects. Their public beta.15 declarations intentionally use `object`; inspect and validate the exact matching RLN payload rather than inventing fields. + +```js +const result = await account.sendBtc(validatedNativeSendRequest) +await account.createUtxos(validatedNativeCreateUtxosRequest) +``` + +`signTransaction()` always throws `NotImplementedError`. Use `sendTransaction()`, `sendBtc()`, `sendPayment()`, or `sendRgbAsset()` so VLS can enforce operation-specific policy. + +## Reconcile ambiguous sends + +After a timeout: + +1. Keep any returned transaction ID. +2. Call `sync()`. +3. Query `getTransactionsByTxid()` and `listUnspents()`. +4. Retry only after proving the first send was not accepted. + +## Next steps + +- [Read balances and history](/sdk/community-modules/wdk-rgb-lightning/guides/balances-history) +- [Connect peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/sign-verify-messages.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/sign-verify-messages.mdx new file mode 100644 index 00000000..6a91137a --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/sign-verify-messages.mdx @@ -0,0 +1,74 @@ +--- +title: Sign and verify Lightning messages +description: Sign domain-separated messages through VLS and verify them with full or read-only RGB Lightning accounts. +docType: how-to +schemaType: TechArticle +--- + +The account signs with its Lightning node identity through the in-process VLS signer. It never exposes a private key through `keyPair`. + + +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. + + +## Build a domain-separated challenge + +```js +const message = [ + 'example-node-auth', + 'version=1', + `origin=${expectedOrigin}`, + `nonce=${serverNonce}`, + `expires=${expiresAt}`, +].join('\n') +``` + +Include purpose, origin, nonce, expiry, and version. Never ask a user to sign opaque bytes or content that could be interpreted as another action. + +## Sign after unlock + +```js +const state = await account.getAddressState() +if (state.status !== 'ready') { + throw new Error('Unlock the node before signing') +} + +const signature = await account.sign(message) +``` + +`account.keyPair.privateKey` remains `null`; VLS owns signing material and policy checks. + +## Verify + +The full account can verify: + +```js +const valid = await account.verify(message, signature) +``` + +The read-only adapter can also verify while its originating manager and node transport remain available: + +```js +const readOnly = await account.toReadOnlyAccount() +const valid = await readOnly.verify(message, signature) +``` + +Reject changed messages, reused nonces, expired challenges, unexpected origins, and signatures tied to the wrong node identity. + +## Do not confuse message and transaction signing + +`sign()` signs a Lightning message. It does not produce a Bitcoin PSBT signature, channel commitment, BOLT11 payment, or RGB consignment authorization. + +`signTransaction()` intentionally throws `NotImplementedError`. Use the operation-specific send methods so VLS can apply the correct policy. + +## Cleanup + +Call `manager.dispose()` when the node session ends. It destroys the attached signer and zeroes retained seed buffers where owned by the module. Disposal cannot erase secrets copied by application code or recover information written to logs. + +## Next steps + +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery.mdx new file mode 100644 index 00000000..476df5db --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery.mdx @@ -0,0 +1,110 @@ +--- +title: Back up and recover with VSS +description: Configure encrypted VSS snapshots, force checkpoints, recover with the original seed, and handle ownership fences safely. +docType: how-to +schemaType: TechArticle +--- + +VSS can mirror LDK channel state and RGB wallet data to a remote key-value service. It complements—not replaces—seed custody and local operational recovery. + + +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. + + +## Enable VSS at construction + +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + vssUrl: 'https://vss.example.com', + vssAllowHttp: false, + vssAllowEmptyRestore: false, +}) +``` + +Non-loopback HTTP is rejected unless `vssAllowHttp` is explicitly enabled. Keep production VSS on an authenticated, monitored HTTPS service. + +VSS payloads are encrypted client-side. Recovery still requires the original BIP-39 seed; VSS ciphertext alone is insufficient. + +## Inspect local configuration state + +```js +const status = await account.vssStatus() + +console.log({ + configured: status.configured, + url: status.url, + lastBackupVersion: status.lastBackupVersion, +}) +``` + + +`vssStatus()` is a local view. It does not contact the server or prove that the latest remote snapshot exists, is readable, or can be decrypted. + + +## Force a checkpoint + +```js +const { version } = await account.vssBackup() +recordCheckpointVersion(version) +``` + +Use forced checkpoints at controlled lifecycle boundaries such as before app suspension or a planned upgrade. A returned version proves only that this call completed; recovery testing remains necessary. + +Calls without `vssUrl` throw `VssNotConfiguredError`. Server and encryption failures throw `VssError` at the wrapped boundaries. + +## Plan recovery + +The released account has no separate `restoreFromVss()` method. Recovery belongs to native node initialization with: + +- the original seed; +- the intended network; +- the same VSS namespace and configuration; +- a safe local `dataDir`; +- compatible beta.15 module and native binding versions. + +Follow the matching RLN/VSS recovery runbook and validate node identity, channels, payments, RGB state, Bitcoin state, and the latest checkpoint before resuming writes. + +`vssAllowEmptyRestore: true` permits an empty remote store. Use it only when creating a deliberately new node; otherwise it can turn missing recovery state into a fresh start. + +## Clear a stale ownership fence only when proven safe + +```js +await account.clearVssFence(vssFencePassword) +``` + +The fence prevents two live writers from using one VSS store. + + +Call `clearVssFence()` only when you are certain the previous owner is permanently stopped. Two live nodes writing the same channel state can corrupt or roll back state and put funds at risk. + + +Before clearing: + +1. Stop and isolate the previous host. +2. Confirm no background service, mobile worklet, or failover instance can restart. +3. Preserve local logs and state for incident analysis. +4. Verify the VSS namespace, seed identity, and expected checkpoint. +5. Start exactly one replacement owner. + +## Recovery testing + +Test on the exact runtime and native peer: + +- seed retrieval and decryption; +- native artifact loading; +- VSS authentication and snapshot retrieval; +- node public key stability; +- channel and payment reconciliation; +- RGB asset and transfer state; +- Bitcoin address, balance, and UTXOs; +- a new forced backup after recovery. + +## Next steps + +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/index.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/index.mdx new file mode 100644 index 00000000..124a3744 --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/index.mdx @@ -0,0 +1,87 @@ +--- +title: RGB Lightning wallet +description: Run a community-maintained RGB-over-Lightning wallet with LDK channels, invoices, payments, VSS, and LSP flows. +docType: explanation +schemaType: TechArticle +--- + +`@utexo/wdk-rgb-lightning` is a community-maintained WDK wallet module that runs an LDK and `rgb-lib` node behind the WDK manager/account interface. + +These pages describe the released [`@utexo/wdk-rgb-lightning@0.1.0-beta.15`](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15). + + +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 is a pre-1.0 beta. Its native payloads, peer compatibility, LSP behavior, and operational recovery model can change between beta releases. Pin exact versions and test the full lifecycle on the target host. + + +## Choose the correct RGB module + +| Requirement | Module | +|---|---| +| Channels, BOLT11 payments, RGB invoices and transfers, Lightning Address, APay, or VSS | `@utexo/wdk-rgb-lightning` | +| On-chain NIA issuance and an independent on-chain RGB wallet | [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb) | +| Bitcoin without RGB or Lightning node state | [`@tetherto/wdk-wallet-btc`](/sdk/wallet-modules/wallet-btc) | + +The two UTEXO RGB modules derive different wallet identities and own different `rgb-lib` databases. They do not share asset records. Give each a separate persistent `dataDir`. + +## Runtime architecture + +The package uses conditional exports: + +| Host | Required optional peer | Released native artifacts verified for the peer | +|---|---|---| +| Node.js 18 or newer | `@utexo/rgb-lightning-node-nodejs` in `>=0.1.0-beta.10 <0.2.0` | macOS arm64/x64; Linux arm64 GNU; Linux x64 GNU/musl | +| Bare / mobile worklet | `@utexo/rgb-lightning-node-bare` in `>=0.1.0-beta.14 <0.2.0` | Android arm/arm64/x64; macOS arm64; iOS arm64 and arm64/x64 simulators | + +The verified Node peer release was `0.1.0-beta.11`; the verified Bare peer release was `0.1.0-beta.15`. No Windows native artifact was published in this release set. + +Each native peer downloads a prebuilt artifact during installation. Validate artifact provenance, platform selection, and native loading in the deployment pipeline. + +## Security and state model + +- The WDK manager retains the BIP-39 secret boundary. +- An in-process VLS external signer handles channel-state cryptography. +- `keyPair.privateKey` is always `null`; the account does not expose signer private bytes. +- RLN persists public node identity plus LDK and RGB state under `dataDir`. +- Optional VSS payloads are encrypted client-side and still require the original seed for recovery. +- `manager.dispose()` owns node shutdown, signer destruction, and retained-seed cleanup. + +The account is single-node and single-account: index `0`, path `m`. + +## Capabilities and boundaries + +- Explicit unlock against Bitcoin RPC, indexer, and RGB proxy services. +- Peer connections, channels, BOLT11 invoices, HODL invoices, payments, and keysend. +- Bitcoin balances, transactions, UTXOs, sends, and fee estimates. +- RGB balances, invoices, transfers, media, and channel-aware payments. +- LSP client, Lightning Address/LNURL-pay helpers, composed UTEXO LSP flows, and APay. +- Optional VSS backup and recovery fencing. +- Message signing and verification through the Lightning node identity. +- Query-only account adapter for least-authority reads. + +Runtime JavaScript contains RGB issuance forwarders, but beta.15's public declarations omit them. These docs do not present them as supported public account APIs. Use the on-chain RGB module for issuance. Atomic-swap methods remain native-binding-only and are also outside the WDK account surface. + +## Start building + + + +Install the matching native peer, construct the node, and unlock the account. + + +Open the task-focused guide catalog. + + +Configure persistent state, signer policy, VSS, LSP, and native unlock services. + + +Review the released beta.15 manager, account, error, and LSP surface. + + +Inspect the exact released declarations and implementation. + + diff --git a/content/docs/sdk/community-modules/wdk-rgb-lightning/usage.mdx b/content/docs/sdk/community-modules/wdk-rgb-lightning/usage.mdx new file mode 100644 index 00000000..6994a11a --- /dev/null +++ b/content/docs/sdk/community-modules/wdk-rgb-lightning/usage.mdx @@ -0,0 +1,57 @@ +--- +title: RGB Lightning wallet usage +description: Task-focused guides for @utexo/wdk-rgb-lightning 0.1.0-beta.15. +docType: how-to +schemaType: TechArticle +icon: BookOpen +--- + +Use these guides for the single-node RGB Lightning account in `@utexo/wdk-rgb-lightning@0.1.0-beta.15`. + + +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 module and matching native binding, then unlock the node. + + +Manage identity, lock state, address rotation, read-only access, and disposal. + + +Read Bitcoin, RGB, Lightning, transaction, and receipt state. + + +Use WDK Bitcoin sends and native UTXO operations. + + +Connect peers and open, inspect, or close Lightning channels. + + +Create invoices, send payments, keysend, and handle HODL invoices. + + +Create RGB invoices and transfer existing assets through the node. + + +Use the LSP client, LNURL-pay, composed flows, and asynchronous payments. + + +Configure encrypted VSS snapshots and handle ownership fences. + + +Use the Lightning identity signer without exposing private key bytes. + + +Branch on typed boundaries, preserve raw native failures, and shut down safely. + + + +## Reference + +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) +- [beta.15 release](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15) 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..c71314c3 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,168 @@ --- 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. | +### 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..6ce633e9 --- /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`](/sdk/community-modules/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..b7800506 --- /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`](/sdk/community-modules/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..263d29a6 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`](/sdk/community-modules/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 4953c100..36d4805c 100644 --- a/content/docs/sdk/core-module/api-reference.mdx +++ b/content/docs/sdk/core-module/api-reference.mdx @@ -832,7 +832,7 @@ interface ISwidgeProtocol extends ISwapProtocol, IBridgeProtocol { 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. +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 = { diff --git a/content/docs/sdk/core-module/guides/protocol-integration.mdx b/content/docs/sdk/core-module/guides/protocol-integration.mdx index 0a7af064..2cb2ecf8 100644 --- a/content/docs/sdk/core-module/guides/protocol-integration.mdx +++ b/content/docs/sdk/core-module/guides/protocol-integration.mdx @@ -94,7 +94,7 @@ const status = await swidge.getSwidgeStatus(result.id, { 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. +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. ### Swapping Tokens diff --git a/content/docs/sdk/lending-modules/index.mdx b/content/docs/sdk/lending-modules/index.mdx index d6159de0..fe2b78c4 100644 --- a/content/docs/sdk/lending-modules/index.mdx +++ b/content/docs/sdk/lending-modules/index.mdx @@ -7,13 +7,19 @@ schemaType: TechArticle 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 | 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/) | +| 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 diff --git a/content/docs/sdk/lending-modules/lending-aave-evm/configuration.mdx b/content/docs/sdk/lending-modules/lending-aave-evm/configuration.mdx index e6abc97e..858bfe85 100644 --- a/content/docs/sdk/lending-modules/lending-aave-evm/configuration.mdx +++ b/content/docs/sdk/lending-modules/lending-aave-evm/configuration.mdx @@ -45,7 +45,7 @@ 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.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. +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. @@ -54,20 +54,24 @@ import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { chainId: 1, - provider: 'https://arb1.arbitrum.io/rpc', + provider: 'https://ethereum-rpc.publicnode.com', bundlerUrl: 'YOUR_BUNDLER_URL', - paymasterUrl: 'YOUR_PAYMASTER_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: '0xdAC17F...ec7', amount: 1000000n }, { +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` diff --git a/content/docs/sdk/lending-modules/lending-aave-evm/guides/lending-operations.mdx b/content/docs/sdk/lending-modules/lending-aave-evm/guides/lending-operations.mdx index fff7668c..f1cec364 100644 --- a/content/docs/sdk/lending-modules/lending-aave-evm/guides/lending-operations.mdx +++ b/content/docs/sdk/lending-modules/lending-aave-evm/guides/lending-operations.mdx @@ -93,17 +93,19 @@ Health factor and collateralization limits still apply. A quote does not guarant ## 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. +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: 42161, - provider: 'https://arb1.arbitrum.io/rpc', + chainId: 1, + provider: 'https://ethereum-rpc.publicnode.com', bundlerUrl: process.env.BUNDLER_URL, - paymasterUrl: process.env.PAYMASTER_URL + paymasterUrl: process.env.PAYMASTER_URL, + paymasterAddress: process.env.PAYMASTER_ADDRESS, + safeModulesVersion: '0.3.0' }) const aaveAA = new AaveProtocolEvm(aa) @@ -119,6 +121,8 @@ const result = await aaveAA.supply( 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` diff --git a/content/docs/sdk/lending-modules/lending-morpho-evm/api-reference.mdx b/content/docs/sdk/lending-modules/lending-morpho-evm/api-reference.mdx index 70fa1caa..7bad41c4 100644 --- a/content/docs/sdk/lending-modules/lending-morpho-evm/api-reference.mdx +++ b/content/docs/sdk/lending-modules/lending-morpho-evm/api-reference.mdx @@ -6,6 +6,12 @@ schemaType: APIReference icon: Code --- + +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 @@ -47,7 +53,7 @@ const morpho = new MorphoProtocolEvm(account, { | `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` | +| `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` | @@ -65,8 +71,9 @@ const morpho = new MorphoProtocolEvm(account, { ## 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`: 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`. +- `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`. --- @@ -198,6 +205,7 @@ Options: - `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 @@ -209,7 +217,7 @@ const tx = await morpho.borrow({ ### `getBorrowRequirements(options)` -Return Morpho authorization requirements for borrow flows. +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({ diff --git a/content/docs/sdk/lending-modules/lending-morpho-evm/configuration.mdx b/content/docs/sdk/lending-modules/lending-morpho-evm/configuration.mdx index 4204058c..abc898b5 100644 --- a/content/docs/sdk/lending-modules/lending-morpho-evm/configuration.mdx +++ b/content/docs/sdk/lending-modules/lending-morpho-evm/configuration.mdx @@ -6,6 +6,12 @@ schemaType: TechArticle icon: Settings --- + +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 diff --git a/content/docs/sdk/lending-modules/lending-morpho-evm/guides/get-started.mdx b/content/docs/sdk/lending-modules/lending-morpho-evm/guides/get-started.mdx index 4d46ac04..014b6672 100644 --- a/content/docs/sdk/lending-modules/lending-morpho-evm/guides/get-started.mdx +++ b/content/docs/sdk/lending-modules/lending-morpho-evm/guides/get-started.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 diff --git a/content/docs/sdk/lending-modules/lending-morpho-evm/guides/handle-errors.mdx b/content/docs/sdk/lending-modules/lending-morpho-evm/guides/handle-errors.mdx index a4c1e5e5..c99c9f9f 100644 --- a/content/docs/sdk/lending-modules/lending-morpho-evm/guides/handle-errors.mdx +++ b/content/docs/sdk/lending-modules/lending-morpho-evm/guides/handle-errors.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 diff --git a/content/docs/sdk/lending-modules/lending-morpho-evm/guides/lending-operations.mdx b/content/docs/sdk/lending-modules/lending-morpho-evm/guides/lending-operations.mdx index 5ae1f4b1..807216ec 100644 --- a/content/docs/sdk/lending-modules/lending-morpho-evm/guides/lending-operations.mdx +++ b/content/docs/sdk/lending-modules/lending-morpho-evm/guides/lending-operations.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 @@ -189,7 +195,7 @@ const tx = await morpho.supply({ }) ``` -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()`: +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({ @@ -197,7 +203,14 @@ const requirements = await morpho.getBorrowRequirements({ 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, @@ -207,7 +220,8 @@ for (const requirement of requirements) { await morpho.borrow({ token: USDT, - amount: 1000000n + amount: 1000000n, + requirementSignature }) ``` diff --git a/content/docs/sdk/lending-modules/lending-morpho-evm/usage.mdx b/content/docs/sdk/lending-modules/lending-morpho-evm/usage.mdx index f014c702..2330d854 100644 --- a/content/docs/sdk/lending-modules/lending-morpho-evm/usage.mdx +++ b/content/docs/sdk/lending-modules/lending-morpho-evm/usage.mdx @@ -6,6 +6,12 @@ schemaType: TechArticle icon: BookOpen --- + +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. diff --git a/content/docs/sdk/sda-modules/index.mdx b/content/docs/sdk/sda-modules/index.mdx deleted file mode 100644 index 25ffcceb..00000000 --- a/content/docs/sdk/sda-modules/index.mdx +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: Smart Deposit Address interface -description: Implement or integrate provider-defined deposit routes with the WDK Smart Deposit Address protocol contract. -docType: reference -schemaType: TechArticle ---- - -The Smart Deposit Address (SDA) interface in `@tetherto/wdk-wallet/protocols` defines how a provider exposes deposit routes, creates route-bound deposit addresses, and optionally reports their lifecycle and transfer status. - - -`@tetherto/wdk-wallet` provides the SDA interface and abstract base class, not a concrete SDA provider. The currently released `@tetherto/wdk` package does not expose SDA-specific orchestration. Instantiate a concrete provider directly according to that provider's documentation. - - -## Imports - -```typescript -import { - SdaProtocol, - type ISdaProtocol, - type SdaCreateDepositAddressOptions, - type SdaDepositAddress, - type SdaDepositOptions, - type SdaDepositQuote, - type SdaRecoveryOptions, - type SdaRecoveryResult, - type SdaRoute, - type SdaRoutesOptions, - type SdaTransfer, - type SdaTransfersOptions -} from '@tetherto/wdk-wallet/protocols' - -import { - NoSuchElementError, - UnsupportedOperationError, - ValueError -} from '@tetherto/wdk-wallet' -``` - -A provider subclass of `SdaProtocol` may be constructed with a writable account, a read-only account, or no account. A bound account supplies the default `destinationAddress` for address creation. - -## Provider Contract - -A concrete provider that extends `SdaProtocol` must implement route discovery and address creation. The remaining methods have default implementations that throw `UnsupportedOperationError`. - -| Method | Support | Purpose | -|--------|---------|---------| -| `getSupportedRoutes(options?)` | Required | List supported source chains, input tokens, destinations, and route metadata. | -| `createDepositAddress(options)` | Required | Create and, when the provider has an activation lifecycle, activate deposit addresses. | -| `quoteDeposit(options)` | Optional | Return a non-binding estimate for a deposit. | -| `deriveDepositAddress(options)` | Optional | Derive an address client-side without activating or monitoring it. | -| `getDepositAddress(id)` | Optional | Look up an address descriptor by its provider identifier. | -| `renewDepositAddress(id)` | Optional | Refresh a time-limited address activation. | -| `getTransfers(address, options?)` | Optional | List transfers observed for an address. | -| `getTransfersByRecipient(destinationChain, recipient, options?)` | Optional | List transfers routed to a destination recipient. | -| `getTransfer(id)` | Optional | Read one transfer's current status. | -| `recoverDepositAddress(options)` | Optional | Ask the provider to reindex a deposit by SDA identifier or address. | -| `disableDepositAddress(id)` | Optional | Stop a provider from accepting deposits at an address. | - -Check provider capabilities before building a flow around an optional method. An `UnsupportedOperationError` is a capability result, not a transient network failure. - -## Deposit Flow - -1. Call `getSupportedRoutes()` and select an advertised source chain, input token, destination chain, and output asset. -2. If the provider supports it, call `quoteDeposit()` for a non-binding estimate. Treat `expiry` as quote metadata, not a delivery guarantee. -3. Call `createDepositAddress()` with the selected route. Set `destinationAddress` explicitly, or bind an account whose address should be used as the destination. -4. Present the returned descriptor and its limits to the depositor before funds are sent. -5. If supported, monitor with `getTransfers()`, `getTransfersByRecipient()`, or `getTransfer()`. -6. Use renew, recovery, or disable operations only when the provider implements the corresponding lifecycle. - - -Address creation does not itself transfer funds. Validate that the returned descriptor matches the intended source chains, tokens, destination chain, output asset, and recipient before displaying or funding an address. - - -`createDepositAddress()` returns one descriptor per distinct address. A provider that uses one address across a chain family can return one descriptor covering every requested source chain; a provider that issues one address per chain returns multiple descriptors. - -## Core Types - -### Routes and tokens - -```typescript -type Blockchain = string | number - -type SdaRoutesOptions = { - sourceChain?: Blockchain - sourceToken?: string - destinationChain?: Blockchain - outputAsset?: string -} - -type SdaToken = { - token: string - chain: Blockchain - symbol: string - decimals: number - address?: string - name?: string -} - -type SdaRoute = { - sourceChains: Blockchain[] - inputTokens: SdaToken[] - destinationChain: Blockchain - outputAsset?: SdaToken - limits?: { - min?: number | bigint - max?: number | bigint - } - reusable?: boolean - estimatedDuration?: number -} -``` - -Route limits are denominated in the input token's base unit. A provider that only has limits in another denomination omits `limits` instead of converting them. - -### Quotes - -```typescript -type SdaDepositOptions = { - sourceChain: Blockchain - inputToken: string - destinationChain: Blockchain - outputAsset?: string - inputAmount: number | bigint -} - -type SdaDepositQuote = { - inputChain: Blockchain - inputToken: string - inputAmount: bigint - destinationChain: Blockchain - outputAsset: string - outputAmount: bigint - fees: Array<{ - type: 'network' | 'protocol' | 'affiliate' | 'other' - amount: bigint - token: string - chain?: Blockchain - included?: boolean - description?: string - }> - rate?: string - expiry?: number - id?: string -} -``` - -Quote amounts and itemized fees use base units. `rate` is a string to avoid precision loss, and the quote is non-binding. - -### Deposit addresses - -```typescript -type SdaCreateDepositAddressOptions = { - sourceChains: Blockchain[] - destinationChain: Blockchain - outputAsset?: string - destinationAddress?: string -} - -type SdaDepositAddress = { - address: string - id: string - sourceChains: Blockchain[] - supportedInputTokens: SdaToken[] - destinationChain: Blockchain - outputAsset?: SdaToken - destinationAddress: string - limits?: { - min?: number | bigint - max?: number | bigint - } - reusable: boolean - expiry?: number -} -``` - -Omitting `destinationAddress` uses the bound account's address. If no account is bound, the provider contract throws `ValueError`. - -`deriveDepositAddress()` uses the same options but only computes an address client-side. It does not activate the address or cause the provider to monitor it. - -### Transfer status - -```typescript -type SdaTransferStatus = - | 'pending' - | 'detected' - | 'processing' - | 'completed' - | 'failed' - | 'refund-pending' - | 'refunded' - | 'expired' - -type SdaTransfersOptions = { - sourceChain?: Blockchain - limit?: number - skip?: number - status?: SdaTransferStatus -} - -type SdaTransfer = { - id: string - status: SdaTransferStatus -} -``` - -The base contract guarantees only an identifier and status for a transfer. Provider-specific implementations may expose more data, but portable integrations should not require amounts, hashes, or timestamps that are absent from `SdaTransfer`. - -`sourceChain` can be required by a provider that keys an address by its address-and-chain pair. `limit`, `skip`, and `status` provide optional history pagination and filtering. - -### Recovery - -```typescript -type SdaRecoveryOptions = - | { id: string } - | { - address: string - sourceChain?: Blockchain - } - -type SdaRecoveryResult = { - status: 'reindexed' | 'pending' | 'failed' - address?: string - id?: string - transfer?: SdaTransfer - message?: string -} -``` - -Recover by the `SdaDepositAddress.id`, or by address plus `sourceChain` when the provider requires chain context. A recovery result reports whether reindexing completed, remains pending, or failed; a recovered transfer is optional. - -## Errors and Capability Checks - -| Error | Meaning | -|-------|---------| -| `UnsupportedOperationError` | The provider does not implement an optional operation. | -| `ValueError` | Required route data is missing or invalid, or `destinationAddress` was omitted without a bound account. | -| `NoSuchElementError` | A requested address or transfer does not exist. | - -Provider documentation remains authoritative for supported routes, activation windows, monitoring behavior, recovery semantics, and any additional option fields. diff --git a/content/docs/sdk/swap-modules/index.mdx b/content/docs/sdk/swap-modules/index.mdx index 35c6fb77..3da654c1 100644 --- a/content/docs/sdk/swap-modules/index.mdx +++ b/content/docs/sdk/swap-modules/index.mdx @@ -30,6 +30,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 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 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. diff --git a/content/docs/sdk/swidge-modules/index.mdx b/content/docs/sdk/swidge-modules/index.mdx index 7779b044..b9f0ba89 100644 --- a/content/docs/sdk/swidge-modules/index.mdx +++ b/content/docs/sdk/swidge-modules/index.mdx @@ -1,223 +1,48 @@ --- -title: Swidge Protocol Interface -description: Learn the preferred WDK swidge interface for swap-only, bridge-only, and combined asset routes. -docType: reference +title: Swidge Modules Overview +description: Compare WDK Swidge providers for swaps, bridges, and combined asset routes. +docType: explanation schemaType: TechArticle --- -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. - - -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. - - -## 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. - -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. - -## 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/) | - -## Interface methods - -Concrete swidge provider modules extend `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. The base interface exposes discovery, quote, execution, and status methods: - -| 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' -}) - -const routeTokens = await swidge.getSupportedTokens({ - fromChain: 'ethereum', - fromToken: '0xSourceToken...', - toChain: 'arbitrum' -}) -``` - -`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 | - -## Quote a route - -Call `quoteSwidge()` before execution so the user can review source amount, expected destination amount, minimum output, fees, and quote expiry. - -```typescript title="Quote a swidge route" -const quote = await swidge.quoteSwidge({ - fromToken: '0xSourceToken...', - toToken: '0xDestinationToken...', - toChain: 'arbitrum', - recipient: '0xRecipient...', - fromTokenAmount: 1000000n, - slippage: 0.01 -}) - -console.log('Expected output:', quote.toTokenAmount) -console.log('Minimum output:', quote.toTokenAmountMin) -console.log('Fees:', quote.fees) -``` - -`SwidgeOptions` supports exact-in and exact-out routes: - -| 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%. | -| `minAmountOut` | `number \| bigint` | Optional minimum destination amount, in destination-token base units, passed to the provider. | -| `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`. | - - -The shared WDK wallet package passes `minAmountOut` to the concrete provider but does not validate or enforce it. Confirm provider-specific support before relying on this field as an execution guard. - - -## Execute a route - -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. - -```typescript title="Execute a swidge route" -const options = { - fromToken: '0xSourceToken...', - toToken: '0xDestinationToken...', - toChain: 'arbitrum', - recipient: '0xRecipient...', - fromTokenAmount: 1000000n, - slippage: 0.01 -} - -const quote = await swidge.quoteSwidge(options) -// Show quote details and ask for confirmation before continuing. - -const result = await swidge.swidge(options, { - maxNetworkFeeBps: 50, - maxProtocolFeeBps: 25 -}) - -console.log('Swidge ID:', result.id) -console.log('Primary transaction:', result.hash) -console.log('Transactions:', result.transactions) -``` +Swidge modules connect WDK accounts to providers that can quote and execute a swap, a bridge, or both as one route. Choose a provider by its supported chains, tokens, runtime, and operational requirements. -`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. - - -`SwidgeProtocolConfig` supports fee limits expressed in basis points of the input amount: - -| 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. | - -## Track status - -Use `getSwidgeStatus()` when the provider returns an execution ID and the final destination result may settle asynchronously. - -```typescript title="Check swidge status" -const status = await swidge.getSwidgeStatus(result.id, { - fromChain: 'ethereum', - toChain: 'arbitrum' -}) +Rows marked Community are developed and maintained independently by third-party contributors. -if (status.status === 'completed') { - console.log('Swidge complete') -} -``` - -Supported statuses are: - -| 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. | - -## Result fields - -`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. - -| 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. | +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 assets and chains +## Released provider modules -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. +| 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 | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | +| [`@moonpay/wdk-protocol-swidge-moonpay-trade`](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade) | MoonPay Trade | Community | [README](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade#readme) | +| [`@swapdk/wdk-protocol-swidge-swapdk`](https://www.npmjs.com/package/@swapdk/wdk-protocol-swidge-swapdk) | SwapDK | Community | [README](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk#readme) | +| [`@gobob/wdk-protocol-swidge-gateway`](https://www.npmjs.com/package/@gobob/wdk-protocol-swidge-gateway) | BOB Gateway | Community | [README](https://github.com/bob-collective/wdk-protocol-swidge-gateway#readme) | ## Next steps - -Register and use protocol modules with WDK accounts - - -Use the Flashnet Orchestra community Swidge provider + +Use the Flashnet Orchestra community provider for routes returned by Orchestra. - -Maintain existing standalone swap modules + +Use the Rhino.fi community provider for its supported cross-chain routes. - -Maintain existing standalone bridge modules + +Use the Symbiosis community provider for dynamically discovered routes. + +Maintaining an integration that does not implement Swidge? Use the released [standalone swap modules](/sdk/swap-modules/) or [standalone bridge modules](/sdk/bridge-modules/). + +*** + +## Need Help? + + diff --git a/content/docs/sdk/swidge-modules/swidge-orchestra/api-reference.mdx b/content/docs/sdk/swidge-modules/swidge-orchestra/api-reference.mdx index f7b87769..1ee62627 100644 --- a/content/docs/sdk/swidge-modules/swidge-orchestra/api-reference.mdx +++ b/content/docs/sdk/swidge-modules/swidge-orchestra/api-reference.mdx @@ -6,6 +6,12 @@ schemaType: APIReference icon: Code --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-orchestra/configuration.mdx b/content/docs/sdk/swidge-modules/swidge-orchestra/configuration.mdx index f69f6c3f..3a95a511 100644 --- a/content/docs/sdk/swidge-modules/swidge-orchestra/configuration.mdx +++ b/content/docs/sdk/swidge-modules/swidge-orchestra/configuration.mdx @@ -5,6 +5,12 @@ docType: reference schemaType: TechArticle --- + +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" diff --git a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/get-started.mdx b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/get-started.mdx index 80176d22..90880f9b 100644 --- a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/get-started.mdx +++ b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/get-started.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/handle-errors.mdx b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/handle-errors.mdx index dd3f0f52..527fcd7e 100644 --- a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/handle-errors.mdx +++ b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/handle-errors.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/quote-and-execute.mdx b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/quote-and-execute.mdx index e174ef91..aff35f30 100644 --- a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/quote-and-execute.mdx +++ b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/quote-and-execute.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery.mdx b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery.mdx index c5f0a62a..74aa3ad3 100644 --- a/content/docs/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery.mdx +++ b/content/docs/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-orchestra/usage.mdx b/content/docs/sdk/swidge-modules/swidge-orchestra/usage.mdx index be66814a..b3748d7f 100644 --- a/content/docs/sdk/swidge-modules/swidge-orchestra/usage.mdx +++ b/content/docs/sdk/swidge-modules/swidge-orchestra/usage.mdx @@ -5,6 +5,12 @@ docType: how-to schemaType: TechArticle --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-rhinofi/api-reference.mdx b/content/docs/sdk/swidge-modules/swidge-rhinofi/api-reference.mdx index b7b4a2bd..11911a9e 100644 --- a/content/docs/sdk/swidge-modules/swidge-rhinofi/api-reference.mdx +++ b/content/docs/sdk/swidge-modules/swidge-rhinofi/api-reference.mdx @@ -6,6 +6,12 @@ schemaType: APIReference icon: Code --- + +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. @@ -127,7 +133,7 @@ Inherited `swap`, `quoteSwap`, `bridge`, and `quoteBridge` calls delegate to `sw Install, quote, execute, and track Rhino.fi routes. - -Review the shared WDK swidge protocol shape. + +Compare released WDK Swidge provider modules. diff --git a/content/docs/sdk/swidge-modules/swidge-rhinofi/configuration.mdx b/content/docs/sdk/swidge-modules/swidge-rhinofi/configuration.mdx index e20eed7d..6c384811 100644 --- a/content/docs/sdk/swidge-modules/swidge-rhinofi/configuration.mdx +++ b/content/docs/sdk/swidge-modules/swidge-rhinofi/configuration.mdx @@ -6,6 +6,12 @@ schemaType: TechArticle icon: Settings --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-rhinofi/index.mdx b/content/docs/sdk/swidge-modules/swidge-rhinofi/index.mdx index 6ae37b62..a37b22a3 100644 --- a/content/docs/sdk/swidge-modules/swidge-rhinofi/index.mdx +++ b/content/docs/sdk/swidge-modules/swidge-rhinofi/index.mdx @@ -5,6 +5,12 @@ docType: explanation schemaType: TechArticle --- + +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. diff --git a/content/docs/sdk/swidge-modules/swidge-rhinofi/usage.mdx b/content/docs/sdk/swidge-modules/swidge-rhinofi/usage.mdx index a68e3786..58805603 100644 --- a/content/docs/sdk/swidge-modules/swidge-rhinofi/usage.mdx +++ b/content/docs/sdk/swidge-modules/swidge-rhinofi/usage.mdx @@ -6,6 +6,12 @@ schemaType: TechArticle icon: BookOpen --- + +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 diff --git a/content/docs/sdk/swidge-modules/swidge-symbiosis/api-reference.mdx b/content/docs/sdk/swidge-modules/swidge-symbiosis/api-reference.mdx new file mode 100644 index 00000000..51c1a127 --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-symbiosis/api-reference.mdx @@ -0,0 +1,280 @@ +--- +title: Symbiosis Swidge API Reference +description: API reference for @symbiosis-finance/wdk-protocol-swidge-symbiosis 1.1.1. +docType: reference +schemaType: APIReference +icon: Code +--- + + +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.1.1`](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.1.1). + +## `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 also uses the account's address as the request sender. | +| `undefined` | Discovery and status; quote-only use 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 + defaultSlippage?: number + partnerAddress?: string + refundAddress?: string + skipApproval?: boolean + maxNetworkFeeBps?: number | bigint + maxProtocolFeeBps?: number | bigint +} +``` + +See [Configuration](/sdk/swidge-modules/swidge-symbiosis/configuration) for defaults 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()`. + +Throws when: + +- `chain` is missing; +- `fromTokenAmount` is missing; +- `toTokenAmount` is present; +- a chain or token cannot be resolved; +- no account address or `recipient` can provide the request sender; +- the API returns a non-success response. + +### `swidge(options, config?)` + +```typescript +swidge( + options: SwidgeOptions, + config?: SwidgeProtocolConfig +): Promise +``` + +Requires an account with `sendTransaction()`. It calls `/v2/swap`, checks 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. Confirm the earlier quote with the user, then treat execution as a fresh provider response. + + +### `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 ignores other status hints. + +HTTP `404` is returned as `pending`, not as `ApiError`. + +### `getSupportedChains()` + +```typescript +getSupportedChains(): Promise +``` + +Calls the Symbiosis chain and token endpoints and maps each 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. | + +### `getSupportedTokens(options?)` + +```typescript +getSupportedTokens( + options?: SwidgeSupportedTokensOptions +): Promise +``` + +The chain filter is resolved as `options.toChain ?? options.fromChain`. Other route context does 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` | Optional destination chain; defaults to the configured source chain. | +| `recipient` | `string` | Destination recipient. Without an account, it is also used as the request sender. | +| `refundAddress` | `string` | Per-call refund address; overrides the constructor default. | +| `slippage` | `number` | Decimal slippage; overrides `defaultSlippage`. | +| `fromTokenAmount` | `number \| bigint` | Required exact input in source-token base units. | +| `toTokenAmount` | `number \| bigint` | Unsupported; throws `ExactOutNotSupportedError`. | + +The `SwidgeOptions` type in the package's tested `@tetherto/wdk-wallet@1.0.0-beta.11` dependency does not include `minAmountOut`. If a newer WDK interface exposes that field, this provider still does not consume it. + +## 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 return a quote expiry through `SwidgeQuote`. + +### `SwidgeResult` + +| Field | Type | Description | +|---|---|---| +| `id` | `string` | `:`. | +| `hash` | `string` | Primary source hash; the final sent message hash for a multi-message TON route. | +| `fees` | `SwidgeFee[]` | Fees from the fresh execution response. | +| `transactions` | `SwidgeTransaction[]` | Approval and recorded source hashes. A later status result can additionally report destination or refund hashes. | +| `fromTokenAmount` | `bigint` | Submitted exact input. | +| `toTokenAmount` | `bigint` | Fresh provider-estimated output. | +| `toTokenAmountMin` | `bigint` | 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 + +| Symbiosis `fees[].provider` | WDK fee type | +|---|---| +| `symbiosis` | `protocol` | +| `partner` | `affiliate` | +| Any other value | `protocol` | + +Mapped fees include `amount`, `token`, `chain`, `description`, and `included: true`. + +The provider emits no `network` fee entry in this release. Inherited legacy `bridge()` results therefore expose `0n` for their network `fee`; mapped protocol fees contribute to `bridgeFee`. + +Fee-cap fallback comparison uses decimal-normalized values when USD prices are absent. 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 provider errors. | Standard `Error` fields | +| `ConfigurationError` | Required source `chain` configuration is missing. | — | +| `ValidationError` | Input, 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 fee exceeds a 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-success status other than the status-specific `404` handling. | `status`, `response` | + +## 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. + + diff --git a/content/docs/sdk/swidge-modules/swidge-symbiosis/configuration.mdx b/content/docs/sdk/swidge-modules/swidge-symbiosis/configuration.mdx new file mode 100644 index 00000000..9db8a423 --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-symbiosis/configuration.mdx @@ -0,0 +1,196 @@ +--- +title: Symbiosis Swidge Configuration +description: Configure source-chain identity, slippage, refunds, approval behavior, and fee caps for the Symbiosis community provider. +docType: reference +schemaType: TechArticle +icon: Settings +--- + + +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', + defaultSlippage: 0.02, + refundAddress: 'bc1qRefund...', + maxProtocolFeeBps: 100 +}) +``` + +| Parameter | Description | +|---|---| +| `account` | Optional WDK wallet account. Discovery and some quotes 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. | +| `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. The current provider fee mapping does not emit a `network` fee item, so this does not constrain a separate network fee in `1.1.1`. | +| `maxProtocolFeeBps` | `number \| bigint` | None | Rejects execution when mapped Symbiosis protocol fees exceed this many basis points of the input amount. | + +## 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, the module returns the provider's native-format address from token discovery when available. + +Token symbols can be ambiguous. Prefer the exact address returned by `getSupportedTokens()` and confirm route availability with `quoteSwidge()`. + +## Slippage + +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 value to basis points for the Symbiosis request. It does not separately validate an additional `minAmountOut` field if a newer WDK interface exposes one. + +## Refund address + +Set a refund address suitable for the source route: + +```javascript +const symbiosis = new SymbiosisProtocol(bitcoinAccount, { + chain: 'Bitcoin', + refundAddress: 'bc1qRefund...' +}) +``` + +Override the default for one request: + +```javascript +await symbiosis.swidge({ + fromToken: 'BTC', + toToken: 'USDC', + toChain: 'Arbitrum One', + recipient: '0xRecipient...', + refundAddress: 'bc1qDifferentRefund...', + fromTokenAmount: 50_000n +}) +``` + +The module forwards the address to Symbiosis; it does not validate that the address belongs to the intended refund chain. + +## 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. approves the exact input amount when allowance is insufficient; +4. approves when allowance lookup fails; +5. waits for the approval receipt when `getTransactionReceipt()` is available. + +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, + maxNetworkFeeBps: 50 +}) +``` + +Override shared fee caps for one execution: + +```javascript +await symbiosis.swidge(options, { + maxProtocolFeeBps: 75, + maxNetworkFeeBps: 40 +}) +``` + +The module checks the fresh `/v2/swap` response before calling a wallet write method. + +When both the input token and fee token have 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 entries currently map to `protocol` or `affiliate`. No returned entry maps to `network`, so `maxNetworkFeeBps` remains at zero for this provider's current fee calculation. + +## 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. + +## 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`. +- The provider does not configure request timeout, cancellation, retry, or backoff. + +Use a trusted `https://` endpoint for `apiUrl`. Apply application-level timeouts and cautious retries, 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. + + diff --git a/content/docs/sdk/swidge-modules/swidge-symbiosis/index.mdx b/content/docs/sdk/swidge-modules/swidge-symbiosis/index.mdx new file mode 100644 index 00000000..253ff40c --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-symbiosis/index.mdx @@ -0,0 +1,108 @@ +--- +title: Symbiosis Swidge Overview +description: Use the Symbiosis community Swidge module for same-chain and cross-chain asset routes. +docType: explanation +schemaType: TechArticle +--- + + +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.1.1`](https://www.npmjs.com/package/@symbiosis-finance/wdk-protocol-swidge-symbiosis/v/1.1.1) 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.1.1`](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.1.1) 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, TON, or Bitcoin source execution through a compatible WDK wallet account; +- cross-chain settlement status mapped to WDK status values. + +The module can quote 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 | +| 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 response, amounts, fees, spender, transaction payload, or Bitcoin deposit address can differ from the earlier quote. The module checks configured fee caps on this fresh response before it calls a wallet write method. + + +Show the source amount, expected and minimum output, fees, recipient, and destination chain to the user before calling `swidge()`. A prior `quoteSwidge()` response is not bound to execution. + + +For an ERC-20 EVM route, `swidge()` can: + +1. Read the current allowance. +2. Approve the spender returned by Symbiosis when the allowance is insufficient. +3. Wait for the approval receipt when the wallet exposes receipt lookup. +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. | +| `ton` | Sends the messages returned by Symbiosis in order. | +| `btc` | Transfers the input amount to the generated deposit address. Configure a suitable refund address. | +| `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`. +- The provider does not consume an additional `minAmountOut` field if a newer WDK interface exposes one. Use `slippage` and inspect `toTokenAmountMin`; do not rely on an extra minimum-output field as an execution guard. +- Discovery responses are cached for ten minutes per protocol instance. The cache duration is not configurable. +- 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. +- The module does not implement request timeout or retry controls. Handle API failures and retry policy in the host application. +- The package documents the `/v2/swap` endpoint 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. Set `skipApproval: true` only when the application manages allowance separately. +- If the wallet does not expose transaction-receipt lookup, the module cannot wait for approval confirmation before submitting the route transaction. +- Symbiosis fees are currently mapped as `protocol` or `affiliate`, not `network`. A configured `maxNetworkFeeBps` does not constrain a separately reported network-fee item in this release. +- For TON routes with multiple messages, the result records the last sent message hash as the source transaction. Do not treat `result.transactions` as a complete list of every TON message. +- Quote-only construction without an account uses `recipient` as both the source sender and destination recipient. Bind a source account when those addresses differ or use different address formats. +- 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? + + diff --git a/content/docs/sdk/swidge-modules/swidge-symbiosis/usage.mdx b/content/docs/sdk/swidge-modules/swidge-symbiosis/usage.mdx new file mode 100644 index 00000000..d39f4b9e --- /dev/null +++ b/content/docs/sdk/swidge-modules/swidge-symbiosis/usage.mdx @@ -0,0 +1,249 @@ +--- +title: Symbiosis Swidge Usage +description: Install and use the released Symbiosis community Swidge provider with WDK wallet accounts. +docType: how-to +schemaType: TechArticle +icon: BookOpen +--- + + +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.1.1`. The EVM example below uses the WDK wallet version tested by that release: + +```bash +npm install @symbiosis-finance/wdk-protocol-swidge-symbiosis@1.1.1 @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' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://eth.drpc.org' +}) + +try { + const symbiosis = new SymbiosisProtocol(account, { + chain: 'Ethereum', + maxProtocolFeeBps: 100 + }) + + const chains = await symbiosis.getSupportedChains() + console.log(chains) + + // Continue with discovery and quoting inside this block. +} finally { + account.dispose() +} +``` + +Keep the account alive through quote review, execution, and source broadcast, then dispose it in `finally`. + +## 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. + +## 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 the Symbiosis quote endpoint and returns an indicative result. + +Only exact-input operations are supported. Pass `fromTokenAmount`; passing `toTokenAmount` throws `ExactOutNotSupportedError`. + + +Do not treat `quoteSwidge()` as a reserved or executable quote. `swidge()` requests a fresh execution response, so the output, fee breakdown, spender, transaction payload, or deposit address can change. + + +## 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; +- itemized fees; +- the selected slippage tolerance. + +The provider does not consume an additional `minAmountOut` field if a newer WDK interface exposes one. Use `slippage`, inspect `toTokenAmountMin`, and perform application-level review instead of relying on an extra minimum-output field as an execution guard. + +## Execute an EVM route + +Call `swidge()` only after confirmation: + +```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 the Symbiosis swap endpoint. +3. Check configured fee caps before a wallet write. +4. For a non-native EVM token, read allowance and approve the returned spender when needed. +5. Wait for the approval receipt when the account supports receipt lookup. +6. Broadcast the API-provided route transaction. +7. Return the source hash and operation ID without waiting for destination settlement. + +If allowance lookup fails, the module falls back to approval. 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 +}) +``` + +## Execute TON and Bitcoin source routes + +For TON, the module sends the messages returned by Symbiosis in order. If a route contains several messages, `result.hash` and the source entry in `result.transactions` represent the last sent message. + +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. Confirm the refund address, recipient, amount, and fresh execution response before the source payment. + +Tron and Solana source routes are quote-only in `1.1.1`; execution throws `UnsupportedRouteError`. + +## 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` for a newly broadcast source transaction. A genuinely unknown ID produces the same result, so enforce your own 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 typed 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 ask the user to review current 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) { + // Apply host-level timeout, retry, and rate-limit policy. + } else if (error instanceof SymbiosisError) { + // Handle another provider error. + } +} +``` + +The package does not add request timeouts or retries. Avoid retrying `swidge()` blindly after an uncertain wallet or network 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. + + diff --git a/content/docs/sdk/wallet-modules/index.mdx b/content/docs/sdk/wallet-modules/index.mdx index 91ee17b0..263a4708 100644 --- a/content/docs/sdk/wallet-modules/index.mdx +++ b/content/docs/sdk/wallet-modules/index.mdx @@ -74,7 +74,6 @@ Standard wallet implementations that use native blockchain tokens for transactio | [`@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) | -| [`@arkade-os/wdk`](https://github.com/arkade-os/arkade-wdk) | Arkade | ✅ Ready | [README](https://github.com/arkade-os/arkade-wdk#readme) | ## Account Abstraction Wallet Modules @@ -94,12 +93,15 @@ Wallet implementations that support [Account Abstraction](/resources/concepts#ac Wallet modules developed by the community. See the [Community Modules](/sdk/community-modules/) page for more details. -Community modules are developed and maintained independently. Use your own judgment and proceed at your own risk. +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 | 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/) | +| [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) | Bitcoin (RGB and Lightning) | RGB Lightning node, channel, payment, and asset integration | [Docs](/sdk/community-modules/wdk-rgb-lightning/) | | [`@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/) | diff --git a/content/docs/sdk/wallet-modules/wallet-solana-gasless/api-reference.mdx b/content/docs/sdk/wallet-modules/wallet-solana-gasless/api-reference.mdx index 2b187d49..3a60edc1 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana-gasless/api-reference.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana-gasless/api-reference.mdx @@ -7,7 +7,7 @@ icon: Code --- -This page documents the published `@tetherto/wdk-wallet-solana-gasless@1.0.0-beta.1` type declarations. +This page documents the published `@tetherto/wdk-wallet-solana-gasless@1.0.0-beta.2` type declarations and runtime behavior. ## Imports @@ -131,9 +131,9 @@ new WalletAccountSolanaGasless( | `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>` | +| `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 a paymaster-funded native transfer or transaction message. | `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` | @@ -165,7 +165,7 @@ signTransaction( ): Promise ``` -Signs a paymaster-funded transaction without broadcasting it. The module adds paymaster payment instructions, signs with the account owner, asks the paymaster to sign, and returns the fully signed transaction. +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. The method throws when the quoted paymaster fee is greater than `transactionMaxFee`. @@ -173,12 +173,12 @@ The method throws when the quoted paymaster fee is greater than `transactionMaxF ```typescript sendTransaction( - tx: SolanaTransaction, + tx: SolanaTransaction | FullySignedTransaction, config?: SolanaGaslessWalletPaymasterConfigOverrides ): Promise ``` -Sends a paymaster-funded native transfer or prebuilt transaction message. +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'`. ```javascript title="Send native SOL" const result = await account.sendTransaction({ @@ -192,7 +192,7 @@ console.log(result.hash) console.log(result.fee) ``` -The method throws when the quoted paymaster fee is greater than `transactionMaxFee`. +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. #### transfer @@ -221,12 +221,12 @@ The method throws when the quoted paymaster fee is greater than `transferMaxFee` ```typescript quoteSendTransaction( - tx: SolanaTransaction, + tx: SolanaTransaction | FullySignedTransaction, config?: SolanaGaslessWalletPaymasterConfigOverrides ): Promise> ``` -Quotes the paymaster fee for `sendTransaction()` or `signTransaction()` inputs. Quote methods return estimates and do not enforce `transactionMaxFee`. +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 @@ -346,7 +346,7 @@ Pass overrides as the second argument to quote, sign, send, or transfer methods. ## ConfigurationError -The beta.1 TypeScript declarations include `ConfigurationError`, and the runtime uses that error name for missing required paymaster fields. The beta.1 JavaScript root entrypoint does not re-export the class, so JavaScript code should not import `ConfigurationError` from `@tetherto/wdk-wallet-solana-gasless`. +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 class ConfigurationError extends Error { diff --git a/content/docs/sdk/wallet-modules/wallet-solana-gasless/configuration.mdx b/content/docs/sdk/wallet-modules/wallet-solana-gasless/configuration.mdx index c310c8ce..382e6fbd 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana-gasless/configuration.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana-gasless/configuration.mdx @@ -160,7 +160,7 @@ const result = await account.transfer({ ### transactionMaxFee -Maximum allowed paymaster fee for `sendTransaction()` and `signTransaction()` calls, in the configured paymaster token's base units. These methods throw when the quoted fee is greater than this cap. +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. **Type:** `number | bigint` @@ -194,7 +194,9 @@ const quote = await account.quoteTransfer({ }) ``` -In `1.0.0-beta.1`, 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. +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 diff --git a/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/get-started.mdx b/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/get-started.mdx index 2f014b81..09fe8d2f 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/get-started.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/get-started.mdx @@ -9,8 +9,8 @@ This guide creates a gasless Solana account, reads its address, and checks the c ## Install -```bash title="Install @tetherto/wdk-wallet-solana-gasless beta.1" -npm install @tetherto/wdk-wallet-solana-gasless@1.0.0-beta.1 +```bash title="Install @tetherto/wdk-wallet-solana-gasless beta.2" +npm install @tetherto/wdk-wallet-solana-gasless@1.0.0-beta.2 ``` ## Create a Wallet diff --git a/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/handle-errors.mdx b/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/handle-errors.mdx index f4d2d654..4844568e 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/handle-errors.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/handle-errors.mdx @@ -24,7 +24,7 @@ try { Required paymaster fields are `paymasterUrl`, `paymasterAddress`, and `paymasterToken`. -In `1.0.0-beta.1`, the JavaScript root entrypoint does not re-export the `ConfigurationError` class. Check `error.name` or `error.message` instead of importing the class from the package root. +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. ## Handle Transaction Fee Caps @@ -100,6 +100,31 @@ try { Use ordered `paymasterUrl` arrays and `retries` when you need endpoint failover. +## Handle Signed-Transaction Broadcasts + +`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: + +```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) +} +``` + + +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. + + ## Dispose of Sensitive Data Call `dispose()` on owned accounts and wallet managers when private keys are no longer needed: diff --git a/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/send-transactions.mdx b/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/send-transactions.mdx index 77e547b0..1f8f6359 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/send-transactions.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana-gasless/guides/send-transactions.mdx @@ -5,7 +5,7 @@ docType: how-to schemaType: TechArticle --- -This guide explains how to send native SOL, sign without broadcasting, quote paymaster fees, and use prebuilt transaction messages. +This guide explains how to send native SOL, sign without broadcasting, quote paymaster fees, send an already signed transaction, and use prebuilt transaction messages. Use `BigInt` values for token and lamport amounts to avoid precision loss. @@ -53,7 +53,7 @@ const result = await account.sendTransaction({ }) ``` -`quoteSendTransaction()` returns the estimated fee without enforcing `transactionMaxFee`. +`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 @@ -70,7 +70,36 @@ const signedTransaction = await account.signTransaction({ console.log('Signed transaction:', signedTransaction) ``` -The module signs with the owner account, asks the paymaster to sign, and returns a fully signed Solana transaction. It does not broadcast from `signTransaction()`. +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()`. + +## Quote and Send a Signed Transaction + +In `1.0.0-beta.2`, owned accounts accept the `FullySignedTransaction` returned by `signTransaction()` in both `quoteSendTransaction()` and `sendTransaction()`: + +```javascript title="Inspect then broadcast a signed transaction" +const signedTransaction = await account.signTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + transactionMaxFee: 500000n +}) + +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) +``` + +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. + +`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. + + +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 @@ -84,7 +113,7 @@ 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 fetches the latest blockhash. +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. @@ -121,6 +150,8 @@ const result = await account.sendTransaction({ }) ``` +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 SPL tokens instead of native SOL, see [Transfer SPL Tokens](/sdk/wallet-modules/wallet-solana-gasless/guides/transfer-tokens). diff --git a/content/docs/sdk/wallet-modules/wallet-solana-gasless/index.mdx b/content/docs/sdk/wallet-modules/wallet-solana-gasless/index.mdx index 67a247dc..ce6153c4 100644 --- a/content/docs/sdk/wallet-modules/wallet-solana-gasless/index.mdx +++ b/content/docs/sdk/wallet-modules/wallet-solana-gasless/index.mdx @@ -8,7 +8,7 @@ schemaType: TechArticle 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. -These pages document the published `@tetherto/wdk-wallet-solana-gasless@1.0.0-beta.1` package. Test your Solana RPC and Kora-compatible paymaster configuration on the target network before production use. +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. @@ -18,9 +18,10 @@ This module requires a Solana RPC endpoint and a Kora-compatible paymaster endpo ## Features - **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 one quote, sign, send, or transfer call; use `transactionMaxFee` for send/sign caps and `transferMaxFee` for transfer caps. +- **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. diff --git a/content/docs/sdk/wallet-modules/which-wallet-module.mdx b/content/docs/sdk/wallet-modules/which-wallet-module.mdx index e6a5cb4b..5df79061 100644 --- a/content/docs/sdk/wallet-modules/which-wallet-module.mdx +++ b/content/docs/sdk/wallet-modules/which-wallet-module.mdx @@ -13,22 +13,29 @@ WDK wallet modules are split by chain and account model. Use this chooser when y 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) | + +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. + + +| If you need | Start with | Ownership | +|---|---|---| +| Standard EVM accounts | [Standard EVM](/sdk/wallet-modules/wallet-evm) | Tether | +| EVM smart accounts with ERC-4337 | [Smart accounts (ERC-4337)](/sdk/wallet-modules/wallet-evm-erc-4337) | Tether | +| EVM EOA addresses with EIP-7702 delegation | [EIP-7702 accounts](/sdk/wallet-modules/wallet-evm-7702-gasless) | Tether | +| Bitcoin base-layer wallets | [Bitcoin](/sdk/wallet-modules/wallet-btc) | Tether | +| Lightning payments through Spark | [Lightning (Spark)](/sdk/wallet-modules/wallet-spark) | Tether | +| Standard TON wallets | [Standard TON](/sdk/wallet-modules/wallet-ton) | Tether | +| Gasless TON Jetton transfers | [Gasless TON](/sdk/wallet-modules/wallet-ton-gasless) | Tether | +| Standard TRON wallets | [Standard TRON](/sdk/wallet-modules/wallet-tron) | Tether | +| Gasfree TRON TRC20 transfers | [Gasfree TRON](/sdk/wallet-modules/wallet-tron-gasfree) | Tether | +| Standard Solana wallets | [Standard Solana](/sdk/wallet-modules/wallet-solana) | Tether | +| Gasless Solana transactions | [Gasless Solana](/sdk/wallet-modules/wallet-solana-gasless) | Tether | +| Aptos wallets | [Aptos](/sdk/wallet-modules/wallet-aptos) | Tether | +| On-chain RGB assets on Bitcoin | [On-chain RGB](/sdk/community-modules/wdk-wallet-rgb) | Community | +| RGB assets over Lightning | [RGB Lightning](/sdk/community-modules/wdk-rgb-lightning) | Community | +| Cosmos-compatible chains | [Cosmos](/sdk/community-modules/wdk-wallet-cosmos) | Community | ## Keep the reference list diff --git a/public/llms-full.txt b/public/llms-full.txt index 18e39979..8adfca90 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -55,8 +55,8 @@ Get started with WDK in a Node.js environment Build mobile wallets with React Native Expo - -Deploy WDK in lightweight environments + +Connect AI assistants to WDK docs and tools Explore our React Native UI Kit with pre-built components @@ -78,7 +78,7 @@ Description: Give any AI agent self-custodial wallet capabilities with WDK agent WDK provides agent skills: structured instruction sets that teach AI agents how to create wallets, send transactions, swap tokens, bridge assets, and interact with DeFi protocols across 20+ blockchains. All operations are self-custodial. Keys stay on your machine, with no third-party custody dependency. -**Skill vs MCP Toolkit**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](mcp-toolkit/) when your agent supports the Model Context Protocol natively (e.g., Claude, Cursor). Use both for maximum coverage. +**Skill vs MCP Toolkit**: Use an **agent skill** when your agent platform works with file-based instructions (e.g., OpenClaw, Cursor). Use the [MCP Toolkit](/ai/mcp-toolkit/) when your agent supports the Model Context Protocol natively (e.g., Claude, Cursor). Use both for maximum coverage. ## What Are Agent Skills? @@ -123,7 +123,7 @@ WDK's agent skills use a self-custodial model where your agent controls its own | Custody model | Self-custodial | Coinbase-hosted | Privy-hosted (server) | | Multi-chain | Yes (EVM, Bitcoin, Solana, TON, Tron, Spark + more) | EVM + Solana | EVM + Solana + Bitcoin + more | | Open source | Yes (SDK + skills) | CLI/skills open, infra closed | Skills open, API closed | -| MCP support | Yes ([MCP Toolkit](mcp-toolkit/)) | Via skills | Via skills | +| MCP support | Yes ([MCP Toolkit](/ai/mcp-toolkit/)) | Via skills | Via skills | | OpenClaw support | Yes ([ClawHub skill](https://clawhub.ai/HumanRupert/tether-wallet-development-kit)) | Yes (npx skills) | Yes (ClawHub skill) | | x402 payments | Via [community extensions](#community-projects) | Yes (native) | No | | Key management | Local / self-managed | Coinbase infrastructure | Privy infrastructure | @@ -132,10 +132,10 @@ WDK's agent skills use a self-custodial model where your agent controls its own | Platform | How to Use | | --- | --- | -| **OpenClaw** | Install from [ClawHub](openclaw) or clone to workspace. See [OpenClaw Integration](openclaw) | +| **OpenClaw** | Install from [ClawHub](/ai/openclaw/) or clone to workspace. See [OpenClaw Integration](/ai/openclaw/) | | **Claude** | Upload `SKILL.md` as project knowledge, or paste into conversation | | **Cursor / Windsurf** | Clone to `.cursor/skills/wdk` or `.windsurf/skills/wdk` | -| **Any MCP-compatible agent** | Use the [MCP Toolkit](mcp-toolkit/) for structured tool calling | +| **Any MCP-compatible agent** | Use the [MCP Toolkit](/ai/mcp-toolkit/) for structured tool calling | | **Any other agent** | Copy `SKILL.md` into system prompt or conversation context | ## Community Projects @@ -1289,8 +1289,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 }) ``` @@ -1379,7 +1378,7 @@ server.registerTools([getAddress, getBalance, getCurrentPrice]) ### Custom Tools -Add your own MCP tools alongside the built-in ones using the standard `registerTool()` method (inherited from `McpServer`). See the [MCP SDK tools documentation](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/server.md#tools) for full details. +Add your own MCP tools alongside the built-in ones using the standard `registerTool()` method (inherited from `McpServer`). See the [MCP SDK tools documentation](https://ts.sdk.modelcontextprotocol.io/v2/servers/tools) for full details. ```javascript server.registerTool( @@ -1418,7 +1417,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 | *** @@ -1449,7 +1447,7 @@ URL: https://docs.wdk.tether.io/ai/mcp-toolkit/get-started Description: Install the MCP Toolkit and run your first AI-powered wallet server -**Building a LangChain agent?** The `serve` command provides zero-config MCP server startup -- no server script needed. See [LangChain Integration](langchain). +**Building a LangChain agent?** The `serve` command provides zero-config MCP server startup -- no server script needed. See [LangChain Integration](/ai/mcp-toolkit/langchain/). ## Setup Wizard @@ -1706,7 +1704,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 }) @@ -1729,14 +1726,13 @@ 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` | *** ## Next Steps -* [**Configuration**](configuration) - Wallets, tokens, protocols, custom tools, and security -* [**API Reference**](api-reference) - All 35 built-in MCP tools with parameters and schemas +* [**Configuration**](/ai/mcp-toolkit/configuration/) - Wallets, tokens, protocols, custom tools, and security +* [**API Reference**](/ai/mcp-toolkit/api-reference/) - All 35 built-in MCP tools with parameters and schemas *** @@ -1759,7 +1755,7 @@ This approach uses LangChain's MCP adapters to connect to the WDK MCP server. WD -**Want more control?** The `serve` command is the fastest way to get running, but you can also [write your own MCP server](get-started#manual-setup) with the programmatic API for full control over wallets, tools, and protocols. Then point LangChain's `MultiServerMCPClient` at it using `node your-server.js` instead of the `serve` command. +**Want more control?** The `serve` command is the fastest way to get running, but you can also [write your own MCP server](/ai/mcp-toolkit/get-started/#manual-setup) with the programmatic API for full control over wallets, tools, and protocols. Then point LangChain's `MultiServerMCPClient` at it using `node your-server.js` instead of the `serve` command. *** @@ -1800,7 +1796,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). @@ -1930,8 +1926,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 @@ -2064,7 +2064,7 @@ Once the skill is loaded, your agent can: - **Lend and borrow** through Aave V3 - **Buy and sell crypto** via MoonPay fiat on/off-ramps -For the full list of capabilities and how skills work, see [Agent Skills](agent-skills). +For the full list of capabilities and how skills work, see [Agent Skills](/ai/agent-skills/). ## Security Risks and Safety Precautions @@ -2107,8 +2107,8 @@ OpenClaw makes use of artificial intelligence and machine learning technologies. ## Next Steps -- [Agent Skills](agent-skills) - Full capabilities, how skills work, and a comparison with other agentic wallet solutions -- [MCP Toolkit](mcp-toolkit/) - Programmatic wallet access for MCP-compatible agents +- [Agent Skills](/ai/agent-skills/) - Full capabilities, how skills work, and a comparison with other agentic wallet solutions +- [MCP Toolkit](/ai/mcp-toolkit/) - Programmatic wallet access for MCP-compatible agents - [OpenClaw Skills Documentation](https://docs.openclaw.ai/tools/skills) - How OpenClaw discovers and loads skills *** @@ -2964,11 +2964,13 @@ WDK natively supports a broad set of blockchains and standards out of the box: | [Bitcoin](/sdk/wallet-modules/wallet-btc/) | ✅ | | [Ethereum & EVM](/sdk/wallet-modules/wallet-evm/) | ✅ | | [Ethereum ERC-4337](/sdk/wallet-modules/wallet-evm-erc-4337/) | ✅ | +| [Ethereum EIP-7702 Gasless](/sdk/wallet-modules/wallet-evm-7702-gasless/) | ✅ | | [TON](/sdk/wallet-modules/wallet-ton/) | ✅ | | [TON Gasless](/sdk/wallet-modules/wallet-ton-gasless/) | ✅ | | [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/) | ✅ | @@ -2995,6 +2997,121 @@ Stay up to date with the latest improvements, new features, and bug fixes across --- +### July 29, 2026 + +**What's New** +- **rgb-lightning** ([v0.1.0-beta.15](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15)): Add the community-maintained RGB Lightning wallet and node module for BTC and RGB balances, peers and channels, Lightning and RGB invoices and payments, LSP and Lightning Address flows, APay, and VSS backup. The beta requires one platform-specific native peer, persistent node state, and an explicit unlock step before account operations. + +**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 the community-maintained Symbiosis Swidge provider for dynamic chain and token discovery, indicative cross-chain quotes, source-chain approval and execution, and status polling. Execution obtains a fresh route rather than consuming the earlier quote, so applications must reconfirm the current response and enforce their own output policy before broadcast. + +--- + +### 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. + +**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** @@ -3196,7 +3313,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. --- @@ -3639,6 +3756,7 @@ Stay up to date with the latest improvements, new features, and bug fixes across - Check this page for the latest updates - Join our [Discord community](https://discord.gg/arYXDhHB2w) for real-time announcements - Star and follow the [GitHub repositories](https://github.com/orgs/tetherto/repositories?q=wdk) for detailed release notes + *** ## Partner with WDK @@ -3913,7 +4031,7 @@ 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-erc-4337` - EVM chains with ERC-4337 standard -- `@tetherto/wdk-wallet-ton-gasless` - TON blockchain with gasless transactions +- `@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. @@ -4022,12 +4140,18 @@ Seed phrases and private keys are the foundation of wallet security in blockchai - **Recovery**: Can recover entire wallet from seed phrase - **Security**: Must be kept secure and never shared +### Seed Lifecycle + +WDK uses the seed provided by your app, but it does not decide where the seed is stored or when the seed should be cleared. Treat the seed as app-owned material: decrypt or load it only when needed, use WDK for the wallet session, call [`dispose()`](/sdk/core-module/guides/error-handling#seed-lifecycle), and clear your own seed buffer when the session ends. + +If your app needs explicit cleanup, pass seed bytes in a mutable `Uint8Array` where possible. JavaScript strings cannot be reliably zeroed. + ### Private Keys - **256-bit numbers**: Cryptographic keys that control wallet funds - **Derived from seed**: Generated deterministically from seed phrase - **Signing**: Used to sign transactions and prove ownership -- **Memory safety**: WDK modules handle private keys securely with automatic cleanup +- **Memory safety**: WDK modules can clear private keys they manage with [`dispose()`](/sdk/core-module/guides/error-handling#seed-lifecycle) ## Network Types @@ -4074,7 +4198,7 @@ The below faucets are for testnets. The USD₮ tokens and other tokens available ## All Modules URL: https://docs.wdk.tether.io/sdk/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. A comprehensive list of all available WDK modules. Each module is designed to be modular and can be used independently or combined with others. @@ -4097,21 +4221,33 @@ Wallet modules provide blockchain-specific wallet functionality for managing add | [`@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 | Cross-chain routes discovered and executed 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 through LI.FI | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | +| [`@moonpay/wdk-protocol-swidge-moonpay-trade`](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade) | MoonPay Trade | Community | Swidge routes through MoonPay Trade | [README](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade#readme) | +| [`@swapdk/wdk-protocol-swidge-swapdk`](https://www.npmjs.com/package/@swapdk/wdk-protocol-swidge-swapdk) | SwapDK | Community | Swidge routes through SwapDK | [README](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk#readme) | +| [`@gobob/wdk-protocol-swidge-gateway`](https://www.npmjs.com/package/@gobob/wdk-protocol-swidge-gateway) | BOB Gateway | Community | Swidge routes through the BOB Gateway | [README](https://github.com/bob-collective/wdk-protocol-swidge-gateway#readme) | ## Pricing Modules @@ -4142,9 +4278,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 @@ -4159,14 +4301,27 @@ 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/) | +| [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) | Wallet | RGB Lightning node and wallet integration | [Docs](/sdk/community-modules/wdk-rgb-lightning/) | +| [`@arkade-os/wdk`](https://github.com/arkade-os/arkade-wdk) | Wallet | 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) | 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 | Cross-chain 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 | Routes through LI.FI | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | +| [`@moonpay/wdk-protocol-swidge-moonpay-trade`](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade) | Swidge | Routes through MoonPay Trade | [README](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade#readme) | +| [`@swapdk/wdk-protocol-swidge-swapdk`](https://www.npmjs.com/package/@swapdk/wdk-protocol-swidge-swapdk) | Swidge | Routes through SwapDK | [README](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk#readme) | +| [`@gobob/wdk-protocol-swidge-gateway`](https://www.npmjs.com/package/@gobob/wdk-protocol-swidge-gateway) | Swidge | Routes through the BOB Gateway | [README](https://github.com/bob-collective/wdk-protocol-swidge-gateway#readme) | + *** ## Bridge Modules Overview @@ -4175,10 +4330,6 @@ Description: Explore WDK bridge modules for moving assets across supported chain The Wallet Development Kit (WDK) provides a set of modules that support bridging between different blockchain networks. All modules share a common interface, ensuring consistent behavior across different blockchain implementations. - -WDK is introducing the [swidge interface](/sdk/swidge-modules) as the preferred interface for new swap, bridge, and combined route providers. Existing bridge modules remain supported, but new protocol integrations should prefer swidge because the standalone bridge interface is expected to be deprecated in a future release once swidge provider coverage is available. - - ## Bridge Protocol Modules Cross-chain bridge functionality for token transfers between blockchains: @@ -4201,20 +4352,26 @@ 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 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. + *** -## Bridge USD₮0 EVM Overview +## Bridge tokens with USD₮0 URL: https://docs.wdk.tether.io/sdk/bridge-modules/bridge-usdt0-evm -Description: Overview of the @tetherto/wdk-protocol-bridge-usdt0-evm module +Description: Bridge USD₮0 and XAU₮0 across supported EVM and non-EVM destinations from WDK accounts. -A simple package that lets EVM wallet accounts bridge USD₮0 tokens across different blockchains. This package provides a clean API for moving tokens between chains using the LayerZero protocol and USD₮0 bridge system. +Use the USD₮0 bridge module to move USD₮0 and XAU₮0 across supported chains from WDK EVM wallet accounts. ## Features - **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 @@ -4262,10 +4419,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 @@ -4315,7 +4495,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 @@ -4327,7 +4507,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 }) ``` @@ -4335,13 +4515,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 @@ -4351,8 +4533,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 @@ -4360,18 +4541,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 @@ -4380,47 +4563,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', @@ -4429,11 +4606,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 @@ -4453,6 +4630,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 @@ -4473,8 +4679,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 } ``` @@ -4482,17 +4688,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 } ``` -### EvmErc4337WalletConfig +```typescript +type Erc4337QuoteConfig = Partial< + | EvmErc4337WalletPaymasterTokenConfig + | EvmErc4337WalletSponsorshipPolicyConfig + | EvmErc4337WalletNativeCoinsConfig +> + +type Erc4337BridgeConfig = Erc4337QuoteConfig & { + bridgeMaxFee?: number | bigint +} +``` + +### 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 } ``` @@ -4501,13 +4750,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) @@ -4524,12 +4773,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: @@ -4583,9 +4834,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({ @@ -4609,7 +4858,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) @@ -4686,28 +4935,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 } ``` @@ -4751,7 +4992,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 }) ``` @@ -4790,16 +5031,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, } @@ -4844,37 +5090,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. @@ -4885,18 +5134,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 @@ -4927,7 +5170,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 = { @@ -4991,7 +5234,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 { @@ -5049,9 +5292,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. + +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 `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. +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 @@ -5150,7 +5404,7 @@ Harden integrations with [Handle errors](/sdk/bridge-modules/bridge-usdt0-evm/gu URL: https://docs.wdk.tether.io/sdk/bridge-modules/bridge-usdt0-evm/guides/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 @@ -5184,8 +5438,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') ``` @@ -5204,8 +5458,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). @@ -5253,7 +5507,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') ``` @@ -5262,7 +5516,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' @@ -5273,7 +5527,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 @@ -5292,6 +5546,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 @@ -5318,27 +5573,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', @@ -5348,18 +5595,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. @@ -5411,7 +5661,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' @@ -5421,6 +5671,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). @@ -5456,7 +5708,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** @@ -5480,11 +5732,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 { @@ -5617,11 +5869,17 @@ Tether and the WDK Team do not endorse or assume responsibility for their code, | Module | Type | Description | Documentation | Author | |--------|------|-------------|---------------|--------| | [@utexo/wdk-wallet-rgb](https://www.npmjs.com/package/@utexo/wdk-wallet-rgb) ([GitHub](https://github.com/UTEXO-Protocol/wdk-wallet-rgb)) | Wallet Module | Wallet module for RGB, Bitcoin-based smart contracts | [Docs](/sdk/community-modules/wdk-wallet-rgb/) | [UTEXO](https://github.com/UTEXO-Protocol) | +| [@utexo/wdk-rgb-lightning](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) ([GitHub](https://github.com/UTEXO-Protocol/wdk-rgb-lightning)) | Wallet Module | RGB Lightning node, channel, payment, and asset integration | [Docs](/sdk/community-modules/wdk-rgb-lightning/) | [UTEXO](https://github.com/UTEXO-Protocol) | | [@arkade-os/wdk](https://www.npmjs.com/package/@arkade-os/wdk) ([GitHub](https://github.com/arkade-os/arkade-wdk)) | Wallet Module | Bitcoin wallet module built on the Arkade SDK with Arkade addresses, boarding addresses, BIP21/LNURL/Lightning send routing, and optional Boltz swaps | [README](https://github.com/arkade-os/arkade-wdk#readme) | [Arkade](https://github.com/arkade-os) | | [@base58-io/wdk-wallet-cosmos](https://www.npmjs.com/package/@base58-io/wdk-wallet-cosmos) ([GitHub](https://github.com/base58-io/wdk-wallet-cosmos)) | Wallet Module | Wallet module for Cosmos-compatible blockchains | [Docs](/sdk/community-modules/wdk-wallet-cosmos/) | [Base58](https://base58.io/) | | [@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/) | +| [@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 | Cross-chain routes discovered and executed through the Symbiosis API | [Docs](/sdk/swidge-modules/swidge-symbiosis/) | [Symbiosis](https://symbiosis.finance/) | +| [@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 | Routes through LI.FI | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | [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/) | --- @@ -5667,5518 +5925,5693 @@ Community modules should: - Provide clear installation and usage instructions - Be open source or publicly accessible - Include appropriate tests and examples + *** -## WDK Core -URL: https://docs.wdk.tether.io/sdk/core-module -Description: Overview of the @tetherto/wdk module +## RGB Lightning wallet +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning +Description: Run a community-maintained RGB-over-Lightning wallet with LDK channels, invoices, payments, VSS, and LSP flows. -This package serves as the main entry point and **orchestrator for all WDK wallet and protocol modules**, allowing you to register and manage different blockchain wallets through a single, unified interface. +`@utexo/wdk-rgb-lightning` is a community-maintained WDK wallet module that runs an LDK and `rgb-lib` node behind the WDK manager/account interface. -## Next Steps +These pages describe the released [`@utexo/wdk-rgb-lightning@0.1.0-beta.15`](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15). + + +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 is a pre-1.0 beta. Its native payloads, peer compatibility, LSP behavior, and operational recovery model can change between beta releases. Pin exact versions and test the full lifecycle on the target host. + + +## Choose the correct RGB module + +| Requirement | Module | +|---|---| +| Channels, BOLT11 payments, RGB invoices and transfers, Lightning Address, APay, or VSS | `@utexo/wdk-rgb-lightning` | +| On-chain NIA issuance and an independent on-chain RGB wallet | [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb) | +| Bitcoin without RGB or Lightning node state | [`@tetherto/wdk-wallet-btc`](/sdk/wallet-modules/wallet-btc) | + +The two UTEXO RGB modules derive different wallet identities and own different `rgb-lib` databases. They do not share asset records. Give each a separate persistent `dataDir`. + +## Runtime architecture + +The package uses conditional exports: + +| Host | Required optional peer | Released native artifacts verified for the peer | +|---|---|---| +| Node.js 18 or newer | `@utexo/rgb-lightning-node-nodejs` in `>=0.1.0-beta.10 <0.2.0` | macOS arm64/x64; Linux arm64 GNU; Linux x64 GNU/musl | +| Bare / mobile worklet | `@utexo/rgb-lightning-node-bare` in `>=0.1.0-beta.14 <0.2.0` | Android arm/arm64/x64; macOS arm64; iOS arm64 and arm64/x64 simulators | + +The verified Node peer release was `0.1.0-beta.11`; the verified Bare peer release was `0.1.0-beta.15`. No Windows native artifact was published in this release set. + +Each native peer downloads a prebuilt artifact during installation. Validate artifact provenance, platform selection, and native loading in the deployment pipeline. + +## Security and state model + +- The WDK manager retains the BIP-39 secret boundary. +- An in-process VLS external signer handles channel-state cryptography. +- `keyPair.privateKey` is always `null`; the account does not expose signer private bytes. +- RLN persists public node identity plus LDK and RGB state under `dataDir`. +- Optional VSS payloads are encrypted client-side and still require the original seed for recovery. +- `manager.dispose()` owns node shutdown, signer destruction, and retained-seed cleanup. + +The account is single-node and single-account: index `0`, path `m`. + +## Capabilities and boundaries + +- Explicit unlock against Bitcoin RPC, indexer, and RGB proxy services. +- Peer connections, channels, BOLT11 invoices, HODL invoices, payments, and keysend. +- Bitcoin balances, transactions, UTXOs, sends, and fee estimates. +- RGB balances, invoices, transfers, media, and channel-aware payments. +- LSP client, Lightning Address/LNURL-pay helpers, composed UTEXO LSP flows, and APay. +- Optional VSS backup and recovery fencing. +- Message signing and verification through the Lightning node identity. +- Query-only account adapter for least-authority reads. + +Runtime JavaScript contains RGB issuance forwarders, but beta.15's public declarations omit them. These docs do not present them as supported public account APIs. Use the on-chain RGB module for issuance. Atomic-swap methods remain native-binding-only and are also outside the WDK account surface. + +## Start building - -Get started with WDK in a Node.js environment + +Install the matching native peer, construct the node, and unlock the account. - -Get started with WDK's configuration + +Open the task-focused guide catalog. - -Get started with WDK's API + +Configure persistent state, signer policy, VSS, LSP, and native unlock services. - -Get started with WDK's usage + +Review the released beta.15 manager, account, error, and LSP surface. + + +Inspect the exact released declarations and implementation. *** -## Need Help? +## RGB Lightning wallet API reference +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/api-reference +Description: Public API reference for @utexo/wdk-rgb-lightning 0.1.0-beta.15. - +This page covers the package-root declarations and released runtime behavior of [`@utexo/wdk-rgb-lightning@0.1.0-beta.15`](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15). -*** + +Community modules are developed and maintained independently by third-party contributors. -## WDK Core API Reference -URL: https://docs.wdk.tether.io/sdk/core-module/api-reference -Description: Complete API documentation for @tetherto/wdk +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. + -## Table of Contents +## Package -| Class | Description | Methods | -|-------|-------------|---------| -| [WDK](#wdk) | Main class for managing wallets across multiple blockchains. Orchestrates wallet managers and protocols. | [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) | +| Field | Value | +|---|---| +| Package | `@utexo/wdk-rgb-lightning@0.1.0-beta.15` | +| Repository | [UTEXO-Protocol/wdk-rgb-lightning](https://github.com/UTEXO-Protocol/wdk-rgb-lightning) | +| Module format | ESM | +| Node/default entry | `index.js` → `index-node.js` | +| Bare entry | `bare.js` → `index-bare.js` | +| Declarations | `index.d.ts` | -## WDK +Install the matching optional native peer. Node and Bare expose the same JavaScript API but load different native bindings. -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. +## Root exports -### Constructor +| Group | Exports | +|---|---| +| Manager and accounts | Default `WalletManagerRgbLightning`, `WalletAccountRgbLightning`, `WalletAccountReadOnlyRgbLightning` | +| Low-level bindings | `NodeRgbLightningBinding`, `BareRgbLightningBinding`, `IRgbLightningBinding` type | +| Wallet errors | `RgbLightningError`, `UnlockError`, `AccountLockedError`, `VssError`, `VssNotConfiguredError`, `ApayError`, `NotImplementedError` | +| LSP | `LspClient`, `LspError`, `UtexoLsp`, LSP result/config types, timeout and settlement errors | +| LNURL / address | `isUmaAddress`, `normalizeLightningAddress`, `parseLightningAddress`, `fetchDiscovery`, `resolveAddressToInvoice`, `LnurlPayError` | +| Account-bound helpers | `payLightningAddress`, `requestLspRgbDeposit`, `payRgbViaLsp` | -```javascript title="Constructor" -new WDK(seed) -``` +Low-level binding classes are advanced escape hatches. Prefer the manager because it owns external-signer attachment, fallback identity handling, shutdown, and secret cleanup. -**Parameters:** -- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes +## `WalletManagerRgbLightning` -**Example:** -```javascript title="Initialize WDK" -import WDK from '@tetherto/wdk' +| Member | Returns | Behavior | +|---|---|---| +| `constructor(seed, config)` | Manager | Requires BIP-39 mnemonic or seed bytes, `network`, and persistent `dataDir`. | +| `getAccount(index = 0)` | `Promise` | Returns the only account. Nonzero indexes and registered WDK signer names are rejected. | +| `getAccountByPath(path)` | `Promise` | Accepts only `m`. | +| `getFeeRates()` | `Promise` | Fetches mempool.space recommendations without selecting the configured network. | +| `dispose()` | `void` | Terminal manager-owned binding shutdown, signer destruction, seed cleanup, and account invalidation. | +| `static Binding` | Binding constructor | Runtime-selected Node or Bare binding. | -// With seed phrase -const wdk = new WDK('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about') +## `WalletAccountReadOnlyRgbLightning` -// With seed bytes -const seedBytes = new Uint8Array([...]) -const wdk2 = new WDK(seedBytes) -``` +The read-only adapter exposes queries without signing, broadcasting, channel mutation, VSS recovery, or LSP credentials. -### Methods +| Group | Methods | +|---|---| +| Bootstrap and node | `getBootstrap()`, `getNodeInfo()`, `getNetworkInfo()` | +| Address | `getAddress()`, `getAddressState()` | +| Channels and peers | `listChannels()`, `getChannelId(tempId)`, `listPeers()` | +| Lightning | `decodeInvoice()`, `getInvoiceStatus()`, `listPayments()`, `getPayment(hash, type)` | +| RGB | `listAssets(filter?)`, `getAssetBalance()`, `getAssetMetadata()`, `listTransfers()`, `listTransfersByTxid()`, `decodeRgbInvoice()`, `getAssetMedia()` | +| Bitcoin | `getBalance(skipSync?)`, `getBalanceDetails(skipSync?)`, `getTransactions(skipSync?)`, `getTransactionsByTxid()`, `listUnspents()`, `estimateFee()` | +| WDK | `getTokenBalance()`, `verify()`, `quoteTransfer()`, `quoteSendTransaction()`, `getTransactionReceipt()` | +| Diagnostics | `checkIndexerUrl()`, `checkProxyEndpoint()`, `vssStatus()` | -| Method | Description | Returns | Throws | -|--------|-------------|---------|--------| -| `registerWallet(blockchain, wallet, config)` | Registers a new wallet manager for a blockchain | `WDK` | - | -| `registerProtocol(blockchain, label, protocol, config)` | Registers a protocol globally for a blockchain | `WDK` | - | -| `registerMiddleware(blockchain, middleware)` | Registers middleware for account decoration | `WDK` | - | -| `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()` | Returns current fee rates | `Promise` | - | -| `dispose(blockchains?)` | Disposes all registered wallets, or only the named blockchains, and clears their sensitive data | `void` | - | +`getAddress()` throws `AccountLockedError` before unlock. `getAddressState()` returns `{status:'locked', address:null}` without throwing. -##### `registerWallet(blockchain, wallet, config)` -Registers a new wallet manager for a specific blockchain. + +Before unlock, `getBalance()` returns `0n`. Use `getAddressState()` to distinguish a locked account from a ready account with a real zero balance. + -**Type Parameters:** -- `W`: `typeof WalletManager` - A class that extends the `@tetherto/wdk-wallet`'s `WalletManager` class +`getTransactionReceipt()` returns only terminal confirmed Bitcoin, settled RGB, or non-pending Lightning records; otherwise it returns `null`. -**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 +## `WalletAccountRgbLightning` -**Returns:** `WDK` - The wdk manager instance (supports method chaining) +The full account has fixed identity fields: -**Example:** -```javascript title="Register Wallets" -import WDK from '@tetherto/wdk' -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerTon from '@tetherto/wdk-wallet-ton' +| Member | Value | +|---|---| +| `index` | `0` | +| `path` | `m` | +| `keyPair.publicKey` | 33-byte compressed Lightning node public key | +| `keyPair.privateKey` | Always `null`; VLS holds signing material | -const wdk = new WDK(seedPhrase) +### Lifecycle and node -// Register EVM wallet -wdk.registerWallet('ethereum', WalletManagerEvm, { - provider: 'https://eth.drpc.org' -}) +| Method | Behavior | +|---|---| +| `unlock(nativeRequest)` | Unlocks the node with native snake_case RPC, indexer, proxy, and announce fields; wraps failures as `UnlockError`. | +| `getBootstrap()` | Returns public signer/bootstrap metadata. | +| `getNodeInfo()` / `getNetworkInfo()` | Query node and chain information. | +| `sync()` | Synchronizes node state. | +| `getAddress()` / `getAddressState()` | Read current stable address or lock state. | +| `rotateAddress()` | Explicitly advances the Bitcoin address. | +| `shutdown()` | Idempotently shuts down the account binding. | +| `dispose()` | Account no-op; the manager owns terminal cleanup. | + +### Peers and channels + +| Method | Behavior | +|---|---| +| `connectPeer(pubkeyAndAddress)` | Connects a `pubkey@host:port` peer. | +| `disconnectPeer(request)` | Forwards the native disconnect request. | +| `listPeers()` | Returns native peer records. | +| `openChannel(request)` | Accepts `OpenChannelRequest` or a native object. | +| `closeChannel(request)` | Forwards the native close request. | +| `listChannels()` | Returns native channel records. | +| `getChannelId(temporaryId)` | Resolves a temporary channel ID. | -// Register TON wallet -wdk.registerWallet('ton', WalletManagerTon, { - tonApiKey: 'YOUR_TON_API_KEY', - tonApiEndpoint: 'https://tonapi.io' -}) +### Invoices and payments -// Method chaining -const wdk2 = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) - .registerWallet('ton', WalletManagerTon, tonWalletConfig) -``` +| Method | Behavior | +|---|---| +| `createInvoice(request)` | Native BOLT11 invoice request. | +| `createLightningInvoice(request)` | Accepts native snake_case or released camelCase convenience fields. | +| `decodeInvoice(invoice)` / `getInvoiceStatus(invoice)` | Query invoice data and status. | +| `createHodlInvoice(params)` | Creates a HODL invoice for a caller-supplied payment hash. | +| `cancelHodlInvoice(request)` / `claimHodlInvoice(request)` | Native HODL lifecycle calls. | +| `sendPayment(request)` / `keysend(request)` | Native Lightning payment calls. | +| `listPayments()` / `getPayment(hash, type)` | Payment history. `type` is `Outbound`, `InboundAutoClaim`, or `InboundHodl`. | -##### `registerProtocol(blockchain, label, protocol, config)` -Registers a protocol globally for all accounts of a specific blockchain. +### RGB assets -**Type Parameters:** -- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocol`'s classes +| Method | Behavior | +|---|---| +| `listAssets(filter?)`, `getAssetBalance()`, `getAssetMetadata()` | Query held RGB assets. | +| `listTransfers(assetId)`, `listTransfersByTxid(txid)` | Query transfer records. | +| `refreshTransfers(request)`, `failTransfers(request)` | Native transfer-state mutation. | +| `createRgbInvoice(request)`, `decodeRgbInvoice(invoice)` | Create or decode an RGB receive invoice. | +| `sendRgbAsset(request)` | Sends native grouped RGB recipients. | +| `getAssetMedia(digest)`, `postAssetMedia(request)` | Reads or uploads asset media. | -**Parameters:** -- `blockchain` (string): The name of the blockchain -- `label` (string): Unique label for the protocol (must be unique per blockchain and protocol type) -- `protocol` (P): The protocol class -- `config` (`ConstructorParameters

[1]`): The protocol configuration +Runtime JavaScript includes issuance forwarders that beta.15's public declaration omits. They are intentionally excluded here. Use [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb) for released, documented issuance. -**Returns:** `WDK` - The wdk manager instance (supports method chaining) +### Bitcoin and WDK operations -**Example:** -```javascript title="Register Protocols" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +| Method | Behavior | +|---|---| +| `sendTransaction({to, value, feeRate?, confirmationTarget?})` | WDK Bitcoin send; VLS signs internally and returns `{hash, fee}`. | +| `sendBtc(request)` | Low-level native Bitcoin send. | +| `createUtxos(request)` | Native UTXO-creation request. | +| `quoteSendTransaction(tx)` | Approximate standard-send quote based on 141 vbytes. | +| `transfer(options)` | Routes BOLT11, node ID, Bitcoin address, or RGB invoice recipients. | +| `quoteTransfer(options)` | Routes to an approximate flow-specific quote. | +| `sign(message)` / `verify(message, signature)` | Lightning message signing and verification. | +| `signTransaction()` | Always throws `NotImplementedError`; use operation-specific send methods. | +| `toReadOnlyAccount()` | Returns the cached query-only adapter. | -// Register swap protocol for Ethereum -wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: 'YOUR_velora_API_KEY' -}) +## Transfer routing and units -// Register bridge protocol for Ethereum -wdk.registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) +`transfer(options)` classifies `recipient`: -// Method chaining -const wdk2 = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) -``` +| Recipient | Route | `amount` unit | Returned `fee` unit | +|---|---|---|---| +| BOLT11 invoice | `sendPayment` | millisatoshis | millisatoshis | +| 66-character hex node ID | `keysend` | millisatoshis | millisatoshis | +| `rgb:` or `utxob:` invoice | RGB send | RGB asset base units | `0n` because the native fee is not exposed | +| Other valid recipient | Bitcoin send | satoshis | satoshis | -##### `registerMiddleware(blockchain, middleware)` -Registers middleware for account decoration and enhanced functionality. +For RGB routing, `token` is the asset ID. The generic router assumes `Fungible`, `donation: false`, and one confirmation. Use `sendRgbAsset()` with the exact native request for other assignment kinds or grouped recipients. -**Parameters:** -- `blockchain` (string): The name of the blockchain -- `middleware` (`(account: A) => Promise`): Middleware function called when deriving accounts + +An RGB transfer result of `fee: 0n` does not prove the operation was fee-free. It means beta.15 does not expose the native fee through the WDK result. + -**Returns:** `WDK` - The wdk manager instance (supports method chaining) +The Lightning quote uses a 50-basis-point allowance, not a live route fee. An RGB-routed HTLC has a hard minimum of 3,000,000 msat. -**Example:** -```javascript title="Register Middleware" -// Simple logging middleware -wdk.registerMiddleware('ethereum', async (account) => { - console.log('New account:', await account.getAddress()) -}) +## LSP, Lightning Address, and VSS -// Failover cascade middleware -import { getFailoverCascadeMiddleware } from '@tetherto/wdk-wrapper-failover-cascade' +| Surface | Key methods | +|---|---| +| `LspClient` | `health`, `getInfo`, LNURL discovery/callback, address resolution, on-chain send bridge, Lightning receive bridge | +| `UtexoLsp` | `connect`, `waitForChannel`, `receiveAsset`, settlement/liquidity waits, `sendAsset`, `payAddress`, `enableLightningAddress`, `claimPendingPayments` | +| APay | `apayNew`, `bootstrapLsp`, `getLspConfig`, `createLsp` | +| VSS | `vssStatus`, `vssBackup`, `clearVssFence` | -wdk.registerMiddleware('ethereum', getFailoverCascadeMiddleware({ - fallbackOptions: { - retries: 3, - delay: 1000 - } -})) +Public HTTP is rejected for LSP and VSS by default, except loopback where supported. LNURL callbacks remain on the discovery host unless `allowCrossHostCallback` is explicitly enabled. `$user@host` is normalized as UMA-style address syntax only; the package does not implement UMA signing, compliance, or currency negotiation. -// Method chaining -const wdk2 = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) - .registerMiddleware('ethereum', async (account) => { - console.log('New account:', await account.getAddress()) - }) -``` +## Errors -##### `getAccount(blockchain, index?)` -Returns a wallet account for a specific blockchain and index using BIP-44 derivation. +Selected boundaries use typed errors: -**Parameters:** -- `blockchain` (string): The name of the blockchain (e.g., "ethereum") -- `index` (number, optional): The index of the account to get (default: 0) +| Error | Boundary | +|---|---| +| `UnlockError` | Node unlock | +| `AccountLockedError` | Locked address/signature operations | +| `VssError`, `VssNotConfiguredError` | VSS operations | +| `ApayError` | APay/bootstrap | +| `NotImplementedError` | Unsupported transaction signing | +| `LspError`, `LnurlPayError` | HTTP LSP and LNURL helpers | +| `LspChannelTimeoutError`, `LspLiquidityTimeoutError`, `LspSettlementError` | Composed LSP waits | -**Returns:** `Promise` - The wallet account with protocol support +Many native methods still throw raw `Rln(): ` errors. Preserve the original error and operation context. -**Throws:** Error if no wallet has been registered for the given blockchain +## Declaration boundary -**Example:** -```javascript title="Get Account" -// Get first account (index 0) -const account = await wdk.getAccount('ethereum', 0) +Many native requests and responses are deliberately typed as `object` or `Record` because RLN owns their shape. Do not invent stable fields from demos, another beta, or an unreleased branch. Validate the exact beta.15 payloads your application consumes. -// Get second account (index 1) -const account1 = await wdk.getAccount('ethereum', 1) +Atomic swap methods are available only on the native binding, not on the released WDK account. They are outside this reference. -// Default index (0) -const defaultAccount = await wdk.getAccount('ethereum') +## Guides -// 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') -} -``` +- [Configure the node](/sdk/community-modules/wdk-rgb-lightning/configuration) +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Use peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) -##### `getAccountByPath(blockchain, path)` -Returns a wallet account for a specific blockchain and BIP-44 derivation path. +*** -**Parameters:** -- `blockchain` (string): The name of the blockchain (e.g., "ethereum") -- `path` (string): The derivation path (e.g., "0'/0/0") +## RGB Lightning wallet configuration +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/configuration +Description: Configure the beta.15 node, persistent state, native unlock services, VSS, LSP, and signer policy. -**Returns:** `Promise` - The wallet account with protocol support +RGB Lightning has two configuration phases: construct the node with stable local settings, then unlock it with live Bitcoin and RGB service settings. -**Throws:** Error if no wallet has been registered for the given blockchain + +Community modules are developed and maintained independently by third-party contributors. -**Example:** -```javascript title="Get Account by Path" -// Full path: m/44'/60'/0'/0/1 -const account = await wdk.getAccountByPath('ethereum', "0'/0/1") +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. + -// Different derivation path -const customAccount = await wdk.getAccountByPath('ton', "1'/2/3") +## Constructor configuration + +```js +import WalletManagerRgbLightning from '@utexo/wdk-rgb-lightning' + +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-lightning', + daemonListeningPort: 0, + ldkPeerListeningPort: 0, + maxMediaUploadSizeMb: 5, + permissiveSignerPolicy: true, + nodeSeedDerivation: 'auto', +}) ``` -##### `getFeeRates()` -Returns current fee rates for all registered blockchains. +## Consumed constructor fields -**Returns:** `Promise` - The fee rates in base units +| Field | Type | Default | Behavior | +|---|---|---|---| +| `network` | `'mainnet' \| 'testnet' \| 'regtest' \| 'signet'` | None | Required. | +| `dataDir` | `string` | None | Required persistent, app-private RLN/LDK/RGB state path. | +| `daemonListeningPort` | `number` | `0` | RLN daemon port; `0` selects an ephemeral port. | +| `ldkPeerListeningPort` | `number` | `0` | LDK peer port; `0` selects an ephemeral port. | +| `maxMediaUploadSizeMb` | `number` | `5` | Maximum RGB media upload size. | +| `enableVirtualChannelsV0` | `boolean` | `false` | Enables trusted non-broadcast virtual channels; required for production APay. | +| `virtualPeerPubkeys` | `string[]` | None | Node-ID allowlist for virtual channel peers. | +| `permissiveSignerPolicy` | `boolean` | `true` | Relaxes VLS policy checks for the in-process single-user integration. | +| `nodeSeedDerivation` | `'auto' \| 'wdk-seed-v2' \| 'legacy-v1'` | `'auto'` | Selects corrected or legacy beta node-identity derivation. | +| `vssUrl` | `string` | None | Enables remote encrypted VSS snapshots. | +| `vssAllowHttp` | `boolean` | `false` | Allows non-HTTPS VSS; use only for explicitly accepted development risk. | +| `vssAllowEmptyRestore` | `boolean` | `false` | Allows startup without remote state when the VSS store is empty. | +| `lspBaseUrl` | `string` | None | LSP base URL used by APay and no-argument `createLsp()`. | +| `lspBearerToken` | `string` | None | Optional bearer credential for internal LSP endpoints. | -**Example:** -```javascript title="Get Fee Rates" -const feeRates = await wdk.getFeeRates() -console.log('Fee rates:', feeRates) +Assess `permissiveSignerPolicy: true` against your threat model. Tightening it can reject operations the integration expects, so test policy changes with real channel lifecycle flows. + +## Native unlock request + +After `getAccount(0)`, pass live services to `unlock()` in native snake_case: + +```js +const account = await manager.getAccount(0) + +await account.unlock({ + bitcoind_rpc_username: 'rpc-user', + bitcoind_rpc_password: await loadRpcPassword(), + bitcoind_rpc_host: '127.0.0.1', + bitcoind_rpc_port: 18443, + indexer_url: 'tcp://127.0.0.1:50001', + proxy_endpoint: 'rpc://127.0.0.1:3000/json-rpc', + announce_addresses: [], + announce_alias: 'example-node', +}) ``` -##### `dispose(blockchains?)` -Disposes all registered wallets when called without arguments, or only the wallets for the named blockchains when you pass a string array. +Do not log the unlock request. It can contain RPC credentials and network-identifying values. -**Parameters:** -- `blockchains` (string[], optional): The blockchain identifiers to dispose. Omit this parameter to dispose every registered wallet. + +The beta.15 `RgbLightningWalletConfig` declaration also lists camelCase Bitcoin RPC, indexer, proxy, and announce fields. `WalletManagerRgbLightning` does not consume or forward those constructor fields. Pass the corresponding native snake_case values to `account.unlock()` instead. + -**Example:** -```javascript title="Dispose WDK" -// Clean up all sensitive data -wdk.dispose() +`proxyEndpoint` in constructor config is also not forwarded to native binding configuration. Well-formed RGB invoices carry consignment transport endpoints; explicit native operations should use their released request shapes. -// Dispose only one registered wallet -wdk.dispose(['ethereum']) +## Persistent state and wallet identity + +Use a different `dataDir` from [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb): + +```text +/app-private/wdk/rgb-onchain +/app-private/wdk/rgb-lightning ``` -### Static Methods +The two modules derive different wallet fingerprints and do not share RGB records. A shared seed and matching asset ID do not create a shared balance. -| 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` | +Only one live owner should use a Lightning `dataDir` or VSS namespace. LDK state has strict consistency and anti-rollback requirements. -##### `getRandomSeedPhrase(wordCount?)` -Returns a random BIP-39 seed phrase. Supports both 12-word (128-bit entropy) and 24-word (256-bit entropy) seed phrases. +## Node seed derivation -**Parameters:** -- `wordCount` (12 | 24, optional): The number of words in the seed phrase. Defaults to 12. +| Value | Use | +|---|---| +| `auto` | Corrected WDK seed derivation for new nodes; retries the beta.14-and-earlier legacy identity only on an exact persisted signer-identity mismatch. | +| `wdk-seed-v2` | Always use corrected derivation. | +| `legacy-v1` | Always use legacy beta derivation for an already-persisted compatible node. | -**Returns:** `string` - The seed phrase +Do not switch derivation modes casually. A changed node identity can make persisted channel state unusable. Back up and test the exact upgrade path before changing a funded node. -**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" +## VSS -// 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" +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + vssUrl: 'https://vss.example.com', +}) ``` -##### `isValidSeedPhrase(seedPhrase)` -Checks if a seed phrase is valid according to BIP-39 standards. +Non-loopback HTTP is rejected unless `vssAllowHttp` is true. The original seed is required to decrypt recovery state. `vssStatus()` is a local configuration view, not a VSS server health check. -**Parameters:** -- `seedPhrase` (string): The seed phrase to validate +## LSP and APay -**Returns:** `boolean` - True if the seed phrase is valid +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + lspBaseUrl: 'https://lsp.example.com', + lspBearerToken: await loadLspToken(), + enableVirtualChannelsV0: true, + virtualPeerPubkeys: [lspNodeId], +}) +``` -**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 +Production APay requires both virtual-channel fields and an LSP that trusts the wallet node. Treat `lspBearerToken` as a secret. The standalone `LspClient` rejects public HTTP by default. -const isInvalid = WDK.isValidSeedPhrase('invalid seed phrase') -console.log('Seed phrase valid:', isInvalid) // false +## Runtime binding + +Install exactly one optional native peer for the host: + +```bash +# Node host +npm install @utexo/rgb-lightning-node-nodejs@0.1.0-beta.11 + +# Bare/mobile host +npm install @utexo/rgb-lightning-node-bare@0.1.0-beta.15 ``` -## IWalletAccount +The declared compatible ranges are wider than these verified versions. Pin and validate a known pair rather than allowing an unreviewed beta upgrade. -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()`. +## Next steps -### Methods +- [Get started](/sdk/community-modules/wdk-rgb-lightning/guides/get-started) +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [VSS backup and recovery](/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) -| 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` | - | +*** -##### `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. +## Read RGB Lightning balances and history +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/balances-history +Description: Query Bitcoin, RGB, Lightning payment, transaction, and terminal receipt state. -**Parameters:** -- `tx` (Transaction): Module-specific transaction object. For example, EVM accounts accept `EvmTransaction`, and Bitcoin accounts accept `BtcTransaction`. +Use the account or its read-only adapter to query state owned by the RGB Lightning node. -**Returns:** `Promise` - The signed transaction payload. Wallet modules may narrow this return type, such as a hex string for EVM and Bitcoin transactions. + +Community modules are developed and maintained independently by third-party contributors. -**Example:** -```typescript title="Sign Without Broadcasting" -const account = await wdk.getAccount('ethereum', 0) +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 signedTransaction = await account.signTransaction({ - to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', - value: 1000000000000000n -}) +## Confirm the account is unlocked -console.log('Signed transaction:', signedTransaction) +```js +const state = await account.getAddressState() +if (state.status !== 'ready') { + throw new Error('Unlock the account before interpreting balances') +} + +await account.sync() ``` -## IWalletAccountWithProtocols +Before unlock, `getBalance()` returns `0n`; that value is not distinguishable from a real zero without `getAddressState()`. -Extended wallet account interface that supports protocol registration and access. Extends `IWalletAccount` from `@tetherto/wdk-wallet`. +## Read Bitcoin balances -### Methods +```js +const spendableSats = await account.getBalance() +const balanceDetails = await account.getBalanceDetails() -| 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 | +console.log({ + spendableSats: spendableSats.toString(), + balanceDetails, +}) +``` -##### `registerProtocol(label, protocol, config)` -Registers a new protocol for this specific account. +`getBalance()` returns spendable vanilla Bitcoin satoshis. `getBalanceDetails()` returns the native breakdown as an opaque object. -**Type Parameters:** -- `P`: `typeof SwapProtocol | typeof BridgeProtocol | typeof LendingProtocol` - A class that extends one of the `@tetherto/wdk-wallet/protocol`'s classes +## Read RGB balances and assets -**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 +```js +const assets = await account.listAssets() +const balance = await account.getAssetBalance(assetId) +const spendableUnits = await account.getTokenBalance(assetId) +const metadata = await account.getAssetMetadata(assetId) +``` -**Returns:** `IWalletAccountWithProtocols` - The account instance (supports method chaining) +`getTokenBalance()` returns spendable asset base units and falls back to settled units when needed. Format values using validated asset metadata. -**Example:** -```javascript title="Register Protocol for Account" -import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +The Lightning node and on-chain RGB wallet do not share records. Query the module that actually received or holds the asset. -const account = await wdk.getAccount('ethereum', 0) +## Read Bitcoin and RGB history -// Register protocol for this specific account -account.registerProtocol('usdt0', Usdt0ProtocolEvm, { - apiKey: 'YOUR_API_KEY' -}) +```js +const [transactions, unspents, rgbTransfers] = await Promise.all([ + account.getTransactions(), + account.listUnspents(), + account.listTransfers(assetId), +]) +``` -// Method chaining -const account2 = await wdk.getAccount('ethereum', 1) - .registerProtocol('usdt0', Usdt0ProtocolEvm, usdt0ProtocolConfig) +You can narrow Bitcoin records with `getTransactionsByTxid(txid)` and RGB records with `listTransfersByTxid(txid)`. + +## Read Lightning payments + +```js +const payments = await account.listPayments() +const outbound = await account.getPayment(paymentHash, 'Outbound') ``` -##### `getSwapProtocol(label)` -Returns the swap protocol with the given label. +The accepted payment discriminants are `Outbound`, `InboundAutoClaim`, and `InboundHodl`. Pre-1.0 HTTP names such as `sent` and `received` are not accepted. -**Parameters:** -- `label` (string): The protocol label +## Read a terminal receipt -**Returns:** `ISwapProtocol` - The swap protocol instance +```js +const receipt = await account.getTransactionReceipt(hash) +``` -**Throws:** Error if no swap protocol with the given label has been registered +The method returns: -**Example:** -```javascript title="Get Swap Protocol" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +- a confirmed Bitcoin record; +- a settled RGB transfer; +- a non-pending Lightning payment; +- or `null`. -// Register swap protocol -account.registerProtocol('velora', veloraProtocolEvm, veloraProtocolConfig) +Treat `null` as pending, unknown, or not yet indexed—not proof that a write failed. -// Get swap protocol -const velora = account.getSwapProtocol('velora') +## Validate native payloads -// Use the protocol -const swapResult = await velora.swap({ - tokenIn: '0x...', - tokenOut: '0x...', - tokenInAmount: 1000000n -}) +Many responses are intentionally declared as `object` because RLN owns their shape. Validate the fields your application consumes at runtime and pin them to beta.15. Do not copy response fields from an unreleased branch or different RLN beta. -// This will throw an error -// try { -// const uniswap = account.getSwapProtocol('uniswap') -// } catch (error) { -// console.error('No swap protocol with label "uniswap" found') -// } -``` +## Next steps -##### `getBridgeProtocol(label)` -Returns the bridge protocol with the given label. +- [Send Bitcoin and manage UTXOs](/sdk/community-modules/wdk-rgb-lightning/guides/send-btc-utxos) +- [Use Lightning payments](/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) -**Parameters:** -- `label` (string): The protocol label +*** -**Returns:** `IBridgeProtocol` - The bridge protocol instance +## Get started with RGB Lightning +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/get-started +Description: Install @utexo/wdk-rgb-lightning beta.15 with a matching native peer and unlock the node. -**Throws:** Error if no bridge protocol with the given label has been registered +This guide creates the single RGB Lightning node account and unlocks it against development services. -**Example:** -```javascript title="Get Bridge Protocol" -import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' + +Community modules are developed and maintained independently by third-party contributors. -// Register bridge protocol -account.registerProtocol('usdt0', Usdt0ProtocolEvm) +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. + -// Get bridge protocol -const usdt0 = account.getBridgeProtocol('usdt0') +## 1. Install the module and one native peer -// Use the protocol -await account.approve({ - token: '0x...', - spender: '0x...', // OFT or bridge spender address - amount: 1000000n -}) +For a Node.js 18-or-newer host: -const bridgeResult = await usdt0.bridge({ - targetChain: 'arbitrum', - recipient: '0x...', - token: '0x...', - amount: 1000000n, - oftContractAddress: '0x...' // Same address used as approval spender -}) +```bash +npm install @utexo/wdk-rgb-lightning@0.1.0-beta.15 @utexo/rgb-lightning-node-nodejs@0.1.0-beta.11 ``` -##### `getLendingProtocol(label)` -Returns the lending protocol with the given label. - -**Parameters:** -- `label` (string): The protocol label +For a Bare/mobile host: -**Returns:** `ILendingProtocol` - The lending protocol instance +```bash +npm install @utexo/wdk-rgb-lightning@0.1.0-beta.15 @utexo/rgb-lightning-node-bare@0.1.0-beta.15 +``` -**Throws:** Error if no lending protocol with the given label has been registered +Install only the peer for the target runtime. No Windows native artifact was published in this release set. -**Example:** -```javascript title="Get Lending Protocol" -import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' +## 2. Construct the manager -// Register lending protocol -account.registerProtocol('aave', AaveProtocolEvm, aaveProtocolConfig) +```js +import WalletManagerRgbLightning from '@utexo/wdk-rgb-lightning' -// Get lending protocol -const aave = account.getLendingProtocol('aave') +const seedPhrase = await loadSeedFromSecretStorage() -// Use the protocol -const supplyResult = await aave.supply({ - token: '0x...', - amount: 1000000n +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'regtest', + dataDir: '/app-private/wdk/rgb-lightning', + nodeSeedDerivation: 'auto', }) ``` -## Complete Example - -```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' +Use a persistent directory that is not shared with the on-chain RGB wallet or another live node instance. -// 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) +## 3. Get and unlock account 0 -// Get accounts -const accountEth = await wdk.getAccount('ethereum', 3) -const accountTon = await wdk.getAccountByPath('ton', "1'/2/3") +```js +const account = await manager.getAccount(0) -// Use wallet account methods -const { hash, fee } = await accountEth.sendTransaction({ - to: '0x...', - value: 1000000000000000000n // 1 ETH +await account.unlock({ + bitcoind_rpc_username: 'rpc-user', + bitcoind_rpc_password: await loadRpcPassword(), + bitcoind_rpc_host: '127.0.0.1', + bitcoind_rpc_port: 18443, + indexer_url: 'tcp://127.0.0.1:50001', + proxy_endpoint: 'rpc://127.0.0.1:3000/json-rpc', + announce_addresses: [], + announce_alias: 'example-node', }) +``` -// Use protocols -const velora = accountEth.getSwapProtocol('velora') -const swapResult = await velora.swap(swapOptions) +Constructor camelCase RPC, indexer, proxy, and announce fields are not consumed in beta.15. Pass native snake_case fields to `unlock()`. -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 bridgeResult = await usdt0.bridge(bridgeOptions) +## 4. Confirm readiness -// Clean up -wdk.dispose() +```js +const addressState = await account.getAddressState() +if (addressState.status !== 'ready') { + throw new Error('RGB Lightning account is still locked') +} + +const [nodeInfo, networkInfo] = await Promise.all([ + account.getNodeInfo(), + account.getNetworkInfo(), +]) + +console.log({ + address: addressState.address, + nodeInfo, + networkInfo, +}) ``` -## Types +The current address remains stable until `rotateAddress()` is called. -### FeeRates +## 5. Dispose at shutdown -```typescript title="Type: FeeRates" -interface FeeRates { - [blockchain: string]: { - normal: number; - fast: number; - }; +```js +try { + await runNodeFlows(account) +} finally { + manager.dispose() } ``` -### Middleware Function +`manager.dispose()` is terminal. It owns binding shutdown, VLS signer destruction, retained-seed cleanup, and account invalidation. `account.dispose()` is a no-op in this release. -```typescript title="Type: MiddlewareFunction" -type MiddlewareFunction = ( - account: A -) => Promise; -``` +## Next steps -### Protocol Types +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Connect peers and open channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) -```typescript title="Types: Protocol Interfaces" -// Swap Protocol -interface ISwapProtocol { - swap(options: SwapOptions): Promise; -} +*** -// Bridge Protocol -interface IBridgeProtocol { - bridge(options: BridgeOptions): Promise; -} +## Handle errors and clean up the node +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup +Description: Handle typed RGB Lightning boundaries, raw RLN failures, ambiguous writes, and terminal manager cleanup. -// Lending Protocol -interface ILendingProtocol { - supply(options: LendingOptions): Promise; - withdraw(options: LendingOptions): Promise; - borrow(options: LendingOptions): Promise; - repay(options: LendingOptions): Promise; -} +Beta.15 wraps selected lifecycle boundaries in typed errors, while many native calls still return raw `Rln(): ` failures. -// 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; -} -``` + +Community modules are developed and maintained independently by third-party contributors. -### Swidge Protocol +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. + -`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. +## Branch on typed boundaries -| 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` | +```js +import { + AccountLockedError, + ApayError, + UnlockError, + VssError, + VssNotConfiguredError, +} from '@utexo/wdk-rgb-lightning' -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. +try { + await account.unlock(unlockRequest) +} catch (error) { + if (error instanceof UnlockError) { + reportUnlockFailure(error.code, error.cause) + } + throw error +} +``` -See the [Swidge Protocol Interface](/sdk/swidge-modules) page for the full discovery, quote, execution, status, fee, and result shapes. +Typed `RgbLightningError` subclasses preserve the original error as `cause` and expose `code` plus `toJSON()`. -```typescript title="Type: ISwidgeProtocol Options and Results" -type SwidgeProtocolConfig = { - maxNetworkFeeBps?: number | bigint; - maxProtocolFeeBps?: number | bigint; -}; +| Error | Handle | +|---|---| +| `UnlockError` | RPC credentials, host reachability, indexer, proxy, VSS initialization, or persisted identity mismatch. | +| `AccountLockedError` | Prompt for unlock or use `getAddressState()` before address/signature operations. | +| `VssNotConfiguredError` | Disable the VSS action or construct with `vssUrl`. | +| `VssError` | Preserve the fence/snapshot state and investigate before retrying. | +| `ApayError` | Inspect peer visibility, LSP auth, virtual-channel config, and partial bootstrap state. | +| `NotImplementedError` | Route to an operation-specific send method. | -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 -); +`LspError`, `LnurlPayError`, and the composed LSP timeout/settlement errors are separate hierarchies. -type SwidgeStatus = - | 'pending' | 'action-required' | 'completed' | 'failed' - | 'refund-pending' | 'refunded' | 'cancelled' | 'expired' | 'partial'; +## Preserve raw native errors + +Many node, channel, payment, Bitcoin, and RGB methods still throw version-specific `Rln(...)` strings. + +```js +try { + await account.openChannel(openRequest) +} catch (error) { + reportWalletFailure({ + operation: 'open_channel', + message: error instanceof Error ? error.message : String(error), + }) + throw error +} ``` -*** +Do not branch on raw message text unless the behavior is pinned and covered by tests. Never log the mnemonic, unlock credentials, LSP token, VSS fence password, complete private invoice context, or native requests containing secrets. -## Next Steps +## Reconcile writes before retrying - - -Get started with WDK's configuration - - -Get started with WDK's Usage - - -Explore blockchain-specific wallet modules - - -Cross-chain USD₮0 bridges - - +A timeout does not prove failure. After a channel, payment, Bitcoin, RGB, or APay write: -*** +1. Preserve returned IDs and the original error. +2. Query the relevant channel, payment, transaction, transfer, peer, or APay state. +3. Synchronize when appropriate. +4. Retry only after an explicit idempotency rule proves it safe. -### Need Help? +`bootstrapLsp()` can leave the LSP peer connected when later APay registration fails. Inspect `listPeers()` before retrying. - +## Distinguish lock from zero balance -*** +Before unlock, `getBalance()` returns `0n`: -## WDK Core Configuration -URL: https://docs.wdk.tether.io/sdk/core-module/configuration -Description: Configuration options and settings for @tetherto/wdk +```js +const state = await account.getAddressState() -# Configuration +if (state.status === 'locked') { + showUnlockRequired() +} else { + showBalance(await account.getBalance()) +} +``` -## WDK Manager Configuration +## Preserve the primary failure during cleanup -```javascript title="Create WDK Instance" -import WDK from '@tetherto/wdk' +```js +let operationError -const wdk = new WDK(seedPhrase) +try { + await runWalletFlow(account) +} catch (error) { + operationError = error + throw error +} finally { + try { + manager.dispose() + } catch (cleanupError) { + reportCleanupFailure(cleanupError, { operationError }) + } +} ``` +`account.shutdown()` is idempotent, but `manager.dispose()` is the terminal owner-level cleanup. It shuts down the binding, destroys the VLS signer, wipes retained seed buffers, and invalidates read-only adapters. `account.dispose()` is a no-op. -The WDK Manager itself only requires a seed phrase for initialization. Configuration is done through the registration of wallets and protocols. +Do not reuse a manager, account, read-only adapter, or LSP object after manager disposal. -## Wallet Registration Configuration +## Investigate common failures -```javascript title="Register WDK Wallet" -import WDK from '@tetherto/wdk' -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerTon from '@tetherto/wdk-wallet-ton' +| Symptom | Evidence to collect | +|---|---| +| Native binding fails to load | Host OS/architecture, installed peer version, downloaded artifact, Node/Bare entry selected. | +| Persisted identity mismatch | `nodeSeedDerivation`, prior beta version, node public key, isolated backup of `dataDir`. | +| Locked address | `getAddressState()`, unlock request and service reachability without secret values. | +| Channel/payment failure | Peer and channel state, route amount units, RGB 3,000,000-msat minimum, native error cause. | +| VSS fence held | Previous owner lifecycle, VSS namespace, last checkpoint, proof no other live writer exists. | +| LSP/APay failure | HTTPS/auth, peer visibility, virtual-channel flags, trusted LSP node ID, partial bootstrap state. | -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' - }) -``` +## Next steps +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) +- [VSS backup and recovery](/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery) +- [LSP, Lightning Address, and APay](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) -## Protocol Registration Configuration +*** -```javascript title="Register WDK Protocol" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +## Create and send Lightning payments +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments +Description: Create BOLT11 and HODL invoices, send payments or keysend, and inspect Lightning payment state. -const wdk = new WDK(seedPhrase) - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: 'YOUR_velora_API_KEY' - }) -``` +Use the released invoice and payment methods after the node is unlocked and has a ready channel. + +Community modules are developed and maintained independently by third-party contributors. -## Configuration 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. + -### Wallet Configuration +## Create an invoice -Each wallet manager requires its own configuration object when registered. The configuration depends on the specific wallet module being used. +The convenience method accepts released camelCase fields: -#### EVM Wallet Configuration +```js +const created = await account.createLightningInvoice({ + amountMsat: 5_000_000, + expirySec: 3_600, +}) +``` -```javascript title="Ethereum WDK Wallet Configuration" -const ethereumWalletConfig = { - provider: 'https://eth.drpc.org', // RPC endpoint - // Additional EVM-specific configuration options -} +It also accepts native snake_case objects. The return is intentionally typed as `object`; validate and extract the BOLT11 field according to the pinned beta.15 response. -wdk.registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) -``` +For an RGB-routed invoice, include `assetId` and `assetAmount`. Such an HTLC must be at least 3,000,000 msat. +## Decode and inspect an invoice -#### TON Wallet Configuration +```js +const decoded = await account.decodeInvoice(bolt11) +const status = await account.getInvoiceStatus(bolt11) +``` -```javascript title="TON WDK Wallet Configuration" -const tonWalletConfig = { - tonClient: { - secretKey: 'YOUR_TON_API_KEY', - url: 'https://toncenter.com/api/v2/jsonRPC' - } -} +Before paying, validate network, destination, amount, expiry, description or description hash, asset fields, and any application-specific approval. -wdk.registerWallet('ton', WalletManagerTon, tonWalletConfig) +## Send a BOLT11 payment + +```js +const sent = await account.sendPayment({ invoice: bolt11 }) ``` +`sendPayment()` forwards the native request and returns a native object. Reconcile with `getPayment()` or `listPayments()` after ambiguous failure. -### Protocol Configuration +The generic WDK router also recognizes a BOLT11 recipient: -Protocols also require their own configuration objects when registered. +```js +const result = await account.transfer({ + recipient: bolt11, + amount: 5_000_000n, +}) +``` -#### Swap Protocol Configuration +Lightning amounts and returned fees are millisatoshis. -```javascript title="Swap WDK Protocol Configuration" -const veloraProtocolConfig = { - apiKey: 'YOUR_velora_API_KEY', - baseUrl: 'https://apiv5.velora.io' -} +## Keysend -wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) +`keysend(request)` accepts a native request object. The generic router treats a 66-character hexadecimal recipient as a node public key: + +```js +const result = await account.transfer({ + recipient: destinationNodeId, + amount: 5_000_000n, +}) ``` +Validate that the recipient is exactly the intended compressed node ID. A malformed recipient can fall through to a different transfer route and fail later. +## Quote limitations +`quoteTransfer()` uses a 50-basis-point Lightning allowance. It is not a live route probe and does not guarantee the route, liquidity, final fee, or success. -### Middleware Configuration +Apply an application policy, then inspect the actual payment record rather than presenting the quote as final. -Middleware functions can be registered to enhance account functionality. +## HODL invoices -```javascript title="Middleware WDK Protocol Configuration" -// Simple logging middleware -wdk.registerMiddleware('ethereum', async (account) => { - console.log('New account created:', await account.getAddress()) +```js +const hodl = await account.createHodlInvoice({ + paymentHash, + amtMsat: 5_000_000, + expirySec: 3_600, }) ``` -## Environment Variables +The caller owns preimage generation and custody. Use `claimHodlInvoice(validatedNativeClaimRequest)` only after the intended condition is satisfied, or `cancelHodlInvoice(validatedNativeCancelRequest)` according to a documented timeout policy. -For production applications, consider using environment variables for sensitive configuration: +Losing, disclosing, or reusing a preimage can violate the payment contract. -```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 - }) -``` +## Inspect payment history +```js +const payments = await account.listPayments() +const payment = await account.getPayment(paymentHash, 'Outbound') +``` -## Configuration Validation +Valid payment types are `Outbound`, `InboundAutoClaim`, and `InboundHodl`. -The WDK Manager will validate configurations when wallets and protocols are registered: +## Next steps -- **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 +- [Connect peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) +- [Use Lightning Address and APay](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) -## Error Handling +*** -Configuration errors will be thrown during registration: +## Use an LSP, Lightning Address, and APay +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay +Description: Use the beta.15 LSP client, composed RGB flows, LNURL-pay helpers, Lightning Address, and asynchronous payments. -```javascript title="Configuration errors" -try { - wdk.registerWallet('ethereum', InvalidWalletClass, config) -} catch (error) { - console.error('Wallet registration failed:', error.message) -} +The package exposes a standalone HTTP client, account-bound helpers, and a composed `UtexoLsp` flow object. -try { - wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, invalidConfig) -} catch (error) { - console.error('Protocol registration failed:', error.message) -} -``` + +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. + -## Next Steps +## Configure a secure LSP endpoint +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + lspBaseUrl: 'https://lsp.example.com', + lspBearerToken: await loadLspToken(), +}) +``` - - -Get started with WDK's usage - - -Get started with WDK's API - - -Explore blockchain-specific wallet modules - - -Cross-chain USD₮0 bridges - - +Treat the bearer token as a secret. Public HTTP is rejected by default. Enable HTTP only for an explicitly accepted loopback/development environment. -*** +## Use the standalone client -### Need Help? +```js +import { LspClient } from '@utexo/wdk-rgb-lightning' - +const client = new LspClient({ + baseUrl: 'https://lsp.example.com', +}) -*** +const [health, info] = await Promise.all([ + client.health(), + client.getInfo(), +]) +``` -## Manage Accounts -URL: https://docs.wdk.tether.io/sdk/core-module/guides/account-management -Description: Learn how to work with accounts and addresses. +The client also exposes LNURL discovery/callback, Lightning Address resolution, RGB on-chain send bridging, and Lightning receive bridging. Validate native response objects. -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. +## Use composed RGB flows -## Retrieve Accounts +With `lspBaseUrl` configured, the no-argument form discovers the peer: -You can retrieve an account using a simple index or a custom derivation path. +```js +const lsp = await account.createLsp() -### By Index (Recommended) +await lsp.connect() -The simplest way to get an account is by its index (starting at `0`). This uses the default derivation path for the specified blockchain. +const receive = await lsp.receiveAsset({ + assetId, + amountRgb: 100, +}) -```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) +const settlement = await lsp.awaitReceiveSettlement(receive.lnInvoice) ``` -### By Derivation Path (Advanced) +`UtexoLsp` also exposes `waitForChannel()`, `waitForOutboundLiquidity()`, `sendAsset()`, `payAddress()`, `enableLightningAddress()`, and `claimPendingPayments()`. -If you need a specific hierarchy, you can request an account by its unique derivation path. +Bound every wait with a timeout or `AbortSignal`. Handle `LspChannelTimeoutError`, `LspLiquidityTimeoutError`, and `LspSettlementError` separately. -```typescript title="Get Account by Path" -// Custom path for Ethereum -const customEthAccount = await wdk.getAccountByPath('ethereum', "0'/0/1") +## Pay a Lightning Address + +```js +const paid = await lsp.payAddress({ + address: 'alice@example.com', + amtMsat: 5_000_000, +}) ``` - -The WDK instance caches accounts. If you call `getAccount` twice using the same index, the function will return the same `Account` object instance. - +The lower-level `payLightningAddress()` and LNURL helpers are also exported from the package root. - -**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. - +LNURL callbacks must remain on the discovery host by default. Set `allowCrossHostCallback: true` only after reviewing the delegated host and redirect threat model. -## View Addresses +Inputs such as `$alice@example.com` are normalized as UMA-style address syntax. This release does not implement UMA signing, compliance, currency negotiation, or exchange-rate semantics. -Once you have an account object, you can retrieve its public blockchain address using the `getAddress` function. +## Enable a Lightning Address -```typescript title="Get Addresses" -const ethAddress = await ethAccount.getAddress() -console.log('Ethereum address:', ethAddress) +```js +const address = await lsp.enableLightningAddress() +console.log(address.address) ``` -## Check Balances +The LSP owns availability and name assignment. Do not present the address as durable until the LSP confirms it and your application stores the returned mapping. -You can check the native token balance of any account (e.g., ETH on Ethereum, TON on TON) by using the `getBalance()` function. +## Configure production APay -```typescript title="Get Balance" -try { - const balance = await ethAccount.getBalance() - console.log('Balance:', balance) -} catch (error) { - console.error('Failed to fetch balance:', error) +APay receives payments while the wallet is offline. Construct the manager with: + +```js +{ + lspBaseUrl: 'https://lsp.example.com', + lspBearerToken, + enableVirtualChannelsV0: true, + virtualPeerPubkeys: [lspNodeId], } ``` -### Multi-Chain Balance Check +Then bootstrap the authenticated LSP peer: -Because WDK offers a unified interface, you can easily iterate through multiple chains to fetch balances. +```js +const result = await account.bootstrapLsp({ + peerPubkeyAndAddr: `${lspNodeId}@${lspHost}:${lspPort}`, + hostNodeId: lspNodeId, + waitForPeerMs: 15_000, + pollIntervalMs: 250, +}) +``` -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. +`bootstrapLsp()` connects the peer, waits for visibility, then calls `apayNew()` when `hostNodeId` is supplied. -```typescript title="Check All Balances" -const chains = ['ethereum', 'ton', 'bitcoin'] + +A failed bootstrap can leave a connected peer even when APay registration did not finish. Inspect `listPeers()` and APay state before retrying; do not assume rollback. + -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`) - } -} -``` +Production APay requires mutual trust for `trusted_no_broadcast` virtual channels. Authenticate the LSP node ID independently. -## Next Steps +## Next steps -Now that you can access your accounts, learn how to [send transactions](/sdk/core-module/guides/transactions). +- [Connect peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use RGB assets](/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) *** -## Error Handling -URL: https://docs.wdk.tether.io/sdk/core-module/guides/error-handling -Description: Learn about common errors and best practices. - -# Error Handling & Best Practices +## Manage the RGB Lightning node account +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account +Description: Manage node identity, lock state, addresses, read-only access, seed derivation, and lifecycle cleanup. -This guide covers recommended patterns for error handling and security when using the WDK. +One manager owns one RLN/LDK node, one account at index `0`, and one persistent state directory. -## Handling Common Errors + +Community modules are developed and maintained independently by third-party contributors. -When interacting with multiple chains and protocols, various runtime issues may occur. +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. + -### Missing Registration +## Use the fixed account identity -The most common error is attempting to access a wallet or protocol that hasn't been registered. +```js +const account = await manager.getAccount(0) -```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) -} +console.log(account.index) // 0 +console.log(account.path) // m ``` - -Always use `try/catch` blocks when initializing sessions or accessing dynamic features. - +`getAccountByPath('m')` returns the same account. Other paths and nonzero indexes are rejected. The manager's signer-name overload does not enable registered WDK signers; this module requires its attached VLS signer. -## Memory Management +`account.keyPair.publicKey` is the compressed Lightning node public key. `account.keyPair.privateKey` is always `null`. -For security, clear sensitive data from memory when a session is complete. The WDK provides [`dispose()`](/sdk/core-module/api-reference) for this purpose. +## Handle locked state explicitly -### Disposing the Instance +```js +const state = await account.getAddressState() -You can clear every registered wallet using [`dispose()`](/sdk/core-module/api-reference): +if (state.status === 'locked') { + showUnlockRequired() +} else { + showAddress(state.address) +} +``` -```typescript title="Dispose WDK" -function endSession(wdk) { - // 1. Clean up sensitive data - wdk.dispose() +`getAddress()` throws `AccountLockedError` before unlock. `getBalance()` instead returns `0n`, so never infer readiness from balance alone. - // 2. Modify app state to reflect logged-out status - // ... +## Keep or rotate the address deliberately - console.log('Session ended, wallet data cleared.') -} +```js +const current = await account.getAddress() +const next = await account.rotateAddress() ``` -### Disposing Specific Wallets +The binding enables address reuse, so repeated `getAddress()` calls return the stable current address. `rotateAddress()` is an explicit state-changing operation; update deposit records and UI only after it succeeds. -You can dispose only the wallets you no longer need using [`dispose()`](/sdk/core-module/api-reference): +## Create a read-only adapter -```typescript title="Dispose Specific Wallets" -// Keep the TON wallet registered, but dispose the Ethereum wallet -wdk.dispose(['ethereum']) +```js +const readOnly = await account.toReadOnlyAccount() + +const [channels, peers, assets] = await Promise.all([ + readOnly.listChannels(), + readOnly.listPeers(), + readOnly.listAssets(), +]) ``` - -**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. - +The adapter can query node, channel, payment, RGB, Bitcoin, fee, receipt, signature-verification, and diagnostic state. It cannot sign, broadcast, mutate channels, recover VSS, or expose LSP credentials. -## Security Best Practices +Do not retain it after `manager.dispose()`, because it uses the manager-owned native query transport. -### Environment Variables +## Preserve node identity across upgrades -Never hardcode API keys or seed phrases in your source code. Use environment variables (e.g., `process.env.TON_API_KEY`). +The default `nodeSeedDerivation: 'auto'` uses corrected seed derivation for new nodes and retries legacy beta derivation only on an exact persisted identity mismatch. -### Secure Storage +Pin `legacy-v1` only for a verified pre-beta.15 node that requires it. Pin `wdk-seed-v2` only after proving the persisted node uses the corrected identity. Changing identity against funded channel state can make the node unusable. -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). +## Shut down safely -*** +`account.shutdown()` is idempotent and stops the binding. `manager.dispose()` is the terminal owner-level cleanup and should still be your application shutdown boundary. -## Getting Started -URL: https://docs.wdk.tether.io/sdk/core-module/guides/getting-started -Description: Install and instantiate the WDK Core module. +```js +try { + await runNode(account) +} finally { + manager.dispose() +} +``` -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. +Cleanup can throw. Preserve the primary operation error and report cleanup failure separately. -## 1. Installation +## Next steps -### Prerequisites +- [Read balances and history](/sdk/community-modules/wdk-rgb-lightning/guides/balances-history) +- [Back up with VSS](/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) -Before you begin, ensure you have the following installed: +*** -* **[Node.js](https://nodejs.org/)**: version 18 or higher. -* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. +## Connect peers and manage channels +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels +Description: Connect Lightning peers and open, inspect, or close standard, RGB, and trusted virtual channels. -### Install Package +Unlock and synchronize the node before changing peer or channel state. -To install the WDK Core package, run the following command in your terminal: + +Community modules are developed and maintained independently by third-party contributors. -```bash -npm install @tetherto/wdk +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. + + +## Connect a peer + +```js +const peerUri = `${peerNodeId}@${peerHost}:${peerPort}` + +await account.connectPeer(peerUri) +const peers = await account.listPeers() ``` -This package allows you to manage different blockchain wallets and protocols through a single interface. +Validate the node ID, host, port, network, and intended counterparty out of band. A successful connection does not prove channel readiness. -## 2. Instantiation +## Open a standard channel -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. +```js +const opened = await account.openChannel({ + peer_pubkey_and_opt_addr: peerUri, + capacity_sat: 1_000_000, + push_msat: 0, + public: true, + with_anchors: true, +}) +``` -### Import the Module +Values are forwarded to RLN. Confirm funding, reserves, feerate, public/private policy, anchor support, and counterparty compatibility before opening. -First, import the `WDK` class from the package: +## Open an RGB channel -```typescript title="Import WDK Core" -import WDK from '@tetherto/wdk' +The released `OpenChannelRequest` also declares `asset_id` and `asset_amount`: + +```js +const opened = await account.openChannel({ + peer_pubkey_and_opt_addr: peerUri, + capacity_sat: 1_000_000, + asset_id: assetId, + asset_amount: 10_000, + public: false, + with_anchors: true, +}) ``` -### Initialize WDK +`asset_amount` is in RGB asset base units. The asset must already exist in this Lightning node's independent wallet state. -You can initialize `WDK` in two ways: with a [new seed phrase](#generate-a-new-wallet) or an [existing one](#restore-an-existing-wallet). + +An RGB-routed HTLC has a hard minimum of 3,000,000 millisatoshis in this release. Smaller RGB-channel invoices or payments fail to route. + -#### Generate a New Wallet +## Inspect channel state -If you are creating a fresh wallet for a user, use the static `getRandomSeedPhrase()` method to generate a secure mnemonic. +```js +const channels = await account.listChannels() +const permanentId = await account.getChannelId(temporaryChannelIdHex) +``` -```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) +Responses are native objects. Validate status, confirmations, channel IDs, balances, and counterparty before enabling payments. -// Or use 12-word seed phrase (default) -// const seedPhrase = WDK.getRandomSeedPhrase() +## Close or disconnect -// 2. Initialize the WDK instance with the new seed -const wdk = new WDK(seedPhrase) +```js +await account.closeChannel(validatedNativeCloseRequest) +await account.disconnectPeer(validatedNativeDisconnectRequest) ``` - -**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. - +The public declaration leaves both request shapes as `object`. Use the exact matching beta.15/RLN schema and validate it at the application boundary. -#### Restore an Existing Wallet +Do not treat peer disconnection as channel closure. Confirm final channel and on-chain state. -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. +## Trusted virtual channels -```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 ...' +Production APay uses non-broadcast trusted channels. Construct the manager with: -const wdk = new WDK(existingSeed) +```js +{ + enableVirtualChannelsV0: true, + virtualPeerPubkeys: [lspNodeId], +} ``` -## Next Steps +Then the native open request can use: -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/). +```js +{ + virtual_open_mode: 'trusted_no_broadcast', + // Other validated OpenChannelRequest fields. +} +``` -*** +Only trust an authenticated LSP node ID. Both sides must configure mutual trust; virtual channels change the normal broadcast and counterparty assumptions. -## Configure Middleware -URL: https://docs.wdk.tether.io/sdk/core-module/guides/middleware -Description: Learn how to intercept and enhance wallet operations with middleware. +## Next steps -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). +- [Use Lightning payments](/sdk/community-modules/wdk-rgb-lightning/guides/lightning-payments) +- [Use LSP and APay](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) -## Register Middleware +*** -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. +## Receive and send RGB assets +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/rgb-assets +Description: Query existing RGB assets, create receive invoices, and transfer assets through the RGB Lightning node. -### Logging +The Lightning node holds and transfers its own RGB asset records. It does not share balances with the on-chain RGB wallet. -This simple middleware logs a message whenever a new account is accessed. + +Community modules are developed and maintained independently by third-party contributors. -```typescript title="Logging Middleware" -wdk.registerMiddleware('ethereum', async (account) => { - const address = await account.getAddress() - console.log('Accessed Ethereum account:', address) +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. + - // You can also attach custom properties or wrap methods here -}) -``` +## Inspect an existing asset -## Failover Protection with Provider Failover +```js +const assets = await account.listAssets() +const balance = await account.getAssetBalance(assetId) +const metadata = await account.getAssetMetadata(assetId) +``` -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. +`getTokenBalance(assetId)` returns spendable base units and falls back to settled units when needed. -### Install `@tetherto/wdk-provider-failover` + +Use [`@utexo/wdk-wallet-rgb`](/sdk/community-modules/wdk-wallet-rgb) for released, documented NIA issuance. The beta.15 runtime contains issuance forwarders, but its public TypeScript declarations omit them; they are not documented here as supported WDK account APIs. + -You can install the `@tetherto/wdk-provider-failover` using npm with the following command: +## Create an RGB invoice -```bash -npm install @tetherto/wdk-provider-failover +```js +const created = await account.createRgbInvoice({ + min_confirmations: 1, + witness: false, + asset_id: assetId, + assignment_kind: 'Fungible', + assignment_amount: 100, + duration_seconds: 3_600, +}) ``` -### Use `createFallbackWallet` +`min_confirmations` and `witness` are required. The result is a native object; validate and extract the complete invoice using the pinned beta.15 response shape. -You can import the `createFallbackWallet` function to ensure that if your primary RPC fails, the wallet automatically retries with the fallback providers. +Treat the invoice as single-use and send it over an authenticated channel. -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. +## Send with the WDK router -```typescript title="Failover Wrapper Usage" -import { createFallbackWallet } from '@tetherto/wdk-provider-failover' -import { WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' - -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' } - ] - } -) +```js +const transfer = await account.transfer({ + recipient: rgbInvoice, + token: assetId, + amount: 100n, + feeRate: 2, +}) -// Use the wallet instance directly -const balance = await wallet.getBalance() +console.log(transfer.hash) ``` -## Next Steps +The router recognizes `rgb:` and `utxob:` invoices. It decodes the invoice and sends one `Fungible` recipient with `donation: false` and `min_confirmations: 1`. -Learn about [error handling and best practices](/sdk/core-module/guides/error-handling) to ensure your application is robust and secure. + +The WDK result returns `fee: 0n` for RGB because beta.15 does not expose the native fee through this route. It does not mean the transfer was fee-free. + -*** +## Use the native grouped send -## 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. +For non-fungible assignment kinds, donation behavior, witness data, multiple recipients, or explicit confirmation policy, use `sendRgbAsset()` with a validated `SendRgbAssetRequest`: -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. +```js +const result = await account.sendRgbAsset({ + donation: false, + fee_rate: 2, + min_confirmations: 1, + recipient_groups: validatedRecipientGroups, +}) +``` -## Register Protocols +Each recipient group contains an `asset_id` and recipients with a decoded `recipient_id`, assignment kind and amount, transport endpoints, and optional witness data. Do not construct those fields from unvalidated display text. -You can register protocols globally (for all new accounts). +## Refresh and inspect transfer state -### Global Registration (Recommended) +```js +await account.refreshTransfers(validatedNativeRefreshRequest) -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 transfers = await account.listTransfers(assetId) +const byTransaction = await account.listTransfersByTxid(txid) +``` -### 1. Install Protocol Modules +`failTransfers(request)` mutates transfer state. Call it only with the exact matching native request after proving the transfer should be failed. -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: +## Media -```bash -npm install @tetherto/wdk-protocol-swap-velora-evm && npm install @tetherto/wdk-protocol-bridge-usdt0-evm +```js +const media = await account.getAssetMedia(digest) +const posted = await account.postAssetMedia(validatedNativeMediaRequest) ``` -### 2. Register in Code +Media is untrusted content. Validate digest, size, MIME type, storage, and rendering. `maxMediaUploadSizeMb` defaults to `5`. -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. +## State boundary -First, import the necessary modules: +Use separate persistent paths: -```typescript title="Import Protocols" -import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -import usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +```text +rgb-onchain/ # @utexo/wdk-wallet-rgb +rgb-lightning/ # @utexo/wdk-rgb-lightning ``` -Then, register the protocols for the specific chains they support: +Moving an asset between them requires a real receive/send flow. Copying files or reusing one path is not a migration. -```typescript title="Register Protocols" -// Register protocols for specific chains -const wdk = new WDK(seedPhrase) - .registerWallet('ethereum', WalletManagerEvm, ethConfig) +## Next steps - // Register Velora Swap for Ethereum - .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { - apiKey: 'YOUR_API_KEY' - }) +- [Read balances and history](/sdk/community-modules/wdk-rgb-lightning/guides/balances-history) +- [Use peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Use LSP flows](/sdk/community-modules/wdk-rgb-lightning/guides/lsp-lightning-address-apay) - // Register USDT0 Bridge for Ethereum - .registerProtocol('ethereum', 'usdt0', usdt0ProtocolEvm, { - ethereumRpcUrl: 'https://eth.drpc.org' // Configuration depends on the module - }) -``` +*** -## Use Protocols +## Send Bitcoin and manage UTXOs +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/send-btc-utxos +Description: Quote and send Bitcoin, inspect history, and use native UTXO operations through the RGB Lightning account. -Once [registered](#register-protocols), you can access the protocol instance using the specific getter methods, such as `getSwapProtocol`, `getBridgeProtocol`, `getLendingProtocol`, or `getFiatProtocol`. +The RGB Lightning account exposes a WDK-shaped Bitcoin send and lower-level RLN Bitcoin/UTXO methods. -### Swidge Routes + +Community modules are developed and maintained independently by third-party contributors. -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. +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="Swidge route flow" -const chains = await swidge.getSupportedChains() -const tokens = await swidge.getSupportedTokens({ - fromChain: 'ethereum', - toChain: 'arbitrum' -}) +## Check readiness and balance -const options = { - fromToken: '0xSourceToken...', - toToken: '0xDestinationToken...', - toChain: 'arbitrum', - recipient: '0xRecipient...', - fromTokenAmount: 1000000n, - slippage: 0.01 +```js +const state = await account.getAddressState() +if (state.status !== 'ready') { + throw new Error('Unlock the account before sending Bitcoin') } -const quote = await swidge.quoteSwidge(options) +await account.sync() +const balance = await account.getBalance() +const unspents = await account.listUnspents() +``` -const result = await swidge.swidge(options, { - maxNetworkFeeBps: 50, - maxProtocolFeeBps: 25 -}) +Validate spendable balance, UTXO status, confirmations, and any channel reserve requirements. -const status = await swidge.getSwidgeStatus(result.id, { - toChain: 'arbitrum' -}) -``` +## Quote a standard send -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. +```js +const transaction = { + to: bitcoinAddress, + value: 50_000n, + feeRate: 2, + confirmationTarget: 6, +} - -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. - +const quote = await account.quoteSendTransaction(transaction) +``` -### Swapping Tokens +The beta.15 quote approximates a standard transaction as 141 vbytes multiplied by a fee rate. It does not construct the final transaction or guarantee its exact size or fee. -Use `getSwapProtocol` to access registered swap services on any wallet account. +`manager.getFeeRates()` reads main mempool.space recommendations without selecting `testnet`, `signet`, or `regtest`. Use a network-appropriate estimator for policy decisions. -```typescript title="Swap Tokens" -const ethAccount = await wdk.getAccount('ethereum', 0) -const velora = ethAccount.getSwapProtocol('velora') +## Send through the WDK shape -const result = await velora.swap({ - tokenIn: '0x...', // Address of token to sell - tokenOut: '0x...', // Address of token to buy - tokenInAmount: 1000000n // Amount to swap -}) -``` +```js +const maximumFee = 2_000n +if (quote.fee > maximumFee) { + throw new Error('Quoted Bitcoin fee exceeds the application limit') +} -### Bridging Assets +const result = await account.sendTransaction(transaction) +console.log({ txid: result.hash, feeSats: result.fee.toString() }) +``` -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. +VLS signs internally. The result fee is in satoshis. -```typescript title="Bridge Assets" -const ethAccount = await wdk.getAccount('ethereum', 0) -const usdt0 = ethAccount.getBridgeProtocol('usdt0') +## Use native methods only with pinned shapes -await ethAccount.approve({ - token: '0x...', // ERC20 Token Address - spender: '0x...', // OFT or bridge spender address - amount: 1000000n -}) +`sendBtc(request)` and `createUtxos(request)` forward native RLN request objects. Their public beta.15 declarations intentionally use `object`; inspect and validate the exact matching RLN payload rather than inventing fields. -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 -}) +```js +const result = await account.sendBtc(validatedNativeSendRequest) +await account.createUtxos(validatedNativeCreateUtxosRequest) ``` - -**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. - - -## Next Steps +`signTransaction()` always throws `NotImplementedError`. Use `sendTransaction()`, `sendBtc()`, `sendPayment()`, or `sendRgbAsset()` so VLS can enforce operation-specific policy. -Learn how to [configure middleware](/sdk/core-module/guides/middleware) to add logging or failover protection to your wallet interactions. +## Reconcile ambiguous sends -*** +After a timeout: -## Send Transactions -URL: https://docs.wdk.tether.io/sdk/core-module/guides/transactions -Description: Learn how to send native tokens on different blockchains. +1. Keep any returned transaction ID. +2. Call `sync()`. +3. Query `getTransactionsByTxid()` and `listUnspents()`. +4. Retry only after proving the first send was not accepted. -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. +## Next steps - -**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. - +- [Read balances and history](/sdk/community-modules/wdk-rgb-lightning/guides/balances-history) +- [Connect peers and channels](/sdk/community-modules/wdk-rgb-lightning/guides/peers-channels) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) - -**BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. - +*** -## Send Native Tokens +## Sign and verify Lightning messages +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/sign-verify-messages +Description: Sign domain-separated messages through VLS and verify them with full or read-only RGB Lightning accounts. -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. +The account signs with its Lightning node identity through the in-process VLS signer. It never exposes a private key through `keyPair`. -### Ethereum Example + +Community modules are developed and maintained independently by third-party contributors. - -On EVM chains, values are typically expressed in Wei (1 ETH = 10^18 Wei). +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 following example will: +## Build a domain-separated challenge -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`. +```js +const message = [ + 'example-node-auth', + 'version=1', + `origin=${expectedOrigin}`, + `nonce=${serverNonce}`, + `expires=${expiresAt}`, +].join('\n') +``` -```typescript title="Send ETH" -const ethAccount = await wdk.getAccount('ethereum', 0) +Include purpose, origin, nonce, expiry, and version. Never ask a user to sign opaque bytes or content that could be interpreted as another action. -const result = await ethAccount.sendTransaction({ - to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', - value: 1000000000000000n // 0.001 ETH (in Wei) -}) +## Sign after unlock -console.log('Transaction sent! Hash:', result.hash) +```js +const state = await account.getAddressState() +if (state.status !== 'ready') { + throw new Error('Unlock the node before signing') +} + +const signature = await account.sign(message) ``` -### TON Example +`account.keyPair.privateKey` remains `null`; VLS owns signing material and policy checks. - -On TON, values are expressed in Nanotons (1 TON = 10^9 Nanotons). - +## Verify -The following example will: +The full account can verify: -1. Retrieve the first TON account -2. Send 1 TON (1000000000 nton) to an account using `sendTransaction`. +```js +const valid = await account.verify(message, signature) +``` -```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) +The read-only adapter can also verify while its originating manager and node transport remain available: + +```js +const readOnly = await account.toReadOnlyAccount() +const valid = await readOnly.verify(message, signature) ``` -## Sign Without Broadcasting +Reject changed messages, reused nonces, expired challenges, unexpected origins, and signatures tied to the wrong node identity. -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. +## Do not confuse message and transaction signing -```typescript title="Sign An EVM Transaction" -const ethAccount = await wdk.getAccount('ethereum', 0) +`sign()` signs a Lightning message. It does not produce a Bitcoin PSBT signature, channel commitment, BOLT11 payment, or RGB consignment authorization. -const signedTransaction = await ethAccount.signTransaction({ - to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', - value: 1000000000000000n -}) +`signTransaction()` intentionally throws `NotImplementedError`. Use the operation-specific send methods so VLS can apply the correct policy. -console.log('Signed transaction:', signedTransaction) -``` +## Cleanup - -`signTransaction()` only signs. Use `sendTransaction()` when you want WDK to sign, broadcast, and return the transaction hash. - +Call `manager.dispose()` when the node session ends. It destroys the attached signer and zeroes retained seed buffers where owned by the module. Disposal cannot erase secrets copied by application code or recover information written to logs. -## Handling Responses +## Next steps -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. +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) -## Multi-Chain Transactions +*** -You can orchestrate payments across different chains in a single function by acting on multiple account objects sequentially. +## Back up and recover with VSS +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/guides/vss-backup-recovery +Description: Configure encrypted VSS snapshots, force checkpoints, recover with the original seed, and handle ownership fences safely. -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. +VSS can mirror LDK channel state and RGB wallet data to a remote key-value service. It complements—not replaces—seed custody and local operational recovery. -```typescript title="Multi-Chain Payment" -async function sendCrossChainPayments(wdk) { - const ethAccount = await wdk.getAccount('ethereum', 0) - const tonAccount = await wdk.getAccount('ton', 0) + +Community modules are developed and maintained independently by third-party contributors. - // 1. Send ETH - await ethAccount.sendTransaction({ - to: '0x...', - value: 1000000000000000000n - }) +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. + - // 2. Send TON - await tonAccount.sendTransaction({ - to: 'EQ...', - value: 1000000000n - }) -} +## Enable VSS at construction + +```js +const manager = new WalletManagerRgbLightning(seedPhrase, { + network: 'mainnet', + dataDir: '/app-private/wdk/rgb-lightning', + vssUrl: 'https://vss.example.com', + vssAllowHttp: false, + vssAllowEmptyRestore: false, +}) ``` -## Next Steps +Non-loopback HTTP is rejected unless `vssAllowHttp` is explicitly enabled. Keep production VSS on an authenticated, monitored HTTPS service. -For more complex interactions like swapping tokens or bridging assets, learn how to [integrate protocols](/sdk/core-module/guides/protocol-integration). +VSS payloads are encrypted client-side. Recovery still requires the original BIP-39 seed; VSS ciphertext alone is insufficient. -*** +## Inspect local configuration 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. +```js +const status = await account.vssStatus() -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). +console.log({ + configured: status.configured, + url: status.url, + lastBackupVersion: status.lastBackupVersion, +}) +``` -## How it works + +`vssStatus()` is a local view. It does not contact the server or prove that the latest remote snapshot exists, is readable, or can be decrypted. + -The WDK uses a builder pattern, allowing you to chain `.registerWallet()` calls. Each call connects a blockchain-specific manager to your central WDK instance. +## Force a checkpoint -### Parameters +```js +const { version } = await account.vssBackup() +recordCheckpointVersion(version) +``` -The `registerWallet` method (see [API Reference](/sdk/core-module/api-reference)) requires three arguments: +Use forced checkpoints at controlled lifecycle boundaries such as before app suspension or a planned upgrade. A returned version proves only that this call completed; recovery testing remains necessary. -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). +Calls without `vssUrl` throw `VssNotConfiguredError`. Server and encryption failures throw `VssError` at the wrapped boundaries. -## Installation +## Plan recovery -Install the [wallet managers](/sdk/wallet-modules/) for the blockchains you want to support: +The released account has no separate `restoreFromVss()` method. Recovery belongs to native node initialization with: -```bash -npm install @tetherto/wdk-wallet-evm @tetherto/wdk-wallet-tron @tetherto/wdk-wallet-btc -``` +- the original seed; +- the intended network; +- the same VSS namespace and configuration; +- a safe local `dataDir`; +- compatible beta.15 module and native binding versions. -## Example: Registering Multiple Wallets +Follow the matching RLN/VSS recovery runbook and validate node identity, channels, payments, RGB state, Bitcoin state, and the latest checkpoint before resuming writes. -### Import the Wallet Manager Packages +`vssAllowEmptyRestore: true` permits an empty remote store. Use it only when creating a deliberately new node; otherwise it can turn missing recovery state into a fresh start. -First, import the necessary wallet manager packages: +## Clear a stale ownership fence only when proven safe -```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' +```js +await account.clearVssFence(vssFencePassword) ``` -### Register the Wallets +The fence prevents two live writers from using one VSS store. -Then, [instantiate WDK](/sdk/core-module/guides/getting-started#initialize-wdk) and chain the registration calls: + +Call `clearVssFence()` only when you are certain the previous owner is permanently stopped. Two live nodes writing the same channel state can corrupt or roll back state and put funds at risk. + -```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' - }) -``` +Before clearing: - -**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. - +1. Stop and isolate the previous host. +2. Confirm no background service, mobile worklet, or failover instance can restart. +3. Preserve local logs and state for incident analysis. +4. Verify the VSS namespace, seed identity, and expected checkpoint. +5. Start exactly one replacement owner. - -**TRON Networks:** Choose the correct provider for your environment. -* **Mainnet:** `https://api.trongrid.io` -* **Shasta (Testnet):** `https://api.shasta.trongrid.io` - +## Recovery testing -## Next Steps +Test on the exact runtime and native peer: -Once your wallets are registered, you can [manage accounts and specific addresses](/sdk/core-module/guides/account-management). +- seed retrieval and decryption; +- native artifact loading; +- VSS authentication and snapshot retrieval; +- node public key stability; +- channel and payment reconciliation; +- RGB asset and transfer state; +- Bitcoin address, balance, and UTXOs; +- a new forced backup after recovery. + +## Next steps + +- [Manage the node account](/sdk/community-modules/wdk-rgb-lightning/guides/manage-node-account) +- [Handle errors and cleanup](/sdk/community-modules/wdk-rgb-lightning/guides/handle-errors-cleanup) +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) *** -## Usage -URL: https://docs.wdk.tether.io/sdk/core-module/usage -Description: Guide to using the WDK Core module. +## RGB Lightning wallet usage +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-rgb-lightning/usage +Description: Task-focused guides for @utexo/wdk-rgb-lightning 0.1.0-beta.15. -The WDK Core module is the central orchestrator for your wallet interactions. +Use these guides for the single-node RGB Lightning account in `@utexo/wdk-rgb-lightning@0.1.0-beta.15`. + + +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 and instantiate the WDK. + +Install the module and matching native binding, then unlock the node. - -Connect specific blockchains (Ethereum, TON, etc.). + +Manage identity, lock state, address rotation, read-only access, and disposal. - -Retrieve accounts and check balances. + +Read Bitcoin, RGB, Lightning, transaction, and receipt state. - -Transfer native tokens. + +Use WDK Bitcoin sends and native UTXO operations. - -Use Swidge, Swap, Bridge, and Lending protocols. + +Connect peers and open, inspect, or close Lightning channels. - -Add logging and failover protection. + +Create invoices, send payments, keysend, and handle HODL invoices. + + +Create RGB invoices and transfer existing assets through the node. + + +Use the LSP client, LNURL-pay, composed flows, and asynchronous payments. - -Best practices for security and stability. + +Configure encrypted VSS snapshots and handle ownership fences. + + +Use the Lightning identity signer without exposing private key bytes. + + +Branch on typed boundaries, preserve raw native failures, and shut down safely. -*** +## Reference -## Fiat Modules Overview -URL: https://docs.wdk.tether.io/sdk/fiat-modules -Description: Explore WDK fiat modules for on-ramp and off-ramp integrations. +- [Configuration](/sdk/community-modules/wdk-rgb-lightning/configuration) +- [API reference](/sdk/community-modules/wdk-rgb-lightning/api-reference) +- [beta.15 release](https://github.com/UTEXO-Protocol/wdk-rgb-lightning/releases/tag/v0.1.0-beta.15) -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. +*** -## Fiat Protocol Modules +## Cosmos wallet +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos +Description: Create Cosmos-compatible accounts, read balances, and send bank or IBC transfers with the Base58 community wallet module. -On-ramp and off-ramp functionality for fiat currency integration: +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. -| 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/) | + +Community modules are developed and maintained independently by third-party contributors. -## Features +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 modules provide: +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. -- **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 +## What you can build -## Next Steps +| Capability | Released behavior | +|---|---| +| 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 | + +## Released beta limitations + +- 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. -To get started with WDK fiat modules, follow these steps: +## Next 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 + + +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 the public API published in `1.0.0-beta.4`. + + +Inspect the source that corresponds to the documented package. + + +Review the community module catalog and submission guidance. + + -You can also: +*** -- 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 +## Cosmos wallet API reference +URL: https://docs.wdk.tether.io/sdk/community-modules/wdk-wallet-cosmos/api-reference +Description: Public API reference for @base58-io/wdk-wallet-cosmos version 1.0.0-beta.4. -*** +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). -## Fiat MoonPay Overview -URL: https://docs.wdk.tether.io/sdk/fiat-modules/fiat-moonpay -Description: Overview of the @tetherto/wdk-protocol-fiat-moonpay module + +Community modules are developed and maintained independently by third-party contributors. -# @tetherto/wdk-protocol-fiat-moonpay Overview +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. + -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. +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. -Get started by reading the [Usage](/sdk/fiat-modules/fiat-moonpay/usage) guide. +## Package - -This module requires a MoonPay developer account. [Create your account here](https://dashboard.moonpay.com/signup). - - -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. - -## Features +| Field | Value | +|---|---| +| Package | `@base58-io/wdk-wallet-cosmos` | +| 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` | -- **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 +The package export map exposes the root module and a `./package` subpath for `package.json`. Internal files are not supported public entrypoints. -## Supported Payment Methods +## Root exports -- Credit and debit cards (Visa, Mastercard, etc.) -- Bank transfers (ACH, SEPA, etc.) -- Apple Pay and Google Pay -- Local payment methods (varies by region) +| 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` -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). +### Constructor -## Supported Cryptocurrencies +```js +new WalletManagerCosmos(seed, config?) +``` -This module supports purchasing and selling cryptocurrencies on networks compatible with WDK wallet modules, including: +| Parameter | Type | Required | Description | +|---|---|---:|---| +| `seed` | `string \| Uint8Array` | Yes | BIP-39 mnemonic or seed bytes | +| `config` | `CosmosWalletConfig` | No | Chain, RPC, fee, retry, and IBC configuration | -- Ethereum and EVM-compatible chains (ETH, USD₮, etc.) -- Bitcoin (BTC) -- TRON (TRX, USD₮) -- TON -- Solana (SOL, USD₮) +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`. -## Next Steps +### Inherited static methods - - -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 - - +| 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 -### MoonPay Resources +| 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 | -- [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 +### Properties ---- +| Property | Type | Description | +|---|---|---| +| `seed` | `Uint8Array` | Inherited sensitive seed bytes; do not log or retain | +| `isDisposed` | `boolean` | Whether `dispose()` has been called | -### Need Help? +`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() +``` -## 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 +The returned values are deterministic fee amounts in the selected fee denomination: -# API Reference +- 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`. -Complete API documentation for the `@tetherto/wdk-protocol-fiat-moonpay` module. +The method requires at least one configured RPC endpoint but does not make an RPC request. -## Constructor +## `WalletAccountCosmos` -### `new MoonPayProtocol(account, config)` +Create accounts through `WalletManagerCosmos`. The exported static factory is also public: -Creates a new MoonPayProtocol instance. +```js +const account = await WalletAccountCosmos.create(seed, "0'/0/0", config) +``` -**Parameters:** +Do not call the class constructor directly. Its parameters are implementation details. -| Name | Type | Description | -|------|------|-------------| -| `account` | `IWalletAccount` \| `IWalletAccountReadOnly` \| `undefined` | Wallet account for transactions | -| `config` | `MoonPayProtocolConfig` | Configuration object | +### Account methods -**Config Options:** +| 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 -| 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 | +| 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 | -**Example:** + +`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. + -```typescript -import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; +### Balance behavior -const moonpay = new MoonPayProtocol(walletAccount, { - apiKey: 'pk_live_xxxxx', - signUrl: async (urlForSignature) => urlForSignature, - environment: 'production', -}); -``` +`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`. -## Methods +### Message signing -### `buy(options)` +`sign(message)` signs UTF-8 text with ADR-36 and returns a JSON string containing the public key and base64 signature. `verify()`: -Generates a MoonPay widget URL for purchasing cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. +- 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. -**Parameters:** +### Transaction input -| 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 | +`signTransaction()`, `quoteSendTransaction()`, and `sendTransaction()` consume the shared WDK transaction shape: -*Either `cryptoAmount` or `fiatAmount` must be provided, but not both. +```ts +type Transaction = { + to: string + value: number | bigint +} +``` -**Returns:** `Promise\<{ buyUrl: string }\>` +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`. -### `sell(options)` +`signTransaction()` needs RPC to obtain signing context and returns a signed CosmJS `TxRaw`. Its generated beta declaration types the result as `unknown`. -Generates a MoonPay widget URL for selling cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. + +`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()`. + -**Parameters:** +`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. -| 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 | +### Transfer input -**Returns:** `Promise\<{ sellUrl: string }\>` +```ts +type TransferOptions = { + token: string + recipient: string + amount: number | bigint +} +``` ---- +| Field | Meaning | +|---|---| +| `token` | Cosmos denomination such as `uatom` or an IBC denomination | +| `recipient` | Destination Bech32 address | +| `amount` | Integer amount in base units | -### `quoteBuy(options)` +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. -Gets a price quote for a cryptocurrency purchase. + +`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()`. + -**Parameters:** +`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. -| 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 | +### Transaction receipts -**Returns:** `Promise\` +`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: -```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 -} +```text +Transaction not found: ``` ---- +The method does not poll. -### `quoteSell(options)` +## `CosmosWalletConfig` -Gets a price quote for selling cryptocurrency. +| 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 | -**Parameters:** +See [Configuration](/sdk/community-modules/wdk-wallet-cosmos/configuration) for precedence and safety details. -| 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 | +## `ResolvedChainConfig` -**Returns:** `Promise\` +`resolveChainConfig()` returns: -```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 -} -``` +| 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` | ---- +## Helper functions -### `getSupportedCryptoAssets()` +### `resolveChainConfig(config?)` -Fetches the list of supported cryptocurrencies. Results are cached. +Returns registry-backed or custom resolved configuration. An unknown `chainName` throws and instructs the caller to use custom configuration. -**Returns:** `Promise\` +### `getAvailableChains()` -```typescript -{ - code: string, // Currency code (e.g., 'eth') - decimals: number, // Decimal places - networkCode: string, // Network identifier - name: string, // Display name - metadata: MoonPayCryptoCurrencyDetails -} -``` +Returns chain names whose bundled registry entry has `chainType === 'cosmos'`. ---- +### `isKnownChain(chainName)` -### `getSupportedFiatCurrencies()` +Returns whether any bundled registry entry has the supplied name. It does not test RPC reachability. -Fetches the list of supported fiat currencies. Results are cached. +## Error and lifecycle behavior -**Returns:** `Promise\` +- 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. -```typescript -{ - code: string, // Currency code (e.g., 'usd') - decimals: number, // Decimal places - name: string, // Display name - metadata: MoonPayFiatCurrencyDetails -} -``` +See [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for safe write and recovery guidance. ---- +*** -### `getSupportedCountries()` +## 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. -Fetches the list of supported countries. +`WalletManagerCosmos` accepts an optional `CosmosWalletConfig` object. The same configuration is resolved for every account created by that manager. -**Returns:** `Promise\` + +Community modules are developed and maintained independently by third-party contributors. -```typescript -{ - code: string, // ISO country code - name: string, // Country name - isBuyAllowed: boolean, // Buy operations allowed - isSellAllowed: boolean,// Sell operations allowed - metadata: MoonPayCountryDetail -} -``` +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 -### `getTransactionDetail(txId, direction?)` +| 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. | -Retrieves details of a specific transaction. +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`. -**Parameters:** +## Choose a configuration mode -| Name | Type | Required | Default | Description | -|------|------|----------|---------|-------------| -| `txId` | string | Yes | - | MoonPay transaction ID | -| `direction` | `'buy' \| 'sell'` | No | `'buy'` | Transaction type | +### Registry-backed configuration -**Returns:** `Promise\` +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. -```typescript -{ - status: 'completed' | 'failed' | 'in_progress', - cryptoAsset: string, - fiatCurrency: string, - metadata: MoonPayBuyTransaction | MoonPaySellTransaction +```js +const cosmosHubConfig = { + chainName: 'cosmoshub', } ``` ---- +Registry data is packaged data, not a runtime discovery service. Verify endpoints and chain parameters before production use. -## Types +### Custom chain configuration -### `MoonPayProtocolConfig` +Omit `chainName` when you need to control every chain-specific field. -```typescript -interface MoonPayProtocolConfig { - apiKey: string; - signUrl?: (urlForSignature: string) => Promise; - cacheTime?: number; - environment?: 'production' | 'sandbox'; +```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, } ``` -### `MoonPayBuyParams` +Replace the example endpoints with trusted endpoints for the intended chain. -Widget configuration options for `buy()` operations: +### Registry metadata with custom endpoints -```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; +Providing both values keeps registry metadata but replaces the registry endpoint list. - // 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; +```js +const hybridConfig = { + chainName: 'cosmoshub', + rpcEndpoints: [ + 'https://rpc-1.example.invalid', + 'https://rpc-2.example.invalid', + ], } ``` -### `MoonPaySellParams` +The custom endpoints are not appended to the registry list. -Widget configuration options for `sell()` operations: +## Fee and gas behavior -```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; +The release uses a fixed gas limit of `200000` for bank sends and transfers. It calculates the fee as: - // Sell-specific options - defaultBaseCurrencyCode?: string; - refundWalletAddresses?: string; - lockAmount?: boolean; - email?: string; - externalTransactionId?: string; - externalCustomerId?: string; - paymentMethod?: string; -} +```text +ceil(gas price amount × 200000) ``` -### `MoonPayQuoteBuyParams` +The account selects a gas price in this order: -```typescript -interface MoonPayQuoteBuyParams { - extraFeePercentage?: number; // 0-10% - paymentMethod?: string; - areFeesIncluded?: boolean; - walletAddress?: string; -} -``` +1. The average `gasPriceStep` from registry fee metadata. +2. The explicit `gasPrice` string. +3. The fallback average gas price `0.025` in `nativeDenom`. -### `MoonPayQuoteSellParams` + +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`. + -```typescript -interface MoonPayQuoteSellParams { - extraFeePercentage?: number; // 0-10% - payoutMethod?: string; -} -``` +`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. -## Next Steps +### Fee limits -- [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) - Setup and configuration options -- [Usage Guide](/sdk/fiat-modules/fiat-moonpay/usage) - Common usage patterns +`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. + -## 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 +## RPC fallback -# Configuration +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`. -This page covers all configuration options for the MoonPay fiat module, including optional URL signing and environment selection. +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. -## Prerequisites + +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. + -Before using this module, you need: +## IBC channels -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 +`transfer()` compares the recipient's Bech32 prefix with `addressPrefix`: -## Installation +- matching prefixes use a Cosmos bank send; +- different prefixes require a matching entry in `ibcChannels` and use IBC `MsgTransfer`. -```bash -npm install @tetherto/wdk-protocol-fiat-moonpay +```js +const ibcConfig = { + chainName: 'cosmoshub', + ibcChannels: { + osmo: { + sourceChannel: '', + }, + }, +} ``` -## Basic Configuration +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. -```typescript -import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; +## Security and cleanup -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 }), - }); +- 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. - if (!response.ok) { - throw new Error(`Failed to sign MoonPay URL: ${response.status} ${response.statusText}`); - } +## Next steps - const { signedUrl } = await response.json(); +- [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) - return signedUrl; - }, - environment: 'sandbox', -}); -``` +*** -## Configuration Options +## 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. -| 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 | +Balance methods return integer base-unit amounts as `bigint`. -## Constructor Overloads + +Community modules are developed and maintained independently by third-party contributors. -The `MoonPayProtocol` class supports three constructor patterns: +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 -// Without account (for public read operations like fetching supported currencies) -const moonpay = new MoonPayProtocol(undefined, config); +## Read the native balance -// With read-only account -const moonpay = new MoonPayProtocol(readOnlyAccount, config); +`getBalance()` uses the configured `nativeDenom`. Pass a denomination to query a different balance. -// With full wallet account (for buy/sell operations) -const moonpay = new MoonPayProtocol(walletAccount, config); -``` +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -## Environment Configuration +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -### Sandbox (Testing) +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -Use sandbox endpoints for development and testing: +try { + const account = await manager.getAccount(0) -```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 }), - }); + const nativeBalance = await account.getBalance() + const atomBalance = await account.getBalance('uatom') - return (await response.json()).signedUrl; - }, - environment: 'sandbox', -}); + console.log({ + nativeBaseUnits: nativeBalance.toString(), + atomBaseUnits: atomBalance.toString(), + }) +} finally { + manager.dispose() +} ``` -In sandbox mode: -- No real transactions are processed -- Use test card numbers provided by MoonPay -- KYC verification is simulated +Do not treat base units as display units. Apply denomination metadata and decimal formatting in your application. -If you do not need signed URLs, omit `signUrl` and the protocol returns unsigned widget URLs directly. +## Read token balances -### Production +Use `getTokenBalance(denom)` for one denomination or `getTokenBalances(denoms)` to filter the account's full balance response: -For production deployments, use live API keys and the production endpoint set: +```js +const atomBalance = await account.getTokenBalance('uatom') -```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 }), - }); +const balances = await account.getTokenBalances([ + 'uatom', + 'ibc/', +]) - return (await response.json()).signedUrl; - }, - environment: 'production', -}); +console.log(atomBalance.toString()) +console.log(balances) ``` -## Widget Customization +Replace the IBC placeholder with a trusted denomination trace hash for the configured chain. -When calling `buy()` or `sell()`, you can customize the MoonPay widget appearance: +`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: -```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', - }, -}); +```js +const atom = balances.uatom ?? 0n ``` -### Available Buy Widget Options +## RPC and account requirements -| 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 | +- 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. -### Available Sell Widget Options +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. -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 | +## 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. -## Next Steps +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. -- [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 + +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. + -## 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. +## 1. Install the package -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`. +Install the exact version documented by this guide: - -Amounts use smallest units: fiat in minor units (cents), crypto in on-chain base units (for example wei for ETH). - +```bash +npm install @base58-io/wdk-wallet-cosmos@1.0.0-beta.4 +``` -## Buy crypto (on-ramp) +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. -You can build a signed purchase URL with [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) when you know the fiat spend: +## 2. Configure the chain -```typescript title="Buy with fiat amount" -const result = await moonpay.buy({ - cryptoAsset: 'usdt', - fiatCurrency: 'usd', - fiatAmount: 10000n -}) +`chainName` loads bundled chain metadata, including the Bech32 prefix, native denomination, coin type, and RPC endpoints. -window.open(result.buyUrl, '_blank') +```js +const config = { + chainName: 'cosmoshub', +} ``` -You can request a fixed crypto amount instead by passing `cryptoAmount` to [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +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. -```typescript title="Buy with crypto amount" -const result = await moonpay.buy({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 100000000000000000n -}) +## 3. Derive an account -window.open(result.buyUrl, '_blank') -``` +Load the mnemonic from secure storage and dispose the manager even when an operation fails: -## Sell crypto (off-ramp) +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -You can generate a sell widget URL with [`sell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +const seedPhrase = process.env.WDK_SEED_PHRASE -```typescript title="Sell ETH for USD" -const result = await moonpay.sell({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 500000000000000000n +if ( + !seedPhrase || + !WalletManagerCosmos.isValidSeedPhrase(seedPhrase) +) { + throw new Error('A valid WDK_SEED_PHRASE is required') +} + +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', }) -window.open(result.sellUrl, '_blank') +try { + const account = await manager.getAccount(0) + const address = await account.getAddress() + + console.log('Cosmos address:', address) +} finally { + manager.dispose() +} ``` -## Get price quotes +Address derivation is local. Balance, signing-context, broadcast, and receipt methods require at least one configured RPC endpoint. -You can preview economics before opening the widget using [`quoteBuy()`](/sdk/fiat-modules/fiat-moonpay/api-reference): + +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. + -```typescript title="Buy quote" -const buyQuote = await moonpay.quoteBuy({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - fiatAmount: 10000n -}) +## Released limitations -console.log('Crypto amount:', buyQuote.cryptoAmount) -console.log('Fee:', buyQuote.fee) -console.log('Exchange rate:', buyQuote.rate) -``` +In `1.0.0-beta.4`: -You can estimate proceeds for a sell with [`quoteSell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +- 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. -```typescript title="Sell quote" -const sellQuote = await moonpay.quoteSell({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 500000000000000000n -}) +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). -console.log('Fiat amount:', sellQuote.fiatAmount) -``` +*** -## Supported currencies and countries +## 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. -You can list tradable assets with [`getSupportedCryptoAssets()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +Classify an error before deciding whether an operation is safe to repeat. -```typescript title="Supported crypto" -const cryptoAssets = await moonpay.getSupportedCryptoAssets() -console.log(cryptoAssets) -``` + +Community modules are developed and maintained independently by third-party contributors. -You can list fiat currencies with [`getSupportedFiatCurrencies()`](/sdk/fiat-modules/fiat-moonpay/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="Supported fiat" -const fiatCurrencies = await moonpay.getSupportedFiatCurrencies() -console.log(fiatCurrencies) -``` +## Common failures -You can check regional availability with [`getSupportedCountries()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +| 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 | -```typescript title="Supported countries" -const countries = await moonpay.getSupportedCountries() -console.log(countries) -``` + +For `transfer()`, a fee-limit error can occur after the transaction was broadcast. Do not interpret that error as proof that the transfer failed. + -## Widget customization +## Validate before a write -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)): +Accept only positive integer base-unit amounts and validate chain-specific identifiers before constructing an operation: -```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' +```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') } -}) +} -window.open(result.buyUrl, '_blank') +assertBaseUnitAmount(transfer.amount) ``` -## Custom recipient addresses +Also verify: -By default [`buy()`](/sdk/fiat-modules/fiat-moonpay/api-reference) credits the connected wallet. You can override the destination with `recipient`: +- 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. -```typescript title="Custom buy recipient" -const result = await moonpay.buy({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - fiatAmount: 10000n, - recipient: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' -}) +Quotes use configured metadata and fixed gas. They do not prove the operation will pass current chain validation. -window.open(result.buyUrl, '_blank') -``` +## Distinguish retryable failures -You can set a refund destination on sells with `refundAddress` on [`sell()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +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. -```typescript title="Custom sell refund address" -const result = await moonpay.sell({ - cryptoAsset: 'eth', - fiatCurrency: 'usd', - cryptoAmount: 500000000000000000n, - refundAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' -}) +```js +function getErrorMessage(error) { + return error instanceof Error ? error.message : String(error) +} -window.open(result.sellUrl, '_blank') +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 +} ``` -## 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. -- [Manage transactions](manage-transactions) -- [Get started](get-started) -- [API reference](/sdk/fiat-modules/fiat-moonpay/api-reference) +## 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. -## 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. +When a write throws: -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. +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. -## Installation +`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. -Run the following to install [@tetherto/wdk-protocol-fiat-moonpay](https://www.npmjs.com/package/@tetherto/wdk-protocol-fiat-moonpay): +## Quote before every write -```bash title="Install with npm" -npm install @tetherto/wdk-protocol-fiat-moonpay +```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) ``` -## Initialize MoonPayProtocol +`sendTransaction()` does not enforce `transferMaxFee`. `transfer()` checks that option only after broadcast. The published package does not expose `transactionMaxFee`. -You can create a fiat ramp client with [`new MoonPayProtocol(account, config)`](/sdk/fiat-modules/fiat-moonpay/api-reference): +## Always dispose sensitive state -```typescript title="Construct MoonPayProtocol" -import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay' +```js +const manager = new WalletManagerCosmos(seedPhrase, config) -const moonpay = new MoonPayProtocol(walletAccount, { - apiKey: process.env.MOONPAY_PUBLISHABLE_KEY, - secretKey: process.env.MOONPAY_SECRET_KEY -}) +try { + const account = await manager.getAccount(0) + // Perform the minimum required work. +} finally { + manager.dispose() +} ``` - -Never ship a secret key to browsers. Run server-side signing where your architecture allows, and rotate keys if they leak. - +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. -See [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) for `cacheTime` and related options. - -## Next Steps +Avoid `account.keyPair` unless an integration strictly requires it. Its `privateKey` field exposes the underlying sensitive buffer; retaining a reference can defeat cleanup assumptions. -- [Buy and sell](buy-and-sell) -- [Manage transactions](manage-transactions) +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. *** -## 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. +## 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. -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). +`WalletManagerCosmos` derives secp256k1 accounts below the configured Cosmos coin type. -## Check transaction status + +Community modules are developed and maintained independently by third-party contributors. -You can read the high-level state of a buy with [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/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="Buy transaction status" -const buyTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'buy') +## Derive accounts by index -console.log('Status:', buyTx.status) -``` +`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`. -`status` is one of `completed`, `failed`, or `in_progress` as described in the API reference. +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -## Read transaction details +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -You can load the same record to inspect assets and currencies using [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -```typescript title="Buy transaction fields" -const buyTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'buy') +try { + const firstAccount = await manager.getAccount(0) + const sixthAccount = await manager.getAccount(5) -console.log('Crypto asset:', buyTx.cryptoAsset) -console.log('Fiat currency:', buyTx.fiatCurrency) -console.log('Metadata:', buyTx.metadata) + console.log({ + firstAddress: await firstAccount.getAddress(), + sixthAddress: await sixthAccount.getAddress(), + sixthPath: sixthAccount.path, + }) +} finally { + manager.dispose() +} ``` -You can query a sell the same way by passing `sell` as the direction to [`getTransactionDetail()`](/sdk/fiat-modules/fiat-moonpay/api-reference): +`getAccount()` defaults to index `0`. -```typescript title="Sell transaction details" -const sellTx = await moonpay.getTransactionDetail(moonpayTransactionId, 'sell') +## Derive an account by path -console.log('Status:', sellTx.status) -console.log('Crypto asset:', sellTx.cryptoAsset) -console.log('Fiat currency:', sellTx.fiatCurrency) -``` +Pass the relative suffix below `m/44'/'/`: - -The second argument defaults to `buy` when omitted; set it explicitly for sell flows. - +```js +const account = await manager.getAccountByPath("0'/0/5") -## Next Steps +console.log(account.path) +// m/44'/118'/0'/0/5 when coinType is 118 +``` -- [Buy and sell](buy-and-sell) -- [Get started](get-started) -- [Configuration](/sdk/fiat-modules/fiat-moonpay/configuration) +Use the same `try`/`finally` manager lifecycle shown above. Invalid derivation paths reject account creation. -*** +## Understand caching -## 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 +The manager caches accounts by relative derivation path. Repeating either lookup for the same path returns the cached account instance: -# Usage +```js +const byIndex = await manager.getAccount(5) +const byPath = await manager.getAccountByPath("0'/0/5") -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. +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. - - -Install the package and initialize MoonPayProtocol. - - -On-ramp, off-ramp, quotes, supported assets, widget options, recipients. - - -Check status and load transaction details from MoonPay. - - +## 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`. - - -Get started with WDK in a Node.js environment - - -API keys, caching, and MoonPay configuration options - - -Constructor, methods, and types for MoonPayProtocol - - +`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). *** -## Get Started -URL: https://docs.wdk.tether.io/sdk/get-started -Description: Learn about the SDK and modules architecture +## 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. -The SDK is a comprehensive, modular plug-in framework designed to simplify multi-chain wallet development. +The native transaction methods create one Cosmos bank send using the configured `nativeDenom`. -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). + +Community modules are developed and maintained independently by third-party contributors. -#### Capabilities +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. + -* **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 + +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 -### Modular Architecture +`quoteSendTransaction()`, `signTransaction()`, and `sendTransaction()` accept the shared WDK transaction shape: -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. +```js +const transaction = { + to: 'cosmos1', + value: 1_000n, +} +``` +Replace the recipient placeholder with a validated address. The module: -Each module has a single responsibility. Wallet modules handle blockchain operations, protocol modules manage DeFi interactions, and the core module orchestrates everything. +- 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`. -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. +Use [`transfer()`](/sdk/community-modules/wdk-wallet-cosmos/guides/transfer-tokens) when you need to choose a denomination. -*** +## Quote before broadcast -#### Module Types +Apply an application-controlled fee limit before the write: -WDK modules are organized into six main categories, each serving a specific purpose in the blockchain application stack: +```js +const applicationMaxFee = 5_000n +const quote = await account.quoteSendTransaction(transaction) - - -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 - - +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. -### How to use the SDK +`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. -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. +## Broadcast a native send -#### Registration Flow +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' -**1. Core Module Initialization** +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') -```typescript title="Initialize WDK" -import WDK from '@tetherto/wdk' +const manager = new WalletManagerCosmos(seedPhrase, { + chainName: 'cosmoshub', +}) -// Generate 24-word seed phrase for higher security -const seedPhrase = WDK.getRandomSeedPhrase(24) +try { + const account = await manager.getAccount(0) + const transaction = { + to: 'cosmos1', + value: 1_000n, + } -// Or use 12-word seed phrase (default) -// const seedPhrase = WDK.getRandomSeedPhrase() + 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 wdk = new WDK(seedPhrase) + const result = await account.sendTransaction(transaction) + console.log({ + hash: result.hash, + fee: result.fee.toString(), + }) +} finally { + manager.dispose() +} ``` -**2. Wallet Module Registration** +## Sign without broadcasting -```typescript title="Register Wallets" -import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -import WalletManagerBtc from '@tetherto/wdk-wallet-btc' +`signTransaction()` obtains the account number, sequence, and chain ID through RPC, then returns a signed CosmJS `TxRaw`: -const wdkWithWallets = wdk - .registerWallet('ethereum', WalletManagerEvm, { - provider: 'https://eth.drpc.org' - }) - .registerWallet('bitcoin', WalletManagerBtc, { - provider: 'https://blockstream.info/api' - }) +```js +const signedTransaction = await account.signTransaction(transaction) ``` -**3. Protocol Module Registration** + +`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`. + -```typescript title="Register Protocols" -import SwapveloraEvm from '@tetherto/wdk-protocol-swap-velora-evm' +## Look up a receipt -const wdkWithProtocols = wdkWithWallets - .registerProtocol('swap-velora-evm', SwapveloraEvm) +After a successful broadcast, query the returned hash: + +```js +const receipt = await account.getTransactionReceipt(result.hash) ``` -#### Unified Operations +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: `. -Once registered, all modules work through the same interface: +## Avoid duplicate writes -```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) +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: -// Check balances using unified interface -const ethBalance = await ethAccount.getBalance() -const btcBalance = await btcAccount.getBalance() +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. -// Send transactions with consistent API -const ethTx = await ethAccount.sendTransaction({ - to: '0x...', - value: '1000000000000000000' -}) +See [Handle errors](/sdk/community-modules/wdk-wallet-cosmos/guides/handle-errors) for a recovery pattern. -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' -}) -``` +## 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. -*** +The released module signs UTF-8 messages using the Cosmos ADR-36 arbitrary-data convention. -### Creating Custom Modules + +Community modules are developed and maintained independently by third-party contributors. -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. +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. + -#### Wallet Module Interface +## Sign a message -```typescript title="Custom Wallet Module Setup" -interface WalletModule { - // Account management - getAccount(index: number): Promise - getAddress(index: number): Promise - getBalance(index: number): Promise +Bind the message to your application's domain, purpose, audience, and nonce before signing: - // Transaction operations - sendTransaction(params: TransactionParams): Promise - estimateTransaction(params: TransactionParams): Promise +```js +import WalletManagerCosmos from '@base58-io/wdk-wallet-cosmos' - // Key management - signMessage(message: string, index: number): Promise - verifySignature(message: string, signature: string, address: string): Promise +const seedPhrase = process.env.WDK_SEED_PHRASE +if (!seedPhrase) throw new Error('WDK_SEED_PHRASE is required') - // Blockchain-specific operations - getTransactionHistory(index: number, limit?: number): Promise - getTokenBalance(index: number, tokenAddress: string): Promise +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() } ``` -#### Protocol Module Interface +`sign()` returns a JSON string containing an ADR-36 `StdSignature`, including the public key and base64 signature. -```typescript title="Custom Protocol Module Setup" -interface ProtocolModule { - // Protocol execution - execute(params: ProtocolParams): Promise - estimate(params: ProtocolParams): Promise +## Verify expected failures - // Supported operations - getSupportedTokens(): Promise - getSupportedChains(): Promise - getOperationTypes(): Promise +`verify()` binds the signature public key to the current account address. It returns `false` when: - // Protocol-specific methods - getLiquidityPools?(): Promise - getLendingRates?(): Promise - getBridgeRoutes?(): Promise -} +- 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 ``` -#### Module Implementation Example +## Apply application-level context -```typescript title="Custom Wallet Module Implementation" -class CustomWalletModule implements WalletModule { - private provider: string - private chainId: number +ADR-36 signs the text you provide. It does not add an application domain, expiry, audience, nonce policy, or replay protection for you. - constructor(config: { provider: string; chainId: number }) { - this.provider = config.provider - this.chainId = config.chainId - } +Before accepting a signature: - async getAccount(index: number): Promise { - // Implement account derivation logic - const privateKey = await this.derivePrivateKey(index) - return new CustomAccount(privateKey, this.provider) - } +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. - async getAddress(index: number): Promise { - const account = await this.getAccount(index) - return account.getAddress() - } + +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. + - 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) +## 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. + +*** + +## 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. + +`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, } - async sendTransaction(params: TransactionParams): Promise { - // Implement transaction sending logic - const account = await this.getAccount(params.accountIndex) - const tx = await account.sendTransaction(params) - return tx + const applicationMaxFee = 5_000n + const { fee } = await account.quoteTransfer(transfer) + if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') } - // Additional methods... + const result = await account.transfer(transfer) + console.log({ + hash: result.hash, + fee: result.fee.toString(), + }) +} finally { + manager.dispose() } ``` -#### Module Registration +Amounts are integer base units. Replace all address, denomination, and fee-limit examples with values validated for the configured chain. -```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 -}) +A matching Bech32 prefix is only the module's routing heuristic; it does not prove that the recipient belongs to the same chain. -// Use it like any other module -const customAccount = await wdkWithCustom.getAccount('custom-chain', 0) -const balance = await customAccount.getBalance() +## 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, +} -### Quickstart Paths +const { fee } = await account.quoteTransfer(transfer) +if (fee >= applicationMaxFee) { + throw new Error('Quoted fee meets or exceeds the application limit') +} -Ready to start building? Choose your development environment: +const result = await account.transfer(transfer) +``` - - -Get started with WDK in a Node.js environment - - -Build mobile wallets with React Native Expo - - +Keep the manager inside a `try`/`finally` lifecycle and call `manager.dispose()` as shown in the same-chain example. -*** +The released IBC flow: -## Need Help? +- 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`. -## Lending Modules Overview -URL: https://docs.wdk.tether.io/sdk/lending-modules -Description: Explore WDK lending modules for integrating lending protocols with WDK. +The quote does not: -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. +- 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. -## Lending & Borrowing Protocol Modules + +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. + -DeFi lending functionality for different lending & borrowing protocols +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. -| 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/) | +*** -## Next Steps +## 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. -Compare the available EVM lending modules and open the implementation that matches your protocol target: +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. + - -Use the Tether-maintained Aave V3 lending module for EVM accounts. + +Install the pinned package and derive a Bech32 account. - -Use the community Morpho module for Vault V2 and Morpho Blue EVM flows. + +Use account indexes and Cosmos BIP-44 derivation paths. - -Install the Morpho module, create the client, and review prerequisites. + +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. *** -## Need Help? +## 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. - +`@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. -*** +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). -## 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 + +Community modules are developed and maintained independently by third-party contributors. -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. +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. + -## Features +## Choose the correct RGB module -- **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 +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. -## Supported Networks +| 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`](/sdk/community-modules/wdk-rgb-lightning) | +| Manage Bitcoin without RGB state | [`@tetherto/wdk-wallet-btc`](/sdk/wallet-modules/wallet-btc) | -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. +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. -## Wallet Compatibility +## Requirements -- **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 +For durable use, provide: -## Key Components +- 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. -- **Aave V3 Integration**: Supply, withdraw, borrow, repay primitives -- **Quote Helpers**: `quoteSupply`, `quoteWithdraw`, `quoteBorrow`, `quoteRepay` -- **Collateral Controls**: Toggle collateral usage; set user eMode +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. -## Next Steps +## 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. + + +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. + + +## Capabilities and boundaries + +- 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. + +This release does not expose multiple accounts, arbitrary derivation paths, or UDA/CFA issuance through its declared WDK account API. + +## Start building - -How to supply, withdraw, borrow and repay with Aave + +Install the released package and create the single RGB account. - -Service setup, account config, ERC‑4337 options + +Follow task-focused guides for assets, UTXOs, storage, migration, and errors. - -Full API for Aave Protocol Evm methods and types + +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. *** -## 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 +## 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. -Main class for Aave V3 lending on EVM. +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). -### Constructor + +Community modules are developed and maintained independently by third-party contributors. -```javascript -new AaveProtocolEvm(account) -``` +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. + -Parameters: -- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` +## Package -Example: +| 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` | -```javascript -const aave = new AaveProtocolEvm(account) -``` +## Exports -### Methods +| 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. | -| 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 }>` | + +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` -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. +| 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. | -### `supply(options, config?)` -Add tokens to the pool. +## `WalletAccountRgb` -Options: -- `token` (`string`): token address -- `amount` (`number | bigint`): amount in base units -- `onBehalfOf` (`string`, optional) +### Identity and WDK methods -Returns: -- May include `approveHash` and `resetAllowanceHash` for standard accounts (e.g., USD₮ allowance reset on Ethereum mainnet) +| 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. | -Example: + +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: -```javascript -const res = await aave.supply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +```typescript +const request = { signedPsbt } as unknown as Parameters[0] +const result = account.sendEnd(request) ``` + ---- +## `WalletAccountReadOnlyRgb` -### `quoteSupply(options, config?)` -Estimate fee to add tokens. +The read-only class is constructed from an address and configuration. It inherits balance methods and exposes: -```javascript -const q = await aave.quoteSupply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +| 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. -### `withdraw(options, config?)` -Remove tokens from the pool. +## Input shapes -Options: -- `token` (`string`) -- `amount` (`number | bigint`) -- `to` (`string`, optional) +### Bitcoin transaction -```javascript -const tx = await aave.withdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +| 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 -### `quoteWithdraw(options, config?)` -Estimate fee to withdraw tokens. +| 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. | -```javascript -const q = await aave.quoteWithdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +## 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. -### `borrow(options, config?)` -Borrow tokens. +## Guides -Options: -- `token` (`string`) -- `amount` (`number | bigint`) -- `onBehalfOf` (`string`, optional) +- [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) -```javascript -const tx = await aave.borrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +*** ---- +## 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. -### `quoteBorrow(options, config?)` -Estimate fee to borrow tokens. +`WalletManagerRgb` accepts a BIP-39 mnemonic or seed bytes and an RGB wallet configuration. -```javascript -const q = await aave.quoteBorrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` + +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. + -### `repay(options, config?)` -Repay borrowed tokens. +## Recommended configuration -Options: -- `token` (`string`) -- `amount` (`number | bigint`) -- `onBehalfOf` (`string`, optional) +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' -```javascript -const tx = await aave.repay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +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', +}) ``` -Returns: -- For standard accounts, may include `approveHash` / `resetAllowanceHash` when applicable. +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 -### `quoteRepay(options, config?)` -Estimate fee to repay borrowed tokens. +| 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. | -```javascript -const q = await aave.quoteRepay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` + +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 -### `setUseReserveAsCollateral(token, use, config?)` -Toggle token as collateral for the user. +Only these values are supported by the released declarations and account path logic: -```javascript -const tx = await aave.setUseReserveAsCollateral('TOKEN_ADDRESS', true) -``` +| 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. -### `setUserEMode(categoryId, config?)` -Set user eMode category. +## Local state -```javascript -const tx = await aave.setUserEMode(1) -``` +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`](/sdk/community-modules/wdk-rgb-lightning). -### `getAccountData(account?)` -Read account stats like total collateral, debt, and health. + +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. + -```javascript -const data = await aave.getAccountData() -``` +## Indexer and transport -Returns the following structure: +`indexerUrl` supplies Bitcoin chain data. `transportEndpoint` carries RGB consignments. Both services can observe request metadata and can be unavailable, stale, or malicious. -```javascript -{ - totalCollateralBase: bigint, - totalDebtBase: bigint, - availableBorrowsBase: bigint, - currentLiquidationThreshold: bigint, - ltv: bigint, - healthFactor: bigint -} -``` +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. -## ERC‑4337 Config Override (optional) +## Fee policy -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. +`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. -- **Paymaster token mode**: `paymasterUrl`, `paymasterAddress`, `paymasterToken`, `transferMaxFee` -- **Sponsorship policy mode**: `isSponsored`, `paymasterUrl`, `sponsorshipPolicyId` -- **Native coin mode**: `useNativeCoins`, `transferMaxFee` +`quoteSendTransaction()` and `quoteTransfer()` create and sign PSBTs to estimate a fee. Apply an application-owned limit before sending: -Example: +```js +const maximumFee = 2_000n +const quote = await account.quoteTransfer(transfer) -```javascript -const res = await aave.supply( - { token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) -``` +if (quote.fee > maximumFee) { + throw new Error('Quoted RGB transfer fee exceeds the application limit') +} -## Rules & Notes +const result = await account.transfer(transfer) +``` -- `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 +Do not use `transferMaxFee` as the sole guard. The manager omits that field when it constructs the released account. +## Runtime artifacts - - -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 - - +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. -### Need Help? +## 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) *** -## 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 +## 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. -# Configuration +The seed and local RGB database are separate recovery inputs. Test both before funding the wallet. -## Service Setup + +Community modules are developed and maintained independently by third-party contributors. -```javascript -import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-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. + -// Create wallet account first -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' +## Create an encrypted backup + +```js +const backup = account.createBackup({ + password: backupPassword, + backupPath: '/secure-backups/rgb-wallet.backup', }) -// Create lending service -const aave = new AaveProtocolEvm(account) +console.log(backup.message) ``` -## Account Configuration +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. -The service uses the wallet account configuration to connect to the target network and sign transactions. +## Restore into an empty directory -```javascript -import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' +Create a fresh manager with the same seed and network. Call `restoreAccountFromBackup()` before opening a normal account in the destination directory. -// Full access account -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +```js +import WalletManagerRgb from '@utexo/wdk-wallet-rgb' -// Read-only account (quotes, reads) -const readOnly = new WalletAccountReadOnlyEvm('0xYourAddress', { - provider: 'https://ethereum-rpc.publicnode.com' +const restoredManager = new WalletManagerRgb(seedPhrase, { + network: 'testnet', + indexerUrl: trustedIndexerUrl, + transportEndpoint: trustedTransportEndpoint, }) -const aave = new AaveProtocolEvm(account) +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() +} ``` -## ERC‑4337 (Account Abstraction) +Use an empty, private destination and do not run the original and restored wallet concurrently against the same state. -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. +## Validate the restore -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. +Compare more than the address: -```javascript -import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' +- 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. -const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { - chainId: 1, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: 'YOUR_BUNDLER_URL', - paymasterUrl: 'YOUR_PAYMASTER_URL' -}) +## Migrate from v1 -const aaveAA = new AaveProtocolEvm(aa) +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. -const result = await aaveAA.supply({ token: '0xdAC17F...ec7', amount: 1000000n }, { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } -}) -``` +Choose one of two paths: -### Supported Override Families +### Privacy-preserving reset -- **Paymaster token mode**: `paymasterUrl`, `paymasterAddress`, `paymasterToken`, `transferMaxFee` -- **Sponsorship policy mode**: `isSponsored`, `paymasterUrl`, `sponsorshipPolicyId` -- **Native coin mode**: `useNativeCoins`, `transferMaxFee` +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. -## Network Support +This is the maintainer-recommended path when historical metadata exposure matters. -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. +### Same-seed state migration -```javascript -// Ethereum Mainnet -const eth = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +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. -// Arbitrum -const arb = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://arb1.arbitrum.io/rpc' -}) -``` + +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. + -## Operation Options +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. -Each operation accepts a simple options object: +## Next steps -```javascript -// Supply -await aave.supply({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +- [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) -// Withdraw -await aave.withdraw({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +*** -// Borrow -await aave.borrow({ token: 'TOKEN_ADDRESS', amount: 1000000n }) +## 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. -// Repay -await aave.repay({ token: 'TOKEN_ADDRESS', amount: 1000000n }) -``` +Use the account's read methods after synchronizing Bitcoin and RGB transfer state. -### Common Parameters + +Community modules are developed and maintained independently by third-party contributors. -- `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) +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. + -> Note: `amount` must be > 0. Addresses must be valid/non‑zero. A provider is required for any write. +## Synchronize first +```js +account.syncWallet() +account.refreshWallet() +``` - - -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 - - +The freshness of every result depends on the selected indexer, transport endpoint, and local `dataDir`. -*** +## Read balances -### Need Help? +```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. -## 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. +To inspect the native asset records: -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. +```js +const assets = account.listAssets() +``` -## Installation +Native result objects come from the pinned `@utexo/rgb-sdk`. Validate the fields your application consumes instead of assuming an unreleased repository shape. -Run the following to install [@tetherto/wdk-protocol-lending-aave-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-lending-aave-evm): +## Read Bitcoin history and UTXOs -```bash title="Install with npm" -npm install @tetherto/wdk-protocol-lending-aave-evm +```js +const transactions = account.listTransactions() +const unspents = account.listUnspents() ``` -## 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): +These methods are synchronous wrappers over local/native state. A returned record is not, by itself, proof of finality; inspect its status and confirmations. -```javascript title="Create AaveProtocolEvm" -import AaveProtocolEvm from '@tetherto/wdk-protocol-lending-aave-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +## Read RGB transfer history -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +Use `listTransfers()` when an error must remain observable: -const aave = new AaveProtocolEvm(account) +```js +const allTransfers = account.listTransfers() +const oneAssetTransfers = account.listTransfers(assetId) ``` -## Prerequisites +Use `getTransfers()` for local filtering and pagination: - -**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). +```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. -Use contract addresses for Aave-supported reserves. On Ethereum mainnet, USD₮ uses `0xdAC17F958D2ee523a2206206994597C13D831ec7` (use `USDT` in code identifiers and literals). +## Read receipts -## Next Steps +```js +const bitcoinReceipt = await account.getTransactionReceipt(txid) +const rgbReceipt = await account.getTransferReceipt(transferHash) +``` -- [Lending operations](lending-operations) -- [Handle errors](handle-errors) +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 -## 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. +```js +const readOnly = await account.toReadOnlyAccount() -This guide explains how to [handle operation errors](#operation-errors) and follow [best practices](#best-practices) for disposing wallet state. +const [btcBalance, rgbBalance] = await Promise.all([ + readOnly.getBalance(), + readOnly.getTokenBalance(assetId), +]) +``` -## Operation errors +Keep the originating wallet state and endpoints available for the lifetime of the read-only view. -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`: +## Next steps -```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') - } -} -``` +- [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) -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) -} -``` +## 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. - -See [Rules & Notes](/sdk/lending-modules/lending-aave-evm/api-reference) for address and amount validation expectations. +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. -## Best Practices +## Inspect unspents -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): +```js +account.syncWallet() -```javascript title="Dispose after lending session" -try { - await aave.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) -} finally { - account.dispose() -} +const unspents = account.listUnspents() +console.log(unspents) ``` -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. +Check native status fields and confirmation requirements before selecting an output for a consequential flow. -## Next Steps +## Create RGB-compatible UTXOs -- [Lending operations](lending-operations) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-aave-evm/api-reference) +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, +}) -## 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. +console.log('UTXOs created:', created) +``` -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`. +`size` is satoshis and `feeRate` is sat/vbyte. Confirm appropriate values for the current network and RGB workflow. -## Supply +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. -You can deposit reserves into the pool using [`supply()`](/sdk/lending-modules/lending-aave-evm/api-reference): +## Quote a Bitcoin send -```javascript title="Supply USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +```js +const transaction = { + to: recipientAddress, + value: 50_000n, + feeRate: 2, +} -const tx = await aave.supply({ token: USDT, amount: 1000000n }) -console.log('Supply tx hash:', tx.hash) +const quote = await account.quoteSendTransaction(transaction) +console.log('Estimated fee:', quote.fee.toString()) ``` -## Withdraw + +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. + -You can remove supplied liquidity using [`withdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference): +## Send Bitcoin -```javascript title="Withdraw USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +```js +const maximumQuotedFee = 2_000n +const quote = await account.quoteSendTransaction(transaction) -const tx = await aave.withdraw({ token: USDT, amount: 1000000n }) -console.log('Withdraw tx hash:', tx.hash) +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) ``` -## Borrow +`sendTransaction()` runs `sendBtcBegin → signPsbt → sendBtcEnd` and returns `{hash, fee}`. -You can draw debt against your collateral using [`borrow()`](/sdk/lending-modules/lending-aave-evm/api-reference): + +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. + -```javascript title="Borrow USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +`manager.getFeeRates()` reads main mempool.space recommendations without selecting the wallet network. Do not treat it as authoritative for `testnet` or `regtest`. -const tx = await aave.borrow({ token: USDT, amount: 1000000n }) -console.log('Borrow tx hash:', tx.hash) -``` +## Next steps -## Repay +- [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) -You can pay down debt using [`repay()`](/sdk/lending-modules/lending-aave-evm/api-reference): +*** -```javascript title="Repay USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +## 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. -const tx = await aave.repay({ token: USDT, amount: 1000000n }) -console.log('Repay tx hash:', tx.hash) -``` +This guide installs the documented release, creates a durable local wallet, and reads its first address. -## Quotes before sending + +Community modules are developed and maintained independently by third-party contributors. -You can estimate the supply fee with [`quoteSupply()`](/sdk/lending-modules/lending-aave-evm/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="Quote supply fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +## 1. Check the target runtime -const supplyQuote = await aave.quoteSupply({ token: USDT, amount: 1000000n }) -console.log('Supply fee (wei):', supplyQuote.fee) -``` +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. -You can estimate the withdraw fee with [`quoteWithdraw()`](/sdk/lending-modules/lending-aave-evm/api-reference): +Test installation and native loading on the exact deployment target before integrating wallet state. -```javascript title="Quote withdraw fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +## 2. Install the released package -const withdrawQuote = await aave.quoteWithdraw({ token: USDT, amount: 1000000n }) -console.log('Withdraw fee (wei):', withdrawQuote.fee) +```bash +npm install @utexo/wdk-wallet-rgb@2.0.3 ``` -You can estimate the borrow fee with [`quoteBorrow()`](/sdk/lending-modules/lending-aave-evm/api-reference): +## 3. Choose durable state and services -```javascript title="Quote borrow fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +Create an app-private, persistent directory. Configure an indexer and transport endpoint for the same network. -const borrowQuote = await aave.quoteBorrow({ token: USDT, amount: 1000000n }) -console.log('Borrow fee (wei):', borrowQuote.fee) +```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', +}) ``` -You can estimate the repay fee with [`quoteRepay()`](/sdk/lending-modules/lending-aave-evm/api-reference): +`loadSeedFromSecretStorage()` represents your application's secret-management boundary. Do not embed a mnemonic in source, logs, telemetry, or crash reports. -```javascript title="Quote repay fee" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +## 4. Get the account -const repayQuote = await aave.quoteRepay({ token: USDT, amount: 1000000n }) -console.log('Repay fee (wei):', repayQuote.fee) +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. + -Health factor and collateralization limits still apply. A quote does not guarantee the transaction will succeed if on-chain state changes. +Do not assume the seed alone restores RGB state. Preserve and test recovery of `dataDir` and encrypted backups. -## ERC-4337 smart accounts +## Next steps -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. +- [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) -```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: 42161, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: process.env.BUNDLER_URL, - paymasterUrl: process.env.PAYMASTER_URL -}) - -const aaveAA = new AaveProtocolEvm(aa) +*** -const result = await aaveAA.supply( - { token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', amount: 1000000n }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) -console.log('Supply hash:', result.hash) -``` +## 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. -You can use the same second argument to: +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. -- 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` + +Community modules are developed and maintained independently by third-party contributors. -Use token addresses that exist on the same chain as the smart account RPC. +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. + -## Reading account data +## Preserve operation context -You can inspect collateral, debt, and health using [`getAccountData()`](/sdk/lending-modules/lending-aave-evm/api-reference): +```js +async function transferRgb(account, transfer) { + try { + return await account.transfer(transfer) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) -```javascript title="Read Aave account data" -const data = await aave.getAccountData() + reportWalletFailure({ + operation: 'rgb_transfer', + message, + }) -console.log({ - totalCollateralBase: data.totalCollateralBase, - totalDebtBase: data.totalDebtBase, - availableBorrowsBase: data.availableBorrowsBase, - currentLiquidationThreshold: data.currentLiquidationThreshold, - ltv: data.ltv, - healthFactor: data.healthFactor -}) + throw error + } +} ``` -## Next Steps +Do not log the seed, keys, backup password, complete invoice, or user-identifying endpoint credentials. -- [Handle errors](handle-errors) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-aave-evm/api-reference) +## Account for misleading and suppressed errors -*** +Two release-specific behaviors require explicit handling: -## 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 +- `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. -# Usage +```js +try { + account.syncWallet() + account.refreshWallet() + const transfers = account.listTransfers(assetId) + renderTransfers(transfers) +} catch (error) { + renderTransferStateUnavailable() + throw error +} +``` -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. +## Reconcile before retrying +Indexer, transport, or broadcast calls can succeed remotely and fail locally. After a timeout or connection loss: - - -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. - - +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. - - -Get started with WDK in a Node.js environment - - -Networks and deployment settings for the Aave lending protocol - - -Methods and parameters for AaveProtocolEvm - - +## 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. -## 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 +## Handle common setup failures - -Community modules are developed and maintained independently by third-party contributors. +| 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. | -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. - +## Clean up without hiding the primary failure -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). +```js +let operationError -## Features +try { + await runWalletFlow(manager) +} catch (error) { + operationError = error + throw error +} finally { + try { + manager.dispose() + } catch (cleanupError) { + reportCleanupFailure(cleanupError, { operationError }) + } +} +``` -- **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 +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. -## Supported Targets +## Next steps -The module supports curated Ethereum mainnet presets and explicit Morpho target configuration. +- [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) -### Earn Presets +*** -| Preset | Vault | -|--------|-------| -| `sky-money-usdt-savings` | sky.money USDT Savings V2 | -| `steakhouse-prime-instant` | Steakhouse Prime Instant V2 | +## 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. -### Borrow Presets +The v2.0.3 account declares Non-Inflatable Asset issuance and blind or witness receive invoices. -| Preset | Collateral | -|--------|------------| -| `susds` | sUSDS | -| `wsteth` | wstETH | -| `wbtc` | WBTC | -| `xaut` | XAUt | + +Community modules are developed and maintained independently by third-party contributors. -## Wallet Compatibility +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. + -- **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 +## Prepare the wallet -## Key Components +Register, synchronize, and confirm that the wallet has suitable Bitcoin UTXOs: -- **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` +```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') +} +``` - - -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. - - +## Issue an NIA -*** +```js +const issued = account.issueAssetNia({ + ticker: 'DEMO', + name: 'Demo Asset', + amounts: [1_000], + precision: 0, +}) -## 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 +console.log(issued) +``` -# API Reference +`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. -## Class: MorphoProtocolEvm + +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. + -Main class for Morpho Vault V2 and Morpho Blue lending on EVM. +## Create a blind receive invoice -### Constructor +```js +const receive = account.receiveAsset({ + assetId, + amount: 100, + witness: false, +}) -```javascript -new MorphoProtocolEvm(account, options) +console.log(receive.invoice) ``` -Parameters: - -- `account`: `WalletAccountEvm | WalletAccountReadOnlyEvm | WalletAccountEvmErc4337 | WalletAccountReadOnlyEvmErc4337` -- `options`: `MorphoProtocolOptions` +A blind receive normally consumes an available allocation-capable UTXO. -Example: +## Create a witness receive invoice -```javascript -const morpho = new MorphoProtocolEvm(account, { - presets: { - earn: 'sky-money-usdt-savings', - borrow: 'wsteth' - } +```js +const receive = account.receiveAsset({ + assetId, + amount: 100, + witness: true, }) ``` -### Methods +Witness receive uses an on-chain witness flow. Select the mode according to the recipient's wallet state, privacy model, and fee requirements. -| 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` | +## 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. -## Requirements and Results +The sender needs the invoice string beginning with `rgb:`. An ordinary Bitcoin address is not an RGB transfer recipient. -- `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`. +## 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) -### `supply(options, config?)` +*** -Deposit assets into the configured Morpho vault. +## 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. -Options: +The on-chain RGB module owns one account and a local RGB database. Treat both limits as part of the wallet identity. -- `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 + +Community modules are developed and maintained independently by third-party contributors. -```javascript -const tx = await morpho.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) -``` +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. + -### `getSupplyRequirements(options, requirementOptions?)` +## Use account index 0 -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. +```js +const account = await manager.getAccount(0) -```javascript -const requirements = await morpho.getSupplyRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) +console.log(account.index) // 0 +console.log(account.path) // BIP-86 vanilla path +console.log(account.coloredPath) // RGB colored path ``` -### `quoteSupply(options, config?)` +`manager.getAccount(1)` throws. `manager.getAccountByPath()` always throws. -Quote the fee for a vault deposit transaction. +| Network | `path` | `coloredPath` | +|---|---|---| +| `mainnet` | `m/86'/0'/0'` | `m/86'/827166'/0'` | +| `testnet` or `regtest` | `m/86'/1'/0'` | `m/86'/827167'/0'` | -```javascript -const quote = await morpho.quoteSupply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n +## 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. -### `withdraw(options, config?)` +Protect the directory from: -Withdraw assets from the configured Morpho vault. +- deletion by cache or temporary-file cleanup; +- concurrent access by multiple wallet instances; +- unencrypted device or cloud backups; +- reuse by [`@utexo/wdk-rgb-lightning`](/sdk/community-modules/wdk-rgb-lightning); +- accidental cross-network reuse. -Options: +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. -- `token` (`string`): configured vault asset -- `amount` (`number | bigint`): amount in base units -- `to` (`string`, optional): must equal the connected wallet address when set +## Register and synchronize -```javascript -const tx = await morpho.withdraw({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) +```js +const { address, btcBalance } = await account.registerWallet() + +account.syncWallet() +account.refreshWallet() + +console.log({ address, btcBalance }) ``` -### `quoteWithdraw(options, config?)` +`syncWallet()` synchronizes Bitcoin state. `refreshWallet()` refreshes RGB transfer state. The released runtime exposes both synchronously; failures can still propagate from native code. -Quote the fee for a vault withdrawal transaction. +## Create a read-only view -```javascript -const quote = await morpho.quoteWithdraw({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) +```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. -### `supplyCollateral(options, config?)` +## Dispose at the owner boundary -Supply collateral to the configured Morpho Blue market. +```js +try { + const account = await manager.getAccount(0) + // Use the account. +} finally { + manager.dispose() +} +``` -Options: +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. -- `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 +## Next steps -```javascript -const tx = await morpho.supplyCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) -``` +- [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) -### `getSupplyCollateralRequirements(options, requirementOptions?)` +*** -Return Morpho SDK approval or signature requirements for collateral supply. +## 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. -```javascript -const requirements = await morpho.getSupplyCollateralRequirements({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) -``` +The full v2.0.3 account exposes Bitcoin message signing and verification through the underlying RGB wallet. -### `quoteSupplyCollateral(options, config?)` + +Community modules are developed and maintained independently by third-party contributors. -Quote the fee for supplying collateral. +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 -const quote = await morpho.quoteSupplyCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) -``` +## Sign a domain-separated message ---- +```js +const message = [ + 'example-wallet-auth', + 'version=1', + `origin=${expectedOrigin}`, + `nonce=${serverNonce}`, + `expires=${expiresAt}`, +].join('\n') -### `borrow(options, config?)` +const signature = await account.sign(message) +``` -Borrow assets from the configured Morpho Blue market. +Include an application name, purpose, origin, nonce, expiry, and version. Do not ask users to sign opaque or transaction-like data. -Options: +## Verify with the full account -- `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 +```js +const valid = await account.verify(message, signature) -```javascript -const tx = await morpho.borrow({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) +if (!valid) { + throw new Error('Invalid RGB wallet message signature') +} ``` -### `getBorrowRequirements(options)` +Verify the exact bytes and application context that were presented to the signer. Reject reused nonces and expired challenges at the application boundary. -Return Morpho authorization requirements for borrow flows. +## Read-only limitation -```javascript -const requirements = await morpho.getBorrowRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) +```js +const readOnly = await account.toReadOnlyAccount() ``` -### `quoteBorrow(options, config?)` +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. -Quote the fee for borrowing. +## Protect key material -```javascript -const quote = await morpho.quoteBorrow({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n -}) -``` +- 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. -### `repay(options, config?)` +## Next steps -Repay assets to the configured Morpho Blue market. +- [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) -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 +## 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. -```javascript -const tx = await morpho.repay({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 'max' -}) -``` +An RGB transfer is invoice-driven: the recipient generates an invoice and the sender funds, signs, and broadcasts the transfer. -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. + +Community modules are developed and maintained independently by third-party contributors. -### `getRepayRequirements(options, requirementOptions?)` +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. + -Return Morpho SDK approval or signature requirements for repayment. +## 1. Obtain a recipient invoice -```javascript -const requirements = await morpho.getRepayRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 'max' +On the receiving wallet: + +```js +const receive = recipientAccount.receiveAsset({ + assetId, + amount: 100, + witness: false, }) + +const rgbInvoice = receive.invoice ``` -### `quoteRepay(options, config?)` +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. -Quote the fee for repayment. +## 2. Build the transfer -```javascript -const quote = await morpho.quoteRepay({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 'max' -}) +```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. -### `withdrawCollateral(options, config?)` +## 3. Quote and enforce policy -Withdraw collateral from the configured Morpho Blue market. +```js +const maximumFee = 2_000n +const quote = await senderAccount.quoteTransfer(transfer) -Options: +if (quote.fee > maximumFee) { + throw new Error('Quoted RGB transfer fee exceeds the application limit') +} +``` -- `token` (`string`): configured market collateral token -- `amount` (`number | bigint`): amount in base units -- `to` (`string`, optional): must equal the connected wallet address when set +`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. -```javascript -const tx = await morpho.withdrawCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n -}) -``` + +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. + -### `quoteWithdrawCollateral(options, config?)` +## 4. Send once -Quote the fee for withdrawing collateral. +```js +const result = await senderAccount.transfer(transfer) -```javascript -const quote = await morpho.quoteWithdrawCollateral({ - token: 'COLLATERAL_TOKEN_ADDRESS', - amount: 1000000000000000000n +console.log({ + txid: result.hash, + estimatedFee: result.fee.toString(), }) ``` ---- - -## Position Reads - -### `getVaultPosition(account?)` +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`. -Read this or another account's configured vault position. +## 5. Reconcile state -Returns: +```js +senderAccount.refreshWallet() -```javascript -{ - shares: bigint, - assets: bigint, - vaultAddress: Address -} +const transfers = senderAccount.listTransfers(assetId) +const receipt = await senderAccount.getTransferReceipt(result.hash) ``` -### `getMarketPosition(account?)` +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. -Read this or another account's configured market position. +## Operational cautions -Returns: +- 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. -```javascript -{ - supplyShares: bigint, - borrowShares: bigint, - borrowAssets: bigint, - collateral: bigint, - marketId: string -} -``` +## Next steps -### `getAccountData(account?)` +- [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) -Read combined configured vault and market position data. +*** -Returns: +## 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. -```javascript -{ - vaultShares: bigint, - vaultAssets: bigint, - marketSupplyShares: bigint, - marketBorrowShares: bigint, - marketBorrowAssets: bigint, - collateral: bigint, - vaultAddress: Address, - marketId: string -} -``` +Use these guides for the single-account, on-chain RGB wallet in `@utexo/wdk-wallet-rgb@2.0.3`. -## Rules & Notes + +Community modules are developed and maintained independently by third-party contributors. -- 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. +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. + - -Presets, explicit targets, and Morpho SDK options + +Install the package and open the index-0 account. - -Get started with Morpho lending operations + +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. -*** - -### Need Help? +## 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) *** -## 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 +## 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. -# Configuration +WDK Core is the main runtime for registering and managing wallet, protocol, middleware, and transaction policy modules through one interface. -## Installation +Use WDK Core to: -Install the Morpho lending module with the EVM wallet module used by the examples and the `viem` peer dependency: +- Register wallet managers for the chains your app supports. +- Attach swap, bridge, lending, fiat, and swidge protocol providers to accounts. +- Decorate accounts with middleware before they reach the application. +- Register local transaction policies that allow, deny, or simulate write operations before they execute. -```bash title="Install with npm" -npm install @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem -``` +## Next Steps -```bash title="Install with pnpm" -pnpm add @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem -``` + + +Get started with WDK in a Node.js environment + + +Get started with WDK's configuration + + +Get started with WDK's API + + +Get started with WDK's usage + + +Learn what `dispose()` clears and how to clean up app-owned seed buffers. + + +Allow, deny, and simulate local write operations before execution + + -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 +## Need Help? -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) -``` +## WDK Core API Reference +URL: https://docs.wdk.tether.io/sdk/core-module/api-reference +Description: Complete API documentation for @tetherto/wdk -Parameters: +## Table of Contents -- `account`: `WalletAccountEvm`, `WalletAccountReadOnlyEvm`, `WalletAccountEvmErc4337`, or `WalletAccountReadOnlyEvmErc4337` -- `options`: Morpho target configuration +| 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 | -The wallet account must include a provider. Read-only accounts can read positions and quote transactions; mutating methods require a writable account. +## WDK -## Presets +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. -Built-in presets target Ethereum mainnet USDT earn and borrow flows. +### Constructor -```javascript title="Use built-in presets" -const morpho = new MorphoProtocolEvm(account, { - presets: { - earn: 'steakhouse-prime-instant', - borrow: 'wbtc' - } -}) +```javascript title="Constructor" +new WDK(seed) ``` -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 | +**Parameters:** +- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes -## Explicit Targets +**Example:** +```javascript title="Initialize WDK" +import WDK from '@tetherto/wdk' -Use explicit targets when you need a vault or market outside the built-in presets. +// With seed phrase +const wdk = new WDK('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about') -```javascript title="Use explicit Morpho targets" -const morpho = new MorphoProtocolEvm(account, { - chainId: 1, - earnVaultAddress: '0x23f5E9c35820f4baB695Ac1F19c203cC3f8e1e11', - borrowMarketId: '0xe7e9694b754c4d4f7e21faf7223f6fa71abaeb10296a4c43a54a7977149687d2' -}) +// With seed bytes +const seedBytes = new Uint8Array([...]) +const wdk2 = new WDK(seedBytes) ``` -When you use `earnVaultAddress`, `borrowMarketParams`, or `borrowMarketId` directly, pass `chainId`. The adapter uses it to guard transaction building if a browser wallet switches chains. +### Methods -Use `borrowMarketParams` when you already know the full Morpho Blue market configuration: +| 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` | - | -```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 - } -}) -``` +##### `registerWallet(blockchain, wallet, config)` +Registers a new wallet manager for a specific blockchain. -`InputMarketParams` contains `loanToken`, `collateralToken`, `oracle`, `irm`, and `lltv`. Confirm explicit market params against Morpho market data before using them in production. +**Type Parameters:** +- `W`: `typeof WalletManager` - A class that extends the `@tetherto/wdk-wallet`'s `WalletManager` class -## Options +**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 -| 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 | +**Returns:** `WDK` - The WDK instance (supports method chaining) -## Native Amounts +**Throws:** Error if a wallet is already registered for the same blockchain. Call `dispose([blockchain])` before registering a replacement wallet for that blockchain. -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. +**Example:** +```javascript title="Register Wallets" +import WDK from '@tetherto/wdk' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import WalletManagerTon from '@tetherto/wdk-wallet-ton' -```javascript title="Supply with native amount" -await morpho.supply({ - token: 'WRAPPED_NATIVE_TOKEN_ADDRESS', - nativeAmount: 1000000000000000n -}) -``` +const wdk = new WDK(seedPhrase) -## ERC-4337 Config Overrides +// Register EVM wallet +wdk.registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' +}) -When using `WalletAccountEvmErc4337`, mutating methods and quote helpers accept an optional second `config` argument for the wallet module's per-call gas payment settings. +// Register TON wallet +wdk.registerWallet('ton', WalletManagerTon, { + tonApiKey: 'YOUR_TON_API_KEY', + tonApiEndpoint: 'https://tonapi.io' +}) -```javascript title="Supply with an ERC-4337 config override" -await morpho.supply( - { - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) +// Method chaining +const wdk2 = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerWallet('ton', WalletManagerTon, tonWalletConfig) ``` -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 - - - -*** +##### `registerProtocol(blockchain, label, protocol, config)` +Registers a protocol globally for all accounts of a specific blockchain. -### Need Help? +For swidge integrations, pass a concrete provider class that extends `SwidgeProtocol` 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 -*** +**Parameters:** +- `blockchain` (string): The name of the blockchain +- `label` (string): Unique label for the protocol (must be unique per blockchain and protocol type) +- `protocol` (P): The protocol class +- `config` (`ConstructorParameters

[1]`): The protocol configuration -## 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. +**Returns:** `WDK` - The WDK instance (supports method chaining) -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. +**Example:** +```javascript title="Register Protocols" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' -## Installation +// Register swap protocol for Ethereum +wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, { + apiKey: 'YOUR_velora_API_KEY' +}) -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: +// Register bridge protocol for Ethereum +wdk.registerProtocol('ethereum', 'usdt0', Usdt0ProtocolEvm) -```bash title="Install with npm" -npm install @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem -``` +// Register a concrete swidge provider for Ethereum +wdk.registerProtocol('ethereum', 'swidge', MySwidgeProtocol, swidgeProtocolConfig) -```bash title="Install with pnpm" -pnpm add @morpho-org/wdk-protocol-lending-morpho-evm @tetherto/wdk-wallet-evm viem +// Method chaining +const wdk2 = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) ``` -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): +##### `registerMiddleware(blockchain, middleware)` +Registers middleware for account decoration and enhanced functionality. -```javascript title="Create MorphoProtocolEvm" -import MorphoProtocolEvm from '@morpho-org/wdk-protocol-lending-morpho-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +**Parameters:** +- `blockchain` (string): The name of the blockchain +- `middleware` (`(account: A) => Promise`): Middleware function called when deriving accounts -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +**Returns:** `WDK` - The WDK instance (supports method chaining) -const morpho = new MorphoProtocolEvm(account, { - presets: { - earn: 'sky-money-usdt-savings', - borrow: 'wsteth' - } +**Example:** +```javascript title="Register Middleware" +// Simple logging middleware +wdk.registerMiddleware('ethereum', async (account) => { + console.log('New account:', await account.getAddress()) }) -``` -## Prerequisites +// Failover cascade middleware +import { getFailoverCascadeMiddleware } from '@tetherto/wdk-wrapper-failover-cascade' - -**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.registerMiddleware('ethereum', getFailoverCascadeMiddleware({ + fallbackOptions: { + retries: 3, + delay: 1000 + } +})) -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). +// Method chaining +const wdk2 = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) + .registerMiddleware('ethereum', async (account) => { + console.log('New account:', await account.getAddress()) + }) +``` -## Requirements before actions +##### `registerPolicy(policies, options?)` +Registers one or more local transaction policies on the WDK instance. -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. +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. -```javascript title="Check supply requirements" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +Evaluation order: -const requirements = await morpho.getSupplyRequirements({ - token: USDT, - amount: 1000000n -}) +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. -console.log('Requirements:', requirements) -``` +For policy scoping, default-deny behavior, account-level overrides, and protocol simulation examples, see [Transaction Policies](/sdk/core-module/guides/transaction-policies). -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`. +**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. -## Next Steps +**Returns:** `WDK` - The WDK instance (supports method chaining) -- [Lending operations](lending-operations) -- [Handle errors](handle-errors) +**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. -*** +**Example:** +```typescript title="Register A Send Policy" +import WDK, { PolicyViolationError } from '@tetherto/wdk' -## 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. +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 + } + ] + } + ] + }) -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. +const account = await wdk.getAccount('ethereum', 0) -## Operation errors +const simulation = await (account as any).simulate.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000000n +}) -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`: +if (simulation.decision === 'DENY') { + console.warn(simulation.reason) +} -```javascript title="Handle a failed supply" try { - await morpho.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 0n + await account.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000000n }) -} catch (e) { - console.error('Morpho lending failed:', e.message) - - if (e.message.includes('amount')) { - console.log('Amount must be greater than zero') +} catch (error) { + if (error instanceof PolicyViolationError) { + console.error(error.reason) } } ``` -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 +Supported `PolicyOperation` values are `sendTransaction`, `signTransaction`, `transfer`, `approve`, `sign`, `signTypedData`, `signAuthorization`, `delegate`, `revokeDelegation`, `swap`, `bridge`, `supply`, `withdraw`, `borrow`, `repay`, `buy`, `sell`, `swidge`, and `*`. -Use this checklist to map the most common failures to fixes: +Use `sign` for message-style signing in this release. `signMessage` and `signHash` are not valid policy operation names. -| 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 | +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`. -## Requirement errors +##### `getAccount(blockchain, index?)` +Returns a wallet account for a specific blockchain and index using BIP-44 derivation. -Requirement helpers can fail if the configured target, token, account, or provider cannot produce a valid Morpho SDK action. +**Parameters:** +- `blockchain` (string): The name of the blockchain (e.g., "ethereum") +- `index` (number, optional): The index of the account to get (default: 0) -```javascript title="Handle requirement errors" -try { - const requirements = await morpho.getBorrowRequirements({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) +**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. - console.log('Borrow requirements:', requirements) -} catch (e) { - console.error('Requirement lookup failed:', e.message) -} -``` +**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. -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. +**Example:** +```javascript title="Get Account" +// Get first account (index 0) +const account = await wdk.getAccount('ethereum', 0) -## Quote errors +// Get second account (index 1) +const account1 = await wdk.getAccount('ethereum', 1) -You can isolate quote failures from write failures when you only need an estimate: +// Default index (0) +const defaultAccount = await wdk.getAccount('ethereum') -```javascript title="Handle quote errors" +// This will throw an error if no wallet registered for 'tron' try { - const quote = await morpho.quoteBorrow({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) - - console.log('Borrow fee:', quote.fee) -} catch (e) { - console.error('Quote failed:', e.message) + const tronAccount = await wdk.getAccount('tron', 0) +} catch (error) { + console.error('No wallet registered for tron blockchain') } ``` - -See [Rules & Notes](/sdk/lending-modules/lending-morpho-evm/api-reference) for address, token, amount, and target validation expectations. - - -## Best Practices +##### `getAccountByPath(blockchain, path)` +Returns a wallet account for a specific blockchain and BIP-44 derivation path. -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. +**Parameters:** +- `blockchain` (string): The name of the blockchain (e.g., "ethereum") +- `path` (string): The derivation path (e.g., "0'/0/0") -```javascript title="Dispose after a Morpho lending session" -try { - await morpho.supply({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }) -} finally { - account.dispose() -} -``` +**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. -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. +**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") -- [Lending operations](lending-operations) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-morpho-evm/api-reference) +// Different derivation path +const customAccount = await wdk.getAccountByPath('ton', "1'/2/3") +``` -*** +##### `getFeeRates(blockchain)` +Returns current fee rates for a registered blockchain. -## 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. +**Parameters:** +- `blockchain` (string): The blockchain identifier passed to `registerWallet()` -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`. +**Returns:** `Promise` - The fee rates in base units -## Vault supply +**Throws:** Error if no wallet has been registered for the given blockchain. -Deposit into the configured Morpho Vault V2 target with [`supply()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +**Example:** +```javascript title="Get Fee Rates" +const feeRates = await wdk.getFeeRates('ethereum') +console.log('Fee rates:', feeRates) +``` -```javascript title="Supply USDT to the configured vault" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +##### `dispose(blockchains?)` +Disposes all registered wallets when called without arguments, or only the wallets for the named blockchains when you pass a string array. -const requirements = await morpho.getSupplyRequirements({ - token: USDT, - amount: 1000000n -}) +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. -console.log('Supply requirements:', requirements) +**Parameters:** +- `blockchains` (string[], optional): The blockchain identifiers to dispose. Omit this parameter to dispose every registered wallet. -const tx = await morpho.supply({ - token: USDT, - amount: 1000000n -}) +**Example:** +```javascript title="Dispose WDK" +// Dispose all registered wallets +wdk.dispose() -console.log('Supply tx hash:', tx.hash) +// Dispose only one registered wallet +wdk.dispose(['ethereum']) ``` -The `token` must match the configured vault asset. +### Static Methods -## Vault withdraw +| Method | Description | Returns | +|--------|-------------|---------| +| `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` | -Withdraw from the configured vault with [`withdraw()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +##### `getRandomSeedPhrase(wordCount?)` +Returns a random BIP-39 seed phrase. Supports both 12-word (128-bit entropy) and 24-word (256-bit entropy) seed phrases. -```javascript title="Withdraw USDT from the configured vault" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +**Parameters:** +- `wordCount` (12 | 24, optional): The number of words in the seed phrase. Defaults to 12. -const tx = await morpho.withdraw({ - token: USDT, - amount: 1000000n -}) +**Returns:** `string` - The seed phrase -console.log('Withdraw tx hash:', tx.hash) +**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" + +// 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" ``` -If you pass `to`, it must equal the connected wallet address. +##### `isValidSeed(seed)` +Checks if a seed phrase or seed bytes value is valid. -## Collateral +**Parameters:** +- `seed` (string | Uint8Array): The seed phrase or seed bytes to validate -Supply collateral to the configured Morpho Blue market with [`supplyCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +**Returns:** `boolean` - True if the seed is valid -```javascript title="Supply collateral" -const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' +**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 -const requirements = await morpho.getSupplyCollateralRequirements({ - token: COLLATERAL, - amount: 1000000000000000000n -}) +const isInvalid = WDK.isValidSeed('invalid seed phrase') +console.log('Seed phrase valid:', isInvalid) // false +``` -console.log('Collateral requirements:', requirements) +## IWalletAccount -const tx = await morpho.supplyCollateral({ - token: COLLATERAL, - amount: 1000000000000000000n -}) +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()`. -console.log('Collateral supply tx hash:', tx.hash) -``` +### Methods -Withdraw collateral with [`withdrawCollateral()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +| 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` | - | -```javascript title="Withdraw collateral" -const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' +##### `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. -const tx = await morpho.withdrawCollateral({ - token: COLLATERAL, - amount: 1000000000000000000n +**Parameters:** +- `tx` (Transaction): Module-specific transaction object. For example, EVM accounts accept `EvmTransaction`, and Bitcoin accounts accept `BtcTransaction`. + +**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) + +const signedTransaction = await account.signTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 1000000000000000n }) -console.log('Collateral withdrawal tx hash:', tx.hash) +console.log('Signed transaction:', signedTransaction) ``` -The collateral token must match the configured market collateral token. If you pass `to`, it must equal the connected wallet address. +## IWalletAccountWithProtocols -## Borrow +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`. -Borrow from the configured market with [`borrow()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +### Methods -```javascript title="Borrow USDT" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +| 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 | -const requirements = await morpho.getBorrowRequirements({ - token: USDT, - amount: 1000000n -}) +##### `registerProtocol(label, protocol, config)` +Registers a new protocol for this specific account. -console.log('Borrow requirements:', requirements) +**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 -const tx = await morpho.borrow({ - token: USDT, - amount: 1000000n +**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 + +**Returns:** `IWalletAccountWithProtocols` - The account instance (supports method chaining) + +**Example:** +```javascript title="Register Protocol for Account" +import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' + +const account = await wdk.getAccount('ethereum', 0) + +// Register protocol for this specific account +account.registerProtocol('usdt0', Usdt0ProtocolEvm, { + apiKey: 'YOUR_API_KEY' }) -console.log('Borrow tx hash:', tx.hash) +// Method chaining +const account2 = await wdk.getAccount('ethereum', 1) + .registerProtocol('usdt0', Usdt0ProtocolEvm, usdt0ProtocolConfig) ``` -The borrow token must match the configured market loan token. +##### `getSwapProtocol(label)` +Returns the swap protocol with the given label. -## Repay +**Parameters:** +- `label` (string): The protocol label -Repay by asset amount, or pass `amount: 'max'` to repay current borrow shares: +**Returns:** `ISwapProtocol` - The swap protocol instance -```javascript title="Repay max borrow shares" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' +**Throws:** Error if no swap protocol with the given label has been registered -const requirements = await morpho.getRepayRequirements({ - token: USDT, - amount: 'max' -}) +**Example:** +```javascript title="Get Swap Protocol" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -console.log('Repay requirements:', requirements) +// Register swap protocol +account.registerProtocol('velora', veloraProtocolEvm, veloraProtocolConfig) -const tx = await morpho.repay({ - token: USDT, - amount: 'max' +// Get swap protocol +const velora = account.getSwapProtocol('velora') + +// Use the protocol +const swapResult = await velora.swap({ + tokenIn: '0x...', + tokenOut: '0x...', + tokenInAmount: 1000000n }) -console.log('Repay tx hash:', tx.hash) +// This will throw an error +// try { +// const uniswap = account.getSwapProtocol('uniswap') +// } catch (error) { +// console.error('No swap protocol with label "uniswap" found') +// } ``` - -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. +##### `getBridgeProtocol(label)` +Returns the bridge protocol with the given label. -| Requirement source | Final action | -|--------------------|--------------| -| `getSupplyRequirements()` | `supply()` | -| `getSupplyCollateralRequirements()` | `supplyCollateral()` | -| `getBorrowRequirements()` | `borrow()` | -| `getRepayRequirements()` | `repay()` | +**Parameters:** +- `label` (string): The protocol label -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`. +**Returns:** `IBridgeProtocol` - The bridge protocol instance -```javascript title="Resolve EOA requirements before the final action" -async function resolveRequirements({ account, walletClient, userAddress, requirements }) { - let requirementSignature +**Throws:** Error if no bridge protocol with the given label has been registered - for (const requirement of requirements) { - if (typeof requirement.sign === 'function') { - requirementSignature = await requirement.sign(walletClient, userAddress) - continue - } +**Example:** +```javascript title="Get Bridge Protocol" +import Usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' - await account.sendTransaction({ - to: requirement.to, - value: requirement.value, - data: requirement.data - }) - } +// Register bridge protocol +account.registerProtocol('usdt0', Usdt0ProtocolEvm) - return requirementSignature -} +// Get bridge protocol +const usdt0 = account.getBridgeProtocol('usdt0') -const requirements = await morpho.getSupplyRequirements({ - token: USDT, +// Use the protocol +await account.approve({ + token: '0x...', + spender: '0x...', // OFT or bridge spender address amount: 1000000n }) -const requirementSignature = await resolveRequirements({ - account, - walletClient, - userAddress, - requirements -}) - -const tx = await morpho.supply({ - token: USDT, +const bridgeResult = await usdt0.bridge({ + targetChain: 'arbitrum', + recipient: '0x...', + token: '0x...', amount: 1000000n, - requirementSignature + oftContractAddress: '0x...' // Same address used as approval spender }) ``` -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()`: +##### `getLendingProtocol(label)` +Returns the lending protocol with the given label. -```javascript title="Resolve borrow authorization requirements" -const requirements = await morpho.getBorrowRequirements({ - token: USDT, - amount: 1000000n -}) +**Parameters:** +- `label` (string): The protocol label -for (const requirement of requirements) { - await account.sendTransaction({ - to: requirement.to, - value: requirement.value, - data: requirement.data - }) -} +**Returns:** `ILendingProtocol` - The lending protocol instance -await morpho.borrow({ - token: USDT, +**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' + +// Register lending protocol +account.registerProtocol('aave', AaveProtocolEvm, aaveProtocolConfig) + +// Get lending protocol +const aave = account.getLendingProtocol('aave') + +// Use the protocol +const supplyResult = await aave.supply({ + token: '0x...', 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. +##### `getFiatProtocol(label)` +Returns the fiat protocol with the given label. - -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. - +**Parameters:** +- `label` (string): The protocol label -## Quotes before sending +**Returns:** `IFiatProtocol` - The fiat protocol instance -Quote helpers build the target transaction and return the account-level fee estimate without sending it: +**Throws:** Error if no fiat protocol with the given label has been registered -```javascript title="Quote Morpho operations" -const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' -const COLLATERAL = 'COLLATERAL_TOKEN_ADDRESS' +**Example:** +```javascript title="Get Fiat Protocol" +import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay' -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' }) +// Register fiat protocol +account.registerProtocol('moonpay', MoonPayProtocol, moonpayProtocolConfig) -console.log({ - supplyQuote, - withdrawQuote, - collateralQuote, - borrowQuote, - repayQuote +// Get fiat protocol +const moonpay = account.getFiatProtocol('moonpay') + +const buyUrl = await moonpay.buy({ + cryptoAsset: 'usdt', + fiatCurrency: 'usd', + fiatAmount: 10000n }) ``` - -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. +##### `getSwidgeProtocol(label)` +Returns the swidge protocol with the given label. -```javascript title="Supply with an ERC-4337 paymaster override" -const result = await morpho.supply( - { - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - amount: 1000000n - }, - { - paymasterToken: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - } - } -) +The examples below use `MySwidgeProtocol` as the concrete provider class supplied by your swidge provider module. -console.log('Supply hash:', result.hash) -``` +**Parameters:** +- `label` (string): The protocol label -Use token addresses that exist on the same chain as the smart account RPC. +**Returns:** `ISwidgeProtocol` - The swidge protocol instance -## Reading positions +**Throws:** Error if no swidge protocol with the given label has been registered -Read the configured vault position with [`getVaultPosition()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +**Example:** +```javascript title="Get Swidge Protocol" +// Register a concrete provider class that extends SwidgeProtocol +account.registerProtocol('swidge', MySwidgeProtocol, swidgeProtocolConfig) -```javascript title="Read configured vault position" -const vaultPosition = await morpho.getVaultPosition() +// Get swidge protocol +const swidge = account.getSwidgeProtocol('swidge') -console.log({ - shares: vaultPosition.shares, - assets: vaultPosition.assets, - vaultAddress: vaultPosition.vaultAddress +const quote = await swidge.quoteSwidge({ + fromToken: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + toToken: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', + toChain: 'arbitrum', + fromTokenAmount: 1000000n }) ``` -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() +## Complete Example -console.log({ - supplyShares: marketPosition.supplyShares, - borrowShares: marketPosition.borrowShares, - borrowAssets: marketPosition.borrowAssets, - collateral: marketPosition.collateral, - marketId: marketPosition.marketId -}) -``` +```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' -Read both configured positions with [`getAccountData()`](/sdk/lending-modules/lending-morpho-evm/api-reference): +// 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) -```javascript title="Read combined Morpho account data" -const data = await morpho.getAccountData() +// Get accounts +const accountEth = await wdk.getAccount('ethereum', 3) +const accountTon = await wdk.getAccountByPath('ton', "1'/2/3") -console.log({ - vaultAssets: data.vaultAssets, - marketBorrowAssets: data.marketBorrowAssets, - collateral: data.collateral, - vaultAddress: data.vaultAddress, - marketId: data.marketId +// Use wallet account methods +const { hash, fee } = await accountEth.sendTransaction({ + to: '0x...', + value: 1000000000000000000n // 1 ETH }) -``` -## Next Steps +// Use protocols +const velora = accountEth.getSwapProtocol('velora') +const swapResult = await velora.swap(swapOptions) -- [Handle errors](handle-errors) -- [Get started](get-started) -- [API reference](/sdk/lending-modules/lending-morpho-evm/api-reference) +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 bridgeResult = await usdt0.bridge(bridgeOptions) -*** +// Clean up +wdk.dispose() +``` -## 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 +## Types -# Usage +### WdkAccount -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. +`WdkAccount` is the exported type returned by `getAccount()` and `getAccountByPath()`. It combines the writable wallet methods with the protocol registration and lookup surface. This is a type-only export; there is no runtime `WdkAccount` value. - - -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. - - +```typescript title="Use WdkAccount" +import WDK, { type WdkAccount } from '@tetherto/wdk' - - -Get started with WDK in a Node.js environment - - -Presets, explicit targets, and Morpho SDK options - - -Methods and parameters for MorphoProtocolEvm - - +async function getBitcoinAccount(wdk: WDK): Promise { + return wdk.getAccount('bitcoin') +} +``` - +The published definition is `IWalletAccount & IWalletAccountWithProtocols`. Concrete wallet packages can expose additional methods beyond this shared shape. The declared return type of `IWalletAccountWithProtocols.registerProtocol()` remains `IWalletAccountWithProtocols`, so assigning the result of that chained call can narrow away the writable-account methods in TypeScript. -*** +### FeeRates -## Swap Modules Overview -URL: https://docs.wdk.tether.io/sdk/swap-modules -Description: Explore WDK swap modules for token swap integrations across supported providers. +```typescript title="Type: FeeRates" +interface FeeRates { + [blockchain: string]: { + normal: number; + fast: number; + }; +} +``` -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. +### Middleware Function - -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. - +```typescript title="Type: MiddlewareFunction" +type MiddlewareFunction = ( + account: A +) => Promise; +``` -## Swap Protocol Modules +### 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' + | '*' + +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 +} -DeFi swap functionality for token exchanges across different DEXs: +interface Policy { + id: string + name: string + scope: PolicyScope + wallet?: string | string[] + accounts?: Array + rules: PolicyRule[] +} -| 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/) | +interface RegisterPolicyOptions { + state?: Record + conditionTimeoutMs?: number +} -## Next steps +interface SimulationTraceEntry { + scope: PolicyScope + policy_id: string + rule_name: string + matched: boolean + error?: string +} -To get started with WDK modules, follow these steps: +interface SimulationResult { + decision: 'ALLOW' | 'DENY' + policy_id: string | null + matched_rule: string | null + reason: string | null + trace: SimulationTraceEntry[] +} +``` -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 +`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. -You can also: +`override_broader_scope` is valid only on account-scoped `ALLOW` rules. When that rule matches, WDK allows the call without evaluating project-scoped policies. -- 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 +`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. -## Swidge provider routes +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`. -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. +### Protocol Types -For a released community provider, see [Orchestra](/sdk/swidge-modules/swidge-orchestra), which implements Swidge for BTC and stablecoin routes returned by Flashnet Orchestra. +```typescript title="Types: Protocol Interfaces" +// Swap Protocol +interface ISwapProtocol { + swap(options: SwapOptions): Promise; +} -*** +// Bridge Protocol +interface IBridgeProtocol { + bridge(options: BridgeOptions): Promise; +} -## 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 +// Lending Protocol +interface ILendingProtocol { + supply(options: LendingOptions): Promise; + withdraw(options: LendingOptions): Promise; + borrow(options: LendingOptions): Promise; + repay(options: LendingOptions): Promise; +} -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. +// 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; +} +``` -## Features +### Swidge Protocol -- **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 +`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. -## Supported Networks +| 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` | -Works with EVM networks supported by velora (e.g., Ethereum, Polygon, Arbitrum, etc.). A working RPC provider is required. +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. -## Wallet Compatibility +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. -The swap service supports multiple EVM wallet types: +```typescript title="Type: ISwidgeProtocol Options and Results" +type SwidgeProtocolConfig = { + maxNetworkFeeBps?: number | bigint; + maxProtocolFeeBps?: number | bigint; +}; -- **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 +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 +); -## Key Components +type SwidgeStatus = + | 'pending' | 'action-required' | 'completed' | 'failed' + | 'refund-pending' | 'refunded' | 'cancelled' | 'expired' | 'partial'; +``` -- **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 +*** ## Next Steps - -Get started with WDK in a Node.js environment + +Get started with WDK's configuration - -Get started with WDK's velora Swap Protocol configuration + +Get started with WDK's Usage - -Get started with WDK's velora Swap Protocol API + +Explore blockchain-specific wallet modules - -Get started with WDK's velora Swap Protocol usage + +Cross-chain USD₮0 bridges *** -## Need Help? +### 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 +## WDK Core Configuration +URL: https://docs.wdk.tether.io/sdk/core-module/configuration +Description: Configuration options and settings for @tetherto/wdk -## Class: VeloraProtocolEvm +# Configuration -Main class for velora token swaps on EVM. +## WDK Manager Configuration -### Constructor +```javascript title="Create WDK Instance" +import WDK from '@tetherto/wdk' -```javascript -new VeloraProtocolEvm(account, config?) +const wdk = new WDK(seedPhrase) ``` -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 }) -``` +The WDK Manager itself only requires a seed phrase for initialization. Configuration is done through the registration of wallets and protocols. -### Methods +## Wallet Registration Configuration -| 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}>` | - ---- - -### `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) +```javascript title="Register WDK Wallet" +import WDK from '@tetherto/wdk' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import WalletManagerTon from '@tetherto/wdk-wallet-ton' -Config (ERC‑4337 only): -- `paymasterToken` (`string`, optional): Token symbol/address for fee sponsorship -- `swapMaxFee` (`bigint`, optional): Per‑swap fee cap (wei) +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' + }) +``` -Returns: -- Standard account: `{ hash, fee, tokenInAmount, tokenOutAmount, approveHash?, resetAllowanceHash? }` -- ERC‑4337 account: `{ hash, fee, tokenInAmount, tokenOutAmount }` (approve may be bundled) -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. +## Protocol Registration Configuration -Example: +```javascript title="Register WDK Protocol" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -```javascript -const tx = await swap.swap({ - tokenIn: '0xdAC17F...ec7', // USD₮ - tokenOut: '0xC02a...6Cc2', // WETH - tokenInAmount: 1000000n -}) +const wdk = new WDK(seedPhrase) + .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { + apiKey: 'YOUR_velora_API_KEY' + }) ``` ---- - -### `quoteSwap(options, config?)` -Get estimated fee and token in/out amounts. -Options are the same as `swap`. +## Configuration Options -Returns: `{ fee, tokenInAmount, tokenOutAmount }` +### Wallet Configuration -Config (ERC‑4337 only): -- `paymasterToken` (`string`, optional): Token symbol/address for fee sponsorship +Each wallet manager requires its own configuration object when registered. The configuration depends on the specific wallet module being used. -Works with read‑only accounts. +#### EVM Wallet Configuration -Example: +```javascript title="Ethereum WDK Wallet Configuration" +const ethereumWalletConfig = { + provider: 'https://eth.drpc.org', // RPC endpoint + // Additional EVM-specific configuration options +} -```javascript -const quote = await swap.quoteSwap({ - tokenIn: '0xdAC17F...ec7', // USD₮ - tokenOut: '0xC02a...6Cc2', // WETH - tokenOutAmount: 500000000000000000n // 0.5 WETH -}) +wdk.registerWallet('ethereum', WalletManagerEvm, ethereumWalletConfig) ``` ---- - -## 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) +#### TON Wallet Configuration ---- +```javascript title="TON WDK Wallet Configuration" +const tonWalletConfig = { + tonClient: { + secretKey: 'YOUR_TON_API_KEY', + url: 'https://toncenter.com/api/v2/jsonRPC' + } +} -## Types +wdk.registerWallet('ton', WalletManagerTon, tonWalletConfig) +``` -- `swapMaxFee: bigint` — Upper bound for gas fees (wei) -- `tokenInAmount/tokenOutAmount: bigint` — ERC‑20 base units -- `paymasterToken: string` — token symbol or address (AA only) - - -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 - - +### Protocol Configuration -*** +Protocols also require their own configuration objects when registered. -## Need Help? +#### Swap Protocol Configuration - +```javascript title="Swap WDK Protocol Configuration" +const veloraProtocolConfig = { + apiKey: 'YOUR_velora_API_KEY', + baseUrl: 'https://apiv5.velora.io' +} -*** +wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, veloraProtocolConfig) +``` -## 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' +### Middleware Configuration -// Create wallet account first -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +Middleware functions can be registered to enhance account functionality. -// Create swap service with configuration -const swapProtocol = new VeloraProtocolEvm(account, { - swapMaxFee: 200000000000000n // Optional: Max swap fee in wei +```javascript title="Middleware WDK Protocol Configuration" +// Simple logging middleware +wdk.registerMiddleware('ethereum', async (account) => { + console.log('New account created:', await account.getAddress()) }) ``` -## 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' - } -) +## Environment Variables -// Read-only account (quotes only) -const readOnly = new WalletAccountReadOnlyEvm( - '0xYourAddress', - { - provider: 'https://ethereum-rpc.publicnode.com' - } -) +For production applications, consider using environment variables for sensitive configuration: -// Create swap service -const swapProtocol = new VeloraProtocolEvm(account, { - swapMaxFee: 200000000000000n -}) +```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 + }) ``` -## Configuration Options -### Swap Max Fee +## Configuration Validation -The `swapMaxFee` option sets an upper bound for total gas costs to prevent excessive fees. +The WDK Manager will validate configurations when wallets and protocols are registered: -**Type:** `bigint` (optional) -**Unit:** Wei +- **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 -**Examples:** +## Error Handling -```javascript -const config = { - // Cap total gas fee to 0.0002 ETH (in wei) - swapMaxFee: 200000000000000n, +Configuration errors will be thrown during registration: + +```javascript title="Configuration errors" +try { + wdk.registerWallet('ethereum', InvalidWalletClass, config) +} catch (error) { + console.error('Wallet registration failed:', error.message) } -// Usage example try { - const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USD₮ (6 decimals) - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH (18 decimals) - tokenInAmount: 1000000n - }) + wdk.registerProtocol('ethereum', 'velora', veloraProtocolEvm, invalidConfig) } catch (error) { - if (error.message.includes('max fee')) { - console.error('Swap stopped: Fee too high') - } + console.error('Protocol registration failed:', error.message) } ``` -## 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: +## Next Steps -```javascript -import { WalletAccountEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' -const aa = new WalletAccountEvmErc4337(seedPhrase, "0'/0/0", { - chainId: 1, - provider: 'https://arb1.arbitrum.io/rpc', - bundlerUrl: 'YOUR_BUNDLER_URL', - paymasterUrl: 'YOUR_PAYMASTER_URL' -}) + + +Get started with WDK's usage + + +Get started with WDK's API + + +Explore blockchain-specific wallet modules + + +Cross-chain USD₮0 bridges + + -const swapAA = new VeloraProtocolEvm(aa, { swapMaxFee: 200000000000000n }) +*** -const result = await swapAA.swap({ - tokenIn: '0xTokenIn', - tokenOut: '0xTokenOut', - tokenInAmount: 1000000n -}, { - paymasterToken: 'USDT', // Token used to pay for gas - swapMaxFee: 200000000000000n // Per‑swap override -}) -``` +### Need Help? -### Paymaster Token (ERC‑4337) + -The `paymasterToken` option indicates which token the paymaster should use to sponsor gas. +*** -**Type:** `string` (optional) -**Format:** Token symbol or address +## Manage Accounts +URL: https://docs.wdk.tether.io/sdk/core-module/guides/account-management +Description: Learn how to work with accounts and addresses. -**Example:** +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. -```javascript -const result = await swapAA.swap({ - tokenIn: '0xdAC17F...ec7', - tokenOut: '0xC02a...6Cc2', // WETH - tokenInAmount: 1000000n -}, { - paymasterToken: 'USDT' -}) -``` +## Retrieve Accounts -## Network Support +You can retrieve an account using a simple index or a custom derivation path. -velora supports multiple EVM networks (e.g., Ethereum, Polygon, Arbitrum). Ensure your account is configured with a valid provider for the target network. +### By Index (Recommended) -```javascript -// Ethereum Mainnet -const eth = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) +The simplest way to get an account is by its index (starting at `0`). This uses the default derivation path for the specified blockchain. -// Polygon -const polygon = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://polygon-bor-rpc.publicnode.com' -}) +```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) ``` -## Swap Options - -When calling `swap`, provide the swap parameters: +### By Derivation Path (Advanced) -```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) -} +If you need a specific hierarchy, you can request an account by its unique derivation path. -const result = await swapProtocol.swap(swapOptions) +```typescript title="Get Account by Path" +// Custom path for Ethereum +const customEthAccount = await wdk.getAccountByPath('ethereum', "0'/0/1") ``` -### 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. + +The WDK instance caches accounts. If you call `getAccount` twice using the same index, the function will return the same `Account` object instance. + - - -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 - - + +**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. + -*** +## View Addresses -## Need Help? +Once you have an account object, you can retrieve its public blockchain address using the `getAddress` function. - +```typescript title="Get Addresses" +const ethAddress = await ethAccount.getAddress() +console.log('Ethereum address:', ethAddress) +``` -*** +## Check Balances -## 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. +You can check the native token balance of any account (e.g., ETH on Ethereum, TON on TON) by using the `getBalance()` function. -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. +```typescript title="Get Balance" +const balance = await ethAccount.getBalance() +console.log('Balance:', balance) +``` - -Swaps spend tokens and gas on-chain. Use amounts you control and an RPC you trust. - +### Multi-Chain Balance Check -## Basic exact-input swap +Because WDK offers a unified interface, you can easily iterate through multiple chains to fetch balances. -You can sell an exact amount of the input token using [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +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 title="Exact input: USDT to WETH" -const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}) +```typescript title="Check All Balances" +const chains = ['ethereum', 'ton', 'bitcoin'] -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) +for (const chain of chains) { + const account = await wdk.getAccount(chain, 0) + const balance = await account.getBalance() + console.log(`${chain} balance:`, balance) +} ``` -## Exact output swap +## Next Steps -You can receive an exact amount of the output token by passing `tokenOutAmount` to [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference): +Now that you can access your accounts, learn how to [send transactions](/sdk/core-module/guides/transactions). -```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) -``` +## Error Handling +URL: https://docs.wdk.tether.io/sdk/core-module/guides/error-handling +Description: Learn about common errors and best practices. -## Swap with ERC-4337 +# Error Handling & Best Practices -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): +This guide covers recommended patterns for error handling and security when using the WDK. -```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' +## Handling Common Errors -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 -}) +When interacting with multiple chains and protocols, various runtime issues may occur. -const swapAA = new VeloraProtocolEvm(aa, { swapMaxFee: 200000000000000n }) +### Missing Registration -const result = await swapAA.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}, { - paymasterToken: 'USDT', - swapMaxFee: 200000000000000n -}) +The most common error is attempting to access a wallet or protocol that hasn't been registered. -console.log('Swap hash:', result.hash) -console.log('Total fee (wei):', result.fee) +```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) +} ``` -Token addresses must match the chain your account uses (for example, mainnet USD₮ addresses differ from Arbitrum). +Always use `try/catch` blocks when initializing sessions or accessing dynamic features. -## Next Steps +## Memory Management -- [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 +For security, clear wallet state from memory when a session is complete. The WDK provides [`dispose()`](/sdk/core-module/api-reference) for this purpose. -*** +### Seed Lifecycle -## 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. +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. -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. +Use this lifecycle for sessions that need explicit cleanup: -## Installation +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. -Run the following to install [@tetherto/wdk-protocol-swap-velora-evm](https://www.npmjs.com/package/@tetherto/wdk-protocol-swap-velora-evm): +```typescript title="Seed lifecycle cleanup" +import WDK from '@tetherto/wdk' +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' -```bash title="Install with npm" -npm install @tetherto/wdk-protocol-swap-velora-evm +type SeedDecrypter = (encryptedSeed: Uint8Array) => Promise + +async function runWalletSession( + encryptedSeed: Uint8Array, + decryptSeedBytes: SeedDecrypter +) { + let seedBytes: Uint8Array | undefined + let wdk: WDK | undefined + + try { + seedBytes = await decryptSeedBytes(encryptedSeed) + + wdk = new WDK(seedBytes) + .registerWallet('ethereum', WalletManagerEvm, { + provider: 'https://eth.drpc.org' + }) + + const account = await wdk.getAccount('ethereum', 0) + const address = await account.getAddress() + return address + } finally { + wdk?.dispose() + seedBytes?.fill(0) + } +} ``` - -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. +In this example, `decryptSeedBytes()` represents your app's secure storage or decryption layer. It should return seed bytes as a `Uint8Array`. + + +[`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. -## Create the swap protocol +### Disposing the Instance -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): +You can dispose every registered wallet using [`dispose()`](/sdk/core-module/api-reference): -```javascript title="Create VeloraProtocolEvm" -import VeloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' +```typescript title="Dispose WDK" +function endSession(wdk) { + // 1. Dispose registered wallets and account private keys + wdk.dispose() -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://ethereum-rpc.publicnode.com' -}) + // 2. Modify app state to reflect logged-out status + // ... -const swapProtocol = new VeloraProtocolEvm(account, { - swapMaxFee: 200000000000000n -}) + console.log('Session ended, wallet data cleared.') +} ``` -Optional `swapMaxFee` caps the total gas fee in wei for swaps. See [configuration](/sdk/swap-modules/swap-velora-evm/configuration) for environment-specific settings. +### Disposing Specific Wallets -## Supported networks +You can dispose only the wallets you no longer need using [`dispose()`](/sdk/core-module/api-reference): -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. +```typescript title="Dispose Specific Wallets" +// Keep the TON wallet registered, but dispose the Ethereum wallet +wdk.dispose(['ethereum']) +``` -## Next Steps + +**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. + -- [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) +## Security Best Practices -*** +### Environment Variables -## 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. +Never hardcode API keys or seed phrases in your source code. Use environment variables (e.g., `process.env.TON_API_KEY`). -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. +### Secure Storage -## Quote before swapping +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). -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 -}) +## Getting Started +URL: https://docs.wdk.tether.io/sdk/core-module/guides/getting-started +Description: Install and instantiate the WDK Core module. -console.log('Estimated fee (wei):', quote.fee) -console.log('Tokens in (base units):', quote.tokenInAmount) -console.log('Tokens out (base units):', quote.tokenOutAmount) -``` +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. -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): +## 1. Installation -```javascript title="Quote exact output swap" -const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenOutAmount: 500000000000000000n -}) +### Prerequisites -console.log('Estimated fee (wei):', quote.fee) -console.log('Required token in (base units):', quote.tokenInAmount) -``` +Before you begin, ensure you have the following installed: -## Fee estimation +* **[Node.js](https://nodejs.org/)**: version 18 or higher. +* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. -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): +### Install Package -```javascript title="Quote fee before deciding" -const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}) +To install the WDK Core package, run the following command in your terminal: -const maxFee = 200000000000000n -console.log('Quoted fee (wei):', quote.fee, 'cap:', maxFee) +```bash +npm install @tetherto/wdk ``` -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: +This package allows you to manage different blockchain wallets and protocols through a single interface. -```javascript title="Swap when fee is under cap" -const maxFee = 200000000000000n -const quote = await swapProtocol.quoteSwap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n -}) +## 2. Instantiation -if (quote.fee <= maxFee) { - const result = await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n - }) - console.log('Swap hash:', result.hash) -} -``` +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. - -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. - +### Import the Module -## Next Steps +First, import the `WDK` class from the package: -- [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) +```typescript title="Import WDK Core" +import WDK from '@tetherto/wdk' +``` -*** +### Initialize WDK -## 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. +You can initialize `WDK` in two ways: with a [new seed phrase](#generate-a-new-wallet) or an [existing one](#restore-an-existing-wallet). -This guide covers [swap errors](#swap-errors), [quote errors](#quote-errors), and [best practices](#best-practices) for clearing wallet material after use. +#### Generate a New Wallet -## Swap errors +If you are creating a fresh wallet for a user, use the static `getRandomSeedPhrase()` method to generate a secure mnemonic. -You can detect failed swaps by wrapping [`swap()`](/sdk/swap-modules/swap-velora-evm/api-reference) in `try/catch` and inspecting `error.message`: +```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) -```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) +// Or use 12-word seed phrase (default) +// const seedPhrase = WDK.getRandomSeedPhrase() - 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') - } -} +// 2. Initialize the WDK instance with the new seed +const wdk = new WDK(seedPhrase) ``` - -Match string fragments only as a convenience; production apps should prefer stable error codes from your runtime when available. + +**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. -## 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) -} -``` +For cleanup expectations when a wallet session ends, see [Seed Lifecycle](/sdk/core-module/guides/error-handling#seed-lifecycle). -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). +#### Restore an Existing Wallet -## Best Practices +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. -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: +```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 title="Dispose wallet accounts" -try { - await swapProtocol.swap({ - tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - tokenInAmount: 1000000n - }) -} finally { - account.dispose() -} +const wdk = new WDK(existingSeed) ``` -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. +For cleanup expectations when a wallet session ends, see [Seed Lifecycle](/sdk/core-module/guides/error-handling#seed-lifecycle). ## 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) +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/). *** -## 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 +## Configure Middleware +URL: https://docs.wdk.tether.io/sdk/core-module/guides/middleware +Description: Learn how to intercept and enhance wallet operations with middleware. -# Usage +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. -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. +## Register Middleware +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. - - -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. - - +### Logging +This simple middleware logs a message whenever a new account is accessed. - - -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 - - +```typescript title="Logging Middleware" +wdk.registerMiddleware('ethereum', async (account) => { + const address = await account.getAddress() + console.log('Accessed Ethereum account:', address) - + // You can also attach custom properties or wrap methods here +}) +``` -*** +## Use provider failover -## 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. +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. -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. +Install the failover provider package: - -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. - +```bash +npm install @tetherto/wdk-failover-provider +``` -## When to use swidge +Import the provider in your wallet or infrastructure setup: -Use a swidge provider when a route matches any of these shapes: +```typescript +import FailoverProvider from '@tetherto/wdk-failover-provider' +``` -- 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. +Use this when your app depends on external RPC providers and needs a backup route if the primary provider is slow or unavailable. -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. +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. -## Released provider modules +## Next Steps -| 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/) | +Learn about [error handling and best practices](/sdk/core-module/guides/error-handling) to ensure your application is robust and secure. -## Interface methods +*** -Concrete swidge provider modules extend `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. The base interface exposes discovery, quote, execution, and status methods: +## 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. -| 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' -}) +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. -const routeTokens = await swidge.getSupportedTokens({ - fromChain: 'ethereum', - fromToken: '0xSourceToken...', - toChain: 'arbitrum' -}) +## Register Protocols + +You can register protocols globally (for all new accounts). + +### Global Registration (Recommended) + +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. + +### 1. Install Protocol Modules + +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: + +```bash +npm install @tetherto/wdk-protocol-swap-velora-evm && npm install @tetherto/wdk-protocol-bridge-usdt0-evm ``` -`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. +### 2. Register in Code -| 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 | +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. -## Quote a route +First, import the necessary modules: -Call `quoteSwidge()` before execution so the user can review source amount, expected destination amount, minimum output, fees, and quote expiry. +```typescript title="Import Protocols" +import veloraProtocolEvm from '@tetherto/wdk-protocol-swap-velora-evm' +import usdt0ProtocolEvm from '@tetherto/wdk-protocol-bridge-usdt0-evm' +``` -```typescript title="Quote a swidge route" -const quote = await swidge.quoteSwidge({ - fromToken: '0xSourceToken...', - toToken: '0xDestinationToken...', - toChain: 'arbitrum', - recipient: '0xRecipient...', - fromTokenAmount: 1000000n, - slippage: 0.01 -}) +Then, register the protocols for the specific chains they support: -console.log('Expected output:', quote.toTokenAmount) -console.log('Minimum output:', quote.toTokenAmountMin) -console.log('Fees:', quote.fees) +```typescript title="Register Protocols" +// Register protocols for specific chains +const wdk = new WDK(seedPhrase) + .registerWallet('ethereum', WalletManagerEvm, ethConfig) + + // Register Velora Swap for Ethereum + .registerProtocol('ethereum', 'velora', veloraProtocolEvm, { + apiKey: 'YOUR_API_KEY' + }) + + // Register USDT0 Bridge for Ethereum + .registerProtocol('ethereum', 'usdt0', usdt0ProtocolEvm, { + ethereumRpcUrl: 'https://eth.drpc.org' // Configuration depends on the module + }) ``` -`SwidgeOptions` supports exact-in and exact-out routes: +## Use Protocols -| 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`. | +Once [registered](#register-protocols), you can access the protocol instance using the specific getter methods, such as [`getSwidgeProtocol()`](/sdk/core-module/api-reference#getswidgeprotocollabel), `getSwapProtocol`, `getBridgeProtocol`, `getLendingProtocol`, or `getFiatProtocol`. + +### Swidge Routes + +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. -## Execute a route +```typescript title="Swidge route flow" +const account = await wdk.getAccount('ethereum', 0) +const swidge = account.getSwidgeProtocol('swidge') -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. +const chains = await swidge.getSupportedChains() +const tokens = await swidge.getSupportedTokens({ + fromChain: 'ethereum', + toChain: 'arbitrum' +}) -```typescript title="Execute a swidge route" const options = { fromToken: '0xSourceToken...', toToken: '0xDestinationToken...', @@ -11189,2488 +11622,12907 @@ const options = { } const quote = await swidge.quoteSwidge(options) -// Show quote details and ask for confirmation before continuing. const result = await swidge.swidge(options, { maxNetworkFeeBps: 50, maxProtocolFeeBps: 25 }) -console.log('Swidge ID:', result.id) -console.log('Primary transaction:', result.hash) -console.log('Transactions:', result.transactions) +const status = await swidge.getSwidgeStatus(result.id, { + toChain: 'arbitrum' +}) ``` - -`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. - +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. -`SwidgeProtocolConfig` supports fee limits expressed in basis points of the input amount: + +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. + -| 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. | +### Swapping Tokens -## Track status +Use `getSwapProtocol` to access registered swap services on any wallet account. -Use `getSwidgeStatus()` when the provider returns an execution ID and the final destination result may settle asynchronously. +```typescript title="Swap Tokens" +const ethAccount = await wdk.getAccount('ethereum', 0) +const velora = ethAccount.getSwapProtocol('velora') -```typescript title="Check swidge status" -const status = await swidge.getSwidgeStatus(result.id, { - fromChain: 'ethereum', - toChain: 'arbitrum' +const result = await velora.swap({ + tokenIn: '0x...', // Address of token to sell + tokenOut: '0x...', // Address of token to buy + tokenInAmount: 1000000n // Amount to swap }) - -if (status.status === 'completed') { - console.log('Swidge complete') -} ``` -Supported statuses are: +### Bridging Assets -| 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. | +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. -## Result fields +```typescript title="Bridge Assets" +const ethAccount = await wdk.getAccount('ethereum', 0) +const usdt0 = ethAccount.getBridgeProtocol('usdt0') + +await ethAccount.approve({ + token: '0x...', // ERC20 Token Address + spender: '0x...', // OFT or bridge spender address + amount: 1000000n +}) -`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. +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 +}) +``` -| 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. | + +**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. + -## Supported assets and chains +## Next Steps -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. +Learn how to [configure middleware](/sdk/core-module/guides/middleware) to add logging or failover protection to your wallet interactions. -## Next steps +*** - - -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 - - +## 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. -## 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. +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. -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. +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. -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. +## Policy Structure -The package is maintained by Flashnet at [`flashnetxyz/wdk-protocol-swidge-orchestra`](https://github.com/flashnetxyz/wdk-protocol-swidge-orchestra). +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. -For provider-maintained route support, integration patterns, and API concepts, see the [Flashnet Orchestra docs](https://docs.flashnet.xyz/products/orchestration/overview). +| 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 | -## When to use it +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. -Use Orchestra when your application needs to route between BTC on Spark or Bitcoin L1 and stablecoin routes returned by Orchestra. +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`. -| 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/) | + +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. + -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. +## Register Policies -## Responsibility model +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. -| 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 | +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. -`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. +```typescript title="Register A Local Send Limit" +import WDK, { PolicyViolationError } from '@tetherto/wdk' -## Key capabilities +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 + } + ] + } + ] + }) -- 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()`. +const account = await wdk.getAccount('ethereum', 0) -## Next steps +try { + await account.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000000n + }) +} catch (error) { + if (error instanceof PolicyViolationError) { + console.error(error.reason) + } +} +``` - - -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. - - +## Scope Policies -*** +Policies can target a whole project, selected wallets, or selected accounts. -## 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. +| 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 | -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`. +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. -## Discover routes +## Evaluation Rules -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. +WDK evaluates policies in this order: -```javascript title="Discover supported routes" -const chains = await orchestra.getSupportedChains() +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'`. -const sparkToTronTokens = await orchestra.getSupportedTokens({ - fromChain: 'spark', - toChain: 'tron' -}) +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. -console.log(chains) -console.log(sparkToTronTokens) +You can create an account-scoped exception using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): + +```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 + } + ] + } + ] + } +]) ``` -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. +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. -## Quote before execution +## Supported Operations -Call `quoteSwidge()` before execution. It calls the Orchestra estimate endpoint and does not reserve a deposit address. +Use these operation names in `PolicyRule.operation`: -```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 -}) +| 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 | +| `*` | 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. -console.log(quote.fromTokenAmount) -console.log(quote.toTokenAmount) -console.log(quote.toTokenAmountMin) -console.log(quote.fees) -``` + +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. + -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. +You can allow sends to approved recipients using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): -Use smallest units: +```typescript title="Address Allowlist" +const allowedRecipients = new Set([ + '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'.toLowerCase() +]) -| Asset | Unit | -|---|---| -| BTC | sats | -| USDT | 6-decimal token units | -| EVM native gas asset | wei | +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()) + } + ] + } + ] +}) +``` -## Execute routes +You can require both a chain and value limit using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): -Use `swidge()` only after showing the quote details, route, recipient, fees, and expected output to the user. +```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 + } + ] + } + ] +}) +``` -```javascript title="Execute after user confirmation" -const options = { - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -} +You can restrict typed-data signing to approved domains using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): -const quote = await orchestra.quoteSwidge(options) -showConfirmation(quote) +```typescript title="Typed Data Domain Gate" +const approvedTypedDataDomains = new Set([ + '1:0x000000000022d473030f116ddee9f6b43ac78ba3' +]) -const result = await orchestra.swidge(options, { - maxNetworkFeeBps: 20n, - maxProtocolFeeBps: 100n +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) + } + ] + } + ] }) - -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). +You can gate protocol write methods by their method parameters using [`wdk.registerPolicy()`](/sdk/core-module/api-reference#registerpolicypolicies-options): -## Track status +```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 + } + ] + } + ] +}) +``` -Use `getSwidgeStatus()` when you have the Swidge result id: +## Inspect Policy Context -```javascript title="Read Swidge status" -const status = await orchestra.getSwidgeStatus(result.id) +Conditions receive a frozen `PolicyContext`: -if (status.status === 'completed') { - console.log('Route completed') -} -``` +| 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 | -For submitted Orchestra states, use `getOrderStatus()`, `waitForCompletion()`, or `subscribeOrder()`: +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. -```javascript title="Wait for final order status" -const finalStatus = await orchestra.waitForCompletion(submitted, { - pollIntervalMs: 5000, - timeoutMs: 7200000, - onStatus: async (status) => { - await saveOrderStatus(status) - } -}) +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. -console.log(finalStatus.order?.status ?? finalStatus.status) -``` +## Simulate Before Execution -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. +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. -```javascript title="Read status with a client read token" -const submitted = await loadSubmittedState(result.id) -const readToken = submitted.readToken +You can dry-run a governed account method through the runtime `simulate` mirror: -const status = await orchestra.getSwidgeStatus(result.id, { - readToken +```typescript title="Dry-Run A Transaction Policy" +const account = await wdk.getAccount('ethereum', 0) + +const result = await (account as any).simulate.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 2000000000000000n }) + +console.log(result.decision, result.reason, result.trace) ``` -## Next steps +Simulation results include `decision`, `policy_id`, `matched_rule`, `reason`, and `trace`. Protocol write methods are also mirrored, for example `account.simulate.getSwapProtocol(label).swap(...)`. - - -Install and configure the package. - - -Persist intents and resume in-flight orders. - - -Recover from API, state, submit, and timeout failures. - - + +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. + -*** +## Handle Errors -## 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. +`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. -`Orchestra` accepts a WDK wallet account and an `OrchestraConfig` object. +`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. -```javascript title="Create an Orchestra instance" -import Orchestra from 'wdk-protocol-swidge-orchestra' +## Runtime Caveats -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - baseUrl: 'https://orchestration.flashnet.xyz' -}) -``` +- Policies wrap the WDK account/protocol proxy surface. Private fields, underscore methods, protocol internals, or nested calls made inside a module can bypass local policy evaluation. +- Quote and read methods are not wrapped. Policies apply to write methods such as sends, signs, swaps, bridges, lending writes, fiat writes, and swidge execution. +- 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. -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. +## Next Steps -## Install +- 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 swap, bridge, lending, fiat, and swidge method setup. -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 -``` +## Need Help? -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 +## Send Transactions +URL: https://docs.wdk.tether.io/sdk/core-module/guides/transactions +Description: Learn how to send native tokens on different blockchains. -```javascript -new Orchestra(account, config?) -``` +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. -Parameters: + +**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. + -- `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. + +**BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. + -## Core config +## Send Native Tokens -| 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. | +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. -## Authentication +### Ethereum Example -Use a backend key from a server or trusted runtime: + +On EVM chains, values are typically expressed in Wei (1 ETH = 10^18 Wei). + -```javascript title="Backend key" -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY +The following example will: + +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) + +const result = await ethAccount.sendTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 1000000000000000n // 0.001 ETH (in Wei) }) + +console.log('Transaction sent! Hash:', result.hash) ``` -Use `authMode: 'client'` for scoped client keys: +### TON Example -```javascript title="Scoped client key" -const orchestra = new Orchestra(account, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_CLIENT_KEY, - authMode: 'client' + +On TON, values are expressed in Nanotons (1 TON = 10^9 Nanotons). + + +The following example will: + +1. Retrieve the first TON account +2. Send 1 TON (1000000000 nton) to an account using `sendTransaction`. + +```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) ``` -Scoped client-key submissions can return `readToken`. Store that token with the submitted state and pass the full state back to status methods. +## Sign Without Broadcasting -Backend proxy integrations can provide headers per request: +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. -```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()}` - }) +```typescript title="Sign An EVM Transaction" +const ethAccount = await wdk.getAccount('ethereum', 0) + +const signedTransaction = await ethAccount.signTransaction({ + to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F', + value: 1000000000000000n }) + +console.log('Signed transaction:', signedTransaction) ``` -For direct browser SSE, provide a scoped SSE token with `sseToken` or `getSseToken`, or proxy SSE through your backend. + +`signTransaction()` only signs. Use `sendTransaction()` when you want WDK to sign, broadcast, and return the transaction hash. + -## Asset config +## Apply Local Transaction Policies -| 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. | +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. -EVM token sources need token contract addresses. The package includes common USDT source addresses, but production wallets should pass their own allowlist. +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. -```javascript title="Configure EVM USDT source token" -const orchestra = new Orchestra(arbitrumAccount, { - sourceChain: 'arbitrum', - apiKey: process.env.FLASHNET_API_KEY, - sourceTokenAddresses: { - 'arbitrum:USDT': '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' - } -}) -``` +See [Transaction Policies](/sdk/core-module/guides/transaction-policies) for policy scope, evaluation order, simulation, and error-handling examples. -Spark tokens other than BTC need Spark token identifiers: +## Handling Responses -```javascript title="Configure Spark token identifiers" -const orchestra = new Orchestra(sparkAccount, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - sparkTokenIdentifiers: { - USDB: 'btkn1...' - } -}) -``` +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. -## Safety and timeout config +## Multi-Chain Transactions -| 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. | +You can orchestrate payments across different chains in a single function by acting on multiple account objects sequentially. -## State callbacks +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. -| 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. | +```typescript title="Multi-Chain Payment" +async function sendCrossChainPayments(wdk) { + const ethAccount = await wdk.getAccount('ethereum', 0) + const tonAccount = await wdk.getAccount('ton', 0) -Use `onStateChange` to persist state transitions that can affect funds: + // 1. Send ETH + await ethAccount.sendTransaction({ + to: '0x...', + value: 1000000000000000000n + }) -```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) - } -}) + // 2. Send TON + await tonAccount.sendTransaction({ + to: 'EQ...', + value: 1000000000n + }) +} ``` -See [State and Recovery](/sdk/swidge-modules/swidge-orchestra/guides/state-and-recovery) before using this package with production funds. +## Next Steps + +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). *** -## 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. +## Register Wallets +URL: https://docs.wdk.tether.io/sdk/core-module/guides/wallet-registration +Description: Learn how to register wallet modules for different blockchains. -# API Reference +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). -## Package +## How it works -```javascript -import Orchestra, { - OrchestraApiError, - OrchestraError, - OrchestraStateError, - OrchestraSubmitError, - OrchestraTimeoutError -} from 'wdk-protocol-swidge-orchestra' -``` +The WDK uses a builder pattern, allowing you to chain `.registerWallet()` calls. Each call connects a blockchain-specific manager to your central WDK instance. -The package exports `Orchestra` as both the default export and a named export. +### Parameters -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. +The `registerWallet` method (see [API Reference](/sdk/core-module/api-reference)) requires three arguments: -## Class: Orchestra +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). -`Orchestra` extends `SwidgeProtocol` from `@tetherto/wdk-wallet/protocols`. +## Installation -### Constructor +Install the [wallet managers](/sdk/wallet-modules/) for the blockchains you want to support: -```javascript -new Orchestra(account, config?) +```bash +npm install @tetherto/wdk-wallet-evm @tetherto/wdk-wallet-tron @tetherto/wdk-wallet-btc ``` -Parameters: +## Example: Registering Multiple Wallets -- `account`: `IWalletAccount | IWalletAccountReadOnly | undefined` -- `config`: `OrchestraConfig` +### Import the Wallet Manager Packages -Use `undefined` only for discovery or status flows that do not send source payments. Write flows require a WDK account with source-payment methods. +First, import the necessary wallet manager packages: -### Swidge methods +```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' +``` -| 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` | +### Register the Wallets -### Production flow methods +Then, [instantiate WDK](/sdk/core-module/guides/getting-started#initialize-wdk) and chain the registration calls: -| 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` | +```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' + }) +``` -### `quoteSwidge(options)` + +**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. + -```javascript -const quote = await orchestra.quoteSwidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -}) -``` + +**TRON Networks:** Choose the correct provider for your environment. +* **Mainnet:** `https://api.trongrid.io` +* **Shasta (Testnet):** `https://api.shasta.trongrid.io` + -`quoteSwidge()` calls the estimate endpoint. It does not reserve a deposit address and does not move funds. +## Next Steps -### `swidge(options, config?)` +Once your wallets are registered, you can [manage accounts and specific addresses](/sdk/core-module/guides/account-management). -```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. +## Usage +URL: https://docs.wdk.tether.io/sdk/core-module/usage +Description: Guide to using the WDK Core module. -## Options +The WDK Core module is the central orchestrator for your wallet interactions. -### `OrchestraSwidgeOptions` + + +Install and instantiate the WDK. + + +Connect specific blockchains (Ethereum, TON, etc.). + + +Retrieve accounts and check balances. + + +Transfer native tokens. + + +Allow, deny, and simulate writes before execution. + + +Use Swidge, Swap, Bridge, and Lending protocols. + + +Add logging and failover protection. + + +Handle errors, dispose wallets, and clean up app-owned seed buffers. + + -| 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` +## Fiat Modules Overview +URL: https://docs.wdk.tether.io/sdk/fiat-modules +Description: Explore WDK fiat modules for on-ramp and off-ramp integrations. -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. +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. -### `OrchestraSwidgeStatusOptions` +## Fiat Protocol Modules -| 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. | +On-ramp and off-ramp functionality for fiat currency integration: -## State objects +| 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/) | -### `OrchestraSwapIntent` +## Features -Returned by `prepareSwap()`. Persist it before calling `executeSwapIntent()`. +Fiat modules provide: -Key fields: +- **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 -- `version` -- `quoteId` -- `sourceChain` -- `sourceAsset` -- `destinationChain` -- `destinationAsset` -- `recipientAddress` -- `amountMode` -- `amountIn` -- `estimatedOut` -- `depositAddress` -- `expiresAt` -- `quoteIdempotencyKey` -- `submitIdempotencyKey` -- `createdAt` +## Next Steps -### `OrchestraSwapState` +To get started with WDK fiat modules, follow these steps: -Returned after source payment, submit, or recovery steps. It extends `OrchestraSwapIntent`. +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 -Additional key fields: +You can also: -- `sourceTxHash` -- `sourceNetworkFee` -- `orderId` -- `status` -- `readToken` -- `sourcePaymentStartedAt` -- `fundedAt` -- `submittedAt` +- 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 -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 +## 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. -`getSwidgeStatus()` maps Orchestra order statuses to WDK Swidge statuses: +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. -| Orchestra status | WDK Swidge status | -|---|---| -| `processing` or unknown in-flight state | `pending` | +Get started by reading the [Usage](/sdk/fiat-modules/fiat-moonpay/usage) guide. + + +This module requires a MoonPay developer account. [Create your account here](https://dashboard.moonpay.com/signup). + + +## Features + +- **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 + +## Check Current Availability + +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. + +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 + + + +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 + + + +--- + +### MoonPay Resources + +- [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 + +--- + +### Need Help? + + + +*** + +## 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 + +# API Reference + +Complete API documentation for the `@tetherto/wdk-protocol-fiat-moonpay` module. + +## Constructor + +### `new MoonPayProtocol(account, config)` + +Creates a new MoonPayProtocol instance. + +**Parameters:** + +| Name | Type | Description | +|------|------|-------------| +| `account` | `IWalletAccount` \| `IWalletAccountReadOnly` \| `undefined` | Wallet account for transactions | +| `config` | `MoonPayProtocolConfig` | Configuration object | + +**Config Options:** + +| 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 | + +**Example:** + +```typescript +import MoonPayProtocol from '@tetherto/wdk-protocol-fiat-moonpay'; + +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. + +--- + +## Methods + +### `buy(options)` + +Generates a MoonPay widget URL for purchasing cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. + +**Parameters:** + +| 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 | + +*Either `cryptoAmount` or `fiatAmount` must be provided, but not both. + +**Returns:** `Promise\<{ buyUrl: string }\>` + +--- + +### `sell(options)` + +Generates a MoonPay widget URL for selling cryptocurrency. If `signUrl` is configured, the URL is signed through that callback before being returned. + +**Parameters:** + +| 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 }\>` + +--- + +### `quoteBuy(options)` + +Gets a price quote for a cryptocurrency purchase. + +**Parameters:** + +| 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 | + +**Returns:** `Promise\` + +```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 + + + + + +*** + +## Swidge Modules Overview +URL: https://docs.wdk.tether.io/sdk/swidge-modules +Description: Compare WDK Swidge providers for swaps, bridges, and combined asset routes. + +Swidge modules connect WDK accounts to providers that can quote and execute a swap, a bridge, or both as one route. Choose a provider by its supported chains, tokens, runtime, and operational requirements. + + +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. + + +## Released provider modules + +| 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 | [README](https://github.com/lifinance/wdk-lifi-swidge-protocol#readme) | +| [`@moonpay/wdk-protocol-swidge-moonpay-trade`](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade) | MoonPay Trade | Community | [README](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade#readme) | +| [`@swapdk/wdk-protocol-swidge-swapdk`](https://www.npmjs.com/package/@swapdk/wdk-protocol-swidge-swapdk) | SwapDK | Community | [README](https://github.com/Swap-DK/wdk-protocol-bridges-swapdk#readme) | +| [`@gobob/wdk-protocol-swidge-gateway`](https://www.npmjs.com/package/@gobob/wdk-protocol-swidge-gateway) | BOB Gateway | Community | [README](https://github.com/bob-collective/wdk-protocol-swidge-gateway#readme) | + +## 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. + + + +Maintaining an integration that does not implement Swidge? Use the released [standalone swap modules](/sdk/swap-modules/) or [standalone bridge modules](/sdk/bridge-modules/). + +*** + +## Need Help? + + + +*** + +## 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 +## 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.1.1`](https://www.npmjs.com/package/@symbiosis-finance/wdk-protocol-swidge-symbiosis/v/1.1.1) 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.1.1`](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.1.1) 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, TON, or Bitcoin source execution through a compatible WDK wallet account; +- cross-chain settlement status mapped to WDK status values. + +The module can quote 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 | +| 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 response, amounts, fees, spender, transaction payload, or Bitcoin deposit address can differ from the earlier quote. The module checks configured fee caps on this fresh response before it calls a wallet write method. + + +Show the source amount, expected and minimum output, fees, recipient, and destination chain to the user before calling `swidge()`. A prior `quoteSwidge()` response is not bound to execution. + + +For an ERC-20 EVM route, `swidge()` can: + +1. Read the current allowance. +2. Approve the spender returned by Symbiosis when the allowance is insufficient. +3. Wait for the approval receipt when the wallet exposes receipt lookup. +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. | +| `ton` | Sends the messages returned by Symbiosis in order. | +| `btc` | Transfers the input amount to the generated deposit address. Configure a suitable refund address. | +| `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`. +- The provider does not consume an additional `minAmountOut` field if a newer WDK interface exposes one. Use `slippage` and inspect `toTokenAmountMin`; do not rely on an extra minimum-output field as an execution guard. +- Discovery responses are cached for ten minutes per protocol instance. The cache duration is not configurable. +- 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. +- The module does not implement request timeout or retry controls. Handle API failures and retry policy in the host application. +- The package documents the `/v2/swap` endpoint 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. Set `skipApproval: true` only when the application manages allowance separately. +- If the wallet does not expose transaction-receipt lookup, the module cannot wait for approval confirmation before submitting the route transaction. +- Symbiosis fees are currently mapped as `protocol` or `affiliate`, not `network`. A configured `maxNetworkFeeBps` does not constrain a separately reported network-fee item in this release. +- For TON routes with multiple messages, the result records the last sent message hash as the source transaction. Do not treat `result.transactions` as a complete list of every TON message. +- Quote-only construction without an account uses `recipient` as both the source sender and destination recipient. Bind a source account when those addresses differ or use different address formats. +- 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.1.1. + + +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.1.1`](https://github.com/symbiosis-finance/wdk-protocol-swidge-symbiosis/tree/v1.1.1). + +## `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 also uses the account's address as the request sender. | +| `undefined` | Discovery and status; quote-only use 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 + defaultSlippage?: number + partnerAddress?: string + refundAddress?: string + skipApproval?: boolean + maxNetworkFeeBps?: number | bigint + maxProtocolFeeBps?: number | bigint +} +``` + +See [Configuration](/sdk/swidge-modules/swidge-symbiosis/configuration) for defaults 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()`. + +Throws when: + +- `chain` is missing; +- `fromTokenAmount` is missing; +- `toTokenAmount` is present; +- a chain or token cannot be resolved; +- no account address or `recipient` can provide the request sender; +- the API returns a non-success response. + +### `swidge(options, config?)` + +```typescript +swidge( + options: SwidgeOptions, + config?: SwidgeProtocolConfig +): Promise +``` + +Requires an account with `sendTransaction()`. It calls `/v2/swap`, checks 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. Confirm the earlier quote with the user, then treat execution as a fresh provider response. + + +### `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 ignores other status hints. + +HTTP `404` is returned as `pending`, not as `ApiError`. + +### `getSupportedChains()` + +```typescript +getSupportedChains(): Promise +``` + +Calls the Symbiosis chain and token endpoints and maps each 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. | + +### `getSupportedTokens(options?)` + +```typescript +getSupportedTokens( + options?: SwidgeSupportedTokensOptions +): Promise +``` + +The chain filter is resolved as `options.toChain ?? options.fromChain`. Other route context does 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` | Optional destination chain; defaults to the configured source chain. | +| `recipient` | `string` | Destination recipient. Without an account, it is also used as the request sender. | +| `refundAddress` | `string` | Per-call refund address; overrides the constructor default. | +| `slippage` | `number` | Decimal slippage; overrides `defaultSlippage`. | +| `fromTokenAmount` | `number \| bigint` | Required exact input in source-token base units. | +| `toTokenAmount` | `number \| bigint` | Unsupported; throws `ExactOutNotSupportedError`. | + +The `SwidgeOptions` type in the package's tested `@tetherto/wdk-wallet@1.0.0-beta.11` dependency does not include `minAmountOut`. If a newer WDK interface exposes that field, this provider still does not consume it. + +## 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 return a quote expiry through `SwidgeQuote`. + +### `SwidgeResult` + +| Field | Type | Description | +|---|---|---| +| `id` | `string` | `:`. | +| `hash` | `string` | Primary source hash; the final sent message hash for a multi-message TON route. | +| `fees` | `SwidgeFee[]` | Fees from the fresh execution response. | +| `transactions` | `SwidgeTransaction[]` | Approval and recorded source hashes. A later status result can additionally report destination or refund hashes. | +| `fromTokenAmount` | `bigint` | Submitted exact input. | +| `toTokenAmount` | `bigint` | Fresh provider-estimated output. | +| `toTokenAmountMin` | `bigint` | 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 + +| Symbiosis `fees[].provider` | WDK fee type | +|---|---| +| `symbiosis` | `protocol` | +| `partner` | `affiliate` | +| Any other value | `protocol` | + +Mapped fees include `amount`, `token`, `chain`, `description`, and `included: true`. + +The provider emits no `network` fee entry in this release. Inherited legacy `bridge()` results therefore expose `0n` for their network `fee`; mapped protocol fees contribute to `bridgeFee`. + +Fee-cap fallback comparison uses decimal-normalized values when USD prices are absent. 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 provider errors. | Standard `Error` fields | +| `ConfigurationError` | Required source `chain` configuration is missing. | — | +| `ValidationError` | Input, 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 fee exceeds a 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-success status other than the status-specific `404` handling. | `status`, `response` | + +## 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', + defaultSlippage: 0.02, + refundAddress: 'bc1qRefund...', + maxProtocolFeeBps: 100 +}) +``` + +| Parameter | Description | +|---|---| +| `account` | Optional WDK wallet account. Discovery and some quotes 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. | +| `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. The current provider fee mapping does not emit a `network` fee item, so this does not constrain a separate network fee in `1.1.1`. | +| `maxProtocolFeeBps` | `number \| bigint` | None | Rejects execution when mapped Symbiosis protocol fees exceed this many basis points of the input amount. | + +## 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, the module returns the provider's native-format address from token discovery when available. + +Token symbols can be ambiguous. Prefer the exact address returned by `getSupportedTokens()` and confirm route availability with `quoteSwidge()`. + +## Slippage + +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 value to basis points for the Symbiosis request. It does not separately validate an additional `minAmountOut` field if a newer WDK interface exposes one. + +## Refund address + +Set a refund address suitable for the source route: + +```javascript +const symbiosis = new SymbiosisProtocol(bitcoinAccount, { + chain: 'Bitcoin', + refundAddress: 'bc1qRefund...' +}) +``` + +Override the default for one request: + +```javascript +await symbiosis.swidge({ + fromToken: 'BTC', + toToken: 'USDC', + toChain: 'Arbitrum One', + recipient: '0xRecipient...', + refundAddress: 'bc1qDifferentRefund...', + fromTokenAmount: 50_000n +}) +``` + +The module forwards the address to Symbiosis; it does not validate that the address belongs to the intended refund chain. + +## 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. approves the exact input amount when allowance is insufficient; +4. approves when allowance lookup fails; +5. waits for the approval receipt when `getTransactionReceipt()` is available. + +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, + maxNetworkFeeBps: 50 +}) +``` + +Override shared fee caps for one execution: + +```javascript +await symbiosis.swidge(options, { + maxProtocolFeeBps: 75, + maxNetworkFeeBps: 40 +}) +``` + +The module checks the fresh `/v2/swap` response before calling a wallet write method. + +When both the input token and fee token have 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 entries currently map to `protocol` or `affiliate`. No returned entry maps to `network`, so `maxNetworkFeeBps` remains at zero for this provider's current fee calculation. + +## 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. + +## 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`. +- The provider does not configure request timeout, cancellation, retry, or backoff. + +Use a trusted `https://` endpoint for `apiUrl`. Apply application-level timeouts and cautious retries, 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.1.1`. The EVM example below uses the WDK wallet version tested by that release: + +```bash +npm install @symbiosis-finance/wdk-protocol-swidge-symbiosis@1.1.1 @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' + +const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { + provider: 'https://eth.drpc.org' +}) + +try { + const symbiosis = new SymbiosisProtocol(account, { + chain: 'Ethereum', + maxProtocolFeeBps: 100 + }) + + const chains = await symbiosis.getSupportedChains() + console.log(chains) + + // Continue with discovery and quoting inside this block. +} finally { + account.dispose() +} +``` + +Keep the account alive through quote review, execution, and source broadcast, then dispose it in `finally`. + +## 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. + +## 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 the Symbiosis quote endpoint and returns an indicative result. + +Only exact-input operations are supported. Pass `fromTokenAmount`; passing `toTokenAmount` throws `ExactOutNotSupportedError`. + + +Do not treat `quoteSwidge()` as a reserved or executable quote. `swidge()` requests a fresh execution response, so the output, fee breakdown, spender, transaction payload, or deposit address can change. + + +## 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; +- itemized fees; +- the selected slippage tolerance. + +The provider does not consume an additional `minAmountOut` field if a newer WDK interface exposes one. Use `slippage`, inspect `toTokenAmountMin`, and perform application-level review instead of relying on an extra minimum-output field as an execution guard. + +## Execute an EVM route + +Call `swidge()` only after confirmation: + +```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 the Symbiosis swap endpoint. +3. Check configured fee caps before a wallet write. +4. For a non-native EVM token, read allowance and approve the returned spender when needed. +5. Wait for the approval receipt when the account supports receipt lookup. +6. Broadcast the API-provided route transaction. +7. Return the source hash and operation ID without waiting for destination settlement. + +If allowance lookup fails, the module falls back to approval. 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 +}) +``` + +## Execute TON and Bitcoin source routes + +For TON, the module sends the messages returned by Symbiosis in order. If a route contains several messages, `result.hash` and the source entry in `result.transactions` represent the last sent message. + +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. Confirm the refund address, recipient, amount, and fresh execution response before the source payment. + +Tron and Solana source routes are quote-only in `1.1.1`; execution throws `UnsupportedRouteError`. + +## 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` for a newly broadcast source transaction. A genuinely unknown ID produces the same result, so enforce your own 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 typed 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 ask the user to review current 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) { + // Apply host-level timeout, retry, and rate-limit policy. + } else if (error instanceof SymbiosisError) { + // Handle another provider error. + } +} +``` + +The package does not add request timeouts or retries. Avoid retrying `swidge()` blindly after an uncertain wallet or network 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 + + + +## Classic Wallet Modules + +Standard wallet implementations that use native blockchain tokens for transaction fees: + +| 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) | + +## 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 | - | + +## Community Wallet Modules + +Wallet modules developed by the community. See the [Community Modules](/sdk/community-modules/) page for more details. + + +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 | 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/) | +| [`@utexo/wdk-rgb-lightning`](https://www.npmjs.com/package/@utexo/wdk-rgb-lightning) | Bitcoin (RGB and Lightning) | RGB Lightning node, channel, payment, and asset integration | [Docs](/sdk/community-modules/wdk-rgb-lightning/) | +| [`@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/) | + +## 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 +}) + +const developmentWallet = new WalletManagerBtc(seedPhrase, { + client: developmentClient, + network: 'bitcoin' +}) +``` + +### Network-Specific Configuration + +#### Bitcoin Mainnet + +```javascript +import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' + +const client = new ElectrumTcp({ + host: 'electrum.blockstream.info', // Or your own server + port: 50001 +}) + +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'bitcoin' +}) +``` + +#### Bitcoin Testnet + +```javascript +import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' + +const client = new ElectrumTcp({ + host: 'testnet.hsmiths.com', // Example testnet server + port: 53011 +}) + +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'testnet' +}) +``` + +#### Bitcoin Regtest + +```javascript +import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' + +const client = new ElectrumTcp({ + host: 'localhost', // Local regtest node + port: 50001 +}) + +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'regtest' +}) +``` + +## Derivation Paths + +Bitcoin wallet addresses are derived using BIP-32 hierarchical deterministic paths: + +### BIP-84 (Native SegWit) - Default + +- `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 + +Addresses start with `bc1` (mainnet) or `tb1` (testnet). + +### BIP-44 (Legacy) + +- `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 + +Addresses start with `1` (mainnet) or `m`/`n` (testnet). + + +**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. + + +## Complete Configuration Example + +```javascript +import WalletManagerBtc, { ElectrumTls } from '@tetherto/wdk-wallet-btc' + +// Create Electrum client +const client = new ElectrumTls({ + host: 'your-electrum-server.com', // Replace with your server + port: 50002 +}) + +// Create wallet manager with configuration +const wallet = new WalletManagerBtc(seedPhrase, { + client, + network: 'bitcoin', + bip: 84 // Native SegWit (default) +}) + +// 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") + +// Clean up when done +wallet.dispose() +``` + +## Performance Considerations + +**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 + +**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 + + +Build mobile wallets with React Native Expo + + +Get started with WDK's BTC Wallet Usage + + +Get started with WDK's BTC Wallet API + + + +*** + +### 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. + +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). + +## Native BTC Balance + +You can retrieve the confirmed balance in satoshis using [`account.getBalance()`](/sdk/wallet-modules/wallet-btc/api-reference): + +```javascript title="Get Native BTC Balance" +const balance = await account.getBalance() +console.log('Total balance:', balance, 'satoshis') +``` + + +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. + + +## Maximum Spendable Amount + +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 Maximum Spendable" +const { amount, fee } = await account.getMaxSpendable() +console.log('Max spendable:', amount, 'satoshis') +console.log('Estimated fee:', fee, 'satoshis') +``` + + +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). + + +## 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' + +const client = new ElectrumTcp({ + host: 'electrum.blockstream.info', + port: 50001 +}) + +const readOnlyAccount = new WalletAccountReadOnlyBtc('bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', { + client, + network: 'bitcoin' +}) +``` + +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') +``` + + +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). + +*** + +## Get Started +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/get-started +Description: Install and create your first Bitcoin 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 20.19.0 or higher. Wallet BTC beta.11 depends on `@bitcoinerlab/descriptors` 3.1.7, which sets this minimum. +* **[npm](https://www.npmjs.com/)**: usually comes with Node.js. + +```bash title="Install @tetherto/wdk-wallet-btc" +npm install @tetherto/wdk-wallet-btc +``` + +## 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: + +```javascript title="Create Bitcoin Wallet" +import WalletManagerBtc, { ElectrumTcp } from '@tetherto/wdk-wallet-btc' + +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' +}) +``` + + +**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. + + +## 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 + +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). + +*** + +## Handle Errors +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 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. + +## 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: + +```javascript title="Handle Transaction Errors" +try { + const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n + }) + 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) + } +} +``` + +## Connection Errors + +Network issues with the Electrum server can cause failures across all operations. Handle connection errors at a higher level: + +```javascript title="Handle Connection Errors" +try { + const balance = await account.getBalance() + console.log('Balance:', balance, 'satoshis') +} 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') + } else { + console.error('Operation failed:', error.message) + } +} +``` + +## Best Practices + +### Fee Management + +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 { + const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n + }) + console.log('Transaction hash:', result.hash) +} finally { + account.dispose() + wallet.dispose() +} +``` + + +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. + + +*** + +## 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. + +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: + +```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) +``` + +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): + +```javascript title="Custom Derivation Path" +const customAccount = await wallet.getAccountByPath("0'/0/5") +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). + +*** + +## Send Transactions +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc/guides/send-transactions +Description: Send BTC and estimate transaction fees. + +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). + +## Send BTC + +You can send Bitcoin to a recipient using [`account.sendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#sendtransactionoptions-timeoutms): + +```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') +``` + + +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). + + +## Extend Post-Broadcast Polling + +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: + +```javascript title="Send BTC With Extended Polling" +const result = await account.sendTransaction( + { + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + }, + 30000 +) + +console.log('Transaction hash:', result.hash) +``` + + +If you omit `timeoutMs`, the wallet uses the default post-broadcast polling window before returning. + -All package-specific errors extend `OrchestraError`. +## Sign Without Broadcasting -| 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` | +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. -## Source repository tooling +```javascript title="Sign BTC Transaction" +const signedTransaction = await account.signTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + feeRate: 10n +}) -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. +console.log('Signed transaction:', signedTransaction) +``` -*** + +`signTransaction()` returns the signed transaction hex. Use `sendTransaction()` when WDK should sign, broadcast, and return the transaction hash. + -## 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. +## Quote and Broadcast Signed Hex -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). +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. -## Install the package +```javascript title="Quote And Broadcast Signed BTC" +const signedTransaction = await account.signTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + feeRate: 10n +}) -Install Orchestra and the WDK wallet base package: +const quote = await account.quoteSendTransaction(signedTransaction) +console.log('Signed transaction fee:', quote.fee, 'satoshis') -```bash title="Install Orchestra" -npm install wdk-protocol-swidge-orchestra @tetherto/wdk-wallet@1.0.0-beta.11 +const result = await account.sendTransaction(signedTransaction) +console.log('Transaction hash:', result.hash) ``` -Install the WDK wallet modules for the chains you plan to support: +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. -```bash title="Install WDK wallet modules" -npm install @tetherto/wdk @tetherto/wdk-wallet-spark @tetherto/wdk-wallet-btc @tetherto/wdk-wallet-evm +## Estimate Fees + +You can estimate the fee for a transaction without broadcasting it using [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-btc/api-reference#quotesendtransactionoptions): + +```javascript title="Estimate Fee" +const quote = await account.quoteSendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n +}) +console.log('Estimated fee:', quote.fee, 'satoshis') ``` -## Create source accounts +## Cap Transaction Fees -Create WDK accounts for the source chains your wallet supports. This example registers Spark, Bitcoin L1, and Arbitrum source accounts. +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="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' +```javascript title="Cap BTC Transaction Fees" +const wallet = new WalletManagerBtc(seedPhrase, { + network: 'bitcoin', + transactionMaxFee: 10000n // satoshis +}) +``` -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 - }) +## Send with Custom Fee Rate -const spark = await wdk.getAccount('spark', 0) -const arbitrum = await wdk.getAccount('arbitrum', 0) +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 +}) ``` -## Create Orchestra + +When `feeRate` is provided, the `confirmationTarget` parameter is ignored. + -Create one `Orchestra` instance per source wallet account. Set `sourceChain` explicitly. +## Send with Confirmation Target -```javascript title="Create Orchestra for Spark source routes" -import Orchestra from 'wdk-protocol-swidge-orchestra' +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): -const orchestra = new Orchestra(spark, { - sourceChain: 'spark', - apiKey: process.env.FLASHNET_API_KEY, - baseUrl: 'https://orchestration.flashnet.xyz' +```javascript title="Confirmation Target" +const result = await account.sendTransaction({ + to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + value: 100000n, + confirmationTarget: 6 // target 6 blocks (~1 hour) }) ``` -EVM token sources need token contract addresses. Common USDT addresses are built in, but production wallets should pass their own allowlist. +## Next Steps -```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' - } -}) +Learn how to [view transaction history](/sdk/wallet-modules/wallet-btc/guides/get-transaction-history). + +*** + +## 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. + +This guide explains how to [sign messages](#sign-a-message) and [verify signatures](#verify-a-signature). + +## Sign a Message + +You can sign a message with the account's private key using [`account.sign()`](/sdk/wallet-modules/wallet-btc/api-reference): + +```javascript title="Sign Message" +const message = 'Hello, Bitcoin!' +const signature = await account.sign(message) +console.log('Signature:', signature) ``` -## Make a first quote + +The signature is returned as a base64-encoded string. + -Use `quoteSwidge()` to estimate a route before showing a confirmation screen. +## Verify a Signature -```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 -}) +You can verify that a signature was produced by the corresponding private key using [`account.verify()`](/sdk/wallet-modules/wallet-btc/api-reference): -console.log(quote.toTokenAmount) -console.log(quote.toTokenAmountMin) -console.log(quote.fees) +```javascript title="Verify Signature" +const isValid = await account.verify(message, signature) +console.log('Signature valid:', isValid) ``` -`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. +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): + +```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) +``` + +## Next Steps + +Learn how to [handle errors and manage resources](/sdk/wallet-modules/wallet-btc/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. + +# Usage + +The `@tetherto/wdk-wallet-btc` module provides wallet management for the Bitcoin blockchain. -## Next steps - -Show confirmation and execute Orchestra routes. + +Install the package and create your first wallet. - -Use the production split flow and persist state. + +Work with multiple accounts and custom derivation paths. - -Review auth, source-chain, token, timeout, and callback options. + +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. + + + + + + +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 *** -## 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. +### Need Help? -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. +## 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="Discover chains and tokens" -const chains = await orchestra.getSupportedChains() +Use the EVM wallet module for standard Ethereum-compatible accounts where users pay gas with the chain native token. -const tokens = await orchestra.getSupportedTokens({ - fromChain: 'spark', - toChain: 'tron' +## Features + +- **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 + +## Supported Networks + +This package works with any EVM-compatible blockchain, including: + +- **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...** + +## Next Steps + + + +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 + + + +*** + +### Need Help? + + + +*** + +## 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. + +Use the EIP-7702 wallet module when users should keep an EOA address while transactions run through bundler and paymaster infrastructure. + +It handles EIP-7702 authorization, UserOperation construction, UserOperation signing, paymaster data, and receipt lookup behind the WDK wallet account interface. + +## What It Provides + +- **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. + +## Requirements + +This module depends on EVM infrastructure that supports both EIP-7702 account delegation and ERC-4337 UserOperations. You need: + +- 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. + + +The EOA delegates execution to the configured `delegationAddress`. Treat that address as security-sensitive and verify it before using it with real funds. + + +## Fee Modes + +| 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 + + + +Install the package and create an EIP-7702 gasless account. + + +Review required fields, fee modes, provider failover, and per-call overrides. + + +See the public classes, methods, config types, and error behavior. + + +Quote and submit EVM transactions through ERC-4337 UserOperations. + + + +*** + +## Need Help? + + + +*** + +## 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. + +## Table of Contents + +| 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. | + +## WalletManagerEvm7702Gasless + +Default export from `@tetherto/wdk-wallet-evm-7702-gasless`. + +```javascript +import WalletManagerEvm7702Gasless from '@tetherto/wdk-wallet-evm-7702-gasless' +``` + +### Constructor + +```javascript +new WalletManagerEvm7702Gasless(seed, 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. | + +### Methods + +| 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. | + +`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. + +## WalletAccountEvm7702Gasless + +Named export for owned EIP-7702 gasless accounts. + +```javascript +import { WalletAccountEvm7702Gasless } from '@tetherto/wdk-wallet-evm-7702-gasless' +``` + +### Constructors + +```javascript +new WalletAccountEvm7702Gasless(seed, path, config) +new WalletAccountEvm7702Gasless(walletAccountEvm, config) +``` + +| 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. | + +### Properties + +| 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. | + +Treat the arrays returned through `keyPair` as read-only. Call `dispose()` when the account is no longer needed. + +### Methods + +| 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. | + +### Quote and Send Behavior + +- 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. + +### Transfer Fee Cap + +`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 + +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. + +## WalletAccountReadOnlyEvm7702Gasless + +Named export for read-only accounts. + +```javascript +import { WalletAccountReadOnlyEvm7702Gasless } from '@tetherto/wdk-wallet-evm-7702-gasless' +``` + +### Constructor + +```javascript +new WalletAccountReadOnlyEvm7702Gasless(address, config) +``` + +| Parameter | Type | Description | +|-----------|------|-------------| +| `address` | `string` | EOA address. | +| `config` | `Omit\` | Read-only configuration. | + +### 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. | + +Read-only accounts cannot sign, approve, send, or transfer. + +## ConfigurationError + +```javascript +import { ConfigurationError } from '@tetherto/wdk-wallet-evm-7702-gasless' +``` + +`ConfigurationError` extends `Error` and is thrown when wallet configuration is invalid. + +Known configuration errors include: + +- 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. + +## Config Types + +### Evm7702GaslessWalletCommonConfig + +| 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. | + +### Evm7702GaslessSponsorshipPolicyConfig + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `isSponsored` | `true` | Yes | Enables sponsored fee mode. | +| `sponsorshipPolicyId` | `string` | No | Paymaster sponsorship policy ID. | + +### Evm7702GaslessPaymasterTokenConfig + +| 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. | + +### Evm7702GaslessWalletConfig + +```typescript +type Evm7702GaslessWalletConfig = + Evm7702GaslessWalletCommonConfig & + (Evm7702GaslessSponsorshipPolicyConfig | Evm7702GaslessPaymasterTokenConfig) +``` + +### Exported Types + +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`. + +Module-specific exported types include `UserOperationReceipt`, `Eip7702AuthorizationOverride`, `BuildSponsoredUserOperationOverrides`, and `SponsoredUserOperation`. + +*** + +## 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. + +## Wallet Configuration + +`WalletManagerEvm7702Gasless`, `WalletAccountEvm7702Gasless`, and `WalletAccountReadOnlyEvm7702Gasless` use the same base configuration. The account must also choose one fee mode: sponsorship policy or paymaster token. + + +Replace `''` with a smart-account implementation address that you have verified for the target chain. The EOA delegates execution to this address. + + +```javascript title="Sponsored wallet configuration" +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', + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy' +}) +``` + +```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 +}) +``` + +## Required Common Fields + +| 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. | + +Account constructors and per-call config overrides throw `ConfigurationError` if any required common field is missing. + +## Optional Common Fields + +| 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`. | + +### Provider Failover + +Pass an ordered array when you want read and quote calls to retry against multiple RPC providers: + +```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 +}) +``` + + +An empty provider array throws `ConfigurationError`. Include at least one RPC URL or EIP-1193 provider. + + +## Fee Mode Configuration + +### Sponsorship Policy + +Use sponsorship mode when a paymaster sponsors UserOperation fees. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `isSponsored` | `true` | Yes | Enables sponsorship mode. | +| `sponsorshipPolicyId` | `string` | No | Policy identifier passed to the paymaster context. | + +```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' +}) +``` + +In sponsorship mode, `quoteSendTransaction()` and `quoteTransfer()` return `{ fee: 0n }`. + +### Paymaster Token + +Use paymaster-token mode when the account pays UserOperation fees with an ERC-20 token. + +| 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. | + +If `isSponsored` is omitted or `false`, `paymasterToken` is required. + +```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 +}) +``` + +When `paymasterAddress` is set, the account checks the paymaster address returned by the RPC response and throws `ConfigurationError` if it does not match. + +## Using Candide + +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): + +- Public: `https://api.candide.dev/public/v3/{chainId}` +- Authenticated: `https://api.candide.dev/api/v3/{chainId}/{apiKey}` + +```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' +}) +``` + +If a provider serves its paymaster from a different URL than the bundler, set `paymasterUrl` to that endpoint; otherwise it defaults to `bundlerUrl`. + +## Per-call Overrides + +`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: + +```javascript title="Override the paymaster token" +const result = await account.sendTransaction({ + to: '0x742C4265F5Ba4F8E0842e2b9EfE66302F7a13B6F', + value: 0n, + data: '0x' +}, { + isSponsored: false, + paymasterToken: { + address: '0x68749665FF8D2d112Fa859AA293F07A622782F38' + } }) ``` -Use chain-qualified asset references such as `spark:BTC`, `bitcoin:BTC`, `bsc:USDT`, or `tron:USDT`. +When an override is provided, the merged config is validated before the operation runs. + + +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. + + +*** + +## 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. + +This guide explains how to read balances from an EIP-7702 gasless account. + +## Native Balance + +Use `getBalance()` to read the native token balance in wei. + +```javascript title="Get native balance" +const balance = await account.getBalance() +console.log('Native balance:', balance) +``` + +## ERC-20 Balance + +Use `getTokenBalance(tokenAddress)` to read one ERC-20 token balance in the token's base units. + +```javascript title="Get token balance" +const usdtBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') +console.log('USDT balance:', usdtBalance) +``` + +## Multiple ERC-20 Balances -## Quotes +Use `getTokenBalances(tokenAddresses)` to read several token balances at once. -`quoteSwidge()` is side-effect-free. It calls Orchestra's estimate endpoint and does not reserve a deposit address. +```javascript title="Get multiple token balances" +const balances = await account.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' +]) -```javascript title="Quote exact source amount" -const quote = await orchestra.quoteSwidge({ - fromToken: 'spark:BTC', - toToken: 'tron:USDT', - fromTokenAmount: 7116n, - recipient: 'TRecipient...', - slippage: 0.01 -}) +console.log(balances) ``` -Show the user: - -- source amount and source asset -- expected destination amount -- minimum destination amount -- fees -- route and recipient -- expiry, if present - -## One-call execution +The return value maps each token address to a `bigint` balance. -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. +## Paymaster Token Balance -```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 -}) +In paymaster-token mode, `getPaymasterTokenBalance()` reads the configured `paymasterToken.address`. -console.log(result.id) -console.log(result.hash) +```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. + + +```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`. -## Next Steps +## 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. - -Configure API authentication, fee caps, API base URL, and config caching. + +Install the package and create your first EIP-7702 gasless account. - -Install the package, discover support, quote a route, execute a route, and poll status. + +Work with account indices, derivation paths, and existing EVM accounts. - -Review constructor options, methods, config fields, status mapping, fee mapping, and errors. + +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. ---- - -## Need Help? + + +Get started with WDK in a Node.js environment. + + +Compare WDK wallet modules across supported chains. + + +Review EIP-7702 gasless wallet configuration. + + +Review classes, methods, config types, and return values. + + - *** -## 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. +### Need Help? -## 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' +## 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. -const rhinofi = new RhinofiProtocol(account, config) -``` +Use the ERC-4337 wallet module when your app needs EVM smart accounts, UserOperations, or sponsored and token-paid transaction flows. -## Constructor +## Features -```typescript -new RhinofiProtocol(account?, config) -``` +- **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 +- **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 -| 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. | +## Supported Networks -## Methods +This package works with any EVM-compatible blockchain, including: -| 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. | +- **Ethereum Mainnet** +- **Ethereum Testnets** (Sepolia) +- **Other EVM Chains** (Polygon, Arbitrum, Avalanche C-chain, Plasma etc.) -### `quoteSwidge(options)` +## Next Steps -```typescript -quoteSwidge(options: SwidgeOptions): Promise -``` + + +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 + + -The source chain is derived from the account when required by the route. +*** -### `swidge(options, config?)` +## Need Help? -```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?)` +## 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 -```typescript -getSwidgeStatus( - id: string, - options?: SwidgeStatusOptions -): Promise -``` +## Table of Contents -Use the `id` returned by `swidge()`. +| 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. | - | -## Config Type +## WalletManagerEvmErc4337 -```typescript -type RhinofiProtocolConfig = { - apiKey: string - apiBaseUrl?: string - maxNetworkFeeBps?: number | bigint - maxProtocolFeeBps?: number | bigint - configTtlMs?: number -} -``` +The main class for managing ERC-4337 EVM wallets. Extends `WalletManager` from `@tetherto/wdk-wallet`. -## Status Mapping +### Fee Rate Behavior -| 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` | +Internally, `getFeeRates()` applies these multipliers to the base fee: -## Fee Mapping +- **Normal**: base fee × 110% +- **Fast**: base fee × 200% -| Rhino.fi quote fee | WDK fee type | Legacy field | -|--------------------|--------------|--------------| -| `gasFee` plus `sourceGasFee` | `network` | `fee` | -| Platform and percentage fee remainder | `protocol` | `bridgeFee` | +These multipliers are internal (`protected static`) and cannot be imported or overridden. -The `network` and `protocol` fee amounts are itemized in `SwidgeFee[]` and denominated in the input token. +### Constructor -## Error Types +```javascript +new WalletManagerEvmErc4337(seed, config) +``` -All Rhino.fi module errors extend `RhinofiProtocolError`. +**Parameters:** +- `seed` (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytes +- `config` (EvmErc4337WalletConfig): Configuration object with common fields and a gas payment mode -| 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. | +**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'`) -## 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. +#### `getAccount(index)` +Returns a wallet account at the specified index using BIP-44 derivation. -## Security Notes +**Parameters:** +- `index` (number, optional): The index of the account to get (default: 0) -- 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()`. +**Returns:** `Promise\` - The wallet account - - -Quote, execute, and track Rhino.fi swidge routes. - - -Detailed method, type, status, fee, and error reference. - - -*** +**Example:** +```javascript +// Get first account (index 0) +const account = await wallet.getAccount(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. +// Get default account +const defaultAccount = await wallet.getAccount() +``` -## Install +#### `getAccountByPath(path)` +Returns a wallet account at the specified BIP-44 derivation path. -```bash -npm install @rhino.fi/wdk-protocol-swidge-rhinofi@1.0.0-beta.2 @tetherto/wdk-wallet-evm -``` +**Parameters:** +- `path` (string): The derivation path (e.g., "0'/0/0") -Install ERC-4337 support when you need smart-account execution: +**Returns:** `Promise\` - The wallet account -```bash -npm install @tetherto/wdk-wallet-evm-erc-4337 +**Example:** +```javascript +// Full derivation path: m/44'/60'/0'/0/1 +const account = await wallet.getAccountByPath("0'/0/1") ``` -## Create the Protocol +#### `getFeeRates()` +Returns current fee rates with ERC-4337 specific multipliers. -```javascript -import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' -import RhinofiProtocol from '@rhino.fi/wdk-protocol-swidge-rhinofi' +**Returns:** `Promise\<{normal: bigint, fast: bigint}\>` - Fee rates in wei -const account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://arb1.arbitrum.io/rpc' -}) +**Throws:** Error if no provider is configured -const rhinofi = new RhinofiProtocol(account, { - apiKey: process.env.RHINO_API_KEY, - maxNetworkFeeBps: 50, - maxProtocolFeeBps: 30 -}) +**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 ``` -## Discover Chains and Tokens +#### `dispose()` +Disposes all wallet accounts, clearing private keys from memory. +**Example:** ```javascript -const chains = await rhinofi.getSupportedChains() -const tokens = await rhinofi.getSupportedTokens({ - fromChain: 'ARBITRUM' -}) +// Clean up when done +wallet.dispose() ``` -The module reads live Rhino.fi chain and token config. The source chain must be derivable from the WDK account for execution. +## WalletAccountEvmErc4337 -## Quote a Route +Represents an individual ERC-4337 wallet account. Extends `WalletAccountReadOnlyEvmErc4337` and implements `IWalletAccount`. -```javascript -const quote = await rhinofi.quoteSwidge({ - fromToken: 'USDT', - toToken: 'USDC', - toChain: 'BASE', - recipient: '0xRecipient...', - fromTokenAmount: 1_000_000n -}) +### Constants -console.log('Expected output:', quote.toTokenAmount) -console.log('Minimum output:', quote.toTokenAmountMin) -console.log('Fees:', quote.fees) +The following constant is used internally for Safe account address derivation: + +```javascript +// Internal: used by predictSafeAddress() for deterministic address generation +const SALT_NONCE = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6' ``` -Use `toTokenAmount` instead of `fromTokenAmount` for exact-output routes when supported by the provider route. +> **Note:** This constant is not re-exported from the package entry point. Use `predictSafeAddress()` instead of referencing it directly. -## Execute a Route +### Constructor -After the user confirms the quote, call `swidge()`. +```javascript +new WalletAccountEvmErc4337(seed, path, 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)) +**Example:** ```javascript -const result = await rhinofi.swidge({ - fromToken: 'USDT', - toToken: 'USDC', - toChain: 'BASE', - recipient: '0xRecipient...', - fromTokenAmount: 1_000_000n +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' + } }) - -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 +### Methods -```javascript -const status = await rhinofi.getSwidgeStatus(result.id) +| 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 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` | - | -if (status.status === 'completed') { - console.log('Route completed') -} -``` +##### `getAddress()` +Returns the Safe smart contract wallet address (not the underlying EOA address). -## Handle Errors +**Returns:** `Promise\` - The Safe account's address +**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 address = await account.getAddress() +console.log('Safe account address:', address) // 0x... (Smart contract address) ``` - - -Review API, fee, and config-cache settings. - - -Detailed method, type, status, fee, and error reference. - - -*** - -## 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-erc-4337`](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` -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 current nonce and the 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/) | - -## Next Steps +##### `sendTransaction(tx, config?)` +Sends a transaction via UserOperation through the bundler. -To get started with WDK modules, follow these 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. Accepts a partial version of the gas payment mode fields (see [Config Override](#config-override)). -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 +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - UserOperation hash and fee -You can also: +**Throws:** Error if a non-sponsored, newly built operation exceeds `transactionMaxFee` -- 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 +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. -*** +**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) -## Wallet BTC Overview -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-btc -Description: Overview of the @tetherto/wdk-wallet-btc module +// Batch transactions +const batchResult = await account.sendTransaction([ + { to: '0x...', value: 100000000000000000n }, + { to: '0x...', value: 200000000000000000n } +]) -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 config override +const customResult = await account.sendTransaction({ + to: '0x...', + value: 1000000000000000000n +}, { + paymasterToken: { address: '0xNewToken...' } +}) - -**Default Derivation Path Change in v1.0.0-beta.4+** +// 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 +}) +``` -The default derivation path was updated in v1.0.0-beta.4 to use BIP-84 (Native SegWit) instead of BIP-44 (Legacy): +The account reserves a local nonce for each pending `sendTransaction()` or `transfer()` from the same account instance. Pre-acceptance failures release the reserved nonce; ambiguous transport failures keep it reserved to avoid nonce reuse. -- **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) +##### `quoteSendTransaction(tx, config?)` +Estimates the fee for a UserOperation without sending it. -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. +UserOperations built while quoting transaction inputs are cached internally for up to 2 minutes. When a later `sendTransaction()` or `signTransaction()` matches the quoted transaction, the account performs a lightweight on-chain nonce check before reuse and re-quotes if the nonce has moved. A supplied signed UserOperation is evaluated directly rather than rebuilt. -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 exceeds `transferMaxFee` +- Error if insufficient token balance -**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 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 +**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)) - - -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 - - +**Returns:** `Promise\<{fee: bigint}\>` - Fee estimate -*** +**Example:** +```javascript +const quote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}) +console.log('Transfer fee estimate:', quote.fee) +``` -### Need Help? +##### `getBalance()` +Returns the Safe account's native token balance. - +**Returns:** `Promise\` - Balance in wei -*** +**Example:** +```javascript +const balance = await account.getBalance() +console.log('Native balance:', balance, '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 +##### `getTokenBalance(tokenAddress)` +Returns the balance of a specific ERC20 token in the Safe account. -## Table of Contents +**Parameters:** +- `tokenAddress` (string): The ERC20 token contract address -| 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) | +**Returns:** `Promise\` - Token balance in base units -## WalletManagerBtc +**Example:** +```javascript +const tokenBalance = await account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') +console.log('USDT balance:', tokenBalance) // In 6 decimal units +``` -The main class for managing Bitcoin wallets. -Extends `WalletManager` from `@tetherto/wdk-wallet`. +##### `getPaymasterTokenBalance()` +Returns the balance of the configured paymaster token used for paying fees. -#### Constructor +**Returns:** `Promise\` - Paymaster token balance in base units +**Example:** ```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 - -### Methods +const paymasterBalance = await account.getPaymasterTokenBalance() +console.log('Paymaster token balance:', paymasterBalance) -| 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` | +// Check if sufficient for transaction +if (paymasterBalance < 10000n) { + console.warn('Low paymaster token balance - may not cover fees') +} +``` -##### `getAccount(index)` -Returns a wallet account at the specified index using BIP-84 (default) or BIP-44 derivation. +##### `approve(options, txOverrides?)` +Approves a spender to spend ERC20 tokens on behalf of the Safe account. **Parameters:** -- `index` (number, optional): The index of the account to get (default: 0) +- `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)) -**Returns:** `Promise` - The wallet account +**Returns:** `Promise\<{hash: string, fee: bigint}\>` - Transaction result **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 result = await account.approve({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + spender: '0xSpenderContract...', + amount: 1000000n // 1 USD₮ +}, { + callGasLimit: 90000n, + verificationGasLimit: 120000n +}) +console.log('Approval hash:', result.hash) ``` -##### `getAccountByPath(path)` -Returns a wallet account at the specified derivation path. +##### `getAllowance(token, spender)` +Returns the current token allowance for the given spender. **Parameters:** -- `path` (string): The derivation path (e.g., "0'/0/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.getAccountByPath("0'/0/1") +const allowance = await account.getAllowance( + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + '0xSpenderContract...' +) +console.log('Current allowance:', allowance) ``` -##### `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 +##### `getTransactionReceipt(hash)` +Returns a transaction receipt by hash. + +**Parameters:** +- `hash` (string): The transaction hash + +**Returns:** `Promise\` - Transaction receipt or null if not mined **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.getTransactionReceipt('0x...') +if (receipt) { + console.log('Confirmed in block:', receipt.blockNumber) +} ``` -##### `dispose()` -Disposes all wallet accounts, clears sensitive data from memory, and closes internal Electrum connections. +##### `getUserOperationReceipt(hash)` +Returns a UserOperation receipt by hash. -**Returns:** `void` +**Parameters:** +- `hash` (string): The UserOperation hash + +**Returns:** `Promise\` - UserOperation receipt or null if not processed **Example:** ```javascript -wallet.dispose() +const receipt = await account.getUserOperationReceipt('0x...') +if (receipt) { + console.log('UserOp receipt:', receipt) +} ``` -## WalletAccountBtc +##### `signTypedData(typedData)` +Signs EIP-712 typed structured data using the underlying EOA private key. -Represents an individual Bitcoin wallet account. Extends `WalletAccountReadOnlyBtc` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. +**Parameters:** +- `typedData` (TypedData): The typed data object containing domain, types, primaryType, and message -#### Constructor +**Returns:** `Promise\` - The typed data signature +**Example:** ```javascript -new WalletAccountBtc(seed, path, config) +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:** -- `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 +- `typedData` (TypedData): The original typed data object +- `signature` (string): The signature to verify -### Methods +**Returns:** `Promise\` - True if the signature is valid -| 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 isValid = await account.verifyTypedData(typedData, signature) +console.log('Typed data signature valid:', isValid) +``` -##### `getAddress()` -Returns the account's Bitcoin address (Native SegWit bech32 by default, or legacy if using BIP-44). +##### `getTokenBalances(tokenAddresses)` +Returns balances for multiple ERC20 tokens in a single call. -**Returns:** `Promise` - The Bitcoin address +**Parameters:** +- `tokenAddresses` (string[]): Array of ERC20 token contract addresses + +**Returns:** `Promise\\>` - Map of token address to balance in base units **Example:** ```javascript -const address = await account.getAddress() -console.log('Address:', address) // bc1q... (BIP-84) or 1... (BIP-44) +const balances = await account.getTokenBalances([ + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC +]) +for (const [address, balance] of balances) { + console.log(`Token ${address}: ${balance}`) +} ``` -##### `getBalance()` -Returns the account's total balance in satoshis, including unconfirmed funds when present. -**Returns:** `Promise` - Balance in satoshis +##### `toReadOnlyAccount()` +Creates a read-only copy of the account with the same Safe address and configuration. + +**Returns:** `Promise\` - Read-only account instance **Example:** ```javascript -const balance = await account.getBalance() -console.log('Balance:', balance, 'satoshis') -``` - -##### `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. +const readOnlyAccount = await account.toReadOnlyAccount() -**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) +// Can check balances but cannot send transactions +const balance = await readOnlyAccount.getBalance() +// readOnlyAccount.sendTransaction() // Would not be available +``` -**Returns:** `Promise<{hash: string, fee: bigint}>` -- `hash`: Transaction hash -- `fee`: Transaction fee in satoshis +##### `dispose()` +Disposes the wallet account, clearing private keys from memory. **Example:** ```javascript -const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 50000n -}) -console.log('Transaction hash:', result.hash) -console.log('Fee:', result.fee, 'satoshis') +account.dispose() ``` -##### `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) +### Properties -**Returns:** `Promise` - Signed raw transaction hex string +| 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 signedTransaction = await account.signTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 50000n, - feeRate: 10n -}) +console.log('Account index:', account.index) // 0, 1, 2, etc. +console.log('Account path:', account.path) // m/44'/60'/0'/0/0 -console.log('Signed transaction:', signedTransaction) +// ⚠️ 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) ``` -##### `quoteSendTransaction(options)` -Estimates the fee for a transaction without broadcasting it. +⚠️ **Security Note**: The `keyPair` property contains sensitive cryptographic material. Never log, display, or expose the private key. -**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) +## WalletAccountReadOnlyEvmErc4337 +Represents a read-only ERC-4337 wallet account that can query balances and estimate fees but cannot send transactions. -**Returns:** `Promise<{fee: bigint}>` -- `fee`: Estimated transaction fee in satoshis +### Constants + +The following constant is used internally for Safe account address derivation: -**Example:** ```javascript -const quote = await account.quoteSendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 50000n -}) -console.log('Estimated fee:', quote.fee, 'satoshis') +// Internal: used by predictSafeAddress() for deterministic address generation +const SALT_NONCE = '0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6' ``` -##### `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) +> **Note:** This constant is not re-exported from the package entry point. Use `predictSafeAddress()` instead of referencing it directly. -**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) +### Constructor -**Example:** ```javascript -const transfers = await account.getTransfers({ - direction: 'incoming', - limit: 5 -}) -console.log('Recent incoming transfers:', transfers) +new WalletAccountReadOnlyEvmErc4337(address, config) ``` -##### `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. +- `address` (string): The EOA address (owner address) +- `config` (`Omit`): Configuration object without send-only fee caps **Example:** ```javascript -const receipt = await account.getTransactionReceipt('abc123...') -if (receipt) { - console.log('Transaction confirmed') -} +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' + } +}) ``` -##### `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 +### Static Methods -**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) +| 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 { 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; // Safe modules version (e.g., '0.3.0') +} -```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 `config` parameter 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`. +The quote cache does not include these override fields in its key. Pass the same fee-mode and paymaster settings to a quote and its matching send, sign, or transfer. -#### Constructor +### EvmErc4337Transaction -```javascript -new ElectrumWs(config) +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 +24532,509 @@ 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', // '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', + 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...', // 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 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 +### Safe Modules Version -The `network` option specifies which Bitcoin network to use. +The `safeModulesVersion` option specifies the Safe modules version for smart contract wallet implementation. **Required** for smart account initialization. **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"` +**Required:** Yes **Example:** ```javascript const config = { - network: 'testnet' // Use testnet for development + safeModulesVersion: '0.3.0' } ``` -### BIP - -The `bip` option specifies the address type derivation standard to use. +### Paymaster Token -**Type:** `number` +The `paymasterToken` option specifies the ERC-20 token used for paying transaction fees through the paymaster. -**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) +**Type:** `object` +**Required:** Yes, for Paymaster Token mode only. Not used in Sponsorship Policy or Native Coins modes. -**Default:** `84` +**Properties:** +- `address` (string): The ERC-20 token contract address **Example:** ```javascript -// Use legacy addresses const config = { - bip: 44 + paymasterToken: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USD₮ + } } ``` -## 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. +### Sponsorship Policy ID -### Recommended Approach +The `sponsorshipPolicyId` option specifies the sponsorship policy identifier for sponsored transactions. -**For Production:** -- Set up your own Fulcrum server for optimal performance and reliability -- Use recent Fulcrum versions that support pagination for high-transaction addresses +**Type:** `string` +**Required:** No (optional), only used in Sponsorship Policy mode. -**For Development/Testing:** -- `fulcrum.frznode.com:50001` - Generally faster than default -- `electrum.blockstream.info:50001` - Default fallback +**Example:** +```javascript +const config = { + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy_id' +} +``` -### Configuration Examples +### Gas Payment Mode Flags -```javascript -import { ElectrumTcp, ElectrumTls } from '@tetherto/wdk-wallet-btc' +These boolean flags control which gas payment mode is used. Only one mode should be active at a time. -// Production with custom Fulcrum server -const productionClient = new ElectrumTls({ - host: 'your-fulcrum-server.com', - port: 50002 -}) +#### `isSponsored` -const productionWallet = new WalletManagerBtc(seedPhrase, { - client: productionClient, - network: 'bitcoin' -}) +Enables Sponsorship Policy mode, where a sponsor covers transaction fees. -// Development with alternative public server -const developmentClient = new ElectrumTcp({ - host: 'fulcrum.frznode.com', - port: 50001 -}) +**Type:** `boolean` +**Default:** `false` -const developmentWallet = new WalletManagerBtc(seedPhrase, { - client: developmentClient, - network: 'bitcoin' -}) +```javascript +const config = { + isSponsored: true, + paymasterUrl: 'https://api.candide.dev/public/v3/1' +} ``` -### Network-Specific Configuration +#### `useNativeCoins` -#### Bitcoin Mainnet +Enables Native Coins mode, where the user pays fees in the chain's native currency (ETH, MATIC, etc.). + +**Type:** `boolean` +**Default:** `false` ```javascript -import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' +const config = { + useNativeCoins: true, + transactionMaxFee: 100000000000000n, // Optional: max send/sign fee in wei + transferMaxFee: 100000000000000n // Optional: max transfer fee in wei +} +``` -const client = new ElectrumTcp({ - host: 'electrum.blockstream.info', // Or your own server - port: 50001 -}) +### Transaction Max Fee -const wallet = new WalletManagerBtc(seedPhrase, { - client, - network: 'bitcoin' -}) -``` +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. -#### Bitcoin Testnet +**Type:** `number | bigint` +**Required:** No (optional) +**Unit:** Paymaster token base units in Paymaster Token mode; native token base units in Native Coins mode +**Example:** ```javascript -import { ElectrumTcp } from '@tetherto/wdk-wallet-btc' - -const client = new ElectrumTcp({ - host: 'testnet.hsmiths.com', // Example testnet server - port: 53011 -}) +const config = { + transactionMaxFee: 100000, // 100,000 paymaster token units + transferMaxFee: 100000 +} -const wallet = new WalletManagerBtc(seedPhrase, { - client, - network: 'testnet' -}) +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 +25043,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 +25150,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 +25180,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,123 +25204,55 @@ 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), 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) } 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 exceeding the maximum fee 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 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 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) } } ``` @@ -14418,27 +25261,23 @@ try { ### 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) } finally { @@ -14448,20 +25287,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 +25312,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 +25322,397 @@ 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), 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('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): +Quotes are 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 the cached UserOperation. If the nonce has moved, it builds a fresh quote before sending. -```javascript title="Estimate Fee" -const quote = await account.quoteSendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n -}) -console.log('Estimated fee:', quote.fee, 'satoshis') -``` + +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. + -## Send with Custom Fee Rate +```javascript title="Quote Then Send" +const tx = { + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000n +} -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): +const quote = await account.quoteSendTransaction(tx) +console.log('Estimated fee:', quote.fee) -```javascript title="Custom Fee Rate" -const result = await account.sendTransaction({ - to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', - value: 100000n, - feeRate: 10n // sat/vB -}) +const result = await account.sendTransaction(tx) +console.log('Transaction hash:', result.hash) ``` +## Send Rapid Transactions + +When you call `sendTransaction()` or `transfer()` rapidly from the same account instance, the wallet reserves local nonces so concurrent UserOperations use sequential nonces. If submission fails before bundler acceptance, the reserved nonce is released. If the failure is ambiguous, such as a transport error after submission, the wallet keeps the nonce reserved to avoid accidental reuse. + -When `feeRate` is provided, the `confirmationTarget` parameter is ignored. +The wallet reads the on-chain nonce before reserving locally. If that read does not complete within 30 seconds, the operation fails instead of guessing the next nonce. -## Send with Confirmation Target +## Send with Custom Paymaster Token -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): +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="Confirmation Target" +```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. - -# Usage +## 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. -The `@tetherto/wdk-wallet-btc` module provides wallet management for the Bitcoin blockchain. +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), and [apply UserOperation gas overrides](#transfer-with-useroperation-gas-overrides). +## Transfer ERC-20 Tokens - - -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. - - +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) +``` - - -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 - - +## 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): -### Need Help? +```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): -## Wallet EVM Overview -URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm -Description: Overview of the @tetherto/wdk-wallet-evm module +```javascript title="Transfer with Fee Limit" +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}, { + transferMaxFee: 100000 +}) +``` -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. + +If the estimated fee exceeds `transferMaxFee`, the transfer is cancelled with an "Exceeded maximum fee" error. + -## Features +## Transfer with UserOperation Gas Overrides -- **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 +Use the third argument to pass UserOperation gas or EIP-1559 fee overrides through `transfer()` or `quoteTransfer()`. Set `maxFeePerGas` and `maxPriorityFeePerGas` together. -## Supported Networks +```javascript title="Transfer with Gas Overrides" +const txOverrides = { + callGasLimit: 90000n, + verificationGasLimit: 120000n, + maxFeePerGas: 30000000000n, + maxPriorityFeePerGas: 2000000000n +} -This package works with any EVM-compatible blockchain, including: +const quote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}, undefined, txOverrides) -- **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...** +const result = await account.transfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000 +}, undefined, txOverrides) +``` ## Next Steps - - -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 - - - -*** - -### Need Help? - - +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. -```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, - transferMaxFee: 100000000000000n // Optional: max fee in wei +**Example:** +```javascript +const wallet = new WalletManagerEvm(seedPhrase, { + provider: 'https://rpc.mevblocker.io/fast', + 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 +25720,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 +25745,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 +25762,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 +25886,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 +26215,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 +26316,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) -} -``` - -##### `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) + console.log('Status:', receipt.status) // 1 = success, 0 = failed +} ``` -##### `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 +26394,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 +26405,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 +26482,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 +26498,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 +26553,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 +26756,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,11 +26766,11 @@ 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 } ``` @@ -15954,65 +26778,62 @@ interface TypedDataDomain { ```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 + 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,480 +26843,320 @@ 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 + // 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, - 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' - } + + // Optional: Additional failover attempts when provider is an array + retries: 2, + + // Optional: Maximum fee for ERC-20 transfer operations (in wei) + transferMaxFee: 100000000000000, // 0.0001 ETH + + // Optional: Maximum fee for native send/provider-backed sign operations (in wei) + transactionMaxFee: 100000000000000 } -const wallet = new WalletManagerEvmErc4337(seedPhrase, config) +const wallet = new WalletManagerEvm(seedPhrase, config) +``` + +## Signer Configuration + +`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: + +```javascript title="Create A Manager From A Seed Signer" +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import { SeedSignerEvm } from '@tetherto/wdk-wallet-evm/signers' + +const signer = new SeedSignerEvm(seedPhrase) + +const wallet = new WalletManagerEvm(signer, { + provider: 'https://eth.drpc.org' +}) +``` + +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: + +```javascript title="Register A Private-Key Signer" +import WalletManagerEvm from '@tetherto/wdk-wallet-evm' +import { PrivateKeySignerEvm } from '@tetherto/wdk-wallet-evm/signers' + +const wallet = new WalletManagerEvm(seedPhrase, { + provider: 'https://eth.drpc.org' +}) + +wallet.addSigner('treasury', new PrivateKeySignerEvm(privateKey)) + +const treasury = await wallet.getAccount('treasury') +``` + +For a standalone account backed by one private key, construct the account directly: + +```javascript title="Standalone Private-Key Account" +import { WalletAccountEvm } from '@tetherto/wdk-wallet-evm' + +const account = WalletAccountEvm.fromPrivateKey(privateKey, { + provider: 'https://eth.drpc.org' +}) ``` ## Account Configuration -Both `WalletAccountEvmErc4337` and `WalletAccountReadOnlyEvmErc4337` use the same configuration structure: +Both `WalletAccountEvm` and `WalletAccountReadOnlyEvm` share similar configuration options: ```javascript -import { WalletAccountEvmErc4337, WalletAccountReadOnlyEvmErc4337 } from '@tetherto/wdk-wallet-evm-erc-4337' +import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '@tetherto/wdk-wallet-evm' // Full access account -const account = new WalletAccountEvmErc4337( +const account = new WalletAccountEvm( seedPhrase, "0'/0/0", // BIP-44 derivation path - config // Same config as wallet manager + { + provider: 'https://eth.drpc.org', + transferMaxFee: 100000000000000, + transactionMaxFee: 100000000000000 + } ) -// Read-only account (transferMaxFee not needed) -const readOnlyAccount = new WalletAccountReadOnlyEvmErc4337( - '0x...', // Smart contract wallet address +// Read-only account +const readOnlyAccount = new WalletAccountReadOnlyEvm( + '0x...', // Ethereum 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 + provider: 'https://eth.drpc.org' } ) ``` ## 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 } - -// Polygon Mainnet -const config = { chainId: 137 } - -// Arbitrum One -const config = { chainId: 42161 } - -// Avalanche C-Chain -const config = { chainId: 43114 } - -``` - ### 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. +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 | Eip1193Provider | Array` -**Required:** Yes **Examples:** + ```javascript -// Using RPC URL +// Option 1: Using RPC URL const config = { - provider: 'https://rpc.mevblocker.io/fast' + provider: 'https://eth.drpc.org' } -// Using browser provider (MetaMask) +// Option 2: Using browser provider (e.g., MetaMask) const config = { provider: window.ethereum } -// Using custom ethers provider -import { JsonRpcProvider } from 'ethers' -const config = { - provider: new JsonRpcProvider('https://rpc.mevblocker.io/fast') +// 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 + } + } } -// 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 + provider: createFetchProvider('https://eth.drpc.org') } -``` - -### 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. - -**Type:** `number` -**Required:** No (optional) -**Default:** `3` -```javascript +// Option 4: Using ordered provider failover const config = { provider: [ - 'https://rpc.mevblocker.io/fast', - 'https://eth.llamarpc.com' + 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY', + 'https://eth.drpc.org', + createFetchProvider('https://ethereum.publicnode.com') ], - retries: 5 + retries: 2 } ``` -### 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. - -**Type:** `string` -**Required:** Yes - -**Example:** -```javascript -const config = { - bundlerUrl: 'https://api.candide.dev/public/v3/1' -} -``` +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. -### Paymaster URL +### Retries -The `paymasterUrl` option specifies the URL of the paymaster service that sponsors transaction fees using ERC-20 tokens or sponsorship policies. +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:** `string` -**Required:** Yes, for Paymaster Token mode and Sponsorship Policy mode. Not used in Native Coins mode. +**Type:** `number` (optional) +**Default:** `3` **Example:** ```javascript const config = { - paymasterUrl: 'https://api.candide.dev/public/v3/1' + provider: [ + 'https://primary.example', + 'https://secondary.example' + ], + retries: 1 } ``` -### Paymaster Address +### Chain ID -The `paymasterAddress` option specifies the address of the paymaster smart contract. +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:** `string` -**Required:** Yes, for Paymaster Token mode only. Not used in Sponsorship Policy or Native Coins modes. +**Type:** `number` (optional) **Example:** ```javascript const config = { - paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba' + provider: 'https://polygon-rpc.com', + chainId: 137 } ``` -### On-Chain Identifier - -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:** `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'`) - -```javascript -// String form -const config = { - onChainIdentifier: 'my-project' -} - -// Object form -const config = { - onChainIdentifier: { - project: 'my-project', - platform: 'Mobile', - tool: 'my-wallet', - toolVersion: '1.0.0' - } -} -``` +### Transfer Max Fee -### Safe Modules Version +The `transferMaxFee` option sets a maximum fee limit for ERC-20 `transfer()` operations. -The `safeModulesVersion` option specifies the Safe modules version for smart contract wallet implementation. **Required** for smart account initialization. +**Type:** `number | bigint` (optional) +**Unit:** Wei (1 ETH = 1000000000000000000 Wei) -**Type:** `string` -**Required:** Yes +**Examples:** -**Example:** ```javascript const config = { - safeModulesVersion: '0.3.0' + // Set maximum fee to 0.0001 ETH + transferMaxFee: 100000000000000n, } -``` - -### 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₮ +// 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') } } ``` -### Sponsorship Policy ID +### Transaction Max Fee -The `sponsorshipPolicyId` option specifies the sponsorship policy identifier for sponsored transactions. +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. -**Type:** `string` -**Required:** No (optional), only used in Sponsorship Policy mode. +**Type:** `number | bigint` (optional) +**Unit:** Wei (1 ETH = 1000000000000000000 Wei) **Example:** ```javascript const config = { - isSponsored: true, - sponsorshipPolicyId: 'sp_my_policy_id' -} -``` - -### Gas Payment Mode Flags - -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 -const config = { - isSponsored: true, - paymasterUrl: 'https://api.candide.dev/public/v3/1' -} -``` - -#### `useNativeCoins` - -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, - transferMaxFee: 100000000000000n // Optional: max fee in wei + provider: 'https://eth.drpc.org', + transactionMaxFee: 100000000000000n } ``` -### 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. +### Fee Rate Multipliers -**Type:** `number | bigint` -**Required:** No (optional) -**Unit:** Paymaster token base units +The wallet manager uses predefined multipliers for fee calculations: -**Example:** ```javascript -const config = { - transferMaxFee: 100000 // 100,000 paymaster token units (e.g., 0.1 USD₮ if 6 decimals) -} +// Normal fee rate = base fee × 1.1 +const normalFee = await wallet.getFeeRates() +console.log('Normal fee:', normalFee.normal) -// 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') - } -} +// Fast fee rate = base fee × 2.0 +const fastFee = await wallet.getFeeRates() +console.log('Fast fee:', fastFee.fast) ``` -## Network-Specific Configurations - -### Ethereum Mainnet - - -**Supported Paymaster Tokens** - -The following tokens are supported for gas payments on Ethereum Mainnet: +## Network Support -* **USD₮**: `0xdAC17F958D2ee523a2206206994597C13D831ec7` -* **USA₮**: `0x07041776f5007aca2a54844f50503a18a72a8b68` -* **XAU₮**: `0x68749665ff8d2d112fa859aa293f07a622782f38` - +The configuration works with any EVM-compatible network. Just change the provider URL: ```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) +// Ethereum Mainnet +const mainnetConfig = { + provider: 'https://eth.drpc.org' } -``` - -### Polygon Mainnet -```javascript +// Polygon (Matic) 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 + provider: 'https://polygon-rpc.com' } -``` - -### Arbitrum One -```javascript +// Arbitrum 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 + provider: 'https://arb1.arbitrum.io/rpc' } -``` -### Avalanche C-Chain +// BSC (Binance Smart Chain) +const bscConfig = { + provider: 'https://bsc-dataseed.binance.org' +} -``` javascript +// Avalanche C-Chain 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) } -``` - -### Plasma -```javascript -// Plasma (example Layer 2) +// Plasma 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) } -``` - -### Sepolia Testnet (USD₮ ERC-20 mock/testnet only) -````javascript - -// 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) +// Stable (uses USD₮ as native gas token) +// No need for ERC-4337 paymaster/bundler setup. +const stableConfig = { + provider: 'https://rpc.stable.xyz', } -// Candide -const sepoliaConfigCandide = { - chainId: 11155111, +// Sepolia Testnet +const sepoliaConfig = { 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 } -```` -**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/) - -**USD₮ on Sepolia contract:** [0xd077a400968890eacc75cdc901f0356c943e4fdb](https://sepolia.etherscan.io/address/0xd077a400968890eacc75cdc901f0356c943e4fdb) +``` -**Get test USD₮:** -- [Pimlico faucet](https://dashboard.pimlico.io/test-erc20-faucet) -- [Candide faucet](https://dashboard.candide.dev/faucet) +## Next Steps @@ -16504,49 +27165,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 +27222,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) -``` +## 3. Get Your First Account -## Verify a Signature +Retrieve an account from the wallet and inspect its address. -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="Get Account" +const account = await wallet.getAccount(0) +const address = await account.getAddress() +console.log('Wallet address:', address) -```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!' - } -} - -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): + +**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. + -```javascript title="Verify Typed Data" -const readOnlyAccount = await account.toReadOnlyAccount() -const isValid = await readOnlyAccount.verifyTypedData(typedData, typedDataSignature) -console.log('Typed data signature valid:', isValid) -``` + +To use test/mock tokens instead of real funds, see the [configuration section](/sdk/wallet-modules/wallet-evm/configuration#network-support). + ## Next Steps -Learn how to [handle errors and manage resources](/sdk/wallet-modules/wallet-evm-erc-4337/guides/handle-errors). +With your wallet ready, learn how to [manage multiple accounts](/sdk/wallet-modules/wallet-evm/guides/manage-accounts). *** -## 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 +## 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. -You can estimate the fee for a token transfer without executing it using [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm-erc-4337/api-reference): +This guide explains how to retrieve multiple accounts from your EVM wallet and use custom derivation paths. -```javascript title="Estimate Transfer Fee" -const quote = await account.quoteTransfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}) -console.log('Estimated transfer fee:', quote.fee) -``` +## Retrieve Accounts by Index -## Transfer with Maximum Fee Limit +Use `getAccount()` with a zero-based index to access accounts derived from the default BIP-44 path (`m/44'/60'/0'/0/{index}`). -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="Get Accounts by Index" +const account = await wallet.getAccount(0) +const address = await account.getAddress() +console.log('Account 0 address:', address) -```javascript title="Transfer with Fee Limit" -const result = await account.transfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}, { - transferMaxFee: 100000 -}) +const account1 = await wallet.getAccount(1) +const address1 = await account1.getAddress() +console.log('Account 1 address:', address1) ``` - -If the estimated fee exceeds `transferMaxFee`, the transfer is cancelled with an "Exceeded maximum fee" error. - - -## 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. +## Retrieve Account by Custom Derivation Path +Use `getAccountByPath()` when you need a specific hierarchy beyond the default sequential index. - - -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. - - +```javascript title="Custom Derivation Path" +const customAccount = await wallet.getAccountByPath("0'/0/5") +const customAddress = await customAccount.getAddress() +console.log('Custom account address:', customAddress) +``` +## Retrieve Accounts from Named Signers - - -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 - - +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' -### Need Help? +const wallet = new WalletManagerEvm(seedPhrase, { + provider: 'https://eth.drpc.org' +}) - +wallet.addSigner('treasury', new PrivateKeySignerEvm(privateKey)) -*** +const treasuryAccount = await wallet.getAccount('treasury') +console.log('Treasury address:', await treasuryAccount.getAddress()) +``` -## 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 +If a registered signer supports derivation, you can derive accounts from that signer by passing `signerName`: -## Table of Contents +```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' }) +``` -| 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) | +## Iterate Over Multiple Accounts -## WalletManagerEvm +You can loop through accounts to inspect addresses and balances in bulk. -The main class for managing EVM wallets. -Extends `WalletManager` from `@tetherto/wdk-wallet`. +```javascript title="Multi-Account Iteration" +async function listAccounts(wallet) { + const accounts = [] -### Constructor + for (let i = 0; i < 5; i++) { + const account = await wallet.getAccount(i) + const address = await account.getAddress() + const balance = await account.getBalance() -```javascript -new WalletManagerEvm(seed, config?) -``` + accounts.push({ + index: i, + path: `m/44'/60'/0'/0/${i}`, + address, + balance + }) -**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) + console.log(`Account ${i}:`, { address, balance: balance.toString() }) + } -**Example:** -```javascript -const wallet = new WalletManagerEvm(seedPhrase, { - provider: 'https://rpc.mevblocker.io/fast', - transferMaxFee: 100000000000000 // Maximum fee in wei -}) + return accounts +} ``` -### Methods +## Next Steps -| 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` | - | +Now that you can access your accounts, learn how to [check balances](/sdk/wallet-modules/wallet-evm/guides/check-balances). -### Properties +*** -| Property | Type | Description | -|----------|------|-------------| -| `seed` | `Uint8Array` | The wallet's seed bytes | +## 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. -#### `getRandomSeedPhrase(wordCount?)` (static) -Returns a random BIP-39 seed phrase. +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). -**Parameters:** -- `wordCount` (12 | 24, optional): The number of words in the seed phrase (default: 12) + +**BigInt Usage:** Always use `BigInt` (the `n` suffix) for monetary values to avoid precision loss with large numbers. + -**Returns:** `string` - The seed phrase +## Send with EIP-1559 Gas Parameters -**Example:** -```javascript -const seedPhrase = WalletManagerEvm.getRandomSeedPhrase() -console.log('Seed phrase:', seedPhrase) // 12 words +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. -const longSeedPhrase = WalletManagerEvm.getRandomSeedPhrase(24) -console.log('Long seed phrase:', longSeedPhrase) // 24 words +```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') ``` -#### `isValidSeedPhrase(seedPhrase)` (static) -Checks if a seed phrase is valid. - -**Parameters:** -- `seedPhrase` (string): The seed phrase to validate +## Send with Legacy Gas Parameters -**Returns:** `boolean` - True if the seed phrase is valid +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. -**Example:** -```javascript -const isValid = WalletManagerEvm.isValidSeedPhrase('abandon abandon abandon ...') -console.log('Valid:', isValid) +```javascript title="Legacy Transaction" +const legacyResult = await account.sendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n, + gasPrice: 20000000000n, + gasLimit: 21000 +}) +console.log('Transaction hash:', legacyResult.hash) ``` -#### `getAccount(index?)` -Returns a wallet account at the specified index following BIP-44 standard. +## Deploy a Contract -**Parameters:** -- `index` (number, optional): The index of the account to get (default: 0) +For contract-creation transactions, omit `to` or pass `to: null` and provide the deployment bytecode in `data`. -**Returns:** `Promise` - The wallet account +```javascript title="Contract Creation" +const result = await account.sendTransaction({ + to: null, + value: 0n, + data: contractBytecode, + maxFeePerGas: 30000000000n, + maxPriorityFeePerGas: 2000000000n +}) -**Example:** -```javascript -// Get first account (index 0) -const account = await wallet.getAccount(0) +console.log('Deployment transaction:', result.hash) +``` -// Get second account (index 1) -const account1 = await wallet.getAccount(1) +## Sign Without Broadcasting -// Get first account (default) -const defaultAccount = await wallet.getAccount() -``` +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. -#### `getAccountByPath(path)` -Returns a wallet account at the specified BIP-44 derivation path. +```javascript title="Sign EVM Transaction" +const signedTransaction = await account.signTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n, + chainId: 1, + maxFeePerGas: 30000000000n, + maxPriorityFeePerGas: 2000000000n +}) -**Parameters:** -- `path` (string): The derivation path (e.g., "0'/0/0") +console.log('Signed transaction:', signedTransaction) +``` -**Returns:** `Promise` - The wallet account + +`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 -// Full path: m/44'/60'/0'/0/1 -const account = await wallet.getAccountByPath("0'/0/1") +## Estimate Transaction Fees -// Custom path: m/44'/60'/0'/0/5 -const customAccount = await wallet.getAccountByPath("0'/0/5") +Use [`account.quoteSendTransaction()`](/sdk/wallet-modules/wallet-evm/api-reference#quotesendtransactiontx) to get a fee estimate before sending. + +```javascript title="Quote Transaction Fee" +const quote = await account.quoteSendTransaction({ + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + value: 1000000000000000000n +}) +console.log('Estimated fee:', quote.fee, 'wei') ``` -#### `getFeeRates()` -Returns current fee rates based on network conditions with predefined multipliers. +## Cap Transaction Fees -**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) +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. -**Throws:** Error if no provider is configured +```javascript title="Cap Native Transaction Fees" +const wallet = new WalletManagerEvm(seedPhrase, { + provider: 'https://eth.drpc.org', + transactionMaxFee: 100000000000000n +}) +``` -**Example:** -```javascript +## Use Dynamic Fee Rates + +Retrieve current fee rates using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-evm/api-reference#getfeerates) and apply them to your transaction. + +```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') -// Use in transaction const result = await account.sendTransaction({ - to: '0x...', + to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', value: 1000000000000000000n, - maxFeePerGas: feeRates.fast + data: '0x', + gasLimit: 21000, + maxFeePerGas: feeRates.fast, + maxPriorityFeePerGas: 2000000000n }) +console.log('Transaction sent:', result.hash) +console.log('Fee paid:', result.fee, 'wei') ``` -#### `dispose()` -Disposes all wallet accounts, clearing private keys from memory. + +**Gas Estimation:** The `maxFeePerGas` and `maxPriorityFeePerGas` fields enable EIP-1559 transactions, ensuring more predictable gas fees and faster inclusion times. + -**Example:** -```javascript -// Clean up when done -wallet.dispose() -``` +## Next Steps -## WalletAccountEvm +To transfer ERC-20 tokens instead of native tokens, see [Transfer ERC-20 Tokens](/sdk/wallet-modules/wallet-evm/guides/transfer-tokens). -Represents an individual wallet account. Extends `WalletAccountReadOnlyEvm` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. +*** -### Constructor +## 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. -```javascript -new WalletAccountEvm(seed, path, config?) +This guide explains how to sign arbitrary messages with an owned account and verify signatures using a read-only account. + +## Sign a Message + +Use [`account.sign()`](/sdk/wallet-modules/wallet-evm/api-reference#signmessage) to produce a cryptographic signature for any string message. + +```javascript title="Sign a Message" +const message = 'Hello, Ethereum!' +const signature = await account.sign(message) +console.log('Signature:', signature) ``` -**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) +## Verify a Signature -**Throws:** -- Error if seed phrase is invalid (BIP-39 validation fails) +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 account = new WalletAccountEvm(seedPhrase, "0'/0/0", { - provider: 'https://rpc.mevblocker.io/fast', - transferMaxFee: 100000000000000 -}) +```javascript title="Verify a Signature" +const readOnlyAccount = await account.toReadOnlyAccount() +const isValid = await readOnlyAccount.verify(message, signature) +console.log('Signature valid:', isValid) ``` -### Methods + +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. + -| 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. +For best practices on handling errors, managing fees, and cleaning up memory, see [Error Handling](/sdk/wallet-modules/wallet-evm/guides/error-handling). -**Returns:** `Promise` - Checksummed Ethereum address +*** -**Example:** -```javascript -const address = await account.getAddress() -console.log('Account address:', address) // 0x... -``` +## 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. -#### `sign(message)` -Signs a message using the account's private key. +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 message to sign +## Transfer Tokens -**Returns:** `Promise` - The message signature +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) -console.log('Signature:', signature) +```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') ``` -#### `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 +## Estimate Transfer Fees -**Returns:** `Promise` - The typed data signature +Use [`account.quoteTransfer()`](/sdk/wallet-modules/wallet-evm/api-reference#quotetransferoptions) to get a fee estimate before executing the transfer. -**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="Quote Token Transfer" +const transferQuote = await account.quoteTransfer({ + token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', + amount: 1000000000000000000n +}) +console.log('Transfer fee estimate:', transferQuote.fee, 'wei') ``` -#### `signTransaction(tx)` -Signs an EVM transaction and returns the signed raw transaction as a hex string. This method does not broadcast the transaction. +## 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` - Signed raw transaction hex string +### 1. Validate Addresses -**Example:** -```javascript -const signedTransaction = await account.signTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000n, - chainId: 1 -}) +```javascript title="Address Validation" +if (!tokenAddress.startsWith('0x') || tokenAddress.length !== 42) { + throw new Error('Invalid token address') +} -console.log('Signed transaction:', signedTransaction) +if (!recipient.startsWith('0x') || recipient.length !== 42) { + throw new Error('Invalid recipient address') +} ``` -#### `verify(message, signature)` -Verifies a message signature against the account's address. +### 2. Check Balances -**Parameters:** -- `message` (string): The original message -- `signature` (string): The signature to verify +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: -**Returns:** `Promise` - True if signature is valid +```javascript title="Balance Check" +const balance = await account.getTokenBalance(tokenAddress) +if (balance < amount) { + throw new Error('Insufficient token balance') +} -**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 +const nativeBalance = await account.getBalance() +if (nativeBalance === 0n) { + throw new Error('Need ETH for gas fees') +} ``` -#### `verifyTypedData(typedData, signature)` -Verifies a typed data signature according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). +### 3. Quote and Execute Transfer -**Parameters:** -- `typedData` (TypedData): The typed data that was signed -- `signature` (string): The signature to verify +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: -**Returns:** `Promise` - True if signature is valid +```javascript title="Quote and Execute" +const quote = await account.quoteTransfer({ + token: tokenAddress, + recipient, + amount +}) +console.log('Transfer fee estimate:', quote.fee, 'wei') -**Example:** -```javascript -const isValid = await account.verifyTypedData(typedData, signature) -console.log('Typed data signature valid:', isValid) // true +const result = await account.transfer({ + token: tokenAddress, + recipient, + amount +}) +console.log('Transfer completed:', result.hash) +console.log('Fee paid:', result.fee, 'wei') ``` -#### `sendTransaction(tx)` -Sends an EVM transaction and returns the result with hash and fee. +## Next Steps -**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 +Learn how to [sign and verify messages](/sdk/wallet-modules/wallet-evm/guides/sign-verify-messages) with your EVM account. + +*** + +## Usage +URL: https://docs.wdk.tether.io/sdk/wallet-modules/wallet-evm/usage +Description: Guide to using the @tetherto/wdk-wallet-evm module. + +The `@tetherto/wdk-wallet-evm` module provides wallet management for Ethereum and EVM-compatible blockchains. + + + +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. + + + + + +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<{hash: string, fee: bigint}>` - Transaction result +--- -**Throws:** Error if no provider is configured +### Need Help? -**Example:** -```javascript -// EIP-1559 transaction -const result = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000, // 1 ETH in wei - maxFeePerGas: 30000000000, - maxPriorityFeePerGas: 2000000000 -}) + -// Legacy transaction -const legacyResult = await account.sendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000, - gasPrice: 20000000000, - gasLimit: 21000 -}) +*** -console.log('Transaction hash:', result.hash) -console.log('Transaction fee:', result.fee, '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. -#### `quoteSendTransaction(tx)` -Estimates the fee for an EVM transaction without sending it. +Use the Solana wallet module to create SLIP-0010 accounts, read SOL and SPL token balances, sign messages, and send Solana transactions. -**Parameters:** -- `tx` (EvmTransaction): The transaction object (same format as sendTransaction) + +**Default Derivation Path Change in v1.0.0-beta.4+** -**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei +The default derivation path was updated in v1.0.0-beta.4 to match ecosystem conventions: -**Throws:** Error if no provider is configured +- **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'` -**Example:** -```javascript -const quote = await account.quoteSendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000 -}) -console.log('Estimated fee:', quote.fee, 'wei') -``` +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. -#### `transfer(options)` -Transfers ERC20 tokens to another address using the standard transfer function. +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. + -**Parameters:** -- `options` (TransferOptions): Transfer options - - `token` (string): Token contract address - - `recipient` (string): Recipient address - - `amount` (number | bigint): Amount in token base units +## Features -**Returns:** `Promise<{hash: string, fee: bigint}>` - Transfer result +- **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 -**Throws:** -- Error if no provider is configured -- Error if fee exceeds `transferMaxFee` (if configured) +## Supported Networks -**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') -``` +This package works with the Solana blockchain, including: -#### `quoteTransfer(options)` -Estimates the fee for an ERC20 token transfer. +- **Solana Mainnet** +- **Solana Devnet** +- **Solana Testnet** +- **Localnet** -**Parameters:** -- `options` (TransferOptions): Transfer options (same as transfer) +## Next Steps -**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei + + +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 + + -**Throws:** Error if no provider is configured +*** -**Example:** -```javascript -const quote = await account.quoteTransfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}) -console.log('Transfer fee estimate:', quote.fee, 'wei') -``` +### Need Help? -#### `getBalance()` -Returns the native token balance (ETH, MATIC, BNB, etc.). + -**Returns:** `Promise` - Balance in wei +*** -**Throws:** Error if no provider is configured +## 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. -**Example:** -```javascript -const balance = await account.getBalance() -console.log('Balance:', balance, 'wei') -console.log('Balance in ETH:', balance / 1000000000000000000) -``` +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. -#### `getTokenBalance(tokenAddress)` -Returns the balance of a specific ERC20 token using the balanceOf function. + +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. + -**Parameters:** -- `tokenAddress` (string): The ERC20 token contract address + +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. + -**Returns:** `Promise` - Token balance in base units +## Features -**Throws:** Error if no provider is configured +- **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. -**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') -``` +## Supported Networks -#### `getTokenBalances(tokenAddresses)` -Returns balances for multiple ERC20 tokens in one call. +The module works with Solana RPC providers and Kora-compatible paymasters on networks where your paymaster is deployed and funded: -**Parameters:** -- `tokenAddresses` (string[]): List of ERC20 token contract addresses +- Solana Mainnet Beta +- Solana Devnet +- Solana Testnet -**Returns:** `Promise>` - Object mapping each token address to its balance in base units +## Key Exports -**Throws:** Error if no provider is configured +| 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. | -**Example:** -```javascript -const balances = await account.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT -]) +## Next Steps -console.log('USDT:', balances['0xdAC17F958D2ee523a2206206994597C13D831ec7']) -console.log('XAUT:', balances['0x68749665FF8D2d112Fa859AA293F07A622782F38']) -``` + + +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. + + -#### `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) +## 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. + + +This page documents the published `@tetherto/wdk-wallet-solana-gasless@1.0.0-beta.2` type declarations and runtime behavior. + + +## Imports + +```javascript title="Default import" +import WalletManagerSolanaGasless from '@tetherto/wdk-wallet-solana-gasless' ``` -#### `getAllowance(token, spender)` -Returns the current token allowance for the given spender. +```javascript title="Named imports" +import { + WalletAccountReadOnlySolanaGasless, + WalletAccountSolanaGasless +} from '@tetherto/wdk-wallet-solana-gasless' +``` -**Parameters:** -- `token` (string): ERC20 token contract address -- `spender` (string): The spender's address +## Exports -**Returns:** `Promise` - The current allowance +| 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`. -**Throws:** Error if no provider is configured +### Constructor -**Example:** -```javascript -const allowance = await account.getAllowance( - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - '0xSpenderContract...' +```typescript +new WalletManagerSolanaGasless( + seed: string | Uint8Array, + config?: SolanaGaslessWalletConfig ) -console.log('Current allowance:', allowance) ``` -#### `getTransactionReceipt(hash)` -Returns a transaction receipt by hash. - **Parameters:** -- `hash` (string): The transaction hash - -**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 -} -``` +- `seed`: BIP-39 mnemonic seed phrase or seed bytes. +- `config`: Solana RPC and Kora-compatible paymaster configuration. -#### `toReadOnlyAccount()` -Creates a read-only copy of the account with the same configuration. +### Methods -**Returns:** `Promise` - Read-only account instance +| 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` | -**Example:** -```javascript -const readOnlyAccount = await account.toReadOnlyAccount() +#### getAccount -// Can check balances but cannot send transactions -const balance = await readOnlyAccount.getBalance() -// readOnlyAccount.sendTransaction() // Would throw error +```typescript +getAccount(index?: number): Promise ``` -#### `dispose()` -Disposes the wallet account, erasing the private key from memory. +Returns the account for `m/44'/501'/index'/0'`. If `index` is omitted, the module uses `0`. -**Example:** -```javascript -// Clean up when done -account.dispose() +```javascript title="Get the first account" +const wallet = new WalletManagerSolanaGasless(seedPhrase, config) +const account = await wallet.getAccount(0) ``` -### Properties +#### getAccountByPath -| 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`) | +```typescript +getAccountByPath(path: string): Promise +``` -**Example:** -```javascript -console.log('Account index:', account.index) // 0, 1, 2, etc. -console.log('Account path:', account.path) // m/44'/60'/0'/0/0 +Returns the account at a specific Solana SLIP-0010 derivation path. -// ⚠️ 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="Get an account by path" +const account = await wallet.getAccountByPath("0'/0'/1'") ``` -⚠️ **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. - -## WalletAccountReadOnlyEvm +## WalletAccountSolanaGasless -Represents a read-only wallet account that can query balances and estimate fees but cannot send transactions. +Owned Solana gasless account. Extends `WalletAccountReadOnlySolanaGasless` and implements `IWalletAccount` from `@tetherto/wdk-wallet`. ### Constructor -```javascript -new WalletAccountReadOnlyEvm(address, config?) +```typescript +new WalletAccountSolanaGasless( + seed: string | Uint8Array, + path: string, + 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' -}) -``` +- `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. ### Properties -| Property | Type | Description | -|----------|------|-------------| -| `address` | `string` | The account's Ethereum address | +| 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` | ### 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. - -**Returns:** `Promise` - Checksummed Ethereum address +| 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 address = await readOnlyAccount.getAddress() -console.log('Account address:', address) // 0x... +```typescript +getAddress(): Promise ``` -#### `getBalance()` -Returns the account's native token balance. - -**Returns:** `Promise` - Balance in wei +Returns the account's base58-encoded Solana address. -**Throws:** Error if no provider is configured +#### sign -**Example:** -```javascript -const balance = await readOnlyAccount.getBalance() -console.log('Balance:', balance, 'wei') +```typescript +sign(message: string): Promise ``` -#### `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 +Signs a message and returns its signature. -**Throws:** Error if no provider is configured +#### signTransaction -**Example:** -```javascript -const tokenBalance = await readOnlyAccount.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') -console.log('USDT balance:', tokenBalance) +```typescript +signTransaction( + tx: SolanaTransaction, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise ``` -#### `getTokenBalances(tokenAddresses)` -Returns balances for multiple ERC20 tokens. - -**Parameters:** -- `tokenAddresses` (string[]): List of ERC20 token contract addresses +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>` - Object mapping each token address to its balance in base units +The method throws when the quoted paymaster fee is greater than `transactionMaxFee`. -**Throws:** Error if no provider is configured +#### sendTransaction -**Example:** -```javascript -const balances = await readOnlyAccount.getTokenBalances([ - '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT - '0x68749665FF8D2d112Fa859AA293F07A622782F38' // XAUT -]) -console.log('Balances:', balances) +```typescript +sendTransaction( + tx: SolanaTransaction | FullySignedTransaction, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise ``` -#### `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 +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'`. -**Example:** -```javascript -const quote = await readOnlyAccount.quoteSendTransaction({ - to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - value: 1000000000000000000 +```javascript title="Send native SOL" +const result = await account.sendTransaction({ + to: 'Recipient1111111111111111111111111111111', + value: 1000000n +}, { + transactionMaxFee: 500000n }) -console.log('Estimated fee:', quote.fee, 'wei') -``` - -#### `quoteTransfer(options)` -Estimates the fee for an ERC20 token transfer. - -**Parameters:** -- `options` (TransferOptions): Transfer options - -**Returns:** `Promise<{fee: bigint}>` - Fee estimate in wei -**Throws:** Error if no provider is configured - -**Example:** -```javascript -const quote = await readOnlyAccount.quoteTransfer({ - token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', - amount: 1000000 -}) -console.log('Transfer fee estimate:', quote.fee, 'wei') +console.log(result.hash) +console.log(result.fee) ``` -#### `verify(message, signature)` -Verifies a message signature against the account's address. +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. -**Parameters:** -- `message` (string): The original message -- `signature` (string): The signature to verify +#### transfer -**Returns:** `Promise` - True if signature is valid +```typescript +transfer( + options: TransferOptions, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise +``` -**Example:** -```javascript -const message = 'Hello, Ethereum!' -const signature = await account.sign(message) +Transfers SPL tokens through the paymaster. Native SOL transfers are handled by `sendTransaction()` instead. -const readOnlyAccount = new WalletAccountReadOnlyEvm('0x...', { provider: '...' }) -const isValid = await readOnlyAccount.verify(message, signature) -console.log('Signature valid:', isValid) // true +```javascript title="Transfer an SPL token" +const result = await account.transfer({ + token: 'TokenMint111111111111111111111111111111111', + recipient: 'Recipient1111111111111111111111111111111', + amount: 1000000n +}, { + transferMaxFee: 500000n +}) ``` -#### `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 +The method throws when the quoted paymaster fee is greater than `transferMaxFee`. -**Returns:** `Promise` - True if signature is valid +#### quoteSendTransaction -**Example:** -```javascript -const isValid = await readOnlyAccount.verifyTypedData(typedData, signature) -console.log('Typed data signature valid:', isValid) // true +```typescript +quoteSendTransaction( + tx: SolanaTransaction | FullySignedTransaction, + config?: SolanaGaslessWalletPaymasterConfigOverrides +): Promise> ``` -#### `getTransactionReceipt(hash)` -Returns a transaction's receipt if it has been mined. - -**Parameters:** -- `hash` (string): The transaction hash - -**Returns:** `Promise` - Transaction receipt or null if not yet mined +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`. -**Throws:** Error if no provider is configured +#### quoteTransfer -**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 +quoteTransfer( + options: TransferOptions, + 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 +Quotes the paymaster fee for `transfer()` inputs. Quote methods return estimates and do not enforce `transferMaxFee`. -**Returns:** `Promise` - The allowance +#### getTransactionReceipt -**Example:** -```javascript -const allowance = await readOnlyAccount.getAllowance( - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - '0xSpenderAddress...' -) -console.log('Allowance:', allowance) +```typescript +getTransactionReceipt(hash: string): Promise ``` -## Types +Returns the Solana transaction receipt for a submitted signature, or `null` if the transaction has not been included in a block yet. -### EvmTransaction +#### toReadOnlyAccount ```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) -} +toReadOnlyAccount(): Promise ``` -### TransferOptions +Returns a read-only account for the same address. -```typescript -interface TransferOptions { - token: string; // ERC20 token contract address - recipient: string; // Recipient's Ethereum address - amount: number | bigint; // Amount in token's base units -} -``` +## WalletAccountReadOnlySolanaGasless -### TransactionResult +Read-only Solana gasless account for an address. Extends `WalletAccountReadOnly` from `@tetherto/wdk-wallet`. + +### Constructor ```typescript -interface TransactionResult { - hash: string; // Transaction hash - fee: bigint; // Transaction fee paid in wei -} +new WalletAccountReadOnlySolanaGasless( + addr: string, + config: Omit +) ``` -### TransferResult +**Parameters:** -```typescript -interface TransferResult { - hash: string; // Transfer transaction hash - fee: bigint; // Transfer fee paid in wei -} -``` +- `addr`: Solana account address. +- `config`: Solana RPC and paymaster configuration. Read-only accounts do not accept `transferMaxFee` or `transactionMaxFee`. -### FeeRates +### Methods -```typescript -interface FeeRates { - normal: bigint; // Normal priority fee rate (base fee × 1.1) - fast: bigint; // Fast priority fee rate (base fee × 2.0) -} -``` +| 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` | -### KeyPair +## Configuration Types + +### SolanaGaslessWalletConfig ```typescript -interface KeyPair { - privateKey: Uint8Array | null; // Private key as Uint8Array (32 bytes, null after dispose) - publicKey: Uint8Array; // Public key as Uint8Array (65 bytes) -} +type SolanaGaslessWalletConfig = + SolanaWalletConfig & SolanaGaslessWalletPaymasterConfig ``` -### TypedData +Combines the base Solana wallet configuration with the required paymaster configuration. -```typescript -interface TypedData { - domain: TypedDataDomain; // The domain separator - types: Record; // The type definitions - message: Record; // The message data -} -``` +| 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. | -### TypedDataDomain +### SolanaGaslessWalletPaymasterConfig ```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 SolanaGaslessWalletPaymasterConfig = { + paymasterUrl: string | KoraClientOptions | (string | KoraClientOptions)[] + paymasterAddress: string + paymasterToken: PaymasterTokenConfig } ``` -### TypedDataField +### PaymasterTokenConfig ```typescript -interface TypedDataField { - name: string; // The field name - type: string; // The field type (e.g., 'address', 'uint256', 'string') +type PaymasterTokenConfig = { + address: string } ``` -### EvmWalletConfig +### SolanaGaslessWalletPaymasterConfigOverrides ```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 SolanaGaslessWalletPaymasterConfigOverrides = Partial< + Pick & + Pick +> ``` -### ApproveOptions +Pass overrides as the second argument to quote, sign, send, or transfer methods. -```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 -} -``` +| 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. | -### EvmTransactionReceipt +## 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 + +Create a gasless Solana account. - -Build mobile wallets with React Native Expo - - -Get started with WDK's EVM Wallet Usage - - -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. - -## Owned Account Balances +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. -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. - - -Send native tokens with EIP-1559 or legacy gas settings. - - -Transfer ERC-20 tokens and estimate fees. + +Install the package and create your first gasless Solana account. - -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 in a Node.js environment. - -Get started with WDK's Solana Wallet configuration - - -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 +29274,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 +29371,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 +29417,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 +29447,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 +29472,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 +29606,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 +29755,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 +29784,7 @@ interface TransferOptions { ```typescript interface KeyPair { publicKey: Uint8Array - privateKey: Uint8Array + privateKey: Uint8Array | null } ``` @@ -19493,7 +29824,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 +29840,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 +29918,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 +29931,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 +29958,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 +30003,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 +30246,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 +30269,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 +30372,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 +30407,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 +30440,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 +30688,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 +30701,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 +30785,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 +31998,8 @@ const config = { sparkscan: { apiKey: 'your-api-key-here' }, - syncAndRetry: true + syncAndRetry: true, + enableLogging: false } const wallet = new WalletManagerSpark(seedPhrase, config) @@ -21686,6 +32076,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 +32137,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 +32867,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 +32894,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 +32932,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 +32945,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 +32972,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 +33010,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 +33046,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 +33078,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. In `1.0.0-beta.7`, 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 +33090,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 +33119,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 +33156,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 +33170,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 +33183,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 +33213,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 +33239,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 +33347,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 +33358,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 +33410,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 +33431,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 +33468,10 @@ console.log('Signature valid:', isValid) ##### `getTransactionReceipt(hash)` Returns a transaction's receipt. +In `1.0.0-beta.7`, 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 +33491,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 +33536,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 +33565,7 @@ interface TransferOptions { /** * Amount in token's base units */ - amount: number; + amount: number | bigint; } ``` @@ -23120,14 +33574,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 +33637,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 +33656,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 +33668,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 +33691,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 +33715,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 +33760,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 +33785,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 +33798,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 +33811,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 +33846,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. + + +In `@tetherto/wdk-wallet-ton-gasless` `1.0.0-beta.7`, 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 +33872,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 +33934,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 +33946,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 +33971,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 +34002,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 +34022,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 +34051,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 +34066,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 +34074,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 +34084,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 +34102,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). In `1.0.0-beta.7`, 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 +34110,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 +34121,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 +34187,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 +34209,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). In `1.0.0-beta.7`, 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 +34257,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 +34283,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 +34295,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 +34314,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 +34333,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: + +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="Validated Gasless Transfer" -async function transferWithValidation(account, jettonAddress, recipient, amount) { +```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 +34413,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 +34425,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 +34435,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 +34493,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 +34518,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 +34549,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. In `1.0.0-beta.10`, 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 +34561,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 +34571,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 +34595,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 +34620,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 +34673,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 +34682,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 +34692,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 +34716,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 +34724,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 +34747,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 +34758,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 +34769,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 +34805,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. +In `1.0.0-beta.10`, 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 +34880,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 +34929,24 @@ console.log('Signature valid:', isValid) ##### `getTransactionReceipt(hash)` Returns a transaction's receipt if it has been mined. +In `1.0.0-beta.10`, 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 +34979,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 +35009,7 @@ interface TransferOptions { * Amount in Jetton's base units * @example 1000000000 // Amount depends on token decimals */ - amount: number; + amount: number | bigint; } ``` @@ -24465,16 +35018,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 +35036,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` in v1.0.0-beta.10 + * @example 100000000n // 0.1 TON */ - fast: number; + fast: bigint; } ``` @@ -24519,8 +35072,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 +35089,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 +35149,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 +35166,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 +35180,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 +35209,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 +35239,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 +35248,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 +35263,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 +35298,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 +35308,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/v2/jsonRPC` +- Testnet: `https://testnet.toncenter.com/api/v2/jsonRPC` -- Mainnet: `https://toncenter.com/api/v3` -- Testnet: `https://testnet.toncenter.com/api/v3` + +In `@tetherto/wdk-wallet-ton` `1.0.0-beta.10`, `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 +35341,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 +35404,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 +35451,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 +35492,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 +35507,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 +35535,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 +35550,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). In `1.0.0-beta.10`, 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 +35558,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 +35569,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 +35634,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 +35650,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 +35667,20 @@ const quote = await account.quoteSendTransaction({ console.log('Estimated fee:', quote.fee, 'nanotons') ``` -## Use Dynamic Fee Rates +## Cap Transaction Fees -You can retrieve current fee rates from the wallet manager using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton/api-reference): +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 mainnet TON API fee rates from the wallet manager using [`wallet.getFeeRates()`](/sdk/wallet-modules/wallet-ton/api-reference#getfeerates). In `1.0.0-beta.10`, 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 +35688,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 +35780,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 +35832,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 +35871,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 +35898,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 +35911,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 +35951,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 +36035,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 +36047,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 +36057,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 +36089,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 +36130,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 +36182,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 +36208,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 +36218,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 +36310,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 +36359,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 +36369,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 +36418,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 +36453,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 +36593,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 +36645,6 @@ Get started with WDK's Tron Gasfree Wallet Configuration ## Need Help? - -*** - -## Need Help? - *** @@ -26035,13 +36674,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 +36686,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 +36718,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 +36808,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 +36829,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 +36851,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 +36870,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 +36889,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 +36921,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 +36934,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 +36958,6 @@ Get started with WDK's Tron Gasfree Wallet API ## Need Help? - -*** - -## Need Help? - *** @@ -26366,11 +37001,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 +37020,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 +37056,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 +37099,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 +37118,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 +37154,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 +37175,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 +37241,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 +37264,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 +37311,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 +37332,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 +37351,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 +37367,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 +37401,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 +37410,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 +37419,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 +37472,7 @@ Get started with WDK's Tron Gasfree Wallet API ## Need Help? + *** ## API Reference @@ -26839,8 +37494,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 +37507,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 +37544,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 +37581,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 +37610,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 +37630,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 +37642,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 +37694,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 + +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: number}\>` - Transaction hash and fee in sun +**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 +37740,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 +37760,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 +37772,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 +37790,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 +37881,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 +37909,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 +37924,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 +37959,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 +38010,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 +38054,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 +38079,7 @@ interface TransactionResult { ```typescript interface TransferResult { hash: string; // Transaction hash - fee: number; // Fee paid in sun + fee: bigint; // Fee paid in sun } ``` @@ -27360,11 +38088,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 +38128,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 +38147,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 +38158,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. -The `transferMaxFee` option sets the maximum fee amount (in sun) for transfer operations. This helps prevent transactions from being sent with unexpectedly high fees. +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 `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 +38392,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 +38441,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 +38525,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 +38546,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 +38601,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 +38734,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 +38837,183 @@ 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: + + +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. + + +| If you need | Start with | Ownership | +|---|---|---| +| Standard EVM accounts | [Standard EVM](/sdk/wallet-modules/wallet-evm) | Tether | +| EVM smart accounts with ERC-4337 | [Smart accounts (ERC-4337)](/sdk/wallet-modules/wallet-evm-erc-4337) | Tether | +| EVM EOA addresses with EIP-7702 delegation | [EIP-7702 accounts](/sdk/wallet-modules/wallet-evm-7702-gasless) | Tether | +| Bitcoin base-layer wallets | [Bitcoin](/sdk/wallet-modules/wallet-btc) | Tether | +| Lightning payments through Spark | [Lightning (Spark)](/sdk/wallet-modules/wallet-spark) | Tether | +| Standard TON wallets | [Standard TON](/sdk/wallet-modules/wallet-ton) | Tether | +| Gasless TON Jetton transfers | [Gasless TON](/sdk/wallet-modules/wallet-ton-gasless) | Tether | +| Standard TRON wallets | [Standard TRON](/sdk/wallet-modules/wallet-tron) | Tether | +| Gasfree TRON TRC20 transfers | [Gasfree TRON](/sdk/wallet-modules/wallet-tron-gasfree) | Tether | +| Standard Solana wallets | [Standard Solana](/sdk/wallet-modules/wallet-solana) | Tether | +| Gasless Solana transactions | [Gasless Solana](/sdk/wallet-modules/wallet-solana-gasless) | Tether | +| Aptos wallets | [Aptos](/sdk/wallet-modules/wallet-aptos) | Tether | +| On-chain RGB assets on Bitcoin | [On-chain RGB](/sdk/community-modules/wdk-wallet-rgb) | Community | +| RGB assets over Lightning | [RGB Lightning](/sdk/community-modules/wdk-rgb-lightning) | Community | +| Cosmos-compatible chains | [Cosmos](/sdk/community-modules/wdk-wallet-cosmos) | Community | + +## 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 +39107,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 +39196,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 +39322,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 +39505,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 +39561,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 +39588,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 +39868,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 +39878,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 +39908,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. @@ -29121,7 +40522,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 +40531,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 +40576,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 +40624,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 +40658,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 +40713,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 +40736,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 +40776,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 +40869,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 +40950,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 +41023,105 @@ 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 + +*** + +## Need Help? + + + +*** + +## 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. + + *** @@ -29620,11 +41131,394 @@ console.log(`Balance: ${balance.amount} ${balance.token.toUpperCase()}`); *** +## 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 +41527,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 +41537,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 +41574,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 +41587,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 +41609,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 +41636,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 +41677,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 +41689,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 +41708,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 +41770,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 +41804,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 +41859,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 +41923,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 +41997,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 +42021,11 @@ const workletConfig = { environment: 'sandbox' } } + }, + modules: { + preferences: { + storagePath: '/app-data/preferences' + } } } ``` @@ -29966,8 +42035,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 +42062,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 +42091,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 +42111,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 +42174,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 +42198,9 @@ Get started with WDK price clients Check the API Reference and examples + +Use CoinGecko for current prices and historical series + *** @@ -30102,13 +42233,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 +42305,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 +42317,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 +42356,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 +42391,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 +42450,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 +42473,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 +42505,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 +42576,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 +42600,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 +42624,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 +42640,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 +42678,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 +42693,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 +42996,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