Skip to content

Commit

Permalink
Use new token data in transfer submit flow (#3602)
Browse files Browse the repository at this point in the history
- **refactor(app): simplify balance fetching**
- **feat(app): sort balances**
- **feat(app): asset loading indicator**
- **fix(app): asset reactivity bugs**
  • Loading branch information
cor authored Jan 23, 2025
2 parents 27a0c7a + 85a579e commit 2844e86
Show file tree
Hide file tree
Showing 55 changed files with 14,426 additions and 437 deletions.
2 changes: 1 addition & 1 deletion app/app.nix
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ _: {
text = ''
${ensureAtRepositoryRoot}
cd app/
npx gql.tada generate-schema --tsconfig ./tsconfig.json --output "./src/generated/schema.graphql" "https://purple.graphql.union.build/v1/graphql"
npx gql.tada generate-schema --tsconfig ./tsconfig.json --output "./src/generated/schema.graphql" "https://staging.graphql.union.build/v1/graphql"
npx gql.tada generate-output --disable-preprocessing --tsconfig ./tsconfig.json --output ./src/generated/graphql-env.d.ts
'';
Expand Down
132 changes: 73 additions & 59 deletions app/src/generated/graphql-env.d.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import type { CubeFaces } from "$lib/components/TransferFrom/components/Cube/typ
import type { RawIntentsStore } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
import type { IntentsStore } from "$lib/components/TransferFrom/transfer/intents.ts"
import { derived, writable } from "svelte/store"
import Token from "$lib/components/token.svelte"
import type { Chain } from "$lib/types"
interface Props {
stores: {
Expand All @@ -16,39 +18,27 @@ interface Props {
rotateTo: (face: CubeFaces) => void
}
export let chains: Array<Chain>
export let stores: Props["stores"]
export let rotateTo: Props["rotateTo"]
let { rawIntents, intents } = stores
const showZeroBalances = writable(false)
$: filteredAssets = derived([intents, showZeroBalances], ([$intents, $showZeroBalances]) =>
$showZeroBalances
? $intents.sourceAssets
: $intents.sourceAssets.filter(asset => BigInt(asset.balance) > 0n)
let sortedTokens = derived([intents], ([$intents]) =>
$intents.baseTokens.toSorted((a, b) => Number(BigInt(b.balance) - BigInt(a.balance)))
)
function setAsset(denom: string) {
rawIntents.updateField("asset", denom)
rawIntents.set({ asset: denom })
rotateTo("intentFace")
}
function toggleZeroBalances() {
showZeroBalances.update(value => !value)
}
</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>
<button
class="border-2 h-6 w-6 flex items-center justify-center"
Expand All @@ -57,28 +47,14 @@ function toggleZeroBalances() {
</button>
</div>

{#if $filteredAssets.length}
<div class="flex-1 overflow-y-auto">
{#each $filteredAssets 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)}
>
<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"}>
{formatUnits(BigInt(asset.balance), asset.metadata.decimals ?? 0)}
</p>
</Button>
</div>
{/each}
</div>
{:else}
<div class="px-4 p-2">
<p>No spendable balances</p>
</div>
{/if}
</div>
<div class="flex flex-col overflow-y-auto">
{#each $sortedTokens as token}
<button
class="px-2 py-1 hover:bg-neutral-400 dark:hover:bg-neutral-800 text-md flex justify-start items-center"
on:click={() => setAsset(token.denom)}
>
<Token chainId={$rawIntents.source} denom={token.denom} amount={token.balance} {chains}/>
</button>
{/each}
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ let { rawIntents, intents, validation } = stores
minlength={1}
maxlength={64}
required={true}
disabled={!$intents.selectedAsset?.metadata.denom}
disabled={!$rawIntents.asset}
autocorrect="off"
placeholder="0.00"
spellcheck="false"
Expand Down Expand Up @@ -86,4 +86,4 @@ let { rawIntents, intents, validation } = stores
on:click={() => rotateTo("verifyFace")}>Transfer
</Button>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ $: translateZ = width / 2

<div class="h-screen w-full flex items-center justify-center perspective-[2000px]">
<div
class="relative transform-style-preserve-3d transition-transform duration-1000"
class="relative transform-style-preserve-3d transition-transform duration-500"
style={`width: ${width}px; height: ${height}px; transform: rotateX(${currentRotation.x}deg) rotateY(${currentRotation.y}deg)`}
>
<FaceWrapper {width} {height} {translateZ} visible rotateY={"0deg"}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { truncate } from "$lib/utilities/format.ts"
import type { IntentsStore } from "$lib/components/TransferFrom/transfer/intents.ts"
import type { ValidationStore } from "$lib/components/TransferFrom/transfer/validation.ts"
import type { RawIntentsStore } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
import Token from "$lib/components/token.svelte"
interface Props {
rawIntents: RawIntentsStore
Expand All @@ -28,15 +29,13 @@ export let onSelectAsset: Props["onSelectAsset"]
class="border-2 font-bold"
on:click={onSelectAsset}
>
{#if $intents.selectedAsset}
{truncate($intents.selectedAsset.metadata.display_symbol || $intents.selectedAsset.metadata.denom, 18)}
{:else if $rawIntents.asset}
{truncate($rawIntents.asset, 6)}
{#if $rawIntents.asset && $intents.sourceChain}
<Token chains={$intents.chains} chainId={$intents.sourceChain.chain_id} denom={$rawIntents.asset}/>
{:else}
Select Asset
{/if}
</Button>
{#if $validation.errors.asset}
<p class="text-red-500 text-sm">{$validation.errors.asset}</p>
{/if}
</div>
</div>
10 changes: 4 additions & 6 deletions app/src/lib/components/TransferFrom/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import { userAddress, balanceStore } from "$lib/components/TransferFrom/transfer
export let chains: Array<Chain>
$: userBalancesQueries = userBalancesQuery({ chains, userAddr: $userAddress, connected: true })
$: balanceStore.set($userBalancesQueries.map(query => query.data || []))
const stores = createTransferStore(chains)
let balances = userBalancesQuery({ chains, userAddr: $userAddress })
const stores = createTransferStore(chains, balances)
</script>

<Cube>
Expand All @@ -33,7 +31,7 @@ const stores = createTransferStore(chains)
</div>

<div slot="assets" let:rotateTo class="w-full h-full">
<Assets {stores} {rotateTo}/>
<Assets {stores} {chains} {rotateTo}/>
</div>

<div slot="transfer" let:rotateTo class="w-full h-full">
Expand All @@ -45,4 +43,4 @@ const stores = createTransferStore(chains)
{#if TRANSFER_DEBUG}
<DebugBox {stores}/>
{/if}
</div>
</div>
5 changes: 3 additions & 2 deletions app/src/lib/components/TransferFrom/transfer/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { UserAddresses } from "$lib/types.ts"
import { userAddrCosmos } from "$lib/wallet/cosmos"
import { userAddrEvm } from "$lib/wallet/evm"
import { userAddressAptos } from "$lib/wallet/aptos"
import type { BalanceData } from "$lib/queries/balance"
import { derived, type Readable, writable } from "svelte/store"

export let userAddress: Readable<UserAddresses> = derived(
Expand All @@ -14,4 +13,6 @@ export let userAddress: Readable<UserAddresses> = derived(
})
)

export const balanceStore = writable<Array<Array<BalanceData>>>([])
export const balanceStore = writable<Array<{ chain_id: string; balances: Record<string, string> }>>(
[]
)
38 changes: 8 additions & 30 deletions app/src/lib/components/TransferFrom/transfer/context.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { derived, type Readable } from "svelte/store"
import type { Chain, UserAddresses } from "$lib/types"
import { balanceStore, userAddress } from "./balances.ts"
import type { BalanceData } from "$lib/queries/balance"
import { userAddress } from "./balances.ts"
import type { BalanceData, userBalancesQuery } from "$lib/queries/balance"
import type { UnwrapReadable } from "$lib/utilities/types.ts"

export type ChainBalances = {
chainId: string
Expand All @@ -13,36 +14,13 @@ export type BalancesList = Array<ChainBalances>
export interface ContextStore {
chains: Array<Chain>
userAddress: UserAddresses
balances: BalancesList
balances: UnwrapReadable<ReturnType<typeof userBalancesQuery>>
}

export function createContextStore(chains: Array<Chain>): Readable<ContextStore> {
const balances = derived(balanceStore, ($rawBalances: Array<Array<BalanceData>>) => {
console.log("context", $rawBalances)
if ($rawBalances?.length === 0) {
return chains.map(chain => ({
chainId: chain.chain_id,
balances: []
}))
}

return chains.map((chain, chainIndex) => {
const chainBalances = $rawBalances[chainIndex]

if (!chainBalances || chainBalances.length === 0) {
return {
chainId: chain.chain_id,
balances: []
}
}

return {
chainId: chain.chain_id,
balances: chainBalances
}
})
}) as Readable<BalancesList>

export function createContextStore(
chains: Array<Chain>,
balances: ReturnType<typeof userBalancesQuery>
): Readable<ContextStore> {
return derived([userAddress, balances], ([$userAddress, $balances]) => ({
chains,
userAddress: $userAddress,
Expand Down
8 changes: 6 additions & 2 deletions app/src/lib/components/TransferFrom/transfer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type ValidationStore
} from "$lib/components/TransferFrom/transfer/validation.ts"
import type { Chain } from "$lib/types"
import type { userBalancesQuery } from "$lib/queries/balance/index.ts"

export interface TransferStore {
rawIntents: RawIntentsStore
Expand All @@ -21,9 +22,12 @@ export interface TransferStore {
validation: Readable<ValidationStore>
}

export function createTransferStore(chains: Array<Chain>): TransferStore {
export function createTransferStore(
chains: Array<Chain>,
balances: ReturnType<typeof userBalancesQuery>
): TransferStore {
const rawIntents = createRawIntentsStore()
const context = createContextStore(chains)
const context = createContextStore(chains, balances)
const intents = createIntentStore(rawIntents, context)
const validation = createValidationStore(rawIntents, intents, context)

Expand Down
37 changes: 13 additions & 24 deletions app/src/lib/components/TransferFrom/transfer/intents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ import { derived, type Readable } from "svelte/store"
import type { Chain } from "$lib/types"
import type { RawIntentsStore } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
import type { ContextStore } from "$lib/components/TransferFrom/transfer/context.ts"
import { showUnsupported } from "$lib/stores/user.ts"
import { get } from "svelte/store"
import type { BalanceData } from "$lib/queries/balance"

export type AssetListItem = BalanceData & {
sourceChain: Chain
}

export type SelectedAsset = BalanceData | null

export interface IntentsStore {
chains: Array<Chain>
sourceChain: Chain | null
destinationChain: Chain | null
selectedAsset: SelectedAsset
sourceAssets: Array<AssetListItem>
baseTokens: Array<{ denom: string; balance: string }>
receiver: string
amount: string
}
Expand All @@ -35,31 +31,24 @@ export function createIntentStore(
$context.chains.find(chain => chain.chain_id === $intents.destination) ?? null
)

const sourceAssets = derived([context, sourceChain], ([$context, $sourceChain]) => {
const baseTokens = derived([context, sourceChain], ([$context, $sourceChain]) => {
if (!$sourceChain) return []
let balances = $context.balances.find(c => c.data?.chain_id === $sourceChain.chain_id)
let baseTokens = $sourceChain.tokens.map(token => ({
denom: token.denom,
balance: balances?.data?.balances[token.denom] ?? "0"
}))

const chainBalances =
$context.balances.find(chain => chain.chainId === $sourceChain.chain_id)?.balances || []

return chainBalances
.filter(balance => get(showUnsupported) || balance.metadata.metadata_level !== "none")
.map(balance => ({
...balance,
sourceChain: $sourceChain
}))
})

const selectedAsset = derived([sourceAssets, rawIntents], ([$sourceAssets, $rawIntents]) => {
return $sourceAssets.find(x => x.metadata.denom === $rawIntents.asset) ?? null
return baseTokens
})

return derived(
[sourceChain, destinationChain, selectedAsset, sourceAssets, rawIntents],
([$sourceChain, $destinationChain, $selectedAsset, $sourceAssets, $rawIntents]) => ({
[sourceChain, destinationChain, baseTokens, rawIntents, context],
([$sourceChain, $destinationChain, $baseTokens, $rawIntents, $context]) => ({
chains: $context.chains,
sourceChain: $sourceChain,
destinationChain: $destinationChain,
selectedAsset: $selectedAsset,
sourceAssets: $sourceAssets,
baseTokens: $baseTokens,
receiver: $rawIntents.receiver,
amount: $rawIntents.amount
})
Expand Down
10 changes: 6 additions & 4 deletions app/src/lib/components/token.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import { toDisplayName } from "$lib/utilities/chains.ts"
import { formatUnits } from "viem"
import { onMount } from "svelte"
import { tokenInfoQuery } from "$lib/queries/tokens"
import LoadingDots from "./loading-dots.svelte"
export let chains: Array<Chain>
export let chainId: string
export let denom: string
export let amount: string | number | bigint | null = null
export let expanded = false
let chain = chains.find(c => c.chain_id === chainId) ?? null
let graphqlToken = chain?.tokens.find(t => t.denom === denom) ?? null
let tokenInfo = tokenInfoQuery(chainId, denom, chains)
$: chain = chains.find(c => c.chain_id === chainId) ?? null
$: graphqlToken = chain?.tokens.find(t => t.denom === denom) ?? null
$: tokenInfo = tokenInfoQuery(chainId, denom, chains)
</script>

{#if $tokenInfo.data}
Expand Down Expand Up @@ -66,4 +66,6 @@ let tokenInfo = tokenInfoQuery(chainId, denom, chains)
</div>
{/if}
</div>
{:else}
<div class="flex max-h-auto overflow-hidden text-muted-foreground"><div class="relative w-12 h-4"><LoadingDots class="absolute -top-4 size-12 h-12 w-12"/></div> <Truncate value={denom} type="address"/></div>
{/if}
Loading

0 comments on commit 2844e86

Please sign in to comment.