Introduction
Qior is a token vesting and distribution platform for Solana teams.
The frontend connects a browser wallet to the Qior Anchor program, builds transactions for SPL token vesting streams, and gives creators and recipients a dashboard for managing unlocks.
This guide documents the frontend integration path: environment setup, wallet and Anchor client initialization, PDA derivation, stream creation, and the program calls used by the app.
Where to start?
Installation
Install dependencies and start the local Next.js app.
git clone https://github.com/mancer-team2/frontend.git
cd frontend
npm install
npm run devEnvironment
Qior requires public environment variables for the Solana cluster, deployed program ID, and RPC endpoint. The app fails fast when these values are missing.
NEXT_PUBLIC_SOLANA_CLUSTER=devnet
NEXT_PUBLIC_PROGRAM_ID=YOUR_PROGRAM_ID
NEXT_PUBLIC_RPC_URL=https://devnet.helius-rpc.com/?api-key=YOUR_HELIUS_API_KEYUse a Helius devnet RPC endpoint from helius.dev for more reliable requests than the public Solana endpoint.
For the current Qior devnet deployment, use BiwY71TrdBzgv2yfa6KfUxUMY8UCpeiUMGnwmCMTsfs9 as the program ID.
Use a devnet wallet with SOL for fees and USDC-Dev balance from the SPL Token Faucet before testing stream creation.
Create an Anchor client
The app centralizes provider and program creation in src/lib/anchor/program.ts. Use the exported helper instead of constructing account lists inside page components.
import { Connection } from "@solana/web3.js";
import { useAnchorWallet } from "@solana/wallet-adapter-react";
import { getProgram } from "@/lib/anchor/program";
const connection = new Connection(process.env.NEXT_PUBLIC_RPC_URL!, "confirmed");
const wallet = useAnchorWallet();
if (!wallet) throw new Error("Connect a wallet first");
const program = getProgram(connection, wallet);Derive stream addresses
Stream accounts use the ["stream", creator, recipient, stream_id] PDA seed. Escrow authority uses ["escrow_authority", stream].
import { BN } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import { getEscrowAuthorityPDA, getStreamPDA } from "@/lib/anchor/program";
const streamId = new BN(Date.now());
const recipient = new PublicKey("RECIPIENT_WALLET_ADDRESS");
const [streamPDA] = getStreamPDA(wallet.publicKey, recipient, streamId);
const [escrowAuthority] = getEscrowAuthorityPDA(streamPDA);Create a stream
The frontend validates mint data, the creator token account, and token balance before asking the wallet to approve the transaction.
import { BN } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import { createStreamTx } from "@/lib/anchor/program";
const now = Math.floor(Date.now() / 1000);
const { tx, signers } = await createStreamTx(program, wallet.publicKey, {
streamId: new BN(Date.now()),
recipient: new PublicKey("RECIPIENT_WALLET_ADDRESS"),
mint: new PublicKey("SPL_TOKEN_MINT"),
totalAmount: new BN("1000000"),
startTime: new BN(now),
cliffTime: new BN(now),
endTime: new BN(now + 30 * 24 * 60 * 60),
cancelable: true,
vestingType: "linear",
milestoneTime: new BN(0),
});
const signature = await sendTransaction(tx, connection, { signers });
await connection.confirmTransaction(signature, "confirmed");Vesting types
Qior supports three explicit vesting types. The frontend sends the type as cliff, linear, or milestone; the Anchor client converts it to the required enum object.
// Cliff
{ vestingType: "cliff", startTime: unlockTime - 1, cliffTime: unlockTime, endTime: unlockTime, milestoneTime: 0 }
// Linear
{ vestingType: "linear", startTime, cliffTime: startTime, endTime, milestoneTime: 0 }
// Milestone
{ vestingType: "milestone", startTime: 0, cliffTime: 0, endTime: 0, milestoneTime }Program calls from the frontend
This is the frontend-facing reference for the program calls used by Qior. The program documentation remains the canonical source for Rust instruction internals.
createStreamCreatorLocks SPL tokens in escrow and writes the vesting schedule.
withdrawRecipientClaims currently vested tokens from escrow.
cancelStreamCreatorCancels a cancelable stream and settles vested/unvested tokens.
setMilestoneCreatorMarks a milestone stream as reached.
closeStreamCreatorCloses a fully settled stream and escrow token account.
Errors surfaced in the frontend
The UI maps common wallet, token-account, and Anchor errors into user-facing messages. These program error names are expected from the current integration.
InvalidAmountInvalidScheduleInvalidCliffInvalidMilestoneTimeNothingToWithdrawStreamNotCancelableNotMilestoneStreamArchitecture decisions
Wallet Adapter plus Anchor client
The app uses Wallet Adapter for browser wallet UX and Anchor for method builders, account validation, and transaction construction.
Typed frontend program wrapper
PDA derivation, account decoding, and transaction builders live in one helper module so UI routes do not duplicate protocol wiring.
Explicit vesting enum
The frontend sends and decodes a real vesting type. It does not infer Cliff, Linear, or Milestone from timestamps.