Skip to content

Complete Example

This page shows full flows for SOL to SOL privacy swaps and EVM Silent Swaps using the Core SDK in Node.js.


1. SOL to SOL swaps

You can use the high-level executeSolanaSwap API or implement the steps manually (e.g. as in the solana-swap-script).

Using executeSolanaSwap

The SDK’s executeSolanaSwap runs the full flow: authenticate, quote, order, Solana bridge deposit, and optional order tracking.

import {
  executeSolanaSwap,
  SB58_CHAIN_ID_SOLANA_MAINNET,
  trackOrderViaWebSocket,
  type SwapAccounts,
  type SwapStatusUpdate,
} from '@silentswap/sdk';
import { createWalletClient, http, publicActions } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { avalanche } from 'viem/chains';
import { Keypair, Connection, Transaction } from '@solana/web3.js';
import { mnemonicToSeedSync } from '@scure/bip39';
import { HDKey } from '@scure/bip32';
import { derivePath } from 'ed25519-hd-key';
 
const SOL_NATIVE = `solana:${SB58_CHAIN_ID_SOLANA_MAINNET}/slip44:501`;
 
function deriveKeysFromSeed(seedPhrase: string) {
  const seed = mnemonicToSeedSync(seedPhrase, '');
  const seedHex = Buffer.from(seed).toString('hex');
  const hdKey = HDKey.fromMasterSeed(seed);
  const evmKey = hdKey.derive("m/44'/60'/0'/0/0");
  const evmPrivateKey = `0x${Buffer.from(evmKey.privateKey!).toString('hex')}` as `0x${string}`;
  const evmAccount = privateKeyToAccount(evmPrivateKey);
  const solanaDerived = derivePath("m/44'/501'/0'/0'", seedHex);
  const solanaKeypair = Keypair.fromSeed(solanaDerived.key);
  return {
    evmPrivateKey,
    evmAddress: evmAccount.address,
    solanaKeypair,
    solanaAddress: solanaKeypair.publicKey.toString(),
  };
}
 
// Build SwapAccounts (evm + solana signers, see SDK examples)
function createSwapAccounts(keys: ReturnType<typeof deriveKeysFromSeed>, connection: Connection): SwapAccounts {
  const evmAccount = privateKeyToAccount(keys.evmPrivateKey);
  const evmClient = createWalletClient({
    account: evmAccount,
    chain: avalanche,
    transport: http(),
  }).extend(publicActions);
  return {
    evm: {
      address: evmAccount.address,
      signMessage: (msg) => evmClient.signMessage({ message: msg }),
      signTypedData: (data) => evmClient.signTypedData(data),
    },
    solana: {
      publicKey: keys.solanaAddress,
      signTransaction: async (txBytes) => {
        const tx = Transaction.from(Buffer.from(txBytes));
        tx.sign(keys.solanaKeypair);
        return Buffer.from(tx.serialize());
      },
    },
  };
}
 
const keys = deriveKeysFromSeed(process.env.PRIVATE_SEED!);
const connection = new Connection(process.env.SOLANA_RPC || 'https://api.mainnet-beta.solana.com');
const accounts = createSwapAccounts(keys, connection);
 
const result = await executeSolanaSwap({
  accounts,
  sourceAsset: SOL_NATIVE,
  sourceAmount: process.env.SWAP_AMOUNT || '0.1',
  destinationAsset: SOL_NATIVE,
  recipientAddress: process.env.RECIPIENT_ADDRESS!,
  onStatus: (u: SwapStatusUpdate) => console.log(u.message),
  trackOrder: true,
});
 
console.log('Order ID:', result.orderId, 'Deposit TX:', result.depositTxHash);
console.log('Viewing auth:', result.viewingAuth);
console.log('Used wallet:', result.usedWallet.evmAddress, result.usedWallet.solanaPublicKey);
// Store result.recoveryData (mnemonic + recoveryPaths) securely for refund/recovery
if (result.finalStatus) {
  console.log('Outputs finalized:', result.finalStatus.outputs?.map((o) => o.stage));
}

Full script example (step-by-step)

Configuration and key derivation
import {
  createSilentSwapClient,
  createViemSigner,
  createHdFacilitatorGroupFromEntropy,
  queryDepositCount,
  hexToBytes,
  quoteResponseToEip712Document,
  solveOptimalUsdcAmount,
  fetchRelayQuote,
  getRelayStatus,
  trackOrderViaWebSocket,
  N_RELAY_CHAIN_ID_SOLANA,
  SB58_ADDR_SOL_PROGRAM_SYSTEM,
  DeliveryMethod,
  FacilitatorKeyType,
  PublicKeyArgGroups,
  ENVIRONMENT,
} from '@silentswap/sdk';
import { Keypair, Connection, Transaction, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { mnemonicToSeedSync } from '@scure/bip39';
import { HDKey } from '@scure/bip32';
import { derivePath } from 'ed25519-hd-key';
import BigNumber from 'bignumber.js';
 
const CONFIG = {
  SOLANA_RPC: process.env.SOLANA_RPC || 'https://api.mainnet-beta.solana.com',
  SILENTSWAP_API: 'https://api.silentswap.com',
  SILENTSWAP_WS: 'wss://api.silentswap.com/websocket',
  ENVIRONMENT: ENVIRONMENT.MAINNET,
  USDC_AVALANCHE: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
  SOLANA_CHAIN_ID: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
};
 
// Derive EVM + Solana from BIP39 seed (EVM: m/44'/60'/0'/0/0, Solana: m/44'/501'/0'/0')
function deriveKeysFromSeed(seedPhrase: string) {
  const seed = mnemonicToSeedSync(seedPhrase, '');
  const seedHex = Buffer.from(seed).toString('hex');
  const hdKey = HDKey.fromMasterSeed(seed);
  const evmKey = hdKey.derive("m/44'/60'/0'/0/0");
  const evmPrivateKey = `0x${Buffer.from(evmKey.privateKey!).toString('hex')}` as `0x${string}`;
  const evmAccount = privateKeyToAccount(evmPrivateKey);
  const solanaDerived = derivePath("m/44'/501'/0'/0'", seedHex);
  const solanaKeypair = Keypair.fromSeed(solanaDerived.key);
  return { evmPrivateKey, evmAddress: evmAccount.address, solanaKeypair, solanaAddress: solanaKeypair.publicKey.toString() };
}
Flow: 1) Authenticate, 2) Facilitator group, 3) Quote, 4) Place order, 5) Execute deposit, 6) Track
  1. Authenticate – Get nonce, sign SIWE, authenticate, derive entropy with createEip712DocForWalletGeneration + sign.
  2. Facilitator groupqueryDepositCount(evmAddress, gatewayAddress), then createHdFacilitatorGroupFromEntropy(hexToBytes(entropy), depositCount).
  3. Quote – Use solveOptimalUsdcAmount (Solana chain ID, SOL mint, lamports, solana address, phony deposit calldata) to get USDC out; request silentswap.quote() with Solana output asset solana:<chainId>/slip44:501 and the USDC value.
  4. Place order – Sign authorizations (empty 0x for Solana), sign EIP-712 order doc, group.approveProxyAuthorizations, then silentswap.order() with metadata sourceAsset / sourceSender (Solana).
  5. Execute deposit – Read deposit params from orderResponse.transaction.metadata.params, encode depositProxy2 and USDC approve calldata; get relay.link quote with fetchRelayQuote (Solana → Avalanche, EXACT_OUTPUT, txs: approve + deposit); build Solana transaction from relay instructions, sign with Solana keypair, send and confirm.
  6. Track order – Get viewing auth from facilitator group viewer, then await trackOrderViaWebSocket(orderId, viewingAuth, { wsUrl: CONFIG.SILENTSWAP_WS }) until all outputs are finalized. Optionally fallback to getRelayStatus(requestId) polling.

For the full runnable script (including relay transaction building and WebSocket handling), see sdk/packages/react/examples/solana-swap-script/src/scripts/sol-to-sol-swap.ts.


2. EVM swaps

EVM Silent Swaps (e.g. USDC on Avalanche → token on Ethereum) use the same concepts: authenticate, create facilitator group, quote, sign and place order, execute deposit, then watch for completion.

Setup

import {
  createSilentSwapClient,
  createViemSigner,
  parseTransactionRequestForViem,
  createSignInMessage,
  createEip712DocForWalletGeneration,
  createEip712DocForOrder,
  createHdFacilitatorGroupFromEntropy,
  quoteResponseToEip712Document,
  queryDepositCount,
  solveOptimalUsdcAmount,
  caip19FungibleEvmToken,
  hexToBytes,
  PublicKeyArgGroups,
  DeliveryMethod,
  FacilitatorKeyType,
  ENVIRONMENT,
  type SilentSwapClient,
  type EvmSigner,
} from '@silentswap/sdk';
import { createWalletClient, http, publicActions, erc20Abi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { avalanche, mainnet } from 'viem/chains';
import BigNumber from 'bignumber.js';
 
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
  account,
  chain: avalanche,
  transport: http(),
}).extend(publicActions);
const signer = createViemSigner(account, client);
const silentswap = createSilentSwapClient({
  environment: ENVIRONMENT.MAINNET,
  baseUrl: 'https://api.silentswap.com',
});

Direct USDC swap (Avalanche → Ethereum)

When you already have USDC on Avalanche:

async function executeSilentSwap(
  recipientAddress: `0x${string}`,
  tokenAddress: `0x${string}`,
  tokenAmount: string,
  tokenDecimals: number = 6,
) {
  const entropy = await authenticateAndDeriveEntropy(silentswap, signer);
  const depositCount = await queryDepositCount(account.address);
  const group = await createHdFacilitatorGroupFromEntropy(hexToBytes(entropy), depositCount);
 
  const quoteResponse = await getQuote(silentswap, signer, group, recipientAddress, tokenAddress, tokenAmount, tokenDecimals);
  const orderResponse = await createOrder(silentswap, signer, group, quoteResponse);
  const depositHash = await executeDeposit(client, orderResponse);
  await watchForCompletion(client, tokenAddress, recipientAddress, group, tokenDecimals);
 
  return { orderId: orderResponse.response.orderId, depositHash, quote: quoteResponse };
}
 
// Execute: send 10 USDC from Avalanche to recipient on Ethereum
const result = await executeSilentSwap(
  account.address,
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
  '10',
  6,
);

Implementations of authenticateAndDeriveEntropy, getQuote, createOrder, executeDeposit, and watchForCompletion follow the same pattern as in Account Setup, Creating an Order, and Deposit: get nonce → SIWE sign → authenticate → EIP-712 entropy; then quote with viewer and facilitator public keys; sign authorizations and order EIP-712; approve proxies; place order; parse transaction with parseTransactionRequestForViem and send; watch ERC-20 Transfer to recipient from facilitator account.

Cross-chain swap with bridge (Ethereum → Ethereum via Avalanche)

For a source token on another chain (e.g. ETH on Ethereum), use solveOptimalUsdcAmount to get the USDC amount that will land on Avalanche, then quote with that amount:

const bridgeResult = await solveOptimalUsdcAmount(
  sourceChainId,
  sourceTokenAddress, // 0x0 for native ETH
  BigNumber(sourceAmount).shiftedBy(sourceTokenDecimals).toFixed(0),
  account.address,
);
const usdcAmount = bridgeResult.usdcAmountOut.toString();
 
// Use usdcAmount in the quote request; then create order and execute deposit.
// The deposit transaction will typically include the bridge (e.g. relay) + deposit in one flow.

After placing the order, execute the deposit (which may be a single tx that bridges and deposits, depending on your integration). Then watch for completion on the destination chain as in the direct USDC case.

Usage examples

// Direct: 10 USDC Avalanche → Ethereum
await executeSilentSwap(account.address, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', '10', 6);
 
// Different tokens (same flow, different token address and decimals)
await executeSilentSwap(account.address, '0xdAC17F958D2ee523a2206206994597C13D831ec7', '100', 6);  // USDT
await executeSilentSwap(account.address, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', '0.1', 8);  // WBTC

Next steps