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.
Quick Reference
Essential Imports
import {
// Client & Signer
createSilentSwapClient,
createViemSigner,
parseTransactionRequestForViem,
// Authentication
createSignInMessage,
createEip712DocForWalletGeneration,
createEip712DocForOrder,
// Facilitator Group
createHdFacilitatorGroupFromEntropy,
queryDepositCount,
hexToBytes,
// Order & Quote
quoteResponseToEip712Document,
// Bridge Utilities
solveOptimalUsdcAmount,
// Token Utilities
caip19FungibleEvmToken,
// Constants
DeliveryMethod,
FacilitatorKeyType,
PublicKeyArgGroups,
ENVIRONMENT,
GATEWAY_ABI,
} from '@silentswap/sdk';
import BigNumber from 'bignumber.js';Setup
import {
createSilentSwapClient,
createViemSigner,
parseTransactionRequestForViem,
createSignInMessage,
createEip712DocForOrder,
createEip712DocForWalletGeneration,
createHdFacilitatorGroupFromEntropy,
quoteResponseToEip712Document,
caip19FungibleEvmToken,
queryDepositCount,
solveOptimalUsdcAmount,
GATEWAY_ABI,
PublicKeyArgGroups,
DeliveryMethod,
FacilitatorKeyType,
ENVIRONMENT,
hexToBytes,
type SilentSwapClient,
type EvmSigner,
type AuthResponse,
type SolveUsdcResult,
} 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',
});Required Core Methods
The following methods from @silentswap/sdk are essential for executing Silent Swaps:
Authentication & Entropy
createSignInMessage(address, nonce, domain): Creates a SIWE (Sign-In with Ethereum) message for authenticationcreateEip712DocForWalletGeneration(secretToken): Creates EIP-712 document for deriving entropy from auth tokenhexToBytes(hex): Converts hex string to bytes array (required for entropy conversion)
Facilitator Group Management
queryDepositCount(userAddress): Queries the Gateway contract to get the number of deposits made by a user. This count is used as a nonce for creating facilitator groups.createHdFacilitatorGroupFromEntropy(entropy, depositCount): Creates a hierarchical deterministic facilitator group from entropy and deposit count. This generates:- Viewer account (for observing order execution)
- Facilitator accounts (for executing swaps on different chains)
Bridge Utilities
solveOptimalUsdcAmount(srcChainId, srcToken, srcAmount, userAddress, depositCalldata?, maxImpactPercent?): Calculates the optimal USDC amount that will be received after bridging a source token to Avalanche. This is essential for cross-chain swaps where you need to bridge first, then use the resulting USDC for SilentSwap.- Parameters:
srcChainId: Source chain ID (e.g., 1 for Ethereum mainnet)srcToken: Source token address (0x0or0x0000...for native tokens)srcAmount: Source amount in raw token unitsuserAddress: User's EVM addressdepositCalldata(optional): Deposit calldata for post-bridge executionmaxImpactPercent(optional): Maximum price impact allowed (default from constants)
- Returns:
SolveUsdcResultwith:usdcAmountOut: USDC amount in microUSDC (6 decimals) - ready to use in quotesactualAmountIn: Actual input amount requiredprovider: Bridge provider used ('relay'or'debridge')allowanceTarget: Allowance target address (for deBridge)
- Parameters:
Token & Asset Utilities
caip19FungibleEvmToken(chainId, tokenAddress): Creates a CAIP-19 identifier for an EVM ERC20 token- Example:
caip19FungibleEvmToken(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')→"eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
- Example:
Order Creation
quoteResponseToEip712Document(quoteResponse): Converts a quote response to an EIP-712 document for signing the ordercreateEip712DocForOrder(quoteResponse): Alternative method to create EIP-712 document for order signing
Signer Adapters
createViemSigner(account, walletClient): Creates an EVM signer adapter for viem wallet clientsparseTransactionRequestForViem(transactionRequest): Parses a transaction request from the API into viem-compatible format
Constants & Types
DeliveryMethod.SNIP: Delivery method constant for SilentSwap ordersFacilitatorKeyType.SECP256K1: Key type for EVM facilitator accountsPublicKeyArgGroups.GENERIC: Predefined public key argument groups for facilitator accountsENVIRONMENT.MAINNET: Environment constant for mainnet configurationGATEWAY_ABI: ABI for the Gateway contract (for querying deposit counts)
Method Usage Flow
// 1. Authentication
const signInMessage = createSignInMessage(address, nonce, domain);
const entropy = await signer.signEip712TypedData(
createEip712DocForWalletGeneration(authResponse.secretToken)
);
// 2. Facilitator Group
const depositCount = await queryDepositCount(address);
const group = await createHdFacilitatorGroupFromEntropy(
hexToBytes(entropy),
depositCount
);
// 3. Token Asset
const asset = caip19FungibleEvmToken(chainId, tokenAddress);
// 4. Order Signing
const orderDoc = quoteResponseToEip712Document(quoteResponse);
const signature = await signer.signEip712TypedData(orderDoc);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 (for direct USDC swaps on Avalanche)
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: Get quote with bridge (for cross-chain swaps)
async function getQuoteWithBridge(
silentswap: SilentSwapClient,
signer: EvmSigner,
group: Awaited<ReturnType<typeof createHdFacilitatorGroupFromEntropy>>,
recipientAddress: `0x${string}`,
destinationTokenAddress: `0x${string}`,
sourceChainId: number,
sourceTokenAddress: `0x${string}`,
sourceAmount: string,
sourceTokenDecimals: number
) {
// Step 1: Calculate USDC amount from bridge
const bridgeResult: SolveUsdcResult = await solveOptimalUsdcAmount(
sourceChainId,
sourceTokenAddress,
BigNumber(sourceAmount).shiftedBy(sourceTokenDecimals).toFixed(0),
signer.address
);
// bridgeResult.usdcAmountOut is already in microUSDC (6 decimals)
const usdcAmount = bridgeResult.usdcAmountOut.toString();
// Step 2: Derive viewer account
const viewer = await group.viewer();
const { publicKeyBytes: pk65_viewer } = viewer.exportPublicKey(
'*',
FacilitatorKeyType.SECP256K1
);
// Step 3: Export public keys for facilitator group
const groupPublicKeys = await group.exportPublicKeys(1, [
...PublicKeyArgGroups.GENERIC,
]);
// Step 4: Request quote using the bridged USDC amount
const [quoteError, quoteResponse] = await silentswap.quote({
signer: signer.address,
viewer: pk65_viewer,
outputs: [
{
method: DeliveryMethod.SNIP,
recipient: recipientAddress,
asset: caip19FungibleEvmToken(1, destinationTokenAddress),
value: usdcAmount as `${bigint}`,
facilitatorPublicKeys: groupPublicKeys[0],
},
],
});
if (quoteError || !quoteResponse) {
throw new Error(`Failed to get quote: ${quoteError?.type}: ${quoteError?.error}`);
}
return {
quoteResponse,
bridgeResult, // Include bridge info for reference
};
}
// 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
Example 1: Direct USDC Swap (Avalanche → Ethereum)
For swaps where you already have USDC on Avalanche:
// Execute a Silent Swap: Send 10 USDC from Avalanche to Ethereum
const result = await executeSilentSwap(
account.address, // Recipient address
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC token address (Ethereum mainnet)
'10', // Amount in human-readable format (10 USDC)
6 // USDC has 6 decimals
);
console.log('Silent Swap completed:', result);
// The function internally converts '10' to '10000000' (10 * 10^6)Example 2: Cross-Chain Swap with Bridge (Ethereum → Ethereum via Avalanche)
For swaps from a different chain (e.g., ETH on Ethereum → USDC on Ethereum):
async function executeCrossChainSilentSwap(
sourceChainId: number,
sourceTokenAddress: `0x${string}`, // 0x0 for native tokens
sourceAmount: string,
sourceTokenDecimals: number,
recipientAddress: `0x${string}`,
destinationTokenAddress: `0x${string}`,
destinationTokenDecimals: 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: Calculate USDC amount from bridge
console.log('\nStep 3: Calculating bridge USDC amount...');
const bridgeResult = await solveOptimalUsdcAmount(
sourceChainId,
sourceTokenAddress,
BigNumber(sourceAmount).shiftedBy(sourceTokenDecimals).toFixed(0),
account.address
);
console.log(`✓ Bridge will provide ${BigNumber(bridgeResult.usdcAmountOut.toString()).shiftedBy(-6).toFixed()} USDC (provider: ${bridgeResult.provider})`);
// Step 4: Get quote using bridged USDC amount
console.log('\nStep 4: Requesting quote with bridged USDC...');
const quoteResult = await getQuoteWithBridge(
silentswap,
signer,
group,
recipientAddress,
destinationTokenAddress,
sourceChainId,
sourceTokenAddress,
sourceAmount,
sourceTokenDecimals
);
console.log(`✓ Quote received (Order ID: ${quoteResult.quoteResponse.quoteId})`);
// Step 5: Sign authorizations and create order
console.log('\nStep 5: Signing authorizations and creating order...');
const orderResponse = await createOrder(
silentswap,
signer,
group,
quoteResult.quoteResponse
);
console.log(`✓ Order created (Order ID: ${orderResponse.response.orderId})`);
// Step 6: Execute deposit (bridge + deposit in one transaction)
console.log('\nStep 6: Executing bridge and deposit transaction...');
const depositHash = await executeDeposit(client, orderResponse);
console.log(`✓ Deposit transaction sent: ${depositHash}`);
// Step 7: Watch for completion
console.log('\nStep 7: Watching for order completion...');
await watchForCompletion(
client,
destinationTokenAddress,
recipientAddress,
group,
destinationTokenDecimals
);
return {
orderId: orderResponse.response.orderId,
depositHash,
quote: quoteResult.quoteResponse,
bridgeProvider: bridgeResult.provider,
usdcAmountReceived: usdcAmount,
};
} catch (err) {
console.error('Cross-chain Silent Swap error:', err);
throw err;
}
}
// Usage: Swap 1 ETH from Ethereum to USDC on Ethereum
const result = await executeCrossChainSilentSwap(
1, // Ethereum mainnet
'0x0000000000000000000000000000000000000000', // Native ETH
'1', // 1 ETH
18, // ETH has 18 decimals
account.address, // Recipient
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
6 // USDC has 6 decimals
);Example 3: Direct USDC Swap (Avalanche → Ethereum)
// Example 1: Swap 1.5 ETH (18 decimals)
const ethResult = await executeSilentSwap(
account.address,
'0x0000000000000000000000000000000000000000', // Native ETH (use slip44:60 for native)
'1.5', // 1.5 ETH
18 // ETH has 18 decimals
);
// Internally converts to: 1500000000000000000
// Example 2: Swap 0.1 WBTC (8 decimals)
const wbtcResult = await executeSilentSwap(
account.address,
'0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', // WBTC token address
'0.1', // 0.1 WBTC
8 // WBTC has 8 decimals
);
// Internally converts to: 10000000
// Example 3: Swap 100 USDT (6 decimals)
const usdtResult = await executeSilentSwap(
account.address,
'0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT token address
'100', // 100 USDT
6 // USDT has 6 decimals
);
// Internally converts to: 100000000Error 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
- Get Nonce: Request a nonce from the SilentSwap API
- Sign Message: Create and sign a SIWE (Sign-In with Ethereum) message
- Authenticate: Send the signed message to get an auth token
- 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
- Quote: Request a quote with your desired outputs
- Sign Authorizations: Sign all required authorizations (deposits, meta-txs, etc.)
- Sign Order: Sign the order's EIP-712 typed data
- Approve Proxies: Approve proxy authorizations from facilitator accounts
- Place Order: Submit the complete order to the API
- Deposit: Execute the deposit transaction on the source chain
- Watch: Monitor for completion on the destination chain
Best Practices
-
Secure Entropy Storage: Store entropy securely for wallet recovery
-
Error Handling: Always handle errors at each step with meaningful error messages
-
Transaction Monitoring: Monitor deposit transactions and watch for completion
-
Nonce Management: Use the deposit count from
queryDepositCount()as the nonce for facilitator groups -
Chain Switching: Ensure you're on the correct chain before executing transactions
-
Bridge Integration: Use
solveOptimalUsdcAmountfor cross-chain swaps to automatically calculate the optimal USDC amount after bridging
Next Steps
- Review Account Setup for detailed setup instructions
- Learn about Creating an Order for order creation details
- See Deposit for deposit transaction handling