@carrot-protocol/clend-rpc 0.0.1-mrgn-fork1-dev-7be6ef2
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/.prettierignore +1 -0
- package/makefile +12 -0
- package/package.json +32 -0
- package/src/addresses.ts +206 -0
- package/src/idl/clend.ts +7509 -0
- package/src/index.ts +31 -0
- package/src/instructions.ts +466 -0
- package/src/jupUtils.ts +347 -0
- package/src/jupiterUtils.ts +288 -0
- package/src/logger.ts +21 -0
- package/src/math.ts +684 -0
- package/src/mockJupiterUtils.ts +109 -0
- package/src/rpc.ts +1296 -0
- package/src/state.ts +512 -0
- package/src/utils.ts +249 -0
- package/test/bank.test.ts +95 -0
- package/test/interest-rate.test.ts +114 -0
- package/test/leverage.test.ts +867 -0
- package/test/token-amounts.test.ts +73 -0
- package/tsconfig.json +17 -0
package/src/jupUtils.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { web3 } from "@coral-xyz/anchor";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import {
|
|
4
|
+
createJupiterApiClient,
|
|
5
|
+
Instruction,
|
|
6
|
+
QuoteGetRequest,
|
|
7
|
+
QuoteResponse,
|
|
8
|
+
ResponseError,
|
|
9
|
+
SwapInfo,
|
|
10
|
+
SwapInstructionsPostRequest,
|
|
11
|
+
SwapMode,
|
|
12
|
+
} from "@jup-ag/api";
|
|
13
|
+
import Decimal from "decimal.js";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_MAX_ACCOUNTS_BUFFER = 2;
|
|
16
|
+
const MAX_LOCKED_ACCOUNTS = 64;
|
|
17
|
+
const JUPITER_PRICE_API = "https://api.jup.ag/price/v2";
|
|
18
|
+
const DEFAULT_JUP_V6_BASE_URL = "https://quote-api.jup.ag/v6";
|
|
19
|
+
|
|
20
|
+
export type ErrorBody = {
|
|
21
|
+
error: string;
|
|
22
|
+
error_code: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type SwapTxResponse = {
|
|
26
|
+
swapTxs: SwapTxs;
|
|
27
|
+
lookupTables: web3.PublicKey[];
|
|
28
|
+
swapResponse: SwapResponse;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type SwapResponse = {
|
|
32
|
+
swapInAmountLamports: Decimal;
|
|
33
|
+
swapOutAmountLamports: Decimal;
|
|
34
|
+
swapMinOutAmountLamports: Decimal;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type SwapTxs = {
|
|
38
|
+
setupIxs: web3.TransactionInstruction[];
|
|
39
|
+
swapIxs: web3.TransactionInstruction[];
|
|
40
|
+
cleanupIxs: web3.TransactionInstruction[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export async function getJupiterPrice(
|
|
44
|
+
mint: web3.PublicKey | string,
|
|
45
|
+
): Promise<number> {
|
|
46
|
+
const params = {
|
|
47
|
+
ids: mint.toString(),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const res = await axios.get(JUPITER_PRICE_API, { params });
|
|
51
|
+
return res.data.data[mint.toString()]?.price || 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type SwapConfig = {
|
|
55
|
+
wrapAndUnwrapSol?: boolean;
|
|
56
|
+
slippageBps: number;
|
|
57
|
+
swapMode: SwapMode;
|
|
58
|
+
onlyDirectRoutes: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
async function quote(
|
|
62
|
+
inputMint: web3.PublicKey,
|
|
63
|
+
outputMint: web3.PublicKey,
|
|
64
|
+
amountLamports: Decimal,
|
|
65
|
+
{ slippageBps, onlyDirectRoutes, swapMode }: SwapConfig,
|
|
66
|
+
maxAccs: number,
|
|
67
|
+
restrictIntermediateTokens: boolean = false,
|
|
68
|
+
): Promise<QuoteResponse> {
|
|
69
|
+
const quoteApi = createJupiterApiClient({
|
|
70
|
+
basePath: DEFAULT_JUP_V6_BASE_URL,
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const quoteParameters: QuoteGetRequest = {
|
|
74
|
+
inputMint: inputMint.toBase58(),
|
|
75
|
+
outputMint: outputMint.toBase58(),
|
|
76
|
+
amount: amountLamports.floor().toNumber(),
|
|
77
|
+
slippageBps: slippageBps,
|
|
78
|
+
onlyDirectRoutes: onlyDirectRoutes,
|
|
79
|
+
restrictIntermediateTokens: restrictIntermediateTokens,
|
|
80
|
+
//maxAccounts: maxAccs, NOT SUPPORTED WITH EXACT OUT
|
|
81
|
+
swapMode,
|
|
82
|
+
};
|
|
83
|
+
return await quoteApi.quoteGet(quoteParameters);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (e instanceof ResponseError) {
|
|
86
|
+
const body = (await e.response.json()) as ErrorBody;
|
|
87
|
+
throw new JupQuoteResponseError(e, body);
|
|
88
|
+
}
|
|
89
|
+
throw e;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function getJupiterQuote(
|
|
94
|
+
inputMint: web3.PublicKey,
|
|
95
|
+
outputMint: web3.PublicKey,
|
|
96
|
+
inputAmountLamports: Decimal,
|
|
97
|
+
slippageBps: number,
|
|
98
|
+
swapMode: SwapMode,
|
|
99
|
+
inputMintDecimals: number,
|
|
100
|
+
outputMintDecimals: number,
|
|
101
|
+
): Promise<SwapQuote<QuoteResponse>> {
|
|
102
|
+
const maxAccounts = 15;
|
|
103
|
+
const quoteResponse = await quote(
|
|
104
|
+
inputMint,
|
|
105
|
+
outputMint,
|
|
106
|
+
inputAmountLamports,
|
|
107
|
+
{
|
|
108
|
+
slippageBps: slippageBps,
|
|
109
|
+
wrapAndUnwrapSol: false,
|
|
110
|
+
onlyDirectRoutes: true,
|
|
111
|
+
swapMode,
|
|
112
|
+
},
|
|
113
|
+
maxAccounts,
|
|
114
|
+
true,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const inAmount = new Decimal(quoteResponse.inAmount).div(
|
|
118
|
+
new Decimal(10).pow(inputMintDecimals),
|
|
119
|
+
);
|
|
120
|
+
const minAmountOut = new Decimal(quoteResponse.otherAmountThreshold).div(
|
|
121
|
+
new Decimal(10).pow(outputMintDecimals),
|
|
122
|
+
);
|
|
123
|
+
const priceDebtToColl = minAmountOut.div(inAmount);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
priceAInB: priceDebtToColl,
|
|
127
|
+
quoteResponse,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function getJupiterSwap(
|
|
132
|
+
payer: web3.PublicKey,
|
|
133
|
+
inputs: SwapInputs,
|
|
134
|
+
quote: SwapQuote<QuoteResponse>,
|
|
135
|
+
): Promise<SwapIxs> {
|
|
136
|
+
const scaledQuoteResponse = scaleJupQuoteResponse(
|
|
137
|
+
quote.quoteResponse!,
|
|
138
|
+
new Decimal(inputs.inputAmountLamports),
|
|
139
|
+
);
|
|
140
|
+
const { swapTxs, lookupTables } = await swapTxFromQuote(
|
|
141
|
+
payer,
|
|
142
|
+
scaledQuoteResponse,
|
|
143
|
+
{
|
|
144
|
+
slippageBps: quote.quoteResponse!.slippageBps,
|
|
145
|
+
wrapAndUnwrapSol: false,
|
|
146
|
+
onlyDirectRoutes: true,
|
|
147
|
+
swapMode: quote.quoteResponse!.swapMode,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
return {
|
|
151
|
+
preActionIxs: [],
|
|
152
|
+
swapIxs: [...swapTxs.setupIxs, ...swapTxs.swapIxs, ...swapTxs.cleanupIxs],
|
|
153
|
+
lookupTables,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function swapTxFromQuote(
|
|
158
|
+
payer: web3.PublicKey,
|
|
159
|
+
quote: QuoteResponse,
|
|
160
|
+
swapConfig: SwapConfig,
|
|
161
|
+
): Promise<SwapTxResponse> {
|
|
162
|
+
const quoteApi = createJupiterApiClient({
|
|
163
|
+
basePath: DEFAULT_JUP_V6_BASE_URL,
|
|
164
|
+
});
|
|
165
|
+
try {
|
|
166
|
+
const swapParameters: SwapInstructionsPostRequest = {
|
|
167
|
+
swapRequest: {
|
|
168
|
+
userPublicKey: payer.toBase58(),
|
|
169
|
+
quoteResponse: quote,
|
|
170
|
+
computeUnitPriceMicroLamports: 1,
|
|
171
|
+
wrapAndUnwrapSol: swapConfig?.wrapAndUnwrapSol ?? false,
|
|
172
|
+
destinationTokenAccount: undefined,
|
|
173
|
+
useSharedAccounts: false,
|
|
174
|
+
feeAccount: undefined,
|
|
175
|
+
skipUserAccountsRpcCalls: true,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const swap = await quoteApi.swapInstructionsPost(swapParameters);
|
|
179
|
+
|
|
180
|
+
const swapIxs = [transformResponseIx(swap.swapInstruction)];
|
|
181
|
+
return {
|
|
182
|
+
swapTxs: {
|
|
183
|
+
setupIxs: transformResponseIxs(swap.setupInstructions),
|
|
184
|
+
swapIxs,
|
|
185
|
+
cleanupIxs: transformResponseIxs(
|
|
186
|
+
swap.cleanupInstruction ? [swap.cleanupInstruction] : [],
|
|
187
|
+
),
|
|
188
|
+
},
|
|
189
|
+
lookupTables: swap.addressLookupTableAddresses.map(
|
|
190
|
+
(k) => new web3.PublicKey(k),
|
|
191
|
+
),
|
|
192
|
+
swapResponse: {
|
|
193
|
+
swapInAmountLamports: new Decimal(quote.inAmount),
|
|
194
|
+
swapOutAmountLamports: new Decimal(quote.outAmount),
|
|
195
|
+
swapMinOutAmountLamports: new Decimal(quote.otherAmountThreshold),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
} catch (e) {
|
|
199
|
+
if (e instanceof ResponseError) {
|
|
200
|
+
const body = (await e.response.json()) as ErrorBody;
|
|
201
|
+
throw new JupSwapResponseError(e, body);
|
|
202
|
+
}
|
|
203
|
+
throw e;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function scaleJupQuoteResponse(
|
|
208
|
+
ogQuote: QuoteResponse,
|
|
209
|
+
swapInputAmountLamports: Decimal,
|
|
210
|
+
): QuoteResponse {
|
|
211
|
+
const ogInLamports = new Decimal(ogQuote.inAmount);
|
|
212
|
+
const ogOutLamports = new Decimal(ogQuote.outAmount);
|
|
213
|
+
const ogOtherAmountThreshold = new Decimal(ogQuote.otherAmountThreshold);
|
|
214
|
+
const scale = swapInputAmountLamports.div(ogInLamports);
|
|
215
|
+
const scaledOutLamports = ogOutLamports.mul(scale).floor();
|
|
216
|
+
const scaledOtherAmountThreshold = ogOtherAmountThreshold.mul(scale).floor();
|
|
217
|
+
const newRoutes = ogQuote.routePlan.map((r) => {
|
|
218
|
+
const ogSwapInfo = r.swapInfo;
|
|
219
|
+
const newSwapInfo: SwapInfo = {
|
|
220
|
+
...ogSwapInfo,
|
|
221
|
+
inAmount: new Decimal(r.swapInfo.inAmount).mul(scale).ceil().toString(),
|
|
222
|
+
outAmount: new Decimal(r.swapInfo.outAmount)
|
|
223
|
+
.mul(scale)
|
|
224
|
+
.floor()
|
|
225
|
+
.toString(),
|
|
226
|
+
feeAmount: new Decimal(r.swapInfo.feeAmount).mul(scale).ceil().toString(),
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
...r,
|
|
230
|
+
swapInfo: newSwapInfo,
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
const newQuote = {
|
|
234
|
+
...ogQuote,
|
|
235
|
+
inAmount: swapInputAmountLamports.toString(),
|
|
236
|
+
outAmount: scaledOutLamports.toString(),
|
|
237
|
+
otherAmountThreshold: scaledOtherAmountThreshold.toString(),
|
|
238
|
+
routePlan: newRoutes,
|
|
239
|
+
};
|
|
240
|
+
return newQuote;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function getMaxAccountsWithBuffer(
|
|
244
|
+
numberOfUniqueAccs: number,
|
|
245
|
+
buffer: number = DEFAULT_MAX_ACCOUNTS_BUFFER,
|
|
246
|
+
): number {
|
|
247
|
+
return maxLockedAccounts(numberOfUniqueAccs + buffer);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function maxLockedAccounts(count: number): number {
|
|
251
|
+
return MAX_LOCKED_ACCOUNTS - count;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Wrapper to read the response body multiple times
|
|
256
|
+
*/
|
|
257
|
+
export class JupQuoteResponseError extends Error {
|
|
258
|
+
err: ResponseError;
|
|
259
|
+
body: ErrorBody;
|
|
260
|
+
|
|
261
|
+
constructor(err: ResponseError, body: ErrorBody) {
|
|
262
|
+
super(
|
|
263
|
+
`Received ${err.response.statusText} (${err.response.status}) error response for jup quote. Request url: ${
|
|
264
|
+
err.response.url
|
|
265
|
+
} \nResponse body:\n${JSON.stringify(body, null, 2)}`,
|
|
266
|
+
);
|
|
267
|
+
this.err = err;
|
|
268
|
+
this.body = body;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Wrapper to read the response body multiple times
|
|
274
|
+
*/
|
|
275
|
+
export class JupSwapResponseError extends Error {
|
|
276
|
+
err: ResponseError;
|
|
277
|
+
body: ErrorBody;
|
|
278
|
+
|
|
279
|
+
constructor(err: ResponseError, body: ErrorBody) {
|
|
280
|
+
super(
|
|
281
|
+
`Received ${err.response.statusText} (${err.response.status}) error response for jup swap. Request url: ${
|
|
282
|
+
err.response.url
|
|
283
|
+
} \nResponse body:\n${JSON.stringify(body, null, 2)}`,
|
|
284
|
+
);
|
|
285
|
+
this.err = err;
|
|
286
|
+
this.body = body;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const getLookupTableAccountsFromKeys = async (
|
|
291
|
+
connection: web3.Connection,
|
|
292
|
+
keys: web3.PublicKey[],
|
|
293
|
+
): Promise<web3.AddressLookupTableAccount[]> => {
|
|
294
|
+
const lookupTableAccounts: web3.AddressLookupTableAccount[] = [];
|
|
295
|
+
|
|
296
|
+
for (const lookupTable of keys) {
|
|
297
|
+
const lookupTableAccount = await connection
|
|
298
|
+
.getAddressLookupTable(lookupTable)
|
|
299
|
+
.then((res) => res.value);
|
|
300
|
+
|
|
301
|
+
if (!lookupTableAccount) {
|
|
302
|
+
throw new Error("lookup table is not found");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
lookupTableAccounts.push(lookupTableAccount);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return lookupTableAccounts;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export function transformResponseIx(
|
|
312
|
+
ix: Instruction,
|
|
313
|
+
): web3.TransactionInstruction {
|
|
314
|
+
return new web3.TransactionInstruction({
|
|
315
|
+
programId: new web3.PublicKey(ix.programId),
|
|
316
|
+
keys: ix.accounts.map((k) => ({
|
|
317
|
+
pubkey: new web3.PublicKey(k.pubkey),
|
|
318
|
+
isSigner: k.isSigner,
|
|
319
|
+
isWritable: k.isWritable,
|
|
320
|
+
})),
|
|
321
|
+
data: ix.data ? Buffer.from(ix.data, "base64") : undefined,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function transformResponseIxs(
|
|
326
|
+
ixs: Instruction[],
|
|
327
|
+
): web3.TransactionInstruction[] {
|
|
328
|
+
return ixs.map((ix) => transformResponseIx(ix));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export type SwapQuote<QuoteResponse> = {
|
|
332
|
+
priceAInB: Decimal;
|
|
333
|
+
quoteResponse?: QuoteResponse;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export type SwapIxs = {
|
|
337
|
+
preActionIxs: web3.TransactionInstruction[];
|
|
338
|
+
swapIxs: web3.TransactionInstruction[];
|
|
339
|
+
lookupTables: web3.PublicKey[];
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export type SwapInputs = {
|
|
343
|
+
inputAmountLamports: Decimal;
|
|
344
|
+
minOutAmountLamports?: Decimal;
|
|
345
|
+
inputMint: web3.PublicKey;
|
|
346
|
+
outputMint: web3.PublicKey;
|
|
347
|
+
};
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { BN, web3 } from "@coral-xyz/anchor";
|
|
2
|
+
import Decimal from "decimal.js";
|
|
3
|
+
import {
|
|
4
|
+
createJupiterApiClient,
|
|
5
|
+
Instruction,
|
|
6
|
+
QuoteGetRequest,
|
|
7
|
+
QuoteResponse,
|
|
8
|
+
ResponseError,
|
|
9
|
+
SwapInfo,
|
|
10
|
+
SwapInstructionsPostRequest,
|
|
11
|
+
SwapMode,
|
|
12
|
+
} from "@jup-ag/api";
|
|
13
|
+
import axios from "axios";
|
|
14
|
+
|
|
15
|
+
const JUPITER_PRICE_API = "https://api.jup.ag/price/v2";
|
|
16
|
+
const DEFAULT_JUP_V6_BASE_URL = "https://quote-api.jup.ag/v6";
|
|
17
|
+
|
|
18
|
+
export type ErrorBody = {
|
|
19
|
+
error: string;
|
|
20
|
+
error_code: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type SwapTxResponse = {
|
|
24
|
+
swapTxs: SwapTxs;
|
|
25
|
+
lookupTables: web3.PublicKey[];
|
|
26
|
+
swapResponse: SwapResponse;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type SwapResponse = {
|
|
30
|
+
swapInAmountLamports: Decimal;
|
|
31
|
+
swapOutAmountLamports: Decimal;
|
|
32
|
+
swapMinOutAmountLamports: Decimal;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type SwapTxs = {
|
|
36
|
+
setupIxs: web3.TransactionInstruction[];
|
|
37
|
+
swapIxs: web3.TransactionInstruction[];
|
|
38
|
+
cleanupIxs: web3.TransactionInstruction[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type SwapConfig = {
|
|
42
|
+
slippageBps: number;
|
|
43
|
+
wrapAndUnwrapSol?: boolean;
|
|
44
|
+
onlyDirectRoutes?: boolean;
|
|
45
|
+
swapMode?: SwapMode;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type SwapInputs = {
|
|
49
|
+
inputMint: web3.PublicKey;
|
|
50
|
+
outputMint: web3.PublicKey;
|
|
51
|
+
inputAmountLamports: Decimal;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type SwapIxs = {
|
|
55
|
+
preActionIxs: web3.TransactionInstruction[];
|
|
56
|
+
swapIxs: web3.TransactionInstruction[];
|
|
57
|
+
lookupTables: web3.PublicKey[];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type SwapQuote<T> = {
|
|
61
|
+
priceAInB: Decimal;
|
|
62
|
+
quoteResponse?: T;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export class JupSwapResponseError extends Error {
|
|
66
|
+
constructor(
|
|
67
|
+
public error: ResponseError,
|
|
68
|
+
public body: ErrorBody,
|
|
69
|
+
) {
|
|
70
|
+
super(`JupSwapResponseError: ${body.error} (${body.error_code})`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Define the JupiterUtils interface
|
|
75
|
+
export interface IJupiterUtils {
|
|
76
|
+
getJupiterPrice(mint: web3.PublicKey | string): Promise<number>;
|
|
77
|
+
|
|
78
|
+
getJupiterQuote(
|
|
79
|
+
inputMint: web3.PublicKey,
|
|
80
|
+
outputMint: web3.PublicKey,
|
|
81
|
+
inputMintDecimals: number,
|
|
82
|
+
outputMintDecimals: number,
|
|
83
|
+
inputAmountLamports: Decimal,
|
|
84
|
+
slippageBps: number,
|
|
85
|
+
swapMode?: SwapMode,
|
|
86
|
+
): Promise<SwapQuote<QuoteResponse>>;
|
|
87
|
+
|
|
88
|
+
getJupiterSwap(
|
|
89
|
+
payer: web3.PublicKey,
|
|
90
|
+
inputs: SwapInputs,
|
|
91
|
+
quote: SwapQuote<QuoteResponse>,
|
|
92
|
+
): Promise<SwapIxs>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Default implementation of JupiterUtils
|
|
96
|
+
export class JupiterUtils implements IJupiterUtils {
|
|
97
|
+
async getJupiterPrice(mint: web3.PublicKey | string): Promise<number> {
|
|
98
|
+
const params = {
|
|
99
|
+
ids: mint.toString(),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const res = await axios.get(JUPITER_PRICE_API, { params });
|
|
103
|
+
return res.data.data[mint.toString()]?.price || 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async getJupiterQuote(
|
|
107
|
+
inputMint: web3.PublicKey,
|
|
108
|
+
outputMint: web3.PublicKey,
|
|
109
|
+
inputMintDecimals: number,
|
|
110
|
+
outputMintDecimals: number,
|
|
111
|
+
inputAmountLamports: Decimal,
|
|
112
|
+
slippageBps: number,
|
|
113
|
+
swapMode: SwapMode = "ExactIn",
|
|
114
|
+
): Promise<SwapQuote<QuoteResponse>> {
|
|
115
|
+
const quoteResponse = await quote(
|
|
116
|
+
inputMint,
|
|
117
|
+
outputMint,
|
|
118
|
+
inputAmountLamports,
|
|
119
|
+
{
|
|
120
|
+
slippageBps: slippageBps,
|
|
121
|
+
wrapAndUnwrapSol: false,
|
|
122
|
+
onlyDirectRoutes: true,
|
|
123
|
+
swapMode,
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const inAmount = new Decimal(quoteResponse.inAmount).div(
|
|
128
|
+
new Decimal(10).pow(inputMintDecimals),
|
|
129
|
+
);
|
|
130
|
+
const minAmountOut = new Decimal(quoteResponse.otherAmountThreshold).div(
|
|
131
|
+
new Decimal(10).pow(outputMintDecimals),
|
|
132
|
+
);
|
|
133
|
+
const priceDebtToColl = minAmountOut.div(inAmount);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
priceAInB: priceDebtToColl,
|
|
137
|
+
quoteResponse,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async getJupiterSwap(
|
|
142
|
+
payer: web3.PublicKey,
|
|
143
|
+
inputs: SwapInputs,
|
|
144
|
+
quote: SwapQuote<QuoteResponse>,
|
|
145
|
+
): Promise<SwapIxs> {
|
|
146
|
+
const scaledQuoteResponse = scaleJupQuoteResponse(
|
|
147
|
+
quote.quoteResponse!,
|
|
148
|
+
new Decimal(inputs.inputAmountLamports),
|
|
149
|
+
);
|
|
150
|
+
const { swapTxs, lookupTables } = await swapTxFromQuote(
|
|
151
|
+
payer,
|
|
152
|
+
scaledQuoteResponse,
|
|
153
|
+
{
|
|
154
|
+
slippageBps: quote.quoteResponse!.slippageBps,
|
|
155
|
+
wrapAndUnwrapSol: false,
|
|
156
|
+
onlyDirectRoutes: true,
|
|
157
|
+
swapMode: quote.quoteResponse!.swapMode,
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
preActionIxs: [],
|
|
162
|
+
swapIxs: [...swapTxs.setupIxs, ...swapTxs.swapIxs, ...swapTxs.cleanupIxs],
|
|
163
|
+
lookupTables,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Helper functions
|
|
169
|
+
export async function quote(
|
|
170
|
+
inputMint: web3.PublicKey,
|
|
171
|
+
outputMint: web3.PublicKey,
|
|
172
|
+
amount: Decimal,
|
|
173
|
+
swapConfig: SwapConfig,
|
|
174
|
+
): Promise<QuoteResponse> {
|
|
175
|
+
const quoteApi = createJupiterApiClient({
|
|
176
|
+
basePath: DEFAULT_JUP_V6_BASE_URL,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const quoteRequest: QuoteGetRequest = {
|
|
180
|
+
inputMint: inputMint.toBase58(),
|
|
181
|
+
outputMint: outputMint.toBase58(),
|
|
182
|
+
amount: Number(amount),
|
|
183
|
+
slippageBps: Number(swapConfig.slippageBps),
|
|
184
|
+
swapMode: swapConfig.swapMode,
|
|
185
|
+
onlyDirectRoutes: swapConfig.onlyDirectRoutes,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
return await quoteApi.quoteGet(quoteRequest);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
if (e instanceof ResponseError) {
|
|
192
|
+
const body = (await e.response.json()) as ErrorBody;
|
|
193
|
+
throw new JupSwapResponseError(e, body);
|
|
194
|
+
}
|
|
195
|
+
throw e;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function swapTxFromQuote(
|
|
200
|
+
payer: web3.PublicKey,
|
|
201
|
+
quote: QuoteResponse,
|
|
202
|
+
swapConfig: SwapConfig,
|
|
203
|
+
): Promise<SwapTxResponse> {
|
|
204
|
+
const quoteApi = createJupiterApiClient({
|
|
205
|
+
basePath: DEFAULT_JUP_V6_BASE_URL,
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
const swapParameters: SwapInstructionsPostRequest = {
|
|
209
|
+
swapRequest: {
|
|
210
|
+
userPublicKey: payer.toBase58(),
|
|
211
|
+
quoteResponse: quote,
|
|
212
|
+
computeUnitPriceMicroLamports: 1,
|
|
213
|
+
wrapAndUnwrapSol: swapConfig?.wrapAndUnwrapSol ?? false,
|
|
214
|
+
destinationTokenAccount: undefined,
|
|
215
|
+
useSharedAccounts: false,
|
|
216
|
+
feeAccount: undefined,
|
|
217
|
+
skipUserAccountsRpcCalls: true,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
const swap = await quoteApi.swapInstructionsPost(swapParameters);
|
|
221
|
+
|
|
222
|
+
const swapIxs = [transformResponseIx(swap.swapInstruction)];
|
|
223
|
+
return {
|
|
224
|
+
swapTxs: {
|
|
225
|
+
setupIxs: transformResponseIxs(swap.setupInstructions),
|
|
226
|
+
swapIxs,
|
|
227
|
+
cleanupIxs: transformResponseIxs(
|
|
228
|
+
swap.cleanupInstruction ? [swap.cleanupInstruction] : [],
|
|
229
|
+
),
|
|
230
|
+
},
|
|
231
|
+
lookupTables: swap.addressLookupTableAddresses.map(
|
|
232
|
+
(k) => new web3.PublicKey(k),
|
|
233
|
+
),
|
|
234
|
+
swapResponse: {
|
|
235
|
+
swapInAmountLamports: new Decimal(quote.inAmount),
|
|
236
|
+
swapOutAmountLamports: new Decimal(quote.outAmount),
|
|
237
|
+
swapMinOutAmountLamports: new Decimal(quote.otherAmountThreshold),
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
} catch (e) {
|
|
241
|
+
if (e instanceof ResponseError) {
|
|
242
|
+
const body = (await e.response.json()) as ErrorBody;
|
|
243
|
+
throw new JupSwapResponseError(e, body);
|
|
244
|
+
}
|
|
245
|
+
throw e;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function scaleJupQuoteResponse(
|
|
250
|
+
quoteResponse: QuoteResponse,
|
|
251
|
+
newAmount: Decimal,
|
|
252
|
+
): QuoteResponse {
|
|
253
|
+
const oldAmount = new Decimal(quoteResponse.inAmount);
|
|
254
|
+
const scaleFactor = newAmount.div(oldAmount);
|
|
255
|
+
|
|
256
|
+
const newQuoteResponse = { ...quoteResponse };
|
|
257
|
+
newQuoteResponse.inAmount = newAmount.toString();
|
|
258
|
+
// Calculate the new output amount based on the price and scale factor
|
|
259
|
+
const outputAmount = new Decimal(quoteResponse.outAmount);
|
|
260
|
+
newQuoteResponse.outAmount = outputAmount.mul(scaleFactor).toString();
|
|
261
|
+
newQuoteResponse.otherAmountThreshold = new Decimal(
|
|
262
|
+
quoteResponse.otherAmountThreshold,
|
|
263
|
+
)
|
|
264
|
+
.mul(scaleFactor)
|
|
265
|
+
.toString();
|
|
266
|
+
|
|
267
|
+
return newQuoteResponse;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function transformResponseIxs(
|
|
271
|
+
instructions: Instruction[],
|
|
272
|
+
): web3.TransactionInstruction[] {
|
|
273
|
+
return instructions.map(transformResponseIx);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function transformResponseIx(
|
|
277
|
+
instruction: Instruction,
|
|
278
|
+
): web3.TransactionInstruction {
|
|
279
|
+
return new web3.TransactionInstruction({
|
|
280
|
+
programId: new web3.PublicKey(instruction.programId),
|
|
281
|
+
keys: instruction.accounts.map((account) => ({
|
|
282
|
+
pubkey: new web3.PublicKey(account.pubkey),
|
|
283
|
+
isSigner: account.isSigner,
|
|
284
|
+
isWritable: account.isWritable,
|
|
285
|
+
})),
|
|
286
|
+
data: Buffer.from(instruction.data, "base64"),
|
|
287
|
+
});
|
|
288
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import winston from "winston";
|
|
2
|
+
|
|
3
|
+
// Logger setup
|
|
4
|
+
export const logger = winston.createLogger({
|
|
5
|
+
level: "debug",
|
|
6
|
+
defaultMeta: {
|
|
7
|
+
service: "rpc-client",
|
|
8
|
+
},
|
|
9
|
+
format: winston.format.combine(
|
|
10
|
+
winston.format.timestamp(),
|
|
11
|
+
winston.format.printf((info) => {
|
|
12
|
+
const { timestamp, level, message, service, ...metadata } = info;
|
|
13
|
+
return (
|
|
14
|
+
`${timestamp} [${level}] [${service}]: ${message}` +
|
|
15
|
+
(Object.keys(metadata).length ? ` ${JSON.stringify(metadata)}` : "")
|
|
16
|
+
);
|
|
17
|
+
}),
|
|
18
|
+
winston.format.json(),
|
|
19
|
+
),
|
|
20
|
+
transports: [new winston.transports.Console()],
|
|
21
|
+
});
|