TokenForge Docs
Back to Dashboard

Documentation

Full reference for the TokenForge issuer dashboard

Architecture

Overview

The TokenForge dashboard is a Next.js 16 application that sits between issuers and the Solana blockchain. It provides a no-code interface for deploying and managing security tokens using the canonical SSTS programs.

┌──────────────────────────────────────────────────┐ │ Next.js 16 Dashboard │ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ Pages │ │ API │ │ DB (PostgreSQL) │ │ │ │ (14) │ │ Routes │ │ Drizzle ORM │ │ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ │ │ │ ┌────────────────────┴──────────────────────┐ │ │ │ Solana Devnet │ │ │ │ SSTS Core · Transfer Hook · FAMP · NOOP │ │ │ └───────────────────────────────────────────┘ │ └──────────────────────────────────────────────────┘

Key Design Decisions

Server-side signing
All on-chain operations use TEST_WALLET_KEYPAIR env var. No browser wallet required for token operations.
Manual instruction building
API routes build raw TransactionInstruction with explicit account flags, bypassing canonicalIxToWeb3 which has known isWritable bugs.
Non-fatal DB writes
On-chain success is primary. Database persistence is best-effort, wrapped in try/catch.
Dual Solana client libraries
@solana/web3.js v1 for transaction construction, @solana/kit v2 for PDA derivation and canonical types.
Dark theme only
Forces className='dark' on <html>. No light mode support.
Client-side data fetching
All dashboard pages use useEffect + fetch(). No server components or server actions in the dashboard.

Route Map

Dashboard Pages

RouteDescription
/dashboardOverview with KPI cards, recent activity, quick actions
/dashboard/tokensToken list with search and filters
/dashboard/tokens/create3-step wizard: Details → Metadata → Deploy
/dashboard/tokens/[mint]Token detail with transaction history and policy tabs
/dashboard/tokens/[mint]/mintMint tokens form
/dashboard/tokens/[mint]/transferTransfer with FAMP gating toggle
/dashboard/tokens/[mint]/policyFull FAMP policy management
/dashboard/policiesPolicy list derived from tokens
/dashboard/policies/[mint]Per-token policy management
/dashboard/distributionsDistribution list
/dashboard/distributions/createCreate escrow + Claim (tabs)
/dashboard/distributions/[id]Distribution detail with claim history
/dashboard/activityFull transaction log with search and type filter
/dashboard/settingsNetwork configuration and RPC endpoint

API Routes

RouteMethodPurpose
/api/statsGETAggregate KPI counts
/api/tokensGET/POSTList tokens / create DB record
/api/tokens/[mint]GET/PATCHSingle token detail / status update
/api/transactionsGET/POSTList transactions / record
/api/test-create-tokenPOSTDeploy SSTS token on-chain + persist
/api/mint-tokensPOSTMint tokens via server-side keypair
/api/transfer-tokensPOSTTransfer with optional FAMP enforcement
/api/famp-policyPOSTFAMP CRUD: 6 actions
/api/distributionPOSTCreate escrow + Claim distribution
/api/init-vc-transferPOSTInit VerificationConfig for transfer
/api/r3-uploadPOSTUpload metadata JSON to Cloudflare R2

API Reference

POST /api/test-create-token

Creates a canonical SSTS security token on Solana devnet.

Request
{ "name": "My Token", "symbol": "MST", "decimals": 6, "uri": "https://..." }
Flow
  1. Derive MintAuthority, FreezeAuthority, VerificationConfig PDAs
  2. Build InitializeMint instruction with Token-2022 extensions
  3. Build InitializeVerificationConfig instruction (disc 12, CPI mode)
  4. Simulate, sign with TEST_WALLET_KEYPAIR + mint keypair, send
  5. Persist token + transaction records to PostgreSQL

POST /api/mint-tokens

Mints tokens to a destination ATA (auto-creates if missing).

Request
{ "mintAddress": "...", "destination": "...", "amount": "1000" }
Key Behavior
  • Derives MintAuthority PDA + VerificationConfig PDA (discriminator 6)
  • Checks if VerificationConfig exists; creates if missing (CPI mode)
  • Appends NOOP verifier as remaining account for SSTS verify_by_programs

POST /api/transfer-tokens

Transfers tokens with optional FAMP policy enforcement.

Request
{
  "mintAddress": "...",
  "source": "...",
  "recipient": "...",
  "amount": "100",
  "enforceFamp": true
}
Verification Selection
  • enforceFamp=true → uses FAMP_PROGRAM_ID as verification program
  • enforceFamp=false → uses NOOP_VERIFICATION_PROGRAM_ID
Transfer Instruction Accounts (10)
  1. Mint address
  2. VerificationConfig PDA (disc 12)
  3. Instructions sysvar
  4. PermanentDelegate PDA
  5. Mint address (again)
  6. Source ATA (writable)
  7. Destination ATA (writable)
  8. Transfer hook program
  9. Token-2022 program
  10. Verification program (FAMP or NOOP)

POST /api/famp-policy

Full FAMP policy CRUD. Supports 6 actions.

ActionExtra ParamsDescription
createallowlistMode (bool)Create FAMP policy
addToAllowlistwallet (base58)Add wallet to allowlist
removeFromAllowlistwalletRemove from allowlist
addToBlocklistwalletAdd to blocklist
removeFromBlocklistwalletRemove from blocklist
getPolicynoneRead policy state from chain
Instruction Discriminators (Anchor 0.31.x)
InstructionDiscriminator
createPolicy[27, 81, 33, 27, 196, 103, 246, 53]
addToAllowlist[149, 143, 78, 134, 241, 244, 7, 56]
removeFromAllowlist[45, 46, 214, 56, 189, 77, 242, 227]
addToBlocklist[201, 138, 75, 216, 252, 201, 26, 106]
removeFromBlocklist[132, 125, 30, 120, 139, 22, 210, 90]

POST /api/distribution

Create escrow (disc 20) and Claim distribution (disc 21).

Create Escrow
{
  "action": "create",
  "mintAddress": "...",
  "merkleRoot": "0x...64hexchars...",
  "escrowTokenAccount": "...",
  "actionId": 0
}
Claim Distribution
{
  "action": "claim",
  "mintAddress": "...",
  "claimantAddress": "...",
  "amount": "1000",
  "merkleRoot": "0x...",
  "actionId": 0,
  "leafIndex": 0,
  "proofs": ["0x...", "0x..."]
}
Claim Flow
  1. Derive VerificationConfig (disc 21), ProofAccount PDA, PermanentDelegate PDA
  2. Derive claimant ATA + escrow ATA (owned by mint PDA)
  3. If proofs: build CreateProofAccount instruction (disc 18)
  4. Build ClaimDistribution instruction (disc 21) with Merkle proof option

Database

Schema

PostgreSQL via Drizzle ORM — 6 tables.

tokens(16 columns)
Security token records
transactions(10 columns)
On-chain transaction log
famp_policies(6 columns)
FAMP compliance policies
famp_policy_entries(4 columns)
Policy list entries
distributions(10 columns)
Merkle-based distributions
distribution_claims(8 columns)
Individual claim records

Relations

tokens ──1:N──> transactions
tokens ──1:1──> famp_policies ──1:N──> famp_policy_entries
tokens ──1:N──> distributions ──1:N──> distribution_claims
Migration commands: npm run db:generate | db:migrate | db:studio

Components

AppSidebar

ItemIconSub-items
OverviewLayoutDashboard
TokensCoinsAll Tokens, Create Token
PoliciesShieldCheck
DistributionsSend
ActivityActivity
SettingsSettings

StatusBadge

Color-coded badge using CVA with 15 variants.

activepausedfrozenconfirmedfailedpendingcompletedcreateminttransferpolicy_createallowlistblocklistdevnetmainnet

AddressLabel

Truncated Solana address with copy-to-clipboard and tooltip.

  • shortenAddress(address, chars) — shows first/last N chars
  • Copy button with checkmark feedback (2s timeout)
  • Tooltip showing full address on hover

SDK Integration

Three-Layer Architecture

L0 — Canonical Client
lib/sdk/l0/
Re-exports from canonical SSTS git submodule. Instruction builders, program IDs, types.
L1 — Adapters
lib/sdk/l1/
PDA derivation (derive.ts), error enrichment (errors.ts), transaction building (transactions.ts).
L2 — Workflows
lib/sdk/l2/
Token lifecycle, distribution, and FAMP policy workflows (13 functions).
Important
API routes bypass L2 workflows and build instructions manually using raw TransactionInstruction. This is because canonicalIxToWeb3 does not correctly set isWritable for TransactionSigner accounts (assigns role: 3 instead of role: 5).

PDA Derivation Functions

FunctionDescription
deriveMintAuthorityPdaMintAuthority PDA for the token
deriveFreezeAuthorityPdaFreezeAuthority PDA
deriveVerificationConfigPdaVerificationConfig PDA per discriminator
deriveFampPolicyPdaFAMP PolicyAccount PDA
deriveProofAccountPdaProofAccount PDA for distributions
deriveRateAccountPdaRateAccount PDA for splits/conversions
derivePermanentDelegatePdaPermanentDelegate PDA
deriveTransferHookPdaTransferHook PDA
deriveExtraAccountMetasPdaExtraAccountMetas PDA

Environment

Variables

VariableRequiredDescription
DATABASE_URLYesPostgreSQL connection string
TEST_WALLET_KEYPAIRYesServer-side signing keypair (base64 or JSON array)
R2_ACCOUNT_IDNoCloudflare account ID (for metadata upload)
R2_ACCESS_KEY_IDNoR2 API access key
R2_SECRET_ACCESS_KEYNoR2 API secret key
R2_BUCKET_NAMENoR2 bucket name
R2_PUBLIC_URLNoPublic URL for bucket

Setup

Installation
cd dashboard
npm install
cp .env.example .env.local
# Edit .env.local with DATABASE_URL and TEST_WALLET_KEYPAIR
npm run db:migrate
npm run dev
Available Scripts
ScriptPurpose
devDevelopment server (turbopack)
buildProduction build
startProduction server
lintESLint check
db:generateGenerate Drizzle migration
db:migrateApply migrations
db:studioDrizzle ORM GUI

Troubleshooting

Common Issues

Simulation fails: 'account not writable'
Cause: isWritable flag incorrect on an account that needs writing
Fix: Check account roles in the instruction keys array
'VerificationProgramNotFound'
Cause: NOOP verifier not deployed or not in remaining accounts
Fix: Deploy verification_policy_noop; append verifier program ID as remaining account
'AccountAlreadyInitialized'
Cause: VerificationConfig already exists
Fix: Check before creating (idempotent pattern)
Token create fails: 'immutable'
Cause: Mint account not set as writable + signer
Fix: Ensure mint keypair has isSigner: true, isWritable: true
DB operations silently fail
Cause: DATABASE_URL not set or connection issue
Fix: Check .env.local; DB writes are non-fatal
Wallet adapter: 'No wallets found'
Cause: No wallet adapters configured in provider
Fix: Install Phantom/Backpack browser extension

Devnet Program IDs

SSTS Core
SSTS8Qk2...vjap
SSTS Transfer Hook
HookXqLK...bmfL
FAMP Policy
99frBpGJ...pk2K
NOOP Verifier
5gPMypQi...GyLd
Token-2022
TokenzQd...xuEb