From ba896dade35b1934805f03808098cdbf3a0aaab3 Mon Sep 17 00:00:00 2001 From: Vasanth T Date: Sat, 10 Oct 2026 15:36:01 +0530 Subject: [PATCH 1/9] feat(solana): add immutable launch pool and invariant tests Implement fixed-supply token/SOL pools without active admin or withdrawal paths. Validate curve accounting and real compiled-program lifecycle behavior before any deployment. --- .gitignore | 7 + packages/solana-model/README.md | 36 + packages/solana-model/differential.test.mjs | 70 + packages/solana-model/golden-vectors.json | 66 + packages/solana-model/model.test.mjs | 238 + packages/solana-model/oracle.mjs | 125 + packages/solana-model/package.json | 13 + solana/.gitignore | 5 + solana/ABI.md | 52 + solana/Anchor.toml | 17 + solana/BUILD.md | 74 + solana/Cargo.lock | 5573 +++++++++++++++++++ solana/Cargo.toml | 13 + solana/SECURITY.md | 38 + solana/programs/launch_pool/Cargo.toml | 31 + solana/programs/launch_pool/src/lib.rs | 774 +++ solana/programs/launch_pool/src/math.rs | 177 + solana/programs/launch_pool/src/tests.rs | 1072 ++++ solana/runtime-tests/Cargo.toml | 33 + solana/runtime-tests/tests/lifecycle.rs | 561 ++ solana/scripts/check-sbf.sh | 24 + 21 files changed, 8999 insertions(+) create mode 100644 packages/solana-model/README.md create mode 100644 packages/solana-model/differential.test.mjs create mode 100644 packages/solana-model/golden-vectors.json create mode 100644 packages/solana-model/model.test.mjs create mode 100644 packages/solana-model/oracle.mjs create mode 100644 packages/solana-model/package.json create mode 100644 solana/.gitignore create mode 100644 solana/ABI.md create mode 100644 solana/Anchor.toml create mode 100644 solana/BUILD.md create mode 100644 solana/Cargo.lock create mode 100644 solana/Cargo.toml create mode 100644 solana/SECURITY.md create mode 100644 solana/programs/launch_pool/Cargo.toml create mode 100644 solana/programs/launch_pool/src/lib.rs create mode 100644 solana/programs/launch_pool/src/math.rs create mode 100644 solana/programs/launch_pool/src/tests.rs create mode 100644 solana/runtime-tests/Cargo.toml create mode 100644 solana/runtime-tests/tests/lifecycle.rs create mode 100644 solana/scripts/check-sbf.sh diff --git a/.gitignore b/.gitignore index 1a88551d..bfdccd9e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,13 @@ broadcast/ .DS_Store *.tsbuildinfo +# Solana build products and local-only test/deployment keys +solana/target/ +solana/.anchor/ +solana/.tools/ +**/*-keypair.json +app/packages/solana-sdk/dist/ + # editor/agent scratch + local deploy metadata AGENTS.md CLAUDE.md diff --git a/packages/solana-model/README.md b/packages/solana-model/README.md new file mode 100644 index 00000000..42b05f47 --- /dev/null +++ b/packages/solana-model/README.md @@ -0,0 +1,36 @@ +# Independent Solana pool model + +This is a dependency-free specification oracle and reproducible regression suite, not a production quote library or a security audit. + +Run from this directory with Node 22: + +```sh +npm test +npm run test:sdk +``` + +The first command uses only Node built-ins. The SDK differential command uses Node's TypeScript stripping to load the production SDK's pure math module; it does not require a wallet, RPC, private key, network request, or an on-chain deployment. + +## Independence + +The oracle deliberately does not import or copy the program/SDK's closed-form trade quotients. It binary-searches the largest output preserving the **previous** constant product. The sell search extends through the effective reserve `R + V`, not just real reserves `R`, then independently asserts that the result cannot spend virtual SOL. Clipping the search to `R` would hide a broken solvency model. + +Fees are the smallest integer meeting the fee inequality. Beneficiary entitlements use an unbounded integer search, so tests exercise lifetime values that would overflow a naive `u128 * weight` implementation. The production implementation instead uses checked quotient/remainder decomposition. + +The shared JSON golden vectors encode every amount as a decimal string. Do not regenerate them from production math when a regression appears; investigate the disagreement. + +## Checked properties + +- 90,000 deterministic accepted mixed trades across all three fee tiers and minimum, middle and maximum virtual offsets. The test asserts substantial coverage of both trade directions, avoiding pseudo-random low-bit selection bias. +- Conservation of token supply and actual SOL balances, separate rent floors, real reserves, fee obligations and unsolicited donations. +- Transfers between holders, token burns, token/SOL/fee-vault donations, repeated fee claims, and independent recipient payouts. +- Nondecreasing constant product and maximal integer output on every accepted trade. +- 3,600 isolated round trips, covering buy/sell and sell/buy, cannot create trader value before external incentives. +- Tiny-input fee ceilings, zero outputs, slippage, malformed states, near-empty inventory, `u64` reserve bounds and `u128` cumulative fees. +- 108,000 differential state transitions against the production TypeScript SDK, plus fee and rejection boundary comparisons. + +## What these tests do not establish + +These are mathematical and balance-sheet tests. They do not execute Solana instructions or prove account ownership, signer validation, CPI safety, PDA initialization under prefunding, expiry enforcement, runtime rent behavior, activation atomicity, deployment immutability, packet/compute limits, wallet compatibility or indexing correctness. Those require separate program/runtime, SDK and integration tests. + +Passing this suite does not approve the virtual-offset valuation policy, establish market demand, make virtual SOL withdrawable, guarantee buyer returns, complete an independent audit, or authorize mainnet deployment. diff --git a/packages/solana-model/differential.test.mjs b/packages/solana-model/differential.test.mjs new file mode 100644 index 00000000..a7785642 --- /dev/null +++ b/packages/solana-model/differential.test.mjs @@ -0,0 +1,70 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { claimableFees, feeEntitlement, quoteBuy, quoteSell, tradingFee } from "../../app/packages/solana-sdk/src/math.ts"; +import { + SUPPLY, U64_MAX, U128_MAX, MIN_VIRTUAL_SOL, MAX_VIRTUAL_SOL, + claimOracle, entitlementOracle, feeOracle, nextState, quoteBuyOracle, quoteSellOracle, +} from "./oracle.mjs"; + +test("SDK agrees with independently solved invariant across 108,000 state transitions", () => { + let seed = 2_910_317n; + const random = () => { + seed ^= seed << 13n; + seed ^= seed >> 7n; + seed ^= seed << 17n; + seed &= U64_MAX; + return seed; + }; + let buys = 0; + let sells = 0; + for (const virtualSol of [MIN_VIRTUAL_SOL, 30_000_000_000n, MAX_VIRTUAL_SOL]) { + for (const feeBps of [0, 100, 300]) { + let state = { tokenInventory: SUPPLY, realSolReserves: 0n, virtualSol, feeBps }; + for (let step = 0; step < 12_000; step++) { + const circulating = SUPPLY - state.tokenInventory; + const buy = circulating === 0n || random() % 2n === 0n; + const input = buy ? 1_000_000n + random() % 1_000_000_000_000n : circulating * (1n + random() % 99n) / 100n; + const oracle = (buy ? quoteBuyOracle : quoteSellOracle)(state, input); + const actual = (buy ? quoteBuy : quoteSell)(state, input); + assert.deepEqual(actual, oracle, `fee=${feeBps}, V=${virtualSol}, step=${step}`); + state = nextState(state, oracle); + if (buy) buys++; + else sells++; + } + } + } + assert.equal(buys + sells, 108_000); + assert(buys > 40_000 && sells > 40_000); +}); + +test("SDK fee ledger agrees at lifetime and tiny-value boundaries", () => { + for (const fee of [0, 100, 300]) for (const amount of [0n, 1n, 2n, 33n, 34n, 99n, 100n, 101n, U64_MAX]) { + assert.equal(tradingFee(amount, fee), feeOracle(amount, fee)); + } + for (const earned of [0n, 1n, 9999n, 10000n, U64_MAX, U64_MAX + 1n, U128_MAX]) for (const weight of [1, 17, 3333, 3334, 9999, 10000]) { + const expected = entitlementOracle(earned, weight); + assert.equal(feeEntitlement(earned, weight), expected); + assert.equal(claimableFees(earned, weight, expected / 2n), claimOracle(earned, weight, expected / 2n)); + } +}); + +test("SDK and oracle both reject invalid states and unsafe quotes", () => { + const initial = { tokenInventory: SUPPLY, realSolReserves: 0n, virtualSol: MIN_VIRTUAL_SOL, feeBps: 0 }; + for (const patch of [ + { tokenInventory: 0n }, { tokenInventory: SUPPLY + 1n }, { tokenInventory: SUPPLY - 1n }, + { realSolReserves: -1n }, { realSolReserves: U64_MAX }, + { virtualSol: MIN_VIRTUAL_SOL - 1n }, { virtualSol: MAX_VIRTUAL_SOL + 1n }, { feeBps: 301 }, + ]) { + const state = { ...initial, ...patch }; + assert.throws(() => quoteBuy(state, 1_000_000n)); + assert.throws(() => quoteBuyOracle(state, 1_000_000n)); + } + for (const input of [0n, -1n, U64_MAX, U64_MAX + 1n]) { + assert.throws(() => quoteBuy(initial, input)); + assert.throws(() => quoteBuyOracle(initial, input)); + } + assert.throws(() => quoteBuy(initial, 1_000_000n, SUPPLY)); + assert.throws(() => quoteBuyOracle(initial, 1_000_000n, SUPPLY)); + assert.throws(() => quoteSell(initial, 1n)); + assert.throws(() => quoteSellOracle(initial, 1n)); +}); diff --git a/packages/solana-model/golden-vectors.json b/packages/solana-model/golden-vectors.json new file mode 100644 index 00000000..e5962cf5 --- /dev/null +++ b/packages/solana-model/golden-vectors.json @@ -0,0 +1,66 @@ +[ + { + "name": "buy-1sol-fee0", + "side": "buy", + "state": { "tokenInventory": "1000000000000000", "realSolReserves": "0", "virtualSol": "30000000000", "feeBps": 0 }, + "input": "1000000000", + "minOutput": "1", + "expected": { "amountOut": "32258064516129", "fee": "0", "nextTokenInventory": "967741935483871", "nextRealSolReserves": "1000000000" } + }, + { + "name": "sell-all-fee0", + "side": "sell", + "state": { "tokenInventory": "967741935483871", "realSolReserves": "1000000000", "virtualSol": "30000000000", "feeBps": 0 }, + "input": "32258064516129", + "minOutput": "1", + "expected": { "amountOut": "999999999", "fee": "0", "nextTokenInventory": "1000000000000000", "nextRealSolReserves": "1" } + }, + { + "name": "buy-1sol-fee100", + "side": "buy", + "state": { "tokenInventory": "1000000000000000", "realSolReserves": "0", "virtualSol": "30000000000", "feeBps": 100 }, + "input": "1000000000", + "minOutput": "1", + "expected": { "amountOut": "31945788964181", "fee": "10000000", "nextTokenInventory": "968054211035819", "nextRealSolReserves": "990000000" } + }, + { + "name": "sell-all-fee100", + "side": "sell", + "state": { "tokenInventory": "968054211035819", "realSolReserves": "990000000", "virtualSol": "30000000000", "feeBps": 100 }, + "input": "31945788964181", + "minOutput": "1", + "expected": { "amountOut": "980099999", "fee": "9900000", "nextTokenInventory": "1000000000000000", "nextRealSolReserves": "1" } + }, + { + "name": "buy-1sol-fee300", + "side": "buy", + "state": { "tokenInventory": "1000000000000000", "realSolReserves": "0", "virtualSol": "30000000000", "feeBps": 300 }, + "input": "1000000000", + "minOutput": "1", + "expected": { "amountOut": "31320632870519", "fee": "30000000", "nextTokenInventory": "968679367129481", "nextRealSolReserves": "970000000" } + }, + { + "name": "sell-all-fee300", + "side": "sell", + "state": { "tokenInventory": "968679367129481", "realSolReserves": "970000000", "virtualSol": "30000000000", "feeBps": 300 }, + "input": "31320632870519", + "minOutput": "1", + "expected": { "amountOut": "940899999", "fee": "29100000", "nextTokenInventory": "1000000000000000", "nextRealSolReserves": "1" } + }, + { + "name": "minimum-offset-dust", + "side": "buy", + "state": { "tokenInventory": "1000000000000000", "realSolReserves": "0", "virtualSol": "1000000000", "feeBps": 300 }, + "input": "2", + "minOutput": "1", + "expected": { "amountOut": "999999", "fee": "1", "nextTokenInventory": "999999999000001", "nextRealSolReserves": "1" } + }, + { + "name": "maximum-offset-large", + "side": "buy", + "state": { "tokenInventory": "1000000000000000", "realSolReserves": "0", "virtualSol": "1000000000000000", "feeBps": 300 }, + "input": "5000000000000000000", + "minOutput": "1", + "expected": { "amountOut": "999793856936714", "fee": "150000000000000000", "nextTokenInventory": "206143063286", "nextRealSolReserves": "4850000000000000000" } + } +] diff --git a/packages/solana-model/model.test.mjs b/packages/solana-model/model.test.mjs new file mode 100644 index 00000000..fc89dc82 --- /dev/null +++ b/packages/solana-model/model.test.mjs @@ -0,0 +1,238 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import { + SUPPLY, U64_MAX, U128_MAX, MIN_VIRTUAL_SOL, MAX_VIRTUAL_SOL, + checkState, claimOracle, entitlementOracle, feeOracle, nextState, + quoteBuyOracle, quoteSellOracle, +} from "./oracle.mjs"; + +const OFFSETS = [MIN_VIRTUAL_SOL, 30_000_000_000n, MAX_VIRTUAL_SOL]; +const FEES = [0, 100, 300]; +const initial = (virtualSol = OFFSETS[1], feeBps = 100) => ({ + tokenInventory: SUPPLY, realSolReserves: 0n, virtualSol, feeBps, +}); +const sum = (values) => values.reduce((a, b) => a + b, 0n); +const product = (state) => state.tokenInventory * (state.realSolReserves + state.virtualSol); +function random(seed) { + let value = seed >>> 0; + return () => { + value = (Math.imul(value, 1_664_525) + 1_013_904_223) >>> 0; + return BigInt(value); + }; +} + +// Independent balance sheet, including ordinary transfers, burns and donations. +// These are specification tests, not a simulation of Solana account execution. +class Ledger { + constructor(virtualSol, feeBps) { + this.state = initial(virtualSol, feeBps); + this.walletSol = Array(7).fill(1_000_000_000_000_000n); + this.walletTokens = Array(7).fill(0n); + this.weights = [1, 3, 17, 999, 2000, 3000, 3980]; + this.paid = Array(7).fill(0n); + this.feesEarned = 0n; + this.reserveRent = 1_234_560n; + this.feeRent = 987_650n; + this.reserveBalance = this.reserveRent; + this.feeBalance = this.feeRent; + this.tokenBalance = SUPPLY; + this.solDonations = 0n; + this.feeDonations = 0n; + this.tokenDonations = 0n; + this.burned = 0n; + this.initialSol = sum(this.walletSol) + this.reserveBalance + this.feeBalance; + } + buy(wallet, input) { + const quote = quoteBuyOracle(this.state, input); + assert(this.walletSol[wallet] >= input); + const before = product(this.state); + this.walletSol[wallet] -= input; + this.walletTokens[wallet] += quote.amountOut; + this.reserveBalance += quote.netInput; + this.feeBalance += quote.fee; + this.tokenBalance -= quote.amountOut; + this.feesEarned += quote.fee; + this.state = nextState(this.state, quote); + assert(product(this.state) >= before); + // One extra token atom would break the old product. This proves maximality. + assert((this.state.tokenInventory - 1n) * (this.state.realSolReserves + this.state.virtualSol) < before); + } + sell(wallet, input) { + const quote = quoteSellOracle(this.state, input); + assert(this.walletTokens[wallet] >= input); + const before = product(this.state); + this.walletSol[wallet] += quote.amountOut; + this.walletTokens[wallet] -= input; + this.reserveBalance -= quote.amountOut + quote.fee; + this.feeBalance += quote.fee; + this.tokenBalance += input; + this.feesEarned += quote.fee; + this.state = nextState(this.state, quote); + assert(product(this.state) >= before); + assert(this.state.tokenInventory * (this.state.realSolReserves + this.state.virtualSol - 1n) < before); + } + claim(wallet) { + const amount = claimOracle(this.feesEarned, this.weights[wallet], this.paid[wallet]); + const before = { ...this.state }; + this.paid[wallet] += amount; + this.feeBalance -= amount; + this.walletSol[wallet] += amount; + assert.deepEqual(this.state, before); + assert.equal(claimOracle(this.feesEarned, this.weights[wallet], this.paid[wallet]), 0n); + } + donate(wallet) { + const before = { ...this.state }; + this.walletSol[wallet] -= 123n; + this.reserveBalance += 100n; + this.solDonations += 100n; + this.feeBalance += 23n; + this.feeDonations += 23n; + if (this.walletTokens[wallet] > 3n) { + this.walletTokens[wallet] -= 3n; + this.tokenBalance += 2n; + this.tokenDonations += 2n; + this.burned += 1n; + } + assert.deepEqual(this.state, before); + } + transfer(from, to) { + const amount = this.walletTokens[from] / 5n; + this.walletTokens[from] -= amount; + this.walletTokens[to] += amount; + } + check() { + checkState(this.state); + assert.equal(sum(this.walletSol) + this.reserveBalance + this.feeBalance, this.initialSol); + assert.equal(sum(this.walletTokens) + this.tokenBalance + this.burned, SUPPLY); + assert.equal(this.tokenBalance, this.state.tokenInventory + this.tokenDonations); + assert.equal(this.reserveBalance, this.reserveRent + this.state.realSolReserves + this.solDonations); + assert.equal(this.feeBalance, this.feeRent + this.feesEarned - sum(this.paid) + this.feeDonations); + assert(this.reserveBalance >= this.reserveRent); + assert(this.feeBalance >= this.feeRent); + assert(this.feesEarned <= U128_MAX); + this.paid.forEach((paid, i) => assert(paid <= entitlementOracle(this.feesEarned, this.weights[i]))); + } +} + +test("independent golden vectors remain stable", async () => { + const vectors = JSON.parse(await readFile(new URL("./golden-vectors.json", import.meta.url), "utf8")); + for (const vector of vectors) { + const state = Object.fromEntries(Object.entries(vector.state).map(([key, value]) => + [key, key === "feeBps" ? value : BigInt(value)])); + const quote = (vector.side === "buy" ? quoteBuyOracle : quoteSellOracle)(state, BigInt(vector.input), BigInt(vector.minOutput)); + for (const [key, value] of Object.entries(vector.expected)) assert.equal(quote[key], BigInt(value), `${vector.name}: ${key}`); + } +}); + +test("90,000 deterministic mixed trades conserve balances with fees, donations, burns and transfers", () => { + let accepted = 0; + let buys = 0; + let sells = 0; + for (const offset of OFFSETS) for (const fee of FEES) for (let scenario = 0; scenario < 40; scenario++) { + const rng = random(29_103 + scenario * 7_919 + fee); + const ledger = new Ledger(offset, fee); + for (let step = 0; step < 250; step++) { + const wallet = Number(rng() % 7n); + if ((rng() >> 16n) % 2n === 0n || ledger.walletTokens[wallet] === 0n) { + ledger.buy(wallet, 1_000_000n + rng() * 29n); + buys++; + } else { + const input = ledger.walletTokens[wallet] * (1n + rng() % 99n) / 100n; + // Generated positions comfortably exceed one lamport at every offset. + ledger.sell(wallet, input); + sells++; + } + accepted++; + if (step % 11 === 0) ledger.claim(Number(rng() % 7n)); + if (step % 17 === 0) ledger.donate(wallet); + if (step % 23 === 0) ledger.transfer(wallet, (wallet + 1) % 7); + if (step % 10 === 0) ledger.check(); + } + for (let i = 6; i >= 0; i--) ledger.claim(i); + ledger.check(); + assert(ledger.feesEarned - sum(ledger.paid) < 7n); + } + assert.equal(accepted, 90_000); + assert(buys > 30_000 && sells > 30_000, "exercise both sides rather than PRNG low-bit alternation"); +}); + +test("3,600 isolated round trips cannot create SOL or tokens", () => { + let directions = 0; + const rng = random(91_337); + for (const offset of OFFSETS) for (const fee of FEES) for (let sample = 0; sample < 200; sample++) { + const state = initial(offset, fee); + const input = 10_000_000n + rng() * 1_001n; + const buy = quoteBuyOracle(state, input); + const sell = quoteSellOracle(nextState(state, buy), buy.amountOut); + assert(sell.amountOut <= input); + checkState(nextState(nextState(state, buy), sell)); + directions++; + + const acquired = nextState(state, buy); + const tokens = buy.amountOut / 3n; + const sold = quoteSellOracle(acquired, tokens); + const boughtBack = quoteBuyOracle(nextState(acquired, sold), sold.amountOut); + assert(boughtBack.amountOut <= tokens); + directions++; + } + assert.equal(directions, 3_600); +}); + +test("cumulative u128 fee entitlements are solvent and claim-order independent", () => { + const weights = [1, 2, 3, 4, 5, 6, 9979]; + for (const earned of [0n, 1n, 6n, 9999n, 10000n, U64_MAX, U64_MAX + 1n, U128_MAX - 1n, U128_MAX]) { + const amounts = weights.map((weight) => entitlementOracle(earned, weight)); + assert(sum(amounts) <= earned); + assert(earned - sum(amounts) < BigInt(weights.length)); + assert.equal(sum([...amounts].reverse()), sum(amounts)); + for (let i = 0; i < weights.length; i++) { + assert.equal(claimOracle(earned, weights[i], amounts[i]), 0n); + assert.throws(() => claimOracle(earned, weights[i], amounts[i] + 1n), /overpaid/); + } + } + assert.equal(entitlementOracle(U128_MAX, 10000), U128_MAX); + assert.throws(() => entitlementOracle(U128_MAX + 1n, 10000), /out of bounds/); +}); + +test("fee ceilings charge the smallest satisfying integer, even at u64 max", () => { + for (const fee of FEES) for (const amount of [0n, 1n, 2n, 33n, 34n, 99n, 100n, 101n, U64_MAX]) { + const charged = feeOracle(amount, fee); + assert(charged * 10000n >= amount * BigInt(fee)); + if (charged > 0n) assert((charged - 1n) * 10000n < amount * BigInt(fee)); + } +}); + +test("unsafe states, zero/dust output, slippage and integer boundaries reject", () => { + const state = initial(); + for (const patch of [ + { tokenInventory: 0n }, { tokenInventory: SUPPLY + 1n }, + { virtualSol: MIN_VIRTUAL_SOL - 1n }, { virtualSol: MAX_VIRTUAL_SOL + 1n }, + { realSolReserves: U64_MAX }, { feeBps: 1 }, { tokenInventory: SUPPLY - 1n }, + ]) assert.throws(() => checkState({ ...state, ...patch })); + assert.throws(() => quoteBuyOracle(state, 0n), /zero input/); + assert.throws(() => quoteBuyOracle(state, 1n), /zero effective input/); + assert.throws(() => quoteBuyOracle({ ...state, feeBps: 0 }, U64_MAX), /overflow/); + assert.throws(() => quoteBuyOracle(state, 1000n, SUPPLY), /slippage/); + assert.throws(() => quoteSellOracle(state, 1n), /circulating/); + const buy = quoteBuyOracle(state, 1_000_000_000n); + const after = nextState(state, buy); + assert.throws(() => quoteSellOracle(after, buy.amountOut + 1n), /circulating/); + assert.throws(() => quoteSellOracle(after, 1n), /zero output/); + assert.throws(() => quoteSellOracle(after, buy.amountOut, U64_MAX), /slippage/); + assert.throws(() => quoteBuyOracle(state, Number.MAX_SAFE_INTEGER), /bigint/); +}); + +test("near-empty inventory and large real reserves never pay virtual SOL", () => { + for (const offset of OFFSETS) for (const fee of FEES) { + const state = initial(offset, fee); + // A large but u64-safe buy approaches the last atom without exhausting it. + const buy = quoteBuyOracle(state, (U64_MAX - offset) / 2n); + assert(buy.nextTokenInventory > 0n); + const after = nextState(state, buy); + const sell = quoteSellOracle(after, buy.amountOut); + assert(sell.amountOut + sell.fee <= after.realSolReserves); + assert.equal(sell.nextTokenInventory, SUPPLY); + assert(sell.nextRealSolReserves >= 0n); + } +}); diff --git a/packages/solana-model/oracle.mjs b/packages/solana-model/oracle.mjs new file mode 100644 index 00000000..075a0df1 --- /dev/null +++ b/packages/solana-model/oracle.mjs @@ -0,0 +1,125 @@ +// Deliberately independent of the program and SDK. Solve the invariant by +// monotone search, instead of implementing their closed-form swap quotients. +import assert from "node:assert/strict"; + +export const SUPPLY = 1_000_000_000_000_000n; +export const U64_MAX = (1n << 64n) - 1n; +export const U128_MAX = (1n << 128n) - 1n; +export const MIN_VIRTUAL_SOL = 1_000_000_000n; +export const MAX_VIRTUAL_SOL = 1_000_000_000_000_000n; +const BPS = 10_000n; + +function integer(value, name, max = U64_MAX) { + assert.equal(typeof value, "bigint", `${name} must be bigint`); + assert(value >= 0n && value <= max, `${name} out of bounds`); +} + +// pred must be true for an initial interval starting at zero. +function largestTrue(limit, pred) { + assert(pred(0n)); + let low = 0n; + let high = limit + 1n; + while (high - low > 1n) { + const mid = (high + low) >> 1n; + if (pred(mid)) low = mid; + else high = mid; + } + return low; +} + +export function feeOracle(amount, feeBps) { + integer(amount, "fee amount"); + assert([0, 100, 300].includes(feeBps), "unsupported fee"); + if (feeBps === 0 || amount === 0n) return 0n; + const target = amount * BigInt(feeBps); + // Find the largest integer strictly below the fee threshold, then add one. + return largestTrue(amount, (value) => value * BPS < target) + 1n; +} + +export function checkState(state) { + const { tokenInventory: x, realSolReserves: r, virtualSol: v, feeBps } = state; + integer(x, "inventory"); + integer(r, "reserve"); + integer(v, "virtual reserve"); + assert(x > 0n && x <= SUPPLY, "invalid inventory"); + assert(v >= MIN_VIRTUAL_SOL && v <= MAX_VIRTUAL_SOL, "virtual reserve out of bounds"); + assert(r + v <= U64_MAX, "effective reserve overflow"); + assert([0, 100, 300].includes(feeBps), "unsupported fee"); + assert(x * (r + v) >= SUPPLY * v, "insolvent state"); +} + +function constraints(input, minOutput) { + integer(input, "input"); + integer(minOutput, "minimum output"); + assert(input > 0n, "zero input"); +} + +export function quoteBuyOracle(state, grossLamports, minOutput = 1n) { + checkState(state); + constraints(grossLamports, minOutput); + const { tokenInventory: x, realSolReserves: r, virtualSol: v } = state; + const fee = feeOracle(grossLamports, state.feeBps); + const netInput = grossLamports - fee; + assert(netInput > 0n, "zero effective input"); + assert(r + v + netInput <= U64_MAX, "effective reserve overflow"); + const product = x * (r + v); + const amountOut = largestTrue(x, (out) => (x - out) * (r + v + netInput) >= product); + assert(amountOut > 0n && amountOut < x, "zero or exhausted output"); + assert(amountOut >= minOutput, "slippage"); + return { + grossInput: grossLamports, + netInput, + fee, + amountOut, + nextTokenInventory: x - amountOut, + nextRealSolReserves: r + netInput, + }; +} + +export function quoteSellOracle(state, tokenAtoms, minOutput = 1n) { + checkState(state); + constraints(tokenAtoms, minOutput); + const { tokenInventory: x, realSolReserves: r, virtualSol: v } = state; + assert(tokenAtoms <= SUPPLY - x, "input exceeds circulating supply"); + const product = x * (r + v); + // Deliberately search beyond actual R. The invariant must imply solvency; + // clipping this limit to R would hide an incorrectly specified curve. + const grossSol = largestTrue(r + v, (out) => (x + tokenAtoms) * (r + v - out) >= product); + assert(grossSol <= r, "virtual reserve withdrawal"); + const fee = feeOracle(grossSol, state.feeBps); + const amountOut = grossSol - fee; + assert(amountOut > 0n, "zero output"); + assert(amountOut >= minOutput, "slippage"); + return { + grossInput: tokenAtoms, + netInput: tokenAtoms, + fee, + amountOut, + nextTokenInventory: x + tokenAtoms, + nextRealSolReserves: r - grossSol, + }; +} + +export function nextState(state, quote) { + return { + ...state, + tokenInventory: quote.nextTokenInventory, + realSolReserves: quote.nextRealSolReserves, + }; +} + +export function entitlementOracle(cumulativeFees, weightBps) { + integer(cumulativeFees, "lifetime fees", U128_MAX); + assert(Number.isInteger(weightBps) && weightBps > 0 && weightBps <= 10_000); + const target = cumulativeFees * BigInt(weightBps); + // JS bigint is unbounded: this intentionally exercises values whose naive + // multiplication overflows the program's u128 representation. + return largestTrue(cumulativeFees, (value) => value * BPS <= target); +} + +export function claimOracle(cumulativeFees, weightBps, paid) { + integer(paid, "paid", U128_MAX); + const entitlement = entitlementOracle(cumulativeFees, weightBps); + assert(paid <= entitlement, "overpaid recipient"); + return entitlement - paid; +} diff --git a/packages/solana-model/package.json b/packages/solana-model/package.json new file mode 100644 index 00000000..5057bf35 --- /dev/null +++ b/packages/solana-model/package.json @@ -0,0 +1,13 @@ +{ + "name": "@openlaunch/solana-model", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "node --test model.test.mjs", + "test:sdk": "node --experimental-strip-types --test differential.test.mjs" + }, + "engines": { + "node": ">=22" + } +} diff --git a/solana/.gitignore b/solana/.gitignore new file mode 100644 index 00000000..5a25853a --- /dev/null +++ b/solana/.gitignore @@ -0,0 +1,5 @@ +/target/ +/.tools/ +/.anchor/ +/test-ledger/ +*-keypair.json diff --git a/solana/ABI.md b/solana/ABI.md new file mode 100644 index 00000000..f4bd4ca1 --- /dev/null +++ b/solana/ABI.md @@ -0,0 +1,52 @@ +# Launch pool ABI v1 + +Unreleased, no mainnet program ID. Anchor 0.31.1 Borsh encoding. All integers little endian. Instruction discriminator: first 8 bytes of SHA-256 `global:`; account discriminator: first 8 bytes of SHA-256 `account:`. + +Seeds: pool `["pool", creator(32), nonce(u64 LE)]`; mint `["mint", pool]`; token vault `["token", pool]`; reserve `["reserve", pool]`; fees `["fees", pool]`; temporary mint authority `["mint-authority", pool]`. Every PDA is scoped to the configured program ID. + +## Instructions + +Accounts in exact order, `w` writable, `s` signer. System is `11111111111111111111111111111111`; token is legacy SPL only. `programData` must be linked by this program executable. + +- `prepare_launch(args: PrepareArgs)`: creator(s), payer(ws), pool(w), systemProgram. Args: nonce u64; virtualSol u64; feeBps u16; name string; symbol string; uri string; contentHash [u8;32]; recipients Vec. RecipientInput: address Pubkey, weightBps u16. +- `activate_launch()`: creator(ws), pool(w), mint(w), tokenVault(w), reserve(w), fees(w), mintAuthority, executable, programData, tokenProgram, systemProgram, rent. Creator pays activation rent. +- `cancel_preparation()`: creator(s), pool(w). Retains full preparation as permanent cancelled tombstone. No refund because preparation has no auxiliary accounts. +- `buy_exact_in(amountIn:u64, minimumOut:u64, expirySlot:u64)`: trader(ws), pool(w), mint, tokenVault(w), reserve(w), fees(w), traderToken(w), tokenProgram, systemProgram. Trader token account must exist and have correct mint/owner. SDK may prepend ATA creation. +- `sell_exact_in(amountIn:u64, minimumOut:u64, expirySlot:u64)`: same account order as buy. Shared ABI retains systemProgram. +- `claim_fees(recipientIndex:u8)`: pool(w), fees(w), recipient(w). Permissionless; recipient must exactly match immutable entry. + +## Accounts + +`Pool` fields after 8-byte discriminator: + +``` +version u8 (=1) +status u8 (0 Prepared, 1 Active, 2 Cancelled) +bump u8 +nonce u64 +creator Pubkey +rentPayer Pubkey +mint Pubkey +virtualSol u64 +feeBps u16 +recipientCount u8 +recipients [Recipient;7] +tokenReserve u64 +solReserve u64 +feesEarned u128 +sequence u64 +name string +symbol string +uri string +contentHash [u8;32] +``` + +`Recipient`: address Pubkey, weightBps u16, paid u128. Unused entries all zero. + +`SolVault`: pool Pubkey, kind u8 (0 reserve, 1 fees), rentFloor u64 after 8-byte discriminator. Size 49 bytes. + +Maximum UTF-8 bytes: name 32, symbol 10, URI 200. Original supply 1,000,000,000,000,000 atoms; decimals 6. Virtual offset 1,000,000,000 through 1,000,000,000,000,000 lamports inclusive. Mechanical limits, not endorsed valuations. Fees 0/100/300; nonzero fees require 1..7 unique nonzero recipients with weights totaling 10000; zero fees require no recipients. + +Checked arithmetic: buy `R+V+net` fits u64; sell `R+V` fits u64; cumulative earned/paid u128. Expiry must be current slot or later. Reject zero input/output, nonpositive net, slippage, expired execution, inventory excess, and insufficient real SOL. + +Events use Anchor `event:` discriminator. `Prepared`: pool, creator, nonce. `Activated`: pool, mint, supply. `Cancelled`: pool. `Swap`: pool Pubkey, trader Pubkey, sequence u64, isBuy bool, amountIn u64, amountOut u64, fee u64, tokenReserve u64, solReserve u64, slot u64. `FeesClaimed`: pool Pubkey, recipient Pubkey, amount u64, cumulativePaid u128. Verify successful transaction origin and reconcile account state; events alone are not evidence of origin. diff --git a/solana/Anchor.toml b/solana/Anchor.toml new file mode 100644 index 00000000..a4714985 --- /dev/null +++ b/solana/Anchor.toml @@ -0,0 +1,17 @@ +[toolchain] +anchor_version = "0.31.1" +# Use scripts/check-sbf.sh for the independently pinned modern SBPFv3 compiler. +# The historical Anchor 0.31 recommended Agave 2.1 is not a release requirement. + +[features] +resolution = true +skip-lint = false + +# Compilation/test fixture only. Kevin must bind the reviewed source and SDK to +# a real deployment ID before reproducible build and immutable release review. +[programs.localnet] +launch_pool = "Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr" + +[provider] +cluster = "Localnet" +wallet = "~/.config/solana/id.json" diff --git a/solana/BUILD.md b/solana/BUILD.md new file mode 100644 index 00000000..03824209 --- /dev/null +++ b/solana/BUILD.md @@ -0,0 +1,74 @@ +# Reproducible build and test handoff + +No deployment is performed by these commands. The source contains an undeployed compilation fixture ID. Never expose this fixture as a real Solana launch option. Kevin must bind the reviewed real program ID into source, app configuration and the release manifest before the final build and audit comparison. + +## Host checks + +From `solana/`: + +```sh +cargo fmt --all --check +cargo test -p launch-pool --locked +``` + +Commit `Cargo.lock`. Host tests exercise curve boundaries, 108,000 sampled transitions, an independently implemented model's vectors, SDK/Rust byte compatibility, generated Anchor dispatch/account constraints, cancellations, fee claims, and buys/sells using the real legacy SPL TransferChecked processor through a limited syscall harness. The harness is explicitly not a validator: it cannot establish SBF stack/compute limits, account-creation prefunding semantics, runtime rent rules, or transaction rollback. + +## SBF build, no cluster access + +The pinned Anchor crates are `0.31.1`. Their historical Agave `2.1` recommendation is not a production-toolchain recommendation in October 2026. Current Anza documentation says deployment of older SBPF formats will be blocked by SIMD-500 activation. Build explicitly for SBPFv3, not the compiler's v0 default. This program's Solana SDK resolves to 2.3 through the lockfile. Verify all toolchain and cluster-feature compatibility before release. [Anza compiler documentation](https://github.com/anza-xyz/cargo-build-sbf), [4.2.0 release](https://github.com/anza-xyz/cargo-build-sbf/releases/tag/cargo-build-sbf%40v4.2.0) + +Install the official build-only command in a repository-local ignored directory: + +```sh +cargo install cargo-build-sbf --version 4.2.0 --locked --root .tools +bash scripts/check-sbf.sh +``` + +Windows PowerShell equivalent: + +```powershell +cargo install cargo-build-sbf --version 4.2.0 --locked --root .tools +.tools/bin/cargo-build-sbf.exe --tools-version v1.56 --arch v3 --manifest-path programs/launch_pool/Cargo.toml -- --locked +``` + +The compiler downloads platform-tools `v1.56` (Rust 1.89) from Anza's releases. Record artifact digests in the final build manifest and independently check the downloaded toolchain provenance. It may generate an ignored local program-keypair file as a build artifact. Do not use that generated fixture for deployment and do not commit keypairs. No script reads wallet configuration, signs transactions, chooses an RPC, or runs `deploy`/`set-upgrade-authority`. + +Expected binary: `solana/target/deploy/launch_pool.so`. Collect its SHA-256 and complete compiler log, reject stack-frame/undefined-symbol diagnostics, then reproduce the build on a separate clean machine/container. Source changes, including changing the program ID, invalidate earlier bytecode evidence. Host tests passing is not evidence that this SBF build passes. + +Local verification on 10 October 2026: the pinned Windows build completed for `--arch v3`, producing a 373,464-byte fixture binary with SHA-256 `a6159ed6a87ee96257436363b1391de18e88fb8e83195306b8182fe1339fd832`. No stack-frame or undefined-symbol diagnostic was emitted. This is one local build, not independently reproduced production bytecode and not a deployment. The compiler notes that keeping both `cdylib` and `lib` crate outputs prevents its LTO optimization; both are retained so host/library tests use the same crate. Reassess size/compute using the validator tests, not assumed LTO benefits. + +## Offline SBF runtime evidence + +After building the ELF, run: + +```sh +cargo test --workspace --features svm-tests --locked +cargo clippy --workspace --all-targets --features svm-tests --locked -- -D warnings +``` + +The separate `runtime-tests` crate pins LiteSVM `0.13.1`, with Agave 4.0 dependencies committed in the lockfile. It loads the actual SBPFv3 ELF with the upgradeable loader and linked immutable ProgramData, then runs real System and legacy SPL Token instructions. Ephemeral fixture identities exist only in test memory; there is no RPC, real wallet, persisted signer or deployment. + +On 10 October 2026, all 21 host tests and all 9 actual-SBF runtime tests passed on Windows. Runtime coverage includes: + +- Complete prepare/activate/buy/sell/claim lifecycle, full supply in custody, mint/freeze authority absence and failed extra mint/freeze attempts. +- Prefunding each of pool, mint, token vault, reserve and fee PDA before account creation. +- Rejected cancellation/activation/initialization replay, retained upgrade authority and unrelated ProgramData. +- Actual transaction rollback after successful activation CPIs or a buy when a subsequent instruction fails. +- Maximum metadata and seven recipients. The maximum preparation packet measured 792 bytes against the 1,232-byte limit; every submitted test transaction checks that limit and runs under runtime compute metering. +- Wrong custody/program/mint accounts, expired execution, donations, holder burns and recipient rent failure isolation. + +Observed compute varies with randomly generated PDA bumps: sampled preparation 23k–48k, activation 44k–61k, buy 25k–33k, sell 21k–30k and claim 9,641 units. These are measured fixture ranges, not worst-case proofs or promised network costs. Runtime tests load the compiled binary from `target/deploy`; rebuild after any source change to avoid testing stale bytecode. + +## Remaining validator/release rehearsal + +Use a compatible SBPFv3 test runtime (Anza documents Solana/Agave 4.0+ for v3 tooling) with a disposable fixture program. Do not reuse the historical Agave 2.1 validator. Pin and record the exact validator version and active feature set in test evidence. [Anchor CLI compatibility](https://www.anchor-lang.com/docs/references/cli) + +- Execute prepare, activate, buy, sell, claim and cancel through real transactions, including maximum metadata and seven fee recipients. Measure transaction bytes, compute, stack and heap use. +- Prove activation fails with retained upgrade authority and unrelated ProgramData, succeeds only for its own immutable deployment, atomically mints the entire supply into custody and clears mint authority. +- Prefund every PDA independently before creation: pool, mint, token vault, SOL reserve, fee vault. Preparation/activation must succeed without accepting preowned/reinitialized accounts. +- Exercise cancellation followed by preparation/activation replay, wrong owners/mints/program IDs/signers/seeds, frozen/delegated accounts, duplicate mutable accounts and counterfeit events. +- Verify transaction rollback after a successful first CPI and deliberately failing later CPI, insufficient fee/rent, expired blockhash, slippage race, failed simulation and multiple swaps in one transaction. +- Verify ordinary SPL transfers/burns/donations, fee claims below recipient rent thresholds, off-curve smart wallets and one unusable fee recipient not blocking others. No transfer to a recipient may consume trading reserves. +- Compare final vault balances, fee liabilities, pool sequence, SDK quotes and indexed successful trades. Run finalized-history recovery and independent RPC reconciliation. + +The offline runtime suite covers the listed subset, not a full networked-validator or production-wallet rehearsal. Remaining adversarial combinations, long randomized runtime campaigns, final-cluster feature compatibility, rent/compute costs, full indexing, real-wallet failure handling and deployment-bytecode reproduction must be evidenced before release. CI should fail on build/test errors; it must never deploy or revoke authority. Independent protocol security and economic review are still required after automated checks. diff --git a/solana/Cargo.lock b/solana/Cargo.lock new file mode 100644 index 00000000..80418b99 --- /dev/null +++ b/solana/Cargo.lock @@ -0,0 +1,5573 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "agave-bls12-381" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210b1ef312273aa81ccb4c52687d96e3cf07621f3619a7998be20eb9741b08e3" +dependencies = [ + "blst", + "blstrs", + "bytemuck", + "bytemuck_derive", + "group", + "pairing", +] + +[[package]] +name = "agave-feature-set" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde74a2d1f2f99a3ea59938d1533c7973c344e47d24c1b645ee81e958c54226a" +dependencies = [ + "ahash", + "solana-epoch-schedule 3.0.0", + "solana-hash 4.2.0", + "solana-keypair", + "solana-pubkey 4.1.0", + "solana-sha256-hasher 3.1.0", + "solana-svm-feature-set", +] + +[[package]] +name = "agave-reserved-account-keys" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798e559c514af005950ea81586a3856f9297ecb80a7359057c19bf6717f5f537" +dependencies = [ + "agave-feature-set", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "agave-syscalls" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84debd4abe0cbab5a6aac2ee50e3969ef0e0961f7dff7e8f96bda0be7998bca2" +dependencies = [ + "agave-bls12-381", + "bincode", + "libsecp256k1", + "num-traits", + "solana-account 3.4.0", + "solana-account-info 3.1.1", + "solana-big-mod-exp 3.0.0", + "solana-blake3-hasher 3.1.0", + "solana-bn254", + "solana-clock 3.0.1", + "solana-cpi 3.1.0", + "solana-curve25519 4.0.1", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-keccak-hasher 3.1.0", + "solana-loader-v3-interface 6.1.0", + "solana-poseidon", + "solana-program-entrypoint 3.1.1", + "solana-program-runtime", + "solana-pubkey 4.1.0", + "solana-sbpf", + "solana-sdk-ids 3.1.0", + "solana-secp256k1-recover 3.3.0", + "solana-sha256-hasher 3.1.0", + "solana-stable-layout 3.0.1", + "solana-stake-interface 2.0.2", + "solana-svm-callback", + "solana-svm-feature-set", + "solana-svm-log-collector", + "solana-svm-measure", + "solana-svm-timings", + "solana-svm-type-overrides", + "solana-sysvar 3.1.1", + "solana-sysvar-id 3.1.0", + "solana-transaction-context", + "thiserror 2.0.21", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anchor-attribute-access-control" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7122e95b3e5256a548bd7f6642704c052db3b14d9d70a65d80358d16e29c98f" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9a9d17a623263f338511b4a849c4accd99be03b84a8f7b9f64d0988a29771c9" +dependencies = [ + "anchor-syn", + "bs58", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c34dc7f6a6d39d64102564e2ca4a747b6b1e97e467fb0763a0c2b94a50f952" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21a0775db8a924515fc252a21c16e8f320af6da1e535d024a7794d813d267ad" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb2e11a313bd117800be14f7ad786c03a3f89faf3159cdc2e5326bcc6b5ea04" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801aa0c02c94cd3456ed336e1a178d3311f0822c5c4b0f1eb59be4c686accbee" +dependencies = [ + "anchor-lang-idl", + "anchor-syn", + "anyhow", + "bs58", + "heck", + "proc-macro2", + "quote", + "serde_json", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933704da621849cd37cd1862fbaa972956328fbafc8edf27d8cc46af6cc1bb" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1160994c286598c7bb4e35ad7884296a483b12c08e1ccd5a8ba36cffb5857668" +dependencies = [ + "anchor-syn", + "borsh-derive-internal", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-space" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fd8555bd071a4f1f43656f27fdaf576532278c4562083676f819529e96ae5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6bab117055905e930f762c196e08f861f8dfe7241b92cee46677a3b15561a0a" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-program", + "anchor-derive-accounts", + "anchor-derive-serde", + "anchor-derive-space", + "anchor-lang-idl", + "base64 0.21.7", + "bincode", + "borsh 0.10.4", + "bytemuck", + "solana-program", + "thiserror 1.0.69", +] + +[[package]] +name = "anchor-lang-idl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47914b4290ae2bdf4ec203aa821e6eba86d7c78ef497918938038dcc6919f953" +dependencies = [ + "anchor-lang-idl-spec", + "anyhow", + "heck", + "regex", + "serde", + "serde_json", + "sha2 0.10.9", +] + +[[package]] +name = "anchor-lang-idl-spec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdf143115440fe621bdac3a29a1f7472e09f6cd82b2aa569429a0c13f103838" +dependencies = [ + "anyhow", + "serde", +] + +[[package]] +name = "anchor-spl" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c08cb5d762c0694f74bd02c9a5b04ea53cefc496e2c27b3234acffca5cd076b" +dependencies = [ + "anchor-lang", + "spl-pod", + "spl-token", + "spl-token-2022", + "spl-token-group-interface", + "spl-token-metadata-interface", +] + +[[package]] +name = "anchor-syn" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f085a93ab107b9ab2ec70be125b17d2c64c05eebf4da52281a16613a588613" +dependencies = [ + "anyhow", + "bs58", + "cargo_toml", + "heck", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.9", + "syn 1.0.109", + "thiserror 1.0.69", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint 0.4.8", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.8", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint 0.4.8", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.8", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.8", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.8", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint 0.4.8", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.8", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.8", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "blst" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "blstrs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" +dependencies = [ + "blst", + "byte-slice-cast", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", +] + +[[package]] +name = "borsh" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" +dependencies = [ + "borsh-derive 0.10.4", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "borsh-derive 1.8.1", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831213f80d9423998dd696e2c5345aba6be7a0bd8cd19e31c5243e13df1cef89" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65d6ba50644c98714aa2a70d13d7df3cd75cd2b523a2b452bf010443800976b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276691d96f063427be83e6692b86148e488ebba9f48f77788724ca027ba3b6d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cargo_toml" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a98356df42a2eb1bd8f1793ae4ee4de48e384dd974ce5eac8eee802edb7492be" +dependencies = [ + "serde", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a649af8a827553c29fb0cb4bd4a6f1a0dd695bd3232b9bc98bd9c8a3ffbb8b" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rand_core 0.6.4", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core 0.24.1", + "darling_macro 0.24.1", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.7", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core 0.24.1", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "eager" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe71d579d1812060163dff96056261deb5bf6729b100fa2e36a68b9649ba3d3" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "five8" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b8549488b4715defcb0d8a8a1c1c76a80661b5fa106b4ca0e7fce59d7d875" +dependencies = [ + "five8_core 0.1.2", +] + +[[package]] +name = "five8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" +dependencies = [ + "five8_core 0.1.2", +] + +[[package]] +name = "five8_const" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b" +dependencies = [ + "five8_core 0.1.2", +] + +[[package]] +name = "five8_const" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" +dependencies = [ + "five8_core 0.1.2", +] + +[[package]] +name = "five8_core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" + +[[package]] +name = "five8_core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand 0.8.8", + "rand_core 0.6.4", + "rand_xorshift", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.9", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "launch-pool" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "bincode", + "serde_json", +] + +[[package]] +name = "launch-pool-svm" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "bincode", + "five8_core 0.1.2", + "launch-pool", + "litesvm", + "solana-account 3.4.0", + "solana-address 2.4.0", + "solana-clock 3.0.1", + "solana-instruction 3.2.0", + "solana-keypair", + "solana-message 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-signer 3.0.0", + "solana-transaction", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ff 0.4.2", + "num-bigint 0.4.8", + "thiserror 1.0.69", +] + +[[package]] +name = "light-poseidon" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d3992daf44efce5f36b8e61dc5373ee5ff7485cc59d02f5d8afac8a57067eb" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "num-bigint 0.4.8", + "thiserror 1.0.69", +] + +[[package]] +name = "litesvm" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e00083aad2a7aa9d6900454604f7776da40be57304e5119f09222a1e9b105a" +dependencies = [ + "agave-feature-set", + "agave-reserved-account-keys", + "agave-syscalls", + "ansi_term", + "bincode", + "indexmap", + "itertools 0.14.0", + "log", + "serde", + "solana-account 3.4.0", + "solana-address 2.4.0", + "solana-address-lookup-table-interface 3.0.1", + "solana-bpf-loader-program", + "solana-builtins", + "solana-clock 3.0.1", + "solana-compute-budget", + "solana-compute-budget-instruction", + "solana-epoch-rewards 3.0.1", + "solana-epoch-schedule 3.0.0", + "solana-feature-gate-interface 3.1.0", + "solana-fee", + "solana-fee-structure", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-instruction-error", + "solana-instructions-sysvar 3.0.0", + "solana-keypair", + "solana-last-restart-slot 3.0.1", + "solana-loader-v3-interface 6.1.0", + "solana-loader-v4-interface 3.1.0", + "solana-loader-v4-program", + "solana-message 3.1.0", + "solana-native-token 3.0.0", + "solana-nonce 3.1.0", + "solana-nonce-account", + "solana-precompile-error", + "solana-program-error 3.0.1", + "solana-program-runtime", + "solana-rent 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-sha256-hasher 3.1.0", + "solana-signature 3.3.0", + "solana-signer 3.0.0", + "solana-slot-hashes 3.0.1", + "solana-slot-history 3.0.1", + "solana-stake-interface 2.0.2", + "solana-svm-callback", + "solana-svm-log-collector", + "solana-svm-timings", + "solana-svm-transaction", + "solana-system-interface 3.1.0", + "solana-system-program", + "solana-sysvar 3.1.1", + "solana-sysvar-id 3.1.0", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error 3.1.0", + "thiserror 2.0.21", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "percentage" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd23b938276f14057220b707937bcb42fa76dda7560e57a2da30cb52d557937" +dependencies = [ + "num", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml 0.5.11", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.17+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_with" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" +dependencies = [ + "serde_core", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" +dependencies = [ + "darling 0.24.1", + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + +[[package]] +name = "solana-account" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f949fe4edaeaea78c844023bfc1c898e0b1f5a100f8a8d2d0f85d0a7b090258" +dependencies = [ + "solana-account-info 2.3.0", + "solana-clock 2.2.3", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", +] + +[[package]] +name = "solana-account" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc0ed36decb689413b9da5d57f2be49eea5bebb3cf7897015167b0c4336e731" +dependencies = [ + "bincode", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info 3.1.1", + "solana-clock 3.0.1", + "solana-instruction-error", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", + "solana-sysvar 3.1.1", +] + +[[package]] +name = "solana-account-info" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f5152a288ef1912300fc6efa6c2d1f9bb55d9398eb6c72326360b8063987da" +dependencies = [ + "bincode", + "serde", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-pubkey 2.4.0", +] + +[[package]] +name = "solana-account-info" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9cf16495d9eb53e3d04e72366a33bb1c20c24e78c171d8b8f5978357b63ae95" +dependencies = [ + "solana-address 2.4.0", + "solana-program-error 3.0.1", + "solana-program-memory 3.1.0", +] + +[[package]] +name = "solana-address" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ecac8e1b7f74c2baa9e774c42817e3e75b20787134b76cc4d45e8a604488f5" +dependencies = [ + "solana-address 2.4.0", +] + +[[package]] +name = "solana-address" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f67735365edc7fb19ed74ec950597107c8ee9cbfebac57b8868b3e78fb6df16" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "five8 1.0.0", + "five8_const 1.0.0", + "serde", + "serde_derive", + "sha2-const-stable", + "solana-atomic-u64 3.0.1", + "solana-define-syscall 5.2.0", + "solana-program-error 3.0.1", + "solana-sanitize 3.0.1", + "solana-sha256-hasher 3.1.0", +] + +[[package]] +name = "solana-address-lookup-table-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1673f67efe870b64a65cb39e6194be5b26527691ce5922909939961a6e6b395" +dependencies = [ + "bincode", + "bytemuck", + "serde", + "serde_derive", + "solana-clock 2.2.3", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-slot-hashes 2.2.1", +] + +[[package]] +name = "solana-address-lookup-table-interface" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e8df0b083c10ce32490410f3795016b1b5d9b4d094658c0a5e496753645b7cd" +dependencies = [ + "bincode", + "bytemuck", + "serde", + "serde_derive", + "solana-clock 3.0.1", + "solana-instruction 3.2.0", + "solana-instruction-error", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", + "solana-slot-hashes 3.0.1", +] + +[[package]] +name = "solana-atomic-u64" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52e52720efe60465b052b9e7445a01c17550666beec855cce66f44766697bc2" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-atomic-u64" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085db4906d89324cef2a30840d59eaecf3d4231c560ec7c9f6614a93c652f501" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-big-mod-exp" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567" +dependencies = [ + "num-bigint 0.4.8", + "num-traits", + "solana-define-syscall 2.3.0", +] + +[[package]] +name = "solana-big-mod-exp" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c80fb6d791b3925d5ec4bf23a7c169ef5090c013059ec3ed7d0b2c04efa085" +dependencies = [ + "num-bigint 0.4.8", + "num-traits", + "solana-define-syscall 3.0.0", +] + +[[package]] +name = "solana-bincode" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" +dependencies = [ + "bincode", + "serde", + "solana-instruction 2.3.3", +] + +[[package]] +name = "solana-bincode" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278a1a5bad62cd9da89ac8d4b7ec444e83caa8ae96aa656dfc27684b28d49a5d" +dependencies = [ + "bincode", + "serde_core", + "solana-instruction-error", +] + +[[package]] +name = "solana-blake3-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0801e25a1b31a14494fc80882a036be0ffd290efc4c2d640bfcca120a4672" +dependencies = [ + "blake3", + "solana-define-syscall 2.3.0", + "solana-hash 2.3.0", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-blake3-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7116e1d942a2432ca3f514625104757ab8a56233787e95144c93950029e31176" +dependencies = [ + "blake3", + "solana-define-syscall 4.0.1", + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-bls-signatures" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3d8a6e1a009bddbdbfe13ee6ff206c16afa9f8fae7d04612d779ac2254ad5f" +dependencies = [ + "base64 0.22.1", + "blst", + "blstrs", + "cfg_eval", + "ff", + "group", + "pairing", + "rand 0.8.8", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-bn254" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ff13a8867fcc7b0f1114764e1bf6191b4551dcaf93729ddc676cd4ec6abc9f" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "bytemuck", + "solana-define-syscall 5.2.0", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-borsh" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718333bcd0a1a7aed6655aa66bef8d7fb047944922b2d3a18f49cbc13e73d004" +dependencies = [ + "borsh 0.10.4", + "borsh 1.8.1", +] + +[[package]] +name = "solana-borsh" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c04abbae16f57178a163125805637b8a076175bb5c0002fb04f4792bea901cf7" +dependencies = [ + "borsh 1.8.1", +] + +[[package]] +name = "solana-bpf-loader-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219bfba64973ac9e64aa181f03fd56ac319e2d50d8a23d16c54bbd7fa9807a47" +dependencies = [ + "agave-syscalls", + "bincode", + "qualifier_attr", + "solana-account 3.4.0", + "solana-bincode 3.1.0", + "solana-clock 3.0.1", + "solana-instruction 3.2.0", + "solana-loader-v3-interface 6.1.0", + "solana-loader-v4-interface 3.1.0", + "solana-packet", + "solana-program-entrypoint 3.1.1", + "solana-program-runtime", + "solana-pubkey 4.1.0", + "solana-sbpf", + "solana-sdk-ids 3.1.0", + "solana-svm-feature-set", + "solana-svm-log-collector", + "solana-svm-measure", + "solana-svm-type-overrides", + "solana-system-interface 3.1.0", + "solana-transaction-context", +] + +[[package]] +name = "solana-builtins" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda9d147935c741533496edf72c5b712885d4793a0bca13a21bd75d8f5dc30e9" +dependencies = [ + "agave-feature-set", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-hash 4.2.0", + "solana-loader-v4-program", + "solana-program-runtime", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", + "solana-system-program", + "solana-vote-program", + "solana-zk-elgamal-proof-program", + "solana-zk-token-proof-program", +] + +[[package]] +name = "solana-builtins-default-costs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3167997e8ac0fe100c4ed54503568d22204aeda56f4d3549e0c09a700b609aa8" +dependencies = [ + "agave-feature-set", + "ahash", + "log", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-loader-v4-program", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", + "solana-system-program", + "solana-vote-program", +] + +[[package]] +name = "solana-clock" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8584296123df8fe229b95e2ebfd37ae637fe9db9b7d4dd677ac5a78e80dbfce" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-clock" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95cf11109c3b6115cc510f1e31f06fdd52f504271bc24ef5f1249fbbcae5f9f3" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-compute-budget" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b591fbaed6d9ab4cba6a5a82eb5df208072ced2e5b74c59e9d309ff87af0615f" +dependencies = [ + "solana-fee-structure", + "solana-program-runtime", +] + +[[package]] +name = "solana-compute-budget-instruction" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006d9b6a34f9d7b719100653317990ed55e572107702104c054133b40f587306" +dependencies = [ + "agave-feature-set", + "log", + "solana-borsh 3.0.2", + "solana-builtins-default-costs", + "solana-compute-budget", + "solana-compute-budget-interface", + "solana-instruction 3.2.0", + "solana-packet", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", + "solana-svm-transaction", + "solana-transaction-error 3.1.0", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-compute-budget-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8292c436b269ad23cecc8b24f7da3ab07ca111661e25e00ce0e1d22771951ab9" +dependencies = [ + "borsh 1.8.1", + "solana-instruction 3.2.0", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "solana-compute-budget-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22bcf5088ebe5cb2aa548580d0a466de813032b425707a7745a2a63a7764cdc" +dependencies = [ + "solana-program-runtime", +] + +[[package]] +name = "solana-cpi" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" +dependencies = [ + "solana-account-info 2.3.0", + "solana-define-syscall 2.3.0", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-stable-layout 2.2.1", +] + +[[package]] +name = "solana-cpi" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dea26709d867aada85d0d3617db0944215c8bb28d3745b912de7db13a23280c" +dependencies = [ + "solana-account-info 3.1.1", + "solana-define-syscall 4.0.1", + "solana-instruction 3.2.0", + "solana-program-error 3.0.1", + "solana-pubkey 4.1.0", + "solana-stable-layout 3.0.1", +] + +[[package]] +name = "solana-curve25519" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae4261b9a8613d10e77ac831a8fa60b6fa52b9b103df46d641deff9f9812a23" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "solana-define-syscall 2.3.0", + "subtle", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-curve25519" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b4d2a4bf0d0b0a86c22111917e86e8bd39a7b31420fb2c7d73eb83761fc7af" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "solana-define-syscall 5.2.0", + "subtle", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-decode-error" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c781686a18db2f942e70913f7ca15dc120ec38dcab42ff7557db2c70c625a35" +dependencies = [ + "num-traits", +] + +[[package]] +name = "solana-define-syscall" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2" + +[[package]] +name = "solana-define-syscall" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" + +[[package]] +name = "solana-define-syscall" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" + +[[package]] +name = "solana-define-syscall" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8209ece2bd9f1450e672858ffc0e5c8c786ff6916d2a862b126dd0128f380f" + +[[package]] +name = "solana-derivation-path" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939756d798b25c5ec3cca10e06212bdca3b1443cb9bb740a38124f58b258737b" +dependencies = [ + "derivation-path", + "qstring", + "uriparse", +] + +[[package]] +name = "solana-derivation-path" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff71743072690fdbdfcdc37700ae1cb77485aaad49019473a81aee099b1e0b8c" +dependencies = [ + "derivation-path", + "qstring", + "uriparse", +] + +[[package]] +name = "solana-epoch-rewards" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b575d3dd323b9ea10bb6fe89bf6bf93e249b215ba8ed7f68f1a3633f384db7" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 2.3.0", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-epoch-rewards" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e7b0ba210593ba8ddd39d6d234d81795d1671cebf3026baa10d5dc23ac42f0" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 4.2.0", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-epoch-schedule" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fce071fbddecc55d727b1d7ed16a629afe4f6e4c217bc8d00af3b785f6f67ed" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-epoch-schedule" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5481e72cc4d52c169db73e4c0cd16de8bc943078aac587ec4817a75cc6388f" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-example-mocks" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3" +dependencies = [ + "serde", + "serde_derive", + "solana-address-lookup-table-interface 2.2.2", + "solana-clock 2.2.3", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-keccak-hasher 2.2.1", + "solana-message 2.4.0", + "solana-nonce 2.2.1", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-feature-gate-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f5c5382b449e8e4e3016fb05e418c53d57782d8b5c30aa372fc265654b956d" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account 2.2.1", + "solana-account-info 2.3.0", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-feature-gate-interface" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ca9b5cbb6f500f7fd73db5bd95640f71a83f04d6121a0e59a43b202dca2731" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account 3.4.0", + "solana-account-info 3.1.1", + "solana-instruction 3.2.0", + "solana-program-error 3.0.1", + "solana-pubkey 4.1.0", + "solana-rent 4.5.0", + "solana-sdk-ids 3.1.0", + "solana-system-interface 3.1.0", +] + +[[package]] +name = "solana-fee" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e506f6ec94e5733b0f2114b43bd8a2abac33a0256e19c65e1d119de008981339" +dependencies = [ + "agave-feature-set", + "solana-fee-structure", + "solana-svm-transaction", +] + +[[package]] +name = "solana-fee-calculator" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89bc408da0fb3812bc3008189d148b4d3e08252c79ad810b245482a3f70cd8d" +dependencies = [ + "log", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-fee-calculator" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef67f01cc6a0c72e99a08d0d484683f995de4c80e9568728fa77d1537f9b7e09" +dependencies = [ + "log", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-fee-structure" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2abdb1223eea8ec64136f39cb1ffcf257e00f915c957c35c0dd9e3f4e700b0" + +[[package]] +name = "solana-hash" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b96e9f0300fa287b545613f007dfe20043d7812bee255f418c1eb649c93b63" +dependencies = [ + "borsh 1.8.1", + "bytemuck", + "bytemuck_derive", + "five8 0.2.1", + "js-sys", + "serde", + "serde_derive", + "solana-atomic-u64 2.2.1", + "solana-sanitize 2.2.1", + "wasm-bindgen", +] + +[[package]] +name = "solana-hash" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "337c246447142f660f778cf6cb582beba8e28deb05b3b24bfb9ffd7c562e5f41" +dependencies = [ + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-hash" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8064ea1d591ec791be95245058ca40f4f5345d390c200069d0f79bbf55bfae55" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "five8 1.0.0", + "serde", + "serde_derive", + "solana-atomic-u64 3.0.1", + "solana-sanitize 3.0.1", +] + +[[package]] +name = "solana-instruction" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab5682934bd1f65f8d2c16f21cb532526fcc1a09f796e2cacdb091eee5774ad" +dependencies = [ + "bincode", + "borsh 1.8.1", + "getrandom 0.2.17", + "js-sys", + "num-traits", + "serde", + "serde_derive", + "serde_json", + "solana-define-syscall 2.3.0", + "solana-pubkey 2.4.0", + "wasm-bindgen", +] + +[[package]] +name = "solana-instruction" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6d22d0a6fdf345be294bb9afdcd40c296cdc095e64e7ceaa3bb3c2f608c1c" +dependencies = [ + "bincode", + "borsh 1.8.1", + "serde", + "serde_derive", + "solana-define-syscall 5.2.0", + "solana-instruction-error", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-instruction-error" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3d048edaaeef5a3dc8c01853e585539a74417e4c2d43a9e2c161270045b838" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-program-error 3.0.1", +] + +[[package]] +name = "solana-instructions-sysvar" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0e85a6fad5c2d0c4f5b91d34b8ca47118fc593af706e523cdbedf846a954f57" +dependencies = [ + "bitflags", + "solana-account-info 2.3.0", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-serialize-utils 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-instructions-sysvar" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddf67876c541aa1e21ee1acae35c95c6fbc61119814bfef70579317a5e26955" +dependencies = [ + "bitflags", + "solana-account-info 3.1.1", + "solana-instruction 3.2.0", + "solana-instruction-error", + "solana-program-error 3.0.1", + "solana-pubkey 3.0.0", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-serialize-utils 3.1.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-keccak-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7aeb957fbd42a451b99235df4942d96db7ef678e8d5061ef34c9b34cae12f79" +dependencies = [ + "sha3", + "solana-define-syscall 2.3.0", + "solana-hash 2.3.0", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-keccak-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed1c0d16d6fdeba12291a1f068cdf0d479d9bff1141bf44afd7aa9d485f65ef8" +dependencies = [ + "sha3", + "solana-define-syscall 4.0.1", + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-keypair" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263d614c12aa267a3278703175fd6440552ca61bc960b5a02a4482720c53438b" +dependencies = [ + "ed25519-dalek", + "five8 1.0.0", + "five8_core 1.0.0", + "rand 0.9.5", + "solana-address 2.4.0", + "solana-seed-phrase 3.0.0", + "solana-signature 3.3.0", + "solana-signer 3.0.0", +] + +[[package]] +name = "solana-last-restart-slot" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6360ac2fdc72e7463565cd256eedcf10d7ef0c28a1249d261ec168c1b55cdd" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-last-restart-slot" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426711c6564b790026e45cabec3c64b971864c48b6b2d83c0ebf52a118bb4cda" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-loader-v2-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ab08006dad78ae7cd30df8eea0539e207d08d91eaefb3e1d49a446e1c49654" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", +] + +[[package]] +name = "solana-loader-v3-interface" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f7162a05b8b0773156b443bccd674ea78bb9aa406325b467ea78c06c99a63a2" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-loader-v3-interface" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee44c9b1328c5c712c68966fb8de07b47f3e7bac006e74ddd1bb053d3e46e5d" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 3.2.0", + "solana-pubkey 3.0.0", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "solana-loader-v4-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706a777242f1f39a83e2a96a2a6cb034cb41169c6ecbee2cf09cb873d9659e7e" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-loader-v4-interface" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c948b33ff81fa89699911b207059e493defdba9647eaf18f23abdf3674e0fb" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 3.2.0", + "solana-pubkey 3.0.0", + "solana-sdk-ids 3.1.0", + "solana-system-interface 2.0.0", +] + +[[package]] +name = "solana-loader-v4-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b5191cd34f04e4ec9fd5f2ac8a431ba9ffd6c827511fd35f2cae0256a0c6b12" +dependencies = [ + "log", + "solana-account 3.4.0", + "solana-bincode 3.1.0", + "solana-bpf-loader-program", + "solana-instruction 3.2.0", + "solana-loader-v3-interface 6.1.0", + "solana-loader-v4-interface 3.1.0", + "solana-packet", + "solana-program-runtime", + "solana-pubkey 4.1.0", + "solana-sbpf", + "solana-sdk-ids 3.1.0", + "solana-svm-log-collector", + "solana-svm-measure", + "solana-svm-type-overrides", + "solana-transaction-context", +] + +[[package]] +name = "solana-message" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1796aabce376ff74bf89b78d268fa5e683d7d7a96a0a4e4813ec34de49d5314b" +dependencies = [ + "bincode", + "blake3", + "lazy_static", + "serde", + "serde_derive", + "solana-bincode 2.2.1", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-short-vec 2.2.1", + "solana-system-interface 1.0.0", + "solana-transaction-error 2.2.1", + "wasm-bindgen", +] + +[[package]] +name = "solana-message" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0448b1fd891c5f46491e5dc7d9986385ba3c852c340db2911dd29faa01d2b08d" +dependencies = [ + "bincode", + "blake3", + "lazy_static", + "serde", + "serde_derive", + "solana-address 2.4.0", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-short-vec 3.3.0", + "solana-transaction-error 3.1.0", +] + +[[package]] +name = "solana-msg" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092" +dependencies = [ + "solana-define-syscall 2.3.0", +] + +[[package]] +name = "solana-msg" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726b7cbbc6be6f1c6f29146ac824343b9415133eee8cce156452ad1db93f8008" +dependencies = [ + "solana-define-syscall 5.2.0", +] + +[[package]] +name = "solana-native-token" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61515b880c36974053dd499c0510066783f0cc6ac17def0c7ef2a244874cf4a9" + +[[package]] +name = "solana-native-token" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8dd4c280dca9d046139eb5b7a5ac9ad10403fbd64964c7d7571214950d758f" + +[[package]] +name = "solana-nonce" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703e22eb185537e06204a5bd9d509b948f0066f2d1d814a6f475dafb3ddf1325" +dependencies = [ + "serde", + "serde_derive", + "solana-fee-calculator 2.2.1", + "solana-hash 2.3.0", + "solana-pubkey 2.4.0", + "solana-sha256-hasher 2.3.0", +] + +[[package]] +name = "solana-nonce" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc469152a63284ef959b80c59cda015262a021da55d3b8fe42171d89c4b64f8" +dependencies = [ + "serde", + "serde_derive", + "solana-fee-calculator 3.2.2", + "solana-hash 4.2.0", + "solana-pubkey 4.1.0", + "solana-sha256-hasher 3.1.0", +] + +[[package]] +name = "solana-nonce-account" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805fd25b29e5a1a0e6c3dd6320c9da80f275fbe4ff6e392617c303a2085c435e" +dependencies = [ + "solana-account 3.4.0", + "solana-hash 3.1.0", + "solana-nonce 3.1.0", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "solana-packet" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad62e1045c2347a0c0e219a6ceb0abfe904be622920996bfcac8d116fabe3c7" +dependencies = [ + "bitflags", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-poseidon" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737b8ab25bf4cc8e618f80f1fe40709b2ace708bc764a36b8a4c81eea8c07034" +dependencies = [ + "ark-bn254 0.4.0", + "ark-bn254 0.5.0", + "light-poseidon 0.2.0", + "light-poseidon 0.4.1", + "solana-define-syscall 4.0.1", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-precompile-error" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cafcd950de74c6c39d55dc8ca108bbb007799842ab370ef26cf45a34453c31e1" +dependencies = [ + "num-traits", +] + +[[package]] +name = "solana-program" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98eca145bd3545e2fbb07166e895370576e47a00a7d824e325390d33bf467210" +dependencies = [ + "bincode", + "blake3", + "borsh 0.10.4", + "borsh 1.8.1", + "bs58", + "bytemuck", + "console_error_panic_hook", + "console_log", + "getrandom 0.2.17", + "lazy_static", + "log", + "memoffset", + "num-bigint 0.4.8", + "num-derive", + "num-traits", + "rand 0.8.8", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info 2.3.0", + "solana-address-lookup-table-interface 2.2.2", + "solana-atomic-u64 2.2.1", + "solana-big-mod-exp 2.2.1", + "solana-bincode 2.2.1", + "solana-blake3-hasher 2.2.1", + "solana-borsh 2.2.1", + "solana-clock 2.2.3", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-define-syscall 2.3.0", + "solana-epoch-rewards 2.2.1", + "solana-epoch-schedule 2.2.1", + "solana-example-mocks", + "solana-feature-gate-interface 2.2.2", + "solana-fee-calculator 2.2.1", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-instructions-sysvar 2.2.2", + "solana-keccak-hasher 2.2.1", + "solana-last-restart-slot 2.2.1", + "solana-loader-v2-interface", + "solana-loader-v3-interface 5.0.0", + "solana-loader-v4-interface 2.2.1", + "solana-message 2.4.0", + "solana-msg 2.2.1", + "solana-native-token 2.3.0", + "solana-nonce 2.2.1", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-program-option", + "solana-program-pack", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-secp256k1-recover 2.2.1", + "solana-serde-varint 2.2.2", + "solana-serialize-utils 2.2.1", + "solana-sha256-hasher 2.3.0", + "solana-short-vec 2.2.1", + "solana-slot-hashes 2.2.1", + "solana-slot-history 2.2.1", + "solana-stable-layout 2.2.1", + "solana-stake-interface 1.2.1", + "solana-system-interface 1.0.0", + "solana-sysvar 2.3.0", + "solana-sysvar-id 2.2.1", + "solana-vote-interface 2.2.6", + "thiserror 2.0.21", + "wasm-bindgen", +] + +[[package]] +name = "solana-program-entrypoint" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ce041b1a0ed275290a5008ee1a4a6c48f5054c8a3d78d313c08958a06aedbd" +dependencies = [ + "solana-account-info 2.3.0", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", +] + +[[package]] +name = "solana-program-entrypoint" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c9b0a1ff494e05f503a08b3d51150b73aa639544631e510279d6375f290997" +dependencies = [ + "solana-account-info 3.1.1", + "solana-define-syscall 4.0.1", + "solana-program-error 3.0.1", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-program-error" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee2e0217d642e2ea4bee237f37bd61bb02aec60da3647c48ff88f6556ade775" +dependencies = [ + "borsh 1.8.1", + "num-traits", + "serde", + "serde_derive", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-pubkey 2.4.0", +] + +[[package]] +name = "solana-program-error" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f04fa578707b3612b095f0c8e19b66a1233f7c42ca8082fcb3b745afcc0add6" + +[[package]] +name = "solana-program-memory" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a5426090c6f3fd6cfdc10685322fede9ca8e5af43cd6a59e98bfe4e91671712" +dependencies = [ + "solana-define-syscall 2.3.0", +] + +[[package]] +name = "solana-program-memory" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4068648649653c2c50546e9a7fb761791b5ab0cda054c771bb5808d3a4b9eb52" +dependencies = [ + "solana-define-syscall 4.0.1", +] + +[[package]] +name = "solana-program-option" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc677a2e9bc616eda6dbdab834d463372b92848b2bfe4a1ed4e4b4adba3397d0" + +[[package]] +name = "solana-program-pack" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" +dependencies = [ + "solana-program-error 2.2.2", +] + +[[package]] +name = "solana-program-runtime" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c7f89c89d5ff25f64a41c8cb00478b1d62f941f14a7dd8537c9e50bb2acc92" +dependencies = [ + "base64 0.22.1", + "bincode", + "cfg-if", + "itertools 0.14.0", + "log", + "percentage", + "rand 0.9.5", + "serde", + "solana-account 3.4.0", + "solana-account-info 3.1.1", + "solana-clock 3.0.1", + "solana-epoch-rewards 3.0.1", + "solana-epoch-schedule 3.0.0", + "solana-fee-structure", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-last-restart-slot 3.0.1", + "solana-loader-v3-interface 6.1.0", + "solana-program-entrypoint 3.1.1", + "solana-pubkey 4.1.0", + "solana-rent 3.1.0", + "solana-sbpf", + "solana-sdk-ids 3.1.0", + "solana-slot-hashes 3.0.1", + "solana-stable-layout 3.0.1", + "solana-stake-interface 2.0.2", + "solana-svm-callback", + "solana-svm-feature-set", + "solana-svm-log-collector", + "solana-svm-measure", + "solana-svm-timings", + "solana-svm-transaction", + "solana-svm-type-overrides", + "solana-system-interface 3.1.0", + "solana-sysvar 3.1.1", + "solana-sysvar-id 3.1.0", + "solana-transaction-context", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-pubkey" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b62adb9c3261a052ca1f999398c388f1daf558a1b492f60a6d9e64857db4ff1" +dependencies = [ + "borsh 0.10.4", + "borsh 1.8.1", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "five8 0.2.1", + "five8_const 0.1.4", + "getrandom 0.2.17", + "js-sys", + "num-traits", + "serde", + "serde_derive", + "solana-atomic-u64 2.2.1", + "solana-decode-error", + "solana-define-syscall 2.3.0", + "solana-sanitize 2.2.1", + "solana-sha256-hasher 2.3.0", + "wasm-bindgen", +] + +[[package]] +name = "solana-pubkey" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" +dependencies = [ + "solana-address 1.1.0", +] + +[[package]] +name = "solana-pubkey" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b06bd918d60111ee1f97de817113e2040ca0cedb740099ee8d646233f6b906c" +dependencies = [ + "solana-address 2.4.0", +] + +[[package]] +name = "solana-rent" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1aea8fdea9de98ca6e8c2da5827707fb3842833521b528a713810ca685d2480" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-rent" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e860d5499a705369778647e97d760f7670adfb6fc8419dd3d568deccd46d5487" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-rent" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7c7590d4e53446a00c363783d98199d8c8f5099ca60eaf2a0f1f50d0e7a31e" +dependencies = [ + "solana-sdk-macro 3.0.1", +] + +[[package]] +name = "solana-sanitize" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" + +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + +[[package]] +name = "solana-sbpf" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733b3657a0fab205102b799dbe17f85d3972cf984232c1b0b108fa6ba438e382" +dependencies = [ + "byteorder", + "combine", + "hash32", + "libc", + "log", + "rand 0.8.8", + "rustc-demangle", + "thiserror 2.0.21", + "winapi", +] + +[[package]] +name = "solana-sdk-ids" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" +dependencies = [ + "solana-pubkey 2.4.0", +] + +[[package]] +name = "solana-sdk-ids" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def234c1956ff616d46c9dd953f251fa7096ddbaa6d52b165218de97882b7280" +dependencies = [ + "solana-address 2.4.0", +] + +[[package]] +name = "solana-sdk-macro" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86280da8b99d03560f6ab5aca9de2e38805681df34e0bb8f238e69b29433b9df" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "solana-sdk-macro" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8765316242300c48242d84a41614cb3388229ec353ba464f6fe62a733e41806f" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "solana-secp256k1-recover" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" +dependencies = [ + "libsecp256k1", + "solana-define-syscall 2.3.0", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-secp256k1-recover" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce0d2e8d53946f04e789476d6c407e4997b7fb412483df5fc10c075c65cb2edf" +dependencies = [ + "k256", + "solana-define-syscall 5.2.0", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-security-txt" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94a02d486b28f219a4f8f5d7dd93cbfbb93c9f466cb7871c22e50cd5ae9a7a2" + +[[package]] +name = "solana-seed-derivable" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beb82b5adb266c6ea90e5cf3967235644848eac476c5a1f2f9283a143b7c97f" +dependencies = [ + "solana-derivation-path 2.2.1", +] + +[[package]] +name = "solana-seed-derivable" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7bdb72758e3bec33ed0e2658a920f1f35dfb9ed576b951d20d63cb61ecd95c" +dependencies = [ + "solana-derivation-path 3.0.0", +] + +[[package]] +name = "solana-seed-phrase" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" +dependencies = [ + "hmac", + "pbkdf2", + "sha2 0.10.9", +] + +[[package]] +name = "solana-seed-phrase" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc905b200a95f2ea9146e43f2a7181e3aeb55de6bc12afb36462d00a3c7310de" +dependencies = [ + "hmac", + "pbkdf2", + "sha2 0.10.9", +] + +[[package]] +name = "solana-serde-varint" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7e155eba458ecfb0107b98236088c3764a09ddf0201ec29e52a0be40857113" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serde-varint" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950e5b83e839dc0f92c66afc124bb8f40e89bc90f0579e8ec5499296d27f54e3" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serialize-utils" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e" +dependencies = [ + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-serialize-utils" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7cc401931d178472358e6b78dc72d031dc08f752d7410f0e8bd259dd6f02fa" +dependencies = [ + "solana-instruction-error", + "solana-pubkey 4.1.0", + "solana-sanitize 3.0.1", +] + +[[package]] +name = "solana-sha256-hasher" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa3feb32c28765f6aa1ce8f3feac30936f16c5c3f7eb73d63a5b8f6f8ecdc44" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall 2.3.0", + "solana-hash 2.3.0", +] + +[[package]] +name = "solana-sha256-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7dc3011ea4c0334aaaa7e7128cb390ecf546b28d412e9bf2064680f57f588f" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall 4.0.1", + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-short-vec" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c54c66f19b9766a56fa0057d060de8378676cb64987533fa088861858fc5a69" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-short-vec" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75782529cc4521114c1ecd71c7e2cdebd41a6fe9844b5d088fd36ef08c57c36" +dependencies = [ + "serde_core", +] + +[[package]] +name = "solana-signature" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c8ec8e657aecfc187522fc67495142c12f35e55ddeca8698edbb738b8dbd8c" +dependencies = [ + "five8 0.2.1", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-signature" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132a93134f1262aa832f1849b83bec6c9945669b866da18661a427943b9e801e" +dependencies = [ + "ed25519-dalek", + "five8 1.0.0", + "serde", + "serde-big-array", + "serde_derive", + "solana-sanitize 3.0.1", + "wincode", +] + +[[package]] +name = "solana-signer" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" +dependencies = [ + "solana-pubkey 2.4.0", + "solana-signature 2.3.0", + "solana-transaction-error 2.2.1", +] + +[[package]] +name = "solana-signer" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bfea97951fee8bae0d6038f39a5efcb6230ecdfe33425ac75196d1a1e3e3235" +dependencies = [ + "solana-pubkey 3.0.0", + "solana-signature 3.3.0", + "solana-transaction-error 3.1.0", +] + +[[package]] +name = "solana-slot-hashes" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8691982114513763e88d04094c9caa0376b867a29577939011331134c301ce" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 2.3.0", + "solana-sdk-ids 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-slot-hashes" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2585f70191623887329dfb5078da3a00e15e3980ea67f42c2e10b07028419f43" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 4.2.0", + "solana-sdk-ids 3.1.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-slot-history" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ccc1b2067ca22754d5283afb2b0126d61eae734fc616d23871b0943b0d935e" +dependencies = [ + "bv", + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-slot-history" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0622d03a823770f7763afd866e012b296d5a3cbbbe51e110b5bd9ab3441efdca" +dependencies = [ + "bv", + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-stable-layout" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" +dependencies = [ + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", +] + +[[package]] +name = "solana-stable-layout" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9f6a291ba063a37780af29e7db14bdd3dc447584d8ba5b3fc4b88e2bbc982fa" +dependencies = [ + "solana-instruction 3.2.0", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-stake-interface" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5269e89fde216b4d7e1d1739cf5303f8398a1ff372a81232abbee80e554a838c" +dependencies = [ + "borsh 0.10.4", + "borsh 1.8.1", + "num-traits", + "serde", + "serde_derive", + "solana-clock 2.2.3", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-system-interface 1.0.0", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-stake-interface" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9bc26191b533f9a6e5a14cca05174119819ced680a80febff2f5051a713f0db" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-clock 3.0.1", + "solana-cpi 3.1.0", + "solana-instruction 3.2.0", + "solana-program-error 3.0.1", + "solana-pubkey 3.0.0", + "solana-system-interface 2.0.0", + "solana-sysvar 3.1.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-svm-callback" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4006b0da7e50cba514ced6b47bcf8f9591552458200e361fd4bdef4068cb2fed" +dependencies = [ + "solana-account 3.4.0", + "solana-clock 3.0.1", + "solana-precompile-error", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-svm-feature-set" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ea15c0d91403375e3d017cc09780cf138b629abba4ccaaa7cf66b1afea1059" + +[[package]] +name = "solana-svm-log-collector" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb7d3ccd3a51b85807ff16b2f513069e8b55e220b280774a3e9b899bcb81987" +dependencies = [ + "log", +] + +[[package]] +name = "solana-svm-measure" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70c9972c1f03cb2bbc64d23dc2079419a66d89b49d6b44f79206530551ddc8c" + +[[package]] +name = "solana-svm-timings" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f3d66aa88c9001a076362108f7967d6a00d121ba38428e56928935566ed5bd" +dependencies = [ + "eager", + "enum-iterator", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-svm-transaction" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "067861db805d135a6fbe489bf2b74d701f270df8d03afd3257f7d51a2ff3467e" +dependencies = [ + "solana-hash 4.2.0", + "solana-message 3.1.0", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", + "solana-signature 3.3.0", + "solana-transaction", +] + +[[package]] +name = "solana-svm-type-overrides" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e41661ebf0edcc296b15251c08fee0ad2da3257e6ab86cea2a0a8f6fba642c6" +dependencies = [ + "rand 0.9.5", +] + +[[package]] +name = "solana-system-interface" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7c18cb1a91c6be5f5a8ac9276a1d7c737e39a21beba9ea710ab4b9c63bc90" +dependencies = [ + "js-sys", + "num-traits", + "serde", + "serde_derive", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "wasm-bindgen", +] + +[[package]] +name = "solana-system-interface" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1790547bfc3061f1ee68ea9d8dc6c973c02a163697b24263a8e9f2e6d4afa2" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-instruction 3.2.0", + "solana-msg 3.1.0", + "solana-program-error 3.0.1", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "solana-system-interface" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a95a6f2e23ed861d6444ad4a6d6896c418d7d101b960787e65a8e33157cee81b" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-address 2.4.0", + "solana-instruction 3.2.0", + "solana-msg 3.1.0", + "solana-program-error 3.0.1", +] + +[[package]] +name = "solana-system-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "450479004fee3396c88cc4aa2f9b2b8db9c77be42ee7c1c53e6fac9eaec5fd51" +dependencies = [ + "bincode", + "log", + "serde", + "solana-account 3.4.0", + "solana-bincode 3.1.0", + "solana-fee-calculator 3.2.2", + "solana-instruction 3.2.0", + "solana-nonce 3.1.0", + "solana-nonce-account", + "solana-packet", + "solana-program-runtime", + "solana-pubkey 4.1.0", + "solana-sdk-ids 3.1.0", + "solana-svm-log-collector", + "solana-svm-type-overrides", + "solana-system-interface 3.1.0", + "solana-sysvar 3.1.1", + "solana-transaction-context", +] + +[[package]] +name = "solana-sysvar" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c3595f95069f3d90f275bb9bd235a1973c4d059028b0a7f81baca2703815db" +dependencies = [ + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info 2.3.0", + "solana-clock 2.2.3", + "solana-define-syscall 2.3.0", + "solana-epoch-rewards 2.2.1", + "solana-epoch-schedule 2.2.1", + "solana-fee-calculator 2.2.1", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-instructions-sysvar 2.2.2", + "solana-last-restart-slot 2.2.1", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-slot-hashes 2.2.1", + "solana-slot-history 2.2.1", + "solana-stake-interface 1.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-sysvar" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6690d3dd88f15c21edff68eb391ef8800df7a1f5cec84ee3e8d1abf05affdf74" +dependencies = [ + "base64 0.22.1", + "bincode", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info 3.1.1", + "solana-clock 3.0.1", + "solana-define-syscall 4.0.1", + "solana-epoch-rewards 3.0.1", + "solana-epoch-schedule 3.0.0", + "solana-fee-calculator 3.2.2", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-last-restart-slot 3.0.1", + "solana-program-entrypoint 3.1.1", + "solana-program-error 3.0.1", + "solana-program-memory 3.1.0", + "solana-pubkey 4.1.0", + "solana-rent 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.1", + "solana-slot-hashes 3.0.1", + "solana-slot-history 3.0.1", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-sysvar-id" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" +dependencies = [ + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", +] + +[[package]] +name = "solana-sysvar-id" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17358d1e9a13e5b9c2264d301102126cf11a47fd394cdf3dec174fe7bc96e1de" +dependencies = [ + "solana-address 2.4.0", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "solana-transaction" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96697cff5075a028265324255efed226099f6d761ca67342b230d09f72cc48d2" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-address 2.4.0", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-instruction-error", + "solana-message 3.1.0", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-short-vec 3.3.0", + "solana-signature 3.3.0", + "solana-signer 3.0.0", + "solana-transaction-error 3.1.0", +] + +[[package]] +name = "solana-transaction-context" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecefe8b30e334e2891ca82da35becd9a3f4c16021d9ca782e2a82adf31084fa3" +dependencies = [ + "bincode", + "serde", + "solana-account 3.4.0", + "solana-instruction 3.2.0", + "solana-instructions-sysvar 3.0.0", + "solana-pubkey 4.1.0", + "solana-rent 3.1.0", + "solana-sbpf", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "solana-transaction-error" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" +dependencies = [ + "solana-instruction 2.3.3", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-transaction-error" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8396904805b0b385b9de115a652fe80fd01e5b98ce0513f4fcd8184ada9bb792" +dependencies = [ + "serde", + "serde_derive", + "solana-instruction-error", + "solana-sanitize 3.0.1", +] + +[[package]] +name = "solana-vote-interface" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b80d57478d6599d30acc31cc5ae7f93ec2361a06aefe8ea79bc81739a08af4c3" +dependencies = [ + "bincode", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "solana-clock 2.2.3", + "solana-decode-error", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-serde-varint 2.2.2", + "solana-serialize-utils 2.2.1", + "solana-short-vec 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-vote-interface" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d444ce30b6b4f9c281ba06061ea96638d063b53c2171b1e41ba02ebff79ed28f" +dependencies = [ + "bincode", + "cfg_eval", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "serde_with", + "solana-clock 3.0.1", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-instruction-error", + "solana-pubkey 4.1.0", + "solana-rent 4.5.0", + "solana-sdk-ids 3.1.0", + "solana-serde-varint 3.0.1", + "solana-serialize-utils 3.1.1", + "solana-short-vec 3.3.0", + "solana-system-interface 3.1.0", +] + +[[package]] +name = "solana-vote-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4537fd6efe65f53ccd28d54d2ad43275b024834a4a8ca4dfa4babfa01e6d11ab" +dependencies = [ + "agave-feature-set", + "bincode", + "log", + "num-derive", + "num-traits", + "serde", + "solana-account 3.4.0", + "solana-bincode 3.1.0", + "solana-bls-signatures", + "solana-clock 3.0.1", + "solana-epoch-schedule 3.0.0", + "solana-hash 4.2.0", + "solana-instruction 3.2.0", + "solana-keypair", + "solana-packet", + "solana-program-runtime", + "solana-pubkey 4.1.0", + "solana-rent 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-signer 3.0.0", + "solana-slot-hashes 3.0.1", + "solana-system-interface 3.1.0", + "solana-transaction", + "solana-transaction-context", + "solana-vote-interface 5.1.1", + "thiserror 2.0.21", +] + +[[package]] +name = "solana-zk-elgamal-proof-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf97ec816e8c6d45b5f05e21381bcc4b856cb3c89b69e465ee20972368b4c31" +dependencies = [ + "agave-feature-set", + "bytemuck", + "num-derive", + "num-traits", + "solana-instruction 3.2.0", + "solana-program-runtime", + "solana-sdk-ids 3.1.0", + "solana-svm-log-collector", + "solana-zk-sdk 5.0.1", +] + +[[package]] +name = "solana-zk-sdk" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b9fc6ec37d16d0dccff708ed1dd6ea9ba61796700c3bb7c3b401973f10f63b" +dependencies = [ + "aes-gcm-siv", + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "itertools 0.12.1", + "js-sys", + "merlin", + "num-derive", + "num-traits", + "rand 0.8.8", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-derivation-path 2.2.1", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-seed-derivable 2.2.1", + "solana-seed-phrase 2.2.1", + "solana-signature 2.3.0", + "solana-signer 2.2.1", + "subtle", + "thiserror 2.0.21", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-zk-sdk" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09670ff59f60e6ddc2209c2e4353992a9b9f01d4e244f3e9d67bd5146e33d388" +dependencies = [ + "aes-gcm-siv", + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "itertools 0.14.0", + "merlin", + "num-derive", + "num-traits", + "rand 0.8.8", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-address 2.4.0", + "solana-derivation-path 3.0.0", + "solana-instruction 3.2.0", + "solana-sdk-ids 3.1.0", + "solana-seed-derivable 3.0.0", + "solana-seed-phrase 3.0.0", + "solana-signature 3.3.0", + "solana-signer 3.0.0", + "subtle", + "thiserror 2.0.21", + "zeroize", +] + +[[package]] +name = "solana-zk-token-proof-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f08a8be7008cec95d74c0ded5ae10b6869bd06bd9761c800e7e098bd45097e6" +dependencies = [ + "solana-program-runtime", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "spl-discriminator" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7398da23554a31660f17718164e31d31900956054f54f52d5ec1be51cb4f4b3" +dependencies = [ + "bytemuck", + "solana-program-error 2.2.2", + "solana-sha256-hasher 2.3.0", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" +dependencies = [ + "quote", + "spl-discriminator-syn", + "syn 2.0.119", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1dbc82ab91422345b6df40a79e2b78c7bce1ebb366da323572dd60b7076b67" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.9", + "syn 2.0.119", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-elgamal-registry" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce0f668975d2b0536e8a8fd60e56a05c467f06021dae037f1d0cfed0de2e231d" +dependencies = [ + "bytemuck", + "solana-program", + "solana-zk-sdk 2.3.13", + "spl-pod", + "spl-token-confidential-transfer-proof-extraction", +] + +[[package]] +name = "spl-memo" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f09647c0974e33366efeb83b8e2daebb329f0420149e74d3a4bd2c08cf9f7cb" +dependencies = [ + "solana-account-info 2.3.0", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", +] + +[[package]] +name = "spl-pod" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d994afaf86b779104b4a95ba9ca75b8ced3fdb17ee934e38cb69e72afbe17799" +dependencies = [ + "borsh 1.8.1", + "bytemuck", + "bytemuck_derive", + "num-derive", + "num-traits", + "solana-decode-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-program-option", + "solana-pubkey 2.4.0", + "solana-zk-sdk 2.3.13", + "thiserror 2.0.21", +] + +[[package]] +name = "spl-program-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d39b5186f42b2b50168029d81e58e800b690877ef0b30580d107659250da1d1" +dependencies = [ + "num-derive", + "num-traits", + "solana-program", + "spl-program-error-derive", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d375dd76c517836353e093c2dbb490938ff72821ab568b545fd30ab3256b3e" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.9", + "syn 2.0.119", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd99ff1e9ed2ab86e3fd582850d47a739fec1be9f4661cba1782d3a0f26805f3" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-account-info 2.3.0", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-token" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed320a6c934128d4f7e54fe00e16b8aeaecf215799d060ae14f93378da6dc834" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-token-2022" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b27f7405010ef816587c944536b0eafbcc35206ab6ba0f2ca79f1d28e488f4f" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "solana-security-txt", + "solana-zk-sdk 2.3.13", + "spl-elgamal-registry", + "spl-memo", + "spl-pod", + "spl-token", + "spl-token-confidential-transfer-ciphertext-arithmetic", + "spl-token-confidential-transfer-proof-extraction", + "spl-token-confidential-transfer-proof-generation", + "spl-token-group-interface", + "spl-token-metadata-interface", + "spl-transfer-hook-interface", + "spl-type-length-value", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-token-confidential-transfer-ciphertext-arithmetic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170378693c5516090f6d37ae9bad2b9b6125069be68d9acd4865bbe9fc8499fd" +dependencies = [ + "base64 0.22.1", + "bytemuck", + "solana-curve25519 2.3.13", + "solana-zk-sdk 2.3.13", +] + +[[package]] +name = "spl-token-confidential-transfer-proof-extraction" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff2d6a445a147c9d6dd77b8301b1e116c8299601794b558eafa409b342faf96" +dependencies = [ + "bytemuck", + "solana-curve25519 2.3.13", + "solana-program", + "solana-zk-sdk 2.3.13", + "spl-pod", + "thiserror 2.0.21", +] + +[[package]] +name = "spl-token-confidential-transfer-proof-generation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8627184782eec1894de8ea26129c61303f1f0adeed65c20e0b10bc584f09356d" +dependencies = [ + "curve25519-dalek", + "solana-zk-sdk 2.3.13", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-token-group-interface" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d595667ed72dbfed8c251708f406d7c2814a3fa6879893b323d56a10bedfc799" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb9c89dbc877abd735f05547dcf9e6e12c00c11d6d74d8817506cab4c99fdbb" +dependencies = [ + "borsh 1.8.1", + "num-derive", + "num-traits", + "solana-borsh 2.2.1", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "spl-type-length-value", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa7503d52107c33c88e845e1351565050362c2314036ddf19a36cd25137c043" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "solana-account-info 2.3.0", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution", + "spl-type-length-value", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-type-length-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba70ef09b13af616a4c987797870122863cba03acc4284f226a4473b043923f9" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-account-info 2.3.0", + "solana-decode-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "spl-discriminator", + "spl-pod", + "thiserror 1.0.69", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime 1.1.2+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.7", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wincode" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "657690780ce23e6f66576a782ffd88eb353512381817029cc1d7a99154bb6d1f" +dependencies = [ + "pastey", + "proc-macro2", + "quote", + "thiserror 2.0.21", + "wincode-derive", +] + +[[package]] +name = "wincode-derive" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15ab90b719560d0fda79c74550ad1c948d17b118765942838055ebaf34d67071" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/solana/Cargo.toml b/solana/Cargo.toml new file mode 100644 index 00000000..6918ed50 --- /dev/null +++ b/solana/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +members = ["programs/launch_pool", "runtime-tests"] +resolver = "2" + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 + +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/solana/SECURITY.md b/solana/SECURITY.md new file mode 100644 index 00000000..d767d0ca --- /dev/null +++ b/solana/SECURITY.md @@ -0,0 +1,38 @@ +# Solana launch pool security model + +This implementation is unaudited and not approved for mainnet funds. Follow the repository's private vulnerability reporting policy in [SECURITY.md](../SECURITY.md). Do not disclose an exploitable vulnerability in a public PR comment. + +## Trust and custody + +The original program holds token inventory and native SOL in per-pool PDAs. There is no external AMM, owner/admin instruction, LP withdrawal, pause, upgrade switch, migration, rescue, arbitrary CPI, or active-account close. All economic terms are fixed in preparation. Activation requires the exact executable's linked ProgramData to have no upgrade authority. Once active, an immutable defect cannot be patched and the website cannot pause direct users. + +The legacy SPL Token Program is the only token CPI target. Anchor's initialization macros require token-2022 helper code to compile, but instruction accounts are constrained to `Program` and legacy `Mint`/`TokenAccount`, so Token-2022 mints/extensions/hooks are not accepted. No Metaplex or other metadata CPI is used. The custom on-chain name/symbol/URI do not guarantee wallet metadata recognition or hosted-media availability. + +## Protected invariants + +- Supply is exactly 1 billion tokens at 6 decimals on activation, all in the vault. Mint authority is atomically revoked and freeze authority is absent. Later holder burns are allowed. +- Accounted token inventory X remains within `(0, S]`; real SOL R excludes rent and fees; `X*(R+V) >= S*V`. V is virtual pricing only, never collateral or available liquidity. +- Buy input is split into real reserves and fixed fees. Sell output plus fee is capped by real reserve solvency, not virtual balances. Slippage and slot expiry are enforced on-chain. +- Actual vault balances must cover accounted obligations. Surplus donations neither affect price nor become claimable; they remain inaccessible because no recovery path exists. +- Cumulative fee entitlements use u128 counters and overflow-safe proportional math. A claim can only pay the exact fixed recipient from the fee vault. No signature is needed to pay someone their own entitlement. Rent is protected and duplicate/aliased protocol recipients are rejected. +- Cancelled preparation is a permanent tombstone, not a reusable nonce. Preparation has no auxiliary accounts, so cancelled preparation rent is not refunded. + +## Threats and limitations + +Review rounding manipulation, dust/near-empty inventory, maximum integer bounds, fee-claim ordering, malicious seed/account substitution, CPI privilege leakage, prefunded-PDA griefing, transaction atomicity, reinitialization, fabricated program data, external SPL transfers/burns, and failed or duplicated transaction ingestion. Host emulation is not a substitute for validator behavior. + +Economic safety is not guaranteed by immutability. A token starts with zero real SOL liquidity. Price/FDV based on V is not an exit-value guarantee. Slippage, sandwich/MEV ordering, declining demand, locked dust, creator-recipient trading incentives and arbitrary starting valuations require economic review and clear UI disclosure. The initial V bounds are mechanical, not investment guidance or endorsed fair valuations. + +Fee recipients are immutable. A recipient unable to receive a lamport transfer (for example runtime rent restrictions or incompatible account state) can lose access to its fees; others must remain able to claim independently. The protocol cannot rewrite destinations to repair mistakes. Applications should simulate and explain recipient compatibility, without claiming that present simulation proves future account availability. + +RPC/indexer/UI may be unavailable or malicious. Users must verify program ID, token mint, transaction bytes, recipients and minimum output. Publish a read/quote/build recovery SDK, preserve signatures until resolved and never silently rebuild a possibly-submitted transaction. Verify successful transaction origin, not only log text. Holders require full token-transfer coverage, not swap-event inference. + +## Release gates + +1. Freeze specification and mechanically bounded parameters; independent economic and security reviews of the exact commit, with findings resolved and retested. +2. Reproducible modern SBF build, independent bytecode match and full validator/SBF acceptance matrix in [BUILD.md](BUILD.md), including maximum transaction/compute/stack measurements. +3. Real wallet desktop/mobile tests, recipient compatibility, failure/reload recovery, durable indexer/finalization and EVM regression tests. +4. Kevin's separately authorized deployment; verify executable/ProgramData linkage and binary. Authority revocation is irreversible and must be explicitly approved and independently verified. No automatic deployment or revocation is provided. +5. Separately approved mainnet test budget and successful round trip/claims/index reconstruction before production UI enablement. The canary is a test budget, not an on-chain permission or TVL cap: once immutable, anyone can activate pools directly. + +No audit or deployment gate is cleared by merging this PR. A frontend rollback cannot recover compromised immutable funds. diff --git a/solana/programs/launch_pool/Cargo.toml b/solana/programs/launch_pool/Cargo.toml new file mode 100644 index 00000000..2a666fb1 --- /dev/null +++ b/solana/programs/launch_pool/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "launch-pool" +version = "0.1.0" +edition = "2021" +license = "MIT" +publish = false + +[lib] +crate-type = ["cdylib", "lib"] +name = "launch_pool" + +[features] +# Exclude Anchor's separately managed on-chain IDL instruction surface. The SDK +# and ABI are published with source, not controlled by an IDL authority account. +default = ["no-idl"] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +anchor-debug = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] + +[dependencies] +anchor-lang = "=0.31.1" +# Anchor's account-init macro references token_2022 helpers even for legacy +# Token account types. No instruction accepts Token-2022 as the program ID. +anchor-spl = { version = "=0.31.1", default-features = false, features = ["token", "token_2022", "token_2022_extensions"] } +bincode = "=1.3.3" + +[dev-dependencies] +serde_json = "=1.0.151" diff --git a/solana/programs/launch_pool/src/lib.rs b/solana/programs/launch_pool/src/lib.rs new file mode 100644 index 00000000..2d60b16d --- /dev/null +++ b/solana/programs/launch_pool/src/lib.rs @@ -0,0 +1,774 @@ +#![allow(unexpected_cfgs)] +// Anchor 0.31 generates a crate-level compatibility use of AccountInfo::realloc. +// Keep all other warnings denied in CI; revisit this exception when upgrading Anchor. +#![allow(deprecated)] +use anchor_lang::prelude::*; +// This pinned Anchor version reexports the legacy loader wire type. Its wire +// format is deliberately checked below; upgrading the framework is a release review. +#[allow(deprecated)] +use anchor_lang::solana_program::{bpf_loader_upgradeable, program_option::COption}; +use anchor_lang::system_program::{self, Transfer as SolTransfer}; +use anchor_spl::token::spl_token::instruction::AuthorityType; +use anchor_spl::token::{self, Mint, MintTo, SetAuthority, Token, TokenAccount, TransferChecked}; + +pub mod math; +use math::{DECIMALS, MAX_VIRTUAL_SOL, MIN_VIRTUAL_SOL, SUPPLY}; + +// NONPRODUCTION compile/test fixture. A release must bind its real program ID +// into source, SDK configuration and verified build manifest before deployment. +declare_id!("Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr"); + +pub const PREPARED: u8 = 0; +pub const ACTIVE: u8 = 1; +pub const CANCELLED: u8 = 2; +pub const MAX_RECIPIENTS: usize = 7; + +#[program] +pub mod launch_pool { + use super::*; + + pub fn prepare_launch(ctx: Context, args: PrepareArgs) -> Result<()> { + validate_terms(&args, &ctx.accounts.pool.key())?; + let pool = &mut ctx.accounts.pool; + pool.version = 1; + pool.status = PREPARED; + pool.bump = ctx.bumps.pool; + pool.nonce = args.nonce; + pool.creator = ctx.accounts.creator.key(); + pool.rent_payer = ctx.accounts.payer.key(); + pool.mint = Pubkey::find_program_address(&[b"mint", pool.key().as_ref()], &crate::ID).0; + pool.virtual_sol = args.virtual_sol; + pool.fee_bps = args.fee_bps; + pool.recipient_count = u8::try_from(args.recipients.len()) + .map_err(|_| error!(PoolError::InvalidRecipients))?; + pool.recipients = [Recipient::default(); MAX_RECIPIENTS]; + for (index, recipient) in args.recipients.iter().enumerate() { + pool.recipients[index] = Recipient { + address: recipient.address, + weight_bps: recipient.weight_bps, + paid: 0, + }; + } + pool.token_reserve = 0; + pool.sol_reserve = 0; + pool.fees_earned = 0; + pool.sequence = 0; + pool.name = args.name; + pool.symbol = args.symbol; + pool.uri = args.uri; + pool.content_hash = args.content_hash; + emit!(Prepared { + pool: pool.key(), + creator: pool.creator, + nonce: pool.nonce + }); + Ok(()) + } + + pub fn activate_launch(ctx: Context) -> Result<()> { + require!( + ctx.accounts.pool.status == PREPARED, + PoolError::InvalidStatus + ); + verify_immutable( + &ctx.accounts.executable.to_account_info(), + &ctx.accounts.program_data.to_account_info(), + )?; + let pool_key = ctx.accounts.pool.key(); + let authority_bump = [ctx.bumps.mint_authority]; + let authority_seeds: &[&[u8]] = &[b"mint-authority", pool_key.as_ref(), &authority_bump]; + let signers = &[authority_seeds]; + require!( + ctx.accounts.mint.supply == 0 && ctx.accounts.mint.decimals == DECIMALS, + PoolError::InvalidMint + ); + require!( + ctx.accounts.mint.mint_authority == COption::Some(ctx.accounts.mint_authority.key()) + && ctx.accounts.mint.freeze_authority == COption::None, + PoolError::InvalidMint + ); + validate_token_vault( + &ctx.accounts.token_vault, + &pool_key, + &ctx.accounts.mint.key(), + )?; + require!( + ctx.accounts.token_vault.amount == 0, + PoolError::InvalidVault + ); + token::mint_to( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + MintTo { + mint: ctx.accounts.mint.to_account_info(), + to: ctx.accounts.token_vault.to_account_info(), + authority: ctx.accounts.mint_authority.to_account_info(), + }, + signers, + ), + SUPPLY, + )?; + token::set_authority( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + SetAuthority { + current_authority: ctx.accounts.mint_authority.to_account_info(), + account_or_mint: ctx.accounts.mint.to_account_info(), + }, + signers, + ), + AuthorityType::MintTokens, + None, + )?; + ctx.accounts.mint.reload()?; + ctx.accounts.token_vault.reload()?; + require!( + ctx.accounts.mint.supply == SUPPLY + && ctx.accounts.mint.mint_authority == COption::None + && ctx.accounts.mint.freeze_authority == COption::None, + PoolError::InvalidMint + ); + require!( + ctx.accounts.token_vault.amount == SUPPLY, + PoolError::InvalidVault + ); + validate_token_vault( + &ctx.accounts.token_vault, + &pool_key, + &ctx.accounts.mint.key(), + )?; + let rent_floor = ctx.accounts.rent.minimum_balance(SolVault::SPACE); + ctx.accounts.reserve.set_inner(SolVault { + pool: pool_key, + kind: 0, + rent_floor, + }); + ctx.accounts.fees.set_inner(SolVault { + pool: pool_key, + kind: 1, + rent_floor, + }); + ctx.accounts.pool.token_reserve = SUPPLY; + ctx.accounts.pool.status = ACTIVE; + check_sol_coverage(&ctx.accounts.reserve, 0)?; + check_sol_coverage(&ctx.accounts.fees, 0)?; + emit!(Activated { + pool: pool_key, + mint: ctx.accounts.mint.key(), + supply: SUPPLY + }); + Ok(()) + } + + pub fn cancel_preparation(ctx: Context) -> Result<()> { + require!( + ctx.accounts.pool.status == PREPARED, + PoolError::InvalidStatus + ); + // Never close: the permanent tombstone consumes this creator/nonce. + ctx.accounts.pool.status = CANCELLED; + emit!(Cancelled { + pool: ctx.accounts.pool.key() + }); + Ok(()) + } + + pub fn buy_exact_in( + ctx: Context, + amount_in: u64, + minimum_out: u64, + expiry_slot: u64, + ) -> Result<()> { + let slot = validate_trade(ctx.accounts, expiry_slot)?; + let pool = &ctx.accounts.pool; + let quote = math::buy( + pool.token_reserve, + pool.sol_reserve, + pool.virtual_sol, + pool.fee_bps, + amount_in, + )?; + require!(quote.amount_out >= minimum_out, PoolError::Slippage); + let next_fees = pool + .fees_earned + .checked_add(u128::from(quote.fee)) + .ok_or(PoolError::Arithmetic)?; + let next_sequence = pool.sequence.checked_add(1).ok_or(PoolError::Arithmetic)?; + sol_transfer( + &ctx.accounts.system_program, + &ctx.accounts.trader.to_account_info(), + &ctx.accounts.reserve.to_account_info(), + amount_in - quote.fee, + )?; + sol_transfer( + &ctx.accounts.system_program, + &ctx.accounts.trader.to_account_info(), + &ctx.accounts.fees.to_account_info(), + quote.fee, + )?; + let nonce = pool.nonce.to_le_bytes(); + let bump = [pool.bump]; + let seeds: &[&[u8]] = &[b"pool", pool.creator.as_ref(), &nonce, &bump]; + token::transfer_checked( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_vault.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + to: ctx.accounts.trader_token.to_account_info(), + authority: pool.to_account_info(), + }, + &[seeds], + ), + quote.amount_out, + DECIMALS, + )?; + apply_trade(&mut ctx.accounts.pool, "e, next_fees, next_sequence); + ctx.accounts.token_vault.reload()?; + validate_balances(ctx.accounts)?; + emit!(Swap { + pool: ctx.accounts.pool.key(), + trader: ctx.accounts.trader.key(), + sequence: next_sequence, + is_buy: true, + amount_in, + amount_out: quote.amount_out, + fee: quote.fee, + token_reserve: quote.token_reserve, + sol_reserve: quote.sol_reserve, + slot + }); + Ok(()) + } + + pub fn sell_exact_in( + ctx: Context, + amount_in: u64, + minimum_out: u64, + expiry_slot: u64, + ) -> Result<()> { + let slot = validate_trade(ctx.accounts, expiry_slot)?; + let pool = &ctx.accounts.pool; + let quote = math::sell( + pool.token_reserve, + pool.sol_reserve, + pool.virtual_sol, + pool.fee_bps, + amount_in, + )?; + require!(quote.amount_out >= minimum_out, PoolError::Slippage); + let next_fees = pool + .fees_earned + .checked_add(u128::from(quote.fee)) + .ok_or(PoolError::Arithmetic)?; + let next_sequence = pool.sequence.checked_add(1).ok_or(PoolError::Arithmetic)?; + token::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.trader_token.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + to: ctx.accounts.token_vault.to_account_info(), + authority: ctx.accounts.trader.to_account_info(), + }, + ), + amount_in, + DECIMALS, + )?; + move_owned_lamports( + &ctx.accounts.reserve.to_account_info(), + &ctx.accounts.trader.to_account_info(), + quote.amount_out, + )?; + move_owned_lamports( + &ctx.accounts.reserve.to_account_info(), + &ctx.accounts.fees.to_account_info(), + quote.fee, + )?; + apply_trade(&mut ctx.accounts.pool, "e, next_fees, next_sequence); + ctx.accounts.token_vault.reload()?; + validate_balances(ctx.accounts)?; + emit!(Swap { + pool: ctx.accounts.pool.key(), + trader: ctx.accounts.trader.key(), + sequence: next_sequence, + is_buy: false, + amount_in, + amount_out: quote.amount_out, + fee: quote.fee, + token_reserve: quote.token_reserve, + sol_reserve: quote.sol_reserve, + slot + }); + Ok(()) + } + + pub fn claim_fees(ctx: Context, recipient_index: u8) -> Result<()> { + let pool = &mut ctx.accounts.pool; + require!(pool.status == ACTIVE, PoolError::InvalidStatus); + let index = usize::from(recipient_index); + require!( + index < usize::from(pool.recipient_count), + PoolError::InvalidRecipients + ); + let recipient = pool.recipients[index]; + require_keys_eq!( + recipient.address, + ctx.accounts.recipient.key(), + PoolError::InvalidRecipients + ); + require!( + !ctx.accounts.recipient.executable && ctx.accounts.recipient.owner != &crate::ID, + PoolError::InvalidRecipients + ); + check_sol_coverage(&ctx.accounts.fees, pool.fee_liability()?)?; + let entitled = math::entitlement(pool.fees_earned, recipient.weight_bps)?; + let amount = u64::try_from( + entitled + .checked_sub(recipient.paid) + .ok_or(PoolError::Arithmetic)?, + ) + .map_err(|_| error!(PoolError::Arithmetic))?; + require!(amount > 0, PoolError::ZeroAmount); + move_owned_lamports( + &ctx.accounts.fees.to_account_info(), + &ctx.accounts.recipient.to_account_info(), + amount, + )?; + pool.recipients[index].paid = entitled; + check_sol_coverage(&ctx.accounts.fees, pool.fee_liability()?)?; + emit!(FeesClaimed { + pool: pool.key(), + recipient: recipient.address, + amount, + cumulative_paid: entitled + }); + Ok(()) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct PrepareArgs { + pub nonce: u64, + pub virtual_sol: u64, + pub fee_bps: u16, + pub name: String, + pub symbol: String, + pub uri: String, + pub content_hash: [u8; 32], + pub recipients: Vec, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct RecipientInput { + pub address: Pubkey, + pub weight_bps: u16, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Default)] +pub struct Recipient { + pub address: Pubkey, + pub weight_bps: u16, + pub paid: u128, +} + +#[account] +pub struct Pool { + pub version: u8, + pub status: u8, + pub bump: u8, + pub nonce: u64, + pub creator: Pubkey, + pub rent_payer: Pubkey, + pub mint: Pubkey, + pub virtual_sol: u64, + pub fee_bps: u16, + pub recipient_count: u8, + pub recipients: [Recipient; MAX_RECIPIENTS], + pub token_reserve: u64, + pub sol_reserve: u64, + pub fees_earned: u128, + pub sequence: u64, + pub name: String, + pub symbol: String, + pub uri: String, + pub content_hash: [u8; 32], +} + +impl Pool { + pub const SPACE: usize = + 8 + 3 + 8 + 96 + 8 + 2 + 1 + 7 * 50 + 16 + 16 + 8 + 12 + 32 + 10 + 200 + 32; + pub fn fee_liability(&self) -> Result { + require!( + usize::from(self.recipient_count) <= MAX_RECIPIENTS, + PoolError::InvalidRecipients + ); + let mut paid = 0u128; + for r in &self.recipients[..usize::from(self.recipient_count)] { + paid = paid.checked_add(r.paid).ok_or(PoolError::Arithmetic)?; + } + self.fees_earned + .checked_sub(paid) + .ok_or_else(|| error!(PoolError::Arithmetic)) + } +} + +#[account] +pub struct SolVault { + pub pool: Pubkey, + pub kind: u8, + pub rent_floor: u64, +} +impl SolVault { + pub const SPACE: usize = 8 + 32 + 1 + 8; +} + +#[derive(Accounts)] +#[instruction(args: PrepareArgs)] +pub struct PrepareLaunch<'info> { + pub creator: Signer<'info>, + #[account(mut)] + pub payer: Signer<'info>, + #[account(init, payer = payer, space = Pool::SPACE, seeds = [b"pool", creator.key().as_ref(), &args.nonce.to_le_bytes()], bump)] + pub pool: Box>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct ActivateLaunch<'info> { + #[account(mut)] + pub creator: Signer<'info>, + #[account(mut, seeds = [b"pool", pool.creator.as_ref(), &pool.nonce.to_le_bytes()], bump = pool.bump, has_one = creator, constraint = pool.version == 1 @ PoolError::InvalidVersion, constraint = pool.status == PREPARED @ PoolError::InvalidStatus)] + pub pool: Box>, + #[account(init, payer = creator, seeds = [b"mint", pool.key().as_ref()], bump, mint::decimals = DECIMALS, mint::authority = mint_authority)] + pub mint: Box>, + #[account(init, payer = creator, seeds = [b"token", pool.key().as_ref()], bump, token::mint = mint, token::authority = pool)] + pub token_vault: Box>, + #[account(init, payer = creator, space = SolVault::SPACE, seeds = [b"reserve", pool.key().as_ref()], bump)] + pub reserve: Box>, + #[account(init, payer = creator, space = SolVault::SPACE, seeds = [b"fees", pool.key().as_ref()], bump)] + pub fees: Box>, + /// CHECK: only this seed-derived temporary authority can mint during activation. + #[account(seeds = [b"mint-authority", pool.key().as_ref()], bump)] + pub mint_authority: UncheckedAccount<'info>, + /// CHECK: exact executable and loader linkage are checked in verify_immutable. + pub executable: UncheckedAccount<'info>, + /// CHECK: exact linked ProgramData and revoked authority checked in verify_immutable. + pub program_data: UncheckedAccount<'info>, + pub token_program: Program<'info, Token>, + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent>, +} + +#[derive(Accounts)] +pub struct CancelPreparation<'info> { + pub creator: Signer<'info>, + #[account(mut, seeds = [b"pool", pool.creator.as_ref(), &pool.nonce.to_le_bytes()], bump = pool.bump, has_one = creator, constraint = pool.version == 1 @ PoolError::InvalidVersion)] + pub pool: Box>, +} + +#[derive(Accounts)] +pub struct Trade<'info> { + #[account(mut)] + pub trader: Signer<'info>, + #[account(mut, seeds = [b"pool", pool.creator.as_ref(), &pool.nonce.to_le_bytes()], bump = pool.bump, has_one = mint, constraint = pool.version == 1 @ PoolError::InvalidVersion, constraint = pool.status == ACTIVE @ PoolError::InvalidStatus)] + pub pool: Box>, + #[account(seeds = [b"mint", pool.key().as_ref()], bump)] + pub mint: Box>, + #[account(mut, seeds = [b"token", pool.key().as_ref()], bump, token::mint = mint, token::authority = pool)] + pub token_vault: Box>, + #[account(mut, seeds = [b"reserve", pool.key().as_ref()], bump, has_one = pool, constraint = reserve.kind == 0 @ PoolError::InvalidVault)] + pub reserve: Box>, + #[account(mut, seeds = [b"fees", pool.key().as_ref()], bump, has_one = pool, constraint = fees.kind == 1 @ PoolError::InvalidVault)] + pub fees: Box>, + #[account(mut, token::mint = mint, token::authority = trader, constraint = trader_token.key() != token_vault.key() @ PoolError::AccountAlias)] + pub trader_token: Box>, + pub token_program: Program<'info, Token>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct ClaimFees<'info> { + #[account(mut, seeds = [b"pool", pool.creator.as_ref(), &pool.nonce.to_le_bytes()], bump = pool.bump, constraint = pool.version == 1 @ PoolError::InvalidVersion)] + pub pool: Box>, + #[account(mut, seeds = [b"fees", pool.key().as_ref()], bump, has_one = pool, constraint = fees.kind == 1 @ PoolError::InvalidVault)] + pub fees: Box>, + /// CHECK: only the fixed stored recipient is accepted; never a caller-selected payout. + #[account(mut)] + pub recipient: UncheckedAccount<'info>, +} + +pub fn validate_terms(args: &PrepareArgs, pool: &Pubkey) -> Result<()> { + require!( + (MIN_VIRTUAL_SOL..=MAX_VIRTUAL_SOL).contains(&args.virtual_sol), + PoolError::InvalidVirtualSol + ); + require!(matches!(args.fee_bps, 0 | 100 | 300), PoolError::InvalidFee); + require!( + !args.name.trim().is_empty() + && args.name.len() <= 32 + && !args.name.chars().any(char::is_control), + PoolError::InvalidMetadata + ); + require!( + !args.symbol.trim().is_empty() + && args.symbol.len() <= 10 + && !args.symbol.chars().any(char::is_control), + PoolError::InvalidMetadata + ); + require!( + args.uri.len() <= 200 && !args.uri.chars().any(char::is_control), + PoolError::InvalidMetadata + ); + if args.fee_bps == 0 { + require!(args.recipients.is_empty(), PoolError::InvalidRecipients); + return Ok(()); + } + require!( + !args.recipients.is_empty() && args.recipients.len() <= MAX_RECIPIENTS, + PoolError::InvalidRecipients + ); + let aliases: Vec = [ + b"mint".as_slice(), + b"token", + b"reserve", + b"fees", + b"mint-authority", + ] + .iter() + .map(|seed| Pubkey::find_program_address(&[seed, pool.as_ref()], &crate::ID).0) + .collect(); + let mut sum = 0u32; + for (i, r) in args.recipients.iter().enumerate() { + require!( + r.address != Pubkey::default() + && r.address != *pool + && !aliases.contains(&r.address) + && r.address != crate::ID, + PoolError::InvalidRecipients + ); + require!( + r.weight_bps > 0 + && r.weight_bps <= 10_000 + && !args.recipients[..i] + .iter() + .any(|other| other.address == r.address), + PoolError::InvalidRecipients + ); + sum = sum + .checked_add(u32::from(r.weight_bps)) + .ok_or(PoolError::Arithmetic)?; + } + require!(sum == 10_000, PoolError::InvalidRecipients); + Ok(()) +} + +pub fn verify_immutable(executable: &AccountInfo, program_data: &AccountInfo) -> Result<()> { + require_keys_eq!(*executable.key, crate::ID, PoolError::MutableProgram); + require!( + executable.executable + && executable.owner == &bpf_loader_upgradeable::ID + && program_data.owner == &bpf_loader_upgradeable::ID, + PoolError::MutableProgram + ); + let expected_data = + Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::ID).0; + require_keys_eq!(*program_data.key, expected_data, PoolError::MutableProgram); + let code_data = executable.try_borrow_data()?; + require!(code_data.len() == 36, PoolError::MutableProgram); + let state: bpf_loader_upgradeable::UpgradeableLoaderState = + bincode::deserialize(&code_data).map_err(|_| error!(PoolError::MutableProgram))?; + match state { + bpf_loader_upgradeable::UpgradeableLoaderState::Program { + programdata_address, + } => require_keys_eq!( + programdata_address, + expected_data, + PoolError::MutableProgram + ), + _ => return err!(PoolError::MutableProgram), + } + let data = program_data.try_borrow_data()?; + require!(data.len() >= 45, PoolError::MutableProgram); + let state: bpf_loader_upgradeable::UpgradeableLoaderState = + bincode::deserialize(&data).map_err(|_| error!(PoolError::MutableProgram))?; + match state { + bpf_loader_upgradeable::UpgradeableLoaderState::ProgramData { + upgrade_authority_address: None, + .. + } => Ok(()), + _ => err!(PoolError::MutableProgram), + } +} + +fn validate_token_vault(vault: &TokenAccount, pool: &Pubkey, mint: &Pubkey) -> Result<()> { + require!( + vault.owner == *pool + && vault.mint == *mint + && vault.delegate == COption::None + && vault.close_authority == COption::None + && vault.is_native == COption::None, + PoolError::InvalidVault + ); + Ok(()) +} +fn check_sol_coverage(vault: &Account, liability: u128) -> Result<()> { + let needed = liability + .checked_add(u128::from(vault.rent_floor)) + .ok_or(PoolError::Arithmetic)?; + require!( + u128::from(vault.to_account_info().lamports()) >= needed, + PoolError::Insolvent + ); + Ok(()) +} +fn validate_balances(accounts: &Trade) -> Result<()> { + require!( + accounts.token_vault.amount >= accounts.pool.token_reserve, + PoolError::Insolvent + ); + check_sol_coverage(&accounts.reserve, u128::from(accounts.pool.sol_reserve))?; + check_sol_coverage(&accounts.fees, accounts.pool.fee_liability()?)?; + Ok(()) +} +fn validate_trade(accounts: &Trade, expiry_slot: u64) -> Result { + let slot = Clock::get()?.slot; + require!(slot <= expiry_slot, PoolError::Expired); + require!( + accounts.trader.owner != &crate::ID && !accounts.trader.executable, + PoolError::AccountAlias + ); + require!( + accounts.mint.mint_authority == COption::None + && accounts.mint.freeze_authority == COption::None + && accounts.mint.decimals == DECIMALS + && accounts.mint.supply <= SUPPLY, + PoolError::InvalidMint + ); + validate_token_vault( + &accounts.token_vault, + &accounts.pool.key(), + &accounts.mint.key(), + )?; + validate_balances(accounts)?; + Ok(slot) +} +fn apply_trade(pool: &mut Account, quote: &math::Quote, fees: u128, sequence: u64) { + pool.token_reserve = quote.token_reserve; + pool.sol_reserve = quote.sol_reserve; + pool.fees_earned = fees; + pool.sequence = sequence; +} +fn sol_transfer<'info>( + program: &Program<'info, System>, + from: &AccountInfo<'info>, + to: &AccountInfo<'info>, + amount: u64, +) -> Result<()> { + if amount > 0 { + system_program::transfer( + CpiContext::new( + program.to_account_info(), + SolTransfer { + from: from.clone(), + to: to.clone(), + }, + ), + amount, + )?; + } + Ok(()) +} +fn move_owned_lamports(from: &AccountInfo, to: &AccountInfo, amount: u64) -> Result<()> { + require!( + from.owner == &crate::ID && from.key != to.key, + PoolError::AccountAlias + ); + let remaining = from + .lamports() + .checked_sub(amount) + .ok_or(PoolError::Insolvent)?; + let received = to + .lamports() + .checked_add(amount) + .ok_or(PoolError::Arithmetic)?; + **from.try_borrow_mut_lamports()? = remaining; + **to.try_borrow_mut_lamports()? = received; + Ok(()) +} + +#[event] +pub struct Prepared { + pub pool: Pubkey, + pub creator: Pubkey, + pub nonce: u64, +} +#[event] +pub struct Activated { + pub pool: Pubkey, + pub mint: Pubkey, + pub supply: u64, +} +#[event] +pub struct Cancelled { + pub pool: Pubkey, +} +#[event] +pub struct Swap { + pub pool: Pubkey, + pub trader: Pubkey, + pub sequence: u64, + pub is_buy: bool, + pub amount_in: u64, + pub amount_out: u64, + pub fee: u64, + pub token_reserve: u64, + pub sol_reserve: u64, + pub slot: u64, +} +#[event] +pub struct FeesClaimed { + pub pool: Pubkey, + pub recipient: Pubkey, + pub amount: u64, + pub cumulative_paid: u128, +} + +#[error_code] +pub enum PoolError { + #[msg("Arithmetic limit exceeded")] + Arithmetic, + #[msg("Invalid immutable fee tier")] + InvalidFee, + #[msg("Invalid fixed recipients")] + InvalidRecipients, + #[msg("Invalid token inventory")] + InvalidInventory, + #[msg("Virtual SOL is outside the supported range")] + InvalidVirtualSol, + #[msg("Actual reserves do not cover accounted obligations")] + Insolvent, + #[msg("Trade or claim amount rounds to zero")] + ZeroAmount, + #[msg("Operation is not allowed in this pool state")] + InvalidStatus, + #[msg("Invalid mint authority, supply, or decimals")] + InvalidMint, + #[msg("Invalid custody vault")] + InvalidVault, + #[msg("Minimum output not met")] + Slippage, + #[msg("Protocol account aliases are not permitted")] + AccountAlias, + #[msg("Unsupported pool version")] + InvalidVersion, + #[msg("Invalid or oversized metadata")] + InvalidMetadata, + #[msg("Activation requires this exact program to be immutable")] + MutableProgram, + #[msg("Quote expired at its maximum slot")] + Expired, +} + +#[cfg(test)] +mod tests; diff --git a/solana/programs/launch_pool/src/math.rs b/solana/programs/launch_pool/src/math.rs new file mode 100644 index 00000000..304f55a7 --- /dev/null +++ b/solana/programs/launch_pool/src/math.rs @@ -0,0 +1,177 @@ +//! Original checked integer curve and cumulative fee arithmetic. No floating point. +use crate::PoolError; +use anchor_lang::prelude::*; + +pub const SUPPLY: u64 = 1_000_000_000_000_000; +pub const DECIMALS: u8 = 6; +pub const MIN_VIRTUAL_SOL: u64 = 1_000_000_000; +pub const MAX_VIRTUAL_SOL: u64 = 1_000_000_000_000_000; +pub const BPS: u128 = 10_000; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Quote { + pub amount_out: u64, + pub fee: u64, + pub token_reserve: u64, + pub sol_reserve: u64, +} + +pub fn fee(amount: u64, fee_bps: u16) -> Result { + require!(matches!(fee_bps, 0 | 100 | 300), PoolError::InvalidFee); + let product = u128::from(amount) * u128::from(fee_bps); + let result = product / BPS + u128::from(product % BPS != 0); + u64::try_from(result).map_err(|_| error!(PoolError::Arithmetic)) +} + +pub fn entitlement(earned: u128, weight: u16) -> Result { + require!(weight > 0 && weight <= 10_000, PoolError::InvalidRecipients); + // Decompose before multiplying: the result never exceeds earned, even at u128::MAX. + (earned / BPS) + .checked_mul(u128::from(weight)) + .and_then(|n| n.checked_add((earned % BPS) * u128::from(weight) / BPS)) + .ok_or_else(|| error!(PoolError::Arithmetic)) +} + +pub fn invariant(x: u64, r: u64, v: u64) -> Result<()> { + require!(x > 0 && x <= SUPPLY, PoolError::InvalidInventory); + require!( + (MIN_VIRTUAL_SOL..=MAX_VIRTUAL_SOL).contains(&v), + PoolError::InvalidVirtualSol + ); + let total = r.checked_add(v).ok_or(PoolError::Arithmetic)?; + require!( + u128::from(x) * u128::from(total) >= u128::from(SUPPLY) * u128::from(v), + PoolError::Insolvent + ); + Ok(()) +} + +pub fn buy(x: u64, r: u64, v: u64, fee_bps: u16, input: u64) -> Result { + invariant(x, r, v)?; + require!(input > 0, PoolError::ZeroAmount); + let fee = fee(input, fee_bps)?; + let net = input.checked_sub(fee).ok_or(PoolError::Arithmetic)?; + require!(net > 0, PoolError::ZeroAmount); + let denominator = r + .checked_add(v) + .and_then(|n| n.checked_add(net)) + .ok_or(PoolError::Arithmetic)?; + let out = u64::try_from(u128::from(x) * u128::from(net) / u128::from(denominator)) + .map_err(|_| error!(PoolError::Arithmetic))?; + require!(out > 0 && out < x, PoolError::ZeroAmount); + let next = Quote { + amount_out: out, + fee, + token_reserve: x - out, + sol_reserve: r.checked_add(net).ok_or(PoolError::Arithmetic)?, + }; + invariant(next.token_reserve, next.sol_reserve, v)?; + Ok(next) +} + +pub fn sell(x: u64, r: u64, v: u64, fee_bps: u16, input: u64) -> Result { + invariant(x, r, v)?; + require!( + input > 0 && input <= SUPPLY - x, + PoolError::InvalidInventory + ); + let total = r.checked_add(v).ok_or(PoolError::Arithmetic)?; + let next_x = x.checked_add(input).ok_or(PoolError::Arithmetic)?; + let gross = u64::try_from(u128::from(total) * u128::from(input) / u128::from(next_x)) + .map_err(|_| error!(PoolError::Arithmetic))?; + require!(gross <= r, PoolError::Insolvent); + let fee = fee(gross, fee_bps)?; + let out = gross.checked_sub(fee).ok_or(PoolError::Arithmetic)?; + require!(out > 0, PoolError::ZeroAmount); + let next = Quote { + amount_out: out, + fee, + token_reserve: next_x, + sol_reserve: r - gross, + }; + invariant(next.token_reserve, next.sol_reserve, v)?; + Ok(next) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn published_round_trip() { + let a = buy(SUPPLY, 0, 30_000_000_000, 100, 1_000_000_000).unwrap(); + assert_eq!(a.amount_out, 31_945_788_964_181); + assert_eq!(a.fee, 10_000_000); + let b = sell( + a.token_reserve, + a.sol_reserve, + 30_000_000_000, + 100, + a.amount_out, + ) + .unwrap(); + assert_eq!(b.amount_out, 980_099_999); + assert_eq!(b.sol_reserve, 1); + } + #[test] + fn ceilings_and_lifetime_boundaries() { + assert_eq!(fee(1, 100).unwrap(), 1); + assert_eq!(fee(u64::MAX, 300).unwrap(), 553_402_322_211_286_549); + assert_eq!(entitlement(u128::MAX, 10_000).unwrap(), u128::MAX); + let allocated = [3333, 3333, 3334] + .iter() + .map(|w| entitlement(u128::MAX, *w).unwrap()) + .sum::(); + assert!(u128::MAX - allocated < 3); + assert!(fee(10, 101).is_err()); + assert!(entitlement(100, 0).is_err()); + } + #[test] + fn rejects_extreme_invalid_states() { + assert!(buy(SUPPLY, 0, MIN_VIRTUAL_SOL, 0, u64::MAX).is_err()); + assert!(buy(0, 0, MIN_VIRTUAL_SOL, 0, 1).is_err()); + assert!(buy(SUPPLY, 0, MIN_VIRTUAL_SOL - 1, 0, 1).is_err()); + assert!(buy(SUPPLY, 0, MIN_VIRTUAL_SOL, 100, 1).is_err()); + assert!(sell(SUPPLY, 0, MIN_VIRTUAL_SOL, 0, 1).is_err()); + assert!(sell(SUPPLY - 100, 0, MIN_VIRTUAL_SOL, 0, 1).is_err()); + } + #[test] + fn randomized_conservation_and_round_trips() { + let mut seed = 29103u64; + for rate in [0, 100, 300] { + for v in [MIN_VIRTUAL_SOL, 30_000_000_000, MAX_VIRTUAL_SOL] { + let (mut x, mut r, mut circulating, mut paid_in, mut paid_out, mut fees) = + (SUPPLY, 0u64, 0u64, 0u128, 0u128, 0u128); + for _ in 0..12_000 { + seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); + if circulating == 0 || seed & 1 == 0 { + let input = seed % 1_000_000_000 + 1; + if let Ok(q) = buy(x, r, v, rate, input) { + let reverse = + sell(q.token_reserve, q.sol_reserve, v, rate, q.amount_out); + if let Ok(reverse) = reverse { + assert!(reverse.amount_out <= input); + } + paid_in += u128::from(input); + circulating += q.amount_out; + fees += u128::from(q.fee); + x = q.token_reserve; + r = q.sol_reserve; + } + } else { + let input = seed % circulating + 1; + if let Ok(q) = sell(x, r, v, rate, input) { + paid_out += u128::from(q.amount_out); + circulating -= input; + fees += u128::from(q.fee); + x = q.token_reserve; + r = q.sol_reserve; + } + } + assert_eq!(x + circulating, SUPPLY); + assert_eq!(paid_in - paid_out, u128::from(r) + fees); + invariant(x, r, v).unwrap(); + } + } + } + } +} diff --git a/solana/programs/launch_pool/src/tests.rs b/solana/programs/launch_pool/src/tests.rs new file mode 100644 index 00000000..024fd77d --- /dev/null +++ b/solana/programs/launch_pool/src/tests.rs @@ -0,0 +1,1072 @@ +//! Host instruction/constraint tests. These execute Anchor's generated dispatch +//! and account validation, not an SBF VM. Full validator/SBF tests are a release gate. +use super::*; +use anchor_lang::solana_program::{ + instruction::Instruction, + program_error::ProgramError, + program_pack::Pack, + program_stubs::{set_syscall_stubs, SyscallStubs}, + system_instruction::SystemInstruction, +}; +use anchor_lang::{AccountSerialize, InstructionData}; +use anchor_spl::token::spl_token::state::{Account as SplAccount, AccountState, Mint as SplMint}; +use std::sync::Mutex; + +static STUB_LOCK: Mutex<()> = Mutex::new(()); + +/// A deliberately limited host harness: real SPL TransferChecked processing and +/// checked native SOL transfer, with PDA signer derivation. No account creation, +/// SBF metering, transaction rollback, rent-state checks or validator semantics. +struct TransferStubs; +impl SyscallStubs for TransferStubs { + fn sol_get_clock_sysvar(&self, pointer: *mut u8) -> u64 { + // Runtime supplies a writable Clock-sized buffer to this documented stub. + unsafe { + std::ptr::write( + pointer.cast::(), + Clock { + slot: 100, + ..Clock::default() + }, + ); + } + 0 + } + fn sol_invoke_signed( + &self, + ix: &Instruction, + infos: &[AccountInfo], + signers: &[&[&[u8]]], + ) -> anchor_lang::solana_program::entrypoint::ProgramResult { + let pdas = signers + .iter() + .map(|seeds| Pubkey::create_program_address(seeds, &crate::ID)) + .collect::, _>>() + .map_err(|_| ProgramError::InvalidSeeds)?; + let mut inner = Vec::new(); + for meta in &ix.accounts { + let mut info = infos + .iter() + .find(|a| a.key == &meta.pubkey) + .ok_or(ProgramError::NotEnoughAccountKeys)? + .clone(); + if meta.is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + if pdas.contains(info.key) { + info.is_signer = true; + } + if meta.is_signer && !info.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + inner.push(info); + } + if ix.program_id == token::ID { + return token::spl_token::processor::Processor::process(&token::ID, &inner, &ix.data); + } + if ix.program_id == system_program::ID { + match bincode::deserialize::(&ix.data) + .map_err(|_| ProgramError::InvalidInstructionData)? + { + SystemInstruction::Transfer { lamports } => { + if inner.len() != 2 + || inner[0].owner != &system_program::ID + || !inner[0].data_is_empty() + || !inner[0].is_signer + { + return Err(ProgramError::InvalidAccountData); + } + let remaining = inner[0] + .lamports() + .checked_sub(lamports) + .ok_or(ProgramError::InsufficientFunds)?; + let received = inner[1] + .lamports() + .checked_add(lamports) + .ok_or(ProgramError::ArithmeticOverflow)?; + **inner[0].try_borrow_mut_lamports()? = remaining; + **inner[1].try_borrow_mut_lamports()? = received; + return Ok(()); + } + _ => return Err(ProgramError::InvalidInstructionData), + } + } + Err(ProgramError::IncorrectProgramId) + } +} + +struct RestoreStubs(Option>); +impl Drop for RestoreStubs { + fn drop(&mut self) { + if let Some(previous) = self.0.take() { + set_syscall_stubs(previous); + } + } +} + +fn packed(value: T) -> Vec { + let mut data = vec![0u8; T::LEN]; + T::pack(value, &mut data).unwrap(); + data +} + +fn trade_fixture() -> Vec> { + let (key, pool) = fixture_pool(ACTIVE); + let trader = Pubkey::new_unique(); + let vault = Pubkey::find_program_address(&[b"token", key.as_ref()], &crate::ID).0; + let reserve = Pubkey::find_program_address(&[b"reserve", key.as_ref()], &crate::ID).0; + let fees = Pubkey::find_program_address(&[b"fees", key.as_ref()], &crate::ID).0; + let token_state = |owner, amount| SplAccount { + mint: pool.mint, + owner, + amount, + delegate: COption::None, + state: AccountState::Initialized, + is_native: COption::None, + delegated_amount: 0, + close_authority: COption::None, + }; + vec![ + account( + trader, + system_program::ID, + vec![], + 100_000_000_000, + true, + true, + false, + ), + pool_account(key, &pool), + account( + pool.mint, + token::ID, + packed(SplMint { + mint_authority: COption::None, + supply: SUPPLY, + decimals: DECIMALS, + is_initialized: true, + freeze_authority: COption::None, + }), + 1_000_000, + false, + false, + false, + ), + account( + vault, + token::ID, + packed(token_state(key, SUPPLY)), + 1_000_000, + false, + true, + false, + ), + account( + reserve, + crate::ID, + serialized( + &SolVault { + pool: key, + kind: 0, + rent_floor: 1000, + }, + SolVault::SPACE, + ), + 1000, + false, + true, + false, + ), + account( + fees, + crate::ID, + serialized( + &SolVault { + pool: key, + kind: 1, + rent_floor: 1000, + }, + SolVault::SPACE, + ), + 1000, + false, + true, + false, + ), + account( + Pubkey::new_unique(), + token::ID, + packed(token_state(trader, 0)), + 1_000_000, + false, + true, + false, + ), + account( + token::ID, + bpf_loader_upgradeable::ID, + vec![], + 1, + false, + false, + true, + ), + account( + system_program::ID, + bpf_loader_upgradeable::ID, + vec![], + 1, + false, + false, + true, + ), + ] +} + +#[test] +fn real_spl_cpi_host_buy_sell_conserves_and_never_spends_virtual_sol() { + let _lock = STUB_LOCK.lock().unwrap(); + let _restore = RestoreStubs(Some(set_syscall_stubs(Box::new(TransferStubs)))); + let accounts = trade_fixture(); + execute( + accounts.clone(), + crate::instruction::BuyExactIn { + amount_in: 1_000_000_000, + minimum_out: 31_945_788_964_181, + expiry_slot: 100, + } + .data(), + ) + .unwrap(); + let tokens = SplAccount::unpack(&accounts[6].try_borrow_data().unwrap()) + .unwrap() + .amount; + assert_eq!(tokens, 31_945_788_964_181); + assert_eq!(accounts[0].lamports(), 99_000_000_000); + assert_eq!(accounts[4].lamports(), 990_001_000); + assert_eq!(accounts[5].lamports(), 10_001_000); + execute( + accounts.clone(), + crate::instruction::SellExactIn { + amount_in: tokens, + minimum_out: 980_099_999, + expiry_slot: 100, + } + .data(), + ) + .unwrap(); + assert_eq!( + SplAccount::unpack(&accounts[6].try_borrow_data().unwrap()) + .unwrap() + .amount, + 0 + ); + assert_eq!( + SplAccount::unpack(&accounts[3].try_borrow_data().unwrap()) + .unwrap() + .amount, + SUPPLY + ); + assert_eq!(accounts[0].lamports(), 99_980_099_999); + assert_eq!(accounts[4].lamports(), 1001); + assert_eq!(accounts[5].lamports(), 19_901_000); + let pool = Pool::try_deserialize(&mut accounts[1].try_borrow_data().unwrap().as_ref()).unwrap(); + assert_eq!(pool.sequence, 2); + assert_eq!(pool.fees_earned, 19_900_000); + assert_eq!(pool.sol_reserve, 1); +} + +#[test] +fn trade_constraints_slippage_expiry_authority_and_substitution_reject_before_cpi() { + let _lock = STUB_LOCK.lock().unwrap(); + let _restore = RestoreStubs(Some(set_syscall_stubs(Box::new(TransferStubs)))); + for mode in 0..8 { + let mut accounts = trade_fixture(); + let mut expiry_slot = 100; + let mut minimum_out = 1; + match mode { + 0 => expiry_slot = 99, + 1 => minimum_out = SUPPLY, + 2 => accounts[0].is_signer = false, + 3 => { + accounts[7] = account( + Pubkey::new_unique(), + bpf_loader_upgradeable::ID, + vec![], + 1, + false, + false, + true, + ) + } + 4 => accounts.swap(4, 5), + 5 => accounts[6] = accounts[3].clone(), + 6 => { + let mut mint = SplMint::unpack(&accounts[2].try_borrow_data().unwrap()).unwrap(); + mint.mint_authority = COption::Some(Pubkey::new_unique()); + SplMint::pack(mint, &mut accounts[2].try_borrow_mut_data().unwrap()).unwrap(); + } + 7 => { + let mut vault = + SplAccount::unpack(&accounts[3].try_borrow_data().unwrap()).unwrap(); + vault.delegate = COption::Some(Pubkey::new_unique()); + SplAccount::pack(vault, &mut accounts[3].try_borrow_mut_data().unwrap()).unwrap(); + } + _ => unreachable!(), + } + let before_sol = accounts[0].lamports(); + assert!( + execute( + accounts.clone(), + crate::instruction::BuyExactIn { + amount_in: 1_000_000_000, + minimum_out, + expiry_slot + } + .data() + ) + .is_err(), + "mode {mode}" + ); + assert_eq!(accounts[0].lamports(), before_sol); + } +} + +#[test] +fn donated_sol_and_tokens_are_not_priced_or_claimable() { + let _lock = STUB_LOCK.lock().unwrap(); + let _restore = RestoreStubs(Some(set_syscall_stubs(Box::new(TransferStubs)))); + let accounts = trade_fixture(); + execute( + accounts.clone(), + crate::instruction::BuyExactIn { + amount_in: 1_000_000_000, + minimum_out: 1, + expiry_slot: 100, + } + .data(), + ) + .unwrap(); + // Ordinary unsolicited transfers represented as external balance changes. + **accounts[4].try_borrow_mut_lamports().unwrap() += 5_000_000_000; + **accounts[5].try_borrow_mut_lamports().unwrap() += 1_000_000; + let mut trader = SplAccount::unpack(&accounts[6].try_borrow_data().unwrap()).unwrap(); + let donated = 1_000_000; + trader.amount -= donated; + let mut vault = SplAccount::unpack(&accounts[3].try_borrow_data().unwrap()).unwrap(); + vault.amount += donated; + SplAccount::pack(trader, &mut accounts[6].try_borrow_mut_data().unwrap()).unwrap(); + SplAccount::pack(vault, &mut accounts[3].try_borrow_mut_data().unwrap()).unwrap(); + let before = + Pool::try_deserialize(&mut accounts[1].try_borrow_data().unwrap().as_ref()).unwrap(); + let expected = math::buy( + before.token_reserve, + before.sol_reserve, + before.virtual_sol, + before.fee_bps, + 1_000_000_000, + ) + .unwrap(); + execute( + accounts.clone(), + crate::instruction::BuyExactIn { + amount_in: 1_000_000_000, + minimum_out: expected.amount_out, + expiry_slot: 100, + } + .data(), + ) + .unwrap(); + let after = + Pool::try_deserialize(&mut accounts[1].try_borrow_data().unwrap().as_ref()).unwrap(); + assert_eq!(after.sol_reserve, expected.sol_reserve); + assert_eq!( + accounts[4].lamports(), + 1000 + expected.sol_reserve + 5_000_000_000 + ); + assert_eq!( + SplAccount::unpack(&accounts[3].try_borrow_data().unwrap()) + .unwrap() + .amount, + after.token_reserve + donated + ); + assert_eq!( + accounts[5].lamports(), + 1000 + u64::try_from(after.fees_earned).unwrap() + 1_000_000 + ); +} + +fn account( + key: Pubkey, + owner: Pubkey, + data: Vec, + lamports: u64, + signer: bool, + writable: bool, + executable: bool, +) -> AccountInfo<'static> { + AccountInfo::new( + Box::leak(Box::new(key)), + signer, + writable, + Box::leak(Box::new(lamports)), + Box::leak(data.into_boxed_slice()), + Box::leak(Box::new(owner)), + executable, + 0, + ) +} +fn serialized(value: &T, size: usize) -> Vec { + let mut bytes = Vec::new(); + value.try_serialize(&mut bytes).unwrap(); + assert!(bytes.len() <= size); + bytes.resize(size, 0); + bytes +} +fn fixture_pool(status: u8) -> (Pubkey, Pool) { + let creator = Pubkey::new_unique(); + let nonce = 91u64; + let (key, bump) = Pubkey::find_program_address( + &[b"pool", creator.as_ref(), &nonce.to_le_bytes()], + &crate::ID, + ); + let mint = Pubkey::find_program_address(&[b"mint", key.as_ref()], &crate::ID).0; + let mut recipients = [Recipient::default(); 7]; + recipients[0] = Recipient { + address: creator, + weight_bps: 10_000, + paid: 0, + }; + ( + key, + Pool { + version: 1, + status, + bump, + nonce, + creator, + rent_payer: creator, + mint, + virtual_sol: 30_000_000_000, + fee_bps: 100, + recipient_count: 1, + recipients, + token_reserve: SUPPLY, + sol_reserve: 0, + fees_earned: 0, + sequence: 0, + name: "Launch".into(), + symbol: "L".into(), + uri: "https://example.invalid/token.json".into(), + content_hash: [0; 32], + }, + ) +} +fn pool_account(key: Pubkey, pool: &Pool) -> AccountInfo<'static> { + account( + key, + crate::ID, + serialized(pool, Pool::SPACE), + 100_000_000, + false, + true, + false, + ) +} +fn execute( + accounts: Vec>, + data: Vec, +) -> anchor_lang::solana_program::entrypoint::ProgramResult { + // Anchor entry ties the slice and account lifetimes. Leaks are bounded fixtures. + crate::entry(&crate::ID, Box::leak(accounts.into_boxed_slice()), &data) +} +fn terms() -> PrepareArgs { + PrepareArgs { + nonce: 1, + virtual_sol: MIN_VIRTUAL_SOL, + fee_bps: 100, + name: "Launch".into(), + symbol: "TOKEN".into(), + uri: String::new(), + content_hash: [0; 32], + recipients: vec![RecipientInput { + address: Pubkey::new_unique(), + weight_bps: 10_000, + }], + } +} + +#[test] +fn canonical_terms_and_metadata_are_bounded() { + let pool = Pubkey::new_unique(); + let mut args = terms(); + validate_terms(&args, &pool).unwrap(); + args.name = "é".repeat(17); + assert!(validate_terms(&args, &pool).is_err()); + args.name = "Good".into(); + args.symbol = "A\nB".into(); + assert!(validate_terms(&args, &pool).is_err()); + args.symbol = "OK".into(); + args.uri = "x".repeat(201); + assert!(validate_terms(&args, &pool).is_err()); + args.uri.clear(); + args.virtual_sol = MAX_VIRTUAL_SOL + 1; + assert!(validate_terms(&args, &pool).is_err()); +} +#[test] +fn recipient_aliases_duplicates_and_weights_rejected() { + let pool = Pubkey::new_unique(); + let mut args = terms(); + args.recipients[0].address = pool; + assert!(validate_terms(&args, &pool).is_err()); + for seed in [ + b"mint".as_slice(), + b"token", + b"fees", + b"reserve", + b"mint-authority", + ] { + args.recipients[0].address = + Pubkey::find_program_address(&[seed, pool.as_ref()], &crate::ID).0; + assert!(validate_terms(&args, &pool).is_err()); + } + args = terms(); + args.recipients.push(args.recipients[0].clone()); + assert!(validate_terms(&args, &pool).is_err()); + args = terms(); + args.recipients[0].weight_bps = 9999; + assert!(validate_terms(&args, &pool).is_err()); + args = terms(); + args.fee_bps = 0; + assert!(validate_terms(&args, &pool).is_err()); + args.recipients.clear(); + validate_terms(&args, &pool).unwrap(); +} +#[test] +fn off_curve_smart_wallet_recipient_is_supported() { + let mut args = terms(); + args.recipients[0].address = + Pubkey::find_program_address(&[b"wallet"], &Pubkey::new_unique()).0; + assert!(!args.recipients[0].address.is_on_curve()); + validate_terms(&args, &Pubkey::new_unique()).unwrap(); +} +fn loader_accounts(authority: Option) -> (AccountInfo<'static>, AccountInfo<'static>) { + let data_key = + Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::ID).0; + let program_bytes = + bincode::serialize(&bpf_loader_upgradeable::UpgradeableLoaderState::Program { + programdata_address: data_key, + }) + .unwrap(); + let mut data_bytes = bincode::serialize( + &bpf_loader_upgradeable::UpgradeableLoaderState::ProgramData { + slot: 1, + upgrade_authority_address: authority, + }, + ) + .unwrap(); + data_bytes.resize(64, 0); + ( + account( + crate::ID, + bpf_loader_upgradeable::ID, + program_bytes, + 1, + false, + false, + true, + ), + account( + data_key, + bpf_loader_upgradeable::ID, + data_bytes, + 1, + false, + false, + false, + ), + ) +} +#[test] +fn immutable_gate_requires_own_program_and_revoked_linked_data() { + let (executable, data) = loader_accounts(None); + verify_immutable(&executable, &data).unwrap(); + let (_, mutable_data) = loader_accounts(Some(Pubkey::new_unique())); + assert!(verify_immutable(&executable, &mutable_data).is_err()); + let unrelated = account( + Pubkey::new_unique(), + bpf_loader_upgradeable::ID, + data.try_borrow_data().unwrap().to_vec(), + 1, + false, + false, + false, + ); + assert!(verify_immutable(&executable, &unrelated).is_err()); + let wrong_owner = account( + *data.key, + system_program::ID, + data.try_borrow_data().unwrap().to_vec(), + 1, + false, + false, + false, + ); + assert!(verify_immutable(&executable, &wrong_owner).is_err()); + let non_executable = account( + crate::ID, + bpf_loader_upgradeable::ID, + executable.try_borrow_data().unwrap().to_vec(), + 1, + false, + false, + false, + ); + assert!(verify_immutable(&non_executable, &data).is_err()); + let spoofed_code = + bincode::serialize(&bpf_loader_upgradeable::UpgradeableLoaderState::Program { + programdata_address: Pubkey::new_unique(), + }) + .unwrap(); + let spoofed = account( + crate::ID, + bpf_loader_upgradeable::ID, + spoofed_code, + 1, + false, + false, + true, + ); + assert!(verify_immutable(&spoofed, &data).is_err()); + let short = account( + *data.key, + bpf_loader_upgradeable::ID, + vec![3, 0, 0, 0], + 1, + false, + false, + false, + ); + assert!(verify_immutable(&executable, &short).is_err()); +} +#[test] +fn cancellation_runs_through_entrypoint_and_cannot_replay() { + let (key, pool) = fixture_pool(PREPARED); + let state = pool_account(key, &pool); + let creator = account( + pool.creator, + system_program::ID, + vec![], + 1, + true, + false, + false, + ); + execute( + vec![creator.clone(), state.clone()], + crate::instruction::CancelPreparation {}.data(), + ) + .unwrap(); + let result = Pool::try_deserialize(&mut state.try_borrow_data().unwrap().as_ref()).unwrap(); + assert_eq!(result.status, CANCELLED); + assert_eq!(state.lamports(), 100_000_000); + assert!(execute( + vec![creator, state], + crate::instruction::CancelPreparation {}.data() + ) + .is_err()); +} +#[test] +fn active_cancellation_and_unsigned_wrong_creator_rejected() { + for (status, signer, wrong_creator) in [ + (ACTIVE, true, false), + (PREPARED, false, false), + (PREPARED, true, true), + ] { + let (key, pool) = fixture_pool(status); + let creator = account( + if wrong_creator { + Pubkey::new_unique() + } else { + pool.creator + }, + system_program::ID, + vec![], + 1, + signer, + false, + false, + ); + assert!(execute( + vec![creator, pool_account(key, &pool)], + crate::instruction::CancelPreparation {}.data() + ) + .is_err()); + } +} +#[test] +fn wrong_pool_owner_pda_and_readonly_state_rejected() { + for mode in 0..3 { + let (key, pool) = fixture_pool(PREPARED); + let creator = account( + pool.creator, + system_program::ID, + vec![], + 1, + true, + false, + false, + ); + let state = account( + if mode == 0 { Pubkey::new_unique() } else { key }, + if mode == 1 { + system_program::ID + } else { + crate::ID + }, + serialized(&pool, Pool::SPACE), + 100_000_000, + false, + mode != 2, + false, + ); + assert!(execute( + vec![creator, state], + crate::instruction::CancelPreparation {}.data() + ) + .is_err()); + } +} +fn claim_fixture( + donation: u64, +) -> ( + AccountInfo<'static>, + AccountInfo<'static>, + AccountInfo<'static>, +) { + let (key, mut pool) = fixture_pool(ACTIVE); + pool.fees_earned = 777; + let fees_key = Pubkey::find_program_address(&[b"fees", key.as_ref()], &crate::ID).0; + let fee_state = SolVault { + pool: key, + kind: 1, + rent_floor: 1000, + }; + ( + pool_account(key, &pool), + account( + fees_key, + crate::ID, + serialized(&fee_state, SolVault::SPACE), + 1777 + donation, + false, + true, + false, + ), + account( + pool.creator, + system_program::ID, + vec![], + 500, + false, + true, + false, + ), + ) +} +#[test] +fn permissionless_claim_preserves_rent_and_donations_and_cannot_double_pay() { + let (pool, fees, recipient) = claim_fixture(5000); + execute( + vec![pool.clone(), fees.clone(), recipient.clone()], + crate::instruction::ClaimFees { recipient_index: 0 }.data(), + ) + .unwrap(); + assert_eq!(recipient.lamports(), 1277); + assert_eq!(fees.lamports(), 6000); + let state = Pool::try_deserialize(&mut pool.try_borrow_data().unwrap().as_ref()).unwrap(); + assert_eq!(state.recipients[0].paid, 777); + assert_eq!(state.sol_reserve, 0); + assert_eq!(state.token_reserve, SUPPLY); + assert!(execute( + vec![pool, fees, recipient], + crate::instruction::ClaimFees { recipient_index: 0 }.data() + ) + .is_err()); +} +#[test] +fn forged_recipient_and_fee_vault_substitution_rejected() { + let (pool, fees, recipient) = claim_fixture(0); + let attacker = account( + Pubkey::new_unique(), + system_program::ID, + vec![], + 1, + false, + true, + false, + ); + assert!(execute( + vec![pool.clone(), fees.clone(), attacker], + crate::instruction::ClaimFees { recipient_index: 0 }.data() + ) + .is_err()); + let forged_fees = account( + Pubkey::new_unique(), + crate::ID, + fees.try_borrow_data().unwrap().to_vec(), + 1777, + false, + true, + false, + ); + assert!(execute( + vec![pool.clone(), forged_fees, recipient.clone()], + crate::instruction::ClaimFees { recipient_index: 0 }.data() + ) + .is_err()); + assert!(execute( + vec![pool, fees, recipient], + crate::instruction::ClaimFees { recipient_index: 7 }.data() + ) + .is_err()); +} +#[test] +fn claims_never_drain_protected_rent() { + let (pool, fees, recipient) = claim_fixture(0); + **fees.try_borrow_mut_lamports().unwrap() = 1776; + assert!(execute( + vec![pool, fees.clone(), recipient.clone()], + crate::instruction::ClaimFees { recipient_index: 0 }.data() + ) + .is_err()); + assert_eq!(fees.lamports(), 1776); + assert_eq!(recipient.lamports(), 500); +} +#[test] +fn max_metadata_account_size_matches_abi() { + let (_, mut pool) = fixture_pool(PREPARED); + pool.name = "x".repeat(32); + pool.symbol = "x".repeat(10); + pool.uri = "x".repeat(200); + let mut bytes = Vec::new(); + pool.try_serialize(&mut bytes).unwrap(); + assert_eq!(bytes.len(), Pool::SPACE); +} +#[test] +fn independent_golden_vectors() { + let rows: serde_json::Value = serde_json::from_str(include_str!( + "../../../../packages/solana-model/golden-vectors.json" + )) + .unwrap(); + for row in rows.as_array().unwrap() { + let s = &row["state"]; + let value = |v: &serde_json::Value| v.as_str().unwrap().parse::().unwrap(); + let calc = if row["side"] == "buy" { + math::buy + } else { + math::sell + }; + let q = calc( + value(&s["tokenInventory"]), + value(&s["realSolReserves"]), + value(&s["virtualSol"]), + s["feeBps"].as_u64().unwrap() as u16, + value(&row["input"]), + ) + .unwrap(); + let expected = &row["expected"]; + assert_eq!( + q.amount_out, + value(&expected["amountOut"]), + "{}", + row["name"] + ); + assert_eq!(q.fee, value(&expected["fee"])); + assert_eq!(q.token_reserve, value(&expected["nextTokenInventory"])); + assert_eq!(q.sol_reserve, value(&expected["nextRealSolReserves"])); + } +} + +#[test] +fn sdk_fixture_matches_rust_serialization_pdas_and_instruction_accounts() { + use anchor_lang::ToAccountMetas; + use std::str::FromStr; + let fixture: serde_json::Value = serde_json::from_str(include_str!( + "../../../../app/packages/solana-sdk/tests/abi-fixture.json" + )) + .unwrap(); + let hex = |value: &serde_json::Value| { + let text = value.as_str().unwrap(); + (0..text.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&text[i..i + 2], 16).unwrap()) + .collect::>() + }; + let pubkey = |value: &serde_json::Value| Pubkey::from_str(value.as_str().unwrap()).unwrap(); + let bytes = hex(&fixture["preparedPoolHex"]); + let pool = Pool::try_deserialize(&mut bytes.as_slice()).unwrap(); + assert_eq!(pool.creator, Pubkey::new_from_array([1; 32])); + assert_eq!(pool.nonce, 7); + assert_eq!(pool.status, PREPARED); + assert_eq!(pool.name, "Example"); + assert_eq!(pool.symbol, "EX"); + assert!(pool.uri.is_empty()); + assert_eq!(pool.fee_bps, 0); + let mut round_trip = Vec::new(); + pool.try_serialize(&mut round_trip).unwrap(); + assert_eq!(round_trip, bytes); + let (pool_key, bump) = Pubkey::find_program_address( + &[b"pool", pool.creator.as_ref(), &7u64.to_le_bytes()], + &crate::ID, + ); + assert_eq!(pool_key, pubkey(&fixture["addresses"]["pool"])); + assert_eq!(bump, pool.bump); + for (seed, name) in [ + (b"mint".as_slice(), "mint"), + (b"token", "tokenVault"), + (b"reserve", "reserve"), + (b"fees", "fees"), + (b"mint-authority", "mintAuthority"), + ] { + assert_eq!( + Pubkey::find_program_address(&[seed, pool_key.as_ref()], &crate::ID).0, + pubkey(&fixture["addresses"][name]) + ); + } + let args = PrepareArgs { + nonce: 7, + virtual_sol: 30_000_000_000, + fee_bps: 0, + name: "Example".into(), + symbol: "EX".into(), + uri: String::new(), + content_hash: [0; 32], + recipients: vec![], + }; + let instruction_data = [ + ( + "prepareLaunch", + crate::instruction::PrepareLaunch { args }.data(), + ), + ( + "activateLaunch", + crate::instruction::ActivateLaunch {}.data(), + ), + ( + "cancelPreparation", + crate::instruction::CancelPreparation {}.data(), + ), + ( + "buyExactIn", + crate::instruction::BuyExactIn { + amount_in: 1_000_000_000, + minimum_out: 10, + expiry_slot: 1000, + } + .data(), + ), + ( + "sellExactIn", + crate::instruction::SellExactIn { + amount_in: 1_000_000_000, + minimum_out: 10, + expiry_slot: 1000, + } + .data(), + ), + ( + "claimFees", + crate::instruction::ClaimFees { recipient_index: 0 }.data(), + ), + ]; + for (name, bytes) in instruction_data { + assert_eq!( + bytes, + hex(&fixture["instructions"][name]["dataHex"]), + "{name}" + ); + } + let address = |name: &str| pubkey(&fixture["addresses"][name]); + let trade = crate::accounts::Trade { + trader: pool.creator, + pool: pool_key, + mint: address("mint"), + token_vault: address("tokenVault"), + reserve: address("reserve"), + fees: address("fees"), + trader_token: pubkey(&fixture["instructions"]["buyExactIn"]["keys"][6]["address"]), + token_program: token::ID, + system_program: system_program::ID, + }; + let metas = [ + ( + "prepareLaunch", + crate::accounts::PrepareLaunch { + creator: pool.creator, + payer: pool.creator, + pool: pool_key, + system_program: system_program::ID, + } + .to_account_metas(None), + ), + ( + "activateLaunch", + crate::accounts::ActivateLaunch { + creator: pool.creator, + pool: pool_key, + mint: address("mint"), + token_vault: address("tokenVault"), + reserve: address("reserve"), + fees: address("fees"), + mint_authority: address("mintAuthority"), + executable: crate::ID, + program_data: Pubkey::find_program_address( + &[crate::ID.as_ref()], + &bpf_loader_upgradeable::ID, + ) + .0, + token_program: token::ID, + system_program: system_program::ID, + rent: anchor_lang::solana_program::sysvar::rent::ID, + } + .to_account_metas(None), + ), + ( + "cancelPreparation", + crate::accounts::CancelPreparation { + creator: pool.creator, + pool: pool_key, + } + .to_account_metas(None), + ), + ("buyExactIn", trade.to_account_metas(None)), + ("sellExactIn", trade.to_account_metas(None)), + ( + "claimFees", + crate::accounts::ClaimFees { + pool: pool_key, + fees: address("fees"), + recipient: pool.creator, + } + .to_account_metas(None), + ), + ]; + for (name, keys) in metas { + let expected = fixture["instructions"][name]["keys"].as_array().unwrap(); + assert_eq!(keys.len(), expected.len()); + for (key, expected) in keys.iter().zip(expected) { + assert_eq!(key.pubkey, pubkey(&expected["address"]), "{name}"); + assert_eq!( + key.is_signer, + expected["signer"].as_bool().unwrap(), + "{name}" + ); + assert_eq!( + key.is_writable, + expected["writable"].as_bool().unwrap(), + "{name}" + ); + } + } +} diff --git a/solana/runtime-tests/Cargo.toml b/solana/runtime-tests/Cargo.toml new file mode 100644 index 00000000..2edf4215 --- /dev/null +++ b/solana/runtime-tests/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "launch-pool-svm" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT" + +[features] +svm-tests = [] + +[[test]] +name = "lifecycle" +path = "tests/lifecycle.rs" +required-features = ["svm-tests"] + +[dev-dependencies] +anchor-lang = "=0.31.1" +anchor-spl = { version = "=0.31.1", default-features = false, features = ["token"] } +bincode = "=1.3.3" +# Anchor's legacy dependency shares this permitted five8_core version with +# modern host SDKs. The host-only Error impl must be enabled for keypair parsing. +five8_core = { version = "=0.1.2", features = ["std"] } +launch-pool = { path = "../programs/launch_pool" } +litesvm = "=0.13.1" +solana-account = "=3.4.0" +solana-address = "=2.4.0" +solana-clock = "=3.0.1" +solana-instruction = "=3.2.0" +solana-keypair = "=3.1.2" +solana-message = "=3.1.0" +solana-sdk-ids = "=3.1.0" +solana-signer = "=3.0.0" +solana-transaction = "=3.1.0" diff --git a/solana/runtime-tests/tests/lifecycle.rs b/solana/runtime-tests/tests/lifecycle.rs new file mode 100644 index 00000000..f667ed14 --- /dev/null +++ b/solana/runtime-tests/tests/lifecycle.rs @@ -0,0 +1,561 @@ +//! Offline execution of the compiled SBPFv3 ELF with real System/SPL programs. +//! Ephemeral VM-only identities are generated in memory; no RPC or wallets. +use anchor_lang::prelude::Pubkey; +#[allow(deprecated)] +// Exercise the same legacy System instruction bytes as Anchor's CPI client. +use anchor_lang::solana_program::{ + instruction::Instruction as OldInstruction, program_pack::Pack, system_instruction, +}; +use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas}; +use anchor_spl::token::spl_token::{ + self, + state::{Account as TokenAccount, Mint}, +}; +use launch_pool::{ + math::SUPPLY, Pool, PrepareArgs, RecipientInput, SolVault, ACTIVE, CANCELLED, PREPARED, +}; +use litesvm::{ + types::{FailedTransactionMetadata, TransactionMetadata}, + LiteSVM, +}; +use solana_address::Address; +use solana_clock::Clock; +use solana_instruction::{account_meta::AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_message::Message; +use solana_signer::Signer; +use solana_transaction::Transaction; + +fn address(key: Pubkey) -> Address { + Address::new_from_array(key.to_bytes()) +} +fn pubkey(key: Address) -> Pubkey { + Pubkey::new_from_array(key.to_bytes()) +} +fn convert(ix: OldInstruction) -> Instruction { + Instruction { + program_id: address(ix.program_id), + accounts: ix + .accounts + .into_iter() + .map(|a| AccountMeta { + pubkey: address(a.pubkey), + is_signer: a.is_signer, + is_writable: a.is_writable, + }) + .collect(), + data: ix.data, + } +} +fn instruction(accounts: impl ToAccountMetas, data: impl InstructionData) -> Instruction { + convert(OldInstruction { + program_id: launch_pool::ID, + accounts: accounts.to_account_metas(None), + data: data.data(), + }) +} + +struct Fixture { + last_packet_bytes: usize, + svm: LiteSVM, + payer: Keypair, + nonce: u64, + pool: Pubkey, + mint: Pubkey, + token: Pubkey, + reserve: Pubkey, + fees: Pubkey, + mint_authority: Pubkey, + program_data: Pubkey, +} +impl Fixture { + fn new() -> Self { + let mut svm = LiteSVM::new(); + let bytes = std::fs::read(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../target/deploy/launch_pool.so" + )) + .expect("Build ELF with scripts/check-sbf.sh before svm-tests"); + svm.add_program(address(launch_pool::ID), &bytes).unwrap(); + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 100_000_000_000).unwrap(); + let nonce = 7u64; + let creator = pubkey(payer.pubkey()); + let pool = Pubkey::find_program_address( + &[b"pool", creator.as_ref(), &nonce.to_le_bytes()], + &launch_pool::ID, + ) + .0; + let derive = + |seed: &[u8]| Pubkey::find_program_address(&[seed, pool.as_ref()], &launch_pool::ID).0; + let program_data = Pubkey::find_program_address( + &[launch_pool::ID.as_ref()], + &anchor_lang::solana_program::bpf_loader_upgradeable::ID, + ) + .0; + Self { + last_packet_bytes: 0, + svm, + payer, + nonce, + pool, + mint: derive(b"mint"), + token: derive(b"token"), + reserve: derive(b"reserve"), + fees: derive(b"fees"), + mint_authority: derive(b"mint-authority"), + program_data, + } + } + fn send( + &mut self, + instructions: &[Instruction], + extra: &[&Keypair], + ) -> Result> { + self.svm.expire_blockhash(); + let blockhash = self.svm.latest_blockhash(); + let mut signers = vec![&self.payer]; + signers.extend_from_slice(extra); + let transaction = Transaction::new( + &signers, + Message::new_with_blockhash(instructions, Some(&self.payer.pubkey()), &blockhash), + blockhash, + ); + let bytes = bincode::serialize(&transaction).unwrap(); + self.last_packet_bytes = bytes.len(); + assert!(bytes.len() <= 1232, "packet {} exceeds 1232", bytes.len()); + self.svm.send_transaction(transaction).map_err(Box::new) + } + fn args(&self) -> PrepareArgs { + PrepareArgs { + nonce: self.nonce, + virtual_sol: 30_000_000_000, + fee_bps: 100, + name: "Openlaunch test".into(), + symbol: "TEST".into(), + uri: String::new(), + content_hash: [0; 32], + recipients: vec![RecipientInput { + address: pubkey(self.payer.pubkey()), + weight_bps: 10_000, + }], + } + } + fn prepare_ix(&self, args: PrepareArgs) -> Instruction { + instruction( + launch_pool::accounts::PrepareLaunch { + creator: pubkey(self.payer.pubkey()), + payer: pubkey(self.payer.pubkey()), + pool: self.pool, + system_program: anchor_lang::system_program::ID, + }, + launch_pool::instruction::PrepareLaunch { args }, + ) + } + fn prepare(&mut self) { + let ix = self.prepare_ix(self.args()); + let result = self.send(&[ix], &[]).unwrap(); + println!("prepare CU={}", result.compute_units_consumed); + } + fn activate_ix(&self) -> Instruction { + instruction( + launch_pool::accounts::ActivateLaunch { + creator: pubkey(self.payer.pubkey()), + pool: self.pool, + mint: self.mint, + token_vault: self.token, + reserve: self.reserve, + fees: self.fees, + mint_authority: self.mint_authority, + executable: launch_pool::ID, + program_data: self.program_data, + token_program: spl_token::ID, + system_program: anchor_lang::system_program::ID, + rent: anchor_lang::solana_program::sysvar::rent::ID, + }, + launch_pool::instruction::ActivateLaunch {}, + ) + } + fn activate(&mut self) { + let ix = self.activate_ix(); + let result = self.send(&[ix], &[]).unwrap(); + println!("activate CU={}", result.compute_units_consumed); + } + fn pool_state(&self) -> Pool { + Pool::try_deserialize( + &mut self + .svm + .get_account(&address(self.pool)) + .unwrap() + .data + .as_slice(), + ) + .unwrap() + } + fn vault_state(&self, key: Pubkey) -> SolVault { + SolVault::try_deserialize(&mut self.svm.get_account(&address(key)).unwrap().data.as_slice()) + .unwrap() + } + fn lamports(&self, key: Pubkey) -> u64 { + self.svm.get_account(&address(key)).unwrap().lamports + } + fn token_amount(&self, key: Pubkey) -> u64 { + TokenAccount::unpack(&self.svm.get_account(&address(key)).unwrap().data) + .unwrap() + .amount + } + fn create_trader_token(&mut self) -> Pubkey { + let token = Keypair::new(); + let token_key = pubkey(token.pubkey()); + let create = convert(system_instruction::create_account( + &pubkey(self.payer.pubkey()), + &token_key, + self.svm + .minimum_balance_for_rent_exemption(TokenAccount::LEN), + TokenAccount::LEN as u64, + &spl_token::ID, + )); + let initialize = convert( + spl_token::instruction::initialize_account3( + &spl_token::ID, + &token_key, + &self.mint, + &pubkey(self.payer.pubkey()), + ) + .unwrap(), + ); + self.send(&[create, initialize], &[&token]).unwrap(); + token_key + } + fn trade_accounts(&self, trader_token: Pubkey) -> launch_pool::accounts::Trade { + launch_pool::accounts::Trade { + trader: pubkey(self.payer.pubkey()), + pool: self.pool, + mint: self.mint, + token_vault: self.token, + reserve: self.reserve, + fees: self.fees, + trader_token, + token_program: spl_token::ID, + system_program: anchor_lang::system_program::ID, + } + } + fn buy_ix(&self, trader_token: Pubkey, input: u64, minimum: u64) -> Instruction { + instruction( + self.trade_accounts(trader_token), + launch_pool::instruction::BuyExactIn { + amount_in: input, + minimum_out: minimum, + expiry_slot: self.svm.get_sysvar::().slot + 100, + }, + ) + } + fn sell_ix(&self, trader_token: Pubkey, input: u64, minimum: u64) -> Instruction { + instruction( + self.trade_accounts(trader_token), + launch_pool::instruction::SellExactIn { + amount_in: input, + minimum_out: minimum, + expiry_slot: self.svm.get_sysvar::().slot + 100, + }, + ) + } + fn claim_ix(&self, index: u8, recipient: Pubkey) -> Instruction { + instruction( + launch_pool::accounts::ClaimFees { + pool: self.pool, + fees: self.fees, + recipient, + }, + launch_pool::instruction::ClaimFees { + recipient_index: index, + }, + ) + } + fn cancel_ix(&self) -> Instruction { + instruction( + launch_pool::accounts::CancelPreparation { + creator: pubkey(self.payer.pubkey()), + pool: self.pool, + }, + launch_pool::instruction::CancelPreparation {}, + ) + } + fn prefund(&mut self, key: Pubkey) { + let ix = convert(system_instruction::transfer( + &pubkey(self.payer.pubkey()), + &key, + 1_000_000, + )); + self.send(&[ix], &[]).unwrap(); + } +} + +#[test] +fn real_sbf_lifecycle_atomic_mint_revoke_buy_sell_claim() { + let mut f = Fixture::new(); + f.prepare(); + assert_eq!(f.pool_state().status, PREPARED); + f.activate(); + let state = f.pool_state(); + assert_eq!(state.status, ACTIVE); + assert_eq!(state.token_reserve, SUPPLY); + let mint = Mint::unpack(&f.svm.get_account(&address(f.mint)).unwrap().data).unwrap(); + assert_eq!(mint.supply, SUPPLY); + assert_eq!(mint.decimals, 6); + assert!(mint.mint_authority.is_none()); + assert!(mint.freeze_authority.is_none()); + assert_eq!(f.token_amount(f.token), SUPPLY); + let token = f.create_trader_token(); + let buy = f.buy_ix(token, 1_000_000_000, 31_945_788_964_181); + let result = f.send(&[buy], &[]).unwrap(); + println!("buy CU={}", result.compute_units_consumed); + assert_eq!(f.token_amount(token), 31_945_788_964_181); + assert_eq!(f.pool_state().sol_reserve, 990_000_000); + let sell = f.sell_ix(token, f.token_amount(token), 980_099_999); + let result = f.send(&[sell], &[]).unwrap(); + println!("sell CU={}", result.compute_units_consumed); + assert_eq!(f.token_amount(token), 0); + assert_eq!(f.pool_state().sol_reserve, 1); + assert_eq!(f.pool_state().fees_earned, 19_900_000); + let rent = f.vault_state(f.fees).rent_floor; + let claim = f.claim_ix(0, pubkey(f.payer.pubkey())); + let result = f.send(std::slice::from_ref(&claim), &[]).unwrap(); + println!("claim CU={}", result.compute_units_consumed); + assert_eq!(f.lamports(f.fees), rent); + assert_eq!(f.pool_state().recipients[0].paid, 19_900_000); + assert!(f.send(&[claim], &[]).is_err()); + assert!(f.send(&[f.cancel_ix()], &[]).is_err()); + assert!(f.send(&[f.activate_ix()], &[]).is_err()); +} + +#[test] +fn prefunded_pool_and_all_activation_pdas_cannot_grief_creation() { + for index in 0..5 { + let mut f = Fixture::new(); + let target = [f.pool, f.mint, f.token, f.reserve, f.fees][index]; + f.prefund(target); + f.prepare(); + f.activate(); + assert_eq!(f.pool_state().status, ACTIVE); + assert_eq!(f.token_amount(f.token), SUPPLY); + } +} + +#[test] +fn cancelled_nonce_is_terminal_and_active_pool_cannot_reinitialize() { + let mut f = Fixture::new(); + f.prepare(); + let cancel = f.cancel_ix(); + f.send(std::slice::from_ref(&cancel), &[]).unwrap(); + assert_eq!(f.pool_state().status, CANCELLED); + assert!(f.send(&[cancel], &[]).is_err()); + assert!(f.send(&[f.prepare_ix(f.args())], &[]).is_err()); + assert!(f.send(&[f.activate_ix()], &[]).is_err()); + assert!(f.svm.get_account(&address(f.mint)).is_none()); +} + +#[test] +fn failed_later_instruction_rolls_back_successful_activation_and_buy_cpis() { + let mut f = Fixture::new(); + f.prepare(); + // Activation succeeds internally; the following active cancellation must fail. + assert!(f.send(&[f.activate_ix(), f.cancel_ix()], &[]).is_err()); + assert_eq!(f.pool_state().status, PREPARED); + assert!(f.svm.get_account(&address(f.mint)).is_none()); + f.activate(); + let token = f.create_trader_token(); + let initial_fees = f.lamports(f.fees); + let initial_reserve = f.lamports(f.reserve); + let good_buy = f.buy_ix(token, 1_000_000_000, 1); + let impossible_buy = f.buy_ix(token, 1_000_000_000, SUPPLY); + assert!(f.send(&[good_buy, impossible_buy], &[]).is_err()); + assert_eq!(f.token_amount(token), 0); + assert_eq!(f.pool_state().sequence, 0); + assert_eq!(f.lamports(f.fees), initial_fees); + assert_eq!(f.lamports(f.reserve), initial_reserve); +} + +#[test] +fn maximum_terms_fit_packet_and_compute_limits() { + let mut f = Fixture::new(); + let mut args = f.args(); + args.name = "N".repeat(32); + args.symbol = "S".repeat(10); + args.uri = "u".repeat(200); + args.recipients = (0..7) + .map(|i| RecipientInput { + address: Pubkey::new_unique(), + weight_bps: if i == 6 { 1432 } else { 1428 }, + }) + .collect(); + let ix = f.prepare_ix(args); + let meta = f.send(&[ix], &[]).unwrap(); + println!( + "max prepare CU={} packet={} bytes", + meta.compute_units_consumed, f.last_packet_bytes + ); + f.activate(); + assert_eq!(f.pool_state().recipient_count, 7); +} + +#[test] +fn wrong_custody_accounts_and_expiry_reject_without_movement() { + let mut f = Fixture::new(); + f.prepare(); + f.activate(); + let token = f.create_trader_token(); + for mode in 0..5 { + let mut ix = f.buy_ix(token, 1_000_000_000, 1); + match mode { + 0 => ix.accounts.swap(4, 5), + 1 => ix.accounts[6].pubkey = address(f.token), + 2 => ix.accounts[7].pubkey = solana_sdk_ids::system_program::ID, + 3 => ix.accounts[2].pubkey = address(f.token), + 4 => { + f.svm.warp_to_slot(200); + ix.data = launch_pool::instruction::BuyExactIn { + amount_in: 1_000_000_000, + minimum_out: 1, + expiry_slot: 100, + } + .data(); + } + _ => unreachable!(), + } + assert!(f.send(&[ix], &[]).is_err()); + assert_eq!(f.pool_state().sequence, 0); + assert_eq!(f.token_amount(token), 0); + } +} + +#[test] +fn mutable_or_unrelated_programdata_cannot_activate() { + #[allow(deprecated)] + use anchor_lang::solana_program::bpf_loader_upgradeable::UpgradeableLoaderState; + let mut f = Fixture::new(); + f.prepare(); + let mut account = f.svm.get_account(&address(f.program_data)).unwrap(); + let state = bincode::serialize(&UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address: Some(pubkey(f.payer.pubkey())), + }) + .unwrap(); + account.data[..45].copy_from_slice(&state); + f.svm.set_account(address(f.program_data), account).unwrap(); + assert!(f.send(&[f.activate_ix()], &[]).is_err()); + assert_eq!(f.pool_state().status, PREPARED); + assert!(f.svm.get_account(&address(f.mint)).is_none()); + let mut wrong_ix = f.activate_ix(); + wrong_ix.accounts[8].pubkey = f.payer.pubkey(); + assert!(f.send(&[wrong_ix], &[]).is_err()); +} + +#[test] +fn donations_and_holder_burns_do_not_change_quotes_or_fee_claims() { + let mut f = Fixture::new(); + f.prepare(); + f.activate(); + let token = f.create_trader_token(); + f.send(&[f.buy_ix(token, 1_000_000_000, 1)], &[]).unwrap(); + let payer = pubkey(f.payer.pubkey()); + let donate_token = convert( + spl_token::instruction::transfer_checked( + &spl_token::ID, + &token, + &f.mint, + &f.token, + &payer, + &[], + 1_000_000, + 6, + ) + .unwrap(), + ); + let burn = convert( + spl_token::instruction::burn_checked( + &spl_token::ID, + &token, + &f.mint, + &payer, + &[], + 2_000_000, + 6, + ) + .unwrap(), + ); + let donate_sol = convert(system_instruction::transfer( + &payer, + &f.reserve, + 5_000_000_000, + )); + let donate_fee = convert(system_instruction::transfer(&payer, &f.fees, 1_000_000)); + f.send(&[donate_token, burn, donate_sol, donate_fee], &[]) + .unwrap(); + let before = f.pool_state(); + let q = launch_pool::math::buy( + before.token_reserve, + before.sol_reserve, + before.virtual_sol, + before.fee_bps, + 1_000_000_000, + ) + .unwrap(); + f.send(&[f.buy_ix(token, 1_000_000_000, q.amount_out)], &[]) + .unwrap(); + assert_eq!(f.pool_state().sol_reserve, q.sol_reserve); + assert_eq!(f.token_amount(f.token), q.token_reserve + 1_000_000); + let quote_rent = f.vault_state(f.reserve).rent_floor; + assert_eq!( + f.lamports(f.reserve), + quote_rent + q.sol_reserve + 5_000_000_000 + ); + f.send(&[f.claim_ix(0, payer)], &[]).unwrap(); + assert_eq!( + f.lamports(f.fees), + f.vault_state(f.fees).rent_floor + 1_000_000 + ); + let sell = f.sell_ix(token, f.token_amount(token), 1); + f.send(&[sell], &[]).unwrap(); + assert!(f.lamports(f.reserve) >= quote_rent + 5_000_000_000); + let extra_mint = convert( + spl_token::instruction::mint_to_checked(&spl_token::ID, &f.mint, &token, &payer, &[], 1, 6) + .unwrap(), + ); + assert!(f.send(&[extra_mint], &[]).is_err()); + let freeze = convert( + spl_token::instruction::freeze_account(&spl_token::ID, &token, &f.mint, &payer, &[]) + .unwrap(), + ); + assert!(f.send(&[freeze], &[]).is_err()); +} + +#[test] +fn rent_incompatible_recipient_cannot_block_another_recipient_claim() { + let mut f = Fixture::new(); + let unused = Pubkey::new_unique(); + let payer = pubkey(f.payer.pubkey()); + let mut args = f.args(); + args.recipients = vec![ + RecipientInput { + address: unused, + weight_bps: 5000, + }, + RecipientInput { + address: payer, + weight_bps: 5000, + }, + ]; + f.send(&[f.prepare_ix(args)], &[]).unwrap(); + f.activate(); + let token = f.create_trader_token(); + f.send(&[f.buy_ix(token, 100, 1)], &[]).unwrap(); + f.send(&[f.buy_ix(token, 100, 1)], &[]).unwrap(); + assert_eq!(f.pool_state().fees_earned, 2); + // One lamport cannot initialize a rent-exempt destination account. + assert!(f.send(&[f.claim_ix(0, unused)], &[]).is_err()); + assert_eq!(f.pool_state().recipients[0].paid, 0); + f.send(&[f.claim_ix(1, payer)], &[]).unwrap(); + assert_eq!(f.pool_state().recipients[1].paid, 1); + assert_eq!(f.pool_state().recipients[0].paid, 0); +} diff --git a/solana/scripts/check-sbf.sh b/solana/scripts/check-sbf.sh new file mode 100644 index 00000000..2c522966 --- /dev/null +++ b/solana/scripts/check-sbf.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Build only. No RPC, wallet, deployment, key upload or authority mutation. +set -euo pipefail +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." + +build_sbf="${SBF_BUILD_BIN:-.tools/bin/cargo-build-sbf}" +if [[ ! -x "$build_sbf" ]]; then + printf '%s\n' 'Install locally first: cargo install cargo-build-sbf --version 4.2.0 --locked --root .tools' >&2 + exit 1 +fi +tool_version="$($build_sbf --version)" +if [[ "$tool_version" != *'cargo-build-sbf 4.2.0'* ]]; then + printf '%s\n' 'Expected cargo-build-sbf 4.2.0; review toolchain changes explicitly.' >&2 + exit 1 +fi +command -v rg >/dev/null || { printf '%s\n' 'ripgrep is required to reject stack diagnostics.' >&2; exit 1; } +mkdir -p target +"$build_sbf" --tools-version v1.56 --arch v3 --manifest-path programs/launch_pool/Cargo.toml -- --locked 2>&1 | tee target/sbf-build.log +if rg -n -i 'stack offset .*exceed|stack frame size .*exceed|undefined symbol|call depth exceeded' target/sbf-build.log; then + printf '%s\n' 'SBF diagnostics require review; do not release this binary.' >&2 + exit 1 +fi +test -s target/deploy/launch_pool.so +printf '%s\n' 'SBF compilation passed. This is not validator testing, audit approval, or deployment authorization.' From ea24bdc3d8f815fd7bc8a5eca60fb8afdd24b377 Mon Sep 17 00:00:00 2001 From: Vasanth T Date: Sat, 10 Oct 2026 15:36:02 +0530 Subject: [PATCH 2/9] feat(solana): add verified transaction SDK Keep quotes exact, bind instructions to canonical accounts, and verify signed messages and deployment bytes. Preserve transaction identities through uncertain submissions. --- app/packages/solana-sdk/README.md | 89 +++++++ app/packages/solana-sdk/package.json | 14 + .../solana-sdk/scripts/print-abi-fixture.ts | 29 +++ .../solana-sdk/scripts/verify-manifest.ts | 46 ++++ app/packages/solana-sdk/src/accounts.ts | 124 +++++++++ app/packages/solana-sdk/src/encoding.ts | 51 ++++ app/packages/solana-sdk/src/index.ts | 5 + app/packages/solana-sdk/src/instructions.ts | 54 ++++ app/packages/solana-sdk/src/math.ts | 108 ++++++++ app/packages/solana-sdk/src/transactions.ts | 71 +++++ app/packages/solana-sdk/src/verification.ts | 31 +++ .../solana-sdk/tests/abi-fixture.json | 243 ++++++++++++++++++ .../solana-sdk/tests/accounts.test.ts | 58 +++++ .../solana-sdk/tests/instructions.test.ts | 51 ++++ app/packages/solana-sdk/tests/math.test.ts | 68 +++++ .../solana-sdk/tests/transactions.test.ts | 48 ++++ .../solana-sdk/tests/verification.test.ts | 27 ++ app/packages/solana-sdk/tsconfig.json | 8 + 18 files changed, 1125 insertions(+) create mode 100644 app/packages/solana-sdk/README.md create mode 100644 app/packages/solana-sdk/package.json create mode 100644 app/packages/solana-sdk/scripts/print-abi-fixture.ts create mode 100644 app/packages/solana-sdk/scripts/verify-manifest.ts create mode 100644 app/packages/solana-sdk/src/accounts.ts create mode 100644 app/packages/solana-sdk/src/encoding.ts create mode 100644 app/packages/solana-sdk/src/index.ts create mode 100644 app/packages/solana-sdk/src/instructions.ts create mode 100644 app/packages/solana-sdk/src/math.ts create mode 100644 app/packages/solana-sdk/src/transactions.ts create mode 100644 app/packages/solana-sdk/src/verification.ts create mode 100644 app/packages/solana-sdk/tests/abi-fixture.json create mode 100644 app/packages/solana-sdk/tests/accounts.test.ts create mode 100644 app/packages/solana-sdk/tests/instructions.test.ts create mode 100644 app/packages/solana-sdk/tests/math.test.ts create mode 100644 app/packages/solana-sdk/tests/transactions.test.ts create mode 100644 app/packages/solana-sdk/tests/verification.test.ts create mode 100644 app/packages/solana-sdk/tsconfig.json diff --git a/app/packages/solana-sdk/README.md b/app/packages/solana-sdk/README.md new file mode 100644 index 00000000..17f6a108 --- /dev/null +++ b/app/packages/solana-sdk/README.md @@ -0,0 +1,89 @@ +# Openlaunch Solana SDK + +Original TypeScript client for the unreleased immutable launch-pool program. This package is not an audited protocol and includes no production program ID. The compile/test fixture ID must never be used as a deployment configuration. + +Kept inside `app/packages` so the existing app-only Docker build context contains the local package. Import `@openlaunch/solana-sdk` from the app. Source exports require a TypeScript-aware bundler or `tsx`; Next supports this without a separate generated client. Public keys remain case-sensitive base58. Amounts are `bigint` in token atoms or SOL lamports, never JavaScript floating-point amounts. + +## Dependencies + +- `@solana/web3.js` 1.99.0: pinned class-based transaction/account API shared with the app wallet integration. The v1 line is maintenance-only; a future major upgrade requires rerunning wallet message and serialization tests, not an unreviewed range bump. +- `@noble/hashes` 1.8.0: browser-compatible SHA-256 for Anchor discriminators and reviewed ELF verification. No private-key handling. +- `buffer` 6.0.3: explicit browser-compatible binary encoding instead of relying on a global Node `Buffer`. + +The SDK does not depend on Anchor's JavaScript client, a DEX SDK, SPL Token metadata services, server signing, or an external quote service. + +## Public API + +| API | Result / contract | +| --- | --- | +| `derivePoolAddresses(programId, creator, nonce)` | Canonical pool, mint, token vault, SOL reserve, fee vault and temporary mint-authority PDAs; `nonce` is u64 bigint. | +| `deriveAssociatedTokenAddress(mint, owner)` | Legacy SPL associated token account, including supported off-curve owners. | +| `decodePoolAccount(address, accountInfo, programId)` | Checked `PoolAccount`; requires owner, discriminator, ABI version, canonical PDAs and immutable terms. | +| `fetchPoolSnapshot(connection, programId, address)` | `{ address, addresses, pool, slot }`; account and custody checks use one confirmed-bank batch. Accepts donated surplus; refuses unsafe numeric RPC balances. | +| `verifyImmutableProgram(connection, programId)` | Finalized linked Program/ProgramData check; rejects retained upgrade authority or wrong loader. | +| `quoteBuy(state, grossLamports, minOutput = 1n)` | Exact SOL-in buy, fee rounded up and token output rounded down. | +| `quoteSell(state, tokenAtoms, minOutput = 1n)` | Exact token-in sell; rejects any attempt to spend virtual SOL. | +| `feeEntitlement(earned, weightBps)` / `claimableFees(earned, weightBps, paid)` | Checked lifetime u128 accounting without multiplication overflow. | +| `minimumAfterSlippage(output, bps)` | Conservative minimum; refuses zero or 100% slippage. | +| `parseAmount(text, decimals)` / `formatAmount(value, decimals)` | Exact human amount handling; rejects exponents, signs and excessive fractional digits. | + +`PoolAccount` contains `status: "prepared" | "active" | "cancelled"`, `creator`, `rentPayer`, `mint`, `nonce`, `virtualSol`, `feeBps`, `recipients`, `tokenInventory`, `realSolReserves`, `feesEarned`, `sequence`, `name`, `symbol`, `uri`, and `contentHash`. Every recipient contains `{ address: PublicKey, weightBps: number, paid: bigint }`. Empty URI is valid; metadata lives in our program, not an automatically recognized wallet metadata mirror. + +`CurveState` needs `{ tokenInventory, realSolReserves, virtualSol, feeBps }`. Both quote functions return `{ grossInput, netInput, fee, amountOut, nextTokenInventory, nextRealSolReserves }`. On sells, `grossInput` and `netInput` are token atoms; `fee` and `amountOut` are lamports. Virtual offset is not reserve liquidity or collateral. The technical 1 through 1,000,000 SOL offset bounds are not endorsed valuations and remain subject to economic review. + +### Instruction builders + +All builders return a `TransactionInstruction`, never send or sign it. + +- `buildPrepareLaunchInstruction(programId, { creator, payer?, nonce, virtualSol, feeBps, name, symbol, uri, contentHash, recipients })` binds immutable configuration. `contentHash` is exactly 32 bytes. `recipients` is an array of `{ address, weightBps }`, empty for 0% fees. +- `buildActivateLaunchInstruction(programId, creator, nonce)` activates only after the program's own upgrade authority is absent. The creator pays activation rent. +- `buildCancelPreparationInstruction(programId, creator, nonce)` makes preparation terminal. The retained tombstone is not a refundable account. +- `buildBuyInstruction(programId, tradeArgs)` / `buildSellInstruction(programId, tradeArgs)` use `{ creator, nonce, trader, traderToken?, amountIn, minimumOut, expirySlot }`. Omitting `traderToken` selects the canonical ATA; it still needs to exist. +- `buildCreateAssociatedTokenInstruction(payer, mint, owner)` may be prepended to a buy, using the official idempotent ATA instruction. Rent is extra, not deducted from the quote. +- `buildClaimFeesInstruction(programId, pool, recipientIndex)` pays the immutable recipient only. Any wallet may pay transaction fees to trigger a claim; it cannot redirect the payment. + +See [`solana/ABI.md`](../../../solana/ABI.md) for exact account order and instruction layouts. Name/symbol/URI sizes count UTF-8 bytes. Slippage and expiry are enforced again on-chain; a client quote is not an execution guarantee. + +### Signing and ambiguous-submission recovery + +```ts +const prepared = await prepareTransaction(connection, walletPublicKey, instructions); +await simulatePreparedTransaction(connection, prepared); + +// Request wallet-standard signing of prepared.wireBytes, using the current account/cluster. +const signed = validateSignedTransaction(prepared.messageBytes, walletSignedBytes, walletPublicKey); +// Persist signed.signature, prepared.blockhash and prepared.lastValidBlockHeight BEFORE submission. +const submitted = await submitSignedTransaction(connection, signed); +const result = await reconcileTransaction(connection, signed.signature, prepared.lastValidBlockHeight); +``` + +The app must check the selected wallet account/cluster again after the wallet prompt. Returned transaction messages must be byte-identical to the reviewed message and all required signatures valid. The SDK intentionally supports legacy transactions only in this first integration. SDK preparation/simulation contains no user signer. + +Submission errors return `unknown`, retaining the locally derived signature. Do not create a replacement transaction after a timeout. Reconciliation reads finalized block height and historical signature status. A processed/confirmed failure remains `pending` because it could exist only on a fork. Only finalized failure or expiry with no observed signature is terminal. Reliable archival RPC history is part of the operational assumptions; compare another trusted endpoint for unresolved anomalies. Never silently clear recovery state just because the UI was closed. + +## Read-only deployment evidence + +From `app/`, set `SOLANA_VERIFY_RPC_URL` privately, then run: + +```text +npx tsx packages/solana-sdk/scripts/verify-manifest.ts +``` + +The manifest requires `schema: 1`, `cluster` (`devnet` or `mainnet-beta`), `programId`, canonical `programData`, an independently verified `genesisHash`, exact 40-character `sourceCommit`, 64-character `binarySha256`, nullable `verifiedSlot`, nullable `securityReview` / `economicReview`, and `releaseApproved` boolean. + +The checker compares RPC genesis, reads both this program and the legacy Token Program at one finalized context, checks their actual linked ProgramData and absent upgrade authorities, hashes the reviewed local ELF, and compares every deployed byte with it (only zero allocation padding is tolerated). It prints proof observations to stdout and **never** changes the manifest, grants approval, deploys, sends transactions or changes authority. Error output excludes RPC URLs, provider errors, local file paths and environment values. + +Retain its output with a reproducible source-to-ELF build attestation and a second independently trusted RPC check. Matching an ELF supplied by the caller does not prove that ELF came from the reviewed source, that the RPC is honest, that the program is secure, or that an audit is complete. `releaseApproved` in evidence is always false. Human release approval remains a separate reviewed action. + +## Verification + +From `app/`: + +```text +npx tsc --noEmit -p packages/solana-sdk/tsconfig.json +npx tsx --test packages/solana-sdk/tests/*.test.ts +``` + +Tests cover golden values shared with Rust, independent binary-search oracle comparisons, u64/u128 limits, fee rounding, slippage, decoder/PDA substitution, donated surplus, immutable ProgramData linkage, wallet message mutation, partial/invalid signatures, ambiguous submission and packet measurements. `tests/abi-fixture.json` contains only deterministic public fixture identities and serialized ABI bytes, no private key or deployable configuration. `scripts/print-abi-fixture.ts` reproduces it for intentional ABI changes. + +Measured unsigned legacy packet sizes with one signer: maximum-length seven-recipient preparation 792 bytes, activation 509 bytes, buy with idempotent ATA creation 508 bytes. Preparation and activation are separate transactions. These checks do not measure runtime compute, rent, wallet compatibility or real-validator lifecycle behavior; those remain separate release gates. diff --git a/app/packages/solana-sdk/package.json b/app/packages/solana-sdk/package.json new file mode 100644 index 00000000..1911abd1 --- /dev/null +++ b/app/packages/solana-sdk/package.json @@ -0,0 +1,14 @@ +{ + "name": "@openlaunch/solana-sdk", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { ".": "./src/index.ts", "./math": "./src/math.ts" }, + "scripts": { + "test": "tsx --test tests/*.test.ts", + "typecheck": "tsc --noEmit", + "verify-manifest": "tsx scripts/verify-manifest.ts" + }, + "dependencies": { "@solana/web3.js": "1.99.0", "@noble/hashes": "1.8.0", "buffer": "6.0.3" }, + "devDependencies": { "@types/node": "^22", "tsx": "4.23.13", "typescript": "5.9.3" } +} diff --git a/app/packages/solana-sdk/scripts/print-abi-fixture.ts b/app/packages/solana-sdk/scripts/print-abi-fixture.ts new file mode 100644 index 00000000..4881520e --- /dev/null +++ b/app/packages/solana-sdk/scripts/print-abi-fixture.ts @@ -0,0 +1,29 @@ +/** Emits public deterministic fixture JSON; never creates keys or transactions on a network. */ +import { Buffer } from "buffer"; +import { PublicKey } from "@solana/web3.js"; +import { derivePoolAddresses } from "../src/accounts.ts"; +import { discriminator, textField, u8, u16, u64 } from "../src/encoding.ts"; +import { buildActivateLaunchInstruction, buildBuyInstruction, buildCancelPreparationInstruction, buildClaimFeesInstruction, buildPrepareLaunchInstruction, buildSellInstruction } from "../src/instructions.ts"; + +const programId = new PublicKey("Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr"); +const creator = new PublicKey(new Uint8Array(32).fill(1)); +const nonce = 7n; +const a = derivePoolAddresses(programId, creator, nonce); +const prepare = { creator, nonce, virtualSol: 30_000_000_000n, feeBps: 0, name: "Example", symbol: "EX", uri: "", contentHash: new Uint8Array(32), recipients: [] }; +const trade = { creator, nonce, trader: creator, amountIn: 1_000_000_000n, minimumOut: 10n, expirySlot: 1000n }; +const pool = Buffer.concat([discriminator("account", "Pool"), u8(1), u8(0), u8(a.bump), u64(nonce), creator.toBuffer(), creator.toBuffer(), a.mint.toBuffer(), u64(prepare.virtualSol), u16(0), u8(0), Buffer.alloc(7 * 50), u64(0n), u64(0n), Buffer.alloc(16), u64(0n), textField("Example", 32, "name"), textField("EX", 10, "symbol"), textField("", 200, "uri", true), Buffer.alloc(32)]); +const instructions = { + prepareLaunch: buildPrepareLaunchInstruction(programId, prepare), + activateLaunch: buildActivateLaunchInstruction(programId, creator, nonce), + cancelPreparation: buildCancelPreparationInstruction(programId, creator, nonce), + buyExactIn: buildBuyInstruction(programId, trade), + sellExactIn: buildSellInstruction(programId, trade), + claimFees: buildClaimFeesInstruction(programId, { creator, nonce, recipients: [{ address: creator, weightBps: 10000, paid: 0n }] }, 0), +}; +console.log(JSON.stringify({ + note: "Undeployed fixture identity only; no private key or network activity.", + programId: programId.toBase58(), creator: creator.toBase58(), nonce: nonce.toString(), + addresses: Object.fromEntries(Object.entries(a).map(([key, value]) => [key, typeof value === "number" ? value : value.toBase58()])), + preparedPoolHex: pool.toString("hex"), + instructions: Object.fromEntries(Object.entries(instructions).map(([key, ix]) => [key, { dataHex: ix.data.toString("hex"), keys: ix.keys.map((k) => ({ address: k.pubkey.toBase58(), signer: k.isSigner, writable: k.isWritable })) }])), +}, null, 2)); diff --git a/app/packages/solana-sdk/scripts/verify-manifest.ts b/app/packages/solana-sdk/scripts/verify-manifest.ts new file mode 100644 index 00000000..3a032eb3 --- /dev/null +++ b/app/packages/solana-sdk/scripts/verify-manifest.ts @@ -0,0 +1,46 @@ +/** Read-only release evidence. No wallet, private-key input, transaction, deployment or authority operation. */ +import { readFile } from "node:fs/promises"; +import { Connection, PublicKey } from "@solana/web3.js"; +import { decodeImmutableProgram, programDataAddress, TOKEN_PROGRAM_ID } from "../src/accounts.ts"; +import { validateDeploymentManifest, verifyDeploymentBytes } from "../src/verification.ts"; + +let stage = "read manifest and reviewed ELF"; +try { + const [manifestPath, elfPath, ...extra] = process.argv.slice(2); + if (!manifestPath || !elfPath || extra.length > 0) throw new Error("usage"); + const manifest = validateDeploymentManifest(JSON.parse(await readFile(manifestPath, "utf8"))); + const elf = await readFile(elfPath); + stage = "connect to the explicitly configured read-only RPC"; + const rpcUrl = process.env.SOLANA_VERIFY_RPC_URL; + if (!rpcUrl) throw new Error("Missing RPC configuration"); + const url = new URL(rpcUrl); + if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) throw new Error("HTTPS required"); + const connection = new Connection(rpcUrl, { commitment: "finalized", disableRetryOnRateLimit: true, fetch: (input, init) => fetch(input, { ...init, signal: AbortSignal.timeout(20_000) }) }); + stage = "match the RPC genesis hash to the reviewed manifest"; + if (await connection.getGenesisHash() !== manifest.genesisHash) throw new Error("Wrong cluster"); + stage = "read finalized program and dependency accounts"; + const programId = new PublicKey(manifest.programId); + const ownData = new PublicKey(manifest.programData); + const tokenData = programDataAddress(TOKEN_PROGRAM_ID); + const { context, value } = await connection.getMultipleAccountsInfoAndContext([programId, ownData, TOKEN_PROGRAM_ID, tokenData], { commitment: "finalized" }); + const [program, programData, tokenProgram, tokenProgramData] = value; + if (!program || !programData || !tokenProgram || !tokenProgramData) throw new Error("Missing program accounts"); + stage = "verify linked immutable launch and legacy Token programs"; + const own = decodeImmutableProgram(programId, program, ownData, programData); + const token = decodeImmutableProgram(TOKEN_PROGRAM_ID, tokenProgram, tokenData, tokenProgramData); + stage = "compare deployed program bytes with the reviewed local ELF"; + const hash = verifyDeploymentBytes(programData.data, elf, manifest.binarySha256); + console.log(JSON.stringify({ + schema: 1, evidence: "read-only-rpc-and-binary-check", observedAt: new Date().toISOString(), + cluster: manifest.cluster, genesisHash: manifest.genesisHash, programId: manifest.programId, + programData: manifest.programData, sourceCommit: manifest.sourceCommit, binarySha256: hash, + verifiedSlot: context.slot, deploymentSlot: own.deploymentSlot.toString(), upgradeAuthority: null, + tokenProgram: TOKEN_PROGRAM_ID.toBase58(), tokenProgramData: tokenData.toBase58(), tokenDeploymentSlot: token.deploymentSlot.toString(), tokenUpgradeAuthority: null, + releaseApproved: false, + limitation: "This evidence is not an audit, source-to-binary reproducibility proof, or release approval. Independently verify using another trusted RPC and retain the build attestation.", + }, null, 2)); +} catch { + // RPC URLs can contain API secrets. Never print third-party errors, stacks, input paths or environment values. + console.error(`Verification failed at: ${stage}. No network state was changed. Check configuration and rerun. Usage: tsx packages/solana-sdk/scripts/verify-manifest.ts ; set SOLANA_VERIFY_RPC_URL privately.`); + process.exitCode = 1; +} diff --git a/app/packages/solana-sdk/src/accounts.ts b/app/packages/solana-sdk/src/accounts.ts new file mode 100644 index 00000000..9aec21fc --- /dev/null +++ b/app/packages/solana-sdk/src/accounts.ts @@ -0,0 +1,124 @@ +import { Buffer } from "buffer"; +import { PublicKey, type AccountInfo, type Connection } from "@solana/web3.js"; +import { discriminator, Reader, u64, utf8 } from "./encoding.ts"; +import { claimableFees, FIXED_SUPPLY, FEE_BPS, MAX_VIRTUAL_SOL, MIN_VIRTUAL_SOL, TOKEN_DECIMALS, validateCurve, type CurveState } from "./math.ts"; + +export const TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); +export const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); +export const UPGRADEABLE_LOADER_ID = new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111"); +export const POOL_ACCOUNT_SPACE = 802; +export const SOL_VAULT_SPACE = 49; +export type PoolAddresses = { + pool: PublicKey; mint: PublicKey; tokenVault: PublicKey; reserve: PublicKey; + fees: PublicKey; mintAuthority: PublicKey; bump: number; +}; +export function derivePoolAddresses(programId: PublicKey, creator: PublicKey, nonce: bigint): PoolAddresses { + const [pool, bump] = PublicKey.findProgramAddressSync([utf8.encode("pool"), creator.toBytes(), u64(nonce)], programId); + const derive = (seed: string) => PublicKey.findProgramAddressSync([utf8.encode(seed), pool.toBytes()], programId)[0]; + return { pool, bump, mint: derive("mint"), tokenVault: derive("token"), reserve: derive("reserve"), fees: derive("fees"), mintAuthority: derive("mint-authority") }; +} +export function deriveAssociatedTokenAddress(mint: PublicKey, owner: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync([owner.toBytes(), TOKEN_PROGRAM_ID.toBytes(), mint.toBytes()], ASSOCIATED_TOKEN_PROGRAM_ID)[0]; +} +export type Recipient = { address: PublicKey; weightBps: number; paid: bigint }; +export type PoolAccount = CurveState & { + version: 1; status: "prepared" | "active" | "cancelled"; bump: number; nonce: bigint; + creator: PublicKey; rentPayer: PublicKey; mint: PublicKey; recipients: Recipient[]; + feesEarned: bigint; sequence: bigint; name: string; symbol: string; uri: string; contentHash: Uint8Array; +}; + +/** Owner and canonical PDA validation are required, not optional decoder flags. */ +export function decodePoolAccount(address: PublicKey, info: AccountInfo, programId: PublicKey): PoolAccount { + if (!info.owner.equals(programId) || info.executable) throw new Error("Wrong pool owner"); + if (info.data.length > POOL_ACCOUNT_SPACE) throw new Error("Unsupported pool allocation size"); + const r = new Reader(info.data); + if (!r.take(8).equals(discriminator("account", "Pool"))) throw new Error("Wrong pool discriminator"); + const version = r.byte(); const statusByte = r.byte(); const bump = r.byte(); + if (version !== 1 || statusByte > 2) throw new Error("Unsupported pool version or status"); + const nonce = r.long(); const creator = r.key(); const rentPayer = r.key(); const mint = r.key(); + const virtualSol = r.long(); const feeBps = r.short(); const recipientCount = r.byte(); + if (recipientCount > 7 || !FEE_BPS.some((fee) => fee === feeBps) || virtualSol < MIN_VIRTUAL_SOL || virtualSol > MAX_VIRTUAL_SOL) throw new Error("Invalid pool terms"); + const recipients: Recipient[] = []; + for (let i = 0; i < 7; i++) { + const recipient = { address: r.key(), weightBps: r.short(), paid: r.wide() }; + if (i < recipientCount) recipients.push(recipient); + else if (!recipient.address.equals(PublicKey.default) || recipient.weightBps !== 0 || recipient.paid !== 0n) throw new Error("Nonempty unused recipient"); + } + const tokenInventory = r.long(); const realSolReserves = r.long(); const feesEarned = r.wide(); const sequence = r.long(); + const name = r.text(32); const symbol = r.text(10); const uri = r.text(200, true); const contentHash = new Uint8Array(r.take(32)); r.finish(); + const addresses = derivePoolAddresses(programId, creator, nonce); + if (!address.equals(addresses.pool) || bump !== addresses.bump || !mint.equals(addresses.mint)) throw new Error("Noncanonical pool or mint"); + validateRecipients(recipients, feeBps, addresses, programId); + for (const recipient of recipients) claimableFees(feesEarned, recipient.weightBps, recipient.paid); + if (recipients.reduce((sum, x) => sum + x.paid, 0n) > feesEarned) throw new Error("Fee liabilities are invalid"); + const result: PoolAccount = { + version, status: (["prepared", "active", "cancelled"] as const)[statusByte], bump, nonce, creator, rentPayer, mint, + virtualSol, feeBps, recipients, tokenInventory, realSolReserves, feesEarned, sequence, name, symbol, uri, contentHash, + }; + if (result.status === "active") validateCurve(result); + else if (tokenInventory !== 0n || realSolReserves !== 0n || feesEarned !== 0n || sequence !== 0n) throw new Error("Inactive pool has live accounting"); + return result; +} +export function validateRecipients(recipients: ReadonlyArray<{ address: PublicKey; weightBps: number }>, feeBps: number, addresses?: PoolAddresses, programId?: PublicKey): void { + if (!FEE_BPS.some((fee) => fee === feeBps)) throw new Error("Unsupported fee"); + if (feeBps === 0 ? recipients.length !== 0 : recipients.length < 1 || recipients.length > 7) throw new Error("Invalid recipient count"); + const seen = new Set(); + const forbidden = addresses ? [addresses.pool, addresses.mint, addresses.tokenVault, addresses.reserve, addresses.fees, addresses.mintAuthority] : []; + for (const r of recipients) { + if (r.address.equals(PublicKey.default) || (programId && r.address.equals(programId)) || forbidden.some((x) => x.equals(r.address)) || seen.has(r.address.toBase58()) || !Number.isInteger(r.weightBps) || r.weightBps < 1 || r.weightBps > 10_000) throw new Error("Invalid or duplicate recipient"); + seen.add(r.address.toBase58()); + } + if (feeBps !== 0 && recipients.reduce((sum, r) => sum + r.weightBps, 0) !== 10_000) throw new Error("Recipient weights must total 10000"); +} +export function programDataAddress(programId: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync([programId.toBytes()], UPGRADEABLE_LOADER_ID)[0]; +} +export function decodeImmutableProgram(programId: PublicKey, executable: AccountInfo, dataAddress: PublicKey, data: AccountInfo): { deploymentSlot: bigint } { + if (!executable.executable || !executable.owner.equals(UPGRADEABLE_LOADER_ID) || executable.data.length !== 36 || executable.data.readUInt32LE(0) !== 2) throw new Error("Unsupported executable loader state"); + const recorded = new PublicKey(executable.data.subarray(4, 36)); + if (!recorded.equals(dataAddress) || !dataAddress.equals(programDataAddress(programId))) throw new Error("ProgramData does not belong to this program"); + if (data.executable || !data.owner.equals(UPGRADEABLE_LOADER_ID) || data.data.length < 45 || data.data.readUInt32LE(0) !== 3) throw new Error("Invalid ProgramData"); + if (data.data[12] !== 0) throw new Error("Program still has an upgrade authority"); + return { deploymentSlot: data.data.readBigUInt64LE(4) }; +} +export async function verifyImmutableProgram(connection: Connection, programId: PublicKey): Promise<{ programData: PublicKey; deploymentSlot: bigint; slot: number }> { + const programData = programDataAddress(programId); + const { context, value } = await connection.getMultipleAccountsInfoAndContext([programId, programData], { commitment: "finalized" }); + if (!value[0] || !value[1]) throw new Error("Program is not deployed"); + return { programData, ...decodeImmutableProgram(programId, value[0], programData, value[1]), slot: context.slot }; +} + +function actualLamports(info: AccountInfo): bigint { + // web3.js v1 exposes RPC lamports as number: refuse unsafe rounding instead of inventing precision. + if (!Number.isSafeInteger(info.lamports) || info.lamports < 0) throw new Error("RPC lamport balance exceeds safe integer precision"); + return BigInt(info.lamports); +} +function verifySolVault(info: AccountInfo, programId: PublicKey, pool: PublicKey, kind: number, liability: bigint): void { + if (!info.owner.equals(programId) || info.executable || info.data.length !== SOL_VAULT_SPACE) throw new Error("Invalid SOL vault owner or size"); + const r = new Reader(info.data); + if (!r.take(8).equals(discriminator("account", "SolVault")) || !r.key().equals(pool) || r.byte() !== kind) throw new Error("Invalid SOL vault linkage"); + const rentFloor = r.long(); + if (actualLamports(info) < rentFloor + liability) throw new Error("SOL vault is underfunded"); +} +export type PoolSnapshot = { address: PublicKey; addresses: PoolAddresses; pool: PoolAccount; slot: number }; +export async function fetchPoolSnapshot(connection: Connection, programId: PublicKey, address: PublicKey): Promise { + const first = await connection.getAccountInfo(address, "confirmed"); + if (!first) throw new Error("Pool not found"); + if (first.data.length !== POOL_ACCOUNT_SPACE) throw new Error("Unexpected on-chain pool allocation"); + const initial = decodePoolAccount(address, first, programId); + const addresses = derivePoolAddresses(programId, initial.creator, initial.nonce); + // One bank/context for state and custody; never mix independent vault read slots. + const { context, value } = await connection.getMultipleAccountsInfoAndContext([address, addresses.mint, addresses.tokenVault, addresses.reserve, addresses.fees], { commitment: "confirmed" }); + if (!value[0]) throw new Error("Pool not found"); + if (value[0].data.length !== POOL_ACCOUNT_SPACE) throw new Error("Unexpected on-chain pool allocation"); + const pool = decodePoolAccount(address, value[0], programId); + if (pool.status === "active") { + const [, mint, token, reserve, fees] = value; + if (!mint || !token || !reserve || !fees) throw new Error("Missing active custody accounts"); + if (!mint.owner.equals(TOKEN_PROGRAM_ID) || mint.executable || mint.data.length !== 82 || mint.data.readUInt32LE(0) !== 0 || mint.data[44] !== TOKEN_DECIMALS || mint.data[45] !== 1 || mint.data.readUInt32LE(46) !== 0 || mint.data.readBigUInt64LE(36) > FIXED_SUPPLY) throw new Error("Invalid mint authorities or supply"); + if (!token.owner.equals(TOKEN_PROGRAM_ID) || token.executable || token.data.length !== 165 || !new PublicKey(token.data.subarray(0, 32)).equals(addresses.mint) || !new PublicKey(token.data.subarray(32, 64)).equals(address) || token.data[108] !== 1 || token.data.readUInt32LE(72) !== 0 || token.data.readUInt32LE(109) !== 0 || token.data.readUInt32LE(129) !== 0 || token.data.readBigUInt64LE(64) < pool.tokenInventory) throw new Error("Invalid token vault custody"); + verifySolVault(reserve, programId, address, 0, pool.realSolReserves); + verifySolVault(fees, programId, address, 1, pool.feesEarned - pool.recipients.reduce((sum, r) => sum + r.paid, 0n)); + } + return { address, addresses, pool, slot: context.slot }; +} diff --git a/app/packages/solana-sdk/src/encoding.ts b/app/packages/solana-sdk/src/encoding.ts new file mode 100644 index 00000000..9405b739 --- /dev/null +++ b/app/packages/solana-sdk/src/encoding.ts @@ -0,0 +1,51 @@ +import { Buffer } from "buffer"; +import { sha256 } from "@noble/hashes/sha256"; +import { PublicKey } from "@solana/web3.js"; +import { bounded, U64_MAX } from "./math.ts"; + +export const utf8 = new TextEncoder(); +export const discriminator = (namespace: "global" | "account" | "event", name: string): Buffer => + Buffer.from(sha256(utf8.encode(`${namespace}:${name}`)).slice(0, 8)); +export function u8(n: number): Buffer { + if (!Number.isInteger(n) || n < 0 || n > 255) throw new RangeError("Invalid u8"); + return Buffer.from([n]); +} +export function u16(n: number): Buffer { + if (!Number.isInteger(n) || n < 0 || n > 65535) throw new RangeError("Invalid u16"); + const b = Buffer.alloc(2); b.writeUInt16LE(n); return b; +} +export function u32(n: number): Buffer { + if (!Number.isSafeInteger(n) || n < 0 || n > 0xffffffff) throw new RangeError("Invalid u32"); + const b = Buffer.alloc(4); b.writeUInt32LE(n); return b; +} +export function u64(n: bigint): Buffer { + bounded(n, U64_MAX, "u64"); + const b = Buffer.alloc(8); b.writeBigUInt64LE(n); return b; +} +export function textField(text: string, maxBytes: number, label: string, allowEmpty = false): Buffer { + const bytes = Buffer.from(utf8.encode(text)); + if ((!allowEmpty && text.trim().length === 0) || bytes.length > maxBytes || /\p{Cc}/u.test(text)) throw new RangeError(`Invalid ${label}`); + return Buffer.concat([u32(bytes.length), bytes]); +} +export class Reader { + offset = 0; + constructor(readonly data: Buffer) {} + take(length: number): Buffer { + if (!Number.isSafeInteger(length) || length < 0 || this.offset + length > this.data.length) throw new Error("Truncated account data"); + const result = this.data.subarray(this.offset, this.offset + length); this.offset += length; return result; + } + byte(): number { return this.take(1)[0]; } + short(): number { return this.take(2).readUInt16LE(0); } + int(): number { return this.take(4).readUInt32LE(0); } + long(): bigint { return this.take(8).readBigUInt64LE(0); } + wide(): bigint { return this.long() + (this.long() << 64n); } + key(): PublicKey { return new PublicKey(this.take(32)); } + text(max: number, allowEmpty = false): string { + const length = this.int(); + if ((!allowEmpty && length === 0) || length > max) throw new Error("Invalid account text length"); + const value = new TextDecoder("utf-8", { fatal: true }).decode(this.take(length)); + if ((!allowEmpty && value.trim().length === 0) || /\p{Cc}/u.test(value)) throw new Error("Invalid account text"); + return value; + } + finish(): void { if (this.take(this.data.length - this.offset).some((x) => x !== 0)) throw new Error("Unexpected account data suffix"); } +} diff --git a/app/packages/solana-sdk/src/index.ts b/app/packages/solana-sdk/src/index.ts new file mode 100644 index 00000000..cb2d3c4b --- /dev/null +++ b/app/packages/solana-sdk/src/index.ts @@ -0,0 +1,5 @@ +export * from "./math.ts"; +export * from "./accounts.ts"; +export * from "./instructions.ts"; +export * from "./transactions.ts"; +export * from "./verification.ts"; diff --git a/app/packages/solana-sdk/src/instructions.ts b/app/packages/solana-sdk/src/instructions.ts new file mode 100644 index 00000000..9b065e8a --- /dev/null +++ b/app/packages/solana-sdk/src/instructions.ts @@ -0,0 +1,54 @@ +import { Buffer } from "buffer"; +import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, TransactionInstruction, type AccountMeta } from "@solana/web3.js"; +import { ASSOCIATED_TOKEN_PROGRAM_ID, deriveAssociatedTokenAddress, derivePoolAddresses, programDataAddress, TOKEN_PROGRAM_ID, validateRecipients, type PoolAccount } from "./accounts.ts"; +import { discriminator, textField, u8, u16, u32, u64 } from "./encoding.ts"; +import { bounded, MAX_VIRTUAL_SOL, MIN_VIRTUAL_SOL, U64_MAX } from "./math.ts"; + +const meta = (pubkey: PublicKey, isWritable = false, isSigner = false): AccountMeta => ({ pubkey, isWritable, isSigner }); +const instruction = (programId: PublicKey, name: string, keys: AccountMeta[], args: Buffer[] = []) => + new TransactionInstruction({ programId, keys, data: Buffer.concat([discriminator("global", name), ...args]) }); +export type PrepareLaunchArgs = { + creator: PublicKey; payer?: PublicKey; nonce: bigint; virtualSol: bigint; feeBps: number; + name: string; symbol: string; uri: string; contentHash: Uint8Array; + recipients: ReadonlyArray<{ address: PublicKey; weightBps: number }>; +}; +export function buildPrepareLaunchInstruction(programId: PublicKey, args: PrepareLaunchArgs): TransactionInstruction { + const addresses = derivePoolAddresses(programId, args.creator, args.nonce); + validateRecipients(args.recipients, args.feeBps, addresses, programId); + bounded(args.virtualSol, MAX_VIRTUAL_SOL, "Virtual SOL"); + if (args.virtualSol < MIN_VIRTUAL_SOL || args.contentHash.length !== 32) throw new Error("Invalid valuation or content hash"); + return instruction(programId, "prepare_launch", [meta(args.creator, false, true), meta(args.payer ?? args.creator, true, true), meta(addresses.pool, true), meta(SystemProgram.programId)], [ + u64(args.nonce), u64(args.virtualSol), u16(args.feeBps), textField(args.name, 32, "name"), textField(args.symbol, 10, "symbol"), textField(args.uri, 200, "URI", true), Buffer.from(args.contentHash), + u32(args.recipients.length), ...args.recipients.flatMap((r) => [r.address.toBuffer(), u16(r.weightBps)]), + ]); +} +export function buildActivateLaunchInstruction(programId: PublicKey, creator: PublicKey, nonce: bigint): TransactionInstruction { + const a = derivePoolAddresses(programId, creator, nonce); + return instruction(programId, "activate_launch", [meta(creator, true, true), meta(a.pool, true), meta(a.mint, true), meta(a.tokenVault, true), meta(a.reserve, true), meta(a.fees, true), meta(a.mintAuthority), meta(programId), meta(programDataAddress(programId)), meta(TOKEN_PROGRAM_ID), meta(SystemProgram.programId), meta(SYSVAR_RENT_PUBKEY)]); +} +export function buildCancelPreparationInstruction(programId: PublicKey, creator: PublicKey, nonce: bigint): TransactionInstruction { + return instruction(programId, "cancel_preparation", [meta(creator, false, true), meta(derivePoolAddresses(programId, creator, nonce).pool, true)]); +} +export type TradeInstructionArgs = { + creator: PublicKey; nonce: bigint; trader: PublicKey; traderToken?: PublicKey; + amountIn: bigint; minimumOut: bigint; expirySlot: bigint; +}; +function tradeInstruction(programId: PublicKey, name: string, args: TradeInstructionArgs): TransactionInstruction { + const a = derivePoolAddresses(programId, args.creator, args.nonce); + bounded(args.amountIn, U64_MAX, "Amount in"); bounded(args.minimumOut, U64_MAX, "Minimum out"); + if (args.amountIn === 0n || args.minimumOut === 0n) throw new Error("Input and slippage minimum must be positive"); + const traderToken = args.traderToken ?? deriveAssociatedTokenAddress(a.mint, args.trader); + return instruction(programId, name, [meta(args.trader, true, true), meta(a.pool, true), meta(a.mint), meta(a.tokenVault, true), meta(a.reserve, true), meta(a.fees, true), meta(traderToken, true), meta(TOKEN_PROGRAM_ID), meta(SystemProgram.programId)], [u64(args.amountIn), u64(args.minimumOut), u64(args.expirySlot)]); +} +export const buildBuyInstruction = (programId: PublicKey, args: TradeInstructionArgs): TransactionInstruction => tradeInstruction(programId, "buy_exact_in", args); +export const buildSellInstruction = (programId: PublicKey, args: TradeInstructionArgs): TransactionInstruction => tradeInstruction(programId, "sell_exact_in", args); +export function buildClaimFeesInstruction(programId: PublicKey, pool: Pick, recipientIndex: number): TransactionInstruction { + if (!Number.isInteger(recipientIndex) || recipientIndex < 0 || recipientIndex >= pool.recipients.length) throw new Error("Invalid recipient index"); + const a = derivePoolAddresses(programId, pool.creator, pool.nonce); + return instruction(programId, "claim_fees", [meta(a.pool, true), meta(a.fees, true), meta(pool.recipients[recipientIndex].address, true)], [u8(recipientIndex)]); +} +/** Idempotent official ATA creation. Rent/fees are extra; this never wraps SOL. */ +export function buildCreateAssociatedTokenInstruction(payer: PublicKey, mint: PublicKey, owner: PublicKey): TransactionInstruction { + return new TransactionInstruction({ programId: ASSOCIATED_TOKEN_PROGRAM_ID, + keys: [meta(payer, true, true), meta(deriveAssociatedTokenAddress(mint, owner), true), meta(owner), meta(mint), meta(SystemProgram.programId), meta(TOKEN_PROGRAM_ID)], data: Buffer.from([1]) }); +} diff --git a/app/packages/solana-sdk/src/math.ts b/app/packages/solana-sdk/src/math.ts new file mode 100644 index 00000000..20d2c34f --- /dev/null +++ b/app/packages/solana-sdk/src/math.ts @@ -0,0 +1,108 @@ +/** Exact atom/lamport arithmetic. Virtual SOL is never available liquidity. */ +export const FIXED_SUPPLY = 1_000_000_000_000_000n; +export const TOKEN_DECIMALS = 6; +export const BPS = 10_000n; +export const U64_MAX = (1n << 64n) - 1n; +export const U128_MAX = (1n << 128n) - 1n; +export const MIN_VIRTUAL_SOL = 1_000_000_000n; +export const MAX_VIRTUAL_SOL = 1_000_000_000_000_000n; +export const FEE_BPS = [0, 100, 300] as const; + +export type CurveState = { + tokenInventory: bigint; + realSolReserves: bigint; + virtualSol: bigint; + feeBps: number; +}; +export type TradeQuote = { + grossInput: bigint; + netInput: bigint; + fee: bigint; + amountOut: bigint; + nextTokenInventory: bigint; + nextRealSolReserves: bigint; +}; + +export function bounded(value: bigint, max: bigint, name: string): bigint { + if (typeof value !== "bigint" || value < 0n || value > max) throw new RangeError(`${name} is out of bounds`); + return value; +} +export function validateCurve(state: CurveState): void { + bounded(state.tokenInventory, FIXED_SUPPLY, "Token inventory"); + bounded(state.realSolReserves, U64_MAX, "Real SOL reserves"); + bounded(state.virtualSol, MAX_VIRTUAL_SOL, "Virtual SOL"); + if (state.tokenInventory === 0n || state.virtualSol < MIN_VIRTUAL_SOL) throw new RangeError("Invalid inventory or virtual SOL"); + if (!FEE_BPS.some((fee) => fee === state.feeBps)) throw new RangeError("Fee must be 0, 100, or 300 basis points"); + bounded(state.realSolReserves + state.virtualSol, U64_MAX, "Effective reserves"); + if (state.tokenInventory * (state.realSolReserves + state.virtualSol) < FIXED_SUPPLY * state.virtualSol) { + throw new RangeError("Pool invariant violated"); + } +} +export function tradingFee(amount: bigint, feeBps: number): bigint { + bounded(amount, U64_MAX, "Fee basis amount"); + if (!FEE_BPS.some((fee) => fee === feeBps)) throw new RangeError("Unsupported fee"); + const product = amount * BigInt(feeBps); + return product / BPS + (product % BPS === 0n ? 0n : 1n); +} +export function quoteBuy(state: CurveState, grossLamports: bigint, minOutput = 1n): TradeQuote { + validateCurve(state); + bounded(grossLamports, U64_MAX, "Buy input"); + bounded(minOutput, U64_MAX, "Minimum output"); + const fee = tradingFee(grossLamports, state.feeBps); + const netInput = grossLamports - fee; + if (netInput <= 0n) throw new RangeError("Buy input is too small after fees"); + const denominator = bounded(state.realSolReserves + state.virtualSol + netInput, U64_MAX, "Effective reserves after buy"); + const amountOut = state.tokenInventory * netInput / denominator; + if (amountOut <= 0n || amountOut >= state.tokenInventory || amountOut < minOutput) throw new RangeError("Buy output is zero or below minimum"); + return { grossInput: grossLamports, netInput, fee, amountOut, + nextTokenInventory: state.tokenInventory - amountOut, nextRealSolReserves: state.realSolReserves + netInput }; +} +export function quoteSell(state: CurveState, tokenAtoms: bigint, minOutput = 1n): TradeQuote { + validateCurve(state); + bounded(tokenAtoms, U64_MAX, "Sell input"); + bounded(minOutput, U64_MAX, "Minimum output"); + if (tokenAtoms === 0n || tokenAtoms > FIXED_SUPPLY - state.tokenInventory) throw new RangeError("Sell exceeds circulating inventory"); + const grossOutput = (state.realSolReserves + state.virtualSol) * tokenAtoms / (state.tokenInventory + tokenAtoms); + if (grossOutput > state.realSolReserves) throw new RangeError("Sell would spend virtual SOL"); + const fee = tradingFee(grossOutput, state.feeBps); + const amountOut = grossOutput - fee; + if (amountOut <= 0n || amountOut < minOutput) throw new RangeError("Sell output is zero or below minimum"); + return { grossInput: tokenAtoms, netInput: tokenAtoms, fee, amountOut, + nextTokenInventory: state.tokenInventory + tokenAtoms, nextRealSolReserves: state.realSolReserves - grossOutput }; +} +/** Same checked-u128 quotient/remainder formula as the program, avoiding F * weight overflow. */ +export function feeEntitlement(cumulativeFees: bigint, weightBps: number): bigint { + bounded(cumulativeFees, U128_MAX, "Cumulative fees"); + if (!Number.isInteger(weightBps) || weightBps < 1 || weightBps > 10_000) throw new RangeError("Invalid beneficiary weight"); + const weight = BigInt(weightBps); + return cumulativeFees / BPS * weight + cumulativeFees % BPS * weight / BPS; +} +export function claimableFees(cumulativeFees: bigint, weightBps: number, paid: bigint): bigint { + bounded(paid, U128_MAX, "Paid fees"); + const entitlement = feeEntitlement(cumulativeFees, weightBps); + if (paid > entitlement) throw new RangeError("Paid fees exceed entitlement"); + return entitlement - paid; +} +export function minimumAfterSlippage(amountOut: bigint, slippageBps: number): bigint { + bounded(amountOut, U64_MAX, "Quoted output"); + if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps >= 10_000) throw new RangeError("Slippage must be below 100%"); + const result = amountOut * (BPS - BigInt(slippageBps)) / BPS; + if (result <= 0n) throw new RangeError("Slippage minimum would be zero"); + return result; +} +/** Parse without float rounding or scientific notation. */ +export function parseAmount(value: string, decimals: number): bigint { + if (!Number.isInteger(decimals) || decimals < 0 || decimals > 18) throw new RangeError("Invalid decimal precision"); + if (!/^(0|[1-9][0-9]*)(\.[0-9]+)?$/.test(value)) throw new RangeError("Use an unsigned decimal amount"); + const [whole, fraction = ""] = value.split("."); + if (fraction.length > decimals) throw new RangeError("Too many decimal places"); + return bounded(BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction.padEnd(decimals, "0") || "0"), U64_MAX, "Amount"); +} +export function formatAmount(value: bigint, decimals: number): string { + bounded(value, U128_MAX, "Amount"); + if (!Number.isInteger(decimals) || decimals < 0 || decimals > 18) throw new RangeError("Invalid decimal precision"); + const text = value.toString().padStart(decimals + 1, "0"); + if (decimals === 0) return text; + const fraction = text.slice(-decimals).replace(/0+$/, ""); + return `${text.slice(0, -decimals)}${fraction ? `.${fraction}` : ""}`; +} diff --git a/app/packages/solana-sdk/src/transactions.ts b/app/packages/solana-sdk/src/transactions.ts new file mode 100644 index 00000000..4ed66149 --- /dev/null +++ b/app/packages/solana-sdk/src/transactions.ts @@ -0,0 +1,71 @@ +import { Buffer } from "buffer"; +import { PublicKey, Transaction, VersionedTransaction, type Connection, type TransactionInstruction } from "@solana/web3.js"; + +export const MAX_TRANSACTION_BYTES = 1232; +export type PreparedTransaction = { + transaction: Transaction; wireBytes: Uint8Array; messageBytes: Uint8Array; + blockhash: string; lastValidBlockHeight: number; +}; +export async function prepareTransaction(connection: Connection, payer: PublicKey, instructions: TransactionInstruction[]): Promise { + if (instructions.length === 0) throw new Error("Transaction has no instructions"); + const latest = await connection.getLatestBlockhash("confirmed"); + const transaction = new Transaction({ feePayer: payer, recentBlockhash: latest.blockhash }).add(...instructions); + const wireBytes = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }); + if (wireBytes.length > MAX_TRANSACTION_BYTES) throw new Error("Transaction exceeds Solana packet budget"); + return { transaction, wireBytes, messageBytes: transaction.serializeMessage(), ...latest }; +} +export async function simulatePreparedTransaction(connection: Connection, prepared: PreparedTransaction): Promise<{ unitsConsumed: number | undefined; logs: string[] }> { + const transaction = VersionedTransaction.deserialize(prepared.wireBytes); + const result = await connection.simulateTransaction(transaction, { sigVerify: false, replaceRecentBlockhash: false, commitment: "confirmed" }); + if (result.value.err) throw new Error(`Simulation failed: ${JSON.stringify(result.value.err)}`); + return { unitsConsumed: result.value.unitsConsumed, logs: result.value.logs ?? [] }; +} + +const BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +/** Local encoding avoids trusting a returned RPC signature after ambiguous submission. */ +export function encodeBase58(bytes: Uint8Array): string { + let n = 0n; + for (const b of bytes) n = n * 256n + BigInt(b); + let result = ""; + while (n > 0n) { result = BASE58[Number(n % 58n)] + result; n /= 58n; } + for (const b of bytes) { if (b !== 0) break; result = "1" + result; } + return result; +} +export type ValidatedSignedTransaction = { bytes: Uint8Array; signature: string }; +/** Reject changed recipients, amounts, blockhashes, added instructions and missing/invalid signatures. */ +export function validateSignedTransaction(expectedMessage: Uint8Array, signedBytes: Uint8Array, payer: PublicKey): ValidatedSignedTransaction { + if (signedBytes.length > MAX_TRANSACTION_BYTES || signedBytes.length < 1) throw new Error("Invalid signed transaction size"); + const transaction = Transaction.from(signedBytes); + if (!transaction.feePayer?.equals(payer) || !transaction.serializeMessage().equals(Buffer.from(expectedMessage))) throw new Error("Wallet changed the transaction message"); + if (!transaction.verifySignatures(true) || !transaction.signature) throw new Error("Wallet returned missing or invalid signatures"); + const bytes = transaction.serialize({ requireAllSignatures: true, verifySignatures: true }); + return { bytes, signature: encodeBase58(transaction.signature) }; +} +export type SubmissionResult = { signature: string; status: "submitted" | "unknown"; error?: string }; +/** Persist the local signature + expiry BEFORE calling. A transport error is not a failed transaction. */ +export async function submitSignedTransaction(connection: Connection, signed: ValidatedSignedTransaction): Promise { + try { + const returned = await connection.sendRawTransaction(signed.bytes, { skipPreflight: false, preflightCommitment: "confirmed", maxRetries: 0 }); + if (returned !== signed.signature) return { signature: signed.signature, status: "unknown", error: "RPC returned an unexpected signature; reconcile the locally signed transaction" }; + return { signature: signed.signature, status: "submitted" }; + } catch { + return { signature: signed.signature, status: "unknown", error: "Submission result unknown. Check this signature before submitting a replacement." }; + } +} +export type Reconciliation = { status: "confirmed" | "finalized" | "failed" | "pending" | "expired"; error?: unknown }; +export async function reconcileTransaction(connection: Connection, signature: string, lastValidBlockHeight: number): Promise { + if (!Number.isSafeInteger(lastValidBlockHeight) || lastValidBlockHeight <= 0) throw new Error("Invalid transaction expiry"); + // Check finalized block height before historical status: if expired, no new landing can race this read. + const height = await connection.getBlockHeight("finalized"); + const result = await connection.getSignatureStatuses([signature], { searchTransactionHistory: true }); + const status = result.value[0]; + if (status) { + // A processed/confirmed error may exist only on a fork. Its signed message + // can still land successfully elsewhere until finalized or expired. + if (status.err) return { status: status.confirmationStatus === "finalized" ? "failed" : "pending", error: status.err }; + if (status.confirmationStatus === "finalized") return { status: "finalized" }; + if (status.confirmationStatus === "confirmed") return { status: "confirmed" }; + return { status: "pending" }; + } + return { status: height > lastValidBlockHeight ? "expired" : "pending" }; +} diff --git a/app/packages/solana-sdk/src/verification.ts b/app/packages/solana-sdk/src/verification.ts new file mode 100644 index 00000000..3c476909 --- /dev/null +++ b/app/packages/solana-sdk/src/verification.ts @@ -0,0 +1,31 @@ +import { Buffer } from "buffer"; +import { sha256 } from "@noble/hashes/sha256"; +import { PublicKey } from "@solana/web3.js"; +import { programDataAddress } from "./accounts.ts"; + +export type DeploymentManifest = { + schema: 1; cluster: "mainnet-beta" | "devnet"; programId: string; programData: string; + genesisHash: string; sourceCommit: string; binarySha256: string; + verifiedSlot: number | null; securityReview: string | null; economicReview: string | null; releaseApproved: boolean; +}; +export function validateDeploymentManifest(input: unknown): DeploymentManifest { + if (!input || typeof input !== "object") throw new Error("Invalid manifest"); + const m = input as Record; + if (m.schema !== 1 || !["mainnet-beta", "devnet"].includes(String(m.cluster)) || typeof m.programId !== "string" || typeof m.programData !== "string" || typeof m.genesisHash !== "string" || typeof m.sourceCommit !== "string" || !/^[0-9a-f]{40}$/.test(m.sourceCommit) || typeof m.binarySha256 !== "string" || !/^[0-9a-f]{64}$/.test(m.binarySha256)) throw new Error("Manifest needs exact program, cluster, genesis, source commit and binary hash"); + const program = new PublicKey(m.programId); + if (program.equals(PublicKey.default) || program.toBase58() === "Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr") throw new Error("Test fixture is not a deployment identity"); + if (!programDataAddress(program).equals(new PublicKey(m.programData))) throw new Error("Manifest ProgramData mismatch"); + if (new PublicKey(m.genesisHash).toBase58() !== m.genesisHash) throw new Error("Invalid genesis hash"); + if (m.verifiedSlot !== null && (!Number.isSafeInteger(m.verifiedSlot) || Number(m.verifiedSlot) < 0)) throw new Error("Invalid verified slot"); + if (typeof m.releaseApproved !== "boolean" || (m.securityReview !== null && typeof m.securityReview !== "string") || (m.economicReview !== null && typeof m.economicReview !== "string")) throw new Error("Invalid review metadata"); + if (m.releaseApproved && (!m.securityReview || !m.economicReview || !m.verifiedSlot)) throw new Error("Release approval requires review evidence"); + return m as unknown as DeploymentManifest; +} +/** Hash the reviewed ELF, compare every deployed code byte, and reject nonzero trailing deployment padding. */ +export function verifyDeploymentBytes(programData: Uint8Array, reviewedElf: Uint8Array, expectedSha256: string): string { + if (reviewedElf.length < 64 || reviewedElf.length > 16 * 1024 * 1024 || !Buffer.from(reviewedElf.subarray(0, 4)).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46]))) throw new Error("Expected a bounded ELF binary"); + const hash = Buffer.from(sha256(reviewedElf)).toString("hex"); + if (hash !== expectedSha256) throw new Error("Reviewed binary SHA-256 does not match manifest"); + if (programData.length < 45 + reviewedElf.length || !Buffer.from(programData.subarray(45, 45 + reviewedElf.length)).equals(Buffer.from(reviewedElf)) || programData.subarray(45 + reviewedElf.length).some((byte) => byte !== 0)) throw new Error("Deployed program bytes differ from reviewed binary"); + return hash; +} diff --git a/app/packages/solana-sdk/tests/abi-fixture.json b/app/packages/solana-sdk/tests/abi-fixture.json new file mode 100644 index 00000000..6cc216e6 --- /dev/null +++ b/app/packages/solana-sdk/tests/abi-fixture.json @@ -0,0 +1,243 @@ +{ + "note": "Undeployed fixture identity only; no private key or network activity.", + "programId": "Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr", + "creator": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "nonce": "7", + "addresses": { + "pool": "3oEc6AMYxzeawVCkWca76kfKqjb12jqRMqJppMM7gZUg", + "bump": 254, + "mint": "2ShZMirdm8opBcM2rZJAwfUgZEEuTvcNWY2DKJD1RdBZ", + "tokenVault": "79Dm9ZhvNeWyzDdmo7yyEk2k8z8HVSvBwk5u9bmzurVR", + "reserve": "6jPwiggjt4ySPQ1eDPU3GMAcoaJ81mE83841FKAaLkF2", + "fees": "EuZ2r6wHKFM4Lnu2DwWPqXz3C8UszMBDST9aVobJU3Rv", + "mintAuthority": "DL5R1ERrV3TF91R19xbZU4kx7RczY1rZFKnnYB1TbHpV" + }, + "preparedPoolHex": "f19a6d0411b16dbc0100fe070000000000000001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101157110ebda47769ceef46063e8554cd51ffe3222eed5ba2017295c4984c033f400ac23fc06000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000004578616d706c65020000004558000000000000000000000000000000000000000000000000000000000000000000000000", + "instructions": { + "prepareLaunch": { + "dataHex": "ce212cf6b5b08ef8070000000000000000ac23fc060000000000070000004578616d706c6502000000455800000000000000000000000000000000000000000000000000000000000000000000000000000000", + "keys": [ + { + "address": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "signer": true, + "writable": false + }, + { + "address": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "signer": true, + "writable": true + }, + { + "address": "3oEc6AMYxzeawVCkWca76kfKqjb12jqRMqJppMM7gZUg", + "signer": false, + "writable": true + }, + { + "address": "11111111111111111111111111111111", + "signer": false, + "writable": false + } + ] + }, + "activateLaunch": { + "dataHex": "90449eb63c849ed7", + "keys": [ + { + "address": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "signer": true, + "writable": true + }, + { + "address": "3oEc6AMYxzeawVCkWca76kfKqjb12jqRMqJppMM7gZUg", + "signer": false, + "writable": true + }, + { + "address": "2ShZMirdm8opBcM2rZJAwfUgZEEuTvcNWY2DKJD1RdBZ", + "signer": false, + "writable": true + }, + { + "address": "79Dm9ZhvNeWyzDdmo7yyEk2k8z8HVSvBwk5u9bmzurVR", + "signer": false, + "writable": true + }, + { + "address": "6jPwiggjt4ySPQ1eDPU3GMAcoaJ81mE83841FKAaLkF2", + "signer": false, + "writable": true + }, + { + "address": "EuZ2r6wHKFM4Lnu2DwWPqXz3C8UszMBDST9aVobJU3Rv", + "signer": false, + "writable": true + }, + { + "address": "DL5R1ERrV3TF91R19xbZU4kx7RczY1rZFKnnYB1TbHpV", + "signer": false, + "writable": false + }, + { + "address": "Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr", + "signer": false, + "writable": false + }, + { + "address": "4ZU2fH7AswhrTMzewvPQsvqxGDDW6ptM9bB1bUDVw5CS", + "signer": false, + "writable": false + }, + { + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "signer": false, + "writable": false + }, + { + "address": "11111111111111111111111111111111", + "signer": false, + "writable": false + }, + { + "address": "SysvarRent111111111111111111111111111111111", + "signer": false, + "writable": false + } + ] + }, + "cancelPreparation": { + "dataHex": "ed04630f79b6aca7", + "keys": [ + { + "address": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "signer": true, + "writable": false + }, + { + "address": "3oEc6AMYxzeawVCkWca76kfKqjb12jqRMqJppMM7gZUg", + "signer": false, + "writable": true + } + ] + }, + "buyExactIn": { + "dataHex": "faea0d7bd59c13ec00ca9a3b000000000a00000000000000e803000000000000", + "keys": [ + { + "address": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "signer": true, + "writable": true + }, + { + "address": "3oEc6AMYxzeawVCkWca76kfKqjb12jqRMqJppMM7gZUg", + "signer": false, + "writable": true + }, + { + "address": "2ShZMirdm8opBcM2rZJAwfUgZEEuTvcNWY2DKJD1RdBZ", + "signer": false, + "writable": false + }, + { + "address": "79Dm9ZhvNeWyzDdmo7yyEk2k8z8HVSvBwk5u9bmzurVR", + "signer": false, + "writable": true + }, + { + "address": "6jPwiggjt4ySPQ1eDPU3GMAcoaJ81mE83841FKAaLkF2", + "signer": false, + "writable": true + }, + { + "address": "EuZ2r6wHKFM4Lnu2DwWPqXz3C8UszMBDST9aVobJU3Rv", + "signer": false, + "writable": true + }, + { + "address": "3iRym2dakRoLA4BSpkAM6jUhgziKHAwM6RPJ3sc1s7YB", + "signer": false, + "writable": true + }, + { + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "signer": false, + "writable": false + }, + { + "address": "11111111111111111111111111111111", + "signer": false, + "writable": false + } + ] + }, + "sellExactIn": { + "dataHex": "9527de9bd37c981a00ca9a3b000000000a00000000000000e803000000000000", + "keys": [ + { + "address": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "signer": true, + "writable": true + }, + { + "address": "3oEc6AMYxzeawVCkWca76kfKqjb12jqRMqJppMM7gZUg", + "signer": false, + "writable": true + }, + { + "address": "2ShZMirdm8opBcM2rZJAwfUgZEEuTvcNWY2DKJD1RdBZ", + "signer": false, + "writable": false + }, + { + "address": "79Dm9ZhvNeWyzDdmo7yyEk2k8z8HVSvBwk5u9bmzurVR", + "signer": false, + "writable": true + }, + { + "address": "6jPwiggjt4ySPQ1eDPU3GMAcoaJ81mE83841FKAaLkF2", + "signer": false, + "writable": true + }, + { + "address": "EuZ2r6wHKFM4Lnu2DwWPqXz3C8UszMBDST9aVobJU3Rv", + "signer": false, + "writable": true + }, + { + "address": "3iRym2dakRoLA4BSpkAM6jUhgziKHAwM6RPJ3sc1s7YB", + "signer": false, + "writable": true + }, + { + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "signer": false, + "writable": false + }, + { + "address": "11111111111111111111111111111111", + "signer": false, + "writable": false + } + ] + }, + "claimFees": { + "dataHex": "52fbe99c0c34b8ca00", + "keys": [ + { + "address": "3oEc6AMYxzeawVCkWca76kfKqjb12jqRMqJppMM7gZUg", + "signer": false, + "writable": true + }, + { + "address": "EuZ2r6wHKFM4Lnu2DwWPqXz3C8UszMBDST9aVobJU3Rv", + "signer": false, + "writable": true + }, + { + "address": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "signer": false, + "writable": true + } + ] + } + } +} diff --git a/app/packages/solana-sdk/tests/accounts.test.ts b/app/packages/solana-sdk/tests/accounts.test.ts new file mode 100644 index 00000000..04226846 --- /dev/null +++ b/app/packages/solana-sdk/tests/accounts.test.ts @@ -0,0 +1,58 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { Buffer } from "buffer"; +import { PublicKey, type AccountInfo, type Connection } from "@solana/web3.js"; +import { decodeImmutableProgram, decodePoolAccount, derivePoolAddresses, fetchPoolSnapshot, POOL_ACCOUNT_SPACE, programDataAddress, TOKEN_PROGRAM_ID, UPGRADEABLE_LOADER_ID } from "../src/accounts.ts"; +import { discriminator, textField, u8, u16, u64 } from "../src/encoding.ts"; +import { FIXED_SUPPLY } from "../src/math.ts"; + +const programId = new PublicKey("Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr"); +const creator = new PublicKey(new Uint8Array(32).fill(1)); +const nonce = 7n; +const a = derivePoolAddresses(programId, creator, nonce); +function account(data: Buffer, owner = programId, lamports = 10000000): AccountInfo { return { data, owner, lamports, executable: false, rentEpoch: 0 }; } +function poolBytes(active = false): Buffer { + return Buffer.concat([discriminator("account", "Pool"), u8(1), u8(active ? 1 : 0), u8(a.bump), u64(nonce), creator.toBuffer(), creator.toBuffer(), a.mint.toBuffer(), u64(30_000_000_000n), u16(0), u8(0), Buffer.alloc(7 * 50), u64(active ? FIXED_SUPPLY : 0n), u64(0n), Buffer.alloc(16), u64(0n), textField("Example", 32, "name"), textField("EX", 10, "symbol"), textField("", 200, "uri", true), Buffer.alloc(32)]); +} +test("Pool decoding preserves case-sensitive addresses, empty URI, exact reserves and PDA", () => { + const pool = decodePoolAccount(a.pool, account(poolBytes()), programId); + assert.equal(pool.status, "prepared"); assert.equal(pool.uri, ""); assert.equal(pool.nonce, 7n); + assert.equal(pool.creator.toBase58(), creator.toBase58()); + assert.equal(decodePoolAccount(a.pool, account(poolBytes(true)), programId).tokenInventory, FIXED_SUPPLY); + assert.throws(() => decodePoolAccount(creator, account(poolBytes()), programId), /Noncanonical/); + assert.throws(() => decodePoolAccount(a.pool, account(poolBytes(), TOKEN_PROGRAM_ID), programId), /owner/); + assert.throws(() => decodePoolAccount(a.pool, account(poolBytes().subarray(0, 40)), programId), /Truncated/); + assert.throws(() => decodePoolAccount(a.pool, account(Buffer.concat([poolBytes(), Buffer.from([1])])), programId), /suffix/); +}); +test("immutable check requires own linked ProgramData, expected loader and null authority", () => { + const dataAddress = programDataAddress(programId); + const exe = account(Buffer.concat([Buffer.from([2, 0, 0, 0]), dataAddress.toBuffer()]), UPGRADEABLE_LOADER_ID); exe.executable = true; + const data = account(Buffer.concat([Buffer.from([3, 0, 0, 0]), u64(123n), Buffer.alloc(33), Buffer.alloc(100)]), UPGRADEABLE_LOADER_ID); + assert.equal(decodeImmutableProgram(programId, exe, dataAddress, data).deploymentSlot, 123n); + assert.throws(() => decodeImmutableProgram(programId, exe, a.pool, data), /belong/); + data.data[12] = 1; assert.throws(() => decodeImmutableProgram(programId, exe, dataAddress, data), /upgrade authority/); + data.data[12] = 0; data.owner = creator; assert.throws(() => decodeImmutableProgram(programId, exe, dataAddress, data), /ProgramData/); +}); +function mintBytes(): Buffer { + const b = Buffer.alloc(82); b.writeBigUInt64LE(FIXED_SUPPLY, 36); b[44] = 6; b[45] = 1; return b; +} +function tokenBytes(): Buffer { + const b = Buffer.alloc(165); a.mint.toBuffer().copy(b, 0); a.pool.toBuffer().copy(b, 32); b.writeBigUInt64LE(FIXED_SUPPLY, 64); b[108] = 1; return b; +} +function vaultBytes(kind: number): Buffer { return Buffer.concat([discriminator("account", "SolVault"), a.pool.toBuffer(), u8(kind), u64(1000n)]); } +test("snapshot accepts donated surplus, rejects invalid custody and reads one context", async () => { + const fullPool = Buffer.alloc(POOL_ACCOUNT_SPACE); poolBytes(true).copy(fullPool); + const values = [account(fullPool), account(mintBytes(), TOKEN_PROGRAM_ID), account(tokenBytes(), TOKEN_PROGRAM_ID), account(vaultBytes(0)), account(vaultBytes(1))]; + const fake = { + getAccountInfo: async () => values[0], + getMultipleAccountsInfoAndContext: async () => ({ context: { slot: 5 }, value: values }), + } as unknown as Connection; + assert.equal((await fetchPoolSnapshot(fake, programId, a.pool)).slot, 5); + values[2].data.writeUInt32LE(1, 72); + await assert.rejects(fetchPoolSnapshot(fake, programId, a.pool), /custody/); + values[2].data.writeUInt32LE(0, 72); + values[3].lamports = 1; + await assert.rejects(fetchPoolSnapshot(fake, programId, a.pool), /underfunded/); + values[3].lamports = Number.MAX_SAFE_INTEGER + 1; + await assert.rejects(fetchPoolSnapshot(fake, programId, a.pool), /precision/); +}); diff --git a/app/packages/solana-sdk/tests/instructions.test.ts b/app/packages/solana-sdk/tests/instructions.test.ts new file mode 100644 index 00000000..d8f817a2 --- /dev/null +++ b/app/packages/solana-sdk/tests/instructions.test.ts @@ -0,0 +1,51 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { Keypair, PublicKey, SystemProgram, Transaction } from "@solana/web3.js"; +import { buildActivateLaunchInstruction, buildBuyInstruction, buildCancelPreparationInstruction, buildClaimFeesInstruction, buildCreateAssociatedTokenInstruction, buildPrepareLaunchInstruction, buildSellInstruction } from "../src/instructions.ts"; +import { derivePoolAddresses, programDataAddress, TOKEN_PROGRAM_ID } from "../src/accounts.ts"; +import { discriminator, Reader } from "../src/encoding.ts"; +import { MAX_TRANSACTION_BYTES } from "../src/transactions.ts"; + +const programId = new PublicKey("Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr"); +const creator = Keypair.fromSeed(new Uint8Array(32).fill(1)).publicKey; +const base = { creator, nonce: 7n, virtualSol: 30_000_000_000n, feeBps: 0, recipients: [], name: "Example", symbol: "EX", uri: "", contentHash: new Uint8Array(32) }; +function size(...ix: ReturnType[]) { return new Transaction({ feePayer: creator, recentBlockhash: PublicKey.default.toBase58() }).add(...ix).serialize({ requireAllSignatures: false, verifySignatures: false }).length; } +test("six instruction discriminators, exact args/account order and signer flags", () => { + const prepare = buildPrepareLaunchInstruction(programId, base); + assert.ok(prepare.data.subarray(0, 8).equals(discriminator("global", "prepare_launch"))); + const reader = new Reader(prepare.data.subarray(8)); + assert.equal(reader.long(), 7n); assert.equal(reader.long(), 30_000_000_000n); assert.equal(reader.short(), 0); + assert.equal(reader.text(32), "Example"); assert.equal(reader.text(10), "EX"); assert.equal(reader.text(200, true), ""); + assert.ok(prepare.keys[0].isSigner); assert.ok(prepare.keys[1].isSigner); assert.equal(prepare.keys[0].isWritable, false); + const a = derivePoolAddresses(programId, creator, 7n); + assert.equal(prepare.keys[2].pubkey.toBase58(), a.pool.toBase58()); + const activate = buildActivateLaunchInstruction(programId, creator, 7n); + assert.equal(activate.keys[8].pubkey.toBase58(), programDataAddress(programId).toBase58()); + assert.equal(activate.keys[9].pubkey.toBase58(), TOKEN_PROGRAM_ID.toBase58()); + assert.equal(buildCancelPreparationInstruction(programId, creator, 7n).keys.length, 2); + for (const build of [buildBuyInstruction, buildSellInstruction]) { + const ix = build(programId, { creator, nonce: 7n, trader: creator, amountIn: 10000n, minimumOut: 1n, expirySlot: 900n }); + const args = new Reader(ix.data.subarray(8)); assert.equal(args.long(), 10000n); assert.equal(args.long(), 1n); assert.equal(args.long(), 900n); + assert.equal(ix.keys.length, 9); assert.ok(ix.keys[0].isSigner); assert.ok(ix.keys[8].pubkey.equals(SystemProgram.programId)); + } + const claim = buildClaimFeesInstruction(programId, { creator, nonce: 7n, recipients: [{ address: creator, weightBps: 10000, paid: 0n }] }, 0); + assert.equal(claim.keys.length, 3); assert.ok(claim.keys.every((key) => !key.isSigner)); assert.equal(claim.data[8], 0); +}); +test("maximum preparation, activation and swap+ATA packets fit 1232 bytes independently", (t) => { + const recipients = Array.from({ length: 7 }, (_, i) => ({ address: new PublicKey(new Uint8Array(32).fill(i + 2)), weightBps: i === 6 ? 1432 : 1428 })); + const prepare = buildPrepareLaunchInstruction(programId, { ...base, feeBps: 100, name: "N".repeat(32), symbol: "S".repeat(10), uri: "https://example.com/" + "x".repeat(180), recipients }); + const activate = buildActivateLaunchInstruction(programId, creator, 7n); + const a = derivePoolAddresses(programId, creator, 7n); + const buy = buildBuyInstruction(programId, { creator, nonce: 7n, trader: creator, amountIn: 10000n, minimumOut: 1n, expirySlot: 900n }); + const sizes = { prepare: size(prepare), activate: size(activate), buyWithAta: size(buildCreateAssociatedTokenInstruction(creator, a.mint, creator), buy) }; + for (const value of Object.values(sizes)) assert.ok(value <= MAX_TRANSACTION_BYTES); + t.diagnostic(JSON.stringify(sizes)); +}); +test("invalid terms, aliases, UTF-8 bounds and zero slippage minimum reject locally", () => { + assert.throws(() => buildPrepareLaunchInstruction(programId, { ...base, name: "🪙".repeat(9) })); + assert.throws(() => buildPrepareLaunchInstruction(programId, { ...base, name: " " })); + assert.throws(() => buildPrepareLaunchInstruction(programId, { ...base, name: "a\u0085b" })); + assert.throws(() => buildPrepareLaunchInstruction(programId, { ...base, feeBps: 100, recipients: [{ address: programId, weightBps: 10000 }] })); + assert.throws(() => buildPrepareLaunchInstruction(programId, { ...base, feeBps: 100, recipients: [{ address: derivePoolAddresses(programId, creator, 7n).reserve, weightBps: 10000 }] })); + assert.throws(() => buildBuyInstruction(programId, { creator, nonce: 7n, trader: creator, amountIn: 10000n, minimumOut: 0n, expirySlot: 900n })); +}); diff --git a/app/packages/solana-sdk/tests/math.test.ts b/app/packages/solana-sdk/tests/math.test.ts new file mode 100644 index 00000000..70ff872a --- /dev/null +++ b/app/packages/solana-sdk/tests/math.test.ts @@ -0,0 +1,68 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; +import { claimableFees, feeEntitlement, FIXED_SUPPLY, formatAmount, MAX_VIRTUAL_SOL, MIN_VIRTUAL_SOL, minimumAfterSlippage, parseAmount, quoteBuy, quoteSell, U64_MAX, U128_MAX, validateCurve, type CurveState } from "../src/math.ts"; +// Independent oracle maintained outside SDK: binary searches the invariant rather than using quotient formulas. +// @ts-expect-error Deliberately dependency-free JavaScript reference implementation. +import { quoteBuyOracle, quoteSellOracle } from "../../../../packages/solana-model/oracle.mjs"; + +const initial = (feeBps = 100): CurveState => ({ tokenInventory: FIXED_SUPPLY, realSolReserves: 0n, virtualSol: 30_000_000_000n, feeBps }); +type Golden = { name: string; side: "buy" | "sell"; state: { tokenInventory: string; realSolReserves: string; virtualSol: string; feeBps: number }; input: string; minOutput: string; expected: Record }; +const vectors = JSON.parse(readFileSync(new URL("../../../../packages/solana-model/golden-vectors.json", import.meta.url), "utf8")) as Golden[]; +for (const vector of vectors) test(`independent golden: ${vector.name}`, () => { + const state = { ...vector.state, tokenInventory: BigInt(vector.state.tokenInventory), realSolReserves: BigInt(vector.state.realSolReserves), virtualSol: BigInt(vector.state.virtualSol) }; + const result = (vector.side === "buy" ? quoteBuy : quoteSell)(state, BigInt(vector.input), BigInt(vector.minOutput)); + for (const [key, value] of Object.entries(vector.expected)) assert.equal(result[key as keyof typeof result], BigInt(value), key); +}); + +test("12,000 differential trades match independent binary-search oracle", () => { + let seed = 29103; + const random = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed; }; + for (const feeBps of [0, 100, 300]) { + let state = initial(feeBps); + for (let i = 0; i < 4000; i++) { + const sell = random() % 2 === 0 && state.tokenInventory < FIXED_SUPPLY; + const circulating = FIXED_SUPPLY - state.tokenInventory; + const input = sell ? circulating * BigInt(random() % 1000 + 1) / 1001n : BigInt(random() % 1_000_000_000 + 10_000); + let quote; + try { quote = (sell ? quoteSell : quoteBuy)(state, input); } + catch { assert.throws(() => (sell ? quoteSellOracle : quoteBuyOracle)(state, input)); continue; } + const oracle = (sell ? quoteSellOracle : quoteBuyOracle)(state, input); + for (const key of ["amountOut", "fee", "nextTokenInventory", "nextRealSolReserves"] as const) assert.equal(quote[key], oracle[key]); + state = { ...state, tokenInventory: quote.nextTokenInventory, realSolReserves: quote.nextRealSolReserves }; + validateCurve(state); + } + } +}); +test("integer boundaries, slippage and underfunded state reject", () => { + assert.throws(() => quoteBuy(initial(), 0n)); + assert.throws(() => quoteBuy(initial(), 1n)); + assert.throws(() => quoteBuy(initial(0), U64_MAX)); + assert.doesNotThrow(() => quoteBuy({ ...initial(300), virtualSol: MIN_VIRTUAL_SOL }, U64_MAX)); + assert.throws(() => quoteBuy(initial(), U64_MAX + 1n)); + assert.throws(() => quoteBuy({ ...initial(), virtualSol: MAX_VIRTUAL_SOL + 1n }, 1000n)); + assert.throws(() => quoteBuy({ ...initial(), virtualSol: MIN_VIRTUAL_SOL - 1n }, 1000n)); + assert.throws(() => quoteBuy(initial(), 10000n, FIXED_SUPPLY)); + assert.throws(() => quoteSell(initial(), 1n)); + assert.throws(() => quoteSell({ ...initial(), tokenInventory: FIXED_SUPPLY / 2n }, 1n)); + assert.throws(() => quoteBuy({ ...initial(), feeBps: 99 }, 10000n)); +}); +test("lifetime fees use full u128 without overflow and cumulative claims leave { + for (const total of [1n, 17n, U64_MAX + 50000n, U128_MAX]) { + const amounts = [3333, 3333, 3334].map((w) => feeEntitlement(total, w)); + const dust = total - amounts.reduce((sum, amount) => sum + amount, 0n); + assert.ok(dust >= 0n && dust < 3n); + assert.equal(claimableFees(total, 3333, amounts[0]), 0n); + } + assert.throws(() => feeEntitlement(U128_MAX + 1n, 10000)); + assert.throws(() => claimableFees(1n, 10000, 2n)); +}); +test("user amount handling never loses decimals or accepts exponent notation", () => { + assert.equal(parseAmount("1.000000001", 9), 1_000_000_001n); + assert.equal(parseAmount("18446744073.709551615", 9), U64_MAX); + assert.equal(formatAmount(U64_MAX, 9), "18446744073.709551615"); + assert.equal(formatAmount(1n, 6), "0.000001"); + for (const value of ["1e9", "-1", " 1", "+1", "1.", ".5", "01", "0.0000000001", "NaN", "Infinity"]) assert.throws(() => parseAmount(value, 9), value); + assert.equal(minimumAfterSlippage(1000n, 50), 995n); + assert.throws(() => minimumAfterSlippage(1n, 50)); +}); diff --git a/app/packages/solana-sdk/tests/transactions.test.ts b/app/packages/solana-sdk/tests/transactions.test.ts new file mode 100644 index 00000000..34c5c6e7 --- /dev/null +++ b/app/packages/solana-sdk/tests/transactions.test.ts @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { Keypair, PublicKey, SystemProgram, Transaction, type Connection } from "@solana/web3.js"; +import { encodeBase58, prepareTransaction, reconcileTransaction, submitSignedTransaction, validateSignedTransaction } from "../src/transactions.ts"; + +const signer = Keypair.fromSeed(new Uint8Array(32).fill(1)); +const recipient = Keypair.fromSeed(new Uint8Array(32).fill(2)).publicKey; +function transaction() { return new Transaction({ feePayer: signer.publicKey, recentBlockhash: PublicKey.default.toBase58() }).add(SystemProgram.transfer({ fromPubkey: signer.publicKey, toPubkey: recipient, lamports: 1n })); } +test("signatures derived locally; changed message, partial signatures, invalid signatures refused", () => { + const tx = transaction(); const expected = tx.serializeMessage(); tx.sign(signer); + const validated = validateSignedTransaction(expected, tx.serialize(), signer.publicKey); + assert.equal(validated.signature, encodeBase58(tx.signature!)); + const changed = transaction().add(SystemProgram.transfer({ fromPubkey: signer.publicKey, toPubkey: recipient, lamports: 100n })); changed.sign(signer); + assert.throws(() => validateSignedTransaction(expected, changed.serialize(), signer.publicKey), /changed/); + assert.throws(() => validateSignedTransaction(expected, transaction().serialize({ requireAllSignatures: false, verifySignatures: false }), signer.publicKey), /signatures/); + const damaged = tx.serialize(); damaged[2] ^= 1; + assert.throws(() => validateSignedTransaction(expected, damaged, signer.publicKey)); + assert.equal(encodeBase58(new Uint8Array(32)), PublicKey.default.toBase58()); +}); +test("ambiguous submission retains local signature and never creates a replacement", async () => { + const tx = transaction(); const expected = tx.serializeMessage(); tx.sign(signer); + const signed = validateSignedTransaction(expected, tx.serialize(), signer.publicKey); + const fake = { sendRawTransaction: async () => { throw new Error("network timeout"); } } as unknown as Connection; + const result = await submitSignedTransaction(fake, signed); + assert.equal(result.signature, signed.signature); assert.equal(result.status, "unknown"); +}); +test("reconciliation needs finalized expiry or finalized failure before safe replacement", async () => { + let height = 99; + let value: unknown = null; + const fake = { getBlockHeight: async () => height, getSignatureStatuses: async () => ({ value: [value] }) } as unknown as Connection; + assert.equal((await reconcileTransaction(fake, "test", 100)).status, "pending"); + height = 101; assert.equal((await reconcileTransaction(fake, "test", 100)).status, "expired"); + value = { err: { InstructionError: [0, "Custom"] }, confirmationStatus: "processed" }; + assert.equal((await reconcileTransaction(fake, "test", 100)).status, "pending"); + value = { err: { InstructionError: [0, "Custom"] }, confirmationStatus: "confirmed" }; + assert.equal((await reconcileTransaction(fake, "test", 100)).status, "pending"); + value = { err: { InstructionError: [0, "Custom"] }, confirmationStatus: "finalized" }; + assert.equal((await reconcileTransaction(fake, "test", 100)).status, "failed"); + value = { err: null, confirmationStatus: "finalized" }; + assert.equal((await reconcileTransaction(fake, "test", 100)).status, "finalized"); +}); +test("preparation reports exact unsigned bytes and enforces packet size", async () => { + const fake = { getLatestBlockhash: async () => ({ blockhash: PublicKey.default.toBase58(), lastValidBlockHeight: 100 }) } as unknown as Connection; + const ready = await prepareTransaction(fake, signer.publicKey, transaction().instructions); + assert.equal(ready.lastValidBlockHeight, 100); + assert.deepEqual(ready.messageBytes, ready.transaction.serializeMessage()); + await assert.rejects(prepareTransaction(fake, signer.publicKey, []), /no instructions/); +}); diff --git a/app/packages/solana-sdk/tests/verification.test.ts b/app/packages/solana-sdk/tests/verification.test.ts new file mode 100644 index 00000000..ddef3eeb --- /dev/null +++ b/app/packages/solana-sdk/tests/verification.test.ts @@ -0,0 +1,27 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { Buffer } from "buffer"; +import { sha256 } from "@noble/hashes/sha256"; +import { PublicKey } from "@solana/web3.js"; +import { programDataAddress } from "../src/accounts.ts"; +import { validateDeploymentManifest, verifyDeploymentBytes } from "../src/verification.ts"; + +test("release identity must be explicit, not compile fixture or unreviewed defaults", () => { + const programId = new PublicKey(new Uint8Array(32).fill(7)); + const manifest = { schema: 1, cluster: "devnet", programId: programId.toBase58(), programData: programDataAddress(programId).toBase58(), genesisHash: new PublicKey(new Uint8Array(32).fill(8)).toBase58(), sourceCommit: "a".repeat(40), binarySha256: "b".repeat(64), verifiedSlot: null, securityReview: null, economicReview: null, releaseApproved: false }; + assert.equal(validateDeploymentManifest(manifest).releaseApproved, false); + assert.throws(() => validateDeploymentManifest({ ...manifest, releaseApproved: true }), /review evidence/); + assert.throws(() => validateDeploymentManifest({ ...manifest, programId: "Fg6PaFpoGXkYsidMpWxTWqkZPknxNpEMTZfkhHQjBcqr" }), /fixture/); + assert.throws(() => validateDeploymentManifest({ ...manifest, genesisHash: null })); + assert.throws(() => validateDeploymentManifest({ ...manifest, programData: programId.toBase58() })); +}); +test("deployed ELF requires exact reviewed hash/bytes and only zero allocation padding", () => { + const elf = Buffer.alloc(100); elf.set([0x7f, 0x45, 0x4c, 0x46]); + const hash = Buffer.from(sha256(elf)).toString("hex"); + const data = Buffer.concat([Buffer.alloc(45), elf, Buffer.alloc(200)]); + assert.equal(verifyDeploymentBytes(data, elf, hash), hash); + assert.throws(() => verifyDeploymentBytes(data, elf, "a".repeat(64)), /SHA-256/); + data[50] ^= 1; assert.throws(() => verifyDeploymentBytes(data, elf, hash), /differ/); data[50] ^= 1; + data[data.length - 1] = 1; assert.throws(() => verifyDeploymentBytes(data, elf, hash), /differ/); + assert.throws(() => verifyDeploymentBytes(Buffer.alloc(50), elf, hash), /differ/); +}); diff --git a/app/packages/solana-sdk/tsconfig.json b/app/packages/solana-sdk/tsconfig.json new file mode 100644 index 00000000..d787883c --- /dev/null +++ b/app/packages/solana-sdk/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", + "strict": true, "skipLibCheck": true, "allowImportingTsExtensions": true, + "noEmit": true, "esModuleInterop": true + }, + "include": ["src/**/*.ts", "tests/**/*.ts", "scripts/**/*.ts"] +} From 767e8ba534b04b090ab5f244d91f92700890a317 Mon Sep 17 00:00:00 2001 From: Vasanth T Date: Sat, 10 Oct 2026 15:36:40 +0530 Subject: [PATCH 3/9] feat(solana): add gated review workspace and release checks Keep the Solana pilot separate and disabled until verified. Add explicit transaction review, wallet-bound recovery, bounded recent history and RPC access, plus an auditable deployment handoff with unresolved mainnet gates. --- .github/workflows/ci.yml | 33 + app/.env.example | 8 + app/Dockerfile | 1 + app/next.config.ts | 1 + app/package-lock.json | 662 +++++++++++++++++- app/package.json | 6 + .../app/api/solana/history/[pool]/route.ts | 98 +++ app/src/app/api/solana/rpc/route.ts | 157 +++++ app/src/app/solana/page.tsx | 52 ++ app/src/app/solana/pool/[address]/page.tsx | 20 + app/src/components/solana/LaunchPanel.tsx | 309 ++++++++ app/src/components/solana/PoolPanel.tsx | 578 +++++++++++++++ app/src/components/solana/SolanaHistory.tsx | 238 +++++++ app/src/components/solana/SolanaWorkspace.tsx | 548 +++++++++++++++ app/src/lib/solana/bounded-json.test.ts | 26 + app/src/lib/solana/bounded-json.ts | 25 + app/src/lib/solana/config.test.ts | 83 +++ app/src/lib/solana/config.ts | 110 +++ app/src/lib/solana/deployment.json | 13 + app/src/lib/solana/history.test.ts | 417 +++++++++++ app/src/lib/solana/history.ts | 490 +++++++++++++ app/src/lib/solana/journal.test.ts | 210 ++++++ app/src/lib/solana/journal.ts | 148 ++++ app/src/lib/solana/rpc-budget.test.ts | 18 + app/src/lib/solana/rpc-budget.ts | 22 + app/src/lib/solana/rpc-policy.test.ts | 146 ++++ app/src/lib/solana/rpc-policy.ts | 114 +++ app/src/lib/solana/server.ts | 37 + app/src/lib/solana/useSolanaAction.ts | 476 +++++++++++++ app/src/lib/solana/wallet.ts | 182 +++++ docs/SOLANA_IMMUTABLE_POOL_PLAN.md | 247 +++++++ docs/SOLANA_MAINNET_PLAN.md | 161 +++++ docs/SOLANA_RELEASE.md | 74 ++ docs/SOLANA_WORKSPACE.md | 21 + 34 files changed, 5721 insertions(+), 10 deletions(-) create mode 100644 app/src/app/api/solana/history/[pool]/route.ts create mode 100644 app/src/app/api/solana/rpc/route.ts create mode 100644 app/src/app/solana/page.tsx create mode 100644 app/src/app/solana/pool/[address]/page.tsx create mode 100644 app/src/components/solana/LaunchPanel.tsx create mode 100644 app/src/components/solana/PoolPanel.tsx create mode 100644 app/src/components/solana/SolanaHistory.tsx create mode 100644 app/src/components/solana/SolanaWorkspace.tsx create mode 100644 app/src/lib/solana/bounded-json.test.ts create mode 100644 app/src/lib/solana/bounded-json.ts create mode 100644 app/src/lib/solana/config.test.ts create mode 100644 app/src/lib/solana/config.ts create mode 100644 app/src/lib/solana/deployment.json create mode 100644 app/src/lib/solana/history.test.ts create mode 100644 app/src/lib/solana/history.ts create mode 100644 app/src/lib/solana/journal.test.ts create mode 100644 app/src/lib/solana/journal.ts create mode 100644 app/src/lib/solana/rpc-budget.test.ts create mode 100644 app/src/lib/solana/rpc-budget.ts create mode 100644 app/src/lib/solana/rpc-policy.test.ts create mode 100644 app/src/lib/solana/rpc-policy.ts create mode 100644 app/src/lib/solana/server.ts create mode 100644 app/src/lib/solana/useSolanaAction.ts create mode 100644 app/src/lib/solana/wallet.ts create mode 100644 docs/SOLANA_IMMUTABLE_POOL_PLAN.md create mode 100644 docs/SOLANA_MAINNET_PLAN.md create mode 100644 docs/SOLANA_RELEASE.md create mode 100644 docs/SOLANA_WORKSPACE.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63598ba9..b21eb85b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,11 @@ jobs: - run: npm run lint - run: npx tsc --noEmit -p . - run: npm test + - name: Solana SDK and independent economic model + run: | + npx tsc --noEmit -p packages/solana-sdk/tsconfig.json + npx tsx --test packages/solana-sdk/tests/*.test.ts + node --experimental-strip-types --test ../packages/solana-model/*.test.mjs - name: production build env: NEXT_PUBLIC_SITE_URL: https://openlaunch.lol @@ -39,6 +44,34 @@ jobs: NEXT_PUBLIC_LAUNCH_LOCKER_ROBINHOOD: "0xcd1680D26922fcd9CabFbb8a56bA40C333fD842a" run: npm run build + solana: + name: Solana · host tests, SBFv3 build, offline VM + runs-on: ubuntu-latest + timeout-minutes: 35 + defaults: + run: + working-directory: solana + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.97.1 + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: solana + - run: cargo fmt --all --check + - run: cargo test -p launch-pool --locked + - name: Install pinned build-only SBF tool + run: cargo install cargo-build-sbf --version 4.2.0 --locked --root .tools + - name: Build current SBPF format, reject stack diagnostics + run: bash scripts/check-sbf.sh + - name: Execute compiled program offline + run: cargo test --workspace --features svm-tests --locked + - name: Rust lint + run: cargo clippy --workspace --all-targets --features svm-tests --locked -- -D warnings + # No RPC credentials, wallet keys, deployments, or authority operations in CI. + contracts: name: contracts · build + unit tests runs-on: ubuntu-latest diff --git a/app/.env.example b/app/.env.example index 92461b96..9f36868c 100644 --- a/app/.env.example +++ b/app/.env.example @@ -52,3 +52,11 @@ IMAGE_STORE=local # Wallets allowed to moderate posts (comma-separated, lowercase) ADMIN_WALLETS= + +# Isolated Solana review workspace (/solana). Disabled by default; not an EVM chain. +# Kevin supplies a reviewed, immutable deployment. No keys or signing on the server. +SOLANA_ENABLED=0 +SOLANA_CLUSTER=devnet +SOLANA_DEVNET_PROGRAM_ID= +# HTTPS only. Mainnet requires a keyed RPC and an approved src/lib/solana/deployment.json. +SOLANA_RPC_URL= diff --git a/app/Dockerfile b/app/Dockerfile index 44a85342..6d13a989 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -5,6 +5,7 @@ FROM node:22-slim AS build WORKDIR /app COPY package.json package-lock.json ./ +COPY packages/solana-sdk ./packages/solana-sdk RUN npm ci --no-audit --no-fund COPY . . diff --git a/app/next.config.ts b/app/next.config.ts index 1e8433ab..e58126e3 100644 --- a/app/next.config.ts +++ b/app/next.config.ts @@ -4,6 +4,7 @@ import { SECURITY_HEADERS } from "./src/lib/security-headers"; const nextConfig: NextConfig = { // Fly: the Dockerfile copies .next/standalone (server.js + traced node_modules). output: "standalone", + transpilePackages: ["@openlaunch/solana-sdk"], // Keep the Postgres driver external (not bundled) so it is traced into // .next/standalone/node_modules and scripts/migrate.mjs can import it inside // the image (release_command runs there, outside the Next server). diff --git a/app/package-lock.json b/app/package-lock.json index 905828d0..0796d9ec 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -12,7 +12,13 @@ "@base-ui/react": "^1.8.0", "@coinbase/wallet-sdk": "^4.3.7", "@number-flow/react": "^0.6.2", + "@openlaunch/solana-sdk": "file:./packages/solana-sdk", + "@solana/wallet-standard-features": "1.5.0", + "@solana/web3.js": "1.99.0", "@tanstack/react-query": "^5.91.3", + "@wallet-standard/app": "1.1.1", + "@wallet-standard/base": "1.1.1", + "@wallet-standard/features": "1.1.1", "clsx": "^2.1.1", "d3-force": "^3.0.0", "lightweight-charts": "^5.2.1", @@ -2357,6 +2363,10 @@ "react-dom": "^18 || ^19" } }, + "node_modules/@openlaunch/solana-sdk": { + "resolved": "packages/solana-sdk", + "link": true + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2481,6 +2491,119 @@ "node": ">=18.0.0" } }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "license": "MIT", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/codecs-core": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.5.1.tgz", + "integrity": "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.5.1.tgz", + "integrity": "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/errors": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.5.1.tgz", + "integrity": "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/wallet-standard-features": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@solana/wallet-standard-features/-/wallet-standard-features-1.5.0.tgz", + "integrity": "sha512-gxlLaEfPAqbhd3LNkK0Ks2pbVvDm1SDGqBK/ynDQy13Gu+qiURLpRCfSPjbtrw5bmqQsZ6rS2YJHL31bY/Koag==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.0", + "@wallet-standard/features": "^1.1.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.99.0.tgz", + "integrity": "sha512-QZYQ2T1z6xWisoyALPq25i/QZTsRlM02BABtAsfaQ1p8wX4SdTfxrKTRue/ZZqrhNVh5oL7T/DUFiTS9DRgxow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^5.5.1", + "agentkeepalive": "^4.6.0", + "bn.js": "^5.2.5", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.3.0", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, "node_modules/@swc/helpers": { "version": "0.5.23", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", @@ -2810,6 +2933,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3-force": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", @@ -2842,19 +2974,11 @@ "version": "20.19.43", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, - "node_modules/@types/node/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", @@ -2875,6 +2999,21 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.67.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", @@ -3524,6 +3663,39 @@ "win32" ] }, + "node_modules/@wallet-standard/app": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/app/-/app-1.1.1.tgz", + "integrity": "sha512-WDGwoByhP5gwHH01r5EaLgQdLVkACPCdOMQhmhn8rsm10h/siSgTorShzBxrn0ExSPof+Lu+C3TfgqBrPa1xoQ==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@wallet-standard/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/base/-/base-1.1.1.tgz", + "integrity": "sha512-gggIHTtxicF9XFMQ12DkfS6NAG92Ak795JeSA7f2whAQ6Y3AkMWWuCMxSZXG2NIPN42kEaZSNVjqMsJRaJRxMQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=22" + } + }, + "node_modules/@wallet-standard/features": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/features/-/features-1.1.1.tgz", + "integrity": "sha512-aCWYmVeSCGViyEU5k7GMoW8zxE4Gs+C1s1Pp2XLesvSNlnZ4PMES9HUnTB3hl0b3RVj7C61yze3IWyrncqg4MA==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.1" + }, + "engines": { + "node": ">=22" + } + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -3547,6 +3719,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3800,6 +3984,35 @@ "dev": true, "license": "MIT" }, + "node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.11.16", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.16.tgz", @@ -3812,6 +4025,23 @@ "node": ">=6.0.0" } }, + "node_modules/bn.js": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", + "license": "MIT" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, "node_modules/bowser": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", @@ -3876,6 +4106,53 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -3956,6 +4233,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3991,6 +4280,15 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4190,6 +4488,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4454,6 +4764,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/esbuild": { "version": "0.28.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", @@ -4979,6 +5304,14 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fancy-canvas": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz", @@ -5036,6 +5369,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", @@ -5472,6 +5811,35 @@ "hermes-estree": "0.25.1" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5969,6 +6337,15 @@ "dev": true, "license": "ISC" }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/isows": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", @@ -6002,6 +6379,65 @@ "node": ">= 0.4" } }, + "node_modules/jayson": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", + "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", + "license": "MIT", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "stream-json": "^1.9.1", + "uuid": "^8.3.2", + "ws": "^7.5.10" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/jayson/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/jayson/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -6069,6 +6505,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6607,7 +7049,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -6761,6 +7202,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.53", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", @@ -7353,6 +7826,51 @@ "node": ">=0.10.0" } }, + "node_modules/rpc-websockets": { + "version": "9.3.9", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.9.tgz", + "integrity": "sha512-2iQDaTB4g5fDB2ihrTFSJSibCEuxaRi1q7qTW7ZO9/M5/TC+ToHA4D9/ffNLEbAoHNNrcdeP05oATNk44SKZXA==", + "license": "LGPL-3.0-only", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "uuid": "^14.0.0", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^6.0.0" + } + }, + "node_modules/rpc-websockets/node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rpc-websockets/node_modules/uuid": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.3.tgz", + "integrity": "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7397,6 +7915,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -7693,6 +8231,21 @@ "node": ">= 0.4" } }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -7853,6 +8406,15 @@ } } }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7910,6 +8472,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -7971,6 +8538,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -8183,6 +8756,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/unrs-resolver": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", @@ -8271,6 +8850,30 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utf-8-validate": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz", + "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/viem": { "version": "2.55.19", "resolved": "https://registry.npmjs.org/viem/-/viem-2.55.19.tgz", @@ -8461,6 +9064,22 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8581,7 +9200,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -8669,6 +9287,30 @@ "optional": true } } + }, + "packages/solana-sdk": { + "name": "@openlaunch/solana-sdk", + "version": "0.1.0", + "dependencies": { + "@noble/hashes": "1.8.0", + "@solana/web3.js": "1.99.0", + "buffer": "6.0.3" + }, + "devDependencies": { + "@types/node": "^22", + "tsx": "4.23.13", + "typescript": "5.9.3" + } + }, + "packages/solana-sdk/node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } } } } diff --git a/app/package.json b/app/package.json index 9dee25df..09102804 100644 --- a/app/package.json +++ b/app/package.json @@ -12,6 +12,12 @@ "migrate": "node scripts/migrate.mjs" }, "dependencies": { + "@openlaunch/solana-sdk": "file:./packages/solana-sdk", + "@solana/web3.js": "1.99.0", + "@solana/wallet-standard-features": "1.5.0", + "@wallet-standard/app": "1.1.1", + "@wallet-standard/base": "1.1.1", + "@wallet-standard/features": "1.1.1", "@aws-sdk/client-s3": "^3.1127.0", "@base-ui/react": "^1.8.0", "@coinbase/wallet-sdk": "^4.3.7", diff --git a/app/src/app/api/solana/history/[pool]/route.ts b/app/src/app/api/solana/history/[pool]/route.ts new file mode 100644 index 00000000..e6a314c2 --- /dev/null +++ b/app/src/app/api/solana/history/[pool]/route.ts @@ -0,0 +1,98 @@ +import { PublicKey } from "@solana/web3.js"; +import { fetchPoolSnapshot } from "../../../../../../packages/solana-sdk/src/accounts"; +import { publicKey } from "@/lib/solana/config"; +import { + createHistoryCache, + fetchRecentSolanaHistory, + type SolanaHistory, +} from "@/lib/solana/history"; +import { configuredSolana, solanaConnection } from "@/lib/solana/server"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; +const cached = createHistoryCache(); +const buckets = new Map(); +const GENESIS = { + "mainnet-beta": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", + devnet: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", +}; +const noStore = { "cache-control": "no-store" }; + +export async function GET( + req: Request, + { params }: { params: Promise<{ pool: string }> }, +) { + const config = configuredSolana(); + if (!config) + return Response.json( + { error: "Solana is not enabled." }, + { status: 503, headers: noStore }, + ); + const { pool } = await params; + if (!publicKey(pool)) + return Response.json( + { error: "Invalid pool address." }, + { status: 400, headers: noStore }, + ); + const ip = ( + req.headers.get("fly-client-ip") ?? + req.headers.get("x-forwarded-for")?.split(",")[0] ?? + "local" + ).slice(0, 128); + const now = Date.now(); + const bucket = buckets.get(ip); + if (bucket && now - bucket.at < 60_000 && bucket.count >= 30) + return Response.json( + { error: "Please wait before refreshing history." }, + { status: 429, headers: { ...noStore, "retry-after": "15" } }, + ); + if (buckets.size >= 5000) buckets.clear(); + buckets.set( + ip, + bucket && now - bucket.at < 60_000 + ? { at: bucket.at, count: bucket.count + 1 } + : { at: now, count: 1 }, + ); + try { + const history = await cached( + `${config.cluster}:${config.programId}:${pool}`, + async () => { + const connection = solanaConnection(); + if ((await connection.getGenesisHash()) !== GENESIS[config.cluster]) + throw new Error("Wrong RPC cluster"); + const snapshot = await fetchPoolSnapshot( + connection, + new PublicKey(config.programId), + new PublicKey(pool), + ); + return fetchRecentSolanaHistory( + connection, + new PublicKey(config.programId), + snapshot, + ); + }, + ); + return Response.json( + { ...history, cluster: config.cluster }, + { + headers: { + "cache-control": + "public, max-age=0, s-maxage=10, stale-while-revalidate=15", + }, + }, + ); + } catch (error) { + const busy = error instanceof Error && error.message === "HISTORY_BUSY"; + return Response.json( + { + error: busy + ? "Recent history is busy. Please retry shortly." + : "Verified recent history is unavailable. No chart data has been substituted.", + }, + { + status: busy ? 429 : 503, + headers: { ...noStore, "retry-after": "15" }, + }, + ); + } +} diff --git a/app/src/app/api/solana/rpc/route.ts b/app/src/app/api/solana/rpc/route.ts new file mode 100644 index 00000000..365453fa --- /dev/null +++ b/app/src/app/api/solana/rpc/route.ts @@ -0,0 +1,157 @@ +import { configuredSolana, solanaRpcUrl } from "@/lib/solana/server"; +import { allowedSolanaRpc } from "@/lib/solana/rpc-policy"; +import { boundedJson } from "@/lib/solana/bounded-json"; +import { rpcBudget } from "@/lib/solana/rpc-budget"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; +const buckets = new Map(); +let network: { url: string; cluster: string; expires: number } | null = null; +const GENESIS = { + "mainnet-beta": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", + devnet: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", +}; +const headers = { "cache-control": "no-store" }; +const acquire = rpcBudget(); + +export async function POST(req: Request) { + const config = configuredSolana(); + const upstream = solanaRpcUrl(); + if (!config || !upstream) + return Response.json( + { error: "Solana is not enabled." }, + { status: 503, headers }, + ); + const origin = req.headers.get("origin"); + if (origin && origin !== new URL(req.url).origin) + return Response.json({ error: "Origin denied." }, { status: 403, headers }); + if (Number(req.headers.get("content-length") ?? 0) > 12_000) + return Response.json( + { error: "Request too large." }, + { status: 413, headers }, + ); + const ip = + req.headers.get("fly-client-ip") ?? + req.headers.get("x-forwarded-for")?.split(",")[0] ?? + "local"; + const now = Date.now(); + const bucket = buckets.get(ip); + if (bucket && now - bucket.at < 60_000 && bucket.count >= 100) + return Response.json( + { error: "Please wait before retrying." }, + { status: 429, headers: { ...headers, "retry-after": "10" } }, + ); + if (buckets.size > 10_000) buckets.clear(); + buckets.set( + ip, + bucket && now - bucket.at < 60_000 + ? { at: bucket.at, count: bucket.count + 1 } + : { at: now, count: 1 }, + ); + let body: unknown; + try { + body = await boundedJson(req.body, 12_000); + } catch { + return Response.json( + { error: "Invalid request." }, + { status: 400, headers }, + ); + } + if (!allowedSolanaRpc(body, config.programId)) + return Response.json( + { + jsonrpc: "2.0", + id: typeof body === "object" && body && "id" in body ? body.id : null, + error: { code: -32601, message: "Method or parameters not allowed." }, + }, + { headers }, + ); + const release = acquire(); + if (!release) + return Response.json( + { error: "Solana RPC is busy. Please retry shortly." }, + { status: 429, headers: { ...headers, "retry-after": "10" } }, + ); + try { + if ( + !network || + network.url !== upstream || + network.cluster !== config.cluster || + network.expires < now + ) { + const check = await fetch(upstream, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "getGenesisHash", + params: [], + }), + signal: AbortSignal.timeout(8_000), + cache: "no-store", + }); + const result = (await boundedJson(check.body, 4_096)) as { + result?: unknown; + }; + if (!check.ok || result.result !== GENESIS[config.cluster]) + throw new Error("Wrong cluster"); + network = { + url: upstream, + cluster: config.cluster, + expires: now + 30_000, + }; + } + const response = await fetch(upstream, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(15_000), + cache: "no-store", + }); + if (!response.ok) throw new Error("Upstream unavailable"); + const result = (await boundedJson(response.body, 4 * 1024 * 1024)) as { + jsonrpc?: unknown; + id?: unknown; + result?: unknown; + error?: unknown; + }; + if ( + !result || + result.jsonrpc !== "2.0" || + result.id !== body.id || + (!("result" in result) && !("error" in result)) + ) + throw new Error("Invalid RPC response"); + if (result.error) + return Response.json( + { + jsonrpc: "2.0", + id: body.id, + error: { + code: -32000, + message: + "Solana RPC rejected this request. Check the transaction status before retrying.", + }, + }, + { headers }, + ); + return Response.json(result, { headers }); + } catch { + network = null; + return Response.json( + { + jsonrpc: "2.0", + id: body.id, + error: { + code: -32000, + message: + "Solana RPC is unavailable or on the wrong network. A submitted transaction may still confirm; check its signature before trying again.", + }, + }, + { status: 503, headers }, + ); + } finally { + release(); + } +} diff --git a/app/src/app/solana/page.tsx b/app/src/app/solana/page.tsx new file mode 100644 index 00000000..27e43442 --- /dev/null +++ b/app/src/app/solana/page.tsx @@ -0,0 +1,52 @@ +import Link from "next/link"; +import { configuredSolana, solanaRpcUrl } from "@/lib/solana/server"; +import SolanaWorkspace from "@/components/solana/SolanaWorkspace"; +import { btn } from "@/components/ui"; + +export const dynamic = "force-dynamic"; +export const metadata = { + title: "Solana workspace", + robots: { index: false, follow: false }, +}; + +export default function SolanaPage() { + const config = configuredSolana(); + if (config && solanaRpcUrl()) return ; + return ( +
+

+ Solana, with no admin controls. +

+

+ The native launch-pool integration is under review. It is separate from + the live launchpad and cannot accept funds here yet. +

+
+

+ Before launches open +

+
    +
  • + Independent security and economic review of the exact program. +
  • +
  • + Verified deployment with the upgrade authority permanently removed. +
  • +
  • + Launch, buy, sell, fee-claim and wallet recovery rehearsal on the + deployed program. +
  • +
+
+

+ Existing Base, Robinhood and Arc launches continue unchanged. +

+ + Back to launchpad + +
+ ); +} diff --git a/app/src/app/solana/pool/[address]/page.tsx b/app/src/app/solana/pool/[address]/page.tsx new file mode 100644 index 00000000..e73cfb61 --- /dev/null +++ b/app/src/app/solana/pool/[address]/page.tsx @@ -0,0 +1,20 @@ +import { notFound } from "next/navigation"; +import SolanaWorkspace from "@/components/solana/SolanaWorkspace"; +import { configuredSolana, solanaRpcUrl } from "@/lib/solana/server"; +import { publicKey } from "@/lib/solana/config"; + +export const dynamic = "force-dynamic"; +export const metadata = { + title: "Solana pool", + robots: { index: false, follow: false }, +}; +export default async function PoolPage({ + params, +}: { + params: Promise<{ address: string }>; +}) { + const { address } = await params; + const config = configuredSolana(); + if (!config || !solanaRpcUrl() || !publicKey(address)) notFound(); + return ; +} diff --git a/app/src/components/solana/LaunchPanel.tsx b/app/src/components/solana/LaunchPanel.tsx new file mode 100644 index 00000000..c7178110 --- /dev/null +++ b/app/src/components/solana/LaunchPanel.tsx @@ -0,0 +1,309 @@ +"use client"; + +import { useState } from "react"; +import { PublicKey } from "@solana/web3.js"; +import { + buildPrepareLaunchInstruction, + derivePoolAddresses, + POOL_ACCOUNT_SPACE, +} from "@openlaunch/solana-sdk"; +import { btn, input, label, helper } from "@/components/ui"; +import { formatUnits, parseUnits } from "@/lib/solana/config"; +import type { SolanaPanelProps } from "./SolanaWorkspace"; + +export default function LaunchPanel({ + connection, + config, + address, + actions, +}: SolanaPanelProps) { + const [name, setName] = useState(""); + const [symbol, setSymbol] = useState(""); + const [valuation, setValuation] = useState(""); + const [feeBps, setFeeBps] = useState(0); + const [recipients, setRecipients] = useState(""); + const [uri, setUri] = useState(""); + const [hash, setHash] = useState(""); + const [acknowledged, setAcknowledged] = useState(false); + const [preparing, setPreparing] = useState(false); + + async function prepare() { + if ( + !address || + preparing || + !acknowledged || + actions.busy || + actions.pending || + !actions.restored + ) + return; + setPreparing(true); + try { + const virtualSol = parseUnits(valuation, 9); + const creator = new PublicKey(address); + const program = new PublicKey(config.programId); + const nonce = new DataView( + crypto.getRandomValues(new Uint8Array(8)).buffer, + ).getBigUint64(0, true); + const contentHash = new Uint8Array(32); + if (hash && !/^[a-fA-F0-9]{64}$/.test(hash)) + throw new Error( + "The metadata SHA-256 must be exactly 64 hexadecimal characters.", + ); + if (hash) + for (let i = 0; i < 32; i++) + contentHash[i] = parseInt(hash.slice(i * 2, i * 2 + 2), 16); + if (uri && !/^https:\/\//.test(uri)) + throw new Error("Use an HTTPS metadata URI, or leave it empty."); + if (Boolean(uri) !== Boolean(hash)) + throw new Error( + "Supply both the metadata URI and its content hash, or leave both empty.", + ); + const beneficiaries = + feeBps === 0 + ? [] + : recipients + .trim() + .split(/\r?\n/) + .filter(Boolean) + .map((line) => { + const [wallet, weight, extra] = line.trim().split(/\s+/); + if (extra || !/^\d+$/.test(weight ?? "")) + throw new Error( + "Use one recipient per line: wallet-address share-in-basis-points.", + ); + return { + address: new PublicKey(wallet), + weightBps: Number(weight), + }; + }); + const ix = buildPrepareLaunchInstruction(program, { + creator, + nonce, + virtualSol, + feeBps, + name, + symbol, + uri, + contentHash, + recipients: beneficiaries, + }); + const addresses = derivePoolAddresses(program, creator, nonce); + const pool = addresses.pool.toBase58(); + // Pool::SPACE is ABI-v1 fixed maximum allocation, even with shorter text. + const rent = + await connection.getMinimumBalanceForRentExemption(POOL_ACCOUNT_SPACE); + await actions.prepare( + "Prepare launch", + [ix], + [ + ["Name / symbol", `${name} / ${symbol}`], + ["Token mint", addresses.mint.toBase58()], + ["Original supply", "1,000,000,000 tokens · 6 decimals"], + [ + "Initial valuation (not a SOL deposit)", + `${formatUnits(virtualSol, 9)} SOL`, + ], + ["Initial real SOL reserve", "0 SOL"], + ["Fixed trading fee", `${feeBps / 100}%`], + ...beneficiaries.map((r, index): [string, string] => [ + `Beneficiary ${index + 1}`, + `${r.address.toBase58()} · ${r.weightBps / 100}% of trading fees`, + ]), + ...(uri + ? ([ + ["Metadata URI (immutable)", uri], + ["Metadata SHA-256", hash], + ] as [string, string][]) + : []), + [ + "Preparation rent (estimated, permanent)", + `${formatUnits(BigInt(rent), 9)} SOL`, + ], + ], + pool, + ); + } catch (e) { + actions.setError( + e instanceof Error + ? e.message + : "Check the launch terms and try again.", + ); + } finally { + setPreparing(false); + } + } + return ( +
+
{ + e.preventDefault(); + void prepare(); + }} + > +

Prepare your launch

+
+ + +
+ +
+ Trading fee, fixed forever +
+ {[0, 100, 300].map((fee) => ( + + ))} +
+
+ {feeBps > 0 && ( +