Skip to content

Commit a771fad

Browse files
committed
add Token module; add iso instruction to hex
Signed-off-by: Eric Hegnes <eric@hegnes.com>
1 parent dc99b6d commit a771fad

File tree

12 files changed

+469
-95
lines changed

12 files changed

+469
-95
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @title Send Funds Holesky → Xion
3+
* @description Example transfer from Holesky to Xion.
4+
* @badge ✓:success
5+
*/
6+
/// <reference types="effect" />
7+
/// <reference types="viem" />
8+
// @paths: {"@unionlabs/sdk": ["../ts-sdk/src"], "@unionlabs/sdk/*": ["../ts-sdk/src/*"]}
9+
// @ts-ignore
10+
if (typeof BigInt.prototype.toJSON !== "function") {
11+
// @ts-ignore
12+
BigInt.prototype.toJSON = function() {
13+
return this.toString()
14+
}
15+
}
16+
// ---cut---
17+
// EVM
18+
import { http, toHex } from "viem"
19+
import { bscTestnet } from "viem/chains"
20+
// Union
21+
import * as Evm from "@unionlabs/sdk/Evm"
22+
import * as Ucs03 from "@unionlabs/sdk/Ucs03"
23+
import * as Ucs05 from "@unionlabs/sdk/Ucs05"
24+
import { Effect, pipe } from "effect"
25+
import { privateKeyToAccount } from "viem/accounts"
26+
27+
const SENDER = Ucs05.AddressEvmZkgm.make(toHex("0xfaebe5bf141cc04a3f0598062b98d2df01ab3c4d"))
28+
const RECEIVER = Ucs05.AddressCosmosZkgm.make(toHex("bbn122ny3mep2l7nhtafpwav2y9e5jrslhekrn8frh"))
29+
30+
const walletClient = Evm.WalletClient.Live({
31+
account: privateKeyToAccount(
32+
"0x0afb32055d944e59e854aea5d4fbabe550f686158aae06e6e9391fb576dc4404" as const,
33+
),
34+
chain: bscTestnet,
35+
transport: http("https://rpc.97.bsc.chain.kitchen"),
36+
})
37+
const sourceChannel = Evm.ChannelSource.Live({
38+
ucs03address: "0x5fbe74a283f7954f10aa04c2edf55578811aeb03",
39+
channelId: 1,
40+
})
41+
42+
const main = pipe(
43+
Ucs03.Batch.fromOperand([
44+
Ucs03.FungibleAssetOrderV2.fromOperand([
45+
SENDER,
46+
RECEIVER,
47+
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
48+
100n,
49+
0,
50+
"0x996be231a091877022ccdbf41da6e2f92e097c0ccc9480f8b3c630e5c2b14ff1", // FIXME: metadata
51+
"0x49aCf968c7E8807B39e980b2a924E97C8ead3a22",
52+
100n,
53+
]),
54+
Ucs03.FungibleAssetOrderV2.fromOperand([
55+
SENDER,
56+
RECEIVER,
57+
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
58+
360422652800n,
59+
0,
60+
"0x996be231a091877022ccdbf41da6e2f92e097c0ccc9480f8b3c630e5c2b14ff1", // FIXME: metadata
61+
"0x49aCf968c7E8807B39e980b2a924E97C8ead3a22",
62+
0n,
63+
]),
64+
]),
65+
(fao) => {
66+
console.log({ fao: JSON.stringify(fao, null, 2) })
67+
return fao
68+
},
69+
Evm.sendInstruction,
70+
Effect.provide(walletClient),
71+
Effect.provide(sourceChannel),
72+
)
73+
74+
// Run main program
75+
Effect.runPromise(main)
76+
.then(console.log)
77+
.catch(console.error)

ts-sdk/examples/UCS03/send-funds-holesky-to-xion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const sourceClient = Evm.PublicClientSource.Live({
3434
chain: holesky,
3535
transport: http("https://rpc.17000.ethereum.chain.kitchen"),
3636
})
37-
const destinationClient = Cosmos.ClientDestination.Live(
37+
const destinationClient = Cosmos.Client.Live(
3838
"https://rpc.xion-testnet-2.xion.chain.kitchen/",
3939
)
4040
const walletClient = Evm.WalletClient.Live({

ts-sdk/src/ClientRequest.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Pipeable } from "effect/Pipeable"
44
import type { ReadonlyRecord } from "effect/Record"
55
import * as internal from "./internal/clientRequest.js"
66
import type { Channel } from "./schema/channel.js"
7+
import * as Token from "./Token.js"
78

89
/**
910
* @category type ids
@@ -33,8 +34,9 @@ export interface ClientRequest extends Inspectable, Pipeable {
3334
readonly method: Method
3435
readonly sender: string
3536
readonly receiver: string
36-
readonly baseToken: Token.Any
37-
readonly quoteToken: Token.Any | "auto"
37+
readonly amount: bigint
38+
readonly baseToken: Token.Any | string
39+
readonly quoteToken: Token.Any | string | "auto"
3840
}
3941

4042
/**
@@ -87,7 +89,7 @@ export const make: <M extends Method>(
8789
) => (
8890
sender: string,
8991
receiver: string,
90-
options?: (M extends "SEND" ? Options.Send : Options.NoUrl) | undefined,
92+
options?: (M extends "SEND" ? Options.Send : Options.NoQuote) | undefined,
9193
) => ClientRequest = internal.make
9294

9395
/**

ts-sdk/src/Evm.ts

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* @since 2.0.0
55
*/
6-
import { Context, Data, Effect, flow, Layer, pipe } from "effect"
6+
import { Context, Data, Effect, flow, Layer, pipe, Schema as S } from "effect"
77
import { type Address, erc20Abi } from "viem"
88
import {
99
Abi,
@@ -582,6 +582,8 @@ export const sendInstruction = (instruction: Ucs03.Instruction) =>
582582
const timeoutTimestamp = Utils.getTimeoutInNanoseconds24HoursFromNow()
583583
const salt = yield* Utils.generateSalt("evm")
584584

585+
const operand = yield* S.encode(Ucs03.InstructionFromHex)(instruction)
586+
585587
return yield* writeContract({
586588
account: walletClient.account,
587589
abi: Ucs03.Abi,
@@ -596,34 +598,8 @@ export const sendInstruction = (instruction: Ucs03.Instruction) =>
596598
{
597599
opcode: instruction.opcode,
598600
version: instruction.version,
599-
operand: Ucs03.encode(instruction),
601+
operand,
600602
},
601603
],
602604
})
603605
})
604-
605-
/**
606-
* @category utils
607-
* @since 2.0.0
608-
*/
609-
export const estimateL1Fee = pipe(
610-
PublicClient,
611-
Effect.andThen(({ client }) =>
612-
Effect.tryPromise({
613-
try: () =>
614-
client
615-
.extend(publicActionsL2())
616-
.estimateL1Fee({
617-
account: "0x0000000000000000000000000000000000000000",
618-
chain: undefined,
619-
}),
620-
catch: cause =>
621-
new GasPriceError({
622-
module: "Evm",
623-
method: "additiveFee",
624-
description: `Could not calculate L1 fee for ${id}`,
625-
cause,
626-
}),
627-
})
628-
),
629-
)

ts-sdk/src/Token.ts

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,88 @@
1-
import {
2-
Effect,
3-
Either as E,
4-
flow,
5-
Match,
6-
Option as O,
7-
ParseResult,
8-
pipe,
9-
Schema as S,
10-
String as Str,
11-
} from "effect"
1+
import { Effect, flow, ParseResult, pipe, Schema as S, Struct } from "effect"
122

133
export const Erc20 = S.Struct({
144
_tag: S.tag("Erc20"),
15-
address: S.String,
5+
address: S.String.pipe(
6+
S.pattern(/^0x[0-9a-fA-F]{40}$/),
7+
S.annotations({
8+
examples: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
9+
}),
10+
),
1611
})
1712
export type Erc20 = typeof Erc20.Type
1813

14+
export const EvmGas = S.Struct({
15+
_tag: S.tag("EvmGas"),
16+
address: S.String.pipe(
17+
S.pattern(/^0x[eE]{40}$/),
18+
S.annotations({
19+
examples: ["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"],
20+
}),
21+
),
22+
})
23+
export type EvmGas = typeof EvmGas.Type
24+
25+
export const CosmosIbcClassic = S.Struct({
26+
_tag: S.tag("CosmosIbcClassic"),
27+
address: S.String.pipe(
28+
S.pattern(/^ibc\/[0-9A-Fa-f]{64}$/),
29+
S.annotations({
30+
examples: [""],
31+
}),
32+
),
33+
})
34+
export type CosmosIbcClassic = typeof CosmosIbcClassic.Type
35+
36+
export const CosmosTokenFactory = S.Struct({
37+
_tag: S.tag("CosmosTokenFactory"),
38+
address: S.String.pipe(
39+
S.pattern(/^factory\/.+$/),
40+
),
41+
})
42+
export type CosmosTokenFactory = typeof CosmosTokenFactory.Type
43+
44+
export const Cw20 = S.Struct({
45+
_tag: S.tag("Cw20"),
46+
address: S.String.pipe(
47+
S.pattern(/^[a-z0-9]{1,15}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,64}$/),
48+
),
49+
})
50+
export type Cw20 = typeof Cw20.Type
51+
1952
export const CosmosBank = S.Struct({
2053
_tag: S.tag("CosmosBank"),
21-
address: S.String,
54+
address: S.String.pipe(
55+
S.pattern(/^[a-z][a-z0-9]{1,127}$/),
56+
),
2257
})
2358
export type CosmosBank = typeof CosmosBank.Type
2459

25-
export const IbcClassic = S.Struct({
26-
_tag: S.tag("IbcClassic"),
27-
address: S.String,
28-
})
29-
export type IbcClassic = typeof IbcClassic.Type
30-
3160
export const Any = S.Union(
3261
Erc20,
62+
EvmGas,
63+
Cw20,
64+
CosmosTokenFactory,
3365
CosmosBank,
34-
IbcClassic,
66+
CosmosIbcClassic,
3567
)
3668
export type Any = typeof Any.Type
3769

3870
export const TokenFromString = S.transformOrFail(
39-
S.NonEmptyString,
71+
S.String,
4072
Any,
4173
{
42-
decode: (fromA, _, ast) =>
74+
decode: (address) =>
4375
pipe(
44-
Match.value(fromA),
45-
// XXX: revise string matching
46-
Match.when(flow(Str.match(/^0x[a-fA-F0-9]{40}$/), O.isSome), (address) =>
47-
Effect.succeed(Erc20.make({ address }))),
48-
Match.when(flow(Str.match(/^[a-zA-Z0-9/.:_-]+$/), O.isSome), (address) =>
49-
Effect.succeed(CosmosBank.make({ address }))),
50-
Match.when(flow(Str.match(/^ibc\/[a-fA-F0-9]{64}$/), O.isSome), (address) =>
51-
Effect.succeed(IbcClassic.make({ address }))),
52-
Match.orElse(() =>
53-
ParseResult.fail(new ParseResult.Type(ast, fromA, "No match"))
54-
),
76+
Effect.raceAll([
77+
S.decodeEither(EvmGas)({ _tag: "EvmGas", address }),
78+
S.decodeEither(CosmosIbcClassic)({ _tag: "CosmosIbcClassic", address }),
79+
S.decodeEither(CosmosTokenFactory)({ _tag: "CosmosTokenFactory", address }),
80+
S.decodeEither(Cw20)({ _tag: "Cw20", address }),
81+
]),
82+
Effect.orElse(() => S.decodeEither(Erc20)({ _tag: "Erc20", address })),
83+
Effect.orElse(() => S.decodeEither(CosmosBank)({ _tag: "CosmosBank", address })),
84+
Effect.catchTag("ParseError", (error) => ParseResult.fail(error.issue)),
5585
),
56-
encode: (toI) => Effect.succeed(toI.address),
86+
encode: flow(Struct.get("address"), Effect.succeed),
5787
},
5888
)
59-
export type TokenFromString = typeof TokenFromString.Type

0 commit comments

Comments
 (0)