3

I have just started learning Solana and my first exercise is to build a script that can create a token and then create vault accounts for use on openbook market:

  1. Create token

    • create & initialize mint account
    • create metadata account v3 (metaplex)
    • create associated token account
    • mint to associated token account
    • revoke mint authority
  2. Create vault accounts for Openbook

    • create base vault account
    • create quote vault account
    • create token account for base vault account for the mint created in step 1
    • create token account for quote vault account for the mint created in step 1

Create Token

import {
  ComputeBudgetProgram,
  Connection,
  Keypair,
  SystemProgram,
  Transaction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
  MINT_SIZE,
  TOKEN_PROGRAM_ID,
  createAssociatedTokenAccountInstruction,
  createInitializeMintInstruction,
  createMintToInstruction,
  getAssociatedTokenAddress,
  getMinimumBalanceForRentExemptMint,
  createSetAuthorityInstruction,
  AuthorityType,
} from "@solana/spl-token";
import { createMetadataAccountV3 } from "@metaplex-foundation/mpl-token-metadata";
import { createSignerFromKeypair } from "@metaplex-foundation/umi";
import {
  fromWeb3JsKeypair,
  fromWeb3JsPublicKey,
  toWeb3JsLegacyTransaction,
} from "@metaplex-foundation/umi-web3js-adapters";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";

export const createToken = async ({
  connection,
  payer,
  mint,
  supply,
  decimals,
  metadata,
}: {
  connection: Connection;
  payer: Keypair;
  mint: Keypair;
  supply: number;
  decimals: number;
  metadata: { [key in string]: any };
}) => {
  const umi = createUmi(connection.rpcEndpoint);
  const umiKeypair = fromWeb3JsKeypair(payer);
  const umiSigner = createSignerFromKeypair(umi, umiKeypair);
  umi.identity = umiSigner;
  umi.payer = umiSigner;

  const associatedTokenAddress = await getAssociatedTokenAddress(
    mint.publicKey,
    payer.publicKey,
    false
  );

  const transaction = new Transaction();

  transaction.add(
    ComputeBudgetProgram.setComputeUnitLimit({
      units: 1_000_000,
    }),
    ComputeBudgetProgram.setComputeUnitPrice({
      microLamports: 1,
    }),
    SystemProgram.createAccount({
      fromPubkey: payer.publicKey,
      newAccountPubkey: mint.publicKey,
      space: MINT_SIZE,
      lamports: await getMinimumBalanceForRentExemptMint(connection),
      programId: TOKEN_PROGRAM_ID,
    }),
    createInitializeMintInstruction(
      mint.publicKey,
      decimals,
      payer.publicKey,
      null,
      TOKEN_PROGRAM_ID
    ),
    toWeb3JsLegacyTransaction(
      await createMetadataAccountV3(umi, {
        mint: fromWeb3JsPublicKey(mint.publicKey),
        mintAuthority: umiSigner,
        payer: umiSigner,
        updateAuthority: umiKeypair.publicKey,
        data: {
          name: metadata.name,
          symbol: metadata.symbol,
          uri: metadata.uri,
          sellerFeeBasisPoints: 0,
          creators: null,
          collection: null,
          uses: null,
        },
        isMutable: false,
        collectionDetails: null,
      }).buildAndSign(umi)
    ),
    createAssociatedTokenAccountInstruction(
      payer.publicKey,
      associatedTokenAddress,
      payer.publicKey,
      mint.publicKey
    ),
    createMintToInstruction(
      mint.publicKey,
      associatedTokenAddress,
      payer.publicKey,
      BigInt(supply) * BigInt(10 ** decimals)
    ),
    createSetAuthorityInstruction(mint.publicKey, payer.publicKey, AuthorityType.MintTokens, null)
  );

  return sendAndConfirmTransaction(connection, transaction, [payer, mint]);
};

Create Vaults

import {
  Connection,
  Keypair,
  PublicKey,
  SystemProgram,
  Transaction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
  TOKEN_PROGRAM_ID,
  createInitializeAccountInstruction,
  ACCOUNT_SIZE,
} from "@solana/spl-token";
import { MarketAccounts } from "../types";

export const createVaults = async ({
  connection,
  payer,
  mint,
  quote,
  marketAccounts,
  vaultOwner,
}: {
  connection: Connection;
  payer: Keypair;
  mint: PublicKey;
  quote: PublicKey;
  marketAccounts: MarketAccounts;
  vaultOwner: PublicKey;
}) => {
  const lamports = await connection.getMinimumBalanceForRentExemption(ACCOUNT_SIZE);
  const transaction = new Transaction();

  transaction.add(
    SystemProgram.createAccount({
      fromPubkey: payer.publicKey,
      newAccountPubkey: marketAccounts.baseVault.publicKey,
      space: ACCOUNT_SIZE,
      lamports,
      programId: TOKEN_PROGRAM_ID,
    }),
    SystemProgram.createAccount({
      fromPubkey: payer.publicKey,
      newAccountPubkey: marketAccounts.quoteVault.publicKey,
      space: ACCOUNT_SIZE,
      lamports,
      programId: TOKEN_PROGRAM_ID,
    }),
    createInitializeAccountInstruction(marketAccounts.baseVault.publicKey, mint, vaultOwner),
    createInitializeAccountInstruction(marketAccounts.quoteVault.publicKey, quote, vaultOwner)
  );

  return sendAndConfirmTransaction(connection, transaction, [
    payer,
    marketAccounts.baseVault,
    marketAccounts.quoteVault,
  ]);
};

Main Program

import "dotenv/config";
import { secretKey } from "../../secret.json";
import { supply, decimals, min_order_size, price_tick, metadata } from "../../config.json";

import { decode } from "bs58";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { createToken, createVaults, createMarket } from "../lib";
import { getVaultOwnerAndNonce } from "../utils/serum";

const main = async (connection: Connection) => {
  const payer = Keypair.fromSecretKey(new Uint8Array(decode(secretKey)));

  const mint = Keypair.generate();
  console.log(`Generated mint account: ${mint.publicKey}`);

  const tokenSignature = await createToken({
    connection,
    payer,
    mint,
    supply,
    decimals,
    metadata,
  });
  console.log(`Token created successfully: ${tokenSignature}`);

  const quoteMint = new PublicKey(process.env.DEVNET_QUOTE_MINT!);
  const marketProgramID = new PublicKey(process.env.DEVNET_MARKET_PROGRAM!);
  const marketAccounts = {
    market: Keypair.generate(),
    requestQueue: Keypair.generate(),
    eventQueue: Keypair.generate(),
    bids: Keypair.generate(),
    asks: Keypair.generate(),
    baseVault: Keypair.generate(),
    quoteVault: Keypair.generate(),
  };
  const [vaultOwner, vaultOwnerNonce] = getVaultOwnerAndNonce(
    marketAccounts.market.publicKey,
    marketProgramID
  );
  const vaultSignature = await createVaults({
    connection,
    payer,
    mint: mint.publicKey,
    quote: quoteMint,
    marketAccounts,
    vaultOwner,
  });
  console.log(`Vaults created successfully: ${vaultSignature}`);

  //   const marketSignature = await createMarket({
  //     connection,
  //     payer,
  //     mint: mint.publicKey,
  //     mintDecimals: decimals,
  //     quote: quoteMint,
  //     quoteDecimals: 9,
  //     marketAccounts,
  //     marketProgram,
  //     minOrderSize: min_order_size,
  //     priceTick: price_tick,
  //   });
  //   console.log(`Market created successfully: ${marketSignature}`);
};

try {
  main(
    new Connection(process.env.DEVNET_RPC_HTTP!, {
      wsEndpoint: process.env.DEVNET_RPC_WSS!,
    })
  );
} catch (err) {
  err && console.error(err);
}

Utils

export function getVaultOwnerAndNonce(
  marketAddress: PublicKey,
  dexAddress: PublicKey
): [vaultOwner: PublicKey, nonce: BN] {
  const nonce = new BN(0);
  // eslint-disable-next-line no-constant-condition
  while (true) {
    try {
      const vaultOwner = PublicKey.createProgramAddressSync(
        [marketAddress.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)],
        dexAddress
      );
      return [vaultOwner, nonce];
    } catch (e) {
      nonce.iaddn(1);
    }
  }
}

Problem

When running the main program, the createToken sequence runs fine and I get a valid signature. However the script fails when trying to send the transaction that creates the vaults: SendTransactionError: failed to send transaction: Transaction simulation failed: Error processing Instruction 2: incorrect program id for instruction.

Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]
Program log: Instruction: InitializeAccount
Program log: Error: IncorrectProgramId
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2928 of 799700 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA failed: incorrect program id for instruction

If I were to comment out the lines that generates the mint keypair and the token creation, and then explicitly specify a mint const mint = new PublicKey("<mint account created from first run>") the script runs fine.

It's as if the Connection.sendAndConfirmTransaction hasn't confirmed the initialise mint transaction yet and therefore the mint account hasn't been created yet?

Is there a way I can ensure the mint account from the first transaction is confirmed and ready to have token accounts created.

Constrain

Mint creation and vault creation needs to be separate sequential transactions (the vault token accounts need to be created after the mint has been created).

3 Answers 3

1

SMH! It's always the little things that get me. I tried to set the commitment value for the createToken method to finalized:

sendAndConfirmTransaction(connection, transaction, [payer, mint], {
  commitment: "finalized",
})

This fixed it. I'm not so sure why as the docs say the default commitment is max which I thought was an alias for finalized.

1
const mintPublicKey = await createMint(
  connection,
  payerKeypair, // Payer of the transaction
  mintAuthority, // Authority that can mint new tokens
  freezeAuthority, // Authority that can freeze token accounts
  decimals, // Decimals the token uses,
  undefined,
  {
    commitment: "finalized",
  }
);
0

TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA is the old Token Program ID

TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb is the new Token 2022 Program ID

In your frontend or Anchor tests, you must specify TOKEN_2022_PROGRAM_ID in the last function argument of the following:

createMint()
getOrCreateAssociatedTokenAccount()
mintTo()

Before if you don't, those above functions will default to use the old token program with the incorrect program id.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.