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 PDA8smibhXARvuYGEadHqc9C9tqTJWFmLUdXAtkabtFaA9M - 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
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).
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? SeeTEMPO-FAQ.md— 60-second briefing for Solana judges.
soltempo gives merchants a single button: earn yield on idle USDC, pull back on demand for payouts. Behind the button:
- Merchant deposits USDC into a Tempo
Buffer.solcontract - Above a configurable threshold, a canonical 122-byte intent is sent cross-chain (Chainlink CCIP) to Solana
- A Solana Anchor vault receives, allocates to Kamino's USDC reserve via real 17-account CPI
- On payout, the vault withdraws from Kamino, invokes
mppsol_cpi.pay_with_receiptfor an auditable on-chain Receipt PDA bound to the cross-VM nonce, and emits aPullbackRequestedevent for Tempo settlement
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–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.
┌──────────────────────┐
│ 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.
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. |
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_v2—deposit_to_kaminoinvokes Kamino via CPI with the vault PDA as obligation owner. 17-account context mirrors klend's upstream exactly — including the legacyplaceholder_user_destination_collateralslot and the optional farm-accounts trio (obligation_farm_user_state/reserve_farm_state/farms_program).klend.init_obligation+init_user_metadata—init_kamino_obligationwraps 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 aspayer_authorityviainvoke_signed. Manual instruction client (no Cargo dep) keeps soltempo independently cloneable.- Chainlink CCIP receiver pattern —
ccip_receivefollows the canonical 3-account security pattern (offramp signer PDA + allowlist PDA owned by router) verified againstchainlink-ccip example-ccip-receiversolana-v1.6.
CPI out (what soltempo exposes):
vault.request_pullback_to_tempo(amount, nonce)— emitsPullbackRequestedwith 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 ofmppsol_cpi.pay_with_receipt+PullbackRequestedemit + 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
0x88fortotal_deposits) without an SDK. - A treasury-management DAO could use
request_pullback_to_tempoas a programmatic withdrawal primitive — the on-chainPullbackRequestedevent is the audit trail.
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.
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_receiptbecomes the de-facto Solana-side settlement primitive for cross-VM payment apps
| 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. |
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)
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_ID—624xoctSeGzq1TAVwZU1xbM9RozAd3xZmjPeFXrAY14j(devnet)RECEIPT_SEED—b"receipt"(mirrors mppsol_cpi)PAY_WITH_RECEIPT_DISC—[45, 221, 79, 34, 209, 140, 222, 126]= first 8 bytes ofsha256("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 ofmppsol_cpi::PayArgs(Borsh layout:amount(u64) | nonce([u8;32]) | request_hash([u8;32]) | expiry(i64)= 80 bytes)build_pay_with_receipt_ix(...)— constructs theInstructionwith discriminator + serialized args + 8 account metas in the canonical orderderive_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.
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:
authority(Signer) — PDA derived as[EXTERNAL_EXECUTION_CONFIG_SEED, our_program_id]under theofframp_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 accip_receivecall, the signer constraint fails.offramp_program(UncheckedAccount) — used asseeds::programforauthorityand in theallowed_offrampderivation.allowed_offramp(UncheckedAccount) — PDA at[ALLOWED_OFFRAMP_SEED, source_chain_le, offramp_program]derived undervault.ccip_router, owned by the router. If the router has not allowlisted this offramp for this source chain, the account doesn't exist and theownerconstraint 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 = Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9CCCIP_SOLANA_DEVNET_CHAIN_SELECTOR = 16423721717087811551
6 unit tests verify sender_matches, the EXTERNAL_EXECUTION_CONFIG PDA derivation, and the ALLOWED_OFFRAMP PDA derivation.
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'sinit_user_metadata+init_obligationin sequence, creating the on-chain obligation owned by the vault PDA. The vault PDA signs both CPIs viainvoke_signedwith[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'sdeposit_reserve_liquidity_and_obligation_collateral_v2via CPI. The vault PDA acts asobligation owner(signer) and as the authority onuser_source_liquidity(the vault's USDC ATA). The full 17-accountKaminoDepositV2context mirrors klend's upstreamDepositReserveLiquidityAndObligationCollateralV2exactly, including the legacyplaceholder_user_destination_collateralslot and theobligation_farm_user_state/reserve_farm_state/farms_programfarm-accounts trio (Optional accounts, but the slots must exist).
Caller responsibilities the vault doesn't re-validate (because klend rejects malformed setups at execution):
- Transaction MUST include
klend.refresh_reserve(reserve)andklend.refresh_obligation(obligation, [reserves])beforedeposit_to_kamino. v2 doesn't enforce this at the ix level, but LTV/borrow-cap checks inside klend assume fresh interest accruals. - Obligation must already be initialized via
init_kamino_obligation. - 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.shThis 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_DISCREFRESH_RESERVE_DISC,REFRESH_OBLIGATION_DISCINIT_USER_METADATA_DISC
Verified against klend master 3f7bd693.
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.solpathUSD balance + configuredbufferTarget- Vault PDA's USDC ATA balance (the bridged destination)
vault.total_depositsdecoded from the vault account at offset0x88- Cross-VM activity feed — auto-detects buffer/vault deltas every 4s and surfaces them as events
The "Deposit + bridge" button performs approve → deposit → sendIntentToSolana 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:4001Without 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.
-
Vault PDA lamport top-up.
mppsol_cpi.pay_with_receiptcreates a Receipt PDA whose rent is paid bypayer_authority— i.e., the vault PDA. The Vault account itself only carries its own rent. The keeper mustSystemProgram::transferlamports to the vault PDA before callingsettle_payout_to_tempo. Future: separate rent-payer from settlement authority via mppsol_cpi instruction shape change. -
CCIP send-side from Solana — partial.
settle_payout_to_tempoandrequest_pullback_to_temponow emit a richerPullbackRequestedevent 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. Theencode_generic_extra_args_v2(gas_limit, allow_ooo)helper produces canonical CCIPextra_argsbytes for EVM destinations. The actualccip_sendCPI is deferred because:- The router's
CcipSendrequires 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 callrouter.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_tempoemitsPullbackRequested, the keeper consumes off-chain, and the keeper performs the EVM-side settlement on Tempo. This mirrors the inboundtrusted_keeper_receivepath exactly. When CCIP ships on Tempo, the off-chain keeper switches to invokingccip_sendviaderive_accounts_ccip_send— no vault redeploy required. Discriminators (CCIP_SEND_DISCRIMINATOR,CCIP_GET_FEE_DISCRIMINATOR),CCIP_SENDER_SEED,SVM2AnyMessage+GetFeeResultBorsh mirrors, andencode_generic_extra_args_v2are all in place ready for the in-program CPI variant. - The router's
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
Required toolchains:
- Node 20+ and
pnpm9+ - 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 dashboardApache 2.0 — see LICENSE.