UNPKG

@layerzerolabs/lz-sui-sdk-v2

Version:

745 lines (700 loc) 27.1 kB
import { bcs } from '@mysten/sui/bcs' import { SuiClient } from '@mysten/sui/client' import { Transaction, TransactionArgument, TransactionResult } from '@mysten/sui/transactions' import { OAppInfoV1Bcs } from '../../bcs' import { ModuleManager } from '../../module-manager' import { ObjectOptions } from '../../types' import { OAppInfoV1 } from '../../types/oapp' import { asAddress, asBytes, asBytes32, asObject, asU16, asU32, asU64, executeSimulate, isTransactionArgument, } from '../../utils' const MODULE_NAME = 'oapp' export const OAppErrorCode = { // OApp related errors (matching oapp.move) OApp_EInvalidAdminCap: 1, OApp_EInvalidOAppCap: 2, OApp_EInvalidRefundAddress: 3, OApp_EInvalidSendingCall: 4, OApp_EOnlyEndpoint: 5, OApp_EOnlyPeer: 6, OApp_ESendingInProgress: 7, // OAppPeer related errors (matching oapp_peer.move) OAppPeer_EPeerNotFound: 0, OAppPeer_EInvalidPeer: 1, // EnforcedOptions related errors (matching enforced_options.move) EnforcedOptions_EEnforcedOptionsNotFound: 1, EnforcedOptions_EInvalidOptionsLength: 2, EnforcedOptions_EInvalidOptionsType: 3, // OAppInfoV1 related errors (matching oapp_info_v1.move) OAppInfoV1_EInvalidData: 1, OAppInfoV1_EInvalidVersion: 2, } as const export class OApp { public packageId: string public oappCallCapId: string public readonly client: SuiClient public oappInfo: OAppInfoV1 | null = null private readonly objects: ObjectOptions constructor( packageId: string, oappCallCapId: string, client: SuiClient, objects: ObjectOptions, private readonly moduleManager: ModuleManager ) { this.packageId = packageId this.oappCallCapId = oappCallCapId this.client = client this.objects = objects } // === Set Functions === /** * Set enforced options for OApp messaging * @param tx - The transaction to add the move call to * @param eid - Endpoint ID or transaction argument * @param msgType - Message type or transaction argument * @param options - Enforced options as bytes or transaction argument */ async setEnforcedOptionsMoveCall( tx: Transaction, eid: number | TransactionArgument, msgType: number | TransactionArgument, options: Uint8Array | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) tx.moveCall({ target: this.#target('set_enforced_options'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asU32(tx, eid), asU16(tx, msgType), asBytes(tx, options), ], }) } /** * Set peer OApp on another chain * @param tx - The transaction to add the move call to * @param eid - Peer endpoint ID or transaction argument * @param peer - Peer OApp address as bytes or transaction argument */ async setPeerMoveCall( tx: Transaction, eid: number | TransactionArgument, peer: Uint8Array | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId) tx.moveCall({ target: this.#target('set_peer'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asObject(tx, messagingChannel), asU32(tx, eid), asBytes32(tx, peer, this.moduleManager.getUtils()), ], }) } // === View Functions === /** * Get admin capability address for OApp * @param tx - The transaction to add the move call to * @param oapp - The OApp object ID or transaction argument * @returns Transaction result containing the admin capability address */ getAdminCapMoveCall(tx: Transaction, oapp: string | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('admin_cap'), arguments: [asObject(tx, oapp)], }) } /** * Get admin capability address for OApp * @param oapp - The OApp object ID * @returns Promise<string> - The admin capability address */ async getAdminCap(oapp: string): Promise<string> { return executeSimulate( this.client, (tx) => { this.getAdminCapMoveCall(tx, oapp) }, (result) => bcs.Address.parse(result[0].value) ) } /** * Get OApp CallCap identifier * @param tx - The transaction to add the move call to * @returns Transaction result containing the OApp CallCap identifier */ async getOAppCapIdMoveCall(tx: Transaction): Promise<TransactionResult> { const oappInfo = await this.#oappInfo() return tx.moveCall({ target: this.#target('oapp_cap_id'), arguments: [asObject(tx, oappInfo.oapp_object)], }) } /** * Get OApp CallCap identifier * @returns Promise<string> - The OApp CallCap identifier */ async getOAppCapId(): Promise<string> { return executeSimulate( this.client, async (tx) => { await this.getOAppCapIdMoveCall(tx) }, (result) => bcs.Address.parse(result[0].value) ) } /** * Combine enforced options with extra options for message execution * @param tx - The transaction to add the move call to * @param eid - Destination endpoint ID or transaction argument * @param msgType - Message type or transaction argument * @param extraOptions - Extra options to combine with enforced options or transaction argument * @returns Transaction result containing combined options */ async combineOptionsMoveCall( tx: Transaction, eid: number | TransactionArgument, msgType: number | TransactionArgument, extraOptions: Uint8Array | TransactionArgument ): Promise<TransactionResult> { const oappInfo = await this.#oappInfo() return tx.moveCall({ target: this.#target('combine_options'), arguments: [ asObject(tx, oappInfo.oapp_object), asU32(tx, eid), asU16(tx, msgType), asBytes(tx, extraOptions), ], }) } /** * Combine enforced options with extra options for message execution * @param eid - Destination endpoint ID * @param msgType - Message type * @param extraOptions - Extra options to combine with enforced options * @returns Promise<Uint8Array> - Combined options as bytes */ async combineOptions(eid: number, msgType: number, extraOptions: Uint8Array): Promise<Uint8Array> { return executeSimulate( this.client, async (tx) => { await this.combineOptionsMoveCall(tx, eid, msgType, extraOptions) }, (result) => new Uint8Array(bcs.vector(bcs.u8()).parse(result[0].value)) ) } /** * Get enforced options for a specific destination and message type * @param tx - The transaction to add the move call to * @param eid - Destination endpoint ID or transaction argument * @param msgType - Message type or transaction argument * @returns Transaction result containing enforced options */ async getEnforcedOptionsMoveCall( tx: Transaction, eid: number | TransactionArgument, msgType: number | TransactionArgument ): Promise<TransactionResult> { const oappInfo = await this.#oappInfo() return tx.moveCall({ target: this.#target('get_enforced_options'), arguments: [asObject(tx, oappInfo.oapp_object), asU32(tx, eid), asU16(tx, msgType)], }) } /** * Get enforced options for a specific destination and message type * @param eid - Destination endpoint ID * @param msgType - Message type * @returns Promise<Uint8Array> - Enforced options as bytes */ async getEnforcedOptions(eid: number, msgType: number): Promise<Uint8Array> { return executeSimulate( this.client, async (tx) => { await this.getEnforcedOptionsMoveCall(tx, eid, msgType) }, (result) => new Uint8Array(bcs.vector(bcs.u8()).parse(result[0].value)) ) } /** * Check if a peer is configured for a specific destination chain * @param tx - The transaction to add the move call to * @param dstEid - Destination endpoint ID or transaction argument * @returns Transaction result containing boolean result */ async hasPeerMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<TransactionResult> { const oappInfo = await this.#oappInfo() return tx.moveCall({ target: this.#target('has_peer'), arguments: [asObject(tx, oappInfo.oapp_object), asU32(tx, dstEid)], }) } /** * Check if a peer is configured for a specific destination chain * @param dstEid - Destination endpoint ID * @returns Promise<boolean> - True if peer is configured, false otherwise */ async hasPeer(dstEid: number): Promise<boolean> { return executeSimulate( this.client, async (tx) => { await this.hasPeerMoveCall(tx, dstEid) }, (result) => bcs.Bool.parse(result[0].value) ) } /** * Get the configured peer address for a specific destination chain * @param tx - The transaction to add the move call to * @param dstEid - Destination endpoint ID or transaction argument * @returns Transaction result containing peer address */ async getPeerMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<TransactionResult> { const oappInfo = await this.#oappInfo() return tx.moveCall({ target: this.#target('get_peer'), arguments: [asObject(tx, oappInfo.oapp_object), asU32(tx, dstEid)], }) } /** * Get the configured peer address for a specific destination chain * @param dstEid - Destination endpoint ID * @returns Promise<Uint8Array> - Peer address as bytes32 */ async getPeer(dstEid: number): Promise<Uint8Array> { return executeSimulate( this.client, async (tx) => { await this.getPeerMoveCall(tx, dstEid) }, (result) => { return new Uint8Array(bcs.vector(bcs.u8()).parse(result[0].value)) } ) } /** * Get OApp information V1 structure * @param tx - The transaction to add the move call to * @returns Transaction result containing the OApp information V1 */ getOAppInfoV1MoveCall(tx: Transaction): TransactionResult { const endpoint = this.moduleManager.getEndpoint() const oappInfoRaw = endpoint.getOappInfoMoveCall(tx, this.oappCallCapId) // the new OAppInfo used to be named as lz_receive_info return tx.moveCall({ target: this.#target('decode', 'oapp_info_v1'), arguments: [oappInfoRaw], }) } /** * Get OApp information V1 structure * @returns Promise<OAppInfoV1> - The OApp information V1 */ async getOAppInfoV1(): Promise<OAppInfoV1> { return executeSimulate( this.client, (tx) => { this.getOAppInfoV1MoveCall(tx) }, (result) => { return OAppInfoV1Bcs.parse(result[0].value) } ) } getOAppInfoV1ExtraInfoMoveCall(tx: Transaction, oappInfo: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('extra_info', 'oapp_info_v1'), arguments: [oappInfo], }) } // === Endpoint Call Functions === /** * Register OApp with the endpoint * @param tx - The transaction to add the move call to * @param oappObjectId - The OApp object ID * @param oappInfo - OApp information as bytes (optional) * @returns Transaction result containing the messaging channel address */ async registerOAppMoveCall( tx: Transaction, oappObjectId: string, oappInfo?: Uint8Array | TransactionArgument ): Promise<TransactionResult> { let oappInfoArg: TransactionArgument if (isTransactionArgument(oappInfo)) { oappInfoArg = oappInfo } else if (oappInfo) { // For Some(vector<u8>), convert Uint8Array to number[] and wrap in Option oappInfoArg = tx.pure.option('vector<u8>', Array.from(oappInfo)) } else { // For None oappInfoArg = tx.pure.option('vector<u8>', null) } const adminCap = await this.getAdminCap(oappObjectId) return tx.moveCall({ target: this.#target('register_oapp', 'endpoint_calls'), arguments: [ asObject(tx, oappObjectId), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), oappInfoArg, ], }) } /** * Set delegate for OApp * @param tx - The transaction to add the move call to * @param newDelegate - New delegate address */ async setDelegateMoveCall(tx: Transaction, newDelegate: string | TransactionArgument): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) tx.moveCall({ target: this.#target('set_delegate', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asAddress(tx, newDelegate), ], }) } /** * Set OApp information in the endpoint * @param tx - The transaction to add the move call to * @param oappInfo - OApp information as bytes * @param oappObjectId - Optional OApp object ID (uses configured oapp if not provided) */ async setOAppInfoMoveCall( tx: Transaction, oappInfo: Uint8Array | TransactionArgument, oappObjectId?: string ): Promise<void> { if (oappObjectId === undefined) { const oappInfoObj = await this.#oappInfo() oappObjectId = oappInfoObj.oapp_object } const adminCap = await this.getAdminCap(oappObjectId) tx.moveCall({ target: this.#target('set_oapp_info', 'endpoint_calls'), arguments: [ asObject(tx, oappObjectId), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asBytes(tx, oappInfo), ], }) } /** * Initialize channel with remote OApp * @param tx - The transaction to add the move call to * @param remoteEid - Remote endpoint ID * @param remoteOApp - Remote OApp address as bytes32 */ async initChannelMoveCall( tx: Transaction, remoteEid: number | TransactionArgument, remoteOApp: Uint8Array | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId) tx.moveCall({ target: this.#target('init_channel', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asObject(tx, messagingChannel), asU32(tx, remoteEid), asBytes32(tx, remoteOApp, this.moduleManager.getUtils()), ], }) } /** * Clear a message from the messaging channel * @param tx - The transaction to add the move call to * @param srcEid - Source endpoint ID * @param sender - Sender address as bytes32 * @param nonce - Message nonce * @param guid - Message GUID as bytes32 * @param message - Message payload */ async clearMoveCall( tx: Transaction, srcEid: number | TransactionArgument, sender: Uint8Array | TransactionArgument, nonce: number | bigint | TransactionArgument, guid: Uint8Array | TransactionArgument, message: Uint8Array | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId) tx.moveCall({ target: this.#target('clear', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asObject(tx, messagingChannel), asU32(tx, srcEid), asBytes32(tx, sender, this.moduleManager.getUtils()), asU64(tx, nonce), asBytes32(tx, guid, this.moduleManager.getUtils()), asBytes(tx, message), ], }) } /** * Skip a message in the messaging channel * @param tx - The transaction to add the move call to * @param srcEid - Source endpoint ID * @param sender - Sender address as bytes32 * @param nonce - Message nonce */ async skipMoveCall( tx: Transaction, srcEid: number | TransactionArgument, sender: Uint8Array | TransactionArgument, nonce: number | bigint | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId) tx.moveCall({ target: this.#target('skip', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asObject(tx, messagingChannel), asU32(tx, srcEid), asBytes32(tx, sender, this.moduleManager.getUtils()), asU64(tx, nonce), ], }) } /** * Nilify a message in the messaging channel * @param tx - The transaction to add the move call to * @param srcEid - Source endpoint ID * @param sender - Sender address as bytes32 * @param nonce - Message nonce * @param payloadHash - Payload hash as bytes32 */ async nilifyMoveCall( tx: Transaction, srcEid: number | TransactionArgument, sender: Uint8Array | TransactionArgument, nonce: number | bigint | TransactionArgument, payloadHash: Uint8Array | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId) tx.moveCall({ target: this.#target('nilify', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asObject(tx, messagingChannel), asU32(tx, srcEid), asBytes32(tx, sender, this.moduleManager.getUtils()), asU64(tx, nonce), asBytes32(tx, payloadHash, this.moduleManager.getUtils()), ], }) } /** * Burn a message in the messaging channel * @param tx - The transaction to add the move call to * @param srcEid - Source endpoint ID * @param sender - Sender address as bytes32 * @param nonce - Message nonce * @param payloadHash - Payload hash as bytes32 */ async burnMoveCall( tx: Transaction, srcEid: number | TransactionArgument, sender: Uint8Array | TransactionArgument, nonce: number | bigint | TransactionArgument, payloadHash: Uint8Array | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId) tx.moveCall({ target: this.#target('burn', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asObject(tx, messagingChannel), asU32(tx, srcEid), asBytes32(tx, sender, this.moduleManager.getUtils()), asU64(tx, nonce), asBytes32(tx, payloadHash, this.moduleManager.getUtils()), ], }) } /** * Set send library for a destination chain * @param tx - The transaction to add the move call to * @param dstEid - Destination endpoint ID * @param sendLibrary - Send library address */ async setSendLibraryMoveCall( tx: Transaction, dstEid: number | TransactionArgument, sendLibrary: string | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) tx.moveCall({ target: this.#target('set_send_library', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asU32(tx, dstEid), asAddress(tx, sendLibrary), ], }) } /** * Set receive library for a source chain * @param tx - The transaction to add the move call to * @param srcEid - Source endpoint ID * @param receiveLibrary - Receive library address * @param gracePeriod - Grace period in seconds */ async setReceiveLibraryMoveCall( tx: Transaction, srcEid: number | TransactionArgument, receiveLibrary: string | TransactionArgument, gracePeriod: number | bigint | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) tx.moveCall({ target: this.#target('set_receive_library', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asU32(tx, srcEid), asAddress(tx, receiveLibrary), asU64(tx, gracePeriod), tx.object.clock(), ], }) } /** * Set receive library timeout for a source chain * @param tx - The transaction to add the move call to * @param srcEid - Source endpoint ID * @param receiveLibrary - Receive library address * @param expiry - Expiry timestamp in seconds */ async setReceiveLibraryTimeoutMoveCall( tx: Transaction, srcEid: number | TransactionArgument, receiveLibrary: string | TransactionArgument, expiry: number | bigint | TransactionArgument ): Promise<void> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) tx.moveCall({ target: this.#target('set_receive_library_timeout', 'endpoint_calls'), arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asU32(tx, srcEid), asAddress(tx, receiveLibrary), asU64(tx, expiry), tx.object.clock(), ], }) } /** * Set configuration for a message library * @param tx - The transaction to add the move call to * @param lib - Library address * @param eid - Endpoint ID * @param configType - Configuration type * @param config - Configuration data as bytes * @returns Transaction result containing Call<MessageLibSetConfigParam, Void> */ async setConfigMoveCall( tx: Transaction, lib: string | TransactionArgument, eid: number | TransactionArgument, configType: number | TransactionArgument, config: Uint8Array | TransactionArgument ): Promise<TransactionResult> { const oappInfo = await this.#oappInfo() const adminCap = await this.getAdminCap(oappInfo.oapp_object) return tx.moveCall({ target: this.#target('set_config', 'endpoint_calls'), typeArguments: [], arguments: [ asObject(tx, oappInfo.oapp_object), asObject(tx, adminCap), asObject(tx, this.objects.endpointV2), asAddress(tx, lib), asU32(tx, eid), asU32(tx, configType), asBytes(tx, config), ], }) } /** * Refresh the cached OApp information by fetching the latest data * @returns Promise<void> - Completes when the OApp info is refreshed */ async refreshOAppInfo(): Promise<void> { this.oappInfo = await this.getOAppInfoV1() } // === Private Functions === /** * Get OApp info, throwing if not set * @returns The OApp info * @throws Error if OApp info is not set * @private */ async #oappInfo(): Promise<OAppInfoV1> { if (!this.oappInfo) { this.oappInfo = await this.getOAppInfoV1() } return this.oappInfo } /** * Generate the full target path for move calls * @param name - The function name to call * @returns The full module path for the move call * @private */ #target(name: string, module_name = MODULE_NAME): string { return `${this.packageId}::${module_name}::${name}` } }