An Anchor program for Solana that runs a single-sided SOL-to-SPL-token swap pool at a fixed price. A pool authority creates a pool for one token mint, liquidity is deposited into the pool's token vault, and users swap SOL for that token at the price set at pool creation. Withdrawals from the pool require both the pool authority and a separate master authority to sign.
The program (programs/swap) is built around one PDA-owned config account and
two PDA-owned vaults, all derived from the same authority and
token_mint_address:
| Account | Seeds | Purpose |
|---|---|---|
pool_config_account |
["pool_config_account_seed", authority, token_mint_address] |
Stores token_price, is_active, the mint, the two vault addresses, authority, and master_authority. |
pool_token_account |
["pool_token_account_seed", authority, token_mint_address, pool_config_account] |
SPL token account, owned by the pool_config_account PDA. Holds the token side of the pool's liquidity. |
pool_native_account |
["pool_native_account_seed", authority, token_mint_address, pool_config_account] |
Plain system account, owned by the pool_config_account PDA. Holds the SOL collected from swaps. |
Instructions (programs/swap/src/instructions/):
init_instruction(init_pool.rs) — createspool_config_account,pool_token_account, andpool_native_account, and recordsauthorityandmaster_authorityon the config account. Takestoken_price: u64, the number of base token units paid out perLAMPORTS_PER_SOL(1 SOL) of input — the program does raw integer math and does not adjust for the mint's decimals. Onlyauthoritysigns;master_authorityis recorded but does not need to sign pool creation.add_liquid_instruction(add_liquid.rs) — transfersamountof the pool's token fromdepositor_token_accountintopool_token_account. Any signer holding the token can call this. There is no LP receipt or share accounting: a deposit cannot be withdrawn by the depositor, only bymaster_authority.swap_token(swap_token.rs) — a user sendslamport_amountof SOL intopool_native_accountand receivestoken_price * lamport_amount / LAMPORTS_PER_SOLof the token frompool_token_account, into an associated token account created for them if needed. Fails ifpool_config_account.is_activeisfalse.update_config_instruction(update_pool_config.rs) — setspool_config_account.is_activeto the givendisable: boolargument. Onlyauthoritycan call it.withdraw_native_instruction/drain_native_instruction(withdraw_native_pool.rs) — move a given amount, or the full balance, of SOL out ofpool_native_accounttomaster_authority. Require bothauthorityandmaster_authorityto sign.withdraw_token_instruction/drain_token_instruction(withdraw_token_pool.rs) — move a given amount, or the full balance, of the pool token out ofpool_token_accountto an associated token account formaster_authority. Require bothauthorityandmaster_authorityto sign.
In short: liquidity deposits are permissionless, but only the pool's
authority and master_authority together can move funds back out.
The versions below are the ones this program is verified to build with.
rust-toolchain.toml pins the Rust channel and Anchor.toml pins the Anchor
version, so a fresh clone should resolve most of this automatically.
- Rust 1.96.0, pinned by
rust-toolchain.tomland installed by rustup - Solana CLI 2.2.0 (Agave) — install guide
- Anchor CLI 0.32.1, installed and pinned with
avm:
avm install 0.32.1 && avm use 0.32.1 - Node.js 20.x
The Anchor CLI version must match the anchor-lang version in
programs/swap/Cargo.toml. A mismatch produces an IDL the TypeScript client
cannot read.
git clone https://github.com/defi0x1/token-swap-program.git
cd token-swap-program
npm installAnchor needs a wallet keypair at the path set in Anchor.toml's
[provider] wallet. If you don't already have one at that path, generate
it:
solana-keygen new -o ~/.config/solana/id.jsonBuild the program and generate its IDL:
anchor buildThe program keypair lives in target/, which is not committed, so the first
build on a new machine generates a fresh program ID that will not match the one
in Anchor.toml and declare_id!. Bring all three back into agreement:
anchor keys sync
anchor buildThis is normal Anchor behaviour, not a defect. Only a deployed program needs a stable ID, and that keypair should be kept outside the repository.
Start a local validator in one terminal:
solana-test-validator --resetIn another terminal, run the test suite against it:
anchor test --skip-local-validatorThis builds the program, deploys it to the running local validator, and
runs tests/swap.ts, which airdrops SOL to fresh keypairs, creates a token
mint, initializes a pool, and exercises add-liquidity, swap, withdraw, and
drain, including their failure cases.
All accounts used in the tests — the pool authority, the user, and the
master authority — are keypairs generated at runtime with
anchor.web3.Keypair.generate() and funded with solana-test-validator
airdrops. No private key is committed to this repository.
This is example and educational code for building an Anchor-based swap program. It has not been audited. Do not deploy it to Solana mainnet with real funds.
See SECURITY.md for how to report a vulnerability.

