Skip to content

Comments

add Genesis program to CLI#57

Merged
blockiosaurus merged 8 commits intomainfrom
claude/slack-add-genesis-cli-be54O
Feb 11, 2026
Merged

add Genesis program to CLI#57
blockiosaurus merged 8 commits intomainfrom
claude/slack-add-genesis-cli-be54O

Conversation

@blockiosaurus
Copy link
Contributor

  • Add @metaplex-foundation/genesis package dependency
  • Register Genesis plugin in Context.ts
  • Add genesis command group with the following commands:
    • genesis create: Create a new Genesis account for token launches
    • genesis fetch: Fetch Genesis account details
    • genesis finalize: Finalize a Genesis launch
    • genesis deposit: Deposit into launch pool buckets
    • genesis claim: Claim tokens from launch pools
    • genesis revoke: Revoke mint/freeze authorities

- Add @metaplex-foundation/genesis package dependency
- Register Genesis plugin in Context.ts
- Add genesis command group with the following commands:
  - genesis create: Create a new Genesis account for token launches
  - genesis fetch: Fetch Genesis account details
  - genesis finalize: Finalize a Genesis launch
  - genesis deposit: Deposit into launch pool buckets
  - genesis claim: Claim tokens from launch pools
  - genesis revoke: Revoke mint/freeze authorities
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Summary by CodeRabbit

  • New Features
    • Full Genesis CLI: create/manage Genesis accounts; add/fetch launch-pool, presale, and unlocked buckets; deposit, withdraw, claim (presale/unlocked), transition, finalize, and revoke flows with detailed summaries and explorer links.
  • Chores
    • Integrated Genesis runtime support and added the genesis package dependency.
  • Tests
    • Extensive automated integration/unit suites, helpers, and a manual end-to-end test script covering lifecycle, buckets, deposits, claims, withdrawals, finalization, and error cases.

Walkthrough

Adds Genesis support: new dependency and validator program entry, registers the genesis plugin in UMI context, implements a full Genesis CLI (create/fetch/bucket management/deposit/withdraw/claim/transition/finalize/revoke), adds lib mappings, and extensive tests/helpers/manual script.

Changes

Cohort / File(s) Summary
Config & Context
\.validator.cjs, package.json, src/lib/Context.ts
Add @metaplex-foundation/genesis dependency, append Genesis program entry to validator config, and register genesis() plugin in the UMI context.
Top-level CLI & core ops
src/commands/genesis/index.ts, src/commands/genesis/create.ts, src/commands/genesis/fetch.ts, src/commands/genesis/finalize.ts, src/commands/genesis/revoke.ts
Add Genesis top-level command and core commands for create, fetch, finalize (collect bucket accounts), and revoke authorities.
Transaction flows
src/commands/genesis/deposit.ts, src/commands/genesis/withdraw.ts, src/commands/genesis/claim.ts, src/commands/genesis/claim-unlocked.ts, src/commands/genesis/transition.ts
Implement deposit/withdraw/claim/transition CLI flows that validate on-chain state, build/send UMI transactions, and print PDAs/tx links.
Bucket management
src/commands/genesis/bucket/index.ts, src/commands/genesis/bucket/*
Add bucket subcommands (add-launch-pool, add-presale, add-unlocked, fetch): flag parsing, time-window construction/padding, end-behavior parsing, PDA derivation, and formatted output.
Presale group
src/commands/genesis/presale/index.ts, src/commands/genesis/presale/deposit.ts, src/commands/genesis/presale/claim.ts
Add presale subcommand group plus deposit and claim workflows for presale buckets.
Library additions
src/lib/genesis.ts
Add KEY_TYPES and FUNDING_MODES mappings used by CLI output and logic.
Tests & helpers
test/commands/genesis/*, test/commands/genesis/genesishelpers.ts, test/commands/genesis/genesis-manual-test.sh
Add unit/integration/manual tests and helpers covering creation, bucket lifecycle, deposits, withdrawals, claims, finalize/revoke/transition, negative cases, and CLI output parsing. Includes a manual integration bash script.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant CLI as CLI
participant Context as UMI Context
participant Plugin as Genesis Plugin
participant Program as Genesis Program
participant RPC as Blockchain/RPC

CLI->>Context: parse flags & build transaction
Context->>Plugin: call genesis() helpers / instruction builders
Plugin->>Program: assemble instruction + accounts
Program->>RPC: submit transaction
RPC-->>Program: confirmation / tx sig
Program-->>Plugin: return result
Plugin-->>Context: forward tx result
Context-->>CLI: print PDA, tx signature, explorer URL

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Feat/bubblegum create #55: Modifies src/lib/Context.ts to register additional UMI plugins—touches the same context/plugin registration area.
  • Config and Defaults #14: Changes createContext and context initialization logic—overlaps with Context.ts edits here.
  • Merge in development #3: Updates validator program registration and context plumbing—related to adding program entries and plugin setup.

Suggested reviewers

  • tonyboylehub
  • nhanphan
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'add Genesis program to CLI' clearly and concisely summarizes the main change in this PR, which is adding Genesis program support to the CLI with multiple new commands.
Description check ✅ Passed The description is related to the changeset, documenting the addition of the Genesis package dependency, Context registration, and the genesis command group with multiple subcommands that are all present in the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/slack-add-genesis-cli-be54O

Comment @coderabbitai help to get the list of available commands and usage tips.

claude and others added 3 commits January 21, 2026 18:47
- Add genesis bucket command group for managing allocation mechanisms
- Add genesis bucket add-launch-pool command to create launch pool buckets
  - Supports configuring token allocation
  - Supports deposit and claim time conditions (Unix timestamps)
  - Auto-increments bucket index
- Add genesis bucket fetch command to view bucket details
  - Displays allocation, deposits, claims, schedule, and fees
@MarkSackerberg MarkSackerberg marked this pull request as ready for review February 7, 2026 21:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🤖 Fix all issues with AI agents
In `@src/commands/genesis/bucket/add-launch-pool.ts`:
- Around line 169-181: The parsed end behavior currently converts
percentageBpsStr directly to Number without validation, which can produce NaN or
out-of-range values; update the mapping in endBehaviors (the flags.endBehavior
parsing block) to parse the percentage with parseInt/Number and validate that
the result is a finite integer (Number.isInteger), not NaN, and within
acceptable basis-point range (e.g. 0 <= percentageBps <= 10000); if validation
fails, throw a clear Error mentioning the specific behavior string and expected
range; keep returning the object with __kind: 'SendQuoteTokenPercentage',
processed: false, percentageBps: validated integer, padding, and
destinationBucket: publicKey(destinationBucketAddr).

In `@src/commands/genesis/bucket/add-presale.ts`:
- Around line 102-103: Do not silently default bucketIndex to 0; instead require
the caller to pass flags.bucketIndex and fail fast if it's missing to avoid
accidental overwrites: remove the fallback assignment "const bucketIndex =
flags.bucketIndex ?? 0", validate that flags.bucketIndex is defined (and is a
non-negative integer) before proceeding, and surface a clear error message
instructing the user to provide --bucketIndex when calling the add-presale
command (reference flags.bucketIndex and any code paths that use bucketIndex to
fetch or write buckets).
- Around line 71-78: The flag descriptions for minimumDeposit and depositLimit
are misleadingly labeled "in base units"; update the Flags.string definitions
for minimumDeposit and depositLimit to state "in quote token units" (or similar
explicit wording like "in quote token base units") so they reflect presale
mechanics where deposits use the quote token (see the minimumDeposit and
depositLimit flag declarations in add-presale.ts). Ensure both descriptions use
identical phrasing and keep them consistent with any other presale-related flag
docs.

In `@src/commands/genesis/bucket/add-unlocked.ts`:
- Around line 57-61: The flag description for bucketIndex is incorrect: it
claims "auto-increment" but the code defaults to 0. Either update the
bucketIndex Flags.integer description to state the actual default (e.g.,
"default: 0") or implement the auto-increment behavior by computing the next
genesis bucket index and using that value as the flag's default/initial value.
Locate the bucketIndex flag definition in add-unlocked.ts (the bucketIndex:
Flags.integer({...}) entry) and either change the description string to reflect
"default: 0" or add logic in the command initialization (e.g., in the
AddUnlocked command's initializer/run) to compute and apply the auto-incremented
index when no flag is provided.

In `@src/commands/genesis/bucket/fetch.ts`:
- Line 58: The class BucketFetch is a read-only command but currently extends
TransactionCommand; change its declaration to extend BaseCommand (i.e., class
BucketFetch extends BaseCommand) and update the import to pull BaseCommand
instead of TransactionCommand; also remove or adapt any transaction-specific
generics/props or commitment-related logic tied to TransactionCommand in the
class (e.g., remove commitment flags or TransactionCommand-specific type
parameters) so the command uses the standard BaseCommand global flags and
behaviors.

In `@src/commands/genesis/create.ts`:
- Around line 117-118: Validate flags.totalSupply before calling BigInt to avoid
a cryptic SyntaxError: ensure flags.totalSupply is a non-negative integer string
(e.g., using a regex like /^[0-9]+$/ or Number.isInteger check on parsed numeric
input) and reject decimal or non-numeric values; if invalid, throw or return a
user-friendly error explaining the expected format and reference the offending
value. Replace the direct BigInt(flags.totalSupply) usage in create.ts (where
totalSupply is assigned) with this validation + conversion path, or wrap the
BigInt conversion in a try/catch that maps SyntaxError to a clearer error
message mentioning flags.totalSupply and the requirement for an integer string.

In `@src/commands/genesis/deposit.ts`:
- Around line 87-88: The parsed deposit amount assigned to the variable amount
(const amount = BigInt(flags.amount)) must be validated to be strictly positive;
immediately after parsing amount, add a check that amount > 0n and, if not,
throw a clear error or exit with a descriptive message (e.g., "Deposit amount
must be > 0") so the deposit command (the handler that processes flags.amount /
variable amount) fails fast on zero or negative input.

In `@src/commands/genesis/fetch.ts`:
- Line 50: GenesisFetch currently extends TransactionCommand even though it's
read-only; change its base class to BaseCommand so it doesn't require
transaction setup. Update the class declaration from "export default class
GenesisFetch extends TransactionCommand<typeof GenesisFetch>" to extend
BaseCommand (adjust any generic/type parameters to match BaseCommand's
signature), remove or refactor any transaction-specific imports/usages tied to
TransactionCommand, and ensure any global flags or run/execute method signatures
conform to BaseCommand's expectations.
- Around line 11-48: Extract the duplicated mappings KEY_TYPES and FUNDING_MODES
into a single shared module (e.g., add an exported constants file under
src/lib/) and update both occurrences (the KEY_TYPES/FUNDING_MODES definitions
in fetch.ts and the duplicate in bucket/fetch.ts) to import those constants; if
the library `@metaplex-foundation/genesis` already exports matching enums, import
them directly instead of re-defining, but otherwise export the same
Record<number,string> from the new shared file and replace the inline objects
with imports in both files.

In `@src/commands/genesis/finalize.ts`:
- Around line 72-87: The nested loops currently call
this.context.umi.rpc.getAccount sequentially (for each i and each finder),
causing genesisAccount.bucketCount × pdaFinders.length RPC round-trips; change
the discovery in the finalize flow to build all PDA lookups (using
finder(this.context.umi, { genesisAccount: genesisAddress, bucketIndex: i }) to
get the pda) into a promise array and use Promise.all (or a concurrency-limited
batcher) to run getAccount calls in parallel, then push existing accounts into
bucketAccounts; ensure you reference pdaFinders, finder,
genesisAccount.bucketCount, this.context.umi.rpc.getAccount and bucketAccounts
when making the change.

In `@src/commands/genesis/presale/deposit.ts`:
- Around line 84-86: The code currently parses the deposit amount into const
amount = BigInt(flags.amount) but doesn't validate it; update the deposit
parsing logic (the code that creates amount from flags.amount in deposit.ts) to
validate that amount > 0n and fail fast if not — return/throw a clear error (or
call the existing CLI error/exit path) when amount <= 0n, with a message
referencing the invalid flags.amount value so users know they supplied zero or a
negative amount.

In `@src/commands/genesis/withdraw.ts`:
- Around line 100-101: The amount parsing using BigInt(flags.amount) can throw
on invalid input; update the code around the amount variable in withdraw.ts to
validate and handle parsing errors by wrapping the BigInt conversion in a
try-catch (or pre-validate the string with a regex) so you return a
user-friendly error when flags.amount is empty, non-integer, or fractional, and
also enforce any business rules (e.g., amount > 0) before proceeding; reference
the amount variable and flags.amount in your changes so the rest of the withdraw
logic uses the validated BigInt value.
- Around line 87-93: findLaunchPoolDepositV2Pda returns a tuple [PublicKey,
number] but the code assigns it to depositPda directly; change to array
destructuring (e.g., [depositPda, depositBump] =
findLaunchPoolDepositV2Pda(...)) so depositPda is a PublicKey before calling
safeFetchLaunchPoolDepositV2(this.context.umi, depositPda) and before any
logging; update any subsequent uses that expect the bump value to reference
depositBump.

In `@test/commands/genesis/genesis-manual-test.sh`:
- Around line 86-101: The test always reports success because both the if and
else branches call pass; update the two enforcement checks so the else branches
report failure and stop the test: in the "minimumDeposit" check (the if invoking
$CLI genesis deposit "$GENESIS" --amount 50000000 --bucketIndex 0) replace the
else pass "Minimum deposit enforced (error thrown)" with a failing action (e.g.,
fail "Minimum deposit NOT enforced" and exit non-zero), and likewise in the
"depositLimit" check (the if invoking $CLI genesis deposit "$GENESIS" --amount
10000000001 --bucketIndex 0) replace the else pass "Deposit limit enforced
(error thrown)" with fail "Deposit limit NOT enforced" (and exit non-zero);
ensure you reuse the existing pass/fail helper functions and keep the grep
checks unchanged.
- Around line 26-28: The extract_address function uses grep -oP which requires
GNU grep; replace it with a portable pipeline using grep -E (or sed/awk) to
match the "<label>: <addr>" pattern and then strip the "<label>: " prefix; e.g.
in extract_address use grep -E -o "$2: [A-Za-z0-9]+" | sed -E "s/^$2: //" (or
awk -F": " '{print $2}' | head -1) so it no longer depends on PCRE and works on
macOS and minimal Linux.

In `@test/commands/genesis/genesis.create.test.ts`:
- Around line 7-13: The before hook destructures unused variables from the
runCli call (stdout, stderr, code) in the before(async () => { ... }) block;
remove the unnecessary destructuring or rename them with an underscore prefix
(e.g., _stdout, _stderr, _code) to silence lint warnings, or simply await
runCli(...) without destructuring to keep the telemetry call but avoid unused
variables—in other words update the before hook surrounding the runCli(...)
invocation to stop creating unused locals.

In `@test/commands/genesis/genesis.integration.test.ts`:
- Around line 1-4: The import list in the test file includes unused symbols
extractGenesisAddress and extractBucketAddress; remove these two from the import
statement (the line importing createGenesisAccount, addLaunchPoolBucket,
addUnlockedBucket, stripAnsi, extractGenesisAddress, extractBucketAddress) so
only used helpers remain, and run the linter/tests to ensure no other references
to extractGenesisAddress or extractBucketAddress exist in the file.

In `@test/commands/genesis/genesis.presale.test.ts`:
- Around line 16-29: The before hook currently awaits runCli(...) calls but
ignores their return values; capture each result (e.g., assign the airdrop and
wrap returns to variables) and add a simple assertion or validation using the
test assertion utilities (e.g., expect/strictEqual) to ensure the commands
succeeded (or explicitly document that failures will propagate via runCli
rejection). Update the before block around the runCli calls (the two calls to
runCli in the before hook) to either assert the returned value is
truthy/successful or add an explicit comment explaining you rely on runCli's
reject behavior.

In `@test/commands/genesis/genesis.withdraw.test.ts`:
- Around line 6-8: Remove the unused variable declaration and any assignment for
baseMint in the test file (symbols: baseMint, genesisAddress, bucketAddress in
genesis.withdraw.test.ts); update the let/assignment block that currently
declares "let genesisAddress: string, let baseMint: string, let bucketAddress:
string" to only declare the variables actually used (e.g., genesisAddress and
bucketAddress) and remove any code that sets or references baseMint so the test
no longer holds an unused variable.

In `@test/commands/genesis/genesishelpers.ts`:
- Around line 4-5: The stripAnsi helper uses a too-narrow regex that only
matches simple SGR codes; update the stripAnsi function to use a more
comprehensive ANSI escape sequence pattern that covers multiple parameters and
other CSI/escape sequences (e.g., sequences like \x1b[1;31m and other CSI
commands), ensuring it removes full ANSI sequences rather than only
'\u001b\[\d+m'; replace the current regex in the stripAnsi arrow function with a
broader ANSI/C SI escape regex that matches ESC [ … final-byte and common
OSC/SS3 variants so all ANSI escapes are stripped.

Resolve pnpm-lock.yaml merge conflict. Fix genesis integration and
withdraw tests to match program requirements: allocate 100% of supply,
add unlocked bucket as graduation destination with endBehavior, and
finalize before depositing.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@test/commands/genesis/genesis.integration.test.ts`:
- Around line 18-33: The test uses a fixed 10s sleep in the before hook after
calling runCli("toolbox","sol","airdrop", ...) which is flaky and slow; replace
that sleep with an active confirmation/polling strategy: after calling runCli
for the airdrop, capture the returned tx signature or target pubkey and poll the
Solana RPC (e.g., via Connection.getBalance or Connection.confirmTransaction)
until the expected balance is observed or a timeout is reached; do the same for
the wrap step (runCli('toolbox','sol','wrap', '50')) by polling the wrapped-SOL
token account balance or transaction confirmation instead of setTimeout, and
factor the logic into a reusable helper like waitForBalance or
waitForConfirmation used by the before hook.
- Around line 81-99: The test "fetches the genesis account and verifies bucket
was added" contains a redundant assertion on the CLI exit code; since runCli
already rejects on non-zero exit or stderr, remove the expect(code).to.equal(0)
line from that test (referencing the runCli call and the it block) so the test
relies on runCli's built-in failure behavior and keeps the remaining assertions
on stdout/stderr.
- Around line 35-48: Remove the redundant regex assertion for genesisAddress in
the integration test: since createGenesisAccount already validates/throws on
invalid addresses, keep the baseMint check but delete the
expect(genesisAddress).to.match(...) assertion; update the test block around
createGenesisAccount, genesisAddress and baseMint so it only asserts baseMint
format and relies on createGenesisAccount to validate genesisAddress.

In `@test/commands/genesis/genesis.withdraw.test.ts`:
- Around line 17-30: The test's before hook uses a fixed 10s sleep (the
setTimeout call) after calling runCli([... "toolbox","sol","airdrop", ...])
which is flaky; replace this hard delay with a polling loop that checks for the
airdrop/wrap confirmation by repeatedly calling the same observable/check used
elsewhere (e.g., query balance via the RPC or inspect transaction status) until
the expected balance or confirmed signature appears or a short overall timeout
elapses; update the before hook surrounding the runCli invocations (the airdrop
call and the subsequent runCli 'toolbox sol wrap 50') to await that polling
helper instead of waiting 10s so the test proceeds as soon as the on-chain state
is ready and fails fast on timeout.
- Around line 77-87: The test asserts exit code unnecessarily because runCli
already rejects non-zero exits; remove the redundant assertion
`expect(code).to.equal(0)` from the `'finalizes the genesis account'` test
(located in genesis.withdraw.test.ts) and leave the existing stderr check via
`stripAnsi(stderr)` intact; update any other success-path tests in this file
using `runCli` similarly to avoid duplicate exit-code assertions.
- Around line 32-44: Remove the redundant assertion that validates
genesisAddress format in the test: locate the test case using
createGenesisAccount (function name) in the 'creates a genesis account for
withdraw workflow' it-block and delete the line that calls
expect(genesisAddress).to.match(...); rely on createGenesisAccount to throw on
invalid addresses and keep storing genesisAddress and baseMint from result as
before.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Fix all issues with AI agents
In `@src/commands/genesis/bucket/add-presale.ts`:
- Around line 102-103: The nullish-coalescing fallback on bucketIndex is dead
because flags.bucketIndex is required; update the declaration to remove the
unreachable fallback by assigning directly from flags.bucketIndex (i.e., replace
"const bucketIndex = flags.bucketIndex ?? 0" with "const bucketIndex =
flags.bucketIndex") and keep the variable name bucketIndex to avoid changing
downstream references in this file (ensure any type assumptions still hold).
- Around line 106-110: Validate the timestamp/numeric flag strings before
calling BigInt for depositStart, depositEnd, claimStart and claimEnd in
add-presale.ts: ensure each input matches an integer-only pattern (e.g.,
/^\d+$/), is within expected range/non-negative, and reject floats or
non-numeric strings with a clear error message (similar to create.ts's
totalSupply validation) before converting to BigInt; keep the existing default
for claimEnd but validate it the same way when present.

In `@src/commands/genesis/bucket/add-unlocked.ts`:
- Around line 88-94: Validate user inputs before calling BigInt to avoid
SyntaxError: check that flags.claimStart, flags.claimEnd (if provided), and
flags.allocation are non-empty strings of digits (e.g., /^\d+$/) or valid
numeric values, then convert to BigInt; if validation fails, throw or return a
clear user-facing error message indicating which flag is invalid. Update the
parsing logic around claimStart, claimEnd and allocation in add-unlocked.ts
(variables claimStart, claimEnd, allocation) to perform these checks and produce
friendly errors instead of relying on the outer try/catch.

In `@src/commands/genesis/deposit.ts`:
- Around line 114-119: The code assigns the entire tuple returned by
findLaunchPoolDepositV2Pda to depositPda instead of extracting the PublicKey;
change the assignment to destructure the first element (e.g., const [depositPda]
= findLaunchPoolDepositV2Pda(...)) so depositPda is the PublicKey used later
when logging or passing to other functions; update the occurrence in the deposit
command where depositPda is defined (compare to withdraw.ts which uses const
[depositPda]) to mirror that destructuring.

In `@test/commands/genesis/genesis-manual-test.sh`:
- Around line 35-39: The chained idiom using && and || with commands like the
airdrop and wrap calls (e.g., the "$CLI toolbox sol airdrop 100 \"$WALLET\"" and
"$CLI toolbox sol wrap 50" invocations) should be replaced with explicit
if-then-else conditionals to avoid the SC2015 hazard; wrap each command in an if
... then pass "Airdrop" (or "Wrap SOL") else fail "Airdrop"/fail "Wrap SOL" fi
block so you test the command's exit status directly and call the pass/fail
functions deterministically (references: pass, fail, and the CLI toolbox sol
airdrop/wrap command invocations).

In `@test/commands/genesis/genesis.create.test.ts`:
- Line 3: The import line in genesis.create.test.ts includes an unused symbol
extractBaseMint; remove extractBaseMint from the import list (leaving
createGenesisAccount, stripAnsi, extractGenesisAddress) or, if the test should
exercise extractBaseMint, add the appropriate assertions using extractBaseMint;
update the import in the file accordingly and run tests to ensure no
unused-import lint errors remain.
- Around line 70-91: The test "creates a genesis account with a custom URI"
constructs cliInput and reads cleanStdout but never asserts the URI; update the
test in genesis.create.test.ts (the it block for creating a genesis account) to
assert that cleanStdout contains the provided URI string (e.g.
expect(cleanStdout).to.contain('https://example.com/metadata.json')) so the
--uri flag is verified as processed; keep the existing assertions for code and
stderr and add this single additional expectation referencing cliInput and
cleanStdout.
- Around line 140-153: The test "fails when fetching a non-existent genesis
account" currently uses a weak assertion on the thrown error message; update the
catch assertion to expect a specific, meaningful message (e.g., assert that
(error as Error).message includes a substring like "not found", "Genesis account
not found", or the CLI's actual error text) so the test verifies the CLI returns
precise feedback; locate the test block and the runCli invocation in this case
and replace the .to.not.be.empty check with a .to.include/.to.equal assertion
against the expected error string.
- Around line 93-121: The create command currently prints flags.fundingMode raw
(producing "new-mint") instead of using the same mapping as fetch; update the
create command in src/commands/genesis/create.ts so it looks up
FUNDING_MODES[flags.fundingMode] (the same mapping used by the fetch handler)
when rendering the "Funding Mode" output, and then update the test in
test/commands/genesis/genesis.create.test.ts to expect the formatted value
("NewMint") instead of the raw flag string; ensure you reference the
FUNDING_MODES constant and the create command's output path that writes the
"Funding Mode" line.

In `@test/commands/genesis/genesis.integration.test.ts`:
- Around line 339-352: The test "fails to claim unlocked bucket before
finalization" currently only asserts the error message is non-empty; tighten it
to assert the specific finalization-related message thrown by the CLI when
calling runCli(['genesis','claim-unlocked', genesisAddress,
'--bucketIndex','0']). Locate this test in genesis.integration.test.ts and
replace the permissive expect((error as Error).message).to.not.be.empty with a
precise assertion (e.g., expect((error as Error).message).to.contain('finalize')
or the exact finalization error string emitted by the claim-unlocked command) so
the test verifies the pre-finalization failure reason from runCli.
- Around line 241-256: The test "it('fails to deposit into a non-existent
bucket')" uses a weak assertion that the error message is non-empty; update it
to assert the error message contains the specific "not found" text (matching the
fetch test) by checking (error as Error).message.includes('not found') after
calling runCli(['genesis','deposit', genesisAddress,
'--amount','1000000','--bucketIndex','99']); this ensures the deposit failure
for the non-existent bucket (bucketIndex 99) is validated against the expected
error string rather than just non-empty output.

In `@test/commands/genesis/genesis.presale.test.ts`:
- Around line 121-151: The test intends to assert claiming fails due to no
deposit but never finalizes the genesis, so the failure occurs for the wrong
reason; after creating the genesis with createGenesisAccount and after
addPresaleBucket, call the genesis finalization helper (e.g., finalizeGenesis or
the project’s finalize function that finalizes newGenesis.genesisAddress) before
invoking runCli(['genesis','presale','claim', ...]) so the claim attempt
proceeds past the finalization check and then fails due to the missing deposit.

In `@test/commands/genesis/genesishelpers.ts`:
- Around line 93-149: The addLaunchPoolBucket helper (function
addLaunchPoolBucket) needs a new optional options.bucketIndex (default 0) to
mirror the CLI's --bucketIndex flag; update the options type to include
bucketIndex?: number | string, default it to 0 when building CLI args, and
insert '--bucketIndex' followed by String(bucketIndex) into cliInput before
calling runCli; keep the existing behavior for allocation/deposit/claim fields
and the same assertions (expect code/stderr) so tests remain consistent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/commands/genesis/genesishelpers.ts`:
- Around line 4-5: The stripAnsi helper currently embeds a control character in
a regex literal and triggers Biome's noControlCharactersInRegex rule; fix by
constructing the escape character at runtime and using RegExp with that string
(e.g., build the ESC char via String.fromCharCode(0x1b) and concatenate with
'\\[[\\d;]*m' before passing to new RegExp with the 'g' flag) in the stripAnsi
function so you avoid literal control characters in the source; alternatively,
if the literal is intentional, add a biome-ignore
lint/suspicious/noControlCharactersInRegex comment immediately above the
stripAnsi declaration.

Comment on lines +4 to +5
// Helper to strip ANSI color codes
const stripAnsi = (str: string) => str.replace(/\u001b\[[\d;]*m/g, '')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "genesishelpers.ts" -type f

Repository: metaplex-foundation/cli

Length of output: 108


🏁 Script executed:

head -20 ./test/commands/genesis/genesishelpers.ts

Repository: metaplex-foundation/cli

Length of output: 612


🏁 Script executed:

find . -maxdepth 3 -name "biome.json" -o -name "biome.jsonc" -o -name ".biomerc*" | head -10

Repository: metaplex-foundation/cli

Length of output: 79


🏁 Script executed:

cat ./biome.json

Repository: metaplex-foundation/cli

Length of output: 1850


🌐 Web query:

Biome lint rule control characters regex literal suspicious noControlCharacters

💡 Result:

In Biome, the lint you’re hitting is lint/suspicious/noControlCharactersInRegex (rule name: noControlCharactersInRegex). It flags ASCII control characters (U+0000–U+001F) when they appear in regular expression literals (and also in RegExp(...) patterns), because they’re usually accidental/hidden characters. It specifically disallows: \x00\x1F, \u0000\u001F, \u{0}\u{1F}, and raw unescaped control chars in that range. Control escapes like \t and \n are explicitly allowed. [1]

Configure / control it in biome.json like this (set severity or turn it off):

{
  "linter": {
    "rules": {
      "suspicious": {
        "noControlCharactersInRegex": "error"
      }
    }
  }
}

(Use "off" to disable.) [1]

Sources: Biome rule docs. [1]

References
[1] https://biomejs.dev/linter/rules/no-control-characters-in-regex/


🏁 Script executed:

# Check if the current code actually fails Biome lint
# First, let's look for npm scripts that run biome
cat package.json | grep -A 10 '"scripts"' | head -15

Repository: metaplex-foundation/cli

Length of output: 571


🌐 Web query:

Biome noControlCharactersInRegex rule RegExp constructor workaround solution

💡 Result:

Biome’s lint/suspicious/noControlCharactersInRegex rejects regex patterns that contain ASCII control characters (U+0000–U+001F), including when they’re written as escapes like \x00\x1F, \u0000\u001F, or \u{0}\u{1F}—and it can also flag the same thing when you build the pattern via new RegExp("...") (because the resulting regex pattern still contains a control character). It explicitly allows some “control escapes” like \t and \n. [1]

Practical workarounds

  1. Prefer an allowed escape if one exists
    If the character you need has a standard escape that Biome allows (the docs mention \t, \n), use that instead of \x../\u..... [1]

  2. Build the pattern at runtime (avoids a literal control-char escape in source)

const c = String.fromCharCode(0x0c); // e.g. form feed
const re = new RegExp(c);

Because the control character isn’t present in a regex literal or as a \x.. / \u.... escape inside a regex pattern in your source, the rule typically won’t trigger. (You’re intentionally opting out of static checking for that piece.)

  1. Suppress the rule for that line / block / file
// biome-ignore lint/suspicious/noControlCharactersInRegex: needed for protocol-level control char
const re = new RegExp("\\x0C");

Biome suppression comments support single-line, range, and whole-file suppression (biome-ignore, biome-ignore-start/end, biome-ignore-all). [2]

  1. Disable or lower the rule in biome.json
{
  "linter": {
    "rules": {
      "suspicious": {
        "noControlCharactersInRegex": "off"
      }
    }
  }
}

(Or change severity instead of turning it off.) [1]

Sources: Biome rule docs for noControlCharactersInRegex (what it flags / what it allows / config key) [1], and Biome suppression syntax (how to biome-ignore a specific rule) [2].


🌐 Web query:

Biome noControlCharactersInRegex new RegExp constructor escape sequence still flagged

💡 Result:

Biome’s lint/suspicious/noControlCharactersInRegex flags control characters (ASCII 0–31) inside the regex pattern, including the escape forms \x00–\x1F, \u0000–\u001F, and \u{0}–\u{1F}. It does this for regex literals and new RegExp("..."). So even if you write new RegExp("\\x0C") (escaped for the JS string), the resulting regex pattern still contains the \x0C control-character escape, and Biome flags it (this exact example is shown as invalid in the rule docs). [1][2]

What to do:

  • If you meant to match the literal text \x0C in an input string, escape the backslash for the regex too:
    new RegExp("\\\\x0C") (or /\\x0C/).
  • If you truly need to match that control character, Biome considers it suspicious by design—use a targeted ignore or disable the rule for that line/file:
    // biome-ignore lint/suspicious/noControlCharactersInRegex: <reason> [1]

Sources: Biome rule documentation for noControlCharactersInRegex and its examples. [1][2]


The proposed RegExp constructor fix will not resolve this Biome lint error.

Biome's lint/suspicious/noControlCharactersInRegex rule flags control characters (including \u001b) in regex patterns regardless of whether they're in a regex literal or a RegExp constructor. Moving the pattern to new RegExp('\\u001b\\[[\\d;]*m', 'g') will still trigger the lint violation.

Instead, use one of these approaches:

  • Build the control character at runtime (preferred):

    const stripAnsi = (str: string) => str.replace(new RegExp(String.fromCharCode(0x1b) + '\\[[\\d;]*m'), '')
  • Suppress the rule (if the control character is intentional):

    // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape pattern is intentional
    const stripAnsi = (str: string) => str.replace(/\u001b\[[\d;]*m/g, '')
  • Disable the rule in biome.json (if acceptable for the project).

🧰 Tools
🪛 Biome (2.3.13)

[error] 5-5: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🤖 Prompt for AI Agents
In `@test/commands/genesis/genesishelpers.ts` around lines 4 - 5, The stripAnsi
helper currently embeds a control character in a regex literal and triggers
Biome's noControlCharactersInRegex rule; fix by constructing the escape
character at runtime and using RegExp with that string (e.g., build the ESC char
via String.fromCharCode(0x1b) and concatenate with '\\[[\\d;]*m' before passing
to new RegExp with the 'g' flag) in the stripAnsi function so you avoid literal
control characters in the source; alternatively, if the literal is intentional,
add a biome-ignore lint/suspicious/noControlCharactersInRegex comment
immediately above the stripAnsi declaration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/commands/genesis/transition.ts`:
- Around line 104-112: Add a fail-fast guard before building the transition
transaction (before the call to transitionV2) that checks there is at least one
configured end behavior / destination bucket (for example by asserting
remainingAccounts.length > 0 or genesisAccount.endBehaviors?.length > 0) and, if
none exist, emit a clear CLI error message and abort (e.g., spinner.fail or
throw new Error) instead of proceeding to call transitionV2; this ensures the
code (around transitionV2(this.context.umi, { genesisAccount, primaryBucketPda,
... }).addRemainingAccounts(remainingAccounts)) never runs when no end
behaviors/destination buckets are configured.

@MarkSackerberg
Copy link
Contributor

@tonyboylehub could you please review? It’s just an mvp, but would be great for the skill

@blockiosaurus blockiosaurus merged commit 964e836 into main Feb 11, 2026
4 checks passed
@blockiosaurus blockiosaurus deleted the claude/slack-add-genesis-cli-be54O branch February 11, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants