diff --git a/explorer/client/src/components/events/eventDisplay.tsx b/explorer/client/src/components/events/eventDisplay.tsx index e57b110aeb41f..b5c02699508ee 100644 --- a/explorer/client/src/components/events/eventDisplay.tsx +++ b/explorer/client/src/components/events/eventDisplay.tsx @@ -67,7 +67,8 @@ function objectContent(label: string, id: ObjectId) { }; } -function fieldsContent(fields: { [key: string]: any }) { +function fieldsContent(fields: { [key: string]: any } | undefined) { + if (!fields) return []; return Object.keys(fields).map((k) => { return { label: k, diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index 02cd8a3689681..7e0d6936d354f 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -77,6 +77,26 @@ const txn = await provider.getTransaction( ); ``` +Fetch transaction events from a transaction digest: + +```typescript +import { JsonRpcProvider } from '@mysten/sui.js'; +const provider = new JsonRpcProvider('https://gateway.devnet.sui.io:443'); +const txEvents = await provider.getEventsByTransaction( + '6mn5W1CczLwitHCO9OIUbqirNrQ0cuKdyxaNe16SAME=' +); +``` + +Fetch events by sender address: + +```typescript +import { JsonRpcProvider } from '@mysten/sui.js'; +const provider = new JsonRpcProvider('https://gateway.devnet.sui.io:443'); +const senderEvents = await provider.getEventsBySender( + '0xbff6ccc8707aa517b4f1b95750a2a8c666012df3' +); +``` + For any operations that involves signing or submitting transactions, you should use the `Signer` API. For example: To transfer a `0x2::coin::Coin`: diff --git a/sdk/typescript/src/providers/json-rpc-provider.ts b/sdk/typescript/src/providers/json-rpc-provider.ts index fa5dca1acc88e..0661c085fdad3 100644 --- a/sdk/typescript/src/providers/json-rpc-provider.ts +++ b/sdk/typescript/src/providers/json-rpc-provider.ts @@ -14,6 +14,7 @@ import { isSuiMoveNormalizedFunction, isSuiMoveNormalizedStruct, isSuiExecuteTransactionResponse, + isSuiEvents } from '../types/index.guard'; import { GatewayTxSeqNumber, @@ -35,6 +36,13 @@ import { SubscriptionId, ExecuteTransactionRequestType, SuiExecuteTransactionResponse, + SuiAddress, + ObjectOwner, + ObjectId, + SuiEvents, + EVENT_QUERY_MAX_LIMIT, + DEFAULT_START_TIME, + DEFAULT_END_TIME, } from '../types'; import { SignatureScheme } from '../cryptography/publickey'; import { DEFAULT_CLIENT_OPTIONS, WebsocketClient, WebsocketClientOptions } from '../rpc/websocket-client'; @@ -431,6 +439,146 @@ export class JsonRpcProvider extends Provider { } } + // Events + + async getEventsByTransaction( + digest: TransactionDigest, + count: number = EVENT_QUERY_MAX_LIMIT + ): Promise { + try { + return await this.client.requestWithType( + 'sui_getEventsByTransaction', + [digest, count], + isSuiEvents, + this.skipDataValidation + ); + } catch (err) { + throw new Error( + `Error getting events by transaction: ${digest}, with error: ${err}` + ); + } + } + + async getEventsByModule( + package_: string, + module: string, + count: number = EVENT_QUERY_MAX_LIMIT, + startTime: number = DEFAULT_START_TIME, + endTime: number = DEFAULT_END_TIME + ): Promise { + try { + return await this.client.requestWithType( + 'sui_getEventsByModule', + [package_, module, count, startTime, endTime], + isSuiEvents, + this.skipDataValidation + ); + } catch (err) { + throw new Error( + `Error getting events by transaction module: ${package_}::${module}, with error: ${err}` + ); + } + } + + async getEventsByMoveEventStructName( + moveEventStructName: string, + count: number = EVENT_QUERY_MAX_LIMIT, + startTime: number = DEFAULT_START_TIME, + endTime: number = DEFAULT_END_TIME + ): Promise { + try { + return await this.client.requestWithType( + 'sui_getEventsByMoveEventStructName', + [moveEventStructName, count, startTime, endTime], + isSuiEvents, + this.skipDataValidation + ); + } catch (err) { + throw new Error( + `Error getting events by move event struct name: ${moveEventStructName}, with error: ${err}` + ); + } + } + + async getEventsBySender( + sender: SuiAddress, + count: number = EVENT_QUERY_MAX_LIMIT, + startTime: number = DEFAULT_START_TIME, + endTime: number = DEFAULT_END_TIME + ): Promise { + try { + return await this.client.requestWithType( + 'sui_getEventsBySender', + [sender, count, startTime, endTime], + isSuiEvents, + this.skipDataValidation + ); + } catch (err) { + throw new Error( + `Error getting events by sender: ${sender}, with error: ${err}` + ); + } + } + + async getEventsByRecipient( + recipient: ObjectOwner, + count: number = EVENT_QUERY_MAX_LIMIT, + startTime: number = DEFAULT_START_TIME, + endTime: number = DEFAULT_END_TIME + ): Promise { + try { + return await this.client.requestWithType( + 'sui_getEventsByRecipient', + [recipient, count, startTime, endTime], + isSuiEvents, + this.skipDataValidation + ); + } catch (err) { + throw new Error( + `Error getting events by receipient: ${recipient}, with error: ${err}` + ); + } + } + + async getEventsByObject( + object: ObjectId, + count: number = EVENT_QUERY_MAX_LIMIT, + startTime: number = DEFAULT_START_TIME, + endTime: number = DEFAULT_END_TIME + ): Promise { + try { + return await this.client.requestWithType( + 'sui_getEventsByObject', + [object, count, startTime, endTime], + isSuiEvents, + this.skipDataValidation + ); + } catch (err) { + throw new Error( + `Error getting events by object: ${object}, with error: ${err}` + ); + } + } + + async getEventsByTimeRange( + count: number = EVENT_QUERY_MAX_LIMIT, + startTime: number = DEFAULT_START_TIME, + endTime: number = DEFAULT_END_TIME + ): Promise { + try { + return await this.client.requestWithType( + 'sui_getEventsByTimeRange', + [count, startTime, endTime], + isSuiEvents, + this.skipDataValidation + ); + } catch (err) { + throw new Error( + `Error getting events by time range: ${startTime} thru ${endTime}, with error: ${err}` + ); + } + } + async subscribeEvent( filter: SuiEventFilter, onMessage: (event: SuiEventEnvelope) => void diff --git a/sdk/typescript/src/providers/provider.ts b/sdk/typescript/src/providers/provider.ts index 1aead7d0f7f9e..99cfce1a9c073 100644 --- a/sdk/typescript/src/providers/provider.ts +++ b/sdk/typescript/src/providers/provider.ts @@ -19,6 +19,11 @@ import { SubscriptionId, ExecuteTransactionRequestType, SuiExecuteTransactionResponse, + TransactionDigest, + ObjectId, + SuiAddress, + ObjectOwner, + SuiEvents, } from '../types'; /////////////////////////////// @@ -140,6 +145,97 @@ export abstract class Provider { abstract syncAccountState(address: string): Promise; + /** + * Get events for one transaction + * @param digest transaction digest to search by + * @param count max result count + */ + abstract getEventsByTransaction(digest: TransactionDigest, count: number): Promise; + + /** + * Get events emitted from within the specified Move module + * @param package_ Move package object ID + * @param module Move module name + * @param count max result count + * @param startTime start of time range + * @param endTime end of time range, exclusive + */ + abstract getEventsByModule( + packageId: ObjectId, + module: string, + count: number, + startTime: number, + endTime: number + ): Promise; + + /** + * Get events with a matching Move type name + * @param moveEventStructName Move struct type name + * @param count max result count + * @param startTime start of time range to search + * @param endTime end of time range + */ + abstract getEventsByMoveEventStructName( + moveEventStructName: string, + count: number, + startTime: number, + endTime: number + ): Promise; + + /** + * Get events from transactions sent by a specific address + * @param sender Sui address of the sender of the transaction that generated the event + * @param count max result count + * @param startTime start of time range to search + * @param endTime end of time range + */ + abstract getEventsBySender( + sender: SuiAddress, + count: number, + startTime: number, + endTime: number + ): Promise; + + /** + * Get events with a matching recipient + * @param recipient object owner that received the transaction that generated the event + * @param count max result count + * @param startTime start of time range to search + * @param endTime end of time range + */ + abstract getEventsByRecipient( + recipient: ObjectOwner, + count: number, + startTime: number, + endTime: number + ): Promise; + + /** + * Get events involving the given object + * @param object object id created, mutated, or deleted in events + * @param count max result count + * @param startTime start of time range to search + * @param endTime end of time range + */ + abstract getEventsByObject( + object: ObjectId, + count: number, + startTime: number, + endTime: number + ): Promise; + + /** + * Get all events within the given time span + * @param count max result count + * @param startTime start of time range to search + * @param endTime end of time range + */ + abstract getEventsByTimeRange( + count: number, + startTime: number, + endTime: number + ): Promise; + /** * Subscribe to get notifications whenever an event matching the filter occurs * @param filter - filter describing the subset of events to follow diff --git a/sdk/typescript/src/providers/void-provider.ts b/sdk/typescript/src/providers/void-provider.ts index 18c1493de5509..2d1ca59871845 100644 --- a/sdk/typescript/src/providers/void-provider.ts +++ b/sdk/typescript/src/providers/void-provider.ts @@ -21,6 +21,10 @@ import { SubscriptionId, ExecuteTransactionRequestType, SuiExecuteTransactionResponse, + ObjectOwner, + SuiAddress, + ObjectId, + SuiEvents, } from '../types'; import { Provider } from './provider'; @@ -126,6 +130,64 @@ export class VoidProvider extends Provider { throw this.newError('syncAccountState'); } + async getEventsByTransaction(_digest: TransactionDigest, _count: number): Promise { + throw this.newError('getEventsByTransaction'); + } + + async getEventsByModule( + _package: string, + _module: string, + _count: number, + _startTime: number, + _endTime: number + ): Promise { + throw this.newError('getEventsByTransactionModule'); + } + + async getEventsByMoveEventStructName( + _moveEventStructName: string, + _count: number, + _startTime: number, + _endTime: number + ): Promise { + throw this.newError('getEventsByMoveEventStructName'); + } + + async getEventsBySender( + _sender: SuiAddress, + _count: number, + _startTime: number, + _endTime: number + ): Promise { + throw this.newError('getEventsBySender'); + } + + async getEventsByRecipient( + _recipient: ObjectOwner, + _count: number, + _startTime: number, + _endTime: number + ): Promise { + throw this.newError('getEventsByRecipient'); + } + + async getEventsByObject( + _object: ObjectId, + _count: number, + _startTime: number, + _endTime: number + ): Promise { + throw this.newError('getEventsByObject'); + } + + async getEventsByTimeRange( + _count: number, + _startTime: number, + _endTime: number + ): Promise { + throw this.newError('getEventsByTimeRange'); + } + async subscribeEvent( _filter: SuiEventFilter, _onMessage: (event: SuiEventEnvelope) => void diff --git a/sdk/typescript/src/types/events.ts b/sdk/typescript/src/types/events.ts index c4f1d95e094c5..b3621be9805d5 100644 --- a/sdk/typescript/src/types/events.ts +++ b/sdk/typescript/src/types/events.ts @@ -12,7 +12,7 @@ export type MoveEvent = { transactionModule: string; sender: SuiAddress; type: string; - fields: { [key: string]: any; }; + fields?: { [key: string]: any; }; bcs: string; }; @@ -89,6 +89,13 @@ export type SuiEventEnvelope = { event: SuiEvent } +export type SuiEvents = SuiEventEnvelope[]; + export type SubscriptionId = number; -export type SubscriptionEvent = { subscription: SubscriptionId, result: SuiEventEnvelope }; \ No newline at end of file +export type SubscriptionEvent = { subscription: SubscriptionId, result: SuiEventEnvelope }; + +// mirrors the value defined in https://github.com/MystenLabs/sui/blob/e12f8c58ef7ba17205c4caf5ad2c350cbb01656c/crates/sui-json-rpc/src/api.rs#L27 +export const EVENT_QUERY_MAX_LIMIT = 100; +export const DEFAULT_START_TIME = 0; +export const DEFAULT_END_TIME = Number.MAX_SAFE_INTEGER; diff --git a/sdk/typescript/src/types/index.guard.ts b/sdk/typescript/src/types/index.guard.ts index b8f7c67c13bed..ad0051d01d0ad 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, MoveEvent, PublishEvent, TransferObjectEvent, DeleteObjectEvent, NewObjectEvent, SuiEvent, MoveEventField, EventType, SuiEventFilter, SuiEventEnvelope, SubscriptionId, SubscriptionEvent, TransferObject, SuiTransferSui, SuiChangeEpoch, ExecuteTransactionRequestType, TransactionKindName, SuiTransactionKind, SuiTransactionData, EpochId, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatusType, ExecutionStatus, OwnedObjectRef, TransactionEffects, SuiTransactionResponse, SuiCertifiedTransactionEffects, SuiExecuteTransactionResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveCall, SuiJsonValue, EmptySignInfo, AuthorityName, AuthoritySignature, TransactionBytes, SuiParsedMergeCoinResponse, SuiParsedSplitCoinResponse, SuiParsedPublishResponse, SuiPackage, SuiParsedTransactionResponse, DelegationData, DelegationSuiObject, TransferObjectTx, TransferSuiTx, PublishTx, ObjectArg, CallArg, StructTag, TypeTag, MoveCallTx, Transaction, TransactionKind, TransactionData } 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, MoveEvent, PublishEvent, TransferObjectEvent, DeleteObjectEvent, NewObjectEvent, SuiEvent, MoveEventField, EventType, SuiEventFilter, SuiEventEnvelope, SuiEvents, SubscriptionId, SubscriptionEvent, TransferObject, SuiTransferSui, SuiChangeEpoch, ExecuteTransactionRequestType, TransactionKindName, SuiTransactionKind, SuiTransactionData, EpochId, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatusType, ExecutionStatus, OwnedObjectRef, TransactionEffects, SuiTransactionResponse, SuiCertifiedTransactionEffects, SuiExecuteTransactionResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveCall, SuiJsonValue, EmptySignInfo, AuthorityName, AuthoritySignature, TransactionBytes, SuiParsedMergeCoinResponse, SuiParsedSplitCoinResponse, SuiParsedPublishResponse, SuiPackage, SuiParsedTransactionResponse, DelegationData, DelegationSuiObject, TransferObjectTx, TransferSuiTx, PublishTx, ObjectArg, CallArg, StructTag, TypeTag, MoveCallTx, Transaction, TransactionKind, TransactionData } from "./index"; export function isTransactionDigest(obj: any, _argumentName?: string): obj is TransactionDigest { return ( @@ -405,9 +405,10 @@ export function isMoveEvent(obj: any, _argumentName?: string): obj is MoveEvent isTransactionDigest(obj.transactionModule) as boolean && isTransactionDigest(obj.sender) as boolean && isTransactionDigest(obj.type) as boolean && - (obj.fields !== null && - typeof obj.fields === "object" || - typeof obj.fields === "function") && + (typeof obj.fields === "undefined" || + (obj.fields !== null && + typeof obj.fields === "object" || + typeof obj.fields === "function")) && isTransactionDigest(obj.bcs) as boolean ) } @@ -585,6 +586,15 @@ export function isSuiEventEnvelope(obj: any, _argumentName?: string): obj is Sui ) } +export function isSuiEvents(obj: any, _argumentName?: string): obj is SuiEvents { + return ( + Array.isArray(obj) && + obj.every((e: any) => + isSuiEventEnvelope(e) as boolean + ) + ) +} + export function isSubscriptionId(obj: any, _argumentName?: string): obj is SubscriptionId { return ( typeof obj === "number"