@metaplex-foundation/umi-rpc-web3js
Version:
An RPC implementation relying on Solana's web3.js
292 lines (286 loc) • 10.5 kB
JavaScript
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