diff --git a/.changeset/smooth-ads-prove.md b/.changeset/smooth-ads-prove.md new file mode 100644 index 0000000000000..94dd68a1871dc --- /dev/null +++ b/.changeset/smooth-ads-prove.md @@ -0,0 +1,5 @@ +--- +"@mysten/sui.js": minor +--- + +Add devInspectTransaction, similar to dryRunTransaction, devInspectTransaction has mutableReferenceOutputs and returnValues as well. diff --git a/crates/sui-json-rpc/src/api.rs b/crates/sui-json-rpc/src/api.rs index 28fe4aca3f2f9..1f4ad3c5e76cf 100644 --- a/crates/sui-json-rpc/src/api.rs +++ b/crates/sui-json-rpc/src/api.rs @@ -9,7 +9,7 @@ use sui_types::sui_system_state::SuiSystemState; use fastcrypto::encoding::Base64; use sui_json::SuiJsonValue; use sui_json_rpc_types::{ - Balance, CoinPage, DynamicFieldPage, EventPage, GetObjectDataResponse, + Balance, CoinPage, DevInspectResults, DynamicFieldPage, EventPage, GetObjectDataResponse, GetPastObjectDataResponse, GetRawObjectDataResponse, MoveFunctionArgType, RPCTransactionRequestParams, SuiCoinMetadata, SuiEventEnvelope, SuiEventFilter, SuiExecuteTransactionResponse, SuiMoveNormalizedFunction, SuiMoveNormalizedModule, @@ -181,6 +181,13 @@ pub trait RpcReadApi { #[open_rpc(namespace = "sui", tag = "Full Node API")] #[rpc(server, client, namespace = "sui")] pub trait RpcFullNodeReadApi { + /// Return dev-inpsect results of the transaction, including both the transaction + /// effects and return values of the transaction. + #[method(name = "devInspectTransaction")] + async fn dev_inspect_transaction(&self, tx_bytes: Base64) -> RpcResult; + + /// Return transaction execution effects including the gas cost summary, + /// while the effects are not committed to the chain. #[method(name = "dryRunTransaction")] async fn dry_run_transaction(&self, tx_bytes: Base64) -> RpcResult; diff --git a/crates/sui-json-rpc/src/read_api.rs b/crates/sui-json-rpc/src/read_api.rs index 422fe6f5857d2..f5f82f6bbafe6 100644 --- a/crates/sui-json-rpc/src/read_api.rs +++ b/crates/sui-json-rpc/src/read_api.rs @@ -16,8 +16,8 @@ use fastcrypto::encoding::Base64; use jsonrpsee::RpcModule; use sui_core::authority::AuthorityState; use sui_json_rpc_types::{ - DynamicFieldPage, GetObjectDataResponse, GetPastObjectDataResponse, MoveFunctionArgType, - ObjectValueKind, Page, SuiMoveNormalizedFunction, SuiMoveNormalizedModule, + DevInspectResults, DynamicFieldPage, GetObjectDataResponse, GetPastObjectDataResponse, + MoveFunctionArgType, ObjectValueKind, Page, SuiMoveNormalizedFunction, SuiMoveNormalizedModule, SuiMoveNormalizedStruct, SuiObjectInfo, SuiTransactionAuthSignersResponse, SuiTransactionEffects, SuiTransactionResponse, TransactionsPage, }; @@ -220,6 +220,17 @@ impl SuiRpcModule for ReadApi { #[async_trait] impl RpcFullNodeReadApiServer for FullNodeApi { + async fn dev_inspect_transaction(&self, tx_bytes: Base64) -> RpcResult { + let tx_data = + bcs::from_bytes(&tx_bytes.to_vec().map_err(|e| anyhow!(e))?).map_err(|e| anyhow!(e))?; + let intent_msg = IntentMessage::new(Intent::default(), tx_data); + let txn_digest = TransactionDigest::new(sha3_hash(&intent_msg.value)); + Ok(self + .state + .dev_inspect_transaction(intent_msg.value, txn_digest) + .await?) + } + async fn dry_run_transaction(&self, tx_bytes: Base64) -> RpcResult { let tx_data = bcs::from_bytes(&tx_bytes.to_vec().map_err(|e| anyhow!(e))?).map_err(|e| anyhow!(e))?; diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index 05069660208ac..83bb8ce6ce6fb 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -142,6 +142,31 @@ } ] }, + { + "name": "sui_devInspectTransaction", + "tags": [ + { + "name": "Full Node API" + } + ], + "description": "Return dev-inpsect results of the transaction, including both the transaction effects and return values of the transaction.", + "params": [ + { + "name": "tx_bytes", + "required": true, + "schema": { + "$ref": "#/components/schemas/Base64" + } + } + ], + "result": { + "name": "DevInspectResults", + "required": true, + "schema": { + "$ref": "#/components/schemas/DevInspectResults" + } + } + }, { "name": "sui_dryRunTransaction", "tags": [ @@ -149,6 +174,7 @@ "name": "Full Node API" } ], + "description": "Return transaction execution effects including the gas cost summary, while the effects are not committed to the chain.", "params": [ { "name": "tx_bytes", @@ -2782,6 +2808,32 @@ } ] }, + "DevInspectResults": { + "description": "The response from processing a dev inspect transaction", + "type": "object", + "required": [ + "effects", + "results" + ], + "properties": { + "effects": { + "description": "Summary of effects that likely would be generated if the transaction is actually run. Note however, that not all dev-inspect transactions are actually usable as transactions so it might not be possible actually generate these effects from a normal transaction.", + "allOf": [ + { + "$ref": "#/components/schemas/TransactionEffects" + } + ] + }, + "results": { + "description": "Execution results (including return values) from executing the transactions Currently contains only return values from Move calls", + "allOf": [ + { + "$ref": "#/components/schemas/Result_of_Array_of_Tuple_of_uint_and_SuiExecutionResult_or_String" + } + ] + } + } + }, "DynamicFieldInfo": { "type": "object", "required": [ @@ -4478,6 +4530,47 @@ } ] }, + "Result_of_Array_of_Tuple_of_uint_and_SuiExecutionResult_or_String": { + "oneOf": [ + { + "type": "object", + "required": [ + "Ok" + ], + "properties": { + "Ok": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + { + "$ref": "#/components/schemas/SuiExecutionResult" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + }, + { + "type": "object", + "required": [ + "Err" + ], + "properties": { + "Err": { + "type": "string" + } + } + } + ] + }, "Secp256k1SuiSignature": { "$ref": "#/components/schemas/Base64" }, @@ -4714,6 +4807,60 @@ } ] }, + "SuiExecutionResult": { + "type": "object", + "properties": { + "mutable_reference_outputs": { + "description": "The value of any arguments that were mutably borrowed. Non-mut borrowed values are not included", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + { + "$ref": "#/components/schemas/TypeTag" + } + ], + "maxItems": 3, + "minItems": 3 + } + }, + "return_values": { + "description": "The return values from the function", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + { + "$ref": "#/components/schemas/TypeTag" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + }, "SuiJsonValue": {}, "SuiMoveAbility": { "type": "string", diff --git a/sdk/typescript/src/providers/json-rpc-provider.ts b/sdk/typescript/src/providers/json-rpc-provider.ts index 76da11991b969..d9f8cfd75a8f1 100644 --- a/sdk/typescript/src/providers/json-rpc-provider.ts +++ b/sdk/typescript/src/providers/json-rpc-provider.ts @@ -17,6 +17,7 @@ import { isSuiMoveNormalizedStruct, isSuiTransactionResponse, isTransactionEffects, + isDevInspectResults, isCoinMetadata, isSuiTransactionAuthSignersResponse, } from '../types/index.guard'; @@ -53,6 +54,7 @@ import { FaucetResponse, Order, TransactionEffects, + DevInspectResults, CoinMetadata, versionToString, isValidTransactionDigest, @@ -694,6 +696,22 @@ export class JsonRpcProvider extends Provider { return this.wsClient.unsubscribeEvent(id); } + async devInspectTransaction(txBytes: string): Promise { + try { + const resp = await this.client.requestWithType( + 'sui_devInspectTransaction', + [txBytes], + isDevInspectResults, + this.options.skipDataValidation + ); + return resp; + } catch (err) { + throw new Error( + `Error dev inspect transaction with request type: ${err}` + ); + } + } + async dryRunTransaction(txBytes: string): Promise { try { const resp = await this.client.requestWithType( diff --git a/sdk/typescript/src/providers/provider.ts b/sdk/typescript/src/providers/provider.ts index 9192a0a7fd3e3..933e60d459107 100644 --- a/sdk/typescript/src/providers/provider.ts +++ b/sdk/typescript/src/providers/provider.ts @@ -33,6 +33,7 @@ import { Order, TransactionEffects, CoinMetadata, + DevInspectResults, } from '../types'; /////////////////////////////// @@ -247,7 +248,9 @@ export abstract class Provider { * @param id - subscription id to unsubscribe from (previously received from subscribeEvent) */ abstract unsubscribeEvent(id: SubscriptionId): Promise; - // TODO: add more interface methods + + abstract devInspectTransaction(txBytes: string): Promise; abstract dryRunTransaction(txBytes: string): Promise; + // TODO: add more interface methods } diff --git a/sdk/typescript/src/providers/void-provider.ts b/sdk/typescript/src/providers/void-provider.ts index 9108f0f2a9702..844303560a4e5 100644 --- a/sdk/typescript/src/providers/void-provider.ts +++ b/sdk/typescript/src/providers/void-provider.ts @@ -34,6 +34,7 @@ import { Order, TransactionEffects, CoinMetadata, + DevInspectResults } from '../types'; import { Provider } from './provider'; @@ -116,6 +117,10 @@ export class VoidProvider extends Provider { throw this.newError('executeTransaction with request Type'); } + devInspectTransaction(_txBytes: string): Promise { + throw this.newError('devInspectTransaction'); + } + dryRunTransaction(_txBytes: string): Promise { throw this.newError('dryRunTransaction'); } diff --git a/sdk/typescript/src/signers/signer-with-provider.ts b/sdk/typescript/src/signers/signer-with-provider.ts index 537c8d9170342..9cabe196d94c3 100644 --- a/sdk/typescript/src/signers/signer-with-provider.ts +++ b/sdk/typescript/src/signers/signer-with-provider.ts @@ -15,6 +15,7 @@ import { SuiAddress, SuiExecuteTransactionResponse, TransactionEffects, + DevInspectResults, } from '../types'; import { SignaturePubkeyPair, Signer } from './signer'; import { RpcTxnDataSerializer } from './txn-data-serializers/rpc-txn-data-serializer'; @@ -32,7 +33,7 @@ import { SignableTransaction, } from './txn-data-serializers/txn-data-serializer'; -// See: sui/crates/sui-types/src/intent.rs +// See: sui/crates/sui-types/src/intent.rs // This is currently hardcoded with [IntentScope::TransactionData = 0, Version::V0 = 0, AppId::Sui = 0] const INTENT_BYTES = [0, 0, 0]; /////////////////////////////// @@ -109,10 +110,12 @@ export abstract class SignerWithProvider implements Signer { dataToSign = txBytes; txBytesToSubmit = txBytes; } else { - const intentMessage = new Uint8Array(INTENT_BYTES.length + txBytes.getLength()); + const intentMessage = new Uint8Array( + INTENT_BYTES.length + txBytes.getLength() + ); intentMessage.set(INTENT_BYTES); intentMessage.set(txBytes.getData(), INTENT_BYTES.length); - + dataToSign = new Base64DataBuffer(intentMessage); txBytesToSubmit = txBytes; } @@ -148,10 +151,13 @@ export abstract class SignerWithProvider implements Signer { ); } const version = await this.provider.getRpcApiVersion(); - const useIntentSigning = version != null && version.major >= 0 && version.minor > 18; + const useIntentSigning = + version != null && version.major >= 0 && version.minor > 18; let dataToSign; if (useIntentSigning) { - const intentMessage = new Uint8Array(INTENT_BYTES.length + txBytes.getLength()); + const intentMessage = new Uint8Array( + INTENT_BYTES.length + txBytes.getLength() + ); intentMessage.set(INTENT_BYTES); intentMessage.set(txBytes.getData(), INTENT_BYTES.length); dataToSign = new Base64DataBuffer(intentMessage); @@ -160,17 +166,44 @@ export abstract class SignerWithProvider implements Signer { } const sig = await this.signData(dataToSign); - const data = deserializeTransactionBytesToTransactionData(useIntentSigning, txBytes); + const data = deserializeTransactionBytesToTransactionData( + useIntentSigning, + txBytes + ); return generateTransactionDigest( data, sig.signatureScheme, sig.signature, sig.pubKey, - (version?.major == 0 && version?.minor < 18) ? 'base64' : 'base58', - (version?.major == 0 && version?.minor < 18) ? false : true + version?.major == 0 && version?.minor < 18 ? 'base64' : 'base58', + version?.major == 0 && version?.minor < 18 ? false : true ); } + async devInspectTransaction( + tx: SignableTransaction | string | Base64DataBuffer + ): Promise { + const address = await this.getAddress(); + let devInspectTxBytes: string; + if (typeof tx === 'string') { + devInspectTxBytes = tx; + } else if (tx instanceof Base64DataBuffer) { + devInspectTxBytes = tx.toString(); + } else { + switch (tx.kind) { + case 'bytes': + devInspectTxBytes = new Base64DataBuffer(tx.data).toString(); + break; + default: + devInspectTxBytes = ( + await this.serializer.serializeToBytes(address, tx) + ).toString(); + break; + } + } + return this.provider.devInspectTransaction(devInspectTxBytes); + } + /** * Dry run a transaction and return the result. * @param tx the transaction as SignableTransaction or string (in base64) that will dry run diff --git a/sdk/typescript/src/types/index.guard.ts b/sdk/typescript/src/types/index.guard.ts index e81bf24a91b0c..109b83fbe682b 100644 --- a/sdk/typescript/src/types/index.guard.ts +++ b/sdk/typescript/src/types/index.guard.ts @@ -7,7 +7,7 @@ * Generated type guards for "index.ts". * WARNING: Do not manually change this file. */ -import { TransactionDigest, SuiAddress, ObjectOwner, SuiObjectRef, SuiObjectInfo, ObjectContentFields, MovePackageContent, SuiData, SuiMoveObject, SuiMovePackage, SuiMoveFunctionArgTypesResponse, SuiMoveFunctionArgType, SuiMoveFunctionArgTypes, SuiMoveNormalizedModules, SuiMoveNormalizedModule, SuiMoveModuleId, SuiMoveNormalizedStruct, SuiMoveStructTypeParameter, SuiMoveNormalizedField, SuiMoveNormalizedFunction, SuiMoveVisibility, SuiMoveTypeParameterIndex, SuiMoveAbilitySet, SuiMoveNormalizedType, SuiMoveNormalizedTypeParameterType, SuiMoveNormalizedStructType, SuiObject, ObjectStatus, ObjectType, GetOwnedObjectsResponse, GetObjectDataResponse, ObjectDigest, ObjectId, SequenceNumber, Order, MoveEvent, PublishEvent, CoinBalanceChangeEvent, TransferObjectEvent, MutateObjectEvent, DeleteObjectEvent, NewObjectEvent, SuiEvent, MoveEventField, EventQuery, EventId, PaginatedEvents, EventType, BalanceChangeType, SuiEventFilter, SuiEventEnvelope, SuiEvents, SubscriptionId, SubscriptionEvent, TransferObject, SuiTransferSui, SuiChangeEpoch, Pay, PaySui, PayAllSui, ExecuteTransactionRequestType, TransactionKindName, SuiTransactionKind, SuiTransactionData, EpochId, GenericAuthoritySignature, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatusType, ExecutionStatus, OwnedObjectRef, TransactionEffects, SuiTransactionResponse, SuiTransactionAuthSignersResponse, SuiCertifiedTransactionEffects, SuiExecuteTransactionResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, PaginatedTransactionDigests, TransactionQuery, MoveCall, SuiJsonValue, EmptySignInfo, AuthorityName, AuthoritySignature, TransactionBytes, SuiParsedMergeCoinResponse, SuiParsedSplitCoinResponse, SuiParsedPublishResponse, SuiPackage, SuiParsedTransactionResponse, CoinMetadata, DelegationData, DelegationSuiObject, TransferObjectTx, TransferSuiTx, PayTx, PaySuiTx, PayAllSuiTx, PublishTx, SharedObjectRef, ObjectArg, CallArg, StructTag, TypeTag, MoveCallTx, Transaction, TransactionKind, TransactionData, RpcApiVersion, FaucetCoinInfo, FaucetResponse } from "./index"; +import { TransactionDigest, SuiAddress, ObjectOwner, SuiObjectRef, SuiObjectInfo, ObjectContentFields, MovePackageContent, SuiData, SuiMoveObject, SuiMovePackage, SuiMoveFunctionArgTypesResponse, SuiMoveFunctionArgType, SuiMoveFunctionArgTypes, SuiMoveNormalizedModules, SuiMoveNormalizedModule, SuiMoveModuleId, SuiMoveNormalizedStruct, SuiMoveStructTypeParameter, SuiMoveNormalizedField, SuiMoveNormalizedFunction, SuiMoveVisibility, SuiMoveTypeParameterIndex, SuiMoveAbilitySet, SuiMoveNormalizedType, SuiMoveNormalizedTypeParameterType, SuiMoveNormalizedStructType, SuiObject, ObjectStatus, ObjectType, GetOwnedObjectsResponse, GetObjectDataResponse, ObjectDigest, ObjectId, SequenceNumber, Order, MoveEvent, PublishEvent, CoinBalanceChangeEvent, TransferObjectEvent, MutateObjectEvent, DeleteObjectEvent, NewObjectEvent, SuiEvent, MoveEventField, EventQuery, EventId, PaginatedEvents, EventType, BalanceChangeType, SuiEventFilter, SuiEventEnvelope, SuiEvents, SubscriptionId, SubscriptionEvent, TransferObject, SuiTransferSui, SuiChangeEpoch, Pay, PaySui, PayAllSui, ExecuteTransactionRequestType, TransactionKindName, SuiTransactionKind, SuiTransactionData, EpochId, GenericAuthoritySignature, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatusType, ExecutionStatus, OwnedObjectRef, DevInspectResults, ResultType, DevInspectResultsType, DevInspectResultTupleType, ExecutionResultType, MutableReferenceOutputType, ReturnValueType, TransactionEffects, SuiTransactionResponse, SuiTransactionAuthSignersResponse, SuiCertifiedTransactionEffects, SuiExecuteTransactionResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, PaginatedTransactionDigests, TransactionQuery, MoveCall, SuiJsonValue, EmptySignInfo, AuthorityName, AuthoritySignature, TransactionBytes, SuiParsedMergeCoinResponse, SuiParsedSplitCoinResponse, SuiParsedPublishResponse, SuiPackage, SuiParsedTransactionResponse, CoinMetadata, DelegationData, DelegationSuiObject, TransferObjectTx, TransferSuiTx, PayTx, PaySuiTx, PayAllSuiTx, PublishTx, SharedObjectRef, ObjectArg, CallArg, StructTag, TypeTag, MoveCallTx, Transaction, TransactionKind, TransactionData, RpcApiVersion, FaucetCoinInfo, FaucetResponse } from "./index"; export function isTransactionDigest(obj: any, _argumentName?: string): obj is TransactionDigest { return ( @@ -974,6 +974,92 @@ export function isOwnedObjectRef(obj: any, _argumentName?: string): obj is Owned ) } +export function isDevInspectResults(obj: any, _argumentName?: string): obj is DevInspectResults { + return ( + (obj !== null && + typeof obj === "object" || + typeof obj === "function") && + isTransactionEffects(obj.effects) as boolean && + isDevInspectResultsType(obj.results) as boolean + ) +} + +export function isResultType(obj: any, _argumentName?: string): obj is ResultType { + return ( + (obj === "Ok" || + obj === "Err;") + ) +} + +export function isDevInspectResultsType(obj: any, _argumentName?: string): obj is DevInspectResultsType { + return ( + (obj !== null && + typeof obj === "object" || + typeof obj === "function") && + isResultType(obj.result) as boolean && + (typeof obj.contents === "undefined" || + Array.isArray(obj.contents) && + obj.contents.every((e: any) => + isDevInspectResultTupleType(e) as boolean + )) && + (typeof obj.error === "undefined" || + isTransactionDigest(obj.error) as boolean) + ) +} + +export function isDevInspectResultTupleType(obj: any, _argumentName?: string): obj is DevInspectResultTupleType { + return ( + (obj !== null && + typeof obj === "object" || + typeof obj === "function") && + isSuiMoveTypeParameterIndex(obj.index) as boolean && + isExecutionResultType(obj.executionResult) as boolean + ) +} + +export function isExecutionResultType(obj: any, _argumentName?: string): obj is ExecutionResultType { + return ( + (obj !== null && + typeof obj === "object" || + typeof obj === "function") && + Array.isArray(obj.mutableReferenceOutputs) && + obj.mutableReferenceOutputs.every((e: any) => + isMutableReferenceOutputType(e) as boolean + ) && + Array.isArray(obj.returnValues) && + obj.returnValues.every((e: any) => + isReturnValueType(e) as boolean + ) + ) +} + +export function isMutableReferenceOutputType(obj: any, _argumentName?: string): obj is MutableReferenceOutputType { + return ( + (obj !== null && + typeof obj === "object" || + typeof obj === "function") && + isSuiMoveTypeParameterIndex(obj.index) as boolean && + Array.isArray(obj.bytes) && + obj.bytes.every((e: any) => + isSuiMoveTypeParameterIndex(e) as boolean + ) && + isTransactionDigest(obj.type_tag) as boolean + ) +} + +export function isReturnValueType(obj: any, _argumentName?: string): obj is ReturnValueType { + return ( + (obj !== null && + typeof obj === "object" || + typeof obj === "function") && + Array.isArray(obj.bytes) && + obj.bytes.every((e: any) => + isSuiMoveTypeParameterIndex(e) as boolean + ) && + isTransactionDigest(obj.type_tag) as boolean + ) +} + export function isTransactionEffects(obj: any, _argumentName?: string): obj is TransactionEffects { return ( (obj !== null && diff --git a/sdk/typescript/src/types/transactions.ts b/sdk/typescript/src/types/transactions.ts index 5eb6c0e60848d..3b696ab2dd91f 100644 --- a/sdk/typescript/src/types/transactions.ts +++ b/sdk/typescript/src/types/transactions.ts @@ -107,6 +107,39 @@ export type OwnedObjectRef = { reference: SuiObjectRef; }; +export type DevInspectResults = { + effects: TransactionEffects; + results: DevInspectResultsType; +}; + +export type ResultType = 'Ok' | 'Err;'; +export type DevInspectResultsType = { + result: ResultType; + contents?: DevInspectResultTupleType[]; + error?: string; +}; + +export type DevInspectResultTupleType = { + index: number; + executionResult: ExecutionResultType; +}; + +export type ExecutionResultType = { + mutableReferenceOutputs: MutableReferenceOutputType[]; + returnValues: ReturnValueType[]; +}; + +export type MutableReferenceOutputType = { + index: number; + bytes: number[]; + type_tag: string; +}; + +export type ReturnValueType = { + bytes: number[]; + type_tag: string; +}; + export type TransactionEffects = { /** The status of the execution */ status: ExecutionStatus; @@ -148,7 +181,7 @@ export type SuiTransactionResponse = { }; export type SuiTransactionAuthSignersResponse = { - signers: AuthorityName[] + signers: AuthorityName[]; }; // TODO: this is likely to go away after https://github.com/MystenLabs/sui/issues/4207 diff --git a/sdk/typescript/test/e2e/dev-inspect.test.ts b/sdk/typescript/test/e2e/dev-inspect.test.ts new file mode 100644 index 0000000000000..5e31b5acfd098 --- /dev/null +++ b/sdk/typescript/test/e2e/dev-inspect.test.ts @@ -0,0 +1,75 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it, expect, beforeAll } from 'vitest'; +import { + getObjectId, + getNewlyCreatedCoinRefsAfterSplit, + LocalTxnDataSerializer, + SignableTransaction, + RawSigner, + } from '../../src'; +import { + DEFAULT_GAS_BUDGET, + DEFAULT_RECIPIENT, + setup, + TestToolbox, + } from './utils/setup'; + +describe.each([{ useLocalTxnBuilder: true }, { useLocalTxnBuilder: false }])( + 'Test dev inspect a payAllSui txn', + ({ useLocalTxnBuilder }) => { + let toolbox: TestToolbox; + let signer: RawSigner; + + beforeAll(async () => { + toolbox = await setup(); + signer = new RawSigner( + toolbox.keypair, + toolbox.provider, + useLocalTxnBuilder + ? new LocalTxnDataSerializer(toolbox.provider) + : undefined + ); + }); + + it('Dev inspect transaction', async () => { + const gasBudget = 1000; + const coins = + await toolbox.provider.selectCoinsWithBalanceGreaterThanOrEqual( + toolbox.address(), + BigInt(DEFAULT_GAS_BUDGET) + ); + + const splitTxn = await signer.splitCoin({ + coinObjectId: getObjectId(coins[0]), + splitAmounts: [2000, 2000, 2000], + gasBudget: gasBudget, + gasPayment: getObjectId(coins[1]), + }); + const splitCoins = getNewlyCreatedCoinRefsAfterSplit(splitTxn)!.map((c) => + getObjectId(c) + ); + + await validateDevInspectTransaction(signer, { + kind: 'payAllSui', + data: { + inputCoins: splitCoins, + recipient: DEFAULT_RECIPIENT, + gasBudget: gasBudget, + }, + } + ); + }); + } +); + +async function validateDevInspectTransaction( + signer: RawSigner, + txn: SignableTransaction + ) { + const localDigest = await signer.getTransactionDigest(txn); + const result = await signer.devInspectTransaction(txn); + expect(localDigest).toEqual(result.effects.transactionDigest); + expect(result.effects.status).toEqual('success'); + }