Skip to content

Commit c681c3e

Browse files
committed
feat(app2): multisig progress
1 parent 1609c75 commit c681c3e

File tree

11 files changed

+556
-684
lines changed

11 files changed

+556
-684
lines changed

app2/src/lib/components/Transfer/index.svelte

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { wallets } from "$lib/stores/wallets.svelte.ts"
2121
import { beforeNavigate } from "$app/navigation"
2222
import { onMount } from "svelte"
2323
import { fly } from "svelte/transition"
24-
import type { TransferIntents } from "$lib/components/Transfer/state/filling/create-intents.ts"
24+
import type {TransferIntent} from "$lib/components/Transfer/state/filling/create-intents.ts"
2525
import { generateMultisigTx } from "$lib/utils/multisig.ts"
2626
2727
let currentPage = $state(0)
@@ -62,7 +62,7 @@ let actionButtonText = $derived.by(() => {
6262
function handleActionButtonClick() {
6363
if (transfer.signingMode === "multi") {
6464
console.log("SIGNING MODE IS MULTISIG")
65-
const b = Effect.runSync(generateMultisigTx(localIntents))
65+
const b = Effect.runSync(generateMultisigTx(localIntent))
6666
console.log({ b })
6767
return
6868
}
@@ -99,7 +99,7 @@ function newTransfer() {
9999
wallets.clearInputAddress()
100100
}
101101
102-
let localIntents = $state<TransferIntents>([])
102+
let localIntent = $state<TransferIntent>()
103103
104104
$effect(() => {
105105
if (currentPage !== 0) return
@@ -111,7 +111,7 @@ $effect(() => {
111111
112112
const machineEffect = Effect.gen(function* () {
113113
let currentState: CreateTransferState = CreateTransferState.Filling()
114-
let intents: TransferIntents = []
114+
let intent: TransferIntent
115115
116116
while (true) {
117117
const result: StateResult = yield* createTransferState(currentState, transfer)
@@ -130,8 +130,8 @@ $effect(() => {
130130
continue
131131
}
132132
133-
if (Option.isSome(result.intents)) {
134-
intents = result.intents.value
133+
if (Option.isSome(result.intent)) {
134+
intent = result.intent.value
135135
}
136136
137137
break
@@ -160,32 +160,36 @@ $effect(() => {
160160
)
161161
}
162162
163-
for (const intent of intents) {
163+
if (intent) {
164+
localIntent = intent
165+
164166
if (Option.isSome(intent.allowances)) {
165-
const allowance = intent.allowances.value
166-
167-
steps.push(
168-
TransferStep.ApprovalRequired({
169-
token: allowance.token,
170-
requiredAmount: allowance.requiredAmount,
171-
currentAllowance: allowance.currentAllowance,
172-
context: intent.context
173-
})
174-
)
167+
const allowances = intent.allowances.value
168+
169+
for (let i = 0; i < allowances.length; i++) {
170+
const allowance = allowances[i]
171+
const context = intent.contexts[i]
172+
173+
steps.push(
174+
TransferStep.ApprovalRequired({
175+
token: allowance.token,
176+
requiredAmount: allowance.requiredAmount,
177+
currentAllowance: allowance.currentAllowance,
178+
context
179+
})
180+
)
181+
}
175182
}
176183
177-
localIntents = intents
178-
179-
if (Option.isSome(intent.instructions)) {
180-
steps.push(
181-
TransferStep.SubmitInstruction({
182-
instruction: intent.instructions.value,
183-
context: intent.context
184-
}),
185-
TransferStep.WaitForIndex({
186-
context: intent.context
187-
})
188-
)
184+
if (Option.isSome(intent.instruction)) {
185+
const instruction = intent.instruction.value
186+
187+
for (const context of intent.contexts) {
188+
steps.push(
189+
TransferStep.SubmitInstruction({ instruction, context }),
190+
TransferStep.WaitForIndex({ context })
191+
)
192+
}
189193
}
190194
}
191195
@@ -307,7 +311,6 @@ const flyRight = (node: Element) =>
307311

308312
</Card>
309313

310-
311314
{#if showDetails}
312315
{#if Option.isSome(transferErrors)}
313316
<strong>Error</strong>
@@ -326,3 +329,4 @@ const flyRight = (node: Element) =>
326329
</div>
327330
{/if}
328331
{/if}
332+

app2/src/lib/components/Transfer/pages/FillingPage.svelte

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import Button from "$lib/components/ui/Button.svelte"
66
import AngleArrowIcon from "$lib/components/icons/AngleArrowIcon.svelte"
77
import AddressComponent from "$lib/components/model/AddressComponent.svelte"
88
import { transfer } from "$lib/components/Transfer/transfer.svelte.ts"
9-
import { Match, Option } from "effect"
9+
import { Match, Option, Schema } from "effect"
1010
import type { TransferFlowError } from "$lib/components/Transfer/state/errors.ts"
1111
import InsetError from "$lib/components/model/InsetError.svelte"
1212
import Input from "$lib/components/ui/Input.svelte"
1313
import { wallets } from "$lib/stores/wallets.svelte.ts"
1414
import { getDerivedReceiverSafe } from "$lib/services/shared"
15+
import {AddressCosmosCanonical, Bech32, Bech32FromAddressCanonicalBytesWithPrefix} from "@unionlabs/sdk/schema";
16+
import { bech32AddressToHex } from "@unionlabs/client"
1517
1618
type Props = {
1719
onContinue: () => void
@@ -64,6 +66,11 @@ const uiStatus = $derived.by(() => {
6466
const isButtonEnabled = $derived.by(() => !loading)
6567
6668
let sender = $state("")
69+
70+
const cosmosAddressFromBech32 = (address: string) => {
71+
const hexAddress = bech32AddressToHex({ address })
72+
return AddressCosmosCanonical.make(hexAddress)
73+
}
6774
</script>
6875

6976
<div class="min-w-full p-4 flex flex-col grow">
@@ -104,14 +111,9 @@ let sender = $state("")
104111
placeholder="0x123"
105112
spellcheck="false"
106113
oninput={(event) => {
107-
getDerivedReceiverSafe(event.target.value).pipe(
108-
Option.match({
109-
onNone: () => {},
110-
onSome: (address) => {
111-
wallets.addInputAddress(address)
112-
}
113-
})
114-
)
114+
console.log(Schema.encodeUnknownSync(Bech32FromAddressCanonicalBytesWithPrefix(''))(event.target.value))
115+
wallets.addInputAddress(bech32AddressToHex({address: event.target.value}))
116+
console.log(wallets.inputAddress)
115117
}}
116118
/>
117119
{/if}

app2/src/lib/components/Transfer/state/filling/check-allowance.ts

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,90 @@
11
import { Data, Effect, Match, Option } from "effect"
22
import type { AddressCanonicalBytes, Chain } from "@unionlabs/sdk/schema"
33
import { fromHex, http, isHex } from "viem"
4-
import { createViemPublicClient, readErc20Allowance, ViemPublicClient } from "@unionlabs/sdk/evm"
5-
import { CosmWasmClientSource, createCosmWasmClient } from "@unionlabs/sdk/cosmos"
4+
import {
5+
createViemPublicClient,
6+
readErc20Allowance,
7+
ViemPublicClient
8+
} from "@unionlabs/sdk/evm"
9+
import {
10+
CosmWasmClientSource,
11+
createCosmWasmClient
12+
} from "@unionlabs/sdk/cosmos"
613
import { isValidBech32ContractAddress } from "@unionlabs/client"
714
import { cosmosSpenderAddresses } from "$lib/constants/spender-addresses.ts"
815
import {
916
AllowanceCheckError,
1017
CosmosQueryError,
1118
type TransferFlowError
1219
} from "$lib/components/Transfer/state/errors.ts"
13-
import type { TransferIntents } from "$lib/components/Transfer/state/filling/create-intents.ts"
14-
import { groupBy } from "effect/Array"
20+
import type { TransferIntent } from "$lib/components/Transfer/state/filling/create-intents.ts"
1521

1622
export class ApprovalStep extends Data.TaggedClass("ApprovalStep")<{
1723
token: string
1824
requiredAmount: bigint
1925
currentAllowance: bigint
2026
}> {}
2127

22-
function gatherNeededAmounts(intents: TransferIntents): Map<string, bigint> {
23-
const neededMap = new Map<string, bigint>()
24-
for (const { context } of intents) {
25-
const current = neededMap.get(context.baseToken) ?? 0n
26-
neededMap.set(context.baseToken, current + context.baseAmount)
28+
function gatherNeededAmounts(contexts: Array<{ baseToken: string; baseAmount: bigint }>) {
29+
const map = new Map<string, bigint>()
30+
for (const { baseToken, baseAmount } of contexts) {
31+
const current = map.get(baseToken) ?? 0n
32+
map.set(baseToken, current + baseAmount)
2733
}
28-
return neededMap
34+
return map
2935
}
3036

3137
export function checkAllowances(
32-
intents: TransferIntents
38+
intent: TransferIntent
3339
): Effect.Effect<Option.Option<Array<ApprovalStep>>, TransferFlowError> {
3440
return Effect.gen(function* () {
35-
if (intents.length === 0) return Option.none()
36-
37-
const grouped = groupBy(intents, intent => intent.context.sourceChain.universal_chain_id)
41+
if (intent.contexts.length === 0) return Option.none()
3842

39-
const allSteps: Array<ApprovalStep> = []
43+
const [firstContext] = intent.contexts
44+
const chain = firstContext.sourceChain
45+
const sender = firstContext.sender
46+
const spender = firstContext.ucs03address
4047

41-
for (const [, group] of Object.entries(grouped)) {
42-
const chain = group[0].context.sourceChain
43-
const sender = group[0].context.sender
44-
const neededMap = gatherNeededAmounts(group)
45-
const tokenAddresses = [...neededMap.keys()]
46-
47-
const allowancesOpt = yield* Match.value(chain.rpc_type).pipe(
48-
Match.when("evm", () =>
49-
handleEvmAllowances(tokenAddresses, sender, group[0].context.ucs03address, chain).pipe(
50-
Effect.mapError(err => new AllowanceCheckError({ cause: err }))
51-
)
52-
),
53-
Match.when("cosmos", () =>
54-
handleCosmosAllowances(tokenAddresses, sender, chain).pipe(
55-
Effect.mapError(err => new AllowanceCheckError({ cause: err }))
56-
)
57-
),
58-
Match.orElse(() => Effect.succeed(Option.none()))
59-
)
48+
const neededMap = gatherNeededAmounts(
49+
intent.contexts.map(({ baseToken, baseAmount }) => ({ baseToken, baseAmount }))
50+
)
51+
const tokenAddresses = [...neededMap.keys()]
6052

61-
const allowances = Option.getOrElse(allowancesOpt, () => [])
53+
const allowancesOpt = yield* Match.value(chain.rpc_type).pipe(
54+
Match.when("evm", () =>
55+
handleEvmAllowances(tokenAddresses, sender, spender, chain).pipe(
56+
Effect.mapError(err => new AllowanceCheckError({ cause: err }))
57+
)
58+
),
59+
Match.when("cosmos", () =>
60+
handleCosmosAllowances(tokenAddresses, sender, chain).pipe(
61+
Effect.mapError(err => new AllowanceCheckError({ cause: err }))
62+
)
63+
),
64+
Match.orElse(() => Effect.succeed(Option.none()))
65+
)
6266

63-
for (const { token, allowance } of allowances) {
64-
const requiredAmount = neededMap.get(token) ?? 0n
65-
if (allowance < requiredAmount) {
66-
allSteps.push(new ApprovalStep({ token, requiredAmount, currentAllowance: allowance }))
67-
}
67+
const allowances = Option.getOrElse(allowancesOpt, () => [])
68+
const steps: Array<ApprovalStep> = []
69+
70+
for (const { token, allowance } of allowances) {
71+
const requiredAmount = neededMap.get(token) ?? 0n
72+
if (allowance < requiredAmount) {
73+
steps.push(
74+
new ApprovalStep({
75+
token,
76+
requiredAmount,
77+
currentAllowance: allowance
78+
})
79+
)
6880
}
6981
}
7082

71-
return allSteps.length > 0 ? Option.some(allSteps) : Option.none()
83+
return steps.length > 0 ? Option.some(steps) : Option.none()
7284
})
7385
}
7486

87+
7588
function handleEvmAllowances(
7689
tokenAddresses: Array<string>,
7790
sender: AddressCanonicalBytes,
@@ -80,9 +93,7 @@ function handleEvmAllowances(
8093
): Effect.Effect<Option.Option<Array<{ token: string; allowance: bigint }>>, unknown> {
8194
return Effect.gen(function* () {
8295
const viemChainOpt = sourceChain.toViemChain()
83-
if (Option.isNone(viemChainOpt)) {
84-
return Option.none()
85-
}
96+
if (Option.isNone(viemChainOpt)) return Option.none()
8697

8798
const publicClientSource = yield* createViemPublicClient({
8899
chain: viemChainOpt.value,
@@ -167,28 +178,20 @@ export function handleCosmosAllowances(
167178
}
168179
}
169180

170-
if (!bech32Address) {
171-
return { token: tokenAddress, allowance: 0n }
172-
}
181+
if (!bech32Address) return { token: tokenAddress, allowance: 0n }
173182

174183
const result = yield* Effect.tryPromise(() =>
175184
cosmwasmClient.queryContractSmart(bech32Address, {
176185
allowance: { owner, spender }
177186
})
178-
).pipe(Effect.mapError(err => new CosmosQueryError({ token: tokenAddress, cause: err })))
187+
).pipe(
188+
Effect.mapError(err => new CosmosQueryError({ token: tokenAddress, cause: err }))
189+
)
179190

180191
const allowance = result.allowance ? BigInt(result.allowance) : 0n
181192
return { token: tokenAddress, allowance }
182193
}).pipe(Effect.provideService(CosmWasmClientSource, { client: cosmwasmClient }))
183194
)
184-
).pipe(
185-
Effect.mapError(
186-
err =>
187-
new CosmosQueryError({
188-
token: "N/A",
189-
cause: err
190-
})
191-
)
192195
)
193196

194197
return Option.some(checks)

0 commit comments

Comments
 (0)