Skip to main content

Deposit with Referral

A UserMetadata account stores the referrer information. The referrer must be included when the account is created. After it is set, the referrer cannot be changed.
1

Import Dependencies

Import the required packages for Solana RPC communication, Kamino SDK operations, and Kit transaction building.
import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  address,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
  getSignatureFromTransaction,
  some,
} from '@solana/kit';
import {
  KaminoMarket,
  KaminoAction,
  VanillaObligation,
  PROGRAM_ID
} from '@kamino-finance/klend-sdk';
import { parseKeypairFile } from '@kamino-finance/klend-sdk/dist/utils/signer.js';
import BN from 'bn.js';
2

Setup Market and Check Existing Referrer

Load the keypair, initialize the market, and check whether a referrer is already associated with the account.
const KEYPAIR_FILE = '/path/to/your/keypair.json';
const signer = await parseKeypairFile(KEYPAIR_FILE);

const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');

const marketPubkey = address('7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF'); // Main Market
const market = await KaminoMarket.load(rpc, marketPubkey, 100);

const usdcMint = address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); // USDC
const depositAmount = new BN(1_000_000); // 1 USDC
Check if a referrer is already associated with the account and set the referrer address.
// Referrer address - UPDATE THIS with the referrer's wallet address
const referrer = address('EZC9wzVCvihCsCHEMGADYdsRhcpdRYWzSCfqY');

// Check if user already has UserMetadata with a different referrer
const [, currentUserMetadata] = await market!.getUserMetadata(signer.address);
const actualReferrer =
  currentUserMetadata && currentUserMetadata.referrer.toString() !== referrer.toString()
    ? currentUserMetadata.referrer
    : referrer;
Once a UserMetadata account has a referrer, it cannot be changed. If a referrer is already set, any new referrer value is ignored, and the original referrer continues to receive commission from the user’s borrowing activity.
3

Build Deposit Instructions with Referrer

Generate deposit instructions that include the referrer information.
let depositAction = await KaminoAction.buildDepositTxns(
  market!,
  depositAmount,
  usdcMint,
  signer,
  new VanillaObligation(PROGRAM_ID),
  true,
  undefined,
  1_000_000,
  true,
  false,
  { skipInitialization: false, skipLutCreation: false },
  some(actualReferrer)
);

const hasSetup = (depositAction.setupIxs || []).length > 0;

// Combine all instructions
let instructions = [
  ...(depositAction.setupIxs || []),
  ...(depositAction.lendingIxs || []),
  ...(depositAction.cleanupIxs || []),
];
The referrer earns commission from the user’s future borrowing activity. This creates a permanent, non-revocable link between the user and the referrer.
4

Send Initial Transaction

Fetch the latest blockhash and build the transaction message.
const signatures: string[] = [];

try {
  const { value: depositBlockhash } = await rpc
    .getLatestBlockhash({ commitment: 'finalized' })
    .send();

  const depositTx = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayerSigner(signer, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(depositBlockhash, tx),
    (tx) => appendTransactionMessageInstructions(instructions, tx)
  );
Sign and send the transaction.
  const depositSigned = await signTransactionMessageWithSigners(depositTx);
  const depositSignature = getSignatureFromTransaction(depositSigned);
  signatures.push(depositSignature);

  await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(depositSigned, {
    commitment: 'confirmed',
    skipPreflight: true,
  });

  console.log(`Deposit successful! Signature: ${depositSignature}`);
5

Handle Retry Logic

Detect and handle setup-related errors that may occur.
} catch (error: any) {
  const errorMsg = error?.message || error?.toString() || '';
  const is0x17a3 =
    errorMsg.includes('0x17a3') ||
    errorMsg.includes('6051') ||
    errorMsg.includes('IncorrectInstructionInPosition');

  if (hasSetup && is0x17a3) {
When setup instructions are included, the first transaction may fail with error 0x17a3. The retry logic handles this by waiting for blockchain state to settle, reloading the market, and rebuilding instructions.
Wait for blockchain state to settle and reload the market.
    await new Promise((resolve) => setTimeout(resolve, 2000));

    const reloadedMarket = await KaminoMarket.load(rpc, marketPubkey, 400);
Rebuild the deposit instructions with the reloaded market state.
    depositAction = await KaminoAction.buildDepositTxns(
      reloadedMarket!,
      depositAmount,
      usdcMint,
      signer,
      new VanillaObligation(PROGRAM_ID),
      true,
      undefined,
      1_000_000,
      true,
      false,
      { skipInitialization: false, skipLutCreation: false },
      some(actualReferrer)
    );

    instructions = [
      ...(depositAction.setupIxs || []),
      ...(depositAction.lendingIxs || []),
      ...(depositAction.cleanupIxs || []),
    ];
Fetch a fresh blockhash and build the retry transaction.
    const { value: retryBlockhash } = await rpc
      .getLatestBlockhash({ commitment: 'finalized' })
      .send();

    const retryTx = pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(signer, tx),
      (tx) => setTransactionMessageLifetimeUsingBlockhash(retryBlockhash, tx),
      (tx) => appendTransactionMessageInstructions(instructions, tx)
    );
Sign and send the retry transaction.
    const retrySigned = await signTransactionMessageWithSigners(retryTx);
    const retrySignature = getSignatureFromTransaction(retrySigned);
    signatures.push(retrySignature);

    await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(retrySigned, {
      commitment: 'confirmed',
      skipPreflight: true,
    });

    console.log(
      `Deposit successful! Signature: ${retrySignature}\nSetup transaction: https://solscan.io/tx/${signatures[0]}`
    );
  } else {
    console.error('Deposit failed:', error);
  }
}
The deposit with referral is complete. The referrer is now permanently linked to the account and will earn commission from the user’s future borrowing activity.