Skip to content

GeckoVision/gecko-programs

Repository files navigation

gecko-programs

Repository for the Gecko programs — txs and all the needed on-chain operations.

DEVNET-ONLY. Never deployed to mainnet. No committed keypairs.

Program Framework Purpose Status
custody-probe Anchor 1.0 Privy custody-policy CPI-introspection probe deployed (devnet)
gecko-firewall Pinocchio Token-2022 transfer-hook + per-mint denylist (the ENFORCE tier) built, not deployed
gecko-receipt Anchor 1.0 verdict-hash PDA + oracle config (the LEDGER tier) built, not deployed

Toolchain note. custody-probe was bumped anchor-lang 0.32.1 → 1.0.2 so the whole Cargo workspace resolves on a single Solana 3.x crate tree (mixing an anchor 0.32 / solana-2.x program with an anchor 1.0 / solana-sdk-3 program in one workspace fails to compile — five8_core 0.1.2's std-only Error impl collides with the 3.x tree's core::error::Error bound). The bump does not change any declare_id! / program ID. Cargo.lock pins five8/five8_const 1.0.0 onto five8_core 1.0.0 (the >=0.1.1,<2 requirement otherwise lets Cargo pick the std-only 0.1.2).


custody-probe — custody-policy CPI introspection probe

Devnet / localnet only. Never deployed to mainnet. Moves only test lamports.

Why this program exists

Our Privy custody policy ALLOWs a small allowlist of program IDs (Jupiter / Kamino / Drift) and pins direct (top-level) system transfers to the user's own address — deny-by-default for everything else.

L2 proved Privy denies a top-level foreign transfer, but left one case open: a foreign transfer nested as a CPI inside an allowed program call. That open case is what gates our custody execute path.

custody-probe is the allowed-program stand-in for that test. The cross-repo test (in gecko-mcpay-api) allowlists this program's ID in a scoped Privy policy, then has the policy-scoped Privy wallet call probe_cpi_transfer, which CPIs a system_program::transfer to an arbitrary recipient.

The test then observes Privy's verdict:

Privy verdict Interpretation Consequence
DENY Privy inspected the CPI-nested transfer Safe — we can un-gate the custody execute
ALLOW Privy only saw the allowed top-level program ID Unsafe — keep execute gated

This is a verification harness, not production.

Program IDs

Program ID
Declared (declare_id! / IDL address) vDSFZB3vgEndA4qmtWfKq8bvBMQAeHauT9bd3uKDdHy
Devnet (deployed) vDSFZB3vgEndA4qmtWfKq8bvBMQAeHauT9bd3uKDdHy

Deployed to devnet (cluster https://api.devnet.solana.com). Confirmed on-chain; the IDL is also published on-chain (fetchable via anchor idl fetch vDSFZB3vgEndA4qmtWfKq8bvBMQAeHauT9bd3uKDdHy --provider.cluster devnet).

The program keypair lives at target/deploy/custody_probe-keypair.json and is gitignored (devnet/localnet only). To rebuild against the same ID, restore that keypair before anchor build; otherwise regenerate and update declare_id! + Anchor.toml.

Instruction: probe_cpi_transfer(amount: u64)

CPIs a System Program transfer of amount lamports from -> to. The transfer is nested one CPI level deep inside this (allowlisted) program — that nesting is the entire point of the probe.

Account layout (the order the cross-repo gecko-mcpay-api L2 smoke must build):

# Name Type Signer Writable Notes
0 from Signer yes yes The policy-scoped Privy custody wallet (pays the lamports)
1 to SystemAccount no yes Arbitrary recipient — intentionally NOT the policy-pinned address
2 system_program Program<System> no no 11111111111111111111111111111111

Arg: amount: u64 — lamports to move (use a few thousand; this is test value).

Artifacts for the cross-repo test

  • IDL: target/idl/custody_probe.json
  • TS types: target/types/custody_probe.ts
  • On-chain IDL: account 7dTQ8JW4nmsQQATNVYFm5GR5bpsfLSVzaWhnLaskWTut on devnet

The gecko-mcpay-api L2 smoke can build the instruction from the IDL or directly: take the discriminator from the IDL for probe_cpi_transfer, then the 3 accounts above in order plus a little-endian u64 amount.


Build & test

anchor build          # compiles custody_probe, regenerates IDL + types
anchor test           # spins up a local validator, runs tests/custody-probe.ts

tests/custody-probe.ts moves a few lamports from the funded payer to a fresh recipient and asserts the recipient balance increased — proving the nested CPI path works on a real validator.

Deploy (devnet only)

anchor deploy --provider.cluster devnet

Program deploy needs roughly 1.5–2.5 SOL on devnet for a ~177 KB program. If the deployer wallet is underfunded:

solana airdrop 2 --url devnet     # may rate-limit

Never deploy to mainnet. This program moves only test lamports.


gecko-firewall — Token-2022 transfer-hook + per-mint denylist (the ENFORCE tier)

Pinocchio, zero-copy, #![no_std]. DEVNET-ONLY. Not deployed.

VERIFY, not execute. This is the issuer's program enforcing the issuer's own denylist at transfer time. Gecko (the oracle) only writes the row (UpdateDenylist populates the denylist PDA with the verdict hash h + the denied wallets). The Token-2022 transfer hook is what reverts a disallowed transfer. Gecko never moves the funds and never blocks the transfer itself.

Program ID (devnet placeholder, frozen)

HqpkdrPNBMEfGAqniF64728PD9JwYbMqZdHcCqypksGP

Hardcoded in declare_id!. No keypair committed; a real deploy manages keys out of tree.

Instructions

Disc Instruction Signer
0x00 InitConfig{oracle} issuer admin
0x01 SetOracle{new_oracle} issuer admin
0x02 UpdateDenylist{mint, wallets[], h, freeze} Gecko oracle
0x03 InitExtraMetas{} mint authority (issuer)
0x04 CloseDenylist{mint} issuer admin
SPL 8-byte Execute (transfer hook) (Token-2022 CPI)

Execute 8-byte discriminator = sha256("spl-transfer-hook-interface:execute")[0..8] = [105,37,101,197,75,251,102,26]. The entrypoint branches on the 8-byte SPL disc FIRST (unambiguous), then falls through to the single-byte admin match.

Frozen interface (the §7.1 contract)

Item Value
FirewallConfig PDA seeds [b"fw_config"]
Denylist PDA seeds [b"denylist", mint]
ExtraAccountMetaList PDA seeds [b"extra-account-metas", mint]
Schema version 1 (== gecko:v1:)
Hash h 32 raw bytes; off-chain receipt_hash (never recomputed on-chain)
verdict_code 0=ok 1=caution 2=block 3=unknown

Account byte layouts in src/state.rs (FirewallConfig 73 bytes; DenylistHeader 73 bytes + 32×N packed entries, bounded ~300). The SPL TLV / transfer-hook-interface crates are NOT used (they depend on solana-program; this is a no_std crate) — the ExtraAccountMetaList bytes are hand-built to match the SPL on-disk format, verified byte-for-byte by a host unit test.

Execute CU (Mollusk hot-path benchmark)

Case CU
allow, denylist empty 2,506
allow, 50 entries (full no-hit scan) 5,906
deny, 50 entries (worst-case scan) 4,038
deny, 300 entries (scan ceiling) 12,538

.so ≈ 39 KB (per-package opt-level = "z" + strip in the workspace profile).


gecko-receipt — verdict-hash PDA + oracle config (the LEDGER tier)

Anchor 1.0. DEVNET-ONLY. Not deployed.

VERIFY, not execute. This is Gecko's own Anchor program: Gecko's oracle anchors the verdict hashes Gecko computed. It STORES, it does not enforce — no token movement. A durable content-addressed PDA successor to the v0 devnet SPL-memo anchor: same h, same gecko:v1: schema.

Program ID (devnet placeholder, frozen)

HFDEukquuWS79DRAUqBVMPEekHq2uYB8Rp5sfkP3dCYt

Hardcoded in declare_id!. No keypair committed.

Instructions

Instruction Signer Notes
init_config(oracle) admin one-time; create the singleton ReceiptConfig PDA
set_oracle(new_oracle) admin rotate the oracle key
set_paused(paused) admin kill-switch over anchor_receipt
anchor_receipt(h, verdict_code) oracle write the content-addressed Receipt PDA
close_receipt() admin revival-safe teardown (Anchor close)

Frozen interface (matches gecko-firewall verbatim)

Item Value
ReceiptConfig PDA seeds [b"receipt_config"]
Receipt PDA seeds [b"receipt", h] (h = 32 raw bytes, one seed slot)
Schema version 1 (== gecko:v1:)
Hash h 32 raw bytes; off-chain receipt_hash (never recomputed on-chain)
verdict_code 0=ok 1=caution 2=block 3=unknown (bucket, never a raw score)

Account byte layouts in gecko-receipt/src/state.rs (ReceiptConfig 94 data bytes; Receipt 104 data bytes; space = T::DISCRIMINATOR.len() + T::INIT_SPACE). Stored canonical bumps, checked ops, no unwrap/expect/init_if_needed, has_one/constraint validation, one #[error_code] enum. anchor_receipt write ≈ 9.7K CU.

Caveat — remaining integration proof. Both programs are devnet-only and not deployed. The Mollusk suites drive every instruction in-process (including Execute directly), but the full Token-2022-CPIs-drive-the-hook live round-trip on a fork is the one path Mollusk does not exercise — that live e2e is the remaining integration verification before any deploy.


Build + test

The two new programs build via cargo build-sbf per-crate (the Pinocchio firewall has no IDL, so it is not an anchor build target). custody-probe + gecko-receipt also build through anchor build for IDL/TS-type generation.

# --- whole workspace ---
cargo check --workspace            # custody-probe + gecko-firewall + gecko-receipt + test crates
cargo build-sbf --manifest-path programs/gecko-firewall/Cargo.toml
cargo build-sbf --manifest-path programs/gecko-receipt/Cargo.toml
cargo build-sbf --manifest-path programs/custody-probe/Cargo.toml

# --- IDL / TS types for the Anchor programs (frozen IDs => --ignore-keys) ---
# `--ignore-keys` honors declare_id! when no matching committed keypair exists.
anchor build --ignore-keys

# --- firewall tests (Mollusk loads the .so from SBF_OUT_DIR) ---
SBF_OUT_DIR="$(pwd)/target/deploy" cargo test -p gecko-firewall-tests   # 14 Mollusk
cargo test -p gecko-firewall                                            # 2 host (TLV byte-exactness)

# --- receipt tests ---
SBF_OUT_DIR="$(pwd)/target/deploy" cargo test -p gecko-receipt-tests    # 11 Mollusk
cargo test -p gecko-receipt --lib                                       # 5 host (frozen layouts)

# --- custody-probe localnet TS test (needs a validator) ---
anchor test

Workspace layout

gecko-programs/
├── Anchor.toml                 # toolchain 1.0.2; localnet + devnet entries for all 3 programs
├── Cargo.toml                  # workspace (explicit members; [workspace.package]; firewall size profile)
├── package.json / tsconfig.json
├── programs/
│   ├── custody-probe/          # Anchor 1.0 — probe_cpi_transfer (deployed devnet)
│   ├── gecko-firewall/         # Pinocchio  — Token-2022 hook + denylist
│   ├── gecko-receipt/          # Anchor 1.0 — verdict-hash PDA
│   └── tests/
│       ├── mollusk/            # gecko-firewall-tests (Mollusk 0.13 + solana-sdk 3)
│       └── receipt/            # gecko-receipt-tests  (Mollusk 0.13 + solana-sdk 3 + sha2)
├── tests/
│   └── custody-probe.ts        # localnet nested-CPI assertion
├── migrations/deploy.ts
└── target/                     # gitignored (idl/, types/, deploy/)

Toolchain

  • rustc/cargo 1.94.x, solana-cli/cargo-build-sbf 3.1.x (Agave), platform-tools v1.52, anchor-cli 1.0.x.
  • Pinocchio =0.8.4 (+ pinocchio-system/-pubkey/-log); Anchor 1.0.2; Mollusk 0.13 + solana-sdk 3 for the test harness.

About

Repository for the Gecko programs - Txs and all the needed operations

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors