-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathlib.js
More file actions
executable file
·277 lines (246 loc) · 8.61 KB
/
lib.js
File metadata and controls
executable file
·277 lines (246 loc) · 8.61 KB
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
require("dotenv").config();
const chalk = require("chalk");
const fs = require("fs");
const path = require("path");
const { GasPrice } = require("@cosmjs/stargate");
const { DirectSecp256k1HdWallet } = require("@cosmjs/proto-signing");
const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm-stargate");
const { USDC_DENOM, OVERRIDE_RPC, BVBCONTRACT, MARKET_CACHE_TIME, PRICE_CACHE_TIME, MAX_LEVERAGE_CACHE_TIME, FUNDING_RATE_CACHE_TIME, CACHE_DIR, MARS } = require("./consts");
const SEED = process.env.SEED;
const { chains } = require("chain-registry");
const chain = chains.find((chain) => chain.chain_name === "neutron");
const DIVISOR = 1_000_000;
var client;
async function getClient(seedOverride = null) {
var useSeed = seedOverride || SEED;
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(useSeed, {
prefix: "neutron",
});
const [{ address }] = await wallet.getAccounts();
const gasPrice = GasPrice.fromString(`0.0065${USDC_DENOM}`);
const options = { gasPrice };
var useRPC;
if (OVERRIDE_RPC) {
useRPC = OVERRIDE_RPC;
} else {
useRPC = chain.apis.rpc[Math.floor(Math.random() * chain.apis.rpc.length)].address;
}
console.log(chalk.blue("Using RPC:", useRPC));
const signingClient = await SigningCosmWasmClient.connectWithSigner(useRPC, wallet, options);
client = { wallet, signingClient, myAddress: address };
return client;
}
async function getPositions(address = null) {
/*Returns an array of position objects that look like below:
[
{
"id": 1234,
"user": "neutron1...",
"credit_account_id": "4567",
"assets": [
{
"denom": "perps/ubtc",
"long": true,
"size": "0.0009",
"collateral_percent": "0.5",
"exec_price": "84800",
"leverage": "3"
},
{
"denom": "perps/ueth",
"long": false,
"size": "-1",
"collateral_percent": "0.5",
"exec_price": "1800.53",
"leverage": "3"
}
],
"collateral_amount": "10000000",
"leverage": "3",
"created_at": "1745516650526230395",
"position_type": "Classic",
"cluster_id": null,
"cluster_name": null
}
]
*/
try {
const positions = await client.signingClient.queryContractSmart(BVBCONTRACT, {
user_positions_new: { user: address || client.myAddress, position_type: "Classic" },
});
return positions;
} catch (err) {
console.error(chalk.red("Error getting positions:", err.message));
return [];
}
}
async function getBalance(denom = USDC_DENOM) {
//Returns the balance of the user in the specified denom, defaults to usdc.
try {
const balance = await client.signingClient.getBalance(client.myAddress, denom);
return balance.amount / DIVISOR || 0;
} catch (err) {
console.error(chalk.red("Error getting balance:", err.message));
return 0;
}
}
async function tryCache(cachePath, cacheTime, fetchDataFn) {
try {
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR);
console.log("Created cache directory");
}
if (fs.existsSync(cachePath)) {
const cachedData = JSON.parse(fs.readFileSync(cachePath, "utf8"));
const now = Date.now();
if (cachedData.lastUpdated && now - cachedData.lastUpdated < cacheTime) {
return { fromCache: true, data: cachedData.data };
}
}
const freshData = await fetchDataFn();
const cacheData = {
lastUpdated: Date.now(),
data: freshData,
};
fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2));
return { fromCache: false, data: freshData };
} catch (err) {
if (fs.existsSync(cachePath)) {
try {
const cachedData = JSON.parse(fs.readFileSync(cachePath, "utf8"));
console.log(chalk.red(`Returning expired cached data from ${cachePath} due to fetch error`));
return { fromCache: true, expired: true, data: cachedData.data };
} catch (cacheErr) {
console.log(`Error reading cache from ${cachePath}:`, cacheErr);
}
}
return { fromCache: false, error: true, data: null };
}
}
async function getMarkets() {
//Gets all available markets enabled on BullBear. Returns an array of objects, containing the denom and the display. [{denom:"perps/ubtc",display:"BTC"}]
const marketCachePath = path.join(CACHE_DIR, "markets.json");
const fetchMarkets = async () => {
const markets = await client.signingClient.queryContractSmart(BVBCONTRACT, {
markets: {},
});
return markets
.filter((market) => market.enabled)
.map((market) => ({
denom: market.denom,
display: market.display,
}));
};
const result = await tryCache(marketCachePath, MARKET_CACHE_TIME, fetchMarkets);
return result.data || [];
}
async function getMaxLeverages() {
//Gets the max leverages for all available markets. Returns an object like: {"perps/ubtc":10, "perps/ueth":10}
const maxLeverageCachePath = path.join(CACHE_DIR, "leverages.json");
const fetchMaxLeverages = async () => {
const maxLeverages = await client.signingClient.queryContractSmart(BVBCONTRACT, {
max_leverages: {},
});
return maxLeverages;
};
const result = await tryCache(maxLeverageCachePath, MAX_LEVERAGE_CACHE_TIME, fetchMaxLeverages);
return result.data || [];
}
async function getFundingRates() {
/*Gets the funding rates for all markets. Returns an object like:
Funding Rates: {
'perps/uakt': {
fundingRate: 13.69071489640399,
longOI: '3969947062',
shortOI: '6066533265'
},
'perps/uarb': {
fundingRate: 32.59113048323023,
longOI: '129937160',
shortOI: '24324580'
}
}
Funding rates are a yearly percentage.
If it is POSITIVE, long positions pay short positions this rate.
If it is negative, short positions pay long positions this rate.
*/
const fundingRateCache = path.join(CACHE_DIR, "rates.json");
const fetchFundingRates = async () => {
const rates = await client.signingClient.queryContractSmart(MARS.PERPS, {
markets: {
limit: 50,
},
});
const fundingData = {};
for (const rate of rates.data) {
fundingData[rate.denom] = {
fundingRate: parseFloat(rate.current_funding_rate || 0) * 365 * 100,
longOI: rate.long_oi_value,
shortOI: rate.short_oi_value,
};
}
return fundingData;
};
const result = await tryCache(fundingRateCache, FUNDING_RATE_CACHE_TIME, fetchFundingRates);
return result.data || [];
}
async function getPrices() {
//Returns all price data for all available markets. Returns an object like: {"perps/ubtc":"93500.50", "perps/ueth":"1850.23"}
const priceCachePath = path.join(CACHE_DIR, "prices.json");
const fetchPrices = async () => {
const markets = await getMarkets();
const allDenoms = markets.map((market) => market.denom);
return await client.signingClient.queryContractSmart(MARS.ORACLE, {
prices_by_denoms: { denoms: allDenoms },
});
};
const result = await tryCache(priceCachePath, PRICE_CACHE_TIME, fetchPrices);
return result.data || {};
}
async function openPosition(assets, leverage, collateral) {
try {
const msg = {
open_position: {
position_input: {
Classic: {
assets: assets,
leverage: leverage.toString(),
},
},
},
};
console.log(chalk.blue("Opening position with message:"), JSON.stringify(msg, null, 2));
console.log(chalk.blue("Funds:"), JSON.stringify([{ denom: USDC_DENOM, amount: (collateral * DIVISOR).toString() }], null, 2));
const funds = [{ denom: USDC_DENOM, amount: (collateral * DIVISOR).toString() }];
const result = await client.signingClient.execute(client.myAddress, BVBCONTRACT, msg, "auto", "BullBear.Zone Position Opened", funds);
console.log(chalk.green("Position Opened: ", JSON.stringify(assets), "with", leverage, "x leverage and", collateral, "USDC collateral"));
return result;
} catch (error) {
console.error(chalk.red("Error opening position:", error.message));
return null;
}
}
async function closePosition(positionId) {
try {
const msg = {
close: { position_id: positionId },
};
const result = await client.signingClient.execute(client.myAddress, BVBCONTRACT, msg, "auto", "BullBear.Zone Position Closed");
console.log(chalk.green("Position Closed: ", positionId));
return result;
} catch (err) {
console.error(chalk.red("Error closing position:", err.message));
return null;
}
}
module.exports = {
getClient,
getPositions,
getBalance,
getMarkets,
getPrices,
getMaxLeverages,
openPosition,
closePosition,
getFundingRates,
};