Skip to main content

Overview

Manage your funds with the LFG SDK through deposits, withdrawals, and transfers. This guide covers all fund movement operations.

Deposit

Move funds from wallet to trading subaccount

Withdraw

Move funds from subaccount back to wallet

Transfer

Transfer between subaccounts

Deposit to Subaccount

Basic Deposit

Deposit USDC from your wallet to a trading subaccount:
import {
  CompositeClient,
  LocalWallet,
  SubaccountInfo,
} from "@oraichain/lfg-client-js";

async function depositToSubaccount(
  client: CompositeClient,
  wallet: LocalWallet,
  amount: number
) {
  // Create subaccount
  const subaccount = SubaccountInfo.forLocalWallet(wallet, 0);

  // Populate account number cache for better performance
  await client.populateAccountNumberCache(subaccount.address);

  // Deposit (amount in USDC)
  const tx = await client.depositToSubaccount(subaccount, amount.toString());

  console.log("✅ Deposit successful!");
  console.log("Amount: $" + amount);
  console.log("TX Hash:", Buffer.from(tx.hash).toString("hex"));

  return Buffer.from(tx.hash).toString("hex");
}

// Deposit $1000 USDC
await depositToSubaccount(client, wallet, 1000);
subaccount
SubaccountInfo
required
The subaccount to deposit into
amount
string
required
Amount in USDC to deposit (as string)
memo
string
Optional memo for the transaction
You must have USDC in your source wallet before depositing to a subaccount. Check your source balance first.

Deposit with Memo

const tx = await client.depositToSubaccount(
  subaccount,
  "500",
  "Initial deposit for trading bot" // Optional memo
);

Check Source Balance Before Deposit

async function depositWithCheck(
  client: CompositeClient,
  wallet: LocalWallet,
  amount: number
) {
  // Get source wallet balances
  const balances = await client.validatorClient.get.getAccountBalances(
    wallet.address
  );

  // Find USDC balance
  const usdcBalance = balances.find((b) =>
    b.denom.toLowerCase().includes("usdc")
  );

  if (!usdcBalance) {
    throw new Error("No USDC found in source wallet");
  }

  const usdcAmount = parseInt(usdcBalance.amount) / 1_000_000; // 6 decimals

  console.log(`Source USDC Balance: $${usdcAmount.toFixed(2)}`);

  if (usdcAmount < amount) {
    throw new Error(
      `Insufficient USDC. Have: $${usdcAmount}, Need: $${amount}`
    );
  }

  // Proceed with deposit
  const subaccount = SubaccountInfo.forLocalWallet(wallet, 0);
  await client.populateAccountNumberCache(subaccount.address);

  const tx = await client.depositToSubaccount(subaccount, amount.toString());

  console.log("✅ Deposited $" + amount);
  return Buffer.from(tx.hash).toString("hex");
}
Deposits require gas fees. Ensure you have enough USDC for both the deposit amount and gas.

Withdraw from Subaccount

Basic Withdrawal

Withdraw USDC from a subaccount back to your wallet:
async function withdrawFromSubaccount(
  client: CompositeClient,
  wallet: LocalWallet,
  amount: number
) {
  const subaccount = SubaccountInfo.forLocalWallet(wallet, 0);

  // Populate account number cache
  await client.populateAccountNumberCache(subaccount.address);

  // Withdraw to the same wallet address
  const tx = await client.withdrawFromSubaccount(
    subaccount,
    amount.toString(),
    wallet.address // Recipient address
  );

  console.log("✅ Withdrawal successful!");
  console.log("Amount: $" + amount);
  console.log("TX Hash:", Buffer.from(tx.hash).toString("hex"));

  return Buffer.from(tx.hash).toString("hex");
}

// Withdraw $500 USDC
await withdrawFromSubaccount(client, wallet, 500);
subaccount
SubaccountInfo
required
The subaccount to withdraw from
amount
string
required
Amount in USDC to withdraw (as string)
recipient
string
required
Address to receive the withdrawal
memo
string
Optional memo for the transaction

Withdraw to Different Address

// Withdraw to a different address
const recipientAddress = "lfg1anotheraddress...";

const tx = await client.withdrawFromSubaccount(
  subaccount,
  "1000",
  recipientAddress,
  "Withdrawal to cold storage" // Optional memo
);

Safe Withdrawal with Balance Check

async function safeWithdraw(
  client: CompositeClient,
  wallet: LocalWallet,
  amount: number
) {
  // Check subaccount balance
  const account = await client.indexerClient.account.getParentSubaccount(
    wallet.address,
    0
  );

  const freeCollateral = parseFloat(account.subaccount.freeCollateral);

  console.log(`Free Collateral: $${freeCollateral.toFixed(2)}`);

  if (freeCollateral < amount) {
    throw new Error(
      `Insufficient free collateral. Available: $${freeCollateral.toFixed(
        2
      )}, Requested: $${amount}`
    );
  }

  // Check for open positions
  let hasPositions = false;
  for (const subaccount of account.subaccount.childSubaccounts) {
    const positions = subaccount.openPerpetualPositions || {};
    if (Object.keys(positions).length > 0) {
      hasPositions = true;
      console.warn("⚠️  Warning: You have open positions!");
      break;
    }
  }

  // Proceed with withdrawal
  const subaccountInfo = SubaccountInfo.forLocalWallet(wallet, 0);
  await client.populateAccountNumberCache(subaccountInfo.address);

  const tx = await client.withdrawFromSubaccount(
    subaccountInfo,
    amount.toString(),
    wallet.address
  );

  console.log("✅ Withdrawn $" + amount);
  return Buffer.from(tx.hash).toString("hex");
}
Withdrawing too much collateral while holding positions can lead to liquidation. Always maintain sufficient margin.

Transfer Between Subaccounts

Basic Transfer

Transfer USDC from one subaccount to another:
async function transferBetweenSubaccounts(
  client: CompositeClient,
  wallet: LocalWallet,
  fromSubaccountNumber: number,
  toSubaccountNumber: number,
  amount: number
) {
  // Source subaccount
  const sourceSubaccount = SubaccountInfo.forLocalWallet(
    wallet,
    fromSubaccountNumber
  );

  // Populate account number cache
  await client.populateAccountNumberCache(sourceSubaccount.address);

  // Transfer
  const tx = await client.transferToSubaccount(
    sourceSubaccount,
    wallet.address, // Recipient wallet address
    toSubaccountNumber, // Recipient subaccount number
    amount.toString()
  );

  console.log("✅ Transfer successful!");
  console.log(`From: Subaccount ${fromSubaccountNumber}`);
  console.log(`To: Subaccount ${toSubaccountNumber}`);
  console.log(`Amount: $${amount}`);
  console.log("TX Hash:", Buffer.from(tx.hash).toString("hex"));

  return Buffer.from(tx.hash).toString("hex");
}

// Transfer $200 from subaccount 0 to subaccount 1
await transferBetweenSubaccounts(client, wallet, 0, 1, 200);
subaccount
SubaccountInfo
required
The source subaccount (sending funds)
recipientAddress
string
required
The recipient wallet address (can be same wallet)
recipientSubaccountNumber
number
required
The destination subaccount number
amount
string
required
Amount in USDC to transfer (as string)
memo
string
Optional memo for the transaction

Transfer Between Different Wallets

Transfer from your subaccount to another user’s subaccount:
const recipientWalletAddress = "lfg1recipientaddress...";
const recipientSubaccountNumber = 0;

const tx = await client.transferToSubaccount(
  sourceSubaccount,
  recipientWalletAddress,
  recipientSubaccountNumber,
  "100",
  "Payment for service"
);

Send Tokens from Source

Send tokens directly from your source wallet (outside subaccounts):
async function sendTokenFromSource(
  client: CompositeClient,
  wallet: LocalWallet,
  recipientAddress: string,
  amount: number,
  denom?: string
) {
  const subaccount = SubaccountInfo.forLocalWallet(wallet, 0);

  // Populate account number cache
  await client.validatorClient.populateAccountNumberCache(subaccount.address);

  // Send tokens (defaults to USDC if denom not specified)
  const tx = await client.validatorClient.post.sendToken(
    subaccount,
    recipientAddress,
    denom || "ibc/...", // USDC IBC denom
    amount.toString()
  );

  console.log("✅ Tokens sent!");
  console.log("TX Hash:", Buffer.from(tx.hash).toString("hex"));

  return Buffer.from(tx.hash).toString("hex");
}
This sends tokens from your source wallet, not from subaccounts. Use transferToSubaccount for subaccount transfers.

Transaction Memos

Adding Memos to Transactions

Memos help you track and organize transactions:
// Deposit with memo
await client.depositToSubaccount(
  subaccount,
  "1000",
  "Initial capital for Q1 2024"
);

// Withdraw with memo
await client.withdrawFromSubaccount(
  subaccount,
  "500",
  wallet.address,
  "Profit taking - January 2024"
);

// Transfer with memo
await client.transferToSubaccount(
  sourceSubaccount,
  recipientAddress,
  recipientSubaccountNumber,
  "250",
  "Strategy rebalancing"
);

Memo Best Practices

class TransactionMemoHelper {
  static deposit(purpose: string): string {
    return `DEPOSIT: ${purpose} - ${new Date().toISOString()}`;
  }

  static withdraw(reason: string): string {
    return `WITHDRAW: ${reason} - ${new Date().toISOString()}`;
  }

  static transfer(from: number, to: number, reason: string): string {
    return `TRANSFER: ${from}->${to} - ${reason}`;
  }
}

// Usage
await client.depositToSubaccount(
  subaccount,
  "1000",
  TransactionMemoHelper.deposit("Trading bot initial funding")
);

Handling Transaction Errors

Retry Logic

async function depositWithRetry(
  client: CompositeClient,
  wallet: LocalWallet,
  amount: number,
  maxRetries: number = 3
): Promise<string> {
  const subaccount = SubaccountInfo.forLocalWallet(wallet, 0);
  await client.populateAccountNumberCache(subaccount.address);

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const tx = await client.depositToSubaccount(
        subaccount,
        amount.toString()
      );

      console.log(`✅ Deposit successful on attempt ${attempt}`);
      return Buffer.from(tx.hash).toString("hex");
    } catch (error) {
      console.error(`Attempt ${attempt} failed:`, error);

      if (attempt === maxRetries) {
        throw new Error(`Deposit failed after ${maxRetries} attempts`);
      }

      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Retrying in ${delay}ms...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }

  throw new Error("Unexpected error in depositWithRetry");
}

Error Handling

async function safeDeposit(
  client: CompositeClient,
  wallet: LocalWallet,
  amount: number
) {
  try {
    const subaccount = SubaccountInfo.forLocalWallet(wallet, 0);
    await client.populateAccountNumberCache(subaccount.address);

    const tx = await client.depositToSubaccount(subaccount, amount.toString());

    return {
      success: true,
      txHash: Buffer.from(tx.hash).toString("hex"),
      amount,
    };
  } catch (error: any) {
    console.error("Deposit failed:", error);

    // Handle specific errors
    if (error.message?.includes("insufficient funds")) {
      return {
        success: false,
        error: "Insufficient USDC in source wallet",
        code: "INSUFFICIENT_FUNDS",
      };
    } else if (error.message?.includes("gas")) {
      return {
        success: false,
        error: "Insufficient gas",
        code: "INSUFFICIENT_GAS",
      };
    } else {
      return {
        success: false,
        error: error.message || "Unknown error",
        code: "UNKNOWN_ERROR",
      };
    }
  }
}

Fund Management Strategies

Automated Rebalancing

class SubaccountRebalancer {
  private client: CompositeClient;
  private wallet: LocalWallet;
  private targetAllocations: Map<number, number>;

  constructor(
    client: CompositeClient,
    wallet: LocalWallet,
    allocations: { subaccount: number; targetPercent: number }[]
  ) {
    this.client = client;
    this.wallet = wallet;
    this.targetAllocations = new Map(
      allocations.map((a) => [a.subaccount, a.targetPercent / 100])
    );
  }

  async rebalance() {
    // Get total equity across all subaccounts
    const account = await this.client.indexerClient.account.getParentSubaccount(
      this.wallet.address,
      0
    );

    const totalEquity = parseFloat(account.subaccount.equity);

    console.log(`Total Equity: $${totalEquity.toFixed(2)}`);

    // Calculate required transfers
    const transfers: { from: number; to: number; amount: number }[] = [];

    for (const subaccount of account.subaccount.childSubaccounts) {
      const subaccountNumber = subaccount.subaccountNumber;
      const currentEquity = parseFloat(subaccount.equity);
      const targetAllocation =
        this.targetAllocations.get(subaccountNumber) || 0;
      const targetEquity = totalEquity * targetAllocation;
      const difference = targetEquity - currentEquity;

      console.log(`Subaccount ${subaccountNumber}:`);
      console.log(`  Current: $${currentEquity.toFixed(2)}`);
      console.log(`  Target: $${targetEquity.toFixed(2)}`);
      console.log(`  Difference: $${difference.toFixed(2)}`);

      // Only transfer if difference is significant
      if (Math.abs(difference) > 10) {
        // TODO: Implement transfer logic
      }
    }

    console.log("Rebalancing complete");
  }
}

// Usage
const rebalancer = new SubaccountRebalancer(client, wallet, [
  { subaccount: 0, targetPercent: 50 }, // 50% in subaccount 0
  { subaccount: 1, targetPercent: 30 }, // 30% in subaccount 1
  { subaccount: 2, targetPercent: 20 }, // 20% in subaccount 2
]);

await rebalancer.rebalance();

Emergency Withdrawal

async function emergencyWithdrawAll(
  client: CompositeClient,
  wallet: LocalWallet
) {
  console.log("⚠️  Initiating emergency withdrawal...");

  const account = await this.client.indexerClient.account.getParentSubaccount(
    wallet.address,
    0
  );

  const withdrawals = [];

  for (const subaccount of account.subaccount.childSubaccounts) {
    const freeCollateral = parseFloat(subaccount.freeCollateral);

    if (freeCollateral > 1) {
      // Min $1 to withdraw
      const subaccountInfo = SubaccountInfo.forLocalWallet(
        wallet,
        subaccount.subaccountNumber
      );

      await client.populateAccountNumberCache(subaccountInfo.address);

      const tx = await client.withdrawFromSubaccount(
        subaccountInfo,
        freeCollateral.toFixed(2),
        wallet.address,
        "Emergency withdrawal"
      );

      withdrawals.push({
        subaccount: subaccount.subaccountNumber,
        amount: freeCollateral,
        txHash: Buffer.from(tx.hash).toString("hex"),
      });

      console.log(
        `✅ Withdrawn $${freeCollateral.toFixed(2)} from subaccount ${
          subaccount.subaccountNumber
        }`
      );
    }
  }

  console.log(`Total withdrawn from ${withdrawals.length} subaccounts`);
  return withdrawals;
}

Best Practices

Verify sufficient funds before initiating transfers:
const account = await client.indexerClient.account.getParentSubaccount(
  wallet.address,
  0
);
const freeCollateral = parseFloat(account.subaccount.freeCollateral);

if (freeCollateral < amount) {
  throw new Error("Insufficient funds");
}
Add descriptive memos to track transaction purposes:
const memo = `Bot #${botId} - ${strategy} - ${timestamp}`;
Never withdraw all free collateral while holding positions:
// Leave 20% buffer for price movements
const maxWithdraw = freeCollateral * 0.8;
Implement retry logic for network failures:
for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await client.depositToSubaccount(/* ... */);
  } catch (error) {
    if (attempt === 2) throw error;
    await sleep(1000 * Math.pow(2, attempt));
  }
}

Next Steps