@@ -569,6 +569,159 @@ export const readCoinSymbol = (address: string) =>
569
569
} )
570
570
571
571
572
+ /**
573
+ * PTB: begin_send -> (send_with_coin<T> ...)* -> end_send
574
+ *
575
+ * Mirrors:
576
+ * sui client ptb \
577
+ * --move-call "$PKG::$MOD::begin_send" $CHANNEL_ID $SALT \
578
+ * --assign send_ctx1 \
579
+ * --move-call "$PKG::$MOD::send_with_coin" "<$TYPE_T>" \
580
+ * $RELAY_STORE $VAULT $IBC_STORE $COIN $VERSION $OPCODE $OPERAND send_ctx1 \
581
+ * --assign send_ctx2 \
582
+ * --move-call "$PKG::$MOD::end_send" $IBC_STORE $CLOCK $T_HEIGHT $T_TS send_ctx2 \
583
+ * --gas-budget 150000000
584
+ */
585
+ export const sendInstruction = ( params : {
586
+ packageId : string
587
+
588
+ /** coin type, e.g. "0x2::sui::SUI" (used in typeArguments of send_with_coin) */
589
+ typeArg : string
590
+
591
+ relayStoreId : string // $RELAY_STORE
592
+ vaultId : string // $VAULT
593
+ ibcStoreId : string // $IBC_STORE
594
+ coinObjectId : string // $COIN
595
+
596
+ // extraSendCalls?: Array<{
597
+ // relayStoreId?: string
598
+ // vaultId?: string
599
+ // ibcStoreId?: string
600
+ // coinObjectId?: string
601
+ // version?: number
602
+ // opcode?: number
603
+ // operandHex?: `0x${string}`
604
+ // typeArg?: string
605
+ // }>
606
+
607
+ /** the instruction used purely for encoding operand just like EVM */
608
+ instruction : Ucs03 . Instruction
609
+
610
+ } ) =>
611
+ Effect . gen ( function * ( ) {
612
+ const module = "zkgm"
613
+ const clockObjectId = "0x6" // Sui system clock object
614
+
615
+ const { client, signer } = yield * WalletClient
616
+ const channelId = ( yield * ChannelSource ) . channelId
617
+
618
+ const salt = yield * Utils . generateSalt ( "evm" ) // TODO: check if evm will work here or not
619
+ const timeoutNs = Utils . getTimeoutInNanoseconds24HoursFromNow ( )
620
+ const tHeight = BigInt ( 0 )
621
+
622
+ const operandHex = ( yield * S . encode ( Ucs03 . InstructionFromHex ) ( params . instruction ) ) as `0x${string } `
623
+
624
+ // helpers
625
+ const hexToBytes = ( hex : `0x${string } `) : Uint8Array => {
626
+ const s = hex . slice ( 2 )
627
+ const out = new Uint8Array ( s . length / 2 )
628
+ for ( let i = 0 ; i < out . length ; i ++ ) out [ i ] = parseInt ( s . slice ( i * 2 , i * 2 + 2 ) , 16 )
629
+ return out
630
+ }
631
+
632
+ const tx = new Transaction ( )
633
+ // if (params.gasBudget !== undefined) tx.setGasBudget(BigInt(params.gasBudget as any))
634
+
635
+ let sendCtx = tx . moveCall ( {
636
+ target : `${ params . packageId } ::${ module } ::begin_send` ,
637
+ typeArguments : [ ] ,
638
+ arguments : [
639
+ tx . pure . u32 ( channelId ) ,
640
+ tx . pure . vector ( "u8" , hexToBytes ( salt as `0x${string } `) ) ,
641
+ ] ,
642
+ } )
643
+
644
+ const pushSendWithCoin = ( cfg : {
645
+ relayStoreId : string
646
+ vaultId : string
647
+ ibcStoreId : string
648
+ coinObjectId : string
649
+ version : number
650
+ opcode : number
651
+ operandHex : `0x${string } `
652
+ typeArg : string
653
+ } ) => {
654
+ sendCtx = tx . moveCall ( {
655
+ target : `${ params . packageId } ::${ module } ::send_with_coin` ,
656
+ typeArguments : [ cfg . typeArg ] ,
657
+ arguments : [
658
+ tx . object ( cfg . relayStoreId ) ,
659
+ tx . object ( cfg . vaultId ) ,
660
+ tx . object ( cfg . ibcStoreId ) ,
661
+ tx . object ( cfg . coinObjectId ) ,
662
+ tx . pure . u8 ( cfg . version ) ,
663
+ tx . pure . u8 ( cfg . opcode ) ,
664
+ tx . pure . vector ( "u8" , hexToBytes ( cfg . operandHex ) ) ,
665
+ sendCtx ,
666
+ ] ,
667
+ } )
668
+ }
669
+
670
+ pushSendWithCoin ( {
671
+ relayStoreId : params . relayStoreId ,
672
+ vaultId : params . vaultId ,
673
+ ibcStoreId : params . ibcStoreId ,
674
+ coinObjectId : params . coinObjectId ,
675
+ version : params . instruction . version ,
676
+ opcode : params . instruction . opcode ,
677
+ operandHex,
678
+ typeArg : params . typeArg ,
679
+ } )
680
+
681
+ // TODO: multiple send_with_coin calls if needed??? will this work?
682
+ // for (const extra of params.extraSendCalls ?? []) {
683
+ // pushSendWithCoin({
684
+ // relayStoreId: extra.relayStoreId ?? params.relayStoreId,
685
+ // vaultId: extra.vaultId ?? params.vaultId,
686
+ // ibcStoreId: extra.ibcStoreId ?? params.ibcStoreId,
687
+ // coinObjectId: extra.coinObjectId ?? params.coinObjectId,
688
+ // version: extra.version ?? params.version,
689
+ // opcode: extra.opcode ?? params.opcode,
690
+ // operandHex: (extra.operandHex ?? operandHex) as `0x${string}`,
691
+ // typeArg: extra.typeArg ?? params.typeArg,
692
+ // })
693
+ // }
694
+
695
+ tx . moveCall ( {
696
+ target : `${ params . packageId } ::${ module } ::end_send` ,
697
+ typeArguments : [ ] ,
698
+ arguments : [
699
+ tx . object ( params . ibcStoreId ) ,
700
+ tx . object ( clockObjectId ) ,
701
+ tx . pure . u64 ( tHeight ) ,
702
+ tx . pure . u64 ( BigInt ( timeoutNs ) ) , // ns
703
+ sendCtx ,
704
+ ] ,
705
+ } )
706
+
707
+ const res = yield * Effect . tryPromise ( {
708
+ try : async ( ) =>
709
+ client . signAndExecuteTransaction ( {
710
+ signer,
711
+ transaction : tx ,
712
+ } ) ,
713
+ catch : ( e ) => new WriteContractError ( { cause : extractErrorDetails ( e as Error ) } ) ,
714
+ } )
715
+
716
+ return res
717
+ } )
718
+
719
+ // turn a hex string like "0xdeadbeef" into a number[] of bytes
720
+ function hexToBytes ( hex : string ) : number [ ] {
721
+ const h = hex . startsWith ( "0x" ) ? hex . slice ( 2 ) : hex
722
+ return h . match ( / .{ 1 , 2 } / g) ! . map ( b => parseInt ( b , 16 ) )
723
+ }
724
+
572
725
// // TODO: Decide the parameters here.
573
726
// /**
574
727
// * @category utils
0 commit comments