Skip to content

Commit 9371deb

Browse files
authored
feat(app): add persisted cache to quotetoken (#3822)
we key this on `${$source}-${$asset}-${$destination}`
2 parents e0efb2c + c78612c commit 9371deb

File tree

5 files changed

+108
-66
lines changed

5 files changed

+108
-66
lines changed

app/src/lib/components/TransferFrom/components/Cube/faces/Intent.svelte

+18-9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Address from "$lib/components/address.svelte"
1212
import type { Intents } from "$lib/components/TransferFrom/transfer/types.ts"
1313
import type { Chain } from "$lib/types.ts"
1414
import TokenBalance from "$lib/components/TransferFrom/components/TokenBalance.svelte"
15+
import InlineLoadingDots from "$lib/components/InlineLoadingDots.svelte"
1516
1617
interface Props {
1718
rawIntents: RawIntentsStore
@@ -26,6 +27,7 @@ export let intents: Props["intents"]
2627
export let validation: Props["validation"]
2728
export let chains: Props["chains"]
2829
export let rotateTo: Props["rotateTo"]
30+
$: console.log(intents.quoteToken)
2931
</script>
3032

3133
<div class="flex flex-col w-full h-full">
@@ -105,23 +107,28 @@ export let rotateTo: Props["rotateTo"]
105107
<div>
106108
{#if !intents.channel}
107109
<div class="flex justify-center">
108-
<p class="text-xs text-center max-w-[230px]">No recommended UCS03 channel to go from {toDisplayName($rawIntents.source, chains)}
109-
to {toDisplayName($rawIntents.destination, chains)}</p>
110+
<p class="text-xs text-center max-w-[230px]">
111+
No recommended UCS03 channel to go from {toDisplayName($rawIntents.source, chains)}
112+
to {toDisplayName($rawIntents.destination, chains)}
113+
</p>
110114
</div>
111115
{:else}
112116
<div class="flex flex-col gap-1 justify-end items-center">
113-
<div class="flex gap-4 text-muted-foreground text-xs">{intents.channel.source_connection_id}
114-
| {intents.channel.source_channel_id}
115-
<ArrowRightIcon/>{intents.channel.destination_connection_id} | {intents.channel.destination_channel_id}
117+
<div class="flex gap-4 text-muted-foreground text-xs">
118+
{intents.channel.source_connection_id} | {intents.channel.source_channel_id}
119+
<ArrowRightIcon /> {intents.channel.destination_connection_id} | {intents.channel.destination_channel_id}
116120
</div>
121+
117122
{#if !$rawIntents.asset}
118123
<p class="text-xs">Select an asset</p>
119124
{:else if !$rawIntents.source || !$rawIntents.destination}
120125
<p class="text-xs">Select source and destination</p>
121-
{:else if validation.args === "NO_QUOTE_AVAILABLE"}
122-
<div class="text-xs text-center">No Quote Token available for this transfer. Sending new assets to Cosmos is
123-
currently not supported and will be enabled in an update soon.
126+
{:else if intents.quoteToken === "NO_QUOTE_AVAILABLE"}
127+
<div class="text-xs text-center">
128+
No Quote Token available for this transfer. Sending new assets to Cosmos is currently not supported and will be enabled in an update soon.
124129
</div>
130+
{:else if intents.quoteToken === "QUOTE_LOADING"}
131+
<InlineLoadingDots />
125132
{:else if intents.quoteToken}
126133
<div class="flex-1 flex flex-col items-center text-xs">
127134
<Token chainId={$rawIntents.destination} denom={intents.quoteToken} {chains}/>
@@ -132,8 +139,10 @@ export let rotateTo: Props["rotateTo"]
132139
{/if}
133140
</div>
134141
{/if}
135-
<Button class="w-full mt-2" disabled={!validation.isValid} on:click={() => rotateTo("verifyFace")}>Transfer
142+
<Button class="w-full mt-2" disabled={!validation.isValid} on:click={() => rotateTo("verifyFace")}>
143+
Transfer
136144
</Button>
137145
</div>
146+
138147
</div>
139148
</div>

app/src/lib/components/TransferFrom/index.svelte

+41-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Assets from "$lib/components/TransferFrom/components/Cube/faces/Assets.sv
55
import Transfer from "$lib/components/TransferFrom/components/Cube/faces/Transfer.svelte"
66
import Cube from "$lib/components/TransferFrom/components/Cube/index.svelte"
77
import type { Chain, Ucs03Channel } from "$lib/types.ts"
8-
import { derived } from "svelte/store"
8+
import { derived, get, type Readable } from "svelte/store"
99
import { getChannelInfo, getQuoteToken } from "@unionlabs/client"
1010
import { createRawIntentsStore } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
1111
import { userAddrCosmos } from "$lib/wallet/cosmos"
@@ -17,6 +17,12 @@ import { balances } from "$lib/stores/balances.ts"
1717
import { tokenInfos } from "$lib/stores/tokens.ts"
1818
import { onMount } from "svelte"
1919
import { queryBalances } from "$lib/stores/balances.ts"
20+
import type {
21+
DerivedSource,
22+
Nullable,
23+
QuoteData
24+
} from "$lib/components/TransferFrom/transfer/types.ts"
25+
import { persisted } from "svelte-persisted-store"
2026
2127
export let chains: Array<Chain>
2228
export let ucs03channels: Array<Ucs03Channel>
@@ -32,7 +38,11 @@ const userAddress = derived(
3238
})
3339
)
3440
35-
const quoteToken = derived(
41+
const quoteResults = persisted<Record<string, QuoteData | null>>("quote-results", {})
42+
const quoteToken: Readable<Nullable<QuoteData>> = derived<
43+
[DerivedSource, DerivedSource, DerivedSource],
44+
Nullable<QuoteData>
45+
>(
3646
[
3747
derived(rawIntents, $r => $r.source),
3848
derived(rawIntents, $r => $r.asset),
@@ -41,22 +51,43 @@ const quoteToken = derived(
4151
([$source, $asset, $destination], set) => {
4252
set(null)
4353
44-
if (!($source && $asset && ucs03channels)) {
45-
return
46-
}
54+
if (!($source && $asset && $destination && ucs03channels)) return
4755
4856
const channel = getChannelInfo($source, $destination, ucs03channels)
49-
if (!channel) {
57+
if (!channel) return
58+
59+
const sourceChain = chains.find(c => c.chain_id === $source)
60+
if (!sourceChain) {
61+
console.error("Invalid source chain in quote token calculation")
5062
return
5163
}
52-
const sourceChain = chains.find(c => c.chain_id === $source)
5364
54-
if (!sourceChain) {
55-
console.error("invalid source chain in quote token calculation")
65+
const cacheKey = `${$source}-${JSON.stringify(channel)}-${$asset}-v1`
66+
const cachedResult = get(quoteResults)[cacheKey] ?? null
67+
68+
if (cachedResult) {
69+
set(cachedResult)
5670
return
5771
}
5872
59-
getQuoteToken($source, $asset, channel).then(quote => set(quote))
73+
set({ type: "QUOTE_LOADING" })
74+
75+
//@ts-ignore
76+
getQuoteToken($source, $asset, channel)
77+
.then(result => {
78+
if (result.isOk()) {
79+
const quoteData = result.value
80+
quoteResults.update(store => ({ ...store, [cacheKey]: quoteData }))
81+
set(quoteData)
82+
} else {
83+
console.error("Error fetching quote token:", result.error)
84+
set(null)
85+
}
86+
})
87+
.catch(err => {
88+
console.error("Unexpected error in getQuoteToken:", err)
89+
set(null)
90+
})
6091
},
6192
null
6293
)

app/src/lib/components/TransferFrom/transfer/intents.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import type { Chain, Ucs03Channel, UserAddresses } from "$lib/types"
22
import type { FormFields } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
33
import { fromHex } from "viem"
44
import { bech32ToBech32Address, getChannelInfo } from "@unionlabs/client"
5-
import type { Intents, QuoteResponse } from "$lib/components/TransferFrom/transfer/types.ts"
5+
import type { Intents, QuoteData } from "$lib/components/TransferFrom/transfer/types.ts"
66
import type { Balances } from "$lib/stores/balances.ts"
7-
import type { Result } from "neverthrow"
87
import type { TokenInfos } from "$lib/stores/tokens.ts"
98

109
export const createIntents = (
@@ -14,7 +13,7 @@ export const createIntents = (
1413
chains: Array<Chain>,
1514
ucs03channels: Array<Ucs03Channel>,
1615
tokenInfos: TokenInfos,
17-
quoteToken: Result<QuoteResponse, Error> | null
16+
quoteToken: QuoteData | null
1817
): Intents => {
1918
// Source Chain
2019
const sourceChain = chains.find(chain => chain.chain_id === rawIntents.source) ?? null
@@ -79,13 +78,12 @@ export const createIntents = (
7978
console.log(`[QuoteToken] is null`)
8079
}
8180

82-
const quoteTokenDenom = quoteToken
83-
? quoteToken.isErr()
84-
? null
85-
: quoteToken.value.type === "NO_QUOTE_AVAILABLE"
81+
const quoteTokenDenom =
82+
quoteToken?.type === "QUOTE_LOADING"
83+
? "QUOTE_LOADING"
84+
: quoteToken?.type === "NO_QUOTE_AVAILABLE"
8685
? "NO_QUOTE_AVAILABLE"
87-
: quoteToken.value.quote_token
88-
: null
86+
: (quoteToken?.quote_token ?? null)
8987

9088
console.log(
9189
`[QuoteToken] quote for ${baseToken?.denom} from ${sourceChain?.chain_id} -> ${destinationChain?.chain_id}:`,

app/src/lib/components/TransferFrom/transfer/types.ts

+10-24
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { getChannelInfo } from "@unionlabs/client"
2-
import type { Chain, UserAddresses } from "$lib/types.ts"
3-
import type { FormFields } from "$lib/components/TransferFrom/transfer/raw-intents.ts"
4-
import type { Result } from "neverthrow"
2+
import type { Chain } from "$lib/types.ts"
53
import type { Balance } from "$lib/stores/balances"
4+
import type { Readable } from "svelte/store"
65

76
export type TransferArgs =
87
| {
@@ -22,11 +21,6 @@ export type TransferContext = {
2221
destinationChain: Chain
2322
}
2423

25-
export type BaseToken = {
26-
denom: string
27-
balance: string
28-
}
29-
3024
export interface Intents {
3125
sourceChain: Chain | null
3226
destinationChain: Chain | null
@@ -47,22 +41,6 @@ export interface TokenInfo {
4741
}
4842
}
4943

50-
// Add this interface to represent the balances structure
51-
export interface ChainBalance {
52-
data?: {
53-
chain_id: string
54-
balances: Record<string, string>
55-
}
56-
}
57-
58-
export type FieldErrors = Partial<Record<keyof FormFields, string>>
59-
60-
export interface ValidationContext {
61-
userAddress: UserAddresses
62-
baseTokenInfo?: TokenInfo | null
63-
quoteToken: Result<QuoteResponse, Error> | null
64-
}
65-
6644
export type QuoteResponse =
6745
| {
6846
quote_token: string
@@ -71,3 +49,11 @@ export type QuoteResponse =
7149
| {
7250
type: "NO_QUOTE_AVAILABLE"
7351
}
52+
53+
export type Nullable<T> = T | null
54+
export type DerivedSource = Readable<Nullable<string>>
55+
export type QuoteTokenType = "UNWRAPPED" | "NEW_WRAPPED" | "NO_QUOTE_AVAILABLE"
56+
export type QuoteData =
57+
| { quote_token: string; type: Extract<QuoteTokenType, "UNWRAPPED" | "NEW_WRAPPED"> }
58+
| { type: Extract<QuoteTokenType, "NO_QUOTE_AVAILABLE"> }
59+
| { type: "QUOTE_LOADING" }

0 commit comments

Comments
 (0)