UNPKG

@mysten/sui

Version:
681 lines (679 loc) 20 kB
import { normalizeSuiNSName } from "../utils/suins.mjs"; import { isValidNamedPackage } from "../utils/move-registry.mjs"; import { isValidSuiAddress, isValidSuiObjectId, isValidTransactionDigest, normalizeSuiAddress, normalizeSuiObjectId } from "../utils/sui-types.mjs"; import { BaseClient } from "../client/client.mjs"; import { hasMvrName } from "../client/mvr.mjs"; import { JsonRpcHTTPTransport } from "./http-transport.mjs"; import { isCoinReservationDigest } from "../utils/coin-reservation.mjs"; import { isTransaction } from "../transactions/Transaction.mjs"; import { JSONRpcCoreClient } from "./core.mjs"; import { fromBase58, toBase64, toHex } from "@mysten/bcs"; //#region src/jsonRpc/client.ts const SUI_CLIENT_BRAND = Symbol.for("@mysten/SuiJsonRpcClient"); function isSuiJsonRpcClient(client) { return typeof client === "object" && client !== null && client[SUI_CLIENT_BRAND] === true; } var SuiJsonRpcClient = class extends BaseClient { get [SUI_CLIENT_BRAND]() { return true; } /** * Establish a connection to a Sui RPC endpoint * * @param options configuration options for the API Client */ constructor(options) { super({ network: options.network }); this.jsonRpc = this; this.transport = options.transport ?? new JsonRpcHTTPTransport({ url: options.url }); this.core = new JSONRpcCoreClient({ jsonRpcClient: this, mvr: options.mvr }); } async getRpcApiVersion({ signal } = {}) { return (await this.transport.request({ method: "rpc.discover", params: [], signal })).info.version; } /** * Get all Coin<`coin_type`> objects owned by an address. */ async getCoins({ coinType, owner, cursor, limit, signal }) { if (!owner || !isValidSuiAddress(normalizeSuiAddress(owner))) throw new Error("Invalid Sui address"); if (coinType && hasMvrName(coinType)) coinType = (await this.core.mvr.resolveType({ type: coinType })).type; const result = await this.transport.request({ method: "suix_getCoins", params: [ owner, coinType, cursor, limit ], signal }); return { ...result, data: result.data.filter((coin) => !isCoinReservationDigest(coin.digest)) }; } /** * Get all Coin objects owned by an address. */ async getAllCoins(input) { if (!input.owner || !isValidSuiAddress(normalizeSuiAddress(input.owner))) throw new Error("Invalid Sui address"); const result = await this.transport.request({ method: "suix_getAllCoins", params: [ input.owner, input.cursor, input.limit ], signal: input.signal }); return { ...result, data: result.data.filter((coin) => !isCoinReservationDigest(coin.digest)) }; } /** * Get the total coin balance for one coin type, owned by the address owner. */ async getBalance({ owner, coinType, signal }) { if (!owner || !isValidSuiAddress(normalizeSuiAddress(owner))) throw new Error("Invalid Sui address"); if (coinType && hasMvrName(coinType)) coinType = (await this.core.mvr.resolveType({ type: coinType })).type; return await this.transport.request({ method: "suix_getBalance", params: [owner, coinType], signal }); } /** * Get the total coin balance for all coin types, owned by the address owner. */ async getAllBalances(input) { if (!input.owner || !isValidSuiAddress(normalizeSuiAddress(input.owner))) throw new Error("Invalid Sui address"); return await this.transport.request({ method: "suix_getAllBalances", params: [input.owner], signal: input.signal }); } /** * Fetch CoinMetadata for a given coin type */ async getCoinMetadata({ coinType, signal }) { if (coinType && hasMvrName(coinType)) coinType = (await this.core.mvr.resolveType({ type: coinType })).type; return await this.transport.request({ method: "suix_getCoinMetadata", params: [coinType], signal }); } /** * Fetch total supply for a coin */ async getTotalSupply({ coinType, signal }) { if (coinType && hasMvrName(coinType)) coinType = (await this.core.mvr.resolveType({ type: coinType })).type; return await this.transport.request({ method: "suix_getTotalSupply", params: [coinType], signal }); } /** * Invoke any RPC method * @param method the method to be invoked * @param args the arguments to be passed to the RPC request */ async call(method, params, { signal } = {}) { return await this.transport.request({ method, params, signal }); } /** * Get Move function argument types like read, write and full access */ async getMoveFunctionArgTypes({ package: pkg, module, function: fn, signal }) { if (pkg && isValidNamedPackage(pkg)) pkg = (await this.core.mvr.resolvePackage({ package: pkg })).package; return await this.transport.request({ method: "sui_getMoveFunctionArgTypes", params: [ pkg, module, fn ], signal }); } /** * Get a map from module name to * structured representations of Move modules */ async getNormalizedMoveModulesByPackage({ package: pkg, signal }) { if (pkg && isValidNamedPackage(pkg)) pkg = (await this.core.mvr.resolvePackage({ package: pkg })).package; return await this.transport.request({ method: "sui_getNormalizedMoveModulesByPackage", params: [pkg], signal }); } /** * Get a structured representation of Move module */ async getNormalizedMoveModule({ package: pkg, module, signal }) { if (pkg && isValidNamedPackage(pkg)) pkg = (await this.core.mvr.resolvePackage({ package: pkg })).package; return await this.transport.request({ method: "sui_getNormalizedMoveModule", params: [pkg, module], signal }); } /** * Get a structured representation of Move function */ async getNormalizedMoveFunction({ package: pkg, module, function: fn, signal }) { if (pkg && isValidNamedPackage(pkg)) pkg = (await this.core.mvr.resolvePackage({ package: pkg })).package; return await this.transport.request({ method: "sui_getNormalizedMoveFunction", params: [ pkg, module, fn ], signal }); } /** * Get a structured representation of Move struct */ async getNormalizedMoveStruct({ package: pkg, module, struct, signal }) { if (pkg && isValidNamedPackage(pkg)) pkg = (await this.core.mvr.resolvePackage({ package: pkg })).package; return await this.transport.request({ method: "sui_getNormalizedMoveStruct", params: [ pkg, module, struct ], signal }); } /** * Get all objects owned by an address */ async getOwnedObjects(input) { if (!input.owner || !isValidSuiAddress(normalizeSuiAddress(input.owner))) throw new Error("Invalid Sui address"); const filter = input.filter ? { ...input.filter } : void 0; if (filter && "MoveModule" in filter && isValidNamedPackage(filter.MoveModule.package)) filter.MoveModule = { module: filter.MoveModule.module, package: (await this.core.mvr.resolvePackage({ package: filter.MoveModule.package })).package }; else if (filter && "StructType" in filter && hasMvrName(filter.StructType)) filter.StructType = (await this.core.mvr.resolveType({ type: filter.StructType })).type; return await this.transport.request({ method: "suix_getOwnedObjects", params: [ input.owner, { filter, options: input.options }, input.cursor, input.limit ], signal: input.signal }); } /** * Get details about an object */ async getObject(input) { if (!input.id || !isValidSuiObjectId(normalizeSuiObjectId(input.id))) throw new Error("Invalid Sui Object id"); return await this.transport.request({ method: "sui_getObject", params: [input.id, input.options], signal: input.signal }); } async tryGetPastObject(input) { return await this.transport.request({ method: "sui_tryGetPastObject", params: [ input.id, input.version, input.options ], signal: input.signal }); } /** * Batch get details about a list of objects. If any of the object ids are duplicates the call will fail */ async multiGetObjects(input) { input.ids.forEach((id) => { if (!id || !isValidSuiObjectId(normalizeSuiObjectId(id))) throw new Error(`Invalid Sui Object id ${id}`); }); if (input.ids.length !== new Set(input.ids).size) throw new Error(`Duplicate object ids in batch call ${input.ids}`); return await this.transport.request({ method: "sui_multiGetObjects", params: [input.ids, input.options], signal: input.signal }); } /** * Get transaction blocks for a given query criteria */ async queryTransactionBlocks({ filter, options, cursor, limit, order, signal }) { if (filter && "MoveFunction" in filter && isValidNamedPackage(filter.MoveFunction.package)) filter = { ...filter, MoveFunction: { package: (await this.core.mvr.resolvePackage({ package: filter.MoveFunction.package })).package } }; return await this.transport.request({ method: "suix_queryTransactionBlocks", params: [ { filter, options }, cursor, limit, (order || "descending") === "descending" ], signal }); } async getTransactionBlock(input) { if (!isValidTransactionDigest(input.digest)) throw new Error("Invalid Transaction digest"); return await this.transport.request({ method: "sui_getTransactionBlock", params: [input.digest, input.options], signal: input.signal }); } async multiGetTransactionBlocks(input) { input.digests.forEach((d) => { if (!isValidTransactionDigest(d)) throw new Error(`Invalid Transaction digest ${d}`); }); if (input.digests.length !== new Set(input.digests).size) throw new Error(`Duplicate digests in batch call ${input.digests}`); return await this.transport.request({ method: "sui_multiGetTransactionBlocks", params: [input.digests, input.options], signal: input.signal }); } async executeTransactionBlock({ transactionBlock, signature, options, signal }) { return await this.transport.request({ method: "sui_executeTransactionBlock", params: [ typeof transactionBlock === "string" ? transactionBlock : toBase64(transactionBlock), Array.isArray(signature) ? signature : [signature], options ], signal }); } async signAndExecuteTransaction({ transaction, signer, ...input }) { let transactionBytes; if (transaction instanceof Uint8Array) transactionBytes = transaction; else { transaction.setSenderIfNotSet(signer.toSuiAddress()); transactionBytes = await transaction.build({ client: this }); } const { signature, bytes } = await signer.signTransaction(transactionBytes); return this.executeTransactionBlock({ transactionBlock: bytes, signature, ...input }); } /** * Get total number of transactions */ async getTotalTransactionBlocks({ signal } = {}) { const resp = await this.transport.request({ method: "sui_getTotalTransactionBlocks", params: [], signal }); return BigInt(resp); } /** * Getting the reference gas price for the network */ async getReferenceGasPrice({ signal } = {}) { const resp = await this.transport.request({ method: "suix_getReferenceGasPrice", params: [], signal }); return BigInt(resp); } /** * Return the delegated stakes for an address */ async getStakes(input) { if (!input.owner || !isValidSuiAddress(normalizeSuiAddress(input.owner))) throw new Error("Invalid Sui address"); return await this.transport.request({ method: "suix_getStakes", params: [input.owner], signal: input.signal }); } /** * Return the delegated stakes queried by id. */ async getStakesByIds(input) { input.stakedSuiIds.forEach((id) => { if (!id || !isValidSuiObjectId(normalizeSuiObjectId(id))) throw new Error(`Invalid Sui Stake id ${id}`); }); return await this.transport.request({ method: "suix_getStakesByIds", params: [input.stakedSuiIds], signal: input.signal }); } /** * Return the latest system state content. */ async getLatestSuiSystemState({ signal } = {}) { return await this.transport.request({ method: "suix_getLatestSuiSystemState", params: [], signal }); } /** * Get events for a given query criteria */ async queryEvents({ query, cursor, limit, order, signal }) { if (query && "MoveEventType" in query && hasMvrName(query.MoveEventType)) query = { ...query, MoveEventType: (await this.core.mvr.resolveType({ type: query.MoveEventType })).type }; if (query && "MoveEventModule" in query && isValidNamedPackage(query.MoveEventModule.package)) query = { ...query, MoveEventModule: { module: query.MoveEventModule.module, package: (await this.core.mvr.resolvePackage({ package: query.MoveEventModule.package })).package } }; if ("MoveModule" in query && isValidNamedPackage(query.MoveModule.package)) query = { ...query, MoveModule: { module: query.MoveModule.module, package: (await this.core.mvr.resolvePackage({ package: query.MoveModule.package })).package } }; return await this.transport.request({ method: "suix_queryEvents", params: [ query, cursor, limit, (order || "descending") === "descending" ], signal }); } /** * Runs the transaction block in dev-inspect mode. Which allows for nearly any * transaction (or Move call) with any arguments. Detailed results are * provided, including both the transaction effects and any return values. */ async devInspectTransactionBlock(input) { let devInspectTxBytes; if (isTransaction(input.transactionBlock)) { input.transactionBlock.setSenderIfNotSet(input.sender); devInspectTxBytes = toBase64(await input.transactionBlock.build({ client: this, onlyTransactionKind: true })); } else if (typeof input.transactionBlock === "string") devInspectTxBytes = input.transactionBlock; else if (input.transactionBlock instanceof Uint8Array) devInspectTxBytes = toBase64(input.transactionBlock); else throw new Error("Unknown transaction block format."); input.signal?.throwIfAborted(); return await this.transport.request({ method: "sui_devInspectTransactionBlock", params: [ input.sender, devInspectTxBytes, input.gasPrice?.toString(), input.epoch ], signal: input.signal }); } /** * Dry run a transaction block and return the result. */ async dryRunTransactionBlock(input) { return await this.transport.request({ method: "sui_dryRunTransactionBlock", params: [typeof input.transactionBlock === "string" ? input.transactionBlock : toBase64(input.transactionBlock)] }); } /** * Return the list of dynamic field objects owned by an object */ async getDynamicFields(input) { if (!input.parentId || !isValidSuiObjectId(normalizeSuiObjectId(input.parentId))) throw new Error("Invalid Sui Object id"); return await this.transport.request({ method: "suix_getDynamicFields", params: [ input.parentId, input.cursor, input.limit ], signal: input.signal }); } /** * Return the dynamic field object information for a specified object */ async getDynamicFieldObject(input) { return await this.transport.request({ method: "suix_getDynamicFieldObject", params: [input.parentId, input.name], signal: input.signal }); } /** * Get the sequence number of the latest checkpoint that has been executed */ async getLatestCheckpointSequenceNumber({ signal } = {}) { const resp = await this.transport.request({ method: "sui_getLatestCheckpointSequenceNumber", params: [], signal }); return String(resp); } /** * Returns information about a given checkpoint */ async getCheckpoint(input) { return await this.transport.request({ method: "sui_getCheckpoint", params: [input.id], signal: input.signal }); } /** * Returns historical checkpoints paginated */ async getCheckpoints(input) { return await this.transport.request({ method: "sui_getCheckpoints", params: [ input.cursor, input?.limit, input.descendingOrder ], signal: input.signal }); } /** * Return the committee information for the asked epoch */ async getCommitteeInfo(input) { return await this.transport.request({ method: "suix_getCommitteeInfo", params: [input?.epoch], signal: input?.signal }); } async getNetworkMetrics({ signal } = {}) { return await this.transport.request({ method: "suix_getNetworkMetrics", params: [], signal }); } async getAddressMetrics({ signal } = {}) { return await this.transport.request({ method: "suix_getLatestAddressMetrics", params: [], signal }); } async getEpochMetrics(input) { return await this.transport.request({ method: "suix_getEpochMetrics", params: [ input?.cursor, input?.limit, input?.descendingOrder ], signal: input?.signal }); } async getAllEpochAddressMetrics(input) { return await this.transport.request({ method: "suix_getAllEpochAddressMetrics", params: [input?.descendingOrder], signal: input?.signal }); } /** * Return the committee information for the asked epoch */ async getEpochs(input) { return await this.transport.request({ method: "suix_getEpochs", params: [ input?.cursor, input?.limit, input?.descendingOrder ], signal: input?.signal }); } /** * Returns list of top move calls by usage */ async getMoveCallMetrics({ signal } = {}) { return await this.transport.request({ method: "suix_getMoveCallMetrics", params: [], signal }); } /** * Return the committee information for the asked epoch */ async getCurrentEpoch({ signal } = {}) { return await this.transport.request({ method: "suix_getCurrentEpoch", params: [], signal }); } /** * Return the Validators APYs */ async getValidatorsApy({ signal } = {}) { return await this.transport.request({ method: "suix_getValidatorsApy", params: [], signal }); } async getChainIdentifier({ signal } = {}) { return toHex(fromBase58((await this.getCheckpoint({ id: "0", signal })).digest).slice(0, 4)); } async resolveNameServiceAddress(input) { return await this.transport.request({ method: "suix_resolveNameServiceAddress", params: [input.name], signal: input.signal }); } async resolveNameServiceNames({ format = "dot", ...input }) { const { nextCursor, hasNextPage, data } = await this.transport.request({ method: "suix_resolveNameServiceNames", params: [ input.address, input.cursor, input.limit ], signal: input.signal }); return { hasNextPage, nextCursor, data: data.map((name) => normalizeSuiNSName(name, format)) }; } async getProtocolConfig(input) { return await this.transport.request({ method: "sui_getProtocolConfig", params: [input?.version], signal: input?.signal }); } async verifyZkLoginSignature(input) { return await this.transport.request({ method: "sui_verifyZkLoginSignature", params: [ input.bytes, input.signature, input.intentScope, input.author ], signal: input.signal }); } /** * Wait for a transaction block result to be available over the API. * This can be used in conjunction with `executeTransactionBlock` to wait for the transaction to * be available via the API. * This currently polls the `getTransactionBlock` API to check for the transaction. */ async waitForTransaction({ signal, timeout = 60 * 1e3, pollInterval = 2 * 1e3, ...input }) { const timeoutSignal = AbortSignal.timeout(timeout); const timeoutPromise = new Promise((_, reject) => { timeoutSignal.addEventListener("abort", () => reject(timeoutSignal.reason)); }); timeoutPromise.catch(() => {}); while (!timeoutSignal.aborted) { signal?.throwIfAborted(); try { return await this.getTransactionBlock(input); } catch { await Promise.race([new Promise((resolve) => setTimeout(resolve, pollInterval)), timeoutPromise]); } } timeoutSignal.throwIfAborted(); throw new Error("Unexpected error while waiting for transaction block."); } }; //#endregion export { SuiJsonRpcClient, isSuiJsonRpcClient }; //# sourceMappingURL=client.mjs.map