Wen Open Lab · open integration

Stage 1: Receipts and Warranty Cards12 checks passed

Wen cat checkout receipt
Every order, one unique receipt

A paper receipt fades, a PDF can be edited, and a shop's database can disappear. A receipt minted as an NFT cannot be forged and stays with the buyer. This model issues one per order, using exactly the token layout WNS introduced: zero decimals, a supply locked at one, metadata stored inside the token itself.

The task

Give every customer a proof of purchase that the shop cannot lose, the customer cannot forge, and that reveals nothing private on a public chain. Optionally, tie a warranty to that one buyer so it cannot be sold separately from the person who bought the item.

The model

  1. The shop hashes the order (id, product, price, date) with SHA-256. Only the hash goes on-chain; the order itself stays in the shop's system and with the customer.
  2. A new Token-2022 mint is created with the Metadata Pointer extension, so name, symbol, link and extra fields live inside the mint. No external metadata account is needed — the WNS approach.
  3. Extra fields: order_hash, warranty_until, sku.
  4. Optional Non-Transferable extension makes the receipt soulbound: the warranty belongs to the buyer and cannot be moved.
  5. One token is minted to the buyer, then the mint authority is removed. Supply is locked at exactly one forever.

To check a warranty, anyone hashes the order the customer presents and compares it with order_hash on-chain. A match proves the purchase; the date field proves the term.

The code

File: lab/code/src/01-receipt-nft.ts · Apache-2.0

// Model 1: digital receipt and warranty card as a Token-2022 NFT (the same on-chain layout WNS uses:
// zero decimals, supply locked at 1, metadata stored inside the mint).
// License: Apache-2.0. Example code, not audited. Devnet first.
import {
  Keypair, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction, type TransactionInstruction,
} from '@solana/web3.js';
import {
  AuthorityType, ExtensionType, LENGTH_SIZE, TOKEN_2022_PROGRAM_ID, TYPE_SIZE,
  createAssociatedTokenAccountIdempotentInstruction, createInitializeMetadataPointerInstruction,
  createInitializeMintInstruction, createInitializeNonTransferableMintInstruction,
  createMintToInstruction, createSetAuthorityInstruction, getAssociatedTokenAddressSync, getMintLen,
} from '@solana/spl-token';
import { createInitializeInstruction, createUpdateFieldInstruction, pack, type TokenMetadata } from '@solana/spl-token-metadata';
import { createHash } from 'node:crypto';
import { getConnection, loadKeypair } from './common.js';

export interface Order {
  orderId: string;
  sku: string;
  priceMinorUnits: number;      // e.g. cents
  currency: string;             // e.g. "USD"
  purchasedAt: string;          // ISO date
  warrantyMonths: number;
}

/** Hash the order so the chain stores proof of purchase, not the customer's personal data. */
export function orderHash(order: Order): string {
  return createHash('sha256').update(JSON.stringify(order)).digest('hex');
}

export interface ReceiptOptions { soulbound: boolean; uri: string }

/** Everything needed to size and build the receipt mint. */
export function receiptLayout(order: Order, mint: PublicKey, merchant: PublicKey, opts: ReceiptOptions) {
  const warrantyUntil = new Date(order.purchasedAt);
  warrantyUntil.setMonth(warrantyUntil.getMonth() + order.warrantyMonths);
  const metadata: TokenMetadata = {
    mint, updateAuthority: merchant,
    name: `Receipt ${order.orderId}`,
    symbol: 'RCPT',
    uri: opts.uri,                                // JSON with public, non-personal details
    additionalMetadata: [
      ['order_hash', orderHash(order)],
      ['warranty_until', warrantyUntil.toISOString().slice(0, 10)],
      ['sku', order.sku],
    ],
  };
  // A soulbound receipt cannot be transferred: good for a warranty tied to one buyer.
  const extensions = [ExtensionType.MetadataPointer, ...(opts.soulbound ? [ExtensionType.NonTransferable] : [])];
  const mintLen = getMintLen(extensions);
  const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;
  return { metadata, mintLen, rentSpace: mintLen + metadataLen };
}

/** Build all instructions: create mint, store metadata, mint 1 to the buyer, lock supply. */
export function buildReceiptInstructions(args: {
  order: Order; mint: PublicKey; merchant: PublicKey; buyer: PublicKey; opts: ReceiptOptions; rentLamports: number;
}): { instructions: TransactionInstruction[]; buyerAta: PublicKey } {
  const { order, mint, merchant, buyer, opts } = args;
  const { metadata, mintLen } = receiptLayout(order, mint, merchant, opts);
  const buyerAta = getAssociatedTokenAddressSync(mint, buyer, false, TOKEN_2022_PROGRAM_ID);
  const instructions = [
    SystemProgram.createAccount({
      fromPubkey: merchant, newAccountPubkey: mint, space: mintLen, lamports: args.rentLamports, programId: TOKEN_2022_PROGRAM_ID,
    }),
    // Metadata lives inside the mint itself: no external metadata account.
    createInitializeMetadataPointerInstruction(mint, merchant, mint, TOKEN_2022_PROGRAM_ID),
    ...(opts.soulbound ? [createInitializeNonTransferableMintInstruction(mint, TOKEN_2022_PROGRAM_ID)] : []),
    createInitializeMintInstruction(mint, 0, merchant, null, TOKEN_2022_PROGRAM_ID),
    createInitializeInstruction({
      programId: TOKEN_2022_PROGRAM_ID, metadata: mint, updateAuthority: merchant, mint, mintAuthority: merchant,
      name: metadata.name, symbol: metadata.symbol, uri: metadata.uri,
    }),
    ...metadata.additionalMetadata.map(([field, value]) => createUpdateFieldInstruction({
      programId: TOKEN_2022_PROGRAM_ID, metadata: mint, updateAuthority: merchant, field, value,
    })),
    createAssociatedTokenAccountIdempotentInstruction(merchant, buyerAta, buyer, mint, TOKEN_2022_PROGRAM_ID),
    createMintToInstruction(mint, buyerAta, merchant, 1, [], TOKEN_2022_PROGRAM_ID),
    // Lock supply at exactly 1: zero decimals + no mint authority = an NFT, as in WNS.
    createSetAuthorityInstruction(mint, merchant, AuthorityType.MintTokens, null, [], TOKEN_2022_PROGRAM_ID),
  ];
  return { instructions, buyerAta };
}

/** Send on a real cluster (devnet by default). */
export async function mintReceipt(order: Order, buyer: PublicKey, opts: ReceiptOptions) {
  const connection = getConnection();
  const merchant = loadKeypair();
  const mint = Keypair.generate();
  const { rentSpace } = receiptLayout(order, mint.publicKey, merchant.publicKey, opts);
  const rentLamports = await connection.getMinimumBalanceForRentExemption(rentSpace);
  const { instructions, buyerAta } = buildReceiptInstructions({ order, mint: mint.publicKey, merchant: merchant.publicKey, buyer, opts, rentLamports });
  const signature = await sendAndConfirmTransaction(connection, new Transaction().add(...instructions), [merchant, mint]);
  return { mint: mint.publicKey.toBase58(), buyerAta: buyerAta.toBase58(), signature };
}

What was verified

In LiteSVM, for both the soulbound and the transferable version: the receipt is minted; supply is exactly 1 with 0 decimals and no mint authority; name, order hash and warranty date are read back from inside the mint; the buyer holds it; a soulbound receipt refuses to move (rejected by Token-2022 itself), a transferable one can be gifted or resold; and nobody can mint a second copy. See the verification log.

Where it is useful

  • Electronics and appliances: warranty claims without paper.
  • Limited goods and collectibles: provenance from the first sale.
  • Second-hand markets: a transferable receipt shows the item's origin to the next owner.
  • Loyalty programs: the receipt itself becomes the membership card.

Limits and risks

  • Each receipt costs rent (a small SOL deposit) and a network fee. Batch or compress for high volumes.
  • Never put personal data (name, address, phone) into metadata: the chain is public and permanent.
  • The hash only proves what the shop hashed. Agree on a fixed order format before you start.
  • The buyer needs a wallet. For mass retail, plan a custodial or embedded wallet option.

Wen in pictures

Wen shopper proof of purchase
Proof of purchase in the wallet
Wen toy with warranty card
A warranty that travels with the item
Wen plush collection receipts
Physical goods, digital proof
Wen shopping list on Solana
From cart to chain
Meet Wen, the receipt keeper
Loyalty starts with a receipt

Where Wen fits in this model

A shop can reward holders of its receipt NFTs with small WEN tips or a loyalty bonus, or accept WEN at checkout through Stage 4. The receipt itself uses the WNS layout that Wen made famous: the first WNS NFT was Meow's poem, and the same structure now carries a proof of purchase.

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