import {
Sodax,
ChainKeys,
type CreateIntentParams,
type SolverIntentQuoteRequest,
type SolverIntentStatusRequest,
SolverIntentStatusCode,
type IEvmWalletProvider
} from "@sodax/sdk";
async function executeSwap(
evmWalletProvider: IEvmWalletProvider,
inputAmount: bigint
): Promise<void> {
try {
// Step 1: Initialize Sodax
console.log('Step 1: Initializing Sodax...');
const sodax = new Sodax();
const initResult = await sodax.initialize();
if (!initResult.ok) {
console.warn('Initialization failed, using packaged defaults:', initResult.error);
}
// Read chain config off the Sodax instance — picks up any constructor overrides
// and any dynamic config loaded by initialize(). Never use a static import of
// `spokeChainConfig` from `@sodax/types` here — overrides will be silently lost.
const arbEthToken = sodax.config.spokeChainConfig[ChainKeys.ARBITRUM_MAINNET].nativeToken; // ETH on Arbitrum
const polygonPolToken = sodax.config.spokeChainConfig[ChainKeys.POLYGON_MAINNET].nativeToken; // POL on Polygon
// Step 2: Get Quote
console.log('Step 2: Getting quote...');
const quoteRequest: SolverIntentQuoteRequest = {
token_src: arbEthToken,
token_dst: polygonPolToken,
token_src_blockchain_id: ChainKeys.ARBITRUM_MAINNET,
token_dst_blockchain_id: ChainKeys.POLYGON_MAINNET,
amount: inputAmount,
quote_type: 'exact_input',
};
const quoteResult = await sodax.swaps.getQuote(quoteRequest);
if (!quoteResult.ok) {
console.error('Failed to get quote:', quoteResult.error);
return;
}
const quotedAmount = quoteResult.value.quoted_amount;
console.log('Quoted amount:', quotedAmount);
// Step 3: Prepare intent parameters
const walletAddress = await evmWalletProvider.getWalletAddress();
const deadlineResult = await sodax.swaps.getSwapDeadline(300n); // 5 minutes
if (!deadlineResult.ok) {
console.error('Failed to compute deadline:', deadlineResult.error);
return;
}
const createIntentParams: CreateIntentParams<typeof ChainKeys.ARBITRUM_MAINNET> = {
inputToken: arbEthToken,
outputToken: polygonPolToken,
inputAmount: inputAmount,
minOutputAmount: (quotedAmount * 95n) / 100n, // 5% slippage tolerance
deadline: deadlineResult.value,
allowPartialFill: false,
srcChainKey: ChainKeys.ARBITRUM_MAINNET,
dstChainKey: ChainKeys.POLYGON_MAINNET,
srcAddress: walletAddress,
dstAddress: walletAddress,
solver: '0x0000000000000000000000000000000000000000',
data: '0x',
};
// Step 4: Check Allowance
console.log('Step 4: Checking allowance...');
const allowanceResult = await sodax.swaps.isAllowanceValid({
params: createIntentParams,
walletProvider: evmWalletProvider,
});
if (!allowanceResult.ok) {
console.error('Failed to check allowance:', allowanceResult.error);
return;
}
// Step 5: Approve if Needed
if (!allowanceResult.value) {
console.log('Step 5: Approving tokens...');
const approveResult = await sodax.swaps.approve({
params: createIntentParams,
walletProvider: evmWalletProvider,
});
if (!approveResult.ok) {
console.error('Failed to approve tokens:', approveResult.error);
return;
}
const approvalTxHash = approveResult.value;
console.log('Approval transaction hash:', approvalTxHash);
// Wait for approval confirmation
await evmWalletProvider.waitForTransactionReceipt(approvalTxHash);
console.log('Approval confirmed');
} else {
console.log('Step 5: Approval not needed');
}
// Step 6: Execute Swap
console.log('Step 6: Executing swap...');
const swapResult = await sodax.swaps.swap({
params: createIntentParams,
walletProvider: evmWalletProvider,
});
// Step 7: Handle Swap Result
if (!swapResult.ok) {
const error = swapResult.error;
console.error('Swap failed');
switch (error.code) {
case 'EXECUTION_FAILED':
console.error('Swap orchestration failed. Cause:', error.cause);
break;
case 'RELAY_TIMEOUT':
console.error('Hub relay timed out. Cause:', error.cause);
break;
default:
console.error('Error:', error.code, error.cause ?? '');
}
return;
}
// Success!
const { solverExecutionResponse, intent, intentDeliveryInfo } = swapResult.value;
console.log('Step 7: Swap transaction submitted successfully!');
console.log('Solver execution response:', solverExecutionResponse);
console.log('Intent:', intent);
console.log('Source transaction hash:', intentDeliveryInfo.srcTxHash);
console.log('Destination transaction hash:', intentDeliveryInfo.dstTxHash);
// Step 8: Check Intent Status (with continuous polling)
console.log('Step 8: Checking intent status...');
await checkIntentStatus(sodax, intentDeliveryInfo.dstTxHash);
} catch (error) {
console.error('Unexpected error during swap:', error);
}
}
/**
* Polls the solver API until the intent reaches a terminal state.
* Pass the hub-chain (destination) tx hash from the swap result.
*/
async function checkIntentStatus(
sodax: Sodax,
dstTxHash: string,
maxAttempts = 60,
intervalMs = 5000,
): Promise<void> {
const statusRequest: SolverIntentStatusRequest = {
intent_tx_hash: dstTxHash as `0x${string}`,
};
let attempt = 0;
let lastStatus: SolverIntentStatusCode | null = null;
let notFoundCount = 0;
while (attempt < maxAttempts) {
attempt++;
const statusResult = await sodax.swaps.getStatus(statusRequest);
if (!statusResult.ok) {
console.error(`[Attempt ${attempt}] Failed to check intent status:`, statusResult.error);
await new Promise(resolve => setTimeout(resolve, intervalMs));
continue;
}
const { status, fill_tx_hash } = statusResult.value;
if (status === SolverIntentStatusCode.SOLVED) {
console.log(`[Attempt ${attempt}] Swap completed successfully!`);
if (fill_tx_hash) {
console.log(`Fill transaction hash: ${fill_tx_hash}`);
}
return;
}
if (status === SolverIntentStatusCode.FAILED) {
console.log(`[Attempt ${attempt}] Swap failed`);
return;
}
if (status === SolverIntentStatusCode.NOT_FOUND) {
notFoundCount++;
if (notFoundCount >= 3) {
console.log(`[Attempt ${attempt}] Intent not found after ${notFoundCount} attempts. Check tx hash manually.`);
return;
}
await new Promise(resolve => setTimeout(resolve, intervalMs));
continue;
}
if (status !== lastStatus) {
switch (status) {
case SolverIntentStatusCode.NOT_STARTED_YET:
console.log(`[Attempt ${attempt}] Intent queued, waiting to be processed`);
break;
case SolverIntentStatusCode.STARTED_NOT_FINISHED:
console.log(`[Attempt ${attempt}] Intent is being processed`);
break;
default:
console.log(`[Attempt ${attempt}] Unknown status (${status})`);
return;
}
lastStatus = status;
} else {
console.log(`[Attempt ${attempt}] Still processing... (status: ${status})`);
}
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
console.log(`Status polling reached maximum attempts (${maxAttempts}).`);
console.log(`Last known status: ${lastStatus ?? 'unknown'}`);
console.log(`Check manually using destination tx hash: ${dstTxHash}`);
}
// Usage
await executeSwap(evmWalletProvider, 100000000000000n); // 0.0001 ETH