Stage 6: Devnet — Repeat It Yourselfready to run
- 0 · Roadmap
- 1 · Receipts & warranty
- 2 · Creator royalties
- 3 · B2B invoices
- 4 · Payments bridge
- 5 · Simulator
- 6 · Devnet: repeat it
- ✓ · Trust chain
- ★ · Benefits & use

A local test proves the logic; a public network proves it to everyone. Devnet is Solana's open test network: the same programs as mainnet, but the coins have no value. This page is the repeatable recipe. Run it once, run it a hundred times — every run creates new addresses that anyone can open in the explorer.
Why devnet matters
The verification log shows 25 checks passing inside LiteSVM, a local copy of the Solana runtime. That is strong evidence, but it happens on one machine. A devnet run moves the same models onto a public network where every transaction has a public link. Nobody has to trust us: they open the link and see the receipt with its supply of one, the invoice payment with its memo, the rejected payment without one.
What you need
- Node.js 20 or newer and the files from lab/code.
- The Solana command-line tools (
solana,solana-keygen). - A new, empty wallet created only for devnet. Never use a wallet that holds real funds.
Repeat it: five commands
# 1. install the example project
npm install
# 2. create a throwaway devnet wallet (it never holds real money)
solana-keygen new -o devnet-wallet.json --no-bip39-passphrase
# 3. get free devnet SOL (test coins with no value; use faucet.solana.com if the CLI is rate-limited)
solana airdrop 2 $(solana-keygen pubkey devnet-wallet.json) --url devnet
# 4. run the local checks first
npm test
# 5. run the public devnet demo
KEYPAIR=./devnet-wallet.json npm run devnet
The script refuses to run against anything that is not devnet, and stops if the wallet has less than 0.2 devnet SOL.
What the script does
- Model 1: mints a soulbound receipt NFT for a new test buyer, with the order hash and warranty date inside the mint, then locks the supply at one.
- Model 3: creates a settlement token with a 0.25% fee capped at 50 units and frozen-by-default accounts, opens a buyer and a seller account (the seller requires an invoice memo), approves both, funds the buyer, pays invoice
INV-DEVNET-0001, then tries a payment without an invoice number and shows that it is rejected. - Prints an explorer link for every transaction and every address.
File: lab/code/scripts/devnet-demo.ts · Apache-2.0
// Wen Open Lab: run Models 1 and 3 on PUBLIC DEVNET and print explorer links anyone can open.
// Devnet only. Use a NEW, EMPTY wallet created just for this. No real funds are ever needed.
// License: Apache-2.0. Example code, not audited.
//
// solana-keygen new -o devnet-wallet.json # new throwaway wallet
// solana airdrop 2 $(solana-keygen pubkey devnet-wallet.json) --url devnet
// KEYPAIR=./devnet-wallet.json npx tsx scripts/devnet-demo.ts
import { Keypair, LAMPORTS_PER_SOL, Transaction, sendAndConfirmTransaction, type TransactionInstruction } from '@solana/web3.js';
import { TOKEN_2022_PROGRAM_ID, createMintToInstruction } from '@solana/spl-token';
import { getConnection, loadKeypair } from '../src/common.js';
import { buildReceiptInstructions, receiptLayout, type Order } from '../src/01-receipt-nft.js';
import {
buildApproveCounterparty, buildInvoiceAccount, buildPayInvoice, buildSettlementMint,
invoiceAccountSpace, settlementMintSpace, DECIMALS,
} from '../src/03-b2b-invoice.js';
const connection = getConnection();
const wallet = loadKeypair();
const link = (kind: 'tx' | 'address', id: string) => `https://explorer.solana.com/${kind}/${id}?cluster=devnet`;
async function send(label: string, ixs: TransactionInstruction[], signers: Keypair[]) {
const sig = await sendAndConfirmTransaction(connection, new Transaction().add(...ixs), signers);
console.log(` ${label}\n ${link('tx', sig)}`);
return sig;
}
async function main() {
const endpoint = (connection as unknown as { rpcEndpoint: string }).rpcEndpoint;
if (!/devnet/.test(endpoint)) throw new Error(`Refusing to run outside devnet: ${endpoint}`);
const balance = await connection.getBalance(wallet.publicKey);
console.log(`Wallet ${wallet.publicKey.toBase58()} has ${balance / LAMPORTS_PER_SOL} devnet SOL`);
if (balance < 0.2 * LAMPORTS_PER_SOL) throw new Error('Need at least 0.2 devnet SOL: run `solana airdrop 2 <address> --url devnet`');
// ---- Model 1: receipt NFT (soulbound) to a fresh "buyer" address ----
console.log('\nModel 1 — receipt and warranty NFT');
const buyer = Keypair.generate();
const mint = Keypair.generate();
const order: Order = { orderId: `DEVNET-${Date.now()}`, sku: 'WEN-PLUSH-01', priceMinorUnits: 2500, currency: 'USD', purchasedAt: new Date().toISOString(), warrantyMonths: 12 };
const opts = { soulbound: true, uri: 'https://wen-watt.github.io/lab/receipt-warranty-wns.html' };
const { rentSpace } = receiptLayout(order, mint.publicKey, wallet.publicKey, opts);
const rent = await connection.getMinimumBalanceForRentExemption(rentSpace);
const { instructions, buyerAta } = buildReceiptInstructions({ order, mint: mint.publicKey, merchant: wallet.publicKey, buyer: buyer.publicKey, opts, rentLamports: rent });
await send('receipt minted, supply locked at 1', instructions, [wallet, mint]);
console.log(` receipt mint: ${link('address', mint.publicKey.toBase58())}`);
console.log(` buyer holds it in: ${link('address', buyerAta.toBase58())}`);
// ---- Model 3: B2B settlement token with fee, allowlist and invoice memo ----
console.log('\nModel 3 — B2B invoice settlement');
const settle = Keypair.generate();
await send('settlement mint created (fee 0.25% cap 50, frozen by default)',
buildSettlementMint(wallet.publicKey, settle.publicKey, await connection.getMinimumBalanceForRentExemption(settlementMintSpace())), [wallet, settle]);
const accRent = await connection.getMinimumBalanceForRentExemption(invoiceAccountSpace());
const payerAcc = Keypair.generate(), sellerAcc = Keypair.generate();
// In this demo one wallet plays both companies; in real life they are different owners.
await send('buyer account opened', buildInvoiceAccount(wallet.publicKey, payerAcc.publicKey, settle.publicKey, accRent, false), [wallet, payerAcc]);
await send('seller account opened (invoice memo required)', buildInvoiceAccount(wallet.publicKey, sellerAcc.publicKey, settle.publicKey, accRent, true), [wallet, sellerAcc]);
await send('both companies approved by the issuer', [
...buildApproveCounterparty(wallet.publicKey, settle.publicKey, payerAcc.publicKey),
...buildApproveCounterparty(wallet.publicKey, settle.publicKey, sellerAcc.publicKey),
], [wallet]);
const unit = 10n ** BigInt(DECIMALS);
await send('buyer funded with 10,000 test units', [createMintToInstruction(settle.publicKey, payerAcc.publicKey, wallet.publicKey, 10_000n * unit, [], TOKEN_2022_PROGRAM_ID)], [wallet]);
await send('invoice INV-DEVNET-0001 paid: 1,000 units, memo on-chain',
buildPayInvoice({ mint: settle.publicKey, from: payerAcc.publicKey, to: sellerAcc.publicKey, payer: wallet.publicKey, invoiceId: 'INV-DEVNET-0001', amount: 1_000n * unit }), [wallet]);
try {
await send('payment WITHOUT invoice number (must fail)',
buildPayInvoice({ mint: settle.publicKey, from: payerAcc.publicKey, to: sellerAcc.publicKey, payer: wallet.publicKey, invoiceId: null, amount: 1n * unit }), [wallet]);
console.log(' !! unexpected: payment without memo succeeded');
} catch {
console.log(' payment WITHOUT invoice number was rejected, as designed');
}
console.log(` settlement mint: ${link('address', settle.publicKey.toBase58())}`);
console.log('\nDone. Paste these links into the Lab devnet page so anyone can inspect them.');
}
main().catch(e => { console.error(e); process.exit(1); });
Published runs
| Run | Date | Receipt mint | Settlement mint | Invoice payment |
|---|---|---|---|---|
| #1 | pending — the first public run will be published here with its explorer links | |||
Ran it yourself? Share your links with the community on X (@wenwencoin). Independent runs are the best proof there is.
Safety rules
- Devnet coins have no value and cannot be sold. Anyone offering to "sell" devnet SOL is a scam.
- Keep
devnet-wallet.jsonout of any repository and never reuse it on mainnet. - Devnet is sometimes reset or slow; if a step fails, wait and run again — every run starts fresh.
Wen in pictures
Where Wen fits in this model
The real WEN token lives on mainnet only; there is no WEN on devnet. The script therefore creates its own test tokens, clearly named as tests. When a model is ready for WEN — for example Stage 7, pay in WEN and receive USDC — it moves to mainnet only after its devnet run and an independent review.
The Wen thread: one contract, everywhere
Every model in the Lab is free and open, and every one carries the same signature: the $WEN token contract. Copy it, repeat it in your own docs, and always verify it against an explorer before use.
WENWENvqqNya429ubCdR81ZmD69brwQaaBYY6p3LCpk
Open use policy
- Free and open. All Lab code is released under the Apache License 2.0, the same license as the Wen Program Library. Use it, change it, ship it, commercially or not; keep the license notice.
- Open tools only. Built on public, documented tools: Solana SPL Token-2022 (Token Extensions), the token layout of the Wen New Standard (WNS), Solana Pay, and LiteSVM for local testing.
- The rule: always verify your own model. Run the tests, start on devnet, read the official documentation, review every account and amount, and get an independent audit before any mainnet use. A model that is not checked is not a model, it is a guess.
- Not audited, not advice. These are educational examples. They are not financial, legal or tax advice, and not a licensed payment or banking product. Payments, stablecoins and acquiring are regulated activities in most countries.
- Security. Never paste a private key into a website. Do not install the npm package
@wen-community/wen-new-standard: npm removed it as malicious. Build from official sources.
Spoken by Wen, for the community. More in our editorial and data policy.
Wen Open Lab · roadmap · all code · Wen Program Library on GitHub




