UpshiftDocs

Solana Actions

Query, deposit into, and redeem from Solana vaults with the Upshift SDK, plus low-level Anchor utilities and constants.

Overview

Solana vault interactions use the same pattern as EVM:

  1. Use sdk.getVault() to query vault data
  2. Use sdk.solana.vaultDeposit() to deposit
  3. Use sdk.solana.vaultRedeem() to withdraw

The SDK handles all Solana-specific complexities (PDAs, token accounts, etc.) automatically.

Setup

Basic Initialization

Include Solana RPC endpoint when initializing the SDK:

import AugustSDK from '@augustdigital/sdk';

const sdk = new AugustSDK({
  appName: '<APP_NAME>', // required
  providers: {
    -1: 'https://api.mainnet-beta.solana.com', // Solana Mainnet
  },
});

// Access Solana adapter
const solana = sdk.solana;

Custom RPC Endpoint

const sdk = new AugustSDK({
  appName: '<APP_NAME>', // required
  providers: {
    -1: 'https://solana-mainnet.g.alchemy.com/v2/YOUR_KEY',
  },
});

For Write Operations

To execute transactions, you need to set a wallet provider:

import { useWallet } from '@solana/wallet-adapter-react';

// In your React component
const { publicKey, signTransaction } = useWallet();

// Set wallet for signing transactions
if (publicKey && signTransaction) {
  sdk.solana.setWalletProvider(publicKey, signTransaction);
}

Adapter API

Methods

setWalletProvider()

Set the wallet provider for write operations.

sdk.solana.setWalletProvider(
  publicKey: PublicKey | string,
  signTransaction: (tx: Transaction) => Promise<Transaction>
): void

Parameters:

  • publicKey: User's Solana public key
  • signTransaction: Function to sign transactions (from wallet adapter)

Example:

import { useWallet } from '@solana/wallet-adapter-react';

function MyComponent() {
  const { publicKey, signTransaction } = useWallet();

  useEffect(() => {
    if (publicKey && signTransaction) {
      sdk.solana.setWalletProvider(publicKey, signTransaction);
    }
  }, [publicKey, signTransaction]);
}

Get Vault Data

Query Solana vault information using the same sdk.getVault() method as EVM vaults.

sdk.getVault({
  vault: string;  // Solana program ID
  chainId?: number;  // -1 for mainnet
  options?: {
    solanaWallet?: string;  // For user position data
  };
}): Promise<IVault>

Parameters

ParameterTypeRequiredDescription
vaultstringYesSolana vault program ID
options.solanaWalletstringNoUser's Solana public key for position data

Example

// Get vault details
const vault = await sdk.getVault({
  vault: 'VaultProgramId...',
  chainId: -1, // Solana mainnet
});

console.log('Name:', vault.name);
console.log('Version:', vault.version); // "sol-0"
console.log('Total Assets:', vault.totalAssets.normalized);
console.log('APY:', vault.apy.apy);
console.log('Deposit Token:', vault.depositAssets[0].symbol);

// Get vault with user position
const vaultWithPosition = await sdk.getVault({
  vault: 'VaultProgramId...',
  chainId: -1,
  options: {
    solanaWallet: 'UserPublicKey...',
  },
});

if (vaultWithPosition.position) {
  console.log(
    'Your balance:',
    vaultWithPosition.position.walletBalance.normalized,
  );
}

Get All Solana Vaults

const solanaVaults = await sdk.getVaults({
  chainIds: [-1], // Filter to Solana mainnet only
  solanaWallet: 'UserPublicKey...', // Optional: include positions
});

solanaVaults.forEach((vault) => {
  console.log(`${vault.name}: ${vault.totalAssets.normalized} TVL`);
});

Get User Positions

const positions = await sdk.getVaultPositions({
  solanaWallet: 'UserPublicKey...',
  chainId: -1,
});

positions.forEach((position) => {
  console.log(`Vault: ${position.vault}`);
  console.log(`Balance: ${position.walletBalance.normalized}`);
});

Vault Deposit

Deposit tokens into a Solana vault. The SDK derives the vault PDAs, creates the depositor's share token account when it is missing, and sends the program's deposit_checked instruction through the wallet registered with setWalletProvider.

sdk.solana.vaultDeposit(
  vaultProgramId: PublicKey | string,
  idl: object,
  publicKey: PublicKey | string,
  depositAmount: number | bigint,
  sendTransaction?: undefined,
  vaultAddress?: PublicKey | string,
  options?: ISolanaDepositOptions,
): Promise<string>

Parameters

ParameterTypeRequiredDescription
vaultProgramIdPublicKey | stringYesVault program ID — Solana.constants.programIds['mainnet-beta'].vault on mainnet
idlobjectYesProgram IDL — pass Solana.constants.vaultIdl
publicKeyPublicKey | stringYesDepositor's wallet public key
depositAmountnumber | bigintYesbigint is raw on-chain units; number is a UI amount converted with the deposit mint's decimals
sendTransactiondeprecated callbackNoAccepted and ignored — share-account creation is prepended to the deposit instruction, so there is no second transaction to send. Removed in the next major. Pass undefined rather than dropping the argument, or vaultAddress shifts into this slot
vaultAddressPublicKey | stringNoVault state account. Omit only for legacy single-vault programs, which derive it from the program ID
optionsISolanaDepositOptionsNoSlippage protection, see below

Slippage protection

Deposits send the program's deposit_checked instruction. The SDK quotes the shares the deposit should mint from the vault's current state and passes that quote, lowered by a tolerance, as min_shares_out; the program reverts with SlippageExceeded if the share price moves past it before execution. Control it with the trailing options argument:

// Default: 50 bps (0.5%) below the quote.
await sdk.solana.vaultDeposit(programId, idl, wallet, amount, undefined, vaultAddress);

// Tighter or looser tolerance, in basis points.
await sdk.solana.vaultDeposit(programId, idl, wallet, amount, undefined, vaultAddress, {
  slippageBps: 10,
});

// Your own floor in raw share units (skips the quote and its snapshot read).
// `0n` restores the unguarded behaviour of the plain `deposit` instruction.
await sdk.solana.vaultDeposit(programId, idl, wallet, amount, undefined, vaultAddress, {
  minSharesOut: 995_000n,
});

Returns

Transaction signature as string.

Example

import { Solana } from '@augustdigital/sdk';
import { useWallet } from '@solana/wallet-adapter-react';

const { publicKey, signTransaction } = useWallet();

if (publicKey && signTransaction) {
  sdk.solana.setWalletProvider(publicKey, signTransaction);
}

const signature = await sdk.solana.vaultDeposit(
  Solana.constants.programIds['mainnet-beta'].vault,
  Solana.constants.vaultIdl,
  publicKey,
  100, // 100 tokens, UI amount
  undefined,
  'VaultAddress...',
);

console.log('Deposit successful:', signature);

Behavior

The SDK automatically:

  1. Fetches the vault state and derives the PDAs
  2. Creates the share token account when the depositor has none, in the same transaction
  3. Converts a number amount to raw units with the deposit mint's decimals
  4. Quotes the expected shares and applies the slippage floor
  5. Signs through the registered wallet and confirms at the adapter's commitment level (finalized by default)

Vault Withdraw

Redeem shares from a Solana vault. The program's redeem_checked instruction burns the shares and transfers the payout in the same transaction.

sdk.solana.vaultRedeem(
  vaultProgramId: PublicKey | string,
  idl: object,
  publicKey: PublicKey | string,
  redeemShares: number | bigint,
  sendTransaction?: undefined,
  vaultAddress?: PublicKey | string,
  options?: ISolanaRedeemOptions,
): Promise<string>

Parameters

ParameterTypeRequiredDescription
vaultProgramIdPublicKey | stringYesVault program ID — Solana.constants.programIds['mainnet-beta'].vault on mainnet
idlobjectYesProgram IDL — pass Solana.constants.vaultIdl
publicKeyPublicKey | stringYesShare holder's wallet public key
redeemSharesnumber | bigintYesbigint is raw share units; number is a UI amount
sendTransactiondeprecated callbackNoAccepted and ignored — payout- and fee-recipient-account creation is prepended to the redeem instruction, so there is no second transaction to send. Removed in the next major. Pass undefined rather than dropping the argument, or vaultAddress shifts into this slot
vaultAddressPublicKey | stringNoVault state account. Omit only for legacy single-vault programs
optionsISolanaRedeemOptionsNoSlippage protection, see below

Slippage protection

Redemptions send redeem_checked. The floor is on what the wallet receives, net of the withdrawal fee, quoted from the vault state the SDK already reads; the default tolerance is 50 bps. Pass slippageBps or an explicit minAssetsOut (raw deposit-mint units) in the trailing options argument, exactly as for deposits. A SlippageExceeded revert is reported with a message saying the price moved past your tolerance and to retry or raise it.

Returns

Transaction signature as string.

Example

const positions = await sdk.getVaultPositions({
  solanaWallet: publicKey.toString(),
  chainId: -1,
});

if (positions[0].walletBalance.raw === '0') {
  throw new Error('No balance to redeem');
}

const signature = await sdk.solana.vaultRedeem(
  Solana.constants.programIds['mainnet-beta'].vault,
  Solana.constants.vaultIdl,
  publicKey,
  50, // redeem 50 shares, UI amount
  undefined,
  'VaultAddress...',
);

console.log('Redemption complete:', signature);

Examples

Complete Deposit Flow

import AugustSDK, { Solana } from '@augustdigital/sdk';
import { useWallet } from '@solana/wallet-adapter-react';

function SolanaVaultDeposit() {
  const { publicKey, signTransaction } = useWallet();
  const [loading, setLoading] = useState(false);

  const sdk = new AugustSDK({
    appName: '<APP_NAME>', // required
    providers: {
      -1: 'https://api.mainnet-beta.solana.com',
    },
  });

  const deposit = async (vaultAddress: string, amount: number) => {
    if (!publicKey || !signTransaction) {
      throw new Error('Wallet not connected');
    }

    setLoading(true);
    try {
      // 1. Set wallet provider
      sdk.solana.setWalletProvider(publicKey, signTransaction);

      // 2. Get vault details
      const vault = await sdk.getVault({
        vault: vaultAddress,
        chainId: -1,
      });

      console.log(`Depositing to ${vault.name}`);

      // 3. Execute deposit (SDK handles everything)
      const signature = await sdk.solana.vaultDeposit(
        Solana.constants.programIds['mainnet-beta'].vault,
        Solana.constants.vaultIdl,
        publicKey,
        amount,
        undefined,
        vaultAddress,
      );

      console.log('Deposit successful:', signature);
      return signature;
    } catch (error) {
      console.error('Deposit failed:', error);
      throw error;
    } finally {
      setLoading(false);
    }
  };

  return (
    <button
      onClick={() => deposit('VaultAddress...', 100)}
      disabled={loading || !publicKey}
    >
      {loading ? 'Depositing...' : 'Deposit 100 Tokens'}
    </button>
  );
}

Complete Withdrawal Flow

function SolanaVaultWithdraw() {
  const { publicKey, signTransaction } = useWallet();

  const sdk = new AugustSDK({
    appName: '<APP_NAME>', // required
    providers: {
      -1: 'https://api.mainnet-beta.solana.com',
    },
  });

  const withdraw = async (vaultAddress: string) => {
    if (!publicKey || !signTransaction) {
      throw new Error('Wallet not connected');
    }

    try {
      // 1. Set wallet provider
      sdk.solana.setWalletProvider(publicKey, signTransaction);

      // 2. Check user's position
      const position = await sdk.getVaultPositions({
        solanaWallet: publicKey.toString(),
        chainId: -1,
      });

      if (!position[0] || position[0].walletBalance.raw === '0') {
        throw new Error('No balance to withdraw');
      }

      const shares = BigInt(position[0].walletBalance.raw);
      console.log(`Redeeming ${position[0].walletBalance.normalized} shares`);

      // 3. Execute redemption (all shares, in raw units)
      const signature = await sdk.solana.vaultRedeem(
        Solana.constants.programIds['mainnet-beta'].vault,
        Solana.constants.vaultIdl,
        publicKey,
        shares,
        undefined,
        vaultAddress,
      );

      console.log('Withdrawal successful:', signature);
      return signature;
    } catch (error) {
      console.error('Withdrawal failed:', error);
      throw error;
    }
  };

  return (
    <button onClick={() => withdraw('VaultAddress...')}>
      Withdraw All
    </button>
  );
}

Query Vault Before Depositing

// 1. Get vault info
const vault = await sdk.getVault({
  vault: 'VaultAddress...',
  chainId: -1,
});

// 2. Check if deposits are enabled
if (vault.isDepositPaused) {
  throw new Error('Vault deposits are paused');
}

// 3. Show user the vault details
console.log(`Vault: ${vault.name}`);
console.log(`APY: ${vault.apy.apy}%`);
console.log(`TVL: ${vault.totalAssets.normalized}`);
console.log(`Deposit token: ${vault.depositAssets[0].symbol}`);

// 4. Deposit
await sdk.solana.vaultDeposit(
  Solana.constants.programIds['mainnet-beta'].vault,
  Solana.constants.vaultIdl,
  publicKey,
  100,
  undefined,
  vault.address,
);

Advanced Operations

For advanced use cases, the Solana adapter exposes low-level methods and utilities.

Direct Program Access

Access Solana connection and provider for custom operations:

// Get connection
const connection = sdk.solana.connection;
const balance = await connection.getBalance(publicKey);

// Get Anchor provider
const provider = sdk.solana.provider;

// Get program instance
const program = sdk.solana.getProgram(Solana.constants.vaultIdl);

// Query vault account directly
const vaultState = await program.account.vault.fetch(vaultPda);

Utility Functions

import { Solana } from '@augustdigital/sdk';

deriveShareMintPda()

Derive the share mint PDA for a vault.

const shareMintPda = Solana.utils.deriveShareMintPda('VaultProgramId...');

console.log('Share Mint PDA:', shareMintPda.toString());

getToken()

Get token metadata from mint address.

const token = await Solana.utils.getToken({
  mintAddress: 'MintPublicKey...',
  endpoint: 'https://api.mainnet-beta.solana.com',
  connection: sdk.solana.connection,
});

console.log('Symbol:', token.symbol);
console.log('Decimals:', token.decimals);
console.log('Supply:', token.supply);

getVaultStateReadOnly()

Get vault state without a wallet provider.

const vaultState = await Solana.utils.getVaultStateReadOnly({
  vaultProgramId: 'VaultProgramId...',
  idl: vaultIdl,
  endpoint: 'https://api.mainnet-beta.solana.com',
  connection: sdk.solana.connection,
});

getProvider()

Create an Anchor provider with wallet.

import { PublicKey } from '@solana/web3.js';

const provider = Solana.utils.getProvider({
  network: 'mainnet-beta',
  connection: sdk.solana.connection,
  publicKey: new PublicKey('UserPublicKey...'),
  signTransaction: signTransactionFunction,
});

getReadOnlyProvider()

Create a read-only Anchor provider.

const provider = Solana.utils.getReadOnlyProvider({
  network: 'mainnet-beta',
  connection: sdk.solana.connection,
});

getProgram()

Create an Anchor program instance.

const program = Solana.utils.getProgram({
  network: 'mainnet-beta',
  provider: anchorProvider,
  idl: programIdl,
});

Constants

import { Solana } from '@augustdigital/sdk';

Vault IDL

const vaultIdl = Solana.constants.vaultIdl;

Program IDs

const programId = Solana.constants.programIds['mainnet-beta'].vault;

// Per network; `undefined` where the program is not deployed.
const devnet = Solana.constants.getDeployedProgramIds('devnet');

Fallback Values

const fallbackDecimals = Solana.constants.fallbackDecimals; // 8
const fallbackNetwork = Solana.constants.fallbackNetwork; // 'mainnet-beta'

Error Handling

Connection Errors

try {
  const vaultState = await sdk.solana.getVaultState('VaultId...', vaultIdl);
} catch (error) {
  if (error.message.includes('404')) {
    console.error('Vault program not found');
  } else if (error.message.includes('Network request failed')) {
    console.error('RPC connection failed');
  } else {
    console.error('Unknown error:', error);
  }
}

Transaction Errors

try {
  await sdk.solana.vaultDeposit(
    Solana.constants.programIds['mainnet-beta'].vault,
    Solana.constants.vaultIdl,
    publicKey,
    1_000_000_000n,
    undefined,
    'VaultAddress...',
  );
} catch (error) {
  if (error.message.includes('User rejected')) {
    console.error('Transaction cancelled by user');
  } else if (error.message.includes('insufficient funds')) {
    console.error('Not enough SOL for transaction');
  } else {
    console.error('Transaction failed:', error);
  }
}