UNPKG

@mysten/sui

Version:
636 lines (634 loc) 25.7 kB
import { normalizeSuiAddress } from "../utils/sui-types.mjs"; import { CoreClient } from "../client/core.mjs"; import { TransactionDataBuilder } from "../transactions/TransactionData.mjs"; import { formatMoveAbortMessage, parseTransactionEffectsBcs } from "../client/utils.mjs"; import { ObjectError, SimulationError } from "../client/errors.mjs"; import { SUI_TYPE_ARG } from "../utils/constants.mjs"; import { DefaultSuinsNameDocument, ExecuteTransactionDocument, ExecutionStatus, GetAllBalancesDocument, GetBalanceDocument, GetChainIdentifierDocument, GetCoinMetadataDocument, GetCoinsDocument, GetCurrentSystemStateDocument, GetMoveFunctionDocument, GetOwnedObjectsDocument, GetProtocolConfigDocument, GetReferenceGasPriceDocument, GetTransactionBlockDocument, MultiGetObjectsDocument, ResolveTransactionDocument, SimulateTransactionDocument, VerifyZkLoginSignatureDocument, ZkLoginIntentScope } from "./generated/queries.mjs"; import { Transaction } from "../grpc/proto/sui/rpc/v2/transaction.mjs"; import { grpcTransactionToTransactionData, transactionDataToGrpcTransaction, transactionToGrpcJson } from "../client/transaction-resolver.mjs"; import { BalanceChange } from "../grpc/proto/sui/rpc/v2/balance_change.mjs"; import { TransactionEffects } from "../grpc/proto/sui/rpc/v2/effects.mjs"; import { chunk, fromBase64, toBase64 } from "@mysten/utils"; //#region src/graphql/core.ts var GraphQLCoreClient = class extends CoreClient { #graphqlClient; constructor({ graphqlClient, mvr }) { super({ network: graphqlClient.network, base: graphqlClient, mvr }); this.#graphqlClient = graphqlClient; } async #graphqlQuery(options, getData) { const { data, errors } = await this.#graphqlClient.query(options); handleGraphQLErrors(errors); const extractedData = data && (getData ? getData(data) : data); if (extractedData == null) throw new Error("Missing response data"); return extractedData; } async getObjects(options) { const batches = chunk(options.objectIds, 50); const results = []; for (const batch of batches) { const page = await this.#graphqlQuery({ query: MultiGetObjectsDocument, variables: { objectKeys: batch.map((address) => ({ address })), includeContent: options.include?.content ?? false, includePreviousTransaction: options.include?.previousTransaction ?? false, includeObjectBcs: options.include?.objectBcs ?? false, includeJson: options.include?.json ?? false, includeDisplay: options.include?.display ?? false } }, (result) => result.multiGetObjects); results.push(...batch.map((id) => normalizeSuiAddress(id)).map((id) => page.find((obj) => obj?.address === id) ?? new ObjectError("notFound", `Object ${id} not found`)).map((obj) => { if (obj instanceof ObjectError) return obj; const bcsContent = obj.asMoveObject?.contents?.bcs ? fromBase64(obj.asMoveObject.contents.bcs) : void 0; const objectBcs = obj.objectBcs ? fromBase64(obj.objectBcs) : void 0; let type; if (obj.asMovePackage) type = "package"; else if (obj.asMoveObject?.contents?.type?.repr) type = obj.asMoveObject.contents.type.repr; else type = ""; const jsonContent = options.include?.json ? obj.asMoveObject?.contents?.json ? obj.asMoveObject.contents.json : null : void 0; const displayData = mapDisplay(options.include?.display, obj.asMoveObject?.contents?.display); return { objectId: obj.address, version: obj.version?.toString(), digest: obj.digest, owner: mapOwner(obj.owner), type, content: bcsContent, previousTransaction: obj.previousTransaction?.digest ?? void 0, objectBcs, json: jsonContent, display: displayData }; })); } return { objects: results }; } async listOwnedObjects(options) { const objects = await this.#graphqlQuery({ query: GetOwnedObjectsDocument, variables: { owner: options.owner, limit: options.limit, cursor: options.cursor, filter: options.type ? { type: (await this.mvr.resolveType({ type: options.type })).type } : void 0, includeContent: options.include?.content ?? false, includePreviousTransaction: options.include?.previousTransaction ?? false, includeObjectBcs: options.include?.objectBcs ?? false, includeJson: options.include?.json ?? false, includeDisplay: options.include?.display ?? false } }, (result) => result.address?.objects); return { objects: objects.nodes.map((obj) => ({ objectId: obj.address, version: obj.version?.toString(), digest: obj.digest, owner: mapOwner(obj.owner), type: obj.contents?.type?.repr, content: obj.contents?.bcs ? fromBase64(obj.contents.bcs) : void 0, previousTransaction: obj.previousTransaction?.digest ?? void 0, objectBcs: obj.objectBcs ? fromBase64(obj.objectBcs) : void 0, json: options.include?.json ? obj.contents?.json ? obj.contents.json : null : void 0, display: mapDisplay(options.include?.display, obj.contents?.display) })), hasNextPage: objects.pageInfo.hasNextPage, cursor: objects.pageInfo.endCursor ?? null }; } async listCoins(options) { const coinType = options.coinType ?? SUI_TYPE_ARG; const coins = await this.#graphqlQuery({ query: GetCoinsDocument, variables: { owner: options.owner, cursor: options.cursor, first: options.limit, type: `0x2::coin::Coin<${(await this.mvr.resolveType({ type: coinType })).type}>` } }, (result) => result.address?.objects); return { cursor: coins.pageInfo.endCursor ?? null, hasNextPage: coins.pageInfo.hasNextPage, objects: coins.nodes.map((coin) => ({ objectId: coin.address, version: coin.version?.toString(), digest: coin.digest, owner: mapOwner(coin.owner), type: coin.contents?.type?.repr, balance: (coin.contents?.json)?.balance })) }; } async getBalance(options) { const coinType = options.coinType ?? SUI_TYPE_ARG; const result = await this.#graphqlQuery({ query: GetBalanceDocument, variables: { owner: options.owner, coinType: (await this.mvr.resolveType({ type: coinType })).type } }, (result$1) => result$1.address?.balance); const addressBalance = BigInt(result.addressBalance ?? "0"); const coinBalance = BigInt(result.totalBalance ?? "0") - addressBalance; return { balance: { coinType: result.coinType?.repr ?? coinType, balance: result.totalBalance ?? "0", coinBalance: coinBalance.toString(), addressBalance: addressBalance.toString() } }; } async getCoinMetadata(options) { const coinType = (await this.mvr.resolveType({ type: options.coinType })).type; const { data, errors } = await this.#graphqlClient.query({ query: GetCoinMetadataDocument, variables: { coinType } }); handleGraphQLErrors(errors); if (!data?.coinMetadata) return { coinMetadata: null }; return { coinMetadata: { id: data.coinMetadata.address, decimals: data.coinMetadata.decimals, name: data.coinMetadata.name, symbol: data.coinMetadata.symbol, description: data.coinMetadata.description, iconUrl: data.coinMetadata.iconUrl ?? null } }; } async listBalances(options) { const balances = await this.#graphqlQuery({ query: GetAllBalancesDocument, variables: { owner: options.owner } }, (result) => result.address?.balances); return { cursor: balances.pageInfo.endCursor ?? null, hasNextPage: balances.pageInfo.hasNextPage, balances: balances.nodes.map((balance) => { const addressBalance = BigInt(balance.addressBalance ?? "0"); const coinBalance = BigInt(balance.totalBalance ?? "0") - addressBalance; return { coinType: balance.coinType?.repr, balance: balance.totalBalance, coinBalance: coinBalance.toString(), addressBalance: addressBalance.toString() }; }) }; } async getTransaction(options) { return parseTransaction(await this.#graphqlQuery({ query: GetTransactionBlockDocument, variables: { digest: options.digest, includeTransaction: options.include?.transaction ?? false, includeEffects: options.include?.effects ?? false, includeEvents: options.include?.events ?? false, includeBalanceChanges: options.include?.balanceChanges ?? false, includeObjectTypes: options.include?.objectTypes ?? false, includeBcs: options.include?.bcs ?? false } }, (result) => result.transaction), options.include); } async executeTransaction(options) { return parseTransaction((await this.#graphqlQuery({ query: ExecuteTransactionDocument, variables: { transactionDataBcs: toBase64(options.transaction), signatures: options.signatures, includeTransaction: options.include?.transaction ?? false, includeEffects: options.include?.effects ?? false, includeEvents: options.include?.events ?? false, includeBalanceChanges: options.include?.balanceChanges ?? false, includeObjectTypes: options.include?.objectTypes ?? false, includeBcs: options.include?.bcs ?? false } }, (result) => result.executeTransaction)).effects?.transaction, options.include); } async simulateTransaction(options) { if (!(options.transaction instanceof Uint8Array)) await options.transaction.prepareForSerialization({ client: this }); const result = await this.#graphqlQuery({ query: SimulateTransactionDocument, variables: { transaction: options.transaction instanceof Uint8Array ? { bcs: { value: toBase64(options.transaction) } } : transactionToGrpcJson(options.transaction), includeTransaction: options.include?.transaction ?? false, includeEffects: options.include?.effects ?? false, includeEvents: options.include?.events ?? false, includeBalanceChanges: options.include?.balanceChanges ?? false, includeObjectTypes: options.include?.objectTypes ?? false, includeCommandResults: options.include?.commandResults ?? false, includeBcs: options.include?.bcs ?? false, checksEnabled: options.checksEnabled ?? true } }, (result$1) => result$1.simulateTransaction); const transactionResult = parseTransaction(result.effects?.transaction, options.include); const commandResults = options.include?.commandResults && result.outputs ? result.outputs.map((output) => ({ returnValues: (output.returnValues ?? []).map((rv) => ({ bcs: rv.value?.bcs ? fromBase64(rv.value.bcs) : null })), mutatedReferences: (output.mutatedReferences ?? []).map((mr) => ({ bcs: mr.value?.bcs ? fromBase64(mr.value.bcs) : null })) })) : void 0; if (transactionResult.$kind === "Transaction") return { $kind: "Transaction", Transaction: transactionResult.Transaction, commandResults }; else return { $kind: "FailedTransaction", FailedTransaction: transactionResult.FailedTransaction, commandResults }; } async getReferenceGasPrice() { return { referenceGasPrice: await this.#graphqlQuery({ query: GetReferenceGasPriceDocument }, (result) => result.epoch?.referenceGasPrice) ?? "" }; } async getProtocolConfig() { const result = await this.#graphqlQuery({ query: GetProtocolConfigDocument }, (result$1) => result$1.epoch?.protocolConfigs); const featureFlags = {}; for (const flag of result?.featureFlags ?? []) featureFlags[flag.key] = flag.value; const attributes = {}; for (const config of result?.configs ?? []) attributes[config.key] = config.value ?? null; return { protocolConfig: { protocolVersion: result?.protocolVersion?.toString() ?? null, featureFlags, attributes } }; } async getCurrentSystemState() { const result = await this.#graphqlQuery({ query: GetCurrentSystemStateDocument }, (result$1) => result$1.epoch); if (!result) throw new Error("Epoch data not found in response"); const startMs = result.startTimestamp ? new Date(result.startTimestamp).getTime().toString() : null; const systemStateJson = result.systemState?.json; return { systemState: { systemStateVersion: systemStateJson?.system_state_version?.toString() ?? null, epoch: result.epochId?.toString() ?? null, protocolVersion: result.protocolConfigs?.protocolVersion?.toString() ?? null, referenceGasPrice: result.referenceGasPrice ?? null, epochStartTimestampMs: startMs, safeMode: systemStateJson?.safe_mode ?? false, safeModeStorageRewards: systemStateJson?.safe_mode_storage_rewards ?? null, safeModeComputationRewards: systemStateJson?.safe_mode_computation_rewards ?? null, safeModeStorageRebates: systemStateJson?.safe_mode_storage_rebates?.toString() ?? null, safeModeNonRefundableStorageFee: systemStateJson?.safe_mode_non_refundable_storage_fee?.toString() ?? null, parameters: { epochDurationMs: systemStateJson?.parameters?.epoch_duration_ms?.toString() ?? null, stakeSubsidyStartEpoch: systemStateJson?.parameters?.stake_subsidy_start_epoch?.toString() ?? null, maxValidatorCount: systemStateJson?.parameters?.max_validator_count?.toString() ?? null, minValidatorJoiningStake: systemStateJson?.parameters?.min_validator_joining_stake?.toString() ?? null, validatorLowStakeThreshold: systemStateJson?.parameters?.validator_low_stake_threshold?.toString() ?? null, validatorLowStakeGracePeriod: systemStateJson?.parameters?.validator_low_stake_grace_period?.toString() ?? null }, storageFund: { totalObjectStorageRebates: systemStateJson?.storage_fund?.total_object_storage_rebates ?? null, nonRefundableBalance: systemStateJson?.storage_fund?.non_refundable_balance ?? null }, stakeSubsidy: { balance: systemStateJson?.stake_subsidy?.balance ?? null, distributionCounter: systemStateJson?.stake_subsidy?.distribution_counter?.toString() ?? null, currentDistributionAmount: systemStateJson?.stake_subsidy?.current_distribution_amount?.toString() ?? null, stakeSubsidyPeriodLength: systemStateJson?.stake_subsidy?.stake_subsidy_period_length?.toString() ?? null, stakeSubsidyDecreaseRate: systemStateJson?.stake_subsidy?.stake_subsidy_decrease_rate ?? null } } }; } async listDynamicFields(options) { return this.#graphqlClient.listDynamicFields(options); } async verifyZkLoginSignature(options) { const intentScope = options.intentScope === "TransactionData" ? ZkLoginIntentScope.TransactionData : ZkLoginIntentScope.PersonalMessage; const { data } = await this.#graphqlClient.query({ query: VerifyZkLoginSignatureDocument, variables: { bytes: options.bytes, signature: options.signature, intentScope, author: options.address } }); return { success: data?.verifyZkLoginSignature?.success ?? false, errors: [] }; } async defaultNameServiceName(options) { return { data: { name: await this.#graphqlQuery({ query: DefaultSuinsNameDocument, signal: options.signal, variables: { address: options.address } }, (result) => result.address?.defaultNameRecord?.domain ?? null) } }; } async getMoveFunction(options) { const moveFunction = await this.#graphqlQuery({ query: GetMoveFunctionDocument, variables: { package: (await this.mvr.resolvePackage({ package: options.packageId })).package, module: options.moduleName, function: options.name } }, (result) => result.package?.module?.function); let visibility = "unknown"; switch (moveFunction.visibility) { case "PUBLIC": visibility = "public"; break; case "PRIVATE": visibility = "private"; break; case "FRIEND": visibility = "friend"; break; } return { function: { packageId: normalizeSuiAddress(options.packageId), moduleName: options.moduleName, name: moveFunction.name, visibility, isEntry: moveFunction.isEntry ?? false, typeParameters: moveFunction.typeParameters?.map(({ constraints }) => ({ isPhantom: false, constraints: constraints.map((constraint) => { switch (constraint) { case "COPY": return "copy"; case "DROP": return "drop"; case "STORE": return "store"; case "KEY": return "key"; default: return "unknown"; } }) ?? [] })) ?? [], parameters: moveFunction.parameters?.map((param) => parseNormalizedSuiMoveType(param.signature)) ?? [], returns: moveFunction.return?.map(({ signature }) => parseNormalizedSuiMoveType(signature)) ?? [] } }; } async getChainIdentifier(_options) { return this.cache.read(["chainIdentifier"], async () => { const checkpoint = await this.#graphqlQuery({ query: GetChainIdentifierDocument }, (result) => result.checkpoint); if (!checkpoint?.digest) throw new Error("Genesis checkpoint digest not found"); return { chainIdentifier: checkpoint.digest }; }); } resolveTransactionPlugin() { const graphqlClient = this.#graphqlClient; return async function resolveTransactionData(transactionData, options, next) { const snapshot = transactionData.snapshot(); if (!snapshot.sender) snapshot.sender = "0x0000000000000000000000000000000000000000000000000000000000000000"; const grpcTransaction = transactionDataToGrpcTransaction(snapshot); const transactionJson = Transaction.toJson(grpcTransaction); const { data, errors } = await graphqlClient.query({ query: ResolveTransactionDocument, variables: { transaction: transactionJson, doGasSelection: !options.onlyTransactionKind && (snapshot.gasData.budget == null || snapshot.gasData.payment == null) } }); handleGraphQLErrors(errors); const transactionEffects = data?.simulateTransaction?.effects?.transaction?.effects; if (!options.onlyTransactionKind && transactionEffects?.status === ExecutionStatus.Failure) { const executionError = parseGraphQLExecutionError(transactionEffects.executionError); throw new SimulationError(`Transaction resolution failed: ${executionError?.message ?? "Transaction failed"}`, { executionError }); } const resolvedTransactionBcs = data?.simulateTransaction?.effects?.transaction?.transactionBcs; if (!resolvedTransactionBcs) throw new Error("simulateTransaction did not return resolved transaction data"); const resolved = TransactionDataBuilder.fromBytes(fromBase64(resolvedTransactionBcs)).snapshot(); if (options.onlyTransactionKind) transactionData.applyResolvedData({ ...resolved, gasData: { budget: null, owner: null, payment: null, price: null }, expiration: null }); else transactionData.applyResolvedData(resolved); return await next(); }; } }; function mapDisplay(include, display) { if (!include) return void 0; if (!display) return null; return { output: display.output ?? null, errors: display.errors ?? null }; } function handleGraphQLErrors(errors) { if (!errors || errors.length === 0) return; const errorInstances = errors.map((error) => new GraphQLResponseError(error)); if (errorInstances.length === 1) throw errorInstances[0]; throw new AggregateError(errorInstances); } var GraphQLResponseError = class extends Error { constructor(error) { super(error.message); this.locations = error.locations; } }; function mapOwner(owner) { switch (owner.__typename) { case "AddressOwner": return { $kind: "AddressOwner", AddressOwner: owner.address?.address }; case "ConsensusAddressOwner": return { $kind: "ConsensusAddressOwner", ConsensusAddressOwner: { owner: owner?.address?.address, startVersion: String(owner.startVersion) } }; case "ObjectOwner": return { $kind: "ObjectOwner", ObjectOwner: owner.address?.address }; case "Immutable": return { $kind: "Immutable", Immutable: true }; case "Shared": return { $kind: "Shared", Shared: { initialSharedVersion: String(owner.initialSharedVersion) } }; } } function parseTransaction(transaction, include) { const objectTypes = {}; if (include?.objectTypes) { const effectsJson = transaction.effects?.effectsJson; if (effectsJson) TransactionEffects.fromJson(effectsJson).changedObjects?.forEach((change) => { if (change.objectId && change.objectType) objectTypes[change.objectId] = change.objectType; }); const objectChanges = transaction.effects?.objectChanges?.nodes; if (objectChanges) for (const change of objectChanges) { const type = change.outputState?.asMoveObject?.contents?.type?.repr; if (change.address && type) objectTypes[change.address] = type; } } let balanceChanges; if (include?.balanceChanges) { const balanceChangesJson = transaction.effects?.balanceChangesJson; if (Array.isArray(balanceChangesJson)) balanceChanges = balanceChangesJson.map((json) => { const change = BalanceChange.fromJson(json); return { coinType: change.coinType, address: change.address, amount: change.amount }; }); else balanceChanges = []; } const status = transaction.effects?.status === ExecutionStatus.Success ? { success: true, error: null } : { success: false, error: parseGraphQLExecutionError(transaction.effects?.executionError) }; let transactionData; if (include?.transaction && transaction.transactionJson) { const resolved = grpcTransactionToTransactionData(Transaction.fromJson(transaction.transactionJson)); transactionData = { gasData: resolved.gasData, sender: resolved.sender, expiration: resolved.expiration, commands: resolved.commands, inputs: resolved.inputs, version: resolved.version }; } const bcsBytes = include?.bcs && transaction.transactionBcs ? fromBase64(transaction.transactionBcs) : void 0; const result = { digest: transaction.digest, status, effects: include?.effects ? parseTransactionEffectsBcs(fromBase64(transaction.effects?.effectsBcs)) : void 0, epoch: transaction.effects?.epoch?.epochId?.toString() ?? null, objectTypes: include?.objectTypes ? objectTypes : void 0, transaction: transactionData, bcs: bcsBytes, signatures: transaction.signatures.map((sig) => sig.signatureBytes), balanceChanges, events: include?.events ? transaction.effects?.events?.nodes.map((event) => { const eventType = event.contents?.type?.repr; const [packageId, module] = eventType.split("::"); return { packageId, module, sender: event.sender?.address, eventType, bcs: event.contents?.bcs ? fromBase64(event.contents.bcs) : new Uint8Array(), json: event.contents?.json ?? null }; }) ?? [] : void 0 }; return status.success ? { $kind: "Transaction", Transaction: result, FailedTransaction: void 0 } : { $kind: "FailedTransaction", Transaction: void 0, FailedTransaction: result }; } function parseNormalizedSuiMoveType(type) { let reference = null; if (type.ref === "&") reference = "immutable"; else if (type.ref === "&mut") reference = "mutable"; return { reference, body: parseNormalizedSuiMoveTypeBody(type.body) }; } function parseGraphQLExecutionError(executionError) { if (mapGraphQLExecutionErrorKind(executionError) === "MoveAbort" && executionError?.abortCode != null) { const location = parseGraphQLMoveLocation(executionError); const cleverError = parseGraphQLCleverError(executionError); const commandMatch = executionError.message?.match(/in (\d+)\w* command/); const command = commandMatch ? parseInt(commandMatch[1], 10) - 1 : void 0; return { $kind: "MoveAbort", message: formatMoveAbortMessage({ command, location: location ? { package: location.package, module: location.module, functionName: location.functionName, instruction: location.instruction } : void 0, abortCode: executionError.abortCode, cleverError: cleverError ? { lineNumber: cleverError.lineNumber, constantName: cleverError.constantName, value: cleverError.value } : void 0 }), command, MoveAbort: { abortCode: executionError.abortCode, location, cleverError } }; } return { $kind: "Unknown", message: executionError?.message ?? "Transaction failed", Unknown: null }; } function mapGraphQLExecutionErrorKind(executionError) { if (executionError?.abortCode != null) return "MoveAbort"; return (executionError?.message?.match(/^(\w+)/))?.[1] ?? "Unknown"; } function parseGraphQLMoveLocation(executionError) { if (!(executionError.module?.package?.address && executionError.module?.name)) return; return { package: executionError.module?.package?.address, module: executionError.module?.name, functionName: executionError.function?.name, instruction: executionError.instructionOffset ?? void 0 }; } function parseGraphQLCleverError(executionError) { if (!(executionError.identifier || executionError.constant)) return; return { constantName: executionError.identifier ?? void 0, value: executionError.constant ?? void 0, lineNumber: executionError.sourceLineNumber ?? void 0 }; } function parseNormalizedSuiMoveTypeBody(type) { switch (type) { case "address": return { $kind: "address" }; case "bool": return { $kind: "bool" }; case "u8": return { $kind: "u8" }; case "u16": return { $kind: "u16" }; case "u32": return { $kind: "u32" }; case "u64": return { $kind: "u64" }; case "u128": return { $kind: "u128" }; case "u256": return { $kind: "u256" }; } if (typeof type === "string") throw new Error(`Unknown type: ${type}`); if ("vector" in type) return { $kind: "vector", vector: parseNormalizedSuiMoveTypeBody(type.vector) }; if ("datatype" in type) return { $kind: "datatype", datatype: { typeName: `${normalizeSuiAddress(type.datatype.package)}::${type.datatype.module}::${type.datatype.type}`, typeParameters: type.datatype.typeParameters.map((t) => parseNormalizedSuiMoveTypeBody(t)) } }; if ("typeParameter" in type) return { $kind: "typeParameter", index: type.typeParameter }; throw new Error(`Unknown type: ${JSON.stringify(type)}`); } //#endregion export { GraphQLCoreClient }; //# sourceMappingURL=core.mjs.map