Skip to main content

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:
  1. Approve transaction — Authorizes the protocol to spend your USDC
  2. Supply transaction — Deposits USDC into the yield strategy

Unsigned Transaction Format

EVM unsigned transactions are returned as JSON objects with these fields:
FieldTypeDescription
tostringContract/recipient address
datastringEncoded call data (hex string)
valuestringNative token value in wei (usually '0')
gasLimitstringGas limit
maxFeePerGasstringEIP-1559 max fee per gas
maxPriorityFeePerGasstringEIP-1559 priority fee per gas
noncenumberTransaction nonce
chainIdnumberChain 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

SolanaEVM
Transactions per supply12 (approve + supply)
Transaction formatBase64-encodedHex-encoded
transactionId on submitOptionalRequired
Gas tokenSOLETH / 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