UNPKG

@metaplex-foundation/umi-rpc-web3js

Version:

An RPC implementation relying on Solana's web3.js

292 lines (286 loc) 10.5 kB
import { resolveClusterFromEndpoint, dateTime, lamports, ACCOUNT_HEADER_SIZE, isZeroAmount, createAmount } from '@metaplex-foundation/umi'; import { toWeb3JsPublicKey, fromWeb3JsPublicKey, fromWeb3JsMessage, toWeb3JsTransaction } from '@metaplex-foundation/umi-web3js-adapters'; import { base58 } from '@metaplex-foundation/umi/serializers'; import { Connection, PublicKey } from '@solana/web3.js'; function createWeb3JsRpc(context, endpointOrConnection, rpcOptions) { let connection = null; const getConnection = () => { if (connection) { return connection; } if (typeof endpointOrConnection === 'string') { connection = new Connection(endpointOrConnection, rpcOptions); } else { connection = endpointOrConnection; } return connection; }; const cluster = resolveClusterFromEndpoint(getConnection().rpcEndpoint); const getAccount = async (publicKey, options = {}) => { const account = await getConnection().getAccountInfo(toWeb3JsPublicKey(publicKey), options); return parseMaybeAccount(account, publicKey); }; const getAccounts = async (publicKeys, options = {}) => { const accounts = await getConnection().getMultipleAccountsInfo(publicKeys.map(toWeb3JsPublicKey), options); return accounts.map((account, index) => parseMaybeAccount(account, publicKeys[index])); }; const getProgramAccounts = async (programId, options = {}) => { const accounts = await getConnection().getProgramAccounts(toWeb3JsPublicKey(programId), { ...options, filters: options.filters?.map(filter => parseDataFilter(filter)) }); return accounts.map(({ pubkey, account }) => parseAccount(account, fromWeb3JsPublicKey(pubkey))); }; const getBlockTime = async (slot, // eslint-disable-next-line @typescript-eslint/no-unused-vars _options = {}) => { const blockTime = await getConnection().getBlockTime(slot); return blockTime ? dateTime(blockTime) : null; }; const getBalance = async (publicKey, options = {}) => { const balanceInLamports = await getConnection().getBalance(toWeb3JsPublicKey(publicKey), options); return lamports(balanceInLamports); }; const getGenesisHash = async () => { const genesisHash = await getConnection().getGenesisHash(); return genesisHash; }; const getRent = async (bytes, options = {}) => { const rentFor = bytes => getConnection().getMinimumBalanceForRentExemption(bytes, options.commitment); if (options.includesHeaderBytes ?? false) { const headerRent = await rentFor(0); const rentPerByte = BigInt(headerRent) / BigInt(ACCOUNT_HEADER_SIZE); return lamports(rentPerByte * BigInt(bytes)); } return lamports(await rentFor(bytes)); }; const getLatestBlockhash = async (options = {}) => getConnection().getLatestBlockhash(options); const getTransaction = async (signature, options = {}) => { const response = await getConnection().getTransaction(base58.deserialize(signature)[0], { commitment: options.commitment, maxSupportedTransactionVersion: 0 }); if (!response) { return null; } if (!response.meta) { // TODO: Custom error. throw new Error('Transaction meta is missing.'); } const { transaction, meta } = response; const message = fromWeb3JsMessage(transaction.message); const mapPublicKey = key => fromWeb3JsPublicKey(new PublicKey(key)); const mapTokenBalance = tokenBalance => ({ accountIndex: tokenBalance.accountIndex, amount: createAmount(tokenBalance.uiTokenAmount.amount, 'splToken', tokenBalance.uiTokenAmount.decimals), mint: mapPublicKey(tokenBalance.mint), owner: tokenBalance.owner ? mapPublicKey(tokenBalance.owner) : null }); return { message, serializedMessage: context.transactions.serializeMessage(message), signatures: transaction.signatures.map(base58.serialize), meta: { fee: lamports(meta.fee), logs: meta.logMessages ?? [], preBalances: meta.preBalances.map(lamports), postBalances: meta.postBalances.map(lamports), preTokenBalances: (meta.preTokenBalances ?? []).map(mapTokenBalance), postTokenBalances: (meta.postTokenBalances ?? []).map(mapTokenBalance), innerInstructions: meta.innerInstructions?.map(inner => ({ index: inner.index, instructions: inner.instructions.map(instruction => ({ programIndex: instruction.programIdIndex, accountIndexes: instruction.accounts, data: base58.serialize(instruction.data) })) })) ?? null, loadedAddresses: { writable: (meta.loadedAddresses?.writable ?? []).map(fromWeb3JsPublicKey), readonly: (meta.loadedAddresses?.readonly ?? []).map(fromWeb3JsPublicKey) }, computeUnitsConsumed: meta.computeUnitsConsumed ? BigInt(meta.computeUnitsConsumed) : null, err: meta.err } }; }; const getSignatureStatuses = async (signatures, options = {}) => { const response = await getConnection().getSignatureStatuses(signatures.map(signature => base58.deserialize(signature)[0]), { searchTransactionHistory: options?.searchTransactionHistory ?? false }); return response.value.map(status => { if (!status) return null; return { slot: status.slot, confirmations: status.confirmations, error: status.err, commitment: status.confirmationStatus ?? null }; }); }; const accountExists = async (publicKey, options = {}) => !isZeroAmount(await getBalance(publicKey, options)); const airdrop = async (publicKey, amount, options = {}) => { const signature = await getConnection().requestAirdrop(toWeb3JsPublicKey(publicKey), Number(amount.basisPoints)); if (options.strategy) { await confirmTransaction(base58.serialize(signature), options); return; } await confirmTransaction(base58.serialize(signature), { ...options, strategy: { type: 'blockhash', ...(await getLatestBlockhash()) } }); }; const call = async (method, params, options = {}) => { const client = getConnection()._rpcClient; // Handle both array and object params const resolvedParams = Array.isArray(params) ? resolveCallParams([...params], options.commitment, options.extra) : resolveNamedCallParams(params, options.commitment, options.extra); return new Promise((resolve, reject) => { const callback = (error, response) => { if (error) { reject(error); } else { resolve(response.result); } }; if (options.id) { client.request(method, resolvedParams, options.id, callback); } else { client.request(method, resolvedParams, callback); } }); }; const sendTransaction = async (transaction, options = {}) => { try { const signature = await getConnection().sendRawTransaction(context.transactions.serialize(transaction), options); return base58.serialize(signature); } catch (error) { let resolvedError = null; if (error instanceof Error && 'logs' in error) { resolvedError = context.programs.resolveError(error, transaction); } throw resolvedError || error; } }; const simulateTransaction = async (transaction, options = {}) => { try { const tx = toWeb3JsTransaction(transaction); const result = await getConnection().simulateTransaction(tx, { sigVerify: options.verifySignatures, accounts: { addresses: options.accounts || [], encoding: 'base64' } }); return result.value; } catch (error) { let resolvedError = null; if (error instanceof Error && 'logs' in error) { resolvedError = context.programs.resolveError(error, transaction); } throw resolvedError || error; } }; const confirmTransaction = async (signature, options) => getConnection().confirmTransaction(parseConfirmStrategy(signature, options), options.commitment); return { getEndpoint: () => getConnection().rpcEndpoint, getCluster: () => cluster, getAccount, getAccounts, getProgramAccounts, getBlockTime, getGenesisHash, getBalance, getRent, getSlot: async (options = {}) => getConnection().getSlot(options), getLatestBlockhash, getTransaction, getSignatureStatuses, accountExists, airdrop, call, sendTransaction, simulateTransaction, confirmTransaction, get connection() { return getConnection(); } }; } function parseAccount(account, publicKey) { return { executable: account.executable, owner: fromWeb3JsPublicKey(account.owner), lamports: lamports(account.lamports), rentEpoch: account.rentEpoch ? BigInt(account.rentEpoch) : undefined, publicKey, data: new Uint8Array(account.data) }; } function parseMaybeAccount(account, publicKey) { return account ? { ...parseAccount(account, publicKey), exists: true } : { exists: false, publicKey }; } function parseDataFilter(filter) { if (!('memcmp' in filter)) return filter; const { bytes, ...rest } = filter.memcmp; return { memcmp: { ...rest, bytes: base58.deserialize(bytes)[0] } }; } function parseConfirmStrategy(signature, options) { if (options.strategy.type === 'blockhash') { return { ...options.strategy, signature: base58.deserialize(signature)[0] }; } return { ...options.strategy, signature: base58.deserialize(signature)[0], nonceAccountPubkey: toWeb3JsPublicKey(options.strategy.nonceAccountPubkey) }; } function resolveCallParams(args, commitment, extra) { if (!commitment && !extra) return args; let options = {}; if (commitment) options.commitment = commitment; if (extra) options = { ...options, ...extra }; args.push(options); return args; } function resolveNamedCallParams(params = {}, commitment, extra) { if (!commitment && !extra) return params; // Create a new object with all original parameters const result = { ...params }; // Add commitment and extra options directly into the params object if (commitment) result.commitment = commitment; if (extra) Object.assign(result, extra); return result; } export { createWeb3JsRpc }; //# sourceMappingURL=createWeb3JsRpc.mjs.map