Skip to content

Instantly share code, notes, and snippets.

@15august
Created July 18, 2025 14:33
Show Gist options
  • Select an option

  • Save 15august/0eb9b1660e2942df2ffff63ba99e717b to your computer and use it in GitHub Desktop.

Select an option

Save 15august/0eb9b1660e2942df2ffff63ba99e717b to your computer and use it in GitHub Desktop.
import { createClient, adaptViemWallet, getClient, MAINNET_RELAY_API, configureDynamicChains, AdaptedWallet, Execute, SvmReceipt, TransactionStepItem, convertViemChainToRelayChain, RelayChain, } from '@reservoir0x/relay-sdk';
import { parseUnits, formatUnits, createWalletClient, createPublicClient, http } from 'viem';
import { mainnet, base, arbitrum, optimism, polygon } from 'viem/chains';
import { SmartWalletClientType } from '@privy-io/js-sdk-core/smart-wallets';
// Constants for Base network
const BASE_USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const BASE_CHAIN_ID = 8453;
const DEFAULT_SLIPPAGE = '100'; // 1%
const APP_FEE_RECIPIENT = '0xbB656a3ed33C26dEe7f956545DD21361D5e788dC';
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 } = {
8453: 8453, // Base
1: 1, // Ethereum mainnet
137: 137, // Polygon
42161: 42161, // Arbitrum
10: 10, // Optimism
};
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 = BASE_CHAIN_ID) {
// Handle both smart wallet clients and regular wallet clients
if (wallet.account && wallet.sendTransaction) {
// This is likely a smart wallet client, use it directly
this.wallet = createSmartWalletClientAdapter(wallet);
} else {
// This is a regular wallet client, adapt it
this.wallet = adaptViemWallet(wallet);
}
this.userAddress = userAddress;
this.chainId = chainId;
this.initializeRelayClient();
}
private async initializeRelayClient() {
try {
// Use manual chain configuration
const relayChains = [
convertViemChainToRelayChain(mainnet),
convertViemChainToRelayChain(base),
convertViemChainToRelayChain(arbitrum),
convertViemChainToRelayChain(optimism),
convertViemChainToRelayChain(polygon),
];
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...');
}
}
async executeQuote(quote: any, options: ExecuteOptions = {}) {
const client = getClient();
return client.actions.execute({
quote,
wallet: this.wallet,
depositGasLimit: options.depositGasLimit,
onProgress: options.onProgress || ((data) => {
if (data?.currentStepItem?.data) {
console.log('๐Ÿš€ Transaction call data:', JSON.stringify(data.currentStepItem.data, null, 2));
}
})
});
}
formatAmount(amount: string, decimals: number = 18): string {
return formatUnits(BigInt(amount), decimals);
}
parseAmount(amount: string, decimals: number = 18): string {
return parseUnits(amount, decimals).toString();
}
getUSDCAddress(): string {
return BASE_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 = BASE_CHAIN_ID) => {
return new RelayTrader(wallet, userAddress, chainId);
};
const createSmartWalletClientAdapter = (
smartWallet: SmartWalletClientType
): AdaptedWallet => {
return {
vmType: 'evm',
async getChainId() {
return smartWallet.chain.id;
},
async address() {
return smartWallet.account.address;
},
async handleSignMessageStep(stepItem, step) {
if (!stepItem.data?.sign) {
throw new Error('Invalid step item: missing sign data');
}
const { message, signatureKind, domain, types, value } = stepItem.data.sign;
if (signatureKind === 'eip191') {
if (typeof message !== 'string') {
throw new Error('Message is required for eip191 signature');
}
return await smartWallet.signMessage({ message });
}
if (signatureKind === 'eip712') {
if (!domain || !types || !value) {
throw new Error('Domain, types, and value are required for eip712 signature');
}
// A more robust way to find the primaryType, excluding the domain.
const primaryType = Object.keys(types).find(key => key !== 'EIP712Domain');
if (!primaryType) {
throw new Error('Could not determine primaryType for EIP712 signature');
}
return await smartWallet.signTypedData({
domain,
types,
primaryType,
message: value,
});
}
throw new Error(`Unsupported signature kind: ${signatureKind}`);
},
async handleSendTransactionStep(chainId, stepItem, step) {
const txData = stepItem.data;
try {
const result = await smartWallet.sendTransaction({
to: txData.to as `0x${string}`,
data: (txData.data as `0x${string}`) || '0x',
// Gas fields are typically handled by the bundler/paymaster
// and can often be omitted.
});
return result;
} catch (error) {
console.error('Smart wallet transaction failed:', error);
throw error;
}
},
async handleConfirmTransactionStep(hash, chainId, onReplaced, onCancelled) {
return new Promise((resolve, reject) => {
const checkTransaction = async () => {
try {
// Use viem client for read operations
const publicClient = createPublicClient({
chain: smartWallet.chain,
transport: http()
});
const receipt = await publicClient.getTransactionReceipt({ hash: hash as `0x${string}` });
if (receipt) {
resolve(receipt);
} else {
// Continue polling
setTimeout(checkTransaction, 2000);
}
} catch (error) {
// Transaction might still be pending
setTimeout(checkTransaction, 2000);
}
};
checkTransaction();
// Timeout after 5 minutes
setTimeout(() => {
reject(new Error('Transaction confirmation timeout'));
}, 300000);
});
},
async switchChain(chainId) {
throw new Error('Chain switching not supported by privy smart wallets. Create a new wallet for different chains.');
},
// Optional: Support for batched transactions (ERC-4337 feature)
async supportsAtomicBatch(chainId) {
return true; // privy smart wallets likely support batching
},
async handleBatchTransactionStep(chainId, stepItems) {
console.log('๐Ÿš€ Sending batch transaction with step items:');
// Map the step items to the structure expected by the batch sendTransaction
const calls = stepItems.map(item => ({
to: item.data.to as `0x${string}`,
data: (item.data.data || '0x') as `0x${string}`,
value: item.data.value ? BigInt(item.data.value) : undefined,
}));
const result = await smartWallet.sendTransaction({
calls: calls as any // Cast to bypass strict typing for raw transaction calls
});
return result;
},
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment