serum-dex-ui/src/utils/send.tsx

1021 lines
27 KiB
TypeScript

import { notify } from './notifications';
import { getDecimalCount, sleep } from './utils';
import { getSelectedTokenAccountForMint } from './markets';
import {
Account,
AccountInfo,
Commitment,
Connection,
PublicKey,
RpcResponseAndContext,
SimulatedTransactionResponse,
SystemProgram,
Transaction,
TransactionSignature,
} from '@solana/web3.js';
import {
Token,
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
} from '@solana/spl-token';
import BN from 'bn.js';
import {
DexInstructions,
Market,
OpenOrders,
parseInstructionErrorResponse,
TOKEN_MINTS,
TokenInstructions,
} from '@project-serum/serum';
import { SelectedTokenAccounts, TokenAccount } from './types';
import { Order } from '@project-serum/serum/lib/market';
import { Buffer } from 'buffer';
import assert from 'assert';
import { struct } from 'superstruct';
import {
BaseSignerWalletAdapter,
WalletAdapter,
} from '@solana/wallet-adapter-base';
export async function createTokenAccountTransaction({
connection,
wallet,
mintPublicKey,
}: {
connection: Connection;
wallet: WalletAdapter;
mintPublicKey: PublicKey;
}): Promise<{
transaction: Transaction;
newAccountPubkey: PublicKey;
}> {
assert(wallet.publicKey, 'Expected `publicKey` to be non-null');
const ata = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPublicKey,
wallet.publicKey,
);
const transaction = new Transaction();
transaction.add(
Token.createAssociatedTokenAccountInstruction(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPublicKey,
ata,
wallet.publicKey,
wallet.publicKey,
),
);
return {
transaction,
newAccountPubkey: ata,
};
}
export async function settleFunds({
market,
openOrders,
connection,
wallet,
baseCurrencyAccount,
quoteCurrencyAccount,
sendNotification = true,
usdcRef = undefined,
usdtRef = undefined,
}: {
market: Market;
openOrders: OpenOrders;
connection: Connection;
wallet: BaseSignerWalletAdapter;
baseCurrencyAccount: TokenAccount;
quoteCurrencyAccount: TokenAccount;
sendNotification?: boolean;
usdcRef?: PublicKey;
usdtRef?: PublicKey;
}): Promise<string | undefined> {
if (
!market ||
!wallet ||
!connection ||
!openOrders ||
(!baseCurrencyAccount && !quoteCurrencyAccount)
) {
if (sendNotification) {
notify({ message: 'Not connected' });
}
return;
}
let createAccountTransaction: Transaction | undefined;
let baseCurrencyAccountPubkey = baseCurrencyAccount?.pubkey;
let quoteCurrencyAccountPubkey = quoteCurrencyAccount?.pubkey;
if (!baseCurrencyAccountPubkey) {
const result = await createTokenAccountTransaction({
connection,
wallet,
mintPublicKey: market.baseMintAddress,
});
baseCurrencyAccountPubkey = result?.newAccountPubkey;
createAccountTransaction = result?.transaction;
}
if (!quoteCurrencyAccountPubkey) {
const result = await createTokenAccountTransaction({
connection,
wallet,
mintPublicKey: market.quoteMintAddress,
});
quoteCurrencyAccountPubkey = result?.newAccountPubkey;
createAccountTransaction = result?.transaction;
}
let referrerQuoteWallet: PublicKey | null = null;
if (market.supportsReferralFees) {
const usdt = TOKEN_MINTS.find(({ name }) => name === 'USDT');
const usdc = TOKEN_MINTS.find(({ name }) => name === 'USDC');
if (usdtRef && usdt && market.quoteMintAddress.equals(usdt.address)) {
referrerQuoteWallet = usdtRef;
} else if (
usdcRef &&
usdc &&
market.quoteMintAddress.equals(usdc.address)
) {
referrerQuoteWallet = usdcRef;
}
}
const {
transaction: settleFundsTransaction,
signers: settleFundsSigners,
} = await market.makeSettleFundsTransaction(
connection,
openOrders,
baseCurrencyAccountPubkey,
quoteCurrencyAccountPubkey,
referrerQuoteWallet,
);
let transaction = mergeTransactions([
createAccountTransaction,
settleFundsTransaction,
]);
return await sendTransaction({
transaction,
signers: settleFundsSigners,
wallet,
connection,
sendingMessage: 'Settling funds...',
sendNotification,
});
}
export async function settleAllFunds({
connection,
wallet,
tokenAccounts,
markets,
selectedTokenAccounts,
}: {
connection: Connection;
wallet: BaseSignerWalletAdapter;
tokenAccounts: TokenAccount[];
markets: Market[];
selectedTokenAccounts?: SelectedTokenAccounts;
}) {
if (!markets || !wallet || !connection || !tokenAccounts) {
return;
}
const programIds: PublicKey[] = [];
markets
.reduce((cumulative, m) => {
// @ts-ignore
cumulative.push(m._programId);
return cumulative;
}, [])
.forEach((programId) => {
if (!programIds.find((p) => p.equals(programId))) {
programIds.push(programId);
}
});
const getOpenOrdersAccountsForProgramId = async (programId) => {
assert(wallet.publicKey, 'Expected `publicKey` to be non-null');
const openOrdersAccounts = await OpenOrders.findForOwner(
connection,
wallet.publicKey,
programId,
);
return openOrdersAccounts.filter(
(openOrders) =>
openOrders.baseTokenFree.toNumber() ||
openOrders.quoteTokenFree.toNumber(),
);
};
const openOrdersAccountsForProgramIds = await Promise.all(
programIds.map((programId) => getOpenOrdersAccountsForProgramId(programId)),
);
const openOrdersAccounts = openOrdersAccountsForProgramIds.reduce(
(accounts, current) => accounts.concat(current),
[],
);
const settleTransactions = (
await Promise.all(
openOrdersAccounts.map((openOrdersAccount) => {
const market = markets.find((m) =>
// @ts-ignore
m._decoded?.ownAddress?.equals(openOrdersAccount.market),
);
if (
openOrdersAccount.baseTokenFree.isZero() &&
openOrdersAccount.quoteTokenFree.isZero()
) {
// nothing to settle for this market.
return null;
}
const baseMint = market?.baseMintAddress;
const quoteMint = market?.quoteMintAddress;
const selectedBaseTokenAccount = getSelectedTokenAccountForMint(
tokenAccounts,
baseMint,
baseMint &&
selectedTokenAccounts &&
selectedTokenAccounts[baseMint.toBase58()],
)?.pubkey;
const selectedQuoteTokenAccount = getSelectedTokenAccountForMint(
tokenAccounts,
quoteMint,
quoteMint &&
selectedTokenAccounts &&
selectedTokenAccounts[quoteMint.toBase58()],
)?.pubkey;
if (!selectedBaseTokenAccount || !selectedQuoteTokenAccount) {
return null;
}
return (
market &&
market.makeSettleFundsTransaction(
connection,
openOrdersAccount,
selectedBaseTokenAccount,
selectedQuoteTokenAccount,
)
);
}),
)
).filter(
(
x,
): x is {
signers: Account[];
transaction: Transaction;
payer: PublicKey;
} => !!x,
);
if (!settleTransactions || settleTransactions.length === 0) return;
const transactions = settleTransactions.slice(0, 4).map((t) => t.transaction);
const signers: Array<Account> = [];
settleTransactions
.reduce((cumulative: Array<Account>, t) => cumulative.concat(t.signers), [])
.forEach((signer) => {
if (!signers.find((s) => s.publicKey.equals(signer.publicKey))) {
signers.push(signer);
}
});
const transaction = mergeTransactions(transactions);
return await sendTransaction({
transaction,
signers,
wallet,
connection,
});
}
export async function cancelOrder(params: {
market: Market;
connection: Connection;
wallet: BaseSignerWalletAdapter;
order: Order;
}) {
return cancelOrders({ ...params, orders: [params.order] });
}
export async function cancelOrders({
market,
wallet,
connection,
orders,
}: {
market: Market;
wallet: BaseSignerWalletAdapter;
connection: Connection;
orders: Order[];
}) {
const transaction = market.makeMatchOrdersTransaction(5);
assert(wallet.publicKey, 'Expected `publicKey` to be non-null');
const publicKey = wallet.publicKey;
orders.forEach((order) => {
transaction.add(
market.makeCancelOrderInstruction(connection, publicKey, order),
);
});
transaction.add(market.makeMatchOrdersTransaction(5));
return await sendTransaction({
transaction,
wallet,
connection,
sendingMessage: 'Sending cancel...',
});
}
export async function placeOrder({
side,
price,
size,
orderType,
market,
connection,
wallet,
baseCurrencyAccount,
quoteCurrencyAccount,
feeDiscountPubkey = undefined,
}: {
side: 'buy' | 'sell';
price: number;
size: number;
orderType: 'ioc' | 'postOnly' | 'limit';
market: Market | undefined | null;
connection: Connection;
wallet: BaseSignerWalletAdapter;
baseCurrencyAccount: PublicKey | undefined;
quoteCurrencyAccount: PublicKey | undefined;
feeDiscountPubkey: PublicKey | undefined;
}) {
let formattedMinOrderSize =
market?.minOrderSize?.toFixed(getDecimalCount(market.minOrderSize)) ||
market?.minOrderSize;
let formattedTickSize =
market?.tickSize?.toFixed(getDecimalCount(market.tickSize)) ||
market?.tickSize;
const isIncrement = (num, step) =>
Math.abs((num / step) % 1) < 1e-5 ||
Math.abs(((num / step) % 1) - 1) < 1e-5;
if (isNaN(price)) {
notify({ message: 'Invalid price', type: 'error' });
return;
}
if (isNaN(size)) {
notify({ message: 'Invalid size', type: 'error' });
return;
}
if (!wallet || !wallet.publicKey) {
notify({ message: 'Connect wallet', type: 'error' });
return;
}
if (!market) {
notify({ message: 'Invalid market', type: 'error' });
return;
}
if (!isIncrement(size, market.minOrderSize)) {
notify({
message: `Size must be an increment of ${formattedMinOrderSize}`,
type: 'error',
});
return;
}
if (size < market.minOrderSize) {
notify({ message: 'Size too small', type: 'error' });
return;
}
if (!isIncrement(price, market.tickSize)) {
notify({
message: `Price must be an increment of ${formattedTickSize}`,
type: 'error',
});
return;
}
if (price < market.tickSize) {
notify({ message: 'Price under tick size', type: 'error' });
return;
}
const owner = wallet.publicKey;
const transaction = new Transaction();
const signers: Account[] = [];
if (!baseCurrencyAccount) {
const {
transaction: createAccountTransaction,
newAccountPubkey,
} = await createTokenAccountTransaction({
connection,
wallet,
mintPublicKey: market.baseMintAddress,
});
transaction.add(createAccountTransaction);
baseCurrencyAccount = newAccountPubkey;
}
if (!quoteCurrencyAccount) {
const {
transaction: createAccountTransaction,
newAccountPubkey,
} = await createTokenAccountTransaction({
connection,
wallet,
mintPublicKey: market.quoteMintAddress,
});
transaction.add(createAccountTransaction);
quoteCurrencyAccount = newAccountPubkey;
}
const payer = side === 'sell' ? baseCurrencyAccount : quoteCurrencyAccount;
if (!payer) {
notify({
message: 'Need an SPL token account for cost currency',
type: 'error',
});
return;
}
const params = {
owner,
payer,
side,
price,
size,
orderType,
feeDiscountPubkey: feeDiscountPubkey || null,
};
console.log(params);
const matchOrderstransaction = market.makeMatchOrdersTransaction(5);
transaction.add(matchOrderstransaction);
const startTime = getUnixTs();
let {
transaction: placeOrderTx,
signers: placeOrderSigners,
} = await market.makePlaceOrderTransaction(
connection,
params,
120_000,
120_000,
);
const endTime = getUnixTs();
console.log(`Creating order transaction took ${endTime - startTime}`);
transaction.add(placeOrderTx);
transaction.add(market.makeMatchOrdersTransaction(5));
signers.push(...placeOrderSigners);
return await sendTransaction({
transaction,
wallet,
connection,
signers,
sendingMessage: 'Sending order...',
});
}
export async function listMarket({
connection,
wallet,
baseMint,
quoteMint,
baseLotSize,
quoteLotSize,
dexProgramId,
}: {
connection: Connection;
wallet: BaseSignerWalletAdapter;
baseMint: PublicKey;
quoteMint: PublicKey;
baseLotSize: number;
quoteLotSize: number;
dexProgramId: PublicKey;
}) {
assert(wallet.publicKey, 'Expected `publicKey` to be non-null');
const market = new Account();
const requestQueue = new Account();
const eventQueue = new Account();
const bids = new Account();
const asks = new Account();
const baseVault = new Account();
const quoteVault = new Account();
const feeRateBps = 0;
const quoteDustThreshold = new BN(100);
async function getVaultOwnerAndNonce() {
const nonce = new BN(0);
while (true) {
try {
const vaultOwner = await PublicKey.createProgramAddress(
[market.publicKey.toBuffer(), nonce.toArrayLike(Buffer, 'le', 8)],
dexProgramId,
);
return [vaultOwner, nonce];
} catch (e) {
nonce.iaddn(1);
}
}
}
const [vaultOwner, vaultSignerNonce] = await getVaultOwnerAndNonce();
const tx1 = new Transaction();
tx1.add(
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: baseVault.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(165),
space: 165,
programId: TokenInstructions.TOKEN_PROGRAM_ID,
}),
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: quoteVault.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(165),
space: 165,
programId: TokenInstructions.TOKEN_PROGRAM_ID,
}),
TokenInstructions.initializeAccount({
account: baseVault.publicKey,
mint: baseMint,
owner: vaultOwner,
}),
TokenInstructions.initializeAccount({
account: quoteVault.publicKey,
mint: quoteMint,
owner: vaultOwner,
}),
);
const tx2 = new Transaction();
tx2.add(
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: market.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(
Market.getLayout(dexProgramId).span,
),
space: Market.getLayout(dexProgramId).span,
programId: dexProgramId,
}),
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: requestQueue.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12),
space: 5120 + 12,
programId: dexProgramId,
}),
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: eventQueue.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12),
space: 262144 + 12,
programId: dexProgramId,
}),
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: bids.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
space: 65536 + 12,
programId: dexProgramId,
}),
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: asks.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
space: 65536 + 12,
programId: dexProgramId,
}),
DexInstructions.initializeMarket({
market: market.publicKey,
requestQueue: requestQueue.publicKey,
eventQueue: eventQueue.publicKey,
bids: bids.publicKey,
asks: asks.publicKey,
baseVault: baseVault.publicKey,
quoteVault: quoteVault.publicKey,
baseMint,
quoteMint,
baseLotSize: new BN(baseLotSize),
quoteLotSize: new BN(quoteLotSize),
feeRateBps,
vaultSignerNonce,
quoteDustThreshold,
programId: dexProgramId,
authority: undefined,
}),
);
const signedTransactions = await signTransactions({
transactionsAndSigners: [
{ transaction: tx1, signers: [baseVault, quoteVault] },
{
transaction: tx2,
signers: [market, requestQueue, eventQueue, bids, asks],
},
],
wallet,
connection,
});
for (let signedTransaction of signedTransactions) {
await sendSignedTransaction({
signedTransaction,
connection,
});
}
return market.publicKey;
}
export const getUnixTs = () => {
return new Date().getTime() / 1000;
};
const DEFAULT_TIMEOUT = 15000;
export async function sendTransaction({
transaction,
wallet,
signers = [],
connection,
sendingMessage = 'Sending transaction...',
sentMessage = 'Transaction sent',
successMessage = 'Transaction confirmed',
timeout = DEFAULT_TIMEOUT,
sendNotification = true,
}: {
transaction: Transaction;
wallet: BaseSignerWalletAdapter;
signers?: Array<Account>;
connection: Connection;
sendingMessage?: string;
sentMessage?: string;
successMessage?: string;
timeout?: number;
sendNotification?: boolean;
}) {
const signedTransaction = await signTransaction({
transaction,
wallet,
signers,
connection,
});
return await sendSignedTransaction({
signedTransaction,
connection,
sendingMessage,
sentMessage,
successMessage,
timeout,
sendNotification,
});
}
export async function signTransaction({
transaction,
wallet,
signers = [],
connection,
}: {
transaction: Transaction;
wallet: BaseSignerWalletAdapter;
signers?: Array<Account>;
connection: Connection;
}) {
assert(wallet.publicKey, 'Expected `publicKey` to be non-null');
transaction.recentBlockhash = (
await connection.getRecentBlockhash('max')
).blockhash;
transaction.setSigners(wallet.publicKey, ...signers.map((s) => s.publicKey));
if (signers.length > 0) {
transaction.partialSign(...signers);
}
return await wallet.signTransaction(transaction);
}
export async function signTransactions({
transactionsAndSigners,
wallet,
connection,
}: {
transactionsAndSigners: {
transaction: Transaction;
signers?: Array<Account>;
}[];
wallet: BaseSignerWalletAdapter;
connection: Connection;
}) {
assert(wallet.publicKey, 'Expected `publicKey` to be non-null');
const publicKey = wallet.publicKey;
const blockhash = (await connection.getRecentBlockhash('max')).blockhash;
transactionsAndSigners.forEach(({ transaction, signers = [] }) => {
transaction.recentBlockhash = blockhash;
transaction.setSigners(publicKey, ...signers.map((s) => s.publicKey));
if (signers?.length > 0) {
transaction.partialSign(...signers);
}
});
return await wallet.signAllTransactions(
transactionsAndSigners.map(({ transaction }) => transaction),
);
}
export async function sendSignedTransaction({
signedTransaction,
connection,
sendingMessage = 'Sending transaction...',
sentMessage = 'Transaction sent',
successMessage = 'Transaction confirmed',
timeout = DEFAULT_TIMEOUT,
sendNotification = true,
}: {
signedTransaction: Transaction;
connection: Connection;
sendingMessage?: string;
sentMessage?: string;
successMessage?: string;
timeout?: number;
sendNotification?: boolean;
}): Promise<string> {
const rawTransaction = signedTransaction.serialize();
const startTime = getUnixTs();
if (sendNotification) {
notify({ message: sendingMessage });
}
const txid: TransactionSignature = await connection.sendRawTransaction(
rawTransaction,
{
skipPreflight: true,
},
);
if (sendNotification) {
notify({ message: sentMessage, type: 'success', txid });
}
console.log('Started awaiting confirmation for', txid);
let done = false;
(async () => {
while (!done && getUnixTs() - startTime < timeout) {
connection.sendRawTransaction(rawTransaction, {
skipPreflight: true,
});
await sleep(300);
}
})();
try {
await awaitTransactionSignatureConfirmation(txid, timeout, connection);
} catch (err) {
if (err.timeout) {
throw new Error('Timed out awaiting confirmation on transaction');
}
let simulateResult: SimulatedTransactionResponse | null = null;
try {
simulateResult = (
await simulateTransaction(connection, signedTransaction, 'single')
).value;
} catch (e) {}
if (simulateResult && simulateResult.err) {
if (simulateResult.logs) {
for (let i = simulateResult.logs.length - 1; i >= 0; --i) {
const line = simulateResult.logs[i];
if (line.startsWith('Program log: ')) {
throw new Error(
'Transaction failed: ' + line.slice('Program log: '.length),
);
}
}
}
let parsedError;
if (
typeof simulateResult.err == 'object' &&
'InstructionError' in simulateResult.err
) {
const parsedErrorInfo = parseInstructionErrorResponse(
signedTransaction,
simulateResult.err['InstructionError'],
);
parsedError = parsedErrorInfo.error;
} else {
parsedError = JSON.stringify(simulateResult.err);
}
throw new Error(parsedError);
}
throw new Error('Transaction failed');
} finally {
done = true;
}
if (sendNotification) {
notify({ message: successMessage, type: 'success', txid });
}
console.log('Latency', txid, getUnixTs() - startTime);
return txid;
}
async function awaitTransactionSignatureConfirmation(
txid: TransactionSignature,
timeout: number,
connection: Connection,
) {
let done = false;
const result = await new Promise((resolve, reject) => {
(async () => {
setTimeout(() => {
if (done) {
return;
}
done = true;
console.log('Timed out for txid', txid);
reject({ timeout: true });
}, timeout);
try {
connection.onSignature(
txid,
(result) => {
console.log('WS confirmed', txid, result);
done = true;
if (result.err) {
reject(result.err);
} else {
resolve(result);
}
},
'recent',
);
console.log('Set up WS connection', txid);
} catch (e) {
done = true;
console.log('WS error in setup', txid, e);
}
while (!done) {
// eslint-disable-next-line no-loop-func
(async () => {
try {
const signatureStatuses = await connection.getSignatureStatuses([
txid,
]);
const result = signatureStatuses && signatureStatuses.value[0];
if (!done) {
if (!result) {
console.log('REST null result for', txid, result);
} else if (result.err) {
console.log('REST error for', txid, result);
done = true;
reject(result.err);
} else if (!result.confirmations) {
console.log('REST no confirmations for', txid, result);
} else {
console.log('REST confirmation for', txid, result);
done = true;
resolve(result);
}
}
} catch (e) {
if (!done) {
console.log('REST connection error: txid', txid, e);
}
}
})();
await sleep(300);
}
})();
});
done = true;
return result;
}
function mergeTransactions(transactions: (Transaction | undefined)[]) {
const transaction = new Transaction();
transactions
.filter((t): t is Transaction => t !== undefined)
.forEach((t) => {
transaction.add(t);
});
return transaction;
}
function jsonRpcResult(resultDescription: any) {
const jsonRpcVersion = struct.literal('2.0');
return struct.union([
struct({
jsonrpc: jsonRpcVersion,
id: 'string',
error: 'any',
}),
struct({
jsonrpc: jsonRpcVersion,
id: 'string',
error: 'null?',
result: resultDescription,
}),
]);
}
function jsonRpcResultAndContext(resultDescription: any) {
return jsonRpcResult({
context: struct({
slot: 'number',
}),
value: resultDescription,
});
}
const AccountInfoResult = struct({
executable: 'boolean',
owner: 'string',
lamports: 'number',
data: 'any',
rentEpoch: 'number?',
});
export const GetMultipleAccountsAndContextRpcResult = jsonRpcResultAndContext(
struct.array([struct.union(['null', AccountInfoResult])]),
);
export async function getMultipleSolanaAccounts(
connection: Connection,
publicKeys: PublicKey[],
): Promise<
RpcResponseAndContext<{ [key: string]: AccountInfo<Buffer> | null }>
> {
const args = [publicKeys.map((k) => k.toBase58()), { commitment: 'recent' }];
// @ts-ignore
const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args);
const res = GetMultipleAccountsAndContextRpcResult(unsafeRes);
if (res.error) {
throw new Error(
'failed to get info about accounts ' +
publicKeys.map((k) => k.toBase58()).join(', ') +
': ' +
res.error.message,
);
}
assert(typeof res.result !== 'undefined');
const accounts: Array<{
executable: any;
owner: PublicKey;
lamports: any;
data: Buffer;
} | null> = [];
for (const account of res.result.value) {
let value: {
executable: any;
owner: PublicKey;
lamports: any;
data: Buffer;
} | null = null;
if (res.result.value) {
const { executable, owner, lamports, data } = account;
assert(data[1] === 'base64');
value = {
executable,
owner: new PublicKey(owner),
lamports,
data: Buffer.from(data[0], 'base64'),
};
}
accounts.push(value);
}
return {
context: {
slot: res.result.context.slot,
},
value: Object.fromEntries(
accounts.map((account, i) => [publicKeys[i].toBase58(), account]),
),
};
}
/** Copy of Connection.simulateTransaction that takes a commitment parameter. */
async function simulateTransaction(
connection: Connection,
transaction: Transaction,
commitment: Commitment,
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
// @ts-ignore
transaction.recentBlockhash = await connection._recentBlockhash(
// @ts-ignore
connection._disableBlockhashCaching,
);
const signData = transaction.serializeMessage();
// @ts-ignore
const wireTransaction = transaction._serialize(signData);
const encodedTransaction = wireTransaction.toString('base64');
const config: any = { encoding: 'base64', commitment };
const args = [encodedTransaction, config];
// @ts-ignore
const res = await connection._rpcRequest('simulateTransaction', args);
if (res.error) {
throw new Error('failed to simulate transaction: ' + res.error.message);
}
return res.result;
}