diff --git a/.github/.workspace-ignore b/.github/.workspace-ignore index 78234e002..278c7faa8 100644 --- a/.github/.workspace-ignore +++ b/.github/.workspace-ignore @@ -11,6 +11,9 @@ games/gacha/pinocchio/clients/rust games/gacha/pinocchio/program games/gacha/pinocchio/tests/integration-tests games/gacha/pinocchio/tests/light-integration-tests +games/gacha/pinocchio-simple/clients/rust +games/gacha/pinocchio-simple/program +games/gacha/pinocchio-simple/tests/integration-tests games/world-cup/pinocchio/clients/rust games/world-cup/pinocchio/program games/world-cup/pinocchio/tests/integration-tests diff --git a/.prettierignore b/.prettierignore index d1d63eb6c..baeb427ea 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,5 @@ Cargo.lock **/Assets/ games/world-cup/ games/gacha/pinocchio/idl/ +games/gacha/pinocchio-simple/idl/ tokens/token-2022/transfer-hook/block-list/pinocchio/sdk/ diff --git a/games/gacha/README.md b/games/gacha/README.md index 69d0e3843..6edb09234 100644 --- a/games/gacha/README.md +++ b/games/gacha/README.md @@ -3,18 +3,22 @@ A provably-fair **gacha** (loot-box / pack-pull) game — the on-chain mechanic behind Solana RWA pack platforms . An admin configures a pool of fixed-weight reward tiers and a fixed entry fee; buyers open pulls that are -revealed with a verifiable random function (RFC 9381 ECVRF) anchored in Collector -Crypt's deployed [`cc-vrf`](https://vrf.collectorcrypt.com) registry by CPI, and each -prize is minted as a Token-2022 NFT carrying its `rarity` in the token metadata. +revealed with a verifiable random function (RFC 9381 ECVRF), and each prize is +minted as a Token-2022 NFT carrying its `rarity` in the token metadata. The VRF input binds buyer-supplied entropy (`SHA-256(pull || client_seed)`), so no one — including the operator — can predict an outcome before the buy lands, and every reveal is publicly verifiable off-chain. Unsettled pulls are refundable after a deadline. -| Framework | Path | -| --------- | ---------------------------- | -| Pinocchio | [`./pinocchio`](./pinocchio) | +Two variants share the same draw semantics (`select_tier`/`derive_alpha` are +byte-identical, pinned by shared test fixtures) and differ in how reveals are +evidenced: -The Pinocchio example is a self-contained nested workspace: it pins its own toolchain +| Variant | Reveal evidence | +| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`./pinocchio`](./pinocchio) | Each reveal is anchored in Collector Crypt's deployed [`cc-vrf`](https://vrf.collectorcrypt.com) registry by CPI (Light Protocol compressed accounts) | +| [`./pinocchio-simple`](./pinocchio-simple) | Each prize NFT carries its full reveal provenance (`pull`, `client_seed`, `beta`, `proof`) in its own Token-2022 metadata and names its pool via the metadata update authority — verifiable from live accounts, no transaction history | + +Both examples are self-contained nested workspaces: each pins its own toolchain and dependencies and builds/tests via its own `justfile`. diff --git a/games/gacha/pinocchio-simple/.gitignore b/games/gacha/pinocchio-simple/.gitignore new file mode 100644 index 000000000..b77a1c214 --- /dev/null +++ b/games/gacha/pinocchio-simple/.gitignore @@ -0,0 +1,36 @@ +**/target +.idea +**/node_modules +dist +**/*.tsbuildinfo +bun.lockb + +**/generated/ + +# Solana test validator ledger data +test-ledger/ +**/test-ledger/ +.validator-ledger + +# Log files +*.log +**/cu_report.md + +# Local config (contains generated mint addresses) +config.json + +# Environment secrets +.env +.env.local +**/.env.local + +keys/ + +# TypeDoc generated API docs +clients/typescript/docs/ + +.claude/ + +# Editor / tool config +.emdash.json +.vscode/ diff --git a/games/gacha/pinocchio-simple/.nvmrc b/games/gacha/pinocchio-simple/.nvmrc new file mode 100644 index 000000000..3fe3b1570 --- /dev/null +++ b/games/gacha/pinocchio-simple/.nvmrc @@ -0,0 +1 @@ +24.13.0 diff --git a/games/gacha/pinocchio-simple/.prettierignore b/games/gacha/pinocchio-simple/.prettierignore new file mode 100644 index 000000000..402437b02 --- /dev/null +++ b/games/gacha/pinocchio-simple/.prettierignore @@ -0,0 +1,23 @@ +# IDL files +idl/ + +# Generated files +clients/typescript/src/generated/ + +# Build outputs +dist/ +build/ +target/ + +# Dependencies +node_modules/ +package.json +pnpm-lock.yaml + +# TypeDoc generated API docs +clients/typescript/docs/ + +# Editor / tool config +.emdash.json +.vscode/ +.remember/ diff --git a/games/gacha/pinocchio-simple/.prettierrc.json b/games/gacha/pinocchio-simple/.prettierrc.json new file mode 100644 index 000000000..070fcfbd0 --- /dev/null +++ b/games/gacha/pinocchio-simple/.prettierrc.json @@ -0,0 +1 @@ +"@solana/prettier-config-solana" diff --git a/games/gacha/pinocchio-simple/CLAUDE.md b/games/gacha/pinocchio-simple/CLAUDE.md new file mode 100644 index 000000000..84a2ae42e --- /dev/null +++ b/games/gacha/pinocchio-simple/CLAUDE.md @@ -0,0 +1,192 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +A provably-fair **gacha** (loot-box / pack-pull) game on Solana — the simplified +sibling of `../pinocchio`. Where that variant anchors every reveal in Collector +Crypt's cc-vrf registry through a Light Protocol CPI, this one replaces the +whole external stack with a **self-certifying Token-2022 prize NFT**: the mint's +metadata carries the rarity plus the complete reveal provenance, so the evidence +travels with the prize. An admin configures a pool of fixed-weight reward tiers +and a fixed entry fee, and records an off-chain VRF operator. A buyer pays the +fee to open a pull, committing buyer-supplied entropy into the VRF input. The +operator reveals the ECVRF output (`beta`) with `settle_and_distribute`, which +selects the tier, mints the prize NFT straight to the buyer, and closes the +pull. + +## Randomness model (important) + +RFC 9381 `ECVRF-EDWARDS25519-SHA512-TAI`. **Solana cannot verify an ECVRF proof +on-chain** (no precompile), so the trust model is _detection, not prevention_: +on-chain the program accepts the registered operator's signed `beta`; off-chain +anyone can prove cheating. The design closes every gap that detection alone +leaves open: + +1. **Fixed ≠ unpredictable.** `alpha = SHA-256(pull_address || client_seed)` + where `client_seed` is 32 random bytes chosen by the buyer at commit. An + alpha that is merely _fixed_ (say, the pull address alone) is worthless + against the operator: `beta = VRF(operator_key, alpha)` is deterministic, so + a predictable alpha lets the operator precompute every outcome before anyone + buys. Buyer entropy is what makes the outcome unknowable at commit time. +2. **Fixed weights ⇒ order-independence.** Tier odds never change after init, + so a pull's outcome depends only on its `beta` — not on supply counters or + the order in which the operator settles. +3. **One reveal per pull, enforced structurally.** Pull PDAs are seeded by a + monotonic pool index, so `buy_pull` can never re-derive an old address; the + prize mint is a PDA of the pull whose creation fails if it already exists; + and `settle_and_distribute` closes the pull account. No registry needed. +4. **The operator key is pinned.** `pool.operator` is fixed at init and doubles + as the ECVRF public key, so the key can never be swapped mid-pool. (This is + what replaces the sibling's frozen cc-vrf registry record — the registry + additionally proved the record was frozen/unrevoked, but with the key pinned + in immutable pool state that guarantee is redundant here.) +5. **Liveness has an escape hatch.** An operator can withhold a reveal (e.g. + after privately computing an unfavorable `beta`), but `refund_pull` returns + the buyer's entry fee and rent after `settle_deadline_slots`, and + `withdraw_fees` can never touch pending buyers' escrow (the vault reserves + `pending_pulls × entry_fee`). Withholding delays; it never steals. +6. **Verification story — the NFT is the evidence.** The prize mint's + `additional_metadata` carries `rarity`, `pull`, `client_seed`, `beta`, and + `proof` (lowercase hex), and its metadata `update_authority` is the pool + PDA — the NFT names its pool, and the pool account supplies the operator + key, weights, and tier count. From those two live accounts (no transaction + history) anyone can recompute `alpha`, verify the proof with + `@collectorcrypt/ecvrf` against `pool.operator`, and reproduce the tier + with `selectTier` — the TS client ships this as `verifyPrizeProvenance`. + The same data is emitted in `PullSettledEvent`. The operator's 32-byte + Ed25519 seed is **both** its Solana signing key and its ECVRF key, so + `pool.operator` equals the ECVRF public key. + +Comparison: oracle VRFs (Switchboard On-Demand, MagicBlock VRF, ORAO) verify the +randomness proof **on-chain** at the cost of oracle fees, extra latency, and an +oracle-network liveness dependency. The `../pinocchio` sibling anchors reveals +in the cc-vrf registry via Light Protocol compressed accounts — stronger +third-party attestation, much heavier stack. This variant trades that +attestation for radical simplicity: the provenance lives in the prize itself, +at ~0.007 SOL of metadata rent per settle, paid by the operator. + +## Required Versions + +- **Rust**: See `rust-toolchain.toml` +- **Node.js**: See `.nvmrc` +- **pnpm**: See `package.json` `packageManager` field + +## Build Commands + +```bash +just build # program .so → IDL → TS client → dist +just generate-idl # Generate IDL via Codama (cargo build with build.rs) +just generate-clients # Generate TypeScript + Rust clients from IDL +just build-program # Build .so binary only (cargo build-sbf) +just test # unit + integration + client tests +just unit-test # Rust host unit tests (selection + alpha + hex) +just integration-test # LiteSVM integration tests (builds the .so first) +just client-test # TypeScript client tests (parity + ECVRF + provenance) +just demo # Off-chain operator/verifier demo (no RPC) +just fmt # cargo fmt + prettier +just check # fmt-check + lint-check +``` + +## Architecture + +Solana program using **Pinocchio** (lightweight `no_std` framework) with **Codama** +for IDL-driven client generation. + +### Client generation pipeline + +``` +Rust code with #[codama(...)] attributes + ↓ +program/build.rs → idl/gacha_simple.json + ↓ +scripts/generate-clients.ts + ↓ +clients/{typescript,rust}/src/generated/ (gitignored; re-exported from src/index.ts / lib.rs) +``` + +### Program + +- `program/src/lib.rs` — declares the program ID, wires modules +- `program/src/gacha.rs` — pure logic: `select_tier`, `derive_alpha`, `format_hex`, prize constants + metadata keys (host unit-tested) +- `program/src/instructions/` — `init_pool`, `buy_pull`, `settle_and_distribute`, `refund_pull`, `withdraw_fees`, `emit_event` (self-CPI target) + `helpers/` (`checks`, `account`, `prize_nft` — the Token-2022 NFT mint + metadata CPIs) +- `program/src/state/` — `Pool`, `Pull` PDA structs + `Vault` / `PrizeMint` markers + `common.rs` (discriminator, PDA derivation) +- `program/src/event_engine.rs` — Anchor-compatible self-CPI event emission +- `program/src/events/` — one event struct per state-changing instruction +- `program/src/errors.rs` — error codes (100s generic / 200s pool / 300s pull / 400s settle / 500s vault / 600s event); codes are stable, gaps from the sibling's removed cc-vrf errors are intentional +- `program/src/tests.rs` — host unit tests for the pure logic + +### Accounts + +- **Pool** — PDA `["pool", admin]`. One machine per admin: `operator` (fixed at + init; doubles as the ECVRF public key), `entry_fee`, `settle_deadline_slots`, + `tier_count`, fixed `weights`, monotonic `pulls_count`, and `pending_pulls` + (open refund liabilities). +- **Pull** — PDA `["pull", pool, buyer, index_le]`. One per _pending_ pull: + `client_seed`, `alpha` (= `SHA-256(pull || client_seed)`), `requested_slot`. + The account existing ⇔ the pull is pending; both settle and refund close it + (rent back to the buyer). No status byte, no stored `beta`. +- **Vault** — program-owned, zero-data PDA `["vault", admin]` that escrows entry + fees; invariant: balance ≥ rent floor + `pending_pulls × entry_fee`. +- **PrizeMint** — Token-2022 mint PDA `["mint", pull]`, created at settle: + decimals 0, supply 1, mint authority discarded, `MetadataPointer` pointing at + itself, `TokenMetadata` with `additional_metadata`: + `rarity`, `pull`, `client_seed`, `beta`, `proof` (all but `rarity` lowercase + hex; `alpha` is omitted because it is derivable from `pull` + `client_seed`). + Its existence doubles as the once-only settle guard. + +### Lifecycle + +`init_pool` (admin sets tiers, fee, deadline, operator) → +`buy_pull` (buyer pays fee + pull rent, supplies `client_seed`) → +either `settle_and_distribute` (operator: tier selection + prize NFT mint to the +buyer + pull close, ~82k CU) or, past the deadline, `refund_pull` (buyer: fee + +rent back, pull closed). `withdraw_fees` (admin) drains settled revenue only. + +Unlike the sibling, settle and claim are a single instruction: without the ~10 +Light passthrough accounts and 129-byte validity proof, the whole flow fits in +one ~615-byte transaction with 11 accounts. + +### Testing layers + +- `tests/integration-tests` — LiteSVM: the **entire** lifecycle including the + settle-and-mint happy path (the sibling needs light-program-test + a local + gnark prover for that). PDAs are derived through `gacha-simple-client`'s + generated `find_pda` helpers, so a seed the IDL gets wrong fails the suite + rather than shipping to clients. Requires `just generate-clients` first — + hence the recipe dependency. The settle happy path also replays the full + provenance verification from the minted metadata. The program never verifies + the ECVRF proof on-chain, so tests pass arbitrary proof bytes. +- `clients/typescript/test` — parity fixtures (pinned against the Rust unit + tests), real ECVRF prove/verify round-trips, forged-reveal detection, and + `verifyPrizeProvenance` acceptance/tampering cases. +- `CU_REPORT=1 cargo test -p tests-gacha-simple` writes per-instruction minimum + CU to `cu_report.md` (settle_and_distribute ≈ 82k of the 200k default). + +## Conventions + +- **Pinocchio, not Anchor**: use `pinocchio::AccountView`, `Address`, `ProgramResult`. +- **Packed state**: `#[repr(C, packed)]`, byte-0 discriminator, zero-copy `transmute`. + Never take a reference to a packed field whose type has alignment > 1 (u32/u64 + arrays) — copy the field into a local first. +- **Foreign CPIs are hand-serialized**: SPL interface programs (token-metadata) + take an 8-byte `SplDiscriminate` hash + borsh args. Program IDs and + discriminators are constants next to the builder that uses them. +- **No `mod.rs` business logic**: module declarations and re-exports only. +- **No code comments** for logic — prefer clear names; use `///` doc comments. +- **Codama attributes drive IDL**: array field types must use a **literal** size + (`[u32; 8]`, not `[u32; MAX_TIERS]`), and Codama cannot express arrays of custom + structs — hence primitive tier arrays. `just generate-idl && git diff` catches drift. +- **Cross-language parity**: `select_tier`/`selectTier` and + `derive_alpha`/`pullAlpha` must stay byte-for-byte identical; both pairs are + pinned by shared fixtures in their respective test suites. The fixtures are + also byte-identical to `../pinocchio`'s — the two programs share the same + draw semantics by design. + +When extending: keep `#[codama(...)]` attributes in sync, emit an event per new +instruction, and add an integration test per instruction in `tests/integration-tests/`. + +## Program ID + +`2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS` (keypair in `keys/`, gitignored) diff --git a/games/gacha/pinocchio-simple/Cargo.toml b/games/gacha/pinocchio-simple/Cargo.toml new file mode 100644 index 000000000..615d1f9d0 --- /dev/null +++ b/games/gacha/pinocchio-simple/Cargo.toml @@ -0,0 +1,44 @@ +[workspace] +members = [ + "clients/rust", + "program", + "tests/integration-tests", +] +resolver = "2" + +[workspace.package] +version = "0.1.0" +edition = "2021" +license = "MIT" +repository = "https://github.com/solana-foundation/program-examples" + +[workspace.metadata.cli] +solana = "3.1.10" + +[workspace.lints.rust] +unused_imports = "deny" +dead_code = "warn" +unused_variables = "warn" +deprecated = "warn" +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(target_os, values("solana"))', +] } + +[workspace.dependencies] +codama = "=0.9.3" +const-crypto = "0.3" +pinocchio = { version = "0.11.1", features = ["cpi", "copy"] } +pinocchio-associated-token-account = "0.4.0" +pinocchio-system = "0.6.1" +pinocchio-token-2022 = "0.3.1" +serde_json = "1" +solana-address = { version = "2", features = ["curve25519"] } +solana-sha256-hasher = { version = "3", default-features = false, features = ["sha2"] } +solana-security-txt = "1.1.3" +thiserror = { version = "2", default-features = false } + +[profile.release] +overflow-checks = true +opt-level = 3 +lto = true +codegen-units = 1 diff --git a/games/gacha/pinocchio-simple/README.md b/games/gacha/pinocchio-simple/README.md new file mode 100644 index 000000000..bec58e865 --- /dev/null +++ b/games/gacha/pinocchio-simple/README.md @@ -0,0 +1,101 @@ +# Gacha Simple (Pinocchio) + +A provably-fair **gacha** (loot-box / pack-pull) program on Solana with a +**self-certifying prize NFT** — the simplified sibling of +[`../pinocchio`](../pinocchio), with the cc-vrf CPI and Light Protocol dependency +removed. Configure a pool of fixed-weight reward tiers, take an entry fee, reveal +each pull with a verifiable random function (VRF), and mint each prize as a +Token-2022 NFT whose metadata carries its `rarity` **and the full reveal +provenance** — everything needed to verify the draw from the mint account alone. + +Built with **Pinocchio** (`no_std`) and **Codama**-generated TypeScript + Rust clients. + +## Randomness: RFC 9381 ECVRF, verified off-chain, evidenced in the NFT + +1. A buyer opens a pull with 32 random bytes of `client_seed`; the VRF input is + `alpha = SHA-256(pull_address || client_seed)`. Buyer entropy makes every + outcome unpredictable — even to the operator — before the buy lands. (A merely + _fixed_ alpha is not enough: `beta = VRF(operator_key, alpha)` is + deterministic, so a predictable alpha lets the operator precompute outcomes.) +2. The pool's operator computes `beta = VRF(alpha)` off-chain and submits + `settle_and_distribute`, which expands `beta` into a fixed-weight tier + selection (odds independent of settle order, by construction), mints the prize + NFT straight to the buyer with `rarity`, `pull`, `client_seed`, `beta`, and + `proof` in its Token-2022 metadata, and closes the pull (rent back to the + buyer). +3. Anyone verifies a prize from live accounts alone — no transaction-history + lookup. The mint's metadata `update_authority` is the pool PDA, so the NFT + names its pool; the pool account supplies the operator key and tier weights. + Recompute `alpha = SHA-256(pull || client_seed)`, check the proof with + [`@collectorcrypt/ecvrf`](https://www.npmjs.com/package/@collectorcrypt/ecvrf) + against `pool.operator`, and reproduce the tier with `selectTier` — the + client ships this as `verifyPrizeProvenance`. +4. If the operator never reveals, the buyer reclaims fee + rent with + `refund_pull` after the pool's deadline; the admin can only ever withdraw + settled revenue. + +On-chain the program trusts the operator's signature; cheating is _detectable_ +off-chain rather than _prevented_ on-chain. One reveal per pull is structural: +pull addresses are seeded by a monotonic pool index, the prize mint is a PDA of +the pull that can only be created once, and the pull closes at settle. See +`CLAUDE.md` for the full trust model and the comparison with the cc-vrf/Light +variant next door. + +## Layout + +| Path | What | +| -------------------------- | ------------------------------------------------------------------------------------- | +| `program/` | Pinocchio on-chain program (`gacha-simple-program`) | +| `program/src/gacha.rs` | Pure `select_tier` + `derive_alpha` + `format_hex` (host unit-tested) | +| `tests/integration-tests/` | LiteSVM integration tests — including the full settle + mint happy path | +| `clients/typescript/` | Codama TS client + `selectTier`/`pullAlpha`/ECVRF/provenance (`@solana/gacha-simple`) | +| `clients/rust/` | Codama Rust client (`gacha-simple-client`) | +| `scripts/` | Client generation, pool setup, buy, operator crank, off-chain demo | +| `idl/` | Committed Codama IDL | + +## Quick start + +```bash +just setup # install deps (needs pnpm, cargo, solana-keygen) +just build # program .so → IDL → clients +just test # unit + integration + client tests +just demo # off-chain operator/verifier walkthrough (no RPC) +``` + +No prover, no Photon RPC, no program dumps — the entire lifecycle, including the +settle-and-mint happy path, runs in LiteSVM. + +## The program + +**Accounts** + +- **Pool** `["pool", admin]` — one machine per admin: operator, entry fee, settle + deadline, up to 8 fixed tier weights, pull and pending counters. +- **Pull** `["pull", pool, buyer, index]` — one pending pull: `client_seed`, + `alpha`, `requested_slot`. The account existing _is_ the pending state; settle + and refund both close it. +- **Vault** `["vault", admin]` — escrows entry fees; always covers pending refunds. +- **Prize mint** `["mint", pull]` — Token-2022 NFT: decimals 0, supply 1, + metadata in the mint itself with `rarity` + `pull` + `client_seed` + `beta` + + `proof` (hex) in `additional_metadata`, and the pool PDA as metadata + `update_authority` (the link verifiers follow to the operator key and + weights). Its existence doubles as the once-only settle guard. + +**Instructions** + +- `init_pool` — admin configures tiers, fee, deadline, operator; creates pool + vault. +- `buy_pull` — buyer pays the entry fee (plus pull rent) and commits a pending + pull with their `client_seed`. +- `settle_and_distribute` — operator reveals `beta` + proof; the program selects + the tier, mints the self-certifying prize NFT to the buyer, and closes the pull. +- `refund_pull` — buyer reclaims fee + rent once the settle deadline passes. +- `withdraw_fees` — admin withdraws settled revenue (never pending escrow). + +The provenance metadata costs ~0.007 SOL of mint rent per settle, paid by the +operator — the price of an NFT that proves its own draw. + +Edit the program, then run `just generate-clients` to regenerate the IDL and clients. + +## License + +MIT diff --git a/games/gacha/pinocchio-simple/clients/rust/Cargo.toml b/games/gacha/pinocchio-simple/clients/rust/Cargo.toml new file mode 100644 index 000000000..b299661e0 --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/rust/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "gacha-simple-client" +version = "0.1.0" +edition = "2021" +description = "Rust client for the Gacha Simple Solana program" +license = { workspace = true } +repository = { workspace = true } + +[dependencies] +borsh = { version = "1.6", features = ["derive"] } +num-derive = "0.4" +num-traits = "0.2" +thiserror = "2.0" + +solana-account = { version = "~3.4", optional = true } +solana-account-info = "~3.1" +solana-address = { version = "2.6.0", features = ["borsh", "copy", "curve25519", "decode"] } +solana-cpi = "~3.1" +solana-instruction = { version = "~3.4", features = ["borsh"] } +solana-program-error = "~3.0" +solana-rpc-client = { version = "3.1.8", optional = true } + +serde = { version = "1.0", features = ["derive"], optional = true } +serde_with = { version = "3.20", optional = true } + +[features] +default = [] +serde = ["dep:serde", "dep:serde_with"] +fetch = ["dep:solana-account", "dep:solana-rpc-client"] diff --git a/games/gacha/pinocchio-simple/clients/rust/src/lib.rs b/games/gacha/pinocchio-simple/clients/rust/src/lib.rs new file mode 100644 index 000000000..5b07d6ddc --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/rust/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(warnings)] +#![allow(unused_imports)] + +pub mod generated; +pub use generated::*; + +pub use generated::accounts::*; +pub use generated::errors::*; +pub use generated::programs::*; diff --git a/games/gacha/pinocchio-simple/clients/typescript/package.json b/games/gacha/pinocchio-simple/clients/typescript/package.json new file mode 100644 index 000000000..b814fdb6e --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/typescript/package.json @@ -0,0 +1,38 @@ +{ + "name": "@solana/gacha-simple", + "version": "0.1.0", + "description": "TypeScript SDK and @solana/kit plugin for the Gacha Simple (provably-fair pack-pull) Solana program.", + "type": "module", + "license": "MIT", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "clean": "rm -rf dist", + "clean:generated": "rm -rf src/generated", + "test": "tsx --test test/*.test.ts" + }, + "dependencies": { + "@collectorcrypt/ecvrf": "^0.1.1", + "@noble/hashes": "^2.2.0", + "@solana/program-client-core": "^7.0.0" + }, + "peerDependencies": { + "@solana/kit": "^7.0.0" + }, + "devDependencies": { + "@solana/kit": "^7.0.0", + "@types/node": "^25.7.0", + "tsup": "^8.3.0", + "tsx": "^4.22.0", + "typescript": "^5.7.0" + } +} diff --git a/games/gacha/pinocchio-simple/clients/typescript/src/constants.ts b/games/gacha/pinocchio-simple/clients/typescript/src/constants.ts new file mode 100644 index 000000000..1b33a2864 --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/typescript/src/constants.ts @@ -0,0 +1,21 @@ +/** Maximum number of reward tiers a pool can define. */ +export const MAX_TIERS = 8; + +/** RFC 9381 ciphersuite used by the operator's ECVRF. */ +export const ECVRF_SUITE = 'ECVRF-EDWARDS25519-SHA512-TAI'; + +/** + * Rarity label per tier index, mirrored from the on-chain `RARITY_LABELS`. + * Recorded in each prize NFT's Token-2022 metadata under the `"rarity"` key. + */ +export const RARITY_LABELS = ['common', 'uncommon', 'rare', 'epic', 'legendary', 'mythic', 'exotic', 'divine'] as const; + +/** + * `additional_metadata` keys of the prize NFT, mirrored from the on-chain + * constants. All values except `rarity` are lowercase hex. + */ +export const METADATA_RARITY_KEY = 'rarity'; +export const METADATA_PULL_KEY = 'pull'; +export const METADATA_CLIENT_SEED_KEY = 'client_seed'; +export const METADATA_BETA_KEY = 'beta'; +export const METADATA_PROOF_KEY = 'proof'; diff --git a/games/gacha/pinocchio-simple/clients/typescript/src/gacha.ts b/games/gacha/pinocchio-simple/clients/typescript/src/gacha.ts new file mode 100644 index 000000000..462fe12f6 --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/typescript/src/gacha.ts @@ -0,0 +1,177 @@ +import { generateKeyPair, proveVRF, publicKeyFromSeed, verifyVRF, vrfProofToHash } from '@collectorcrypt/ecvrf'; +import { sha256 } from '@noble/hashes/sha2.js'; +import { type Address, getAddressEncoder } from '@solana/kit'; + +import { MAX_TIERS, RARITY_LABELS } from './constants.js'; + +/** + * The VRF input for a pull: `SHA-256(pull_address || client_seed)`. + * + * Byte-for-byte mirror of the on-chain `derive_alpha`. Binding the buyer's + * `clientSeed` (32 random bytes generated client-side at buy time) makes alpha + * unpredictable to the operator before the buy lands — a fixed alpha alone + * would let the operator precompute every outcome. Verifiers recompute this + * from the `PullRequestedEvent` (or the pull account) to confirm the operator + * did not choose the VRF input. + */ +export function pullAlpha(pull: Address, clientSeed: Uint8Array): Uint8Array { + return pullAlphaBytes(new Uint8Array(getAddressEncoder().encode(pull)), clientSeed); +} + +/** + * Same as {@link pullAlpha}, taking the pull address as raw bytes — the form it + * appears in (hex) in the prize NFT's metadata. + */ +export function pullAlphaBytes(pullBytes: Uint8Array, clientSeed: Uint8Array): Uint8Array { + const input = new Uint8Array(pullBytes.length + clientSeed.length); + input.set(pullBytes, 0); + input.set(clientSeed, pullBytes.length); + return sha256(input); +} + +/** + * Selects a reward tier from a VRF output, weighted by the pool's fixed tier + * weights. + * + * Byte-for-byte mirror of the on-chain `select_tier`: the first 16 bytes of `beta` + * are read as a little-endian u128 and reduced modulo the total weight, then the + * target walks the tiers in order. Weights are fixed at pool init, so every pull + * faces identical odds regardless of settle order. Throws when the total weight + * is zero. + */ +export function selectTier(beta: Uint8Array, weights: readonly number[], tierCount: number): number { + const count = Math.min(tierCount, MAX_TIERS); + + let total = 0n; + for (let i = 0; i < count; i++) { + total += BigInt(weights[i] ?? 0); + } + if (total === 0n) { + throw new Error('invalid tier config: total weight is zero'); + } + + let seed = 0n; + for (let i = 0; i < 16; i++) { + seed |= BigInt(beta[i] ?? 0) << BigInt(8 * i); + } + let target = seed % total; + + for (let i = 0; i < count; i++) { + const weight = BigInt(weights[i] ?? 0); + if (target < weight) { + return i; + } + target -= weight; + } + + throw new Error('invalid tier config: total weight is zero'); +} + +/** The ECVRF output and proof produced by an operator for a pull's `alpha`. */ +export interface PullReveal { + beta: Uint8Array; + proof: Uint8Array; +} + +/** + * Operator side: produces the 80-byte proof and 64-byte `beta` for a pull's `alpha`. + * `operatorSecretKey` is the 32-byte Ed25519 seed registered as the pool operator. + */ +export function provePull(operatorSecretKey: Uint8Array, alpha: Uint8Array): PullReveal { + const { proof } = proveVRF(operatorSecretKey, alpha); + const beta = vrfProofToHash(proof); + return { beta, proof }; +} + +/** + * Verifier side: checks that `proof` is a valid ECVRF proof of `beta` for `alpha` + * under the operator's public key. This is the off-chain verification the program + * cannot perform on-chain. + */ +export function verifyPull(operatorPublicKey: Uint8Array, alpha: Uint8Array, proof: Uint8Array): boolean { + return verifyVRF(operatorPublicKey, alpha, proof); +} + +/** + * The reveal provenance a prize NFT carries in its Token-2022 + * `additional_metadata`, hex-decoded. Together with the pool account the mint + * names via its metadata update authority (which supplies the operator key, + * weights, and tier count), this is everything needed to verify the reveal — + * no transaction-history lookup required. + */ +export interface PrizeProvenance { + /** The ECVRF output, decoded from the `beta` key. */ + beta: Uint8Array; + /** The buyer's entropy, decoded from the `client_seed` key. */ + clientSeed: Uint8Array; + /** The 80-byte ECVRF proof, decoded from the `proof` key. */ + proof: Uint8Array; + /** The pull address, decoded from the `pull` key. */ + pull: Uint8Array; + /** Rarity label recorded under the `rarity` key. */ + rarity: string; +} + +/** Decodes a lowercase-hex metadata value into bytes. */ +export function decodeHexField(hex: string): Uint8Array { + if (hex.length % 2 !== 0 || !/^[0-9a-f]*$/.test(hex)) { + throw new Error(`invalid hex metadata value: ${hex}`); + } + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(hex.slice(2 * i, 2 * i + 2), 16); + } + return bytes; +} + +/** + * Verifies a prize NFT's reveal end-to-end from its metadata provenance: + * recomputes `alpha = SHA-256(pull || client_seed)`, checks the ECVRF proof + * against the pool's operator key, checks `beta` matches the proof, and + * reproduces the tier from the pool's weights to confirm the recorded rarity. + * + * `operatorPublicKey`, `weights`, and `tierCount` come from the pool account + * the mint names via its metadata update authority. + */ +export function verifyPrizeProvenance( + provenance: PrizeProvenance, + operatorPublicKey: Uint8Array, + weights: readonly number[], + tierCount: number, +): boolean { + const alpha = pullAlphaBytes(provenance.pull, provenance.clientSeed); + if (!verifyVRF(operatorPublicKey, alpha, provenance.proof)) { + return false; + } + const beta = vrfProofToHash(provenance.proof); + if (beta.length !== provenance.beta.length || !beta.every((b, i) => b === provenance.beta[i])) { + return false; + } + const tier = selectTier(provenance.beta, weights, tierCount); + return RARITY_LABELS[tier] === provenance.rarity; +} + +/** + * Verifies a prize NFT's reveal end-to-end from caller-fetched accounts, + * binding the pool configuration to the pool the mint actually names: + * `mintUpdateAuthority` (read from the mint's token metadata) must equal + * `poolAddress` (the address the pool account was fetched from), and the + * operator key, weights, and tier count are taken from that pool's decoded + * data. Pool state is fixed at init, so the values cannot be stale. + * + * `pool` accepts the generated `Pool` account data shape directly. + */ +export function verifyPrizeAgainstPool( + provenance: PrizeProvenance, + mintUpdateAuthority: Address, + poolAddress: Address, + pool: { operator: Address; tierCount: number; weights: readonly number[] }, +): boolean { + if (mintUpdateAuthority !== poolAddress) { + return false; + } + const operatorKey = new Uint8Array(getAddressEncoder().encode(pool.operator)); + return verifyPrizeProvenance(provenance, operatorKey, pool.weights, pool.tierCount); +} + +export { generateKeyPair, proveVRF, publicKeyFromSeed, verifyVRF, vrfProofToHash }; diff --git a/games/gacha/pinocchio-simple/clients/typescript/src/index.ts b/games/gacha/pinocchio-simple/clients/typescript/src/index.ts new file mode 100644 index 000000000..295dadb4e --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/typescript/src/index.ts @@ -0,0 +1,6 @@ +// Re-export everything generated (instruction builders, find*Pda, codecs, account types, program). +export * from './generated/index.js'; +// Hand-written constants. +export * from './constants.js'; +// Hand-written gacha helpers: alpha derivation and tier selection (mirror the on-chain logic), ECVRF operator/verify wrappers. +export * from './gacha.js'; diff --git a/games/gacha/pinocchio-simple/clients/typescript/test/gacha.test.ts b/games/gacha/pinocchio-simple/clients/typescript/test/gacha.test.ts new file mode 100644 index 000000000..4d5533918 --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/typescript/test/gacha.test.ts @@ -0,0 +1,189 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import type { Address } from '@solana/kit'; + +import { getAddressDecoder } from '@solana/kit'; + +import { RARITY_LABELS } from '../src/constants.js'; +import { + decodeHexField, + generateKeyPair, + provePull, + pullAlpha, + selectTier, + verifyPrizeAgainstPool, + verifyPrizeProvenance, + verifyPull, +} from '../src/gacha.js'; + +/** Builds a `beta` whose first 16 bytes encode `value` as a little-endian u128. */ +function betaFrom(value: bigint): Uint8Array { + const beta = new Uint8Array(64); + let v = value; + for (let i = 0; i < 16; i++) { + beta[i] = Number(v & 0xffn); + v >>= 8n; + } + return beta; +} + +// These fixtures mirror the on-chain `select_tier` host unit tests exactly, so the +// two implementations are pinned to the same weighted-bucket behavior. +test('selectTier matches the on-chain weighted buckets', () => { + const weights = [60, 30, 10]; + assert.equal(selectTier(betaFrom(0n), weights, 3), 0); + assert.equal(selectTier(betaFrom(59n), weights, 3), 0); + assert.equal(selectTier(betaFrom(60n), weights, 3), 1); + assert.equal(selectTier(betaFrom(89n), weights, 3), 1); + assert.equal(selectTier(betaFrom(90n), weights, 3), 2); + assert.equal(selectTier(betaFrom(99n), weights, 3), 2); +}); + +test('selectTier wraps via modulo', () => { + const weights = [60, 30, 10]; + assert.equal(selectTier(betaFrom(100n), weights, 3), 0); + assert.equal(selectTier(betaFrom(190n), weights, 3), 2); +}); + +test('selectTier respects tierCount', () => { + assert.equal(selectTier(betaFrom(99n), [60, 40, 10], 2), 1); +}); + +test('selectTier throws on zero total weight', () => { + assert.throws(() => selectTier(betaFrom(0n), [0, 0, 0], 3)); +}); + +// Pinned cross-language fixture: the on-chain `derive_alpha` host unit test uses +// the same inputs and digest, keeping the two implementations byte-identical. +// The address below is the base58 form of 32 bytes of 0x01. +test('pullAlpha matches the on-chain pinned fixture', () => { + const pull = '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address; + const clientSeed = new Uint8Array(32).fill(2); + const expected = 'f818afd37a6dc3bc92fb44731011277006db4efa6e9023cd7468c02335d22a4d'; + assert.equal(Buffer.from(pullAlpha(pull, clientSeed)).toString('hex'), expected); +}); + +test('pullAlpha depends on both the pull and the client seed', () => { + const pull = '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address; + const base = pullAlpha(pull, new Uint8Array(32).fill(2)); + assert.notDeepEqual(pullAlpha(pull, new Uint8Array(32).fill(3)), base); +}); + +// End-to-end ECVRF: prove a pull's alpha, verify the proof, and expand beta. +test('ECVRF prove/verify round-trip drives a tier selection', () => { + const { sk, pk } = generateKeyPair(); + const alpha = pullAlpha('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address, new Uint8Array(32).fill(7)); + + const { proof, beta } = provePull(sk, alpha); + assert.equal(beta.length, 64); + assert.equal(verifyPull(pk, alpha, proof), true); + + const tier = selectTier(beta, [70, 25, 5], 3); + assert.ok(tier >= 0 && tier < 3); +}); + +// The guarantee the whole design rests on: a reveal the operator did not honestly +// derive from `alpha` fails off-chain verification. The program cannot check the +// proof on-chain, so this detection is what keeps a cheating operator accountable. +test('forged reveals fail verification', () => { + const { sk, pk } = generateKeyPair(); + const alpha = pullAlpha('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address, new Uint8Array(32).fill(9)); + const { proof } = provePull(sk, alpha); + + // Any tampering with the proof bytes invalidates it. + for (const i of [0, 40, 79]) { + const tampered = proof.slice(); + tampered[i] ^= 0x01; + assert.equal(verifyPull(pk, alpha, tampered), false); + } + + // A proof for a different alpha does not verify (no reveal reuse). + const otherAlpha = pullAlpha('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address, new Uint8Array(32).fill(10)); + assert.equal(verifyPull(pk, otherAlpha, proof), false); + + // A proof from a key that is not the registered operator does not verify. + const { sk: rogueSk } = generateKeyPair(); + const { proof: rogueProof } = provePull(rogueSk, alpha); + assert.equal(verifyPull(pk, alpha, rogueProof), false); +}); + +test('decodeHexField round-trips and rejects malformed values', () => { + assert.deepEqual(decodeHexField('00abff1c'), new Uint8Array([0x00, 0xab, 0xff, 0x1c])); + assert.deepEqual(decodeHexField(''), new Uint8Array(0)); + assert.throws(() => decodeHexField('abc')); // odd length + assert.throws(() => decodeHexField('zz')); // non-hex + assert.throws(() => decodeHexField('AB')); // uppercase — the program writes lowercase +}); + +// The self-certifying-NFT guarantee: the metadata a prize mint carries is enough +// to verify the whole reveal off-chain, with no transaction-history lookup. +test('verifyPrizeProvenance accepts an honest reveal and rejects tampering', () => { + const { sk, pk } = generateKeyPair(); + const weights = [70, 25, 5]; + const pullBytes = new Uint8Array(32).fill(1); + const clientSeed = new Uint8Array(32).fill(2); + + // '4vJ9…' is the base58 form of 32 bytes of 0x01 — the same pull as `pullBytes`. + const alpha = pullAlpha('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address, clientSeed); + const { proof, beta } = provePull(sk, alpha); + const tier = selectTier(beta, weights, 3); + + const provenance = { + beta, + clientSeed, + proof, + pull: pullBytes, + rarity: RARITY_LABELS[tier]!, + }; + assert.equal(verifyPrizeProvenance(provenance, pk, weights, 3), true); + + // A mislabeled rarity is caught. + const wrongRarity = { ...provenance, rarity: RARITY_LABELS[(tier + 1) % 3]! }; + assert.equal(verifyPrizeProvenance(wrongRarity, pk, weights, 3), false); + + // A swapped beta (operator lying about the output) is caught. + const wrongBeta = { ...provenance, beta: new Uint8Array(64).fill(9) }; + assert.equal(verifyPrizeProvenance(wrongBeta, pk, weights, 3), false); + + // A tampered proof is caught. + const tamperedProof = proof.slice(); + tamperedProof[0] ^= 0x01; + assert.equal(verifyPrizeProvenance({ ...provenance, proof: tamperedProof }, pk, weights, 3), false); + + // A different operator key is caught. + const { pk: roguePk } = generateKeyPair(); + assert.equal(verifyPrizeProvenance(provenance, roguePk, weights, 3), false); +}); + +// The binding step: pool configuration only counts if it comes from the pool +// the mint names via its metadata update authority. +test('verifyPrizeAgainstPool binds the configuration to the named pool', () => { + const { sk, pk } = generateKeyPair(); + const weights = [70, 25, 5]; + const clientSeed = new Uint8Array(32).fill(2); + const alpha = pullAlpha('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address, clientSeed); + const { proof, beta } = provePull(sk, alpha); + const provenance = { + beta, + clientSeed, + proof, + pull: new Uint8Array(32).fill(1), + rarity: RARITY_LABELS[selectTier(beta, weights, 3)]!, + }; + + const poolAddress = '8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR' as Address; + const pool = { operator: getAddressDecoder().decode(pk), tierCount: 3, weights }; + + assert.equal(verifyPrizeAgainstPool(provenance, poolAddress, poolAddress, pool), true); + + // A mint whose update authority is not the supplied pool is rejected, even + // with otherwise-valid configuration. + const otherPool = '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' as Address; + assert.equal(verifyPrizeAgainstPool(provenance, otherPool, poolAddress, pool), false); + + // A pool whose operator did not produce the reveal is rejected. + const { pk: roguePk } = generateKeyPair(); + const roguePool = { ...pool, operator: getAddressDecoder().decode(roguePk) }; + assert.equal(verifyPrizeAgainstPool(provenance, poolAddress, poolAddress, roguePool), false); +}); diff --git a/games/gacha/pinocchio-simple/clients/typescript/tsconfig.json b/games/gacha/pinocchio-simple/clients/typescript/tsconfig.json new file mode 100644 index 000000000..e668b6870 --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/typescript/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["node"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/games/gacha/pinocchio-simple/clients/typescript/tsup.config.ts b/games/gacha/pinocchio-simple/clients/typescript/tsup.config.ts new file mode 100644 index 000000000..2d1438ec4 --- /dev/null +++ b/games/gacha/pinocchio-simple/clients/typescript/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm', 'cjs'], + dts: true, + clean: true, + splitting: false, + sourcemap: true, + shims: true, +}); diff --git a/games/gacha/pinocchio-simple/eslint.config.mjs b/games/gacha/pinocchio-simple/eslint.config.mjs new file mode 100644 index 000000000..f2264bf76 --- /dev/null +++ b/games/gacha/pinocchio-simple/eslint.config.mjs @@ -0,0 +1,36 @@ +import solanaConfig from '@solana/eslint-config-solana'; + +export default [ + ...solanaConfig, + { + files: ['scripts/**/*.ts'], + rules: { + '@typescript-eslint/no-base-to-string': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/unbound-method': 'off', + }, + }, + { + ignores: [ + '**/.claude/**', + '**/.remember/**', + '**/.git/**', + '**/dist/**', + '**/node_modules/**', + '**/target/**', + '**/generated/**', + 'clients/typescript/src/generated/**', + 'clients/typescript/test/**', + 'clients/typescript/*.config.ts', + 'eslint.config.mjs', + '**/*.mjs', + ], + }, +]; diff --git a/games/gacha/pinocchio-simple/idl/gacha_simple.json b/games/gacha/pinocchio-simple/idl/gacha_simple.json new file mode 100644 index 000000000..be8c4d92e --- /dev/null +++ b/games/gacha/pinocchio-simple/idl/gacha_simple.json @@ -0,0 +1,1360 @@ +{ + "additionalPrograms": [], + "kind": "rootNode", + "program": { + "accounts": [ + { + "data": { + "fields": [], + "kind": "structTypeNode" + }, + "kind": "accountNode", + "name": "eventAuthority", + "pda": { + "kind": "pdaLinkNode", + "name": "eventAuthority" + } + }, + { + "data": { + "fields": [ + { + "kind": "structFieldTypeNode", + "name": "discriminator", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "bump", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "tierCount", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "admin", + "type": { + "kind": "publicKeyTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "operator", + "type": { + "kind": "publicKeyTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "entryFee", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "pullsCount", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "pendingPulls", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "settleDeadlineSlots", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "weights", + "type": { + "count": { + "kind": "fixedCountNode", + "value": 8 + }, + "item": { + "endian": "le", + "format": "u32", + "kind": "numberTypeNode" + }, + "kind": "arrayTypeNode" + } + } + ], + "kind": "structTypeNode" + }, + "kind": "accountNode", + "name": "pool", + "pda": { + "kind": "pdaLinkNode", + "name": "pool" + } + }, + { + "data": { + "fields": [], + "kind": "structTypeNode" + }, + "kind": "accountNode", + "name": "prizeMint", + "pda": { + "kind": "pdaLinkNode", + "name": "prizeMint" + } + }, + { + "data": { + "fields": [ + { + "kind": "structFieldTypeNode", + "name": "discriminator", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "bump", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "pool", + "type": { + "kind": "publicKeyTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "buyer", + "type": { + "kind": "publicKeyTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "index", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "clientSeed", + "type": { + "count": { + "kind": "fixedCountNode", + "value": 32 + }, + "item": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + }, + "kind": "arrayTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "alpha", + "type": { + "count": { + "kind": "fixedCountNode", + "value": 32 + }, + "item": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + }, + "kind": "arrayTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "requestedSlot", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + } + ], + "kind": "structTypeNode" + }, + "kind": "accountNode", + "name": "pull", + "pda": { + "kind": "pdaLinkNode", + "name": "pull" + } + }, + { + "data": { + "fields": [], + "kind": "structTypeNode" + }, + "kind": "accountNode", + "name": "vault", + "pda": { + "kind": "pdaLinkNode", + "name": "vault" + } + } + ], + "constants": [], + "definedTypes": [ + { + "kind": "definedTypeNode", + "name": "buyPullData", + "type": { + "fields": [ + { + "kind": "structFieldTypeNode", + "name": "clientSeed", + "type": { + "count": { + "kind": "fixedCountNode", + "value": 32 + }, + "item": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + }, + "kind": "arrayTypeNode" + } + } + ], + "kind": "structTypeNode" + } + }, + { + "kind": "definedTypeNode", + "name": "initPoolData", + "type": { + "fields": [ + { + "kind": "structFieldTypeNode", + "name": "operator", + "type": { + "kind": "publicKeyTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "entryFee", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "settleDeadlineSlots", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "tierCount", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "weights", + "type": { + "count": { + "kind": "fixedCountNode", + "value": 8 + }, + "item": { + "endian": "le", + "format": "u32", + "kind": "numberTypeNode" + }, + "kind": "arrayTypeNode" + } + } + ], + "kind": "structTypeNode" + } + }, + { + "kind": "definedTypeNode", + "name": "settleAndDistributeData", + "type": { + "fields": [ + { + "kind": "structFieldTypeNode", + "name": "proof", + "type": { + "count": { + "kind": "fixedCountNode", + "value": 80 + }, + "item": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + }, + "kind": "arrayTypeNode" + } + }, + { + "kind": "structFieldTypeNode", + "name": "beta", + "type": { + "count": { + "kind": "fixedCountNode", + "value": 64 + }, + "item": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + }, + "kind": "arrayTypeNode" + } + } + ], + "kind": "structTypeNode" + } + }, + { + "kind": "definedTypeNode", + "name": "withdrawFeesData", + "type": { + "fields": [ + { + "kind": "structFieldTypeNode", + "name": "amount", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + } + ], + "kind": "structTypeNode" + } + }, + { + "kind": "definedTypeNode", + "name": "accountDiscriminator", + "type": { + "kind": "enumTypeNode", + "size": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + }, + "variants": [ + { + "discriminator": 1, + "kind": "enumEmptyVariantTypeNode", + "name": "pool" + }, + { + "discriminator": 2, + "kind": "enumEmptyVariantTypeNode", + "name": "pull" + } + ] + } + } + ], + "errors": [ + { + "code": 100, + "kind": "errorNode", + "message": "Account must be a signer", + "name": "notSigner" + }, + { + "code": 101, + "kind": "errorNode", + "message": "Account must be writable", + "name": "accountNotWritable" + }, + { + "code": 102, + "kind": "errorNode", + "message": "Expected system program", + "name": "notSystemProgram" + }, + { + "code": 103, + "kind": "errorNode", + "message": "Not enough account keys provided", + "name": "notEnoughAccountKeys" + }, + { + "code": 104, + "kind": "errorNode", + "message": "Invalid instruction", + "name": "invalidInstruction" + }, + { + "code": 105, + "kind": "errorNode", + "message": "Invalid account data", + "name": "invalidAccountData" + }, + { + "code": 106, + "kind": "errorNode", + "message": "Invalid account discriminator", + "name": "invalidAccountDiscriminator" + }, + { + "code": 107, + "kind": "errorNode", + "message": "Arithmetic overflow", + "name": "arithmeticOverflow" + }, + { + "code": 108, + "kind": "errorNode", + "message": "Account is not owned by this program", + "name": "notProgramOwned" + }, + { + "code": 109, + "kind": "errorNode", + "message": "Expected the Token-2022 program", + "name": "notTokenProgram" + }, + { + "code": 110, + "kind": "errorNode", + "message": "Expected the associated token account program", + "name": "notAtaProgram" + }, + { + "code": 200, + "kind": "errorNode", + "message": "Invalid pool PDA derivation", + "name": "invalidPoolPda" + }, + { + "code": 201, + "kind": "errorNode", + "message": "Pool account already exists", + "name": "poolAlreadyExists" + }, + { + "code": 202, + "kind": "errorNode", + "message": "Signer is not the pool admin", + "name": "unauthorized" + }, + { + "code": 203, + "kind": "errorNode", + "message": "Tier configuration is invalid (zero weight)", + "name": "invalidTierConfig" + }, + { + "code": 204, + "kind": "errorNode", + "message": "Tier count is zero or exceeds the maximum", + "name": "tooManyTiers" + }, + { + "code": 205, + "kind": "errorNode", + "message": "Entry fee must be nonzero", + "name": "invalidEntryFee" + }, + { + "code": 206, + "kind": "errorNode", + "message": "Operator must be a nonzero, on-curve key distinct from the admin", + "name": "invalidOperator" + }, + { + "code": 207, + "kind": "errorNode", + "message": "Settle deadline must be nonzero", + "name": "invalidSettleDeadline" + }, + { + "code": 300, + "kind": "errorNode", + "message": "Invalid pull PDA derivation", + "name": "invalidPullPda" + }, + { + "code": 301, + "kind": "errorNode", + "message": "Pull already exists for this index", + "name": "pullAlreadyExists" + }, + { + "code": 303, + "kind": "errorNode", + "message": "Pull does not belong to the provided pool", + "name": "poolMismatch" + }, + { + "code": 304, + "kind": "errorNode", + "message": "Settle deadline has not passed yet", + "name": "refundTooEarly" + }, + { + "code": 305, + "kind": "errorNode", + "message": "Account is not the pull's buyer", + "name": "buyerMismatch" + }, + { + "code": 400, + "kind": "errorNode", + "message": "Signer is not the registered pool operator", + "name": "notOperator" + }, + { + "code": 401, + "kind": "errorNode", + "message": "Invalid prize mint PDA derivation", + "name": "invalidMintPda" + }, + { + "code": 500, + "kind": "errorNode", + "message": "Invalid vault PDA derivation", + "name": "invalidVaultPda" + }, + { + "code": 501, + "kind": "errorNode", + "message": "Withdrawal exceeds the vault balance net of pending-pull liabilities", + "name": "insufficientVaultFunds" + }, + { + "code": 600, + "kind": "errorNode", + "message": "Invalid event authority PDA", + "name": "invalidEventAuthority" + }, + { + "code": 601, + "kind": "errorNode", + "message": "Invalid event data", + "name": "invalidEventData" + } + ], + "events": [], + "instructions": [ + { + "accounts": [ + { + "docs": [ + "Pool admin; funds and owns the pool" + ], + "isSigner": true, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "admin" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "pool" + }, + "seeds": [ + { + "kind": "pdaSeedValueNode", + "name": "admin", + "value": { + "kind": "accountValueNode", + "name": "admin" + } + } + ] + }, + "docs": [ + "The pool PDA being created" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "pool" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "vault" + }, + "seeds": [ + { + "kind": "pdaSeedValueNode", + "name": "admin", + "value": { + "kind": "accountValueNode", + "name": "admin" + } + } + ] + }, + "docs": [ + "Pot vault PDA" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "vault" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "11111111111111111111111111111111" + }, + "docs": [ + "The system program" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "systemProgram" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "eventAuthority" + }, + "seeds": [] + }, + "docs": [ + "The event authority PDA" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "eventAuthority" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS" + }, + "docs": [ + "This program (for self-CPI event emission)" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "selfProgram" + } + ], + "arguments": [ + { + "defaultValue": { + "kind": "numberValueNode", + "number": 0 + }, + "defaultValueStrategy": "omitted", + "kind": "instructionArgumentNode", + "name": "discriminator", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "instructionArgumentNode", + "name": "initPoolData", + "type": { + "kind": "definedTypeLinkNode", + "name": "initPoolData" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + } + ], + "kind": "instructionNode", + "name": "initPool" + }, + { + "accounts": [ + { + "docs": [ + "The buyer opening and paying for a pull" + ], + "isSigner": true, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "buyer" + }, + { + "docs": [ + "The pool being pulled from" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "pool" + }, + { + "docs": [ + "The pull PDA being created" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "pull" + }, + { + "docs": [ + "Pot vault PDA for the pool" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "vault" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "11111111111111111111111111111111" + }, + "docs": [ + "The system program" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "systemProgram" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "eventAuthority" + }, + "seeds": [] + }, + "docs": [ + "The event authority PDA" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "eventAuthority" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS" + }, + "docs": [ + "This program (for self-CPI event emission)" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "selfProgram" + } + ], + "arguments": [ + { + "defaultValue": { + "kind": "numberValueNode", + "number": 1 + }, + "defaultValueStrategy": "omitted", + "kind": "instructionArgumentNode", + "name": "discriminator", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "instructionArgumentNode", + "name": "buyPullData", + "type": { + "kind": "definedTypeLinkNode", + "name": "buyPullData" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + } + ], + "kind": "instructionNode", + "name": "buyPull" + }, + { + "accounts": [ + { + "docs": [ + "Registered VRF operator revealing the pull; funds the mint and ATA rent" + ], + "isSigner": true, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "operator" + }, + { + "docs": [ + "The pool being pulled from; mint and metadata authority" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "pool" + }, + { + "docs": [ + "The pending pull being settled and closed" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "pull" + }, + { + "docs": [ + "The pull's buyer; receives the prize NFT and the pull rent" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "buyer" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "prizeMint" + }, + "seeds": [ + { + "kind": "pdaSeedValueNode", + "name": "pull", + "value": { + "kind": "accountValueNode", + "name": "pull" + } + } + ] + }, + "docs": [ + "Prize mint PDA for the pull" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "mint" + }, + { + "docs": [ + "Buyer's associated token account for the prize mint" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "buyerAta" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "11111111111111111111111111111111" + }, + "docs": [ + "The system program" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "systemProgram" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + }, + "docs": [ + "The Token-2022 program" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "tokenProgram" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + "docs": [ + "The associated token account program" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "ataProgram" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "eventAuthority" + }, + "seeds": [] + }, + "docs": [ + "The event authority PDA" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "eventAuthority" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS" + }, + "docs": [ + "This program (for self-CPI event emission)" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "selfProgram" + } + ], + "arguments": [ + { + "defaultValue": { + "kind": "numberValueNode", + "number": 2 + }, + "defaultValueStrategy": "omitted", + "kind": "instructionArgumentNode", + "name": "discriminator", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "instructionArgumentNode", + "name": "settleAndDistributeData", + "type": { + "kind": "definedTypeLinkNode", + "name": "settleAndDistributeData" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + } + ], + "kind": "instructionNode", + "name": "settleAndDistribute" + }, + { + "accounts": [ + { + "docs": [ + "The pull's buyer reclaiming their entry fee" + ], + "isSigner": true, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "buyer" + }, + { + "docs": [ + "The pool the pull belongs to" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "pool" + }, + { + "docs": [ + "The pending pull being refunded and closed" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "pull" + }, + { + "docs": [ + "Pot vault PDA for the pool" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "vault" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "eventAuthority" + }, + "seeds": [] + }, + "docs": [ + "The event authority PDA" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "eventAuthority" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS" + }, + "docs": [ + "This program (for self-CPI event emission)" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "selfProgram" + } + ], + "arguments": [ + { + "defaultValue": { + "kind": "numberValueNode", + "number": 3 + }, + "defaultValueStrategy": "omitted", + "kind": "instructionArgumentNode", + "name": "discriminator", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + } + ], + "kind": "instructionNode", + "name": "refundPull" + }, + { + "accounts": [ + { + "docs": [ + "Pool admin receiving the fees" + ], + "isSigner": true, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "admin" + }, + { + "docs": [ + "The pool whose fees are withdrawn" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "pool" + }, + { + "docs": [ + "Pot vault PDA for the pool" + ], + "isSigner": false, + "isWritable": true, + "kind": "instructionAccountNode", + "name": "vault" + }, + { + "defaultValue": { + "kind": "pdaValueNode", + "pda": { + "kind": "pdaLinkNode", + "name": "eventAuthority" + }, + "seeds": [] + }, + "docs": [ + "The event authority PDA" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "eventAuthority" + }, + { + "defaultValue": { + "kind": "publicKeyValueNode", + "publicKey": "2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS" + }, + "docs": [ + "This program (for self-CPI event emission)" + ], + "isSigner": false, + "isWritable": false, + "kind": "instructionAccountNode", + "name": "selfProgram" + } + ], + "arguments": [ + { + "defaultValue": { + "kind": "numberValueNode", + "number": 4 + }, + "defaultValueStrategy": "omitted", + "kind": "instructionArgumentNode", + "name": "discriminator", + "type": { + "endian": "le", + "format": "u8", + "kind": "numberTypeNode" + } + }, + { + "kind": "instructionArgumentNode", + "name": "withdrawFeesData", + "type": { + "kind": "definedTypeLinkNode", + "name": "withdrawFeesData" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + } + ], + "kind": "instructionNode", + "name": "withdrawFees" + } + ], + "kind": "programNode", + "name": "gacha_simple", + "pdas": [ + { + "kind": "pdaNode", + "name": "eventAuthority", + "seeds": [ + { + "kind": "constantPdaSeedNode", + "type": { + "encoding": "utf8", + "kind": "stringTypeNode" + }, + "value": { + "kind": "stringValueNode", + "string": "event_authority" + } + } + ] + }, + { + "kind": "pdaNode", + "name": "pool", + "seeds": [ + { + "kind": "constantPdaSeedNode", + "type": { + "encoding": "utf8", + "kind": "stringTypeNode" + }, + "value": { + "kind": "stringValueNode", + "string": "pool" + } + }, + { + "kind": "variablePdaSeedNode", + "name": "admin", + "type": { + "kind": "publicKeyTypeNode" + } + } + ] + }, + { + "kind": "pdaNode", + "name": "prizeMint", + "seeds": [ + { + "kind": "constantPdaSeedNode", + "type": { + "encoding": "utf8", + "kind": "stringTypeNode" + }, + "value": { + "kind": "stringValueNode", + "string": "mint" + } + }, + { + "kind": "variablePdaSeedNode", + "name": "pull", + "type": { + "kind": "publicKeyTypeNode" + } + } + ] + }, + { + "kind": "pdaNode", + "name": "pull", + "seeds": [ + { + "kind": "constantPdaSeedNode", + "type": { + "encoding": "utf8", + "kind": "stringTypeNode" + }, + "value": { + "kind": "stringValueNode", + "string": "pull" + } + }, + { + "kind": "variablePdaSeedNode", + "name": "pool", + "type": { + "kind": "publicKeyTypeNode" + } + }, + { + "kind": "variablePdaSeedNode", + "name": "buyer", + "type": { + "kind": "publicKeyTypeNode" + } + }, + { + "kind": "variablePdaSeedNode", + "name": "index", + "type": { + "endian": "le", + "format": "u64", + "kind": "numberTypeNode" + } + } + ] + }, + { + "kind": "pdaNode", + "name": "vault", + "seeds": [ + { + "kind": "constantPdaSeedNode", + "type": { + "encoding": "utf8", + "kind": "stringTypeNode" + }, + "value": { + "kind": "stringValueNode", + "string": "vault" + } + }, + { + "kind": "variablePdaSeedNode", + "name": "admin", + "type": { + "kind": "publicKeyTypeNode" + } + } + ] + } + ], + "publicKey": "2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS", + "version": "0.1.0" + }, + "standard": "codama", + "version": "1.6.0" +} diff --git a/games/gacha/pinocchio-simple/justfile b/games/gacha/pinocchio-simple/justfile new file mode 100644 index 000000000..0d0aeafbc --- /dev/null +++ b/games/gacha/pinocchio-simple/justfile @@ -0,0 +1,172 @@ +# Gacha Simple - Solana program build automation +# https://github.com/casey/just + +# Use bash for all recipes +set shell := ["bash", "-uc"] + +# Variables +program_dir := "program" +ts_client_dir := "clients/typescript" +idl_file := "idl/gacha_simple.json" +generated_paths := "idl clients/typescript/src/generated clients/rust/src/generated" + +# List available recipes +default: + @just --list + +# ============================================ +# Setup +# ============================================ + +# Install dependencies +setup: + #!/usr/bin/env bash + set -euo pipefail + commands=(pnpm cargo solana-keygen) + for cmd in "${commands[@]}"; do + if ! command -v "$cmd" &>/dev/null; then + echo "Error: $cmd is required but not installed" + exit 1 + fi + done + pnpm install + echo "✓ Setup complete" + +# Print program ID from keypair +program-id: + @sed -n 's/.*declare_id!("\([^"]*\)").*/\1/p' "{{program_dir}}/src/lib.rs" + +# ============================================ +# Build +# ============================================ + +# Build everything (program + client) +build: build-program build-client + +# Compile Solana program to .so +build-program: + cd {{program_dir}} && cargo build-sbf + @echo "✓ Program built" + +# Generate IDL from Rust source +generate-idl: + pnpm run generate-idl + @echo "✓ IDL generated" + +# Generate TypeScript + Rust clients from IDL +generate-clients: generate-idl + pnpm run generate-clients + @echo "✓ Clients generated" + +# Check that committed IDL and generated clients are current +check-generated: generate-clients + #!/usr/bin/env bash + set -euo pipefail + if ! git diff --quiet -- {{generated_paths}} || [[ -n "$(git ls-files --others --exclude-standard -- {{generated_paths}})" ]]; then + echo "Error: IDL or generated clients are out of date" + echo "Run: just generate-clients" + git status --short -- {{generated_paths}} + exit 1 + fi + echo "✓ IDL and generated clients are up-to-date" + +# Build TypeScript client +build-client: generate-clients + cd {{ts_client_dir}} && pnpm run build + @echo "✓ TypeScript client built" + +# Build the generated Rust client +build-rust-client: generate-clients + cargo build -p gacha-simple-client + @echo "✓ Rust client built" + +# ============================================ +# Test +# ============================================ + +# Run all tests (unit + integration + client) +test: unit-test integration-test client-test + +# Run Rust unit tests +unit-test: + cargo test -p gacha-simple-program + +# Run LiteSVM integration tests (the tests derive PDAs through the generated +# Rust client, so the clients are generated before the .so is built) +integration-test: generate-clients build-program + cargo test -p tests-gacha-simple + +# Run TypeScript client tests +client-test: build-client + cd {{ts_client_dir}} && pnpm test + +# ============================================ +# Demo +# ============================================ + +# Run the off-chain operator/verifier demo (no RPC required) +demo: build-client + pnpm exec tsx scripts/operator-demo.ts + +# Deploy the program to devnet (needs a devnet-configured Solana CLI + funded wallet; run `just build-program` first) +deploy-devnet: + solana program deploy target/deploy/gacha_simple_program.so --program-id keys/gacha-simple-keypair.json + +# Create a pool on-chain (config via env, e.g. OPERATOR_PUBKEY, ADMIN_KEYPAIR, ENTRY_FEE_SOL, WEIGHTS) +setup-pool: + pnpm exec tsx scripts/setup-pool.ts + +# Buy a pull (config via env, e.g. RPC_URL, BUYER_KEYPAIR, POOL_ADMIN) +buy-pull: + pnpm exec tsx scripts/buy-pull.ts + +# Run the operator crank once +operator-settle: + pnpm exec tsx scripts/operator-settle.ts + +# Run the operator crank as a watcher +operator-watch: + pnpm exec tsx scripts/operator-settle.ts --watch + +# ============================================ +# Format and lint +# ============================================ + +# Check formatting without fixing +fmt-check: + @cargo fmt -p gacha-simple-program -p tests-gacha-simple --check + @pnpm run format:check + @echo "✓ Format check passed" + +# Auto-format all code +fmt: + @cargo fmt -p gacha-simple-program -p tests-gacha-simple + @pnpm run format + @echo "✓ Code formatted" + +# Lint with auto-fix +lint: + @cargo clippy --workspace --exclude gacha-simple-client --all-targets --no-deps --fix -- -D warnings + @pnpm run lint:fix + @echo "✓ Code linted" + +# Check linting without fixing +lint-check: + @cargo clippy --workspace --exclude gacha-simple-client --all-targets --no-deps -- -D warnings + @pnpm run lint + @echo "✓ Lint check passed" + +# Run all code quality checks +check: fmt-check lint-check + +# ============================================ +# Clean +# ============================================ + +# Clean build artifacts and deps +clean: + #!/usr/bin/env bash + set -euo pipefail + cargo clean + cd {{ts_client_dir}} && pnpm run clean || true + echo "✓ Clean complete" diff --git a/games/gacha/pinocchio-simple/package.json b/games/gacha/pinocchio-simple/package.json new file mode 100644 index 000000000..7978591d7 --- /dev/null +++ b/games/gacha/pinocchio-simple/package.json @@ -0,0 +1,51 @@ +{ + "name": "gacha-simple-monorepo", + "type": "module", + "private": true, + "packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67", + "scripts": { + "format": "prettier --write .", + "format:check": "prettier --check .", + "generate-idl": "cd program && GENERATE_IDL=1 cargo check", + "generate-clients": "tsx ./scripts/generate-clients.ts", + "lint": "pnpm run generate-clients && pnpm --filter @solana/gacha-simple build && eslint .", + "lint:fix": "pnpm run generate-clients && pnpm --filter @solana/gacha-simple build && eslint . --fix" + }, + "devDependencies": { + "@codama/nodes-from-anchor": "^1.5.0", + "@codama/renderers-js": "^2.2.0", + "@codama/renderers-rust": "^3.1.3", + "@eslint/js": "^9.39.4", + "@solana/eslint-config-solana": "6.0.0", + "@solana/gacha-simple": "workspace:*", + "@solana/kit": "^7.0.0", + "@solana/kit-plugin-rpc": "^0.15.0", + "@solana/kit-plugin-signer": "^0.13.0", + "@solana/prettier-config-solana": "^0.0.6", + "@types/eslint": "^9.6.1", + "@types/eslint__js": "^9.14.0", + "@types/node": "^25.7.0", + "codama": "^1.7.0", + "eslint": "^9.39.2", + "eslint-plugin-jest": "^29.12.1", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sort-keys-fix": "^1.1.2", + "eslint-plugin-typescript-sort-keys": "^3.3.0", + "globals": "^16.5.0", + "prettier": "^3.8.3", + "tsx": "^4.22.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.60.0" + }, + "pnpm": { + "overrides": { + "bn.js": "5.2.3", + "flatted": "3.4.2", + "minimatch": "9.0.9", + "picomatch": "4.0.4", + "rollup": "4.60.2", + "rpc-websockets>uuid": "9.0.1", + "ws@^8.0.0": "8.20.1" + } + } +} diff --git a/games/gacha/pinocchio-simple/pnpm-lock.yaml b/games/gacha/pinocchio-simple/pnpm-lock.yaml new file mode 100644 index 000000000..21ae04625 --- /dev/null +++ b/games/gacha/pinocchio-simple/pnpm-lock.yaml @@ -0,0 +1,6568 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + bn.js: 5.2.3 + flatted: 3.4.2 + minimatch: 9.0.9 + picomatch: 4.0.4 + rollup: 4.60.2 + rpc-websockets>uuid: 9.0.1 + ws@^8.0.0: 8.20.1 + +importers: + + .: + devDependencies: + '@codama/nodes-from-anchor': + specifier: ^1.5.0 + version: 1.5.3(typescript@5.9.3) + '@codama/renderers-js': + specifier: ^2.2.0 + version: 2.3.1(typescript@5.9.3) + '@codama/renderers-rust': + specifier: ^3.1.3 + version: 3.1.3(typescript@5.9.3) + '@eslint/js': + specifier: ^9.39.4 + version: 9.39.5 + '@solana/eslint-config-solana': + specifier: 6.0.0 + version: 6.0.0(@eslint/js@9.39.5)(@types/eslint@9.6.1)(@types/eslint__js@9.14.0)(eslint-plugin-jest@29.16.0(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(jest@30.4.2(@types/node@25.9.5))(typescript@5.9.3))(eslint-plugin-react-hooks@7.1.1(eslint@9.39.5))(eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.5))(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(globals@16.5.0)(jest@30.4.2(@types/node@25.9.5))(typescript-eslint@8.66.0(eslint@9.39.5)(typescript@5.9.3))(typescript@5.9.3) + '@solana/gacha-simple': + specifier: workspace:* + version: link:clients/typescript + '@solana/kit': + specifier: ^7.0.0 + version: 7.0.0(typescript@5.9.3) + '@solana/kit-plugin-rpc': + specifier: ^0.15.0 + version: 0.15.0(@solana/kit@7.0.0(typescript@5.9.3)) + '@solana/kit-plugin-signer': + specifier: ^0.13.0 + version: 0.13.0(@solana/kit@7.0.0(typescript@5.9.3)) + '@solana/prettier-config-solana': + specifier: ^0.0.6 + version: 0.0.6(prettier@3.9.6) + '@types/eslint': + specifier: ^9.6.1 + version: 9.6.1 + '@types/eslint__js': + specifier: ^9.14.0 + version: 9.14.0 + '@types/node': + specifier: ^25.7.0 + version: 25.9.5 + codama: + specifier: ^1.7.0 + version: 1.10.0 + eslint: + specifier: ^9.39.2 + version: 9.39.5 + eslint-plugin-jest: + specifier: ^29.12.1 + version: 29.16.0(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(jest@30.4.2(@types/node@25.9.5))(typescript@5.9.3) + eslint-plugin-simple-import-sort: + specifier: ^12.1.1 + version: 12.1.1(eslint@9.39.5) + eslint-plugin-sort-keys-fix: + specifier: ^1.1.2 + version: 1.1.2 + eslint-plugin-typescript-sort-keys: + specifier: ^3.3.0 + version: 3.3.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) + globals: + specifier: ^16.5.0 + version: 16.5.0 + prettier: + specifier: ^3.8.3 + version: 3.9.6 + tsx: + specifier: ^4.22.0 + version: 4.23.11 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.60.0 + version: 8.66.0(eslint@9.39.5)(typescript@5.9.3) + + clients/typescript: + dependencies: + '@collectorcrypt/ecvrf': + specifier: ^0.1.1 + version: 0.1.1 + '@noble/hashes': + specifier: ^2.2.0 + version: 2.3.0 + '@solana/program-client-core': + specifier: ^7.0.0 + version: 7.0.0(typescript@5.9.3) + devDependencies: + '@solana/kit': + specifier: ^7.0.0 + version: 7.0.0(typescript@5.9.3) + '@types/node': + specifier: ^25.7.0 + version: 25.9.5 + tsup: + specifier: ^8.3.0 + version: 8.5.1(tsx@4.23.11)(typescript@5.9.3) + tsx: + specifier: ^4.22.0 + version: 4.23.11 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.29.7': + resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@codama/cli@1.6.0': + resolution: {integrity: sha512-68fam0kzWsKp2ip/LZn3kDyFjWcbzHExCuDgEHGTX8C0gSscMch/c4EgtTq7NsWKc1cse0GMC5WIxCFumroTyQ==} + hasBin: true + + '@codama/errors@1.10.0': + resolution: {integrity: sha512-/qiWQcbPIsyFlP1LrLqJfHt1QNbkY2wtC4UxZyKLnuZtc62YPlgDrN2wp4wcfRuE/DC0n3Y6ZSwYUrDBZyAnvA==} + hasBin: true + + '@codama/fragments@0.1.3': + resolution: {integrity: sha512-ITa3UYs74AWx7se/6EZN+YXqV1Vu89L3n2v0/irsQNbPcy8wP26p/GKns/1H4a8vGvAKuzWdIGO535dwcauInw==} + + '@codama/node-types@1.10.0': + resolution: {integrity: sha512-fMoJQ8TgB9A5Pl+GA8ffhDbGK63cV1nrbDrYTgIhPDKeVqBSdkSAoAuAtvWfRd7X2ML73H6RRi6jZRJmo2xfHQ==} + + '@codama/nodes-from-anchor@1.5.3': + resolution: {integrity: sha512-EcQ2QIty7LK2Vv5vKLprE/qsT13iDBRAvhH79NQauqP+QL6dFD9hCNmu0gfXZK0i7T0vafs55pddExO79qG7Dw==} + + '@codama/nodes@1.10.0': + resolution: {integrity: sha512-P3NXVw6kZq2VDb4wYU6zQ3xFksDqSuDYqp11P6Dpei9XIZZ11NtL2jBjBcqsIudGIx104bMgSo4SZ7Hlej056A==} + + '@codama/renderers-core@1.3.11': + resolution: {integrity: sha512-VSGrfmwPVv5cRAiHitgIq+rc9ktIN/Q9czbUORaqhZCmM+M3Ux5cd5r90PVTbeIt8vI0Zlw74OUFwdKqdEfSNw==} + + '@codama/renderers-js@2.3.1': + resolution: {integrity: sha512-+7WtjpcqnlPkweG+tWWiER2XkJP7SF+RJgYpI0RHc91ZP60Umt0qc+0JPuqMyiPhVrLTN2U/lpsBDQw2MeEsvA==} + engines: {node: '>=20.18.0'} + + '@codama/renderers-rust@3.1.3': + resolution: {integrity: sha512-z5hPKlqT/0umZOdVMZfRw03mfN/GkdZ0z646cIComcosDk4VKTO4AFr98sg1L/aO5MXfoBrTfNOqYzD1fyUotg==} + engines: {node: '>=20.18.0'} + + '@codama/validators@1.10.0': + resolution: {integrity: sha512-5WH1IHmyVVG/9QThZsrVM2ROv5w3yLF/8+4HK/kr8uw0ZXnIBGidnmyrxr1P8V+X6DgZqKJaX46V30dSs/NOTQ==} + + '@codama/visitors-core@1.10.0': + resolution: {integrity: sha512-8koBGs4m/NI4nDLJZx7Q1KzRUKkBy/GAnZq4xOEFGlhTvDCIAzEgo3biAhNFynGY45gzZJSX08QTDWKsPRjnaA==} + + '@codama/visitors@1.10.0': + resolution: {integrity: sha512-xAGKosZQnXBo9MiHFipA9Dsec1k9nbpv5UgUyY1j3l85FGEZQT2tHqrNxtBmPrC3LNSLVOfAdiUUMVUXUX5ydA==} + + '@collectorcrypt/ecvrf@0.1.1': + resolution: {integrity: sha512-Hgkl5ZyeHSbAQB13fSZBqgUPBzdcXuwDUXQFNrG2D04Hc9kZVGJF3DLQQaoOLZ8hIMAsfVby1gLJTWfeB3eCCQ==} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.6': + resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jest/console@30.4.1': + resolution: {integrity: sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/core@30.4.2': + resolution: {integrity: sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/diff-sequences@30.4.0': + resolution: {integrity: sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment@30.4.1': + resolution: {integrity: sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.4.1': + resolution: {integrity: sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.4.1': + resolution: {integrity: sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/fake-timers@30.4.1': + resolution: {integrity: sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/globals@30.4.1': + resolution: {integrity: sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.4.0': + resolution: {integrity: sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.4.1': + resolution: {integrity: sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@30.4.1': + resolution: {integrity: sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.4.1': + resolution: {integrity: sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/source-map@30.0.1': + resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-result@30.4.1': + resolution: {integrity: sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-sequencer@30.4.1': + resolution: {integrity: sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/transform@30.4.1': + resolution: {integrity: sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.4.1': + resolution: {integrity: sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.2.2': + resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 + + '@noble/ed25519@3.1.0': + resolution: {integrity: sha512-pfcObRY3CtvwfaG9Mt5XqZdKmAQppl37tHUeuBhDUbiwJBCVY4/A4lbMvb1xKhMDx96AqAqZpMWuBX1HulhX4g==} + + '@noble/hashes@2.3.0': + resolution: {integrity: sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==} + engines: {node: '>= 20.19.0'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} + + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + cpu: [x64] + os: [win32] + + '@sinclair/typebox@0.34.52': + resolution: {integrity: sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@15.4.0': + resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} + + '@solana/accounts@7.0.0': + resolution: {integrity: sha512-RfbinkhuWxcObxZIdjeWEn/mzLqRp/h2hAk/ZQCUxPdDBW8h4XxEybK9GBItGOAcrUz5HuusXTD+cXXnlIxWcg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/addresses@7.0.0': + resolution: {integrity: sha512-E7sJtV5d3bXrmw3I30rcKY+xoqM++6KIVJCi+q8ZaSMyP04UMfEENPHIJ+TkyS1RUgjzPT91ka/oWrtTh6EfkQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/assertions@7.0.0': + resolution: {integrity: sha512-CShOQLPezI0tbrih+L88fzt8FMHDyJoWkmulk4wfRp3HhpQL2yNlH/SWLH033qysnvkmE7TxBFUtcnbnf7jz1g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-core@5.5.1': + resolution: {integrity: sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-core@7.0.0': + resolution: {integrity: sha512-6HtEisZEtFb6okARUgYqmKdDbn2aHRrSCDgB1/GEwr0s6fK5XNYpafaSjorbs2MEyZV3tCUFTj2j6fk/4nNcLg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-data-structures@5.5.1': + resolution: {integrity: sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-data-structures@7.0.0': + resolution: {integrity: sha512-P0Ys1mB4lYlz3MMTCaJSysE3OYrq8WvsveU1ta8U/yG1qChXFGCOxPVh06swjaRxwPrEakb9VUESS5vNtp1rRA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-numbers@5.5.1': + resolution: {integrity: sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-numbers@7.0.0': + resolution: {integrity: sha512-XL0jnmnXr3ceoX4tusT+XkBVR2iGKEJecTIXbIV7ILi9xObg3fNXafNbTmbIOvKc0ByTyjo8EWZ9jQKSRWgsgA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-strings@5.5.1': + resolution: {integrity: sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A==} + engines: {node: '>=20.18.0'} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: ^5.0.0 + peerDependenciesMeta: + fastestsmallesttextencoderdecoder: + optional: true + typescript: + optional: true + + '@solana/codecs-strings@7.0.0': + resolution: {integrity: sha512-zXE1PE9HkVk6phZ6aqHTXvLZ0qIl5bJNIvG9eMB7LuFO1XBVQywJUtjKS8fE3/xmRCWSMilFSrEXGA+SpOyLrQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5.4.0' + peerDependenciesMeta: + fastestsmallesttextencoderdecoder: + optional: true + typescript: + optional: true + + '@solana/codecs@5.5.1': + resolution: {integrity: sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs@7.0.0': + resolution: {integrity: sha512-xT1IbwKkPZ544u/eqhb9SZ0fNYJidWgIUzKNQMbvLCwduayAkQp+czlGdvLQ6CVlqtCewtH3gW8biGU7YuBdEw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/errors@5.5.1': + resolution: {integrity: sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg==} + engines: {node: '>=20.18.0'} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/errors@7.0.0': + resolution: {integrity: sha512-94r+LLSzZ0XVp+LOogwxWGXeo138uvwqtqRW9Tjl1DIXrFgh8euXnJSXZyydu1UXJs7ItOSm0QreJviSGw3TGQ==} + engines: {node: '>=20.18.0'} + hasBin: true + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/eslint-config-solana@6.0.0': + resolution: {integrity: sha512-tl3C2ZK8buItUQNVCwnveR2iiLALr5XZ8cevswbTDQN1SA29xQWPFRYR/JPyXLd7iOGNAo4HwvIoxNmkLjdZsQ==} + deprecated: It has been replaced by @solana-config/eslint, part of the js-configs monorepo (https://github.com/solana-foundation/js-configs). Please migrate to the new package; this one will no longer receive updates. + peerDependencies: + '@eslint/js': ^9.39.1 + '@types/eslint': ^9.6.1 + '@types/eslint__js': ^9.14.0 + eslint: ^9.39.1 + eslint-plugin-jest: ^29.2.1 + eslint-plugin-react-hooks: ^7.0.1 + eslint-plugin-simple-import-sort: ^12.1.1 + eslint-plugin-sort-keys-fix: ^1.1.2 + eslint-plugin-typescript-sort-keys: ^3.3.0 + globals: ^16.5.0 + jest: ^30.0.0 + typescript: ^5.9.3 + typescript-eslint: ^8.49.0 + + '@solana/fast-stable-stringify@7.0.0': + resolution: {integrity: sha512-i/b5ZJMqMJXa6etjypANa2/ErPZfNG9/EVIYl7HpWooyCRgZ8hZJmJ2Cgrp0r4EMXCLeWn4aEG2HOBTzOJ4F7Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/fixed-points@7.0.0': + resolution: {integrity: sha512-Y3gcyHTponi5kXpWVEJIhuZ7yT84N+8He4dbjXWqwMBJnzKM+4tqFCbzy6y+6+Jxt44RT1lDmbpxmZopFRXU8Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/functional@7.0.0': + resolution: {integrity: sha512-ix9fzYhc2hCLiYf+hGI00mzzayANKDExEBxbwrtMj/BdQkwgUIvIlOssqHeSqDChL3UZhq9lR24Nz4JwYX8Jbw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/instruction-plans@7.0.0': + resolution: {integrity: sha512-uzXHztc8hLoT5TNWFsBX2DIETyp/Lr2l36O330s3YCgpRmfI4IRbBrjjq7TxDYFm/QY8+DD4CRG8p7wZSqG8dQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/instructions@7.0.0': + resolution: {integrity: sha512-ZN0gKAtCOKDuIaStcvLZDf5H20fkk7jr4dZE0Rk7z0kslf6mrRam9Y23N6AzeHp/b5ZeVKyfaTf4M35mOktLNg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/keys@7.0.0': + resolution: {integrity: sha512-JsdYR/YN3AGHZN2aZoeE5cymHYkNoBLnqgXoRncW/VyD7fMcR350aHU1hCMYd0b5BtITLsGmvvtvrgVkzK83Eg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/kit-plugin-instruction-plan@0.13.0': + resolution: {integrity: sha512-9RA94e0LLtVLN+bvGclpu8l0lAXGOGS72DxPIQ1VyGZs7vK/WTwPOqKnWhjJs2Cv/SDJ2xsCAhYkXr0UltVrOg==} + peerDependencies: + '@solana/kit': ^7.0.0 + + '@solana/kit-plugin-rpc@0.15.0': + resolution: {integrity: sha512-Abymr7WLP9yQ6ixCCv+tKzjoWpAkxJ90JWzL+pfkTAFGTpVk9N0myhMcO2ohl6yztJRMOxsw1CXWjcVecxxUlg==} + peerDependencies: + '@solana/kit': ^7.0.0 + + '@solana/kit-plugin-signer@0.13.0': + resolution: {integrity: sha512-Vtlyt2Td8WfeQvC30yOU7a+CFo4+loMCSQKUFwvSQvbpKG39S6UyQc1euhcvtwCbJRZR3m5nAZvHayQ1rxeUkA==} + peerDependencies: + '@solana/kit': ^7.0.0 + + '@solana/kit@7.0.0': + resolution: {integrity: sha512-ZCeai4LRJQooUmJXvpgMEGFTrCdJnV1ODbDJ8oqFZ+Y4t/9x1baQsFFpruqsdRyeGv2Rr+X6jV7cldVD+hyzRA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/nominal-types@7.0.0': + resolution: {integrity: sha512-ff21hmKKMckDkGWah9tRXsEyFCtSnkugH+EMLGJOn7tiXdtFljOXW5Q12IXyeil87EE8aWb1MS6p8v5+hi71Vg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/offchain-messages@7.0.0': + resolution: {integrity: sha512-fGrzxmqVStweGHRlXVAPKACdDboFCwXq5m8C+aD9Nupax6Q9rnvjICzYRLypwqB9X90XIZazh0ys+0KGLMpIJQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/options@5.5.1': + resolution: {integrity: sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@solana/options@7.0.0': + resolution: {integrity: sha512-6DhvMqRcL3mG0R5JejYIW5PDTDr7HcLX2R9iCs6OPN1HdsyXmE2rX2EldnuA0rYz1buOodeWYsu4N1lpDcEpaQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/plugin-core@7.0.0': + resolution: {integrity: sha512-EwTUfOGoQQ3aXooRlQFVbk+sJW7NqJl1K+bmSLiJUjaBTSqtbr3GtMu7aoybJqzZQKjOGSG4Hn0BzK24SvWy3A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/plugin-interfaces@7.0.0': + resolution: {integrity: sha512-fz/HknZLGnVIjhjXrMzW3Qm1x80oeEMSOk4RzMwjcyAEyfzhHtGNW74NsU5W+uFpYz6UBbV5mB1jpuAdJdnj9A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/prettier-config-solana@0.0.6': + resolution: {integrity: sha512-/s55hDoAyh5QyltQh/jjNK3AgACEq885+DnC6lYhrmYZiV6I0iHITWYnKd8d23KRKs/RBjlaQH54MiafeoI9hw==} + deprecated: This package is deprecated. It has been replaced by @solana-config/prettier, part of the @solana-foundation/js-configs monorepo (https://github.com/solana-foundation/js-configs). Please migrate to the new package; this one will no longer receive updates. + peerDependencies: + prettier: ^3.7.4 + + '@solana/program-client-core@7.0.0': + resolution: {integrity: sha512-+N8HImlR3MTbbvhOShsLelQXGZKbi6KhPhyy+4ZkDLbnRs4QikTamIChXZmoCyxB51S8/9cNRAKyQhbeF5Y8Qg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/programs@7.0.0': + resolution: {integrity: sha512-a1HNgzr9YiiZ8vK4VaKHEXjnZmKX7W5ab2ghuseIalEw/+PJLFcTRTOw8n3efRu677b/bG2Icmb3MBBEXmvbPg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/promises@7.0.0': + resolution: {integrity: sha512-rjoHnaR4zeEIHqIzfgotxRrLKqY4Goj0G5duZOnjHm8ZC+7eDkH5/mXj1bDJ4ROM70dVM+y1Xkrjg7IL6k6StA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-api@7.0.0': + resolution: {integrity: sha512-MTtBO883st83CjWpo8B4g8EKzXaeoBX5N7+sv4vcvsn2q1NpY4SG3XejdX1FrKeTe9Xjbv0/FzFtykstbKe1UA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-parsed-types@7.0.0': + resolution: {integrity: sha512-80VjPbB/TZ/Hy5qdRF7onPfMPHk5cwBVbtNUGbilrmFuqRzSvzSOY1ynNXXODPZBytNmPq7u7oJfSCsQ5MA9Ig==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-spec-types@7.0.0': + resolution: {integrity: sha512-V4Hp2//fW8eYq1zcGgHPu7FXKHyIWERBQd4+NRaKn2m8rPiVAm3pVRwPzSvVE0+8Nyf5qzdcfcMf7NQdhl6j7Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-spec@7.0.0': + resolution: {integrity: sha512-GRGlXpLgap9yVh3qmCl4huuKAoLMp/p82/9Q9ONj6lmFH+RqJE4Q+16V+HxHI5ZakmwZYoVZU4GEKjumse9SCg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-api@7.0.0': + resolution: {integrity: sha512-o2PZDeNC/kg3VO3ry4R0JySJ1xMHLchZwmzVwamxGidlLe9uvzm6CHlCqv/JCq4/zHLo7qbLqnmOckZ6vlDqaw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-channel-websocket@7.0.0': + resolution: {integrity: sha512-79ecXBCT2pG+vXNBZim80vUz+B04L1mEduXobBIXM55VvxsHYT+4H4V+/ZHoFJUK6Lhbt+6zhpconx8Wa3H+JA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-spec@7.0.0': + resolution: {integrity: sha512-Oips5ciWqPGO5Cx7hcEQ/czbcrUjPf+4cTam0UMrK3BnvHPcEAWkPYlIgabQiqa60/pjOOz7B936oMXA5XwL+g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions@7.0.0': + resolution: {integrity: sha512-jLNjUCGBbCIfABqqHopNJIeAkhd4GzhbodgCH4x2X+S0ycBaERX393+k+fG8JPJpGujkmeQFBCeIKO3lK+PoYg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-transformers@7.0.0': + resolution: {integrity: sha512-NHkTKC2J4oaMMzyOtIEDOLCGtzg1Y8vlPoBmUeA82o+DNjBSiZLR2Ariy7n7chmwKchCZ8aGirBxjLCSRc6b/g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-transport-http@7.0.0': + resolution: {integrity: sha512-W0G15BN0xljXzRRo/ZwepJNiOjKhRImF5SxhjZauh2yVBoUjfd6NSCmYcdWu0tj9lypKKrB1ReS4oPmb4yCHbA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-types@7.0.0': + resolution: {integrity: sha512-Lf2csFSWHwN/8EL03uWfS7n1J19vWLK4DBGkQ5jebRhoJ3dD+xPcJtS+epI2281t5aghJvoV7D+RIM0NextZJg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc@7.0.0': + resolution: {integrity: sha512-hCf4XEhspsNb8TnQ+E961+rBuJcTyrmwNr4LDfVHEeO/VTqaN+yVwAI1wpxcnzwc+T4OoXcyujNiQWhVZPOhiA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/signers@7.0.0': + resolution: {integrity: sha512-4E3xYQ0b9OZCTLghqfeHh66lfipy3jV1REdFJLk9WqPBaXVa/wSgGGIqZVa744ATSd9AeEfL/I5n/LrMNQOhoA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/subscribable@7.0.0': + resolution: {integrity: sha512-dJQA5AxDv/7YxuNe7GXIkaOUNhXczFaL3/SNOvXe7k77bC4dx4HPkySfcVDfEWBOePe3+8iyVbT8DZ3aOcp8Ng==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/sysvars@7.0.0': + resolution: {integrity: sha512-GKND6hCcBcrak3/VAtx6aEoAi9wQjJQzU2Fcu6JYmnTjGe5MHf21FqbjGl0aQ0C2HlJQQJmA07wgsuOiYDTDog==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-confirmation@7.0.0': + resolution: {integrity: sha512-SaU2CY9ZDJGK47DtQ7xwKFmbgzDGqIN/Ug89ZSUzDPD9QdMRXhtxgH8KZgb0gSgpyel85sSt0QH9wkWzhp69ow==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-introspection@7.0.0': + resolution: {integrity: sha512-aO+5ewxGaziXYcXJZ6F3doq/KI1L3WU2z5eCjs4DBO0kRQBHp4bH6ZKygVX2JIxOZrd1rB9SxP/CCCX2wRm8Xw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-messages@7.0.0': + resolution: {integrity: sha512-qCBYR3QQvykcI36vnqwI5090hGXS3mmCe72b/f/n9kBsBaA2oowdBXZupB1wwKe8+6x8o8VkhKQaK9oTKKbWTg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transactions@7.0.0': + resolution: {integrity: sha512-y5nayd2Ozld/4Bxefz50e/E6qxyZteuZCZ7suh7z96KY6QUJlZDR+eCG1v/lA7Azt3GSOevJuYFX/ept/mqZkw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/eslint__js@9.14.0': + resolution: {integrity: sha512-s0jepCjOJWB/GKcuba4jISaVpBudw3ClXJ3fUK4tugChUMQsp6kSwuA8Dcx6wFd/JsJqcY8n4rEpa5RTHs5ypA==} + deprecated: This is a stub types definition. @eslint/js provides its own type definitions, so you do not need this installed. + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@25.9.5': + resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==} + + '@types/semver@7.8.0': + resolution: {integrity: sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@typescript-eslint/eslint-plugin@8.66.0': + resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.66.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/experimental-utils@5.62.0': + resolution: {integrity: sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/parser@8.66.0': + resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.66.0': + resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/scope-manager@8.66.0': + resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.66.0': + resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.66.0': + resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@8.66.0': + resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@8.66.0': + resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/utils@8.66.0': + resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/visitor-keys@8.66.0': + resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.3': + resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} + cpu: [x64] + os: [win32] + + a-sync-waterfall@1.0.1: + resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + babel-jest@30.4.1: + resolution: {integrity: sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-0 + + babel-plugin-istanbul@7.0.1: + resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} + engines: {node: '>=12'} + + babel-plugin-jest-hoist@30.4.0: + resolution: {integrity: sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.4.0: + resolution: {integrity: sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-beta.1 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.11.12: + resolution: {integrity: sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.7: + resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + codama@1.10.0: + resolution: {integrity: sha512-fH1wz1P6bbPOWNM23Sk4VzRSpqxRW1CpXNiWQY23vHT2tno/K1/JtaaUc7DS5ddl5rGFKtq9R2UFsegR96IxRQ==} + hasBin: true + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.402: + resolution: {integrity: sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-jest@29.16.0: + resolution: {integrity: sha512-0WFBxDHlT2ratGQfnFQEVIsgQJ5cfd+0IV8Kc6U3X2onB8ATLG23voD2Ch5G9fCkEpCPmCMuzW0tbS0kYb8biw==} + engines: {node: ^20.12.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + jest: '*' + typescript: '>=4.8.4 <8.0.0' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + typescript: + optional: true + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-simple-import-sort@12.1.1: + resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==} + peerDependencies: + eslint: '>=5.0.0' + + eslint-plugin-sort-keys-fix@1.1.2: + resolution: {integrity: sha512-DNPHFGCA0/hZIsfODbeLZqaGY/+q3vgtshF85r+YWDNCQ2apd9PNs/zL6ttKm0nD1IFwvxyg3YOTI7FHl4unrw==} + engines: {node: '>=0.10.0'} + + eslint-plugin-typescript-sort-keys@3.3.0: + resolution: {integrity: sha512-bRW3Rc/VNdrSP9OoY5wgjjaXCOOkZKpzvl/Mk6l8Sg8CMehVIcg9K4y33l+ZcZiknpl0aR6rKusxuCJNGZWmVw==} + engines: {node: '>= 16'} + peerDependencies: + '@typescript-eslint/parser': '>=6' + eslint: ^7 || ^8 + typescript: ^3 || ^4 || ^5 + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@6.2.1: + resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} + engines: {node: '>=6.0.0'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit-x@0.2.2: + resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} + engines: {node: '>= 0.8.0'} + + expect@30.4.1: + resolution: {integrity: sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: 4.0.4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-changed-files@30.4.1: + resolution: {integrity: sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-circus@30.4.2: + resolution: {integrity: sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-cli@30.4.2: + resolution: {integrity: sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.4.2: + resolution: {integrity: sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@types/node': '*' + esbuild-register: '>=3.4.0' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.4.1: + resolution: {integrity: sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-docblock@30.4.0: + resolution: {integrity: sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-each@30.4.1: + resolution: {integrity: sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-environment-node@30.4.1: + resolution: {integrity: sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-haste-map@30.4.1: + resolution: {integrity: sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-leak-detector@30.4.1: + resolution: {integrity: sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.4.1: + resolution: {integrity: sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.4.1: + resolution: {integrity: sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.4.1: + resolution: {integrity: sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@30.4.0: + resolution: {integrity: sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.4.2: + resolution: {integrity: sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve@30.4.1: + resolution: {integrity: sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runner@30.4.2: + resolution: {integrity: sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runtime@30.4.2: + resolution: {integrity: sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-snapshot@30.4.1: + resolution: {integrity: sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.4.1: + resolution: {integrity: sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.4.1: + resolution: {integrity: sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-watcher@30.4.1: + resolution: {integrity: sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-worker@30.4.1: + resolution: {integrity: sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest@30.4.2: + resolution: {integrity: sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.15.1: + resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} + hasBin: true + + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stable-stringify@1.3.0: + resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} + engines: {node: '>= 0.4'} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonify@0.0.1: + resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nunjucks@3.2.4: + resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==} + engines: {node: '>= 6.9.0'} + hasBin: true + peerDependencies: + chokidar: ^3.3.0 + peerDependenciesMeta: + chokidar: + optional: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@30.4.1: + resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.2.8: + resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requireindex@1.2.0: + resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} + engines: {node: '>=0.10.5'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} + engines: {node: ^14.18.0 || >=16.0.0} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + tsx@4.23.11: + resolution: {integrity: sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + typescript-eslint@8.66.0: + resolution: {integrity: sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici-types@8.10.0: + resolution: {integrity: sha512-ibvdovq3nCFs8Msrd95BW+zUOq+aOVbT+wpHUoPWhztbHEoPc6oof51iFDB6Es8lTKvNvVW9jNSAB8dwrKTMGg==} + + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + + update-browserslist-db@1.3.0: + resolution: {integrity: sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.7 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@0.2.3': {} + + '@codama/cli@1.6.0': + dependencies: + '@codama/nodes': 1.10.0 + '@codama/visitors': 1.10.0 + '@codama/visitors-core': 1.10.0 + commander: 14.0.3 + picocolors: 1.1.1 + prompts: 2.4.2 + + '@codama/errors@1.10.0': + dependencies: + '@codama/node-types': 1.10.0 + commander: 14.0.3 + picocolors: 1.1.1 + + '@codama/fragments@0.1.3': + dependencies: + '@codama/errors': 1.10.0 + + '@codama/node-types@1.10.0': {} + + '@codama/nodes-from-anchor@1.5.3(typescript@5.9.3)': + dependencies: + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/visitors': 1.10.0 + '@noble/hashes': 2.3.0 + '@solana/codecs': 5.5.1(typescript@5.9.3) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + + '@codama/nodes@1.10.0': + dependencies: + '@codama/errors': 1.10.0 + '@codama/node-types': 1.10.0 + + '@codama/renderers-core@1.3.11': + dependencies: + '@codama/errors': 1.10.0 + '@codama/fragments': 0.1.3 + '@codama/nodes': 1.10.0 + '@codama/visitors-core': 1.10.0 + + '@codama/renderers-js@2.3.1(typescript@5.9.3)': + dependencies: + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/renderers-core': 1.3.11 + '@codama/visitors-core': 1.10.0 + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + prettier: 3.9.6 + semver: 7.8.5 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + + '@codama/renderers-rust@3.1.3(typescript@5.9.3)': + dependencies: + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/renderers-core': 1.3.11 + '@codama/visitors-core': 1.10.0 + '@iarna/toml': 2.2.5 + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + nunjucks: 3.2.4 + semver: 7.8.5 + transitivePeerDependencies: + - chokidar + - fastestsmallesttextencoderdecoder + - typescript + + '@codama/validators@1.10.0': + dependencies: + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/visitors-core': 1.10.0 + + '@codama/visitors-core@1.10.0': + dependencies: + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + json-stable-stringify: 1.3.0 + + '@codama/visitors@1.10.0': + dependencies: + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/visitors-core': 1.10.0 + + '@collectorcrypt/ecvrf@0.1.1': + dependencies: + '@noble/ed25519': 3.1.0 + '@noble/hashes': 2.3.0 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5)': + dependencies: + eslint: 9.39.5 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 9.0.9 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.6': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.1 + minimatch: 9.0.9 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.5': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@iarna/toml@2.2.5': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.15.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jest/console@30.4.1': + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + chalk: 4.1.2 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + slash: 3.0.0 + + '@jest/core@30.4.2': + dependencies: + '@jest/console': 30.4.1 + '@jest/pattern': 30.4.0 + '@jest/reporters': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.4.0 + exit-x: 0.2.2 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-changed-files: 30.4.1 + jest-config: 30.4.2(@types/node@25.9.5) + jest-haste-map: 30.4.1 + jest-message-util: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-resolve-dependencies: 30.4.2 + jest-runner: 30.4.2 + jest-runtime: 30.4.2 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 + jest-validate: 30.4.1 + jest-watcher: 30.4.1 + pretty-format: 30.4.1 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + '@jest/diff-sequences@30.4.0': {} + + '@jest/environment@30.4.1': + dependencies: + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + jest-mock: 30.4.1 + + '@jest/expect-utils@30.4.1': + dependencies: + '@jest/get-type': 30.1.0 + + '@jest/expect@30.4.1': + dependencies: + expect: 30.4.1 + jest-snapshot: 30.4.1 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@30.4.1': + dependencies: + '@jest/types': 30.4.1 + '@sinonjs/fake-timers': 15.4.0 + '@types/node': 25.9.5 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + + '@jest/get-type@30.1.0': {} + + '@jest/globals@30.4.1': + dependencies: + '@jest/environment': 30.4.1 + '@jest/expect': 30.4.1 + '@jest/types': 30.4.1 + jest-mock: 30.4.1 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.4.0': + dependencies: + '@types/node': 25.9.5 + jest-regex-util: 30.4.0 + + '@jest/reporters@30.4.1': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 25.9.5 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit-x: 0.2.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + jest-worker: 30.4.1 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@30.4.1': + dependencies: + '@sinclair/typebox': 0.34.52 + + '@jest/snapshot-utils@30.4.1': + dependencies: + '@jest/types': 30.4.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + '@jest/source-map@30.0.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@30.4.1': + dependencies: + '@jest/console': 30.4.1 + '@jest/types': 30.4.1 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@30.4.1': + dependencies: + '@jest/test-result': 30.4.1 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + slash: 3.0.0 + + '@jest/transform@30.4.1': + dependencies: + '@babel/core': 7.29.7 + '@jest/types': 30.4.1 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + '@jest/types@30.4.1': + dependencies: + '@jest/pattern': 30.4.0 + '@jest/schemas': 30.4.1 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.9.5 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@noble/ed25519@3.1.0': {} + + '@noble/hashes@2.3.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.3.6': {} + + '@rollup/rollup-android-arm-eabi@4.60.2': + optional: true + + '@rollup/rollup-android-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-x64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + + '@sinclair/typebox@0.34.52': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@15.4.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@solana/accounts@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/addresses@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/assertions': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/assertions@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-core@5.5.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 5.5.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-core@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-data-structures@5.5.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@5.9.3) + '@solana/codecs-numbers': 5.5.1(typescript@5.9.3) + '@solana/errors': 5.5.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-data-structures@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-numbers@5.5.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@5.9.3) + '@solana/errors': 5.5.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-numbers@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-strings@5.5.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@5.9.3) + '@solana/codecs-numbers': 5.5.1(typescript@5.9.3) + '@solana/errors': 5.5.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-strings@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs@5.5.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@5.9.3) + '@solana/codecs-data-structures': 5.5.1(typescript@5.9.3) + '@solana/codecs-numbers': 5.5.1(typescript@5.9.3) + '@solana/codecs-strings': 5.5.1(typescript@5.9.3) + '@solana/options': 5.5.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/codecs@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/fixed-points': 7.0.0(typescript@5.9.3) + '@solana/options': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/errors@5.5.1(typescript@5.9.3)': + dependencies: + chalk: 5.6.2 + commander: 14.0.2 + optionalDependencies: + typescript: 5.9.3 + + '@solana/errors@7.0.0(typescript@5.9.3)': + dependencies: + chalk: 5.6.2 + commander: 15.0.0 + optionalDependencies: + typescript: 5.9.3 + + '@solana/eslint-config-solana@6.0.0(@eslint/js@9.39.5)(@types/eslint@9.6.1)(@types/eslint__js@9.14.0)(eslint-plugin-jest@29.16.0(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(jest@30.4.2(@types/node@25.9.5))(typescript@5.9.3))(eslint-plugin-react-hooks@7.1.1(eslint@9.39.5))(eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.5))(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(globals@16.5.0)(jest@30.4.2(@types/node@25.9.5))(typescript-eslint@8.66.0(eslint@9.39.5)(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@eslint/js': 9.39.5 + '@types/eslint': 9.6.1 + '@types/eslint__js': 9.14.0 + eslint: 9.39.5 + eslint-plugin-jest: 29.16.0(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(jest@30.4.2(@types/node@25.9.5))(typescript@5.9.3) + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5) + eslint-plugin-simple-import-sort: 12.1.1(eslint@9.39.5) + eslint-plugin-sort-keys-fix: 1.1.2 + eslint-plugin-typescript-sort-keys: 3.3.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) + globals: 16.5.0 + jest: 30.4.2(@types/node@25.9.5) + typescript: 5.9.3 + typescript-eslint: 8.66.0(eslint@9.39.5)(typescript@5.9.3) + + '@solana/fast-stable-stringify@7.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/fixed-points@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/functional@7.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/instruction-plans@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/instructions': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/promises': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + '@solana/transactions': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/instructions@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/keys@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/assertions': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + '@solana/promises': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/kit-plugin-instruction-plan@0.13.0(@solana/kit@7.0.0(typescript@5.9.3))': + dependencies: + '@solana/kit': 7.0.0(typescript@5.9.3) + + '@solana/kit-plugin-rpc@0.15.0(@solana/kit@7.0.0(typescript@5.9.3))': + dependencies: + '@solana/kit': 7.0.0(typescript@5.9.3) + '@solana/kit-plugin-instruction-plan': 0.13.0(@solana/kit@7.0.0(typescript@5.9.3)) + + '@solana/kit-plugin-signer@0.13.0(@solana/kit@7.0.0(typescript@5.9.3))': + dependencies: + '@solana/kit': 7.0.0(typescript@5.9.3) + + '@solana/kit@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/accounts': 7.0.0(typescript@5.9.3) + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/functional': 7.0.0(typescript@5.9.3) + '@solana/instruction-plans': 7.0.0(typescript@5.9.3) + '@solana/instructions': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/offchain-messages': 7.0.0(typescript@5.9.3) + '@solana/plugin-core': 7.0.0(typescript@5.9.3) + '@solana/plugin-interfaces': 7.0.0(typescript@5.9.3) + '@solana/program-client-core': 7.0.0(typescript@5.9.3) + '@solana/programs': 7.0.0(typescript@5.9.3) + '@solana/rpc': 7.0.0(typescript@5.9.3) + '@solana/rpc-api': 7.0.0(typescript@5.9.3) + '@solana/rpc-parsed-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + '@solana/signers': 7.0.0(typescript@5.9.3) + '@solana/subscribable': 7.0.0(typescript@5.9.3) + '@solana/sysvars': 7.0.0(typescript@5.9.3) + '@solana/transaction-confirmation': 7.0.0(typescript@5.9.3) + '@solana/transaction-introspection': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + '@solana/transactions': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/nominal-types@7.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/offchain-messages@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/options@5.5.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 5.5.1(typescript@5.9.3) + '@solana/codecs-data-structures': 5.5.1(typescript@5.9.3) + '@solana/codecs-numbers': 5.5.1(typescript@5.9.3) + '@solana/codecs-strings': 5.5.1(typescript@5.9.3) + '@solana/errors': 5.5.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/options@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/plugin-core@7.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/plugin-interfaces@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/instruction-plans': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + '@solana/signers': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/prettier-config-solana@0.0.6(prettier@3.9.6)': + dependencies: + prettier: 3.9.6 + + '@solana/program-client-core@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/accounts': 7.0.0(typescript@5.9.3) + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/instruction-plans': 7.0.0(typescript@5.9.3) + '@solana/instructions': 7.0.0(typescript@5.9.3) + '@solana/plugin-interfaces': 7.0.0(typescript@5.9.3) + '@solana/rpc-api': 7.0.0(typescript@5.9.3) + '@solana/signers': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/programs@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/promises@7.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-api@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/rpc-parsed-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + '@solana/transactions': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-parsed-types@7.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-spec-types@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-spec@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 7.0.0(typescript@5.9.3) + '@solana/subscribable': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-subscriptions-api@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + '@solana/transactions': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-subscriptions-channel-websocket@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/functional': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.0.0(typescript@5.9.3) + '@solana/subscribable': 7.0.0(typescript@5.9.3) + ws: 8.20.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@solana/rpc-subscriptions-spec@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/promises': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 7.0.0(typescript@5.9.3) + '@solana/subscribable': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-subscriptions@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/fast-stable-stringify': 7.0.0(typescript@5.9.3) + '@solana/functional': 7.0.0(typescript@5.9.3) + '@solana/promises': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-api': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-channel-websocket': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + '@solana/subscribable': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/rpc-transformers@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/functional': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-transport-http@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 7.0.0(typescript@5.9.3) + undici-types: 8.10.0 + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-types@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/fixed-points': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/fast-stable-stringify': 7.0.0(typescript@5.9.3) + '@solana/functional': 7.0.0(typescript@5.9.3) + '@solana/rpc-api': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec': 7.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 7.0.0(typescript@5.9.3) + '@solana/rpc-transport-http': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/signers@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/instructions': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + '@solana/offchain-messages': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + '@solana/transactions': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/subscribable@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/promises': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/sysvars@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/accounts': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transaction-confirmation@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/promises': 7.0.0(typescript@5.9.3) + '@solana/rpc': 7.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + '@solana/transactions': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/transaction-introspection@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/instructions': 7.0.0(typescript@5.9.3) + '@solana/rpc-api': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + '@solana/transactions': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transaction-messages@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/functional': 7.0.0(typescript@5.9.3) + '@solana/instructions': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transactions@7.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.0.0(typescript@5.9.3) + '@solana/codecs-core': 7.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 7.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 7.0.0(typescript@5.9.3) + '@solana/codecs-strings': 7.0.0(typescript@5.9.3) + '@solana/errors': 7.0.0(typescript@5.9.3) + '@solana/functional': 7.0.0(typescript@5.9.3) + '@solana/instructions': 7.0.0(typescript@5.9.3) + '@solana/keys': 7.0.0(typescript@5.9.3) + '@solana/nominal-types': 7.0.0(typescript@5.9.3) + '@solana/rpc-types': 7.0.0(typescript@5.9.3) + '@solana/transaction-messages': 7.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + + '@types/eslint__js@9.14.0': + dependencies: + '@eslint/js': 9.39.5 + + '@types/estree@1.0.8': {} + + '@types/estree@1.0.9': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/node@25.9.5': + dependencies: + undici-types: 7.24.6 + + '@types/semver@7.8.0': {} + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/type-utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.66.0 + eslint: 9.39.5 + ignore: 7.0.6 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/experimental-utils@5.62.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@9.39.5)(typescript@5.9.3) + eslint: 9.39.5 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.66.0 + debug: 4.4.3 + eslint: 9.39.5 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.66.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3) + '@typescript-eslint/types': 8.66.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/scope-manager@8.66.0': + dependencies: + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 + + '@typescript-eslint/tsconfig-utils@8.66.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.66.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.5 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/types@8.66.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.8.5 + tsutils: 3.21.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.66.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.66.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3) + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 + debug: 4.4.3 + minimatch: 9.0.9 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@types/json-schema': 7.0.15 + '@types/semver': 7.8.0 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) + eslint: 9.39.5 + eslint-scope: 5.1.1 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@8.66.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3) + eslint: 9.39.5 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@8.66.0': + dependencies: + '@typescript-eslint/types': 8.66.0 + eslint-visitor-keys: 5.0.1 + + '@ungap/structured-clone@1.3.3': {} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + optional: true + + a-sync-waterfall@1.0.1: {} + + acorn-jsx@5.3.2(acorn@7.4.1): + dependencies: + acorn: 7.4.1 + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@7.4.1: {} + + acorn@8.18.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 4.0.4 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + asap@2.0.6: {} + + babel-jest@30.4.1(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@jest/transform': 30.4.1 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 7.0.1 + babel-preset-jest: 30.4.0(@babel/core@7.29.7) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@7.0.1: + dependencies: + '@babel/helper-plugin-utils': 7.29.7 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.4.0: + dependencies: + '@types/babel__core': 7.20.5 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + + babel-preset-jest@30.4.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + babel-plugin-jest-hoist: 30.4.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.11.12: {} + + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.7: + dependencies: + baseline-browser-mapping: 2.11.12 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.402 + node-releases: 2.0.53 + update-browserslist-db: 1.3.0(browserslist@4.28.7) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + bundle-require@5.1.0(esbuild@0.27.7): + dependencies: + esbuild: 0.27.7 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001809: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + char-regex@1.0.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@4.4.0: {} + + cjs-module-lexer@2.2.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + co@4.6.0: {} + + codama@1.10.0: + dependencies: + '@codama/cli': 1.6.0 + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/validators': 1.10.0 + '@codama/visitors': 1.10.0 + + collect-v8-coverage@1.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@14.0.2: {} + + commander@14.0.3: {} + + commander@15.0.0: {} + + commander@4.1.1: {} + + commander@5.1.0: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dedent@1.7.2: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + detect-newline@3.1.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.402: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-jest@29.16.0(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(jest@30.4.2(@types/node@25.9.5))(typescript@5.9.3): + dependencies: + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + eslint: 9.39.5 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) + jest: 30.4.2(@types/node@25.9.5) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-hooks@7.1.1(eslint@9.39.5): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.8 + eslint: 9.39.5 + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.5): + dependencies: + eslint: 9.39.5 + + eslint-plugin-sort-keys-fix@1.1.2: + dependencies: + espree: 6.2.1 + esutils: 2.0.3 + natural-compare: 1.4.0 + requireindex: 1.2.0 + + eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3): + dependencies: + '@typescript-eslint/experimental-utils': 5.62.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/parser': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + eslint: 9.39.5 + json-schema: 0.4.0 + natural-compare-lite: 1.4.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@1.3.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.5: + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.6 + '@eslint/js': 9.39.5 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 9.0.9 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 4.2.1 + + espree@6.2.1: + dependencies: + acorn: 7.4.1 + acorn-jsx: 5.3.2(acorn@7.4.1) + eslint-visitor-keys: 1.3.0 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-x@0.2.2: {} + + expect@30.4.1: + dependencies: + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.60.2 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 9.0.9 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globals@16.5.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + html-escaper@2.0.2: {} + + human-signals@2.1.0: {} + + ignore@5.3.2: {} + + ignore@7.0.6: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-arrayish@0.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@2.0.1: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.8 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@30.4.1: + dependencies: + execa: 5.1.1 + jest-util: 30.4.1 + p-limit: 3.1.0 + + jest-circus@30.4.2: + dependencies: + '@jest/environment': 30.4.1 + '@jest/expect': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.2 + is-generator-fn: 2.1.0 + jest-each: 30.4.1 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-runtime: 30.4.2 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 + p-limit: 3.1.0 + pretty-format: 30.4.1 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.4.2(@types/node@25.9.5): + dependencies: + '@jest/core': 30.4.2 + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.4.2(@types/node@25.9.5) + jest-util: 30.4.1 + jest-validate: 30.4.1 + yargs: 17.7.3 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.4.2(@types/node@25.9.5): + dependencies: + '@babel/core': 7.29.7 + '@jest/get-type': 30.1.0 + '@jest/pattern': 30.4.0 + '@jest/test-sequencer': 30.4.1 + '@jest/types': 30.4.1 + babel-jest: 30.4.1(@babel/core@7.29.7) + chalk: 4.1.2 + ci-info: 4.4.0 + deepmerge: 4.3.1 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-circus: 30.4.2 + jest-docblock: 30.4.0 + jest-environment-node: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-runner: 30.4.2 + jest-util: 30.4.1 + jest-validate: 30.4.1 + parse-json: 5.2.0 + pretty-format: 30.4.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 25.9.5 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.4.1: + dependencies: + '@jest/diff-sequences': 30.4.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.4.1 + + jest-docblock@30.4.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + '@jest/types': 30.4.1 + chalk: 4.1.2 + jest-util: 30.4.1 + pretty-format: 30.4.1 + + jest-environment-node@30.4.1: + dependencies: + '@jest/environment': 30.4.1 + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + jest-mock: 30.4.1 + jest-util: 30.4.1 + jest-validate: 30.4.1 + + jest-haste-map@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 + jest-worker: 30.4.1 + picomatch: 4.0.4 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + pretty-format: 30.4.1 + + jest-matcher-utils@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.4.1 + pretty-format: 30.4.1 + + jest-message-util@30.4.1: + dependencies: + '@babel/code-frame': 7.29.7 + '@jest/types': 30.4.1 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-util: 30.4.1 + picomatch: 4.0.4 + pretty-format: 30.4.1 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + jest-util: 30.4.1 + + jest-pnp-resolver@1.2.3(jest-resolve@30.4.1): + optionalDependencies: + jest-resolve: 30.4.1 + + jest-regex-util@30.4.0: {} + + jest-resolve-dependencies@30.4.2: + dependencies: + jest-regex-util: 30.4.0 + jest-snapshot: 30.4.1 + transitivePeerDependencies: + - supports-color + + jest-resolve@30.4.1: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + jest-pnp-resolver: 1.2.3(jest-resolve@30.4.1) + jest-util: 30.4.1 + jest-validate: 30.4.1 + slash: 3.0.0 + unrs-resolver: 1.12.2 + + jest-runner@30.4.2: + dependencies: + '@jest/console': 30.4.1 + '@jest/environment': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.4.0 + jest-environment-node: 30.4.1 + jest-haste-map: 30.4.1 + jest-leak-detector: 30.4.1 + jest-message-util: 30.4.1 + jest-resolve: 30.4.1 + jest-runtime: 30.4.2 + jest-util: 30.4.1 + jest-watcher: 30.4.1 + jest-worker: 30.4.1 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@30.4.2: + dependencies: + '@jest/environment': 30.4.1 + '@jest/fake-timers': 30.4.1 + '@jest/globals': 30.4.1 + '@jest/source-map': 30.0.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + chalk: 4.1.2 + cjs-module-lexer: 2.2.0 + collect-v8-coverage: 1.0.3 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.4.1: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.8 + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + chalk: 4.1.2 + expect: 30.4.1 + graceful-fs: 4.2.11 + jest-diff: 30.4.1 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + pretty-format: 30.4.1 + semver: 7.8.5 + synckit: 0.11.13 + transitivePeerDependencies: + - supports-color + + jest-util@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + + jest-validate@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + '@jest/types': 30.4.1 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.4.1 + + jest-watcher@30.4.1: + dependencies: + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.9.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.4.1 + string-length: 4.0.2 + + jest-worker@30.4.1: + dependencies: + '@types/node': 25.9.5 + '@ungap/structured-clone': 1.3.3 + jest-util: 30.4.1 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.4.2(@types/node@25.9.5): + dependencies: + '@jest/core': 30.4.2 + '@jest/types': 30.4.1 + import-local: 3.2.0 + jest-cli: 30.4.2(@types/node@25.9.5) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + joycon@3.1.1: {} + + js-tokens@4.0.0: {} + + js-yaml@3.15.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stable-stringify@1.3.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + isarray: 2.0.5 + jsonify: 0.0.1 + object-keys: 1.1.1 + + json5@2.2.3: {} + + jsonify@0.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 4.0.4 + + mimic-fn@2.1.0: {} + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.4 + + minipass@7.1.3: {} + + mlly@1.8.2: + dependencies: + acorn: 8.18.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + napi-postinstall@0.3.4: {} + + natural-compare-lite@1.4.0: {} + + natural-compare@1.4.0: {} + + node-int64@0.4.0: {} + + node-releases@2.0.53: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nunjucks@3.2.4: + dependencies: + a-sync-waterfall: 1.0.1 + asap: 2.0.6 + commander: 5.1.0 + + object-assign@4.1.1: {} + + object-keys@1.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + postcss-load-config@6.0.1(tsx@4.23.11): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + tsx: 4.23.11 + + prelude-ls@1.2.1: {} + + prettier@3.9.6: {} + + pretty-format@30.4.1: + dependencies: + '@jest/schemas': 30.4.1 + ansi-styles: 5.2.0 + react-is-18: react-is@18.3.1 + react-is-19: react-is@19.2.8 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + punycode@2.3.1: {} + + pure-rand@7.0.1: {} + + queue-microtask@1.2.3: {} + + react-is@18.3.1: {} + + react-is@19.2.8: {} + + readdirp@4.1.2: {} + + require-directory@2.1.1: {} + + requireindex@1.2.0: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + reusify@1.1.0: {} + + rollup@4.60.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + semver@6.3.1: {} + + semver@7.8.5: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.17 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + synckit@0.11.13: + dependencies: + '@pkgr/core': 0.3.6 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 7.2.3 + minimatch: 9.0.9 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyexec@0.3.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tree-kill@1.2.2: {} + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tslib@1.14.1: {} + + tslib@2.8.1: + optional: true + + tsup@8.5.1(tsx@4.23.11)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.7) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.7 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(tsx@4.23.11) + resolve-from: 5.0.0 + rollup: 4.60.2 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tsutils@3.21.0(typescript@5.9.3): + dependencies: + tslib: 1.14.1 + typescript: 5.9.3 + + tsx@4.23.11: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + typescript-eslint@8.66.0(eslint@9.39.5)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/parser': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + eslint: 9.39.5 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + ufo@1.6.4: {} + + undici-types@7.24.6: {} + + undici-types@8.10.0: {} + + unrs-resolver@1.12.2: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + + update-browserslist-db@1.3.0(browserslist@4.28.7): + dependencies: + browserslist: 4.28.7 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + ws@8.20.1: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/games/gacha/pinocchio-simple/pnpm-workspace.yaml b/games/gacha/pinocchio-simple/pnpm-workspace.yaml new file mode 100644 index 000000000..1649fa65f --- /dev/null +++ b/games/gacha/pinocchio-simple/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'clients/*' diff --git a/games/gacha/pinocchio-simple/program/Cargo.toml b/games/gacha/pinocchio-simple/program/Cargo.toml new file mode 100644 index 000000000..2d2e93e4a --- /dev/null +++ b/games/gacha/pinocchio-simple/program/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "gacha-simple-program" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[lib] +name = "gacha_simple_program" +crate-type = ["lib", "cdylib"] + +[lints] +workspace = true + +[features] +no-entrypoint = [] + +[build-dependencies] +codama = { workspace = true } +serde_json = { workspace = true } + +[dependencies] +codama = { workspace = true } +const-crypto = { workspace = true } +pinocchio = { workspace = true } +pinocchio-associated-token-account = { workspace = true } +pinocchio-system = { workspace = true } +pinocchio-token-2022 = { workspace = true } +solana-address = { workspace = true } +solana-security-txt = { workspace = true } +solana-sha256-hasher = { workspace = true } +thiserror = { workspace = true } diff --git a/games/gacha/pinocchio-simple/program/build.rs b/games/gacha/pinocchio-simple/program/build.rs new file mode 100644 index 000000000..791126a12 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/build.rs @@ -0,0 +1,38 @@ +//! Codama IDL build script. + +use { + codama::Codama, + std::{env, fs, path::Path}, +}; + +fn main() { + println!("cargo:rerun-if-changed=src/"); + println!("cargo:rerun-if-env-changed=GENERATE_IDL"); + + if let Err(e) = generate_idl() { + println!("cargo:warning=Failed to generate IDL: {}", e) + } +} + +fn generate_idl() -> Result<(), Box> { + let manifest_dir = env::var("CARGO_MANIFEST_DIR")?; + let crate_path = Path::new(&manifest_dir).join("src"); + let codama = Codama::load(&crate_path)?; + let idl_json = codama.get_json_idl()?; + + let mut parsed: serde_json::Value = serde_json::from_str(&idl_json)?; + if let Some(program) = parsed.get_mut("program").and_then(serde_json::Value::as_object_mut) { + program.insert("name".to_string(), serde_json::Value::String("gacha_simple".to_string())); + } + let mut formatted_json = serde_json::to_string_pretty(&parsed)?; + formatted_json.push('\n'); + + let project_root = Path::new(&manifest_dir).parent().unwrap(); + let idl_dir = project_root.join("idl"); + fs::create_dir_all(&idl_dir)?; + let idl_path = idl_dir.join("gacha_simple.json"); + fs::write(&idl_path, formatted_json)?; + + println!("cargo:warning=IDL written to: {}", idl_path.display()); + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/entrypoint.rs b/games/gacha/pinocchio-simple/program/src/entrypoint.rs new file mode 100644 index 000000000..79a5f861e --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/entrypoint.rs @@ -0,0 +1,22 @@ +use pinocchio::{account::AccountView, entrypoint, Address, ProgramResult}; + +use crate::instructions::{ + buy_pull, emit_event, init_pool, refund_pull, settle_and_distribute, withdraw_fees, GachaInstruction, +}; + +entrypoint!(process_instruction); + +pub fn process_instruction( + program_id: &Address, + accounts: &mut [AccountView], + instruction_data: &[u8], +) -> ProgramResult { + match GachaInstruction::from_bytes(instruction_data)? { + GachaInstruction::InitPool(data) => init_pool::process(accounts, &data), + GachaInstruction::BuyPull(data) => buy_pull::process(accounts, &data), + GachaInstruction::SettleAndDistribute(data) => settle_and_distribute::process(accounts, &data), + GachaInstruction::RefundPull => refund_pull::process(accounts), + GachaInstruction::WithdrawFees(data) => withdraw_fees::process(accounts, &data), + GachaInstruction::EmitEvent => emit_event::process(program_id, accounts), + } +} diff --git a/games/gacha/pinocchio-simple/program/src/errors.rs b/games/gacha/pinocchio-simple/program/src/errors.rs new file mode 100644 index 000000000..2568cf6b6 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/errors.rs @@ -0,0 +1,133 @@ +use codama::CodamaErrors; +use pinocchio::error::ProgramError; +use thiserror::Error; + +impl From for ProgramError { + fn from(e: GachaError) -> Self { + ProgramError::Custom(e as u32) + } +} + +#[cfg(test)] +impl TryFrom for GachaError { + type Error = u32; + + fn try_from(code: u32) -> Result { + match code { + 100 => Ok(Self::NotSigner), + 101 => Ok(Self::AccountNotWritable), + 102 => Ok(Self::NotSystemProgram), + 103 => Ok(Self::NotEnoughAccountKeys), + 104 => Ok(Self::InvalidInstruction), + 105 => Ok(Self::InvalidAccountData), + 106 => Ok(Self::InvalidAccountDiscriminator), + 107 => Ok(Self::ArithmeticOverflow), + 108 => Ok(Self::NotProgramOwned), + 109 => Ok(Self::NotTokenProgram), + 110 => Ok(Self::NotAtaProgram), + 200 => Ok(Self::InvalidPoolPda), + 201 => Ok(Self::PoolAlreadyExists), + 202 => Ok(Self::Unauthorized), + 203 => Ok(Self::InvalidTierConfig), + 204 => Ok(Self::TooManyTiers), + 205 => Ok(Self::InvalidEntryFee), + 206 => Ok(Self::InvalidOperator), + 207 => Ok(Self::InvalidSettleDeadline), + 300 => Ok(Self::InvalidPullPda), + 301 => Ok(Self::PullAlreadyExists), + 303 => Ok(Self::PoolMismatch), + 304 => Ok(Self::RefundTooEarly), + 305 => Ok(Self::BuyerMismatch), + 400 => Ok(Self::NotOperator), + 401 => Ok(Self::InvalidMintPda), + 500 => Ok(Self::InvalidVaultPda), + 501 => Ok(Self::InsufficientVaultFunds), + 600 => Ok(Self::InvalidEventAuthority), + 601 => Ok(Self::InvalidEventData), + _ => Err(code), + } + } +} + +/// Program-specific error codes for the gacha program. +/// +/// - **100--199**: Generic account and data validation errors. +/// - **200--299**: Pool configuration errors. +/// - **300--399**: Pull (commit / refund) errors. +/// - **400--499**: Settle (reveal) errors. +/// - **500--599**: Vault errors. +/// - **600--699**: Event emission errors. +#[derive(Debug, Copy, Clone, Error, CodamaErrors)] +pub enum GachaError { + // --- Generic errors (100--199) --- + #[error("Account must be a signer")] + NotSigner = 100, + #[error("Account must be writable")] + AccountNotWritable, + #[error("Expected system program")] + NotSystemProgram, + #[error("Not enough account keys provided")] + NotEnoughAccountKeys, + #[error("Invalid instruction")] + InvalidInstruction, + #[error("Invalid account data")] + InvalidAccountData, + #[error("Invalid account discriminator")] + InvalidAccountDiscriminator, + #[error("Arithmetic overflow")] + ArithmeticOverflow, + #[error("Account is not owned by this program")] + NotProgramOwned, + #[error("Expected the Token-2022 program")] + NotTokenProgram, + #[error("Expected the associated token account program")] + NotAtaProgram, + + // --- Pool errors (200--299) --- + #[error("Invalid pool PDA derivation")] + InvalidPoolPda = 200, + #[error("Pool account already exists")] + PoolAlreadyExists, + #[error("Signer is not the pool admin")] + Unauthorized, + #[error("Tier configuration is invalid (zero weight)")] + InvalidTierConfig, + #[error("Tier count is zero or exceeds the maximum")] + TooManyTiers, + #[error("Entry fee must be nonzero")] + InvalidEntryFee, + #[error("Operator must be a nonzero, on-curve key distinct from the admin")] + InvalidOperator, + #[error("Settle deadline must be nonzero")] + InvalidSettleDeadline, + + // --- Pull errors (300--399) --- + #[error("Invalid pull PDA derivation")] + InvalidPullPda = 300, + #[error("Pull already exists for this index")] + PullAlreadyExists = 301, + #[error("Pull does not belong to the provided pool")] + PoolMismatch = 303, + #[error("Settle deadline has not passed yet")] + RefundTooEarly = 304, + #[error("Account is not the pull's buyer")] + BuyerMismatch = 305, + + // --- Settle errors (400--499) --- + #[error("Signer is not the registered pool operator")] + NotOperator = 400, + #[error("Invalid prize mint PDA derivation")] + InvalidMintPda = 401, + + // --- Vault errors (500--599) --- + #[error("Invalid vault PDA derivation")] + InvalidVaultPda = 500, + #[error("Withdrawal exceeds the vault balance net of pending-pull liabilities")] + InsufficientVaultFunds, + + // --- Event errors (600--699) --- + #[error("Invalid event authority PDA")] + InvalidEventAuthority = 600, + #[error("Invalid event data")] + InvalidEventData, +} diff --git a/games/gacha/pinocchio-simple/program/src/event_engine.rs b/games/gacha/pinocchio-simple/program/src/event_engine.rs new file mode 100644 index 000000000..afb740bb8 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/event_engine.rs @@ -0,0 +1,182 @@ +//! Event emission engine using Anchor-compatible self-CPI. +//! +//! Events are emitted by invoking this program's own [`EmitEvent`](crate::instructions::emit_event) +//! instruction via CPI, signed by the event authority PDA. Indexers detect these +//! inner instructions by the 8-byte [`EVENT_IX_TAG`] prefix in the instruction data. + +use core::mem::size_of; + +use alloc::vec::Vec; +use codama::CodamaAccount; +use const_crypto::ed25519; +use pinocchio::cpi::{invoke_signed, Seed, Signer}; +use pinocchio::error::ProgramError; +use pinocchio::instruction::{InstructionAccount, InstructionView}; +use pinocchio::{AccountView, Address, ProgramResult}; + +use crate::errors::GachaError; + +/// Event authority PDA — no account data, only used for CPI event emission signing. +#[derive(CodamaAccount)] +#[codama(seed(type = string(utf8), value = "event_authority"))] +pub struct EventAuthority; + +/// PDA seed for the event authority account. +pub const EVENT_AUTHORITY_SEED: &[u8] = b"event_authority"; + +/// Anchor-compatible event tag: `Sha256("anchor:event")[..8]`. +pub const EVENT_IX_TAG: u64 = 0x1d9acb512ea545e4; + +/// Little-endian byte representation of [`EVENT_IX_TAG`]. +pub const EVENT_IX_TAG_LE: [u8; 8] = EVENT_IX_TAG.to_le_bytes(); + +/// Wire format prefix length: 8-byte tag + 1-byte event discriminator. +pub const EVENT_DISCRIMINATOR_LEN: usize = size_of::() + 1; + +/// Instruction discriminator for the EmitEvent no-op instruction. +pub const EMIT_EVENT_IX_DISC: u8 = 228; + +/// Compile-time derived PDA for the event authority. +pub mod event_authority_pda { + use super::*; + + const EVENT_AUTHORITY_AND_BUMP: ([u8; 32], u8) = + ed25519::derive_program_address(&[EVENT_AUTHORITY_SEED], crate::ID.as_array()); + + /// The event authority PDA address, derived at compile time. + pub const ID: Address = Address::new_from_array(EVENT_AUTHORITY_AND_BUMP.0); + + /// The PDA bump seed for the event authority. + pub const BUMP: u8 = EVENT_AUTHORITY_AND_BUMP.1; +} + +/// Defines the event discriminator byte used in the wire format prefix. +pub trait EventDiscriminator { + const DISCRIMINATOR: u8; +} + +#[cfg(test)] +fn discriminator_bytes() -> Vec { + let mut bytes = Vec::with_capacity(EVENT_DISCRIMINATOR_LEN); + bytes.extend_from_slice(&EVENT_IX_TAG_LE); + bytes.push(T::DISCRIMINATOR); + bytes +} + +/// Serializes an event into its wire format: tag + discriminator + field data. +pub trait EventSerialize: EventDiscriminator { + /// The length of the serialized event data (excluding discriminator). + const DATA_LEN: usize; + + /// Appends the event's field data to the given buffer. + fn write_inner(&self, writer: &mut Vec); + + fn to_bytes(&self) -> Vec { + let mut data = Vec::with_capacity(Self::DATA_LEN + EVENT_DISCRIMINATOR_LEN); + data.extend_from_slice(&EVENT_IX_TAG_LE); + data.push(Self::DISCRIMINATOR); + self.write_inner(&mut data); + data + } +} + +/// Registry of all event discriminator values. +/// +/// Each variant's `u8` value is written as the 9th byte of the event wire format +/// (after the 8-byte [`EVENT_IX_TAG_LE`] prefix), letting indexers identify the +/// event type. +#[repr(u8)] +pub enum EventDiscriminators { + PoolInitialized = 0, + PullRequested = 1, + PullSettled = 2, + PullRefunded = 3, + FeesWithdrawn = 4, +} + +impl TryFrom for EventDiscriminators { + type Error = u8; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::PoolInitialized), + 1 => Ok(Self::PullRequested), + 2 => Ok(Self::PullSettled), + 3 => Ok(Self::PullRefunded), + 4 => Ok(Self::FeesWithdrawn), + _ => Err(value), + } + } +} + +/// Verifies that the given account matches the compile-time event authority PDA. +#[inline(always)] +pub fn verify_event_authority(account: &AccountView) -> Result<(), ProgramError> { + if account.address() != &event_authority_pda::ID { + return Err(GachaError::InvalidEventAuthority.into()); + } + Ok(()) +} + +/// Emits an event via self-CPI, recording event data in inner instruction data. +pub fn emit_event( + program_id: &Address, + event_authority: &AccountView, + self_program: &AccountView, + event_data: &[u8], +) -> ProgramResult { + verify_event_authority(event_authority)?; + + let bump = [event_authority_pda::BUMP]; + let signer_seeds: [Seed; 2] = [Seed::from(EVENT_AUTHORITY_SEED), Seed::from(&bump)]; + let signer = Signer::from(&signer_seeds); + + let accounts = [InstructionAccount::readonly_signer(event_authority.address())]; + + let instruction = InstructionView { program_id, data: event_data, accounts: &accounts }; + + invoke_signed::<2, _>(&instruction, &[event_authority, self_program], &[signer]) +} + +#[cfg(test)] +mod tests { + use super::*; + + struct StubEventA { + value: u64, + } + + impl EventDiscriminator for StubEventA { + const DISCRIMINATOR: u8 = 10; + } + + impl EventSerialize for StubEventA { + const DATA_LEN: usize = 8; + fn write_inner(&self, writer: &mut Vec) { + writer.extend_from_slice(&self.value.to_le_bytes()); + } + } + + #[test] + fn constants_are_consistent() { + assert_eq!(EVENT_IX_TAG_LE, EVENT_IX_TAG.to_le_bytes()); + assert_eq!(EVENT_DISCRIMINATOR_LEN, 8 + 1); + } + + #[test] + fn to_bytes_prepends_tag_and_discriminator() { + let event = StubEventA { value: 42 }; + let bytes = event.to_bytes(); + assert_eq!(&bytes[..8], &EVENT_IX_TAG_LE); + assert_eq!(bytes[8], StubEventA::DISCRIMINATOR); + assert_eq!(&bytes[9..], &42u64.to_le_bytes()); + } + + #[test] + fn discriminator_bytes_has_correct_prefix() { + let disc = discriminator_bytes::(); + assert_eq!(disc.len(), EVENT_DISCRIMINATOR_LEN); + assert_eq!(&disc[..8], &EVENT_IX_TAG_LE); + assert_eq!(disc[8], StubEventA::DISCRIMINATOR); + } +} diff --git a/games/gacha/pinocchio-simple/program/src/events/fees_withdrawn.rs b/games/gacha/pinocchio-simple/program/src/events/fees_withdrawn.rs new file mode 100644 index 000000000..020243b47 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/events/fees_withdrawn.rs @@ -0,0 +1,37 @@ +use core::mem::size_of; + +use alloc::vec::Vec; +use pinocchio::Address; + +use crate::event_engine::{EventDiscriminator, EventDiscriminators, EventSerialize}; + +/// Emitted when the admin withdraws settled entry fees from the vault. +#[repr(C, packed)] +pub struct FeesWithdrawnEvent { + pub pool: Address, + pub admin: Address, + pub amount: u64, +} + +impl FeesWithdrawnEvent { + pub const DATA_LEN: usize = size_of::(); + + pub fn new(pool: Address, admin: Address, amount: u64) -> Self { + Self { pool, admin, amount } + } +} + +impl EventDiscriminator for FeesWithdrawnEvent { + const DISCRIMINATOR: u8 = EventDiscriminators::FeesWithdrawn as u8; +} + +impl EventSerialize for FeesWithdrawnEvent { + const DATA_LEN: usize = Self::DATA_LEN; + + fn write_inner(&self, writer: &mut Vec) { + let amount = self.amount; + writer.extend_from_slice(self.pool.as_ref()); + writer.extend_from_slice(self.admin.as_ref()); + writer.extend_from_slice(&amount.to_le_bytes()); + } +} diff --git a/games/gacha/pinocchio-simple/program/src/events/mod.rs b/games/gacha/pinocchio-simple/program/src/events/mod.rs new file mode 100644 index 000000000..157261c24 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/events/mod.rs @@ -0,0 +1,17 @@ +//! Event types emitted by the gacha program via self-CPI. +//! +//! Each event struct implements [`EventDiscriminator`](crate::event_engine::EventDiscriminator) +//! and [`EventSerialize`](crate::event_engine::EventSerialize): an 8-byte tag prefix, +//! a 1-byte discriminator, then the event-specific payload. + +pub mod fees_withdrawn; +pub mod pool_initialized; +pub mod pull_refunded; +pub mod pull_requested; +pub mod pull_settled; + +pub use fees_withdrawn::FeesWithdrawnEvent; +pub use pool_initialized::PoolInitializedEvent; +pub use pull_refunded::PullRefundedEvent; +pub use pull_requested::PullRequestedEvent; +pub use pull_settled::PullSettledEvent; diff --git a/games/gacha/pinocchio-simple/program/src/events/pool_initialized.rs b/games/gacha/pinocchio-simple/program/src/events/pool_initialized.rs new file mode 100644 index 000000000..05b2d97ad --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/events/pool_initialized.rs @@ -0,0 +1,42 @@ +use core::mem::size_of; + +use alloc::vec::Vec; +use pinocchio::Address; + +use crate::event_engine::{EventDiscriminator, EventDiscriminators, EventSerialize}; + +/// Emitted when an admin creates a gacha pool. +#[repr(C, packed)] +pub struct PoolInitializedEvent { + pub admin: Address, + pub operator: Address, + pub entry_fee: u64, + pub settle_deadline_slots: u64, + pub tier_count: u8, +} + +impl PoolInitializedEvent { + pub const DATA_LEN: usize = size_of::(); + + pub fn new(admin: Address, operator: Address, entry_fee: u64, settle_deadline_slots: u64, tier_count: u8) -> Self { + Self { admin, operator, entry_fee, settle_deadline_slots, tier_count } + } +} + +impl EventDiscriminator for PoolInitializedEvent { + const DISCRIMINATOR: u8 = EventDiscriminators::PoolInitialized as u8; +} + +impl EventSerialize for PoolInitializedEvent { + const DATA_LEN: usize = Self::DATA_LEN; + + fn write_inner(&self, writer: &mut Vec) { + let entry_fee = self.entry_fee; + let settle_deadline_slots = self.settle_deadline_slots; + writer.extend_from_slice(self.admin.as_ref()); + writer.extend_from_slice(self.operator.as_ref()); + writer.extend_from_slice(&entry_fee.to_le_bytes()); + writer.extend_from_slice(&settle_deadline_slots.to_le_bytes()); + writer.push(self.tier_count); + } +} diff --git a/games/gacha/pinocchio-simple/program/src/events/pull_refunded.rs b/games/gacha/pinocchio-simple/program/src/events/pull_refunded.rs new file mode 100644 index 000000000..d91da13f2 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/events/pull_refunded.rs @@ -0,0 +1,42 @@ +use core::mem::size_of; + +use alloc::vec::Vec; +use pinocchio::Address; + +use crate::event_engine::{EventDiscriminator, EventDiscriminators, EventSerialize}; + +/// Emitted when a buyer reclaims a pull the operator never settled. `amount` is +/// the refunded entry fee; the pull's rent is returned separately when the +/// account closes. +#[repr(C, packed)] +pub struct PullRefundedEvent { + pub pool: Address, + pub buyer: Address, + pub index: u64, + pub amount: u64, +} + +impl PullRefundedEvent { + pub const DATA_LEN: usize = size_of::(); + + pub fn new(pool: Address, buyer: Address, index: u64, amount: u64) -> Self { + Self { pool, buyer, index, amount } + } +} + +impl EventDiscriminator for PullRefundedEvent { + const DISCRIMINATOR: u8 = EventDiscriminators::PullRefunded as u8; +} + +impl EventSerialize for PullRefundedEvent { + const DATA_LEN: usize = Self::DATA_LEN; + + fn write_inner(&self, writer: &mut Vec) { + let index = self.index; + let amount = self.amount; + writer.extend_from_slice(self.pool.as_ref()); + writer.extend_from_slice(self.buyer.as_ref()); + writer.extend_from_slice(&index.to_le_bytes()); + writer.extend_from_slice(&amount.to_le_bytes()); + } +} diff --git a/games/gacha/pinocchio-simple/program/src/events/pull_requested.rs b/games/gacha/pinocchio-simple/program/src/events/pull_requested.rs new file mode 100644 index 000000000..eb68cb8c4 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/events/pull_requested.rs @@ -0,0 +1,43 @@ +use core::mem::size_of; + +use alloc::vec::Vec; +use pinocchio::Address; + +use crate::event_engine::{EventDiscriminator, EventDiscriminators, EventSerialize}; + +/// Emitted when a buyer opens a pull (the commit phase). Carries the buyer's +/// `client_seed` and the derived VRF input `alpha` so verifiers can check +/// `alpha = SHA-256(pull || client_seed)` and later reproduce the reveal. +#[repr(C, packed)] +pub struct PullRequestedEvent { + pub pool: Address, + pub buyer: Address, + pub index: u64, + pub client_seed: [u8; 32], + pub alpha: [u8; 32], +} + +impl PullRequestedEvent { + pub const DATA_LEN: usize = size_of::(); + + pub fn new(pool: Address, buyer: Address, index: u64, client_seed: [u8; 32], alpha: [u8; 32]) -> Self { + Self { pool, buyer, index, client_seed, alpha } + } +} + +impl EventDiscriminator for PullRequestedEvent { + const DISCRIMINATOR: u8 = EventDiscriminators::PullRequested as u8; +} + +impl EventSerialize for PullRequestedEvent { + const DATA_LEN: usize = Self::DATA_LEN; + + fn write_inner(&self, writer: &mut Vec) { + let index = self.index; + writer.extend_from_slice(self.pool.as_ref()); + writer.extend_from_slice(self.buyer.as_ref()); + writer.extend_from_slice(&index.to_le_bytes()); + writer.extend_from_slice(&self.client_seed); + writer.extend_from_slice(&self.alpha); + } +} diff --git a/games/gacha/pinocchio-simple/program/src/events/pull_settled.rs b/games/gacha/pinocchio-simple/program/src/events/pull_settled.rs new file mode 100644 index 000000000..b82d158a7 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/events/pull_settled.rs @@ -0,0 +1,60 @@ +use core::mem::size_of; + +use alloc::vec::Vec; +use pinocchio::Address; + +use crate::event_engine::{EventDiscriminator, EventDiscriminators, EventSerialize}; + +/// Emitted when the operator reveals a pull and its prize NFT is minted. +/// Carries `alpha`, `beta`, the 80-byte ECVRF `proof`, and the prize `mint` so +/// anyone can verify `beta = VRF(alpha)` off-chain and reproduce the selected +/// `tier`. The same provenance also lives in the mint's metadata. +#[repr(C, packed)] +pub struct PullSettledEvent { + pub pool: Address, + pub buyer: Address, + pub index: u64, + pub tier: u8, + pub alpha: [u8; 32], + pub beta: [u8; 64], + pub proof: [u8; 80], + pub mint: Address, +} + +impl PullSettledEvent { + pub const DATA_LEN: usize = size_of::(); + + #[allow(clippy::too_many_arguments)] + pub fn new( + pool: Address, + buyer: Address, + index: u64, + tier: u8, + alpha: [u8; 32], + beta: [u8; 64], + proof: [u8; 80], + mint: Address, + ) -> Self { + Self { pool, buyer, index, tier, alpha, beta, proof, mint } + } +} + +impl EventDiscriminator for PullSettledEvent { + const DISCRIMINATOR: u8 = EventDiscriminators::PullSettled as u8; +} + +impl EventSerialize for PullSettledEvent { + const DATA_LEN: usize = Self::DATA_LEN; + + fn write_inner(&self, writer: &mut Vec) { + let index = self.index; + writer.extend_from_slice(self.pool.as_ref()); + writer.extend_from_slice(self.buyer.as_ref()); + writer.extend_from_slice(&index.to_le_bytes()); + writer.push(self.tier); + writer.extend_from_slice(&self.alpha); + writer.extend_from_slice(&self.beta); + writer.extend_from_slice(&self.proof); + writer.extend_from_slice(self.mint.as_ref()); + } +} diff --git a/games/gacha/pinocchio-simple/program/src/gacha.rs b/games/gacha/pinocchio-simple/program/src/gacha.rs new file mode 100644 index 000000000..467652bcb --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/gacha.rs @@ -0,0 +1,118 @@ +//! Pure gacha logic: alpha derivation, weighted tier selection, and prize constants. +//! +//! [`select_tier`] maps a 64-byte VRF output (`beta`) to a reward tier and +//! [`derive_alpha`] binds the VRF input to buyer-supplied entropy. Both are host +//! unit-tested and mirrored byte-for-byte by the off-chain verifier in the +//! TypeScript client (`selectTier` / `pullAlpha` in `@solana/gacha-simple`), so +//! anyone can reproduce a pull result from on-chain data and check it against +//! the recorded tier. +//! +//! Tier weights are a primitive array rather than an array of structs so the +//! layout maps cleanly through Codama to the client. + +use pinocchio::Address; + +use crate::GachaError; + +/// Maximum number of reward tiers a pool can define. +pub const MAX_TIERS: usize = 8; + +/// Human-readable rarity label per tier index, recorded in each prize NFT's +/// Token-2022 metadata under the `"rarity"` key. Tier 0 is the most common; +/// pools list tiers from highest to lowest weight by convention. +pub const RARITY_LABELS: [&str; MAX_TIERS] = + ["common", "uncommon", "rare", "epic", "legendary", "mythic", "exotic", "divine"]; + +/// Prize NFT name prefix; the pull index is appended in decimal. +pub const NFT_NAME_PREFIX: &str = "Gacha Pull #"; +/// Prize NFT symbol. +pub const NFT_SYMBOL: &str = "GACHAS"; +/// Prize NFT metadata URI. +pub const NFT_URI: &str = "https://example.com/gacha.json"; + +/// `additional_metadata` key for the tier's rarity label. +pub const METADATA_RARITY_KEY: &str = "rarity"; +/// `additional_metadata` key for the pull address (lowercase hex). +pub const METADATA_PULL_KEY: &str = "pull"; +/// `additional_metadata` key for the buyer's entropy (lowercase hex). +pub const METADATA_CLIENT_SEED_KEY: &str = "client_seed"; +/// `additional_metadata` key for the ECVRF output (lowercase hex). +pub const METADATA_BETA_KEY: &str = "beta"; +/// `additional_metadata` key for the 80-byte ECVRF proof (lowercase hex). +pub const METADATA_PROOF_KEY: &str = "proof"; + +/// Derives a pull's VRF input: `SHA-256(pull_address || client_seed)`. +/// +/// Binding the buyer's `client_seed` makes `alpha` unpredictable to the operator +/// before the buy transaction lands — a *fixed* alpha (like the pull address +/// alone) is not enough, because `beta = VRF(operator_key, alpha)` is +/// deterministic and an operator who can predict alpha can precompute every +/// outcome. Hashing in the pull address makes alpha unique per pull even if a +/// buyer reuses a seed. +pub fn derive_alpha(pull: &Address, client_seed: &[u8; 32]) -> [u8; 32] { + solana_sha256_hasher::hashv(&[pull.as_ref(), client_seed]).to_bytes() +} + +/// Selects a reward tier from a VRF output, weighted by the pool's fixed tier +/// weights. +/// +/// The first 16 bytes of `beta` are read as a little-endian `u128` and reduced +/// modulo the total weight; the resulting target walks the tiers in order. The +/// weights never change after init, so every pull faces identical odds and the +/// outcome depends only on `beta` — never on how many other pulls exist or the +/// order in which the operator settles them. +pub fn select_tier(beta: &[u8; 64], weights: &[u32; MAX_TIERS], tier_count: u8) -> Result { + let count = (tier_count as usize).min(MAX_TIERS); + + let mut total: u64 = 0; + for &weight in &weights[..count] { + total = total.checked_add(weight as u64).ok_or(GachaError::ArithmeticOverflow)?; + } + if total == 0 { + return Err(GachaError::InvalidTierConfig); + } + + let mut seed = [0u8; 16]; + seed.copy_from_slice(&beta[..16]); + let mut target = (u128::from_le_bytes(seed) % total as u128) as u64; + + for (i, &weight) in weights[..count].iter().enumerate() { + let weight = weight as u64; + if target < weight { + return Ok(i as u8); + } + target -= weight; + } + + Err(GachaError::InvalidTierConfig) +} + +/// Formats `value` in decimal into `buf`, returning the used suffix of the +/// buffer. Used to build prize NFT names without `alloc`. +pub fn format_u64(value: u64, buf: &mut [u8; 20]) -> &str { + let mut i = buf.len(); + let mut v = value; + loop { + i -= 1; + buf[i] = b'0' + (v % 10) as u8; + v /= 10; + if v == 0 { + break; + } + } + // Digits are ASCII, so the slice is always valid UTF-8. + unsafe { core::str::from_utf8_unchecked(&buf[i..]) } +} + +/// Encodes `bytes` as lowercase hex into `buf`, returning the used prefix of the +/// buffer. Used to write reveal provenance into prize NFT metadata without +/// `alloc`. `buf` must hold at least `2 * bytes.len()` bytes. +pub fn format_hex<'a>(bytes: &[u8], buf: &'a mut [u8]) -> &'a str { + const HEX: &[u8; 16] = b"0123456789abcdef"; + for (i, &byte) in bytes.iter().enumerate() { + buf[2 * i] = HEX[(byte >> 4) as usize]; + buf[2 * i + 1] = HEX[(byte & 0x0f) as usize]; + } + // Hex digits are ASCII, so the slice is always valid UTF-8. + unsafe { core::str::from_utf8_unchecked(&buf[..bytes.len() * 2]) } +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/buy_pull.rs b/games/gacha/pinocchio-simple/program/src/instructions/buy_pull.rs new file mode 100644 index 000000000..cdd1d6ed8 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/buy_pull.rs @@ -0,0 +1,157 @@ +use core::mem::{size_of, transmute}; + +use codama::CodamaType; +use pinocchio::{ + cpi::Seed, + error::ProgramError, + sysvars::{clock::Clock, Sysvar}, + AccountView, ProgramResult, +}; +use pinocchio_system::instructions::Transfer; + +use crate::{ + event_engine::{self, EventSerialize}, + events::PullRequestedEvent, + gacha::derive_alpha, + instructions::helpers::{check_signer, check_system_program, check_writable, create_pda_account}, + state::{ + common::{find_pull_pda, find_vault_pda, PULL_SEED}, + pool::Pool, + pull::Pull, + }, + GachaError, +}; + +/// Instruction discriminator byte for `BuyPull`. +pub const DISCRIMINATOR: &u8 = &1; + +/// Instruction data for [`BuyPull`](crate::GachaInstruction::BuyPull). +#[repr(C, packed)] +#[derive(CodamaType, Debug, Clone)] +pub struct BuyPullData { + /// Buyer-supplied entropy mixed into the VRF input: + /// `alpha = SHA-256(pull_address || client_seed)`. Should be 32 random bytes + /// generated client-side; it is what makes the outcome unpredictable to the + /// operator before the buy lands. + pub client_seed: [u8; 32], +} + +impl BuyPullData { + pub const LEN: usize = size_of::(); + + pub fn load(data: &[u8]) -> Result<&Self, ProgramError> { + if data.len() != Self::LEN { + return Err(GachaError::InvalidInstruction.into()); + } + Ok(unsafe { &*transmute::<*const u8, *const Self>(data.as_ptr()) }) + } +} + +/// Validated accounts for [`BuyPull`](crate::GachaInstruction::BuyPull). +pub struct BuyPullAccounts<'a> { + pub buyer: &'a AccountView, + pub pool: &'a mut AccountView, + pub pull: &'a mut AccountView, + pub vault: &'a AccountView, + pub system_program: &'a AccountView, + pub event_authority: &'a AccountView, + pub self_program: &'a AccountView, +} + +impl<'a> TryFrom<&'a mut [AccountView]> for BuyPullAccounts<'a> { + type Error = ProgramError; + + fn try_from(accounts: &'a mut [AccountView]) -> Result { + let [buyer, pool, pull, vault, system_program, event_authority, self_program] = accounts else { + return Err(GachaError::NotEnoughAccountKeys.into()); + }; + + check_signer(buyer)?; + check_writable(buyer)?; + check_writable(pool)?; + Pool::check(pool)?; + check_writable(pull)?; + check_writable(vault)?; + check_system_program(system_program)?; + + Ok(Self { buyer, pool, pull, vault, system_program, event_authority, self_program }) + } +} + +/// Opens a pull: escrows the entry fee, commits the VRF input `alpha`, and emits a +/// [`PullRequestedEvent`]. The pull is created pending; the operator settles it +/// later, or the buyer refunds it after the pool's settle deadline. +/// +/// The buyer pays the pull account's rent on top of the entry fee; the full +/// entry fee goes to the vault, and the rent comes back when the pull account +/// closes on refund. +pub fn process(accounts: &mut [AccountView], data: &BuyPullData) -> ProgramResult { + let accounts = BuyPullAccounts::try_from(accounts)?; + + let entry_fee; + let index; + let admin; + { + let pool_data = accounts.pool.try_borrow()?; + let pool = Pool::load(&pool_data)?; + entry_fee = pool.entry_fee; + index = pool.pulls_count; + admin = pool.admin; + } + + if find_vault_pda(&admin).0 != *accounts.vault.address() { + return Err(GachaError::InvalidVaultPda.into()); + } + + let pool_key = *accounts.pool.address(); + let (pull_pda, pull_bump) = find_pull_pda(&pool_key, accounts.buyer.address(), index); + if pull_pda != *accounts.pull.address() { + return Err(GachaError::InvalidPullPda.into()); + } + if accounts.pull.data_len() > 0 { + return Err(GachaError::PullAlreadyExists.into()); + } + + let client_seed = data.client_seed; + let alpha = derive_alpha(&pull_pda, &client_seed); + let requested_slot = Clock::get()?.slot; + + let index_bytes = index.to_le_bytes(); + let bump_bytes = [pull_bump]; + let seeds = [ + Seed::from(PULL_SEED), + Seed::from(pool_key.as_ref()), + Seed::from(accounts.buyer.address().as_ref()), + Seed::from(&index_bytes[..]), + Seed::from(&bump_bytes[..]), + ]; + create_pda_account(accounts.buyer, accounts.pull, &seeds, Pull::LEN, &crate::ID)?; + + Transfer { from: accounts.buyer, to: accounts.vault, lamports: entry_fee }.invoke()?; + + { + let mut pull_data = accounts.pull.try_borrow_mut()?; + Pull::init( + &mut pull_data, + pull_bump, + &pool_key, + accounts.buyer.address(), + index, + &client_seed, + &alpha, + requested_slot, + )?; + } + + { + let mut pool_data = accounts.pool.try_borrow_mut()?; + let pool = Pool::load_mut(&mut pool_data)?; + pool.pulls_count = pool.pulls_count.checked_add(1).ok_or(GachaError::ArithmeticOverflow)?; + pool.pending_pulls = pool.pending_pulls.checked_add(1).ok_or(GachaError::ArithmeticOverflow)?; + } + + let event = PullRequestedEvent::new(pool_key, *accounts.buyer.address(), index, client_seed, alpha); + event_engine::emit_event(&crate::ID, accounts.event_authority, accounts.self_program, &event.to_bytes())?; + + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/emit_event.rs b/games/gacha/pinocchio-simple/program/src/instructions/emit_event.rs new file mode 100644 index 000000000..dd3163c03 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/emit_event.rs @@ -0,0 +1,22 @@ +use pinocchio::{error::ProgramError, AccountView, Address, ProgramResult}; + +use crate::event_engine::verify_event_authority; + +/// No-op instruction used as the target of self-CPI event emission. +/// +/// It only verifies that the caller is the event authority PDA. It exists so +/// indexers can detect event data in the inner instruction. It is never invoked +/// directly by external callers. +pub fn process(_program_id: &Address, accounts: &[AccountView]) -> ProgramResult { + let [event_authority] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if !event_authority.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + verify_event_authority(event_authority)?; + + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/helpers/account.rs b/games/gacha/pinocchio-simple/program/src/instructions/helpers/account.rs new file mode 100644 index 000000000..74740dfba --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/helpers/account.rs @@ -0,0 +1,37 @@ +//! PDA creation. + +use pinocchio::{ + cpi::{Seed, Signer}, + sysvars::{rent::Rent, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_system::instructions::{Allocate, Assign, CreateAccount, Transfer}; + +/// Creates and allocates an `owner`-owned PDA, funding rent from `payer`. +/// +/// `seeds` must include the bump seed as the final element. Idempotent against a +/// pre-funded PDA address: tops up rent then allocates and assigns, so a +/// griefer's donation to the address cannot brick creation. +pub fn create_pda_account( + payer: &AccountView, + account: &AccountView, + seeds: &[Seed], + space: usize, + owner: &Address, +) -> ProgramResult { + let lamports = Rent::get()?.try_minimum_balance(space)?; + let signer = [Signer::from(seeds)]; + + if account.lamports() == 0 { + CreateAccount { from: payer, to: account, lamports, space: space as u64, owner }.invoke_signed(&signer)?; + } else { + let required = lamports.saturating_sub(account.lamports()); + if required > 0 { + Transfer { from: payer, to: account, lamports: required }.invoke()?; + } + Allocate { account, space: space as u64 }.invoke_signed(&signer)?; + Assign { account, owner }.invoke_signed(&signer)?; + } + + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/helpers/checks.rs b/games/gacha/pinocchio-simple/program/src/instructions/helpers/checks.rs new file mode 100644 index 000000000..34982b1bd --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/helpers/checks.rs @@ -0,0 +1,29 @@ +//! Account-flag and program-identity guards shared by every instruction. + +use pinocchio::{error::ProgramError, AccountView}; + +use crate::GachaError; + +/// Returns an error unless `account` is a transaction signer. +pub fn check_signer(account: &AccountView) -> Result<(), ProgramError> { + if !account.is_signer() { + return Err(GachaError::NotSigner.into()); + } + Ok(()) +} + +/// Returns an error unless `account` is marked writable. +pub fn check_writable(account: &AccountView) -> Result<(), ProgramError> { + if !account.is_writable() { + return Err(GachaError::AccountNotWritable.into()); + } + Ok(()) +} + +/// Returns an error unless `account` is the System Program. +pub fn check_system_program(account: &AccountView) -> Result<(), ProgramError> { + if account.address().ne(&pinocchio_system::ID) { + return Err(GachaError::NotSystemProgram.into()); + } + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/helpers/mod.rs b/games/gacha/pinocchio-simple/program/src/instructions/helpers/mod.rs new file mode 100644 index 000000000..073e8c356 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/helpers/mod.rs @@ -0,0 +1,9 @@ +//! Shared instruction helpers: account guards, PDA creation, prize minting. + +pub mod account; +pub mod checks; +pub mod prize_nft; + +pub use account::create_pda_account; +pub use checks::{check_signer, check_system_program, check_writable}; +pub use prize_nft::{mint_prize_nft, PrizeNftAccounts}; diff --git a/games/gacha/pinocchio-simple/program/src/instructions/helpers/prize_nft.rs b/games/gacha/pinocchio-simple/program/src/instructions/helpers/prize_nft.rs new file mode 100644 index 000000000..e1a851045 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/helpers/prize_nft.rs @@ -0,0 +1,215 @@ +//! Mints a pull's prize as a Token-2022 NFT carrying its rarity and reveal +//! provenance in-mint. +//! +//! The mint is a PDA of the pull, so each pull has exactly one prize. The pool +//! PDA signs as metadata-pointer authority, metadata update authority, and mint +//! authority; the mint authority is discarded once a supply of one is minted, so +//! the NFT can never be inflated. The `additional_metadata` pairs make the NFT +//! self-certifying: they carry everything needed to verify the ECVRF reveal +//! off-chain without any transaction-history lookup. + +use alloc::vec::Vec; + +use pinocchio::{ + cpi::{invoke_signed, Seed, Signer}, + instruction::{InstructionAccount, InstructionView}, + sysvars::{rent::Rent, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_associated_token_account::instructions::CreateIdempotent; +use pinocchio_system::instructions::Transfer; +use pinocchio_token_2022::instructions::{metadata_pointer, AuthorityType, InitializeMint2, MintTo, SetAuthority}; + +use crate::{ + gacha::{format_u64, NFT_NAME_PREFIX, NFT_SYMBOL, NFT_URI}, + instructions::helpers::create_pda_account, + state::common::{MINT_SEED, POOL_SEED}, +}; + +/// SPL token-metadata-interface instruction discriminators +/// (`sha256("spl_token_metadata_interface:")[..8]`). +const TOKEN_METADATA_INITIALIZE_DISC: [u8; 8] = [210, 225, 30, 162, 88, 184, 77, 141]; +const TOKEN_METADATA_UPDATE_FIELD_DISC: [u8; 8] = [221, 233, 49, 45, 181, 202, 220, 200]; +/// `Field::Key(String)` variant tag in the token-metadata-interface `Field` enum. +const FIELD_KEY_VARIANT: u8 = 3; + +/// Size of a Token-2022 mint account carrying a `MetadataPointer` extension: +/// 82-byte base mint + 83-byte padding to the account-type offset + 1-byte +/// account type + 4-byte TLV header + 64-byte metadata-pointer state. Matches +/// `ExtensionType::try_calculate_account_len::(&[MetadataPointer])`. +const MINT_LEN: usize = 234; + +/// Accounts the prize mint touches, in the order +/// [`SettleAndDistribute`](crate::GachaInstruction::SettleAndDistribute) declares them. +pub struct PrizeNftAccounts<'a> { + pub payer: &'a AccountView, + pub pool: &'a AccountView, + pub buyer: &'a AccountView, + pub mint: &'a AccountView, + pub buyer_ata: &'a AccountView, + pub system_program: &'a AccountView, + pub token_program: &'a AccountView, +} + +/// Creates the prize mint, writes its metadata, and delivers a supply of one to +/// the buyer's associated token account. +/// +/// `admin` and `pool_bump` reconstruct the pool PDA's signer seeds; `pull` and +/// `mint_bump` those of the mint. `index` is appended to the NFT name and each +/// `(key, value)` pair in `metadata_pairs` is stored in the mint's +/// `additional_metadata`. +pub fn mint_prize_nft( + accounts: &PrizeNftAccounts, + admin: &Address, + pool_bump: u8, + pull: &Address, + mint_bump: u8, + index: u64, + metadata_pairs: &[(&str, &str)], +) -> ProgramResult { + let mint_bump_bytes = [mint_bump]; + let mint_seeds = [Seed::from(MINT_SEED), Seed::from(pull.as_ref()), Seed::from(&mint_bump_bytes[..])]; + + let pool_bump_bytes = [pool_bump]; + let pool_seeds = [Seed::from(POOL_SEED), Seed::from(admin.as_ref()), Seed::from(&pool_bump_bytes[..])]; + let pool_signer = [Signer::from(&pool_seeds[..])]; + + let pool_address = accounts.pool.address(); + + create_pda_account(accounts.payer, accounts.mint, &mint_seeds, MINT_LEN, &pinocchio_token_2022::ID)?; + + metadata_pointer::Initialize { + mint: accounts.mint, + authority: Some(pool_address), + metadata_address: Some(accounts.mint.address()), + token_program: &pinocchio_token_2022::ID, + } + .invoke()?; + + InitializeMint2 { + mint: accounts.mint, + decimals: 0, + mint_authority: pool_address, + freeze_authority: None, + token_program: &pinocchio_token_2022::ID, + } + .invoke()?; + + let mut digits = [0u8; 20]; + let index_str = format_u64(index, &mut digits); + let name_len = NFT_NAME_PREFIX.len() + index_str.len(); + + fund_metadata_rent(accounts, name_len, metadata_pairs)?; + initialize_metadata(accounts, index_str, name_len, &pool_signer)?; + for (key, value) in metadata_pairs { + set_metadata_field(accounts, key, value, &pool_signer)?; + } + + CreateIdempotent { + funding_account: accounts.payer, + account: accounts.buyer_ata, + wallet: accounts.buyer, + mint: accounts.mint, + system_program: accounts.system_program, + token_program: accounts.token_program, + } + .invoke()?; + + MintTo { + mint: accounts.mint, + account: accounts.buyer_ata, + mint_authority: accounts.pool, + amount: 1, + token_program: &pinocchio_token_2022::ID, + } + .invoke_signed(&pool_signer)?; + + SetAuthority { + account: accounts.mint, + authority: accounts.pool, + authority_type: AuthorityType::MintTokens, + new_authority: None, + token_program: &pinocchio_token_2022::ID, + } + .invoke_signed(&pool_signer) +} + +/// Tops the mint up to the rent floor of its final size, before any metadata is +/// written. +/// +/// Token-2022 re-checks rent-exemption at the full account size on every +/// metadata write, so the account must already hold rent for the TLV entry that +/// [`initialize_metadata`] and [`set_metadata_field`] grow it to: a 4-byte TLV +/// header, the update authority and mint (32 each), three borsh strings, and +/// every `(key, value)` pair. +fn fund_metadata_rent(accounts: &PrizeNftAccounts, name_len: usize, metadata_pairs: &[(&str, &str)]) -> ProgramResult { + let mut metadata_len = 4 + 64 + (4 + name_len) + (4 + NFT_SYMBOL.len()) + (4 + NFT_URI.len()) + 4; + for (key, value) in metadata_pairs { + metadata_len += (4 + key.len()) + (4 + value.len()); + } + + let rent = Rent::get()?; + let funded = rent.try_minimum_balance(MINT_LEN)?; + let required = rent.try_minimum_balance(MINT_LEN + metadata_len)?; + let top_up = required.saturating_sub(funded); + if top_up > 0 { + Transfer { from: accounts.payer, to: accounts.mint, lamports: top_up }.invoke()?; + } + + Ok(()) +} + +/// Writes the NFT's name, symbol, and URI into the mint itself. +fn initialize_metadata( + accounts: &PrizeNftAccounts, + index_str: &str, + name_len: usize, + pool_signer: &[Signer], +) -> ProgramResult { + let mut data = Vec::with_capacity(8 + 4 + name_len + 4 + NFT_SYMBOL.len() + 4 + NFT_URI.len()); + data.extend_from_slice(&TOKEN_METADATA_INITIALIZE_DISC); + data.extend_from_slice(&(name_len as u32).to_le_bytes()); + data.extend_from_slice(NFT_NAME_PREFIX.as_bytes()); + data.extend_from_slice(index_str.as_bytes()); + data.extend_from_slice(&(NFT_SYMBOL.len() as u32).to_le_bytes()); + data.extend_from_slice(NFT_SYMBOL.as_bytes()); + data.extend_from_slice(&(NFT_URI.len() as u32).to_le_bytes()); + data.extend_from_slice(NFT_URI.as_bytes()); + + // Accounts: metadata, update authority, mint, mint authority — the metadata + // lives in the mint itself and the pool PDA is both authorities. + let pool_address = accounts.pool.address(); + let metas = [ + InstructionAccount::writable(accounts.mint.address()), + InstructionAccount::readonly(pool_address), + InstructionAccount::readonly(accounts.mint.address()), + InstructionAccount::readonly_signer(pool_address), + ]; + + invoke_signed( + &InstructionView { program_id: &pinocchio_token_2022::ID, accounts: &metas, data: &data }, + &[accounts.mint, accounts.pool, accounts.mint, accounts.pool], + pool_signer, + ) +} + +/// Adds one `(key, value)` entry to the mint's `additional_metadata`. +fn set_metadata_field(accounts: &PrizeNftAccounts, key: &str, value: &str, pool_signer: &[Signer]) -> ProgramResult { + let mut data = Vec::with_capacity(8 + 1 + 4 + key.len() + 4 + value.len()); + data.extend_from_slice(&TOKEN_METADATA_UPDATE_FIELD_DISC); + data.push(FIELD_KEY_VARIANT); + data.extend_from_slice(&(key.len() as u32).to_le_bytes()); + data.extend_from_slice(key.as_bytes()); + data.extend_from_slice(&(value.len() as u32).to_le_bytes()); + data.extend_from_slice(value.as_bytes()); + + let pool_address = accounts.pool.address(); + let metas = + [InstructionAccount::writable(accounts.mint.address()), InstructionAccount::readonly_signer(pool_address)]; + + invoke_signed( + &InstructionView { program_id: &pinocchio_token_2022::ID, accounts: &metas, data: &data }, + &[accounts.mint, accounts.pool], + pool_signer, + ) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/init_pool.rs b/games/gacha/pinocchio-simple/program/src/instructions/init_pool.rs new file mode 100644 index 000000000..83bb034e9 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/init_pool.rs @@ -0,0 +1,161 @@ +use core::mem::{size_of, transmute}; + +use codama::CodamaType; +use pinocchio::{cpi::Seed, error::ProgramError, AccountView, Address, ProgramResult}; + +use crate::{ + event_engine::{self, EventSerialize}, + events::PoolInitializedEvent, + gacha::MAX_TIERS, + instructions::helpers::{check_signer, check_system_program, check_writable, create_pda_account}, + state::{ + common::{find_pool_pda, find_vault_pda, POOL_SEED, VAULT_SEED}, + pool::Pool, + }, + GachaError, +}; + +/// Instruction discriminator byte for `InitPool`. +pub const DISCRIMINATOR: &u8 = &0; + +/// Instruction data for [`InitPool`](crate::GachaInstruction::InitPool). +/// +/// `weights` is fixed-length; only the first `tier_count` entries are used. +/// Remaining entries should be zero. +#[repr(C, packed)] +#[derive(CodamaType, Debug, Clone)] +pub struct InitPoolData { + /// VRF operator registered as the only signer allowed to settle pulls. + /// Doubles as the ECVRF public key verifiers check reveals against. + pub operator: Address, + /// Entry fee per pull, in lamports. + pub entry_fee: u64, + /// Slots a pull may stay pending before the buyer can claim a refund. + pub settle_deadline_slots: u64, + /// Number of active tiers. + pub tier_count: u8, + /// Relative draw weight per tier. + pub weights: [u32; 8], +} + +impl InitPoolData { + pub const LEN: usize = size_of::(); + + pub fn load(data: &[u8]) -> Result<&Self, ProgramError> { + if data.len() != Self::LEN { + return Err(GachaError::InvalidInstruction.into()); + } + Ok(unsafe { &*transmute::<*const u8, *const Self>(data.as_ptr()) }) + } +} + +/// Validated accounts for [`InitPool`](crate::GachaInstruction::InitPool). +pub struct InitPoolAccounts<'a> { + pub admin: &'a AccountView, + pub pool: &'a mut AccountView, + pub vault: &'a AccountView, + pub system_program: &'a AccountView, + pub event_authority: &'a AccountView, + pub self_program: &'a AccountView, +} + +impl<'a> TryFrom<&'a mut [AccountView]> for InitPoolAccounts<'a> { + type Error = ProgramError; + + fn try_from(accounts: &'a mut [AccountView]) -> Result { + let [admin, pool, vault, system_program, event_authority, self_program] = accounts else { + return Err(GachaError::NotEnoughAccountKeys.into()); + }; + + check_signer(admin)?; + check_writable(admin)?; + check_writable(pool)?; + check_writable(vault)?; + check_system_program(system_program)?; + + Ok(Self { admin, pool, vault, system_program, event_authority, self_program }) + } +} + +/// Creates a pool and its pot vault, and emits a [`PoolInitializedEvent`]. +pub fn process(accounts: &mut [AccountView], data: &InitPoolData) -> ProgramResult { + let accounts = InitPoolAccounts::try_from(accounts)?; + + let operator = data.operator; + let entry_fee = data.entry_fee; + let settle_deadline_slots = data.settle_deadline_slots; + let tier_count = data.tier_count; + let weights = data.weights; + + if tier_count == 0 || tier_count as usize > MAX_TIERS { + return Err(GachaError::TooManyTiers.into()); + } + for &weight in &weights[..tier_count as usize] { + if weight == 0 { + return Err(GachaError::InvalidTierConfig.into()); + } + } + for &weight in &weights[tier_count as usize..] { + if weight != 0 { + return Err(GachaError::InvalidTierConfig.into()); + } + } + + if entry_fee == 0 { + return Err(GachaError::InvalidEntryFee.into()); + } + if settle_deadline_slots == 0 { + return Err(GachaError::InvalidSettleDeadline.into()); + } + + // The operator address doubles as the ECVRF public key, so it must be a + // valid curve point. On-curve here only rules out malformed keys; that the + // operator actually controls the ECVRF secret is proven by every reveal + // verifying off-chain against this key. + if operator == Address::default() || operator == *accounts.admin.address() || !operator.is_on_curve() { + return Err(GachaError::InvalidOperator.into()); + } + + if accounts.pool.data_len() > 0 { + return Err(GachaError::PoolAlreadyExists.into()); + } + + let (pool_pda, pool_bump) = find_pool_pda(accounts.admin.address()); + if pool_pda != *accounts.pool.address() { + return Err(GachaError::InvalidPoolPda.into()); + } + let (vault_pda, vault_bump) = find_vault_pda(accounts.admin.address()); + if vault_pda != *accounts.vault.address() { + return Err(GachaError::InvalidVaultPda.into()); + } + + let pool_bump_bytes = [pool_bump]; + let pool_seeds = + [Seed::from(POOL_SEED), Seed::from(accounts.admin.address().as_ref()), Seed::from(&pool_bump_bytes[..])]; + create_pda_account(accounts.admin, accounts.pool, &pool_seeds, Pool::LEN, &crate::ID)?; + + let vault_bump_bytes = [vault_bump]; + let vault_seeds = + [Seed::from(VAULT_SEED), Seed::from(accounts.admin.address().as_ref()), Seed::from(&vault_bump_bytes[..])]; + create_pda_account(accounts.admin, accounts.vault, &vault_seeds, 0, &crate::ID)?; + + { + let mut pool_data = accounts.pool.try_borrow_mut()?; + Pool::init( + &mut pool_data, + pool_bump, + accounts.admin.address(), + &operator, + entry_fee, + settle_deadline_slots, + &weights, + tier_count, + )?; + } + + let event = + PoolInitializedEvent::new(*accounts.admin.address(), operator, entry_fee, settle_deadline_slots, tier_count); + event_engine::emit_event(&crate::ID, accounts.event_authority, accounts.self_program, &event.to_bytes())?; + + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/mod.rs b/games/gacha/pinocchio-simple/program/src/instructions/mod.rs new file mode 100644 index 000000000..50baa4eb0 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/mod.rs @@ -0,0 +1,162 @@ +//! Instruction definitions and dispatch for the gacha-simple program. +//! +//! Each instruction variant carries its own discriminator (the first byte of +//! instruction data). The Codama annotations on each variant describe the required +//! accounts in positional order. + +pub mod buy_pull; +pub mod emit_event; +pub mod helpers; +pub mod init_pool; +pub mod refund_pull; +pub mod settle_and_distribute; +pub mod withdraw_fees; + +pub use buy_pull::BuyPullData; +pub use helpers::*; +pub use init_pool::InitPoolData; +pub use settle_and_distribute::SettleAndDistributeData; +pub use withdraw_fees::WithdrawFeesData; + +use core::fmt; + +use codama::CodamaInstructions; +use pinocchio::error::ProgramError; + +use crate::event_engine::EMIT_EVENT_IX_DISC; +use crate::GachaError; + +/// All instructions supported by the gacha-simple program. +#[derive(Debug, CodamaInstructions)] +#[repr(u8)] +#[allow(clippy::large_enum_variant)] +pub enum GachaInstruction { + #[codama(account(name = "admin", signer, writable, docs = "Pool admin; funds and owns the pool"))] + #[codama(account( + name = "pool", + writable, + docs = "The pool PDA being created", + default_value = pda("pool", [seed("admin", account("admin"))]) + ))] + #[codama(account( + name = "vault", + writable, + docs = "Pot vault PDA", + default_value = pda("vault", [seed("admin", account("admin"))]) + ))] + #[codama(account(name = "system_program", docs = "The system program", default_value = program("system")))] + #[codama(account(name = "event_authority", docs = "The event authority PDA", default_value = pda("event_authority", [])))] + #[codama(account( + name = "self_program", + docs = "This program (for self-CPI event emission)", + default_value = public_key("2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS") + ))] + InitPool(#[codama(name = "init_pool_data")] InitPoolData) = 0, + + #[codama(account(name = "buyer", signer, writable, docs = "The buyer opening and paying for a pull"))] + #[codama(account(name = "pool", writable, docs = "The pool being pulled from"))] + #[codama(account(name = "pull", writable, docs = "The pull PDA being created"))] + #[codama(account(name = "vault", writable, docs = "Pot vault PDA for the pool"))] + #[codama(account(name = "system_program", docs = "The system program", default_value = program("system")))] + #[codama(account(name = "event_authority", docs = "The event authority PDA", default_value = pda("event_authority", [])))] + #[codama(account( + name = "self_program", + docs = "This program (for self-CPI event emission)", + default_value = public_key("2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS") + ))] + BuyPull(#[codama(name = "buy_pull_data")] BuyPullData) = 1, + + #[codama(account( + name = "operator", + signer, + writable, + docs = "Registered VRF operator revealing the pull; funds the mint and ATA rent" + ))] + #[codama(account(name = "pool", writable, docs = "The pool being pulled from; mint and metadata authority"))] + #[codama(account(name = "pull", writable, docs = "The pending pull being settled and closed"))] + #[codama(account(name = "buyer", writable, docs = "The pull's buyer; receives the prize NFT and the pull rent"))] + #[codama(account( + name = "mint", + writable, + docs = "Prize mint PDA for the pull", + default_value = pda("prize_mint", [seed("pull", account("pull"))]) + ))] + #[codama(account(name = "buyer_ata", writable, docs = "Buyer's associated token account for the prize mint"))] + #[codama(account(name = "system_program", docs = "The system program", default_value = program("system")))] + #[codama(account( + name = "token_program", + docs = "The Token-2022 program", + default_value = public_key("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") + ))] + #[codama(account( + name = "ata_program", + docs = "The associated token account program", + default_value = public_key("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + ))] + #[codama(account(name = "event_authority", docs = "The event authority PDA", default_value = pda("event_authority", [])))] + #[codama(account( + name = "self_program", + docs = "This program (for self-CPI event emission)", + default_value = public_key("2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS") + ))] + SettleAndDistribute(#[codama(name = "settle_and_distribute_data")] SettleAndDistributeData) = 2, + + #[codama(account(name = "buyer", signer, writable, docs = "The pull's buyer reclaiming their entry fee"))] + #[codama(account(name = "pool", writable, docs = "The pool the pull belongs to"))] + #[codama(account(name = "pull", writable, docs = "The pending pull being refunded and closed"))] + #[codama(account(name = "vault", writable, docs = "Pot vault PDA for the pool"))] + #[codama(account(name = "event_authority", docs = "The event authority PDA", default_value = pda("event_authority", [])))] + #[codama(account( + name = "self_program", + docs = "This program (for self-CPI event emission)", + default_value = public_key("2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS") + ))] + RefundPull = 3, + + #[codama(account(name = "admin", signer, writable, docs = "Pool admin receiving the fees"))] + #[codama(account(name = "pool", docs = "The pool whose fees are withdrawn"))] + #[codama(account(name = "vault", writable, docs = "Pot vault PDA for the pool"))] + #[codama(account(name = "event_authority", docs = "The event authority PDA", default_value = pda("event_authority", [])))] + #[codama(account( + name = "self_program", + docs = "This program (for self-CPI event emission)", + default_value = public_key("2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS") + ))] + WithdrawFees(#[codama(name = "withdraw_fees_data")] WithdrawFeesData) = 4, + + #[codama(skip)] + #[codama(account(name = "event_authority", signer, docs = "The event authority PDA"))] + EmitEvent = 228, +} + +impl GachaInstruction { + /// Parse a `GachaInstruction` from raw instruction bytes. + pub fn from_bytes(data: &[u8]) -> Result { + let (discriminator, rest) = data.split_first().ok_or(GachaError::InvalidInstruction)?; + + match discriminator { + init_pool::DISCRIMINATOR => Ok(Self::InitPool(InitPoolData::load(rest)?.clone())), + buy_pull::DISCRIMINATOR => Ok(Self::BuyPull(BuyPullData::load(rest)?.clone())), + settle_and_distribute::DISCRIMINATOR => { + Ok(Self::SettleAndDistribute(SettleAndDistributeData::load(rest)?.clone())) + } + refund_pull::DISCRIMINATOR => Ok(Self::RefundPull), + withdraw_fees::DISCRIMINATOR => Ok(Self::WithdrawFees(WithdrawFeesData::load(rest)?.clone())), + &EMIT_EVENT_IX_DISC => Ok(Self::EmitEvent), + _ => Err(GachaError::InvalidInstruction.into()), + } + } +} + +impl fmt::Display for GachaInstruction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InitPool(_) => write!(f, "init_pool"), + Self::BuyPull(_) => write!(f, "buy_pull"), + Self::SettleAndDistribute(_) => write!(f, "settle_and_distribute"), + Self::RefundPull => write!(f, "refund_pull"), + Self::WithdrawFees(_) => write!(f, "withdraw_fees"), + Self::EmitEvent => write!(f, "emit_event"), + } + } +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/refund_pull.rs b/games/gacha/pinocchio-simple/program/src/instructions/refund_pull.rs new file mode 100644 index 000000000..fd38e90c5 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/refund_pull.rs @@ -0,0 +1,110 @@ +use pinocchio::{ + error::ProgramError, + sysvars::{clock::Clock, Sysvar}, + AccountView, ProgramResult, +}; + +use crate::{ + event_engine::{self, EventSerialize}, + events::PullRefundedEvent, + instructions::helpers::{check_signer, check_writable}, + state::{common::find_vault_pda, pool::Pool, pull::Pull}, + GachaError, +}; + +/// Instruction discriminator byte for `RefundPull`. +pub const DISCRIMINATOR: &u8 = &3; + +/// Validated accounts for [`RefundPull`](crate::GachaInstruction::RefundPull). +pub struct RefundPullAccounts<'a> { + pub buyer: &'a mut AccountView, + pub pool: &'a mut AccountView, + pub pull: &'a mut AccountView, + pub vault: &'a mut AccountView, + pub event_authority: &'a AccountView, + pub self_program: &'a AccountView, +} + +impl<'a> TryFrom<&'a mut [AccountView]> for RefundPullAccounts<'a> { + type Error = ProgramError; + + fn try_from(accounts: &'a mut [AccountView]) -> Result { + let [buyer, pool, pull, vault, event_authority, self_program] = accounts else { + return Err(GachaError::NotEnoughAccountKeys.into()); + }; + + check_signer(buyer)?; + check_writable(buyer)?; + check_writable(pool)?; + Pool::check(pool)?; + check_writable(pull)?; + Pull::check(pull)?; + check_writable(vault)?; + + Ok(Self { buyer, pool, pull, vault, event_authority, self_program }) + } +} + +/// Refunds a pull the operator failed to settle within the pool's deadline: the +/// entry fee moves back from the vault to the buyer, the pull account closes +/// (returning its rent to the buyer), and a [`PullRefundedEvent`] is emitted. +/// +/// This is the liveness escape hatch of the commit-reveal scheme — an operator +/// who withholds a reveal (for example after seeing an unfavorable `beta`) can +/// delay a payout, but can never keep the buyer's funds. A pull account existing +/// means the pull is still pending — settling closes it — and a refunded pull +/// can never be re-created: pull PDAs are seeded by a monotonic pool index. +pub fn process(accounts: &mut [AccountView]) -> ProgramResult { + let accounts = RefundPullAccounts::try_from(accounts)?; + + let entry_fee; + let settle_deadline_slots; + let admin; + { + let pool_data = accounts.pool.try_borrow()?; + let pool = Pool::load(&pool_data)?; + entry_fee = pool.entry_fee; + settle_deadline_slots = pool.settle_deadline_slots; + admin = pool.admin; + } + + if find_vault_pda(&admin).0 != *accounts.vault.address() { + return Err(GachaError::InvalidVaultPda.into()); + } + + let index; + { + let pull_data = accounts.pull.try_borrow()?; + let pull = Pull::load(&pull_data)?; + if pull.pool != *accounts.pool.address() { + return Err(GachaError::PoolMismatch.into()); + } + if pull.buyer != *accounts.buyer.address() { + return Err(GachaError::BuyerMismatch.into()); + } + let deadline = pull.requested_slot.checked_add(settle_deadline_slots).ok_or(GachaError::ArithmeticOverflow)?; + if Clock::get()?.slot <= deadline { + return Err(GachaError::RefundTooEarly.into()); + } + index = pull.index; + } + + let vault_lamports = accounts.vault.lamports().checked_sub(entry_fee).ok_or(GachaError::InsufficientVaultFunds)?; + accounts.vault.set_lamports(vault_lamports); + let refund = entry_fee.checked_add(accounts.pull.lamports()).ok_or(GachaError::ArithmeticOverflow)?; + let buyer_lamports = accounts.buyer.lamports().checked_add(refund).ok_or(GachaError::ArithmeticOverflow)?; + accounts.pull.set_lamports(0); + accounts.buyer.set_lamports(buyer_lamports); + accounts.pull.close()?; + + { + let mut pool_data = accounts.pool.try_borrow_mut()?; + let pool = Pool::load_mut(&mut pool_data)?; + pool.pending_pulls = pool.pending_pulls.checked_sub(1).ok_or(GachaError::ArithmeticOverflow)?; + } + + let event = PullRefundedEvent::new(*accounts.pool.address(), *accounts.buyer.address(), index, entry_fee); + event_engine::emit_event(&crate::ID, accounts.event_authority, accounts.self_program, &event.to_bytes())?; + + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/settle_and_distribute.rs b/games/gacha/pinocchio-simple/program/src/instructions/settle_and_distribute.rs new file mode 100644 index 000000000..cdea0c4ae --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/settle_and_distribute.rs @@ -0,0 +1,207 @@ +use core::mem::{size_of, transmute}; + +use codama::CodamaType; +use pinocchio::{error::ProgramError, AccountView, ProgramResult}; + +use crate::{ + event_engine::{self, EventSerialize}, + events::PullSettledEvent, + gacha::{ + format_hex, select_tier, METADATA_BETA_KEY, METADATA_CLIENT_SEED_KEY, METADATA_PROOF_KEY, METADATA_PULL_KEY, + METADATA_RARITY_KEY, RARITY_LABELS, + }, + instructions::helpers::{check_signer, check_system_program, check_writable, mint_prize_nft, PrizeNftAccounts}, + state::{common::find_mint_pda, pool::Pool, pull::Pull}, + GachaError, +}; + +/// Instruction discriminator byte for `SettleAndDistribute`. +pub const DISCRIMINATOR: &u8 = &2; + +/// Instruction data for [`SettleAndDistribute`](crate::GachaInstruction::SettleAndDistribute). +#[repr(C, packed)] +#[derive(CodamaType, Debug, Clone)] +pub struct SettleAndDistributeData { + /// The 80-byte RFC 9381 ECVRF proof for the pull's `alpha`. Recorded in the + /// prize NFT's metadata and emitted for off-chain verification. + pub proof: [u8; 80], + /// The 64-byte ECVRF output. Drives tier selection. + pub beta: [u8; 64], +} + +impl SettleAndDistributeData { + pub const LEN: usize = size_of::(); + + pub fn load(data: &[u8]) -> Result<&Self, ProgramError> { + if data.len() != Self::LEN { + return Err(GachaError::InvalidInstruction.into()); + } + Ok(unsafe { &*transmute::<*const u8, *const Self>(data.as_ptr()) }) + } +} + +/// Validated accounts for [`SettleAndDistribute`](crate::GachaInstruction::SettleAndDistribute). +pub struct SettleAndDistributeAccounts<'a> { + pub operator: &'a AccountView, + pub pool: &'a mut AccountView, + pub pull: &'a mut AccountView, + pub buyer: &'a mut AccountView, + pub mint: &'a AccountView, + pub buyer_ata: &'a AccountView, + pub system_program: &'a AccountView, + pub token_program: &'a AccountView, + pub ata_program: &'a AccountView, + pub event_authority: &'a AccountView, + pub self_program: &'a AccountView, +} + +impl<'a> TryFrom<&'a mut [AccountView]> for SettleAndDistributeAccounts<'a> { + type Error = ProgramError; + + fn try_from(accounts: &'a mut [AccountView]) -> Result { + let [operator, pool, pull, buyer, mint, buyer_ata, system_program, token_program, ata_program, event_authority, self_program] = + accounts + else { + return Err(GachaError::NotEnoughAccountKeys.into()); + }; + + check_signer(operator)?; + check_writable(operator)?; + check_writable(pool)?; + Pool::check(pool)?; + check_writable(pull)?; + Pull::check(pull)?; + check_writable(buyer)?; + check_writable(mint)?; + check_writable(buyer_ata)?; + check_system_program(system_program)?; + + if token_program.address() != &pinocchio_token_2022::ID { + return Err(GachaError::NotTokenProgram.into()); + } + if ata_program.address() != &pinocchio_associated_token_account::ID { + return Err(GachaError::NotAtaProgram.into()); + } + + Ok(Self { + operator, + pool, + pull, + buyer, + mint, + buyer_ata, + system_program, + token_program, + ata_program, + event_authority, + self_program, + }) + } +} + +/// Reveals a pending pull and delivers its prize in one step: selects a tier +/// from `beta` against the pool's fixed weights, mints the prize — a Token-2022 +/// NFT whose metadata carries the rarity and the full reveal provenance +/// (`pull`, `client_seed`, `beta`, `proof`, all lowercase hex) — to the buyer, +/// closes the pull (rent back to the buyer), and emits a [`PullSettledEvent`]. +/// +/// One reveal per pull is structural: the prize mint is a PDA of the pull that +/// can only be created once, the pull account closes here, and pull addresses +/// are seeded by a monotonic pool index so a settled pull can never be +/// re-created. The NFT is self-certifying — its metadata `update_authority` is +/// the pool PDA, so from the mint and the pool account it names anyone can +/// recompute `alpha = SHA-256(pull || client_seed)` and verify +/// `beta = VRF(pool.operator, alpha)` off-chain. +pub fn process(accounts: &mut [AccountView], data: &SettleAndDistributeData) -> ProgramResult { + let accounts = SettleAndDistributeAccounts::try_from(accounts)?; + + let admin; + let pool_bump; + let weights; + let tier_count; + { + let pool_data = accounts.pool.try_borrow()?; + let pool = Pool::load(&pool_data)?; + pool.check_operator(accounts.operator.address())?; + admin = pool.admin; + pool_bump = pool.bump; + weights = pool.weights; + tier_count = pool.tier_count; + } + + let alpha; + let client_seed; + let index; + { + let pull_data = accounts.pull.try_borrow()?; + let pull = Pull::load(&pull_data)?; + if pull.pool != *accounts.pool.address() { + return Err(GachaError::PoolMismatch.into()); + } + if pull.buyer != *accounts.buyer.address() { + return Err(GachaError::BuyerMismatch.into()); + } + alpha = pull.alpha; + client_seed = pull.client_seed; + index = pull.index; + } + + let (mint_pda, mint_bump) = find_mint_pda(accounts.pull.address()); + if mint_pda != *accounts.mint.address() { + return Err(GachaError::InvalidMintPda.into()); + } + + let proof = data.proof; + let beta = data.beta; + let tier = select_tier(&beta, &weights, tier_count)?; + let rarity = RARITY_LABELS.get(tier as usize).ok_or(GachaError::InvalidTierConfig)?; + + let mut pull_hex = [0u8; 64]; + let mut seed_hex = [0u8; 64]; + let mut beta_hex = [0u8; 128]; + let mut proof_hex = [0u8; 160]; + let provenance = [ + (METADATA_RARITY_KEY, *rarity), + (METADATA_PULL_KEY, format_hex(accounts.pull.address().as_ref(), &mut pull_hex)), + (METADATA_CLIENT_SEED_KEY, format_hex(&client_seed, &mut seed_hex)), + (METADATA_BETA_KEY, format_hex(&beta, &mut beta_hex)), + (METADATA_PROOF_KEY, format_hex(&proof, &mut proof_hex)), + ]; + + let nft_accounts = PrizeNftAccounts { + payer: accounts.operator, + pool: accounts.pool, + buyer: accounts.buyer, + mint: accounts.mint, + buyer_ata: accounts.buyer_ata, + system_program: accounts.system_program, + token_program: accounts.token_program, + }; + mint_prize_nft(&nft_accounts, &admin, pool_bump, accounts.pull.address(), mint_bump, index, &provenance)?; + + { + let mut pool_data = accounts.pool.try_borrow_mut()?; + let pool = Pool::load_mut(&mut pool_data)?; + pool.pending_pulls = pool.pending_pulls.checked_sub(1).ok_or(GachaError::ArithmeticOverflow)?; + } + + let rent_refund = accounts.pull.lamports(); + let buyer_lamports = accounts.buyer.lamports().checked_add(rent_refund).ok_or(GachaError::ArithmeticOverflow)?; + accounts.pull.set_lamports(0); + accounts.buyer.set_lamports(buyer_lamports); + accounts.pull.close()?; + + let event = PullSettledEvent::new( + *accounts.pool.address(), + *accounts.buyer.address(), + index, + tier, + alpha, + beta, + proof, + mint_pda, + ); + event_engine::emit_event(&crate::ID, accounts.event_authority, accounts.self_program, &event.to_bytes())?; + + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/instructions/withdraw_fees.rs b/games/gacha/pinocchio-simple/program/src/instructions/withdraw_fees.rs new file mode 100644 index 000000000..8c5023d84 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/instructions/withdraw_fees.rs @@ -0,0 +1,113 @@ +use core::mem::{size_of, transmute}; + +use codama::CodamaType; +use pinocchio::{ + error::ProgramError, + sysvars::{rent::Rent, Sysvar}, + AccountView, ProgramResult, +}; + +use crate::{ + event_engine::{self, EventSerialize}, + events::FeesWithdrawnEvent, + instructions::helpers::{check_signer, check_writable}, + state::{common::find_vault_pda, pool::Pool}, + GachaError, +}; + +/// Instruction discriminator byte for `WithdrawFees`. +pub const DISCRIMINATOR: &u8 = &4; + +/// Instruction data for [`WithdrawFees`](crate::GachaInstruction::WithdrawFees). +#[repr(C, packed)] +#[derive(CodamaType, Debug, Clone)] +pub struct WithdrawFeesData { + /// Lamports to withdraw from the vault. + pub amount: u64, +} + +impl WithdrawFeesData { + pub const LEN: usize = size_of::(); + + pub fn load(data: &[u8]) -> Result<&Self, ProgramError> { + if data.len() != Self::LEN { + return Err(GachaError::InvalidInstruction.into()); + } + Ok(unsafe { &*transmute::<*const u8, *const Self>(data.as_ptr()) }) + } +} + +/// Validated accounts for [`WithdrawFees`](crate::GachaInstruction::WithdrawFees). +pub struct WithdrawFeesAccounts<'a> { + pub admin: &'a mut AccountView, + pub pool: &'a AccountView, + pub vault: &'a mut AccountView, + pub event_authority: &'a AccountView, + pub self_program: &'a AccountView, +} + +impl<'a> TryFrom<&'a mut [AccountView]> for WithdrawFeesAccounts<'a> { + type Error = ProgramError; + + fn try_from(accounts: &'a mut [AccountView]) -> Result { + let [admin, pool, vault, event_authority, self_program] = accounts else { + return Err(GachaError::NotEnoughAccountKeys.into()); + }; + + check_signer(admin)?; + check_writable(admin)?; + Pool::check(pool)?; + check_writable(vault)?; + + Ok(Self { admin, pool, vault, event_authority, self_program }) + } +} + +/// Withdraws settled entry fees from the vault to the admin, and emits a +/// [`FeesWithdrawnEvent`]. +/// +/// The withdrawable amount is capped at the vault balance minus the vault's own +/// rent floor and minus `pending_pulls * entry_fee` — the fees still owed as +/// refunds if the operator never settles those pulls. The admin can therefore +/// only ever take revenue from *settled* pulls, never a pending buyer's escrow. +pub fn process(accounts: &mut [AccountView], data: &WithdrawFeesData) -> ProgramResult { + let accounts = WithdrawFeesAccounts::try_from(accounts)?; + + let amount = data.amount; + if amount == 0 { + return Err(GachaError::InsufficientVaultFunds.into()); + } + + let entry_fee; + let pending_pulls; + let admin; + { + let pool_data = accounts.pool.try_borrow()?; + let pool = Pool::load(&pool_data)?; + pool.check_admin(accounts.admin.address())?; + entry_fee = pool.entry_fee; + pending_pulls = pool.pending_pulls; + admin = pool.admin; + } + + if find_vault_pda(&admin).0 != *accounts.vault.address() { + return Err(GachaError::InvalidVaultPda.into()); + } + + let rent_floor = Rent::get()?.try_minimum_balance(0)?; + let liability = pending_pulls.checked_mul(entry_fee).ok_or(GachaError::ArithmeticOverflow)?; + let reserved = rent_floor.checked_add(liability).ok_or(GachaError::ArithmeticOverflow)?; + let available = accounts.vault.lamports().saturating_sub(reserved); + if amount > available { + return Err(GachaError::InsufficientVaultFunds.into()); + } + + accounts.vault.set_lamports(accounts.vault.lamports() - amount); + let admin_lamports = accounts.admin.lamports().checked_add(amount).ok_or(GachaError::ArithmeticOverflow)?; + accounts.admin.set_lamports(admin_lamports); + + let event = FeesWithdrawnEvent::new(*accounts.pool.address(), admin, amount); + event_engine::emit_event(&crate::ID, accounts.event_authority, accounts.self_program, &event.to_bytes())?; + + Ok(()) +} diff --git a/games/gacha/pinocchio-simple/program/src/lib.rs b/games/gacha/pinocchio-simple/program/src/lib.rs new file mode 100644 index 000000000..6f1779e2e --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/lib.rs @@ -0,0 +1,69 @@ +//! Gacha Simple Solana Program. +//! +//! A provably-fair gacha (loot-box / pack-pull) game with a self-certifying +//! prize NFT. An admin configures a pool of fixed-weight reward tiers and a +//! fixed entry fee, and registers an off-chain VRF operator. A buyer pays the +//! fee to open a pull, committing a VRF input +//! `alpha = SHA-256(pull_address || client_seed)` — buyer entropy the operator +//! cannot predict. The operator reveals the ECVRF output (`beta`): +//! `settle_and_distribute` expands `beta` into a weighted tier and mints the +//! prize — a Token-2022 NFT whose metadata carries the tier's rarity plus the +//! full reveal provenance (`pull`, `client_seed`, `beta`, `proof`) — straight to +//! the buyer, then closes the pull. Unsettled pulls are refundable after a +//! deadline; the admin can withdraw only settled fees. +//! +//! Solana cannot verify an RFC 9381 ECVRF proof on-chain, so on-chain the +//! program trusts the registered operator's signature; anyone verifies +//! `beta = VRF(alpha)` off-chain with `@collectorcrypt/ecvrf` — cheating is +//! detectable, and the NFT carries its own evidence. One reveal per pull is +//! structural: pull addresses are seeded by a monotonic pool index, the prize +//! mint is a PDA of the pull that can only be created once, and the pull +//! account closes at settle. +//! +//! Built on the [Pinocchio](https://docs.rs/pinocchio) runtime; uses +//! [Codama](https://github.com/codama-idl/codama) for IDL generation. + +#![no_std] + +extern crate alloc; + +#[cfg(test)] +#[macro_use] +extern crate std; + +use pinocchio::address::declare_id; + +pub mod errors; +pub use errors::*; + +pub mod event_engine; +pub mod events; + +pub mod gacha; +pub use gacha::*; + +pub mod instructions; +pub use instructions::*; + +pub mod state; +pub use state::*; + +#[cfg(not(feature = "no-entrypoint"))] +pub mod entrypoint; + +#[cfg(test)] +mod tests; + +declare_id!("2nAHovvq1Ju2VZtZWvaAyvTrD18DRzG5pBEUwwGQDAWS"); + +#[cfg(not(feature = "no-entrypoint"))] +use solana_security_txt::security_txt; + +#[cfg(not(feature = "no-entrypoint"))] +security_txt! { + name: "Gacha Simple Program", + project_url: "https://github.com/solana-foundation/program-examples", + contacts: "link:https://github.com/solana-foundation/program-examples/security/advisories/new", + policy: "https://github.com/solana-foundation/program-examples/security/policy", + source_code: "https://github.com/solana-foundation/program-examples" +} diff --git a/games/gacha/pinocchio-simple/program/src/state/common.rs b/games/gacha/pinocchio-simple/program/src/state/common.rs new file mode 100644 index 000000000..938eb4b18 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/state/common.rs @@ -0,0 +1,66 @@ +//! Shared account discriminator, PDA seeds, and derivation helpers. + +use codama::CodamaType; +use pinocchio::{error::ProgramError, Address}; + +use crate::GachaError; + +/// PDA seed prefix for pool accounts. +pub const POOL_SEED: &[u8] = b"pool"; +/// PDA seed prefix for per-pool pot vaults. +pub const VAULT_SEED: &[u8] = b"vault"; +/// PDA seed prefix for pull accounts. +pub const PULL_SEED: &[u8] = b"pull"; +/// PDA seed prefix for per-pull prize mints. +pub const MINT_SEED: &[u8] = b"mint"; + +/// One-byte discriminator identifying the type of a program-owned account. +/// +/// Stored at byte offset 0 of every data-carrying account created by this +/// program. Values start at 1 so all-zero account data never carries a valid +/// discriminator. +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Debug, CodamaType)] +pub enum AccountDiscriminator { + /// [`Pool`](super::pool::Pool) account. + Pool = 1, + /// [`Pull`](super::pull::Pull) account. + Pull = 2, +} + +impl TryFrom for AccountDiscriminator { + type Error = ProgramError; + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Self::Pool), + 2 => Ok(Self::Pull), + _ => Err(GachaError::InvalidAccountDiscriminator.into()), + } + } +} + +impl From for u8 { + fn from(val: AccountDiscriminator) -> Self { + val as u8 + } +} + +/// Finds the pool PDA and bump for an admin. +pub fn find_pool_pda(admin: &Address) -> (Address, u8) { + Address::find_program_address(&[POOL_SEED, admin.as_ref()], &crate::ID) +} + +/// Finds the pot vault PDA and bump for an admin's pool. +pub fn find_vault_pda(admin: &Address) -> (Address, u8) { + Address::find_program_address(&[VAULT_SEED, admin.as_ref()], &crate::ID) +} + +/// Finds the pull PDA and bump for a `(pool, buyer, index)` triple. +pub fn find_pull_pda(pool: &Address, buyer: &Address, index: u64) -> (Address, u8) { + Address::find_program_address(&[PULL_SEED, pool.as_ref(), buyer.as_ref(), &index.to_le_bytes()], &crate::ID) +} + +/// Finds the prize mint PDA and bump for a pull. +pub fn find_mint_pda(pull: &Address) -> (Address, u8) { + Address::find_program_address(&[MINT_SEED, pull.as_ref()], &crate::ID) +} diff --git a/games/gacha/pinocchio-simple/program/src/state/mod.rs b/games/gacha/pinocchio-simple/program/src/state/mod.rs new file mode 100644 index 000000000..2f3a94737 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/state/mod.rs @@ -0,0 +1,20 @@ +//! On-chain account state types for the gacha program. +//! +//! Each data-carrying account is stored as a packed C struct in a Program Derived +//! Account (PDA), with a one-byte discriminator at offset 0. The pot [`Vault`] is a +//! zero-data PDA; the [`PrizeMint`] is a Token-2022 mint owned by the token program. + +pub mod common; +pub mod pool; +pub mod prize_mint; +pub mod pull; +pub mod vault; + +pub use common::{ + find_mint_pda, find_pool_pda, find_pull_pda, find_vault_pda, AccountDiscriminator, MINT_SEED, POOL_SEED, PULL_SEED, + VAULT_SEED, +}; +pub use pool::Pool; +pub use prize_mint::PrizeMint; +pub use pull::Pull; +pub use vault::Vault; diff --git a/games/gacha/pinocchio-simple/program/src/state/pool.rs b/games/gacha/pinocchio-simple/program/src/state/pool.rs new file mode 100644 index 000000000..b593d4767 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/state/pool.rs @@ -0,0 +1,134 @@ +//! Pool account: gacha machine configuration, reward tiers, and pull counter. + +use codama::CodamaAccount; +use core::mem::{size_of, transmute}; +use pinocchio::{error::ProgramError, AccountView, Address}; + +use crate::{gacha::MAX_TIERS, state::common::AccountDiscriminator, GachaError}; + +/// A gacha pool: one machine, owned by an admin. +/// +/// Holds the fixed entry fee, the registered off-chain VRF `operator`, the fixed +/// tier `weights`, a monotonic `pulls_count` used to derive unique pull +/// accounts, and `pending_pulls` tracking outstanding refund liabilities. One +/// pool per admin wallet. +/// +/// **PDA seeds:** `["pool", admin]` +#[repr(C, packed)] +#[derive(CodamaAccount)] +#[codama(seed(type = string(utf8), value = "pool"))] +#[codama(seed(name = "admin", type = public_key))] +pub struct Pool { + /// Account type discriminator ([`AccountDiscriminator::Pool`]). + pub discriminator: u8, + /// PDA bump seed. + pub bump: u8, + /// Number of active tiers (leading entries of `weights`). + pub tier_count: u8, + /// Admin that configured and owns this pool. + pub admin: Address, + /// Registered VRF operator; the only signer allowed to settle pulls. Doubles + /// as the ECVRF public key verifiers check reveals against — it is fixed at + /// init, so the key can never be swapped mid-pool. + pub operator: Address, + /// Entry fee per pull, in lamports. Escrowed in the vault until the pull is + /// settled (operator revenue) or refunded (returned to the buyer). + pub entry_fee: u64, + /// Number of pulls opened against this pool; the next pull's index. + pub pulls_count: u64, + /// Pulls still awaiting settle or refund. The vault must always hold at + /// least `pending_pulls * entry_fee` on top of its rent floor. + pub pending_pulls: u64, + /// Slots after a pull's `requested_slot` before the buyer may claim a refund. + pub settle_deadline_slots: u64, + /// Relative draw weight per tier. Fixed at init, so tier odds are identical + /// for every pull and independent of settle order. + pub weights: [u32; 8], +} + +impl Pool { + /// Total serialized size in bytes. + pub const LEN: usize = size_of::(); + + /// PDA seed prefix. + pub const SEED: &'static [u8] = b"pool"; + + /// Initializes a freshly created account. + #[inline(always)] + #[allow(clippy::too_many_arguments)] + pub fn init( + bytes: &mut [u8], + bump: u8, + admin: &Address, + operator: &Address, + entry_fee: u64, + settle_deadline_slots: u64, + weights: &[u32; MAX_TIERS], + tier_count: u8, + ) -> Result<(), ProgramError> { + if bytes.len() != Self::LEN { + return Err(GachaError::InvalidAccountData.into()); + } + let account = unsafe { &mut *transmute::<*mut u8, *mut Self>(bytes.as_mut_ptr()) }; + account.discriminator = AccountDiscriminator::Pool as u8; + account.bump = bump; + account.tier_count = tier_count; + account.admin = *admin; + account.operator = *operator; + account.entry_fee = entry_fee; + account.pulls_count = 0; + account.pending_pulls = 0; + account.settle_deadline_slots = settle_deadline_slots; + account.weights = *weights; + Ok(()) + } + + /// Deserializes a mutable reference from raw account data. + #[inline(always)] + pub fn load_mut(bytes: &mut [u8]) -> Result<&mut Self, ProgramError> { + Self::validate(bytes)?; + Ok(unsafe { &mut *transmute::<*mut u8, *mut Self>(bytes.as_mut_ptr()) }) + } + + /// Deserializes an immutable reference from raw account data. + #[inline(always)] + pub fn load(bytes: &[u8]) -> Result<&Self, ProgramError> { + Self::validate(bytes)?; + Ok(unsafe { &*transmute::<*const u8, *const Self>(bytes.as_ptr()) }) + } + + /// Verifies an account is a genuine pool: owned by this program, correctly + /// sized, and carrying the pool discriminator. Call in account validation. + pub fn check(account: &AccountView) -> Result<(), ProgramError> { + if !account.owned_by(&crate::ID) { + return Err(GachaError::NotProgramOwned.into()); + } + Self::validate(&account.try_borrow()?) + } + + fn validate(bytes: &[u8]) -> Result<(), ProgramError> { + if bytes.len() != Self::LEN { + return Err(GachaError::InvalidAccountData.into()); + } + if bytes[0] != AccountDiscriminator::Pool as u8 { + return Err(GachaError::InvalidAccountDiscriminator.into()); + } + Ok(()) + } + + /// Asserts `signer` is the recorded admin. + pub fn check_admin(&self, signer: &Address) -> Result<(), ProgramError> { + if self.admin != *signer { + return Err(GachaError::Unauthorized.into()); + } + Ok(()) + } + + /// Asserts `signer` is the recorded operator. + pub fn check_operator(&self, signer: &Address) -> Result<(), ProgramError> { + if self.operator != *signer { + return Err(GachaError::NotOperator.into()); + } + Ok(()) + } +} diff --git a/games/gacha/pinocchio-simple/program/src/state/prize_mint.rs b/games/gacha/pinocchio-simple/program/src/state/prize_mint.rs new file mode 100644 index 000000000..01ad12cf2 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/state/prize_mint.rs @@ -0,0 +1,15 @@ +//! Prize mint marker for client PDA derivation. + +use codama::CodamaAccount; + +/// The prize mint — a Token-2022 mint created by `settle_and_distribute`, one +/// per settled pull. It is owned by the Token-2022 program, not this program; +/// this marker exists only so the generated client can derive its address. Its +/// existence is also the structural once-only guard: a second settle for the +/// same pull fails when this account cannot be created again. +/// +/// **PDA seeds:** `["mint", pull]` +#[derive(CodamaAccount)] +#[codama(seed(type = string(utf8), value = "mint"))] +#[codama(seed(name = "pull", type = public_key))] +pub struct PrizeMint; diff --git a/games/gacha/pinocchio-simple/program/src/state/pull.rs b/games/gacha/pinocchio-simple/program/src/state/pull.rs new file mode 100644 index 000000000..b931755bf --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/state/pull.rs @@ -0,0 +1,114 @@ +//! Pull account: one gacha pull, alive from commit until settle or refund. + +use codama::CodamaAccount; +use core::mem::{size_of, transmute}; +use pinocchio::{error::ProgramError, AccountView, Address}; + +use crate::{state::common::AccountDiscriminator, GachaError}; + +/// A single pull against a [`Pool`](super::pool::Pool). +/// +/// Created at commit with a fixed VRF input `alpha = SHA-256(pull_address || +/// client_seed)`, where `client_seed` is buyer-supplied entropy — so the operator +/// cannot precompute `beta` for a pull that has not been bought yet. The account +/// exists only while the pull is pending: `settle_and_distribute` and +/// `refund_pull` both close it, returning its rent to the buyer. A pull account +/// existing *is* the pending state; `beta` and the proof are never stored here — +/// they live in the prize NFT's metadata and the settle event. +/// +/// **PDA seeds:** `["pull", pool, buyer, index_le]` +#[repr(C, packed)] +#[derive(CodamaAccount)] +#[codama(seed(type = string(utf8), value = "pull"))] +#[codama(seed(name = "pool", type = public_key))] +#[codama(seed(name = "buyer", type = public_key))] +#[codama(seed(name = "index", type = number(u64)))] +pub struct Pull { + /// Account type discriminator ([`AccountDiscriminator::Pull`]). + pub discriminator: u8, + /// PDA bump seed. + pub bump: u8, + /// Pool this pull belongs to. + pub pool: Address, + /// Buyer that opened (and owns) this pull. + pub buyer: Address, + /// Pool pull index at commit; part of this account's seeds. + pub index: u64, + /// Buyer-supplied entropy mixed into `alpha`; stored so anyone can recompute + /// `alpha` and verify it was not operator-chosen. + pub client_seed: [u8; 32], + /// VRF input, fixed at commit: `SHA-256(pull_address || client_seed)`. + pub alpha: [u8; 32], + /// Slot at which the pull was opened; refunds unlock + /// `pool.settle_deadline_slots` after this. + pub requested_slot: u64, +} + +impl Pull { + /// Total serialized size in bytes. + pub const LEN: usize = size_of::(); + + /// PDA seed prefix. + pub const SEED: &'static [u8] = b"pull"; + + /// Initializes a freshly created pending pull. + #[inline(always)] + #[allow(clippy::too_many_arguments)] + pub fn init( + bytes: &mut [u8], + bump: u8, + pool: &Address, + buyer: &Address, + index: u64, + client_seed: &[u8; 32], + alpha: &[u8; 32], + requested_slot: u64, + ) -> Result<(), ProgramError> { + if bytes.len() != Self::LEN { + return Err(GachaError::InvalidAccountData.into()); + } + let account = unsafe { &mut *transmute::<*mut u8, *mut Self>(bytes.as_mut_ptr()) }; + account.discriminator = AccountDiscriminator::Pull as u8; + account.bump = bump; + account.pool = *pool; + account.buyer = *buyer; + account.index = index; + account.client_seed = *client_seed; + account.alpha = *alpha; + account.requested_slot = requested_slot; + Ok(()) + } + + /// Deserializes a mutable reference from raw account data. + #[inline(always)] + pub fn load_mut(bytes: &mut [u8]) -> Result<&mut Self, ProgramError> { + Self::validate(bytes)?; + Ok(unsafe { &mut *transmute::<*mut u8, *mut Self>(bytes.as_mut_ptr()) }) + } + + /// Deserializes an immutable reference from raw account data. + #[inline(always)] + pub fn load(bytes: &[u8]) -> Result<&Self, ProgramError> { + Self::validate(bytes)?; + Ok(unsafe { &*transmute::<*const u8, *const Self>(bytes.as_ptr()) }) + } + + /// Verifies an account is a genuine pull: owned by this program, correctly + /// sized, and carrying the pull discriminator. Call in account validation. + pub fn check(account: &AccountView) -> Result<(), ProgramError> { + if !account.owned_by(&crate::ID) { + return Err(GachaError::NotProgramOwned.into()); + } + Self::validate(&account.try_borrow()?) + } + + fn validate(bytes: &[u8]) -> Result<(), ProgramError> { + if bytes.len() != Self::LEN { + return Err(GachaError::InvalidAccountData.into()); + } + if bytes[0] != AccountDiscriminator::Pull as u8 { + return Err(GachaError::InvalidAccountDiscriminator.into()); + } + Ok(()) + } +} diff --git a/games/gacha/pinocchio-simple/program/src/state/vault.rs b/games/gacha/pinocchio-simple/program/src/state/vault.rs new file mode 100644 index 000000000..bbcd523f4 --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/state/vault.rs @@ -0,0 +1,13 @@ +//! Pot vault marker for client PDA derivation. + +use codama::CodamaAccount; + +/// The pot vault — a program-owned, zero-data PDA that escrows a pool's pooled +/// entry lamports. It carries no account data; this marker exists only so the +/// generated client can derive its address. +/// +/// **PDA seeds:** `["vault", admin]` +#[derive(CodamaAccount)] +#[codama(seed(type = string(utf8), value = "vault"))] +#[codama(seed(name = "admin", type = public_key))] +pub struct Vault; diff --git a/games/gacha/pinocchio-simple/program/src/tests.rs b/games/gacha/pinocchio-simple/program/src/tests.rs new file mode 100644 index 000000000..929230f2e --- /dev/null +++ b/games/gacha/pinocchio-simple/program/src/tests.rs @@ -0,0 +1,109 @@ +//! Host unit tests for the pure gacha logic: tier selection, alpha derivation, +//! and name formatting. + +use pinocchio::Address; + +use crate::{ + gacha::{derive_alpha, format_hex, format_u64, select_tier, MAX_TIERS}, + GachaError, +}; + +/// Copies `vals` into a fixed-length tier array, zero-padding the rest. +fn arr(vals: &[u32]) -> [u32; MAX_TIERS] { + let mut a = [0u32; MAX_TIERS]; + a[..vals.len()].copy_from_slice(vals); + a +} + +/// Builds a `beta` whose first 16 bytes encode `value` as a little-endian u128. +fn beta_from(value: u128) -> [u8; 64] { + let mut beta = [0u8; 64]; + beta[..16].copy_from_slice(&value.to_le_bytes()); + beta +} + +#[test] +fn selects_tier_by_weight_bucket() { + // Weights 60 / 30 / 10, total 100. + let weights = arr(&[60, 30, 10]); + + assert_eq!(select_tier(&beta_from(0), &weights, 3).unwrap(), 0); + assert_eq!(select_tier(&beta_from(59), &weights, 3).unwrap(), 0); + assert_eq!(select_tier(&beta_from(60), &weights, 3).unwrap(), 1); + assert_eq!(select_tier(&beta_from(89), &weights, 3).unwrap(), 1); + assert_eq!(select_tier(&beta_from(90), &weights, 3).unwrap(), 2); + assert_eq!(select_tier(&beta_from(99), &weights, 3).unwrap(), 2); +} + +#[test] +fn wraps_via_modulo() { + let weights = arr(&[60, 30, 10]); + // 100 % 100 == 0 -> tier 0; 190 % 100 == 90 -> tier 2. + assert_eq!(select_tier(&beta_from(100), &weights, 3).unwrap(), 0); + assert_eq!(select_tier(&beta_from(190), &weights, 3).unwrap(), 2); +} + +#[test] +fn respects_tier_count() { + // Only the first two tiers are active even though a third is present. + let weights = arr(&[60, 40, 10]); + assert_eq!(select_tier(&beta_from(99), &weights, 2).unwrap(), 1); +} + +#[test] +fn errors_on_zero_total_weight() { + let weights = arr(&[0, 0, 0]); + assert!(matches!(select_tier(&beta_from(0), &weights, 3), Err(GachaError::InvalidTierConfig))); +} + +/// Pinned cross-language fixture: the TypeScript client's `pullAlpha` test uses +/// the same inputs and digest, keeping the two implementations byte-identical. +#[test] +fn derive_alpha_matches_pinned_fixture() { + let pull = Address::from([1u8; 32]); + let seed = [2u8; 32]; + let expected: [u8; 32] = [ + 0xf8, 0x18, 0xaf, 0xd3, 0x7a, 0x6d, 0xc3, 0xbc, 0x92, 0xfb, 0x44, 0x73, 0x10, 0x11, 0x27, 0x70, 0x06, 0xdb, + 0x4e, 0xfa, 0x6e, 0x90, 0x23, 0xcd, 0x74, 0x68, 0xc0, 0x23, 0x35, 0xd2, 0x2a, 0x4d, + ]; + assert_eq!(derive_alpha(&pull, &seed), expected); +} + +#[test] +fn derive_alpha_depends_on_both_inputs() { + let pull = Address::from([1u8; 32]); + let base = derive_alpha(&pull, &[2u8; 32]); + assert_ne!(derive_alpha(&pull, &[3u8; 32]), base); + assert_ne!(derive_alpha(&Address::from([9u8; 32]), &[2u8; 32]), base); +} + +#[test] +fn formats_u64_decimal() { + let mut buf = [0u8; 20]; + assert_eq!(format_u64(0, &mut buf), "0"); + let mut buf = [0u8; 20]; + assert_eq!(format_u64(42, &mut buf), "42"); + let mut buf = [0u8; 20]; + assert_eq!(format_u64(u64::MAX, &mut buf), "18446744073709551615"); +} + +#[test] +fn formats_hex_lowercase() { + let mut buf = [0u8; 8]; + assert_eq!(format_hex(&[0x00, 0xab, 0xff, 0x1c], &mut buf), "00abff1c"); + let mut buf = [0u8; 4]; + assert_eq!(format_hex(&[], &mut buf), ""); +} + +#[test] +fn formats_hex_proof_sized_input() { + let mut bytes = [0u8; 80]; + for (i, byte) in bytes.iter_mut().enumerate() { + *byte = i as u8; + } + let mut buf = [0u8; 160]; + let hex = format_hex(&bytes, &mut buf); + assert_eq!(hex.len(), 160); + assert!(hex.starts_with("000102030405")); + assert!(hex.ends_with("4d4e4f")); +} diff --git a/games/gacha/pinocchio-simple/rust-toolchain.toml b/games/gacha/pinocchio-simple/rust-toolchain.toml new file mode 100644 index 000000000..50b3f5d47 --- /dev/null +++ b/games/gacha/pinocchio-simple/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.92" diff --git a/games/gacha/pinocchio-simple/rustfmt.toml b/games/gacha/pinocchio-simple/rustfmt.toml new file mode 100644 index 000000000..5e1292f3e --- /dev/null +++ b/games/gacha/pinocchio-simple/rustfmt.toml @@ -0,0 +1,5 @@ +edition = "2021" +max_width = 120 +tab_spaces = 4 +use_small_heuristics = "Max" +reorder_imports = true diff --git a/games/gacha/pinocchio-simple/scripts/buy-pull.ts b/games/gacha/pinocchio-simple/scripts/buy-pull.ts new file mode 100644 index 000000000..b74396e18 --- /dev/null +++ b/games/gacha/pinocchio-simple/scripts/buy-pull.ts @@ -0,0 +1,65 @@ +/** + * Opens a pull on a gacha pool with `buy_pull`, signed by the buyer keypair, + * leaving it Pending for the operator crank to settle. Mints 32 random bytes of + * `clientSeed` (the buyer entropy that makes the VRF input unpredictable) and + * uses `pool.pulls_count` as the pull index. + * + * Env: + * RPC_URL RPC endpoint (default https://api.devnet.solana.com) + * BUYER_KEYPAIR path to the buyer's keypair (default ~/.config/solana/id.json) + * POOL_ADMIN admin whose pool to buy from (default = buyer) + * + * Run: `RPC_URL=… pnpm exec tsx scripts/buy-pull.ts` + */ + +import { randomBytes } from 'node:crypto'; +import { homedir } from 'node:os'; + +import { findPullPda, gachaSimpleProgram } from '@solana/gacha-simple'; +import { address, createClient } from '@solana/kit'; +import { solanaRpc } from '@solana/kit-plugin-rpc'; +import { signerFromFile } from '@solana/kit-plugin-signer'; + +const RPC_URL = process.env.RPC_URL ?? 'https://api.devnet.solana.com'; + +async function main(): Promise { + const buyerPath = process.env.BUYER_KEYPAIR ?? `${homedir()}/.config/solana/id.json`; + const client = await createClient() + .use(signerFromFile(buyerPath)) + .use(solanaRpc({ rpcUrl: RPC_URL })) + .use(gachaSimpleProgram()); + const buyer = client.payer; + const admin = process.env.POOL_ADMIN ? address(process.env.POOL_ADMIN) : buyer.address; + + const [pool] = await client.gachaSimple.pdas.pool({ admin }); + const [vault] = await client.gachaSimple.pdas.vault({ admin }); + + const poolAccount = await client.gachaSimple.accounts.pool.fetch(pool); + const index = poolAccount.data.pullsCount; + const [pull] = await findPullPda({ buyer: buyer.address, index, pool }); + + const clientSeed = new Uint8Array(randomBytes(32)); + + const { context } = await client.gachaSimple.instructions + .buyPull({ + buyPullData: { clientSeed: Array.from(clientSeed) }, + buyer, + pool, + pull, + vault, + }) + .sendTransaction(); + + console.log('✓ Pull opened (Pending)'); + console.log(` pool: ${pool}`); + console.log(` buyer: ${buyer.address}`); + console.log(` pull: ${pull}`); + console.log(` index: ${index}`); + console.log(` clientSeed: ${Buffer.from(clientSeed).toString('hex')}`); + console.log(` tx: ${context.signature}`); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/games/gacha/pinocchio-simple/scripts/generate-clients.ts b/games/gacha/pinocchio-simple/scripts/generate-clients.ts new file mode 100644 index 000000000..219d73710 --- /dev/null +++ b/games/gacha/pinocchio-simple/scripts/generate-clients.ts @@ -0,0 +1,34 @@ +import type { AnchorIdl } from '@codama/nodes-from-anchor'; +import { renderVisitor as renderJavaScriptVisitor } from '@codama/renderers-js'; +import { renderVisitor as renderRustVisitor } from '@codama/renderers-rust'; +import { createFromJson } from 'codama'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const projectRoot = path.join(__dirname, '..'); +const idlPath = path.join(projectRoot, 'idl', 'gacha_simple.json'); +const idl = JSON.parse(fs.readFileSync(idlPath, 'utf-8')) as AnchorIdl; +const rustClientsDir = path.join(projectRoot, 'clients', 'rust'); +const typescriptClientsDir = path.join(projectRoot, 'clients', 'typescript'); + +const codama = createFromJson(JSON.stringify(idl)); + +void codama.accept( + renderRustVisitor(rustClientsDir, { + anchorTraits: false, + deleteFolderBeforeRendering: true, + formatCode: true, + generatedFolder: 'src/generated', + }), +); + +void codama.accept( + renderJavaScriptVisitor(typescriptClientsDir, { + deleteFolderBeforeRendering: true, + formatCode: true, + generatedFolder: 'src/generated', + }), +); diff --git a/games/gacha/pinocchio-simple/scripts/operator-demo.ts b/games/gacha/pinocchio-simple/scripts/operator-demo.ts new file mode 100644 index 000000000..ff44e3e89 --- /dev/null +++ b/games/gacha/pinocchio-simple/scripts/operator-demo.ts @@ -0,0 +1,78 @@ +/** + * Demonstrates the off-chain operator and verifier roles for a gacha pull, with no + * RPC. It mirrors what happens around a real `buyPull` -> `settleAndDistribute`: + * + * 1. A pool records an operator (an Ed25519 key that is also its ECVRF key). + * 2. A buyer opens a pull with 32 random bytes of `clientSeed`; the VRF input is + * `alpha = SHA-256(pull_address || clientSeed)` — unpredictable to the + * operator until the buy lands, so outcomes cannot be precomputed. + * 3. The operator proves `beta = VRF(alpha)` and submits it via + * `settleAndDistribute`, which mints the prize NFT carrying the full reveal + * provenance in its metadata. + * 4. Anyone recomputes `alpha` from the NFT's metadata, verifies the proof, + * and reproduces the selected tier — no transaction history needed. + * + * Run: `just demo` + */ + +import { randomBytes } from 'node:crypto'; + +import { generateKeyPair, provePull, publicKeyFromSeed, pullAlpha, selectTier, verifyPull } from '@solana/gacha-simple'; +import { findPullPda, RARITY_LABELS, verifyPrizeProvenance } from '@solana/gacha-simple'; +import { GACHA_SIMPLE_PROGRAM_ADDRESS } from '@solana/gacha-simple'; +import { getAddressEncoder } from '@solana/kit'; + +async function main() { + // The operator's 32-byte seed is both its Solana signing key and its ECVRF key. + const { sk, pk } = generateKeyPair(); + console.log(`Operator key matches its Ed25519 public key: ${bytesEqual(pk, publicKeyFromSeed(sk))}`); + + // A pool (here, stand-in addresses) and buyer produce a deterministic pull PDA. + // The buyer's random clientSeed is what makes alpha unpredictable. + const pool = GACHA_SIMPLE_PROGRAM_ADDRESS; + const buyer = GACHA_SIMPLE_PROGRAM_ADDRESS; + const clientSeed = new Uint8Array(randomBytes(32)); + const [pull] = await findPullPda({ buyer, index: 0, pool }); + const alpha = pullAlpha(pull, clientSeed); + console.log(`Pull: ${pull}`); + console.log(`alpha = SHA-256(pull || clientSeed): ${Buffer.from(alpha).toString('hex')}`); + + // Operator reveals; verifier recomputes alpha, checks the proof, and + // reproduces the tier. + const { proof, beta } = provePull(sk, alpha); + const verifierAlpha = pullAlpha(pull, clientSeed); + const verified = verifyPull(pk, verifierAlpha, proof); + console.log(`Proof verifies off-chain: ${verified}`); + + // A tampered proof must not verify — this detection is the accountability + // the on-chain program cannot provide itself. + const tampered = proof.slice(); + tampered[0] ^= 1; + console.log(`Tampered proof rejected: ${!verifyPull(pk, verifierAlpha, tampered)}`); + + const weights = [70, 25, 5]; + const tier = selectTier(beta, weights, weights.length); + console.log(`Selected tier: ${tier} (${RARITY_LABELS[tier]}, weights ${weights.join('/')})`); + + // The same check driven purely from what the prize NFT's metadata carries. + const provenance = { + beta, + clientSeed, + proof, + pull: new Uint8Array(getAddressEncoder().encode(pull)), + rarity: RARITY_LABELS[tier]!, + }; + const selfCertified = verifyPrizeProvenance(provenance, pk, weights, weights.length); + console.log(`NFT metadata self-certifies: ${selfCertified}`); + + if (!verified || !selfCertified) process.exit(1); +} + +function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { + return a.length === b.length && a.every((x, i) => x === b[i]); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/games/gacha/pinocchio-simple/scripts/operator-settle.ts b/games/gacha/pinocchio-simple/scripts/operator-settle.ts new file mode 100644 index 000000000..4e1e654af --- /dev/null +++ b/games/gacha/pinocchio-simple/scripts/operator-settle.ts @@ -0,0 +1,218 @@ +/** + * The reveal crank. Finds pending pulls for a pool (every pull account that + * exists is pending — settling closes it), produces each pull's ECVRF `beta` + * with the operator's key, and submits `settle_and_distribute` — which selects + * the tier and mints the prize NFT (metadata carrying the full reveal + * provenance) straight to the buyer. + * + * Env: + * RPC_URL RPC endpoint (default https://api.devnet.solana.com) + * ADMIN_KEYPAIR admin keypair whose pool to crank (default ~/.config/solana/id.json). + * Ignored when POOL is set. + * POOL pool address to crank (overrides ADMIN_KEYPAIR derivation) + * POLL_MS --watch poll interval in ms (default 5000) + * + * Run one-shot: `RPC_URL=… pnpm exec tsx scripts/operator-settle.ts` + * Run watcher: `RPC_URL=… pnpm exec tsx scripts/operator-settle.ts --watch` + */ + +import { readFileSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { resolve } from 'node:path'; + +import { + GACHA_SIMPLE_PROGRAM_ADDRESS, + gachaSimpleProgram, + getPullDecoder, + provePull, + pullAlpha, + RARITY_LABELS, + selectTier, +} from '@solana/gacha-simple'; +import { + type Address, + address, + type Base58EncodedBytes, + createClient, + createKeyPairSignerFromBytes, + getAddressEncoder, + getBase64Encoder, + getProgramDerivedAddress, + type KeyPairSigner, +} from '@solana/kit'; +import { solanaRpc } from '@solana/kit-plugin-rpc'; +import { signer } from '@solana/kit-plugin-signer'; + +const RPC_URL = process.env.RPC_URL ?? 'https://api.devnet.solana.com'; +const POLL_MS = Number(process.env.POLL_MS ?? '5000'); +const OPERATOR_KEYPAIR_PATH = resolve(process.cwd(), 'keys/operator-keypair.json'); + +const TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb' as Address; +const ATA_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address; + +/** `Pull` account layout: discriminator, bump, then the pool address. */ +const PULL_ACCOUNT_SIZE = 146n; +const PULL_POOL_OFFSET = 2n; + +function loadKeypairBytes(path: string): Uint8Array { + return Uint8Array.from(JSON.parse(readFileSync(path, 'utf-8')) as number[]); +} + +function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { + return a.length === b.length && a.every((x, i) => x === b[i]); +} + +type Client = ReturnType; + +function createGachaClient(operatorSigner: KeyPairSigner) { + return createClient() + .use(signer(operatorSigner)) + .use(solanaRpc({ rpcUrl: RPC_URL })) + .use(gachaSimpleProgram()); +} + +async function findBuyerAta(buyer: Address, mint: Address): Promise
{ + const encoder = getAddressEncoder(); + const [ata] = await getProgramDerivedAddress({ + programAddress: ATA_PROGRAM, + seeds: [encoder.encode(buyer), encoder.encode(TOKEN_2022_PROGRAM), encoder.encode(mint)], + }); + return ata; +} + +async function resolvePoolAddress(client: Client, adminKeypairPath: string): Promise
{ + if (process.env.POOL) return address(process.env.POOL); + const admin = await createKeyPairSignerFromBytes(loadKeypairBytes(adminKeypairPath)); + const [pool] = await client.gachaSimple.pdas.pool({ admin: admin.address }); + return pool; +} + +async function settlePull( + client: Client, + operatorSigner: KeyPairSigner, + operatorSeed: Uint8Array, + pool: Address, + poolWeights: number[], + tierCount: number, + pull: Address, + buyer: Address, + pullClientSeed: Uint8Array, + pullAlphaOnChain: Uint8Array, +): Promise { + const alpha = pullAlpha(pull, pullClientSeed); + if (!bytesEqual(alpha, pullAlphaOnChain)) { + throw new Error(`recomputed alpha mismatch for pull ${pull}`); + } + + const { beta, proof } = provePull(operatorSeed, alpha); + const [mint] = await client.gachaSimple.pdas.prizeMint({ pull }); + const buyerAta = await findBuyerAta(buyer, mint); + + const { context: txContext } = await client.gachaSimple.instructions + .settleAndDistribute({ + buyer, + buyerAta, + operator: operatorSigner, + pool, + pull, + settleAndDistributeData: { beta: Array.from(beta), proof: Array.from(proof) }, + }) + .sendTransaction(); + + const tier = selectTier(beta, poolWeights, tierCount); + console.log(` ✓ settled ${pull} → tier ${tier} (${RARITY_LABELS[tier]})`); + console.log(` prize mint: ${mint}`); + console.log(` tx: ${txContext.signature}`); +} + +async function crankOnce( + client: Client, + operatorSigner: KeyPairSigner, + operatorSeed: Uint8Array, + pool: Address, +): Promise { + const { data: poolData } = await client.gachaSimple.accounts.pool.fetch(pool); + if (poolData.operator !== operatorSigner.address) { + throw new Error(`pool operator ${poolData.operator} != loaded operator ${operatorSigner.address}`); + } + const weights = poolData.weights.map(Number); + const tierCount = poolData.tierCount; + + const accounts = await client.rpc + .getProgramAccounts(GACHA_SIMPLE_PROGRAM_ADDRESS, { + encoding: 'base64', + filters: [ + { dataSize: PULL_ACCOUNT_SIZE }, + { + memcmp: { + bytes: pool as string as Base58EncodedBytes, + encoding: 'base58', + offset: PULL_POOL_OFFSET, + }, + }, + ], + }) + .send(); + + const base64Encoder = getBase64Encoder(); + const pending = accounts.map(({ account, pubkey }) => ({ + decoded: getPullDecoder().decode(base64Encoder.encode(account.data[0])), + pubkey, + })); + + if (pending.length === 0) return 0; + + for (const { decoded, pubkey } of pending) { + try { + await settlePull( + client, + operatorSigner, + operatorSeed, + pool, + weights, + tierCount, + pubkey, + decoded.buyer, + Uint8Array.from(decoded.clientSeed), + Uint8Array.from(decoded.alpha), + ); + } catch (err) { + console.error(` ✗ failed to settle ${pubkey}:`, err); + } + } + return pending.length; +} + +async function main(): Promise { + const watch = process.argv.includes('--watch'); + const operatorSecret = loadKeypairBytes(OPERATOR_KEYPAIR_PATH); + const operatorSigner = await createKeyPairSignerFromBytes(operatorSecret); + const operatorSeed = operatorSecret.slice(0, 32); + + const client = createGachaClient(operatorSigner); + const pool = await resolvePoolAddress(client, process.env.ADMIN_KEYPAIR ?? `${homedir()}/.config/solana/id.json`); + + console.log(`Operator: ${operatorSigner.address}`); + console.log(`Pool: ${pool}`); + + if (!watch) { + const count = await crankOnce(client, operatorSigner, operatorSeed, pool); + console.log(count === 0 ? 'No pending pulls.' : `Processed ${count} pending pull(s).`); + return; + } + + console.log(`Watching for pending pulls every ${POLL_MS}ms (Ctrl-C to stop)…`); + for (;;) { + try { + await crankOnce(client, operatorSigner, operatorSeed, pool); + } catch (err) { + console.error('crank error:', err); + } + await new Promise(r => setTimeout(r, POLL_MS)); + } +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/games/gacha/pinocchio-simple/scripts/setup-pool.ts b/games/gacha/pinocchio-simple/scripts/setup-pool.ts new file mode 100644 index 000000000..d6d6a49cb --- /dev/null +++ b/games/gacha/pinocchio-simple/scripts/setup-pool.ts @@ -0,0 +1,80 @@ +/** + * Creates a gacha pool with `init_pool`, signed by an admin keypair. + * + * The `operator` you pass is the Ed25519 key whose 32-byte seed doubles as the + * pool's ECVRF key — every reveal must verify off-chain against it. `init_pool` + * only records it; no registry interaction is needed. + * + * Env: + * RPC_URL RPC endpoint (default https://api.devnet.solana.com) + * ADMIN_KEYPAIR path to the admin's Solana CLI keypair JSON + * (default ~/.config/solana/id.json) + * OPERATOR_PUBKEY base58 operator public key (required) + * ENTRY_FEE_SOL entry fee per pull in SOL (default 0.05) + * DEADLINE_SLOTS refund deadline in slots (default 300) + * WEIGHTS comma-separated tier weights (default "28,23,18,14,9,4,3,1") + * + * Run: `RPC_URL=… OPERATOR_PUBKEY=… pnpm exec tsx scripts/setup-pool.ts` + */ + +import { homedir } from 'node:os'; + +import { gachaSimpleProgram, MAX_TIERS } from '@solana/gacha-simple'; +import { address, createClient } from '@solana/kit'; +import { solanaRpc } from '@solana/kit-plugin-rpc'; +import { signerFromFile } from '@solana/kit-plugin-signer'; + +const RPC_URL = process.env.RPC_URL ?? 'https://api.devnet.solana.com'; +const LAMPORTS_PER_SOL = 1_000_000_000; + +async function main() { + const operatorEnv = process.env.OPERATOR_PUBKEY; + if (!operatorEnv) throw new Error('OPERATOR_PUBKEY is required'); + const operator = address(operatorEnv); + + const adminPath = process.env.ADMIN_KEYPAIR ?? `${homedir()}/.config/solana/id.json`; + const client = await createClient() + .use(signerFromFile(adminPath)) + .use(solanaRpc({ rpcUrl: RPC_URL })) + .use(gachaSimpleProgram()); + const admin = client.payer; + + const weights = (process.env.WEIGHTS ?? '28,23,18,14,9,4,3,1') + .split(',') + .map(w => Number(w.trim())) + .filter(w => Number.isFinite(w) && w > 0); + if (weights.length === 0 || weights.length > MAX_TIERS) { + throw new Error(`WEIGHTS must list 1–${MAX_TIERS} positive numbers`); + } + const paddedWeights = [...weights, ...Array(MAX_TIERS - weights.length).fill(0)]; + + const entryFee = BigInt(Math.round(Number(process.env.ENTRY_FEE_SOL ?? '0.05') * LAMPORTS_PER_SOL)); + const settleDeadlineSlots = BigInt(Math.max(0, Math.floor(Number(process.env.DEADLINE_SLOTS ?? '300')))); + + const { context } = await client.gachaSimple.instructions + .initPool({ + admin, + initPoolData: { + entryFee, + operator, + settleDeadlineSlots, + tierCount: weights.length, + weights: paddedWeights, + }, + }) + .sendTransaction(); + + const [poolAddress] = await client.gachaSimple.pdas.pool({ admin: admin.address }); + console.log('✓ Pool created'); + console.log(` admin: ${admin.address}`); + console.log(` operator: ${operator}`); + console.log(` pool: ${poolAddress}`); + console.log(` entry fee: ${Number(entryFee) / LAMPORTS_PER_SOL} SOL`); + console.log(` weights: ${weights.join('/')}`); + console.log(` tx: ${context.signature}`); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/Cargo.toml b/games/gacha/pinocchio-simple/tests/integration-tests/Cargo.toml new file mode 100644 index 000000000..b4fc7d0b4 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "tests-gacha-simple" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[lints] +workspace = true + +[dependencies] +gacha = { package = "gacha-simple-program", path = "../../program", features = ["no-entrypoint"] } +gacha-client = { package = "gacha-simple-client", path = "../../clients/rust" } +litesvm = "^0.12" +pinocchio-associated-token-account = { workspace = true } +pinocchio-system = { workspace = true } +pinocchio-token-2022 = { workspace = true } +serde_json = "1" +solana-account = "~3.4" +solana-address = { workspace = true, features = ["atomic"] } +solana-clock = "^3" +solana-instruction = "^3" +solana-keypair = "=3.1.0" +solana-message = "^3.1" +solana-native-token = "^3" +solana-sha256-hasher = { workspace = true } +solana-signer = "^3" +solana-transaction = "^3" +solana-transaction-error = "^3.2" +spl-token-2022-interface = "2.1" +spl-token-metadata-interface = "0.8" diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/lib.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/lib.rs new file mode 100644 index 000000000..aef12c8f3 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/lib.rs @@ -0,0 +1,27 @@ +pub use ::gacha::*; +/// The codama-generated client, used for account decoding and PDA derivation so +/// the tests never re-derive what the IDL already declares. +pub use ::gacha_client as client; + +pub mod utils; + +pub mod tests { + pub use crate::utils::{asserts, constants, idl, pda}; + + pub mod utils { + pub use crate::utils::test_helpers::*; + } +} + +#[cfg(test)] +mod test_account_meta; +#[cfg(test)] +mod test_buy_pull; +#[cfg(test)] +mod test_init_pool; +#[cfg(test)] +mod test_refund_pull; +#[cfg(test)] +mod test_settle_and_distribute; +#[cfg(test)] +mod test_withdraw_fees; diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/test_account_meta.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_account_meta.rs new file mode 100644 index 000000000..e7920d9c1 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_account_meta.rs @@ -0,0 +1,118 @@ +//! IDL-driven account-meta tests: every account an instruction's IDL declares +//! writable must be rejected by the program when demoted to read-only. The fee +//! payer (index 0) is skipped everywhere: the runtime forces it writable. + +use solana_instruction::{AccountMeta, Instruction}; +use solana_signer::Signer; + +use crate::{ + tests::{asserts::TransactionResultExt, constants::PROGRAM_ID, idl, utils::*}, + GachaError, +}; + +fn demote(metas: &mut [AccountMeta], index: usize) { + let demoted = &metas[index]; + metas[index] = AccountMeta::new_readonly(demoted.pubkey, demoted.is_signer); +} + +fn writable_indices(instruction: &str) -> Vec { + let indices: Vec = idl::instruction_accounts(instruction) + .into_iter() + .filter(|a| a.is_writable && a.index != 0) + .map(|a| a.index) + .collect(); + assert!(!indices.is_empty(), "IDL declares demotable writable accounts for {instruction}"); + indices +} + +#[test] +fn init_pool_writable_accounts_are_enforced() { + for index in writable_indices("initPool") { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + + let mut metas = init_pool_metas(&admin.pubkey()); + demote(&mut metas, index); + let data = init_pool_data(&operator.pubkey(), ENTRY_FEE, SETTLE_DEADLINE, &[100]); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data }; + build_and_send(&mut svm, &[&admin], &admin.pubkey(), &ix).assert_err(GachaError::AccountNotWritable); + } +} + +#[test] +fn buy_pull_writable_accounts_are_enforced() { + for index in writable_indices("buyPull") { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let mut metas = buy_pull_metas(&admin.pubkey(), &buyer.pubkey(), 0); + demote(&mut metas, index); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data: buy_pull_data(&random_seed()) }; + build_and_send(&mut svm, &[&buyer], &buyer.pubkey(), &ix).assert_err(GachaError::AccountNotWritable); + } +} + +#[test] +fn settle_and_distribute_writable_accounts_are_enforced() { + for index in writable_indices("settleAndDistribute") { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let mut metas = settle_metas(&admin.pubkey(), &operator.pubkey(), &pull, &buyer.pubkey()); + demote(&mut metas, index); + let data = settle_data(&beta_from(0), &[0u8; 80]); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data }; + build_and_send(&mut svm, &[&operator], &operator.pubkey(), &ix).assert_err(GachaError::AccountNotWritable); + } +} + +#[test] +fn refund_pull_writable_accounts_are_enforced() { + for index in writable_indices("refundPull") { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let mut metas = refund_pull_metas(&admin.pubkey(), &buyer.pubkey(), &pull); + demote(&mut metas, index); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data: vec![3u8] }; + build_and_send(&mut svm, &[&buyer], &buyer.pubkey(), &ix).assert_err(GachaError::AccountNotWritable); + } +} + +#[test] +fn withdraw_fees_writable_accounts_are_enforced() { + for index in writable_indices("withdrawFees") { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let mut metas = withdraw_fees_metas(&admin.pubkey(), &admin.pubkey()); + demote(&mut metas, index); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data: withdraw_fees_data(1) }; + build_and_send(&mut svm, &[&admin], &admin.pubkey(), &ix).assert_err(GachaError::AccountNotWritable); + } +} + +#[test] +fn admin_must_sign_init_pool() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let fee_payer = funded_keypair(&mut svm); + + let mut metas = init_pool_metas(&admin.pubkey()); + metas[0] = AccountMeta::new(admin.pubkey(), false); // admin present but not a signer + let data = init_pool_data(&operator.pubkey(), ENTRY_FEE, SETTLE_DEADLINE, &[100]); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data }; + + build_and_send(&mut svm, &[&fee_payer], &fee_payer.pubkey(), &ix).assert_err(GachaError::NotSigner); +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/test_buy_pull.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_buy_pull.rs new file mode 100644 index 000000000..d21b6c3a5 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_buy_pull.rs @@ -0,0 +1,67 @@ +use solana_signer::Signer; + +use crate::{ + client, + tests::{asserts::TransactionResultExt, utils::*}, + Pull, +}; + +#[test] +fn opens_a_pending_pull_and_escrows_fee() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[70, 30]).assert_ok(); + + let (result, pull, client_seed) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let vault_rent_floor = svm.minimum_balance_for_rent_exemption(0); + assert_eq!(vault_balance(&svm, &admin.pubkey()), vault_rent_floor + ENTRY_FEE); + + let view = read_pull(&svm, &pull); + assert_eq!(view.pool, client::Pool::find_pda(&admin.pubkey()).0.to_bytes()); + assert_eq!(view.buyer, buyer.pubkey().to_bytes()); + assert_eq!(view.index, 0); + assert_eq!(view.client_seed, client_seed); + assert_eq!(view.alpha, solana_sha256_hasher::hashv(&[pull.as_ref(), &client_seed]).to_bytes()); + assert!(view.requested_slot > 0); + + let pool = read_pool(&svm, &admin.pubkey()); + assert_eq!(pool.pulls_count, 1); + assert_eq!(pool.pending_pulls, 1); +} + +#[test] +fn increments_pull_index() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[70, 30]).assert_ok(); + + let (r0, _, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + r0.assert_ok(); + let (r1, pull1, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + r1.assert_ok(); + + assert_eq!(read_pull(&svm, &pull1).index, 1); + let pool = read_pool(&svm, &admin.pubkey()); + assert_eq!(pool.pulls_count, 2); + assert_eq!(pool.pending_pulls, 2); +} + +#[test] +fn buyer_pays_rent_plus_fee() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let buyer_before = svm.get_balance(&buyer.pubkey()).unwrap(); + let (result, _, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + let buyer_after = svm.get_balance(&buyer.pubkey()).unwrap(); + + let pull_rent = svm.minimum_balance_for_rent_exemption(Pull::LEN); + assert_eq!(buyer_before - buyer_after, ENTRY_FEE + pull_rent + TX_FEE); +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/test_init_pool.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_init_pool.rs new file mode 100644 index 000000000..cc47a5455 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_init_pool.rs @@ -0,0 +1,84 @@ +use solana_address::Address; +use solana_signer::Signer; + +use crate::{ + client, + tests::{asserts::TransactionResultExt, utils::*}, + GachaError, +}; + +#[test] +fn creates_pool() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let weights = [70u32, 25, 5]; + + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &weights).assert_ok(); + + let pool = read_pool(&svm, &admin.pubkey()); + assert_eq!(pool.tier_count, 3); + assert_eq!(pool.admin, admin.pubkey().to_bytes()); + assert_eq!(pool.operator, operator.pubkey().to_bytes()); + assert_eq!(pool.entry_fee, ENTRY_FEE); + assert_eq!(pool.settle_deadline_slots, SETTLE_DEADLINE); + assert_eq!(pool.pulls_count, 0); + assert_eq!(pool.pending_pulls, 0); + assert_eq!(&pool.weights[..3], &weights); + assert_eq!(&pool.weights[3..], &[0u32; 5]); +} + +#[test] +fn rejects_zero_tiers() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[]).assert_err(GachaError::TooManyTiers); +} + +#[test] +fn rejects_zero_weight() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[70, 0]).assert_err(GachaError::InvalidTierConfig); +} + +#[test] +fn rejects_zero_entry_fee() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), 0, &[100]).assert_err(GachaError::InvalidEntryFee); +} + +#[test] +fn rejects_zero_deadline() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool_with_deadline(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, 0, &[100]) + .assert_err(GachaError::InvalidSettleDeadline); +} + +#[test] +fn rejects_zero_operator() { + let (mut svm, admin) = setup(); + init_pool(&mut svm, &admin, &Address::default(), ENTRY_FEE, &[100]).assert_err(GachaError::InvalidOperator); +} + +#[test] +fn rejects_operator_equal_admin() { + let (mut svm, admin) = setup(); + init_pool(&mut svm, &admin, &admin.pubkey(), ENTRY_FEE, &[100]).assert_err(GachaError::InvalidOperator); +} + +#[test] +fn rejects_off_curve_operator() { + let (mut svm, admin) = setup(); + let (off_curve, _) = client::Vault::find_pda(&admin.pubkey()); + init_pool(&mut svm, &admin, &off_curve, ENTRY_FEE, &[100]).assert_err(GachaError::InvalidOperator); +} + +#[test] +fn rejects_duplicate_pool() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_err(GachaError::PoolAlreadyExists); +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/test_refund_pull.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_refund_pull.rs new file mode 100644 index 000000000..ffe19896b --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_refund_pull.rs @@ -0,0 +1,81 @@ +use solana_signer::Signer; + +use crate::{ + tests::{asserts::TransactionResultExt, utils::*}, + GachaError, Pull, +}; + +#[test] +fn refund_too_early() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + refund_pull(&mut svm, &admin.pubkey(), &buyer, &pull).assert_err(GachaError::RefundTooEarly); +} + +#[test] +fn refund_after_deadline_returns_fee_and_rent() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let requested_slot = read_pull(&svm, &pull).requested_slot; + svm.warp_to_slot(requested_slot + SETTLE_DEADLINE + 2); + + let buyer_before = svm.get_balance(&buyer.pubkey()).unwrap(); + let vault_before = vault_balance(&svm, &admin.pubkey()); + + refund_pull(&mut svm, &admin.pubkey(), &buyer, &pull).assert_ok(); + + let pull_rent = svm.minimum_balance_for_rent_exemption(Pull::LEN); + let buyer_after = svm.get_balance(&buyer.pubkey()).unwrap(); + assert_eq!(buyer_after - buyer_before, ENTRY_FEE + pull_rent - TX_FEE); + assert_eq!(vault_before - vault_balance(&svm, &admin.pubkey()), ENTRY_FEE); + + let closed = svm.get_account(&pull); + assert!(closed.is_none_or(|a| a.lamports == 0), "pull account should be closed"); + + let pool = read_pool(&svm, &admin.pubkey()); + assert_eq!(pool.pending_pulls, 0); + assert_eq!(pool.pulls_count, 1); +} + +#[test] +fn non_buyer_cannot_refund() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let impostor = funded_keypair(&mut svm); + refund_pull(&mut svm, &admin.pubkey(), &impostor, &pull).assert_err(GachaError::BuyerMismatch); +} + +#[test] +fn settled_pull_cannot_refund() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta_from(0), &[0u8; 80]) + .assert_ok(); + + let requested_slot = 1; + svm.warp_to_slot(requested_slot + SETTLE_DEADLINE + 2); + refund_pull(&mut svm, &admin.pubkey(), &buyer, &pull).assert_err(GachaError::NotProgramOwned); +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/test_settle_and_distribute.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_settle_and_distribute.rs new file mode 100644 index 000000000..eea328007 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_settle_and_distribute.rs @@ -0,0 +1,318 @@ +use solana_address::Address; +use solana_instruction::{AccountMeta, Instruction}; +use solana_signer::Signer; +use spl_token_2022_interface::{ + extension::{metadata_pointer::MetadataPointer, BaseStateWithExtensions, StateWithExtensions}, + state::{Account as TokenAccount, Mint}, +}; +use spl_token_metadata_interface::state::TokenMetadata; + +use crate::{ + client, + gacha::{format_hex, select_tier}, + tests::{ + asserts::TransactionResultExt, + constants::{PROGRAM_ID, TOKEN_2022_ID}, + pda::get_ata, + utils::*, + }, + GachaError, Pull, MAX_TIERS, NFT_NAME_PREFIX, NFT_SYMBOL, NFT_URI, RARITY_LABELS, +}; + +fn hex(bytes: &[u8]) -> String { + let mut buf = vec![0u8; bytes.len() * 2]; + format_hex(bytes, &mut buf).to_string() +} + +fn unhex(s: &str) -> Vec { + (0..s.len() / 2).map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).unwrap()).collect() +} + +/// An arbitrary proof: the program never verifies it on-chain, it only records it. +fn test_proof() -> [u8; 80] { + let mut proof = [0u8; 80]; + for (i, byte) in proof.iter_mut().enumerate() { + *byte = i as u8; + } + proof +} + +#[test] +fn settle_mints_self_certifying_prize_and_closes_pull() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + let weights = [70u32, 25, 5]; + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &weights).assert_ok(); + + let (result, pull, client_seed) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + // 95 % 100 = 95 -> past 70 and 25 -> tier 2 ("rare"). + let beta = beta_from(95); + let proof = test_proof(); + let expected_tier = 2u8; + + let pull_rent = svm.get_account(&pull).expect("pull exists").lamports; + let buyer_before = svm.get_balance(&buyer.pubkey()).unwrap(); + + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta, &proof).assert_ok(); + + let (pool, _) = client::Pool::find_pda(&admin.pubkey()); + let (mint, _) = client::PrizeMint::find_pda(&pull); + let mint_account = svm.get_account(&mint).expect("mint exists"); + assert_eq!(mint_account.owner, TOKEN_2022_ID); + + let mint_state = StateWithExtensions::::unpack(&mint_account.data).expect("valid mint"); + assert_eq!(mint_state.base.decimals, 0); + assert_eq!(mint_state.base.supply, 1); + assert!(mint_state.base.mint_authority.is_none()); + + let pointer = mint_state.get_extension::().expect("metadata pointer"); + assert_eq!(pointer.metadata_address.0.to_bytes(), mint.to_bytes()); + assert_eq!(pointer.authority.0.to_bytes(), pool.to_bytes()); + + let metadata = mint_state.get_variable_len_extension::().expect("token metadata"); + assert_eq!(metadata.name, format!("{NFT_NAME_PREFIX}0")); + assert_eq!(metadata.symbol, NFT_SYMBOL); + assert_eq!(metadata.uri, NFT_URI); + assert_eq!(metadata.mint.to_bytes(), mint.to_bytes()); + assert_eq!(metadata.update_authority.0.to_bytes(), pool.to_bytes()); + assert_eq!( + metadata.additional_metadata, + vec![ + ("rarity".to_string(), RARITY_LABELS[expected_tier as usize].to_string()), + ("pull".to_string(), hex(pull.as_ref())), + ("client_seed".to_string(), hex(&client_seed)), + ("beta".to_string(), hex(&beta)), + ("proof".to_string(), hex(&proof)), + ] + ); + + // The NFT is self-certifying: replay the whole verification from metadata alone. + let fields: std::collections::HashMap<_, _> = metadata.additional_metadata.iter().cloned().collect(); + let pull_bytes = unhex(&fields["pull"]); + let seed_bytes = unhex(&fields["client_seed"]); + let alpha = solana_sha256_hasher::hashv(&[&pull_bytes, &seed_bytes]).to_bytes(); + assert_eq!(alpha, solana_sha256_hasher::hashv(&[pull.as_ref(), &client_seed]).to_bytes()); + let beta_bytes: [u8; 64] = unhex(&fields["beta"]).try_into().unwrap(); + let mut padded = [0u32; MAX_TIERS]; + padded[..weights.len()].copy_from_slice(&weights); + let tier = select_tier(&beta_bytes, &padded, weights.len() as u8).unwrap(); + assert_eq!(RARITY_LABELS[tier as usize], fields["rarity"]); + + let ata_account = svm.get_account(&get_ata(&buyer.pubkey(), &mint)).expect("buyer ata exists"); + let ata_state = StateWithExtensions::::unpack(&ata_account.data).expect("valid token account"); + assert_eq!(ata_state.base.amount, 1); + assert_eq!(ata_state.base.owner.to_bytes(), buyer.pubkey().to_bytes()); + + let closed = svm.get_account(&pull); + assert!(closed.is_none_or(|a| a.lamports == 0), "pull account should be closed"); + let buyer_after = svm.get_balance(&buyer.pubkey()).unwrap(); + assert_eq!(buyer_after - buyer_before, pull_rent, "pull rent refunds to the buyer"); + + let pool_view = read_pool(&svm, &admin.pubkey()); + assert_eq!(pool_view.pending_pulls, 0); + assert_eq!(pool_view.pulls_count, 1); +} + +#[test] +fn settled_fee_becomes_withdrawable() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + withdraw_fees(&mut svm, &admin.pubkey(), &admin, ENTRY_FEE).assert_err(GachaError::InsufficientVaultFunds); + + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta_from(0), &test_proof()) + .assert_ok(); + + withdraw_fees(&mut svm, &admin.pubkey(), &admin, ENTRY_FEE).assert_ok(); +} + +#[test] +fn double_settle_rejected() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta_from(0), &test_proof()) + .assert_ok(); + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta_from(1), &test_proof()) + .assert_err(GachaError::NotProgramOwned); +} + +#[test] +fn non_operator_cannot_settle() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let impostor = funded_keypair(&mut svm); + settle_and_distribute(&mut svm, &admin.pubkey(), &impostor, &pull, &buyer.pubkey(), &beta_from(0), &test_proof()) + .assert_err(GachaError::NotOperator); +} + +#[test] +fn rejects_wrong_buyer() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let impostor = funded_keypair(&mut svm); + settle_and_distribute( + &mut svm, + &admin.pubkey(), + &operator, + &pull, + &impostor.pubkey(), + &beta_from(0), + &test_proof(), + ) + .assert_err(GachaError::BuyerMismatch); +} + +#[test] +fn rejects_wrong_mint_pda() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let mut metas = settle_metas(&admin.pubkey(), &operator.pubkey(), &pull, &buyer.pubkey()); + metas[4] = AccountMeta::new(Address::new_unique(), false); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data: settle_data(&beta_from(0), &test_proof()) }; + build_and_send(&mut svm, &[&operator], &operator.pubkey(), &ix).assert_err(GachaError::InvalidMintPda); +} + +#[test] +fn rejects_wrong_token_program() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let spl_token_v1 = Address::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + let mut metas = settle_metas(&admin.pubkey(), &operator.pubkey(), &pull, &buyer.pubkey()); + metas[7] = AccountMeta::new_readonly(spl_token_v1, false); + let ix = Instruction { program_id: PROGRAM_ID, accounts: metas, data: settle_data(&beta_from(0), &test_proof()) }; + build_and_send(&mut svm, &[&operator], &operator.pubkey(), &ix).assert_err(GachaError::NotTokenProgram); +} + +#[test] +fn rejects_pull_from_other_pool() { + let (mut svm, admin_a) = setup(); + let admin_b = funded_keypair(&mut svm); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin_a, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + init_pool(&mut svm, &admin_b, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull_a, _) = buy_pull(&mut svm, &admin_a.pubkey(), &buyer); + result.assert_ok(); + + settle_and_distribute( + &mut svm, + &admin_b.pubkey(), + &operator, + &pull_a, + &buyer.pubkey(), + &beta_from(0), + &test_proof(), + ) + .assert_err(GachaError::PoolMismatch); +} + +#[test] +fn settle_after_refund_rejected() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let requested_slot = read_pull(&svm, &pull).requested_slot; + svm.warp_to_slot(requested_slot + SETTLE_DEADLINE + 2); + refund_pull(&mut svm, &admin.pubkey(), &buyer, &pull).assert_ok(); + + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta_from(0), &test_proof()) + .assert_err(GachaError::NotProgramOwned); +} + +#[test] +fn each_tier_is_reachable() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + let weights = [70u32, 25, 5]; + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &weights).assert_ok(); + + for (target, expected_rarity) in [(0u128, "common"), (70, "uncommon"), (95, "rare")] { + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + settle_and_distribute( + &mut svm, + &admin.pubkey(), + &operator, + &pull, + &buyer.pubkey(), + &beta_from(target), + &test_proof(), + ) + .assert_ok(); + + let (mint, _) = client::PrizeMint::find_pda(&pull); + let mint_account = svm.get_account(&mint).expect("mint exists"); + let mint_state = StateWithExtensions::::unpack(&mint_account.data).expect("valid mint"); + let metadata = mint_state.get_variable_len_extension::().expect("token metadata"); + let rarity = metadata.additional_metadata.iter().find(|(k, _)| k == "rarity").map(|(_, v)| v.clone()); + assert_eq!(rarity.as_deref(), Some(expected_rarity)); + } +} + +#[test] +fn operator_pays_mint_rent_and_pull_never_stores_beta() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + let pull_account = svm.get_account(&pull).expect("pull exists"); + assert_eq!(pull_account.data.len(), Pull::LEN); + + let operator_before = svm.get_balance(&operator.pubkey()).unwrap(); + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta_from(0), &test_proof()) + .assert_ok(); + let operator_after = svm.get_balance(&operator.pubkey()).unwrap(); + + let (mint, _) = client::PrizeMint::find_pda(&pull); + let mint_rent = svm.get_account(&mint).expect("mint exists").lamports; + let ata_rent = svm.get_account(&get_ata(&buyer.pubkey(), &mint)).expect("ata exists").lamports; + assert_eq!(operator_before - operator_after, mint_rent + ata_rent + TX_FEE); +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/test_withdraw_fees.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_withdraw_fees.rs new file mode 100644 index 000000000..0a79d6c01 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/test_withdraw_fees.rs @@ -0,0 +1,55 @@ +use solana_signer::Signer; + +use crate::{ + tests::{asserts::TransactionResultExt, utils::*}, + GachaError, +}; + +#[test] +fn non_admin_rejected() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let impostor = funded_keypair(&mut svm); + withdraw_fees(&mut svm, &admin.pubkey(), &impostor, 1).assert_err(GachaError::Unauthorized); +} + +#[test] +fn zero_amount_rejected() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + withdraw_fees(&mut svm, &admin.pubkey(), &admin, 0).assert_err(GachaError::InsufficientVaultFunds); +} + +#[test] +fn withdraw_respects_pending_liability() { + let (mut svm, admin) = setup(); + let operator = funded_keypair(&mut svm); + let buyer = funded_keypair(&mut svm); + init_pool(&mut svm, &admin, &operator.pubkey(), ENTRY_FEE, &[100]).assert_ok(); + + let (result, pull, _) = buy_pull(&mut svm, &admin.pubkey(), &buyer); + result.assert_ok(); + + let vault_rent_floor = svm.minimum_balance_for_rent_exemption(0); + let available = vault_balance(&svm, &admin.pubkey()) - vault_rent_floor; + assert_eq!(available, ENTRY_FEE); + + withdraw_fees(&mut svm, &admin.pubkey(), &admin, available).assert_err(GachaError::InsufficientVaultFunds); + + settle_and_distribute(&mut svm, &admin.pubkey(), &operator, &pull, &buyer.pubkey(), &beta_from(0), &[0u8; 80]) + .assert_ok(); + assert_eq!(read_pool(&svm, &admin.pubkey()).pending_pulls, 0); + + let admin_before = svm.get_balance(&admin.pubkey()).unwrap(); + let vault_before = vault_balance(&svm, &admin.pubkey()); + withdraw_fees(&mut svm, &admin.pubkey(), &admin, available).assert_ok(); + + let admin_after = svm.get_balance(&admin.pubkey()).unwrap(); + assert_eq!(admin_after - admin_before, ENTRY_FEE - TX_FEE); + assert_eq!(vault_before - vault_balance(&svm, &admin.pubkey()), ENTRY_FEE); + assert_eq!(vault_balance(&svm, &admin.pubkey()), vault_rent_floor); +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/asserts.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/asserts.rs new file mode 100644 index 000000000..358372084 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/asserts.rs @@ -0,0 +1,52 @@ +use std::string::String; + +use litesvm::types::{FailedTransactionMetadata, TransactionMetadata, TransactionResult}; +use solana_instruction::error::InstructionError; +use solana_transaction_error::TransactionError; + +use crate::errors::GachaError; + +pub trait TransactionResultExt { + /// Assert the transaction succeeded and return its metadata. + fn assert_ok(self) -> TransactionMetadata; + + /// Assert the transaction failed with the expected program error. + fn assert_err(self, expected: GachaError); +} + +impl TransactionResultExt for TransactionResult { + fn assert_ok(self) -> TransactionMetadata { + match self { + Ok(meta) => meta, + Err(failed_tx) => panic!( + "Expected transaction to succeed, but got: {}\nLogs:\n{}", + format_error(&failed_tx), + failed_tx.meta.logs.join("\n") + ), + } + } + + fn assert_err(self, expected: GachaError) { + match self { + Ok(_) => panic!("Expected transaction to fail with {:?}", expected), + Err(failed_tx) => { + let expected_err = TransactionError::InstructionError(0, InstructionError::Custom(expected as u32)); + if failed_tx.err != expected_err { + panic!( + "Expected {:?}, got: {}\nLogs:\n{}", + expected, + format_error(&failed_tx), + failed_tx.meta.logs.join("\n") + ); + } + } + } + } +} + +fn format_error(failed_tx: &FailedTransactionMetadata) -> String { + match &failed_tx.err { + TransactionError::InstructionError(_, InstructionError::Custom(code)) => format!("Custom error code: {}", code), + other => format!("{:?}", other), + } +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/constants.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/constants.rs new file mode 100644 index 000000000..db4b211d7 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/constants.rs @@ -0,0 +1,12 @@ +//! Program IDs the tests build instruction metas from, each taken from the crate +//! that owns it. + +use solana_address::Address; + +/// The gacha-simple program, as the generated client declares it — so a +/// program-ID mismatch between the program and its client fails the whole suite. +pub const PROGRAM_ID: Address = gacha_client::GACHA_SIMPLE_ID; + +pub const SYSTEM_PROGRAM_ID: Address = pinocchio_system::ID; +pub const TOKEN_2022_ID: Address = pinocchio_token_2022::ID; +pub const ATA_PROGRAM_ID: Address = pinocchio_associated_token_account::ID; diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/cu_tracker.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/cu_tracker.rs new file mode 100644 index 000000000..06311a448 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/cu_tracker.rs @@ -0,0 +1,36 @@ +//! Opt-in compute-unit tracking. Enable with `CU_REPORT=1 cargo test`; the minimum +//! CU observed per instruction is written to `cu_report.md`. Off by default (and in +//! CI), so it adds no overhead to normal runs. + +use std::{ + collections::BTreeMap, + fs, + sync::{Mutex, OnceLock}, +}; + +fn enabled() -> bool { + static ENABLED: OnceLock = OnceLock::new(); + *ENABLED.get_or_init(|| std::env::var("CU_REPORT").is_ok()) +} + +fn tracker() -> &'static Mutex> { + static TRACKER: OnceLock>> = OnceLock::new(); + TRACKER.get_or_init(|| Mutex::new(BTreeMap::new())) +} + +/// Records the minimum compute units seen for an instruction. +pub fn record_cu(instruction: &str, cus: u64) { + if !enabled() { + return; + } + let mut map = tracker().lock().unwrap(); + let entry = map.entry(instruction.to_string()).or_insert(u64::MAX); + if cus < *entry { + *entry = cus; + } + let mut out = String::from("# Compute Unit Report\n\n| Instruction | Min CU |\n| --- | --- |\n"); + for (name, cu) in map.iter() { + out.push_str(&format!("| {name} | {cu} |\n")); + } + let _ = fs::write("cu_report.md", out); +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/idl.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/idl.rs new file mode 100644 index 000000000..bfe243d79 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/idl.rs @@ -0,0 +1,40 @@ +//! Reads the generated IDL so tests can assert on-chain account requirements +//! (writable/signer flags) match what the program enforces. + +const IDL: &str = include_str!("../../../../idl/gacha_simple.json"); + +pub struct IdlAccount { + pub index: usize, + pub name: String, + pub is_writable: bool, + pub is_signer: bool, +} + +/// The ordered account metas the IDL declares for an instruction. +/// +/// `test_account_meta` drives its cases off this: for each account the IDL marks +/// writable, it rebuilds the instruction with that account demoted to read-only +/// and asserts the program rejects it. A flag that drifts out of sync between the +/// IDL and the program's validation therefore fails a test rather than shipping +/// as a client that silently under-declares its writes. +pub fn instruction_accounts(instruction: &str) -> Vec { + let value: serde_json::Value = serde_json::from_str(IDL).expect("valid IDL json"); + let program = value.get("program").unwrap_or(&value); + let instructions = program["instructions"].as_array().expect("instructions array"); + let ix = instructions + .iter() + .find(|i| i["name"].as_str() == Some(instruction)) + .unwrap_or_else(|| panic!("instruction {instruction} not in IDL")); + ix["accounts"] + .as_array() + .expect("accounts array") + .iter() + .enumerate() + .map(|(index, a)| IdlAccount { + index, + name: a["name"].as_str().unwrap_or_default().to_string(), + is_writable: a.get("isWritable").and_then(|v| v.as_bool()).unwrap_or(false), + is_signer: a.get("isSigner").and_then(|v| v.as_bool()).unwrap_or(false), + }) + .collect() +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/mod.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/mod.rs new file mode 100644 index 000000000..9bc077f35 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/mod.rs @@ -0,0 +1,8 @@ +pub mod asserts; +pub mod constants; +pub mod cu_tracker; +pub mod idl; +pub mod pda; +pub mod test_helpers; + +pub use test_helpers::*; diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/pda.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/pda.rs new file mode 100644 index 000000000..492e5c567 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/pda.rs @@ -0,0 +1,14 @@ +//! Address derivations the generated client does not cover. +//! +//! Pool, vault, pull, prize-mint, and event-authority PDAs are derived through +//! `gacha_client`'s `find_pda` helpers, so the tests exercise the seeds the IDL +//! declares rather than a second copy of them. + +use solana_address::Address; + +use crate::tests::constants::{ATA_PROGRAM_ID, TOKEN_2022_ID}; + +/// Derives the wallet's Token-2022 associated token account for a mint. +pub fn get_ata(wallet: &Address, mint: &Address) -> Address { + Address::find_program_address(&[wallet.as_ref(), TOKEN_2022_ID.as_ref(), mint.as_ref()], &ATA_PROGRAM_ID).0 +} diff --git a/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/test_helpers.rs b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/test_helpers.rs new file mode 100644 index 000000000..0859d8bb3 --- /dev/null +++ b/games/gacha/pinocchio-simple/tests/integration-tests/src/utils/test_helpers.rs @@ -0,0 +1,324 @@ +use litesvm::{types::TransactionResult, LiteSVM}; +use solana_address::Address; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::Keypair; +use solana_message::Message; +use solana_native_token::LAMPORTS_PER_SOL; +use solana_signer::Signer; +use solana_transaction::Transaction; + +use crate::{ + client, + event_engine::event_authority_pda, + tests::{ + constants::{ATA_PROGRAM_ID, PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_2022_ID}, + pda::get_ata, + }, + utils::cu_tracker::record_cu, + GachaInstruction, Pool, Pull, +}; + +/// Default entry fee used by the helpers (0.1 SOL). +pub const ENTRY_FEE: u64 = 100_000_000; +/// Default settle deadline used by the helpers, in slots. +pub const SETTLE_DEADLINE: u64 = 100; + +pub fn setup() -> (LiteSVM, Keypair) { + let mut litesvm = LiteSVM::new(); + litesvm.warp_to_slot(1); + + let so_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/deploy/gacha_simple_program.so"); + litesvm.add_program_from_file(PROGRAM_ID.to_bytes(), so_path).unwrap(); + + let admin = Keypair::new(); + litesvm.airdrop(&admin.pubkey(), LAMPORTS_PER_SOL * 100).unwrap(); + + (litesvm, admin) +} + +pub fn funded_keypair(litesvm: &mut LiteSVM) -> Keypair { + let kp = Keypair::new(); + litesvm.airdrop(&kp.pubkey(), LAMPORTS_PER_SOL * 100).unwrap(); + kp +} + +/// LiteSVM's default fee for a single-signature transaction. +pub const TX_FEE: u64 = 5_000; + +#[allow(clippy::result_large_err)] +pub fn build_and_send( + litesvm: &mut LiteSVM, + signers: &[&Keypair], + payer: &Address, + ix: &Instruction, +) -> TransactionResult { + let tx = Transaction::new(signers, Message::new(std::slice::from_ref(ix), Some(payer)), litesvm.latest_blockhash()); + let result = litesvm.send_transaction(tx); + if let Ok(meta) = &result { + if let Ok(parsed) = GachaInstruction::from_bytes(&ix.data) { + record_cu(&parsed.to_string(), meta.compute_units_consumed); + } + } + litesvm.expire_blockhash(); + result +} + +/// Serializes `InitPool` instruction data (weights zero-padded to 8 tiers). +pub fn init_pool_data(operator: &Address, entry_fee: u64, settle_deadline_slots: u64, weights: &[u32]) -> Vec { + let mut data = vec![0u8]; + data.extend_from_slice(operator.as_ref()); + data.extend_from_slice(&entry_fee.to_le_bytes()); + data.extend_from_slice(&settle_deadline_slots.to_le_bytes()); + data.push(weights.len() as u8); + let mut w = [0u32; 8]; + w[..weights.len()].copy_from_slice(weights); + for value in w { + data.extend_from_slice(&value.to_le_bytes()); + } + data +} + +pub fn init_pool_metas(admin: &Address) -> Vec { + let (pool, _) = client::Pool::find_pda(admin); + let (vault, _) = client::Vault::find_pda(admin); + vec![ + AccountMeta::new(*admin, true), + AccountMeta::new(pool, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), + AccountMeta::new_readonly(event_authority_pda::ID, false), + AccountMeta::new_readonly(PROGRAM_ID, false), + ] +} + +#[allow(clippy::result_large_err)] +pub fn init_pool( + litesvm: &mut LiteSVM, + admin: &Keypair, + operator: &Address, + entry_fee: u64, + weights: &[u32], +) -> TransactionResult { + init_pool_with_deadline(litesvm, admin, operator, entry_fee, SETTLE_DEADLINE, weights) +} + +#[allow(clippy::result_large_err)] +pub fn init_pool_with_deadline( + litesvm: &mut LiteSVM, + admin: &Keypair, + operator: &Address, + entry_fee: u64, + settle_deadline_slots: u64, + weights: &[u32], +) -> TransactionResult { + let data = init_pool_data(operator, entry_fee, settle_deadline_slots, weights); + let ix = Instruction { program_id: PROGRAM_ID, accounts: init_pool_metas(&admin.pubkey()), data }; + build_and_send(litesvm, &[admin], &admin.pubkey(), &ix) +} + +/// 32 bytes of fresh test entropy for a buyer's client seed. +pub fn random_seed() -> [u8; 32] { + Keypair::new().pubkey().to_bytes() +} + +pub fn buy_pull_metas(admin: &Address, buyer: &Address, index: u64) -> Vec { + let (pool, _) = client::Pool::find_pda(admin); + let (vault, _) = client::Vault::find_pda(admin); + let (pull, _) = client::Pull::find_pda(&pool, buyer, index); + vec![ + AccountMeta::new(*buyer, true), + AccountMeta::new(pool, false), + AccountMeta::new(pull, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), + AccountMeta::new_readonly(event_authority_pda::ID, false), + AccountMeta::new_readonly(PROGRAM_ID, false), + ] +} + +/// Serializes `BuyPull` instruction data. +pub fn buy_pull_data(client_seed: &[u8; 32]) -> Vec { + let mut data = vec![1u8]; + data.extend_from_slice(client_seed); + data +} + +#[allow(clippy::result_large_err)] +pub fn buy_pull_with_seed( + litesvm: &mut LiteSVM, + admin: &Address, + buyer: &Keypair, + client_seed: &[u8; 32], +) -> (TransactionResult, Address) { + let (pool, _) = client::Pool::find_pda(admin); + let index = read_pool(litesvm, admin).pulls_count; + let (pull, _) = client::Pull::find_pda(&pool, &buyer.pubkey(), index); + + let accounts = buy_pull_metas(admin, &buyer.pubkey(), index); + let ix = Instruction { program_id: PROGRAM_ID, accounts, data: buy_pull_data(client_seed) }; + (build_and_send(litesvm, &[buyer], &buyer.pubkey(), &ix), pull) +} + +/// Buys a pull with a random client seed, returning the seed for alpha checks. +#[allow(clippy::result_large_err)] +pub fn buy_pull(litesvm: &mut LiteSVM, admin: &Address, buyer: &Keypair) -> (TransactionResult, Address, [u8; 32]) { + let client_seed = random_seed(); + let (result, pull) = buy_pull_with_seed(litesvm, admin, buyer, &client_seed); + (result, pull, client_seed) +} + +/// Serializes `SettleAndDistribute` instruction data. The program never verifies +/// the ECVRF proof on-chain, so tests may pass arbitrary bytes. +pub fn settle_data(beta: &[u8; 64], proof: &[u8; 80]) -> Vec { + let mut data = vec![2u8]; + data.extend_from_slice(proof); + data.extend_from_slice(beta); + data +} + +pub fn settle_metas(admin: &Address, operator: &Address, pull: &Address, buyer: &Address) -> Vec { + let (pool, _) = client::Pool::find_pda(admin); + let (mint, _) = client::PrizeMint::find_pda(pull); + let buyer_ata = get_ata(buyer, &mint); + vec![ + AccountMeta::new(*operator, true), + AccountMeta::new(pool, false), + AccountMeta::new(*pull, false), + AccountMeta::new(*buyer, false), + AccountMeta::new(mint, false), + AccountMeta::new(buyer_ata, false), + AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), + AccountMeta::new_readonly(TOKEN_2022_ID, false), + AccountMeta::new_readonly(ATA_PROGRAM_ID, false), + AccountMeta::new_readonly(event_authority_pda::ID, false), + AccountMeta::new_readonly(PROGRAM_ID, false), + ] +} + +#[allow(clippy::result_large_err)] +pub fn settle_and_distribute( + litesvm: &mut LiteSVM, + admin: &Address, + operator: &Keypair, + pull: &Address, + buyer: &Address, + beta: &[u8; 64], + proof: &[u8; 80], +) -> TransactionResult { + let accounts = settle_metas(admin, &operator.pubkey(), pull, buyer); + let ix = Instruction { program_id: PROGRAM_ID, accounts, data: settle_data(beta, proof) }; + build_and_send(litesvm, &[operator], &operator.pubkey(), &ix) +} + +pub fn refund_pull_metas(admin: &Address, buyer: &Address, pull: &Address) -> Vec { + let (pool, _) = client::Pool::find_pda(admin); + let (vault, _) = client::Vault::find_pda(admin); + vec![ + AccountMeta::new(*buyer, true), + AccountMeta::new(pool, false), + AccountMeta::new(*pull, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(event_authority_pda::ID, false), + AccountMeta::new_readonly(PROGRAM_ID, false), + ] +} + +#[allow(clippy::result_large_err)] +pub fn refund_pull(litesvm: &mut LiteSVM, admin: &Address, buyer: &Keypair, pull: &Address) -> TransactionResult { + let accounts = refund_pull_metas(admin, &buyer.pubkey(), pull); + let ix = Instruction { program_id: PROGRAM_ID, accounts, data: vec![3u8] }; + build_and_send(litesvm, &[buyer], &buyer.pubkey(), &ix) +} + +pub fn withdraw_fees_metas(admin: &Address, signer: &Address) -> Vec { + let (pool, _) = client::Pool::find_pda(admin); + let (vault, _) = client::Vault::find_pda(admin); + vec![ + AccountMeta::new(*signer, true), + AccountMeta::new_readonly(pool, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(event_authority_pda::ID, false), + AccountMeta::new_readonly(PROGRAM_ID, false), + ] +} + +/// Serializes `WithdrawFees` instruction data. +pub fn withdraw_fees_data(amount: u64) -> Vec { + let mut data = vec![4u8]; + data.extend_from_slice(&amount.to_le_bytes()); + data +} + +#[allow(clippy::result_large_err)] +pub fn withdraw_fees(litesvm: &mut LiteSVM, admin: &Address, signer: &Keypair, amount: u64) -> TransactionResult { + let accounts = withdraw_fees_metas(admin, &signer.pubkey()); + let ix = Instruction { program_id: PROGRAM_ID, accounts, data: withdraw_fees_data(amount) }; + build_and_send(litesvm, &[signer], &signer.pubkey(), &ix) +} + +/// Snapshot of the pool fields tests assert on (copied out of the packed struct). +pub struct PoolView { + pub tier_count: u8, + pub admin: [u8; 32], + pub operator: [u8; 32], + pub entry_fee: u64, + pub pulls_count: u64, + pub pending_pulls: u64, + pub settle_deadline_slots: u64, + pub weights: [u32; 8], +} + +pub fn read_pool(litesvm: &LiteSVM, admin: &Address) -> PoolView { + let (pool, _) = client::Pool::find_pda(admin); + let account = litesvm.get_account(&pool).expect("pool exists"); + let p = Pool::load(&account.data).expect("valid pool"); + let admin_key = p.admin; + let operator = p.operator; + PoolView { + tier_count: p.tier_count, + admin: admin_key.to_bytes(), + operator: operator.to_bytes(), + entry_fee: p.entry_fee, + pulls_count: p.pulls_count, + pending_pulls: p.pending_pulls, + settle_deadline_slots: p.settle_deadline_slots, + weights: p.weights, + } +} + +/// Snapshot of the pull fields tests assert on. +pub struct PullView { + pub pool: [u8; 32], + pub buyer: [u8; 32], + pub index: u64, + pub client_seed: [u8; 32], + pub alpha: [u8; 32], + pub requested_slot: u64, +} + +pub fn read_pull(litesvm: &LiteSVM, pull: &Address) -> PullView { + let account = litesvm.get_account(pull).expect("pull exists"); + let p = Pull::load(&account.data).expect("valid pull"); + let pool = p.pool; + let buyer = p.buyer; + PullView { + pool: pool.to_bytes(), + buyer: buyer.to_bytes(), + index: p.index, + client_seed: p.client_seed, + alpha: p.alpha, + requested_slot: p.requested_slot, + } +} + +pub fn vault_balance(litesvm: &LiteSVM, admin: &Address) -> u64 { + let (vault, _) = client::Vault::find_pda(admin); + litesvm.get_account(&vault).map(|a| a.lamports).unwrap_or(0) +} + +/// A `beta` whose first 16 bytes encode `value` as a little-endian u128. +pub fn beta_from(value: u128) -> [u8; 64] { + let mut beta = [0u8; 64]; + beta[..16].copy_from_slice(&value.to_le_bytes()); + beta +} diff --git a/games/gacha/pinocchio-simple/tsconfig.json b/games/gacha/pinocchio-simple/tsconfig.json new file mode 100644 index 000000000..a12667ce8 --- /dev/null +++ b/games/gacha/pinocchio-simple/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "noUncheckedIndexedAccess": true + } +}