EVM Integration (Ethereum, Polygon, Base)
On EVM chains, the signing and submission flow differs from Solana. This guide covers the key differences.
Multi-Transaction Operations
On EVM chains, supply operations return two transactions that must be signed and submitted in order:
- Approve transaction — Authorizes the protocol to spend your USDC
- Supply transaction — Deposits USDC into the yield strategy
EVM unsigned transactions are returned as JSON objects with these fields:
| Field | Type | Description |
|---|
to | string | Contract/recipient address |
data | string | Encoded call data (hex string) |
value | string | Native token value in wei (usually '0') |
gasLimit | string | Gas limit |
maxFeePerGas | string | EIP-1559 max fee per gas |
maxPriorityFeePerGas | string | EIP-1559 priority fee per gas |
nonce | number | Transaction nonce |
chainId | number | Chain ID for replay protection |
Signing with ethers.js
import { ethers } from 'ethers';
import { RebelfiClient } from '@rebelfi/sdk';
const client = new RebelfiClient({ apiKey: process.env.REBELFI_API_KEY });
// 1. Plan the supply
const operation = await client.operations.supply({
walletAddress: '0xYourWalletAddress',
strategyId: strategy.strategyId,
amount: '1000000', // 1 USDC
tokenAddress: strategy.tokenAddress
});
// 2. Sign and submit each transaction in order
for (const tx of operation.transactions) {
const wallet = new ethers.Wallet(privateKey, provider);
const signedTx = await wallet.signTransaction({
to: tx.to,
data: tx.data,
value: tx.value || '0x0',
gasLimit: tx.gasLimit,
maxFeePerGas: tx.maxFeePerGas,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
nonce: tx.nonce,
chainId: tx.chainId
});
// Submit to RebelFi
await client.transactions.submitSigned({
operationId: operation.operationId,
transactionId: tx.id, // Required for multi-tx operations
signedTransaction: signedTx // Hex-encoded
});
}
Key Differences from Solana
| Solana | EVM |
|---|
| Transactions per supply | 1 | 2 (approve + supply) |
| Transaction format | Base64-encoded | Hex-encoded |
transactionId on submit | Optional | Required |
| Gas token | SOL | ETH / POL / ETH (Base) |
The transactionId parameter is required when submitting EVM transactions because operations contain multiple transactions. It tells RebelFi which transaction you are submitting. For Solana single-transaction operations, it is optional.
Complete EVM Example
import { ethers } from 'ethers';
import { RebelfiClient } from '@rebelfi/sdk';
const client = new RebelfiClient({ apiKey: process.env.REBELFI_API_KEY });
const provider = new ethers.JsonRpcProvider('https://polygon-mainnet.infura.io/v3/YOUR_KEY');
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
async function supplyOnEVM(walletAddress: string, strategyId: number, amount: string, tokenAddress: string) {
// Plan the supply
const operation = await client.operations.supply({
walletAddress,
strategyId,
amount,
tokenAddress
});
console.log(`Operation ${operation.operationId}: ${operation.transactions.length} transactions`);
// Sign and submit each transaction sequentially
for (const tx of operation.transactions) {
console.log(`Signing: ${tx.description}`);
const signedTx = await signer.signTransaction({
to: tx.to,
data: tx.data,
value: tx.value || '0x0',
gasLimit: tx.gasLimit,
maxFeePerGas: tx.maxFeePerGas,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
nonce: tx.nonce,
chainId: tx.chainId
});
await client.transactions.submitSigned({
operationId: operation.operationId,
transactionId: tx.id,
signedTransaction: signedTx
});
console.log(`Submitted: ${tx.description}`);
}
// Wait for confirmation
const start = Date.now();
while (Date.now() - start < 120_000) {
const op = await client.operations.get(operation.operationId);
if (op.status === 'CONFIRMED') {
console.log('Supply confirmed!');
return op;
}
if (op.status === 'FAILED') {
throw new Error(op.transactions[0]?.error || 'Operation failed');
}
await new Promise(r => setTimeout(r, 3000));
}
throw new Error('Timeout waiting for confirmation');
}
Next Steps