Full lesson
samui-wallet
Open Source wallet and toolbox for Solana builders ๐ Cypherpunk Hackathon winner
What it does
From the project README:
If you're interested in contributing to Samui, please read our contributing docs before submitting a pull request.
- Original
- samui-build/samui-wallet
- SuperStack fork
- Blockchains/samui-wallet @
6fc3250 - Stars
- 167
- Last push (original)
- 2026-10-25
- Languages
- TypeScript, MDX, CSS, HTML, JavaScript, Handlebars
- Detected stack
- @solana/kit
- Categories
- Wallets & IdentityInfrastructure & Tooling
- Licence
- MIT
- Homepage
- https://samui.build
Architecture
860 source files. By extension: .ts ร447, .tsx ร413.
| Top-level directory | Source files |
|---|---|
packages | 810 |
apps | 31 |
examples | 11 |
(root) | 5 |
turbo | 3 |
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/samui-wallet/. 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.
packages/feature-tools/src/tools-routes.tsx L1โ24 @ 6fc3250 ยท MIT ยท originalimport { useAccountActive } from '@workspace/db-react/use-account-active'import { useAccountGetTransactionSigner } from '@workspace/db-react/use-account-get-transaction-signer'import { useNetworkActive } from '@workspace/db-react/use-network-active'import { lazy } from 'react'import { useRoutes } from 'react-router'import { ToolsUiLayout } from './ui/tools-ui-layout.tsx' const ToolsFeatureAirdrop = lazy(() => import('./tools-feature-airdrop.tsx'))const ToolsFeatureCreateToken = lazy(() => import('./tools-feature-create-token.tsx'))const ToolsFeatureMintToken = lazy(() => import('./tools-feature-mint-token.tsx'))const ToolsFeatureStake = lazy(() => import('./tools-feature-stake.tsx'))const ToolsFeatureTransactionInspector = lazy(() => import('./tools-feature-transaction-inspector.tsx')) export default function ToolsRoutes() { const account = useAccountActive() const network = useNetworkActive() const getTransactionSigner = useAccountGetTransactionSigner({ account }) return useRoutes([ { children: [ { element: <ToolsFeatureAirdrop account={account} network={network} />, index: true }, { element: <ToolsFeatureAirdrop account={account} network={network} />, path: 'airdrop' }, { element: (- Line 10
const ToolsFeatureMintToken = lazy(() => import('./tools-feature-mint-token.tsx')): Mints new tokens. The mint authority must sign. - Line 11
const ToolsFeatureStake = lazy(() => import('./tools-feature-stake.tsx')): Touches staking state: the amount locked and when it was locked.
packages/feature-tools/src/tools-feature-stake.tsx L121โ144 @ 6fc3250 ยท MIT ยท original const stakeAccount = accounts.find((item) => item.pubkey.toString() === address) if (!stakeAccount) { return <UiEmpty description="Stake account not found for the active wallet." /> } return ( <ToolsUiStakeAccountDetail account={stakeAccount} actions={actions} authority={authority} close={async () => { const closed = await actions.close(stakeAccount) if (closed) { navigate('/tools/stake/accounts') } }} /> )} function ToolsFeatureStakeCreate({ address, getTransactionSigner,- Line 132
close={async () => {: Closes the account at the end of the instruction and refunds its lamports. - Line 135
navigate('/tools/stake/accounts'): Touches staking state: the amount locked and when it was locked.
packages/feature-tools/src/ui/tools-ui-stake-account-detail.tsx L25โ48 @ 6fc3250 ยท MIT ยท original authority: Address close: () => Promise<void>}) { return ( <div className="space-y-4"> <div className="flex items-center justify-between gap-4"> <div className="flex items-center gap-2"> <UiBackButton aria-label="Back to stake accounts" to="/tools/stake/accounts" /> <div> <div className="text-muted-foreground text-sm">Stake account</div> <ExplorerUiLinkAddress address={account.pubkey} basePath="/explorer" /> </div> </div> <div className="flex items-center gap-2"> <Badge variant={getStakeAccountBadgeVariant(account)}>{getStakeAccountStatus(account)}</Badge> <ToolsUiStakeAccountAction account={account} actions={actions} authority={authority} close={close} /> </div> </div> <Table> <TableBody> <ToolsUiStakeAccountDetailRow label="Balance" value={`${lamportsToSol(account.lamports)} SOL`} /> <ToolsUiStakeAccountDetailRow label="Delegated stake" value={`${formatStakeSol(account)} SOL`} /> <ToolsUiStakeAccountDetailRow label="Validator" value={<ToolsUiStakeValidatorLink account={account} />} /> <ToolsUiStakeAccountDetailRow- Line 32
<UiBackButton aria-label="Back to stake accounts" to="/tools/stake/accounts" />: Touches staking state: the amount locked and when it was locked. - Line 40
<ToolsUiStakeAccountAction account={account} actions={actions} authority={authority} close: Closes the account at the end of the instruction and refunds its lamports.
packages/solana-client/src/create-transfer-instructions-spl.ts L1โ24 @ 6fc3250 ยท MIT ยท originalimport { type Address, assertIsAddress, assertIsTransactionSigner, type Instruction, type TransactionSigner,} from '@solana/kit'import { findAssociatedTokenPda, getTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'import { createGetOrCreateAtaInstruction } from './create-get-or-create-ata-instruction.ts'import type { TransferRecipient } from './transfer-recipient.ts' interface CreateTransferInstructionsSplOptions { decimals: number mint: Address recipients: TransferRecipient[] source?: TransactionSigner tokenProgram?: Address transactionSigner: TransactionSigner} export async function createTransferInstructionsSpl({ decimals, transactionSigner, mint,- Line 8
import { findAssociatedTokenPda, getTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS } fr: A checked token transfer, which also verifies the mint and decimals. - Line 21
export async function createTransferInstructionsSpl({: An SPL token transfer through the Token Program.
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 | turbo run dev |
. | npm run build | turbo run build |
. | npm run test | vitest run |
. | npm run lint | biome check --error-on-warnings |
packages/background | npm run test | vitest run |
packages/db-react | npm run test | vitest run |
packages/ui | npm run test | vitest run |
packages/db | npm run test | vitest run |
packages/solana-client | npm run test | vitest run |
packages/vault-react | npm run test | vitest run |
packages/keypair | npm run test | vitest run |
packages/vault | npm run test | vitest run |
packages/feature-portfolio | npm run test | vitest run |
examples/basic | npm run dev | vite |
examples/basic | npm run build | tsc -b && vite build |
apps/api | npm run dev | wrangler dev |
What you could build from it
- Fork the SuperStack copy and extend it within the Wallets & Identity gaps listed on the idea page.
- Learn the pattern first: Building and sending transactions from a client
- Learn the pattern first: Program Derived Addresses (PDAs)