From 0015f8a7b37ee6e70f3770ba5e8c5564cd656db1 Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:57:23 +0200 Subject: [PATCH 1/5] Add genesis claim-creator-rewards command New `mplx genesis claim-creator-rewards` command that calls the Genesis API to claim accrued creator rewards across all bonding-curve and Raydium CPMM buckets where a wallet is the creator fee recipient. Pre-fetches buckets via GPA and prints pending amounts (formatted as SOL when quote is wSOL) before sending. The configured signer always pays fees so `--wallet ` works for claiming on behalf of another recipient. Bumps @metaplex-foundation/genesis to ^0.36.1 for the `claimCreatorRewards` SDK function. --- package.json | 2 +- pnpm-lock.yaml | 10 +- src/commands/genesis/claim-creator-rewards.ts | 257 ++++++++++++++++++ 3 files changed, 263 insertions(+), 6 deletions(-) create mode 100644 src/commands/genesis/claim-creator-rewards.ts diff --git a/package.json b/package.json index e3b3c407..033e47b0 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@ledgerhq/hw-transport": "6.31.4", "@ledgerhq/hw-transport-node-hid-singleton": "6.31.5", "@metaplex-foundation/digital-asset-standard-api": "^2.0.0", - "@metaplex-foundation/genesis": "^0.35.0", + "@metaplex-foundation/genesis": "^0.36.1", "@metaplex-foundation/mpl-agent-registry": "^0.2.5", "@metaplex-foundation/mpl-bubblegum": "^5.0.2", "@metaplex-foundation/mpl-core": "^1.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc9b7f4e..6d17ae94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^2.0.0 version: 2.0.0(@metaplex-foundation/umi@1.5.1) '@metaplex-foundation/genesis': - specifier: ^0.35.0 - version: 0.35.0(@metaplex-foundation/umi@1.5.1) + specifier: ^0.36.1 + version: 0.36.1(@metaplex-foundation/umi@1.5.1) '@metaplex-foundation/mpl-agent-registry': specifier: ^0.2.5 version: 0.2.5(@metaplex-foundation/umi@1.5.1)(@noble/hashes@1.8.0) @@ -909,8 +909,8 @@ packages: peerDependencies: '@metaplex-foundation/umi': '>= 0.8.2 <= 1' - '@metaplex-foundation/genesis@0.35.0': - resolution: {integrity: sha512-rWba2yLrvMk5EdO0UkEekp0mzPNPIkoxS8qJLbsuIEQKd7oxxtV2F0ilPfnFOUVQITb609pucmyXGoMk4HJUiw==} + '@metaplex-foundation/genesis@0.36.1': + resolution: {integrity: sha512-3g33v7K8PDgHOEHLdGRl0leBX/FUvRKfBE5VcIzXbeW1kpRz3OJP6HvU37LwI2EVW2AP4GiKEccbD7AhXs6y2w==} peerDependencies: '@metaplex-foundation/umi': ^1.4.1 @@ -6105,7 +6105,7 @@ snapshots: dependencies: '@metaplex-foundation/umi': 1.5.1 - '@metaplex-foundation/genesis@0.35.0(@metaplex-foundation/umi@1.5.1)': + '@metaplex-foundation/genesis@0.36.1(@metaplex-foundation/umi@1.5.1)': dependencies: '@metaplex-foundation/mpl-token-metadata': 3.4.0(@metaplex-foundation/umi@1.5.1) '@metaplex-foundation/mpl-toolbox': 0.10.0(@metaplex-foundation/umi@1.5.1) diff --git a/src/commands/genesis/claim-creator-rewards.ts b/src/commands/genesis/claim-creator-rewards.ts new file mode 100644 index 00000000..a56bbebf --- /dev/null +++ b/src/commands/genesis/claim-creator-rewards.ts @@ -0,0 +1,257 @@ +import { + BondingCurveBucketV2, + claimCreatorRewards, + GenesisApiConfig, + getBondingCurveBucketV2GpaBuilder, + getRaydiumCpmmBucketV2GpaBuilder, + isGenesisApiError, + Key, + RaydiumCpmmBucketV2, + SvmNetwork, +} from '@metaplex-foundation/genesis' +import { isPublicKey, PublicKey, publicKey, Umi, unwrapOptionRecursively } from '@metaplex-foundation/umi' +import { base58 } from '@metaplex-foundation/umi/serializers' +import { Flags } from '@oclif/core' +import ora from 'ora' + +import { TransactionCommand } from '../../TransactionCommand.js' +import { generateExplorerUrl } from '../../explorers.js' +import { getDefaultApiUrl } from '../../lib/genesis/launchApi.js' +import { detectSvmNetwork } from '../../lib/util.js' + +const NO_REWARDS_MESSAGE = 'No rewards available to claim' + +// extensions @ 488 + reserved(8) + backendSigner(32) + firstBuy(40) + firstBuyDone(1) + paddingAlign(7) = +88. +const BC_CREATOR_FEE_WALLET_OFFSET = 488 + 88 +// extensions @ 576 + reserved(8) + backendSigner(32) + lpLockSchedule option (272) = +312. +const RAYDIUM_CREATOR_FEE_WALLET_OFFSET = 576 + 312 + +const WSOL_MINT = 'So11111111111111111111111111111111111111112' + +interface ClaimablePreview { + address: string + pending: bigint + quoteMint: string + type: 'Bonding Curve' | 'Raydium CPMM' +} + +export default class GenesisClaimCreatorRewards extends TransactionCommand { + static override description = `Claim accrued creator rewards across all Genesis bonding-curve and Raydium buckets. + +Calls the Metaplex Genesis API to discover every bucket where the wallet is the +creator fee recipient — across bonding-curve launches and the Raydium CPMM pools +that bonding-curves and launchpools graduate to — and submits one transaction +per claimable bucket. The configured signer always pays transaction fees; the +creator fee wallet receives the claimed rewards. + +Returns a friendly message when there is nothing to claim.` + + static override examples = [ + '$ mplx genesis claim-creator-rewards', + '$ mplx genesis claim-creator-rewards --wallet ', + '$ mplx genesis claim-creator-rewards --network solana-devnet', + '$ mplx genesis claim-creator-rewards --apiUrl https://api.metaplex.dev', + ] + + static override flags = { + apiUrl: Flags.string({ + description: 'Genesis API base URL (defaults to https://api.metaplex.com for mainnet, https://api.metaplex.dev for devnet)', + required: false, + }), + network: Flags.option({ + description: 'Network override (auto-detected from RPC if not set)', + options: ['solana-mainnet', 'solana-devnet'] as const, + required: false, + })(), + wallet: Flags.string({ + char: 'w', + description: 'Creator fee wallet to claim for. The signer always pays fees; rewards go to this wallet. Defaults to the configured signer.', + required: false, + }), + } + + static override usage = 'genesis claim-creator-rewards [FLAGS]' + + public async run(): Promise { + const { flags } = await this.parse(GenesisClaimCreatorRewards) + + if (flags.wallet && !isPublicKey(flags.wallet)) { + this.error('--wallet must be a valid public key') + } + + const wallet: PublicKey = flags.wallet ? publicKey(flags.wallet) : this.context.umi.identity.publicKey + const network: SvmNetwork = flags.network ?? detectSvmNetwork(this.context.chain) + const apiConfig: GenesisApiConfig = { + baseUrl: flags.apiUrl ?? getDefaultApiUrl(network), + } + + const spinner = ora('Looking up creator rewards...').start() + + const claimable = await this.fetchClaimablePreview(this.context.umi, wallet) + + if (claimable.length === 0) { + spinner.info(`No rewards to claim for ${wallet}`) + return { claimed: 0, wallet: wallet.toString() } + } + + spinner.stop() + this.printPreview(wallet, claimable) + + spinner.start('Building transactions via Genesis API...') + + let result + try { + result = await claimCreatorRewards(this.context.umi, apiConfig, { + network, + payer: this.context.umi.identity.publicKey, + wallet, + }) + } catch (error) { + if (isGenesisApiError(error) && error.message === NO_REWARDS_MESSAGE) { + spinner.info(`No rewards to claim for ${wallet}`) + return { claimed: 0, wallet: wallet.toString() } + } + + spinner.fail('Failed to fetch creator rewards from Genesis API') + throw error + } + + if (result.transactions.length === 0) { + spinner.info(`No rewards to claim for ${wallet}`) + return { claimed: 0, wallet: wallet.toString() } + } + + const allowedCommitments = ['processed', 'confirmed', 'finalized'] as const + const commitment = allowedCommitments.includes(this.context.commitment as typeof allowedCommitments[number]) + ? (this.context.commitment as typeof allowedCommitments[number]) + : 'confirmed' + + const results: { explorer: string; signature: string }[] = [] + for (const [index, tx] of result.transactions.entries()) { + spinner.text = `Sending transaction ${index + 1} of ${result.transactions.length}...` + const signed = await this.context.umi.identity.signTransaction(tx) + const signatureBytes = await this.context.umi.rpc.sendTransaction(signed, { + commitment, + preflightCommitment: commitment, + }) + await this.context.umi.rpc.confirmTransaction(signatureBytes, { + commitment, + strategy: { type: 'blockhash', ...result.blockhash }, + }) + + const signature = base58.deserialize(signatureBytes)[0] + results.push({ + explorer: generateExplorerUrl(this.context.explorer, this.context.chain, signature, 'transaction'), + signature, + }) + } + + spinner.succeed(`Claimed creator rewards across ${results.length} bucket${results.length === 1 ? '' : 's'}`) + + this.log('') + this.logSuccess(`Creator rewards claimed for ${wallet}`) + this.log('') + this.log('Transactions:') + for (const { explorer, signature } of results) { + this.log(` ${signature}`) + this.log(` ${explorer}`) + } + + return { + buckets: claimable.map((b) => ({ + address: b.address, + pending: b.pending.toString(), + quoteMint: b.quoteMint, + type: b.type, + })), + signatures: results, + wallet: wallet.toString(), + } + } + + private async fetchClaimablePreview(umi: Umi, wallet: PublicKey): Promise { + const [bcBuckets, raydiumBuckets] = await Promise.all([ + getBondingCurveBucketV2GpaBuilder(umi) + .whereField('key', Key.BondingCurveBucketV2) + .where(BC_CREATOR_FEE_WALLET_OFFSET, wallet) + .getDeserialized(), + getRaydiumCpmmBucketV2GpaBuilder(umi) + .whereField('key', Key.RaydiumCpmmBucketV2) + .where(RAYDIUM_CREATOR_FEE_WALLET_OFFSET, wallet) + .getDeserialized(), + ]) + + const previews: ClaimablePreview[] = [] + + for (const bucket of bcBuckets) { + const pending = bucket.creatorFeeAccrued - bucket.creatorFeeClaimed + if (pending <= 0n) continue + previews.push({ + address: bucket.publicKey.toString(), + pending, + quoteMint: getQuoteMint(bucket), + type: 'Bonding Curve', + }) + } + + for (const bucket of raydiumBuckets) { + // Raydium claim is special: the LP may have uncollected fees that the + // claim transaction collects first. We can't preview those without + // loading the pool state, so we display only the bucket-tracked accrued. + const pending = bucket.creatorFeeAccrued - bucket.creatorFeeClaimed + if (pending <= 0n) continue + previews.push({ + address: bucket.publicKey.toString(), + pending, + quoteMint: getQuoteMint(bucket), + type: 'Raydium CPMM', + }) + } + + return previews + } + + private printPreview(wallet: PublicKey, claimable: ClaimablePreview[]): void { + this.log('') + this.log(`Claimable creator rewards for ${wallet}:`) + for (const item of claimable) { + this.log(` ${item.type.padEnd(13)} ${item.address} pending: ${formatAmount(item.pending, item.quoteMint)}`) + } + + const totalsByQuote = new Map() + for (const item of claimable) { + totalsByQuote.set(item.quoteMint, (totalsByQuote.get(item.quoteMint) ?? 0n) + item.pending) + } + + if (claimable.length > 1 || totalsByQuote.size > 1) { + this.log('') + this.log('Total pending:') + for (const [quoteMint, total] of totalsByQuote) { + this.log(` ${formatAmount(total, quoteMint)}`) + } + } + + this.log('') + } +} + +function getQuoteMint(bucket: BondingCurveBucketV2 | RaydiumCpmmBucketV2): string { + const quoteMint = unwrapOptionRecursively(bucket.bucket.quoteMint) + return quoteMint?.toString() ?? '' +} + +function formatAmount(amount: bigint, quoteMint: string): string { + if (quoteMint === WSOL_MINT) { + return `${formatLamports(amount)} SOL` + } + + return `${amount.toString()} (quote: ${quoteMint})` +} + +function formatLamports(lamports: bigint): string { + const negative = lamports < 0n + const abs = negative ? -lamports : lamports + const whole = abs / 1_000_000_000n + const frac = (abs % 1_000_000_000n).toString().padStart(9, '0').replace(/0+$/, '') + return `${negative ? '-' : ''}${whole}${frac ? `.${frac}` : ''}` +} From 4d1e6c680022c6d1caaf0c569d0a69718bf7f263 Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:43:58 +0200 Subject: [PATCH 2/5] Address claim-creator-rewards PR feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use this.context.signer / this.context.payer instead of umi.identity so asset-signer, --payer, and ledger flows sign correctly. - Unify the JSON return shape between the empty and success paths so --json consumers always get { buckets, signatures, wallet }. - Call the API before the GPA preview and treat the preview as informational only — Raydium buckets can have uncollected pool fees the API sweeps up even when the bucket-tracked accrued is zero. --- src/commands/genesis/claim-creator-rewards.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/commands/genesis/claim-creator-rewards.ts b/src/commands/genesis/claim-creator-rewards.ts index a56bbebf..f266bc60 100644 --- a/src/commands/genesis/claim-creator-rewards.ts +++ b/src/commands/genesis/claim-creator-rewards.ts @@ -79,37 +79,28 @@ Returns a friendly message when there is nothing to claim.` this.error('--wallet must be a valid public key') } - const wallet: PublicKey = flags.wallet ? publicKey(flags.wallet) : this.context.umi.identity.publicKey + const wallet: PublicKey = flags.wallet ? publicKey(flags.wallet) : this.context.signer.publicKey const network: SvmNetwork = flags.network ?? detectSvmNetwork(this.context.chain) const apiConfig: GenesisApiConfig = { baseUrl: flags.apiUrl ?? getDefaultApiUrl(network), } + const payerSigner = this.context.payer ?? this.context.signer const spinner = ora('Looking up creator rewards...').start() - const claimable = await this.fetchClaimablePreview(this.context.umi, wallet) - - if (claimable.length === 0) { - spinner.info(`No rewards to claim for ${wallet}`) - return { claimed: 0, wallet: wallet.toString() } - } - - spinner.stop() - this.printPreview(wallet, claimable) - - spinner.start('Building transactions via Genesis API...') + const emptyResult = { buckets: [], signatures: [], wallet: wallet.toString() } let result try { result = await claimCreatorRewards(this.context.umi, apiConfig, { network, - payer: this.context.umi.identity.publicKey, + payer: payerSigner.publicKey, wallet, }) } catch (error) { if (isGenesisApiError(error) && error.message === NO_REWARDS_MESSAGE) { spinner.info(`No rewards to claim for ${wallet}`) - return { claimed: 0, wallet: wallet.toString() } + return emptyResult } spinner.fail('Failed to fetch creator rewards from Genesis API') @@ -118,9 +109,18 @@ Returns a friendly message when there is nothing to claim.` if (result.transactions.length === 0) { spinner.info(`No rewards to claim for ${wallet}`) - return { claimed: 0, wallet: wallet.toString() } + return emptyResult } + spinner.stop() + + const claimable = await this.fetchClaimablePreview(this.context.umi, wallet) + if (claimable.length > 0) { + this.printPreview(wallet, claimable) + } + + spinner.start(`Signing and sending ${result.transactions.length} transaction${result.transactions.length === 1 ? '' : 's'}...`) + const allowedCommitments = ['processed', 'confirmed', 'finalized'] as const const commitment = allowedCommitments.includes(this.context.commitment as typeof allowedCommitments[number]) ? (this.context.commitment as typeof allowedCommitments[number]) @@ -129,7 +129,7 @@ Returns a friendly message when there is nothing to claim.` const results: { explorer: string; signature: string }[] = [] for (const [index, tx] of result.transactions.entries()) { spinner.text = `Sending transaction ${index + 1} of ${result.transactions.length}...` - const signed = await this.context.umi.identity.signTransaction(tx) + const signed = await payerSigner.signTransaction(tx) const signatureBytes = await this.context.umi.rpc.sendTransaction(signed, { commitment, preflightCommitment: commitment, From 5fde6bd0fb4c10c3fffeb037a3c7fa9807148121 Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Mon, 4 May 2026 11:13:41 +0200 Subject: [PATCH 3/5] Make preview fetch non-blocking in claim-creator-rewards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap fetchClaimablePreview in try/catch so a GPA/RPC failure during the informational preview step doesn't abort the claim — by that point the API has already returned valid transactions and we shouldn't throw them away. --- src/commands/genesis/claim-creator-rewards.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/commands/genesis/claim-creator-rewards.ts b/src/commands/genesis/claim-creator-rewards.ts index f266bc60..2c87fd09 100644 --- a/src/commands/genesis/claim-creator-rewards.ts +++ b/src/commands/genesis/claim-creator-rewards.ts @@ -114,9 +114,14 @@ Returns a friendly message when there is nothing to claim.` spinner.stop() - const claimable = await this.fetchClaimablePreview(this.context.umi, wallet) - if (claimable.length > 0) { - this.printPreview(wallet, claimable) + let claimable: ClaimablePreview[] = [] + try { + claimable = await this.fetchClaimablePreview(this.context.umi, wallet) + if (claimable.length > 0) { + this.printPreview(wallet, claimable) + } + } catch (error) { + this.warn(`Could not load claimable preview for ${wallet}: ${(error as Error).message}. Continuing with claim submission.`) } spinner.start(`Signing and sending ${result.transactions.length} transaction${result.transactions.length === 1 ? '' : 's'}...`) From 74aa037140bc0441c4af2d9e7168ecb2dc696e31 Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Mon, 4 May 2026 13:14:23 +0200 Subject: [PATCH 4/5] Add devnet smoke tests for claim-creator-rewards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tests against the live devnet API at api.metaplex.dev: - no-rewards path: hits the API with the system program pubkey and asserts the friendly "No rewards to claim" message. - client-side --wallet validation: rejects an invalid pubkey before any network call. Extracts DEVNET_RPC_URL in src/lib/util.ts (already used inline by DUMMY_UMI) so the new test can reuse it without re-hardcoding the URL. Spawns the CLI directly rather than via runCli — the existing helper auto-injects a localnet RPC that would override --network solana-devnet at the CLI level. Mirrors the inline pattern already used by test/commands/core/core.asset-signer.test.ts. --- src/lib/util.ts | 4 +- .../genesis.claim-creator-rewards.test.ts | 61 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 test/commands/genesis/genesis.claim-creator-rewards.test.ts diff --git a/src/lib/util.ts b/src/lib/util.ts index 75ecd7ef..581865cc 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -32,8 +32,10 @@ export const jsonParse = (str: string, parseBigint: boolean = false) => { return JSON.parse(str) } +export const DEVNET_RPC_URL = 'https://api.devnet.solana.com' + // create a temporary umi to access eddsa/keygen methods -export const DUMMY_UMI = createUmi('https://api.devnet.solana.com') +export const DUMMY_UMI = createUmi(DEVNET_RPC_URL) // open directory in file explorer diff --git a/test/commands/genesis/genesis.claim-creator-rewards.test.ts b/test/commands/genesis/genesis.claim-creator-rewards.test.ts new file mode 100644 index 00000000..8b871e1b --- /dev/null +++ b/test/commands/genesis/genesis.claim-creator-rewards.test.ts @@ -0,0 +1,61 @@ +import { expect } from 'chai' +import { spawn } from 'node:child_process' + +import { DEVNET_RPC_URL } from '../../../src/lib/util.js' +import { CLI_PATH, KEYPAIR_PATH } from '../../runCli' + +const stripAnsi = (s: string) => s.replace(/\[[\d;]*m/g, '') + +// We can't reuse runCli here because it auto-appends -r TEST_RPC, which +// would override the devnet RPC this command needs to reach the Genesis +// API and run the GPA scan against devnet state. +const runCliRaw = (args: string[]) => + new Promise<{ stdout: string; stderr: string; code: number }>((resolve, reject) => { + const child = spawn('node', [CLI_PATH, ...args], { stdio: ['pipe', 'pipe', 'pipe'] }) + let stdout = '' + let stderr = '' + child.stdout.on('data', (d) => { stdout += d.toString() }) + child.stderr.on('data', (d) => { stderr += d.toString() }) + child.on('error', reject) + child.on('close', (code) => resolve({ stdout, stderr, code: code ?? 0 })) + }) + +describe('genesis claim-creator-rewards', () => { + // System program: a real, valid pubkey guaranteed to never be the + // creator-fee wallet on any bucket. Exercises the full API round-trip + // (validation, GPA scan, 400 mapping, friendly path) without needing + // any on-chain setup or a funded keypair. + const SYSTEM_PROGRAM = '11111111111111111111111111111111' + + it('reports no rewards for a wallet with no buckets (devnet API)', async function () { + this.timeout(30000) + + const { stdout, stderr, code } = await runCliRaw([ + 'genesis', 'claim-creator-rewards', + '--wallet', SYSTEM_PROGRAM, + '--network', 'solana-devnet', + '-r', DEVNET_RPC_URL, + '-k', KEYPAIR_PATH, + ]) + + expect(code).to.equal(0) + const out = stripAnsi(stdout + stderr) + expect(out).to.contain('No rewards to claim') + expect(out).to.contain(SYSTEM_PROGRAM) + }) + + it('rejects an invalid --wallet before reaching the API', async function () { + this.timeout(15000) + + const { stdout, stderr, code } = await runCliRaw([ + 'genesis', 'claim-creator-rewards', + '--wallet', 'not-a-pubkey', + '-r', DEVNET_RPC_URL, + '-k', KEYPAIR_PATH, + ]) + + expect(code).to.not.equal(0) + const out = stripAnsi(stdout + stderr) + expect(out).to.contain('--wallet must be a valid public key') + }) +}) From c9d57a7de3e59b8247c7b1eb7b041ebe3d130ed8 Mon Sep 17 00:00:00 2001 From: MarkSackerberg <93528482+MarkSackerberg@users.noreply.github.com> Date: Mon, 4 May 2026 13:32:58 +0200 Subject: [PATCH 5/5] Reject RPC/network mismatches in claim-creator-rewards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fail fast when the connected RPC and the resolved Genesis API network disagree, so users get a clear error instead of confusing tx submission failures: - Localnet RPC errors out — the API only supports mainnet and devnet. - Explicit --network that disagrees with the detected RPC cluster errors out with both values surfaced. --- src/commands/genesis/claim-creator-rewards.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/commands/genesis/claim-creator-rewards.ts b/src/commands/genesis/claim-creator-rewards.ts index 2c87fd09..83972351 100644 --- a/src/commands/genesis/claim-creator-rewards.ts +++ b/src/commands/genesis/claim-creator-rewards.ts @@ -17,7 +17,7 @@ import ora from 'ora' import { TransactionCommand } from '../../TransactionCommand.js' import { generateExplorerUrl } from '../../explorers.js' import { getDefaultApiUrl } from '../../lib/genesis/launchApi.js' -import { detectSvmNetwork } from '../../lib/util.js' +import { detectSvmNetwork, RpcChain } from '../../lib/util.js' const NO_REWARDS_MESSAGE = 'No rewards available to claim' @@ -80,7 +80,17 @@ Returns a friendly message when there is nothing to claim.` } const wallet: PublicKey = flags.wallet ? publicKey(flags.wallet) : this.context.signer.publicKey - const network: SvmNetwork = flags.network ?? detectSvmNetwork(this.context.chain) + + if (this.context.chain === RpcChain.Localnet) { + this.error('genesis claim-creator-rewards requires a mainnet or devnet RPC endpoint') + } + + const detectedNetwork = detectSvmNetwork(this.context.chain) + if (flags.network && flags.network !== detectedNetwork) { + this.error(`--network ${flags.network} does not match the connected RPC cluster (${detectedNetwork}). Use --rpc to point at a matching endpoint or omit --network.`) + } + + const network: SvmNetwork = flags.network ?? detectedNetwork const apiConfig: GenesisApiConfig = { baseUrl: flags.apiUrl ?? getDefaultApiUrl(network), }