@exponent-labs/exponent-sdk 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/EventDecoderV2.d.ts +31 -0
- package/build/EventDecoderV2.js +76 -0
- package/build/EventDecoderV2.js.map +1 -0
- package/build/addressLookupTableUtil.d.ts +17 -1
- package/build/addressLookupTableUtil.js +35 -1
- package/build/addressLookupTableUtil.js.map +1 -1
- package/build/clmm/events.d.ts +10 -0
- package/build/clmm/events.js +10 -0
- package/build/clmm/events.js.map +1 -0
- package/build/clmm/index.d.ts +1 -0
- package/build/clmm/index.js +18 -0
- package/build/clmm/index.js.map +1 -0
- package/build/events.d.ts +200 -9
- package/build/events.js +73 -24
- package/build/events.js.map +1 -1
- package/build/eventsV2.d.ts +7 -0
- package/build/eventsV2.js +10 -0
- package/build/eventsV2.js.map +1 -0
- package/build/flavors.d.ts +2 -0
- package/build/flavors.js +81 -27
- package/build/flavors.js.map +1 -1
- package/build/index.d.ts +6 -0
- package/build/index.js +14 -4
- package/build/index.js.map +1 -1
- package/build/lpPosition.js +4 -1
- package/build/lpPosition.js.map +1 -1
- package/build/market.d.ts +14 -2
- package/build/market.js +70 -29
- package/build/market.js.map +1 -1
- package/build/marketThree.d.ts +664 -0
- package/build/marketThree.js +1415 -0
- package/build/marketThree.js.map +1 -0
- package/build/marketThree.test.d.ts +1 -0
- package/build/marketThree.test.js +166 -0
- package/build/marketThree.test.js.map +1 -0
- package/build/orderbook/events.d.ts +7 -0
- package/build/orderbook/events.js +10 -0
- package/build/orderbook/events.js.map +1 -0
- package/build/orderbook/index.d.ts +4 -0
- package/build/orderbook/index.js +41 -0
- package/build/orderbook/index.js.map +1 -0
- package/build/orderbook/math.d.ts +26 -0
- package/build/orderbook/math.js +111 -0
- package/build/orderbook/math.js.map +1 -0
- package/build/orderbook/orderbook.d.ts +175 -0
- package/build/orderbook/orderbook.js +756 -0
- package/build/orderbook/orderbook.js.map +1 -0
- package/build/orderbook/types.d.ts +49 -0
- package/build/orderbook/types.js +27 -0
- package/build/orderbook/types.js.map +1 -0
- package/build/orderbook/utils.d.ts +18 -0
- package/build/orderbook/utils.js +74 -0
- package/build/orderbook/utils.js.map +1 -0
- package/build/router.d.ts +92 -0
- package/build/router.js +214 -0
- package/build/router.js.map +1 -0
- package/build/syPosition.js +6 -0
- package/build/syPosition.js.map +1 -1
- package/build/utils/index.d.ts +3 -2
- package/build/utils/index.js +22 -1
- package/build/utils/index.js.map +1 -1
- package/build/vault.d.ts +3 -1
- package/build/vault.js +98 -62
- package/build/vault.js.map +1 -1
- package/build/ytPosition.d.ts +2 -0
- package/build/ytPosition.js +18 -5
- package/build/ytPosition.js.map +1 -1
- package/package.json +28 -23
- package/src/EventDecoderV2.ts +96 -0
- package/src/addressLookupTableUtil.ts +42 -1
- package/src/clmm/events.ts +17 -0
- package/src/clmm/index.ts +1 -0
- package/src/events.ts +280 -27
- package/src/eventsV2.ts +13 -0
- package/src/flavors.ts +97 -27
- package/src/index.ts +6 -0
- package/src/lpPosition.ts +5 -2
- package/src/market.ts +100 -31
- package/src/marketThree.test.ts +208 -0
- package/src/marketThree.ts +2430 -0
- package/src/orderbook/events.ts +13 -0
- package/src/orderbook/index.ts +12 -0
- package/src/orderbook/math.ts +122 -0
- package/src/orderbook/orderbook.ts +1153 -0
- package/src/orderbook/types.ts +45 -0
- package/src/orderbook/utils.ts +74 -0
- package/src/router.ts +360 -0
- package/src/syPosition.ts +4 -0
- package/src/utils/index.ts +27 -2
- package/src/vault.ts +100 -62
- package/src/ytPosition.ts +28 -7
- package/tsconfig.json +4 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { web3 } from "@coral-xyz/anchor"
|
|
2
|
+
|
|
3
|
+
export type OrderbookLoadOptions = {
|
|
4
|
+
syConfig?: {
|
|
5
|
+
skipWrap?: boolean
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type EmptyObject = Record<never, never>
|
|
10
|
+
export type OfferTypeAnchor = { buyYt: EmptyObject; sellYt?: never } | { sellYt: EmptyObject; buyYt?: never }
|
|
11
|
+
|
|
12
|
+
export type OfferOptionsAnchor = { fillOrKill: { "0": boolean } }
|
|
13
|
+
|
|
14
|
+
export enum OfferType {
|
|
15
|
+
SellYT = 1,
|
|
16
|
+
BuyYT = 2,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Offer = {
|
|
20
|
+
type: OfferType
|
|
21
|
+
priceApy: number
|
|
22
|
+
amount: number
|
|
23
|
+
userEscrow: web3.PublicKey
|
|
24
|
+
expiryAt: number
|
|
25
|
+
createdAt: number
|
|
26
|
+
isVirtual: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export enum OfferTradeDirection {
|
|
30
|
+
SY_TO_YT,
|
|
31
|
+
SY_TO_PT,
|
|
32
|
+
YT_TO_SY,
|
|
33
|
+
PT_TO_SY,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export enum QuoteDirection {
|
|
37
|
+
BASE_TO_YT,
|
|
38
|
+
BASE_TO_PT,
|
|
39
|
+
YT_TO_BASE,
|
|
40
|
+
PT_TO_BASE,
|
|
41
|
+
SY_TO_YT,
|
|
42
|
+
SY_TO_PT,
|
|
43
|
+
YT_TO_SY,
|
|
44
|
+
PT_TO_SY,
|
|
45
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { OfferType, OfferTradeDirection, OfferTypeAnchor, QuoteDirection } from "./types"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get offer type by offer.orderTypeFlag value
|
|
5
|
+
*/
|
|
6
|
+
export function getOfferType(orderTypeFlag: number): OfferType {
|
|
7
|
+
return orderTypeFlag === 1 ? OfferType.SellYT : OfferType.BuyYT
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get price in percents and cast it to implied rate
|
|
12
|
+
*/
|
|
13
|
+
export function priceToImpliedRate(price: number): number {
|
|
14
|
+
return Math.log(1 + price / 100)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getOfferTypeFromTradeDirection(tradeDirection: OfferTradeDirection): {
|
|
18
|
+
offerType: OfferType
|
|
19
|
+
isVirtual: boolean
|
|
20
|
+
} {
|
|
21
|
+
const isVirtual = tradeDirection === OfferTradeDirection.PT_TO_SY || tradeDirection === OfferTradeDirection.SY_TO_PT
|
|
22
|
+
|
|
23
|
+
const offerType: OfferType = (() => {
|
|
24
|
+
if (tradeDirection === OfferTradeDirection.SY_TO_YT || tradeDirection === OfferTradeDirection.PT_TO_SY) {
|
|
25
|
+
return OfferType.BuyYT
|
|
26
|
+
} else {
|
|
27
|
+
return OfferType.SellYT
|
|
28
|
+
}
|
|
29
|
+
})()
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
isVirtual,
|
|
33
|
+
offerType,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function convertOfferTypeToAnchorType(offerType: OfferType): OfferTypeAnchor {
|
|
38
|
+
return offerType === OfferType.BuyYT ? { buyYt: {} } : { sellYt: {} }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function convertOfferTypeFromAnchorType(offerTypeAnchor: OfferTypeAnchor): OfferType {
|
|
42
|
+
return offerTypeAnchor?.buyYt ? OfferType.BuyYT : OfferType.SellYT
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getOfferDirectionFromQuoteDirection(direction: QuoteDirection): OfferTradeDirection {
|
|
46
|
+
switch (direction) {
|
|
47
|
+
case QuoteDirection.BASE_TO_YT:
|
|
48
|
+
return OfferTradeDirection.SY_TO_YT
|
|
49
|
+
case QuoteDirection.BASE_TO_PT:
|
|
50
|
+
return OfferTradeDirection.SY_TO_PT
|
|
51
|
+
case QuoteDirection.YT_TO_BASE:
|
|
52
|
+
return OfferTradeDirection.YT_TO_SY
|
|
53
|
+
case QuoteDirection.PT_TO_BASE:
|
|
54
|
+
return OfferTradeDirection.PT_TO_SY
|
|
55
|
+
case QuoteDirection.SY_TO_YT:
|
|
56
|
+
return OfferTradeDirection.SY_TO_YT
|
|
57
|
+
case QuoteDirection.SY_TO_PT:
|
|
58
|
+
return OfferTradeDirection.SY_TO_PT
|
|
59
|
+
case QuoteDirection.YT_TO_SY:
|
|
60
|
+
return OfferTradeDirection.YT_TO_SY
|
|
61
|
+
case QuoteDirection.PT_TO_SY:
|
|
62
|
+
return OfferTradeDirection.PT_TO_SY
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`Unable to determine offer direction from quote direction: ${direction}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isQuoteInputTokenBase(direction: QuoteDirection): boolean {
|
|
69
|
+
return direction === QuoteDirection.BASE_TO_YT || direction === QuoteDirection.BASE_TO_PT
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isQuoteOutputTokenBase(direction: QuoteDirection): boolean {
|
|
73
|
+
return direction === QuoteDirection.YT_TO_BASE || direction === QuoteDirection.PT_TO_BASE
|
|
74
|
+
}
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { web3 } from "@coral-xyz/anchor"
|
|
2
|
+
|
|
3
|
+
import { ExponentFetcher, MarketThree, Ticks } from "@exponent-labs/exponent-fetcher"
|
|
4
|
+
import {
|
|
5
|
+
QuoteDirection as ClmmQuoteDirection,
|
|
6
|
+
MarketThreeState,
|
|
7
|
+
getSwapQuote as getClmmSwapQuote,
|
|
8
|
+
} from "@exponent-labs/market-three-math"
|
|
9
|
+
|
|
10
|
+
import { Environment } from "./environment"
|
|
11
|
+
import { Market, MarketLoadOptions } from "./market"
|
|
12
|
+
import { Orderbook, OrderbookLoadOptions, QuoteDirection as OrderbookQuoteDirection } from "./orderbook"
|
|
13
|
+
import { Vault } from "./vault"
|
|
14
|
+
|
|
15
|
+
export class Router {
|
|
16
|
+
private legacyMarkets: Market[]
|
|
17
|
+
private orderbooks: Orderbook[]
|
|
18
|
+
private clmmsWithTicks: ClmmWithTicks[]
|
|
19
|
+
//? For possible future use inside refetch functionality
|
|
20
|
+
private vault: Vault | null
|
|
21
|
+
private connection: web3.Connection
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Use the constructor if markets are already initialized in your app
|
|
25
|
+
*/
|
|
26
|
+
constructor(
|
|
27
|
+
connection: web3.Connection,
|
|
28
|
+
markets: { legacyMarkets?: Market[]; orderbooks?: Orderbook[]; clmmsWithTicks?: ClmmWithTicks[] },
|
|
29
|
+
vault?: Vault,
|
|
30
|
+
) {
|
|
31
|
+
this.connection = connection
|
|
32
|
+
|
|
33
|
+
this.legacyMarkets = markets.legacyMarkets ?? []
|
|
34
|
+
this.orderbooks = markets.orderbooks ?? []
|
|
35
|
+
this.clmmsWithTicks = markets.clmmsWithTicks ?? []
|
|
36
|
+
|
|
37
|
+
this.vault = vault ?? null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Use the load method if you don't have already loaded markets in your app
|
|
42
|
+
*/
|
|
43
|
+
public static async load(
|
|
44
|
+
connection: web3.Connection,
|
|
45
|
+
args: {
|
|
46
|
+
vault?: Vault
|
|
47
|
+
env: Environment
|
|
48
|
+
legacyMarketsParams?: {
|
|
49
|
+
address: web3.PublicKey
|
|
50
|
+
options?: MarketLoadOptions
|
|
51
|
+
}[]
|
|
52
|
+
orderbookParams?: {
|
|
53
|
+
address: web3.PublicKey
|
|
54
|
+
options?: OrderbookLoadOptions
|
|
55
|
+
}[]
|
|
56
|
+
clmmParams?: {
|
|
57
|
+
marketThreeAddress: web3.PublicKey
|
|
58
|
+
}[]
|
|
59
|
+
},
|
|
60
|
+
): Promise<Router> {
|
|
61
|
+
const { vault, legacyMarketsParams = [], orderbookParams = [], clmmParams = [], env } = args
|
|
62
|
+
|
|
63
|
+
const legacyMarketsPromises = this.fetchLegacyMarkets(connection, { env, vault, legacyMarketsParams })
|
|
64
|
+
|
|
65
|
+
const orderbooksPromises = this.fetchOrderbooks(connection, { env, vault, orderbookParams })
|
|
66
|
+
|
|
67
|
+
const clmmsWithTicksPromises = this.fetchClmmsWithTicks(
|
|
68
|
+
connection,
|
|
69
|
+
clmmParams.map(({ marketThreeAddress }) => marketThreeAddress),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const [legacyMarkets, orderbooks, clmmsWithTicks] = await Promise.all([
|
|
73
|
+
legacyMarketsPromises,
|
|
74
|
+
orderbooksPromises,
|
|
75
|
+
clmmsWithTicksPromises,
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
return new Router(
|
|
79
|
+
connection,
|
|
80
|
+
{ legacyMarkets: legacyMarkets, orderbooks: orderbooks, clmmsWithTicks: clmmsWithTicks },
|
|
81
|
+
args.vault,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Refreshes the states of all markets, orderbooks and CLMMs
|
|
87
|
+
*/
|
|
88
|
+
public async refresh(): Promise<Router> {
|
|
89
|
+
const legacyMarkets = await Promise.all(this.legacyMarkets.map((market) => market.reload(this.connection)))
|
|
90
|
+
|
|
91
|
+
const orderbooks = await Promise.all(this.orderbooks.map((orderbook) => orderbook.reload(this.connection)))
|
|
92
|
+
|
|
93
|
+
const clmmsWithTicks = await Router.fetchClmmsWithTicks(
|
|
94
|
+
this.connection,
|
|
95
|
+
this.clmmsWithTicks.map((clmmWithTicks) => clmmWithTicks.marketThree.selfAddress),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return new Router(
|
|
99
|
+
this.connection,
|
|
100
|
+
{ legacyMarkets: legacyMarkets, orderbooks: orderbooks, clmmsWithTicks: clmmsWithTicks },
|
|
101
|
+
this.vault,
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public getQuote(params: { direction: RouterQuoteDirection; inAmount: number; syExchangeRate: number }): Quote | null {
|
|
106
|
+
//? Better to refetch market and orderbook states before every call or trigger reload method here
|
|
107
|
+
const { direction, inAmount, syExchangeRate } = params
|
|
108
|
+
|
|
109
|
+
const marketQuotes = this.legacyMarkets.map((market) => this.getLegacyMarketQuote({ market, direction, inAmount }))
|
|
110
|
+
const orderbookQuotes = this.orderbooks.map((orderbook) =>
|
|
111
|
+
this.getOrderbookQuote({ orderbook, direction, inAmount, syExchangeRate }),
|
|
112
|
+
)
|
|
113
|
+
const clmmQuotes = this.clmmsWithTicks.map((clmmWithTicks) =>
|
|
114
|
+
this.getClmmQuote({ clmmWithTicks, syExchangeRate, direction, inAmount }),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const bestQuote: Quote | null =
|
|
118
|
+
[...marketQuotes, ...orderbookQuotes, ...clmmQuotes]
|
|
119
|
+
.filter(({ outAmount }) => outAmount > 0)
|
|
120
|
+
.sort((a, b) => b.outAmount - a.outAmount)
|
|
121
|
+
.at(0) ?? null
|
|
122
|
+
|
|
123
|
+
return bestQuote
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static async fetchLegacyMarkets(
|
|
127
|
+
connection: web3.Connection,
|
|
128
|
+
args: {
|
|
129
|
+
env: Environment
|
|
130
|
+
vault?: Vault
|
|
131
|
+
legacyMarketsParams: {
|
|
132
|
+
address: web3.PublicKey
|
|
133
|
+
options?: MarketLoadOptions
|
|
134
|
+
}[]
|
|
135
|
+
},
|
|
136
|
+
): Promise<Market[]> {
|
|
137
|
+
return await Promise.all(
|
|
138
|
+
args.legacyMarketsParams.map((market) =>
|
|
139
|
+
Market.load(args.env, connection, market.address, args.vault, market.options),
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static async fetchOrderbooks(
|
|
145
|
+
connection: web3.Connection,
|
|
146
|
+
args: {
|
|
147
|
+
env: Environment
|
|
148
|
+
vault?: Vault
|
|
149
|
+
orderbookParams: {
|
|
150
|
+
address: web3.PublicKey
|
|
151
|
+
options?: OrderbookLoadOptions
|
|
152
|
+
}[]
|
|
153
|
+
},
|
|
154
|
+
): Promise<Orderbook[]> {
|
|
155
|
+
return await Promise.all(
|
|
156
|
+
args.orderbookParams.map((orderbook) =>
|
|
157
|
+
Orderbook.load(args.env, connection, orderbook.address, orderbook.options, args.vault),
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static async fetchClmmsWithTicks(
|
|
163
|
+
connection: web3.Connection,
|
|
164
|
+
clmmAddresses: web3.PublicKey[],
|
|
165
|
+
): Promise<{ marketThree: MarketThree; ticks: Ticks }[]> {
|
|
166
|
+
const fetchClmmWithTicks = async (marketThreeAddress: web3.PublicKey) => {
|
|
167
|
+
const xponFetcher = new ExponentFetcher({ connection })
|
|
168
|
+
const marketThree = await xponFetcher.fetchMarketThree(marketThreeAddress)
|
|
169
|
+
const ticks = await xponFetcher.fetchMarketThreeTicks(marketThree.ticks)
|
|
170
|
+
|
|
171
|
+
return { marketThree, ticks }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return await Promise.all(clmmAddresses.map((address) => fetchClmmWithTicks(address)))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private getLegacyMarketQuote(params: { market: Market; direction: RouterQuoteDirection; inAmount: number }): Quote {
|
|
178
|
+
const { direction, inAmount, market } = params
|
|
179
|
+
|
|
180
|
+
const marketCalculator = market.marketCalculator()
|
|
181
|
+
|
|
182
|
+
if (direction === RouterQuoteDirection.BASE_TO_YT) {
|
|
183
|
+
const { ytOut, feeInAsset } = marketCalculator.estimateYtOutForExactAssetIn(inAmount)
|
|
184
|
+
return {
|
|
185
|
+
outAmount: ytOut,
|
|
186
|
+
feesInOutToken: feeInAsset,
|
|
187
|
+
source: RouterTradeSource.MARKET,
|
|
188
|
+
sourceAddress: market.selfAddress,
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (direction === RouterQuoteDirection.BASE_TO_PT) {
|
|
193
|
+
const ptOut = marketCalculator.estimateNetPtForExactNetAsset(inAmount)
|
|
194
|
+
const { feeAsset, netTraderPt } = marketCalculator.calcTradePt(ptOut, false)
|
|
195
|
+
return {
|
|
196
|
+
outAmount: netTraderPt,
|
|
197
|
+
feesInOutToken: feeAsset,
|
|
198
|
+
source: RouterTradeSource.MARKET,
|
|
199
|
+
sourceAddress: market.selfAddress,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (direction === RouterQuoteDirection.PT_TO_BASE) {
|
|
204
|
+
const { feeAsset, netTraderAsset } = marketCalculator.calcTradePt(inAmount * -1)
|
|
205
|
+
return {
|
|
206
|
+
outAmount: netTraderAsset,
|
|
207
|
+
feesInOutToken: feeAsset,
|
|
208
|
+
source: RouterTradeSource.MARKET,
|
|
209
|
+
sourceAddress: market.selfAddress,
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (direction === RouterQuoteDirection.YT_TO_BASE) {
|
|
214
|
+
const { feeInAsset, assetOut } = marketCalculator.simSellYt(inAmount)
|
|
215
|
+
return {
|
|
216
|
+
outAmount: assetOut,
|
|
217
|
+
feesInOutToken: feeInAsset,
|
|
218
|
+
source: RouterTradeSource.MARKET,
|
|
219
|
+
sourceAddress: market.selfAddress,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw new Error(`Unknown quote direction: ${direction}`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private getOrderbookQuote(params: {
|
|
227
|
+
orderbook: Orderbook
|
|
228
|
+
direction: RouterQuoteDirection
|
|
229
|
+
inAmount: number
|
|
230
|
+
syExchangeRate: number
|
|
231
|
+
}): Quote {
|
|
232
|
+
const { direction, inAmount, orderbook, syExchangeRate } = params
|
|
233
|
+
|
|
234
|
+
const unixNow = Math.round(Date.now() / 1000)
|
|
235
|
+
const { outAmount, takerFees } = orderbook.getQuote({
|
|
236
|
+
inAmount,
|
|
237
|
+
unixNow,
|
|
238
|
+
direction: castToOrderbookQuoteDirection(direction),
|
|
239
|
+
syExchangeRate,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
outAmount: outAmount,
|
|
244
|
+
feesInOutToken: takerFees,
|
|
245
|
+
source: RouterTradeSource.ORDERBOOK,
|
|
246
|
+
sourceAddress: orderbook.selfAddress,
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private getClmmQuote(params: {
|
|
251
|
+
clmmWithTicks: ClmmWithTicks
|
|
252
|
+
syExchangeRate: number
|
|
253
|
+
direction: RouterQuoteDirection
|
|
254
|
+
inAmount: number
|
|
255
|
+
}): Quote {
|
|
256
|
+
const { clmmWithTicks, syExchangeRate, inAmount, direction } = params
|
|
257
|
+
const sourceAddress = clmmWithTicks.marketThree.selfAddress
|
|
258
|
+
|
|
259
|
+
const marketThreeState: MarketThreeState = {
|
|
260
|
+
financials: clmmWithTicks.marketThree.financials,
|
|
261
|
+
configurationOptions: clmmWithTicks.marketThree.configurationOptions,
|
|
262
|
+
ticks: clmmWithTicks.ticks,
|
|
263
|
+
currentSyExchangeRate: syExchangeRate,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (direction === RouterQuoteDirection.BASE_TO_PT) {
|
|
267
|
+
const syInAmount = inAmount / syExchangeRate
|
|
268
|
+
|
|
269
|
+
const { amountOut, lpFee, protocolFee } = getClmmSwapQuote(
|
|
270
|
+
marketThreeState,
|
|
271
|
+
syInAmount,
|
|
272
|
+
ClmmQuoteDirection.SyToPt,
|
|
273
|
+
)
|
|
274
|
+
return {
|
|
275
|
+
outAmount: amountOut,
|
|
276
|
+
feesInOutToken: lpFee + protocolFee,
|
|
277
|
+
source: RouterTradeSource.CLMM,
|
|
278
|
+
sourceAddress,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (direction === RouterQuoteDirection.PT_TO_BASE) {
|
|
283
|
+
const { amountOut, lpFee, protocolFee } = getClmmSwapQuote(marketThreeState, inAmount, ClmmQuoteDirection.PtToSy)
|
|
284
|
+
|
|
285
|
+
const baseOutAmount = amountOut * syExchangeRate
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
outAmount: baseOutAmount,
|
|
289
|
+
feesInOutToken: lpFee + protocolFee,
|
|
290
|
+
source: RouterTradeSource.CLMM,
|
|
291
|
+
sourceAddress,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (direction === RouterQuoteDirection.BASE_TO_YT) {
|
|
296
|
+
const syInAmount = inAmount / syExchangeRate
|
|
297
|
+
const { amountOut, lpFee, protocolFee } = getClmmSwapQuote(
|
|
298
|
+
marketThreeState,
|
|
299
|
+
syInAmount,
|
|
300
|
+
ClmmQuoteDirection.SyToYt,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
outAmount: amountOut,
|
|
305
|
+
feesInOutToken: lpFee + protocolFee,
|
|
306
|
+
source: RouterTradeSource.CLMM,
|
|
307
|
+
sourceAddress,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (direction === RouterQuoteDirection.YT_TO_BASE) {
|
|
312
|
+
const { amountOut, lpFee, protocolFee } = getClmmSwapQuote(marketThreeState, inAmount, ClmmQuoteDirection.YtToSy)
|
|
313
|
+
const baseOutAmount = amountOut * syExchangeRate
|
|
314
|
+
return {
|
|
315
|
+
outAmount: baseOutAmount,
|
|
316
|
+
feesInOutToken: lpFee + protocolFee,
|
|
317
|
+
source: RouterTradeSource.CLMM,
|
|
318
|
+
sourceAddress,
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
throw new Error(`Unknown quote direction: ${direction}`)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export type ClmmWithTicks = {
|
|
327
|
+
marketThree: MarketThree
|
|
328
|
+
ticks: Ticks
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export type Quote = {
|
|
332
|
+
outAmount: number
|
|
333
|
+
feesInOutToken: number
|
|
334
|
+
source: RouterTradeSource
|
|
335
|
+
sourceAddress: web3.PublicKey
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* As orderbook supports more trade directions than market, we need to cast it for type safety
|
|
340
|
+
*/
|
|
341
|
+
function castToOrderbookQuoteDirection(direction: RouterQuoteDirection): OrderbookQuoteDirection {
|
|
342
|
+
if (direction === RouterQuoteDirection.BASE_TO_YT) return OrderbookQuoteDirection.BASE_TO_YT
|
|
343
|
+
if (direction === RouterQuoteDirection.BASE_TO_PT) return OrderbookQuoteDirection.BASE_TO_PT
|
|
344
|
+
if (direction === RouterQuoteDirection.YT_TO_BASE) return OrderbookQuoteDirection.YT_TO_BASE
|
|
345
|
+
if (direction === RouterQuoteDirection.PT_TO_BASE) return OrderbookQuoteDirection.PT_TO_BASE
|
|
346
|
+
throw new Error("Unknown quote direction")
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export enum RouterTradeSource {
|
|
350
|
+
MARKET = "MARKET",
|
|
351
|
+
ORDERBOOK = "ORDERBOOK",
|
|
352
|
+
CLMM = "CLMM",
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export enum RouterQuoteDirection {
|
|
356
|
+
BASE_TO_YT,
|
|
357
|
+
BASE_TO_PT,
|
|
358
|
+
YT_TO_BASE,
|
|
359
|
+
PT_TO_BASE,
|
|
360
|
+
}
|
package/src/syPosition.ts
CHANGED
|
@@ -168,6 +168,10 @@ async function makeSyPositionGeneric(
|
|
|
168
168
|
interfaceIndex = 11 // InterfaceType.KaminoVault
|
|
169
169
|
} else if ("solstice" in interfaceType) {
|
|
170
170
|
interfaceIndex = 12 // InterfaceType.Solstice
|
|
171
|
+
} else if ("reflect" in interfaceType) {
|
|
172
|
+
interfaceIndex = 13 // InterfaceType.Reflect
|
|
173
|
+
} else if ("ore" in interfaceType) {
|
|
174
|
+
interfaceIndex = 14 // InterfaceType.Ore
|
|
171
175
|
} else {
|
|
172
176
|
throw new Error("Unsupported interface type")
|
|
173
177
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { web3 } from "@coral-xyz/anchor"
|
|
1
|
+
import { Program as AnchorProgram, AnchorProvider, Idl, web3 } from "@coral-xyz/anchor"
|
|
2
2
|
|
|
3
3
|
import { EXPONENT_ADMIN_PROGRAM_ID, ExponentAdminPda } from "@exponent-labs/exponent-admin-pda"
|
|
4
4
|
|
|
5
|
-
export { InstructionAccounts } from "./ix"
|
|
5
|
+
export type { InstructionAccounts } from "./ix"
|
|
6
6
|
|
|
7
7
|
export function uniqueRemainingAccounts(xs: web3.AccountMeta[]): web3.AccountMeta[] {
|
|
8
8
|
const seen: Record<string, web3.AccountMeta> = {}
|
|
@@ -32,3 +32,28 @@ export function getExponentAdminStatePda() {
|
|
|
32
32
|
const exponentAdminPda = new ExponentAdminPda(EXPONENT_ADMIN_PROGRAM_ID)
|
|
33
33
|
return exponentAdminPda.exponentAdmin()
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
// Browser-compatible mock wallet for creating anchor programs
|
|
37
|
+
class MockWallet {
|
|
38
|
+
constructor(readonly payer: web3.Keypair) {}
|
|
39
|
+
|
|
40
|
+
async signTransaction<T extends web3.Transaction | web3.VersionedTransaction>(tx: T): Promise<T> {
|
|
41
|
+
return tx
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async signAllTransactions<T extends web3.Transaction | web3.VersionedTransaction>(txs: T[]): Promise<T[]> {
|
|
45
|
+
return txs
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get publicKey(): web3.PublicKey {
|
|
49
|
+
return this.payer.publicKey
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createAnchorProgram<IDL extends Idl = Idl>(idl: IDL) {
|
|
54
|
+
const mockProvider = new AnchorProvider(
|
|
55
|
+
new web3.Connection(web3.clusterApiUrl("devnet")),
|
|
56
|
+
new MockWallet(web3.Keypair.generate()) as any,
|
|
57
|
+
)
|
|
58
|
+
return new AnchorProgram<IDL>(idl, mockProvider)
|
|
59
|
+
}
|