From 31dc44eaaaaf06016ecd904d11bf05f31de10670 Mon Sep 17 00:00:00 2001 From: jarry-xiao Date: Fri, 24 Jun 2022 17:31:55 -0400 Subject: [PATCH] feat(cli): Add Gumball Machine CLI --- .../sdk/gumball-machine/instructions/index.ts | 51 +++++++++---------- contracts/sdk/utils/index.ts | 41 +++++++++++---- contracts/tests/bubblegum-test.ts | 4 +- contracts/tests/gumball-machine-test.ts | 39 +++++++------- contracts/tests/gummyroll-test.ts | 3 +- contracts/tests/sugar-shack-test.ts | 8 +-- contracts/tests/utils.ts | 31 ----------- 7 files changed, 83 insertions(+), 94 deletions(-) diff --git a/contracts/sdk/gumball-machine/instructions/index.ts b/contracts/sdk/gumball-machine/instructions/index.ts index 9554fba459a..a817d8bffff 100644 --- a/contracts/sdk/gumball-machine/instructions/index.ts +++ b/contracts/sdk/gumball-machine/instructions/index.ts @@ -15,6 +15,8 @@ import { createInitializeGumballMachineInstruction, createDispenseNftSolInstruction, createDispenseNftTokenInstruction, + DispenseNftSolInstructionArgs, + DispenseNftTokenInstructionArgs } from "../src/generated"; import { getWillyWonkaPDAKey } from "../utils"; import { CANDY_WRAPPER_PROGRAM_ID } from "../../utils"; @@ -31,6 +33,7 @@ export async function createInitializeGumballMachineIxs( merkleRollAccountSize: number, gumballMachineInitArgs: InitializeGumballMachineInstructionArgs, mint: PublicKey, + creator: PublicKey, gummyrollProgramId: PublicKey, bubblegumProgramId: PublicKey, gumballMachine: Program @@ -68,7 +71,7 @@ export async function createInitializeGumballMachineIxs( const initGumballMachineInstr = createInitializeGumballMachineInstruction( { gumballMachine: gumballMachineAcctKeypair.publicKey, - creator: payer.publicKey, + creator, mint, willyWonka: willyWonkaPDAKey, bubblegumAuthority: bubblegumAuthorityPDAKey, @@ -90,26 +93,26 @@ export async function createInitializeGumballMachineIxs( * Wrapper on top of Solita's createDispenseNftSolInstruction. Automatically fetches necessary PDA keys for instruction * */ export async function createDispenseNFTForSolIx( - numNFTs: BN, - payer: Keypair, + args: DispenseNftSolInstructionArgs, + payer: PublicKey, receiver: PublicKey, - gumballMachineAcctKeypair: Keypair, - merkleRollKeypair: Keypair, + gumballMachinePubkey: PublicKey, + merkleRollPubkey: PublicKey, gummyrollProgramId: PublicKey, bubblegumProgramId: PublicKey, gumballMachine: Program ): Promise { const willyWonkaPDAKey = await getWillyWonkaPDAKey( - gumballMachineAcctKeypair.publicKey, + gumballMachinePubkey, gumballMachine.programId ); const bubblegumAuthorityPDAKey = await getBubblegumAuthorityPDA( - merkleRollKeypair.publicKey, + merkleRollPubkey, ); const dispenseInstr = createDispenseNftSolInstruction( { - gumballMachine: gumballMachineAcctKeypair.publicKey, - payer: payer.publicKey, + gumballMachine: gumballMachinePubkey, + payer, receiver: receiver, willyWonka: willyWonkaPDAKey, recentBlockhashes: SYSVAR_SLOT_HASHES_PUBKEY, @@ -117,12 +120,10 @@ export async function createDispenseNFTForSolIx( bubblegumAuthority: bubblegumAuthorityPDAKey, candyWrapper: CANDY_WRAPPER_PROGRAM_ID, gummyroll: gummyrollProgramId, - merkleSlab: merkleRollKeypair.publicKey, + merkleSlab: merkleRollPubkey, bubblegum: bubblegumProgramId, }, - { - numItems: numNFTs, - } + args ); return dispenseInstr; } @@ -131,27 +132,27 @@ export async function createDispenseNFTForSolIx( * Wrapper on top of Solita's createDispenseNftTokenInstruction. Automatically fetches necessary PDA keys for instruction * */ export async function createDispenseNFTForTokensIx( - numNFTs: BN, - payer: Keypair, + args: DispenseNftTokenInstructionArgs, + payer: PublicKey, payerTokens: PublicKey, receiver: PublicKey, - gumballMachineAcctKeypair: Keypair, - merkleRollKeypair: Keypair, + gumballMachinePubkey: PublicKey, + merkleRollPubkey: PublicKey, gummyrollProgramId: PublicKey, bubblegumProgramId: PublicKey, gumballMachine: Program, ): Promise { const willyWonkaPDAKey = await getWillyWonkaPDAKey( - gumballMachineAcctKeypair.publicKey, + gumballMachinePubkey, gumballMachine.programId ); const bubblegumAuthorityPDAKey = await getBubblegumAuthorityPDA( - merkleRollKeypair.publicKey, + merkleRollPubkey, ); const dispenseInstr = createDispenseNftTokenInstruction( { - gumballMachine: gumballMachineAcctKeypair.publicKey, - payer: payer.publicKey, + gumballMachine: gumballMachinePubkey, + payer, payerTokens, receiver, willyWonka: willyWonkaPDAKey, @@ -160,12 +161,10 @@ export async function createDispenseNFTForTokensIx( bubblegumAuthority: bubblegumAuthorityPDAKey, candyWrapper: CANDY_WRAPPER_PROGRAM_ID, gummyroll: gummyrollProgramId, - merkleSlab: merkleRollKeypair.publicKey, + merkleSlab: merkleRollPubkey, bubblegum: bubblegumProgramId, }, - { - numItems: numNFTs, - } + args ); return dispenseInstr; -} +} \ No newline at end of file diff --git a/contracts/sdk/utils/index.ts b/contracts/sdk/utils/index.ts index a109e4e8c5e..e33e1a1831a 100644 --- a/contracts/sdk/utils/index.ts +++ b/contracts/sdk/utils/index.ts @@ -1,10 +1,39 @@ -import { PublicKey } from "@solana/web3.js"; +import { PublicKey, TransactionInstruction, Transaction, Signer } from "@solana/web3.js"; import * as borsh from "borsh"; import { bignum } from "@metaplex-foundation/beet"; -import { BN } from "@project-serum/anchor"; +import { BN, Provider } from "@project-serum/anchor"; export const CANDY_WRAPPER_PROGRAM_ID = new PublicKey("WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh"); +/// Wait for a transaction of a certain id to confirm and optionally log its messages +export async function logTx(provider: Provider, txId: string, verbose: boolean = true) { + await provider.connection.confirmTransaction(txId, "confirmed"); + if (verbose) { + console.log( + (await provider.connection.getConfirmedTransaction(txId, "confirmed")).meta + .logMessages + ); + } +}; + +/// Execute a series of instructions in a txn +export async function execute( + provider: Provider, + instructions: TransactionInstruction[], + signers: Signer[], + skipPreflight: boolean = false, + verbose: boolean = false, +): Promise { + let tx = new Transaction(); + instructions.map((ix) => { tx = tx.add(ix) }); + const txid = await provider.send(tx, signers, { + commitment: "confirmed", + skipPreflight, + }); + await logTx(provider, txid, verbose); + return txid; +} + export function readPublicKey(reader: borsh.BinaryReader): PublicKey { return new PublicKey(reader.readFixedArray(32)); } @@ -31,11 +60,3 @@ export function strToByteUint8Array(str: string): Uint8Array { [...str].reduce((acc, c, ind) => acc.concat([str.charCodeAt(ind)]), []) ); } - -export async function getBubblegumAuthorityPDAKey(merkleRollPubKey: PublicKey, bubblegumProgramId: PublicKey) { - const [bubblegumAuthorityPDAKey] = await PublicKey.findProgramAddress( - [merkleRollPubKey.toBuffer()], - bubblegumProgramId - ); - return bubblegumAuthorityPDAKey; -} diff --git a/contracts/tests/bubblegum-test.ts b/contracts/tests/bubblegum-test.ts index a001e5ab496..b62f53cd2e0 100644 --- a/contracts/tests/bubblegum-test.ts +++ b/contracts/tests/bubblegum-test.ts @@ -38,9 +38,9 @@ import { TOKEN_PROGRAM_ID, Token, } from "@solana/spl-token"; -import { execute, logTx, bufferToArray } from "./utils"; +import { bufferToArray } from "./utils"; import { TokenProgramVersion, Version } from "../sdk/bubblegum/src/generated"; -import { CANDY_WRAPPER_PROGRAM_ID } from "../sdk/utils"; +import { CANDY_WRAPPER_PROGRAM_ID, execute, logTx } from "../sdk/utils"; import { getBubblegumAuthorityPDA, getCreateTreeIxs, getNonceCount, getVoucherPDA } from "../sdk/bubblegum/src/convenience"; // @ts-ignore diff --git a/contracts/tests/gumball-machine-test.ts b/contracts/tests/gumball-machine-test.ts index 17ac576e554..970ae446e04 100644 --- a/contracts/tests/gumball-machine-test.ts +++ b/contracts/tests/gumball-machine-test.ts @@ -37,7 +37,7 @@ import { val, strToByteArray, strToByteUint8Array, - getBubblegumAuthorityPDAKey + logTx } from "../sdk/utils/index"; import { GumballMachineHeader, @@ -51,7 +51,7 @@ import { getAccount, } from "../../deps/solana-program-library/token/js/src"; import { NATIVE_MINT } from "@solana/spl-token"; -import { logTx, num32ToBuffer, arrayEquals } from "./utils"; +import { num32ToBuffer, arrayEquals } from "./utils"; import { EncodeMethod } from "../sdk/gumball-machine/src/generated/types/EncodeMethod"; import { getBubblegumAuthorityPDA } from "../sdk/bubblegum/src/convenience"; @@ -211,6 +211,7 @@ describe("gumball-machine", () => { merkleRollAccountSize, gumballMachineInitArgs, mint, + payer.publicKey, GummyrollProgramId, BubblegumProgramId, GumballMachine @@ -413,11 +414,11 @@ describe("gumball-machine", () => { verbose?: boolean ) { const dispenseInstr = await createDispenseNFTForSolIx( - numNFTs, - payer, + { numItems: numNFTs }, + payer.publicKey, receiver, - gumballMachineAcctKeypair, - merkleRollKeypair, + gumballMachineAcctKeypair.publicKey, + merkleRollKeypair.publicKey, GummyrollProgramId, BubblegumProgramId, GumballMachine @@ -441,12 +442,12 @@ describe("gumball-machine", () => { verbose?: boolean ) { const dispenseInstr = await createDispenseNFTForTokensIx( - numNFTs, - payer, + { numItems: numNFTs }, + payer.publicKey, payerTokens, receiver, - gumballMachineAcctKeypair, - merkleRollKeypair, + gumballMachineAcctKeypair.publicKey, + merkleRollKeypair.publicKey, GummyrollProgramId, BubblegumProgramId, GumballMachine @@ -542,7 +543,7 @@ describe("gumball-machine", () => { botWallet: Keypair.generate().publicKey, receiver: creatorPaymentWallet.publicKey, authority: creatorAddress.publicKey, - collectionKey: SystemProgram.programId, // 0x0 -> no collection key + collectionKey: SystemProgram.programId, extensionLen: new BN(28), maxMintSize: new BN(10), maxItems: new BN(250), @@ -603,11 +604,11 @@ describe("gumball-machine", () => { beforeEach(async () => { dispenseNFTForSolInstr = await createDispenseNFTForSolIx( - new BN(1), - nftBuyer, + { numItems: new BN(1) }, + nftBuyer.publicKey, baseGumballMachineInitProps.receiver, - gumballMachineAcctKeypair, - merkleRollKeypair, + gumballMachineAcctKeypair.publicKey, + merkleRollKeypair.publicKey, GummyrollProgramId, BubblegumProgramId, GumballMachine @@ -887,12 +888,12 @@ describe("gumball-machine", () => { beforeEach(async () => { dispenseNFTForTokensInstr = await createDispenseNFTForTokensIx( - new BN(1), - nftBuyer, + { numItems: new BN(1) }, + nftBuyer.publicKey, nftBuyerTokenAccount.address, creatorReceiverTokenAccount.address, - gumballMachineAcctKeypair, - merkleRollKeypair, + gumballMachineAcctKeypair.publicKey, + merkleRollKeypair.publicKey, GummyrollProgramId, BubblegumProgramId, GumballMachine diff --git a/contracts/tests/gummyroll-test.ts b/contracts/tests/gummyroll-test.ts index 083ce1c1689..2e47ae0e992 100644 --- a/contracts/tests/gummyroll-test.ts +++ b/contracts/tests/gummyroll-test.ts @@ -30,8 +30,7 @@ import { assertOnChainMerkleRollProperties, createAllocTreeIx, } from "../sdk/gummyroll"; -import { execute, logTx } from "./utils"; -import NodeWallet from "@project-serum/anchor/dist/cjs/nodewallet"; +import { execute, logTx } from "../sdk/utils"; import { CANDY_WRAPPER_PROGRAM_ID } from "../sdk/utils"; // @ts-ignore diff --git a/contracts/tests/sugar-shack-test.ts b/contracts/tests/sugar-shack-test.ts index bdda3eb4c93..d7490b018ae 100644 --- a/contracts/tests/sugar-shack-test.ts +++ b/contracts/tests/sugar-shack-test.ts @@ -30,8 +30,7 @@ import { getListingPDAKeyForPrice } from "../sdk/sugar-shack"; import { - CANDY_WRAPPER_PROGRAM_ID, - getBubblegumAuthorityPDAKey + CANDY_WRAPPER_PROGRAM_ID } from "../sdk/utils/index"; import { MetadataArgs, @@ -52,9 +51,10 @@ import { TreeNode, } from "./merkle-tree"; import NodeWallet from "@project-serum/anchor/dist/cjs/nodewallet"; -import { execute, logTx, bufferToArray } from "./utils"; +import { bufferToArray } from "./utils"; import { TokenProgramVersion, Version } from "../sdk/bubblegum/src/generated"; import { SugarShack } from "../target/types/sugar_shack"; +import { getBubblegumAuthorityPDA } from "../sdk/bubblegum/src/convenience"; // @ts-ignore let BubblegumProgramId; @@ -296,7 +296,7 @@ describe("sugar-shack", () => { payer.publicKey, merkleRollKeypair.publicKey, ) - bubblegumAuthority = await getBubblegumAuthorityPDAKey(merkleRollKeypair.publicKey, BubblegumProgramId); + bubblegumAuthority = await getBubblegumAuthorityPDA(merkleRollKeypair.publicKey); // Instruction to create merkle tree for compressed NFTs through Bubblegum const createCompressedNFTTreeIx = createCreateTreeInstruction( diff --git a/contracts/tests/utils.ts b/contracts/tests/utils.ts index b411c99f824..778cfa4d861 100644 --- a/contracts/tests/utils.ts +++ b/contracts/tests/utils.ts @@ -1,34 +1,3 @@ -import { Provider } from "@project-serum/anchor"; -import { TransactionInstruction, Transaction, Signer } from "@solana/web3.js"; - -/// Wait for a transaction of a certain id to confirm and optionally log its messages -export async function logTx(provider: Provider, txId: string, verbose: boolean = true) { - await provider.connection.confirmTransaction(txId, "confirmed"); - if (verbose) { - console.log( - (await provider.connection.getConfirmedTransaction(txId, "confirmed")).meta - .logMessages - ); - } -}; - -/// Execute a series of instructions in a txn -export async function execute( - provider: Provider, - instructions: TransactionInstruction[], - signers: Signer[], - skipPreflight: boolean = false -): Promise { - let tx = new Transaction(); - instructions.map((ix) => { tx = tx.add(ix) }); - const txid = await provider.send(tx, signers, { - commitment: "confirmed", - skipPreflight, - }); - await logTx(provider, txid, false); - return txid; -} - /// Convert a 32 bit number to a buffer of bytes export function num32ToBuffer(num: number) { const isU32 = (num >= 0 && num < Math.pow(2,32));