@layerzerolabs/lz-iotal1-oft-sdk-v2 3.0.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1464 @@
1
+ import { bcs } from '@iota/iota-sdk/bcs'
2
+ import { IotaClient } from '@iota/iota-sdk/client'
3
+ import { Transaction, TransactionArgument, TransactionResult } from '@iota/iota-sdk/transactions'
4
+
5
+ import {
6
+ SDK,
7
+ asAddress,
8
+ asArgWithTx,
9
+ asArgWithTxAsync,
10
+ asBool,
11
+ asBytes,
12
+ asBytes32,
13
+ asObject,
14
+ asU32,
15
+ asU64,
16
+ asU8,
17
+ executeSimulate,
18
+ isTransactionArgument,
19
+ } from '@layerzerolabs/lz-iotal1-sdk-v2'
20
+ import type { IPTBValidator, MessagingFee, ObjectOptions } from '@layerzerolabs/lz-iotal1-sdk-v2'
21
+
22
+ import { parseOFTFeeDetails, parseOFTInfoV1, parseOFTLimit, parseOFTReceipt } from '../bcs/oft'
23
+ import { OFTFeeDetail, OFTInfoV1, OFTLimit, OFTReceipt, SendParam } from '../types'
24
+
25
+ const MODULE_NAME = 'oft'
26
+ const OFT_SENDER_MODULE_NAME = 'oft_sender'
27
+ const OFT_IMPL_MODULE_NAME = 'oft_impl'
28
+ const OFT_PTB_BUILDER_MODULE_NAME = 'oft_ptb_builder'
29
+
30
+ // ==========================================
31
+ // ERROR CODES
32
+ // ==========================================
33
+ // Standard error codes that may be returned by OFT operations
34
+
35
+ export const OFTErrorCode = {
36
+ // OFT related errors
37
+ EComposeMsgNotAllowed: 1,
38
+ EComposeMsgRequired: 2,
39
+ EInsufficientBalance: 3,
40
+ EInvalidAdminCap: 4,
41
+ EInvalidComposeQueue: 5,
42
+ EInvalidLocalDecimals: 6,
43
+ EInvalidMigrationCap: 7,
44
+ EInvalidSendContext: 8,
45
+ ESlippageExceeded: 9,
46
+ EWrongPackageVersion: 10,
47
+ } as const
48
+
49
+ /**
50
+ * OFT (Omnichain Fungible Token) Class
51
+ *
52
+ * The OFT class provides a comprehensive interface for interacting with LayerZero's
53
+ * Omnichain Fungible Token standard on the Iota blockchain. OFTs enable seamless
54
+ * cross-chain token transfers while maintaining fungibility across multiple networks.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * // Initialize OFT instance
59
+ * const oft = new OFT(protocolSDK, oftCallCapId);
60
+ *
61
+ * // Send tokens cross-chain
62
+ * const tx = new Transaction();
63
+ * const coin = await oft.splitCoinMoveCall(tx, sender, amount);
64
+ * await oft.sendMoveCall(tx, sender, sendParam, coin, nativeFee, zroFee, refundAddress);
65
+ * tx.transferObjects([coin], sender);
66
+ * ...
67
+ *
68
+ * // Quote transfer fees
69
+ * const fees = await oft.quoteSend(sender, sendParam, payInZro);
70
+ * ```
71
+ */
72
+ export class OFT {
73
+ /** Iota client for blockchain interactions */
74
+ public readonly client: IotaClient
75
+ /** The OFTCallCap ID */
76
+ public readonly oftCallCapId: string
77
+ /** The package ID of the OFT */
78
+ private oftPackageId?: string
79
+ /** LayerZero protocol object references (endpoint, messaging channels, etc.) */
80
+ private readonly objects: ObjectOptions
81
+ /** The unique object ID of this OFT instance on Iota */
82
+ private oftObjectId?: string
83
+ /** Admin capability object ID for privileged operations (retrieved dynamically when needed) */
84
+ private adminCapId?: string
85
+ /** The Iota coin type this OFT represents (e.g., "0x123::mycoin::MYCOIN") */
86
+ private coinType?: string
87
+ /** The unique object ID of the associated OApp instance on Iota */
88
+ private oappObjectId?: string
89
+ /** Reference to the LayerZero protocol SDK for cross-chain operations */
90
+ private readonly protocolSDK: SDK
91
+ /** The OFTInfoV1 structure */
92
+ private oftInfo?: OFTInfoV1
93
+
94
+ /**
95
+ * Creates a new OFT instance for interacting with an Omnichain Fungible Token
96
+ *
97
+ * @param protocolSDK - The LayerZero protocol SDK instance providing core cross-chain functionality
98
+ * @param oftCallCapId - The OFT call capability ID used for OFT operations and accessing OFT information
99
+ * @param oftObjectId - Optional OFT object ID on Iota blockchain for direct OFT instance access
100
+ * @param coinType - Optional Iota coin type string (e.g., "0x123::mycoin::MYCOIN") that this OFT represents
101
+ * @param oappObjectId - Optional associated OApp object ID for cross-chain messaging operations
102
+ * @param adminCapId - Optional admin capability object ID for privileged operations
103
+ */
104
+ constructor(
105
+ protocolSDK: SDK,
106
+ oftCallCapId: string,
107
+ oftObjectId?: string,
108
+ coinType?: string,
109
+ oappObjectId?: string, // the associated oapp object id
110
+ adminCapId?: string
111
+ ) {
112
+ this.protocolSDK = protocolSDK
113
+ this.oftCallCapId = oftCallCapId
114
+ this.client = protocolSDK.client
115
+ this.objects = protocolSDK.objects
116
+ this.oftObjectId = oftObjectId
117
+ this.oappObjectId = oappObjectId
118
+ this.adminCapId = adminCapId
119
+ this.coinType = coinType
120
+ }
121
+
122
+ /**
123
+ * Updates the associated OApp object ID
124
+ * @param oappObjectId - The new OApp object ID
125
+ */
126
+ setOappObjectId(oappObjectId: string): void {
127
+ this.oappObjectId = oappObjectId
128
+ }
129
+
130
+ // ==========================================
131
+ // INITIALIZATION FUNCTIONS
132
+ // ==========================================
133
+ // These functions are used to initialize OFT instances from OFTCreationTicket
134
+
135
+ /**
136
+ * Initialize an OFT instance with a treasury capability
137
+ * Creates a new OFT that mints its own tokens
138
+ * @param tx - The transaction to add the move call to
139
+ * @param coinType - The Iota coin type string (e.g., "0x123::mycoin::MYCOIN")
140
+ * @param ticket - The OFTCreationTicket object ID or TransactionArgument
141
+ * @param oapp - The OApp object ID or TransactionArgument
142
+ * @param treasury - The TreasuryCap object ID or TransactionArgument for the coin type
143
+ * @param metadata - The CoinMetadata object ID or TransactionArgument for the coin type
144
+ * @param sharedDecimals - Number of decimals to use for cross-chain operations
145
+ * @returns TransactionResult array containing [AdminCap, MigrationCap] - MigrationCap must be transferred or stored
146
+ */
147
+ initOftMoveCall(
148
+ tx: Transaction,
149
+ coinType: string,
150
+ ticket: string | TransactionArgument,
151
+ oapp: string | TransactionArgument,
152
+ treasury: string | TransactionArgument,
153
+ metadata: string | TransactionArgument,
154
+ sharedDecimals: number | TransactionArgument
155
+ ): TransactionResult {
156
+ return tx.moveCall({
157
+ target: `${this.oftCallCapId}::${OFT_IMPL_MODULE_NAME}::init_oft`,
158
+ typeArguments: [coinType],
159
+ arguments: [
160
+ asObject(tx, ticket),
161
+ asObject(tx, oapp),
162
+ asObject(tx, treasury),
163
+ asObject(tx, metadata),
164
+ asU8(tx, sharedDecimals),
165
+ ],
166
+ })
167
+ }
168
+
169
+ /**
170
+ * Initialize an OFT adapter instance
171
+ * Creates an OFT adapter that wraps an existing coin type
172
+ * @param tx - The transaction to add the move call to
173
+ * @param coinType - The Iota coin type string (e.g., "0x123::mycoin::MYCOIN")
174
+ * @param ticket - The OFTCreationTicket object ID or TransactionArgument
175
+ * @param oapp - The OApp object ID or TransactionArgument
176
+ * @param metadata - The CoinMetadata object ID or TransactionArgument for the coin type
177
+ * @param sharedDecimals - Number of decimals to use for cross-chain operations
178
+ * @returns TransactionResult array containing [AdminCap, MigrationCap] - MigrationCap must be transferred or stored
179
+ */
180
+ initOftAdapterMoveCall(
181
+ tx: Transaction,
182
+ coinType: string,
183
+ ticket: string | TransactionArgument,
184
+ oapp: string | TransactionArgument,
185
+ metadata: string | TransactionArgument,
186
+ sharedDecimals: number | TransactionArgument
187
+ ): TransactionResult {
188
+ return tx.moveCall({
189
+ target: `${this.oftCallCapId}::${OFT_IMPL_MODULE_NAME}::init_oft_adapter`,
190
+ typeArguments: [coinType],
191
+ arguments: [asObject(tx, ticket), asObject(tx, oapp), asObject(tx, metadata), asU8(tx, sharedDecimals)],
192
+ })
193
+ }
194
+
195
+ // ==========================================
196
+ // ADMIN FUNCTIONS
197
+ // ==========================================
198
+ // These functions require admin privileges and are used for OFT configuration
199
+ // and management. The admin capability is automatically retrieved from the OFT instance.
200
+
201
+ /**
202
+ * Get LayerZero receive information for OFT registration
203
+ *
204
+ * This function prepares the necessary metadata for registering an OFT
205
+ * with the LayerZero endpoint, enabling it to receive cross-chain messages.
206
+ *
207
+ * @param tx - The transaction to add the move call to
208
+ * @param composerManager - The composer manager object ID for routing compose transfers
209
+ * @returns TransactionResult containing serialized execution metadata for endpoint registration
210
+ */
211
+ async lzReceiveInfoMoveCall(
212
+ tx: Transaction,
213
+ composerManager: string | TransactionArgument
214
+ ): Promise<TransactionResult> {
215
+ return tx.moveCall({
216
+ target: await this.#target('lz_receive_info', OFT_PTB_BUILDER_MODULE_NAME),
217
+ typeArguments: [await this.#coinType()],
218
+ arguments: [
219
+ tx.object(await this.#oftObjectId()),
220
+ tx.object(this.objects.endpointV2),
221
+ asObject(tx, composerManager),
222
+ tx.object.clock(),
223
+ ],
224
+ })
225
+ }
226
+
227
+ /**
228
+ * Register OFT as an OApp with LayerZero endpoint
229
+ * @param tx - The transaction to add the move call to
230
+ * @param coinType - The Iota coin type string (e.g., "0x123::mycoin::MYCOIN")
231
+ * @param oftObjectId - The OFT object ID
232
+ * @param oappObjectId - The OApp object ID
233
+ * @param composerManager - The composer manager object ID or TransactionArgument
234
+ * @param lzReceiveInfo - Optional LayerZero receive info as Uint8Array or TransactionArgument
235
+ */
236
+ async registerOAppMoveCall(
237
+ tx: Transaction,
238
+ coinType: string,
239
+ oftObjectId: string,
240
+ oappObjectId: string,
241
+ composerManager: string | TransactionArgument,
242
+ lzReceiveInfo?: Uint8Array | TransactionArgument
243
+ ): Promise<void> {
244
+ const adminCapId = await executeSimulate(
245
+ this.client,
246
+ (tx) => {
247
+ tx.moveCall({
248
+ target: `${this.oftCallCapId}::${MODULE_NAME}::admin_cap`,
249
+ typeArguments: [coinType],
250
+ arguments: [tx.object(oftObjectId)],
251
+ })
252
+ },
253
+ (result) => bcs.Address.parse(result[0].value)
254
+ )
255
+
256
+ if (lzReceiveInfo === undefined) {
257
+ lzReceiveInfo = tx.moveCall({
258
+ target: `${this.oftCallCapId}::${OFT_PTB_BUILDER_MODULE_NAME}::lz_receive_info`,
259
+ typeArguments: [coinType],
260
+ arguments: [
261
+ tx.object(oftObjectId),
262
+ tx.object(this.objects.endpointV2),
263
+ asObject(tx, composerManager),
264
+ tx.object.clock(),
265
+ ],
266
+ })
267
+ }
268
+
269
+ tx.moveCall({
270
+ target: `${this.oftCallCapId}::${MODULE_NAME}::register_oapp`,
271
+ typeArguments: [coinType],
272
+ arguments: [
273
+ tx.object(oftObjectId),
274
+ tx.object(oappObjectId),
275
+ tx.object(adminCapId),
276
+ tx.object(this.objects.endpointV2),
277
+ asBytes(tx, lzReceiveInfo),
278
+ ],
279
+ })
280
+ }
281
+
282
+ /**
283
+ * Set pause state for OFT operations
284
+ * @param tx - The transaction to add the move call to
285
+ * @param pause - Whether to pause or unpause operations
286
+ */
287
+ async setPauseMoveCall(tx: Transaction, pause: boolean | TransactionArgument): Promise<void> {
288
+ tx.moveCall({
289
+ target: await this.#target('set_pause'),
290
+ typeArguments: [await this.#coinType()],
291
+ arguments: [tx.object(await this.#oftObjectId()), tx.object(await this.#adminCapId()), asBool(tx, pause)],
292
+ })
293
+ }
294
+
295
+ // ==========================================
296
+ // FEE MANAGEMENT ADMIN FUNCTIONS
297
+ // ==========================================
298
+ // Configure fee collection and distribution for OFT transfers
299
+
300
+ /**
301
+ * Set fee deposit address for OFT fees
302
+ * @param tx - The transaction to add the move call to
303
+ * @param feeDepositAddress - The new fee deposit address
304
+ */
305
+ async setFeeDepositAddressMoveCall(
306
+ tx: Transaction,
307
+ feeDepositAddress: string | TransactionArgument
308
+ ): Promise<void> {
309
+ tx.moveCall({
310
+ target: await this.#target('set_fee_deposit_address'),
311
+ typeArguments: [await this.#coinType()],
312
+ arguments: [
313
+ tx.object(await this.#oftObjectId()),
314
+ tx.object(await this.#adminCapId()),
315
+ asAddress(tx, feeDepositAddress),
316
+ ],
317
+ })
318
+ }
319
+
320
+ /**
321
+ * Set default fee basis points for OFT transfers
322
+ * @param tx - The transaction to add the move call to
323
+ * @param feeBps - Default fee rate in basis points (0-10,000, where 10,000 = 100%)
324
+ */
325
+ async setDefaultFeeBpsMoveCall(
326
+ tx: Transaction,
327
+ feeBps: bigint | number | string | TransactionArgument
328
+ ): Promise<void> {
329
+ tx.moveCall({
330
+ target: await this.#target('set_default_fee_bps'),
331
+ typeArguments: [await this.#coinType()],
332
+ arguments: [tx.object(await this.#oftObjectId()), tx.object(await this.#adminCapId()), asU64(tx, feeBps)],
333
+ })
334
+ }
335
+
336
+ /**
337
+ * Set fee basis points for a specific destination chain
338
+ * @param tx - The transaction to add the move call to
339
+ * @param dstEid - Destination endpoint ID
340
+ * @param feeBps - Fee rate in basis points (0-10,000, where 10,000 = 100%)
341
+ */
342
+ async setFeeBpsMoveCall(
343
+ tx: Transaction,
344
+ dstEid: number | TransactionArgument,
345
+ feeBps: bigint | number | string | TransactionArgument
346
+ ): Promise<void> {
347
+ tx.moveCall({
348
+ target: await this.#target('set_fee_bps'),
349
+ typeArguments: [await this.#coinType()],
350
+ arguments: [
351
+ tx.object(await this.#oftObjectId()),
352
+ tx.object(await this.#adminCapId()),
353
+ asU32(tx, dstEid),
354
+ asU64(tx, feeBps),
355
+ ],
356
+ })
357
+ }
358
+
359
+ /**
360
+ * Removes the fee rate for a specific destination chain
361
+ * @param tx - The transaction to add the move call to
362
+ * @param dstEid - Destination endpoint ID
363
+ */
364
+ async unsetFeeBpsMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<void> {
365
+ tx.moveCall({
366
+ target: await this.#target('unset_fee_bps'),
367
+ typeArguments: [await this.#coinType()],
368
+ arguments: [tx.object(await this.#oftObjectId()), tx.object(await this.#adminCapId()), asU32(tx, dstEid)],
369
+ })
370
+ }
371
+
372
+ // ==========================================
373
+ // MIGRATION ADMIN FUNCTIONS
374
+ // ==========================================
375
+ // Handle OFT migration to new contracts
376
+
377
+ /**
378
+ * Migrate OFT instance to a new contract
379
+ * @param tx - The transaction to add the move call to
380
+ * @param migrationCap - Migration capability object ID or transaction argument
381
+ * @returns TransactionResult containing the migration ticket
382
+ */
383
+ async migrateMoveCall(tx: Transaction, migrationCap: string | TransactionArgument): Promise<TransactionResult> {
384
+ return tx.moveCall({
385
+ target: await this.#target('migrate'),
386
+ typeArguments: [await this.#coinType()],
387
+ arguments: [tx.object(await this.#oftObjectId()), asObject(tx, migrationCap)],
388
+ })
389
+ }
390
+
391
+ // ==========================================
392
+ // RATE LIMITER ADMIN FUNCTIONS
393
+ // ==========================================
394
+ // Configure transfer rate limits for security and compliance
395
+
396
+ /**
397
+ * Set rate limit for OFT transfers
398
+ * @param tx - The transaction to add the move call to
399
+ * @param eid - Endpoint ID
400
+ * @param inbound - Whether this is for inbound or outbound transfers
401
+ * @param rateLimit - Rate limit amount
402
+ * @param windowSeconds - Time window in seconds
403
+ */
404
+ async setRateLimitMoveCall(
405
+ tx: Transaction,
406
+ eid: number | TransactionArgument,
407
+ inbound: boolean | TransactionArgument,
408
+ rateLimit: bigint | number | string | TransactionArgument,
409
+ windowSeconds: bigint | number | string | TransactionArgument
410
+ ): Promise<void> {
411
+ tx.moveCall({
412
+ target: await this.#target('set_rate_limit'),
413
+ typeArguments: [await this.#coinType()],
414
+ arguments: [
415
+ tx.object(await this.#oftObjectId()),
416
+ tx.object(await this.#adminCapId()),
417
+ asU32(tx, eid),
418
+ asBool(tx, inbound),
419
+ asU64(tx, rateLimit),
420
+ asU64(tx, windowSeconds),
421
+ tx.object.clock(),
422
+ ],
423
+ })
424
+ }
425
+
426
+ /**
427
+ * Remove rate limit for OFT transfers
428
+ * @param tx - The transaction to add the move call to
429
+ * @param eid - Endpoint ID
430
+ * @param inbound - Whether this is for inbound or outbound transfers
431
+ */
432
+ async unsetRateLimitMoveCall(
433
+ tx: Transaction,
434
+ eid: number | TransactionArgument,
435
+ inbound: boolean | TransactionArgument
436
+ ): Promise<void> {
437
+ tx.moveCall({
438
+ target: await this.#target('unset_rate_limit'),
439
+ typeArguments: [await this.#coinType()],
440
+ arguments: [
441
+ tx.object(await this.#oftObjectId()),
442
+ tx.object(await this.#adminCapId()),
443
+ asU32(tx, eid),
444
+ asBool(tx, inbound),
445
+ ],
446
+ })
447
+ }
448
+
449
+ // ==========================================
450
+ // CORE FUNCTIONS
451
+ // ==========================================
452
+ // Primary OFT operations for token transfers and coin management
453
+
454
+ /**
455
+ * Splits specified amount of coins from user's wallet
456
+ * @param tx - The transaction to add the move call to
457
+ * @param owner - Address of the user whose coins to split
458
+ * @param amount - Amount of coins to split (in smallest units)
459
+ * @param limit - Maximum total number of coins to collect across all pages (default: 200)
460
+ * @param pageSize - Maximum number of coins to fetch per page (default: 50)
461
+ * @returns Promise resolving to split coin as TransactionResult
462
+ * @throws Error if insufficient coins balance or no coins found
463
+ */
464
+ async splitCoinMoveCall(
465
+ tx: Transaction,
466
+ owner: string,
467
+ amount: bigint,
468
+ limit = 200,
469
+ pageSize = 50
470
+ ): Promise<TransactionResult> {
471
+ return this.protocolSDK.getUtils().splitCoinMoveCall(tx, await this.#coinType(), owner, amount, limit, pageSize)
472
+ }
473
+
474
+ /**
475
+ * Send OFT tokens to destination chain
476
+ * @param tx - The transaction to add the move call to
477
+ * @param sender - Sender address for ZRO token operations
478
+ * @param sendParam - Send parameters including destination and amounts
479
+ * @param coinProvided - Coin object ID or transaction result to send
480
+ * @param nativeFee - Native token fee amount
481
+ * @param zroFee - ZRO token fee amount
482
+ * @param refundAddress - Address for fee refunds
483
+ * @param validators - Optional PTB validators for transaction validation
484
+ * @param maxSimulationTimes - Optional maximum number of simulation attempts
485
+ * @returns Promise<void> - Completes when the send operation is processed
486
+ */
487
+ async sendMoveCall(
488
+ tx: Transaction,
489
+ sender: string,
490
+ sendParam: SendParam | TransactionArgument,
491
+ coinProvided: string | TransactionArgument,
492
+ nativeFee: bigint | TransactionArgument,
493
+ zroFee: bigint | TransactionArgument,
494
+ refundAddress: string | TransactionArgument,
495
+ validators?: IPTBValidator[],
496
+ maxSimulationTimes?: number
497
+ ): Promise<void> {
498
+ const sendParamArg = isTransactionArgument(sendParam) ? sendParam : await this.#buildSendParam(tx, sendParam)
499
+ const txSender = await this.txSenderMoveCall(tx)
500
+ const addressArg = isTransactionArgument(refundAddress)
501
+ ? refundAddress
502
+ : tx.pure.option('address', refundAddress)
503
+
504
+ // The oft::send returns a tuple (Call<EndpointSendParam, MessagingReceipt>, OFTReceipt)
505
+ const transactionResult = tx.moveCall({
506
+ target: await this.#target('send'),
507
+ typeArguments: [await this.#coinType()],
508
+ arguments: [
509
+ tx.object(await this.#oftObjectId()),
510
+ tx.object(await this.#oappObjectId()),
511
+ txSender,
512
+ sendParamArg,
513
+ asObject(tx, coinProvided),
514
+ asArgWithTx(tx, nativeFee, (tx, val) => tx.splitCoins(tx.gas, [asU64(tx, val)])[0]),
515
+ await asArgWithTxAsync(tx, zroFee, async (tx, val) =>
516
+ this.protocolSDK.getZro().splitOptionZroTokenMoveCall(tx, sender, val)
517
+ ),
518
+ addressArg,
519
+ tx.object.clock(),
520
+ ],
521
+ })
522
+ const endpointCall = transactionResult[0]
523
+ const oftSendContext = transactionResult[1]
524
+
525
+ await this.protocolSDK
526
+ .getEndpoint()
527
+ .populateSendTransaction(
528
+ tx,
529
+ endpointCall as unknown as TransactionResult,
530
+ sender,
531
+ validators,
532
+ maxSimulationTimes
533
+ )
534
+
535
+ const confirmSendResult = tx.moveCall({
536
+ target: await this.#target('confirm_send'),
537
+ typeArguments: [await this.#coinType()],
538
+ arguments: [
539
+ tx.object(await this.#oftObjectId()),
540
+ tx.object(await this.#oappObjectId()),
541
+ txSender,
542
+ endpointCall,
543
+ oftSendContext,
544
+ ],
545
+ })
546
+ // destroy the empty coins
547
+ const nativeCoin = confirmSendResult[2]
548
+ const zroCoin = confirmSendResult[3]
549
+ tx.moveCall({
550
+ target: '0x1::option::destroy_none',
551
+ arguments: [nativeCoin],
552
+ typeArguments: [`0x2::coin::Coin<0x2::iota::IOTA>`],
553
+ })
554
+ tx.moveCall({
555
+ target: '0x1::option::destroy_none',
556
+ arguments: [zroCoin],
557
+ typeArguments: [`0x2::coin::Coin<${this.protocolSDK.getZro().zroType}>`],
558
+ })
559
+ }
560
+
561
+ /**
562
+ * Process inbound cross-chain token transfers
563
+ * @param tx - The transaction to add the move call to
564
+ * @param call - LayerZero receive call containing the verified cross-chain message
565
+ * @returns TransactionResult containing the processed transfer
566
+ */
567
+ async lzReceiveMoveCall(tx: Transaction, call: string | TransactionArgument): Promise<TransactionResult> {
568
+ return tx.moveCall({
569
+ target: await this.#target('lz_receive'),
570
+ typeArguments: [await this.#coinType()],
571
+ arguments: [
572
+ tx.object(await this.#oftObjectId()),
573
+ tx.object(await this.#oappObjectId()),
574
+ asObject(tx, call),
575
+ tx.object.clock(),
576
+ ],
577
+ })
578
+ }
579
+
580
+ /**
581
+ * Process inbound cross-chain token transfers with compose functionality
582
+ * @param tx - The transaction to add the move call to
583
+ * @param composeQueue - The composer's message queue for sequencing operations
584
+ * @param composerManager - Manager managing token deposits for composers
585
+ * @param call - LayerZero receive call containing the verified cross-chain message
586
+ * @returns TransactionResult containing the processed transfer
587
+ */
588
+ async lzReceiveWithComposeMoveCall(
589
+ tx: Transaction,
590
+ composeQueue: string | TransactionArgument,
591
+ composerManager: string | TransactionArgument,
592
+ call: string | TransactionArgument
593
+ ): Promise<TransactionResult> {
594
+ return tx.moveCall({
595
+ target: await this.#target('lz_receive_with_compose'),
596
+ typeArguments: [await this.#coinType()],
597
+ arguments: [
598
+ tx.object(await this.#oftObjectId()),
599
+ tx.object(await this.#oappObjectId()),
600
+ asObject(tx, composeQueue),
601
+ asObject(tx, composerManager),
602
+ asObject(tx, call),
603
+ tx.object.clock(),
604
+ ],
605
+ })
606
+ }
607
+
608
+ /**
609
+ * Confirms and extracts results from a quote operation
610
+ * @param tx - The transaction to add the move call to
611
+ * @param call - Completed Call object from quote_send() execution
612
+ * @returns TransactionResult containing the messaging fee
613
+ */
614
+ async confirmQuoteSendMoveCall(tx: Transaction, call: string | TransactionArgument): Promise<TransactionResult> {
615
+ return tx.moveCall({
616
+ target: await this.#target('confirm_quote_send'),
617
+ typeArguments: [await this.#coinType()],
618
+ arguments: [
619
+ tx.object(await this.#oftObjectId()),
620
+ tx.object(await this.#oappObjectId()),
621
+ asObject(tx, call),
622
+ ],
623
+ })
624
+ }
625
+
626
+ // ==========================================
627
+ // QUOTE FUNCTIONS
628
+ // ==========================================
629
+ // Calculate fees, limits, and receipts before executing operations
630
+
631
+ /**
632
+ * Quote OFT sending operation with detailed fee breakdown
633
+ * @param sendParam - Send parameters for the OFT operation
634
+ * @returns Promise with limit, fee details, and receipt information
635
+ */
636
+ async quoteOft(
637
+ sendParam: SendParam | TransactionArgument
638
+ ): Promise<{ limit: OFTLimit; feeDetails: OFTFeeDetail[]; receipt: OFTReceipt }> {
639
+ return executeSimulate(
640
+ this.client,
641
+ async (tx) => {
642
+ const sendParamArg = isTransactionArgument(sendParam)
643
+ ? sendParam
644
+ : await this.#buildSendParam(tx, sendParam)
645
+ tx.moveCall({
646
+ target: await this.#target('quote_oft'),
647
+ typeArguments: [await this.#coinType()],
648
+ arguments: [tx.object(await this.#oftObjectId()), sendParamArg, tx.object.clock()],
649
+ })
650
+ },
651
+ (result) => {
652
+ return {
653
+ limit: parseOFTLimit(result[0].value),
654
+ feeDetails: parseOFTFeeDetails(result[1].value),
655
+ receipt: parseOFTReceipt(result[2].value),
656
+ }
657
+ }
658
+ )
659
+ }
660
+
661
+ /**
662
+ * Quote messaging fees for OFT sending
663
+ * @param sender - Sender address
664
+ * @param sendParam - Send parameters for the OFT operation
665
+ * @param payInZro - Whether to pay in ZRO tokens
666
+ * @param validators - Optional PTB validators for transaction validation
667
+ * @param maxSimulationTimes - Optional maximum number of simulation attempts
668
+ * @returns Promise<MessagingFee> - The calculated messaging fees
669
+ */
670
+ async quoteSend(
671
+ sender: string,
672
+ sendParam: SendParam | TransactionArgument,
673
+ payInZro: boolean | TransactionArgument,
674
+ validators?: IPTBValidator[],
675
+ maxSimulationTimes?: number
676
+ ): Promise<MessagingFee> {
677
+ const tx = new Transaction()
678
+ const sendParamArg = isTransactionArgument(sendParam) ? sendParam : await this.#buildSendParam(tx, sendParam)
679
+
680
+ const quoteCall = tx.moveCall({
681
+ target: await this.#target('quote_send'),
682
+ typeArguments: [await this.#coinType()],
683
+ arguments: [
684
+ tx.object(await this.#oftObjectId()),
685
+ tx.object(await this.#oappObjectId()),
686
+ asAddress(tx, sender),
687
+ sendParamArg,
688
+ asBool(tx, payInZro),
689
+ ],
690
+ })
691
+
692
+ return this.protocolSDK.getEndpoint().quote(tx, quoteCall, sender, validators, maxSimulationTimes)
693
+ }
694
+
695
+ // ==========================================
696
+ // VIEW FUNCTIONS
697
+ // ==========================================
698
+ // Read-only functions to query OFT state and configuration
699
+
700
+ /**
701
+ * Get the upgrade version of this OFT instance
702
+ * @param tx - The transaction to add the move call to
703
+ * @returns Transaction result containing the upgrade version
704
+ */
705
+ async upgradeVersionMoveCall(tx: Transaction): Promise<TransactionResult> {
706
+ return tx.moveCall({
707
+ target: await this.#target('upgrade_version'),
708
+ typeArguments: [await this.#coinType()],
709
+ arguments: [tx.object(await this.#oftObjectId())],
710
+ })
711
+ }
712
+
713
+ /**
714
+ * Get the upgrade version of this OFT instance
715
+ * @returns Promise<bigint> - The upgrade version
716
+ */
717
+ async upgradeVersion(): Promise<bigint> {
718
+ return executeSimulate(
719
+ this.client,
720
+ async (tx) => {
721
+ await this.upgradeVersionMoveCall(tx)
722
+ },
723
+ (result) => BigInt(bcs.U64.parse(result[0].value))
724
+ )
725
+ }
726
+
727
+ /**
728
+ * Get the associated OApp object address
729
+ * @param tx - The transaction to add the move call to
730
+ * @returns Transaction result containing the OApp object address
731
+ */
732
+ async oappObjectMoveCall(tx: Transaction): Promise<TransactionResult> {
733
+ return tx.moveCall({
734
+ target: await this.#target('oapp_object'),
735
+ typeArguments: [await this.#coinType()],
736
+ arguments: [tx.object(await this.#oftObjectId())],
737
+ })
738
+ }
739
+
740
+ /**
741
+ * Get the associated OApp object address
742
+ * @returns Promise<string> - The OApp object address
743
+ */
744
+ async oappObject(): Promise<string> {
745
+ return executeSimulate(
746
+ this.client,
747
+ async (tx) => {
748
+ await this.oappObjectMoveCall(tx)
749
+ },
750
+ (result) => bcs.Address.parse(result[0].value)
751
+ )
752
+ }
753
+
754
+ /**
755
+ * Get OFT version information
756
+ * @param tx - The transaction to add the move call to
757
+ * @returns Transaction result containing version information
758
+ */
759
+ async oftVersionMoveCall(tx: Transaction): Promise<TransactionResult> {
760
+ return tx.moveCall({
761
+ target: await this.#target('oft_version'),
762
+ typeArguments: [await this.#coinType()],
763
+ arguments: [tx.object(await this.#oftObjectId())],
764
+ })
765
+ }
766
+
767
+ /**
768
+ * Get OFT version information
769
+ * @returns Promise<{ major: number; minor: number }> - The OFT version
770
+ */
771
+ async oftVersion(): Promise<{ major: number; minor: number }> {
772
+ return executeSimulate(
773
+ this.client,
774
+ async (tx) => {
775
+ await this.oftVersionMoveCall(tx)
776
+ },
777
+ (result) => {
778
+ const major = Number(bcs.U64.parse(result[0].value))
779
+ const minor = Number(bcs.U64.parse(result[1].value))
780
+ return { major, minor }
781
+ }
782
+ )
783
+ }
784
+
785
+ /**
786
+ * Get the OFT capability ID
787
+ * @param tx - The transaction to add the move call to
788
+ * @returns Transaction result containing the OFT capability ID
789
+ */
790
+ async oftCapIdMoveCall(tx: Transaction): Promise<TransactionResult> {
791
+ return tx.moveCall({
792
+ target: await this.#target('oft_cap_id'),
793
+ typeArguments: [await this.#coinType()],
794
+ arguments: [tx.object(await this.#oftObjectId())],
795
+ })
796
+ }
797
+
798
+ /**
799
+ * Get the OFT capability ID
800
+ * @returns Promise<string> - The OFT capability ID
801
+ */
802
+ async oftCapId(): Promise<string> {
803
+ return executeSimulate(
804
+ this.client,
805
+ async (tx) => {
806
+ await this.oftCapIdMoveCall(tx)
807
+ },
808
+ (result) => bcs.Address.parse(result[0].value)
809
+ )
810
+ }
811
+
812
+ /**
813
+ * Get the migration capability address for this OFT
814
+ * @param tx - The transaction to add the move call to
815
+ * @returns Transaction result containing the migration capability address
816
+ */
817
+ async migrationCapMoveCall(tx: Transaction): Promise<TransactionResult> {
818
+ return tx.moveCall({
819
+ target: await this.#target('migration_cap'),
820
+ typeArguments: [await this.#coinType()],
821
+ arguments: [tx.object(await this.#oftObjectId())],
822
+ })
823
+ }
824
+
825
+ /**
826
+ * Get the migration capability address for this OFT
827
+ * @returns Promise<string> - The migration capability address
828
+ */
829
+ async migrationCap(): Promise<string> {
830
+ return executeSimulate(
831
+ this.client,
832
+ async (tx) => {
833
+ await this.migrationCapMoveCall(tx)
834
+ },
835
+ (result) => bcs.Address.parse(result[0].value)
836
+ )
837
+ }
838
+
839
+ async messagingChannel(): Promise<string> {
840
+ return executeSimulate(
841
+ this.client,
842
+ async (tx) => {
843
+ const oftCapId = await this.oftCapIdMoveCall(tx)
844
+ this.protocolSDK.getEndpoint().getMessagingChannelMoveCall(tx, oftCapId)
845
+ },
846
+ (result) => bcs.Address.parse(result[0].value)
847
+ )
848
+ }
849
+
850
+ /**
851
+ * Get coin metadata address
852
+ * @param tx - The transaction to add the move call to
853
+ * @returns Transaction result containing coin metadata address
854
+ */
855
+ async coinMetadataMoveCall(tx: Transaction): Promise<TransactionResult> {
856
+ return tx.moveCall({
857
+ target: await this.#target('coin_metadata'),
858
+ typeArguments: [await this.#coinType()],
859
+ arguments: [tx.object(await this.#oftObjectId())],
860
+ })
861
+ }
862
+
863
+ /**
864
+ * Get coin metadata address
865
+ * @returns Promise<string> - The coin metadata address
866
+ */
867
+ async coinMetadata(): Promise<string> {
868
+ return executeSimulate(
869
+ this.client,
870
+ async (tx) => {
871
+ await this.coinMetadataMoveCall(tx)
872
+ },
873
+ (result) => bcs.Address.parse(result[0].value)
874
+ )
875
+ }
876
+
877
+ /**
878
+ * Get OFT admin capability address
879
+ * @param tx - The transaction to add the move call to
880
+ * @returns Transaction result containing the admin capability address
881
+ */
882
+ async adminCapMoveCall(tx: Transaction): Promise<TransactionResult> {
883
+ return tx.moveCall({
884
+ target: await this.#target('admin_cap'),
885
+ typeArguments: [await this.#coinType()],
886
+ arguments: [tx.object(await this.#oftObjectId())],
887
+ })
888
+ }
889
+
890
+ /**
891
+ * Get OFT admin capability address
892
+ * @returns Promise<string> - The admin capability address
893
+ */
894
+ async adminCap(): Promise<string> {
895
+ return executeSimulate(
896
+ this.client,
897
+ async (tx) => {
898
+ await this.adminCapMoveCall(tx)
899
+ },
900
+ (result) => bcs.Address.parse(result[0].value)
901
+ )
902
+ }
903
+
904
+ /**
905
+ * Get shared decimals for cross-chain compatibility
906
+ * @param tx - The transaction to add the move call to
907
+ * @returns Transaction result containing shared decimals
908
+ */
909
+ async sharedDecimalsMoveCall(tx: Transaction): Promise<TransactionResult> {
910
+ return tx.moveCall({
911
+ target: await this.#target('shared_decimals'),
912
+ typeArguments: [await this.#coinType()],
913
+ arguments: [tx.object(await this.#oftObjectId())],
914
+ })
915
+ }
916
+
917
+ /**
918
+ * Get shared decimals for cross-chain compatibility
919
+ * @returns Promise<number> - The shared decimal precision
920
+ */
921
+ async sharedDecimals(): Promise<number> {
922
+ return executeSimulate(
923
+ this.client,
924
+ async (tx) => {
925
+ await this.sharedDecimalsMoveCall(tx)
926
+ },
927
+ (result) => bcs.U8.parse(result[0].value)
928
+ )
929
+ }
930
+
931
+ /**
932
+ * Get the decimal conversion rate for this OFT
933
+ * @param tx - The transaction to add the move call to
934
+ * @returns Transaction result containing the decimal conversion rate
935
+ */
936
+ async decimalConversionRateMoveCall(tx: Transaction): Promise<TransactionResult> {
937
+ return tx.moveCall({
938
+ target: await this.#target('decimal_conversion_rate'),
939
+ typeArguments: [await this.#coinType()],
940
+ arguments: [tx.object(await this.#oftObjectId())],
941
+ })
942
+ }
943
+
944
+ /**
945
+ * Get the decimal conversion rate for this OFT
946
+ * @returns Promise<bigint> - The decimal conversion rate multiplier
947
+ */
948
+ async decimalConversionRate(): Promise<bigint> {
949
+ return executeSimulate(
950
+ this.client,
951
+ async (tx) => {
952
+ await this.decimalConversionRateMoveCall(tx)
953
+ },
954
+ (result) => BigInt(bcs.U64.parse(result[0].value))
955
+ )
956
+ }
957
+
958
+ /**
959
+ * Check if OFT is paused
960
+ * @param tx - The transaction to add the move call to
961
+ * @returns Transaction result containing the paused status
962
+ */
963
+ async isPausedMoveCall(tx: Transaction): Promise<TransactionResult> {
964
+ return tx.moveCall({
965
+ target: await this.#target('is_paused'),
966
+ typeArguments: [await this.#coinType()],
967
+ arguments: [tx.object(await this.#oftObjectId())],
968
+ })
969
+ }
970
+
971
+ /**
972
+ * Check if OFT is paused
973
+ * @returns Promise<boolean> - True if OFT is paused
974
+ */
975
+ async isPaused(): Promise<boolean> {
976
+ return executeSimulate(
977
+ this.client,
978
+ async (tx) => {
979
+ await this.isPausedMoveCall(tx)
980
+ },
981
+ (result) => bcs.Bool.parse(result[0].value)
982
+ )
983
+ }
984
+
985
+ /**
986
+ * Check if this OFT is an adapter (wraps existing token)
987
+ * @param tx - The transaction to add the move call to
988
+ * @returns Transaction result containing the adapter status
989
+ */
990
+ async isAdapterMoveCall(tx: Transaction): Promise<TransactionResult> {
991
+ return tx.moveCall({
992
+ target: await this.#target('is_adapter'),
993
+ typeArguments: [await this.#coinType()],
994
+ arguments: [tx.object(await this.#oftObjectId())],
995
+ })
996
+ }
997
+
998
+ /**
999
+ * Check if this OFT is an adapter (wraps existing token)
1000
+ * @returns Promise<boolean> - True if this is an OFT adapter
1001
+ */
1002
+ async isAdapter(): Promise<boolean> {
1003
+ return executeSimulate(
1004
+ this.client,
1005
+ async (tx) => {
1006
+ await this.isAdapterMoveCall(tx)
1007
+ },
1008
+ (result) => bcs.Bool.parse(result[0].value)
1009
+ )
1010
+ }
1011
+
1012
+ /**
1013
+ * Decode OFTInfoV1 from encoded bytes
1014
+ * @param tx - The transaction to add the move call to
1015
+ * @param encodedBytes - The encoded OFTInfoV1 bytes to decode
1016
+ * @returns Transaction result containing the decoded OFTInfoV1
1017
+ */
1018
+ decodeOftInfoV1MoveCall(tx: Transaction, encodedBytes: Uint8Array | TransactionArgument): TransactionResult {
1019
+ return tx.moveCall({
1020
+ target: `${this.oftCallCapId}::oft_info_v1::decode`,
1021
+ arguments: [asBytes(tx, encodedBytes)],
1022
+ })
1023
+ }
1024
+
1025
+ /**
1026
+ * Decode OFTInfoV1 from encoded bytes
1027
+ * @param encodedBytes - The encoded OFTInfoV1 bytes to decode
1028
+ * @returns Promise<OFTInfoV1> - The decoded OFTInfoV1 structure
1029
+ */
1030
+ async decodeOftInfoV1(encodedBytes: Uint8Array): Promise<OFTInfoV1> {
1031
+ return executeSimulate(
1032
+ this.client,
1033
+ (tx) => {
1034
+ this.decodeOftInfoV1MoveCall(tx, encodedBytes)
1035
+ },
1036
+ (result) => parseOFTInfoV1(result[0].value)
1037
+ )
1038
+ }
1039
+
1040
+ // ==========================================
1041
+ // FEE VIEW FUNCTIONS
1042
+ // ==========================================
1043
+ // Query current fee configuration and settings
1044
+
1045
+ /**
1046
+ * Check if the OFT has a fee rate greater than 0 for the specified destination
1047
+ * @param tx - The transaction to add the move call to
1048
+ * @param dstEid - Destination endpoint ID
1049
+ * @returns Transaction result containing whether fee exists
1050
+ */
1051
+ async hasOftFeeMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<TransactionResult> {
1052
+ return tx.moveCall({
1053
+ target: await this.#target('has_oft_fee'),
1054
+ typeArguments: [await this.#coinType()],
1055
+ arguments: [tx.object(await this.#oftObjectId()), asU32(tx, dstEid)],
1056
+ })
1057
+ }
1058
+
1059
+ /**
1060
+ * Check if the OFT has a fee rate greater than 0 for the specified destination
1061
+ * @param dstEid - Destination endpoint ID
1062
+ * @returns Promise<boolean> - True if fee exists
1063
+ */
1064
+ async hasOftFee(dstEid: number): Promise<boolean> {
1065
+ return executeSimulate(
1066
+ this.client,
1067
+ async (tx) => {
1068
+ await this.hasOftFeeMoveCall(tx, dstEid)
1069
+ },
1070
+ (result) => bcs.Bool.parse(result[0].value)
1071
+ )
1072
+ }
1073
+
1074
+ /**
1075
+ * Get the effective fee rate for a specific destination chain
1076
+ * @param tx - The transaction to add the move call to
1077
+ * @param dstEid - Destination endpoint ID
1078
+ * @returns Transaction result containing the effective fee basis points
1079
+ */
1080
+ async effectiveFeeBpsMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<TransactionResult> {
1081
+ return tx.moveCall({
1082
+ target: await this.#target('effective_fee_bps'),
1083
+ typeArguments: [await this.#coinType()],
1084
+ arguments: [tx.object(await this.#oftObjectId()), asU32(tx, dstEid)],
1085
+ })
1086
+ }
1087
+
1088
+ /**
1089
+ * Get the effective fee rate for a specific destination chain
1090
+ * @param dstEid - Destination endpoint ID
1091
+ * @returns Promise<bigint> - The effective fee in basis points
1092
+ */
1093
+ async effectiveFeeBps(dstEid: number): Promise<bigint> {
1094
+ return executeSimulate(
1095
+ this.client,
1096
+ async (tx) => {
1097
+ await this.effectiveFeeBpsMoveCall(tx, dstEid)
1098
+ },
1099
+ (result) => BigInt(bcs.U64.parse(result[0].value))
1100
+ )
1101
+ }
1102
+
1103
+ /**
1104
+ * Get the default fee rate
1105
+ * @param tx - The transaction to add the move call to
1106
+ * @returns Transaction result containing the default fee basis points
1107
+ */
1108
+ async defaultFeeBpsMoveCall(tx: Transaction): Promise<TransactionResult> {
1109
+ return tx.moveCall({
1110
+ target: await this.#target('default_fee_bps'),
1111
+ typeArguments: [await this.#coinType()],
1112
+ arguments: [tx.object(await this.#oftObjectId())],
1113
+ })
1114
+ }
1115
+
1116
+ /**
1117
+ * Get the default fee rate
1118
+ * @returns Promise<bigint> - The default fee in basis points
1119
+ */
1120
+ async defaultFeeBps(): Promise<bigint> {
1121
+ return executeSimulate(
1122
+ this.client,
1123
+ async (tx) => {
1124
+ await this.defaultFeeBpsMoveCall(tx)
1125
+ },
1126
+ (result) => BigInt(bcs.U64.parse(result[0].value))
1127
+ )
1128
+ }
1129
+
1130
+ /**
1131
+ * Get fee basis points for a specific destination chain
1132
+ * @param tx - The transaction to add the move call to
1133
+ * @param dstEid - Destination endpoint ID
1134
+ * @returns Transaction result containing the fee basis points
1135
+ */
1136
+ async feeBpsMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<TransactionResult> {
1137
+ return tx.moveCall({
1138
+ target: await this.#target('fee_bps'),
1139
+ typeArguments: [await this.#coinType()],
1140
+ arguments: [tx.object(await this.#oftObjectId()), asU32(tx, dstEid)],
1141
+ })
1142
+ }
1143
+
1144
+ /**
1145
+ * Get fee basis points for a specific destination chain
1146
+ * @param dstEid - Destination endpoint ID
1147
+ * @returns Promise<bigint> - The fee in basis points
1148
+ */
1149
+ async feeBps(dstEid: number): Promise<bigint> {
1150
+ return executeSimulate(
1151
+ this.client,
1152
+ async (tx) => {
1153
+ await this.feeBpsMoveCall(tx, dstEid)
1154
+ },
1155
+ (result) => BigInt(bcs.U64.parse(result[0].value))
1156
+ )
1157
+ }
1158
+
1159
+ /**
1160
+ * Get fee deposit address for OFT
1161
+ * @param tx - The transaction to add the move call to
1162
+ * @returns Transaction result containing the fee deposit address
1163
+ */
1164
+ async feeDepositAddressMoveCall(tx: Transaction): Promise<TransactionResult> {
1165
+ return tx.moveCall({
1166
+ target: await this.#target('fee_deposit_address'),
1167
+ typeArguments: [await this.#coinType()],
1168
+ arguments: [tx.object(await this.#oftObjectId())],
1169
+ })
1170
+ }
1171
+
1172
+ /**
1173
+ * Get fee deposit address for OFT
1174
+ * @returns Promise<string> - The fee deposit address
1175
+ */
1176
+ async feeDepositAddress(): Promise<string> {
1177
+ return executeSimulate(
1178
+ this.client,
1179
+ async (tx) => {
1180
+ await this.feeDepositAddressMoveCall(tx)
1181
+ },
1182
+ (result) => bcs.Address.parse(result[0].value)
1183
+ )
1184
+ }
1185
+
1186
+ // ==========================================
1187
+ // RATE LIMITER VIEW FUNCTIONS
1188
+ // ==========================================
1189
+ // Query current rate limiting configuration and status
1190
+
1191
+ /**
1192
+ * Get rate limit configuration for an endpoint
1193
+ * @param tx - The transaction to add the move call to
1194
+ * @param eid - Endpoint ID
1195
+ * @param inbound - Whether this is for inbound or outbound transfers
1196
+ * @returns Transaction result containing rate limit configuration
1197
+ */
1198
+ async rateLimitConfigMoveCall(
1199
+ tx: Transaction,
1200
+ eid: number | TransactionArgument,
1201
+ inbound: boolean | TransactionArgument
1202
+ ): Promise<TransactionResult> {
1203
+ return tx.moveCall({
1204
+ target: await this.#target('rate_limit_config'),
1205
+ typeArguments: [await this.#coinType()],
1206
+ arguments: [tx.object(await this.#oftObjectId()), asU32(tx, eid), asBool(tx, inbound)],
1207
+ })
1208
+ }
1209
+
1210
+ /**
1211
+ * Get rate limit configuration for an endpoint
1212
+ * @param eid - Endpoint ID
1213
+ * @param inbound - Whether this is for inbound or outbound transfers
1214
+ * @returns Promise<{ limit: bigint; windowSeconds: bigint }> - Rate limit configuration
1215
+ */
1216
+ async rateLimitConfig(eid: number, inbound: boolean): Promise<{ limit: bigint; windowSeconds: bigint }> {
1217
+ return executeSimulate(
1218
+ this.client,
1219
+ async (tx) => {
1220
+ await this.rateLimitConfigMoveCall(tx, eid, inbound)
1221
+ },
1222
+ (result) => {
1223
+ const limit = BigInt(bcs.U64.parse(result[0].value))
1224
+ const windowSeconds = BigInt(bcs.U64.parse(result[1].value))
1225
+ return { limit, windowSeconds }
1226
+ }
1227
+ )
1228
+ }
1229
+
1230
+ /**
1231
+ * Get current in-flight amount for rate limiting
1232
+ * @param tx - The transaction to add the move call to
1233
+ * @param eid - Endpoint ID
1234
+ * @param inbound - Whether this is for inbound or outbound transfers
1235
+ * @returns Transaction result containing in-flight amount
1236
+ */
1237
+ async rateLimitInFlightMoveCall(
1238
+ tx: Transaction,
1239
+ eid: number | TransactionArgument,
1240
+ inbound: boolean | TransactionArgument
1241
+ ): Promise<TransactionResult> {
1242
+ return tx.moveCall({
1243
+ target: await this.#target('rate_limit_in_flight'),
1244
+ typeArguments: [await this.#coinType()],
1245
+ arguments: [tx.object(await this.#oftObjectId()), asU32(tx, eid), asBool(tx, inbound), tx.object.clock()],
1246
+ })
1247
+ }
1248
+
1249
+ /**
1250
+ * Get current in-flight amount for rate limiting
1251
+ * @param eid - Endpoint ID
1252
+ * @param inbound - Whether this is for inbound or outbound transfers
1253
+ * @returns Promise<bigint> - Current in-flight amount
1254
+ */
1255
+ async rateLimitInFlight(eid: number, inbound: boolean): Promise<bigint> {
1256
+ return executeSimulate(
1257
+ this.client,
1258
+ async (tx) => {
1259
+ await this.rateLimitInFlightMoveCall(tx, eid, inbound)
1260
+ },
1261
+ (result) => BigInt(bcs.U64.parse(result[0].value))
1262
+ )
1263
+ }
1264
+
1265
+ /**
1266
+ * Get rate limit capacity (remaining available amount)
1267
+ * @param tx - The transaction to add the move call to
1268
+ * @param eid - Endpoint ID
1269
+ * @param inbound - Whether this is for inbound or outbound transfers
1270
+ * @returns Transaction result containing rate limit capacity
1271
+ */
1272
+ async rateLimitCapacityMoveCall(
1273
+ tx: Transaction,
1274
+ eid: number | TransactionArgument,
1275
+ inbound: boolean | TransactionArgument
1276
+ ): Promise<TransactionResult> {
1277
+ return tx.moveCall({
1278
+ target: await this.#target('rate_limit_capacity'),
1279
+ typeArguments: [await this.#coinType()],
1280
+ arguments: [tx.object(await this.#oftObjectId()), asU32(tx, eid), asBool(tx, inbound), tx.object.clock()],
1281
+ })
1282
+ }
1283
+
1284
+ /**
1285
+ * Get rate limit capacity (remaining available amount)
1286
+ * @param eid - Endpoint ID
1287
+ * @param inbound - Whether this is for inbound or outbound transfers
1288
+ * @returns Promise<bigint> - Remaining rate limit capacity
1289
+ */
1290
+ async rateLimitCapacity(eid: number, inbound: boolean): Promise<bigint> {
1291
+ return executeSimulate(
1292
+ this.client,
1293
+ async (tx) => {
1294
+ await this.rateLimitCapacityMoveCall(tx, eid, inbound)
1295
+ },
1296
+ (result) => BigInt(bcs.U64.parse(result[0].value))
1297
+ )
1298
+ }
1299
+
1300
+ // ==========================================
1301
+ // OFT SENDER
1302
+ // ==========================================
1303
+
1304
+ /**
1305
+ * Create a transaction sender object for OFT operations
1306
+ * @param tx - The transaction to add the move call to
1307
+ * @returns Transaction result containing the transaction sender object
1308
+ */
1309
+ async txSenderMoveCall(tx: Transaction): Promise<TransactionResult> {
1310
+ return tx.moveCall({
1311
+ target: await this.#target('tx_sender', OFT_SENDER_MODULE_NAME),
1312
+ })
1313
+ }
1314
+
1315
+ // ==========================================
1316
+ // PRIVATE HELPER FUNCTIONS
1317
+ // ==========================================
1318
+ // Internal utility functions for OFT operations
1319
+
1320
+ /**
1321
+ * Build SendParam struct for OFT operations
1322
+ * @param tx - The transaction to add the move call to
1323
+ * @param param - Send parameters to build
1324
+ * @returns Transaction result containing the SendParam struct
1325
+ * @private
1326
+ */
1327
+ async #buildSendParam(tx: Transaction, param: SendParam): Promise<TransactionResult> {
1328
+ return tx.moveCall({
1329
+ target: await this.#target('create', 'send_param'),
1330
+ arguments: [
1331
+ asU32(tx, param.dstEid),
1332
+ asBytes32(tx, param.to, this.protocolSDK.getUtils()),
1333
+ asU64(tx, param.amountLd),
1334
+ asU64(tx, param.minAmountLd),
1335
+ asBytes(tx, param.extraOptions),
1336
+ asBytes(tx, param.composeMsg),
1337
+ asBytes(tx, param.oftCmd),
1338
+ ],
1339
+ })
1340
+ }
1341
+
1342
+ /**
1343
+ * Generate the full target path for move calls
1344
+ * @param name - The function name to call
1345
+ * @param module_name - The module name (defaults to 'oft')
1346
+ * @returns The full module path for the move call
1347
+ * @private
1348
+ */
1349
+ async #target(name: string, module_name = MODULE_NAME): Promise<string> {
1350
+ return `${await this.#oftPackageId()}::${module_name}::${name}`
1351
+ }
1352
+
1353
+ /**
1354
+ * Get the OApp object ID, throwing an error if not available
1355
+ * @returns The OApp object ID
1356
+ * @throws Error if OApp object ID was not set
1357
+ * @private
1358
+ */
1359
+ async #oappObjectId(): Promise<string> {
1360
+ if (this.oappObjectId === undefined) {
1361
+ const oappSdk = this.protocolSDK.getOApp(this.oftCallCapId)
1362
+ const oappInfo = await oappSdk.getOAppInfoV1()
1363
+ this.oappObjectId = oappInfo.oapp_object
1364
+ }
1365
+ return this.oappObjectId
1366
+ }
1367
+
1368
+ /**
1369
+ * Get the OFT object ID, automatically retrieving it from the OFT info if not cached
1370
+ * @returns The OFT object ID
1371
+ * @throws Error if OFT object ID cannot be retrieved
1372
+ * @private
1373
+ */
1374
+ async #oftObjectId(): Promise<string> {
1375
+ if (this.oftObjectId === undefined) {
1376
+ const oftInfo = await this.#OftInfo()
1377
+ this.oftObjectId = oftInfo.oftObject
1378
+ }
1379
+ return this.oftObjectId
1380
+ }
1381
+
1382
+ /**
1383
+ * Get the admin capability ID, automatically retrieving it from the OFT instance if not cached
1384
+ * @returns The admin capability ID
1385
+ * @throws Error if admin capability cannot be retrieved from the OFT instance
1386
+ * @private
1387
+ */
1388
+ async #adminCapId(): Promise<string> {
1389
+ if (this.adminCapId === undefined) {
1390
+ this.adminCapId = await this.adminCap()
1391
+ }
1392
+ return this.adminCapId
1393
+ }
1394
+
1395
+ /**
1396
+ * Get the coin type, automatically extracting it from the OFT object if not cached
1397
+ * @returns The coin type string (e.g., "0x123::mycoin::MYCOIN")
1398
+ * @throws Error if coin type cannot be extracted from the OFT object
1399
+ * @private
1400
+ */
1401
+ async #coinType(): Promise<string> {
1402
+ if (this.coinType === undefined) {
1403
+ const oftInfo = await this.client.getObject({
1404
+ id: await this.#oftObjectId(),
1405
+ options: {
1406
+ showContent: true,
1407
+ },
1408
+ })
1409
+
1410
+ const content = oftInfo.data?.content as { type?: string; dataType?: string } | undefined
1411
+ if (content?.dataType !== 'moveObject' || content.type == null || content.type.length === 0) {
1412
+ throw new Error('Invalid OFT object data or missing type field')
1413
+ }
1414
+
1415
+ // Extract the coin type from the first pair of angle brackets, e.g.:
1416
+ // "0xdd39db3a038c70a71fbffedf6b32a50383ace0b5f219a617238bd2fbee1995b0::oft::OFT<0x428907130f475ef50f76c8944d5960772e135e4d64bcb019e181030377049215::test_coin::TEST_COIN>"
1417
+ const typeStr = content.type
1418
+ // Match the first angle-bracketed substring
1419
+ const angleBracketMatch = typeStr.match(/<([^>]+)>/)
1420
+ const coinType = angleBracketMatch?.[1]
1421
+ if (coinType === undefined || coinType === '') {
1422
+ throw new Error('Failed to extract coinType from object type')
1423
+ }
1424
+ this.coinType = coinType
1425
+ }
1426
+ return this.coinType
1427
+ }
1428
+
1429
+ /**
1430
+ * Get OApp info, throwing if not set
1431
+ * @returns The OApp info
1432
+ * @throws Error if OApp info is not set
1433
+ * @private
1434
+ */
1435
+ async #OftInfo(): Promise<OFTInfoV1> {
1436
+ if (!this.oftInfo) {
1437
+ const oappSdk = this.protocolSDK.getOApp(this.oftCallCapId)
1438
+ this.oftInfo = await executeSimulate(
1439
+ this.client,
1440
+ (tx) => {
1441
+ const oappInfo = oappSdk.getOAppInfoV1MoveCall(tx)
1442
+ const extraInfo = oappSdk.getOAppInfoV1ExtraInfoMoveCall(tx, oappInfo)
1443
+ this.decodeOftInfoV1MoveCall(tx, extraInfo)
1444
+ },
1445
+ (result) => parseOFTInfoV1(result[0].value)
1446
+ )
1447
+ }
1448
+ return this.oftInfo
1449
+ }
1450
+
1451
+ /**
1452
+ * Get the OFT package ID, automatically retrieving it from the OFT info if not cached
1453
+ * @returns The OFT package ID
1454
+ * @throws Error if OFT package ID cannot be retrieved
1455
+ * @private
1456
+ */
1457
+ async #oftPackageId(): Promise<string> {
1458
+ if (this.oftPackageId === undefined) {
1459
+ const oftInfo = await this.#OftInfo()
1460
+ this.oftPackageId = oftInfo.oftPackage
1461
+ }
1462
+ return this.oftPackageId
1463
+ }
1464
+ }