Skip to content

Commit 9fa2502

Browse files
committed
- add Token module; add iso instruction to hex
- add faov2 support to ucs03 Signed-off-by: Eric Hegnes <eric@hegnes.com>
1 parent 4f4d4b1 commit 9fa2502

File tree

14 files changed

+707
-206
lines changed

14 files changed

+707
-206
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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("0xfaebe5bf141cc04a3f0598062b98d2df01ab3c4d")
28+
const RECEIVER = Ucs05.AddressCosmosZkgm.make(toHex("bbn122ny3mep2l7nhtafpwav2y9e5jrslhekrn8frh"))
29+
30+
const walletClient = Evm.WalletClient.Live({
31+
account: privateKeyToAccount(
32+
"0x..." 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.FungibleAssetOrderV2.fromOperand([
44+
SENDER,
45+
RECEIVER,
46+
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
47+
10n,
48+
1,
49+
"0x996be231a091877022ccdbf41da6e2f92e097c0ccc9480f8b3c630e5c2b14ff1",
50+
toHex("bbn1gm8473g2vszxepfyn884trrxtgkyf8572wa4csev5t8hjumja7csnllkkr"),
51+
10n,
52+
]),
53+
Evm.sendInstruction,
54+
Effect.provide(walletClient),
55+
Effect.provide(sourceChannel),
56+
)
57+
58+
// Run main program
59+
Effect.runPromise(main)
60+
.then(console.log)
61+
.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/Client.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
/**
2-
3-
* TODO: - assume user specifies quote token up-front
4-
- later consider deterimning ranked quote token
52
* @since 1.0.0
63
*/
74
import type * as Context from "effect/Context"
@@ -94,3 +91,23 @@ export const make: (
9491
fiber: RuntimeFiber<ClientResponse.ClientResponse, ClientError.ClientError>,
9592
) => Effect.Effect<ClientResponse.ClientResponse, ClientError.ClientError>,
9693
) => Client = internal.make
94+
95+
/**
96+
* @since 1.0.0
97+
* @category mapping & sequencing
98+
*/
99+
export const transform: {
100+
<E, R, E1, R1>(
101+
f: (
102+
effect: Effect.Effect<ClientResponse.ClientResponse, E, R>,
103+
request: ClientRequest.ClientRequest,
104+
) => Effect.Effect<ClientResponse.ClientResponse, E1, R1>,
105+
): (self: Client.With<E, R>) => Client.With<E | E1, R | R1>
106+
<E, R, E1, R1>(
107+
self: Client.With<E, R>,
108+
f: (
109+
effect: Effect.Effect<ClientResponse.ClientResponse, E, R>,
110+
request: ClientRequest.ClientRequest,
111+
) => Effect.Effect<ClientResponse.ClientResponse, E1, R1>,
112+
): Client.With<E | E1, R | R1>
113+
} = internal.transform

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: 5 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,9 @@ export const sendInstruction = (instruction: Ucs03.Instruction) =>
596598
{
597599
opcode: instruction.opcode,
598600
version: instruction.version,
599-
operand: Ucs03.encode(instruction),
601+
operand,
600602
},
601603
],
604+
value: 10n,
602605
})
603606
})
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)