Skip to main content
This tutorial walks through a complete integration: supplying funds, tracking yield, and unwinding positions. We’ll build a simple yield service that your application can use.

Setup

import { RebelfiClient, RebelfiError, ErrorCode } from '@rebelfi/sdk';

const client = new RebelfiClient({
  apiKey: process.env.REBELFI_API_KEY
});

Step 0: Register the Wallet

Register the user’s wallet before any operations:
const wallet = await client.wallets.register({
  walletAddress: userWallet,
  blockchain: 'solana',
  userId: 'your-user-id' // optional — enables querying by userId
});

const walletId = wallet.walletId;
You can now use walletId instead of walletAddress in all subsequent calls.

Step 1: Discover Venues and Strategies

First, find available yield opportunities for the user:
async function getAvailableStrategies(blockchain: 'solana', token: string) {
  const { venues, strategyCount } = await client.venues.list({
    blockchain,
    token
  });

  // Flatten strategies with venue info
  const strategies = venues.flatMap(venue =>
    venue.strategies.map(strategy => ({
      venueId: venue.id,
      venueName: venue.name,
      venueIcon: venue.iconUrl,
      ...strategy
    }))
  );

  // Sort by APY descending
  strategies.sort((a, b) => b.apy - a.apy);

  return strategies;
}

// Usage
const strategies = await getAvailableStrategies('solana', 'USDC');
console.log('Best APY:', (strategies[0].apy * 100).toFixed(2) + '%');

Step 2: Check Existing Allocations

Before supplying, check what the user already has:
async function getUserAllocations(walletId: number) {
  const { allocations, totalValue, totalYieldEarned } = await client.allocations.list({
    walletId
  });

  return {
    allocations,
    summary: {
      totalValue,
      totalYieldEarned,
      positionCount: allocations.length
    }
  };
}

// Usage
const { allocations, summary } = await getUserAllocations(walletId);

if (allocations.length > 0) {
  console.log(`User has ${summary.positionCount} positions`);
  console.log(`Total value: ${formatUSDC(summary.totalValue)}`);
  console.log(`Total yield: ${formatUSDC(summary.totalYieldEarned)}`);
}

Step 3: Plan a Supply Operation

Create an operation and get the unsigned transaction:
interface SupplyParams {
  walletId: number;
  strategyId: number;
  amount: string;
  tokenAddress: string;
}

async function planSupply(params: SupplyParams) {
  const operation = await client.operations.supply(params);

  // Validate we got a transaction
  if (operation.transactions.length === 0) {
    throw new Error('No transactions returned');
  }

  const tx = operation.transactions[0];

  return {
    operationId: operation.operationId,
    unsignedTransaction: tx.unsignedTransaction,
    description: tx.description,
    expiresAt: new Date(operation.expiresAt)
  };
}

// Usage
const supply = await planSupply({
  walletId,
  strategyId: strategies[0].strategyId,
  amount: '1000000000', // 1000 USDC
  tokenAddress: strategies[0].tokenAddress
});

console.log('Transaction ready:', supply.description);
console.log('Expires:', supply.expiresAt.toISOString());

Step 4: User Signs the Transaction

Present the transaction to the user for signing. This example uses @solana/web3.js:
import { Transaction, Connection, sendAndConfirmRawTransaction } from '@solana/web3.js';

async function signAndBroadcast(
  unsignedTxBase64: string,
  wallet: { signTransaction: (tx: Transaction) => Promise<Transaction> }
) {
  // Decode the unsigned transaction
  const txBuffer = Buffer.from(unsignedTxBase64, 'base64');
  const transaction = Transaction.from(txBuffer);

  // User signs via their wallet
  const signedTx = await wallet.signTransaction(transaction);

  // Broadcast to Solana
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  const signature = await sendAndConfirmRawTransaction(
    connection,
    signedTx.serialize()
  );

  return signature;
}

// Usage with a wallet adapter
const txHash = await signAndBroadcast(
  supply.unsignedTransaction,
  walletAdapter
);
console.log('Broadcast:', txHash);

Step 5: Submit Transaction Hash

After broadcasting, notify RebelFi:
async function submitTransaction(operationId: number, txHash: string) {
  const result = await client.transactions.submitHash({
    operationId,
    txHash
  });

  return result;
}

// Usage
await submitTransaction(supply.operationId, txHash);
console.log('Transaction submitted, waiting for confirmation...');
Alternatively, use submitSigned to let RebelFi broadcast the transaction for you.

Step 6: Poll for Confirmation

Wait for on-chain confirmation:
async function waitForConfirmation(
  operationId: number,
  options: { timeoutMs?: number; pollIntervalMs?: number } = {}
) {
  const { timeoutMs = 60000, pollIntervalMs = 2000 } = options;
  const startTime = Date.now();

  while (Date.now() - startTime < timeoutMs) {
    const operation = await client.operations.get(operationId);

    if (operation.status === 'confirmed') {
      return { success: true, operation };
    }

    // Note: cancelled operations also appear as 'failed'
    if (operation.status === 'failed') {
      const tx = operation.transactions[0];
      return {
        success: false,
        error: tx?.error || 'Operation failed or was cancelled',
        failureCode: tx?.failureCode,
        operation
      };
    }

    // Wait before polling again
    await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
  }

  throw new Error('Timeout waiting for confirmation');
}

// Usage
const result = await waitForConfirmation(supply.operationId);

if (result.success) {
  console.log('Supply confirmed!');
} else {
  console.error('Supply failed:', result.error);
}

Step 7: View Updated Allocation

After confirmation, the allocation is updated:
// Get the specific allocation
const allocation = await client.allocations.get(
  strategies[0].venueId,
  userWallet
);

console.log('Current position:');
console.log(`  Principal: ${formatUSDC(allocation.principal)}`);
console.log(`  Current value: ${formatUSDC(allocation.currentValue)}`);
console.log(`  Yield earned: ${formatUSDC(allocation.yieldEarned)}`);
console.log(`  APY: ${(allocation.apy * 100).toFixed(2)}%`);

Step 8: Track Earnings Over Time

Get historical earnings data:
const earnings = await client.allocations.earnings({
  walletId,
  blockchain: 'solana',
  token: 'USDC',
  days: 30,
  includeBreakdown: true
});

console.log(`Period: ${earnings.periodStart} to ${earnings.periodEnd}`);
console.log(`Total yield: ${formatUSDC(earnings.totalYieldEarned)}`);

// Daily breakdown
for (const day of earnings.history) {
  console.log(`${day.date}: +${formatUSDC(day.yieldEarned)}`);
}

// Per-venue breakdown
if (earnings.byVenue) {
  for (const venue of earnings.byVenue) {
    console.log(`${venue.venueName}: ${formatUSDC(venue.totalYieldEarned)}`);
  }
}

Step 9: Unwind Position

When the user wants to withdraw:
async function unwind(
  walletId: number,
  strategyId: number,
  amount: string
) {
  // Plan the unwind
  const operation = await client.operations.unwind({
    walletId,
    strategyId,
    amount
  });

  return {
    operationId: operation.operationId,
    unsignedTransaction: operation.transactions[0].unsignedTransaction,
    expiresAt: operation.expiresAt
  };
}

// Unwind full position
const allocation = await client.allocations.get(strategy.strategyId, walletId);
const unwindOp = await unwind(
  walletId,
  allocation.strategyId,
  allocation.currentValue // Full withdrawal
);

// Sign, broadcast, and wait for confirmation (same as supply)
const txHash = await signAndBroadcast(unwindOp.unsignedTransaction, wallet);
await submitTransaction(unwindOp.operationId, txHash);
await waitForConfirmation(unwindOp.operationId);

console.log('Position unwound successfully');

Complete Service Example

Here’s a complete yield service wrapping all operations:
import { RebelfiClient, RebelfiError, ErrorCode } from '@rebelfi/sdk';

export class YieldService {
  private client: RebelfiClient;

  constructor(apiKey: string) {
    this.client = new RebelfiClient({ apiKey });
  }

  async getStrategies(token: string = 'USDC') {
    const { venues } = await this.client.venues.list({
      blockchain: 'solana',
      token
    });
    return venues.flatMap(v =>
      v.strategies.map(s => ({ ...s, venueName: v.name, venueId: v.id }))
    );
  }

  async getAllocations(walletAddress: string) {
    return this.client.allocations.list({ walletAddress });
  }

  async getEarnings(walletAddress: string, days: number = 30) {
    return this.client.allocations.earnings({
      walletAddress,
      blockchain: 'solana',
      token: 'USDC',
      days,
      includeBreakdown: true
    });
  }

  async planSupply(
    walletAddress: string,
    strategyId: number,
    amount: string,
    tokenAddress: string
  ) {
    return this.client.operations.supply({
      walletAddress,
      strategyId,
      amount,
      tokenAddress
    });
  }

  async planUnwind(
    walletAddress: string,
    strategyId: number,
    amount: string
  ) {
    return this.client.operations.unwind({
      walletAddress,
      strategyId,
      amount
    });
  }

  async submitTx(operationId: number, txHash: string) {
    return this.client.transactions.submitHash({ operationId, txHash });
  }

  async waitForConfirmation(operationId: number, timeoutMs = 60000) {
    const start = Date.now();
    while (Date.now() - start < timeoutMs) {
      const op = await this.client.operations.get(operationId);
      if (op.status === 'confirmed') return { success: true, op };
      // Note: cancelled operations also appear as 'failed'
      if (op.status === 'failed') {
        return { success: false, error: op.transactions[0]?.error || 'Operation failed or was cancelled', op };
      }
      await new Promise(r => setTimeout(r, 2000));
    }
    throw new Error('Timeout');
  }

  async cancelOperation(operationId: number) {
    return this.client.operations.cancel(operationId);
  }

  async recoverTransaction(operationId: number, txHash: string) {
    return this.client.transactions.recover(operationId, { txHash });
  }
}

Next Steps