diff --git a/app2/examples/bond.ts b/app2/examples/bond.ts new file mode 100644 index 0000000000..8be13b5fe9 --- /dev/null +++ b/app2/examples/bond.ts @@ -0,0 +1,233 @@ +/** + * ETH -> UNION + * + * Execute from CLI with `KEY="0xprivatekey" pnpm dlx vite-node ./path/to/bond.ts` + */ +// @ts-ignore +if (typeof BigInt.prototype.toJSON !== "function") { + // @ts-ignore + BigInt.prototype.toJSON = function() { + return this.toString() + } +} +import { + Batch, + Call, + Token, + TokenOrder, + Ucs03, + Ucs05, + Utils, + ZkgmClient, + ZkgmClientRequest, + ZkgmIncomingMessage, +} from "@unionlabs/sdk" +import { Evm, EvmZkgmClient } from "@unionlabs/sdk-evm" +import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry" +import { + EU_ERC20, + EU_LST, + EU_SOLVER_METADATA, + ON_ZKGM_CALL_PROXY, + U_BANK, + U_ERC20, + U_SOLVER_METADATA, +} from "@unionlabs/sdk/Constants" +import { UniversalChainId } from "@unionlabs/sdk/schema/chain" +import { ChannelId } from "@unionlabs/sdk/schema/channel" +import { HexFromJson } from "@unionlabs/sdk/schema/hex" +import { Effect, Logger, pipe, Schema } from "effect" +import { http } from "viem" +import { privateKeyToAccount } from "viem/accounts" +import { holesky } from "viem/chains" + +const JsonFromBase64 = Schema.compose( + Schema.StringFromBase64, + Schema.parseJson(), +) + +const ETHEREUM_CHAIN_ID = UniversalChainId.make("ethereum.17000") +const UNION_CHAIN_ID = UniversalChainId.make("union.union-testnet-10") +const SOURCE_CHANNEL_ID = ChannelId.make(1) // FIXME +const UCS03_MINTER = Ucs05.EvmDisplay.make({ + address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03", +}) +const MIN_MINT_AMOUNT = 1n +const VIEM_CHAIN = holesky +const RPC_URL = "https://rpc.17000.ethereum.chain.kitchen" +const SENDER = Ucs05.EvmDisplay.make({ + address: "0x06627714f3F17a701f7074a12C02847a5D2Ca487", +}) +const EU_STAKING_HUB = Ucs05.CosmosDisplay.make({ + address: "union1eueueueu9var4yhdruyzkjcsh74xzeug6ckyy60hs0vcqnzql2hq0lxc2f", // FIXME +}) + +const VIEM_ACCOUNT = privateKeyToAccount( + process.env.KEY as any, +) + +const sendBond = Effect.gen(function*() { + const ethereumChain = yield* ChainRegistry.byUniversalId(ETHEREUM_CHAIN_ID) + const unionChain = yield* ChainRegistry.byUniversalId(UNION_CHAIN_ID) + + const eu_staking_hub = yield* Ucs05.anyDisplayToZkgm(EU_STAKING_HUB) + const on_zkgm_call_proxy = yield* Ucs05.anyDisplayToZkgm(ON_ZKGM_CALL_PROXY) + const ucs03_minter = yield* Ucs05.anyDisplayToZkgm(UCS03_MINTER) + const eu_lst = yield* Ucs05.anyDisplayToZkgm(EU_LST) + + const tokenOrder = yield* TokenOrder.make({ + source: ethereumChain, + destination: unionChain, + sender: SENDER, + receiver: ON_ZKGM_CALL_PROXY, + baseToken: U_ERC20, + baseAmount: 1n, + quoteToken: U_BANK, + quoteAmount: 1n, + kind: "solve", + metadata: U_SOLVER_METADATA, + version: 2, + }) + + const bondCall = yield* pipe( + { + mint_to: on_zkgm_call_proxy, + min_mint_amount: MIN_MINT_AMOUNT, + } as const, + Schema.encode(JsonFromBase64), + Effect.map((msg) => ({ + contract: eu_staking_hub, + msg, + funds: [{ + denom: tokenOrder.quoteToken.address, + amount: tokenOrder.quoteAmount, + }], + call_action: "call_proxy", + } as const)), + Effect.flatMap(Schema.decode(HexFromJson)), + Effect.map((contractCalldata) => + Call.make({ + sender: SENDER, + eureka: false, + contractAddress: ON_ZKGM_CALL_PROXY, + contractCalldata, + }) + ), + ) + + const increaseAllowanceCall = yield* pipe( + { + spender: ucs03_minter, + amount: MIN_MINT_AMOUNT, + } as const, + Schema.encode(JsonFromBase64), + Effect.map((msg) => ({ + contract: eu_lst, + msg, + funds: [], + call_action: "direct", + } as const)), + Effect.flatMap(Schema.decode(HexFromJson)), + Effect.map((contractCalldata) => + Call.make({ + sender: SENDER, + eureka: false, + contractAddress: ON_ZKGM_CALL_PROXY, + contractCalldata, + }) + ), + ) + + const salt = yield* Utils.generateSalt("cosmos") + + const sendCall = yield* pipe( + TokenOrder.make({ + source: unionChain, + destination: ethereumChain, + sender: ON_ZKGM_CALL_PROXY, // FIXME: foundation multisig + receiver: SENDER, + baseToken: Token.Cw20.make({ address: EU_LST.address }), + baseAmount: MIN_MINT_AMOUNT, + quoteToken: EU_ERC20, + quoteAmount: MIN_MINT_AMOUNT, + kind: "solve", + metadata: EU_SOLVER_METADATA, + version: 2, + }), + Effect.flatMap(TokenOrder.encodeV2), + Effect.flatMap(Schema.encode(Ucs03.Ucs03WithInstructionFromHex)), + Effect.tap((instr) => Effect.log("instr:", instr)), + Effect.map((instruction) => ({ + path: 0n, + channel_id: 19, + salt, + instruction, + } as const)), + Effect.flatMap(Schema.encode(JsonFromBase64)), + Effect.map((msg) => ({ + contract: ucs03_minter, + msg, + funds: [], + call_action: "direct", + } as const)), + Effect.flatMap(Schema.decode(HexFromJson)), + Effect.map((contractCalldata) => + Call.make({ + sender: SENDER, + eureka: false, + contractAddress: ON_ZKGM_CALL_PROXY, + contractCalldata, + }) + ), + ) + + const batch = Batch.make([ + tokenOrder, + bondCall, + increaseAllowanceCall, + sendCall, + ]) + + const request = ZkgmClientRequest.make({ + source: ethereumChain, + destination: unionChain, + channelId: SOURCE_CHANNEL_ID, + ucs03Address: UCS03_MINTER.address, + instruction: batch, + }) + + const client = yield* ZkgmClient.ZkgmClient + + const response = yield* client.execute(request) + yield* Effect.log("Submission TX Hash:", response.txHash) + + const receipt = yield* response.waitFor( + ZkgmIncomingMessage.LifecycleEvent.$is("EvmTransactionReceiptComplete"), + ) + + yield* Effect.log("Receipt:", receipt) +}).pipe( + Effect.provide(EvmZkgmClient.layerWithoutWallet), + Effect.provide(Evm.WalletClient.Live({ + account: VIEM_ACCOUNT, + chain: VIEM_CHAIN, + transport: http(RPC_URL), + })), + Effect.provide(Evm.PublicClient.Live({ + chain: VIEM_CHAIN, + transport: http(RPC_URL), + })), + Effect.provide(ChainRegistry.Default), + Effect.provide(Logger.replace( + Logger.defaultLogger, + Logger.prettyLogger({ + stderr: true, + colors: true, + mode: "tty", + }), + )), +) + +Effect.runPromise(sendBond) + .then(console.log) + .catch(console.error) diff --git a/app2/examples/unbond.ts b/app2/examples/unbond.ts new file mode 100644 index 0000000000..68f98ca236 --- /dev/null +++ b/app2/examples/unbond.ts @@ -0,0 +1,145 @@ +/** + * ETH -> UNION + * + * Execute from CLI with `KEY="0xprivatekey" pnpm dlx vite-node ./path/to/bond.ts` + */ +// @ts-ignore +if (typeof BigInt.prototype.toJSON !== "function") { + // @ts-ignore + BigInt.prototype.toJSON = function() { + return this.toString() + } +} +import { + Batch, + Call, + TokenOrder, + Ucs05, + ZkgmClient, + ZkgmClientRequest, + ZkgmIncomingMessage, +} from "@unionlabs/sdk" +import { Evm, EvmZkgmClient } from "@unionlabs/sdk-evm" +import { ChainRegistry } from "@unionlabs/sdk/ChainRegistry" +import { ON_ZKGM_CALL_PROXY, U_BANK, U_ERC20, U_SOLVER_METADATA } from "@unionlabs/sdk/Constants" +import { UniversalChainId } from "@unionlabs/sdk/schema/chain" +import { ChannelId } from "@unionlabs/sdk/schema/channel" +import { HexFromJson } from "@unionlabs/sdk/schema/hex" +import { Effect, Logger, pipe, Schema } from "effect" +import { http } from "viem" +import { privateKeyToAccount } from "viem/accounts" +import { holesky } from "viem/chains" + +const JsonFromBase64 = Schema.compose( + Schema.StringFromBase64, + Schema.parseJson(), +) + +const AMOUNT = 1n +const ETHEREUM_CHAIN_ID = UniversalChainId.make("ethereum.17000") +const UNION_CHAIN_ID = UniversalChainId.make("union.union-testnet-10") +const SOURCE_CHANNEL_ID = ChannelId.make(1) // FIXME +const UCS03_MINTER = Ucs05.EvmDisplay.make({ + address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03", +}) +const VIEM_CHAIN = holesky +const RPC_URL = "https://rpc.17000.ethereum.chain.kitchen" +const SENDER = Ucs05.EvmDisplay.make({ + address: "0x06627714f3F17a701f7074a12C02847a5D2Ca487", +}) +const EU_STAKING_HUB = Ucs05.CosmosDisplay.make({ + address: "union1eueueueu9var4yhdruyzkjcsh74xzeug6ckyy60hs0vcqnzql2hq0lxc2f", // FIXME +}) + +const VIEM_ACCOUNT = privateKeyToAccount( + process.env.KEY as any, +) + +const sendUnbond = Effect.gen(function*() { + const ethereumChain = yield* ChainRegistry.byUniversalId(ETHEREUM_CHAIN_ID) + const unionChain = yield* ChainRegistry.byUniversalId(UNION_CHAIN_ID) + + const eu_staking_hub = yield* Ucs05.anyDisplayToZkgm(EU_STAKING_HUB) + + const tokenOrder = yield* TokenOrder.make({ + source: ethereumChain, + destination: unionChain, + sender: SENDER, + receiver: ON_ZKGM_CALL_PROXY, + baseToken: U_ERC20, + baseAmount: AMOUNT, + quoteToken: U_BANK, + quoteAmount: AMOUNT, + kind: "solve", + metadata: U_SOLVER_METADATA, + version: 2, + }) + + const unbondCall = yield* pipe( + { amount: tokenOrder.quoteAmount } as const, + Schema.encode(JsonFromBase64), + Effect.map((msg) => ({ + contract: eu_staking_hub, + msg, + funds: [], + call_action: "call_proxy", + } as const)), + Effect.flatMap(Schema.decode(HexFromJson)), + Effect.map((contractCalldata) => + Call.make({ + sender: SENDER, + eureka: false, + contractAddress: ON_ZKGM_CALL_PROXY, + contractCalldata, + }) + ), + ) + + const batch = Batch.make([ + tokenOrder, + unbondCall, + ]) + + const request = ZkgmClientRequest.make({ + source: ethereumChain, + destination: unionChain, + channelId: SOURCE_CHANNEL_ID, + ucs03Address: UCS03_MINTER.address, + instruction: batch, + }) + + const client = yield* ZkgmClient.ZkgmClient + + const response = yield* client.execute(request) + yield* Effect.log("Submission TX Hash:", response.txHash) + + const receipt = yield* response.waitFor( + ZkgmIncomingMessage.LifecycleEvent.$is("EvmTransactionReceiptComplete"), + ) + + yield* Effect.log("Receipt:", receipt) +}).pipe( + Effect.provide(EvmZkgmClient.layerWithoutWallet), + Effect.provide(Evm.WalletClient.Live({ + account: VIEM_ACCOUNT, + chain: VIEM_CHAIN, + transport: http(RPC_URL), + })), + Effect.provide(Evm.PublicClient.Live({ + chain: VIEM_CHAIN, + transport: http(RPC_URL), + })), + Effect.provide(ChainRegistry.Default), + Effect.provide(Logger.replace( + Logger.defaultLogger, + Logger.prettyLogger({ + stderr: true, + colors: true, + mode: "tty", + }), + )), +) + +Effect.runPromise(sendUnbond) + .then(console.log) + .catch(console.error) diff --git a/app2/tsconfig.examples.json b/app2/tsconfig.examples.json new file mode 100644 index 0000000000..9779f213ea --- /dev/null +++ b/app2/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/app2/tsconfig.json b/app2/tsconfig.json index b50fd4b573..e2372e1821 100644 --- a/app2/tsconfig.json +++ b/app2/tsconfig.json @@ -13,6 +13,9 @@ }, { "path": "../typescript-sdk" + }, + { + "path": "tsconfig.examples.json" } ], "compilerOptions": { @@ -27,7 +30,9 @@ "exactOptionalPropertyTypes": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, - "types": ["vitest/importMeta"], + "types": [ + "vitest/importMeta" + ], "plugins": [ { "name": "@effect/language-service" @@ -44,4 +49,4 @@ // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in -} +} \ No newline at end of file diff --git a/ts-sdk/src/ChainRegistry.ts b/ts-sdk/src/ChainRegistry.ts index 76485203b0..2a8fbc418f 100644 --- a/ts-sdk/src/ChainRegistry.ts +++ b/ts-sdk/src/ChainRegistry.ts @@ -54,10 +54,11 @@ export class ChainRegistry extends Effect.Service()("@unionlabs/s effect: Effect.gen(function*() { const client = yield* Indexer - const byUniversalId = Effect.fn((id: UniversalChainId) => - pipe( - client.fetch({ - document: graphql(` + const byUniversalId: (id: UniversalChainId) => Effect.Effect = + Effect.fn((id) => + pipe( + client.fetch({ + document: graphql(` query GetChainByUniversalId($id: String!) @cached(ttl: 60) { v2_chains(args: { p_universal_chain_id: $id }) { chain_id @@ -95,32 +96,32 @@ query GetChainByUniversalId($id: String!) @cached(ttl: 60) { } } `), - variables: { - id, - }, - }), - Effect.map(Struct.get("v2_chains")), - Effect.flatMap(O.liftPredicate(Predicate.isTupleOf(1))), - Effect.map(A.headNonEmpty), - Effect.flatMap(Schema.decodeUnknown(Chain)), - Effect.catchTags({ - IndexerError: (cause) => - new ChainRegistryError({ - message: cause.message, - cause, - }), - NoSuchElementException: () => - new ChainRegistryError({ - message: `no such element or duplicate elements for ${id}`, - }), - ParseError: (cause) => - new ChainRegistryError({ - message: "failed to decode", - cause, - }), - }), + variables: { + id, + }, + }), + Effect.map(Struct.get("v2_chains")), + Effect.flatMap(O.liftPredicate(Predicate.isTupleOf(1))), + Effect.map(A.headNonEmpty), + Effect.flatMap(Schema.decodeUnknown(Chain)), + Effect.catchTags({ + IndexerError: (cause) => + new ChainRegistryError({ + message: cause.message, + cause, + }), + NoSuchElementException: () => + new ChainRegistryError({ + message: `no such element or duplicate elements for ${id}`, + }), + ParseError: (cause) => + new ChainRegistryError({ + message: "failed to decode", + cause, + }), + }), + ) ) - ) return { byUniversalId, diff --git a/ts-sdk/src/Constants.ts b/ts-sdk/src/Constants.ts index 2cd67a36ef..d341c3fbff 100644 --- a/ts-sdk/src/Constants.ts +++ b/ts-sdk/src/Constants.ts @@ -6,6 +6,8 @@ import { Match, Schedule } from "effect" import { UniversalChainId } from "./schema/chain.js" import { TokenRawDenom } from "./schema/token.js" +import * as Token from "./Token.js" +import * as Ucs05 from "./Ucs05.js" /** * @category models @@ -198,3 +200,52 @@ export const tokenMetaOverride = Match.type().pipe( }) as const ), ) + +/** + * FIXME + * @category constants + * @since 2.0.0 + */ +export const ON_ZKGM_CALL_PROXY = Ucs05.CosmosDisplay.make({ + address: "union122ny3mep2l7nhtafpwav2y9e5jrslhek76hsjl", +}) + +/** + * @category constants + * @since 2.0.0 + */ +export const U_ERC20 = Token.Erc20.make({ address: "0xba5eD44733953d79717F6269357C77718C8Ba5ed" }) + +/** + * @category constants + * @since 2.0.0 + */ +export const EU_ERC20 = Token.Erc20.make({ address: "0xe5Cf13C84c0fEa3236C101Bd7d743d30366E5CF1" }) + +/** + * @category constants + * @since 2.0.0 + */ +export const U_BANK = Token.CosmosBank.make({ address: "au" }) + +/** + * @category constants + * @since 2.0.0 + */ +export const EU_LST = Ucs05.CosmosDisplay.make({ + address: "union1eueueueu9var4yhdruyzkjcsh74xzeug6ckyy60hs0vcqnzql2hq0lxc2f", +}) +/** + * @category constants + * @since 2.0.0 + */ +export const U_SOLVER_METADATA = + "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000014ba5ed44733953d79717f6269357c77718c8ba5ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" as const + +/** + * FIXME + * @category constants + * @since 2.0.0 + */ +export const EU_SOLVER_METADATA = + "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000014ba5ed44733953d79717f6269357c77718c8ba5ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" as const diff --git a/ts-sdk/test/Ucs03.test.ts b/ts-sdk/test/Ucs03.test.ts index d00cb05243..c139cb8428 100644 --- a/ts-sdk/test/Ucs03.test.ts +++ b/ts-sdk/test/Ucs03.test.ts @@ -80,6 +80,15 @@ describe("UCS03", () => { }), ) })) + it.effect.only("qlp", () => + Effect.gen(function*() { + const result = yield* pipe( + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000007400000000000000000000000000000000000000000000000000000000000000a2000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000001406627714f3F17a701f7074a12C02847a5D2Ca487000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c756e696f6e3132326e79336d6570326c376e687461667077617632793965356a72736c68656b373668736a6c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014ba5eD44733953d79717F6269357C77718C8Ba5ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000002617500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000014ba5ed44733953d79717f6269357c77718c8ba5ed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001406627714f3F17a701f7074a12C02847a5D2Ca487000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c756e696f6e3132326e79336d6570326c376e687461667077617632793965356a72736c68656b373668736a6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001837b22636f6e7472616374223a2230783735366536393666366533313635373536353735363537353635373533393736363137323334373936383634373237353739376136623661363337333638333733343738376136353735363733363633366237393739333633303638373333303736363337313665376137313663333236383731333036633738363333323636222c226d7367223a2265794a746157353058335276496a6f694d4867334e545a6c4e6a6b325a6a5a6c4d7a457a4d6a4d794e6d55334f544d7a4e6d51324e5463774d7a4932597a4d334e6d55324f4463304e6a45324e6a63774e7a63324d5463324d7a49334f544d354e6a557a4e545a684e7a49334d7a5a6a4e6a67324e545a694d7a637a4e6a59344e7a4d3259545a6a4969776962576c7558323170626e52665957317664573530496a6f694d534a39222c2266756e6473223a5b7b2264656e6f6d223a226175222c22616d6f756e74223a2231227d5d2c2263616c6c5f616374696f6e223a2263616c6c5f70726f7879227d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001406627714f3F17a701f7074a12C02847a5D2Ca487000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c756e696f6e3132326e79336d6570326c376e687461667077617632793965356a72736c68656b373668736a6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001187b22636f6e7472616374223a2230783735366536393666366533313635373536353735363537353635373533393736363137323334373936383634373237353739376136623661363337333638333733343738376136353735363733363633366237393739333633303638373333303736363337313665376137313663333236383731333036633738363333323636222c226d7367223a2265794a7a634756755a475679496a6f694d4867315a6d4a6c4e7a52684d6a677a5a6a63354e54526d4d54426859544130597a4a6c5a4759314e5455334f4467784d57466c596a417a496977695957317664573530496a6f694d534a39222c2266756e6473223a5b5d2c2263616c6c5f616374696f6e223a22646972656374227d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001406627714f3F17a701f7074a12C02847a5D2Ca487000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c756e696f6e3132326e79336d6570326c376e687461667077617632793965356a72736c68656b373668736a6c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017c7b22636f6e7472616374223a22307835666265373461323833663739353466313061613034633265646635353537383831316165623033222c226d7367223a2265794a66615751694f694a465a6d5a6c593351694c434a66623341694f694a50626c4e3159324e6c63334d694c434a6c5a6d5a6c593352666157357a64484a315933527062323566615441694f6e736958326c6b496a6f6952575a6d5a574e304969776958323977496a6f695432355464574e6a5a584e7a496977695a575a6d5a574e3058326c756333527964574e306157397558326b77496a7037496c39705a434936496b566d5a6d566a64434973496c397663434936496b39755533566a5932567a63794973496d566d5a6d566a64463970626e4e30636e566a64476c76626c39704d43493665794a66615751694f694a465a6d5a6c593351694c434a66623341694f694a4462323174615851696658313966513d3d222c2266756e6473223a5b5d2c2263616c6c5f616374696f6e223a22646972656374227d00000000" as const, + S.decode(Ucs03.Ucs03FromHex), + ) + + console.log({ result: JSON.stringify(result, null, 2) }) + })) describe.only("Call", () => { it.effect("decodes from packet", () =>