Skip to content

Commit 76e63b7

Browse files
committed
move writer; add fao
Signed-off-by: Eric Hegnes <eric@hegnes.com>
1 parent 1930ba3 commit 76e63b7

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

ts-sdk/src/FungibleAssetOrder.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Effect, pipe } from "effect"
2+
import * as O from "effect/Option"
3+
import * as S from "effect/Schema"
4+
import * as Evm from "./Evm.js"
5+
import { graphqlQuoteTokenUnwrapQuery } from "./graphql/unwrapped-quote-token.js"
6+
import { UniversalChainId } from "./schema/chain.js"
7+
import { ChannelId } from "./schema/channel.js"
8+
import { Hex } from "./schema/hex.js"
9+
import { TokenRawDenom } from "./schema/token.js"
10+
import * as Ucs03 from "./Ucs03.js"
11+
import * as Ucs05 from "./Ucs05.js"
12+
import * as Utils from "./Utils.js"
13+
14+
const BaseIntent = S.Struct({
15+
baseAmount: S.BigIntFromSelf.pipe(
16+
S.greaterThanBigInt(0n),
17+
),
18+
quoteAmount: S.BigIntFromSelf,
19+
sourceChainId: UniversalChainId,
20+
sourceChannelId: ChannelId,
21+
})
22+
type BaseIntent = typeof BaseIntent.Type
23+
24+
export const XAS = S.Struct({
25+
sender: Ucs05.AddressEvmZkgm,
26+
baseToken: TokenRawDenom,
27+
sourceChainId: UniversalChainId,
28+
sourceChannelId: ChannelId,
29+
})
30+
export type XAS = typeof XAS.Type
31+
32+
export const YAS = S.Struct({
33+
receiver: Ucs05.AddressEvmZkgm,
34+
})
35+
export type YAS = typeof YAS.Type
36+
37+
const XA = (xas: XAS) => (base: BaseIntent) =>
38+
Effect.gen(function*() {
39+
const sourceClient = (yield* Evm.PublicClientSource).client
40+
const tokenMeta = yield* Evm.readErc20Meta(
41+
xas.baseToken as unknown as any,
42+
xas.sourceChainId,
43+
).pipe(
44+
Effect.provideService(Evm.PublicClient, { client: sourceClient }),
45+
)
46+
47+
const graphqlDenom = yield* graphqlQuoteTokenUnwrapQuery({
48+
baseToken: Utils.ensureHex(xas.baseToken),
49+
sourceChainId: xas.sourceChainId,
50+
sourceChannelId: xas.sourceChannelId,
51+
})
52+
53+
const finalQuoteToken = yield* O.match(graphqlDenom, {
54+
onNone: () => Evm.predictQuoteToken(xas.baseToken),
55+
onSome: Effect.succeed,
56+
})
57+
58+
const path = O.match(graphqlDenom, {
59+
onNone: () => 0n,
60+
onSome: () => xas.sourceChannelId,
61+
})
62+
63+
return [
64+
O.some(xas.sender),
65+
O.none(), // receiver
66+
O.some(Utils.ensureHex(xas.baseToken)),
67+
O.some(base.baseAmount),
68+
O.some(tokenMeta.symbol),
69+
O.some(tokenMeta.name),
70+
O.some(tokenMeta.decimals),
71+
O.some(path), // path is source channel when unwrapping, else 0
72+
O.some(finalQuoteToken),
73+
O.some(base.quoteAmount),
74+
] as const
75+
})
76+
77+
const YA = (yas: YAS) => (base: BaseIntent) =>
78+
Effect.succeed(
79+
[
80+
O.none(),
81+
O.some(yas.receiver),
82+
O.none(),
83+
O.none(),
84+
O.none(),
85+
O.none(),
86+
O.none(),
87+
O.none(),
88+
O.none(),
89+
O.none(),
90+
] as const,
91+
)
92+
93+
const combine = (xas: XAS, yas: YAS) => (base: BaseIntent) =>
94+
pipe(
95+
Effect.all([
96+
XA(xas)(base),
97+
YA(yas)(base),
98+
]),
99+
Effect.map([x, y] => O.all),
100+
x =>
101+
)

ts-sdk/src/typeclass/Writer.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type * as covariant from "@effect/typeclass/Covariant"
2+
import type * as flatmap from "@effect/typeclass/FlatMap"
3+
import type * as monad from "@effect/typeclass/Monad"
4+
import type * as monoid from "@effect/typeclass/Monoid"
5+
import type * as of from "@effect/typeclass/Of"
6+
import { dual } from "effect/Function"
7+
import type { TypeLambda } from "effect/HKT"
8+
9+
export type Writer<W, A> = readonly [A, W]
10+
11+
interface WriterTypeLambda<W> extends TypeLambda {
12+
readonly type: Writer<W, this["Target"]>
13+
}
14+
15+
export const censor = dual(
16+
2,
17+
<A, W>(fa: readonly [A, W], f: (w: W) => W): [A, W] => [fa[0], f(fa[1])],
18+
)
19+
20+
export const mapWriter: {
21+
<W1, W2, A, B>(
22+
f: (a: A, w: W1) => readonly [B, W2],
23+
): (writer: readonly [A, W1]) => readonly [B, W2]
24+
<W1, W2, A, B>(
25+
writer: readonly [A, W1],
26+
f: (a: A, w: W1) => readonly [B, W2],
27+
): readonly [B, W2]
28+
} = dual(
29+
2,
30+
<W1, W2, A, B>(
31+
writer: readonly [A, W1],
32+
f: (a: A, w: W1) => readonly [B, W2],
33+
): readonly [B, W2] => f(writer[0], writer[1]),
34+
)
35+
36+
export const getOf = <W>(M: monoid.Monoid<W>): of.Of<WriterTypeLambda<W>> => ({
37+
of: <A>(a: A) => [a, M.empty],
38+
})
39+
40+
export const getFlatMap = <W>(M: monoid.Monoid<W>): flatmap.FlatMap<WriterTypeLambda<W>> => ({
41+
flatMap: dual(2, <A, B>(fa: Writer<W, A>, f: (a: A) => Writer<W, B>): Writer<W, B> => {
42+
const [a, w1] = fa
43+
const [b, w2] = f(a)
44+
return [b, M.combine(w1, w2)]
45+
}),
46+
})
47+
48+
export const getCovariant = <W>(): covariant.Covariant<WriterTypeLambda<W>> => ({
49+
map: dual(2, <A, B>(fa: Writer<W, A>, f: (a: A) => B): Writer<W, B> => [f(fa[0]), fa[1]]),
50+
imap: dual(
51+
3,
52+
<A, B>(
53+
fa: Writer<W, A>,
54+
to: (a: A) => B,
55+
_from: (b: B) => A,
56+
): Writer<W, B> => [to(fa[0]), fa[1]],
57+
),
58+
})
59+
60+
export const getMonad = <W>(M: monoid.Monoid<W>): monad.Monad<WriterTypeLambda<W>> => ({
61+
...getOf(M),
62+
...getCovariant(),
63+
...getFlatMap(M),
64+
})

ts-sdk/test/typeclass/Writer.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as Writer from "$lib/typeclass/Writer.js"
2+
import * as ArrayInstances from "@effect/typeclass/data/Array"
3+
import * as StringInstances from "@effect/typeclass/data/String"
4+
import * as FlatMap from "@effect/typeclass/FlatMap"
5+
import { assert, describe, it } from "@effect/vitest"
6+
import * as A from "effect/Array"
7+
import { pipe } from "effect/Function"
8+
9+
describe.concurrent("Writer", () => {
10+
describe("FlatMap", () => {
11+
it.each([
12+
[
13+
"getMonad",
14+
pipe(
15+
StringInstances.Monoid,
16+
Writer.getMonad,
17+
FlatMap.composeK,
18+
),
19+
],
20+
[
21+
"getFlatMap",
22+
pipe(
23+
Writer.getFlatMap(StringInstances.Monoid),
24+
FlatMap.composeK,
25+
),
26+
],
27+
])("composeK (%s)", (_, composeK) => {
28+
const f = (s: string): [number, string] => [s.length, "[length]"]
29+
const g = (n: number): [boolean, string] => [n > 3, `[(>) 3 ${n}]`]
30+
const h = pipe(f, composeK(g))
31+
assert.deepStrictEqual(h(""), [false, "[length][(>) 3 0]"])
32+
assert.deepStrictEqual(h("abc"), [false, "[length][(>) 3 3]"])
33+
assert.deepStrictEqual(h("abcd"), [true, "[length][(>) 3 4]"])
34+
})
35+
36+
it("mapWriter", () => {
37+
const composeK = pipe(
38+
Writer.getFlatMap(ArrayInstances.getMonoid<string>()),
39+
FlatMap.composeK,
40+
)
41+
const f = (s: string): [number, string[]] => [s.length, ["length"]]
42+
const g = (n: number): [boolean, string[]] => [n > 3, [">", "3", `${n}`]]
43+
const h = pipe(f, composeK(g))
44+
const j = Writer.mapWriter(
45+
(a: boolean, w: readonly string[]) => [a, pipe(w, A.reverse, A.join(" · "))],
46+
)
47+
assert.deepStrictEqual(j(h("a")), [false, "1 · 3 · > · length"])
48+
})
49+
})
50+
})

0 commit comments

Comments
 (0)