A gene splicing NFT game built on Stellar with verifiable randomness.
Splicers is an on-chain NFT game where players splice genetic material to create unique creatures. The game uses BLS12-381 signature verification (CAP-0059) to ensure cryptographically verifiable randomness from the drand network.
Key Features:
- 𧬠Gene Splicing Mechanics: Combine head, body, and leg genes to create unique creatures
- π² Verifiable Randomness: Uses drand's distributed randomness beacon with on-chain BLS12-381 verification
- π¨ Dynamic NFTs: Creatures rendered from layered PNG assets with gene-based variations
- ποΈ Cartridge System: Genome Cartridges with cosmetic skins generated via Soroban PRNG
- π Cryptographic Security: Full CAP-0059 implementation with pairing checks, H2C, and subgroup verification
Built with:
- β‘οΈ Scaffold Stellar - Vite + React + TypeScript
- π¦ Soroban smart contracts in Rust
- π OpenZeppelin Stellar Contracts for NFT standards
- π‘ Drand Quicknet (3-second rounds)
Before getting started, make sure youβve met the requirements listed in the Soroban documentation and that the following tools are installed :
- Rust
- Cargo (comes with Rust)
- Rust target: install the compilation target listed in the Soroban setup guide
- Node.js (v22, or higher)
- npm: Comes with the node installer or can also be installed package managers such as Homebrew, Chocolatey, apt, etc.
- Stellar CLI
- Scaffold Stellar CLI Plugin
- Clone and install dependencies:
git clone https://github.com/AshFrancis/splicers.git
cd splicers
npm install- Configure environment:
# Copy the example environment file
cp .env.example .env
# Edit .env to set:
# - STELLAR_SCAFFOLD_ENV=development (for local) or staging (for testnet)
# - Network configuration (already set for testnet by default)- Start development server:
npm run devThe scaffold will automatically:
- Build the gene-splicer contract
- Deploy to your configured network
- Generate TypeScript bindings
- Start the Vite dev server at http://localhost:5173
- Deploy Native XLM Contract (Local Development only):
If running locally, deploy the XLM token contract:
stellar contract asset deploy --source me --network local --asset nativeThis deploys the native XLM token at its deterministic address (CDMLFMKMMD7MWZP3FKUBZPVHTUEDLSX4BYGYKH4GCESXYHS3IHQ4EIG4), required for splice fees.
The game is currently deployed on Stellar testnet. To deploy your own instance:
- Build the contract:
cargo build --release --target wasm32-unknown-unknown- Get drand public key:
npx tsx scripts/getDrandPubkey.ts
# Copy the 192-byte hex output- Deploy via Stellar CLI:
# Install WASM
stellar contract install \
--wasm target/wasm32-unknown-unknown/release/gene_splicer.wasm \
--network testnet
# Deploy with constructor args
stellar contract deploy \
--wasm-hash <hash-from-install> \
--network testnet \
-- \
--admin <your-address> \
--xlm_token CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC \
--cartridge_skin_count 10 \
--dev_mode false \
--drand_public_key <192-byte-hex-from-step-2>- Update contract IDs (see section below)
splicers/
βββ contracts/
β βββ gene-splicer/ # Main game contract (Rust/Soroban)
β βββ src/lib.rs # Contract logic with BLS12-381 verification
β βββ Cargo.toml
βββ docs/
β βββ specs/ # Game design specifications
β βββ reference-implementations/ # Solidity & JS reference code
βββ packages/ # Auto-generated TypeScript clients
β βββ gene_splicer/ # Contract bindings (generated by scaffold)
βββ public/
β βββ assets/creatures/ # Gene segment PNG assets (15 variants)
βββ scripts/ # Utility scripts
β βββ getDrandPubkey.ts # Extract drand public key
β βββ testBLS12381.sh # End-to-end BLS verification test
β βββ fetchAndDecompressDrand.ts
βββ src/ # Frontend React application
β βββ components/
β β βββ GenomeSplicer.tsx # Main game UI
β β βββ CreatureRenderer.tsx # Dynamic creature rendering
β βββ contracts/
β β βββ gene_splicer.ts # Contract client wrapper
β β βββ util.ts # Network utilities
β βββ services/
β β βββ entropyRelayer.ts # BLS12-381 point decompression
β βββ hooks/ # React hooks for wallet, balance, etc.
β βββ pages/ # App routes
β βββ App.tsx
βββ .github/workflows/
β βββ deploy.yml # GitHub Pages deployment
βββ environments.toml # Network configurations
βββ package.json
βββ .env # Local environment (gitignored)
Key Files:
contracts/gene-splicer/src/lib.rs- Smart contract with CAP-0059 BLS verificationsrc/services/entropyRelayer.ts- Decompresses drand signatures using @noble/curvessrc/components/GenomeSplicer.tsx- Game UI for splicing and finalizing creaturessrc/components/CreatureRenderer.tsx- Dynamic creature rendering from gene combinations
-
Splice Genome (
splice_genome()):- Player pays 1 XLM fee
- Receives Genome Cartridge NFT with cosmetic skin (generated via Soroban PRNG)
- Contract stores the drand round number for future entropy
-
Submit Entropy (
submit_entropy()):- Off-chain relayer fetches drand quicknet entropy
- Submits round, randomness, and BLS signature to contract
- Contract performs full BLS12-381 verification (CAP-0059)
-
Finalize Splice (
finalize_splice()):- Permissionless function anyone can call
- Uses verified drand entropy to select genes (head, body, legs)
- Each gene has rarity: Legendary (10%), Rare (30%), Normal (60%)
- Burns Genome Cartridge and mints final Creature NFT
The contract implements cryptographically secure randomness using drand's distributed beacon:
Architecture:
- Off-chain relayer (NON-SECURITY-CRITICAL): Fetches and decompresses BLS points
- On-chain contract (SECURITY-CRITICAL): Verifies signatures with pairing checks
Verification Steps:
- Deserialize G1 signature (96 bytes uncompressed)
- Verify signature in G1 subgroup
- Construct drand message:
prev_sig || round(big-endian) - Hash-to-Curve (H2C) with DST:
BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_ - Verify hashed point in G1 subgroup
- Deserialize drand public key (192 bytes uncompressed G2)
- Verify public key in G2 subgroup
- Pairing check:
e(sig, G2_gen) == e(H(msg), pubkey)
Security Properties:
- Signature authenticity proven via pairing equation
- Message integrity from on-chain H2C
- Replay protection via previous signature chaining
- Randomness derived as
SHA256(compressed_signature)- matches drand spec
Testing:
# Test full BLS verification with live drand data
bash scripts/testBLS12381.shWhen you deploy or redeploy contracts, you must keep the contract IDs in sync across all config files, otherwise your frontend will fail silently!
-
Check the new contract ID - After deployment, the contract ID is stored in:
cat .config/stellar/contract-ids/gene_splicer.json
-
Update ALL contract ID locations - Copy the contract ID to:
-
.envfile (for local development):PUBLIC_GENE_SPLICER_CONTRACT_ID="CCL5G4HRTTBFASEBGJVF4OTLF2K3PWWUNLL3IWYZXI2CZAO4ZFJRHSPX" -
.github/workflows/deploy.yml(for GitHub Pages deployment):env: PUBLIC_GENE_SPLICER_CONTRACT_ID: CCL5G4HRTTBFASEBGJVF4OTLF2K3PWWUNLL3IWYZXI2CZAO4ZFJRHSPX
-
-
Restart dev server - The Vite dev server should auto-reload, but if not:
# Stop npm run dev (Ctrl+C) and restart npm run dev
# Contract ID in Stellar config
cat .config/stellar/contract-ids/gene_splicer.json
# Contract ID in .env
grep PUBLIC_GENE_SPLICER_CONTRACT_ID .env
# Contract ID in GitHub Actions workflow
grep PUBLIC_GENE_SPLICER_CONTRACT_ID .github/workflows/deploy.yml
# These should ALL match!Why this matters: The frontend reads contract IDs from environment variables. If the .env file has an old contract ID but the blockchain has a new one, transactions will succeed in simulation but fail silently on the network, and you won't see any data!