1+ import { formatUnits , parseUnits } from "viem" ;
12import { apiAtom } from "@/lib/atoms" ;
23import { PvqProgram } from "@open-web3/pvq" ;
34import { atom } from "jotai" ;
45import { atomWithQuery } from "jotai-tanstack-query" ;
56import { guestSwapInfoProgram } from "./assets/guest-swap-info" ;
67import metadata from "./assets/guest-swap-info-metadata.json" ;
8+ import type { Bytes , Vec , Option , u128 } from "@polkadot/types" ;
9+ import type { Codec , ITuple } from "@polkadot/types/types" ;
10+ import { compactStripLength , u8aToString } from "@polkadot/util" ;
11+ import { atomFamily } from "jotai/utils" ;
712
813export const lpProgramAtom = atom < PvqProgram | null > ( ( get ) => {
914 const api = get ( apiAtom ) ;
@@ -16,27 +21,292 @@ export const poolListAtom = atomWithQuery((get) => ({
1621 queryFn : async ( ) => {
1722 const program = get ( lpProgramAtom ) ;
1823 if ( ! program ) throw new Error ( "Program not initialized" ) ;
19- const lpList = await program . executeQuery (
24+ const lpList = await program . executeQuery < Vec < ITuple < [ Bytes , Bytes ] > > > (
2025 "entrypoint_list_pools" ,
2126 undefined ,
2227 [ ]
2328 ) ;
24- return lpList ;
29+ return lpList . map (
30+ ( [ assetId1 , assetId2 ] ) => [ assetId1 . toHex ( ) , assetId2 . toHex ( ) ] as const
31+ ) ;
2532 } ,
2633 enabled : ! ! get ( lpProgramAtom ) ,
2734} ) ) ;
2835
29- export const assetsInfoAtom = atomWithQuery ( ( get ) => ( {
30- queryKey : [ "assetsInfo" ] ,
31- queryFn : async ( ) => {
32- const program = get ( lpProgramAtom ) ;
36+ export const assetsInfoAtom = atomWithQuery ( ( get ) => {
37+ const { data : poolList , isFetched } = get ( poolListAtom ) ;
38+ const program = get ( lpProgramAtom ) ;
39+ const assetIds = [
40+ ...new Set (
41+ poolList ?. flatMap ( ( [ assetId1 , assetId2 ] ) => [ assetId1 , assetId2 ] )
42+ ) ,
43+ ] ;
44+
45+ return {
46+ queryKey : [ "assetsInfo" , assetIds ] ,
47+ queryFn : async ( ) : Promise <
48+ {
49+ assetId : string ;
50+ decimals : number ;
51+ name : string ;
52+ symbol : string ;
53+ } [ ]
54+ > => {
55+ if ( ! program ) throw new Error ( "Program not initialized" ) ;
56+ const result = await Promise . all (
57+ assetIds !
58+ . filter ( ( assetId ) => assetId !== "0x040d000000" )
59+ . map ( async ( assetId ) => {
60+ // console.log("assetId1", assetId);
61+
62+ const res = await program . executeQuery < Option < Codec > > (
63+ "entrypoint_asset_info" ,
64+ undefined ,
65+ [ assetId ]
66+ ) ;
67+
68+ if ( res . isEmpty ) return null ;
69+
70+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71+ const asset : any = res . unwrap ( ) ;
72+ const [ , name ] = compactStripLength ( asset . name ) ;
73+ const [ , symbol ] = compactStripLength ( asset . symbol ) ;
74+ return {
75+ assetId : asset . assetId . toHex ( ) as string ,
76+ decimals : asset . decimals . toNumber ( ) as number ,
77+ name : u8aToString ( name ) ,
78+ symbol : u8aToString ( symbol ) ,
79+ } ;
80+ } )
81+ ) ;
82+
83+ return result
84+ . concat ( {
85+ assetId : "0x040d000000" ,
86+ decimals : 10 ,
87+ name : "lcDOT" ,
88+ symbol : "lcDOT" ,
89+ } )
90+ . filter ( Boolean ) as {
91+ assetId : string ;
92+ decimals : number ;
93+ name : string ;
94+ symbol : string ;
95+ } [ ] ;
96+ } ,
97+ enabled : isFetched && ! ! program ,
98+ } ;
99+ } ) ;
100+
101+ export const assetsInfoFamily = atomFamily ( ( id : string ) =>
102+ atom ( ( get ) => {
103+ const { data : assetsInfo } = get ( assetsInfoAtom ) ;
104+
105+ return assetsInfo ?. find ( ( asset ) => asset . assetId === id ) ;
106+ } )
107+ ) ;
108+
109+ export const poolListDetailAtom = atomWithQuery ( ( get ) => {
110+ const { data : poolList , isFetched : isPoolListFetched } = get ( poolListAtom ) ;
111+ const { data : assetsInfo , isFetched : isAssetsInfoFetched } =
112+ get ( assetsInfoAtom ) ;
113+
114+ return {
115+ queryKey : [ "poolListDetail" ] ,
116+ queryFn : async ( ) => {
117+ console . log ( "poolList" , poolList , isPoolListFetched , isAssetsInfoFetched ) ;
118+ return poolList ?. map ( ( [ assetId1 , assetId2 ] ) => {
119+ const asset1 = assetsInfo ?. find ( ( asset ) => asset . assetId === assetId1 ) ;
120+ const asset2 = assetsInfo ?. find ( ( asset ) => asset . assetId === assetId2 ) ;
121+ return {
122+ asset1 : asset1 ! ,
123+ asset2 : asset2 ! ,
124+ key : `${ assetId1 } -${ assetId2 } ` ,
125+ } ;
126+ } ) ;
127+ } ,
128+ enabled : isPoolListFetched && isAssetsInfoFetched ,
129+ } ;
130+ } ) ;
131+
132+ export const selectedPoolAtom = atom < string | null > ( null ) ;
133+ export const selectedPoolInfoAtom = atom ( ( get ) => {
134+ const selectedPool = get ( selectedPoolAtom ) ;
135+ const { data : poolListDetail } = get ( poolListDetailAtom ) ;
136+ return poolListDetail ?. find ( ( pool ) => pool . key === selectedPool ) ;
137+ } ) ;
138+
139+ export const poolSizeAtom = atomWithQuery ( ( get ) => {
140+ const selectedPool = get ( selectedPoolInfoAtom ) ;
141+ const program = get ( lpProgramAtom ) ;
142+
143+ return {
144+ queryKey : [ "poolSize" , selectedPool ] ,
145+ queryFn : async ( ) => {
146+ if ( ! program ) throw new Error ( "Program not initialized" ) ;
147+ if ( ! selectedPool ) throw new Error ( "selectedPool is not set" ) ;
148+ console . log ( "querying pool size" , selectedPool ) ;
149+ const poolSize = await program . executeQuery < Option < ITuple < [ u128 , u128 ] > > > (
150+ "entrypoint_get_liquidity_pool" ,
151+ undefined ,
152+ [ selectedPool . asset1 . assetId , selectedPool . asset2 . assetId ]
153+ ) ;
154+ if ( poolSize . isEmpty ) return ;
155+ const [ asset1Amount , asset2Amount ] = poolSize . unwrap ( ) ;
156+ const asset1AmountFormatted = formatUnits (
157+ asset1Amount . toBigInt ( ) ,
158+ selectedPool . asset1 . decimals
159+ ) ;
160+ const asset2AmountFormatted = formatUnits (
161+ asset2Amount . toBigInt ( ) ,
162+ selectedPool . asset2 . decimals
163+ ) ;
164+ return {
165+ size : [ asset1Amount . toBigInt ( ) , asset2Amount . toBigInt ( ) ] ,
166+ asset1 : selectedPool . asset1 ,
167+ asset2 : selectedPool . asset2 ,
168+ asset1Amount : asset1AmountFormatted ,
169+ asset2Amount : asset2AmountFormatted ,
170+ price : Number (
171+ (
172+ Number ( asset2AmountFormatted ) / Number ( asset1AmountFormatted )
173+ ) . toFixed ( 4 )
174+ ) ,
175+ } ;
176+ } ,
177+ enabled : ! ! selectedPool && ! ! program ,
178+ } ;
179+ } ) ;
180+
181+ export const getQuoteAmountAtom = atom ( ( get ) => {
182+ const program = get ( lpProgramAtom ) ;
183+
184+ return async ( baseToken : string , quoteToken : string , baseAmount : string ) => {
33185 if ( ! program ) throw new Error ( "Program not initialized" ) ;
34- const assetsInfo = await program . executeQuery (
35- "entrypoint_asset_info" ,
186+
187+ const quoteTokenDecimals = get ( assetsInfoFamily ( quoteToken ) ) ?. decimals ;
188+ const baseTokenDecimals = get ( assetsInfoFamily ( baseToken ) ) ?. decimals ;
189+ if ( ! quoteTokenDecimals || ! baseTokenDecimals )
190+ throw new Error ( "quoteTokenDecimals or baseTokenDecimals is not set" ) ;
191+
192+ const quoteAmount = await program . executeQuery < Option < u128 > > (
193+ "entrypoint_quote_price_exact_tokens_for_tokens" ,
36194 undefined ,
37- [ ]
195+ [ baseToken , quoteToken , parseUnits ( baseAmount , baseTokenDecimals ) ]
38196 ) ;
39- return assetsInfo ;
40- } ,
41- enabled : ! ! get ( lpProgramAtom ) ,
42- } ) ) ;
197+
198+ console . log (
199+ `Get ${ quoteToken } amount:` ,
200+ baseToken ,
201+ quoteToken ,
202+ baseAmount ,
203+ quoteAmount . toHuman ( )
204+ ) ;
205+
206+ if ( quoteAmount . isEmpty ) return "0" ;
207+ return formatUnits (
208+ quoteAmount . unwrap ( ) . toBigInt ( ) as bigint ,
209+ quoteTokenDecimals
210+ ) ;
211+ } ;
212+ } ) ;
213+
214+ export const getBaseAmountAtom = atom ( ( get ) => {
215+ const program = get ( lpProgramAtom ) ;
216+
217+ return async ( baseToken : string , quoteToken : string , quoteAmount : string ) => {
218+ if ( ! program ) throw new Error ( "Program not initialized" ) ;
219+
220+ const quoteTokenDecimals = get ( assetsInfoFamily ( quoteToken ) ) ?. decimals ;
221+ const baseTokenDecimals = get ( assetsInfoFamily ( baseToken ) ) ?. decimals ;
222+ if ( ! quoteTokenDecimals || ! baseTokenDecimals )
223+ throw new Error ( "quoteTokenDecimals or baseTokenDecimals is not set" ) ;
224+
225+ const baseAmount = await program . executeQuery < Option < u128 > > (
226+ "entrypoint_quote_price_tokens_for_exact_tokens" ,
227+ undefined ,
228+ [ baseToken , quoteToken , parseUnits ( quoteAmount , quoteTokenDecimals ) ]
229+ ) ;
230+
231+ console . log (
232+ `Get ${ baseToken } amount:` ,
233+ baseToken ,
234+ quoteToken ,
235+ quoteAmount ,
236+ baseAmount . toHuman ( )
237+ ) ;
238+
239+ if ( baseAmount . isEmpty ) return "0" ;
240+
241+ return formatUnits (
242+ baseAmount . unwrap ( ) . toBigInt ( ) as bigint ,
243+ baseTokenDecimals
244+ ) ;
245+ } ;
246+ } ) ;
247+
248+ export const getReceiveAmountAtom = atom ( ( get ) => {
249+ return async (
250+ sellToken : string ,
251+ buyToken : string ,
252+ amount : string ,
253+ isSell : boolean
254+ ) => {
255+ const selectedPoolInfo = get ( selectedPoolInfoAtom ) ;
256+ const { refetch } = get ( poolSizeAtom ) ;
257+ const { data : poolSize } = await refetch ( ) ;
258+ if ( ! poolSize ?. size ) throw new Error ( "poolSize is not set" ) ;
259+ if ( ! selectedPoolInfo ) throw new Error ( "selectedPoolInfo is not set" ) ;
260+ const baseAssetId = selectedPoolInfo . asset1 . assetId ;
261+ const quoteAssetId = selectedPoolInfo . asset2 . assetId ;
262+ const isSwaped =
263+ sellToken === baseAssetId && buyToken === quoteAssetId
264+ ? false
265+ : sellToken === quoteAssetId && buyToken === baseAssetId
266+ ? true
267+ : undefined ;
268+
269+ const parsedAmount = parseUnits (
270+ amount ,
271+ ! isSwaped
272+ ? isSell
273+ ? selectedPoolInfo . asset1 . decimals
274+ : selectedPoolInfo . asset2 . decimals
275+ : isSell
276+ ? selectedPoolInfo . asset2 . decimals
277+ : selectedPoolInfo . asset1 . decimals
278+ ) ;
279+ const receiveDecimals = ! isSwaped
280+ ? isSell
281+ ? selectedPoolInfo . asset2 . decimals
282+ : selectedPoolInfo . asset1 . decimals
283+ : isSell
284+ ? selectedPoolInfo . asset1 . decimals
285+ : selectedPoolInfo . asset2 . decimals ;
286+
287+ if ( parsedAmount <= BigInt ( 0 ) ) return "0" ;
288+
289+ const isBaseToQuote =
290+ sellToken === baseAssetId && buyToken === quoteAssetId ;
291+ const isQuoteToBase =
292+ sellToken === quoteAssetId && buyToken === baseAssetId ;
293+
294+ if ( ! isBaseToQuote && ! isQuoteToBase ) {
295+ throw new Error ( "Token pair does not match the pool" ) ;
296+ }
297+
298+ const reserveIn = isBaseToQuote ? poolSize ?. size [ 0 ] : poolSize ?. size [ 1 ] ;
299+ const reserveOut = isBaseToQuote ? poolSize ?. size [ 1 ] : poolSize ?. size [ 0 ] ;
300+
301+ if ( isSell ) {
302+ const dx = parsedAmount ;
303+ const dy = ( dx * reserveOut ) / ( reserveIn + dx ) ;
304+ return formatUnits ( dy , receiveDecimals ) ;
305+ } else {
306+ const dy = parsedAmount ;
307+ if ( dy >= reserveOut ) return "Infinity" ; // 超出池子
308+ const dx = ( dy * reserveIn ) / ( reserveOut - dy ) ;
309+ return formatUnits ( dx , receiveDecimals ) ;
310+ }
311+ } ;
312+ } ) ;
0 commit comments