Skip to content

Instantly share code, notes, and snippets.

@15august
Created July 17, 2025 07:34
Show Gist options
  • Select an option

  • Save 15august/7e083c085f3629e7e2cd2cd91663dc95 to your computer and use it in GitHub Desktop.

Select an option

Save 15august/7e083c085f3629e7e2cd2cd91663dc95 to your computer and use it in GitHub Desktop.
import { createClient, getClient, MAINNET_RELAY_API, configureDynamicChains, AdaptedWallet, Execute, SvmReceipt, TransactionStepItem, convertViemChainToRelayChain, RelayChain, } from '@reservoir0x/relay-sdk';
import { parseUnits, formatUnits } from 'viem';
import { mainnet, base, arbitrum, optimism, polygon } from 'viem/chains';
// Constants
const SOLANA_USDC_ADDRESS = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
const SOLANA_CHAIN_ID = 792703809;
const DEFAULT_FEE_PAYER = 'a23tFfciWmKgTmLhtTGS1NPg8eqUWmqQNyoUF16UBdF';
const DEFAULT_SPONSOR_ENDPOINT = 'http://localhost:3000/api/solana/sponsor';
const DEFAULT_SLIPPAGE = '100'; // 1%
const APP_FEE_RECIPIENT = '5MApngvd5TkmLXbhCzpyi5jBASE3GgDMeqxosMdPbPZs';
const DEFAULT_APP_FEE = '50'; // 0.5%
const SMALL_TRADE_APP_FEE = '25'; // 0.25%
// Chain ID mapping
const CHAIN_ID_MAPPING: { [key: number]: number } = {
1399811149: 792703809,
792703809: 792703809,
};
export function mapToRelayChainId(apiNetworkId: number): number {
return CHAIN_ID_MAPPING[apiNetworkId] || apiNetworkId;
}
export interface QuoteOptions {
amount: string;
fromChainId?: number;
toChainId?: number;
fromCurrency?: string;
toCurrency?: string;
slippageTolerance?: string;
recipient?: string;
}
export interface ExecuteOptions {
onProgress?: (data: any) => void;
depositGasLimit?: string;
}
export class RelayTrader {
private wallet: any;
private userAddress: string;
private chainId: number;
constructor(wallet: any, userAddress: string, chainId: number = SOLANA_CHAIN_ID) {
this.wallet = createSolanaAdapter(wallet);
this.userAddress = userAddress;
this.chainId = mapToRelayChainId(chainId);
this.initializeRelayClient();
}
private async initializeRelayClient() {
try {
const solanaRelayChain: RelayChain = {
id: 792703809,
name: "solana",
displayName: "Solana",
httpRpcUrl: "https://api.mainnet-beta.solana.com",
wsRpcUrl: "", // or "wss://api.mainnet-beta.solana.com" if needed
explorerUrl: "https://solscan.io",
explorerQueryParams: {},
iconUrl: "https://assets.relay.link/icons/792703809/light.png",
currency: {
id: "sol",
symbol: "SOL",
name: "Solana",
address: "11111111111111111111111111111111",
decimals: 9,
supportsBridging: false,
},
depositEnabled: true,
blockProductionLagging: false,
erc20Currencies: [
{
id: "pengu",
symbol: "PENGU",
name: "Pudgy Penguins",
address: "2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv",
decimals: 6,
supportsBridging: true,
withdrawalFee: 0,
depositFee: 0,
surgeEnabled: false,
},
{
id: "usdc",
symbol: "USDC",
name: "USD Coin",
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
decimals: 6,
supportsBridging: true,
supportsPermit: true,
withdrawalFee: 5,
depositFee: 2,
surgeEnabled: false,
}
],
featuredTokens: [
{
id: "sol",
symbol: "SOL",
name: "Solana",
address: "11111111111111111111111111111111",
decimals: 9,
supportsBridging: false,
metadata: {
logoURI: "https://upload.wikimedia.org/wikipedia/en/b/b9/Solana_logo.png",
},
},
{
id: "usdc",
symbol: "USDC",
name: "USD Coin",
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
decimals: 6,
supportsBridging: true,
metadata: {
logoURI: "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694",
},
},
],
tags: [],
vmType: "svm",
baseChainId: 1,
};
// Use manual chain configuration instead of dynamic
const relayChains = [
convertViemChainToRelayChain(mainnet),
convertViemChainToRelayChain(base),
convertViemChainToRelayChain(arbitrum),
convertViemChainToRelayChain(optimism),
convertViemChainToRelayChain(polygon),
solanaRelayChain,
];
createClient({
baseApiUrl: MAINNET_RELAY_API,
source: "yolo.meme",
chains: relayChains
});
console.log('Relay client initialized successfully');
} catch (error) {
console.error('Failed to initialize Relay client:', error);
}
}
async buyWithUSDC(tokenAddress: string, usdcAmount: string, fromChainId: number = this.chainId, options: Partial<QuoteOptions> = {}) {
const mappedFromChainId = mapToRelayChainId(fromChainId);
await new Promise(resolve => setTimeout(resolve, 1000));
const client = getClient();
const amountInWei = parseUnits(usdcAmount, 6).toString();
const isSmallTrade = parseFloat(usdcAmount) < 1;
// First try with normal settings
try {
return await client.actions.getQuote({
chainId: mappedFromChainId,
toChainId: this.chainId,
currency: this.getUSDCAddress(),
toCurrency: tokenAddress,
amount: amountInWei,
tradeType: 'EXACT_INPUT' as const,
user: this.userAddress,
recipient: options.recipient || this.userAddress,
wallet: this.wallet,
options: {
slippageTolerance: options.slippageTolerance || DEFAULT_SLIPPAGE,
appFees: [{
recipient: APP_FEE_RECIPIENT,
fee: isSmallTrade ? SMALL_TRADE_APP_FEE : DEFAULT_APP_FEE
}]
},
});
} catch (error) {
// If quote fails, try with optimized settings for smaller transactions
console.log('Retrying with optimized settings for transaction size...');
return await client.actions.getQuote({
chainId: mappedFromChainId,
toChainId: this.chainId,
currency: this.getUSDCAddress(),
toCurrency: tokenAddress,
amount: amountInWei,
tradeType: 'EXACT_INPUT' as const,
user: this.userAddress,
recipient: options.recipient || this.userAddress,
wallet: this.wallet,
options: {
slippageTolerance: '500', // Higher slippage = smaller transaction
appFees: [], // Remove app fees to reduce transaction size
},
});
}
}
async executeQuote(quote: any, options: ExecuteOptions = {}) {
const client = getClient();
return client.actions.execute({
quote,
wallet: this.wallet,
depositGasLimit: options.depositGasLimit,
onProgress: options.onProgress || ((data) => console.log('Progress:', data))
});
}
async getPrice(fromToken: string, toToken: string, amount: string, fromDecimals: number = 9, tradeType: 'EXACT_INPUT' | 'EXACT_OUTPUT' = 'EXACT_INPUT') {
const client = getClient();
const amountInWei = parseUnits(amount, fromDecimals).toString();
return client.actions.getQuote({
chainId: this.chainId,
toChainId: this.chainId,
currency: fromToken,
toCurrency: toToken,
amount: amountInWei,
tradeType,
user: this.userAddress,
recipient: this.userAddress,
wallet: this.wallet,
options: {
appFees: [{
recipient: APP_FEE_RECIPIENT,
fee: DEFAULT_APP_FEE
}]
}
});
}
formatAmount(amount: string, decimals: number = 9): string {
return formatUnits(BigInt(amount), decimals);
}
parseAmount(amount: string, decimals: number = 9): string {
return parseUnits(amount, decimals).toString();
}
getUSDCAddress(): string {
return SOLANA_USDC_ADDRESS;
}
getChainId(): number {
return this.chainId;
}
getBaseUSDCAddress(): string {
return this.getUSDCAddress();
}
getBaseChainId(): number {
return this.getChainId();
}
}
export const createRelayTrader = (wallet: any, userAddress: string, chainId: number = SOLANA_CHAIN_ID) => {
return new RelayTrader(wallet, userAddress, chainId);
};
const createSolanaAdapter = (embeddedWallet: any): AdaptedWallet => {
return {
vmType: 'svm',
async getChainId() {
return SOLANA_CHAIN_ID;
},
async handleSignMessageStep(stepItem, step) {
throw new Error('Message signing not implemented for Solana');
},
async handleSendTransactionStep(chainId: number, stepItem: TransactionStepItem, step: Execute['steps'][0]) {
const txData = stepItem.data;
const solanaWeb3 = await import('@solana/web3.js');
const { Buffer } = await import('buffer');
const { TransactionInstruction, TransactionMessage, VersionedTransaction, PublicKey, Connection } = solanaWeb3;
const connection = new Connection('https://api.mainnet-beta.solana.com');
const instructions = txData.instructions?.map((i: any) => {
return new TransactionInstruction({
keys: i.keys.map((k: any) => ({
isSigner: k.isSigner,
isWritable: k.isWritable,
pubkey: new PublicKey(k.pubkey)
})),
programId: new PublicKey(i.programId),
data: Buffer.from(i.data, 'hex')
});
}) ?? [];
const { blockhash } = await connection.getLatestBlockhash();
const addressLookupTableAccounts = await Promise.all(
txData.addressLookupTableAddresses?.map(
async (address: string) =>
await connection
.getAddressLookupTable(new PublicKey(address))
.then((res: any) => res.value)
) ?? []
);
const messageV0 = new TransactionMessage({
payerKey: new PublicKey(DEFAULT_FEE_PAYER),
recentBlockhash: blockhash,
instructions
}).compileToV0Message(addressLookupTableAccounts);
const transaction = new VersionedTransaction(messageV0);
// Check transaction size before signing to avoid size limits
const preliminarySize = Buffer.from(transaction.message.serialize()).length;
console.log('Transaction size before signing:', preliminarySize);
if (preliminarySize > 1000) { // Leave larger buffer for signatures (can add 500+ bytes)
console.warn('Transaction too large, trying to remove unnecessary instructions...');
// Strategy: Filter out optional instructions to reduce size
console.log('πŸ“‹ Original transaction instructions:');
txData.instructions?.forEach((instruction: any, index: number) => {
console.log(` Instruction ${index + 1}:`, {
programId: instruction.programId,
dataLength: instruction.data?.length || 0,
keysCount: instruction.keys?.length || 0,
firstDataByte: instruction.data?.[0],
keys: instruction.keys?.map((k: any) => ({
pubkey: k.pubkey,
isSigner: k.isSigner,
isWritable: k.isWritable
}))
});
});
const essentialInstructions = txData.instructions?.filter((instruction: any, index: number) => {
const programId = instruction.programId;
// Keep essential instructions only
if (programId === '11111111111111111111111111111111') {
console.log(`βœ… Keeping instruction ${index + 1}: System program`);
return true; // System program
}
if (programId === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') {
console.log(`βœ… Keeping instruction ${index + 1}: Token program`);
return true; // Token program
}
if (programId === 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') {
console.log(`βœ… Keeping instruction ${index + 1}: Associated token program`);
return true; // Associated token
}
if (programId === 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4') {
console.log(`βœ… Keeping instruction ${index + 1}: Jupiter program`);
return true; // Jupiter
}
// Skip memo and other optional instructions
if (programId === 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr') {
console.log(`❌ Removing instruction ${index + 1}: Memo program`);
return false; // Memo
}
if (programId === 'ComputeBudget111111111111111111111111111111') {
console.log(`❌ Removing instruction ${index + 1}: Compute Budget program`);
return false; // Compute budget
}
console.log(`βœ… Keeping instruction ${index + 1}: Unknown program (${programId})`);
return true; // Keep other instructions by default
}) || [];
console.log(`Reduced instructions from ${txData.instructions?.length || 0} to ${essentialInstructions.length}`);
// Rebuild transaction with fewer instructions
const optimizedInstructions = essentialInstructions.map((i: any) => {
return new TransactionInstruction({
keys: i.keys.map((k: any) => ({
isSigner: k.isSigner,
isWritable: k.isWritable,
pubkey: new PublicKey(k.pubkey)
})),
programId: new PublicKey(i.programId),
data: Buffer.from(i.data, 'hex')
});
});
const optimizedMessageV0 = new TransactionMessage({
payerKey: new PublicKey(DEFAULT_FEE_PAYER),
recentBlockhash: blockhash,
instructions: optimizedInstructions
}).compileToV0Message(addressLookupTableAccounts);
const optimizedTransaction = new VersionedTransaction(optimizedMessageV0);
const optimizedSize = Buffer.from(optimizedTransaction.message.serialize()).length;
console.log(`Optimized transaction size: ${optimizedSize} bytes`);
if (optimizedSize > 1230) {
throw new Error(`Transaction still too large after optimization: ${optimizedSize} bytes. Try reducing trade amount or using a different token pair.`);
}
// Use optimized transaction
const serializedMessage = Buffer.from(optimizedTransaction.message.serialize()).toString('base64');
const provider = await embeddedWallet.getProvider();
const { signature: serializedUserSignature } = await provider.request({
method: 'signMessage',
params: { message: serializedMessage }
});
const userSignature = Buffer.from(serializedUserSignature, 'base64');
optimizedTransaction.addSignature(new PublicKey(embeddedWallet.address), userSignature);
const serializedTransaction = Buffer.from(optimizedTransaction.serialize()).toString('base64');
console.log('Optimized serializedTransaction length: ', serializedTransaction.length);
const response = await fetch(DEFAULT_SPONSOR_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transaction: serializedTransaction })
});
const { transactionHash } = await response.json();
return transactionHash;
}
const serializedMessage = Buffer.from(transaction.message.serialize()).toString('base64');
const provider = await embeddedWallet.getProvider();
const { signature: serializedUserSignature } = await provider.request({
method: 'signMessage',
params: { message: serializedMessage }
});
const userSignature = Buffer.from(serializedUserSignature, 'base64');
transaction.addSignature(new PublicKey(embeddedWallet.address), userSignature);
const serializedTransaction = Buffer.from(transaction.serialize()).toString('base64');
console.log('serializedTransaction length: ', serializedTransaction.length);
// Check final size after signing
if (serializedTransaction.length > 1644) {
throw new Error(`Transaction too large: ${serializedTransaction.length} bytes (max: 1644). Consider reducing transaction complexity.`);
}
const response = await fetch(DEFAULT_SPONSOR_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transaction: serializedTransaction })
});
const { transactionHash } = await response.json();
return transactionHash;
},
async handleConfirmTransactionStep(tx: string, chainId: number, onReplaced?: (replacementTxHash: string) => void, onCancelled?: () => void): Promise<SvmReceipt> {
const solanaWeb3 = await import('@solana/web3.js');
const { Connection } = solanaWeb3;
const connection = new Connection('https://api.mainnet-beta.solana.com');
const confirmation = await connection.confirmTransaction(tx, 'confirmed');
return {
txHash: tx,
blockNumber: confirmation.context.slot,
blockHash: '',
} as SvmReceipt;
},
async address() {
return embeddedWallet.address;
},
async switchChain(chainId: number): Promise<void> {
throw new Error('Chain switching not supported for Solana wallets');
}
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment