Full lesson
zalary
Confidential payroll on Solana: Token-2022 confidential transfer amounts, mint auditor keys, MoonPay off-ramp to NGN/INR/BRL
What it does
From the project README:
Cross-border contractor pay with Token-2022 confidential transfer amounts. Employees cash out to local currency in the same app.
- Original
- ajanaku1/zalary
- SuperStack fork
- Blockchains/zalary @
b4a3f12 - Stars
- 0
- Last push (original)
- 2026-07-21
- Languages
- TypeScript, CSS, HTML, JavaScript
- Detected stack
- @solana/kitweb3.js
- Categories
- PaymentsInfrastructure & Tooling
- Licence
- MIT
- Homepage
- https://zalary.vercel.app
Architecture
63 source files. By extension: .tsx ×34, .ts ×28, .js ×1.
| Top-level directory | Source files |
|---|---|
app | 63 |
Dapp: devnet deploy pending
⏳ Devnet deploy pending — goes live when the devnet wallet is topped up. This fork's own dapp is not deployed yet, so there is no program address or transaction to show. It is queued in dapp-queue.txt; once devnet SOL is available it is deployed, exercised with real devnet transactions, and this section switches to the live dapp at /dapps/zalary/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.
Key code, explained
Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.
app/api/faucet.ts L49–72 @ b4a3f12 · MIT · original tx.add( createAssociatedTokenAccountIdempotentInstruction( faucet.publicKey, recipientAta, recipient, ZUSDC_MINT, TOKEN_2022_PROGRAM_ID, ), ) tx.add( createMintToInstruction( ZUSDC_MINT, recipientAta, faucet.publicKey, AMOUNT_PER_REQUEST, [], TOKEN_2022_PROGRAM_ID, ), ) const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed') tx.recentBlockhash = blockhash tx.feePayer = faucet.publicKey tx.sign(faucet)- Line 55
TOKEN_2022_PROGRAM_ID,: Targets the Token-2022 program instead of the original Token Program. - Line 59
createMintToInstruction(: Mints new tokens. The mint authority must sign. - Line 69
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed: Fetches a recent blockhash. Transactions expire if it gets too old.
app/src/lib/send-plan.ts L85–108 @ b4a3f12 · MIT · original connection: Connection, feePayer: PublicKey, sendTransaction: SendTransactionFn, instruction: Instruction, extraSigners: Keypair[] = [],): Promise<string> { return sendInstructionBatch(connection, feePayer, sendTransaction, [instruction], extraSigners)} export async function sendInstructionBatch( connection: Connection, feePayer: PublicKey, sendTransaction: SendTransactionFn, instructions: Instruction[], extraSigners: Keypair[] = [],): Promise<string> { const tx = new Transaction() for (const ix of instructions) tx.add(kitIxToWeb3(ix)) tx.feePayer = feePayer const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed') tx.recentBlockhash = blockhash if (extraSigners.length > 0) { tx.partialSign(...extraSigners)- Line 87
sendTransaction: SendTransactionFn,: Submits the signed transaction to the cluster. - Line 104
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed: Fetches a recent blockhash. Transactions expire if it gets too old.
app/src/lib/program.ts L1–24 @ b4a3f12 · MIT · originalimport { Program, AnchorProvider, BN } from '@coral-xyz/anchor'import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, ComputeBudgetProgram, type Connection, type Transaction } from '@solana/web3.js'import { TOKEN_2022_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddressSync } from '@solana/spl-token'import { IDL } from './zalary_idl'import { getPriorityFeeEstimate } from './helius-enhanced' // Poll signature status manually instead of using connection.confirmTransaction,// which relies on a WebSocket signatureSubscribe that some RPCs (Helius free tier,// public devnet) drop silently — leading to 30s/120s legacy timeouts.export async function pollConfirm(connection: Connection, sig: string, timeoutMs = 90_000): Promise<void> { const start = Date.now() while (Date.now() - start < timeoutMs) { const { value } = await connection.getSignatureStatuses([sig]) const status = value[0] if (status?.err) throw new Error(`On-chain failure: ${JSON.stringify(status.err)} (sig: ${sig})`) if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') return await new Promise(r => setTimeout(r, 2000)) } throw new Error(`Confirmation timed out after ${timeoutMs / 1000}s. Signature: ${sig}`)} export const PROGRAM_ID = new PublicKey('FGBieAeHERm7CJxtXsicQ7NaQ4FqsDixSwmMqKhovfpH') export type ZalaryProgram = Program<any>- Line 2
import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, ComputeBudgetProgram, type Connecti: Sets the compute-unit limit or price (priority fee). - Line 3
import { TOKEN_2022_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, getAsso: Targets the Token-2022 program instead of the original Token Program.
app/src/lib/confidential.ts L133–156 @ b4a3f12 · MIT · original owner: asAddress(owner), mint: asAddress(mint), tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, }) return ata} export function findTokenAtaSync(owner: PublicKey, mint: PublicKey): PublicKey { return getAssociatedTokenAddressSync(mint, owner, false, TOKEN_2022_PROGRAM_ID)} export async function createConfidentialMint(params: { connection: Connection owner: PublicKey signMessage: WalletSignMessage sendTransaction: SendTransactionFn}): Promise<{ mint: string; signatures: string[] }> { const { connection, owner, signMessage, sendTransaction } = params // Kit's generateKeyPairSigner uses non-extractable CryptoKeys, so we co-sign // with a web3.js Keypair instead (partialSign + wallet fee-payer). const mintKeypair = Keypair.generate() const payer = createAddressSigner(owner) const mintAddr = asAddress(mintKeypair.publicKey) const newMintSigner = {- Line 141
return getAssociatedTokenAddressSync(mint, owner, false, TOKEN_2022_PROGRAM_ID): Targets the Token-2022 program instead of the original Token Program. - Line 148
sendTransaction: SendTransactionFn: Submits the signed transaction to the cluster.
How to run, build and test
These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.
| Directory | Command | Runs |
|---|---|---|
app | npm run dev | vite |
app | npm run build | tsc -b && vite build |
app | npm run lint | eslint . |
What you could build from it
- Fork the SuperStack copy and extend it within the Payments gaps listed on the idea page.
- Learn the pattern first: Solana Pay
- Learn the pattern first: SPL Tokens: mints, accounts and transfers
- Learn the pattern first: Blinks and Solana Actions