/
fetchFormatMakerData.ts
146 lines (130 loc) · 3.78 KB
/
fetchFormatMakerData.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import {
http,
createPublicClient,
Address,
PublicClient,
zeroAddress,
} from "viem";
import {
chains,
fetchMarketSnapshots2,
calcSkew,
calcMakerExposure,
calcLpUtilization,
SupportedChainId,
SupportedAsset,
UserMarketSnapshot,
MarketSnapshot,
} from "perennial-sdk-ts";
// Alchemy Key
const AlchemyURL = process.env.ALCHEMY_URL;
if (!AlchemyURL) throw new Error("Missing alchemy key configuration");
/// Fetch Maker Data
async function main(
url: string,
userAddress: Address | undefined,
assets: string[]
) {
// Get Chain
const chainID = await createPublicClient({
transport: http(AlchemyURL, {
batch: true,
}),
}).getChainId();
const chain = chains[chainID as SupportedChainId];
// Create Public Client
const publicClient = createPublicClient({
chain,
transport: http(AlchemyURL, {
batch: true,
}),
});
const marketInfo = await fetchMarketSnapshots2(
publicClient as PublicClient,
userAddress,
assets
);
if (!marketInfo) throw new Error("No market info found");
console.log(marketInfo);
// Filter out user positions with no exposure
const marketsWithUserPositions =
marketInfo.user &&
(Object.entries(marketInfo.user)
.filter(([market, position]) => position.side !== "none")
.reduce(
(acc, [market, position]) => ({ ...acc, [market]: position }),
{}
) as Record<SupportedAsset, UserMarketSnapshot>);
if (!marketsWithUserPositions) throw new Error("No user positions found");
// Filter out market snapshots without users positions
const relevantGlobalMarkets =
marketInfo.market &&
(Object.entries(marketInfo.market)
.filter(([market, position]) =>
Object.keys(marketsWithUserPositions).includes(market)
)
.reduce(
(acc, [market, position]) => ({ ...acc, [market]: position }),
{}
) as Record<SupportedAsset, MarketSnapshot>);
if (!relevantGlobalMarkets)
throw new Error("No relevant global markets found");
/// Format data for Maker
const data = Object.entries(marketsWithUserPositions).map(
([ticker, userData]) => {
const asset = ticker as SupportedAsset;
const {
local,
nextPosition,
prices: latestPrices,
side,
nextLeverage,
market,
} = userData;
// Calculate Liqudiation
const marketSkew = calcSkew(relevantGlobalMarkets[asset]);
// Calculate LP Utilization
const lpUtilization = calcLpUtilization(relevantGlobalMarkets[asset]);
// Calculate Exposure (units of underlying asset)
const lpExposure = calcMakerExposure(
nextPosition.maker,
relevantGlobalMarkets[asset].nextPosition.maker,
relevantGlobalMarkets[asset].nextPosition.long,
relevantGlobalMarkets[asset].nextPosition.short
);
// Calculate Liqudiation
// Not working?????
// const liquidation = positionUtils.calcLiquidationPrice(
// relevantGlobalMarkets[ticker],
// local.collateral,
// nextPosition.maker
// );
// Get current market price & latest update price
const prices = {
currentPrice: relevantGlobalMarkets[asset].latestOracleVersion.price,
latestPrice: latestPrices[0],
};
return {
asset,
market,
side,
prices,
collateral: local.collateral,
position: {
maker: nextPosition.maker,
long: nextPosition.long,
short: nextPosition.short,
},
leverage: nextLeverage,
marketSkew,
lpUtilization,
lpExposure,
// liquidation,
};
}
);
console.log("Formatted Data");
console.log(data);
return { user: userAddress, chain: chainID, data };
}
main(AlchemyURL, zeroAddress, ["eth", "btc", "sol"]);