Full lesson
Bonding_Curve_SOL_contracts
SketchXpress is a Solana-based NFT AMM protocol using bonding curves and Token-Owned Escrows (TOE) for dynamic pricing, direct SOL liquidity, and Tensor marketplace integration.
What it does
From the project README:
[](https://solana.com/) [](https://www.rust-lang.org/) [](https://www.anchor-lang.com/) [](https://www.typescriptlang.org/)
- Original
- SketchXpress/Bonding_Curve_SOL_contracts
- SuperStack fork
- Blockchains/Bonding_Curve_SOL_contracts @
7c60166 - Stars
- 5
- Last push (original)
- 2025-08-19
- Languages
- TypeScript, Rust, JavaScript, Shell, Dockerfile, CSS
- Detected stack
- AnchorMetaplexNext.jsweb3.js
- Categories
- DeFiNFTs & Digital Assets
- Licence
- Apache-2.0
- Homepage
- https://sketchxpress.tech
Architecture
149 source files. By extension: .rs ×56, .ts ×53, .tsx ×32, .js ×7, .py ×1.
| Top-level directory | Source files |
|---|---|
nextjs-frontend | 88 |
programs | 55 |
tests | 4 |
migrations | 1 |
scripts | 1 |
On-chain programs: programs/bonding-curve-system
Dapp: deploying
This fork is queued for its own live dapp on Sol.College, with a Devnet/Mainnet switch. It appears here, at /dapps/bonding-curve-sol-contracts/, 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.
programs/bonding-curve-system/src/instructions/migrate_to_tensor.rs L13–36 @ 7c60166 · Apache-2.0 · original pub authority: Signer<'info>, #[account( mut, seeds = [b"bonding-curve-pool", collection_mint.key().as_ref()], bump = pool.bump, constraint = pool.config.creator == authority.key() @ ErrorCode::InvalidAuthority )] pub pool: Account<'info, BondingCurvePool>, /// CHECK: This is the collection mint used for pool PDA derivation pub collection_mint: UncheckedAccount<'info>, pub system_program: Program<'info, System>,} pub fn migrate_to_tensor(ctx: Context<MigrateToTensor>) -> Result<()> { // Access pool data directly let pool = &mut ctx.accounts.pool; // Verify not already migrated require!( !pool.state.is_migrated_to_tensor, ErrorCode::AlreadyMigrated // Use existing error code if applicable- Line 13
pub authority: Signer<'info>,: Requires this account to have signed the transaction. - Line 17
seeds = [b"bonding-curve-pool", collection_mint.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 18
bump = pool.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 34
require!(: An explicit validation that aborts the instruction with an error.
programs/bonding-curve-system/src/instructions/sell_nft.rs L121–144 @ 7c60166 · Apache-2.0 · original let net_amount_to_seller_calculated = available_lamports .checked_sub(sell_fee_calculated) .ok_or(ErrorCode::MathOverflow)?; let total_payout_amount = sell_fee_calculated .checked_add(net_amount_to_seller_calculated) .ok_or(ErrorCode::MathOverflow)?; if escrow_total_lamports < total_payout_amount .checked_add(rent_exempt_minimum) .ok_or(ErrorCode::MathOverflow)? { return err!(ErrorCode::InsufficientEscrowBalance); } let final_amount_to_seller_transfer = net_amount_to_seller_calculated .checked_add(rent_exempt_minimum) .ok_or(ErrorCode::MathOverflow)?; let nft_mint_key = ctx.accounts.nft_mint.key(); let escrow_bump = ctx.accounts.escrow.bump; let seeds = &[- Line 123
.checked_sub(sell_fee_calculated): Uses checked arithmetic so overflow fails instead of wrapping. - Line 135
return err!(ErrorCode::InsufficientEscrowBalance);: An explicit validation that aborts the instruction with an error. - Line 143
let escrow_bump = ctx.accounts.escrow.bump;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 144
let seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
programs/bonding-curve-system/src/instructions/create_pool.rs L1–24 @ 7c60166 · Apache-2.0 · originaluse anchor_lang::prelude::*; use crate::state::BondingCurvePool; #[derive(AnchorSerialize, AnchorDeserialize, Clone)]pub struct CreatePoolArgs { pub base_price: u64, pub growth_factor: u64,} #[derive(Accounts)]pub struct CreatePool<'info> { #[account(mut)] pub creator: Signer<'info>, /// CHECK: This is the collection mint for the NFTs pub collection_mint: UncheckedAccount<'info>, #[account( init, payer = creator, space = BondingCurvePool::SPACE, seeds = [b"bonding-curve-pool", collection_mint.key().as_ref()], bump- Line 14
pub creator: Signer<'info>,: Requires this account to have signed the transaction. - Line 20
init,: Creates and pays for a new account. Space and payer must be specified. - Line 23
seeds = [b"bonding-curve-pool", collection_mint.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 24
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/bonding-curve-system/src/instructions/cancel_bid.rs L49–72 @ 7c60166 · Apache-2.0 · original /// CHECK: This account holds the SOL for the bid #[account( mut, seeds = [b"bid-escrow", bid.key().as_ref()], bump, )] pub bid_escrow: UncheckedAccount<'info>, pub system_program: Program<'info, System>,} pub fn cancel_bid( ctx: Context<CancelBid>, args: CancelBidArgs,) -> Result<()> { let bid_id = args.bid_id; let current_timestamp = Clock::get()?.unix_timestamp; let bid = &mut ctx.accounts.bid; let bid_listing = &mut ctx.accounts.bid_listing; // Check if bid can be cancelled require!( bid.can_cancel(&ctx.accounts.bidder.key()), ErrorCode::CannotCancelBid- Line 52
seeds = [b"bid-escrow", bid.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 53
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 65
let current_timestamp = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar. - Line 70
require!(: An explicit validation that aborts the instruction with an error.
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 | cd nextjs-frontend && npm run dev |
. | npm run start | cd nextjs-frontend && npm run start |
. | npm run build | anchor build && cd nextjs-frontend && npm run build |
. | npm run test | anchor test |
. | npm run lint | cd nextjs-frontend && npm run lint |
. | npm run deploy | anchor deploy |
nextjs-frontend | npm run dev | next dev |
nextjs-frontend | npm run start | cross-env NODE_ENV=production node server.js |
nextjs-frontend | npm run build | next build |
nextjs-frontend | npm run lint | next lint |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | echo 'Tests should be run from the frontend directory with npm test' |
. | cargo build | Rust workspace build |
. | cargo test | Rust tests |
What you could build from it
- Fork the SuperStack copy and extend it within the DeFi gaps listed on the idea page.
- Learn the pattern first: Program Derived Addresses (PDAs)
- Learn the pattern first: Cross-Program Invocations (CPIs)
- Learn the pattern first: SPL Tokens: mints, accounts and transfers