Skip to content

mppsol/soltempo

Repository files navigation

soltempo

Solana is where the next wave of payment merchants earns yield. soltempo is the first proof, live on devnet.

Stripe merchants today earn $0 on operating balances. soltempo is a working cross-VM yield account that routes idle merchant USDC from Tempo (the Stripe + Paradigm payment L1, mainnet 2026-03-18, with Visa as anchor validator) into Solana DeFi (Kamino) — and pulls it back on demand for payouts. A full settle cycle costs 5,000 lamports ($0.001) on Solana; the same flow on Ethereum L1 would cost $5–50 and break the unit economics. Solana is the only L1 where Stripe-grade merchant volume can be served profitably.

Live on Solana devnet:

  • Vault program: 2YhYmfCoCj3VvyN2HQ3cuavMiZzEUdUTrhvo6nmGRXe3 — 8 instructions, IDL upgraded, vault PDA 8smibhXARvuYGEadHqc9C9tqTJWFmLUdXAtkabtFaA9M
  • End-to-end cross-VM payout tx: 24CJ82M…mK5d
  • 37 vault unit tests passing + 9 Foundry tests on the Tempo side
  • Composes with Kamino, mppsol_cpi, and Chainlink CCIP via real CPIs — no mocks

Demo

Primary submission video (3:05): demo-video/output/demo-v0.3-terminal.mp4 — full cross-VM cycle on live testnets (Tempo Moderato + Solana devnet). All transactions verifiable on public block explorers. Opening → terminal screencast → Kamino allocation proof → closing.

Browser cut (2:47): demo-video/output/demo-v0.3.mp4 — same flow driven through the merchant-web dashboard.

Voiceover scripts (per-segment + continuous): demo-video/voiceover/ and demo-video/voiceover-plain.txt. Recording playbooks: DEMO.md (terminal cut), demo-video/record-merchant-web.mjs (browser cut), demo-video/kamino-proof.tape (VHS for the Kamino segment).

The problem

Stripe merchants earn 0% on operating balances. Stripe processes ~$1T+ annual payment volume across ~4M merchants. Industry-standard operating-balance ratios put $20–40B sitting idle at any moment. None of it earns yield, because integrating DeFi is operationally untenable for non-crypto-native merchants — wrong UX, wrong tools, wrong risk model.

Tempo (Stripe + Paradigm's Reth-based payment L1) launched mainnet on 2026-03-18 with merchant onboarding as its core thesis. Visa is anchor validator. The merchant base is coming. The yield layer is the missing piece — and it has to be on Solana.

New to Tempo or unsure how mppsol relates to @solana/mpp? See TEMPO-FAQ.md — 60-second briefing for Solana judges.

The solution

soltempo gives merchants a single button: earn yield on idle USDC, pull back on demand for payouts. Behind the button:

  1. Merchant deposits USDC into a Tempo Buffer.sol contract
  2. Above a configurable threshold, a canonical 122-byte intent is sent cross-chain (Chainlink CCIP) to Solana
  3. A Solana Anchor vault receives, allocates to Kamino's USDC reserve via real 17-account CPI
  4. On payout, the vault withdraws from Kamino, invokes mppsol_cpi.pay_with_receipt for an auditable on-chain Receipt PDA bound to the cross-VM nonce, and emits a PullbackRequested event for Tempo settlement

Why Solana, specifically

This product is not chain-agnostic. Solana is essential to the unit economics, the UX, and the composability:

Property Solana Ethereum L1
Settlement cost per cycle 5,000 lamports ($0.001) $5–50
Finality 400ms 12+ minutes
Stablecoin DeFi composability Real CPI into Kamino (17-account ix), Marginfi, Drift in one tx External calls + cross-protocol fragmentation
Minimum viable merchant balance $10K $1M+
Mass-merchant viability Yes No

A 5,000-lamport settlement cost is what makes the business model viable from $10K merchant balances upward. The same flow on Ethereum L1 would require $1M+ minimum balances to clear fees. Solana is the only chain where Stripe-grade merchant volume can be served profitably and at sub-second UX. soltempo is built around that fact, not despite it.

Architecture

   ┌──────────────────────┐
   │ Merchant USDC balance │
   │     on Tempo (EVM)    │
   └──────────┬────────────┘
              │ deposit
              ▼
   ┌──────────────────────┐         ┌────────────────────┐
   │   Buffer.sol on Tempo │ ──CCIP─▶│  Vault on Solana   │
   │ - holds liquid buffer │         │  - receives intent │
   │ - emits intent above  │         │  - allocates USDC  │
   │   threshold via CCIP  │         │    to Kamino USDC  │
   └──────────────────────┘         └─────────┬──────────┘
                                              │
                                              │ CPI
                                              ▼
                                    ┌────────────────────┐
                                    │    mppsol_cpi      │
                                    │ pay_with_receipt   │
                                    │ → Receipt PDA      │
                                    │   bound to Tempo   │
                                    │   origin           │
                                    └────────────────────┘

Reverse path (payout): merchant requests payout → keeper triggers vault to withdraw from Kamino → vault calls mppsol_cpi.pay_with_receipt for the settlement Receipt → PullbackRequested event consumed by keeper → CCIP/trusted-relay message back to Tempo Buffer.sol → merchant withdraws.

Status — live on testnet

All v0.3 components on testnet. Vault upgraded to program data length 346,616 bytes at slot 461142588 (devnet). IDL at 4y5DcbBJgnuqe5fcSwEm8MFr6ATJ2oUvr9zq6EHodrAr.

Component Status
Vault on Solana devnet ✅ deployed — program 2YhYmfCoCj3VvyN2HQ3cuavMiZzEUdUTrhvo6nmGRXe3, vault PDA 8smibhXARvuYGEadHqc9C9tqTJWFmLUdXAtkabtFaA9M. 8 instructions live: initialize, ccip_receive, trusted_keeper_receive, deposit_to_kamino, init_kamino_obligation, withdraw_from_kamino, settle_payout_to_tempo, request_pullback_to_tempo. 37 unit tests passing.
Kamino CPI integration deposit_to_kamino + init_kamino_obligation wire real CPIs to klend's deposit_reserve_liquidity_and_obligation_collateral_v2, init_obligation, init_user_metadata. 17-account context mirrors klend's upstream layout exactly. End-to-end exercised on localnet via scripts/localnet-with-klend.sh (klend ships mainnet-only — see localnet note below).
mppsol_cpi CPI integration settle_payout_to_tempo invokes mppsol_cpi.pay_with_receipt via invoke_signed with the vault PDA as signer — Receipt PDA bound to the cross-VM nonce on every settlement.
CCIP receiver hardening ✅ canonical 3-account pattern (offramp signer PDA + allowlist PDA owned by router) matching chainlink-ccip example-ccip-receiver solana-v1.6.
Pull-back path request_pullback_to_tempo emits PullbackRequested event with full canonical intent — validated end-to-end on devnet (tx 24CJ82M…mK5d).
Buffer.sol on Tempo Moderato ✅ deployed — 0xe8c675523AFd81587c35Da2BeF6ECc268654D0BE, with mock CCIP router + canonical CrossVMIntent send/receive. 9 Foundry tests passing.
CrossVMIntent canonical encoding ✅ fixed 122-byte big-endian layout shared across Solidity / Rust / TS. Cross-language test vector validated in all three.
Off-chain keeper ✅ trusted-relayer mode running against the reference deploy — observes Tempo MockMessageSent, transfers SPL USDC from inventory, calls trusted_keeper_receive.
Merchant frontend ✅ Next.js dashboard at apps/merchant-web/. Live cards, deposit + bridge button, pull-back button. Both buttons validated end-to-end against testnet via puppeteer.
End-to-end cross-VM demo ✅ recorded — see Demo.

Composability — built from Solana primitives, exposes Solana primitives

soltempo is built from public Solana programs via real CPIs and exposes its own primitives as composable building blocks for downstream Solana protocols.

CPI in (what soltempo composes with):

  • klend.deposit_reserve_liquidity_and_obligation_collateral_v2deposit_to_kamino invokes Kamino via CPI with the vault PDA as obligation owner. 17-account context mirrors klend's upstream exactly — including the legacy placeholder_user_destination_collateral slot and the optional farm-accounts trio (obligation_farm_user_state / reserve_farm_state / farms_program).
  • klend.init_obligation + init_user_metadatainit_kamino_obligation wraps both in one ix so a vault is Kamino-ready in a single tx.
  • mppsol_cpi.pay_with_receipt — every settlement invokes mppsol_cpi to mint a Receipt PDA bound to the cross-VM nonce. Vault PDA signs as payer_authority via invoke_signed. Manual instruction client (no Cargo dep) keeps soltempo independently cloneable.
  • Chainlink CCIP receiver patternccip_receive follows the canonical 3-account security pattern (offramp signer PDA + allowlist PDA owned by router) verified against chainlink-ccip example-ccip-receiver solana-v1.6.

CPI out (what soltempo exposes):

  • vault.request_pullback_to_tempo(amount, nonce) — emits PullbackRequested with the full 122-byte CrossVMIntent. Any downstream Solana protocol holding USDC in a soltempo vault can request a Tempo payout through this single instruction; the keeper handles the EVM-side settlement.
  • vault.settle_payout_to_tempo — atomic composition of mppsol_cpi.pay_with_receipt + PullbackRequested emit + USDC transfer in one ix. Other Solana programs can wrap this for payment-binding workflows.
  • CrossVMIntentPayload (122-byte canonical encoding) — shared layout between Solidity, Rust, and TS. Any Solana ↔ EVM bridge can adopt the same encoding for cross-VM intents.

Drift-catcher tests (15 tests) re-derive every external Anchor discriminator (Kamino, mppsol_cpi, CCIP) from sha256("global:<name>")[..8] at test time. Anyone forking soltempo and seeing those tests pass has cryptographic confirmation that the on-chain klend / mppsol_cpi / CCIP discriminators haven't drifted from upstream.

Reference protocols that could compose:

  • A multi-venue yield aggregator could CPI into multiple vault::deposit_to_kamino-like adapters routed by oracle price.
  • A merchant-facing wallet UI could read vault state directly (offset 0x88 for total_deposits) without an SDK.
  • A treasury-management DAO could use request_pullback_to_tempo as a programmatic withdrawal primitive — the on-chain PullbackRequested event is the audit trail.

Business model + TAM

soltempo monetizes the spread between Solana DeFi yield and merchants' 0% baseline.

Revenue model: performance fee on yield earned (10–20%, industry standard for managed yield products — avoids regulatory framing as asset management). At Kamino's USDC supply APY (~5–8% historically), a $10M merchant treasury earns $500K–$800K gross yield annually; soltempo captures $50K–$160K of that. Per-merchant operating cost is near-zero — a single keeper relays for many merchants and the vault PDA pattern is shared infrastructure.

Why this is a business, not a tech demo:

  • Distribution is pre-solved. Every Stripe merchant adopting Tempo is a soltempo prospect by default. We don't acquire merchants; we plug into Stripe's existing onboarding once Tempo mainnet adoption scales.
  • Product replaces a $0 baseline. Stripe merchants today earn nothing on operating balances. Even a 4% net yield (after take rate) is unambiguously additive — no incumbent to displace.
  • Most legible yield in DeFi. Kamino USDC supply is the simplest, most auditable yield on Solana. Merchant CFOs can understand it in a sentence. v1.0 ships with one venue; v1.1+ adds Marginfi/Drift for redundancy and rate optimization.
  • Solana fees make the unit economics work. $0.001 per cycle keeps per-merchant profitability viable from $10K balances upward. Impossible at Ethereum L1 fee levels.

Total addressable market:

  • Stripe merchants: ~4M businesses globally, ~$1T+ in annual processed payments
  • Median operating-balance to processed-payments ratio: ~2–4%, so ~$20–40B sitting idle
  • 1% TAM penetration + 100 bps net take rate = $2–4M annual revenue (single product run-rate)
  • 10% TAM penetration unlocks $20–40M (still tiny vs Stripe's $14B revenue → well within distribution-channel feasibility)

Cost structure: keeper infrastructure ~$200–500/month per region (shared across all merchants); pay-per-tx Solana validator costs scale linearly with merchant count; one-time $50–150K audit (vault + Buffer) before mainnet.

Roadmap

v0.3 (today, 2026-05-09) — what's shipped on testnet:

  • Cross-VM cycle live on Tempo Moderato + Solana devnet
  • Vault deployed with 8 instructions including Kamino CPI and pull-back
  • Trusted-keeper relay with verifiable on-chain Receipt PDAs
  • Merchant dashboard with live state polling

v1.0 (mainnet) — gating items:

  • Smart-contract audit (vault + Buffer + mppsol_cpi)
  • Tempo mainnet deployment (waiting on Tempo merchant onboarding scale)
  • CCIP-on-Tempo activation (waiting on Chainlink) → swap from trusted-keeper to in-program ccip_send
  • Multisig upgrade authority on vault program
  • First merchant pilot (1–3 design partners, target $1–10M treasury each)

v1.1+ — yield depth:

  • Multi-venue routing (Marginfi + Drift in addition to Kamino) with rate-driven rebalancing
  • Per-merchant yield-share tier negotiation
  • Webhook integrations for merchant accounting systems

v2.0 — beyond Tempo:

  • Same vault + Receipt PDA pattern accepts intents from other EVM L2s (any chain Chainlink CCIP supports)
  • mppsol_cpi.pay_with_receipt becomes the de-facto Solana-side settlement primitive for cross-VM payment apps

Technical details

Locked architectural decisions (2026-05-09)

Decision Choice Rationale
Cross-chain rail Chainlink CCIP Activated on Tempo 2026-05-08; only confirmed Tempo↔Solana rail. CCTP doesn't cover Tempo; Wormhole hasn't added it.
Yield venue (v1.0 MVP) Kamino USDC Single venue. Multi-venue allocation deferred to v1.1+ post-beta-merchant feedback.
Solana-side settlement mppsol_cpi.pay_with_receipt Atomic on-chain payment-binding; Receipt PDA references the Tempo origin for cross-VM auditability.
HTTP-MPP integration Deferred to v1.1+ MVP doesn't need paid signal feeds. When v1.1 adds depeg oracles/yield comparison feeds, soltempo will import @solana/mpp directly.
Tempo side at MVP Real testnet, not mocked Distribution thesis only matters if proven end-to-end.

Canonical CrossVMIntent encoding

Fixed 122-byte big-endian layout, version-prefixed. Solidity, Rust, and TS implementations all round-trip the same shared hex vector — see test files for the canonical vector and round-trip assertions in each language.

Offset  Size  Field
─────────────────────────────────────────────────────────
0       1     version          (= 0x01)
1       1     kind             (0=DepositForYield, 1=PullbackForPayout, 2=ReceiptAck)
2       8     sourceChain      (BE u64 — CCIP chain selector)
10      16    amount           (BE u128 — USDC base units, 6 decimals)
26      32    sourceAddress
58      32    merchant
90      32    nonce
─────────────────────────────────────────────────────────
Total: 122 bytes, big-endian, no padding.

Implementation files:

  • Solidity: contracts/buffer/src/CrossVMIntent.sol
  • Rust: programs/vault/src/lib.rs (CrossVMIntentPayload)
  • TypeScript: packages/types/src/index.ts (CrossVMIntent + encodeIntent + decodeIntent)

Tests:

  • contracts/buffer/test/Buffer.t.sol::test_canonicalEncoding_* (Foundry)
  • programs/vault/src/lib.rs::tests (cargo test -p vault)
  • packages/types/src/intent.test.ts (pnpm --filter @soltempo/types test)

mppsol_cpi CPI integration

settle_payout_to_tempo invokes mppsol_cpi.pay_with_receipt via invoke_signed. The vault PDA acts as payer_authority (signed via [b"vault", authority, bump] seeds), atomically transferring USDC from the vault to the pending-payout account AND emitting a Receipt PDA bound to the cross-VM nonce.

To avoid a Cargo dependency on mppsol_cpi (which lives in a separate Anchor workspace), soltempo includes a small mppsol_cpi_client module that builds the instruction by hand:

  • PROGRAM_ID624xoctSeGzq1TAVwZU1xbM9RozAd3xZmjPeFXrAY14j (devnet)
  • RECEIPT_SEEDb"receipt" (mirrors mppsol_cpi)
  • PAY_WITH_RECEIPT_DISC[45, 221, 79, 34, 209, 140, 222, 126] = first 8 bytes of sha256("global:pay_with_receipt"). Re-derived at test time so any future drift (e.g., function rename in mppsol_cpi) fails the test rather than silently failing on mainnet.
  • PayArgs — mirror of mppsol_cpi::PayArgs (Borsh layout: amount(u64) | nonce([u8;32]) | request_hash([u8;32]) | expiry(i64) = 80 bytes)
  • build_pay_with_receipt_ix(...) — constructs the Instruction with discriminator + serialized args + 8 account metas in the canonical order
  • derive_receipt_pda(payer, nonce)find_program_address([RECEIPT_SEED, payer, nonce])

Tests cover discriminator correctness, args round-trip, account ordering + signer/writable flags, and PDA derivation determinism.

CCIP receiver hardening

vault::ccip_receive follows the canonical Chainlink CCIP receiver pattern (per smartcontractkit/chainlink-ccip example-ccip-receiver, solana-v1.6.0). Three security checks happen at the Anchor account-constraint level before the instruction body runs:

  1. authority (Signer) — PDA derived as [EXTERNAL_EXECUTION_CONFIG_SEED, our_program_id] under the offramp_program. Only the offramp can produce a CPI where this PDA signs (the runtime enforces this). If anyone other than the offramp tries to forge a ccip_receive call, the signer constraint fails.
  2. offramp_program (UncheckedAccount) — used as seeds::program for authority and in the allowed_offramp derivation.
  3. allowed_offramp (UncheckedAccount) — PDA at [ALLOWED_OFFRAMP_SEED, source_chain_le, offramp_program] derived under vault.ccip_router, owned by the router. If the router has not allowlisted this offramp for this source chain, the account doesn't exist and the owner constraint fails.

Then the instruction body validates:

  • message.source_chain_selector == vault.expected_tempo_chain_selector (configured at vault init)
  • sender_matches(message.sender, vault.expected_tempo_sender) — accepts both 20-byte raw EVM addresses and 32-byte left-padded form

On the Solidity side, Buffer.sol::_ccipReceive performs the symmetric check: message.sourceChainSelector == solanaChainSelector and keccak256(message.sender) == keccak256(solanaVaultAddress).

Constants:

  • EXTERNAL_EXECUTION_CONFIG_SEED = b"external_execution_config"
  • ALLOWED_OFFRAMP_SEED = b"allowed_offramp"
  • CCIP_ROUTER_DEVNET = Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C
  • CCIP_SOLANA_DEVNET_CHAIN_SELECTOR = 16423721717087811551

6 unit tests verify sender_matches, the EXTERNAL_EXECUTION_CONFIG PDA derivation, and the ALLOWED_OFFRAMP PDA derivation.

Kamino integration

The vault deposits idle USDC into Kamino's USDC reserve via real CPIs. Two instructions handle the lifecycle:

  • init_kamino_obligation — runs once per vault. Calls klend's init_user_metadata + init_obligation in sequence, creating the on-chain obligation owned by the vault PDA. The vault PDA signs both CPIs via invoke_signed with [b"vault", authority, bump]. Fee payer is passed in (typically the merchant or keeper) and pays rent for the new accounts.
  • deposit_to_kamino — invokes klend's deposit_reserve_liquidity_and_obligation_collateral_v2 via CPI. The vault PDA acts as obligation owner (signer) and as the authority on user_source_liquidity (the vault's USDC ATA). The full 17-account KaminoDepositV2 context mirrors klend's upstream DepositReserveLiquidityAndObligationCollateralV2 exactly, including the legacy placeholder_user_destination_collateral slot and the obligation_farm_user_state / reserve_farm_state / farms_program farm-accounts trio (Optional accounts, but the slots must exist).

Caller responsibilities the vault doesn't re-validate (because klend rejects malformed setups at execution):

  1. Transaction MUST include klend.refresh_reserve(reserve) and klend.refresh_obligation(obligation, [reserves]) before deposit_to_kamino. v2 doesn't enforce this at the ix level, but LTV/borrow-cap checks inside klend assume fresh interest accruals.
  2. Obligation must already be initialized via init_kamino_obligation.
  3. The Kamino market chosen at vault init (vault.kamino_market) must have a USDC reserve.

Why localnet, not devnet: klend has no devnet deployment — only mainnet (KLend2g3...) and a staging build that also lives on mainnet under a separate ID (SLendK7y...). To exercise the CPI without paying real mainnet fees:

./scripts/localnet-with-klend.sh

This boots solana-test-validator with the klend program, the Kamino farms program, the Main market, and the USDC reserve all cloned from mainnet via --clone. The vault then deploys to localnet and deposit_to_kamino runs against the cloned reserve.

For mainnet deployment: vault.kamino_market is set to one of Kamino's published markets (Main, JLP, Altcoins) at init. No vault code change required.

Discriminators (re-derived at test time via sha256("global:<name>")[..8] drift catchers):

  • INIT_OBLIGATION_DISC, DEPOSIT_RESERVE_LIQUIDITY_AND_OBLIGATION_COLLATERAL_V2_DISC, WITHDRAW_OBLIGATION_COLLATERAL_AND_REDEEM_RESERVE_COLLATERAL_V2_DISC
  • REFRESH_RESERVE_DISC, REFRESH_OBLIGATION_DISC
  • INIT_USER_METADATA_DISC

Verified against klend master 3f7bd693.

Merchant dashboard

apps/merchant-web is a Next.js single-page dashboard that visualizes the cross-VM state in real time:

  • Merchant pathUSD balance on Tempo
  • Buffer.sol pathUSD balance + configured bufferTarget
  • Vault PDA's USDC ATA balance (the bridged destination)
  • vault.total_deposits decoded from the vault account at offset 0x88
  • Cross-VM activity feed — auto-detects buffer/vault deltas every 4s and surfaces them as events

The "Deposit + bridge" button performs approvedepositsendIntentToSolana through the merchant's hot wallet (env-loaded — testnet only) and waits for the keeper to relay. The polling loop catches the resulting vault.total_deposits increment within ~25s.

cp apps/merchant-web/.env.local.example apps/merchant-web/.env.local
# Set NEXT_PUBLIC_MERCHANT_PRIVATE_KEY to enable the deposit button
pnpm --filter @soltempo/merchant-web dev   # → http://localhost:4001

Without a merchant key, the dashboard runs in read-only mode — useful for showing live testnet state to viewers who don't have the demo key.

Known TODOs (intentional, marked in code)

  1. Vault PDA lamport top-up. mppsol_cpi.pay_with_receipt creates a Receipt PDA whose rent is paid by payer_authority — i.e., the vault PDA. The Vault account itself only carries its own rent. The keeper must SystemProgram::transfer lamports to the vault PDA before calling settle_payout_to_tempo. Future: separate rent-payer from settlement authority via mppsol_cpi instruction shape change.

  2. CCIP send-side from Solana — partial. settle_payout_to_tempo and request_pullback_to_tempo now emit a richer PullbackRequested event carrying the full CrossVMIntent payload, destination chain selector, Tempo Buffer receiver address, and suggested gas limit — everything the keeper needs to construct the EVM-side tx. The encode_generic_extra_args_v2(gas_limit, allow_ooo) helper produces canonical CCIP extra_args bytes for EVM destinations. The actual ccip_send CPI is deferred because:

    • The router's CcipSend requires 18 named accounts plus 13-account pool blocks per token bridged, plus per-token Address Lookup Tables that the router validates against on-chain.
    • The recommended pattern (per chainlink-ccip solana-v1.6.2) is for the off-chain client to call router.derive_accounts_ccip_send (multi-stage) to discover the full account list + LUTs, then build the tx — this is fundamentally an off-chain plumbing problem, not an on-chain CPI problem.
    • CCIP is not yet deployed on Tempo Moderato testnet, so there's no destination to test against.

    Until CCIP-on-Tempo ships, soltempo uses the trusted-keeper pull-back path: request_pullback_to_tempo emits PullbackRequested, the keeper consumes off-chain, and the keeper performs the EVM-side settlement on Tempo. This mirrors the inbound trusted_keeper_receive path exactly. When CCIP ships on Tempo, the off-chain keeper switches to invoking ccip_send via derive_accounts_ccip_send — no vault redeploy required. Discriminators (CCIP_SEND_DISCRIMINATOR, CCIP_GET_FEE_DISCRIMINATOR), CCIP_SENDER_SEED, SVM2AnyMessage + GetFeeResult Borsh mirrors, and encode_generic_extra_args_v2 are all in place ready for the in-program CPI variant.

Repo layout

Single monorepo. Polyglot by necessity (Anchor + Solidity + TS) but the components evolve together — version skew between vault and keeper would be a bug, not a feature.

soltempo/
├── programs/vault/           Solana Anchor program (Rust)
├── contracts/buffer/         Tempo Solidity contracts (Foundry)
│   └── src/
│       ├── Buffer.sol         Merchant treasury + CCIP send/receive
│       └── CrossVMIntent.sol  Canonical intent encoding (must match Solana side)
├── apps/keeper/              Off-chain keeper (TypeScript)
├── apps/merchant-web/        Next.js merchant dashboard
├── packages/types/           Shared TS types (CrossVMIntent, receipt formats)
├── tests/                    Anchor integration tests
├── Anchor.toml               Solana workspace config
├── Cargo.toml                Rust workspace
├── pnpm-workspace.yaml       TS workspace
└── tsconfig.base.json        Shared TS config

Getting started

Required toolchains:

  • Node 20+ and pnpm 9+
  • Rust + Solana CLI 3.1.14+ + Anchor 0.30.x
  • Foundry (forge, cast, anvil)
pnpm install                          # install TS workspace deps

# Foundry: install Chainlink CCIP contracts and forge-std
cd contracts/buffer
forge install foundry-rs/forge-std --no-commit
forge install smartcontractkit/chainlink-local --no-commit
cd ../..

forge build --root contracts/buffer   # build the Tempo buffer contract
forge test --root contracts/buffer    # run Foundry tests

anchor build                          # build the Solana vault program

pnpm --filter @soltempo/keeper dev          # run the keeper
pnpm --filter @soltempo/merchant-web dev    # run the merchant dashboard

License

Apache 2.0 — see LICENSE.

About

Tempo merchant treasury — auto-yields idle USDC on Solana via mppsol

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors