Full lesson
three.ws
Give your AI agent a 3D body: text or a photo to a rigged GLB avatar, an LLM brain, a Solana wallet and a one-tag embed. Keyless MCP server for Claude, Cursor and Codex. 50k+ models forged.
What it does
From the project README:
**Website** · **Docs** · **Changelog** · **X / Twitter** · **GitHub** · **$THREE on pump.fun**
- Original
- nirholas/three.ws
- SuperStack fork
- Blockchains/three.ws @
ab4b79e - Stars
- 266
- Last push (original)
- 2026-11-03
- Languages
- JavaScript, HTML, Markdown, TypeScript, CSS, Solidity
- Detected stack
- @solana/kitAnchorMetaplexSolana Agent KitSolana Payweb3.js
- Categories
- AI & AgentsDeFi
- Licence
- Apache-2.0
- Homepage
- https://three.ws
Architecture
12246 source files. By extension: .js ×9770, .ts ×1175, .sol ×655, .py ×366, .tsx ×184, .jsx ×69.
| Top-level directory | Source files |
|---|---|
api | 3144 |
tests | 2687 |
src | 1712 |
packages | 1057 |
contracts | 921 |
apps | 612 |
robinhood | 451 |
workers | 392 |
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/three-ws/. 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.
contracts/event-markets-stake/src/lib.rs L145–168 @ ab4b79e · Apache-2.0 · original p.max_pool = max_pool; p.lock_ts = lock_ts; p.void_after_ts = void_after_ts; p.status = STATUS_OPEN; p.winning_outcome = 0; p.totals = [0; MAX_OUTCOMES]; p.total_staked = 0; p.fee_paid = 0; p.paid_out = 0; p.bump = ctx.bumps.pool; emit!(PoolCreated { pool: p.key(), pool_id, mint: p.mint, lock_ts, void_after_ts }); Ok(()) } pub fn stake(ctx: Context<Stake>, outcome: u8, amount: u64) -> Result<()> { require!(!ctx.accounts.config.paused, StakeError::Paused); let now = Clock::get()?.unix_timestamp; let pool = &mut ctx.accounts.pool; require!(pool.status == STATUS_OPEN, StakeError::NotOpen); require!(now < pool.lock_ts, StakeError::Locked); require!(outcome < pool.outcome_count, StakeError::BadOutcome); require!(amount >= pool.min_stake, StakeError::BelowMinimum); let pos = &mut ctx.accounts.position;- Line 154
p.bump = ctx.bumps.pool;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 155
emit!(PoolCreated { pool: p.key(), pool_id, mint: p.mint, lock_ts, void_after_ts });: Emits an Anchor event that clients can parse from the logs. - Line 159
pub fn stake(ctx: Context<Stake>, outcome: u8, amount: u64) -> Result<()> {: Touches staking state: the amount locked and when it was locked. - Line 160
require!(!ctx.accounts.config.paused, StakeError::Paused);: An explicit validation that aborts the instruction with an error. - Line 161
let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar.
contracts/skill-license/src/lib.rs L169–192 @ ab4b79e · Apache-2.0 · original /// longer access-bearing. pub fn revoke_skill_license(ctx: Context<RevokeSkillLicense>) -> Result<()> { require!( ctx.accounts.skill_license.revoked_at == 0, SkillLicenseError::AlreadyRevoked ); let marketplace_bump = ctx.accounts.marketplace.bump; let signer_seeds: &[&[&[u8]]] = &[&[MARKETPLACE_SEED, &[marketplace_bump]]]; token::freeze_account(CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), FreezeAccount { account: ctx.accounts.owner_token_account.to_account_info(), mint: ctx.accounts.nft_mint.to_account_info(), authority: ctx.accounts.marketplace.to_account_info(), }, signer_seeds, ))?; let now = Clock::get()?.unix_timestamp; ctx.accounts.skill_license.revoked_at = now; emit!(SkillLicenseRevoked {- Line 171
require!(: An explicit validation that aborts the instruction with an error. - Line 176
let marketplace_bump = ctx.accounts.marketplace.bump;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 179
token::freeze_account(CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address. - Line 189
let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar. - Line 192
emit!(SkillLicenseRevoked {: Emits an Anchor event that clients can parse from the logs.
contracts/knock-escrow/src/lib.rs L169–192 @ ab4b79e · Apache-2.0 · original if let Some(o) = open { door.open = o; } Ok(()) } /// Pay the door's price into escrow and record one knock. /// /// `nonce` lets one sender knock the same door repeatedly; `message_hash` /// is the SHA-256 of the message body, which stays off-chain. pub fn knock(ctx: Context<Knock>, nonce: u64, message_hash: [u8; 32]) -> Result<()> { let door = &ctx.accounts.door; require!(door.open, KnockError::DoorClosed); require_keys_eq!(door.mint, ctx.accounts.mint.key(), KnockError::WrongMint); let price = door.price; let now = Clock::get()?.unix_timestamp; let expires_at = now .checked_add(door.reply_window) .ok_or(KnockError::MathOverflow)?; token_interface::transfer_checked( CpiContext::new( ctx.accounts.token_program.to_account_info(),- Line 181
require!(door.open, KnockError::DoorClosed);: An explicit validation that aborts the instruction with an error. - Line 185
let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar. - Line 187
.checked_add(door.reply_window): Uses checked arithmetic so overflow fails instead of wrapping. - Line 190
token_interface::transfer_checked(: A checked token transfer, which also verifies the mint and decimals. - Line 191
CpiContext::new(: Makes a cross-program invocation into another program.
contracts/agent-invocation/src/lib.rs L61–84 @ ab4b79e · Apache-2.0 · original ); emit!(SkillInvoked { invoker_agent, target_agent, invoker_authority: ctx.accounts.invoker_authority.key(), skill_name, parameters, timestamp: Clock::get()?.unix_timestamp, }); Ok(()) }} #[derive(Accounts)]#[instruction(skill_name: String, parameters: String)]pub struct InvokeSkill<'info> { /// The invoking agent's program-derived identity. Constrained to the PDA /// for `invoker_authority`, binding the on-chain agent to the signer. /// CHECK: validated by the `seeds`/`bump` PDA constraint against this /// program; never read or written, only its key is recorded. #[account( seeds = [AGENT_SEED, invoker_authority.key().as_ref()],- Line 63
emit!(SkillInvoked {: Emits an Anchor event that clients can parse from the logs. - Line 69
timestamp: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar. - Line 81
/// CHECK: validated by the `seeds`/`bump` PDA constraint against this: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 84
seeds = [AGENT_SEED, invoker_authority.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
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 |
|---|---|---|
. | npm run dev | vite --port 3000 |
. | npm run build | NODE_OPTIONS='--no-deprecation --max-old-space-size=6144' vite build && node scripts/strip-sw-from-embeds.mjs && node sc |
. | npm run test | vitest run && playwright test |
. | npm run lint | eslint . |
. | npm run deploy | npm run deploy:gcp:full |
third_party/threeui | npm run dev | vite |
third_party/threeui | npm run build | npm run test && npm run build:site && npm run build:lib |
third_party/threeui | npm run test | node --test scripts/community-sanitizers.test.mjs scripts/public-boundary.test.mjs scripts/library-package.test.mjs scri |
third_party/image-blaster | npm run dev | bun --cwd=app run dev |
third_party/image-blaster | npm run build | bun --cwd=app run build |
third_party/image-blaster | npm run test | bun --cwd=app run test |
tour-sdk | npm run build | node build.mjs |
tour-sdk | npm run test | vitest run |
chat | npm run dev | vite |
chat | npm run build | vite build |
agent-protocol-sdk | npm run build | tsc |
What you could build from it
- Fork the SuperStack copy and extend it within the AI & Agents gaps listed on the idea page.
- Learn the pattern first: AI agents on Solana
- Learn the pattern first: Building and sending transactions from a client
- Learn the pattern first: Blinks and Solana Actions