cli: add support for custom spl-token mints (#388)

This commit is contained in:
Chris Heaney 2021-09-13 22:26:13 -04:00 committed by GitHub
parent 308fb29a7d
commit e9841d4bb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 12 deletions

View File

@ -21,6 +21,7 @@
},
"dependencies": {
"@project-serum/anchor": "^0.13.2",
"@solana/spl-token": "^0.1.8",
"arweave": "^1.10.16",
"bn.js": "^5.2.0",
"commander": "^8.1.0",

View File

@ -6,6 +6,7 @@ import * as anchor from '@project-serum/anchor';
import BN from 'bn.js';
import { fromUTF8Array, parsePrice } from './helpers/various';
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { PublicKey } from '@solana/web3.js';
import { CACHE_PATH, CONFIG_ARRAY_START, CONFIG_LINE_SIZE, EXTENSION_JSON, EXTENSION_PNG, } from './helpers/constants';
import { getCandyMachineAddress, loadAnchorProgram, loadWalletKey, } from './helpers/accounts';
@ -144,16 +145,54 @@ programCommand('verify')
});
programCommand('create_candy_machine')
.option('-p, --price <string>', 'SOL price', '1')
.option('-p, --price <string>', 'Price denominated in SOL or spl-token override', '1')
.option('-t, --spl-token <string>', 'SPL token used to price NFT mint. To use SOL leave this empty.')
.option('-t, --spl-token-account <string>', 'SPL token account that receives mint payments. Only required if spl-token is specified.')
.action(async (directory, cmd) => {
const { keypair, env, price, cacheName } = cmd.opts();
const { keypair, env, price, cacheName, splToken, splTokenAccount } = cmd.opts();
const lamports = parsePrice(price);
let parsedPrice = parsePrice(price);
const cacheContent = loadCache(cacheName, env);
const walletKeyPair = loadWalletKey(keypair);
const anchorProgram = await loadAnchorProgram(walletKeyPair, env);
let wallet = walletKeyPair.publicKey;
const remainingAccounts = [];
if (splToken || splTokenAccount) {
if (!splToken) {
throw new Error("If spl-token-account is set, spl-token must also be set")
}
const splTokenKey = new PublicKey(splToken);
const splTokenAccountKey = new PublicKey(splTokenAccount);
if (!splTokenAccount) {
throw new Error("If spl-token is set, spl-token-account must also be set")
}
const token = new Token(
anchorProgram.provider.connection,
splTokenKey,
TOKEN_PROGRAM_ID,
walletKeyPair
);
const mintInfo = await token.getMintInfo();
if (!mintInfo.isInitialized) {
throw new Error(`The specified spl-token is not initialized`);
}
const tokenAccount = await token.getAccountInfo(splTokenAccountKey);
if (!tokenAccount.isInitialized) {
throw new Error(`The specified spl-token-account is not initialized`);
}
if (!tokenAccount.mint.equals(splTokenKey)) {
throw new Error(`The spl-token-account's mint (${tokenAccount.mint.toString()}) does not match specified spl-token ${splTokenKey.toString()}`);
}
wallet = splTokenAccountKey;
parsedPrice = parsePrice(price, 10 ** mintInfo.decimals);
remainingAccounts.push({ pubkey: splTokenKey, isWritable: false, isSigner: false });
}
const config = new PublicKey(cacheContent.program.config);
const [candyMachine, bump] = await getCandyMachineAddress(
config,
@ -163,14 +202,14 @@ programCommand('create_candy_machine')
bump,
{
uuid: cacheContent.program.uuid,
price: new anchor.BN(lamports),
price: new anchor.BN(parsedPrice),
itemsAvailable: new anchor.BN(Object.keys(cacheContent.items).length),
goLiveDate: null,
},
{
accounts: {
candyMachine,
wallet: walletKeyPair.publicKey,
wallet,
config: config,
authority: walletKeyPair.publicKey,
payer: walletKeyPair.publicKey,
@ -178,6 +217,7 @@ programCommand('create_candy_machine')
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [],
remainingAccounts,
},
);
@ -215,12 +255,14 @@ programCommand('set_start_date')
});
programCommand('mint_one_token')
.option('-t, --spl-token-account <string>', 'SPL token account to payfrom')
.action(async (directory, cmd) => {
const {keypair, env, cacheName} = cmd.opts();
const {keypair, env, cacheName, splTokenAccount} = cmd.opts();
const cacheContent = loadCache(cacheName, env);
const configAddress = new PublicKey(cacheContent.program.config);
const tx = await mint(keypair, env, configAddress);
const splTokenAccountKey = splTokenAccount ? new PublicKey(splTokenAccount) : undefined;
const tx = await mint(keypair, env, configAddress, splTokenAccountKey);
log.info('Done', tx);
});

View File

@ -13,7 +13,7 @@ import * as anchor from "@project-serum/anchor";
import { MintLayout, Token } from "@solana/spl-token";
import { createAssociatedTokenAccountInstruction } from "../helpers/instructions";
export async function mint(keypair: string, env: string, configAddress: PublicKey): Promise<string> {
export async function mint(keypair: string, env: string, configAddress: PublicKey, splTokenAccountKey?: PublicKey): Promise<string> {
const mint = Keypair.generate();
const userKeyPair = loadWalletKey(keypair);
@ -28,10 +28,36 @@ export async function mint(keypair: string, env: string, configAddress: PublicKe
configAddress,
uuid,
);
const candyMachine = await anchorProgram.account.candyMachine.fetch(
const candyMachine : any = await anchorProgram.account.candyMachine.fetch(
candyMachineAddress,
);
const remainingAccounts = [];
if (splTokenAccountKey) {
const candyMachineTokenMintKey = candyMachine.tokenMint;
if (!candyMachineTokenMintKey) {
throw new Error('Candy machine data does not have token mint configured. Can\'t use spl-token-account');
}
const token = new Token(
anchorProgram.provider.connection,
candyMachine.tokenMint,
TOKEN_PROGRAM_ID,
userKeyPair
);
const tokenAccount = await token.getAccountInfo(splTokenAccountKey);
if (!candyMachine.tokenMint.equals(tokenAccount.mint)) {
throw new Error(`Specified spl-token-account's mint (${tokenAccount.mint.toString()}) does not match candy machine's token mint (${candyMachine.tokenMint.toString()})`);
}
if (!tokenAccount.owner.equals(userKeyPair.publicKey)) {
throw new Error(`Specified spl-token-account's owner (${tokenAccount.owner.toString()}) does not match user public key (${userKeyPair.publicKey})`);
}
remainingAccounts.push({ pubkey: splTokenAccountKey, isWritable: true, isSigner: false });
remainingAccounts.push({ pubkey: userKeyPair.publicKey, isWritable: false, isSigner: true });
}
const metadataAddress = await getMetadata(mint.publicKey);
const masterEdition = await getMasterEdition(mint.publicKey);
return await anchorProgram.rpc.mintNft({
@ -53,6 +79,7 @@ export async function mint(keypair: string, env: string, configAddress: PublicKe
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
},
signers: [mint, userKeyPair],
remainingAccounts,
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: userKeyPair.publicKey,

View File

@ -48,6 +48,6 @@ export function fromUTF8Array(data: number[]) {
return str;
}
export function parsePrice(price) {
return Math.ceil(parseFloat(price) * LAMPORTS_PER_SOL);
export function parsePrice(price: string, mantissa: number = LAMPORTS_PER_SOL) {
return Math.ceil(parseFloat(price) * mantissa);
}

View File

@ -1818,6 +1818,18 @@
buffer-layout "^1.2.0"
dotenv "10.0.0"
"@solana/spl-token@^0.1.8":
version "0.1.8"
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.1.8.tgz#f06e746341ef8d04165e21fc7f555492a2a0faa6"
integrity sha512-LZmYCKcPQDtJgecvWOgT/cnoIQPWjdH+QVyzPcFvyDUiT0DiRjZaam4aqNUyvchLFhzgunv3d9xOoyE34ofdoQ==
dependencies:
"@babel/runtime" "^7.10.5"
"@solana/web3.js" "^1.21.0"
bn.js "^5.1.0"
buffer "6.0.3"
buffer-layout "^1.2.0"
dotenv "10.0.0"
"@solana/wallet-adapter-base@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.4.1.tgz#3264220c7eef5abaf7eca0e77cc51403f4f0fcf6"
@ -7981,7 +7993,7 @@ log-update@^4.0.0:
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
loglevel@^1.6.8:
loglevel@^1.6.8, loglevel@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==