Overview
Add Solana as a supported chain on CryptoBazaar. Unlike BSC/Polygon, Solana is not EVM — the escrow program must be written in Rust using the Anchor framework, and the frontend integration uses a completely different wallet stack (@solana/wallet-adapter-react + Phantom). This is the most significant engineering effort of all chain expansions.
Why Solana
Solana has become a dominant chain for USDT/USDC in India, driven by extremely low fees (~$0.00025/tx) and fast finality (~400ms). Many Indian traders moved from Ethereum/Polygon to Solana after the 2021-22 gas crisis. Supporting Solana opens CryptoBazaar to a large segment of the Indian stablecoin market.
Architecture Differences vs EVM
| Concern |
EVM (Polygon/BSC) |
Solana |
| Smart contract language |
Solidity |
Rust (Anchor) |
| Token standard |
ERC-20 |
SPL Token |
| Token custody pattern |
approve() + transferFrom() |
PDA-owned Associated Token Account |
| State storage |
Contract storage mapping |
Per-order PDA account |
| Wallet |
MetaMask (ThirdWeb) |
Phantom / Solflare (@solana/wallet-adapter) |
| Gas token |
MATIC / BNB |
SOL |
| Order identity |
uint256 id auto-increment |
PDA derived from [b"order", seller_pubkey, order_nonce] |
| Frontend SDK |
ThirdWeb v5 |
@solana/web3.js + @coral-xyz/anchor |
Part 1 — Rust/Anchor Escrow Program
Directory structure
contracts/
evm/ ← existing Foundry project
solana/ ← new Anchor workspace
programs/
cryptobazaar-escrow/
src/
lib.rs
instructions/
create_order.rs
lock_order.rs
mark_paid.rs
confirm_payment.rs
cancel_order.rs
timeout_cancel.rs
raise_dispute.rs
resolve_dispute.rs
state.rs
errors.rs
Anchor.toml
Cargo.toml
tests/
escrow.ts ← Anchor test suite (TypeScript)
State account (state.rs)
#[account]
pub struct EscrowOrder {
pub seller: Pubkey, // 32
pub buyer: Option<Pubkey>, // 33
pub token_mint: Pubkey, // 32
pub amount: u64, // 8
pub price_inr: u64, // 8 (paise, e.g. 8900 = ₹89.00)
pub status: OrderStatus, // 1
pub locked_at: i64, // 8 (Unix timestamp)
pub paid_at: i64, // 8
pub bump: u8, // 1
pub nonce: u64, // 8 (seller's per-order counter)
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum OrderStatus {
Open,
Locked,
Paid,
Disputed,
Completed,
Cancelled,
}
PDA derivation
Each order is a PDA:
seeds = [b"order", seller.key().as_ref(), &nonce.to_le_bytes()]
The escrow's SPL token vault is a separate PDA ATA:
seeds = [b"vault", order_pda.key().as_ref()]
The vault is a token account owned by the program PDA — no approve() needed. Tokens are transferred directly into the vault on create_order using transfer_checked.
Key instructions
create_order — Seller deposits tokens into the PDA vault.
Accounts: seller (signer), token_mint, seller_ata, vault_ata (PDA), order_account (PDA), token_program, system_program
lock_order — Buyer records themselves on the order account. No token movement.
Accounts: buyer (signer), order_account (PDA, mut), system_program
mark_paid — Buyer changes status to Paid.
Accounts: buyer (signer, must == order.buyer), order_account (PDA, mut)
confirm_payment — Seller releases vault → buyer ATA, fee → insurance fund ATA.
Accounts: seller (signer), order_account (PDA, mut), vault_ata, buyer_ata, insurance_fund_ata, token_program
timeout_cancel — Seller reclaims vault after 30 min with no markPaid.
Accounts: seller (signer), order_account (PDA, mut), vault_ata, seller_ata, token_program
cancel_order — Seller reclaims while still Open.
Accounts: seller (signer), order_account (PDA, mut), vault_ata, seller_ata, token_program
raise_dispute — Seller or buyer escalates after markPaid.
Accounts: raiser (signer, must be seller or buyer), order_account (PDA, mut)
resolve_dispute — Admin sends vault to winner.
Accounts: admin (signer), order_account (PDA, mut), vault_ata, winner_ata, token_program
Devnet test tokens
- USDT devnet:
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU (Circle's devnet USDT)
- USDC devnet:
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU (same faucet endpoint)
- Get test SOL + tokens:
solana airdrop 2 on devnet, then use Circle's devnet faucet for USDC.
Part 2 — Frontend Integration
New dependencies
npm install @solana/web3.js @solana/wallet-adapter-react \
@solana/wallet-adapter-phantom @solana/spl-token \
@coral-xyz/anchor
Wallet context — separate from ThirdWeb
Solana wallets (Phantom, Solflare) are completely separate from MetaMask/ThirdWeb. Add a SolanaWalletProvider component that wraps Solana-specific pages:
// components/SolanaWalletProvider.tsx
"use client";
import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
const wallets = [new PhantomWalletAdapter()];
export function SolanaWalletProvider({ children }) {
return (
<ConnectionProvider endpoint={process.env.NEXT_PUBLIC_SOLANA_RPC_URL}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
}
This provider is only mounted on Solana trade pages — it does not replace ThirdWeb for EVM pages.
isEvm guard already exists
The trade page already distinguishes chains:
const isEvm = order.chain === "POLYGON" || order.chain === "BSC";
// Solana: isEvm === false → no ThirdWeb wallet check, no MetaMask popups
The Solana trade page will need a parallel walletOk check using useWallet() from @solana/wallet-adapter-react.
Calling the program
Use the Anchor-generated IDL + @coral-xyz/anchor Program class. The IDL is output by anchor build and checked into contracts/solana/target/idl/cryptobazaar_escrow.json.
import { Program, AnchorProvider } from "@coral-xyz/anchor";
import idl from "@/contracts/cryptobazaar_escrow.json";
const provider = new AnchorProvider(connection, wallet, {});
const program = new Program(idl, provider);
await program.methods
.lockOrder()
.accounts({ buyer: wallet.publicKey, orderAccount: orderPda })
.rpc();
Sell page — Solana chain selection
When BSC/Solana selector is built (see issue #10), choosing Solana will:
- Render a Phantom connect button instead of the MetaMask reconnect banner.
- Derive a new order PDA from
[b"order", sellerPubkey, nonce].
- Call
create_order via the Anchor program client.
- Store the PDA address (base58) as
orderId in the DB.
Part 3 — DB / API Changes
The existing DB chain enum already includes SOLANA. Two small additions needed:
orderId for Solana orders will be the PDA pubkey (base58 string, 44 chars) — already fits in the existing String field.
escrowContractAddress for Solana will be the program ID (not a contract address) — same field, same type.
No schema migration needed.
Test Procedure
solana config set --url devnet, airdrop 2 SOL to test wallets.
- Mint devnet USDC to the seller wallet via Circle faucet.
anchor test — all 8 instruction tests should pass against devnet.
- Open
/marketplace/sell in a browser with Phantom installed, select Solana, post an order — confirm 1 popup (no approve, tokens go direct to vault PDA).
- Open the order as a buyer with a second Phantom wallet — lock, markPaid, confirm each triggers exactly 1 Phantom popup.
- Confirm payment as seller — verify buyer Phantom wallet receives USDC.
- Verify
✓ on-chain ↗ link points to Solana Explorer (explorer.solana.com?cluster=devnet).
Files to Create / Touch
| Path |
Action |
contracts/solana/ |
New Anchor workspace (full) |
contracts/solana/programs/cryptobazaar-escrow/src/ |
Rust program |
contracts/solana/tests/escrow.ts |
Anchor test suite |
frontend/src/components/SolanaWalletProvider.tsx |
New wallet context |
frontend/src/app/marketplace/sell/page.tsx |
Solana chain option, Phantom connect |
frontend/src/app/marketplace/[id]/page.tsx |
Solana branch (useWallet, Anchor calls) |
frontend/src/lib/explorer.ts |
Solana Explorer URL |
.env.example |
NEXT_PUBLIC_SOLANA_RPC_URL, NEXT_PUBLIC_SOLANA_PROGRAM_ID, NEXT_PUBLIC_SOLANA_USDC_MINT |
Estimated Effort
| Phase |
Work |
| Rust/Anchor program |
3–5 days |
| Anchor test suite |
1–2 days |
| Frontend wallet adapter + sell page |
1–2 days |
| Frontend trade page Solana branch |
2–3 days |
| End-to-end devnet testing |
1 day |
| Total |
~8–13 days |
Overview
Add Solana as a supported chain on CryptoBazaar. Unlike BSC/Polygon, Solana is not EVM — the escrow program must be written in Rust using the Anchor framework, and the frontend integration uses a completely different wallet stack (
@solana/wallet-adapter-react+ Phantom). This is the most significant engineering effort of all chain expansions.Why Solana
Solana has become a dominant chain for USDT/USDC in India, driven by extremely low fees (~$0.00025/tx) and fast finality (~400ms). Many Indian traders moved from Ethereum/Polygon to Solana after the 2021-22 gas crisis. Supporting Solana opens CryptoBazaar to a large segment of the Indian stablecoin market.
Architecture Differences vs EVM
approve()+transferFrom()@solana/wallet-adapter)uint256 idauto-increment[b"order", seller_pubkey, order_nonce]@solana/web3.js+@coral-xyz/anchorPart 1 — Rust/Anchor Escrow Program
Directory structure
State account (
state.rs)PDA derivation
Each order is a PDA:
The escrow's SPL token vault is a separate PDA ATA:
The vault is a token account owned by the program PDA — no
approve()needed. Tokens are transferred directly into the vault oncreate_orderusingtransfer_checked.Key instructions
create_order— Seller deposits tokens into the PDA vault.lock_order— Buyer records themselves on the order account. No token movement.mark_paid— Buyer changes status to Paid.confirm_payment— Seller releases vault → buyer ATA, fee → insurance fund ATA.timeout_cancel— Seller reclaims vault after 30 min with no markPaid.cancel_order— Seller reclaims while still Open.raise_dispute— Seller or buyer escalates after markPaid.resolve_dispute— Admin sends vault to winner.Devnet test tokens
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU(Circle's devnet USDT)4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU(same faucet endpoint)solana airdrop 2on devnet, then use Circle's devnet faucet for USDC.Part 2 — Frontend Integration
New dependencies
Wallet context — separate from ThirdWeb
Solana wallets (Phantom, Solflare) are completely separate from MetaMask/ThirdWeb. Add a
SolanaWalletProvidercomponent that wraps Solana-specific pages:This provider is only mounted on Solana trade pages — it does not replace ThirdWeb for EVM pages.
isEvmguard already existsThe trade page already distinguishes chains:
The Solana trade page will need a parallel
walletOkcheck usinguseWallet()from@solana/wallet-adapter-react.Calling the program
Use the Anchor-generated IDL +
@coral-xyz/anchorProgramclass. The IDL is output byanchor buildand checked intocontracts/solana/target/idl/cryptobazaar_escrow.json.Sell page — Solana chain selection
When BSC/Solana selector is built (see issue #10), choosing Solana will:
[b"order", sellerPubkey, nonce].create_ordervia the Anchor program client.orderIdin the DB.Part 3 — DB / API Changes
The existing DB
chainenum already includesSOLANA. Two small additions needed:orderIdfor Solana orders will be the PDA pubkey (base58 string, 44 chars) — already fits in the existingStringfield.escrowContractAddressfor Solana will be the program ID (not a contract address) — same field, same type.No schema migration needed.
Test Procedure
solana config set --url devnet, airdrop 2 SOL to test wallets.anchor test— all 8 instruction tests should pass against devnet./marketplace/sellin a browser with Phantom installed, select Solana, post an order — confirm 1 popup (noapprove, tokens go direct to vault PDA).✓ on-chain ↗link points to Solana Explorer (explorer.solana.com?cluster=devnet).Files to Create / Touch
contracts/solana/contracts/solana/programs/cryptobazaar-escrow/src/contracts/solana/tests/escrow.tsfrontend/src/components/SolanaWalletProvider.tsxfrontend/src/app/marketplace/sell/page.tsxfrontend/src/app/marketplace/[id]/page.tsxfrontend/src/lib/explorer.ts.env.exampleNEXT_PUBLIC_SOLANA_RPC_URL,NEXT_PUBLIC_SOLANA_PROGRAM_ID,NEXT_PUBLIC_SOLANA_USDC_MINTEstimated Effort