@render-foundation/utils 0.0.165 → 0.0.167

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.
@@ -1,194 +1,445 @@
1
- import { PublicKey } from '@solana/web3.js';
2
- import { renderMint, buyAndBurnEscrow, passthruEscrow, v3SquadsVault, upgradeRewardsEscrow, upgradeRewardsCircuitBreaker, usdcMint, devsSquadsVault, masterSquadsVault, bridgeEscrow, } from './constants';
3
- import { emissionDistributorKey, emissionDistributorV1Key, } from '@render-foundation/emission-distributor-sdk';
4
- export const solscan = (cookie) => {
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { renderMint, buyAndBurnEscrow, passthruEscrow, v3SquadsVault, upgradeRewardsEscrow, upgradeRewardsCircuitBreaker, usdcMint, devsSquadsVault, masterSquadsVault, bridgeEscrow, polyBridgeEscrow, } from "./constants";
3
+ import { pgClient } from "./client/pg/v2Client";
4
+ import { emissionDistributorKey, emissionDistributorV1Key } from "@render-foundation/emission-distributor-sdk";
5
+ import { Helius } from "helius-sdk";
6
+ export const solscanProClient = (apiKey) => {
5
7
  const headers = {
6
- accept: 'application/json, text/plain, */*',
7
- 'accept-language': 'en-US,en;q=0.9',
8
- 'accept-encoding': 'gzip, deflate, br',
9
- connection: 'keep-alive',
10
- cookie: cookie,
11
- origin: 'https://solscan.io',
12
- referer: 'https://solscan.io/',
13
- 'sec-ch-ua': '"Not A(Brand";v="99", "Google Chrome";v="120", "Chromium";v="120"',
14
- 'sec-ch-ua-mobile': '?0',
15
- 'sec-ch-ua-platform': '"macOS"',
16
- 'sec-fetch-dest': 'empty',
17
- 'sec-fetch-mode': 'cors',
18
- 'sec-fetch-site': 'same-site',
19
- 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
20
- 'cache-control': 'no-cache',
21
- pragma: 'no-cache',
8
+ token: apiKey,
22
9
  };
23
- const url = 'https://api-v2.solscan.io';
10
+ const baseUrl = "https://pro-api.solscan.io/v2.0";
24
11
  return {
25
- async getBalanceChange(f) {
26
- const r = {
27
- page: '1',
28
- page_size: '100',
12
+ async getSignaturesForAddress(addr, options) {
13
+ const allSignatures = [];
14
+ let before = options?.before;
15
+ const limit = Math.min(options?.limit || 40, 40);
16
+ while (true) {
17
+ const params = new URLSearchParams({
18
+ address: addr.toString(),
19
+ limit: limit.toString(),
20
+ });
21
+ if (before) {
22
+ params.append("before", before);
23
+ }
24
+ const endpoint = `${baseUrl}/account/transactions?${params}`;
25
+ const response = await fetch(endpoint, { headers });
26
+ if (!response.ok) {
27
+ throw new Error(`Failed to fetch signatures: ${response.statusText}`);
28
+ }
29
+ const data = await response.json();
30
+ const signatures = data.data || [];
31
+ const transformedSigs = signatures.map((tx) => ({
32
+ signature: tx.txHash || tx.signature,
33
+ blockTime: tx.blockTime || tx.block_time || 0,
34
+ err: tx.status !== "Success" ? { err: tx.status } : null,
35
+ }));
36
+ allSignatures.push(...transformedSigs);
37
+ if (signatures.length < limit || (options?.limit && allSignatures.length >= options.limit)) {
38
+ break;
39
+ }
40
+ // Set before to the last signature for next page
41
+ if (signatures.length > 0) {
42
+ before = signatures[signatures.length - 1].txHash || signatures[signatures.length - 1].signature;
43
+ }
44
+ }
45
+ return allSignatures.slice(0, options?.limit);
46
+ },
47
+ async getParsedTransaction(signature) {
48
+ const endpoint = `${baseUrl}/transaction/details?tx=${signature}`;
49
+ const response = await fetch(endpoint, { headers });
50
+ if (!response.ok) {
51
+ throw new Error(`Failed to fetch transaction: ${response.statusText}`);
52
+ }
53
+ const data = await response.json();
54
+ return {
55
+ meta: {
56
+ err: data.status !== "Success" ? { err: data.status } : null,
57
+ logMessages: data.logMessages || [],
58
+ preTokenBalances: data.inputAccount || [],
59
+ postTokenBalances: data.outputAccount || [],
60
+ },
61
+ blockTime: data.blockTime || data.block_time || 0,
62
+ status: data.status,
63
+ err: data.status !== "Success" ? { err: data.status } : null,
64
+ tokenBalances: data.tokenBalances || [],
65
+ preTokenBalances: data.inputAccount || [],
66
+ postTokenBalances: data.outputAccount || [],
29
67
  };
30
- if (f.mint) {
31
- r['token'] = f.mint.toString();
32
- }
33
- if (f.flow) {
34
- r['flow'] = f.flow;
35
- }
36
- if (f.addr) {
37
- r['address'] = f.addr.toString();
38
- }
39
- if (f.tokenAccount) {
40
- r['token_account'] = f.tokenAccount.toString();
41
- }
42
- const params = new URLSearchParams(r);
43
- const ep = `${url}/v2/account/balance_change?${params}`;
44
- console.log(`fetching ${ep}`);
45
- const res = await fetch(ep, { headers });
46
- const body = await res.json();
47
- return body.data.filter((t) => t.change_type == 'inc').map(({ trans_id, block_time, amount, token_decimals }) => ({
48
- amount: amount,
49
- tokenDecimals: token_decimals,
50
- sig: trans_id,
51
- blockTime: block_time,
52
- })).filter((t) => t.amount != 0);
68
+ },
69
+ async getBalanceChange(f) {
70
+ try {
71
+ const allTransfers = [];
72
+ let page = 1;
73
+ const pageSize = Math.min(f.options?.limit || 100, 100);
74
+ while (true) {
75
+ const params = new URLSearchParams({
76
+ address: f.addr.toString(),
77
+ page: page.toString(),
78
+ page_size: pageSize.toString(),
79
+ });
80
+ if (f.mint) {
81
+ params.append("token", f.mint.toString());
82
+ }
83
+ if (f.flow) {
84
+ params.append("flow", f.flow);
85
+ }
86
+ if (f.tokenAccount) {
87
+ params.append("token_account", f.tokenAccount.toString());
88
+ }
89
+ const endpoint = `${baseUrl}/account/balance_change?${params}`;
90
+ const response = await fetch(endpoint, { headers });
91
+ if (!response.ok) {
92
+ console.error(`Failed to fetch balance changes: ${response.statusText}`);
93
+ break;
94
+ }
95
+ const body = await response.json();
96
+ const transfers = [];
97
+ for (const item of body.data || []) {
98
+ if (item.change_type === (f.flow === "in" ? "inc" : "dec")) {
99
+ transfers.push({
100
+ amount: Math.abs(item.amount || 0),
101
+ tokenDecimals: item.token_decimals || 0,
102
+ sig: item.trans_id,
103
+ blockTime: item.block_time || 0,
104
+ });
105
+ }
106
+ }
107
+ allTransfers.push(...transfers);
108
+ if (transfers.length === 0 || transfers.length < pageSize) {
109
+ break;
110
+ }
111
+ page++;
112
+ }
113
+ return allTransfers.filter((t) => t.amount !== 0);
114
+ }
115
+ catch (error) {
116
+ console.error("Error in getBalanceChange:", error);
117
+ return [];
118
+ }
53
119
  },
54
120
  async getTransfers(f) {
55
- const r = {
56
- page: '1',
57
- page_size: f.pageSize?.toString() ?? '100',
58
- };
59
- if (f.mint) {
60
- r['token'] = f.mint.toString();
61
- }
62
- if (f.flow) {
63
- r['flow'] = f.flow;
64
- }
65
- if (f.addr) {
66
- r['address'] = f.addr.toString();
67
- }
68
- const params = new URLSearchParams(r);
69
- const ep = `${url}/v2/account/transfer?${params}`;
70
- const res = await fetch(ep, { headers });
71
- if (res.status != 200) {
72
- throw new Error(`failed ${await res.text()}`);
73
- }
74
- const body = await res.json();
75
- return body.data.map(({ trans_id, block_time, amount, token_decimals, to_address, from_address, }) => ({
76
- amount: amount,
77
- tokenDecimals: token_decimals,
78
- fromAddress: new PublicKey(from_address),
79
- toAddress: new PublicKey(to_address),
80
- sig: trans_id,
81
- blockTime: block_time,
82
- }));
121
+ try {
122
+ const allTransfers = [];
123
+ let page = 1;
124
+ const pageSize = Math.min(f.pageSize || 100, 100);
125
+ while (true) {
126
+ const params = new URLSearchParams({
127
+ address: f.addr.toString(),
128
+ token: f.mint.toString(),
129
+ flow: f.flow,
130
+ page: page.toString(),
131
+ page_size: pageSize.toString(),
132
+ });
133
+ const endpoint = `${baseUrl}/account/transfer?${params}`;
134
+ const response = await fetch(endpoint, { headers });
135
+ if (!response.ok) {
136
+ throw new Error(`Failed to fetch transfers: ${response.statusText}`);
137
+ }
138
+ const body = await response.json();
139
+ const transfers = [];
140
+ for (const item of body.data || []) {
141
+ transfers.push({
142
+ amount: item.amount || 0,
143
+ tokenDecimals: item.token_decimals || 0,
144
+ fromAddress: item.from_address ? new PublicKey(item.from_address) : undefined,
145
+ toAddress: item.to_address ? new PublicKey(item.to_address) : undefined,
146
+ sig: item.trans_id,
147
+ blockTime: item.block_time || 0,
148
+ });
149
+ }
150
+ allTransfers.push(...transfers);
151
+ if (transfers.length === 0 || transfers.length < pageSize) {
152
+ break;
153
+ }
154
+ page++;
155
+ }
156
+ return allTransfers.filter((t) => t.amount !== 0);
157
+ }
158
+ catch (error) {
159
+ console.error("Error in getTransfers:", error);
160
+ throw new Error(`Failed to get transfers: ${error}`);
161
+ }
162
+ },
163
+ };
164
+ };
165
+ export const heliusClient = (apiKey) => {
166
+ const helius = new Helius(apiKey, 'mainnet-beta');
167
+ return {
168
+ async getSignaturesForAddress(addr, options) {
169
+ return helius.connection.getSignaturesForAddress(addr, {
170
+ before: options?.before,
171
+ limit: options?.limit || 1000,
172
+ });
173
+ },
174
+ async getParsedTransaction(signature) {
175
+ return helius.connection.getTransaction(signature, {
176
+ maxSupportedTransactionVersion: 0,
177
+ });
178
+ },
179
+ async getBalanceChange(f) {
180
+ try {
181
+ // Get transactions for the address
182
+ const response = await helius.connection.getSignaturesForAddress(f.addr, {
183
+ limit: 100,
184
+ });
185
+ const transfers = [];
186
+ // Process each signature to get transaction details
187
+ for (const sig of response) {
188
+ try {
189
+ const tx = await helius.connection.getTransaction(sig.signature, {
190
+ maxSupportedTransactionVersion: 0,
191
+ });
192
+ if (!tx || tx.meta?.err)
193
+ continue;
194
+ // Look for token balance changes
195
+ const preBalances = tx.meta.preTokenBalances || [];
196
+ const postBalances = tx.meta.postTokenBalances || [];
197
+ for (const postBalance of postBalances) {
198
+ if (postBalance.mint !== f.mint.toString())
199
+ continue;
200
+ if (postBalance.owner !== f.addr.toString())
201
+ continue;
202
+ const preBalance = preBalances.find((pre) => pre.accountIndex === postBalance.accountIndex);
203
+ const preAmount = Number(preBalance?.uiTokenAmount?.amount || 0);
204
+ const postAmount = Number(postBalance.uiTokenAmount?.amount || 0);
205
+ const change = postAmount - preAmount;
206
+ if (change !== 0) {
207
+ const isInflow = change > 0;
208
+ if ((f.flow === 'in' && isInflow) || (f.flow === 'out' && !isInflow)) {
209
+ transfers.push({
210
+ amount: Math.abs(change),
211
+ tokenDecimals: postBalance.uiTokenAmount?.decimals || 0,
212
+ sig: sig.signature,
213
+ blockTime: tx.blockTime || 0,
214
+ });
215
+ }
216
+ }
217
+ }
218
+ }
219
+ catch (e) {
220
+ console.error(`Error processing signature ${sig.signature}:`, e);
221
+ }
222
+ }
223
+ return transfers.filter(t => t.amount !== 0);
224
+ }
225
+ catch (error) {
226
+ console.error('Error in getBalanceChange:', error);
227
+ return [];
228
+ }
229
+ },
230
+ async getTransfers(f) {
231
+ try {
232
+ // Use enhanced transactions API for better token transfer data
233
+ const response = await helius.connection.getSignaturesForAddress(f.addr, {
234
+ limit: f.pageSize || 100,
235
+ });
236
+ const transfers = [];
237
+ for (const sig of response) {
238
+ try {
239
+ const tx = await helius.connection.getTransaction(sig.signature, {
240
+ maxSupportedTransactionVersion: 0,
241
+ });
242
+ if (!tx || tx.meta?.err)
243
+ continue;
244
+ // Parse token transfers from transaction
245
+ const preTokenBalances = tx.meta.preTokenBalances || [];
246
+ const postTokenBalances = tx.meta.postTokenBalances || [];
247
+ // Group by account index to track transfers
248
+ const balanceChanges = new Map();
249
+ for (const postBalance of postTokenBalances) {
250
+ if (postBalance.mint !== f.mint.toString())
251
+ continue;
252
+ const preBalance = preTokenBalances.find(pre => pre.accountIndex === postBalance.accountIndex);
253
+ const preAmount = Number(preBalance?.uiTokenAmount?.amount || 0);
254
+ const postAmount = Number(postBalance.uiTokenAmount?.amount || 0);
255
+ const change = postAmount - preAmount;
256
+ if (change !== 0) {
257
+ balanceChanges.set(postBalance.accountIndex, {
258
+ mint: postBalance.mint,
259
+ owner: postBalance.owner,
260
+ change,
261
+ decimals: postBalance.uiTokenAmount?.decimals || 0
262
+ });
263
+ }
264
+ }
265
+ // Find transfers involving the target address
266
+ for (const [accountIndex, balanceChange] of balanceChanges) {
267
+ const isFromTarget = balanceChange.owner === f.addr.toString() && balanceChange.change < 0;
268
+ const isToTarget = balanceChange.owner === f.addr.toString() && balanceChange.change > 0;
269
+ if ((f.flow === 'out' && isFromTarget) || (f.flow === 'in' && isToTarget)) {
270
+ // Find the counterpart transfer
271
+ let fromAddress;
272
+ let toAddress;
273
+ if (isFromTarget) {
274
+ fromAddress = f.addr;
275
+ // Find who received the tokens
276
+ for (const [_, otherChange] of balanceChanges) {
277
+ if (otherChange.owner !== f.addr.toString() && otherChange.change > 0) {
278
+ toAddress = new PublicKey(otherChange.owner);
279
+ break;
280
+ }
281
+ }
282
+ }
283
+ else {
284
+ toAddress = f.addr;
285
+ // Find who sent the tokens
286
+ for (const [_, otherChange] of balanceChanges) {
287
+ if (otherChange.owner !== f.addr.toString() && otherChange.change < 0) {
288
+ fromAddress = new PublicKey(otherChange.owner);
289
+ break;
290
+ }
291
+ }
292
+ }
293
+ transfers.push({
294
+ amount: Math.abs(balanceChange.change),
295
+ tokenDecimals: balanceChange.decimals,
296
+ fromAddress,
297
+ toAddress,
298
+ sig: sig.signature,
299
+ blockTime: tx.blockTime || 0,
300
+ });
301
+ }
302
+ }
303
+ }
304
+ catch (e) {
305
+ console.error(`Error processing signature ${sig.signature}:`, e);
306
+ }
307
+ }
308
+ return transfers.filter(t => t.amount !== 0);
309
+ }
310
+ catch (error) {
311
+ console.error('Error in getTransfers:', error);
312
+ throw new Error(`Failed to get transfers: ${error}`);
313
+ }
83
314
  },
84
315
  };
85
316
  };
86
317
  export class Backfiller {
87
318
  _types = {};
88
- conn;
89
319
  _txs = [];
320
+ solscanClient;
321
+ heliusClient;
90
322
  pg;
323
+ isDryRun = false;
91
324
  solscan = { pageSize: 100 };
92
- constructor(conn, pg) {
93
- this.conn = conn;
325
+ constructor(solscanApiKey, heliusApiKey, pg, isDryRun = false) {
326
+ this.solscan.apiKey = solscanApiKey;
327
+ this.solscanClient = solscanProClient(solscanApiKey);
328
+ this.heliusClient = heliusClient(heliusApiKey);
94
329
  this.pg = pg;
330
+ this.isDryRun = isDryRun;
95
331
  }
96
332
  emit() {
97
- this._types['emit'] = {};
333
+ this._types["emit"] = {};
98
334
  return this;
99
335
  }
100
336
  loans() {
101
- this._types['loan'] = {};
337
+ this._types["loan"] = {};
102
338
  return this;
103
339
  }
104
- buyBurnUSDCInflow(solscanCookie) {
105
- this._types['buyBurnUSDCInflow'] = {};
106
- this.solscan.cookie = solscanCookie;
340
+ buyBurnUSDCInflow() {
341
+ this._types["buyBurnUSDCInflow"] = {};
107
342
  return this;
108
343
  }
109
- manualMint(solscanCookie) {
110
- this._types['manualMint'] = {};
111
- this.solscan.cookie = solscanCookie;
344
+ manualMint() {
345
+ this._types["manualMint"] = {};
112
346
  return this;
113
347
  }
114
348
  vaultOutflows(p) {
115
- this._types['vaultOutflows'] = {};
116
- this.solscan.cookie = p.solscanCookie;
117
- this.solscan.pageSize = p.pageSize;
349
+ this._types["vaultOutflows"] = {};
350
+ this.solscan.pageSize = p.pageSize || 100;
118
351
  return this;
119
352
  }
120
353
  txs(...sigs) {
121
354
  this._txs.push(...sigs);
122
355
  }
123
- indexLoans = async () => {
356
+ formatAmount(amount, decimals = 8) {
357
+ const amt = typeof amount === "bigint" ? Number(amount) : amount;
358
+ return (amt / Math.pow(10, decimals)).toLocaleString(undefined, {
359
+ maximumFractionDigits: 8,
360
+ minimumFractionDigits: 0,
361
+ });
362
+ }
363
+ formatTimestamp(blockTime) {
364
+ return new Date(blockTime * 1000).toISOString();
365
+ }
366
+ async indexLoans() {
124
367
  const trs = [
125
368
  {
126
369
  transfer: {
127
- toSolKey: '2P2gisXpbsYEUsTDQKdcDKSg1UZvx7qxfLTkvJGZdeJC',
370
+ toSolKey: "2P2gisXpbsYEUsTDQKdcDKSg1UZvx7qxfLTkvJGZdeJC",
128
371
  fromSolKey: v3SquadsVault,
129
- solTx: '3oC8BybX5YrkfPyvV8rAE1tnJr6egtZFHLghqHuLi3eaT4aWm87qZPJFrjFRn6nwVemd2ponsew85tBs6VVQzTJv',
130
- executedAt: new Date('December 22, 2023 04:13:58 UTC'),
131
- symbol: 'RENDER',
132
- channel: 'loan',
372
+ solTx: "3oC8BybX5YrkfPyvV8rAE1tnJr6egtZFHLghqHuLi3eaT4aWm87qZPJFrjFRn6nwVemd2ponsew85tBs6VVQzTJv",
373
+ executedAt: new Date("December 22, 2023 04:13:58 UTC"),
374
+ symbol: "RENDER",
375
+ channel: "loan",
133
376
  amountPayed: BigInt(44999 * Math.pow(10, 8)),
134
377
  },
135
378
  },
136
379
  {
137
380
  transfer: {
138
- toSolKey: '2P2gisXpbsYEUsTDQKdcDKSg1UZvx7qxfLTkvJGZdeJC',
381
+ toSolKey: "2P2gisXpbsYEUsTDQKdcDKSg1UZvx7qxfLTkvJGZdeJC",
139
382
  fromSolKey: v3SquadsVault,
140
- channel: 'loan',
141
- solTx: '2f5sEBHcP8wpF79voUEUiP8pryDt7FvqrTXQrEpXzAxbzyFYPkavZGtJc5N8CXhfbJav8hQTr9YcENKseiouaWzc',
142
- executedAt: new Date('December 22, 2023 02:39:53 UTC'),
143
- symbol: 'RENDER',
383
+ channel: "loan",
384
+ solTx: "2f5sEBHcP8wpF79voUEUiP8pryDt7FvqrTXQrEpXzAxbzyFYPkavZGtJc5N8CXhfbJav8hQTr9YcENKseiouaWzc",
385
+ executedAt: new Date("December 22, 2023 02:39:53 UTC"),
386
+ symbol: "RENDER",
144
387
  amountPayed: BigInt(1 * Math.pow(10, 8)),
145
388
  },
146
389
  },
147
390
  {
148
391
  transfer: {
149
- toSolKey: '5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1',
392
+ toSolKey: "5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1",
150
393
  fromSolKey: v3SquadsVault,
151
- channel: 'loan',
152
- solTx: '42n55BZcKVBCSZQoaneBQLn57v7zZGZorGe3uBLheGu4BQtpaTChdT1z3vHwgU4MHghJw3wJ72rFLkib761ewJCj',
153
- executedAt: new Date('January 12, 2024 00:17:02 UTC'),
154
- symbol: 'RENDER',
394
+ channel: "loan",
395
+ solTx: "42n55BZcKVBCSZQoaneBQLn57v7zZGZorGe3uBLheGu4BQtpaTChdT1z3vHwgU4MHghJw3wJ72rFLkib761ewJCj",
396
+ executedAt: new Date("January 12, 2024 00:17:02 UTC"),
397
+ symbol: "RENDER",
155
398
  amountPayed: BigInt(1 * Math.pow(10, 8)),
156
399
  },
157
400
  },
158
401
  {
159
402
  transfer: {
160
- toSolKey: '5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1',
403
+ toSolKey: "5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1",
161
404
  fromSolKey: v3SquadsVault,
162
- channel: 'loan',
163
- solTx: 'THJLvA1UTqH22hNP7Ecnfn4iY736VJAZ8scgSUjrDDaGpSQJmGYKJYXGtDu6TB96fKo6mJBtUBZvLb4ors17JUR',
164
- executedAt: new Date('January 17, 2024 22:09:58 UTC'),
165
- symbol: 'RENDER',
405
+ channel: "loan",
406
+ solTx: "THJLvA1UTqH22hNP7Ecnfn4iY736VJAZ8scgSUjrDDaGpSQJmGYKJYXGtDu6TB96fKo6mJBtUBZvLb4ors17JUR",
407
+ executedAt: new Date("January 17, 2024 22:09:58 UTC"),
408
+ symbol: "RENDER",
166
409
  amountPayed: BigInt(49999 * Math.pow(10, 8)),
167
410
  },
168
411
  },
169
412
  {
170
413
  transfer: {
171
- toSolKey: '5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1',
414
+ toSolKey: "5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1",
172
415
  fromSolKey: devsSquadsVault,
173
- channel: 'loan',
174
- solTx: '5BcxrMExq34jtMB5c3bH3A8TN4pCp9sxjC5HMcS7Mh8pig3r2eQhvRnJeE1RBTyQ4JDXyANWmDU9SKdYFyT2wDoq',
175
- executedAt: new Date('February 28, 2024 22:43:21 UTC'),
176
- symbol: 'RENDER',
416
+ channel: "loan",
417
+ solTx: "5BcxrMExq34jtMB5c3bH3A8TN4pCp9sxjC5HMcS7Mh8pig3r2eQhvRnJeE1RBTyQ4JDXyANWmDU9SKdYFyT2wDoq",
418
+ executedAt: new Date("February 28, 2024 22:43:21 UTC"),
419
+ symbol: "RENDER",
177
420
  amountPayed: BigInt(1 * Math.pow(10, 8)),
178
421
  },
179
422
  },
180
423
  {
181
424
  transfer: {
182
- toSolKey: '5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1',
425
+ toSolKey: "5DGbogKUo8haEYC9Tt4FTHE5ue55bjLC4DpSgE1g6yT1",
183
426
  fromSolKey: devsSquadsVault,
184
- channel: 'loan',
185
- solTx: '5E4E35CT4e3fhbMAwcKdfAvJL4uUHR8hHE8REmKnDCEsFZ6mxaE1YyeYK8Sbg5AJqBJxsZkcYSohY8T14YLC5pXs',
186
- executedAt: new Date('February 29, 2024 01:17:51 UTC'),
187
- symbol: 'RENDER',
427
+ channel: "loan",
428
+ solTx: "5E4E35CT4e3fhbMAwcKdfAvJL4uUHR8hHE8REmKnDCEsFZ6mxaE1YyeYK8Sbg5AJqBJxsZkcYSohY8T14YLC5pXs",
429
+ executedAt: new Date("February 29, 2024 01:17:51 UTC"),
430
+ symbol: "RENDER",
188
431
  amountPayed: BigInt(199999 * Math.pow(10, 8)),
189
432
  },
190
433
  },
191
434
  ];
435
+ if (this.isDryRun) {
436
+ for (const t of trs) {
437
+ const amount = this.formatAmount(t.transfer.amountPayed);
438
+ const timestamp = this.formatTimestamp(t.transfer.executedAt.getTime() / 1000);
439
+ console.log(`${t.transfer.solTx} | ${timestamp} | ${t.transfer.channel} | ${t.transfer.fromSolKey} -> ${t.transfer.toSolKey} | ${amount} ${t.transfer.symbol}`);
440
+ }
441
+ return;
442
+ }
192
443
  for (const t of trs) {
193
444
  try {
194
445
  await this.pg.createPayments(this.pg.db, [t]);
@@ -197,7 +448,7 @@ export class Backfiller {
197
448
  console.log(`except ${e}`);
198
449
  }
199
450
  }
200
- };
451
+ }
201
452
  async findEmitTxs() {
202
453
  const fetchSigs = async (ads) => {
203
454
  const uniqSigs = {};
@@ -205,9 +456,10 @@ export class Backfiller {
205
456
  for (const ad of ads) {
206
457
  const sigs = [];
207
458
  while (true) {
208
- const sigsBatch = await this.conn.getSignaturesForAddress(ad, {
459
+ const sigsBatch = await this.heliusClient.getSignaturesForAddress(ad, {
209
460
  before: sigs.length > 0 ? sigs[sigs.length - 1].signature : undefined,
210
- }, 'confirmed');
461
+ limit: 1000,
462
+ });
211
463
  sigs.push(...sigsBatch.filter((s) => !uniqSigs[s.signature]));
212
464
  for (const s of sigsBatch) {
213
465
  uniqSigs[s.signature] = true;
@@ -216,7 +468,9 @@ export class Backfiller {
216
468
  break;
217
469
  }
218
470
  }
219
- console.log(`fetched ${sigs.length} sigs for ${ad}`);
471
+ if (!this.isDryRun) {
472
+ console.log(`fetched ${sigs.length} sigs for ${ad}`);
473
+ }
220
474
  sigsAgg.push(...sigs);
221
475
  }
222
476
  sigsAgg.sort((a, b) => b.blockTime - a.blockTime);
@@ -226,98 +480,130 @@ export class Backfiller {
226
480
  emissionDistributorKey(new PublicKey(renderMint))[0],
227
481
  emissionDistributorV1Key(new PublicKey(renderMint))[0],
228
482
  ]);
229
- console.log(`sigs ${sigs.length}`);
483
+ if (!this.isDryRun) {
484
+ console.log(`sigs ${sigs.length}`);
485
+ }
230
486
  let proced = 0;
231
487
  const out = [];
232
488
  for (const s of sigs) {
233
- const p = await this.conn.getParsedTransaction(s.signature, {
234
- maxSupportedTransactionVersion: 0,
235
- commitment: 'confirmed',
236
- });
237
- if (p.meta.err) {
489
+ const p = await this.heliusClient.getParsedTransaction(s.signature);
490
+ if (p?.meta?.err || p?.err) {
238
491
  continue;
239
492
  }
240
- for (const l of p.meta.logMessages) {
241
- if (l.includes('DistributeEmissionsV0') ||
242
- l.includes('DistributeEmissionsV1')) {
493
+ const logMessages = p?.meta?.logMessages || [];
494
+ for (const l of logMessages) {
495
+ if (l.includes("DistributeEmissionsV0") || l.includes("DistributeEmissionsV1")) {
243
496
  out.push(s.signature);
497
+ if (this.isDryRun) {
498
+ console.log(`${s.signature} | ${this.formatTimestamp(s.blockTime)} | emission | Emission Distributor -> Various Recipients | N/A RENDER`);
499
+ }
500
+ break;
244
501
  }
245
502
  }
246
503
  proced += 1;
247
- if (proced % 50 == 0) {
504
+ if (proced % 10 == 0 && !this.isDryRun) {
248
505
  console.log(`proced ${proced}`);
249
506
  }
250
507
  }
251
508
  console.log(`emit txs: ${out.length}`);
252
- //console.log(`last ${sigs.slice(sigs.length - 5).map((s) => s.signature)}`)
253
509
  return out;
254
510
  }
255
- async run() {
256
- for (const t of Object.keys(this._types)) {
257
- if (t == 'emit') {
258
- const txs = await this.findEmitTxs();
259
- await this.indexTxs(txs, 'emit');
511
+ async indexTxs(txs, channel, reindexTx = false, deleteFailed = false, mints = [renderMint, usdcMint], senders = [passthruEscrow, v3SquadsVault, upgradeRewardsEscrow, upgradeRewardsCircuitBreaker, renderMint], assertSender = "") {
512
+ let proced = 0;
513
+ console.log(`processing ${txs.length} txs`);
514
+ const insertedIds = [];
515
+ for (const solTx of txs) {
516
+ if (solTx == "") {
517
+ continue;
260
518
  }
261
- else if (t == 'buyBurnUSDCInflow') {
262
- const trs = await solscan(this.solscan.cookie).getTransfers({
263
- mint: new PublicKey(usdcMint),
264
- flow: 'in',
265
- addr: new PublicKey(buyAndBurnEscrow),
266
- });
267
- for (const t of trs) {
268
- console.log(`buyBurnUSDCInflows: ${JSON.stringify(t)}`);
269
- const { stop } = await this.indexTransfers({ [buyAndBurnEscrow]: { mint: usdcMint, amt: t.amount } }, { blockTime: t.blockTime, sig: t.sig }, t.fromAddress.toString(), 'internal');
270
- if (stop) {
271
- break;
272
- }
519
+ console.log(`solTx ${solTx}`);
520
+ const tx = await this.heliusClient.getParsedTransaction(solTx);
521
+ if (!tx || (tx.status !== "Success" && tx.err)) {
522
+ if (this.isDryRun) {
523
+ console.log(`[DRY RUN] Would delete failed transaction ${solTx} from database`);
524
+ continue;
273
525
  }
526
+ console.log(`shouldnt exist ${solTx} in db. deleting`);
527
+ const res = await this.pg.db.trx.deleteFrom("sol_tx").where("sig", "=", solTx).executeTakeFirstOrThrow();
528
+ console.log(`deleted ${res.numDeletedRows} tx`);
529
+ continue;
274
530
  }
275
- else if (t == 'manualMint') {
276
- const trs = await solscan(this.solscan.cookie).getBalanceChange({
277
- mint: new PublicKey(renderMint),
278
- addr: new PublicKey('AYm4Knn6Sw1f52Eq42ujQ2ez5Xb7iBJeviprFCA7ADCy'),
279
- flow: 'in',
280
- tokenAccount: new PublicKey(bridgeEscrow),
281
- });
282
- for (const t of trs) {
283
- console.log(`manualMints: ${JSON.stringify(t)}`);
284
- const { stop } = await this.indexTransfers({ [bridgeEscrow]: { mint: renderMint, amt: t.amount } }, { blockTime: t.blockTime, sig: t.sig }, renderMint, 'manual_mint');
285
- if (stop) {
286
- break;
287
- }
531
+ const onchain = {};
532
+ const tokenBalances = tx.tokenBalances || [];
533
+ const preTokenBalances = tx.preTokenBalances || [];
534
+ const postTokenBalances = tx.postTokenBalances || [];
535
+ for (const balance of postTokenBalances) {
536
+ const mint = mints.find((m) => balance.mint === m);
537
+ if (mint) {
538
+ const amt = Number(balance.uiTokenAmount?.amount || balance.amount || 0);
539
+ onchain[balance.owner] = { mint, amt };
288
540
  }
289
541
  }
290
- else if (t == 'vaultOutflows') {
291
- for (const addr of [devsSquadsVault, masterSquadsVault]) {
292
- const trs = await solscan(this.solscan.cookie).getTransfers({
293
- mint: new PublicKey(renderMint),
294
- flow: 'out',
295
- addr: new PublicKey(addr),
296
- pageSize: this.solscan.pageSize,
297
- });
298
- inner: for (const t of trs) {
299
- console.log(`vaultOutflow: ${JSON.stringify(t)}`);
300
- const { stop } = await this.indexTransfers({
301
- [t.toAddress.toString()]: { mint: renderMint, amt: t.amount },
302
- }, { blockTime: t.blockTime, sig: t.sig }, addr, 'internal');
303
- if (stop) {
304
- break inner;
542
+ let sender = "";
543
+ for (const balance of preTokenBalances) {
544
+ const mint = mints.find((m) => balance.mint === m);
545
+ if (mint) {
546
+ const amt = Number(balance.uiTokenAmount?.amount || balance.amount || 0);
547
+ if (onchain[balance.owner]) {
548
+ onchain[balance.owner] = { mint, amt: onchain[balance.owner].amt - amt };
549
+ if (onchain[balance.owner].amt < 0) {
550
+ const foundSender = senders.find((s) => s === balance.owner.toString());
551
+ if (!foundSender) {
552
+ throw new Error(`sender ${balance.owner} not in expected senders ${senders}`);
553
+ }
554
+ if (assertSender !== "" && foundSender !== assertSender) {
555
+ throw new Error(`assert sender ${balance.owner} != ${assertSender}`);
556
+ }
557
+ sender = foundSender;
305
558
  }
306
559
  }
307
560
  }
308
561
  }
309
- else if (t == 'loan') {
310
- await this.indexLoans();
562
+ if (channel === "emit") {
563
+ sender = renderMint;
564
+ }
565
+ if (!sender) {
566
+ console.log(`no matching sender found`);
567
+ }
568
+ if (Object.keys(onchain).length === 0) {
569
+ console.log(`skipping ${solTx}. nothing to index`);
570
+ continue;
571
+ }
572
+ const { stop } = await this.indexTransfers(onchain, { blockTime: tx.blockTime || tx.blocktime || 0, sig: solTx }, sender, channel);
573
+ if (stop) {
574
+ break;
575
+ }
576
+ proced += 1;
577
+ if (proced % 10 === 0 && !this.isDryRun) {
578
+ console.log(`proced ${proced}`);
311
579
  }
312
580
  }
581
+ console.log(`done. inserted ${insertedIds.length} transfers: ${insertedIds.join(",")}`);
313
582
  }
314
583
  async indexTransfers(onchain, tx, sender, channel, reindexTx = false) {
315
584
  const ents = Object.entries(onchain).filter(([k, { mint, amt }]) => amt != 0);
585
+ if (this.isDryRun) {
586
+ for (const [k, { amt, mint }] of ents) {
587
+ if (k == sender) {
588
+ continue;
589
+ }
590
+ const symbol = (() => {
591
+ if (mint == renderMint) {
592
+ return "RENDER";
593
+ }
594
+ if (mint == usdcMint) {
595
+ return "USDC";
596
+ }
597
+ return "UNKNOWN";
598
+ })();
599
+ const amount = this.formatAmount(amt);
600
+ const timestamp = this.formatTimestamp(tx.blockTime);
601
+ console.log(`${tx.sig} | ${timestamp} | ${channel} | ${sender} -> ${k} | ${amount} ${symbol}`);
602
+ }
603
+ return { stop: false };
604
+ }
316
605
  if (reindexTx) {
317
- let res = await this.pg.db.trx
318
- .deleteFrom('sol_tx')
319
- .where('sig', '=', tx.sig)
320
- .executeTakeFirstOrThrow();
606
+ const res = await this.pg.db.trx.deleteFrom("sol_tx").where("sig", "=", tx.sig).executeTakeFirstOrThrow();
321
607
  console.log(`deleted ${res.numDeletedRows} tx`);
322
608
  }
323
609
  console.log(`diffs: ${ents.map(([k, v]) => `k ${k} v -> ${JSON.stringify(v)}`)}`);
@@ -335,10 +621,10 @@ export class Backfiller {
335
621
  toSolKey: k,
336
622
  symbol: (() => {
337
623
  if (mint == renderMint) {
338
- return 'RENDER';
624
+ return "RENDER";
339
625
  }
340
626
  if (mint == usdcMint) {
341
- return 'USDC';
627
+ return "USDC";
342
628
  }
343
629
  throw new Error(`unknown mint ${mint}`);
344
630
  })(),
@@ -355,7 +641,7 @@ export class Backfiller {
355
641
  }
356
642
  catch (e) {
357
643
  trx.rollback();
358
- if (`${e}`.includes('duplicate key value violates unique constraint')) {
644
+ if (`${e}`.includes("duplicate key value violates unique constraint")) {
359
645
  console.log(`caught up indexing ${e}`);
360
646
  return { stop: true };
361
647
  }
@@ -365,81 +651,146 @@ export class Backfiller {
365
651
  console.log(`tx ${tx.sig} -> insertedIds ${insertedIds.length}`);
366
652
  return { stop: false };
367
653
  }
368
- async indexTxs(txs, channel, reindexTx = false, deleteFailed = false, mints = [renderMint, usdcMint],
369
- // squadsV4
370
- senders = [
371
- passthruEscrow,
372
- v3SquadsVault,
373
- upgradeRewardsEscrow,
374
- upgradeRewardsCircuitBreaker,
375
- renderMint,
376
- ], assertSender = '') {
377
- let proced = 0;
378
- console.log(`processing ${txs.length} txs`);
379
- const insertedIds = [];
380
- for (const solTx of txs) {
381
- if (solTx == '') {
382
- continue;
654
+ async run() {
655
+ for (const t of Object.keys(this._types)) {
656
+ if (t == "emit") {
657
+ const txs = await this.findEmitTxs();
658
+ await this.indexTxs(txs, "emit");
383
659
  }
384
- console.log(`solTx ${solTx}`);
385
- const tx = await this.conn.getParsedTransaction(solTx, {
386
- maxSupportedTransactionVersion: 0,
387
- commitment: 'confirmed',
388
- });
389
- if (!tx || tx.meta.err) {
390
- console.log(`shouldnt exist ${solTx} in db. deleting`);
391
- let res = await this.pg.db.trx
392
- .deleteFrom('sol_tx')
393
- .where('sig', '=', solTx)
394
- .executeTakeFirstOrThrow();
395
- console.log(`deleted ${res.numDeletedRows} tx`);
396
- continue;
660
+ else if (t == "buyBurnUSDCInflow") {
661
+ const trs = await this.solscanClient.getTransfers({
662
+ mint: new PublicKey(usdcMint),
663
+ flow: "in",
664
+ addr: new PublicKey(buyAndBurnEscrow),
665
+ });
666
+ for (const t of trs) {
667
+ if (!this.isDryRun) {
668
+ console.log(`buyBurnUSDCInflows: ${JSON.stringify(t)}`);
669
+ }
670
+ const { stop } = await this.indexTransfers({ [buyAndBurnEscrow]: { mint: usdcMint, amt: t.amount } }, { blockTime: t.blockTime, sig: t.sig }, t.fromAddress.toString(), "internal");
671
+ if (stop) {
672
+ break;
673
+ }
674
+ }
397
675
  }
398
- const onchain = {};
399
- for (const b of tx.meta.postTokenBalances) {
400
- const mint = mints.find((m) => b.mint == m);
401
- const amt = Number(b.uiTokenAmount.amount ?? 0);
402
- if (mint) {
403
- onchain[b.owner] = { mint, amt };
676
+ else if (t == "manualMint") {
677
+ const trs = await this.solscanClient.getBalanceChange({
678
+ mint: new PublicKey(renderMint),
679
+ addr: new PublicKey("AYm4Knn6Sw1f52Eq42ujQ2ez5Xb7iBJeviprFCA7ADCy"),
680
+ flow: "in",
681
+ tokenAccount: new PublicKey(bridgeEscrow),
682
+ });
683
+ for (const t of trs) {
684
+ if (!this.isDryRun) {
685
+ console.log(`manualMints: ${JSON.stringify(t)}`);
686
+ }
687
+ const { stop } = await this.indexTransfers({ [bridgeEscrow]: { mint: renderMint, amt: t.amount } }, { blockTime: t.blockTime, sig: t.sig }, renderMint, "manual_mint");
688
+ if (stop) {
689
+ break;
690
+ }
691
+ }
692
+ const polyTrs = await this.solscanClient.getBalanceChange({
693
+ mint: new PublicKey(renderMint),
694
+ addr: new PublicKey("AYm4Knn6Sw1f52Eq42ujQ2ez5Xb7iBJeviprFCA7ADCy"),
695
+ flow: "in",
696
+ tokenAccount: new PublicKey(polyBridgeEscrow),
697
+ });
698
+ for (const t of polyTrs) {
699
+ if (!this.isDryRun) {
700
+ console.log(`polyManualMints: ${JSON.stringify(t)}`);
701
+ }
702
+ const { stop } = await this.indexTransfers({ [polyBridgeEscrow]: { mint: renderMint, amt: t.amount } }, { blockTime: t.blockTime, sig: t.sig }, renderMint, "manual_mint");
703
+ if (stop) {
704
+ break;
705
+ }
404
706
  }
405
707
  }
406
- let sender;
407
- for (const b of tx.meta.preTokenBalances) {
408
- const mint = mints.find((m) => b.mint == m);
409
- const amt = Number(b.uiTokenAmount.amount ?? 0);
410
- if (mint) {
411
- onchain[b.owner] = { mint, amt: onchain[b.owner].amt - amt };
412
- if (onchain[b.owner].amt < 0) {
413
- sender = senders.find((s) => s == b.owner.toString());
414
- if (!sender) {
415
- throw new Error(`sender ${b.owner} not in expected senders ${senders}`);
708
+ else if (t == "vaultOutflows") {
709
+ for (const addr of [devsSquadsVault, masterSquadsVault]) {
710
+ const trs = await this.solscanClient.getTransfers({
711
+ mint: new PublicKey(renderMint),
712
+ flow: "out",
713
+ addr: new PublicKey(addr),
714
+ pageSize: this.solscan.pageSize,
715
+ });
716
+ for (const t of trs) {
717
+ if (!this.isDryRun) {
718
+ console.log(`vaultOutflow: ${JSON.stringify(t)}`);
416
719
  }
417
- if (assertSender != '' && sender != assertSender) {
418
- throw new Error(`assert sender ${b.owner} != ${assertSender}`);
720
+ const { stop } = await this.indexTransfers({
721
+ [t.toAddress.toString()]: { mint: renderMint, amt: t.amount },
722
+ }, { blockTime: t.blockTime, sig: t.sig }, addr, "internal");
723
+ if (stop) {
724
+ break;
419
725
  }
420
726
  }
421
727
  }
422
728
  }
423
- if (channel == 'emit') {
424
- sender = renderMint;
425
- }
426
- if (!sender) {
427
- console.log(`no matching sender found`);
428
- }
429
- if (Object.keys(onchain).length == 0) {
430
- console.log(`skipping ${solTx}. nothing to index`);
431
- continue;
432
- }
433
- const { stop } = await this.indexTransfers(onchain, { blockTime: tx.blockTime, sig: solTx }, sender, channel);
434
- if (stop) {
435
- break;
436
- }
437
- proced += 1;
438
- if (proced % 10 == 0) {
439
- console.log(`proced ${proced}`);
729
+ else if (t == "loan") {
730
+ await this.indexLoans();
440
731
  }
441
732
  }
442
- console.log(`done. inserted ${insertedIds.length} transfers: ${insertedIds.join(',')}`);
443
733
  }
444
734
  }
735
+ async function main() {
736
+ const args = process.argv.slice(2);
737
+ const getArgValue = (argName) => {
738
+ const argIndex = args.indexOf(`--${argName}`);
739
+ return argIndex !== -1 && argIndex + 1 < args.length ? args[argIndex + 1] : undefined;
740
+ };
741
+ const hasFlag = (flagName) => {
742
+ return args.includes(`--${flagName}`);
743
+ };
744
+ const solscanApiKey = getArgValue("solscanApiKey");
745
+ if (!solscanApiKey) {
746
+ console.error("Error: --solscanApiKey is required");
747
+ process.exit(1);
748
+ }
749
+ const heliusApiKey = getArgValue("heliusApiKey");
750
+ if (!heliusApiKey) {
751
+ console.error("Error: --heliusApiKey is required");
752
+ process.exit(1);
753
+ }
754
+ const isDryRun = hasFlag("dryRun");
755
+ let pg;
756
+ if (!isDryRun) {
757
+ const pgUrl = getArgValue("pgUrl");
758
+ if (!pgUrl) {
759
+ console.error("Error: --pgUrl is required (unless using --dryRun)");
760
+ process.exit(1);
761
+ }
762
+ pg = pgClient({
763
+ connectionString: pgUrl,
764
+ });
765
+ }
766
+ const backfiller = new Backfiller(solscanApiKey, heliusApiKey, pg, isDryRun);
767
+ if (hasFlag("loans")) {
768
+ backfiller.loans();
769
+ }
770
+ if (hasFlag("vaultOutflows")) {
771
+ backfiller.vaultOutflows({});
772
+ }
773
+ if (hasFlag("emissions")) {
774
+ backfiller.emit();
775
+ }
776
+ if (hasFlag("buyBurnUSDCInflow")) {
777
+ backfiller.buyBurnUSDCInflow();
778
+ }
779
+ if (hasFlag("manualMints")) {
780
+ backfiller.manualMint();
781
+ }
782
+ if (hasFlag("loans") ||
783
+ hasFlag("vaultOutflows") ||
784
+ hasFlag("emissions") ||
785
+ hasFlag("buyBurnUSDCInflow") ||
786
+ hasFlag("manualMints")) {
787
+ await backfiller.run();
788
+ }
789
+ else {
790
+ console.log("No operations specified. Available flags: --loans, --vaultOutflows, --emissions, --buyBurnUSDCInflow, --manualMints, --dryRun");
791
+ }
792
+ }
793
+ if (require.main === module) {
794
+ main().catch(console.error);
795
+ }
445
796
  //# sourceMappingURL=backfiller.js.map