From 127a60a81414aec56509f53f3f73ed98690800cf Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 24 Sep 2025 14:25:28 +0300 Subject: [PATCH 01/24] chore(ts-sdk-sui): started Signed-off-by: kaancaglan --- pnpm-workspace.yaml | 1 + ts-sdk-sui/.gitignore | 1 + ts-sdk-sui/LICENSE | 21 + ts-sdk-sui/README.md | 3 + ts-sdk-sui/docgen.json | 5 + .../sui-create-client-read-and-write.ts | 65 ++ ts-sdk-sui/package.json | 74 +++ ts-sdk-sui/src/Sui.ts | 581 ++++++++++++++++++ ts-sdk-sui/src/SuiZkgmClient.ts | 19 + ts-sdk-sui/src/index.ts | 20 + ts-sdk-sui/src/internal/sui.ts | 63 ++ ts-sdk-sui/src/internal/zkgmClient.ts | 237 +++++++ ts-sdk-sui/ts-sdk-sui.nix | 46 ++ ts-sdk-sui/tsconfig.build.json | 10 + ts-sdk-sui/tsconfig.examples.json | 11 + ts-sdk-sui/tsconfig.json | 15 + ts-sdk-sui/tsconfig.src.json | 10 + ts-sdk-sui/tsconfig.test.json | 10 + ts-sdk-sui/vitest.config.ts | 6 + 19 files changed, 1198 insertions(+) create mode 100644 ts-sdk-sui/.gitignore create mode 100644 ts-sdk-sui/LICENSE create mode 100644 ts-sdk-sui/README.md create mode 100644 ts-sdk-sui/docgen.json create mode 100644 ts-sdk-sui/examples/sui-create-client-read-and-write.ts create mode 100644 ts-sdk-sui/package.json create mode 100644 ts-sdk-sui/src/Sui.ts create mode 100644 ts-sdk-sui/src/SuiZkgmClient.ts create mode 100644 ts-sdk-sui/src/index.ts create mode 100644 ts-sdk-sui/src/internal/sui.ts create mode 100644 ts-sdk-sui/src/internal/zkgmClient.ts create mode 100644 ts-sdk-sui/ts-sdk-sui.nix create mode 100644 ts-sdk-sui/tsconfig.build.json create mode 100644 ts-sdk-sui/tsconfig.examples.json create mode 100644 ts-sdk-sui/tsconfig.json create mode 100644 ts-sdk-sui/tsconfig.src.json create mode 100644 ts-sdk-sui/tsconfig.test.json create mode 100644 ts-sdk-sui/vitest.config.ts diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6f3c17c06e..ee1397bbc9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,3 +9,4 @@ packages: - 'ts-sdk-evm' - 'typescript-sdk' - 'zkgm-dev' + - 'ts-sdk-sui' \ No newline at end of file diff --git a/ts-sdk-sui/.gitignore b/ts-sdk-sui/.gitignore new file mode 100644 index 0000000000..06c5e16854 --- /dev/null +++ b/ts-sdk-sui/.gitignore @@ -0,0 +1 @@ +dist/** diff --git a/ts-sdk-sui/LICENSE b/ts-sdk-sui/LICENSE new file mode 100644 index 0000000000..9453b523bd --- /dev/null +++ b/ts-sdk-sui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Union.fi Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ts-sdk-sui/README.md b/ts-sdk-sui/README.md new file mode 100644 index 0000000000..a5b9b42afe --- /dev/null +++ b/ts-sdk-sui/README.md @@ -0,0 +1,3 @@ +# Union TypeScript SDK for SUI + +`@unionlabs/sdk-sui` diff --git a/ts-sdk-sui/docgen.json b/ts-sdk-sui/docgen.json new file mode 100644 index 0000000000..e9f6ec68ed --- /dev/null +++ b/ts-sdk-sui/docgen.json @@ -0,0 +1,5 @@ +{ + "$schema": "../node_modules/@effect/docgen/schema.json", + "srcLink": "https://github.com/unionlabs/union/tree/main/ts-sdk-sui/src/", + "exclude": ["src/internal/**/*.ts"] +} diff --git a/ts-sdk-sui/examples/sui-create-client-read-and-write.ts b/ts-sdk-sui/examples/sui-create-client-read-and-write.ts new file mode 100644 index 0000000000..5a3612fd76 --- /dev/null +++ b/ts-sdk-sui/examples/sui-create-client-read-and-write.ts @@ -0,0 +1,65 @@ +// @ts-ignore +if (typeof BigInt.prototype.toJSON !== "function") { + // @ts-ignore + BigInt.prototype.toJSON = function () { + return this.toString() + } +} +import { Effect, Logger } from "effect" +import { getFullnodeUrl } from "@mysten/sui/client" +import { PublicClient, WalletClient, writeContract, readCoinMetadata, readCoinBalances } from "../src/Sui.js" +import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" +import { Transaction } from "@mysten/sui/transactions" + +const MNEMONIC = process.env.MNEMONIC ?? "fix auto gallery heart practice drip joke nice decline lift attend bread" +const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" + +const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) + + +const program = Effect.gen(function* () { + const { client } = yield* PublicClient + yield* Effect.log("Sui public client initialized", client.network ) + const meta = yield* readCoinMetadata("0x2::sui::SUI" as any) + yield* Effect.log("SUI metadata", meta) + + yield* Effect.log("keypair.getPublicKey().toSuiAddress()", keypair.getPublicKey().toSuiAddress()) + const balances = yield* readCoinBalances("0x2::sui::SUI" as any, keypair.getPublicKey().toSuiAddress() as any) + yield* Effect.log("SUI balances", balances) + + + const wallet = yield* WalletClient + const amountMist = 10_000_000n // 0.01 SUI + + const tx = new Transaction() + const coin = tx.splitCoins(tx.gas, [tx.pure.u64(amountMist)]) + const recipient = tx.pure.address(RECIPIENT) + + const res = yield* writeContract( + client, + keypair, + "0x2", // packageId: Sui framework + "transfer", // module: sui::transfer + "public_transfer", // function + ["0x2::coin::Coin<0x2::sui::SUI>"], // type arg T + [coin, recipient], // (obj: T, recipient: address) + tx, + ) + + yield* Effect.log("Transfer submitted", res) + + +}).pipe( + Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), + Effect.provide( + WalletClient.Live({ + url: getFullnodeUrl("testnet"), + account: keypair, // signer + chain: "sui-testnet" as any, // placeholder; not used internally + }), + ), + + Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), +) + +Effect.runPromise(program).catch(console.error) diff --git a/ts-sdk-sui/package.json b/ts-sdk-sui/package.json new file mode 100644 index 0000000000..5f469dc246 --- /dev/null +++ b/ts-sdk-sui/package.json @@ -0,0 +1,74 @@ +{ + "name": "@unionlabs/sdk-sui", + "version": "0.0.0", + "type": "module", + "license": "MIT", + "author": "@unionlabs", + "homepage": "https://docs.union.build/typescript", + "description": "Union TypeScript SDK for Sui", + "repository": { + "type": "git", + "url": "https://github.com/unionlabs/union.git", + "directory": "ts-sdk-sui" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "directory": "dist", + "linkDirectory": false + }, + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./examples/*": "./examples/*.ts", + "./internal/*": null + }, + "scripts": { + "build": "pnpm build-esm && pnpm build-annotate && pnpm build-cjs && build-utils pack-v3", + "build-annotate": "babel build/esm --plugins annotate-pure-calls --out-dir build/esm --source-maps", + "build-cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "build-esm": "tsc -b tsconfig.build.json", + "check": "tsc -b tsconfig.json", + "check:circular": "dpdm -T src", + "check:examples": "tsc -p tsconfig.examples.json", + "codegen": "build-utils prepare-v3", + "test": "vitest run", + "test:watch": "vitest" + }, + "peerDependencies": { + "@effect/platform": "^0.84", + "@unionlabs/sdk": "workspace:^", + "@safe-global/safe-apps-sdk": "^9", + "effect": "^3.16", + "viem": "^2" + }, + "peerDependenciesMeta": { + "@safe-global/safe-apps-sdk": { + "optional": true + } + }, + "devDependencies": { + "@babel/cli": "^7.27.2", + "@babel/core": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@cosmjs/math": "^0.33.1", + "@effect/build-utils": "^0.8.3", + "@effect/platform": "0.84.6", + "@safe-global/safe-apps-sdk": "~9.1.0", + "@types/node": "^22.13.1", + "@unionlabs/sdk": "workspace:^", + "babel-plugin-annotate-pure-calls": "^0.5.0", + "dpdm": "^3.14.0", + "effect": "3.16.3", + "madge": "^8.0.0", + "viem": "^2.33.3", + "vitest": "^3.0.5" + }, + "dependencies": { + "@scure/base": "1.2.4", + "crc": "^4.3.2", + "@mysten/sui": "^1.38.0" + } +} diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts new file mode 100644 index 0000000000..5b106637e0 --- /dev/null +++ b/ts-sdk-sui/src/Sui.ts @@ -0,0 +1,581 @@ +/** + * This module handles EVM related functionality. + * + * @since 0.0.0 + */ +import { GAS_DENOMS } from "@unionlabs/sdk/Constants" +import { UniversalChainId } from "@unionlabs/sdk/schema/chain" +import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" +import { extractErrorDetails} from "@unionlabs/sdk/Utils" +import * as Ucs03 from "@unionlabs/sdk/Ucs03" +import * as Utils from "@unionlabs/sdk/Utils" +import { Context, Data, Effect, flow, Layer, pipe, Schema as S } from "effect" +import { type Address, erc20Abi } from "viem" +import type { Hex } from "viem" +import * as internal from "./internal/sui.js" +import { SuiClient, SuiClientOptions } from "@mysten/sui/client" +import { Transaction } from "@mysten/sui/transactions" + +/** + * @category models + * @since 0.0.0 + */ +export namespace Sui { + /** + * @category models + * @since 0.0.0 + */ + export interface PublicClient { + readonly client: SuiClient + } + + /** + * @category models + * @since 0.0.0 + */ + export interface WalletClient { + readonly client: SuiClient + readonly signer: Ed25519Keypair + } + + /** + * @category models + * @since 0.0.0 + */ + export interface Channel { + readonly ucs03address: Address + readonly channelId: number + } +} + +// /** +// * @category utils +// * @since 0.0.0 +// */ +// export const channelBalance = (path: bigint, token: Hex) => +// Effect.gen(function*() { +// const client = (yield* PublicClientDestination).client +// const config = yield* ChannelDestination + +// const result = yield* readContract(client, { +// address: config.ucs03address, +// abi: Ucs03.Abi, +// functionName: "_deprecated_channelBalanceV1", +// args: [config.channelId, path, token], +// }) + +// return result +// }) + +// /** +// * @category utils +// * @since 0.0.0 +// */ +// export const channelBalanceAtBlock = (path: bigint, token: Hex, blockNumber: bigint) => +// Effect.gen(function*() { +// const client = (yield* PublicClientDestination).client +// const config = yield* ChannelDestination + +// const result = yield* readContract(client, { +// address: config.ucs03address, +// abi: Ucs03.Abi, +// functionName: "_deprecated_channelBalanceV1", +// args: [config.channelId, path, token], +// blockNumber: blockNumber, +// }) + +// return result +// }) + +/** + * @category errors + * @since 0.0.0 + */ +export class WaitForTransactionReceiptError extends Data.TaggedError( + "WaitForTransactionReceiptError", +)<{ + cause: unknown +}> {} + + +export class ReadCoinError extends Data.TaggedError("ReadCoinError")<{ + cause: unknown +}> {} + +// /** +// * Wait for a transaction receipt +// * @param hash The transaction hash for which to wait +// * @returns An Effect that resolves to the transaction receipt +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const waitForTransactionReceipt = (hash: Hash) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// const receipt = yield* Effect.tryPromise({ +// try: () => client.waitForTransactionReceipt({ hash }), +// catch: err => +// new WaitForTransactionReceiptError({ +// cause: Utils.extractErrorDetails(err as WaitForTransactionReceiptTimeoutErrorType), +// }), +// }) + +// return receipt +// }) + +export const readContract = ( + client: SuiClient, + sender: string, + packageId: string, + module: string, + fn: string, + typeArgs: string[], + args: any[], + tx: Transaction, +) => + Effect.tryPromise({ + try: async () => { + tx.moveCall({ + target: `${packageId}::${module}::${fn}`, + typeArguments: typeArgs, + arguments: args, + }) + const result = await client.devInspectTransactionBlock({ + transactionBlock: tx, + sender, + }) + return result.results // result as unknown as T + }, + catch: e => new ReadContractError({ cause: extractErrorDetails(e as Error) }), + }).pipe( + // optional: e.g. timeout & retry like your Aptos wrapper + Effect.timeout("10 seconds"), + Effect.retry({ times: 5 }), + ) + +export const writeContract = ( + client: SuiClient, + signer: Ed25519Keypair, + packageId: string, + module: string, + fn: string, + typeArgs: string[], + args: any[], + tx: Transaction, +) => + Effect.tryPromise({ + try: async () => { + tx.moveCall({ + target: `${packageId}::${module}::${fn}`, + typeArguments: typeArgs, + arguments: args, + }) + // sign & execute + const res = await client.signAndExecuteTransaction({ + signer, + transaction: tx, + }) + return res + }, + catch: e => new WriteContractError({ cause: extractErrorDetails(e as Error) }), + }) + +/** + * @category context + * @since 0.0.0 + */ +export class ChannelDestination extends Context.Tag("@unionlabs/sdk/Sui/ChannelDestination")< + ChannelDestination, + Sui.Channel +>() { + static Live = flow( + ChannelDestination.of, + Layer.succeed(this), + ) +} + +/** + * @category context + * @since 0.0.0 + */ +export class ChannelSource extends Context.Tag("@unionlabs/sdk/Sui/ChannelSource")< + ChannelSource, + Sui.Channel +>() { + static Live = flow( + ChannelSource.of, + Layer.succeed(this), + ) +} + +/** + * @category context + * @since 0.0.0 + */ + +export class PublicClientSource extends Context.Tag("@unionlabs/sdk/Sui/PublicClientSource")< + PublicClientSource, + Sui.PublicClient +>() { + static Live = internal.publicClientLayer(this) +} + +/** + * @category context + * @since 0.0.0 + */ +export class PublicClientDestination + extends Context.Tag("@unionlabs/sdk/Sui/PublicClientDestination")< + PublicClientDestination, + Sui.PublicClient + >() +{ + static Live = internal.publicClientLayer(this) +} + +/** + * A neutral public client that can be used for general-purpose operations + * that don't specifically target source or destination chains + * + * @category context + * @since 0.0.0 + */ +export class PublicClient extends Context.Tag("@unionlabs/sdk-sui/Sui/PublicClient")< + PublicClient, + Sui.PublicClient +>() { + static Live = internal.publicClientLayer(this) +} + +/** + * A wallet client that can be used for signing transactions + * + * @category context + * @since 0.0.0 + */ +export class WalletClient extends Context.Tag("@unionlabs/sdk/Sui/WalletClient")< + WalletClient, + Sui.WalletClient +>() { + static Live = internal.walletClientLayer(this) +} + +/** + * @category errors + * @since 0.0.0 + */ +export class ReadContractError extends Data.TaggedError("@unionlabs/sdk/Sui/ReadContractError")<{ + cause: unknown +}> {} + +/** + * @category errors + * @since 0.0.0 + */ +export class WriteContractError extends Data.TaggedError("@unionlabs/sdk/Sui/WriteContractError")<{ + cause: unknown +}> {} + +/** + * @category errors + * @since 0.0.0 + */ +export class CreatePublicClientError + extends Data.TaggedError("@unionlabs/sdk/Sui/CreatePublicClientError")<{ + cause: unknown + }> +{} + +/** + * @category errors + * @since 0.0.0 + */ +export class CreateWalletClientError + extends Data.TaggedError("@unionlabs/sdk/Sui/CreateWalletClientError")<{ + cause: unknown + }> +{} + +/** + * Read Coin token metadata (name, symbol, decimals) + * @param tokenAddress The address of the Coin token + * @returns An Effect that resolves to the coin metadata + * + * @category utils + * @since 0.0.0 + */ +export const readCoinMetadata = (tokenAddress: Address) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + + const metadata = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getCoinMetadata({ coinType: tokenAddress }) + return result + }, + catch: err => + new Error({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + return metadata + }) + +export const readCoinBalances = (contractAddress: string, address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + let params = { + owner: address, + coinType: contractAddress, + } + + const coins = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getCoins(params) + return result.data + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + return coins + }) + +// /** +// * Read the balance of an ERC20 token for a specific address +// * +// * @param tokenAddress The address of the ERC20 token +// * @param ownerAddress The address to check the balance for +// * +// * @returns An Effect that resolves to the token balance +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20Balance = (tokenAddress: Address, ownerAddress: Address) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "balanceOf", +// args: [ownerAddress], +// }) +// }) + +// /** +// * Read the balance of an ERC20 token for a specific address +// * @param tokenAddress The address of the ERC20 token +// * @param ownerAddress The address to check the balance for +// * @param blockNumber The blockNumber at certain point +// * @returns An Effect that resolves to the token balance +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20BalanceAtBlock = ( +// tokenAddress: Address, +// ownerAddress: Address, +// blockNumber: bigint, +// ) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "balanceOf", +// args: [ownerAddress], +// blockNumber: blockNumber, +// }) +// }) + +// /** +// * Read the name of an ERC20 token +// * @param tokenAddress The address of the ERC20 token +// * @returns An Effect that resolves to the token name +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20Name = (tokenAddress: Address) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "name", +// }) +// }) + +// /** +// * Read the symbol of an ERC20 token +// * @param tokenAddress The address of the ERC20 token +// * @returns An Effect that resolves to the token symbol +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20Symbol = (tokenAddress: Address) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "symbol", +// }) +// }) + +// /** +// * Read the decimals of an ERC20 token +// * @param tokenAddress The address of the ERC20 token +// * @returns An Effect that resolves to the token decimals +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20Decimals = (tokenAddress: Address) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "decimals", +// }) +// }) + +// /** +// * Read the TotalSupply of an ERC20 token +// * @param tokenAddress The address of the ERC20 token +// * @param blockNumber The blockNumber at certain point +// * @returns An Effect that resolves to the totalSupply +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20TotalSupplyAtBlock = (tokenAddress: Address, blockNumber: bigint) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "totalSupply", +// blockNumber: blockNumber, +// }) +// }) + +// /** +// * Read the TotalSupply of an ERC20 token +// * @param tokenAddress The address of the ERC20 token +// * @returns An Effect that resolves to the totalSupply +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20TotalSupply = (tokenAddress: Address) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "totalSupply", +// }) +// }) + +// /** +// * Read the allowance of an ERC20 token for a specific owner and spender +// * @param tokenAddress The address of the ERC20 token +// * @param ownerAddress The address of the token owner +// * @param spenderAddress The address of the spender +// * @returns An Effect that resolves to the token allowance +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const readErc20Allowance = ( +// tokenAddress: Address, +// ownerAddress: Address, +// spenderAddress: Address, +// ) => +// Effect.gen(function*() { +// const client = (yield* PublicClient).client + +// return yield* readContract(client, { +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "allowance", +// args: [ownerAddress, spenderAddress], +// }) +// }) + +// /** +// * Increase the allowance of an ERC20 token for a specific spender +// * @param tokenAddress The address of the ERC20 token +// * @param spenderAddress The address of the spender +// * @param amount The amount to increase the allowance by +// * @returns An Effect that resolves to the transaction hash +// * +// * @category utils +// * @since 0.0.0 +// */ +// export const increaseErc20Allowance = ( +// tokenAddress: Address, +// spenderAddress: Address, +// amount: bigint, +// ) => +// Effect.gen(function*() { +// const walletClient = yield* WalletClient + +// return yield* writeContract({ +// account: walletClient.account, +// chain: walletClient.chain, +// address: tokenAddress, +// abi: erc20Abi, +// functionName: "approve", +// args: [spenderAddress, amount], +// }) +// }) + +// /** +// * @category utils +// * @since 0.0.0 +// */ +// export const sendInstruction = (instruction: Ucs03.Instruction) => +// Effect.gen(function*() { +// const walletClient = yield* WalletClient +// const sourceConfig = yield* ChannelSource + +// const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow() +// const salt = yield* Utils.generateSalt("evm") + +// const operand = yield* S.encode(Ucs03.InstructionFromHex)(instruction) + +// return yield* writeContract({ +// account: walletClient.account, +// abi: Ucs03.Abi, +// chain: walletClient.chain, +// functionName: "send", +// address: sourceConfig.ucs03address, +// args: [ +// sourceConfig.channelId, +// 0n, +// timeoutTimestamp, +// salt, +// { +// opcode: instruction.opcode, +// version: instruction.version, +// operand, +// }, +// ], +// value: 10n, +// }) +// }) diff --git a/ts-sdk-sui/src/SuiZkgmClient.ts b/ts-sdk-sui/src/SuiZkgmClient.ts new file mode 100644 index 0000000000..23576eb57e --- /dev/null +++ b/ts-sdk-sui/src/SuiZkgmClient.ts @@ -0,0 +1,19 @@ +/** + * This module defines a concrete {@link ZkgmClient} for EVM source chain usage. + * + * @since 0.0.0 + */ +import type * as ZkgmClient from "@unionlabs/sdk/ZkgmClient" +import type * as Layer from "effect/Layer" +import type * as Sui from "./Sui.js" +import * as internal from "./internal/zkgmClient.js" + +/** + * @category layers + * @since 0.0.0 + */ +export const layerWithoutWallet: Layer.Layer< + ZkgmClient.ZkgmClient, + never, + Sui.WalletClient | Sui.PublicClient +> = internal.layerWithoutWallet diff --git a/ts-sdk-sui/src/index.ts b/ts-sdk-sui/src/index.ts new file mode 100644 index 0000000000..99ae07db21 --- /dev/null +++ b/ts-sdk-sui/src/index.ts @@ -0,0 +1,20 @@ +/** + * This module handles EVM related functionality. + * + * @since 0.0.0 + */ +export * as Sui from "./Sui.js" + +// /** +// * This module defines a concrete {@link ZkgmClient} for Sui source chain usage. +// * +// * @since 0.0.0 +// */ +// export * as EvmZkgmClient from "./EvmZkgmClient.js" + +// /** +// * This module allows usage of Safe wallet. +// * +// * @since 0.0.0 +// */ +// export * as Safe from "./Safe.js" diff --git a/ts-sdk-sui/src/internal/sui.ts b/ts-sdk-sui/src/internal/sui.ts new file mode 100644 index 0000000000..e5551dbcbe --- /dev/null +++ b/ts-sdk-sui/src/internal/sui.ts @@ -0,0 +1,63 @@ +import * as Utils from "@unionlabs/sdk/Utils" +import { Context, Effect, Layer, pipe } from "effect" +import * as V from "viem" +import * as Sui from "../Sui.js" +import { SuiClient, SuiClientOptions } from "@mysten/sui/client" +import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" + +/** @internal */ +export const publicClientLayer = < + Id, +>(tag: Context.Tag) => +( + // interface unchanged (variadic) + ...options: Parameters +): Layer.Layer => + Layer.effect( + tag, + pipe( + Effect.try({ + try: () => new SuiClient(options[0] as SuiClientOptions), + catch: (err) => + new Sui.CreatePublicClientError({ + // mirror your Sui error-detail extraction + cause: Utils.extractErrorDetails(err as Sui.CreatePublicClientError), + }), + }), + // keep the { client } shape + Effect.map((client) => ({ client })), + ), + ) + +/** @internal */ +export const walletClientLayer = < + Id, +>(tag: Context.Tag) => +( + // interface unchanged + options: Parameters[0] & { + account: Ed25519Keypair + chain: unknown + }, +): Layer.Layer => + Layer.effect( + tag, + pipe( + Effect.try({ + try: () => ({ + client: new SuiClient(options as unknown as SuiClientOptions), + signer: options.account as Ed25519Keypair, + }), + catch: (err) => + new Sui.CreateWalletClientError({ + cause: Utils.extractErrorDetails(err as Sui.SuiCreateWalletClientErrorType), + }), + }), + // return the *same* payload shape you had before + Effect.map(({ client }) => ({ + client, + account: options.account, + chain: options.chain, + })), + ), + ) diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts new file mode 100644 index 0000000000..c5cf2b2e72 --- /dev/null +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -0,0 +1,237 @@ +import { Indexer, ZkgmIncomingMessage } from "@unionlabs/sdk" +import * as Call from "@unionlabs/sdk/Call" +import type { Hex } from "@unionlabs/sdk/schema/hex" +import * as Token from "@unionlabs/sdk/Token" +import * as TokenOrder from "@unionlabs/sdk/TokenOrder" +import * as Ucs03 from "@unionlabs/sdk/Ucs03" +import * as Utils from "@unionlabs/sdk/Utils" +import * as Client from "@unionlabs/sdk/ZkgmClient" +import * as ClientError from "@unionlabs/sdk/ZkgmClientError" +import * as ClientRequest from "@unionlabs/sdk/ZkgmClientRequest" +import * as ClientResponse from "@unionlabs/sdk/ZkgmClientResponse" +import * as IncomingMessage from "@unionlabs/sdk/ZkgmIncomingMessage" +import * as ZkgmInstruction from "@unionlabs/sdk/ZkgmInstruction" +import { Brand, Chunk, flow, Match, ParseResult, pipe, Predicate, Tuple } from "effect" +import * as A from "effect/Array" +import * as Effect from "effect/Effect" +import * as Inspectable from "effect/Inspectable" +import * as O from "effect/Option" +import * as S from "effect/Schema" +import * as Stream from "effect/Stream" +import * as Sui from "../Sui.js" +import * as Safe from "../Safe.js" +// import { Sui } from "../index.js" + +export const fromWallet = ( + opts: { client: Sui.Sui.PublicClient; wallet: Sui.Sui.WalletClient }, +): Client.ZkgmClient => + Client.make((request, signal, fiber) => + Effect.gen(function*() { + const { + wallet, + client, + } = opts + + const encodeInstruction: ( + u: ZkgmInstruction.ZkgmInstruction, + ) => Effect.Effect = pipe( + Match.type(), + Match.tagsExhaustive({ + Batch: (batch) => + pipe( + batch.instructions, + A.map(encodeInstruction), + Effect.allWith({ concurrency: "unbounded" }), + Effect.map((operand) => + new Ucs03.Batch({ + opcode: batch.opcode, + version: batch.version, + operand, + }) + ), + ), + TokenOrder: (self) => + pipe( + Match.value(self), + Match.when( + { version: 1 }, + (v1) => + Effect.gen(function*() { + const meta = yield* pipe( + Sui.readCoinMetadata( + v1.baseToken.address as unknown as any + ), + Effect.provideService(Sui.PublicClient, client), + ) + + return yield* TokenOrder.encodeV1(v1)({ + ...meta, + sourceChannelId: request.channelId, + }) + }), + ), + Match.when( + { version: 2 }, + (v2) => TokenOrder.encodeV2(v2), + ), + Match.exhaustive, + ), + Call: Call.encode, + } + ), + ) + + console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { wallet, client }) + + const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow() + const salt = yield* Utils.generateSalt("sui").pipe( + Effect.mapError((cause) => + new ClientError.RequestError({ + reason: "Transport", + request, + cause, + description: "crypto error", + }) + ), + ) + + console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { salt, timeoutTimestamp }) + + const operand = yield* pipe( + encodeInstruction(request.instruction), + Effect.flatMap(S.encode(Ucs03.Ucs03FromHex)), + Effect.mapError((cause) => + new ClientError.RequestError({ + reason: "Transport", + request, + cause, + description: "instruction encode", + }) + ), + ) + + console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { operand }) + + const funds = ClientRequest.requiredFunds(request).pipe( + O.map(A.filter(([x]) => Token.isNative(x))), + O.flatMap(O.liftPredicate(A.isNonEmptyReadonlyArray)), + O.map(A.map(flow(Tuple.getSecond))), + O.map(A.reduce(0n, (acc, n) => acc + n)), + O.getOrUndefined, + ) + + console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { funds }) + + const args = [ + request.channelId, + 0n, + timeoutTimestamp, + salt, + { + opcode: request.instruction.opcode, + version: request.instruction.version, + operand, + }, + ] as const + + console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { args }) + + // // TODO: Fix writecontract calling, decide parameters etc. + // const sendInstruction = Sui.writeContract({ + // client: client, + // account: wallet.signer, + // abi: Ucs03.Abi, + // chain: wallet.chain, + // functionName: "send", + // address: request.ucs03Address as unknown as any, + // args, + // value: funds, + // }).pipe( + // Effect.mapError((cause) => + // new ClientError.RequestError({ + // reason: "Transport", + // request, + // cause, + // description: "writeContract", + // }) + // ), + // Effect.provideService(Evm.WalletClient, wallet), + // ) + + // return yield* pipe( + // sendInstruction, + // Effect.map((txHash) => new ClientResponseImpl(request, client, txHash)), + // ) + }) + ) + +/** @internal */ +export abstract class IncomingMessageImpl extends Inspectable.Class + implements IncomingMessage.ZkgmIncomingMessage +{ + readonly [IncomingMessage.TypeId]: IncomingMessage.TypeId + + constructor( + readonly client: Sui.Sui.PublicClient, + readonly txHash: Hex, + readonly onError: (error: unknown) => E, + ) { + super() + this[IncomingMessage.TypeId] = IncomingMessage.TypeId + } + + get stream() { + return Stream.empty + } + + waitFor( + refinement: Predicate.Refinement, A>, + ) { + return pipe( + this.stream, + Stream.filter(refinement), + Stream.runHead, + ) + } +} + +export class ClientResponseImpl extends IncomingMessageImpl + implements ClientResponse.ZkgmClientResponse +{ + readonly [ClientResponse.TypeId]: ClientResponse.TypeId + + constructor( + readonly request: ClientRequest.ZkgmClientRequest, + readonly client: Sui.Sui.PublicClient, + readonly txHash: Hex, + ) { + super(client, txHash, (error) => + new ClientError.ResponseError({ + reason: "OnChain", + request, + response: this, + cause: error, + })) + this[ClientResponse.TypeId] = ClientResponse.TypeId + } + + toString(): string { + return `SuiZkgmClient::ClientResponseImpl::toString not implemented` + } + + toJSON(): unknown { + return IncomingMessage.inspect(this, { + _id: "@unionlabs/sdk/ZkgmClientResponse", + request: this.request.toJSON(), + }) + } +} + +/** @internal */ +export const make = Effect.map( + Effect.all({ client: Sui.PublicClient, wallet: Sui.WalletClient }), + fromWallet, +) + +/** @internal */ +export const layerWithoutWallet = Client.layerMergedContext(make) diff --git a/ts-sdk-sui/ts-sdk-sui.nix b/ts-sdk-sui/ts-sdk-sui.nix new file mode 100644 index 0000000000..a5e5b3504b --- /dev/null +++ b/ts-sdk-sui/ts-sdk-sui.nix @@ -0,0 +1,46 @@ +_: { + perSystem = + { + pkgs, + lib, + ... + }: + let + buildPnpmPackage = import ../tools/typescript/buildPnpmPackage.nix { + inherit pkgs lib; + }; + pnpm = pkgs.pnpm_10; + in + { + packages = { + ts-sdk-sui = buildPnpmPackage { + inherit pnpm; + packageJsonPath = ./package.json; + extraSrcs = [ + ../ts-sdk + ../ts-sdk-sui + ]; + pnpmWorkspaces = [ + "@unionlabs/sdk" + "@unionlabs/sdk-sui" + ]; + hash = "sha256-XpCYNgRkkUN0O43Bodkk9XNpVRHkAZV6s20bfMUGNkk="; + doCheck = true; + buildPhase = '' + runHook preBuild + pnpm --filter=@unionlabs/sdk-sui build + runHook postBuild + ''; + installPhase = '' + mkdir -p $out + cp -r ./ts-sdk-sui/* $out + ''; + checkPhase = '' + pnpm run --filter=@unionlabs/sdk-sui check + pnpm run --filter=@unionlabs/sdk-sui test + ''; + }; + }; + apps = { }; + }; +} diff --git a/ts-sdk-sui/tsconfig.build.json b/ts-sdk-sui/tsconfig.build.json new file mode 100644 index 0000000000..769219ad88 --- /dev/null +++ b/ts-sdk-sui/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.src.json", + "references": [{ "path": "../ts-sdk/tsconfig.build.json" }], + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "outDir": "build/esm", + "declarationDir": "build/dts", + "stripInternal": true + } +} diff --git a/ts-sdk-sui/tsconfig.examples.json b/ts-sdk-sui/tsconfig.examples.json new file mode 100644 index 0000000000..9779f213ea --- /dev/null +++ b/ts-sdk-sui/tsconfig.examples.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["examples"], + "references": [{ "path": "tsconfig.src.json" }], + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/examples.tsbuildinfo", + "noEmit": true, + "types": ["node"], + "rootDir": "examples" + } +} diff --git a/ts-sdk-sui/tsconfig.json b/ts-sdk-sui/tsconfig.json new file mode 100644 index 0000000000..b509ddd704 --- /dev/null +++ b/ts-sdk-sui/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.base.json", + "include": [], + "references": [ + { + "path": "tsconfig.examples.json" + }, + { + "path": "tsconfig.src.json" + }, + { + "path": "tsconfig.test.json" + } + ] +} diff --git a/ts-sdk-sui/tsconfig.src.json b/ts-sdk-sui/tsconfig.src.json new file mode 100644 index 0000000000..6007867705 --- /dev/null +++ b/ts-sdk-sui/tsconfig.src.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["src"], + "references": [{ "path": "../ts-sdk/tsconfig.src.json" }], + "compilerOptions": { + "outDir": "build/src", + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src" + } +} diff --git a/ts-sdk-sui/tsconfig.test.json b/ts-sdk-sui/tsconfig.test.json new file mode 100644 index 0000000000..2c0eac7288 --- /dev/null +++ b/ts-sdk-sui/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["test"], + "references": [{ "path": "tsconfig.src.json" }, { "path": "../ts-sdk/tsconfig.test.json" }], + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "outDir": "build/test" + } +} diff --git a/ts-sdk-sui/vitest.config.ts b/ts-sdk-sui/vitest.config.ts new file mode 100644 index 0000000000..0fa07f9af2 --- /dev/null +++ b/ts-sdk-sui/vitest.config.ts @@ -0,0 +1,6 @@ +import { mergeConfig, type UserConfigExport } from "vitest/config" +import shared from "../vitest.shared.js" + +const config: UserConfigExport = {} + +export default mergeConfig(shared, config) From e7ea531d680c8fcbd02867da150e0c14cf639be4 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 24 Sep 2025 14:33:43 +0300 Subject: [PATCH 02/24] chore(ts-sdk-sui): added new function Signed-off-by: kaancaglan --- ts-sdk-sui/src/Sui.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index 5b106637e0..d85c6752db 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -344,6 +344,31 @@ export const readCoinBalances = (contractAddress: string, address: string) => return coins }) + export const readTotalCoinBalance = (contractAddress: string, address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + let params = { + owner: address, + coinType: contractAddress, + } + + const coins = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getCoins(params) + return result.data + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + // Calculate total balance + const totalBalance = coins.reduce((acc, coin) => acc + BigInt(coin.balance), BigInt(0)) + + return totalBalance + }) + + // /** // * Read the balance of an ERC20 token for a specific address // * From 52dcff8c0d081c064cc20f9ec2ea9afe22bf9013 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 24 Sep 2025 14:34:05 +0300 Subject: [PATCH 03/24] chore(ts-sdk-sui): added new function Signed-off-by: kaancaglan --- ts-sdk-sui/src/Sui.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index d85c6752db..4a221a0bf1 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -368,6 +368,25 @@ export const readCoinBalances = (contractAddress: string, address: string) => return totalBalance }) + export const getAllCoins = (address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + let params = { + owner: address, + } + + const coins = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getAllCoins(params) + return result.data + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + return coins + }) // /** // * Read the balance of an ERC20 token for a specific address From 1b728044df02b5e6c9fd0fa7005bd5355b271b12 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 24 Sep 2025 14:34:23 +0300 Subject: [PATCH 04/24] chore(ts-sdk-sui): added new function Signed-off-by: kaancaglan --- ts-sdk-sui/src/Sui.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index 4a221a0bf1..69b68be582 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -387,6 +387,48 @@ export const readCoinBalances = (contractAddress: string, address: string) => }) return coins }) + + export const getAllCoinsUnique = (address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + + const params = { + owner: address, + } + + const coins = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getAllCoins(params) + return result.data + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + + // Group by coinType and sum balances + const coinMap: Record = {} + + for (const coin of coins) { + const coinType = coin.coinType + const balance = BigInt(coin.balance) + + if (!coinMap[coinType]) { + coinMap[coinType] = balance + } else { + coinMap[coinType] += balance + } + } + + // Convert to array of objects + const result = Object.entries(coinMap).map(([coinType, totalBalance]) => ({ + coinType, + balance: totalBalance.toString(), // or keep as BigInt if preferred + })) + + return result + }) // /** // * Read the balance of an ERC20 token for a specific address From d9544ce4112307c0e1e584c247a794947709af65 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 24 Sep 2025 14:35:03 +0300 Subject: [PATCH 05/24] chore(ts-sdk-sui): added new functions Signed-off-by: kaancaglan --- ts-sdk-sui/src/Sui.ts | 54 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index 69b68be582..78200a8b73 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -387,7 +387,7 @@ export const readCoinBalances = (contractAddress: string, address: string) => }) return coins }) - + export const getAllCoinsUnique = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -430,6 +430,58 @@ export const readCoinBalances = (contractAddress: string, address: string) => return result }) + export const getCoinName = (address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + + const name = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getCoinMetadata({ coinType: address }) + return result?.name + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + return name + }) + + export const getCoinDecimals = (address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + + const decimals = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getCoinMetadata({ coinType: address }) + return result?.decimals + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + return decimals + }) + +export const readCoinSymbol = (address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + + const symbol = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getCoinMetadata({ coinType: address }) + return result?.symbol + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), + }) + return symbol + }) + + // /** // * Read the balance of an ERC20 token for a specific address // * From 321dd24429b5aba32e997fb1fbbab1422c39358e Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 24 Sep 2025 14:42:26 +0300 Subject: [PATCH 06/24] chore(ts-sdk-sui): added different examples Signed-off-by: kaancaglan --- .../sui-create-client-read-coin-related.ts | 59 +++++++++++++++++++ ...ts => sui-create-client-write-contract.ts} | 10 ---- 2 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 ts-sdk-sui/examples/sui-create-client-read-coin-related.ts rename ts-sdk-sui/examples/{sui-create-client-read-and-write.ts => sui-create-client-write-contract.ts} (80%) diff --git a/ts-sdk-sui/examples/sui-create-client-read-coin-related.ts b/ts-sdk-sui/examples/sui-create-client-read-coin-related.ts new file mode 100644 index 0000000000..45737bdf00 --- /dev/null +++ b/ts-sdk-sui/examples/sui-create-client-read-coin-related.ts @@ -0,0 +1,59 @@ +// @ts-ignore +if (typeof BigInt.prototype.toJSON !== "function") { + // @ts-ignore + BigInt.prototype.toJSON = function () { + return this.toString() + } +} + +import { Effect, Logger } from "effect" +import { getFullnodeUrl } from "@mysten/sui/client" + +import { + PublicClient, + readCoinMetadata, + readCoinBalances, + readTotalCoinBalance, + getAllCoinsUnique, + getCoinName, + getCoinDecimals, + readCoinSymbol, +} from "../src/Sui.js" + +const ADDRESS = + process.env.ADDRESS ?? + "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" + +const COIN_TYPE = "0x2::sui::SUI" as any + +const program = Effect.gen(function* () { + const { client } = yield* PublicClient + yield* Effect.log("Sui public client initialized", client.network) + + const meta = yield* readCoinMetadata(COIN_TYPE) + yield* Effect.log("SUI metadata", meta) + + const [name, symbol, decimals] = yield* Effect.all([ + getCoinName(COIN_TYPE), + readCoinSymbol(COIN_TYPE), + getCoinDecimals(COIN_TYPE), + ]) + yield* Effect.log("SUI meta (granular)", { name, symbol, decimals }) + + yield* Effect.log("Address", ADDRESS) + const coins = yield* readCoinBalances(COIN_TYPE, ADDRESS as any) + yield* Effect.log("SUI coins (objects)", coins) + + const total = yield* readTotalCoinBalance(COIN_TYPE, ADDRESS as any) + yield* Effect.log("SUI total balance (mist as BigInt)", total.toString()) + + + const unique = yield* getAllCoinsUnique(ADDRESS as any) + + yield* Effect.log("All coins (unique, summed)", unique) +}).pipe( + Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), + Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), +) + +Effect.runPromise(program).catch(console.error) diff --git a/ts-sdk-sui/examples/sui-create-client-read-and-write.ts b/ts-sdk-sui/examples/sui-create-client-write-contract.ts similarity index 80% rename from ts-sdk-sui/examples/sui-create-client-read-and-write.ts rename to ts-sdk-sui/examples/sui-create-client-write-contract.ts index 5a3612fd76..a392e451db 100644 --- a/ts-sdk-sui/examples/sui-create-client-read-and-write.ts +++ b/ts-sdk-sui/examples/sui-create-client-write-contract.ts @@ -19,16 +19,6 @@ const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) const program = Effect.gen(function* () { const { client } = yield* PublicClient - yield* Effect.log("Sui public client initialized", client.network ) - const meta = yield* readCoinMetadata("0x2::sui::SUI" as any) - yield* Effect.log("SUI metadata", meta) - - yield* Effect.log("keypair.getPublicKey().toSuiAddress()", keypair.getPublicKey().toSuiAddress()) - const balances = yield* readCoinBalances("0x2::sui::SUI" as any, keypair.getPublicKey().toSuiAddress() as any) - yield* Effect.log("SUI balances", balances) - - - const wallet = yield* WalletClient const amountMist = 10_000_000n // 0.01 SUI const tx = new Transaction() From 32bdba63c95622a3cbc7d7782a7e532d8e2fd36c Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 24 Sep 2025 14:46:53 +0300 Subject: [PATCH 07/24] chore(ts-sdk-sui): added comments Signed-off-by: kaancaglan --- ts-sdk-sui/src/Sui.ts | 303 +++++++++++++++++------------------------- 1 file changed, 120 insertions(+), 183 deletions(-) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index 78200a8b73..6853af24cf 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -299,9 +299,16 @@ export class CreateWalletClientError {} /** - * Read Coin token metadata (name, symbol, decimals) - * @param tokenAddress The address of the Coin token - * @returns An Effect that resolves to the coin metadata + * Read Coin metadata (name, symbol, decimals, …) for a given `coinType`. + * + * Example: + * ```ts + * const meta = yield* readCoinMetadata("0x2::sui::SUI") + * ``` + * + * @param tokenAddress Canonical coin type string (e.g., `"0x2::sui::SUI"`) + * @returns Effect resolving to `getCoinMetadata` result + * @throws ReadCoinError on RPC failure * * @category utils * @since 0.0.0 @@ -323,6 +330,20 @@ export const readCoinMetadata = (tokenAddress: Address) => return metadata }) +/** + * Read all coin objects for a given `coinType` and owner address. + * + * Note: + * - Sui splits balances across multiple coin objects; each carries `balance` and `coinObjectId`. + * - Use {@link readTotalCoinBalance} if you want a single summed value. + * + * @param contractAddress Canonical coin type (e.g., `"0x2::sui::SUI"`) + * @param address Owner Sui address + * @returns Effect resolving to the paged coin objects’ `data` array + * + * @category utils + * @since 0.0.0 + */ export const readCoinBalances = (contractAddress: string, address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -344,6 +365,16 @@ export const readCoinBalances = (contractAddress: string, address: string) => return coins }) + /** + * Read and sum all coin object balances for a given `coinType` and owner. + * + * @param contractAddress Canonical coin type + * @param address Owner Sui address + * @returns Effect resolving to a `bigint` total (in the coin’s base units) + * + * @category utils + * @since 0.0.0 + */ export const readTotalCoinBalance = (contractAddress: string, address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -368,6 +399,16 @@ export const readCoinBalances = (contractAddress: string, address: string) => return totalBalance }) + +/** + * Fetch *all* coin objects (any coin type) for an owner. + * + * @param address Owner Sui address + * @returns Effect resolving to `getAllCoins().data` + * + * @category utils + * @since 0.0.0 + */ export const getAllCoins = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -388,6 +429,24 @@ export const readCoinBalances = (contractAddress: string, address: string) => return coins }) + /** + * Fetch all coins for an owner and return a unique list grouped by `coinType`, + * with balances summed across coin objects. + * + * Example output: + * ```ts + * [ + * { coinType: "0x2::sui::SUI", balance: "123456789" }, + * { coinType: "0x...::USDC::USDC", balance: "4200000" } + * ] + * ``` + * + * @param address Owner Sui address + * @returns Effect resolving to `{ coinType, balance }[]` (balance as string) + * + * @category utils + * @since 0.0.0 + */ export const getAllCoinsUnique = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -430,6 +489,16 @@ export const readCoinBalances = (contractAddress: string, address: string) => return result }) + +/** + * Convenience: read coin **name** for a given `coinType`. + * + * @param address Canonical coin type + * @returns Effect resolving to `string | undefined` + * + * @category utils + * @since 0.0.0 + */ export const getCoinName = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -447,6 +516,15 @@ export const readCoinBalances = (contractAddress: string, address: string) => return name }) +/** + * Convenience: read coin **decimals** for a given `coinType`. + * + * @param address Canonical coin type + * @returns Effect resolving to `number | undefined` + * + * @category utils + * @since 0.0.0 + */ export const getCoinDecimals = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -463,7 +541,16 @@ export const readCoinBalances = (contractAddress: string, address: string) => }) return decimals }) - + +/** + * Convenience: read coin **symbol** for a given `coinType`. + * + * @param address Canonical coin type + * @returns Effect resolving to `string | undefined` + * + * @category utils + * @since 0.0.0 + */ export const readCoinSymbol = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -482,26 +569,41 @@ export const readCoinSymbol = (address: string) => }) +// // TODO: Decide the parameters here. // /** -// * Read the balance of an ERC20 token for a specific address -// * -// * @param tokenAddress The address of the ERC20 token -// * @param ownerAddress The address to check the balance for -// * -// * @returns An Effect that resolves to the token balance -// * // * @category utils // * @since 0.0.0 // */ -// export const readErc20Balance = (tokenAddress: Address, ownerAddress: Address) => +// export const sendInstruction = (instruction: Ucs03.Instruction) => // Effect.gen(function*() { -// const client = (yield* PublicClient).client +// const walletClient = yield* WalletClient +// const sourceConfig = yield* ChannelSource -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "balanceOf", -// args: [ownerAddress], +// const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow() +// const salt = yield* Utils.generateSalt("evm") + +// const operand = yield* S.encode(Ucs03.InstructionFromHex)(instruction) + +// return yield* writeContract({ +// client: walletClient.client, +// signer: walletClient.signer, +// account: walletClient.account, +// abi: Ucs03.Abi, +// // chain: walletClient.chain, TODO: Do we need this? +// fn: "send", +// address: sourceConfig.ucs03address, +// args: [ +// sourceConfig.channelId, +// 0n, +// timeoutTimestamp, +// salt, +// { +// opcode: instruction.opcode, +// version: instruction.version, +// operand, +// }, +// ], +// value: 10n, // }) // }) @@ -532,63 +634,6 @@ export const readCoinSymbol = (address: string) => // }) // }) -// /** -// * Read the name of an ERC20 token -// * @param tokenAddress The address of the ERC20 token -// * @returns An Effect that resolves to the token name -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const readErc20Name = (tokenAddress: Address) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "name", -// }) -// }) - -// /** -// * Read the symbol of an ERC20 token -// * @param tokenAddress The address of the ERC20 token -// * @returns An Effect that resolves to the token symbol -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const readErc20Symbol = (tokenAddress: Address) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "symbol", -// }) -// }) - -// /** -// * Read the decimals of an ERC20 token -// * @param tokenAddress The address of the ERC20 token -// * @returns An Effect that resolves to the token decimals -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const readErc20Decimals = (tokenAddress: Address) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "decimals", -// }) -// }) - // /** // * Read the TotalSupply of an ERC20 token // * @param tokenAddress The address of the ERC20 token @@ -609,111 +654,3 @@ export const readCoinSymbol = (address: string) => // blockNumber: blockNumber, // }) // }) - -// /** -// * Read the TotalSupply of an ERC20 token -// * @param tokenAddress The address of the ERC20 token -// * @returns An Effect that resolves to the totalSupply -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const readErc20TotalSupply = (tokenAddress: Address) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "totalSupply", -// }) -// }) - -// /** -// * Read the allowance of an ERC20 token for a specific owner and spender -// * @param tokenAddress The address of the ERC20 token -// * @param ownerAddress The address of the token owner -// * @param spenderAddress The address of the spender -// * @returns An Effect that resolves to the token allowance -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const readErc20Allowance = ( -// tokenAddress: Address, -// ownerAddress: Address, -// spenderAddress: Address, -// ) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "allowance", -// args: [ownerAddress, spenderAddress], -// }) -// }) - -// /** -// * Increase the allowance of an ERC20 token for a specific spender -// * @param tokenAddress The address of the ERC20 token -// * @param spenderAddress The address of the spender -// * @param amount The amount to increase the allowance by -// * @returns An Effect that resolves to the transaction hash -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const increaseErc20Allowance = ( -// tokenAddress: Address, -// spenderAddress: Address, -// amount: bigint, -// ) => -// Effect.gen(function*() { -// const walletClient = yield* WalletClient - -// return yield* writeContract({ -// account: walletClient.account, -// chain: walletClient.chain, -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "approve", -// args: [spenderAddress, amount], -// }) -// }) - -// /** -// * @category utils -// * @since 0.0.0 -// */ -// export const sendInstruction = (instruction: Ucs03.Instruction) => -// Effect.gen(function*() { -// const walletClient = yield* WalletClient -// const sourceConfig = yield* ChannelSource - -// const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow() -// const salt = yield* Utils.generateSalt("evm") - -// const operand = yield* S.encode(Ucs03.InstructionFromHex)(instruction) - -// return yield* writeContract({ -// account: walletClient.account, -// abi: Ucs03.Abi, -// chain: walletClient.chain, -// functionName: "send", -// address: sourceConfig.ucs03address, -// args: [ -// sourceConfig.channelId, -// 0n, -// timeoutTimestamp, -// salt, -// { -// opcode: instruction.opcode, -// version: instruction.version, -// operand, -// }, -// ], -// value: 10n, -// }) -// }) From e916e1c7c28ed8068b7bfc15cbcae894fd4f85cf Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Thu, 25 Sep 2025 18:09:42 +0300 Subject: [PATCH 08/24] chore(ts-sdk-sui): created submit instruction function Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 65 ++++++++ ts-sdk-sui/src/Sui.ts | 153 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 ts-sdk-sui/examples/sui-create-client-generate-instruction.ts diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts new file mode 100644 index 0000000000..5893fdd3b5 --- /dev/null +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -0,0 +1,65 @@ +// @ts-ignore +if (typeof BigInt.prototype.toJSON !== "function") { + // @ts-ignore + BigInt.prototype.toJSON = function () { + return this.toString() + } +} +import { Effect, Logger } from "effect" +import { getFullnodeUrl } from "@mysten/sui/client" +import { PublicClient, WalletClient, writeContract, readCoinMetadata, readCoinBalances, sendInstruction } from "../src/Sui.js" +import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" +import { Transaction } from "@mysten/sui/transactions" + +const MNEMONIC = process.env.MNEMONIC ?? "fix auto gallery heart practice drip joke nice decline lift attend bread" +const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" + +const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) + + +const program = Effect.gen(function* () { + const { client } = yield* PublicClient + yield* Effect.log("Sui public client initialized", client.network ) + const meta = yield* readCoinMetadata("0x2::sui::SUI" as any) + yield* Effect.log("SUI metadata", meta) + + yield* Effect.log("keypair.getPublicKey().toSuiAddress()", keypair.getPublicKey().toSuiAddress()) + const balances = yield* readCoinBalances("0x2::sui::SUI" as any, keypair.getPublicKey().toSuiAddress() as any) + yield* Effect.log("SUI balances", balances) + + + const wallet = yield* WalletClient + const amountMist = 10_000_000n // 0.01 SUI + + const tx = new Transaction() + const coin = tx.splitCoins(tx.gas, [tx.pure.u64(amountMist)]) + const recipient = tx.pure.address(RECIPIENT) + + const res = yield* writeContract( + client, + keypair, + "0x2", // packageId: Sui framework + "transfer", // module: sui::transfer + "public_transfer", // function + ["0x2::coin::Coin<0x2::sui::SUI>"], // type arg T + [coin, recipient], // (obj: T, recipient: address) + tx, + ) + + yield* Effect.log("Transfer submitted", res) + + +}).pipe( + Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), + Effect.provide( + WalletClient.Live({ + url: getFullnodeUrl("testnet"), + account: keypair, // signer + chain: "sui-testnet" as any, // placeholder; not used internally + }), + ), + + Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), +) + +Effect.runPromise(program).catch(console.error) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index 6853af24cf..f489262964 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -569,6 +569,159 @@ export const readCoinSymbol = (address: string) => }) + /** + * PTB: begin_send -> (send_with_coin ...)* -> end_send + * + * Mirrors: + * sui client ptb \ + * --move-call "$PKG::$MOD::begin_send" $CHANNEL_ID $SALT \ + * --assign send_ctx1 \ + * --move-call "$PKG::$MOD::send_with_coin" "<$TYPE_T>" \ + * $RELAY_STORE $VAULT $IBC_STORE $COIN $VERSION $OPCODE $OPERAND send_ctx1 \ + * --assign send_ctx2 \ + * --move-call "$PKG::$MOD::end_send" $IBC_STORE $CLOCK $T_HEIGHT $T_TS send_ctx2 \ + * --gas-budget 150000000 + */ +export const sendInstruction = (params: { + packageId: string + + /** coin type, e.g. "0x2::sui::SUI" (used in typeArguments of send_with_coin) */ + typeArg: string + + relayStoreId: string // $RELAY_STORE + vaultId: string // $VAULT + ibcStoreId: string // $IBC_STORE + coinObjectId: string // $COIN + + // extraSendCalls?: Array<{ + // relayStoreId?: string + // vaultId?: string + // ibcStoreId?: string + // coinObjectId?: string + // version?: number + // opcode?: number + // operandHex?: `0x${string}` + // typeArg?: string + // }> + + /** the instruction used purely for encoding operand just like EVM */ + instruction: Ucs03.Instruction + +}) => + Effect.gen(function* () { + const module = "zkgm" + const clockObjectId = "0x6" // Sui system clock object + + const { client, signer } = yield* WalletClient + const channelId = (yield* ChannelSource).channelId + + const salt = yield* Utils.generateSalt("evm") // TODO: check if evm will work here or not + const timeoutNs = Utils.getTimeoutInNanoseconds24HoursFromNow() + const tHeight = BigInt(0) + + const operandHex = (yield* S.encode(Ucs03.InstructionFromHex)(params.instruction)) as `0x${string}` + + // helpers + const hexToBytes = (hex: `0x${string}`): Uint8Array => { + const s = hex.slice(2) + const out = new Uint8Array(s.length / 2) + for (let i = 0; i < out.length; i++) out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16) + return out + } + + const tx = new Transaction() + // if (params.gasBudget !== undefined) tx.setGasBudget(BigInt(params.gasBudget as any)) + + let sendCtx = tx.moveCall({ + target: `${params.packageId}::${module}::begin_send`, + typeArguments: [], + arguments: [ + tx.pure.u32(channelId), + tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), + ], + }) + + const pushSendWithCoin = (cfg: { + relayStoreId: string + vaultId: string + ibcStoreId: string + coinObjectId: string + version: number + opcode: number + operandHex: `0x${string}` + typeArg: string + }) => { + sendCtx = tx.moveCall({ + target: `${params.packageId}::${module}::send_with_coin`, + typeArguments: [cfg.typeArg], + arguments: [ + tx.object(cfg.relayStoreId), + tx.object(cfg.vaultId), + tx.object(cfg.ibcStoreId), + tx.object(cfg.coinObjectId), + tx.pure.u8(cfg.version), + tx.pure.u8(cfg.opcode), + tx.pure.vector("u8", hexToBytes(cfg.operandHex)), + sendCtx, + ], + }) + } + + pushSendWithCoin({ + relayStoreId: params.relayStoreId, + vaultId: params.vaultId, + ibcStoreId: params.ibcStoreId, + coinObjectId: params.coinObjectId, + version: params.instruction.version, + opcode: params.instruction.opcode, + operandHex, + typeArg: params.typeArg, + }) + + // TODO: multiple send_with_coin calls if needed??? will this work? + // for (const extra of params.extraSendCalls ?? []) { + // pushSendWithCoin({ + // relayStoreId: extra.relayStoreId ?? params.relayStoreId, + // vaultId: extra.vaultId ?? params.vaultId, + // ibcStoreId: extra.ibcStoreId ?? params.ibcStoreId, + // coinObjectId: extra.coinObjectId ?? params.coinObjectId, + // version: extra.version ?? params.version, + // opcode: extra.opcode ?? params.opcode, + // operandHex: (extra.operandHex ?? operandHex) as `0x${string}`, + // typeArg: extra.typeArg ?? params.typeArg, + // }) + // } + + tx.moveCall({ + target: `${params.packageId}::${module}::end_send`, + typeArguments: [], + arguments: [ + tx.object(params.ibcStoreId), + tx.object(clockObjectId), + tx.pure.u64(tHeight), + tx.pure.u64(BigInt(timeoutNs)), // ns + sendCtx, + ], + }) + + const res = yield* Effect.tryPromise({ + try: async () => + client.signAndExecuteTransaction({ + signer, + transaction: tx, + }), + catch: (e) => new WriteContractError({ cause: extractErrorDetails(e as Error) }), + }) + + return res + }) + +// turn a hex string like "0xdeadbeef" into a number[] of bytes +function hexToBytes(hex: string): number[] { + const h = hex.startsWith("0x") ? hex.slice(2) : hex + return h.match(/.{1,2}/g)!.map(b => parseInt(b, 16)) +} + // // TODO: Decide the parameters here. // /** // * @category utils From a1b6f0884771c57190f06c372126aaaff3fe11ce Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Fri, 26 Sep 2025 14:28:45 +0300 Subject: [PATCH 09/24] chore(ts-sdk-sui): added readCoinMeta function Signed-off-by: kaancaglan --- ts-sdk-sui/src/Sui.ts | 44 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index f489262964..ef69128e9d 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -330,6 +330,47 @@ export const readCoinMetadata = (tokenAddress: Address) => return metadata }) +/** + * Read Sui coin metadata (name, symbol, decimals) for a given `coinType`. + * + * Example: + * ```ts + * const meta = yield* readCoinMeta("0x2::sui::SUI") + * // -> { name: "Sui", symbol: "SUI", decimals: 9 } + * ``` + * + * @param coinType Canonical coin type string (e.g., "0x2::sui::SUI") + * @returns Effect resolving to `{ name, symbol, decimals }` + * @throws ReadCoinError on RPC failure + * + * @category utils + * @since 0.0.0 + */ +export const readCoinMeta = (coinType: string) => + Effect.gen(function* () { + const client = (yield* PublicClient).client + + const out = yield* Effect.tryPromise({ + try: async () => { + const meta = await client.getCoinMetadata({ coinType }) + // meta can be null if the type has no metadata published + if (!meta) { + // normalize to a typed error consistent with your pattern + throw new ReadCoinError({ cause: `No CoinMetadata found for ${coinType}` }) + } + const { name, symbol, decimals } = meta + return { name, symbol, decimals } + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as Error), + }), + }) + + return out + }) + + /** * Read all coin objects for a given `coinType` and owner address. * @@ -604,7 +645,6 @@ export const sendInstruction = (params: { // typeArg?: string // }> - /** the instruction used purely for encoding operand just like EVM */ instruction: Ucs03.Instruction }) => @@ -631,7 +671,7 @@ export const sendInstruction = (params: { const tx = new Transaction() // if (params.gasBudget !== undefined) tx.setGasBudget(BigInt(params.gasBudget as any)) - + // adress: 0x1234 let sendCtx = tx.moveCall({ target: `${params.packageId}::${module}::begin_send`, typeArguments: [], From 99ed826cca22c73646c36324a2bc81b6ea62222a Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Fri, 26 Sep 2025 16:35:59 +0300 Subject: [PATCH 10/24] chore(ts-sdk-sui): fromWallet function is being called, error fixed on example Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 90 +++++++++++++------ ts-sdk-sui/src/internal/zkgmClient.ts | 54 +++++------ 2 files changed, 91 insertions(+), 53 deletions(-) diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts index 5893fdd3b5..3cf19b5e79 100644 --- a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -9,7 +9,17 @@ import { Effect, Logger } from "effect" import { getFullnodeUrl } from "@mysten/sui/client" import { PublicClient, WalletClient, writeContract, readCoinMetadata, readCoinBalances, sendInstruction } from "../src/Sui.js" import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" +import * as ZkgmClientRequest from "@unionlabs/sdk/ZkgmClientRequest" +import * as ZkgmClientResponse from "@unionlabs/sdk/ZkgmClientResponse" +import { ChannelId } from "@unionlabs/sdk/schema/channel" +import * as ZkgmIncomingMessage from "@unionlabs/sdk/ZkgmIncomingMessage" +import * as ZkgmClient from "@unionlabs/sdk/ZkgmClient" +import { layerWithoutWallet } from "../src/SuiZkgmClient.js" + import { Transaction } from "@mysten/sui/transactions" +import * as TokenOrder from "@unionlabs/sdk/TokenOrder" +import { UniversalChainId } from "@unionlabs/sdk/schema/chain" +import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry" const MNEMONIC = process.env.MNEMONIC ?? "fix auto gallery heart practice drip joke nice decline lift attend bread" const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" @@ -18,38 +28,65 @@ const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) const program = Effect.gen(function* () { + const source = yield* ChainRegistry.byUniversalId( + UniversalChainId.make("ethereum.17000"), + ) + + console.log("source", source) + const destination = yield* ChainRegistry.byUniversalId( + UniversalChainId.make("ethereum.11155111"), + ) const { client } = yield* PublicClient - yield* Effect.log("Sui public client initialized", client.network ) - const meta = yield* readCoinMetadata("0x2::sui::SUI" as any) - yield* Effect.log("SUI metadata", meta) - - yield* Effect.log("keypair.getPublicKey().toSuiAddress()", keypair.getPublicKey().toSuiAddress()) - const balances = yield* readCoinBalances("0x2::sui::SUI" as any, keypair.getPublicKey().toSuiAddress() as any) - yield* Effect.log("SUI balances", balances) - - - const wallet = yield* WalletClient - const amountMist = 10_000_000n // 0.01 SUI - - const tx = new Transaction() - const coin = tx.splitCoins(tx.gas, [tx.pure.u64(amountMist)]) - const recipient = tx.pure.address(RECIPIENT) - - const res = yield* writeContract( - client, - keypair, - "0x2", // packageId: Sui framework - "transfer", // module: sui::transfer - "public_transfer", // function - ["0x2::coin::Coin<0x2::sui::SUI>"], // type arg T - [coin, recipient], // (obj: T, recipient: address) - tx, + + const tokenOrder = yield* TokenOrder.make({ + source, + destination, + sender: "0x06627714f3F17a701f7074a12C02847a5D2Ca487", + receiver: "0x50A22f95bcB21E7bFb63c7A8544AC0683dCeA302", + // LINK on Holesky + baseToken: "0x685ce6742351ae9b618f383883d6d1e0c5a31b4b", + baseAmount: 10n, + // Holesky LINK on Sepolia + quoteToken: "0x80fdbf104ec58a527ec40f7b03f88c404ef4ba63", + quoteAmount: 10n, + kind: "escrow", + metadata: undefined, + version: 2, + }) + + yield* Effect.log("Token Order V2", tokenOrder) + + const request = ZkgmClientRequest.make({ + source, + destination, + channelId: ChannelId.make(2), + ucs03Address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03", + kind: "simulateAndExecute", + instruction: tokenOrder, + }) + + const zkgmClient = yield* ZkgmClient.ZkgmClient + + // NOTE: 1. switch chain is assumed + // NOTE: 2. write in progress + + const response: ZkgmClientResponse.ZkgmClientResponse = yield* zkgmClient.execute(request) + + // NOTE: 3. write complete (with tx hash) + + yield* Effect.log("Submission Hash", response.txHash) + + const completion = yield* response.waitFor( + ZkgmIncomingMessage.LifecycleEvent.$is("EvmTransactionReceiptComplete"), ) - yield* Effect.log("Transfer submitted", res) + // NOTE: 4. tx complete + + yield* Effect.log("Completion", completion) }).pipe( + Effect.provide(layerWithoutWallet), Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), Effect.provide( WalletClient.Live({ @@ -59,6 +96,7 @@ const program = Effect.gen(function* () { }), ), + Effect.provide(ChainRegistry.Default), Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), ) diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index c5cf2b2e72..8ef7cf6714 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -58,7 +58,7 @@ export const fromWallet = ( (v1) => Effect.gen(function*() { const meta = yield* pipe( - Sui.readCoinMetadata( + Sui.readCoinMeta( v1.baseToken.address as unknown as any ), Effect.provideService(Sui.PublicClient, client), @@ -136,32 +136,32 @@ export const fromWallet = ( console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { args }) - // // TODO: Fix writecontract calling, decide parameters etc. - // const sendInstruction = Sui.writeContract({ - // client: client, - // account: wallet.signer, - // abi: Ucs03.Abi, - // chain: wallet.chain, - // functionName: "send", - // address: request.ucs03Address as unknown as any, - // args, - // value: funds, - // }).pipe( - // Effect.mapError((cause) => - // new ClientError.RequestError({ - // reason: "Transport", - // request, - // cause, - // description: "writeContract", - // }) - // ), - // Effect.provideService(Evm.WalletClient, wallet), - // ) - - // return yield* pipe( - // sendInstruction, - // Effect.map((txHash) => new ClientResponseImpl(request, client, txHash)), - // ) + // TODO: Fix writecontract calling, decide parameters etc. + const sendInstruction = Sui.writeContract({ + client: client, + account: wallet.signer, + abi: Ucs03.Abi, + chain: wallet.chain, + functionName: "send", + address: request.ucs03Address as unknown as any, + args, + value: funds, + }).pipe( + Effect.mapError((cause) => + new ClientError.RequestError({ + reason: "Transport", + request, + cause, + description: "writeContract", + }) + ), + Effect.provideService(Evm.WalletClient, wallet), + ) + + return yield* pipe( + sendInstruction, + Effect.map((txHash) => new ClientResponseImpl(request, client, txHash)), + ) }) ) From 7fdf910a5bafe935dec2dfcf0e37c4b60c1cfd28 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Fri, 26 Sep 2025 18:20:32 +0300 Subject: [PATCH 11/24] chore(ts-sdk-sui): fromWallet improvements Signed-off-by: kaancaglan --- ts-sdk-sui/src/internal/zkgmClient.ts | 126 ++++++++++++++++---------- 1 file changed, 80 insertions(+), 46 deletions(-) diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index 8ef7cf6714..151e5182d8 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -20,6 +20,8 @@ import * as S from "effect/Schema" import * as Stream from "effect/Stream" import * as Sui from "../Sui.js" import * as Safe from "../Safe.js" +import { Transaction } from "@mysten/sui/transactions" + // import { Sui } from "../index.js" export const fromWallet = ( @@ -96,7 +98,6 @@ export const fromWallet = ( ) console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { salt, timeoutTimestamp }) - const operand = yield* pipe( encodeInstruction(request.instruction), Effect.flatMap(S.encode(Ucs03.Ucs03FromHex)), @@ -112,59 +113,92 @@ export const fromWallet = ( console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { operand }) - const funds = ClientRequest.requiredFunds(request).pipe( - O.map(A.filter(([x]) => Token.isNative(x))), - O.flatMap(O.liftPredicate(A.isNonEmptyReadonlyArray)), - O.map(A.map(flow(Tuple.getSecond))), - O.map(A.reduce(0n, (acc, n) => acc + n)), - O.getOrUndefined, - ) + // ---- Sui PTB: begin_send -> send_with_coin -> end_send ---- - console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { funds }) - - const args = [ - request.channelId, - 0n, - timeoutTimestamp, - salt, - { - opcode: request.instruction.opcode, - version: request.instruction.version, - operand, - }, - ] as const - - console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { args }) - - // TODO: Fix writecontract calling, decide parameters etc. - const sendInstruction = Sui.writeContract({ - client: client, - account: wallet.signer, - abi: Ucs03.Abi, - chain: wallet.chain, - functionName: "send", - address: request.ucs03Address as unknown as any, - args, - value: funds, - }).pipe( - Effect.mapError((cause) => + const tx = new Transaction() + const CLOCK_OBJECT_ID = "0x6" // Sui system clock + const tHeight = 0n + const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" //zkgm package id TODO: This should be fetched from somewhere + const module = "zkgm" //zkgm module name + const typeArg = "0x2::sui::SUI" // TODO: This should be dynamic based on the token sent + const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // TODO: This should be fetched from somewhere + const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // TODO: This should be fetched from somewhere + const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // TODO: This should be fetched from somewhere + const coinObjectId = "0x3997d4c40cb190283291270d326401fd77320af42cd7a891ded2eba3e52f4b16" // TODO: This should be given by user + + // helpers + const hexToBytes = (hex: `0x${string}`): Uint8Array => { + const s = hex.slice(2) + const out = new Uint8Array(s.length / 2) + for (let i = 0; i < out.length; i++) out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16) + return out + } + + // 1) begin_send(channel_id: u32, salt: vector) -> SendCtx + let sendCtx = tx.moveCall({ + target: `${packageId}::${module}::begin_send`, + typeArguments: [], + arguments: [ + tx.pure.u32(Number(request.channelId)), // channel id (u32) + tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), // salt bytes + ], + }) + + // 2) send_with_coin(relay_store, vault, ibc_store, coin, version, opcode, operand, ctx) -> SendCtx + sendCtx = tx.moveCall({ + target: `${packageId}::${module}::send_with_coin`, + typeArguments: [typeArg], + arguments: [ + tx.object(relayStoreId), + tx.object(vaultId), + tx.object(ibcStoreId), + tx.object(coinObjectId), + tx.pure.u8(Number(request.instruction.version)), + tx.pure.u8(Number(request.instruction.opcode)), + tx.pure.vector("u8", hexToBytes(operand as `0x${string}`)), + sendCtx, + ], + }) + + // 3) end_send(ibc_store, clock, t_height: u64, timeout_ns: u64, ctx) + tx.moveCall({ + target: `${packageId}::${module}::end_send`, + typeArguments: [], + arguments: [ + tx.object(ibcStoreId), + tx.object(CLOCK_OBJECT_ID), + tx.pure.u64(tHeight), + tx.pure.u64(BigInt(timeoutTimestamp)), + sendCtx, + ], + }) + + // sign & execute + const submit = Effect.tryPromise({ + try: async () => + wallet.client.signAndExecuteTransaction({ + signer: wallet.signer, + transaction: tx, + }), + catch: (cause) => new ClientError.RequestError({ reason: "Transport", request, cause, - description: "writeContract", - }) - ), - Effect.provideService(Evm.WalletClient, wallet), - ) + description: "signAndExecuteTransaction", + }), + }) - return yield* pipe( - sendInstruction, - Effect.map((txHash) => new ClientResponseImpl(request, client, txHash)), - ) - }) + const res = yield* submit + + console.log("Res.transaction:", res.transaction) + const txHash = (res.digest ?? res.transaction?.txSignatures[0] ?? "") as Hex + + return new ClientResponseImpl(request, client, txHash) + }), ) + /** @internal */ export abstract class IncomingMessageImpl extends Inspectable.Class implements IncomingMessage.ZkgmIncomingMessage From 6825e0b5a086d139563e39279d3a4d08bbbebccd Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Fri, 26 Sep 2025 18:43:23 +0300 Subject: [PATCH 12/24] chore(ts-sdk-sui): working POC added Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 36 +++++++++------ .../sui-create-client-write-contract.ts | 17 +++++-- ts-sdk-sui/src/internal/sui.ts | 46 ++++++++----------- ts-sdk-sui/src/internal/zkgmClient.ts | 2 +- 4 files changed, 54 insertions(+), 47 deletions(-) diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts index 3cf19b5e79..456d7a9bb3 100644 --- a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -21,23 +21,30 @@ import * as TokenOrder from "@unionlabs/sdk/TokenOrder" import { UniversalChainId } from "@unionlabs/sdk/schema/chain" import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry" -const MNEMONIC = process.env.MNEMONIC ?? "fix auto gallery heart practice drip joke nice decline lift attend bread" +const MNEMONIC = process.env.SUI_MNEMONIC ?? "..." const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) const program = Effect.gen(function* () { + + // TODO: Source will be SUI testnet const source = yield* ChainRegistry.byUniversalId( UniversalChainId.make("ethereum.17000"), ) console.log("source", source) + + // TODO: Destination will be somewhere const destination = yield* ChainRegistry.byUniversalId( UniversalChainId.make("ethereum.11155111"), ) - const { client } = yield* PublicClient + const wallet = yield* WalletClient + + const sender = wallet.signer.toSuiAddress(); + // TODO: Fix this tokenOrder and write something working const tokenOrder = yield* TokenOrder.make({ source, destination, @@ -56,10 +63,11 @@ const program = Effect.gen(function* () { yield* Effect.log("Token Order V2", tokenOrder) + // TODO: Fix this request too const request = ZkgmClientRequest.make({ source, destination, - channelId: ChannelId.make(2), + channelId: ChannelId.make(1), ucs03Address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03", kind: "simulateAndExecute", instruction: tokenOrder, @@ -67,12 +75,8 @@ const program = Effect.gen(function* () { const zkgmClient = yield* ZkgmClient.ZkgmClient - // NOTE: 1. switch chain is assumed - // NOTE: 2. write in progress - const response: ZkgmClientResponse.ZkgmClientResponse = yield* zkgmClient.execute(request) - // NOTE: 3. write complete (with tx hash) yield* Effect.log("Submission Hash", response.txHash) @@ -80,8 +84,6 @@ const program = Effect.gen(function* () { ZkgmIncomingMessage.LifecycleEvent.$is("EvmTransactionReceiptComplete"), ) - // NOTE: 4. tx complete - yield* Effect.log("Completion", completion) @@ -90,14 +92,20 @@ const program = Effect.gen(function* () { Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), Effect.provide( WalletClient.Live({ - url: getFullnodeUrl("testnet"), - account: keypair, // signer - chain: "sui-testnet" as any, // placeholder; not used internally - }), + url: getFullnodeUrl("testnet"), + signer: keypair, // ✅ Sui signer + }), ), Effect.provide(ChainRegistry.Default), Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), ) -Effect.runPromise(program).catch(console.error) +Effect.runPromise(program).catch((e: any) => { + console.error("\n--- TOP-LEVEL ERROR ---") + console.dir(e, { depth: 10 }) + if (e?.cause) { + console.error("\n--- ORIGINAL CAUSE ---") + console.dir(e.cause, { depth: 10 }) + } +}) diff --git a/ts-sdk-sui/examples/sui-create-client-write-contract.ts b/ts-sdk-sui/examples/sui-create-client-write-contract.ts index a392e451db..9ff689043d 100644 --- a/ts-sdk-sui/examples/sui-create-client-write-contract.ts +++ b/ts-sdk-sui/examples/sui-create-client-write-contract.ts @@ -19,6 +19,16 @@ const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) const program = Effect.gen(function* () { const { client } = yield* PublicClient + yield* Effect.log("Sui public client initialized", client.network ) + const meta = yield* readCoinMetadata("0x2::sui::SUI" as any) + yield* Effect.log("SUI metadata", meta) + + yield* Effect.log("keypair.getPublicKey().toSuiAddress()", keypair.getPublicKey().toSuiAddress()) + const balances = yield* readCoinBalances("0x2::sui::SUI" as any, keypair.getPublicKey().toSuiAddress() as any) + yield* Effect.log("SUI balances", balances) + + + const wallet = yield* WalletClient const amountMist = 10_000_000n // 0.01 SUI const tx = new Transaction() @@ -43,10 +53,9 @@ const program = Effect.gen(function* () { Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), Effect.provide( WalletClient.Live({ - url: getFullnodeUrl("testnet"), - account: keypair, // signer - chain: "sui-testnet" as any, // placeholder; not used internally - }), + url: getFullnodeUrl("testnet"), + signer: keypair, // ✅ Sui signer + }), ), Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), diff --git a/ts-sdk-sui/src/internal/sui.ts b/ts-sdk-sui/src/internal/sui.ts index e5551dbcbe..b24d02c15b 100644 --- a/ts-sdk-sui/src/internal/sui.ts +++ b/ts-sdk-sui/src/internal/sui.ts @@ -29,35 +29,25 @@ export const publicClientLayer = < ), ) + /** @internal */ -export const walletClientLayer = < - Id, ->(tag: Context.Tag) => -( - // interface unchanged - options: Parameters[0] & { - account: Ed25519Keypair - chain: unknown - }, -): Layer.Layer => +export const walletClientLayer = ( + tag: Context.Tag, +) => +(opts: { url: string; signer: Ed25519Keypair }): Layer.Layer => Layer.effect( tag, - pipe( - Effect.try({ - try: () => ({ - client: new SuiClient(options as unknown as SuiClientOptions), - signer: options.account as Ed25519Keypair, + Effect.try({ + try: () => { + if (!opts?.signer || typeof opts.signer.getPublicKey !== "function") { + throw new Error("Invalid Sui signer: expected Ed25519Keypair") + } + const client = new SuiClient({ url: opts.url } satisfies SuiClientOptions) + return { client, signer: opts.signer } // <-- matches Sui.Sui.WalletClient interface + }, + catch: (err) => + new Sui.CreateWalletClientError({ + cause: Utils.extractErrorDetails(err as Error), }), - catch: (err) => - new Sui.CreateWalletClientError({ - cause: Utils.extractErrorDetails(err as Sui.SuiCreateWalletClientErrorType), - }), - }), - // return the *same* payload shape you had before - Effect.map(({ client }) => ({ - client, - account: options.account, - chain: options.chain, - })), - ), - ) + }), + ) \ No newline at end of file diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index 151e5182d8..2eeca85c53 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -124,7 +124,7 @@ export const fromWallet = ( const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // TODO: This should be fetched from somewhere const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // TODO: This should be fetched from somewhere const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // TODO: This should be fetched from somewhere - const coinObjectId = "0x3997d4c40cb190283291270d326401fd77320af42cd7a891ded2eba3e52f4b16" // TODO: This should be given by user + const coinObjectId = "0xc2c61b8de5aa6167dc744d6db61854fa29b53257d5e54a40c2f5b7dcc6f04d0b" // TODO: This should be given by user // helpers const hexToBytes = (hex: `0x${string}`): Uint8Array => { From d2238ada3d986a3256d8a6f1919390844af5c01e Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Mon, 29 Sep 2025 17:03:50 +0300 Subject: [PATCH 13/24] chore(ts-sdk-sui): fixed token order , still issues with display Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 34 +++++++++---------- ts-sdk-sui/src/internal/zkgmClient.ts | 28 +++++++++------ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts index 456d7a9bb3..59faa419b0 100644 --- a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -38,41 +38,41 @@ const program = Effect.gen(function* () { // TODO: Destination will be somewhere const destination = yield* ChainRegistry.byUniversalId( - UniversalChainId.make("ethereum.11155111"), + UniversalChainId.make("union.union-1"), ) const wallet = yield* WalletClient const sender = wallet.signer.toSuiAddress(); - // TODO: Fix this tokenOrder and write something working + + // deployed contract: union1l0rpy8yauy7nzv4vu6mgz6kjpqzvws85l8mgzm6eansasx90t57sc7k4ue + + console.log("sender:", sender) + const tokenOrder = yield* TokenOrder.make({ source, destination, sender: "0x06627714f3F17a701f7074a12C02847a5D2Ca487", - receiver: "0x50A22f95bcB21E7bFb63c7A8544AC0683dCeA302", - // LINK on Holesky - baseToken: "0x685ce6742351ae9b618f383883d6d1e0c5a31b4b", - baseAmount: 10n, - // Holesky LINK on Sepolia - quoteToken: "0x80fdbf104ec58a527ec40f7b03f88c404ef4ba63", - quoteAmount: 10n, - kind: "escrow", - metadata: undefined, + receiver: "0x756E696F6E317779637938673876357366663667736A6C3979686A73343371393878706C30357033676E3273", + baseToken: "0x3078323A3A7375693A3A535549", + baseAmount: 100000n, + quoteToken: "0x756E696F6E316C307270793879617579376E7A76347675366D677A366B6A70717A76777338356C386D677A6D3665616E7361737839307435377363376B347565", + quoteAmount: 10000n, + kind: "solve", + metadata: + "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040756e696f6e31736b673532343468706b61643630337a7a37376b64656b7a77366666677066726465336c646b387270647a30366e36326b34687163743077346a", version: 2, }) - yield* Effect.log("Token Order V2", tokenOrder) - - // TODO: Fix this request too const request = ZkgmClientRequest.make({ source, destination, channelId: ChannelId.make(1), - ucs03Address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03", - kind: "simulateAndExecute", + ucs03Address: "union1rfz3ytg6l60wxk5rxsk27jvn2907cyav04sz8kde3xhmmf9nplxqr8y05c", instruction: tokenOrder, }) + const zkgmClient = yield* ZkgmClient.ZkgmClient const response: ZkgmClientResponse.ZkgmClientResponse = yield* zkgmClient.execute(request) @@ -93,7 +93,7 @@ const program = Effect.gen(function* () { Effect.provide( WalletClient.Live({ url: getFullnodeUrl("testnet"), - signer: keypair, // ✅ Sui signer + signer: keypair, }), ), diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index 2eeca85c53..773080bb2c 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -113,20 +113,28 @@ export const fromWallet = ( console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { operand }) - // ---- Sui PTB: begin_send -> send_with_coin -> end_send ---- const tx = new Transaction() const CLOCK_OBJECT_ID = "0x6" // Sui system clock const tHeight = 0n - const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" //zkgm package id TODO: This should be fetched from somewhere const module = "zkgm" //zkgm module name + + + // These will be fetched from hubble or from deployments.json + const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" //zkgm package id + // TODO: packageId can be changed when zkgm updated + const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // This won't be changed for a while + const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // This won't be changed for a while + const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // This won't be changed + + + // This 2 will be get by user all the time const typeArg = "0x2::sui::SUI" // TODO: This should be dynamic based on the token sent - const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // TODO: This should be fetched from somewhere - const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // TODO: This should be fetched from somewhere - const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // TODO: This should be fetched from somewhere - const coinObjectId = "0xc2c61b8de5aa6167dc744d6db61854fa29b53257d5e54a40c2f5b7dcc6f04d0b" // TODO: This should be given by user - - // helpers + const coinObjectId = "0xfdb7b5b54d61ed21373f2785c7583c1ff6fd85e9b04fa289da50bba563190b62" // TODO: This should be given by user + // Note: There can be multiple coins, for simplicity we are using one coin here + // User should be able to provide typeArgs and coinObjectIds array + + const hexToBytes = (hex: `0x${string}`): Uint8Array => { const s = hex.slice(2) const out = new Uint8Array(s.length / 2) @@ -139,8 +147,8 @@ export const fromWallet = ( target: `${packageId}::${module}::begin_send`, typeArguments: [], arguments: [ - tx.pure.u32(Number(request.channelId)), // channel id (u32) - tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), // salt bytes + tx.pure.u32(Number(request.channelId)), + tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), ], }) From 2ec3db8f1d61a86efc68d5227b17faaa35e41a02 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Mon, 29 Sep 2025 17:15:25 +0300 Subject: [PATCH 14/24] chore(ts-sdk-sui): added suidisplay Signed-off-by: kaancaglan --- ts-sdk/src/Ucs05.ts | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/ts-sdk/src/Ucs05.ts b/ts-sdk/src/Ucs05.ts index 6406a5e168..c17a61d90d 100644 --- a/ts-sdk/src/Ucs05.ts +++ b/ts-sdk/src/Ucs05.ts @@ -18,9 +18,41 @@ import { import { isAddress, toHex } from "viem" import { AddressCanonicalBytes } from "./schema/address.js" import { Hex, HexFromString } from "./schema/hex.js" +import { isValidSuiAddress, normalizeSuiAddress } from "@mysten/sui/utils" // const AddressFromChain = (chain: Chain) => + +/** + * @category models + * @since 2.0.0 + */ +export const SuiAddress = S.NonEmptyString.pipe( + S.filter((a) => isValidSuiAddress(a), { + description: + "Sui address (32-byte hex). Accepts with/without 0x; even length; hex only.", + }), +) +/** + * @category models + * @since 2.0.0 + */ +export type SuiAddress = typeof SuiAddress.Type + +/** + * @category models + * @since 2.0.0 + */ +export const SuiDisplay = S.Struct({ + _tag: S.tag("SuiDisplay"), + address: SuiAddress, +}) +/** + * @category models + * @since 2.0.0 + */ +export type SuiDisplay = typeof SuiDisplay.Type + /** * @category models * @since 2.0.0 @@ -209,6 +241,7 @@ export type CosmosDisplay = typeof CosmosDisplay.Type export const AnyDisplay = S.Union( CosmosDisplay, EvmDisplay, + SuiDisplay, ) /** * @category models @@ -229,6 +262,7 @@ export const AnyDisplayFromString = S.transformOrFail( Effect.raceAll([ S.decodeUnknownEither(EvmDisplay)({ _tag: "EvmDisplay", address }), S.decodeUnknownEither(CosmosDisplay)({ _tag: "CosmosDisplay", address }), + S.decodeUnknownEither(SuiDisplay)({ _tag: "SuiDisplay", address }), ]), Effect.catchTag("ParseError", (error) => ParseResult.fail(error.issue)), ), @@ -266,6 +300,7 @@ export const ZkgmFromAnyDisplay = S.transform( Match.tagsExhaustive({ CosmosDisplay: ({ address }) => toHex(address), EvmDisplay: ({ address }) => identity(address), + SuiDisplay: ({ address }) => identity(normalizeSuiAddress(address) as Hex) }), ), encode: (_) => absurd(void 0 as never), @@ -280,6 +315,7 @@ export const anyDisplayToZkgm = Match.type().pipe( Match.tagsExhaustive({ CosmosDisplay: ({ address }) => S.decode(HexFromString)(address), EvmDisplay: ({ address }) => Effect.succeed(address), + SuiDisplay: ({ address }) => S.decode(HexFromString)(normalizeSuiAddress(address)), }), ) @@ -298,6 +334,7 @@ export const anyDisplayToCanonical = Match.type().pipe( console.log("bytes", { result }) }, EvmDisplay: ({ address }) => AddressCanonicalBytes.make(address), + SuiDisplay: ({ address }) => AddressCanonicalBytes.make(normalizeSuiAddress(address)) }), ) /** @@ -306,7 +343,7 @@ export const anyDisplayToCanonical = Match.type().pipe( * @category models * @since 2.0.0 */ -export const ValidAddress = S.Union(ERC55, Bech32) +export const ValidAddress = S.Union(ERC55, Bech32, SuiAddress) /** * @category models * @since 2.0.0 From cbfe7c2a95ab7092331d7bc77de70c72097ed621 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Tue, 30 Sep 2025 17:41:27 +0300 Subject: [PATCH 15/24] chore(ts-sdk-sui): added suitoken Signed-off-by: kaancaglan --- ts-sdk/src/Token.ts | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/ts-sdk/src/Token.ts b/ts-sdk/src/Token.ts index 089ba2139f..ca1b1b5ea5 100644 --- a/ts-sdk/src/Token.ts +++ b/ts-sdk/src/Token.ts @@ -8,6 +8,41 @@ import { constFalse, constTrue } from "effect/Function" import * as Chain from "./schema/chain.js" import * as Hex from "./schema/hex.js" import * as Utils from "./Utils.js" +import { isValidSuiAddress, normalizeSuiAddress } from "@mysten/sui/utils" + +/** + * @category schemas + * @since 2.0.0 + */ +export class SuiCoin extends S.TaggedClass()("SuiCoin", { + address: S.String.pipe( + S.filter((value) => { + const parts = value.split("::") + if (parts.length !== 3) return false + const [addr, module, name] = parts + + const ident = /^[A-Za-z_][A-Za-z0-9_]*$/ + if (!ident.test(module) || !ident.test(name)) return false + + const norm = normalizeSuiAddress(addr) + return isValidSuiAddress(norm) + }, { + description: + "Sui coin type in the form 0x:::: with a valid Sui address and Move identifiers", + }), + S.annotations({ + examples: [ + "0x2::sui::SUI", + "0x9003c05db750fe8fb33d8e9a7de814b2ca1af024dc67e06f8529260b03d86fdd::usdt_faucet::USDT_FAUCET", + ], + }), + ), +}) { + [Hash.symbol](): number { + return Hash.string(this.address) + } +} + /** * @category schemas @@ -133,6 +168,7 @@ export const Any = S.Union( CosmosTokenFactory, CosmosBank, CosmosIbcClassic, + SuiCoin, ) /** * @category models @@ -155,6 +191,7 @@ export const TokenFromString = S.transformOrFail( S.decodeEither(CosmosIbcClassic)({ _tag: "CosmosIbcClassic", address }), S.decodeEither(CosmosTokenFactory)({ _tag: "CosmosTokenFactory", address }), S.decodeEither(Cw20)({ _tag: "Cw20", address }), + S.decodeEither(SuiCoin)({ _tag: "SuiCoin", address }), ]), Effect.orElse(() => S.decodeEither(Erc20)({ _tag: "Erc20", address })), Effect.orElse(() => S.decodeEither(CosmosBank)({ _tag: "CosmosBank", address })), @@ -185,6 +222,8 @@ export const AnyFromEncoded = (rpcType: Chain.RpcType) => )), Match.when("aptos", (fromA) => Effect.fail(new ParseResult.Type(ast, fromA, "Aptos not supported."))), + Match.when("sui", () => + pipe(fromA, S.decode(S.compose(Hex.StringFromHex, TokenFromString)))), Match.exhaustive, Effect.catchTag("ParseError", (error) => ParseResult.fail(error.issue)), @@ -197,6 +236,17 @@ export const AnyFromEncoded = (rpcType: Chain.RpcType) => }, ) +export const normalizeSuiTypeTag = (t: string): string => { + const [addr, mod, name] = t.split("::") + return `${normalizeSuiAddress(addr)}::${mod}::${name}` +} + +const isNativeSui = (t: string): boolean => { + // compare on normalized address to avoid short/long mismatch + const norm = normalizeSuiTypeTag(t) + return norm === "0x2::sui::SUI" +} + /** * @category predicates * @since 2.0.0 @@ -209,5 +259,6 @@ export const isNative = Match.type().pipe( Cw20: constFalse, Erc20: constFalse, EvmGas: constTrue, + SuiCoin: (t) => isNativeSui(t.address), }), ) From 1a5e346a7b01bf4625da516e8a90b9136bfe6101 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Tue, 30 Sep 2025 17:41:44 +0300 Subject: [PATCH 16/24] chore(ts-sdk-sui): fully functional ts-sdk example for sui->union Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 21 ++++++++----------- ts-sdk-sui/src/internal/zkgmClient.ts | 3 +-- ts-sdk/src/schema/chain.ts | 4 +++- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts index 59faa419b0..a7f8ba6704 100644 --- a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -29,6 +29,7 @@ const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) const program = Effect.gen(function* () { + // TODO: Source will be SUI testnet const source = yield* ChainRegistry.byUniversalId( UniversalChainId.make("ethereum.17000"), @@ -44,30 +45,27 @@ const program = Effect.gen(function* () { const sender = wallet.signer.toSuiAddress(); - - // deployed contract: union1l0rpy8yauy7nzv4vu6mgz6kjpqzvws85l8mgzm6eansasx90t57sc7k4ue - console.log("sender:", sender) const tokenOrder = yield* TokenOrder.make({ source, destination, - sender: "0x06627714f3F17a701f7074a12C02847a5D2Ca487", - receiver: "0x756E696F6E317779637938673876357366663667736A6C3979686A73343371393878706C30357033676E3273", - baseToken: "0x3078323A3A7375693A3A535549", - baseAmount: 100000n, - quoteToken: "0x756E696F6E316C307270793879617579376E7A76347675366D677A366B6A70717A76777338356C386D677A6D3665616E7361737839307435377363376B347565", - quoteAmount: 10000n, + sender: sender, + receiver: "union1wycy8g8v5sff6gsjl9yhjs43q98xpl05p3gn2s", + baseToken: "0x2::sui::SUI", + baseAmount: 10000000n, + quoteToken: "union1y05e0p2jcvhjzf7kcqsrqx93d4g3u93hc2hykaq8hrvkqrp5ltrssagzyd", + quoteAmount: 10000000n, kind: "solve", metadata: - "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040756e696f6e31736b673532343468706b61643630337a7a37376b64656b7a77366666677066726465336c646b387270647a30366e36326b34687163743077346a", + "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040756e696f6e31793035653070326a6376686a7a66376b63717372717839336434673375393368633268796b6171386872766b717270356c7472737361677a7964", version: 2, }) const request = ZkgmClientRequest.make({ source, destination, - channelId: ChannelId.make(1), + channelId: ChannelId.make(5), ucs03Address: "union1rfz3ytg6l60wxk5rxsk27jvn2907cyav04sz8kde3xhmmf9nplxqr8y05c", instruction: tokenOrder, }) @@ -77,7 +75,6 @@ const program = Effect.gen(function* () { const response: ZkgmClientResponse.ZkgmClientResponse = yield* zkgmClient.execute(request) - yield* Effect.log("Submission Hash", response.txHash) const completion = yield* response.waitFor( diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index 773080bb2c..699d5f145a 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -130,11 +130,10 @@ export const fromWallet = ( // This 2 will be get by user all the time const typeArg = "0x2::sui::SUI" // TODO: This should be dynamic based on the token sent - const coinObjectId = "0xfdb7b5b54d61ed21373f2785c7583c1ff6fd85e9b04fa289da50bba563190b62" // TODO: This should be given by user + const coinObjectId = "0x89c430d35fa9f2778b0a635027b178146eb26d70d16292c289304d476ecf76cd" // TODO: This should be given by user // Note: There can be multiple coins, for simplicity we are using one coin here // User should be able to provide typeArgs and coinObjectIds array - const hexToBytes = (hex: `0x${string}`): Uint8Array => { const s = hex.slice(2) const out = new Uint8Array(s.length / 2) diff --git a/ts-sdk/src/schema/chain.ts b/ts-sdk/src/schema/chain.ts index bc6c547736..76755a1a67 100644 --- a/ts-sdk/src/schema/chain.ts +++ b/ts-sdk/src/schema/chain.ts @@ -23,7 +23,7 @@ export type UniversalChainId = typeof UniversalChainId.Type export const ChainDisplayName = S.String.pipe(S.brand("ChainDisplayName")) -export const RpcType = S.Literal("evm", "cosmos", "aptos") +export const RpcType = S.Literal("evm", "cosmos", "aptos", "sui") export type RpcType = typeof RpcType.Type export class ChainFeatures extends S.Class("ChainFeatures")({ @@ -154,6 +154,8 @@ export class Chain extends S.Class("Chain")({ case "aptos": // Aptos uses the canonical format return Effect.succeed(address) + case "sui": + return Effect.succeed(address) default: return Effect.fail(new NotACosmosChainError({ chain: this })) } From bbb70a9bf200e066aec6f082ff981ae00f4b988c Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 1 Oct 2025 10:52:38 +0300 Subject: [PATCH 17/24] chore(ts-sdk-sui): nix-fmt applied Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 43 +++--- .../sui-create-client-read-coin-related.ts | 22 ++- .../sui-create-client-write-contract.ts | 48 ++++--- ts-sdk-sui/src/Sui.ts | 131 +++++++++--------- ts-sdk-sui/src/SuiZkgmClient.ts | 2 +- ts-sdk-sui/src/internal/sui.ts | 7 +- ts-sdk-sui/src/internal/zkgmClient.ts | 25 ++-- ts-sdk/src/Token.ts | 13 +- ts-sdk/src/Ucs05.ts | 14 +- 9 files changed, 152 insertions(+), 153 deletions(-) diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts index a7f8ba6704..4ef2bd1c11 100644 --- a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -1,49 +1,54 @@ // @ts-ignore if (typeof BigInt.prototype.toJSON !== "function") { // @ts-ignore - BigInt.prototype.toJSON = function () { + BigInt.prototype.toJSON = function() { return this.toString() } } -import { Effect, Logger } from "effect" import { getFullnodeUrl } from "@mysten/sui/client" -import { PublicClient, WalletClient, writeContract, readCoinMetadata, readCoinBalances, sendInstruction } from "../src/Sui.js" import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" +import { ChannelId } from "@unionlabs/sdk/schema/channel" +import * as ZkgmClient from "@unionlabs/sdk/ZkgmClient" import * as ZkgmClientRequest from "@unionlabs/sdk/ZkgmClientRequest" import * as ZkgmClientResponse from "@unionlabs/sdk/ZkgmClientResponse" -import { ChannelId } from "@unionlabs/sdk/schema/channel" import * as ZkgmIncomingMessage from "@unionlabs/sdk/ZkgmIncomingMessage" -import * as ZkgmClient from "@unionlabs/sdk/ZkgmClient" +import { Effect, Logger } from "effect" +import { + PublicClient, + readCoinBalances, + readCoinMetadata, + sendInstruction, + WalletClient, + writeContract, +} from "../src/Sui.js" import { layerWithoutWallet } from "../src/SuiZkgmClient.js" import { Transaction } from "@mysten/sui/transactions" -import * as TokenOrder from "@unionlabs/sdk/TokenOrder" -import { UniversalChainId } from "@unionlabs/sdk/schema/chain" import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry" +import { UniversalChainId } from "@unionlabs/sdk/schema/chain" +import * as TokenOrder from "@unionlabs/sdk/TokenOrder" const MNEMONIC = process.env.SUI_MNEMONIC ?? "..." -const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" +const RECIPIENT = process.env.RECIPIENT + ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) - -const program = Effect.gen(function* () { - - +const program = Effect.gen(function*() { // TODO: Source will be SUI testnet const source = yield* ChainRegistry.byUniversalId( UniversalChainId.make("ethereum.17000"), ) console.log("source", source) - + // TODO: Destination will be somewhere const destination = yield* ChainRegistry.byUniversalId( UniversalChainId.make("union.union-1"), ) const wallet = yield* WalletClient - const sender = wallet.signer.toSuiAddress(); + const sender = wallet.signer.toSuiAddress() console.log("sender:", sender) @@ -70,7 +75,6 @@ const program = Effect.gen(function* () { instruction: tokenOrder, }) - const zkgmClient = yield* ZkgmClient.ZkgmClient const response: ZkgmClientResponse.ZkgmClientResponse = yield* zkgmClient.execute(request) @@ -82,18 +86,15 @@ const program = Effect.gen(function* () { ) yield* Effect.log("Completion", completion) - - }).pipe( Effect.provide(layerWithoutWallet), Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), Effect.provide( WalletClient.Live({ - url: getFullnodeUrl("testnet"), - signer: keypair, - }), + url: getFullnodeUrl("testnet"), + signer: keypair, + }), ), - Effect.provide(ChainRegistry.Default), Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), ) diff --git a/ts-sdk-sui/examples/sui-create-client-read-coin-related.ts b/ts-sdk-sui/examples/sui-create-client-read-coin-related.ts index 45737bdf00..266648b518 100644 --- a/ts-sdk-sui/examples/sui-create-client-read-coin-related.ts +++ b/ts-sdk-sui/examples/sui-create-client-read-coin-related.ts @@ -1,32 +1,31 @@ // @ts-ignore if (typeof BigInt.prototype.toJSON !== "function") { // @ts-ignore - BigInt.prototype.toJSON = function () { + BigInt.prototype.toJSON = function() { return this.toString() } } -import { Effect, Logger } from "effect" import { getFullnodeUrl } from "@mysten/sui/client" +import { Effect, Logger } from "effect" import { - PublicClient, - readCoinMetadata, - readCoinBalances, - readTotalCoinBalance, getAllCoinsUnique, - getCoinName, getCoinDecimals, + getCoinName, + PublicClient, + readCoinBalances, + readCoinMetadata, readCoinSymbol, + readTotalCoinBalance, } from "../src/Sui.js" -const ADDRESS = - process.env.ADDRESS ?? - "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" +const ADDRESS = process.env.ADDRESS + ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" const COIN_TYPE = "0x2::sui::SUI" as any -const program = Effect.gen(function* () { +const program = Effect.gen(function*() { const { client } = yield* PublicClient yield* Effect.log("Sui public client initialized", client.network) @@ -47,7 +46,6 @@ const program = Effect.gen(function* () { const total = yield* readTotalCoinBalance(COIN_TYPE, ADDRESS as any) yield* Effect.log("SUI total balance (mist as BigInt)", total.toString()) - const unique = yield* getAllCoinsUnique(ADDRESS as any) yield* Effect.log("All coins (unique, summed)", unique) diff --git a/ts-sdk-sui/examples/sui-create-client-write-contract.ts b/ts-sdk-sui/examples/sui-create-client-write-contract.ts index 9ff689043d..3aa9bf9792 100644 --- a/ts-sdk-sui/examples/sui-create-client-write-contract.ts +++ b/ts-sdk-sui/examples/sui-create-client-write-contract.ts @@ -1,33 +1,42 @@ // @ts-ignore if (typeof BigInt.prototype.toJSON !== "function") { // @ts-ignore - BigInt.prototype.toJSON = function () { + BigInt.prototype.toJSON = function() { return this.toString() } } -import { Effect, Logger } from "effect" import { getFullnodeUrl } from "@mysten/sui/client" -import { PublicClient, WalletClient, writeContract, readCoinMetadata, readCoinBalances } from "../src/Sui.js" import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" import { Transaction } from "@mysten/sui/transactions" +import { Effect, Logger } from "effect" +import { + PublicClient, + readCoinBalances, + readCoinMetadata, + WalletClient, + writeContract, +} from "../src/Sui.js" -const MNEMONIC = process.env.MNEMONIC ?? "fix auto gallery heart practice drip joke nice decline lift attend bread" -const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" +const MNEMONIC = process.env.MNEMONIC + ?? "fix auto gallery heart practice drip joke nice decline lift attend bread" +const RECIPIENT = process.env.RECIPIENT + ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) - -const program = Effect.gen(function* () { +const program = Effect.gen(function*() { const { client } = yield* PublicClient - yield* Effect.log("Sui public client initialized", client.network ) + yield* Effect.log("Sui public client initialized", client.network) const meta = yield* readCoinMetadata("0x2::sui::SUI" as any) yield* Effect.log("SUI metadata", meta) yield* Effect.log("keypair.getPublicKey().toSuiAddress()", keypair.getPublicKey().toSuiAddress()) - const balances = yield* readCoinBalances("0x2::sui::SUI" as any, keypair.getPublicKey().toSuiAddress() as any) + const balances = yield* readCoinBalances( + "0x2::sui::SUI" as any, + keypair.getPublicKey().toSuiAddress() as any, + ) yield* Effect.log("SUI balances", balances) - const wallet = yield* WalletClient const amountMist = 10_000_000n // 0.01 SUI @@ -38,26 +47,23 @@ const program = Effect.gen(function* () { const res = yield* writeContract( client, keypair, - "0x2", // packageId: Sui framework - "transfer", // module: sui::transfer - "public_transfer", // function - ["0x2::coin::Coin<0x2::sui::SUI>"], // type arg T - [coin, recipient], // (obj: T, recipient: address) + "0x2", // packageId: Sui framework + "transfer", // module: sui::transfer + "public_transfer", // function + ["0x2::coin::Coin<0x2::sui::SUI>"], // type arg T + [coin, recipient], // (obj: T, recipient: address) tx, ) yield* Effect.log("Transfer submitted", res) - - }).pipe( Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), Effect.provide( WalletClient.Live({ - url: getFullnodeUrl("testnet"), - signer: keypair, // ✅ Sui signer - }), + url: getFullnodeUrl("testnet"), + signer: keypair, // ✅ Sui signer + }), ), - Effect.provide(Logger.replace(Logger.defaultLogger, Logger.prettyLoggerDefault)), ) diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index ef69128e9d..8a5763cb87 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -3,18 +3,18 @@ * * @since 0.0.0 */ +import { SuiClient, SuiClientOptions } from "@mysten/sui/client" +import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" +import { Transaction } from "@mysten/sui/transactions" import { GAS_DENOMS } from "@unionlabs/sdk/Constants" import { UniversalChainId } from "@unionlabs/sdk/schema/chain" -import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" -import { extractErrorDetails} from "@unionlabs/sdk/Utils" import * as Ucs03 from "@unionlabs/sdk/Ucs03" +import { extractErrorDetails } from "@unionlabs/sdk/Utils" import * as Utils from "@unionlabs/sdk/Utils" import { Context, Data, Effect, flow, Layer, pipe, Schema as S } from "effect" import { type Address, erc20Abi } from "viem" import type { Hex } from "viem" import * as internal from "./internal/sui.js" -import { SuiClient, SuiClientOptions } from "@mysten/sui/client" -import { Transaction } from "@mysten/sui/transactions" /** * @category models @@ -97,7 +97,6 @@ export class WaitForTransactionReceiptError extends Data.TaggedError( cause: unknown }> {} - export class ReadCoinError extends Data.TaggedError("ReadCoinError")<{ cause: unknown }> {} @@ -347,7 +346,7 @@ export const readCoinMetadata = (tokenAddress: Address) => * @since 0.0.0 */ export const readCoinMeta = (coinType: string) => - Effect.gen(function* () { + Effect.gen(function*() { const client = (yield* PublicClient).client const out = yield* Effect.tryPromise({ @@ -370,7 +369,6 @@ export const readCoinMeta = (coinType: string) => return out }) - /** * Read all coin objects for a given `coinType` and owner address. * @@ -406,7 +404,7 @@ export const readCoinBalances = (contractAddress: string, address: string) => return coins }) - /** +/** * Read and sum all coin object balances for a given `coinType` and owner. * * @param contractAddress Canonical coin type @@ -416,30 +414,29 @@ export const readCoinBalances = (contractAddress: string, address: string) => * @category utils * @since 0.0.0 */ - export const readTotalCoinBalance = (contractAddress: string, address: string) => - Effect.gen(function*() { - const client = (yield* PublicClient).client - let params = { - owner: address, - coinType: contractAddress, - } - - const coins = yield* Effect.tryPromise({ - try: async () => { - const result = await client.getCoins(params) - return result.data - }, - catch: err => - new ReadCoinError({ - cause: extractErrorDetails(err as ReadCoinError), - }), - }) - // Calculate total balance - const totalBalance = coins.reduce((acc, coin) => acc + BigInt(coin.balance), BigInt(0)) +export const readTotalCoinBalance = (contractAddress: string, address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + let params = { + owner: address, + coinType: contractAddress, + } - return totalBalance + const coins = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getCoins(params) + return result.data + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), }) + // Calculate total balance + const totalBalance = coins.reduce((acc, coin) => acc + BigInt(coin.balance), BigInt(0)) + return totalBalance + }) /** * Fetch *all* coin objects (any coin type) for an owner. @@ -450,27 +447,27 @@ export const readCoinBalances = (contractAddress: string, address: string) => * @category utils * @since 0.0.0 */ - export const getAllCoins = (address: string) => - Effect.gen(function*() { - const client = (yield* PublicClient).client - let params = { - owner: address, - } +export const getAllCoins = (address: string) => + Effect.gen(function*() { + const client = (yield* PublicClient).client + let params = { + owner: address, + } - const coins = yield* Effect.tryPromise({ - try: async () => { - const result = await client.getAllCoins(params) - return result.data - }, - catch: err => - new ReadCoinError({ - cause: extractErrorDetails(err as ReadCoinError), - }), - }) - return coins + const coins = yield* Effect.tryPromise({ + try: async () => { + const result = await client.getAllCoins(params) + return result.data + }, + catch: err => + new ReadCoinError({ + cause: extractErrorDetails(err as ReadCoinError), + }), }) + return coins + }) - /** +/** * Fetch all coins for an owner and return a unique list grouped by `coinType`, * with balances summed across coin objects. * @@ -488,7 +485,7 @@ export const readCoinBalances = (contractAddress: string, address: string) => * @category utils * @since 0.0.0 */ - export const getAllCoinsUnique = (address: string) => +export const getAllCoinsUnique = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -530,7 +527,6 @@ export const readCoinBalances = (contractAddress: string, address: string) => return result }) - /** * Convenience: read coin **name** for a given `coinType`. * @@ -540,7 +536,7 @@ export const readCoinBalances = (contractAddress: string, address: string) => * @category utils * @since 0.0.0 */ - export const getCoinName = (address: string) => +export const getCoinName = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -566,7 +562,7 @@ export const readCoinBalances = (contractAddress: string, address: string) => * @category utils * @since 0.0.0 */ - export const getCoinDecimals = (address: string) => +export const getCoinDecimals = (address: string) => Effect.gen(function*() { const client = (yield* PublicClient).client @@ -582,7 +578,7 @@ export const readCoinBalances = (contractAddress: string, address: string) => }) return decimals }) - + /** * Convenience: read coin **symbol** for a given `coinType`. * @@ -609,8 +605,7 @@ export const readCoinSymbol = (address: string) => return symbol }) - - /** +/** * PTB: begin_send -> (send_with_coin ...)* -> end_send * * Mirrors: @@ -629,10 +624,10 @@ export const sendInstruction = (params: { /** coin type, e.g. "0x2::sui::SUI" (used in typeArguments of send_with_coin) */ typeArg: string - relayStoreId: string // $RELAY_STORE - vaultId: string // $VAULT - ibcStoreId: string // $IBC_STORE - coinObjectId: string // $COIN + relayStoreId: string // $RELAY_STORE + vaultId: string // $VAULT + ibcStoreId: string // $IBC_STORE + coinObjectId: string // $COIN // extraSendCalls?: Array<{ // relayStoreId?: string @@ -646,9 +641,8 @@ export const sendInstruction = (params: { // }> instruction: Ucs03.Instruction - }) => - Effect.gen(function* () { + Effect.gen(function*() { const module = "zkgm" const clockObjectId = "0x6" // Sui system clock object @@ -659,13 +653,16 @@ export const sendInstruction = (params: { const timeoutNs = Utils.getTimeoutInNanoseconds24HoursFromNow() const tHeight = BigInt(0) - const operandHex = (yield* S.encode(Ucs03.InstructionFromHex)(params.instruction)) as `0x${string}` + const operandHex = + (yield* S.encode(Ucs03.InstructionFromHex)(params.instruction)) as `0x${string}` // helpers const hexToBytes = (hex: `0x${string}`): Uint8Array => { const s = hex.slice(2) const out = new Uint8Array(s.length / 2) - for (let i = 0; i < out.length; i++) out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16) + for (let i = 0; i < out.length; i++) { + out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16) + } return out } @@ -676,8 +673,8 @@ export const sendInstruction = (params: { target: `${params.packageId}::${module}::begin_send`, typeArguments: [], arguments: [ - tx.pure.u32(channelId), - tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), + tx.pure.u32(channelId), + tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), ], }) @@ -700,7 +697,7 @@ export const sendInstruction = (params: { tx.object(cfg.ibcStoreId), tx.object(cfg.coinObjectId), tx.pure.u8(cfg.version), - tx.pure.u8(cfg.opcode), + tx.pure.u8(cfg.opcode), tx.pure.vector("u8", hexToBytes(cfg.operandHex)), sendCtx, ], @@ -755,7 +752,7 @@ export const sendInstruction = (params: { return res }) - + // turn a hex string like "0xdeadbeef" into a number[] of bytes function hexToBytes(hex: string): number[] { const h = hex.startsWith("0x") ? hex.slice(2) : hex @@ -781,7 +778,7 @@ function hexToBytes(hex: string): number[] { // client: walletClient.client, // signer: walletClient.signer, // account: walletClient.account, -// abi: Ucs03.Abi, +// abi: Ucs03.Abi, // // chain: walletClient.chain, TODO: Do we need this? // fn: "send", // address: sourceConfig.ucs03address, diff --git a/ts-sdk-sui/src/SuiZkgmClient.ts b/ts-sdk-sui/src/SuiZkgmClient.ts index 23576eb57e..cf1d222ef6 100644 --- a/ts-sdk-sui/src/SuiZkgmClient.ts +++ b/ts-sdk-sui/src/SuiZkgmClient.ts @@ -5,8 +5,8 @@ */ import type * as ZkgmClient from "@unionlabs/sdk/ZkgmClient" import type * as Layer from "effect/Layer" -import type * as Sui from "./Sui.js" import * as internal from "./internal/zkgmClient.js" +import type * as Sui from "./Sui.js" /** * @category layers diff --git a/ts-sdk-sui/src/internal/sui.ts b/ts-sdk-sui/src/internal/sui.ts index b24d02c15b..a1d1b5a693 100644 --- a/ts-sdk-sui/src/internal/sui.ts +++ b/ts-sdk-sui/src/internal/sui.ts @@ -1,9 +1,9 @@ +import { SuiClient, SuiClientOptions } from "@mysten/sui/client" +import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" import * as Utils from "@unionlabs/sdk/Utils" import { Context, Effect, Layer, pipe } from "effect" import * as V from "viem" import * as Sui from "../Sui.js" -import { SuiClient, SuiClientOptions } from "@mysten/sui/client" -import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" /** @internal */ export const publicClientLayer = < @@ -29,7 +29,6 @@ export const publicClientLayer = < ), ) - /** @internal */ export const walletClientLayer = ( tag: Context.Tag, @@ -50,4 +49,4 @@ export const walletClientLayer = ( cause: Utils.extractErrorDetails(err as Error), }), }), - ) \ No newline at end of file + ) diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index 699d5f145a..54c7ccb0a5 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -1,3 +1,4 @@ +import { Transaction } from "@mysten/sui/transactions" import { Indexer, ZkgmIncomingMessage } from "@unionlabs/sdk" import * as Call from "@unionlabs/sdk/Call" import type { Hex } from "@unionlabs/sdk/schema/hex" @@ -18,9 +19,8 @@ import * as Inspectable from "effect/Inspectable" import * as O from "effect/Option" import * as S from "effect/Schema" import * as Stream from "effect/Stream" -import * as Sui from "../Sui.js" import * as Safe from "../Safe.js" -import { Transaction } from "@mysten/sui/transactions" +import * as Sui from "../Sui.js" // import { Sui } from "../index.js" @@ -61,7 +61,7 @@ export const fromWallet = ( Effect.gen(function*() { const meta = yield* pipe( Sui.readCoinMeta( - v1.baseToken.address as unknown as any + v1.baseToken.address as unknown as any, ), Effect.provideService(Sui.PublicClient, client), ) @@ -79,8 +79,7 @@ export const fromWallet = ( Match.exhaustive, ), Call: Call.encode, - } - ), + }), ) console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { wallet, client }) @@ -113,21 +112,18 @@ export const fromWallet = ( console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { operand }) - const tx = new Transaction() const CLOCK_OBJECT_ID = "0x6" // Sui system clock const tHeight = 0n - const module = "zkgm" //zkgm module name - + const module = "zkgm" // zkgm module name // These will be fetched from hubble or from deployments.json - const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" //zkgm package id + const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" // zkgm package id // TODO: packageId can be changed when zkgm updated const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // This won't be changed for a while const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // This won't be changed for a while const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // This won't be changed - // This 2 will be get by user all the time const typeArg = "0x2::sui::SUI" // TODO: This should be dynamic based on the token sent const coinObjectId = "0x89c430d35fa9f2778b0a635027b178146eb26d70d16292c289304d476ecf76cd" // TODO: This should be given by user @@ -137,7 +133,9 @@ export const fromWallet = ( const hexToBytes = (hex: `0x${string}`): Uint8Array => { const s = hex.slice(2) const out = new Uint8Array(s.length / 2) - for (let i = 0; i < out.length; i++) out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16) + for (let i = 0; i < out.length; i++) { + out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16) + } return out } @@ -146,7 +144,7 @@ export const fromWallet = ( target: `${packageId}::${module}::begin_send`, typeArguments: [], arguments: [ - tx.pure.u32(Number(request.channelId)), + tx.pure.u32(Number(request.channelId)), tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), ], }) @@ -202,10 +200,9 @@ export const fromWallet = ( const txHash = (res.digest ?? res.transaction?.txSignatures[0] ?? "") as Hex return new ClientResponseImpl(request, client, txHash) - }), + }) ) - /** @internal */ export abstract class IncomingMessageImpl extends Inspectable.Class implements IncomingMessage.ZkgmIncomingMessage diff --git a/ts-sdk/src/Token.ts b/ts-sdk/src/Token.ts index ca1b1b5ea5..08b07eab04 100644 --- a/ts-sdk/src/Token.ts +++ b/ts-sdk/src/Token.ts @@ -3,12 +3,12 @@ * * @since 2.0.0 */ +import { isValidSuiAddress, normalizeSuiAddress } from "@mysten/sui/utils" import { Effect, flow, Hash, Match, ParseResult, pipe, Schema as S, Struct } from "effect" import { constFalse, constTrue } from "effect/Function" import * as Chain from "./schema/chain.js" import * as Hex from "./schema/hex.js" import * as Utils from "./Utils.js" -import { isValidSuiAddress, normalizeSuiAddress } from "@mysten/sui/utils" /** * @category schemas @@ -18,11 +18,15 @@ export class SuiCoin extends S.TaggedClass()("SuiCoin", { address: S.String.pipe( S.filter((value) => { const parts = value.split("::") - if (parts.length !== 3) return false + if (parts.length !== 3) { + return false + } const [addr, module, name] = parts const ident = /^[A-Za-z_][A-Za-z0-9_]*$/ - if (!ident.test(module) || !ident.test(name)) return false + if (!ident.test(module) || !ident.test(name)) { + return false + } const norm = normalizeSuiAddress(addr) return isValidSuiAddress(norm) @@ -43,7 +47,6 @@ export class SuiCoin extends S.TaggedClass()("SuiCoin", { } } - /** * @category schemas * @since 2.0.0 @@ -222,7 +225,7 @@ export const AnyFromEncoded = (rpcType: Chain.RpcType) => )), Match.when("aptos", (fromA) => Effect.fail(new ParseResult.Type(ast, fromA, "Aptos not supported."))), - Match.when("sui", () => + Match.when("sui", () => pipe(fromA, S.decode(S.compose(Hex.StringFromHex, TokenFromString)))), Match.exhaustive, Effect.catchTag("ParseError", (error) => diff --git a/ts-sdk/src/Ucs05.ts b/ts-sdk/src/Ucs05.ts index c17a61d90d..62101b1029 100644 --- a/ts-sdk/src/Ucs05.ts +++ b/ts-sdk/src/Ucs05.ts @@ -3,6 +3,7 @@ * * @since 2.0.0 */ +import { isValidSuiAddress, normalizeSuiAddress } from "@mysten/sui/utils" import { bech32, bytes } from "@scure/base" import { absurd, @@ -18,19 +19,16 @@ import { import { isAddress, toHex } from "viem" import { AddressCanonicalBytes } from "./schema/address.js" import { Hex, HexFromString } from "./schema/hex.js" -import { isValidSuiAddress, normalizeSuiAddress } from "@mysten/sui/utils" // const AddressFromChain = (chain: Chain) => - /** * @category models * @since 2.0.0 */ export const SuiAddress = S.NonEmptyString.pipe( S.filter((a) => isValidSuiAddress(a), { - description: - "Sui address (32-byte hex). Accepts with/without 0x; even length; hex only.", + description: "Sui address (32-byte hex). Accepts with/without 0x; even length; hex only.", }), ) /** @@ -262,7 +260,7 @@ export const AnyDisplayFromString = S.transformOrFail( Effect.raceAll([ S.decodeUnknownEither(EvmDisplay)({ _tag: "EvmDisplay", address }), S.decodeUnknownEither(CosmosDisplay)({ _tag: "CosmosDisplay", address }), - S.decodeUnknownEither(SuiDisplay)({ _tag: "SuiDisplay", address }), + S.decodeUnknownEither(SuiDisplay)({ _tag: "SuiDisplay", address }), ]), Effect.catchTag("ParseError", (error) => ParseResult.fail(error.issue)), ), @@ -300,7 +298,7 @@ export const ZkgmFromAnyDisplay = S.transform( Match.tagsExhaustive({ CosmosDisplay: ({ address }) => toHex(address), EvmDisplay: ({ address }) => identity(address), - SuiDisplay: ({ address }) => identity(normalizeSuiAddress(address) as Hex) + SuiDisplay: ({ address }) => identity(normalizeSuiAddress(address) as Hex), }), ), encode: (_) => absurd(void 0 as never), @@ -315,7 +313,7 @@ export const anyDisplayToZkgm = Match.type().pipe( Match.tagsExhaustive({ CosmosDisplay: ({ address }) => S.decode(HexFromString)(address), EvmDisplay: ({ address }) => Effect.succeed(address), - SuiDisplay: ({ address }) => S.decode(HexFromString)(normalizeSuiAddress(address)), + SuiDisplay: ({ address }) => S.decode(HexFromString)(normalizeSuiAddress(address)), }), ) @@ -334,7 +332,7 @@ export const anyDisplayToCanonical = Match.type().pipe( console.log("bytes", { result }) }, EvmDisplay: ({ address }) => AddressCanonicalBytes.make(address), - SuiDisplay: ({ address }) => AddressCanonicalBytes.make(normalizeSuiAddress(address)) + SuiDisplay: ({ address }) => AddressCanonicalBytes.make(normalizeSuiAddress(address)), }), ) /** From 00459afec91a7cbac5c93cb51c65060ebd7e52cb Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 1 Oct 2025 11:00:28 +0300 Subject: [PATCH 18/24] chore(ts-sdk-sui): fixed ts-sdk-evm error Signed-off-by: kaancaglan --- ts-sdk/src/Ucs05.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts-sdk/src/Ucs05.ts b/ts-sdk/src/Ucs05.ts index 62101b1029..9b5ed961ab 100644 --- a/ts-sdk/src/Ucs05.ts +++ b/ts-sdk/src/Ucs05.ts @@ -332,7 +332,8 @@ export const anyDisplayToCanonical = Match.type().pipe( console.log("bytes", { result }) }, EvmDisplay: ({ address }) => AddressCanonicalBytes.make(address), - SuiDisplay: ({ address }) => AddressCanonicalBytes.make(normalizeSuiAddress(address)), + SuiDisplay: ({ address }) => AddressCanonicalBytes.make(normalizeSuiAddress(address) as Hex), + }), ) /** From c80d88f2f799ca38c465c65702739b272edb71aa Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 1 Oct 2025 11:24:57 +0300 Subject: [PATCH 19/24] chore(ts-sdk-sui): added sui to generateSalt type Signed-off-by: kaancaglan --- ts-sdk/src/Utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts-sdk/src/Utils.ts b/ts-sdk/src/Utils.ts index 3b30b478b6..112ed22858 100644 --- a/ts-sdk/src/Utils.ts +++ b/ts-sdk/src/Utils.ts @@ -13,7 +13,7 @@ import { fromBytes, fromHex, isHex, toHex } from "viem" const CHKSUM_LEN = 4 -type RpcType = "evm" | "cosmos" | "aptos" +type RpcType = "evm" | "cosmos" | "aptos" | "sui" /** * @category errors From 7caace9f9e00e7341a8e74bf7e1bf8455bbdcfb2 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Wed, 1 Oct 2025 16:58:53 +0300 Subject: [PATCH 20/24] chore(ts-sdk-sui): added transport interface for extra params Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 25 +++++- ts-sdk-sui/src/internal/zkgmClient.ts | 86 +++++++++++-------- ts-sdk/src/ZkgmClientRequest.ts | 38 ++++++++ ts-sdk/src/internal/zkgmClientRequest.ts | 30 +++++++ 4 files changed, 142 insertions(+), 37 deletions(-) diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts index 4ef2bd1c11..ebbb95bb9a 100644 --- a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -30,7 +30,7 @@ import * as TokenOrder from "@unionlabs/sdk/TokenOrder" const MNEMONIC = process.env.SUI_MNEMONIC ?? "..." const RECIPIENT = process.env.RECIPIENT - ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" + ?? "union1wycy8g8v5sff6gsjl9yhjs43q98xpl05p3gn2s" const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC) @@ -56,7 +56,7 @@ const program = Effect.gen(function*() { source, destination, sender: sender, - receiver: "union1wycy8g8v5sff6gsjl9yhjs43q98xpl05p3gn2s", + receiver: RECIPIENT, baseToken: "0x2::sui::SUI", baseAmount: 10000000n, quoteToken: "union1y05e0p2jcvhjzf7kcqsrqx93d4g3u93hc2hykaq8hrvkqrp5ltrssagzyd", @@ -71,8 +71,27 @@ const program = Effect.gen(function*() { source, destination, channelId: ChannelId.make(5), - ucs03Address: "union1rfz3ytg6l60wxk5rxsk27jvn2907cyav04sz8kde3xhmmf9nplxqr8y05c", + ucs03Address: "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830", instruction: tokenOrder, + + // NEW — only read by the Sui client + transport: { + sui: { + relayStoreId: + "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924", + vaultId: + "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3", + ibcStoreId: + "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175", + coins: [ + { + typeArg: "0x2::sui::SUI", + objectId: + "0xab70523198047a482febffab381a2a564002459bdcfa98991c747a013b3fd3e4", + }, + ], + }, + }, }) const zkgmClient = yield* ZkgmClient.ZkgmClient diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index 54c7ccb0a5..dd27fca57b 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -1,8 +1,6 @@ import { Transaction } from "@mysten/sui/transactions" -import { Indexer, ZkgmIncomingMessage } from "@unionlabs/sdk" import * as Call from "@unionlabs/sdk/Call" import type { Hex } from "@unionlabs/sdk/schema/hex" -import * as Token from "@unionlabs/sdk/Token" import * as TokenOrder from "@unionlabs/sdk/TokenOrder" import * as Ucs03 from "@unionlabs/sdk/Ucs03" import * as Utils from "@unionlabs/sdk/Utils" @@ -12,17 +10,14 @@ import * as ClientRequest from "@unionlabs/sdk/ZkgmClientRequest" import * as ClientResponse from "@unionlabs/sdk/ZkgmClientResponse" import * as IncomingMessage from "@unionlabs/sdk/ZkgmIncomingMessage" import * as ZkgmInstruction from "@unionlabs/sdk/ZkgmInstruction" -import { Brand, Chunk, flow, Match, ParseResult, pipe, Predicate, Tuple } from "effect" +import { Match, ParseResult, pipe, Predicate } from "effect" import * as A from "effect/Array" import * as Effect from "effect/Effect" import * as Inspectable from "effect/Inspectable" -import * as O from "effect/Option" import * as S from "effect/Schema" import * as Stream from "effect/Stream" -import * as Safe from "../Safe.js" import * as Sui from "../Sui.js" -// import { Sui } from "../index.js" export const fromWallet = ( opts: { client: Sui.Sui.PublicClient; wallet: Sui.Sui.WalletClient }, @@ -84,6 +79,8 @@ export const fromWallet = ( console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { wallet, client }) + + const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow() const salt = yield* Utils.generateSalt("sui").pipe( Effect.mapError((cause) => @@ -117,18 +114,36 @@ export const fromWallet = ( const tHeight = 0n const module = "zkgm" // zkgm module name - // These will be fetched from hubble or from deployments.json - const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" // zkgm package id - // TODO: packageId can be changed when zkgm updated - const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // This won't be changed for a while - const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // This won't be changed for a while - const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // This won't be changed - // This 2 will be get by user all the time - const typeArg = "0x2::sui::SUI" // TODO: This should be dynamic based on the token sent - const coinObjectId = "0x89c430d35fa9f2778b0a635027b178146eb26d70d16292c289304d476ecf76cd" // TODO: This should be given by user - // Note: There can be multiple coins, for simplicity we are using one coin here - // User should be able to provide typeArgs and coinObjectIds array + + const suiParams = request.transport?.sui + console.log("request.transport:", request.transport) + if (!suiParams) { + return yield* Effect.fail( + new ClientError.RequestError({ + reason: "Transport", + request, + cause: new Error("Missing Sui transport params on ZkgmClientRequest.transport.sui"), + description: "Provide relayStoreId/vaultId/ibcStoreId and coins[]", + }), + ) + } + + const { relayStoreId, vaultId, ibcStoreId, coins } = suiParams + + console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { relayStoreId, vaultId, ibcStoreId, coins }) + // // These will be fetched from hubble or from deployments.json + // const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" // zkgm package id + // // TODO: packageId can be changed when zkgm updated + // const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // This won't be changed for a while + // const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // This won't be changed for a while + // const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // This won't be changed + + // // This 2 will be get by user all the time + // const typeArg = "0x2::sui::SUI" // TODO: This should be dynamic based on the token sent + // const coinObjectId = "0x89c430d35fa9f2778b0a635027b178146eb26d70d16292c289304d476ecf76cd" // TODO: This should be given by user + // // Note: There can be multiple coins, for simplicity we are using one coin here + // // User should be able to provide typeArgs and coinObjectIds array const hexToBytes = (hex: `0x${string}`): Uint8Array => { const s = hex.slice(2) @@ -141,7 +156,7 @@ export const fromWallet = ( // 1) begin_send(channel_id: u32, salt: vector) -> SendCtx let sendCtx = tx.moveCall({ - target: `${packageId}::${module}::begin_send`, + target: `${request.ucs03Address}::${module}::begin_send`, typeArguments: [], arguments: [ tx.pure.u32(Number(request.channelId)), @@ -149,25 +164,28 @@ export const fromWallet = ( ], }) - // 2) send_with_coin(relay_store, vault, ibc_store, coin, version, opcode, operand, ctx) -> SendCtx - sendCtx = tx.moveCall({ - target: `${packageId}::${module}::send_with_coin`, - typeArguments: [typeArg], - arguments: [ - tx.object(relayStoreId), - tx.object(vaultId), - tx.object(ibcStoreId), - tx.object(coinObjectId), - tx.pure.u8(Number(request.instruction.version)), - tx.pure.u8(Number(request.instruction.opcode)), - tx.pure.vector("u8", hexToBytes(operand as `0x${string}`)), - sendCtx, - ], - }) + // 2) For each coin: send_with_coin(relay_store, vault, ibc_store, coin, version, opcode, operand, ctx) -> SendCtx + for (const { typeArg, objectId } of coins) { + sendCtx = tx.moveCall({ + target: `${request.ucs03Address}::${module}::send_with_coin`, + typeArguments: [typeArg], + arguments: [ + tx.object(relayStoreId), + tx.object(vaultId), + tx.object(ibcStoreId), + tx.object(objectId), + tx.pure.u8(Number(request.instruction.version)), + tx.pure.u8(Number(request.instruction.opcode)), + tx.pure.vector("u8", hexToBytes(operand as `0x${string}`)), + sendCtx, + ], + }) + } + // 3) end_send(ibc_store, clock, t_height: u64, timeout_ns: u64, ctx) tx.moveCall({ - target: `${packageId}::${module}::end_send`, + target: `${request.ucs03Address}::${module}::end_send`, typeArguments: [], arguments: [ tx.object(ibcStoreId), diff --git a/ts-sdk/src/ZkgmClientRequest.ts b/ts-sdk/src/ZkgmClientRequest.ts index e3a7881b6d..6852673d52 100644 --- a/ts-sdk/src/ZkgmClientRequest.ts +++ b/ts-sdk/src/ZkgmClientRequest.ts @@ -13,6 +13,38 @@ import { ChannelId } from "./schema/channel.js" import type * as Token from "./Token.js" import type * as ZkgmInstruction from "./ZkgmInstruction.js" + +/** @since 2.0.0 */ +export namespace Transport { + + export interface Sui { + readonly relayStoreId: string + readonly vaultId: string + readonly ibcStoreId: string + /** One or more coins a user wants to spend. Keep array for multi-coin support. */ + readonly coins: ReadonlyArray<{ + /** e.g. "0x2::sui::SUI" or a custom coin type */ + readonly typeArg: string + /** Concrete coin object id(s) for spending */ + readonly objectId: string + }> + } + + export interface Evm { + readonly _?: never + } + + export interface Cosmos { + readonly _?: never + } + + export interface Params { + readonly sui?: Sui | undefined + readonly evm?: Evm | undefined + readonly cosmos?: Cosmos | undefined + } +} + /** * @category type ids * @since 2.0.0 @@ -40,6 +72,8 @@ export interface ZkgmClientRequest extends Inspectable, Pipeable { * **NOTE:** only for EVM submission */ readonly kind: "execute" | "simulateAndExecute" + /** NEW: optional, per-runtime parameters (non-breaking) */ + readonly transport?: Transport.Params | undefined } /** @@ -53,6 +87,8 @@ export interface Options { readonly ucs03Address: string // XXX: narrow readonly instruction?: ZkgmInstruction.ZkgmInstruction | undefined readonly kind?: "execute" | "simulateAndExecute" | undefined + /** NEW: optional, per-runtime parameters (non-breaking) */ + readonly transport?: Transport.Params | undefined } /** @@ -66,8 +102,10 @@ export const make: (options: { ucs03Address: string // XXX: narrow instruction: ZkgmInstruction.ZkgmInstruction kind?: "execute" | "simulateAndExecute" | undefined + transport?: Transport.Params | undefined }) => ZkgmClientRequest = internal.make + /** * @category combinators * @since 2.0.0 diff --git a/ts-sdk/src/internal/zkgmClientRequest.ts b/ts-sdk/src/internal/zkgmClientRequest.ts index f90d786e41..47212a517d 100644 --- a/ts-sdk/src/internal/zkgmClientRequest.ts +++ b/ts-sdk/src/internal/zkgmClientRequest.ts @@ -9,6 +9,7 @@ import { Hex } from "../schema/hex.js" import type * as Token from "../Token.js" import type * as ClientRequest from "../ZkgmClientRequest.js" import { ZkgmInstruction } from "../ZkgmInstruction.js" +import type { Transport } from "../ZkgmClientRequest.js" /** @internal */ export const TypeId: ClientRequest.TypeId = Symbol.for( @@ -44,6 +45,7 @@ function makeProto( ucs03Address: string, instruction: ZkgmInstruction, kind: "execute" | "simulateAndExecute", + transport?: Transport.Params | undefined ): ClientRequest.ZkgmClientRequest { const self = Object.create(Proto) self.source = source @@ -52,6 +54,7 @@ function makeProto( self.ucs03Address = ucs03Address self.instruction = instruction self.kind = kind + self.transport = transport return self } @@ -67,6 +70,7 @@ export const empty: ClientRequest.ZkgmClientRequest = makeProto( void 0 as unknown as Hex, void 0 as unknown as ZkgmInstruction, "execute", + undefined, ) /** @internal */ @@ -77,6 +81,7 @@ export const make = (options: { ucs03Address: string instruction: ZkgmInstruction kind?: "execute" | "simulateAndExecute" | undefined + transport?: Transport.Params | undefined }) => modify(empty, options) /** @internal */ @@ -110,6 +115,8 @@ export const modify = dual< result = setKind(result, options.kind) } + if (options.transport) result = setTransport(result, options.transport) + return result }) @@ -127,6 +134,7 @@ export const setSource = dual< self.ucs03Address, self.instruction, self.kind, + self.transport, )) /** @internal */ @@ -143,8 +151,26 @@ export const setDestination = dual< self.ucs03Address, self.instruction, self.kind, + self.transport, )) +export const setTransport = dual< + (transport: Transport.Params) => + (self: ClientRequest.ZkgmClientRequest) => ClientRequest.ZkgmClientRequest, + (self: ClientRequest.ZkgmClientRequest, transport: Transport.Params) => + ClientRequest.ZkgmClientRequest +>(2, (self, transport) => + makeProto( + self.source, + self.destination, + self.channelId, + self.ucs03Address, + self.instruction, + self.kind, + transport, + )) + + /** @internal */ export const setChannelId = dual< ( @@ -159,6 +185,7 @@ export const setChannelId = dual< self.ucs03Address, self.instruction, self.kind, + self.transport, )) /** @internal */ export const setUcs03Address = dual< @@ -174,6 +201,7 @@ export const setUcs03Address = dual< ucs03Address, self.instruction, self.kind, + self.transport, )) /** @internal */ @@ -193,6 +221,7 @@ export const setInstruction = dual< self.ucs03Address, instruction, self.kind, + self.transport, )) /** @internal */ @@ -212,6 +241,7 @@ export const setKind = dual< self.ucs03Address, self.instruction, kind, + self.transport, )) /** @internal */ From 951db4c608c9f123a04aa49d07d94d30b06ffd32 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Fri, 3 Oct 2025 17:04:28 +0300 Subject: [PATCH 21/24] chore(ts-sdk-sui): some cleanup Signed-off-by: kaancaglan --- .../sui-create-client-generate-instruction.ts | 23 +- .../sui-create-client-write-contract.ts | 3 +- ts-sdk-sui/src/Sui.ts | 286 +----------------- ts-sdk-sui/src/SuiZkgmClient.ts | 2 +- ts-sdk-sui/src/index.ts | 21 +- ts-sdk-sui/src/internal/sui.ts | 2 +- ts-sdk-sui/src/internal/zkgmClient.ts | 25 +- ts-sdk/src/Ucs05.ts | 1 - ts-sdk/src/ZkgmClientRequest.ts | 3 - ts-sdk/src/internal/zkgmClientRequest.ts | 24 +- 10 files changed, 41 insertions(+), 349 deletions(-) diff --git a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts index ebbb95bb9a..47ccbd3c10 100644 --- a/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts +++ b/ts-sdk-sui/examples/sui-create-client-generate-instruction.ts @@ -42,7 +42,6 @@ const program = Effect.gen(function*() { console.log("source", source) - // TODO: Destination will be somewhere const destination = yield* ChainRegistry.byUniversalId( UniversalChainId.make("union.union-1"), ) @@ -73,38 +72,30 @@ const program = Effect.gen(function*() { channelId: ChannelId.make(5), ucs03Address: "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830", instruction: tokenOrder, - + // NEW — only read by the Sui client transport: { sui: { - relayStoreId: - "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924", - vaultId: - "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3", - ibcStoreId: - "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175", + relayStoreId: "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924", + vaultId: "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3", + ibcStoreId: "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175", coins: [ { typeArg: "0x2::sui::SUI", - objectId: - "0xab70523198047a482febffab381a2a564002459bdcfa98991c747a013b3fd3e4", + objectId: "0x266d00c4b329111255339c041cc57a1b616cfeddafdae47df8f814002578e95b", }, ], }, }, }) + yield* Effect.log("ZKGM Client Request", request) + const zkgmClient = yield* ZkgmClient.ZkgmClient const response: ZkgmClientResponse.ZkgmClientResponse = yield* zkgmClient.execute(request) yield* Effect.log("Submission Hash", response.txHash) - - const completion = yield* response.waitFor( - ZkgmIncomingMessage.LifecycleEvent.$is("EvmTransactionReceiptComplete"), - ) - - yield* Effect.log("Completion", completion) }).pipe( Effect.provide(layerWithoutWallet), Effect.provide(PublicClient.Live({ url: getFullnodeUrl("testnet") })), diff --git a/ts-sdk-sui/examples/sui-create-client-write-contract.ts b/ts-sdk-sui/examples/sui-create-client-write-contract.ts index 3aa9bf9792..e2cb494c1c 100644 --- a/ts-sdk-sui/examples/sui-create-client-write-contract.ts +++ b/ts-sdk-sui/examples/sui-create-client-write-contract.ts @@ -17,8 +17,7 @@ import { writeContract, } from "../src/Sui.js" -const MNEMONIC = process.env.MNEMONIC - ?? "fix auto gallery heart practice drip joke nice decline lift attend bread" +const MNEMONIC = process.env.SUI_MNEMONIC ?? "..." const RECIPIENT = process.env.RECIPIENT ?? "0x03ff9dd9e093387bdd4432c6a3eb6a1bd5a8f39a530042ac7efe576f18d3232b" diff --git a/ts-sdk-sui/src/Sui.ts b/ts-sdk-sui/src/Sui.ts index 8a5763cb87..798968bece 100644 --- a/ts-sdk-sui/src/Sui.ts +++ b/ts-sdk-sui/src/Sui.ts @@ -1,19 +1,14 @@ /** - * This module handles EVM related functionality. + * This module handles Sui related functionality. * * @since 0.0.0 */ -import { SuiClient, SuiClientOptions } from "@mysten/sui/client" +import { SuiClient } from "@mysten/sui/client" import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519" import { Transaction } from "@mysten/sui/transactions" -import { GAS_DENOMS } from "@unionlabs/sdk/Constants" -import { UniversalChainId } from "@unionlabs/sdk/schema/chain" -import * as Ucs03 from "@unionlabs/sdk/Ucs03" import { extractErrorDetails } from "@unionlabs/sdk/Utils" -import * as Utils from "@unionlabs/sdk/Utils" -import { Context, Data, Effect, flow, Layer, pipe, Schema as S } from "effect" -import { type Address, erc20Abi } from "viem" -import type { Hex } from "viem" +import { Context, Data, Effect, flow, Layer } from "effect" +import { type Address } from "viem" import * as internal from "./internal/sui.js" /** @@ -87,43 +82,10 @@ export namespace Sui { // return result // }) -/** - * @category errors - * @since 0.0.0 - */ -export class WaitForTransactionReceiptError extends Data.TaggedError( - "WaitForTransactionReceiptError", -)<{ - cause: unknown -}> {} - export class ReadCoinError extends Data.TaggedError("ReadCoinError")<{ cause: unknown }> {} -// /** -// * Wait for a transaction receipt -// * @param hash The transaction hash for which to wait -// * @returns An Effect that resolves to the transaction receipt -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const waitForTransactionReceipt = (hash: Hash) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// const receipt = yield* Effect.tryPromise({ -// try: () => client.waitForTransactionReceipt({ hash }), -// catch: err => -// new WaitForTransactionReceiptError({ -// cause: Utils.extractErrorDetails(err as WaitForTransactionReceiptTimeoutErrorType), -// }), -// }) - -// return receipt -// }) - export const readContract = ( client: SuiClient, sender: string, @@ -604,243 +566,3 @@ export const readCoinSymbol = (address: string) => }) return symbol }) - -/** - * PTB: begin_send -> (send_with_coin ...)* -> end_send - * - * Mirrors: - * sui client ptb \ - * --move-call "$PKG::$MOD::begin_send" $CHANNEL_ID $SALT \ - * --assign send_ctx1 \ - * --move-call "$PKG::$MOD::send_with_coin" "<$TYPE_T>" \ - * $RELAY_STORE $VAULT $IBC_STORE $COIN $VERSION $OPCODE $OPERAND send_ctx1 \ - * --assign send_ctx2 \ - * --move-call "$PKG::$MOD::end_send" $IBC_STORE $CLOCK $T_HEIGHT $T_TS send_ctx2 \ - * --gas-budget 150000000 - */ -export const sendInstruction = (params: { - packageId: string - - /** coin type, e.g. "0x2::sui::SUI" (used in typeArguments of send_with_coin) */ - typeArg: string - - relayStoreId: string // $RELAY_STORE - vaultId: string // $VAULT - ibcStoreId: string // $IBC_STORE - coinObjectId: string // $COIN - - // extraSendCalls?: Array<{ - // relayStoreId?: string - // vaultId?: string - // ibcStoreId?: string - // coinObjectId?: string - // version?: number - // opcode?: number - // operandHex?: `0x${string}` - // typeArg?: string - // }> - - instruction: Ucs03.Instruction -}) => - Effect.gen(function*() { - const module = "zkgm" - const clockObjectId = "0x6" // Sui system clock object - - const { client, signer } = yield* WalletClient - const channelId = (yield* ChannelSource).channelId - - const salt = yield* Utils.generateSalt("evm") // TODO: check if evm will work here or not - const timeoutNs = Utils.getTimeoutInNanoseconds24HoursFromNow() - const tHeight = BigInt(0) - - const operandHex = - (yield* S.encode(Ucs03.InstructionFromHex)(params.instruction)) as `0x${string}` - - // helpers - const hexToBytes = (hex: `0x${string}`): Uint8Array => { - const s = hex.slice(2) - const out = new Uint8Array(s.length / 2) - for (let i = 0; i < out.length; i++) { - out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16) - } - return out - } - - const tx = new Transaction() - // if (params.gasBudget !== undefined) tx.setGasBudget(BigInt(params.gasBudget as any)) - // adress: 0x1234 - let sendCtx = tx.moveCall({ - target: `${params.packageId}::${module}::begin_send`, - typeArguments: [], - arguments: [ - tx.pure.u32(channelId), - tx.pure.vector("u8", hexToBytes(salt as `0x${string}`)), - ], - }) - - const pushSendWithCoin = (cfg: { - relayStoreId: string - vaultId: string - ibcStoreId: string - coinObjectId: string - version: number - opcode: number - operandHex: `0x${string}` - typeArg: string - }) => { - sendCtx = tx.moveCall({ - target: `${params.packageId}::${module}::send_with_coin`, - typeArguments: [cfg.typeArg], - arguments: [ - tx.object(cfg.relayStoreId), - tx.object(cfg.vaultId), - tx.object(cfg.ibcStoreId), - tx.object(cfg.coinObjectId), - tx.pure.u8(cfg.version), - tx.pure.u8(cfg.opcode), - tx.pure.vector("u8", hexToBytes(cfg.operandHex)), - sendCtx, - ], - }) - } - - pushSendWithCoin({ - relayStoreId: params.relayStoreId, - vaultId: params.vaultId, - ibcStoreId: params.ibcStoreId, - coinObjectId: params.coinObjectId, - version: params.instruction.version, - opcode: params.instruction.opcode, - operandHex, - typeArg: params.typeArg, - }) - - // TODO: multiple send_with_coin calls if needed??? will this work? - // for (const extra of params.extraSendCalls ?? []) { - // pushSendWithCoin({ - // relayStoreId: extra.relayStoreId ?? params.relayStoreId, - // vaultId: extra.vaultId ?? params.vaultId, - // ibcStoreId: extra.ibcStoreId ?? params.ibcStoreId, - // coinObjectId: extra.coinObjectId ?? params.coinObjectId, - // version: extra.version ?? params.version, - // opcode: extra.opcode ?? params.opcode, - // operandHex: (extra.operandHex ?? operandHex) as `0x${string}`, - // typeArg: extra.typeArg ?? params.typeArg, - // }) - // } - - tx.moveCall({ - target: `${params.packageId}::${module}::end_send`, - typeArguments: [], - arguments: [ - tx.object(params.ibcStoreId), - tx.object(clockObjectId), - tx.pure.u64(tHeight), - tx.pure.u64(BigInt(timeoutNs)), // ns - sendCtx, - ], - }) - - const res = yield* Effect.tryPromise({ - try: async () => - client.signAndExecuteTransaction({ - signer, - transaction: tx, - }), - catch: (e) => new WriteContractError({ cause: extractErrorDetails(e as Error) }), - }) - - return res - }) - -// turn a hex string like "0xdeadbeef" into a number[] of bytes -function hexToBytes(hex: string): number[] { - const h = hex.startsWith("0x") ? hex.slice(2) : hex - return h.match(/.{1,2}/g)!.map(b => parseInt(b, 16)) -} - -// // TODO: Decide the parameters here. -// /** -// * @category utils -// * @since 0.0.0 -// */ -// export const sendInstruction = (instruction: Ucs03.Instruction) => -// Effect.gen(function*() { -// const walletClient = yield* WalletClient -// const sourceConfig = yield* ChannelSource - -// const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow() -// const salt = yield* Utils.generateSalt("evm") - -// const operand = yield* S.encode(Ucs03.InstructionFromHex)(instruction) - -// return yield* writeContract({ -// client: walletClient.client, -// signer: walletClient.signer, -// account: walletClient.account, -// abi: Ucs03.Abi, -// // chain: walletClient.chain, TODO: Do we need this? -// fn: "send", -// address: sourceConfig.ucs03address, -// args: [ -// sourceConfig.channelId, -// 0n, -// timeoutTimestamp, -// salt, -// { -// opcode: instruction.opcode, -// version: instruction.version, -// operand, -// }, -// ], -// value: 10n, -// }) -// }) - -// /** -// * Read the balance of an ERC20 token for a specific address -// * @param tokenAddress The address of the ERC20 token -// * @param ownerAddress The address to check the balance for -// * @param blockNumber The blockNumber at certain point -// * @returns An Effect that resolves to the token balance -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const readErc20BalanceAtBlock = ( -// tokenAddress: Address, -// ownerAddress: Address, -// blockNumber: bigint, -// ) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "balanceOf", -// args: [ownerAddress], -// blockNumber: blockNumber, -// }) -// }) - -// /** -// * Read the TotalSupply of an ERC20 token -// * @param tokenAddress The address of the ERC20 token -// * @param blockNumber The blockNumber at certain point -// * @returns An Effect that resolves to the totalSupply -// * -// * @category utils -// * @since 0.0.0 -// */ -// export const readErc20TotalSupplyAtBlock = (tokenAddress: Address, blockNumber: bigint) => -// Effect.gen(function*() { -// const client = (yield* PublicClient).client - -// return yield* readContract(client, { -// address: tokenAddress, -// abi: erc20Abi, -// functionName: "totalSupply", -// blockNumber: blockNumber, -// }) -// }) diff --git a/ts-sdk-sui/src/SuiZkgmClient.ts b/ts-sdk-sui/src/SuiZkgmClient.ts index cf1d222ef6..fe050f2c7b 100644 --- a/ts-sdk-sui/src/SuiZkgmClient.ts +++ b/ts-sdk-sui/src/SuiZkgmClient.ts @@ -1,5 +1,5 @@ /** - * This module defines a concrete {@link ZkgmClient} for EVM source chain usage. + * This module defines a concrete {@link ZkgmClient} for Sui source chain usage. * * @since 0.0.0 */ diff --git a/ts-sdk-sui/src/index.ts b/ts-sdk-sui/src/index.ts index 99ae07db21..6450939d4c 100644 --- a/ts-sdk-sui/src/index.ts +++ b/ts-sdk-sui/src/index.ts @@ -1,20 +1,13 @@ /** - * This module handles EVM related functionality. + * This module handles SUI related functionality. * * @since 0.0.0 */ export * as Sui from "./Sui.js" -// /** -// * This module defines a concrete {@link ZkgmClient} for Sui source chain usage. -// * -// * @since 0.0.0 -// */ -// export * as EvmZkgmClient from "./EvmZkgmClient.js" - -// /** -// * This module allows usage of Safe wallet. -// * -// * @since 0.0.0 -// */ -// export * as Safe from "./Safe.js" +/** + * This module defines a concrete {@link ZkgmClient} for Sui source chain usage. + * + * @since 0.0.0 + */ +export * as SuiZkgmClient from "./SuiZkgmClient.js" diff --git a/ts-sdk-sui/src/internal/sui.ts b/ts-sdk-sui/src/internal/sui.ts index a1d1b5a693..49aaab16b6 100644 --- a/ts-sdk-sui/src/internal/sui.ts +++ b/ts-sdk-sui/src/internal/sui.ts @@ -42,7 +42,7 @@ export const walletClientLayer = ( throw new Error("Invalid Sui signer: expected Ed25519Keypair") } const client = new SuiClient({ url: opts.url } satisfies SuiClientOptions) - return { client, signer: opts.signer } // <-- matches Sui.Sui.WalletClient interface + return { client, signer: opts.signer } }, catch: (err) => new Sui.CreateWalletClientError({ diff --git a/ts-sdk-sui/src/internal/zkgmClient.ts b/ts-sdk-sui/src/internal/zkgmClient.ts index dd27fca57b..dbafa0a4fd 100644 --- a/ts-sdk-sui/src/internal/zkgmClient.ts +++ b/ts-sdk-sui/src/internal/zkgmClient.ts @@ -18,7 +18,6 @@ import * as S from "effect/Schema" import * as Stream from "effect/Stream" import * as Sui from "../Sui.js" - export const fromWallet = ( opts: { client: Sui.Sui.PublicClient; wallet: Sui.Sui.WalletClient }, ): Client.ZkgmClient => @@ -79,8 +78,6 @@ export const fromWallet = ( console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { wallet, client }) - - const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow() const salt = yield* Utils.generateSalt("sui").pipe( Effect.mapError((cause) => @@ -114,8 +111,6 @@ export const fromWallet = ( const tHeight = 0n const module = "zkgm" // zkgm module name - - const suiParams = request.transport?.sui console.log("request.transport:", request.transport) if (!suiParams) { @@ -131,19 +126,12 @@ export const fromWallet = ( const { relayStoreId, vaultId, ibcStoreId, coins } = suiParams - console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { relayStoreId, vaultId, ibcStoreId, coins }) - // // These will be fetched from hubble or from deployments.json - // const packageId = "0x8675045186976da5b60baf20dc94413fb5415a7054052dc14d93c13d3dbdf830" // zkgm package id - // // TODO: packageId can be changed when zkgm updated - // const relayStoreId = "0x393a99c6d55d9a79efa52dea6ea253fef25d2526787127290b985222cc20a924" // This won't be changed for a while - // const vaultId = "0x7c4ade19208295ed6bf3c4b58487aa4b917ba87d31460e9e7a917f7f12207ca3" // This won't be changed for a while - // const ibcStoreId = "0xac7814eebdfbf975235bbb796e07533718a9d83201346769e5f281dc90009175" // This won't be changed - - // // This 2 will be get by user all the time - // const typeArg = "0x2::sui::SUI" // TODO: This should be dynamic based on the token sent - // const coinObjectId = "0x89c430d35fa9f2778b0a635027b178146eb26d70d16292c289304d476ecf76cd" // TODO: This should be given by user - // // Note: There can be multiple coins, for simplicity we are using one coin here - // // User should be able to provide typeArgs and coinObjectIds array + console.log("[@unionlabs/sdk-sui/internal/zkgmClient]", { + relayStoreId, + vaultId, + ibcStoreId, + coins, + }) const hexToBytes = (hex: `0x${string}`): Uint8Array => { const s = hex.slice(2) @@ -182,7 +170,6 @@ export const fromWallet = ( }) } - // 3) end_send(ibc_store, clock, t_height: u64, timeout_ns: u64, ctx) tx.moveCall({ target: `${request.ucs03Address}::${module}::end_send`, diff --git a/ts-sdk/src/Ucs05.ts b/ts-sdk/src/Ucs05.ts index 9b5ed961ab..7c9d4ac200 100644 --- a/ts-sdk/src/Ucs05.ts +++ b/ts-sdk/src/Ucs05.ts @@ -333,7 +333,6 @@ export const anyDisplayToCanonical = Match.type().pipe( }, EvmDisplay: ({ address }) => AddressCanonicalBytes.make(address), SuiDisplay: ({ address }) => AddressCanonicalBytes.make(normalizeSuiAddress(address) as Hex), - }), ) /** diff --git a/ts-sdk/src/ZkgmClientRequest.ts b/ts-sdk/src/ZkgmClientRequest.ts index 6852673d52..2858e1c160 100644 --- a/ts-sdk/src/ZkgmClientRequest.ts +++ b/ts-sdk/src/ZkgmClientRequest.ts @@ -13,10 +13,8 @@ import { ChannelId } from "./schema/channel.js" import type * as Token from "./Token.js" import type * as ZkgmInstruction from "./ZkgmInstruction.js" - /** @since 2.0.0 */ export namespace Transport { - export interface Sui { readonly relayStoreId: string readonly vaultId: string @@ -105,7 +103,6 @@ export const make: (options: { transport?: Transport.Params | undefined }) => ZkgmClientRequest = internal.make - /** * @category combinators * @since 2.0.0 diff --git a/ts-sdk/src/internal/zkgmClientRequest.ts b/ts-sdk/src/internal/zkgmClientRequest.ts index 47212a517d..2bc2af0e60 100644 --- a/ts-sdk/src/internal/zkgmClientRequest.ts +++ b/ts-sdk/src/internal/zkgmClientRequest.ts @@ -8,8 +8,8 @@ import { ChannelId } from "../schema/channel.js" import { Hex } from "../schema/hex.js" import type * as Token from "../Token.js" import type * as ClientRequest from "../ZkgmClientRequest.js" -import { ZkgmInstruction } from "../ZkgmInstruction.js" import type { Transport } from "../ZkgmClientRequest.js" +import { ZkgmInstruction } from "../ZkgmInstruction.js" /** @internal */ export const TypeId: ClientRequest.TypeId = Symbol.for( @@ -45,7 +45,7 @@ function makeProto( ucs03Address: string, instruction: ZkgmInstruction, kind: "execute" | "simulateAndExecute", - transport?: Transport.Params | undefined + transport?: Transport.Params | undefined, ): ClientRequest.ZkgmClientRequest { const self = Object.create(Proto) self.source = source @@ -54,7 +54,7 @@ function makeProto( self.ucs03Address = ucs03Address self.instruction = instruction self.kind = kind - self.transport = transport + self.transport = transport return self } @@ -115,7 +115,9 @@ export const modify = dual< result = setKind(result, options.kind) } - if (options.transport) result = setTransport(result, options.transport) + if (options.transport) { + result = setTransport(result, options.transport) + } return result }) @@ -155,10 +157,13 @@ export const setDestination = dual< )) export const setTransport = dual< - (transport: Transport.Params) => - (self: ClientRequest.ZkgmClientRequest) => ClientRequest.ZkgmClientRequest, - (self: ClientRequest.ZkgmClientRequest, transport: Transport.Params) => - ClientRequest.ZkgmClientRequest + ( + transport: Transport.Params, + ) => (self: ClientRequest.ZkgmClientRequest) => ClientRequest.ZkgmClientRequest, + ( + self: ClientRequest.ZkgmClientRequest, + transport: Transport.Params, + ) => ClientRequest.ZkgmClientRequest >(2, (self, transport) => makeProto( self.source, @@ -167,10 +172,9 @@ export const setTransport = dual< self.ucs03Address, self.instruction, self.kind, - transport, + transport, )) - /** @internal */ export const setChannelId = dual< ( From 08f1ee33b613f5615c146129237ac2ca3a82ab3c Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Mon, 6 Oct 2025 12:31:45 +0300 Subject: [PATCH 22/24] chore(ts-sdk-sui): fixing ci errors Signed-off-by: kaancaglan --- pnpm-workspace.yaml | 2 +- ts-sdk-sui/src/internal/sui.ts | 3 --- ts-sdk/src/Token.ts | 7 +++++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ee1397bbc9..436cc84279 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,4 +9,4 @@ packages: - 'ts-sdk-evm' - 'typescript-sdk' - 'zkgm-dev' - - 'ts-sdk-sui' \ No newline at end of file + - 'ts-sdk-sui' diff --git a/ts-sdk-sui/src/internal/sui.ts b/ts-sdk-sui/src/internal/sui.ts index 49aaab16b6..a41b8aec88 100644 --- a/ts-sdk-sui/src/internal/sui.ts +++ b/ts-sdk-sui/src/internal/sui.ts @@ -10,7 +10,6 @@ export const publicClientLayer = < Id, >(tag: Context.Tag) => ( - // interface unchanged (variadic) ...options: Parameters ): Layer.Layer => Layer.effect( @@ -20,11 +19,9 @@ export const publicClientLayer = < try: () => new SuiClient(options[0] as SuiClientOptions), catch: (err) => new Sui.CreatePublicClientError({ - // mirror your Sui error-detail extraction cause: Utils.extractErrorDetails(err as Sui.CreatePublicClientError), }), }), - // keep the { client } shape Effect.map((client) => ({ client })), ), ) diff --git a/ts-sdk/src/Token.ts b/ts-sdk/src/Token.ts index 08b07eab04..69957dd80b 100644 --- a/ts-sdk/src/Token.ts +++ b/ts-sdk/src/Token.ts @@ -239,11 +239,18 @@ export const AnyFromEncoded = (rpcType: Chain.RpcType) => }, ) +/** + * @since 2.0.0 + */ export const normalizeSuiTypeTag = (t: string): string => { const [addr, mod, name] = t.split("::") return `${normalizeSuiAddress(addr)}::${mod}::${name}` } + +/** + * @since 2.0.0 + */ const isNativeSui = (t: string): boolean => { // compare on normalized address to avoid short/long mismatch const norm = normalizeSuiTypeTag(t) From 11b4efbbfcccf417806c0a8441f070091fc22a1a Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Mon, 6 Oct 2025 12:48:18 +0300 Subject: [PATCH 23/24] chore(ts-sdk-sui): fixing ci errors Signed-off-by: kaancaglan --- .gitignore | 3 +++ app2/src/lib/stores/wallets.svelte.ts | 4 ++++ .../lib/transfer/shared/services/filling/check-allowance.ts | 4 ++++ flake.nix | 1 + ts-sdk/src/Token.ts | 1 - 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3c65d89b36..58b3021b03 100644 --- a/.gitignore +++ b/.gitignore @@ -113,6 +113,9 @@ ts-sdk-evm/docs ts-sdk-cosmos/build ts-sdk-cosmos/dist ts-sdk-cosmos/docs +ts-sdk-sui/build +ts-sdk-sui/dist +ts-sdk-sui/docs sentinel2/build docs/src/content/docs/reference/@unionlabs/sdk docs/src/content/docs/reference/@unionlabs/sdk-evm diff --git a/app2/src/lib/stores/wallets.svelte.ts b/app2/src/lib/stores/wallets.svelte.ts index 01e2f3775f..d741b9289a 100644 --- a/app2/src/lib/stores/wallets.svelte.ts +++ b/app2/src/lib/stores/wallets.svelte.ts @@ -7,6 +7,7 @@ class WalletsStore { evmAddress: Option.Option = $state(Option.none()) cosmosAddress: Option.Option = $state(Option.none()) aptosAddress: Option.Option = $state(Option.none()) + suiAddress: Option.Option = $state(Option.none()) inputAddress: Option.Option = $state(Option.none()) hasAnyWallet() { @@ -14,6 +15,7 @@ class WalletsStore { Option.isSome(this.evmAddress) || Option.isSome(this.cosmosAddress) || Option.isSome(this.aptosAddress) + || Option.isSome(this.suiAddress) || Option.isSome(this.inputAddress) ) } @@ -34,6 +36,7 @@ class WalletsStore { this.evmAddress, this.cosmosAddress, this.aptosAddress, + this.suiAddress, ]), A.map(Ucs05.anyDisplayToCanonical), ) @@ -52,6 +55,7 @@ class WalletsStore { Option.map((address) => Ucs05.CosmosDisplay.make({ address })), )), Match.when("aptos", () => this.aptosAddress), + Match.when("sui", () => this.suiAddress), Match.exhaustive, ) } diff --git a/app2/src/lib/transfer/shared/services/filling/check-allowance.ts b/app2/src/lib/transfer/shared/services/filling/check-allowance.ts index e9cb57e0b2..1d5bd40a2b 100644 --- a/app2/src/lib/transfer/shared/services/filling/check-allowance.ts +++ b/app2/src/lib/transfer/shared/services/filling/check-allowance.ts @@ -52,6 +52,10 @@ export const checkAllowances = Effect.fn(( sender, chain, ), + SuiDisplay: (sender) => + Effect.fail( + new AllowanceCheckError({ message: "Sui allowance check not implemented" }), + ), }), Effect.map(A.map(({ token, allowance }) => [token, allowance] as const)), Effect.map(HashMap.fromIterable), diff --git a/flake.nix b/flake.nix index 49e4ab5ffa..73cb7b4b22 100644 --- a/flake.nix +++ b/flake.nix @@ -197,6 +197,7 @@ ./ts-sdk/ts-sdk.nix ./ts-sdk-evm/ts-sdk-evm.nix ./ts-sdk-cosmos/ts-sdk-cosmos.nix + ./ts-sdk-sui/ts-sdk-sui.nix ./typescript-sdk/typescript-sdk.nix ./cosmwasm/cosmwasm.nix ./evm/evm.nix diff --git a/ts-sdk/src/Token.ts b/ts-sdk/src/Token.ts index 69957dd80b..71ee8b73c0 100644 --- a/ts-sdk/src/Token.ts +++ b/ts-sdk/src/Token.ts @@ -247,7 +247,6 @@ export const normalizeSuiTypeTag = (t: string): string => { return `${normalizeSuiAddress(addr)}::${mod}::${name}` } - /** * @since 2.0.0 */ From de77b97d42553fe6bd9039df88cc0d862d6a0756 Mon Sep 17 00:00:00 2001 From: kaancaglan Date: Mon, 6 Oct 2025 12:54:15 +0300 Subject: [PATCH 24/24] chore(ts-sdk-sui): fixing ci errors Signed-off-by: kaancaglan --- ts-sdk/src/Token.ts | 1 + ts-sdk/src/ZkgmClientRequest.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/ts-sdk/src/Token.ts b/ts-sdk/src/Token.ts index 71ee8b73c0..29a82b69cb 100644 --- a/ts-sdk/src/Token.ts +++ b/ts-sdk/src/Token.ts @@ -42,6 +42,7 @@ export class SuiCoin extends S.TaggedClass()("SuiCoin", { }), ), }) { + /** @since 2.0.0 */ [Hash.symbol](): number { return Hash.string(this.address) } diff --git a/ts-sdk/src/ZkgmClientRequest.ts b/ts-sdk/src/ZkgmClientRequest.ts index 2858e1c160..56fa2b06b6 100644 --- a/ts-sdk/src/ZkgmClientRequest.ts +++ b/ts-sdk/src/ZkgmClientRequest.ts @@ -15,6 +15,10 @@ import type * as ZkgmInstruction from "./ZkgmInstruction.js" /** @since 2.0.0 */ export namespace Transport { + /** + * Sui client request params. + * @since 2.0.0 + */ export interface Sui { readonly relayStoreId: string readonly vaultId: string @@ -28,14 +32,26 @@ export namespace Transport { }> } + /** + * EVM client request params. + * @since 2.0.0 + */ export interface Evm { readonly _?: never } + /** + * Cosmos client request params. + * @since 2.0.0 + */ export interface Cosmos { readonly _?: never } + /** + * Common request params. + * @since 2.0.0 + */ export interface Params { readonly sui?: Sui | undefined readonly evm?: Evm | undefined