Wen Open Lab · open integration

Stage 6: Devnet — Repeat It Yourselfready to run

Testing Wen on Solana devnet
Test in public, not in theory

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

  1. 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.
  2. 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.
  3. 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

RunDateReceipt mintSettlement mintInvoice payment
#1pending — 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.json out 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

Wen Seeker devnet run
Your own run, your own proof
Wen cat checks the explorer
Every step has a link
Wen builder repeats the test
Repeat it any time
Wen on the phone, devnet links
Share the links
Wen developers on devnet
Builders welcome

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

Check: Solana Explorer · how to find the contract ID

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