Quickstart·Installation
Get Started
Add trustless escrow to your Solana dApp in minutes. Install the SDK, initialize a client, and create your first on-chain escrow with a few lines of TypeScript.
1. Install the SDK
Install the SDK and its peer dependencies. The IDL is bundled inside the package, no separate fetch needed.
npm install @zende/sdk @coral-xyz/anchor @solana/web3.js bn.js
The SDK requires @coral-xyz/anchor for the provider and bn.js for lamport amounts.
2. Initialize the client
Import ZendetsuClient and the bundled IDL, then initialize with an Anchor provider. Pass lang: "pidgin" as the third argument to get Nigerian Pidgin error messages instead of English.
import { ZendetsuClient, IDL } from "@zende/sdk";
import { AnchorProvider } from "@coral-xyz/anchor";
import { Connection, clusterApiUrl } from "@solana/web3.js";
// Use your wallet adapter's provider (Phantom, Backpack, etc.)
// In a React app this comes from useAnchorWallet() + useConnection()
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const provider = new AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
// English errors (default)
const client = new ZendetsuClient(provider, IDL);
// Nigerian Pidgin errors, "E no gree. Check your inputs and try again"
const client = new ZendetsuClient(provider, IDL, { lang: "pidgin" });IDL. Pass it as the second argument to the constructor, it is required.3. Create your first escrow
Call createEscrow to lock funds on-chain. The depositor's wallet signs the transaction. Save the returned escrowId, you will need it for every subsequent action on this escrow.
import { ZendetsuClient, IDL } from "@zende/sdk";
import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
const TREASURY = new PublicKey("JCnTFWQnrsMha1hxbXumb9wLZuS989MT8tShaUDTjskr");
// Amounts are in lamports as BN (1 SOL = 1_000_000_000 lamports)
const ONE_SOL = new BN(1_000_000_000);
const FEE = new BN(10_000_000); // 0.01 SOL, ~1% of a 1 SOL deal
const { escrowId, signature } = await client.createEscrow({
// Amount the recipient will receive (in lamports)
amount: ONE_SOL,
// Platform fee in lamports, calculate as ~1% of amount
feeAmount: FEE,
// Escrow mode: { simple: {} } | { timed: {} } | { milestone: {} } | { multiParty: {} }
mode: { simple: {} },
// Release condition: { majority: {} } | { unanimous: {} }
releaseCondition: { majority: {} },
// Speed setting: { standard: {} } | { express: {} } | { instant: {} }
speed: { standard: {} },
// Recipient wallet address
recipient: new PublicKey("RECIPIENT_WALLET_ADDRESS"),
// Protocol treasury (receives the fee on settlement)
treasury: TREASURY,
});
console.log("Escrow created:", signature);
console.log("Escrow ID:", escrowId); // Save this, needed for every follow-up callThe timelock is set automatically by deal size, 1 hr under ~$100, 6 hrs up to ~$1K, 24 hrs up to ~$10K, 72 hrs above. Both parties can release instantly by both calling confirmRelease. Fee tiers also drop as deals get larger.
| Deal size | Timelock | Fee tier |
|---|---|---|
| < $100 | 1 hour | 1.0% |
| $100 – $1,000 | 6 hours | 0.8% |
| $1,000 – $10,000 | 24 hours | 0.6% |
| $10,000 – $100,000 | 72 hours | 0.45% |
| > $100,000 | 72 hours | negotiated |
4. Confirm release
Both parties call confirmRelease to signal they are happy with the deal. When both have confirmed, funds transfer to the recipient and the fee goes to treasury automatically, no third call needed.
// Both the depositor and recipient call this with the same params.
// When both have confirmed, funds release instantly.
const sig = await client.confirmRelease({
escrowId, // Uint8Array returned from createEscrow
depositor, // PublicKey of the depositor
recipient, // PublicKey of the recipient
treasury: TREASURY,
});
console.log("Confirmed:", sig);claimRelease after the timelock expires to claim funds unilaterally. If there is zero activity for 180 days, the depositor can call claimInactivity to recover their funds, the fee is also returned in full.5. Cancel an escrow
The depositor can cancel and get a full refund, including the fee, but only while the escrow is still in Pending status. The escrow moves to Active the moment the recipient calls confirmRelease, after which cancel is permanently blocked.
// Only the depositor can call this, and only before recipient confirms.
const sig = await client.cancelEscrow({
escrowId, // Uint8Array returned from createEscrow
recipient, // PublicKey of the recipient
});
console.log("Cancelled and refunded:", sig);Try the demo
Create a real escrow on Solana Devnet directly from the browser. No setup needed.
View on GitHub
Read the full source code, Anchor program, and test suite.
More docs coming soon
Milestone mode, Multi-Sig mode, dispute resolution, and the full SDK reference are being documented. Check back or watch the GitHub repo for updates.