Skip to main content
Deposit and withdraw assets from Jupiter Earn vaults using Privy embedded wallets, allowing your users to earn yield without managing their own private keys.

Prerequisites

npm install @solana/web3.js@1 bn.js @jup-ag/lend @privy-io/node

Setup

Shared setup for both deposit and withdraw operations.
import { PrivyClient } from "@privy-io/node";
import {
  Connection,
  PublicKey,
  TransactionMessage,
  VersionedTransaction,
} from "@solana/web3.js";
import BN from "bn.js";
import { getDepositIxs, getWithdrawIxs } from "@jup-ag/lend/earn";

const PRIVY_APP_ID = "put-your-privy-app-id-here";
const PRIVY_APP_SECRET = "put-your-privy-app-secret-here";
const RPC_ENDPOINT = "https://api.mainnet-beta.solana.com";
const ASSET_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDC
const AUTH_KEY_ID = "put-your-auth-key-id-here"; // from Privy dashboard or REST API
const AUTH_KEY_PRIVATE = "put-your-auth-key-private-here"; // P-256 private key
const WALLET_ID = "put-your-wallet-id-here";

const privy = new PrivyClient({
  appId: PRIVY_APP_ID,
  appSecret: PRIVY_APP_SECRET,
});

const connection = new Connection(RPC_ENDPOINT);

const wallet = await privy.wallets().get(WALLET_ID);
if (!wallet) {
  throw new Error(`Wallet ${WALLET_ID} not found`);
}
const signer = new PublicKey(wallet.address);
Authorization keys: Generate a P-256 key pair via the Privy dashboard, generateP256KeyPair() from @privy-io/node, or OpenSSL. The dashboard/API returns a key ID (AUTH_KEY_ID) used to identify the key when configuring wallet ownership. The private key (AUTH_KEY_PRIVATE) is used in the authorization_context when signing transactions. See the Privy authorization keys docs for details.

Deposit

1

Build Deposit Instructions

const DEPOSIT_AMOUNT = new BN(10_000_000); // 10 USDC (6 decimals)

const { ixs: depositIxs } = await getDepositIxs({
  connection,
  signer,
  asset: new PublicKey(ASSET_MINT),
  amount: DEPOSIT_AMOUNT,
});

if (!depositIxs?.length) {
  throw new Error("No deposit instructions returned by Jupiter Lend SDK");
}
2

Sign and Send

const { blockhash, lastValidBlockHeight } =
  await connection.getLatestBlockhash();

const message = new TransactionMessage({
  payerKey: signer,
  recentBlockhash: blockhash,
  instructions: depositIxs,
}).compileToV0Message();

const transaction = new VersionedTransaction(message);
const serializedTx = Buffer.from(transaction.serialize()).toString("base64");

const signResponse = await privy
  .wallets()
  .solana()
  .signTransaction(WALLET_ID, {
    transaction: serializedTx,
    authorization_context: {
      authorization_private_keys: [AUTH_KEY_PRIVATE],
    },
  });

if (!signResponse?.signed_transaction) {
  throw new Error("Privy signing failed: no signed transaction returned");
}

const signedTxBytes = Buffer.from(
  signResponse.signed_transaction as string,
  "base64"
);
const signature = await connection.sendRawTransaction(signedTxBytes, {
  skipPreflight: false,
});
await connection.confirmTransaction(
  { signature, blockhash, lastValidBlockHeight },
  "confirmed"
);
console.log("Deposit successful! Signature:", signature);

Withdraw

1

Build Withdraw Instructions

const WITHDRAW_AMOUNT = new BN(10_000_000); // 10 USDC (6 decimals)

const { ixs: withdrawIxs } = await getWithdrawIxs({
  connection,
  signer,
  asset: new PublicKey(ASSET_MINT),
  amount: WITHDRAW_AMOUNT,
});

if (!withdrawIxs?.length) {
  throw new Error("No withdraw instructions returned by Jupiter Lend SDK");
}
To withdraw the entire balance instead of a fixed amount, use the Read SDK to fetch the position:
import { Client } from "@jup-ag/lend-read";

const readClient = new Client(connection);
const position = await readClient.lending.getUserPosition(
  new PublicKey(ASSET_MINT),
  signer
);

if (position.underlyingAssets.isZero()) {
  throw new Error("No Earn position to withdraw");
}

const WITHDRAW_AMOUNT = position.underlyingAssets;
This requires @jup-ag/lend-read: npm install @jup-ag/lend-read
2

Sign and Send

const { blockhash, lastValidBlockHeight } =
  await connection.getLatestBlockhash();

const message = new TransactionMessage({
  payerKey: signer,
  recentBlockhash: blockhash,
  instructions: withdrawIxs,
}).compileToV0Message();

const transaction = new VersionedTransaction(message);
const serializedTx = Buffer.from(transaction.serialize()).toString("base64");

const signResponse = await privy
  .wallets()
  .solana()
  .signTransaction(WALLET_ID, {
    transaction: serializedTx,
    authorization_context: {
      authorization_private_keys: [AUTH_KEY_PRIVATE],
    },
  });

if (!signResponse?.signed_transaction) {
  throw new Error("Privy signing failed: no signed transaction returned");
}

const signedTxBytes = Buffer.from(
  signResponse.signed_transaction as string,
  "base64"
);
const signature = await connection.sendRawTransaction(signedTxBytes, {
  skipPreflight: false,
});
await connection.confirmTransaction(
  { signature, blockhash, lastValidBlockHeight },
  "confirmed"
);
console.log("Withdrawal successful! Signature:", signature);