Skip to content

Commit ef0e39d

Browse files
committed
chore(ts-sdk-sui): created submit instruction function
Signed-off-by: kaancaglan <[email protected]>
1 parent 38c5462 commit ef0e39d

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// @ts-ignore
2+
if (typeof BigInt.prototype.toJSON !== "function") {
3+
// @ts-ignore
4+
BigInt.prototype.toJSON = function () {
5+
return this.toString()
6+
}
7+
}
8+
import { Effect, Logger } from "effect"
9+
import { getFullnodeUrl } from "@mysten/sui/client"
10+
import { PublicClient, WalletClient, writeContract, readCoinMetadata, readCoinBalances, sendInstruction } from "../src/Sui.js"
11+
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519"
12+
import { Transaction } from "@mysten/sui/transactions"
13+
14+
const MNEMONIC = process.env.MNEMONIC ?? "fix auto gallery heart practice drip joke nice decline lift attend bread"
15+
const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b"
16+
17+
const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC)
18+
19+
20+
const program = Effect.gen(function* () {
21+
const { client } = yield* PublicClient
22+
yield* Effect.log("Sui public client initialized", client.network )
23+
const meta = yield* readCoinMetadata("0x2::sui::SUI" as any)
24+
yield* Effect.log("SUI metadata", meta)
25+
26+
yield* Effect.log("keypair.getPublicKey().toSuiAddress()", keypair.getPublicKey().toSuiAddress())
27+
const balances = yield* readCoinBalances("0x2::sui::SUI" as any, keypair.getPublicKey().toSuiAddress() as any)
28+
yield* Effect.log("SUI balances", balances)
29+
30+
31+
const wallet = yield* WalletClient
32+
const amountMist = 10_000_000n // 0.01 SUI
33+
34+
const tx = new Transaction()
35+
const coin = tx.splitCoins(tx.gas, [tx.pure.u64(amountMist)])
36+
const recipient = tx.pure.address(RECIPIENT)
37+
38+
const res = yield* writeContract(
39+
client,
40+
keypair,
41+
"0x2", // packageId: Sui framework
42+
"transfer", // module: sui::transfer
43+
"public_transfer", // function
44+
["0x2::coin::Coin<0x2::sui::SUI>"], // type arg T
45+
[coin, recipient], // (obj: T, recipient: address)
46+
tx,
47+
)
48+
49+
yield* Effect.log("Transfer submitted", res)
50+
51+
52+
}).pipe(
53+
Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })),
54+
Effect.provide(
55+
WalletClient.Live({
56+
url: getFullnodeUrl("testnet"),
57+
account: keypair, // signer
58+
chain: "sui-testnet" as any, // placeholder; not used internally
59+
}),
60+
),
61+
62+
Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)),
63+
)
64+
65+
Effect.runPromise(program).catch(console.error)

ts-sdk-sui/src/Sui.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,159 @@ export const readCoinSymbol = (address: string) =>
569569
})
570570

571571

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+
572725
// // TODO: Decide the parameters here.
573726
// /**
574727
// * @category utils

0 commit comments

Comments
 (0)