Sol.College

Full lesson

w3-solana

What it does

From the project README:

w3-solana provides a simple and easy way for developers and users to access resources on the chain, we'll call it w3solana for the time being.
Original
solanafuns/w3-solana
SuperStack fork
Blockchains/w3-solana @ 4516015
Stars
0
Last push (original)
2024-04-20
Languages
Rust, Go, TypeScript, CSS, Dockerfile, JavaScript
Detected stack
Native Rustsolana-go
Categories
Other
Licence
MIT

Architecture

27 source files. By extension: .rs ×15, .go ×8, .ts ×2, .tsx ×2.

Top-level directorySource files
gateway8
w37
w3-uploader6
vite-project4
src2

❌ Does not build as-is, checked 2026-11-02 Tested on 2026-11-02: full log results.json

Build it yourself: step-by-step guide

Every command below was run on 2026-11-02 by tests/run_fork.py against the pinned commit, and the results come from that run. The full output is in the committed log.

1. Prerequisites (versions we tested with)

2. Clone at the tested commit

git clone https://github.com/Blockchains/w3-solana.git && cd w3-solana
git checkout 4516015499860a2326345f4351b36f6853ad983f

3. Configure

No environment file is needed.

7. Deploy to a local validator

8. Run and exercise it

DirectoryCommandResultTime
.bash /workspace/sol-college/repo/tests/flows/w3-solana.sh❌ exit 12s
Output tail: bash /workspace/sol-college/repo/tests/flows/w3-solana.sh
lock file version 4 requires `-Znext-lockfile-bump`
Error: Unable to open program file: No such file or directory (os error 2)

Notes: The repo commits a sample signer private key (w3-uploader.yaml) and a keypair byte array (docker/Dockerfile). Never use or fund these; the flow generates throwaway keys. Gateway resolves <name>.w3sol.xyz Host headers; locally we send the Host header with curl.

How it works

Every instruction handler in the program, with the accounts struct it validates. Each link points to the line at the pinned commit.

InstructionAccountsWhere
PutContentnative instruction variantw3-uploader/src/sdk.rs:78
PutContentnative instruction variantw3/src/instruction.rs:11

Extend it

  1. Add an integration test that runs the main flow end to end and asserts every account state change.
  2. Add a negative test: call each instruction with the wrong signer or account and check that it fails.
  3. Add an event (emit!) to each state-changing instruction and subscribe to it from the client.
  4. Replace any unchecked AccountInfo with typed accounts plus has_one/seeds constraints.
  5. Make the frontend read the cluster from an env var, deploy it to GitHub Pages and point it at your devnet program.
  6. Wire the build into CI by copying this site's verify-forks workflow.

Dapp: deploying

This fork is queued for its own live dapp on Sol.College, with a Devnet/Mainnet switch. It appears here, at /dapps/w3-solana/, once it is deployed and checked on devnet. Until then, 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.

solanafuns/w3-solana · gateway/pkg/solana/client.go L85–108 @ 4516015 · MIT · original
	} 	k, bump_seed, err := common.FindProgramAddress(seeds, s.Program)	logrus.WithFields(logrus.Fields{		"seeds":   seeds,		"program": s.Program.ToBase58(),		"bump":    bump_seed,		"account": k.ToBase58(),	}).Info("find url account !!!")	return k, err} // let base_seed = ".w3-solana-name";// let (config_pda, bump_seed) = PdaHelper::new(program_id.clone())// .find_program_address(&[base_seed.as_bytes(), name.as_bytes()]); func (s *W3Site) GetSiteConfig(name string) (*NameConfig, error) {	baseSeed := ".w3-solana-name"	seeds := [][]byte{}	seeds = append(seeds, []byte(baseSeed))	seeds = append(seeds, []byte(name))	k, _, err := common.FindProgramAddress(seeds, s.NameResolver)	if err != nil {		return nil, err
  1. Line 91 "bump": bump_seed,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  2. Line 99 // .find_program_address(&[base_seed.as_bytes(), name.as_bytes()]);: Derives a PDA from seeds and the program ID and returns the address with its canonical bump.
  3. Line 104 seeds = append(seeds, []byte(baseSeed)): Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
solanafuns/w3-solana · w3/src/process/trunk.rs L25–48 @ 4516015 · MIT · original
    let trunk_account: &AccountInfo<'_> = next_account_info(account_info_iter)?;    let system_program_account: &AccountInfo<'_> = next_account_info(account_info_iter)?;     let pda_helper = PdaHelper::new(program_id.clone());     {        msg!("do trunk meta_account update !!!");        let (meta_pda, bump_seed) = pda_helper.find_program_address_by_text(&path);        msg!("meta_pda: {:?}", meta_pda);        msg!("meta_account.key: {:?}", meta_account.key);        assert!(meta_account.key == &meta_pda);         let meta_page = &PageData::TrunkPage { trunks: trunk_no };        let meta_data = meta_page.try_to_vec()?;         if meta_account.data_is_empty() {            let rent: Rent = Rent::get()?;            let data_size = meta_data.len();            let rent_lamports = rent.minimum_balance(data_size);            invoke_signed(                &system_instruction::create_account(                    payer.key,                    &meta_pda,                    rent_lamports,
  1. Line 31 msg!("do trunk meta_account update !!!");: Logs to the program log, which is visible in explorers and the test output.
  2. Line 32 let (meta_pda, bump_seed) = pda_helper.find_program_address_by_text(&path);: Derives a PDA from seeds and the program ID and returns the address with its canonical bump.
  3. Line 44 invoke_signed(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
solanafuns/w3-solana · gateway/pkg/solana/client_test.go L13–36 @ 4516015 · MIT · original
	bytes := program.Bytes()	t.Logf("bytes is -> %v ", bytes) 	name := "w3sol" 	baseSeed := ".w3-solana-name"	seeds := [][]byte{}	seeds = append(seeds, []byte(baseSeed))	seeds = append(seeds, []byte(name)) 	t.Logf("seeds is -> %v", seeds) 	k, bump, err := common.FindProgramAddress(seeds, program)	t.Error(err)	t.Logf("key is %s", k.ToBase58())	t.Logf("bump is %d", bump) } func TestLoadPageAccount(t *testing.T) {	viper.SetDefault("network", "localnet")	program := common.PublicKeyFromString("3P8bzeDY4i2QFU7AYtKH9jqnZqa7gShqB56tJPQDvmsS") 	path := "/index.html"
  1. Line 20 seeds = append(seeds, []byte(baseSeed)): Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 25 k, bump, err := common.FindProgramAddress(seeds, program): Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
solanafuns/w3-solana · w3-uploader/src/client.rs L97–120 @ 4516015 · MIT · original
                            let web_path = format!("/{}", relative_path.replace("\\", "/")); // 确保路径使用 web 标准的斜杠                            info!("Web path: {}", web_path);                             self.upload_file(&web_path, full_path);                        }                    }                }            }        }        Ok(())    }     pub fn config_name(&self, name: &str) {        info!("Configuring with name: {}", name);        let config_seeds = vec![".w3-solana-name".as_bytes(), name.as_bytes()];        info!("config_seeds {:?}", config_seeds);        let (config_account, bump_seed) = self.helper.find_program_address(&config_seeds);        info!("Account: {}", config_account);        info!("Bump seed: {}", bump_seed);        let instruction_enum = InstructionData::NameMapping {            name: name.to_string(),            program: self.program,            default_page: "/index.html".to_string(),        };
  1. Line 111 let config_seeds = vec![".w3-solana-name".as_bytes(), name.as_bytes()];: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 113 let (config_account, bump_seed) = self.helper.find_program_address(&config_seeds);: Derives a PDA from seeds and the program ID and returns the address with its canonical bump.

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.

DirectoryCommandRuns
vite-projectnpm run devvite
vite-projectnpm run buildtsc && vite build
vite-projectnpm run linteslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0
.cargo buildRust workspace build
.cargo testRust tests

What you could build from it

Used in lessons