SDK

Integrate the Castle ecosystem of products into your app to supercharge your UX

Client-Side Integration

Client-side integrations are powered by our SDK, which lives alongside the program code in the Vault repository. The primary actions a user takes are deposit and withdraw.

The SDK exposes deposit() and withdraw(), both abstract away intermediate instructions like creating ATAs, (un)wrapping sol, refreshing or reconciling the vault.

If you run into transaction size limits (for example: sending transactions as part of governance proposals that increase the bytes to over 1232), then you can split up certain instructions into their own transactions. Each section will provide code for both single SDK call and multi-SDK call cases.

Here's the full list of instructions the vault contract expects:

  1. refresh: (permissionless) Refreshes the vaults accounts

  2. reconcile: (permissionless) Withdraws from the underlying lending markets to allow a user to withdraw

  3. deposit: Sends reserve token (e.g. USDC) assets into the vault in exchange for LP tokens

  4. withdraw: Sends LP tokens into the vault in exchange for reserve token assets

Vault Client

Installation

SDK NPM: https://www.npmjs.com/package/@castlefinance/vault-sdk

yarn add @castlefinance/vault-sdk

Usage

The VaultClient contains static and member functions for loading information about a specific vault, along with all the necessary instructions for depositing and withdrawing.

import { VaultClient, VaultConfig } from '@castlefinance/vault-sdk'

// Pull down the appropriate vault from the API.
const configResponse = await fetch('https://api.castle.finance/configs')
const vaults = (await response.json()) as VaultConfig[]
const vault = vaults.find(
  (v) => v.deploymentEnv == 'mainnet' && v.token_label == 'USDC'
)


// Create the vault client
const vaultClient = await VaultClient.load(
  new anchor.Provider(...),
  vault.vault_id,
  vault.deploymentEnv
)

Here is an example deployed vault along with the Anchor annotations: https://explorer.solana.com/address/9n6ekjHHgkPB9fVuWHzH6iNuxBxN22hEBryZXYFg6cNk/anchor-account?cluster=devnet

Deposit

Here are the following instructions needed to make a deposit:

  1. Create the user's LP token account if it does not exist

  2. Refresh the Vault

  3. Deposit into the Vault

Note: The refresh and deposit instructions need to be sent in the same transaction.

Deposit (single SDK call)

// Get the user's reserve token ATA
const userReserveTokenAccount = await splToken.Token.getAssociatedTokenAddress(
  ASSOCIATED_TOKEN_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
  vaultClient.getReserveTokenMint(),
  reserveTokenAccountOwner, // e.g. wallet.pubkey or DAO's account
  true
);

// Deposit into the vault
const sig = await vaultClient.deposit(wallet, amount, userReserveTokenAccount)

Deposit (multi-SDK call)

Create LP Token Account Transaction:

let createLpAcctIx: TransactionInstruction | undefined = undefined

// Owner of the SPL Token account, e.g. the user or DAO. 
// In this example we assume a wallet logged-in user.
const reserveTokenAccountOwner = wallet.publicKey

const userLpTokenAccount = await Token.getAssociatedTokenAddress(
  ASSOCIATED_TOKEN_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
  vaultClient.getLpTokenMint(),
  reserveTokenAccountOwner,
  true
)

const userLpTokenAccountInfo =
  await vaultClient.program.provider.connection.getAccountInfo(
    userLpTokenAccount
  )

if (userLpTokenAccountInfo == null) {
  createLpAcctIx = Token.createAssociatedTokenAccountInstruction(
    ASSOCIATED_TOKEN_PROGRAM_ID,
    TOKEN_PROGRAM_ID,
    vaultClient.getLpTokenMint(),
    userLpTokenAccount,
    reserveTokenAccountOwner,
    wallet.publicKey // payer
  )
}

// Construct, sign, and send
const tx = new Transaction.add(createLpAcctIx)
const signedTx = await wallet.signTransaction(tx)
const sig = connection.send(signedTx)

Refresh and Deposit into the Vault:

// Get the refresh instruction
const refreshIx = vaultClient.getRefreshIx()

// Get the user reserve ATA
const userReserveTokenAccount = await Token.getAssociatedTokenAddress(
  ASSOCIATED_TOKEN_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
  vaultClient.getReserveTokenMint(),
  reserveTokenAccountOwner,
  true,
)

// Get the deposit instruction
const depositIx = vaultClient.getDepositIx(
  new anchor.BN(amount),
  reserveTokenOwner,
  userLpTokenAccount,
  userReserveTokenAccount,
)

// Construct, sign, and send
let tx = new Transaction();
tx.instructions = [refreshIx, depositIx];
const signedTx = await wallet.signTransaction(tx)
const sig = connection.send(signedTx)

Withdraw

Withdrawing requires the following transactions to be sent:

  1. Transaction #1

    1. Refresh the vault

    2. Reconcile the markets

  2. Transaction #2

    1. Refresh the vault

    2. Withdraw from the vault

Withdraw (single SDK call)

const sig = await vaultClient.withdraw(wallet, amount)

Withdraw (multi-SDK call)

Create the reconcile transactions:

// Get reconcile transactions (note: each tx has a prepended refresh ix)
const txs = await vaultClient.getReconcileTxs(amount)

// Send all of them
txs.forEach(tx => {
  ...
})

Note: There may be up to 3 different reconcile transactions. Simply send off all of them.

Create the ATAs as needed and withdraw from the vault:

// Create the user reserve ATA if it does not exist already
let createReserveAcctIx: TransactionInstruction | undefined = undefined
const userReserveTokenAccount = await Token.getAssociatedTokenAddress(
  ASSOCIATED_TOKEN_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
  vaultClient.getReserveTokenMint(),
  lpTokenAccountOwner,
  true
)
const userReserveTokenAccountInfo =
  await vaultClient.program.provider.connection.getAccountInfo(
    userReserveTokenAccount
  )
if (userReserveTokenAccountInfo == null) {
  createReserveAcctIx = Token.createAssociatedTokenAccountInstruction(
    ASSOCIATED_TOKEN_PROGRAM_ID,
    TOKEN_PROGRAM_ID,
    vaultClient.getReserveTokenMint(),
    userReserveTokenAccount,
    lpTokenAccountOwner,
    wallet.publicKey
  )
}

// Get the refresh instruction
const refreshIx = vaultClient.getRefreshIx()

// Get the LP ATA
const userLpTokenAccount = await Token.getAssociatedTokenAddress(
  ASSOCIATED_TOKEN_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
  vaultClient.getLpTokenMint(),
  lpTokenAccountOwner,
  true
)

// Get withdraw instruction. User selects the LP token to deposit back
// into the vault in exchange for the reserve token
const withdrawIx = vaultClient.program.instruction.withdraw(
  new anchor.BN(amount),
  lpTokenAccountOwner,
  userLpTokenAccount,
  userReserveTokenAccount,
)

// Construct, sign, and send
let tx = new Transaction();
tx.instructions = [refreshIx, withdraw];
const signedTx = await wallet.signTransaction(tx)
const sig = connection.send(signedTx)

Final Notes

These docs are still a work-in-progress. Please reach out to our Twitter or Discord if you have any questions or something is not working.

Last updated