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.

terminal
git clone https://github.com/mancer-team2/frontend.git
cd frontend
npm install
npm run dev

Environment

Qior requires public environment variables for the Solana cluster, deployed program ID, and RPC endpoint. The app fails fast when these values are missing.

.env.local
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_KEY

Use 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.

program-client.tsTS
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].

stream-pda.tsTS
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.

create-stream.tsTS
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.

vesting-types.tsTS
// 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.

createStreamCreator

Locks SPL tokens in escrow and writes the vesting schedule.

withdrawRecipient

Claims currently vested tokens from escrow.

cancelStreamCreator

Cancels a cancelable stream and settles vested/unvested tokens.

setMilestoneCreator

Marks a milestone stream as reached.

closeStreamCreator

Closes 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.

InvalidAmountInvalidScheduleInvalidCliffInvalidMilestoneTimeNothingToWithdrawStreamNotCancelableNotMilestoneStream

Architecture 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.