diff --git a/packages/cli/src/commands/create/arcade-token.ts b/packages/cli/src/commands/create/arcade-token.ts new file mode 100644 index 0000000..05611ab --- /dev/null +++ b/packages/cli/src/commands/create/arcade-token.ts @@ -0,0 +1,168 @@ +import { Command } from 'commander'; +import chalk from 'chalk'; +import ora from 'ora'; +import { createArcadeTokenInitTransaction } from '@mosaic/sdk'; +import { createSolanaClient } from '../../utils/rpc.js'; +import { loadKeypair } from '../../utils/solana.js'; +import { + generateKeyPairSigner, + signTransactionMessageWithSigners, + type Address, +} from 'gill'; + +interface ArcadeTokenOptions { + name: string; + symbol: string; + decimals: string; + uri?: string; + mintAuthority?: string; + metadataAuthority?: string; + pausableAuthority?: string; + permanentDelegateAuthority?: string; + mintKeypair?: string; + rpcUrl?: string; + keypair?: string; +} + +export const createArcadeTokenCommand = new Command('arcade-token') + .description('Create a new arcade token with Token-2022 extensions') + .requiredOption('-n, --name ', 'Token name') + .requiredOption('-s, --symbol ', 'Token symbol') + .option('-d, --decimals ', 'Number of decimals', '0') + .option('-u, --uri ', 'Metadata URI', '') + .option( + '--mint-authority
', + 'Mint authority address (defaults to signer)' + ) + .option( + '--metadata-authority
', + 'Metadata authority address (defaults to mint authority)' + ) + .option( + '--pausable-authority
', + 'Pausable authority address (defaults to mint authority)' + ) + .option( + '--permanent-delegate-authority
', + 'Permanent delegate authority address (defaults to mint authority)' + ) + .option( + '--mint-keypair ', + 'Path to mint keypair file (generates new one if not provided)' + ) + .action(async (options: ArcadeTokenOptions, command) => { + const spinner = ora('Creating arcade token...').start(); + + try { + // Get global options from parent command + const parentOpts = command.parent?.opts() || {}; + const rpcUrl = options.rpcUrl || parentOpts.rpcUrl; + const keypairPath = options.keypair || parentOpts.keypair; + + // Create Solana client with sendAndConfirmTransaction + const { rpc, sendAndConfirmTransaction } = createSolanaClient(rpcUrl); + + // Load signer keypair + const signerKeypair = await loadKeypair(keypairPath); + const signerAddress = signerKeypair.address; + + spinner.text = 'Loading keypairs...'; + + // Generate or load mint keypair + let mintKeypair; + if (options.mintKeypair) { + mintKeypair = await loadKeypair(options.mintKeypair); + } else { + mintKeypair = await generateKeyPairSigner(); + } + + // Parse decimals + const decimals = parseInt(options.decimals, 10); + if (isNaN(decimals) || decimals < 0 || decimals > 9) { + throw new Error('Decimals must be a number between 0 and 9'); + } + + // Set authorities (default to signer if not provided) + const mintAuthority = (options.mintAuthority || signerAddress) as Address; + const metadataAuthority = (options.metadataAuthority || + mintAuthority) as Address; + const pausableAuthority = (options.pausableAuthority || + mintAuthority) as Address; + const permanentDelegateAuthority = (options.permanentDelegateAuthority || + mintAuthority) as Address; + + spinner.text = 'Building transaction...'; + + // Create arcade token transaction + const transaction = await createArcadeTokenInitTransaction( + rpc, + options.name, + options.symbol, + decimals, + options.uri || '', + mintAuthority, + mintKeypair, + signerKeypair, + metadataAuthority, + pausableAuthority, + undefined, // Arcade tokens don't use confidential balances + permanentDelegateAuthority + ); + + spinner.text = 'Signing transaction...'; + + // Sign the transaction + const signedTransaction = + await signTransactionMessageWithSigners(transaction); + + spinner.text = 'Sending transaction...'; + + // Send and confirm transaction + const signature = await sendAndConfirmTransaction(signedTransaction); + + spinner.succeed('Arcade token created successfully!'); + + // Display results + console.log(chalk.green('✅ Arcade Token Creation Successful')); + console.log(chalk.cyan('📋 Details:')); + console.log(` ${chalk.bold('Name:')} ${options.name}`); + console.log(` ${chalk.bold('Symbol:')} ${options.symbol}`); + console.log(` ${chalk.bold('Decimals:')} ${decimals}`); + console.log(` ${chalk.bold('Mint Address:')} ${mintKeypair.address}`); + console.log(` ${chalk.bold('Transaction:')} ${signature}`); + + console.log(chalk.cyan('🔐 Authorities:')); + console.log(` ${chalk.bold('Mint Authority:')} ${mintAuthority}`); + console.log( + ` ${chalk.bold('Metadata Authority:')} ${metadataAuthority}` + ); + console.log( + ` ${chalk.bold('Pausable Authority:')} ${pausableAuthority}` + ); + console.log( + ` ${chalk.bold('Permanent Delegate Authority:')} ${permanentDelegateAuthority}` + ); + + console.log(chalk.cyan('🎮 Token Extensions:')); + console.log(` ${chalk.green('✓')} Metadata (Rich Gaming Metadata)`); + console.log(` ${chalk.green('✓')} Pausable`); + console.log(` ${chalk.green('✓')} Default Account State (Allowlist)`); + console.log( + chalk.yellow( + ' ⚠️ You must add addresses to the allowlist for your arcade token to be usable by end users.' + ) + ); + console.log(` ${chalk.green('✓')} Permanent Delegate`); + + if (options.uri) { + console.log(`${chalk.bold('Metadata URI:')} ${options.uri}`); + } + } catch (error) { + spinner.fail('Failed to create arcade token'); + console.error( + chalk.red('❌ Error:'), + error instanceof Error ? error.message : 'Unknown error' + ); + process.exit(1); + } + }); diff --git a/packages/cli/src/commands/create/stablecoin.ts b/packages/cli/src/commands/create/stablecoin.ts index c7147e1..055391f 100644 --- a/packages/cli/src/commands/create/stablecoin.ts +++ b/packages/cli/src/commands/create/stablecoin.ts @@ -100,7 +100,7 @@ export const createStablecoinCommand = new Command('stablecoin') spinner.text = 'Building transaction...'; - // Create stablecoin using Token class + // Create stablecoin transaction const transaction = await createStablecoinInitTransaction( rpc, options.name, diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 26bd2ec..60825dc 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -2,6 +2,7 @@ import { Command } from 'commander'; import { createStablecoinCommand } from './commands/create/stablecoin.js'; +import { createArcadeTokenCommand } from './commands/create/arcade-token.js'; const program = new Command(); @@ -15,8 +16,9 @@ const createCommand = program .command('create') .description('Create new tokens with Token-2022 extensions'); -// Add stablecoin creation command +// Add token creation commands createCommand.addCommand(createStablecoinCommand); +createCommand.addCommand(createArcadeTokenCommand); // Global options program diff --git a/packages/sdk/src/templates/arcadeToken.ts b/packages/sdk/src/templates/arcadeToken.ts index 4c79322..36cc597 100644 --- a/packages/sdk/src/templates/arcadeToken.ts +++ b/packages/sdk/src/templates/arcadeToken.ts @@ -6,6 +6,8 @@ import type { FullTransaction, TransactionMessageWithFeePayer, TransactionVersion, + TransactionSigner, + TransactionWithBlockhashLifetime, } from 'gill'; import { createNoopSigner } from 'gill'; @@ -37,18 +39,25 @@ export const createArcadeTokenInitTransaction = async ( decimals: number, uri: string, mintAuthority: Address, - mint: Address, - feePayer: Address, + mint: Address | TransactionSigner, + feePayer: Address | TransactionSigner, metadataAuthority?: Address, pausableAuthority?: Address, confidentialBalancesAuthority?: Address, permanentDelegateAuthority?: Address ): Promise< - FullTransaction + FullTransaction< + TransactionVersion, + TransactionMessageWithFeePayer, + TransactionWithBlockhashLifetime + > > => { + const mintSigner = typeof mint === 'string' ? createNoopSigner(mint) : mint; + const feePayerSigner = + typeof feePayer === 'string' ? createNoopSigner(feePayer) : feePayer; const tx = await new Token() .withMetadata({ - mintAddress: mint, + mintAddress: mintSigner.address, authority: metadataAuthority || mintAuthority, metadata: { name, @@ -66,8 +75,8 @@ export const createArcadeTokenInitTransaction = async ( rpc, decimals, authority: mintAuthority, - mint: createNoopSigner(mint), - feePayer: createNoopSigner(feePayer), + mint: mintSigner, + feePayer: feePayerSigner, }); return tx;