Skip to content

Commit

Permalink
refactor(app): reactivity fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Swepool committed Jan 9, 2025
1 parent 1076fb6 commit a70a04d
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 161 deletions.
Original file line number Diff line number Diff line change
@@ -1,91 +1,57 @@
<script lang="ts">
import type { Readable } from "svelte/store"
import { truncate } from "$lib/utilities/format.ts"
import { formatUnits } from "viem"
import { Button } from "$lib/components/ui/button"
import type { CubeFaces } from "$lib/components/TransferFrom/components/Cube/types.ts"
import type { RawIntentsStore } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
import type { IntentsStore, AssetListItem } from "$lib/components/TransferFrom/transfer/intents.ts"
import { derived, writable } from "svelte/store"
import type {EnhancedBalanceData} from "$lib/queries/balance";
interface Props {
stores: {
rawIntents: RawIntentsStore
intents: Readable<IntentsStore>
}
rotateTo: (face: CubeFaces) => void
import type { Readable } from "svelte/store"
import { truncate } from "$lib/utilities/format.ts"
import { formatUnits } from "viem"
import { Button } from "$lib/components/ui/button"
import type { CubeFaces } from "$lib/components/TransferFrom/components/Cube/types.ts"
import type { RawIntentsStore } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
import type { IntentsStore } from "$lib/components/TransferFrom/transfer/intents.ts"
interface Props {
stores: {
rawIntents: RawIntentsStore
intents: Readable<IntentsStore>
}
rotateTo: (face: CubeFaces) => void
}
export let stores: Props["stores"]
export let rotateTo: Props["rotateTo"]
let { rawIntents, intents } = stores
const showZeroBalances = writable(false)
export let stores: Props["stores"]
export let rotateTo: Props["rotateTo"]
$: filteredAssets = derived([intents, showZeroBalances], ([$intents, $showZeroBalances]) =>
$showZeroBalances
? $intents.sourceAssets
: ($intents.sourceAssets as EnhancedBalanceData[]).filter(asset =>
BigInt(asset.balance) > 0n || asset.isLoading
)
)
function setAsset(denom: string) {
rawIntents.updateField("asset", denom)
rotateTo("intentFace")
}
let { rawIntents, intents } = stores
function toggleZeroBalances() {
showZeroBalances.update(value => !value)
}
function formatBalance(asset: EnhancedBalanceData): string {
if (asset.isLoading) return 'Loading...'
if (asset.error) return 'Error'
return formatUnits(BigInt(asset.balance), asset.metadata.decimals ?? 0)
}
function setAsset(denom: string) {
rawIntents.updateField("asset", denom)
rotateTo("intentFace")
}
</script>

<div class="flex flex-col h-full w-full">
<div class="text-primary p-2 px-4 flex items-center justify-between border-b-2">
<div class="flex items-center gap-2">
<span class="font-bold uppercase">Assets</span>
<button
class="text-xs border px-2 py-1 rounded"
on:click={toggleZeroBalances}
>
{$showZeroBalances ? 'Hide' : 'Show'} Zero Balances
</button>
</div>
<span class="font-bold uppercase">Assets</span>
<button
class="border-2 h-6 w-6 flex items-center justify-center"
on:click={() => rotateTo("intentFace")}
>✕
</button>
</div>

{#if $filteredAssets.length}
{#if $intents.sourceAssets.length}
<div class="flex-1 overflow-y-auto">
{#each $filteredAssets as asset (asset.metadata.denom)}
{#each $intents.sourceAssets as asset (asset)}
<div class="pb-2 flex flex-col justify-start">
<Button
variant="ghost"
class="px-4 py-2 w-full rounded-none flex justify-between items-center"
on:click={() => setAsset(asset.metadata.denom)}
disabled={asset.isLoading}
>
<div class:opacity-30={asset.metadata.metadata_level === "none"}>
{truncate(asset.metadata.display_symbol || asset.metadata.denom, 6)}
</div>
<p class:opacity-30={asset.metadata.metadata_level === "none"}>
{formatBalance(asset)}
{formatUnits(BigInt(asset.balance), asset.metadata.decimals ?? 0)}
</p>
</Button>
{#if asset.error}
<p class="text-xs text-red-500 px-4">{asset.error.message}</p>
{/if}
</div>
{/each}
</div>
Expand Down
7 changes: 4 additions & 3 deletions app/src/lib/components/TransferFrom/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import Transfer from "$lib/components/TransferFrom/components/Cube/faces/Transfe
import Cube from "$lib/components/TransferFrom/components/Cube/index.svelte"
import type { Chain } from "$lib/types.ts"
import { allChainBalances } from "$lib/queries/balance"
import { balanceStore, userAddress } from "$lib/components/TransferFrom/transfer/balances.ts"
import { userAddress } from "$lib/components/TransferFrom/transfer/balances.ts"
export let chains: Array<Chain>
$: allChainBalances(chains, userAddress).subscribe(data => {
balanceStore.set(data)
const balances = allChainBalances(chains, userAddress)
balances.subscribe(data => {
console.log(data)
})
const stores = createTransferStore(chains)
Expand Down
8 changes: 4 additions & 4 deletions app/src/lib/components/TransferFrom/transfer/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ export function createValidationStore(
const errors = derived([rawIntents, intents, context], ([$rawIntents, $intents, $context]) => {
const errors: FieldErrors = {}

if ($rawIntents.source) {
if (!$intents.sourceChain) errors.source = "Chain not supported"
if ($rawIntents.source && !$intents.sourceChain) {
errors.source = "Chain not supported"
}

if ($rawIntents.destination) {
if (!$intents.destinationChain) errors.destination = "Chain not supported"
if ($rawIntents.destination && !$intents.destinationChain) {
errors.destination = "Chain not supported"
}

// Source chain wallet validation
Expand Down
155 changes: 60 additions & 95 deletions app/src/lib/queries/balance/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import {derived, get, type Readable} from "svelte/store"
import {bech32ToBech32Address} from "@unionlabs/client"
import {type Address, isAddress} from "viem"
import type {Chain, ChainAsset, UserAddresses} from "$lib/types"
import {erc20ReadMulticall} from "./evm/multicall.ts"
import {getCosmosChainBalances} from "./cosmos.ts"
import {getAptosChainBalances} from "./aptos.ts"
import {createQueries} from "@tanstack/svelte-query"
import type {QueryObserverResult} from "@tanstack/query-core"
import {balanceStore} from "$lib/components/TransferFrom/transfer/balances.ts"
import { derived, type Readable } from "svelte/store"
import { bech32ToBech32Address } from "@unionlabs/client"
import { type Address, isAddress } from "viem"
import type { Chain, ChainAsset, UserAddresses } from "$lib/types"
import { erc20ReadMulticall } from "./evm/multicall.ts"
import { getCosmosChainBalances } from "./cosmos.ts"
import { getAptosChainBalances } from "./aptos.ts"
import { balanceStore } from "$lib/components/TransferFrom/transfer/balances.ts"

export type AssetMetadata = {
denom: string
Expand Down Expand Up @@ -103,8 +101,8 @@ export async function getUserBalances(
const contractAddresses = denoms
? denoms.filter((denom): denom is Address => isAddress(denom)).map(normalizeAddress)
: chain.assets
.filter((asset): asset is ChainAsset & { denom: Address } => isAddress(asset.denom))
.map(asset => normalizeAddress(asset.denom))
.filter((asset): asset is ChainAsset & { denom: Address } => isAddress(asset.denom))
.map(asset => normalizeAddress(asset.denom))

const results = await erc20ReadMulticall({
chainId: chain.chain_id,
Expand Down Expand Up @@ -221,93 +219,60 @@ let querySubscription: (() => void) | undefined
let lastData: Array<Array<BalanceData>> = []

export function allChainBalances(chains: Array<Chain>, addressStore: Readable<UserAddresses>) {
if (querySubscription) {
querySubscription()
querySubscription = undefined
}

lastData = Array(chains.length).fill([])
balanceStore.set(lastData)

const chainStores = chains.map((chain, chainIndex) => {
const store = createChainBalances(chain, addressStore)

const address = getAddressForChain(chain, get(addressStore))
if (!address) {
lastData[chainIndex] = []
balanceStore.set([...lastData])
return store
const clearAndResetData = () => {
if (querySubscription) {
querySubscription()
querySubscription = undefined
}
lastData = new Array(chains.length).fill([])
balanceStore.set(lastData)
}

querySubscription = createQueries({
queries: [
{
queryKey: ["balances", chain.chain_id, address],
queryFn: async () => {
try {
const balances = await getUserBalances(chain, address)
const initialBalances = get(store)

const mergedBalances = initialBalances.map(placeholder => {
const enriched = balances.find(
b => b.metadata.denom === placeholder.metadata.denom
)
return enriched || placeholder
})
return derived(
addressStore,
($addresses, set) => {
clearAndResetData()

const chainStores = chains.map((chain, chainIndex) => {
const address = getAddressForChain(chain, $addresses)
if (!address) {
lastData[chainIndex] = []
balanceStore.set([...lastData])
return []
}

balances.forEach(balance => {
if (!mergedBalances.some(b => b.metadata.denom === balance.metadata.denom)) {
mergedBalances.push(balance)
return getUserBalances(chain, address)
.then(balances => {
const sortedBalances = balances
.map(balance => ({
...balance,
balance: balance.balance === "Loading..." ? "0" : balance.balance,
metadata: {
...balance.metadata,
decimals: balance.metadata.decimals !== null ? balance.metadata.decimals : 18,
metadata_level: balance.metadata.metadata_level as "graphql" | "onchain" | "none"
}
}))
.sort((a, b) => {
const aValue = BigInt(a.balance) * BigInt(10 ** (18 - (a.metadata.decimals ?? 18)))
const bValue = BigInt(b.balance) * BigInt(10 ** (18 - (b.metadata.decimals ?? 18)))
return bValue > aValue ? 1 : -1
})

const sortedBalances = mergedBalances
.map(balance => ({
...balance,
balance: balance.balance === "Loading..." ? "0" : balance.balance,
metadata: {
...balance.metadata,
decimals: balance.metadata.decimals !== null ? balance.metadata.decimals : 18,
metadata_level: balance.metadata.metadata_level as "graphql" | "onchain" | "none"
}
}))
.sort((a, b) => {
const aValue =
BigInt(a.balance) * BigInt(10 ** (18 - (a.metadata.decimals ?? 18)))
const bValue =
BigInt(b.balance) * BigInt(10 ** (18 - (b.metadata.decimals ?? 18)))
return bValue > aValue ? 1 : -1
})

lastData[chainIndex] = sortedBalances
balanceStore.set([...lastData])
return sortedBalances

} catch (error) {
console.error("Error fetching balances:", error)
return get(store)
}
},
refetchInterval: 4000
}
]
}).subscribe(results => {
const queryResult = results[0] as QueryObserverResult<Array<BalanceData>, Error>
if (queryResult.data) {
lastData[chainIndex] = queryResult.data
balanceStore.set([...lastData])
}
})

return store
})
lastData[chainIndex] = sortedBalances
balanceStore.set([...lastData])
return sortedBalances
})
.catch(error => {
console.error("Error fetching balances:", error)
return []
})
})

return derived([addressStore, ...chainStores], ([$addresses, ...$chainStores]) => {
const hasAddress = chains.some(chain => getAddressForChain(chain, $addresses))
if (!hasAddress) {
lastData = Array(chains.length).fill([])
balanceStore.set(lastData)
}
return $chainStores
})
}
Promise.all(chainStores).then(results => {
set(results)
})
},
[] as Array<Array<BalanceData>>
)
}

0 comments on commit a70a04d

Please sign in to comment.