Skip to main content

Client Integration

In this guide, we will go through the steps to set up a local development environment for building onchain integrations with Lockup using TypeScript and Anchor's IDL.

caution

The following code examples are for demonstration purposes only and are not production-ready. They are intended to provide guidance on how to interact with the Lockup program.

Dependencies

Download Types

First, download the TypeScript types for the Lockup program from the GitHub release:

curl -L -o sablier_lockup.ts \
https://github.com/sablier-labs/solsab/releases/download/v1.0.0/sablier_lockup.ts

Install NPM Packages

Install the required Solana and Anchor packages:

bun add @coral-xyz/anchor@0.31.1 @solana/web3.js@1.98.2 bn.js@5.2.2

Setup

Import the necessary modules and types:

import * as anchor from "@coral-xyz/anchor";
import { Connection, PublicKey, Keypair } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { SablierLockup } from "./sablier_lockup"; // Path to your downloaded types
import BN from "bn.js";

Set up the Anchor provider and program instance:

// Create connection to Solana cluster
const connection = new Connection("https://api.devnet.solana.com", "confirmed");

// Set up wallet (example using a keypair)
const wallet = new anchor.Wallet(yourKeypair);

// Create provider
const provider = new anchor.AnchorProvider(connection, wallet, {
commitment: "confirmed",
});

// Set the provider
anchor.setProvider(provider);

// Initialize the program (PROGRAM_ID is included in the types)
const program = new anchor.Program<SablierLockup>(
idl as SablierLockup, // Your IDL object
provider,
);

Creating a Stream

Using createWithTimestampsLL

Create a linear lockup stream with specific timestamps:

async function createStream() {
// Using the current timestamp as the unique identifier
const salt = new BN(Date.now());
// 1000 tokens (assuming 6 decimals)
const depositAmount = new BN(1000 * 10 ** 6);
// Current timestamp
const startTime = new BN(Math.floor(Date.now()));
// No cliff
const cliffTime = new BN(0);
// 1 hour from start time
const endTime = new BN(startTime.add(new BN(3600)));
// No unlock amounts
const startUnlockAmount = new BN(0);
const cliffUnlockAmount = new BN(0);
const isCancelable = true;

// Account addresses
const creator = provider.wallet.publicKey;
const recipient = new PublicKey("YOUR_RECIPIENT_HERE");
const sender = creator;
const depositTokenMint = new PublicKey("YOUR_TOKEN_MINT_HERE");

// Set a higher compute unit limit so that the transaction doesn't fail
const increaseCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 });

const txSignature = await program.methods
.createWithTimestampsLl(
salt,
depositAmount,
startTime,
cliffTime,
endTime,
startUnlockAmount,
cliffUnlockAmount,
isCancelable,
)
.accounts({
creator,
recipient,
sender,
depositTokenMint,
depositTokenProgram: TOKEN_PROGRAM_ID,
nftTokenProgram: TOKEN_PROGRAM_ID,
})
.preInstructions([increaseCULimitIx])
.rpc();

console.log("Stream created successfully!");
console.log("Transaction signature:", txSignature);

return txSignature;
}

Withdrawing from a Stream

Using withdraw

Withdraw tokens from an existing stream:

async function withdrawFromStream(streamNftMint: PublicKey, amount: BN) {
// Required account addresses
const signer = provider.wallet.publicKey;
const streamRecipient = signer; // The recipient of the stream
const withdrawalRecipient = signer; // Where to send withdrawn tokens
const depositedTokenMint = new PublicKey("YOUR_TOKEN_MINT_HERE");

// Chainlink program and feed addresses
const chainlinkProgram = new PublicKey("HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny");
const chainlinkSolUsdFeed = new PublicKey("99B2bTijsU6f1GCT73HmdR7HCFFjGMBcPZY6jZ96ynrR");

const txSignature = await program.methods
.withdraw(amount)
.accounts({
chainlinkProgram,
chainlinkSolUsdFeed,
depositedTokenMint,
depositedTokenProgram,
nftTokenProgram: token.TOKEN_PROGRAM_ID,
signer: signer.publicKey,
streamNftMint,
streamRecipient,
withdrawalRecipient,
})
.rpc();

console.log("Withdrawal successful!");
console.log("Transaction signature:", txSignature);

return txSignature;
}

Using withdrawMax

Withdraw the maximum available amount from a stream:

async function withdrawMaxFromStream(streamNftMint: PublicKey) {
// Same account setup as withdraw
const signer = provider.wallet.publicKey;
const streamRecipient = signer;
const withdrawalRecipient = signer;
const depositedTokenMint = new PublicKey("YOUR_TOKEN_MINT_HERE");

// Chainlink program and feed addresses
const chainlinkProgram = new PublicKey("HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny");
const chainlinkSolUsdFeed = new PublicKey("99B2bTijsU6f1GCT73HmdR7HCFFjGMBcPZY6jZ96ynrR");

const txSignature = await program.methods
.withdrawMax()
.accounts({
signer,
streamRecipient,
withdrawalRecipient,
depositedTokenMint,
streamNftMint,
depositedTokenProgram: TOKEN_PROGRAM_ID,
nftTokenProgram: TOKEN_PROGRAM_ID,
chainlinkProgram,
chainlinkSolUsdFeed,
})
.rpc();

console.log("Max withdrawal successful!");
console.log("Transaction signature:", txSignature);

return txSignature;
}

Cancel a Stream

async function cancelStream(streamNftMint: PublicKey) {
const sender = provider.wallet.publicKey; // The sender of the stream

const txSignature = await this.lockup.methods
.cancel()
.accounts({
depositedTokenMint,
depositedTokenProgram,
sender,
streamNftMint,
})
.rpc();

console.log("Stream canceled successfully!");
console.log("Transaction signature:", txSignature);
}

For more examples and advanced usage, refer to the SolSab repository test files and scripts.