@@ -10,10 +10,10 @@ import {
1010 createSolanaClient ,
1111 generateKeyPairSigner ,
1212 isStringifiedNumber ,
13- signTransactionMessageWithSigners ,
1413 getExplorerLink ,
1514 isAddress ,
1615 address ,
16+ isSolanaError ,
1717} from "gill" ;
1818import {
1919 buildCreateTokenTransaction ,
@@ -25,6 +25,7 @@ import {
2525import { loadKeypairSignerFromFile } from "gill/node" ;
2626import { parseOrLoadSignerAddress } from "@/lib/gill/keys" ;
2727import { parseOptionsFlagForRpcUrl } from "@/lib/cli/parsers" ;
28+ import { simulateTransactionOnThrow } from "@/lib/gill/errors" ;
2829
2930export function createTokenCommand ( ) {
3031 return new Command ( "create" )
@@ -110,6 +111,7 @@ export function createTokenCommand() {
110111 . addOption ( COMMON_OPTIONS . url )
111112 . action ( async ( options ) => {
112113 titleMessage ( "Create a new token" ) ;
114+ const spinner = ora ( ) ;
113115
114116 if ( ! options . name ) {
115117 return errorOutro (
@@ -174,142 +176,176 @@ export function createTokenCommand() {
174176 ) ;
175177 }
176178
177- const parsedRpcUrl = parseOptionsFlagForRpcUrl (
178- options . url ,
179- /* use the Solana cli config's rpc url as the fallback */
180- loadSolanaCliConfig ( ) . json_rpc_url ,
181- ) ;
182-
183- const tokenProgram = checkedTokenProgramAddress (
184- address ( options . tokenProgram ) ,
185- ) ;
186-
187- // payer will always be used to pay the fees
188- const payer = await loadKeypairSignerFromFile ( options . keypair ) ;
189-
190- // mint authority is required to sign in order to mint the initial tokens
191- const mintAuthority = options . mintAuthority
192- ? await loadKeypairSignerFromFile ( options . mintAuthority )
193- : payer ;
194-
195- const freezeAuthority = await getAddressFromStringOrFilePath (
196- options . freezeAuthority || payer . address ,
197- ) ;
198-
199- const mint = options . customMint
200- ? await loadKeypairSignerFromFile ( options . customMint )
201- : await generateKeyPairSigner ( ) ;
202-
203- console . log ( ) ; // line spacer after the common "ExperimentalWarning for Ed25519 Web Crypto API"
204- const spinner = ora ( "Preparing to create token" ) . start ( ) ;
205-
206- const { rpc, sendAndConfirmTransaction } = createSolanaClient ( {
207- urlOrMoniker : parsedRpcUrl . url ,
208- } ) ;
209-
210- spinner . text = "Fetching the latest blockhash" ;
211- const { value : latestBlockhash } = await rpc . getLatestBlockhash ( ) . send ( ) ;
212-
213- spinner . text = "Preparing to create token mint" ;
214-
215- const createMintTx = await buildCreateTokenTransaction ( {
216- feePayer : payer ,
217- latestBlockhash,
218- mint,
219- mintAuthority,
220- tokenProgram,
221- freezeAuthority,
222- updateAuthority : mintAuthority ,
223- metadata : {
224- isMutable : true ,
225- name : options . name ,
226- symbol : options . symbol ,
227- uri : options . metadata ,
228- } ,
229- decimals : Number ( options . decimals ) ,
230- } ) ;
231-
232- spinner . text = "Creating token mint: " + mint . address ;
233- let signature = await sendAndConfirmTransaction (
234- await signTransactionMessageWithSigners ( createMintTx ) ,
235- ) ;
236-
237- spinner . succeed ( "Token mint created: " + mint . address ) ;
238-
239- console . log (
240- " " ,
241- getExplorerLink ( {
242- cluster : parsedRpcUrl . cluster ,
243- transaction : signature ,
244- } ) ,
245- "\n" ,
246- ) ;
247-
248- if ( ! options . amount ) {
249- warnMessage (
250- "No amount provided, skipping minting tokens to your account" ,
179+ spinner . start ( "Preparing to create token" ) ;
180+
181+ try {
182+ const parsedRpcUrl = parseOptionsFlagForRpcUrl (
183+ options . url ,
184+ /* use the Solana cli config's rpc url as the fallback */
185+ loadSolanaCliConfig ( ) . json_rpc_url ,
186+ ) ;
187+
188+ const tokenProgram = checkedTokenProgramAddress (
189+ address ( options . tokenProgram ) ,
190+ ) ;
191+
192+ // payer will always be used to pay the fees
193+ const payer = await loadKeypairSignerFromFile ( options . keypair ) ;
194+
195+ // mint authority is required to sign in order to mint the initial tokens
196+ const mintAuthority = options . mintAuthority
197+ ? await loadKeypairSignerFromFile ( options . mintAuthority )
198+ : payer ;
199+
200+ const freezeAuthority = await getAddressFromStringOrFilePath (
201+ options . freezeAuthority || payer . address ,
251202 ) ;
252203
253- const mintCommand = [
254- "npx mucho token mint" ,
255- `--url ${ parsedRpcUrl . cluster } ` ,
256- `--mint ${ mint . address } ` ,
257- `--destination <DESTINATION_ADDRESS>` ,
258- `--amount <AMOUNT>` ,
259- ] ;
204+ const mint = options . customMint
205+ ? await loadKeypairSignerFromFile ( options . customMint )
206+ : await generateKeyPairSigner ( ) ;
207+
208+ const { rpc, sendAndConfirmTransaction, simulateTransaction } =
209+ createSolanaClient ( {
210+ urlOrMoniker : parsedRpcUrl . url ,
211+ } ) ;
212+
213+ spinner . text = "Fetching the latest blockhash" ;
214+ const { value : latestBlockhash } = await rpc
215+ . getLatestBlockhash ( )
216+ . send ( ) ;
217+
218+ spinner . text = "Preparing to create token mint" ;
219+
220+ const createMintTx = await buildCreateTokenTransaction ( {
221+ feePayer : payer ,
222+ latestBlockhash,
223+ mint,
224+ mintAuthority,
225+ tokenProgram,
226+ freezeAuthority,
227+ updateAuthority : mintAuthority ,
228+ metadata : {
229+ isMutable : true ,
230+ name : options . name ,
231+ symbol : options . symbol ,
232+ uri : options . metadata ,
233+ } ,
234+ decimals : Number ( options . decimals ) ,
235+ } ) ;
236+
237+ spinner . text = "Creating token mint: " + mint . address ;
238+ let signature = await sendAndConfirmTransaction ( createMintTx , {
239+ commitment : "confirmed" ,
240+ // skipPreflight: true,
241+ } ) . catch ( async ( err ) => {
242+ await simulateTransactionOnThrow (
243+ simulateTransaction ,
244+ err ,
245+ createMintTx ,
246+ ) ;
247+ throw err ;
248+ } ) ;
249+
250+ spinner . succeed ( "Token mint created: " + mint . address ) ;
260251
261252 console . log (
262- "To mint new tokens to any destination wallet in the future, run the following command:" ,
253+ " " ,
254+ getExplorerLink ( {
255+ cluster : parsedRpcUrl . cluster ,
256+ transaction : signature ,
257+ } ) ,
258+ "\n" ,
263259 ) ;
264- console . log ( mintCommand . join ( " " ) ) ;
265- spinner . stop ( ) ;
266- return ;
267- }
268260
269- if ( ! isStringifiedNumber ( options . amount ) ) {
270- return errorOutro (
271- "Please provide a valid amount with -a <AMOUNT>" ,
272- "Invalid value" ,
261+ if ( ! options . amount ) {
262+ warnMessage (
263+ "No amount provided, skipping minting tokens to your account" ,
264+ ) ;
265+
266+ const mintCommand = [
267+ "npx mucho token mint" ,
268+ `--url ${ parsedRpcUrl . cluster } ` ,
269+ `--mint ${ mint . address } ` ,
270+ `--destination <DESTINATION_ADDRESS>` ,
271+ `--amount <AMOUNT>` ,
272+ ] ;
273+
274+ console . log (
275+ "To mint new tokens to any destination wallet in the future, run the following command:" ,
276+ ) ;
277+ console . log ( mintCommand . join ( " " ) ) ;
278+ spinner . stop ( ) ;
279+ return ;
280+ }
281+
282+ if ( ! isStringifiedNumber ( options . amount ) ) {
283+ return errorOutro (
284+ "Please provide a valid amount with -a <AMOUNT>" ,
285+ "Invalid value" ,
286+ ) ;
287+ }
288+
289+ const destination = options . destination
290+ ? await parseOrLoadSignerAddress ( options . destination )
291+ : payer . address ;
292+
293+ spinner . start (
294+ `Preparing to mint '${ options . amount } ' tokens to ${ destination } ` ,
273295 ) ;
274- }
275296
276- const destination = options . destination
277- ? await parseOrLoadSignerAddress ( options . destination )
278- : payer . address ;
279-
280- spinner . start (
281- `Preparing to mint '${ options . amount } ' tokens to ${ destination } ` ,
282- ) ;
283-
284- const mintTokensTx = await buildMintTokensTransaction ( {
285- feePayer : payer ,
286- latestBlockhash,
287- mint,
288- mintAuthority,
289- tokenProgram,
290- amount : Number ( options . amount ) * 10 ** Number ( options . decimals ) ,
291- destination,
292- } ) ;
293-
294- const tokenPlurality = wordWithPlurality (
295- options . amount ,
296- "token" ,
297- "tokens" ,
298- ) ;
299-
300- spinner . text = `Minting '${ options . amount } ' ${ tokenPlurality } to ${ destination } ` ;
301- signature = await sendAndConfirmTransaction (
302- await signTransactionMessageWithSigners ( mintTokensTx ) ,
303- ) ;
304- spinner . succeed (
305- `Minted '${ options . amount } ' ${ tokenPlurality } to ${ destination } ` ,
306- ) ;
307- console . log (
308- " " ,
309- getExplorerLink ( {
310- cluster : parsedRpcUrl . cluster ,
311- transaction : signature ,
312- } ) ,
313- ) ;
297+ const mintTokensTx = await buildMintTokensTransaction ( {
298+ feePayer : payer ,
299+ latestBlockhash,
300+ mint,
301+ mintAuthority,
302+ tokenProgram,
303+ amount : Number ( options . amount ) * 10 ** Number ( options . decimals ) ,
304+ destination,
305+ } ) ;
306+
307+ const tokenPlurality = wordWithPlurality (
308+ options . amount ,
309+ "token" ,
310+ "tokens" ,
311+ ) ;
312+
313+ spinner . text = `Minting '${ options . amount } ' ${ tokenPlurality } to ${ destination } ` ;
314+ signature = await sendAndConfirmTransaction ( mintTokensTx , {
315+ commitment : "confirmed" ,
316+ skipPreflight : true ,
317+ } ) . catch ( async ( err ) => {
318+ await simulateTransactionOnThrow (
319+ simulateTransaction ,
320+ err ,
321+ createMintTx ,
322+ ) ;
323+ throw err ;
324+ } ) ;
325+
326+ spinner . succeed (
327+ `Minted '${ options . amount } ' ${ tokenPlurality } to ${ destination } ` ,
328+ ) ;
329+ console . log (
330+ " " ,
331+ getExplorerLink ( {
332+ cluster : parsedRpcUrl . cluster ,
333+ transaction : signature ,
334+ } ) ,
335+ ) ;
336+ } catch ( err ) {
337+ spinner . stop ( ) ;
338+ let title = "Failed to complete operation" ;
339+ let message = err ;
340+ let extraLog = null ;
341+
342+ if ( isSolanaError ( err ) ) {
343+ title = "SolanaError" ;
344+ message = err . message ;
345+ extraLog = err . context ;
346+ }
347+
348+ errorOutro ( message , title , extraLog ) ;
349+ }
314350 } ) ;
315351}
0 commit comments