Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use new token data in transfer submit flow #3602

Merged
merged 21 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fd8bb06
refactor(app): simplify balance fetching
cor Jan 21, 2025
04eef29
feat(app): sort balances
cor Jan 21, 2025
d629399
feat(app): asset loading indicator
cor Jan 21, 2025
f314073
fix(app): asset reactivity bugs
cor Jan 21, 2025
2ed33dc
feat(typescript-sdk): wip new transfer submission
cor Jan 22, 2025
7e12ede
feat(app): improve schema fetching endpoint
cor Jan 22, 2025
420d2ec
feat(typescript-sdk): unwrap to existing quote tokens
cor Jan 22, 2025
5e6580d
feat(typescript-sdk): bidirectional token mappings
cor Jan 22, 2025
856bc77
feat(typescript-sdk): holesky-to-sepolia example
cor Jan 22, 2025
5ede5be
feat(typescript-sdk): improve safety
cor Jan 23, 2025
7afe73c
feat(typescript-sdk): transfer stargaze -> holesky
cor Jan 23, 2025
d170f74
feat(typescript-sdk): make new transfer funcs more symmetric
cor Jan 23, 2025
b2d7615
feat(typescript-sdk): improve transfer types
cor Jan 23, 2025
32889f9
refactor(typescript-sdk): rename transferassset to transferassetlegacy
cor Jan 23, 2025
ab7b143
refactor(typescript-sdk): rename transferAssetNew to transferAsset
cor Jan 23, 2025
fed5133
refactor(typescript-sdk): update TransferAssetParameter type names
cor Jan 23, 2025
e6836bc
refactor(typescript-sdk): playground name items
cor Jan 23, 2025
043c784
feat(typescript-sdk): add babylon to holesky
cor Jan 23, 2025
326644e
feat(typescript-sdk): add wip osmosis-to-holesky
cor Jan 23, 2025
40a9066
fix(typescript-sdk): bun error
cor Jan 23, 2025
85a579e
chore(typescript-sdk): spellcheck and fmt
cor Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
35 changes: 13 additions & 22 deletions app/src/lib/components/TransferFrom/transfer/intents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ 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 +33,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
Loading