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:
Create token
- create & initialize mint account
- create metadata account v3 (metaplex)
- create associated token account
- mint to associated token account
- revoke mint authority
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).