Skip to content

Commit 5fe4f11

Browse files
authored
Merge pull request #8 from gitteri/cli-stablecoin-mint
add stablecoin issuance cli
2 parents ed6466d + 87520e3 commit 5fe4f11

28 files changed

+1565
-651
lines changed

CLAUDE.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Architecture Overview
6+
7+
Mosaic is a TypeScript monorepo for managing Token-2022 extensions on Solana, specifically designed for Stablecoin and Arcade Token use cases. The project uses pnpm workspaces with the following structure:
8+
9+
- **@mosaic/sdk** (`packages/sdk/`) - Core SDK with token templates and management utilities
10+
- Uses `gill` library for Solana interactions
11+
- Provides `Token` class for building token transactions with extensions
12+
- Contains predefined templates for stablecoin and arcade tokens
13+
- Token extensions include: Metadata, Pausable, Default Account State, Confidential Balances, Permanent Delegate
14+
- **@mosaic/cli** (`packages/cli/`) - Command-line interface (scaffolded)
15+
- **@mosaic/ui** (`packages/ui/`) - Next.js web interface with Tailwind CSS and Radix UI components
16+
17+
## Token Types
18+
19+
### Stablecoin
20+
21+
- Default Account State (SRFC blocklist for compliance)
22+
- Metadata, Confidential Balances, Pausable, Permanent Delegate
23+
24+
### Arcade Token
25+
26+
- Default Account State (SRFC allowlist for programs/users)
27+
- Metadata (rich gaming metadata), Permanent Delegate, Pausable
28+
29+
## Common Development Commands
30+
31+
```bash
32+
# Install dependencies
33+
pnpm install
34+
35+
# Development (runs dev for all packages)
36+
pnpm dev
37+
38+
# Build all packages
39+
pnpm build
40+
41+
# Testing
42+
pnpm test # Run all tests
43+
pnpm test:watch # SDK watch mode (in packages/sdk/)
44+
pnpm test:coverage # SDK coverage (in packages/sdk/)
45+
46+
# Code quality
47+
pnpm lint # Lint all packages
48+
pnpm lint:fix # Fix linting issues
49+
pnpm format # Format with Prettier
50+
pnpm format:check # Check formatting
51+
pnpm type-check # TypeScript checking
52+
pnpm check # Run format:check + lint + type-check
53+
54+
# Before committing
55+
pnpm precommit # format + lint:fix
56+
```
57+
58+
## Package-Specific Commands
59+
60+
### SDK (packages/sdk/)
61+
62+
- Uses Jest for testing
63+
- Main entry point exports `Token` class and templates
64+
- Test setup in `src/__tests__/setup.ts`
65+
66+
### UI (packages/ui/)
67+
68+
- Next.js 14 with App Router
69+
- Tailwind CSS + Radix UI components
70+
- Theme support with next-themes
71+
72+
## Development Notes
73+
74+
- Project is currently scaffolded - implementation depends on Token-2022 program stabilization and SRFC 37 spec
75+
- Uses `gill` library for Solana RPC interactions
76+
- All token creation functions return `FullTransaction` objects ready for signing
77+
- Node.js 18+ and pnpm 9+ required
78+
- TypeScript 5+ for all packages

packages/cli/package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@mosaic/cli",
3+
"version": "0.1.0",
4+
"description": "Command-line interface for managing Token-2022 tokens with extensions",
5+
"type": "module",
6+
"main": "./dist/index.js",
7+
"bin": {
8+
"mosaic": "./dist/index.js"
9+
},
10+
"files": [
11+
"dist"
12+
],
13+
"scripts": {
14+
"build": "tsc",
15+
"dev": "tsx src/index.ts",
16+
"start": "node dist/index.js",
17+
"test": "echo 'Tests will be implemented'",
18+
"lint": "eslint src --ext .ts",
19+
"lint:fix": "eslint src --ext .ts --fix",
20+
"type-check": "tsc --noEmit",
21+
"clean": "rm -rf dist"
22+
},
23+
"dependencies": {
24+
"@mosaic/sdk": "workspace:*",
25+
"commander": "^12.0.0",
26+
"chalk": "^5.3.0",
27+
"ora": "^8.0.1",
28+
"gill": "^0.10.2"
29+
},
30+
"devDependencies": {
31+
"@types/node": "^20.0.0",
32+
"@typescript-eslint/eslint-plugin": "^8.34.0",
33+
"@typescript-eslint/parser": "^8.34.0",
34+
"eslint": "^9.32.0",
35+
"tsx": "^4.0.0",
36+
"typescript": "^5.0.0"
37+
},
38+
"keywords": [
39+
"solana",
40+
"token-2022",
41+
"extensions",
42+
"cli",
43+
"stablecoin",
44+
"arcade-token"
45+
]
46+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { Command } from 'commander';
2+
import chalk from 'chalk';
3+
import ora from 'ora';
4+
import { createStablecoinInitTransaction } from '@mosaic/sdk';
5+
import { createSolanaClient } from '../../utils/rpc.js';
6+
import { loadKeypair } from '../../utils/solana.js';
7+
import {
8+
generateKeyPairSigner,
9+
signTransactionMessageWithSigners,
10+
type Address,
11+
} from 'gill';
12+
13+
interface StablecoinOptions {
14+
name: string;
15+
symbol: string;
16+
decimals: string;
17+
uri?: string;
18+
mintAuthority?: string;
19+
metadataAuthority?: string;
20+
pausableAuthority?: string;
21+
confidentialBalancesAuthority?: string;
22+
permanentDelegateAuthority?: string;
23+
mintKeypair?: string;
24+
rpcUrl?: string;
25+
keypair?: string;
26+
}
27+
28+
export const createStablecoinCommand = new Command('stablecoin')
29+
.description('Create a new stablecoin with Token-2022 extensions')
30+
.requiredOption('-n, --name <name>', 'Token name')
31+
.requiredOption('-s, --symbol <symbol>', 'Token symbol')
32+
.option('-d, --decimals <decimals>', 'Number of decimals', '6')
33+
.option('-u, --uri <uri>', 'Metadata URI', '')
34+
.option(
35+
'--mint-authority <address>',
36+
'Mint authority address (defaults to signer)'
37+
)
38+
.option(
39+
'--metadata-authority <address>',
40+
'Metadata authority address (defaults to mint authority)'
41+
)
42+
.option(
43+
'--pausable-authority <address>',
44+
'Pausable authority address (defaults to mint authority)'
45+
)
46+
.option(
47+
'--confidential-balances-authority <address>',
48+
'Confidential balances authority address (defaults to mint authority)'
49+
)
50+
.option(
51+
'--permanent-delegate-authority <address>',
52+
'Permanent delegate authority address (defaults to mint authority)'
53+
)
54+
.option(
55+
'--mint-keypair <path>',
56+
'Path to mint keypair file (generates new one if not provided)'
57+
)
58+
.action(async (options: StablecoinOptions, command) => {
59+
const spinner = ora('Creating stablecoin...').start();
60+
61+
try {
62+
// Get global options from parent command
63+
const parentOpts = command.parent?.opts() || {};
64+
const rpcUrl = options.rpcUrl || parentOpts.rpcUrl;
65+
const keypairPath = options.keypair || parentOpts.keypair;
66+
67+
// Create Solana client with sendAndConfirmTransaction
68+
const { rpc, sendAndConfirmTransaction } = createSolanaClient(rpcUrl);
69+
70+
// Load signer keypair
71+
const signerKeypair = await loadKeypair(keypairPath);
72+
const signerAddress = signerKeypair.address;
73+
74+
spinner.text = 'Loading keypairs...';
75+
76+
// Generate or load mint keypair
77+
let mintKeypair;
78+
if (options.mintKeypair) {
79+
mintKeypair = await loadKeypair(options.mintKeypair);
80+
} else {
81+
mintKeypair = await generateKeyPairSigner();
82+
}
83+
84+
// Parse decimals
85+
const decimals = parseInt(options.decimals, 10);
86+
if (isNaN(decimals) || decimals < 0 || decimals > 9) {
87+
throw new Error('Decimals must be a number between 0 and 9');
88+
}
89+
90+
// Set authorities (default to signer if not provided)
91+
const mintAuthority = (options.mintAuthority || signerAddress) as Address;
92+
const metadataAuthority = (options.metadataAuthority ||
93+
mintAuthority) as Address;
94+
const pausableAuthority = (options.pausableAuthority ||
95+
mintAuthority) as Address;
96+
const confidentialBalancesAuthority =
97+
(options.confidentialBalancesAuthority || mintAuthority) as Address;
98+
const permanentDelegateAuthority = (options.permanentDelegateAuthority ||
99+
mintAuthority) as Address;
100+
101+
spinner.text = 'Building transaction...';
102+
103+
// Create stablecoin using Token class
104+
const transaction = await createStablecoinInitTransaction(
105+
rpc,
106+
options.name,
107+
options.symbol,
108+
decimals,
109+
options.uri || '',
110+
mintAuthority,
111+
mintKeypair,
112+
signerKeypair,
113+
metadataAuthority,
114+
pausableAuthority,
115+
confidentialBalancesAuthority,
116+
permanentDelegateAuthority
117+
);
118+
119+
spinner.text = 'Signing transaction...';
120+
121+
// Sign the transaction (buildTransaction includes signers but doesn't auto-sign)
122+
const signedTransaction =
123+
await signTransactionMessageWithSigners(transaction);
124+
125+
spinner.text = 'Sending transaction...';
126+
127+
// Send and confirm transaction
128+
const signature = await sendAndConfirmTransaction(signedTransaction);
129+
130+
spinner.succeed('Stablecoin created successfully!');
131+
132+
// Display results
133+
console.log(chalk.green('✅ Stablecoin Creation Successful'));
134+
console.log(chalk.cyan('📋 Details:'));
135+
console.log(` ${chalk.bold('Name:')} ${options.name}`);
136+
console.log(` ${chalk.bold('Symbol:')} ${options.symbol}`);
137+
console.log(` ${chalk.bold('Decimals:')} ${decimals}`);
138+
console.log(` ${chalk.bold('Mint Address:')} ${mintKeypair.address}`);
139+
console.log(` ${chalk.bold('Transaction:')} ${signature}`);
140+
141+
console.log(chalk.cyan('🔐 Authorities:'));
142+
console.log(` ${chalk.bold('Mint Authority:')} ${mintAuthority}`);
143+
console.log(
144+
` ${chalk.bold('Metadata Authority:')} ${metadataAuthority}`
145+
);
146+
console.log(
147+
` ${chalk.bold('Pausable Authority:')} ${pausableAuthority}`
148+
);
149+
console.log(
150+
` ${chalk.bold('Confidential Balances Authority:')} ${confidentialBalancesAuthority}`
151+
);
152+
console.log(
153+
` ${chalk.bold('Permanent Delegate Authority:')} ${permanentDelegateAuthority}`
154+
);
155+
156+
console.log(chalk.cyan('🛡️ Token Extensions:'));
157+
console.log(` ${chalk.green('✓')} Metadata`);
158+
console.log(` ${chalk.green('✓')} Pausable`);
159+
console.log(` ${chalk.green('✓')} Default Account State (Blocklist)`);
160+
console.log(` ${chalk.green('✓')} Confidential Balances`);
161+
console.log(` ${chalk.green('✓')} Permanent Delegate`);
162+
163+
if (options.uri) {
164+
console.log(`${chalk.bold('Metadata URI:')} ${options.uri}`);
165+
}
166+
} catch (error) {
167+
spinner.fail('Failed to create stablecoin');
168+
console.error(
169+
chalk.red('❌ Error:'),
170+
error instanceof Error ? error.message : 'Unknown error'
171+
);
172+
process.exit(1);
173+
}
174+
});

packages/cli/src/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env node
2+
3+
import { Command } from 'commander';
4+
import { createStablecoinCommand } from './commands/create/stablecoin.js';
5+
6+
const program = new Command();
7+
8+
program
9+
.name('mosaic')
10+
.description('CLI for managing Token-2022 tokens with extensions')
11+
.version('0.1.0');
12+
13+
// Create command group
14+
const createCommand = program
15+
.command('create')
16+
.description('Create new tokens with Token-2022 extensions');
17+
18+
// Add stablecoin creation command
19+
createCommand.addCommand(createStablecoinCommand);
20+
21+
// Global options
22+
program
23+
.option('--rpc-url <url>', 'Solana RPC URL', 'https://api.devnet.solana.com')
24+
.option(
25+
'--keypair <path>',
26+
'Path to keypair file (defaults to Solana CLI default)'
27+
);
28+
29+
program.parse(process.argv);

packages/cli/src/utils/rpc.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {
2+
createSolanaRpc,
3+
createSolanaClient as createClient,
4+
type Rpc,
5+
type SolanaRpcApi,
6+
} from 'gill';
7+
import { getSolanaConfig } from './solana.js';
8+
9+
export function createRpcClient(rpcUrl?: string): Rpc<SolanaRpcApi> {
10+
const url =
11+
rpcUrl ||
12+
getSolanaConfig()?.json_rpc_url ||
13+
'https://api.devnet.solana.com';
14+
return createSolanaRpc(url);
15+
}
16+
17+
export function createSolanaClient(rpcUrl?: string) {
18+
const url =
19+
rpcUrl ||
20+
getSolanaConfig()?.json_rpc_url ||
21+
'https://api.devnet.solana.com';
22+
return createClient({ urlOrMoniker: url });
23+
}

0 commit comments

Comments
 (0)