Skip to content

Complete Example

This example demonstrates a complete Silent Swap flow using the Core SDK in a Node.js backend environment. Silent Swap enables private, non-custodial cross-chain swaps.

Setup

import { 
  createSilentSwapClient,
  createViemSigner,
  parseTransactionRequestForViem,
  createSignInMessage,
  createEip712DocForOrder,
  createEip712DocForWalletGeneration,
  createHdFacilitatorGroupFromEntropy,
  quoteResponseToEip712Document,
  caip19FungibleEvmToken,
  queryDepositCount,
  GATEWAY_ABI,
  PublicKeyArgGroups,
  DeliveryMethod,
  FacilitatorKeyType,
  ENVIRONMENT,
  hexToBytes,
  type SilentSwapClient,
  type EvmSigner,
  type AuthResponse,
} 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';
 
// Create wallet client
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
  account,
  chain: avalanche,
  transport: http(),
}).extend(publicActions);
 
// Create EVM signer
const signer = createViemSigner(account, client);
 
// Create SilentSwap client
const silentswap = createSilentSwapClient({
  environment: ENVIRONMENT.MAINNET,
  baseUrl: 'https://api.silentswap.com',
});

Complete Silent Swap Flow

async function executeSilentSwap(
  recipientAddress: `0x${string}`,
  tokenAddress: `0x${string}`,
  tokenAmount: string,
  tokenDecimals: number = 6
) {
  try {
    // Step 1: Authenticate and derive entropy
    console.log('Step 1: Authenticating with SilentSwap...');
    const entropy = await authenticateAndDeriveEntropy(silentswap, signer);
    console.log('✓ Authentication successful');
 
    // Step 2: Create facilitator group
    console.log('\nStep 2: Creating facilitator group...');
    const depositCount = await queryDepositCount(account.address);
    const group = await createHdFacilitatorGroupFromEntropy(
      hexToBytes(entropy),
      depositCount
    );
    console.log(`✓ Facilitator group created (deposit count: ${depositCount})`);
 
    // Step 3: Get quote
    console.log('\nStep 3: Requesting quote...');
    const quoteResponse = await getQuote(
      silentswap,
      signer,
      group,
      recipientAddress,
      tokenAddress,
      tokenAmount,
      tokenDecimals
    );
    console.log(`✓ Quote received (Order ID: ${quoteResponse.quoteId})`);
 
    // Step 4: Sign authorizations and create order
    console.log('\nStep 4: Signing authorizations and creating order...');
    const orderResponse = await createOrder(
      silentswap,
      signer,
      group,
      quoteResponse
    );
    console.log(`✓ Order created (Order ID: ${orderResponse.response.orderId})`);
 
    // Step 5: Execute deposit
    console.log('\nStep 5: Executing deposit transaction...');
    const depositHash = await executeDeposit(client, orderResponse);
    console.log(`✓ Deposit transaction sent: ${depositHash}`);
 
    // Step 6: Watch for completion
    console.log('\nStep 6: Watching for order completion...');
    await watchForCompletion(
      client,
      tokenAddress,
      recipientAddress,
      group,
      tokenDecimals
    );
 
    return {
      orderId: orderResponse.response.orderId,
      depositHash,
      quote: quoteResponse,
    };
  } catch (err) {
    console.error('Silent Swap error:', err);
    throw err;
  }
}
 
// Helper: Authenticate and derive entropy
async function authenticateAndDeriveEntropy(
  silentswap: SilentSwapClient,
  signer: EvmSigner
): Promise<`0x${string}`> {
  // Get nonce
  const [nonceError, nonceResponse] = await silentswap.nonce(signer.address);
  if (!nonceResponse || nonceError) {
    throw new Error(`Failed to get nonce: ${nonceError?.type}: ${nonceError?.error}`);
  }
 
  // Create sign-in message
  const signInMessage = createSignInMessage(
    signer.address,
    nonceResponse.nonce,
    'silentswap.com'
  );
 
  // Sign message
  const siweSignature = await signer.signEip191Message(signInMessage.message);
 
  // Authenticate
  const [authError, authResponse] = await silentswap.authenticate({
    siwe: {
      message: signInMessage.message,
      signature: siweSignature,
    },
  });
 
  if (!authResponse || authError) {
    throw new Error(`Failed to authenticate: ${authError?.type}: ${authError?.error}`);
  }
 
  // Derive entropy from auth token
  const eip712Doc = createEip712DocForWalletGeneration(authResponse.secretToken);
  const entropy = await signer.signEip712TypedData(eip712Doc);
 
  return entropy;
}
 
// Helper: Get quote
async function getQuote(
  silentswap: SilentSwapClient,
  signer: EvmSigner,
  group: Awaited<ReturnType<typeof createHdFacilitatorGroupFromEntropy>>,
  recipientAddress: `0x${string}`,
  tokenAddress: `0x${string}`,
  tokenAmount: string,
  tokenDecimals: number
) {
  // Derive viewer account
  const viewer = await group.viewer();
  const { publicKeyBytes: pk65_viewer } = viewer.exportPublicKey(
    '*',
    FacilitatorKeyType.SECP256K1
  );
 
  // Export public keys for facilitator group
  const groupPublicKeys = await group.exportPublicKeys(1, [
    ...PublicKeyArgGroups.GENERIC,
  ]);
 
  // Request quote
  const [quoteError, quoteResponse] = await silentswap.quote({
    signer: signer.address,
    viewer: pk65_viewer,
    outputs: [
      {
        method: DeliveryMethod.SNIP,
        recipient: recipientAddress,
        asset: caip19FungibleEvmToken(1, tokenAddress),
        value: BigNumber(tokenAmount).shiftedBy(tokenDecimals).toFixed(0) as `${bigint}`,
        facilitatorPublicKeys: groupPublicKeys[0],
      },
    ],
  });
 
  if (quoteError || !quoteResponse) {
    throw new Error(`Failed to get quote: ${quoteError?.type}: ${quoteError?.error}`);
  }
 
  return quoteResponse;
}
 
// Helper: Create order
async function createOrder(
  silentswap: SilentSwapClient,
  signer: EvmSigner,
  group: Awaited<ReturnType<typeof createHdFacilitatorGroupFromEntropy>>,
  quoteResponse: Awaited<ReturnType<typeof getQuote>>
) {
  // Sign authorizations
  const signedAuths = await Promise.all(
    quoteResponse.authorizations.map(async (g_auth) => ({
      ...g_auth,
      signature: await (async () => {
        if ('eip3009_deposit' === g_auth.type) {
          return await signer.signEip712TypedData(g_auth.eip712);
        }
        throw Error(`Authorization instruction type not implemented: ${g_auth.type}`);
      })(),
    }))
  );
 
  // Sign the order's EIP-712
  const orderDoc = quoteResponseToEip712Document(quoteResponse);
  const signedQuote = await signer.signEip712TypedData(orderDoc);
 
  // Approve proxy authorizations
  const facilitatorReplies = await group.approveProxyAuthorizations(
    quoteResponse.facilitators,
    {
      proxyPublicKey: silentswap.proxyPublicKey,
    }
  );
 
  // Place the order
  const [orderError, orderResponse] = await silentswap.order({
    quote: quoteResponse.quote,
    quoteId: quoteResponse.quoteId,
    authorizations: signedAuths,
    eip712Domain: orderDoc.domain,
    signature: signedQuote,
    facilitators: facilitatorReplies,
  });
 
  if (orderError || !orderResponse) {
    throw new Error(`Failed to place order: ${orderError?.type}: ${orderError?.error}`);
  }
 
  return orderResponse;
}
 
// Helper: Execute deposit
async function executeDeposit(
  client: ReturnType<typeof createWalletClient>,
  orderResponse: Awaited<ReturnType<typeof createOrder>>
) {
  // Parse transaction request
  const txRequestParams = parseTransactionRequestForViem(orderResponse.transaction);
 
  // Send transaction
  const hash = await client.sendTransaction(txRequestParams);
 
  // Wait for confirmation
  const txReceipt = await client.waitForTransactionReceipt({ hash });
  console.log(
    `Deposit confirmed: ${BigNumber(orderResponse.response.order.deposit)
      .shiftedBy(-6)
      .toFixed()} USDC at ${txReceipt.transactionHash}`
  );
 
  return hash;
}
 
// Helper: Watch for completion
async function watchForCompletion(
  client: ReturnType<typeof createWalletClient>,
  tokenAddress: `0x${string}`,
  recipientAddress: `0x${string}`,
  group: Awaited<ReturnType<typeof createHdFacilitatorGroupFromEntropy>>,
  tokenDecimals: number
) {
  // Get facilitator account for coin type 60 (ETH) at output index 0
  const facilitator0Eth = await group.account('60', 0);
  const facilitator0EthEvm = await facilitator0Eth.evmSigner();
 
  // Create client for destination chain (Mainnet)
  const destinationClient = createWalletClient({
    chain: mainnet,
    transport: http(),
  }).extend(publicActions);
 
  // Watch for ERC-20 transfer event
  return new Promise<void>((resolve) => {
    destinationClient.watchContractEvent({
      address: tokenAddress,
      abi: erc20Abi,
      eventName: 'Transfer',
      args: {
        to: recipientAddress,
        from: facilitator0EthEvm.address,
      },
      onLogs: (logs) => {
        for (const log of logs) {
          const { to, value } = log.args;
          console.log(
            `✓ Recipient ${to} received ${BigNumber(value!)
              .shiftedBy(-tokenDecimals)
              .toFixed()} tokens`
          );
        }
        resolve();
      },
    });
  });
}

Usage Example

// Execute a Silent Swap: Send 10 USDC from Avalanche to Ethereum
const result = await executeSilentSwap(
  account.address, // Recipient address
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC token address
  '10', // Amount (will be converted using decimals)
  6 // USDC has 6 decimals
);
 
console.log('Silent Swap completed:', result);

Error Handling Example

async function safeSilentSwap(...args: Parameters<typeof executeSilentSwap>) {
  try {
    return await executeSilentSwap(...args);
  } catch (err) {
    if (err instanceof Error) {
      if (err.message.includes('Failed to get nonce')) {
        console.error('Nonce retrieval failed. Check your connection.');
      } else if (err.message.includes('Failed to authenticate')) {
        console.error('Authentication failed. Check your signature.');
      } else if (err.message.includes('Failed to get quote')) {
        console.error('Quote request failed. Check your parameters.');
      } else if (err.message.includes('Failed to place order')) {
        console.error('Order creation failed. Check your authorizations.');
      } else {
        console.error('Unexpected error:', err.message);
      }
    }
    throw err;
  }
}

Backend Service Example

import express from 'express';
import { executeSilentSwap } from './silent-swap';
 
const app = express();
app.use(express.json());
 
app.post('/api/silent-swap', async (req, res) => {
  try {
    const { recipientAddress, tokenAddress, amount, decimals } = req.body;
 
    const result = await executeSilentSwap(
      recipientAddress,
      tokenAddress,
      amount,
      decimals
    );
 
    res.json({
      success: true,
      orderId: result.orderId,
      depositHash: result.depositHash,
    });
  } catch (err) {
    res.status(500).json({
      success: false,
      error: err instanceof Error ? err.message : 'Unknown error',
    });
  }
});
 
app.listen(3000, () => {
  console.log('Silent Swap API server running on port 3000');
});

Key Concepts

Authentication Flow

  1. Get Nonce: Request a nonce from the SilentSwap API
  2. Sign Message: Create and sign a SIWE (Sign-In with Ethereum) message
  3. Authenticate: Send the signed message to get an auth token
  4. Derive Entropy: Sign the auth token to derive entropy for wallet generation

Facilitator Group

The facilitator group is a hierarchical deterministic (HD) wallet system that generates:

  • Viewer Account: Used to observe order execution
  • Facilitator Accounts: Used to execute the swap on different chains

Order Flow

  1. Quote: Request a quote with your desired outputs
  2. Sign Authorizations: Sign all required authorizations (deposits, meta-txs, etc.)
  3. Sign Order: Sign the order's EIP-712 typed data
  4. Approve Proxies: Approve proxy authorizations from facilitator accounts
  5. Place Order: Submit the complete order to the API
  6. Deposit: Execute the deposit transaction on the source chain
  7. Watch: Monitor for completion on the destination chain

Best Practices

  1. Secure Entropy Storage: Store entropy securely for wallet recovery
  2. Error Handling: Always handle errors at each step
  3. Transaction Monitoring: Monitor deposit transactions and watch for completion
  4. Nonce Management: Use the deposit count as the nonce for facilitator groups
  5. Chain Switching: Ensure you're on the correct chain before executing transactions

Next Steps