Beam: Project Story ⚡

The story behind building a non-custodial Solana payment protocol that lets anyone pay with any token and settle in exact USDC.


Inspiration

It started with a simple frustration. As a freelancer and open-source contributor, I wanted to get paid in USDC—stable, predictable, no volatility. But my clients? They held SOL, BONK, JUP, RAY.

Every payment became a chore:

  1. Client goes to an exchange or DEX
  2. Swaps their token → USDC (paying fees, slippage)
  3. Sends USDC to my wallet
  4. I wait, verify, confirm

Why isn't there a "Stripe for Solana" where the payer uses whatever they have and the recipient gets exactly what they asked for?

That question became Beam.


What It Does

Beam is a non-custodial Solana payment-link protocol and dApp. Merchants and creators specify an exact amount in USDC (e.g. $10.00 USDC), while payers can check out using any token in their Solana wallet (SOL, JUP, BONK, RAY, USDT, PYTH, etc.).

Beam calculates the exact route via Jupiter Aggregator and constructs a single atomic Solana Versioned Transaction (v0) that:

  1. Swaps the input token → exact USDC amount (exact-out)
  2. Creates the recipient's USDC Associated Token Account (idempotent)
  3. Transfers the exact requested USDC to the recipient

If anything slips or fails, the entire transaction reverts atomically—ensuring complete safety for both parties. No partial states. No trust required.

The Atomic Transaction Formula

$$ \text{Single Tx} = \begin{cases} \text{DEX Swap (exact-out via Jupiter)} \

  • \text{Recipient ATA Creation (idempotent)} \
  • \text{TransferChecked (exact USDC amount)} \end{cases} $$

How We Built It

Architecture Overview

┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   Payer     │────►│  Atomic Tx (v0)  │────►│  Recipient      │
│  (any SPL)  │     │ 1. Jupiter Swap  │     │  Exact USDC     │
└─────────────┘     │ 2. ATA Create    │     └─────────────────┘
                    │ 3. Transfer USDC │
                    └──────────────────┘

Key Components

Component Purpose Tech
Intent Store Persist payment requests (amount, recipient, expiry) In-memory + JSON backup
Quote Engine Calculate exact-in for exact-out USDC via Jupiter /quote + /swap APIs
Tx Composer Decompile Jupiter's v0 tx, append ATA + transfer, recompile with ALTs @solana/web3.js v2, VersionedTransaction
Verifier On-chain delta verification via parsed token balance changes getParsedTransaction + RPC

The Composer: Where the Magic Happens

The hardest part wasn't calling Jupiter—it was stitching the result into a valid v0 transaction with:

  1. Address Lookup Tables (ALTs) — Jupiter returns ALTs for compressed accounts. We preserve and extend them.
  2. Idempotent ATA Creation — createAssociatedTokenAccountIdempotentInstruction so recipient doesn't need pre-existing USDC ATA.
  3. TransferChecked — Enforces exact decimals + amount, prevents over/under-transfer.
  4. Fresh Blockhash + Signatures — Recompile with payer as fee-payer, ready for wallet signing.
// Simplified flow in src/swap/composer.ts
const jupiterTx = await jupiterClient.swap({ 
  inputMint, 
  outputMint: USDC_MINT, 
  exactOut: targetAmountRaw 
});

// Decompile → Append instructions → Recompile with ALTs
const composedTx = composeAtomicTx({
  jupiterTx,
  recipient,
  targetAmountRaw,
  payerPublicKey
});

On-Chain Verification

The backend re-fetches the transaction via getParsedTransaction and computes:

$$ \Delta \text{USDC}_{\text{recipient}} = \text{postTokenBalances} - \text{preTokenBalances} $$

Only if $\Delta \geq \text{targetAmountRaw}$ do we mark intent.status = "paid".


Challenges We Ran Into

1. Jupiter v6 API Changes Mid-Development

Jupiter migrated from v6 to v6.1 with breaking changes in response formats.

Solution: Abstracted behind JupiterClient interface. Swapped implementations in one file. Added integration tests against live devnet.

2. ALTs Mismatch on Recompile

Initial composer failed with AddressLookupTableAccountInvalid because we weren't preserving all ALTs from Jupiter's response.

Solution: Extract addressLookupTableAccounts from Jupiter's VersionedTransaction, merge with any new ones (e.g., for recipient ATA), pass full array to compileToV0Message.

3. Phantom Wallet Signing v0 Transactions

Early versions: Phantom prompted "Sign Transaction" but returned Transaction not VersionedTransaction.

Solution: Detect wallet adapter version. Use signAndSendTransaction for v0-aware wallets; fallback to legacy for older versions.

4. RPC Rate Limits on Verification

getParsedTransaction is heavy. On devnet, quick retries hit rate limits.

Solution: Exponential backoff + configurable RPC endpoint (Helius/QuickNode). Added verify endpoint idempotency key.

5. Decimal Precision Hell

SPL tokens: 6 decimals (USDC), 9 decimals (SOL), variable for others. TransferChecked requires exact decimals.

Solution: Token metadata cache (token-catalog.json) with mint → decimals mapping. Validate on quote + build.


Accomplishments We're Proud Of

  • True atomic settlement — One transaction, exact USDC out, or full revert. No custodial risk, no partial failures.
  • Exact-out pricing with slippage protection — Payers never overpay; surplus stays in their wallet.
  • Zero-config dev experience — bun install → bun run dev → working on localhost:3000 in seconds.
  • On-chain verification, not webhook trust — Cryptographic proof of settlement via RPC-parsed balance deltas.
  • Versioned Transaction (v0) composition from scratch — Decompiling Jupiter's tx, extending with ATA creation + TransferChecked, recompiling with ALTs and fresh blockhash.
  • Preloaded demo intents — Coffee ($5) and Invoice ($25) links work instantly for hackathon demos.
  • Full TypeScript + Bun stack — Type-safe end-to-end, native testing, 10x faster installs.

What We Learned

1. Exact-Out Math Is Subtle

Jupiter's /quote with swapMode: "ExactOut" returns the minimum input for a given output. But on-chain, prices move. We added a slippage buffer (default 1%):

$$ \text{inputAmount} = \text{quote.inputAmount} \times (1 + \text{slippageBps} / 10000) $$

Surplus tokens? Stay in payer's wallet. The swap is exact-out; any favorable price movement benefits the payer.

2. Versioned Transactions Are Powerful But Fragile

  • ALTs must match exactly what the runtime expects
  • Message decompilation/recompilation loses signature data—must rebuild from scratch
  • TransactionMessage.decompile() + compileToV0Message() is the pattern

3. On-Chain Verification > Webhook Trust

We don't trust the frontend. The backend re-fetches the transaction and computes balance deltas. Only cryptographic proof marks an intent paid.

4. Bun + TypeScript = Delightful DX

  • Native bun test, bun --watch, zero-config TypeScript
  • bun install is 10x faster than npm
  • Top-level await in scripts—no wrapper functions

What's Next for Beam

Feature Status
Multi-recipient splits (e.g., 80% merchant, 20% platform) 🔄 Design
Recurring intents (subscriptions via cron + atomic tx) 📋 Planned
Cross-chain via Wormhole (Ethereum USDC → Solana USDC) 💭 Research
Mobile-first PWA (offline intent creation, QR checkout) 🎨 Design
Merchant dashboard (analytics, refunds, webhooks) 📋 Planned

Acknowledgments

  • Jupiter Aggregator — The best swap infrastructure on Solana
  • Solana Foundation — For Versioned Transactions, ALTs, and TransferChecked
  • Bun Team — For making TypeScript feel like a scripting language
  • Phantom & Solflare — For first-class v0 transaction signing UX

Built with ☕, 🌙, and the belief that payments should be as composable as the rest of crypto.

Built With

Share this project:

Updates

Submission history