# LendaSwap SDK Documentation > LendaSwap enables instant, trustless atomic swaps between Bitcoin (Lightning, Arkade, on-chain) and stablecoins (USDC, USDT) on Polygon and Ethereum. The `@lendasat/lendaswap-sdk-pure` package is a pure TypeScript SDK — no WASM, no native dependencies — that works in browsers, React Native, and Node.js. --- ## Installation ```bash npm install @lendasat/lendaswap-sdk-pure ``` No bundler configuration needed. No WASM. Works everywhere TypeScript runs. --- ## Client Initialization Every operation starts with building a `Client` instance: ```typescript import { Client, IdbWalletStorage, IdbSwapStorage } from "@lendasat/lendaswap-sdk-pure"; const client = await Client.builder() .withSignerStorage(new IdbWalletStorage()) .withSwapStorage(new IdbSwapStorage()) .build(); ``` To initialize with an existing mnemonic: ```typescript const client = await Client.builder() .withSignerStorage(new IdbWalletStorage()) .withSwapStorage(new IdbSwapStorage()) .withMnemonic("your twelve word mnemonic phrase goes here abandon abandon") .build(); ``` The client is the single entry point for all SDK operations: creating swaps, claiming, refunding, querying, and recovery. --- ## Wallet & Mnemonic ### Generate New Mnemonic ```typescript import { Signer } from "@lendasat/lendaswap-sdk-pure"; const signer = Signer.generate(); console.log("New mnemonic:", signer.mnemonic); // New mnemonic: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ``` ### Get User ID Xpub Used for swap recovery — this is an identifier, not a wallet: ```typescript const xpub = client.getUserIdXpub(); console.log("User ID xpub:", xpub); // User ID xpub: xpub6CUGRUo... ``` --- ## Supported Tokens & Pairs | Token | Chain | ID | Description | |-------|-----------|-----------------|------------------------------| | BTC | Lightning | `btc_lightning` | Bitcoin via Lightning Network | | BTC | Arkade | `btc_arkade` | Bitcoin via Arkade (instant) | | BTC | On-chain | `btc_onchain` | Bitcoin on-chain | | USDC | Polygon | `usdc_pol` | USD Coin on Polygon | | USDT | Polygon | `usdt0_pol` | Tether USD on Polygon | | USDC | Ethereum | `usdc_eth` | USD Coin on Ethereum | | USDT | Ethereum | `usdt_eth` | Tether USD on Ethereum | | XAUT | Ethereum | `xaut_eth` | Tether Gold on Ethereum | Token IDs follow the format: `{symbol}_{chain}`. ### Retrieve Pairs Programmatically ```typescript const pairs = await client.getAssetPairs(); ``` ### Key Rules - **Polygon swaps are gasless** — claiming via Gelato Relay, no gas fees - **Ethereum swaps require WalletConnect** — user pays gas to claim tokens - **EVM → BTC swaps require WalletConnect** — user must approve + deposit tokens --- ## Get Quote ```typescript const quote = await client.getQuote("btc_arkade", "usdc_pol", BigInt(100000)); console.log("Quote:", quote); // Quote: { sourceAmount: 100000, targetAmount: 67500000, exchangeRate: 67500, fee: 500, ... } ``` **Parameters:** | Parameter | Type | Description | |-----------|--------|--------------------------------------------| | `from` | string | Source token ID (e.g., `btc_arkade`) | | `to` | string | Destination token ID (e.g., `usdc_pol`) | | `amount` | bigint | Amount in smallest unit (satoshis for BTC) | **Response:** ```typescript interface QuoteResponse { sourceAmount: number; targetAmount: number; exchangeRate: number; fee: number; minAmount: number; expiresAt: string; } ``` --- ## Get Version ```typescript const version = await client.getVersion(); console.log("API version:", version); // API version: 1.2.3 ``` --- ## Creating Swaps ### BTC → EVM (Lightning) ```typescript const swap = await client.createLightningToEvmSwap({ targetAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f3bB88", sourceAmount: 100000n, // Send exactly 100,000 sats targetToken: "usdc_pol", targetChain: "polygon", }); console.log("Pay Lightning invoice:", swap.lnInvoice); // Pay Lightning invoice: lnbc100u1p... console.log("Swap ID:", swap.id); // Swap ID: 550e8400-e29b-41d4-a716-446655440000 ``` You can use `targetAmount` instead of `sourceAmount` to receive an exact stablecoin amount: ```typescript const swap = await client.createLightningToEvmSwap({ targetAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f3bB88", targetAmount: 100, // Receive exactly 100 USDC targetToken: "usdc_pol", targetChain: "polygon", }); ``` ### BTC → EVM (Arkade) ```typescript const swap = await client.createArkadeToEvmSwap({ targetAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f3bB88", sourceAmount: 100000n, targetToken: "usdc_pol", targetChain: "polygon", }); console.log("VHTLC address:", swap.vhtlcAddress); // VHTLC address: tark1q... ``` ### BTC → EVM Parameters | Parameter | Type | Required | Description | |-----------------|--------|----------|------------------------------------------------------------| | `targetAddress` | string | Yes | EVM address to receive stablecoins | | `targetAmount` | number | * | Amount of stablecoins to receive (e.g., 100) | | `sourceAmount` | bigint | * | Amount of sats to send (e.g., 100000n) | | `targetToken` | string | Yes | Token ID (`usdc_pol`, `usdt0_pol`, `usdc_eth`, `usdt_eth`) | | `targetChain` | string | Yes | Target chain (`polygon`, `ethereum`) | | `referralCode` | string | No | Optional referral code | Specify either `targetAmount` OR `sourceAmount`, not both. ### BTC → EVM Response ```typescript interface BtcToEvmSwapResponse { id: string; sourceAmount: number; targetAmount: number; targetAddress: string; vhtlcAddress?: string; // Arkade swaps lnInvoice?: string; // Lightning swaps htlcAddress?: string; // On-chain swaps exchangeRate: number; status: string; createdAt: string; expiresAt: string; } ``` ### EVM → Arkade ```typescript const swap = await client.createEvmToArkadeSwap({ userAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f3bB88", sourceAmount: 100, sourceToken: "usdc_pol", targetChain: "polygon", }); console.log(`Deposit ${swap.sourceAmount} USDC to: ${swap.contractAddress}`); // Deposit 100 USDC to: 0x1234...abcd ``` ### EVM → Lightning ```typescript const swap = await client.createEvmToLightningSwap({ bolt11Invoice: "lnbc100u1p...", userAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f3bB88", sourceToken: "usdc_pol", targetChain: "polygon", }); ``` ### EVM → BTC Parameters **EVM → Arkade:** | Parameter | Type | Required | Description | |----------------|--------|----------|-----------------------------------------| | `userAddress` | string | Yes | Your EVM wallet address | | `sourceAmount` | number | Yes | Amount of stablecoins to swap | | `sourceToken` | string | Yes | Token ID (`usdc_pol`, `usdc_eth`, etc.) | | `targetChain` | string | Yes | Target chain (`polygon`, `ethereum`) | | `referralCode` | string | No | Optional referral code | **EVM → Lightning:** | Parameter | Type | Required | Description | |-----------------|--------|----------|-----------------------------------------| | `bolt11Invoice` | string | Yes | Lightning invoice to receive payment | | `userAddress` | string | Yes | Your EVM wallet address | | `sourceToken` | string | Yes | Token ID (`usdc_pol`, `usdc_eth`, etc.) | | `targetChain` | string | Yes | Target chain (`polygon`, `ethereum`) | ### Deposit EVM Tokens (after creating EVM → BTC swap) Use `getEvmFundingCallData()` to get transaction data for depositing tokens: ```typescript const callData = await client.getEvmFundingCallData(swap.id); // callData contains: { to, data, value } // Use with wagmi/viem or ethers.js to send the transaction ``` **With wagmi/viem:** ```typescript import { useSendTransaction, useWaitForTransactionReceipt } from "wagmi"; const { sendTransaction, data: hash } = useSendTransaction(); await sendTransaction({ to: callData.to as `0x${string}`, data: callData.data as `0x${string}`, value: callData.value ? BigInt(callData.value) : undefined, }); ``` **With ethers.js:** ```typescript import { ethers } from "ethers"; const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const tx = await signer.sendTransaction({ to: callData.to, data: callData.data, value: callData.value ? BigInt(callData.value) : undefined, }); await tx.wait(); ``` ### On-chain BTC → Arkade ```typescript const swap = await client.createBitcoinToEvmSwap({ sourceAmount: 100000n, targetChain: "arkade", }); console.log("Send BTC to:", swap.htlcAddress); // Send BTC to: bc1q... console.log("Amount:", swap.sourceAmount, "sats"); // Amount: 100000 sats ``` --- ## Monitoring Swaps ### Get Swap by ID ```typescript const swap = await client.getSwap(swapId); console.log("Swap ID:", swap.id); // Swap ID: 550e8400-e29b-41d4-a716-446655440000 console.log("Status:", swap.status); // Status: ServerFunded console.log("Amount:", swap.sourceAmount, "sats"); // Amount: 100000 sats console.log("Target:", swap.targetAmount, "USDC"); // Target: 67.50 USDC ``` ### Poll for Status Changes ```typescript let status = swap.status; while (status !== "ServerFunded") { await new Promise((r) => setTimeout(r, 5000)); const updated = await client.getSwap(swap.id); status = updated.status; console.log("Status:", status); // Status: ClientFunded } ``` ### List All Local Swaps ```typescript const swaps = await client.swapStorage.getAll(); console.log(`Found ${swaps.length} swap(s)`); // Found 3 swap(s) for (const swap of swaps) { console.log(`Swap ${swap.id}: ${swap.status}`); // Swap 550e8400-...: Done } ``` ### Delete Swaps from Local Storage ```typescript // Delete a specific swap await client.swapStorage.delete("550e8400-e29b-41d4-a716-446655440000"); // Clear all local swap data await client.swapStorage.clear(); ``` ### Get Stored Swap Data ```typescript const storedSwap = await client.getStoredSwap(swapId); // Access storedSwap.preimage if needed ``` --- ## Swap State Machine Swaps progress through these states: ``` Pending → ClientFunded → ServerFunded → Done ``` | Code | State | Description | Action Required | |------|--------------------------------|-----------------------------------------|-----------------| | 0 | Pending | Swap created, waiting for BTC payment | Pay BTC | | 1 | ClientFunded | BTC received, preparing HTLC | Wait | | 2 | ServerFunded | HTLC created, ready to claim | **Claim now** | | 3 | Done | Swap complete, user received tokens | None | | 4 | Expired | 30 min timeout, no payment received | None | | 5 | ClientRefunded | User refunded before HTLC created | None | | 6 | ClientFundedServerRefunded | HTLC timeout, service reclaimed funds | Refund | ### Transition Rules | From | Event | To | |--------------|----------------|----------------------------| | Pending | User pays BTC | ClientFunded | | Pending | 30 min timeout | Expired | | ClientFunded | HTLC created | ServerFunded | | ServerFunded | User claims | Done | | ServerFunded | HTLC timeout | ClientFundedServerRefunded | ### Handling Each Status ```typescript async function handleSwapStatus(swapId: string) { const swap = await client.getSwap(swapId); switch (swap.status) { case "Pending": console.log("Waiting for BTC payment..."); break; case "ClientFunded": console.log("Payment received! Waiting for HTLC..."); break; case "ServerFunded": console.log("Ready to claim!"); await client.claim(swapId); break; case "Done": console.log("Swap completed!"); break; case "Expired": console.log("Swap expired - no payment received"); break; case "ClientRefunded": console.log("BTC refunded to user"); break; case "ClientFundedServerRefunded": console.log("HTLC timed out - service reclaimed funds"); break; } } ``` --- ## Claiming ### Claim via Gelato (Gasless — Polygon only) ```typescript await client.claim(swapId); console.log("Claimed! Stablecoins sent to your wallet."); // Claimed! Stablecoins sent to your wallet. ``` This is the recommended method for Polygon destinations. No gas fees, no wallet connection needed. ### Claim with WalletConnect (Required for Ethereum) For Ethereum destinations (`usdc_eth`, `usdt_eth`, `xaut_eth`), users must sign a transaction: ```typescript const claimData = await client.claim(swapId); // If ethereumClaimData is returned, send via user's wallet if (claimData.ethereumClaimData) { // With wagmi: await sendTransaction({ to: claimData.ethereumClaimData.to as `0x${string}`, data: claimData.ethereumClaimData.data as `0x${string}`, }); // Or with ethers.js: const tx = await signer.sendTransaction({ to: claimData.ethereumClaimData.to, data: claimData.ethereumClaimData.data, }); await tx.wait(); } ``` ### Claim Arkade VHTLC (On-chain BTC → Arkade swaps) ```typescript await client.claimArkade(swapId, { destinationAddress: "your-arkade-address" }); console.log("VHTLC claimed! BTC added to your Arkade wallet."); // VHTLC claimed! BTC added to your Arkade wallet. ``` ### When to Use Which Claim Method | Swap Type | Claim Method | |------------------------|----------------| | BTC → Polygon | `claim()` | | BTC → Ethereum | `claim()` → send `ethereumClaimData` via wallet | | On-chain BTC → Arkade | `claimArkade()` | | EVM → Arkade | Automatic (service claims) | --- ## Refunding ### Refund VHTLC (Arkade) Refunds are available after the VHTLC locktime expires: ```typescript const txid = await client.refundSwap(swapId, { destinationAddress: "your-arkade-address", }); console.log("Refund successful!", txid); // Refund successful! 3a1b2c3d4e5f... ``` ### Refund On-chain HTLC For on-chain BTC swaps, refunds include a fee rate: ```typescript const txid = await client.refundSwap(swapId, { destinationAddress: "bc1q...", feeRateSatPerVb: 10, }); console.log("Refund broadcast:", txid); // Refund broadcast: 3a1b2c3d4e5f... ``` ### Refund EVM HTLC Use `getEvmRefundCallData()` and send via user's wallet: ```typescript const refundData = await client.getEvmRefundCallData(swapId); // With wagmi: await sendTransaction({ to: refundData.to as `0x${string}`, data: refundData.data as `0x${string}`, }); // Or with ethers.js: const tx = await signer.sendTransaction({ to: refundData.to, data: refundData.data, }); await tx.wait(); ``` ### Locktime Durations | Type | Duration | Purpose | |-------------------|-------------|----------------------------------| | Swap Request | 30 min | Time to make initial payment | | Lightning Invoice | 10 min | Lightning payment window | | EVM HTLC | 10-30 min | Time for user to claim EVM funds | | Arkade VHTLC | 2 hours | Time for service to claim | | Onchain HTLC | 24-48 hours | Time for onchain refund | ### Check If Refundable ```typescript const swap = await client.getSwap(swapId); const now = Date.now(); const locktime = swap.locktime * 1000; const canRefund = now > locktime; ``` --- ## Recovery from Seed If you lose access to local storage, recover swaps using your mnemonic: ```typescript import { Client, IdbWalletStorage, IdbSwapStorage } from "@lendasat/lendaswap-sdk-pure"; const mnemonic = "your twelve word mnemonic phrase goes here abandon abandon"; const client = await Client.builder() .withSignerStorage(new IdbWalletStorage()) .withSwapStorage(new IdbSwapStorage()) .withMnemonic(mnemonic) .build(); // Get xpub for recovery const xpub = client.getUserIdXpub(); // Query the API for swaps associated with this xpub const response = await fetch( `https://apilendaswap.lendasat.com/swaps?xpub=${xpub}`, ); const recovered = await response.json(); for (const swap of recovered) { switch (swap.status) { case "ServerFunded": await client.claim(swap.id); break; case "Done": console.log("Already completed"); break; default: console.log(`Status: ${swap.status}`); } } ``` --- ## Error Handling ```typescript try { await client.createArkadeToEvmSwap({ targetAddress: "0x...", sourceAmount: 100000n, targetToken: "usdc_pol", targetChain: "polygon", }); } catch (error) { console.error("Error:", error.message); } ``` ### Common Error Codes | Error Code | Description | Solution | |------------------------|-----------------------------|----------------------------| | `insufficient_balance` | Not enough BTC in wallet | Add funds to Arkade wallet | | `amount_too_low` | Below minimum swap amount | Increase swap amount | | `amount_too_high` | Exceeds maximum swap amount | Reduce swap amount | | `invalid_address` | Target address invalid | Check address format | | `unsupported_token` | Token not supported | Use supported token | | `swap_not_found` | Swap ID doesn't exist | Check swap ID | | `swap_expired` | Swap timed out | Create new swap | | `already_claimed` | Funds already claimed | No action needed | | `locktime_not_expired` | Too early to refund | Wait for locktime | | `invalid_preimage` | Wrong preimage provided | Check preimage | | `invalid_mnemonic` | Seed phrase is invalid | Check 12/24 word phrase | | `storage_unavailable` | IndexedDB not accessible | Check browser permissions | | `network_error` | Connection failed | Check internet, retry | | `rate_limit_exceeded` | Too many requests | Wait and retry | ### Retry Strategy ```typescript async function withRetry( operation: () => Promise, maxRetries: number = 3, delayMs: number = 1000, ): Promise { let lastError: Error; for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { lastError = error; const nonRetryable = [ "insufficient_balance", "invalid_address", "already_claimed", "invalid_mnemonic", ]; if (nonRetryable.some((code) => error.message.includes(code))) { throw error; } const delay = delayMs * Math.pow(2, i); await new Promise((r) => setTimeout(r, delay)); } } throw lastError; } ``` --- ## Complete Example: Lightning → Polygon Swap ```typescript import { Client, IdbWalletStorage, IdbSwapStorage } from "@lendasat/lendaswap-sdk-pure"; // 1. Initialize client const client = await Client.builder() .withSignerStorage(new IdbWalletStorage()) .withSwapStorage(new IdbSwapStorage()) .build(); // 2. Create swap const swap = await client.createLightningToEvmSwap({ targetAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f3bB88", sourceAmount: 100000n, targetToken: "usdc_pol", targetChain: "polygon", }); console.log("Pay invoice:", swap.lnInvoice); // 3. Wait for server to fund HTLC let status = swap.status; while (status !== "ServerFunded") { await new Promise((r) => setTimeout(r, 5000)); const updated = await client.getSwap(swap.id); status = updated.status; } // 4. Claim via Gelato (gasless) await client.claim(swap.id); console.log("Swap complete!"); ``` --- ## Complete Example: EVM → Arkade Swap (with wagmi) ```typescript import { Client, IdbWalletStorage, IdbSwapStorage } from "@lendasat/lendaswap-sdk-pure"; import { useSendTransaction, useWaitForTransactionReceipt } from "wagmi"; // 1. Initialize client const client = await Client.builder() .withSignerStorage(new IdbWalletStorage()) .withSwapStorage(new IdbSwapStorage()) .build(); // 2. Create swap const swap = await client.createEvmToArkadeSwap({ userAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f3bB88", sourceAmount: 100, sourceToken: "usdc_pol", targetChain: "polygon", }); // 3. Get funding call data and deposit via wallet const callData = await client.getEvmFundingCallData(swap.id); const { sendTransaction, data: hash } = useSendTransaction(); await sendTransaction({ to: callData.to as `0x${string}`, data: callData.data as `0x${string}`, value: callData.value ? BigInt(callData.value) : undefined, }); // 4. BTC is sent automatically after deposit confirms ``` --- ## API Reference Summary ### Client Methods | Method | Description | |--------|-------------| | `Client.builder()` | Start building a client instance | | `.withSignerStorage(storage)` | Set wallet/signer storage provider | | `.withSwapStorage(storage)` | Set swap data storage provider | | `.withMnemonic(mnemonic)` | Import existing mnemonic | | `.build()` | Build and return the client | | `client.getVersion()` | Get API version | | `client.getUserIdXpub()` | Get user ID xpub for recovery | | `client.getAssetPairs()` | Get all supported token pairs | | `client.getQuote(from, to, amount)` | Get exchange rate quote | | `client.createLightningToEvmSwap(params)` | Create Lightning → EVM swap | | `client.createArkadeToEvmSwap(params)` | Create Arkade → EVM swap | | `client.createEvmToArkadeSwap(params)` | Create EVM → Arkade swap | | `client.createEvmToLightningSwap(params)` | Create EVM → Lightning swap | | `client.createBitcoinToEvmSwap(params)` | Create on-chain BTC → Arkade swap | | `client.getSwap(swapId)` | Get swap status and details | | `client.getStoredSwap(swapId)` | Get locally stored swap data | | `client.claim(swapId)` | Claim via Gelato (gasless on Polygon) or get ethereumClaimData | | `client.claimArkade(swapId, { destinationAddress })` | Claim Arkade VHTLC | | `client.refundSwap(swapId, { destinationAddress, feeRateSatPerVb? })` | Refund BTC/Arkade swap | | `client.getEvmFundingCallData(swapId)` | Get EVM deposit transaction data | | `client.getEvmRefundCallData(swapId)` | Get EVM refund transaction data | | `client.swapStorage.getAll()` | List all locally stored swaps | | `client.swapStorage.delete(swapId)` | Delete a swap from local storage | | `client.swapStorage.clear()` | Clear all local swap data | | `Signer.generate()` | Generate new mnemonic (`.mnemonic` property) | ### Storage Providers | Provider | Description | |----------|-------------| | `IdbWalletStorage` | IndexedDB-based wallet/signer storage (browser) | | `IdbSwapStorage` | IndexedDB-based swap data storage (browser) | --- ## Protocol Overview LendaSwap uses Hash Time-Locked Contracts (HTLCs) for trustless atomic swaps: 1. **Hash Lock** — Funds can only be claimed by revealing a secret (preimage). `SHA256(secret) == hashLock` 2. **Time Lock** — Funds are automatically refundable after a timeout period The atomic property guarantees: either both parties get their funds, or neither does. There is no scenario where one party can steal from the other. ### Swap Flow 1. User creates swap request 2. User pays BTC (Lightning invoice / Arkade VHTLC / on-chain HTLC) 3. Service receives BTC and creates HTLC on EVM chain (locks WBTC) 4. User claims stablecoins by revealing the preimage (gasless on Polygon) 5. Service extracts preimage from blockchain to claim BTC ### Gasless Execution (Polygon) LendaSwap uses Gelato Relay with ERC-2771 meta-transactions. Users sign a claim intent off-chain, Gelato submits the transaction and pays gas. Users receive stablecoins with zero gas cost. --- ## Links - NPM: https://www.npmjs.com/package/@lendasat/lendaswap-sdk-pure - GitHub: https://github.com/lendasat/lendaswap/tree/main/client-sdk/ts-pure-sdk - REST API Swagger: https://apilendaswap.lendasat.com/swagger-ui/ - Documentation: https://docs.lendasat.com/docs/lendaswap