@raac/rpc 1.1.0-beta.56 → 1.1.0-beta.57

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.
Files changed (29) hide show
  1. package/dist/RPCLibrary.js +11 -0
  2. package/dist/oracles/houses/request.js +3 -13
  3. package/dist/oracles/houses/simulate.js +21 -0
  4. package/dist/oracles/prime-rate/config.js +3 -36
  5. package/dist/oracles/prime-rate/request.js +8 -11
  6. package/dist/oracles/prime-rate/simulate.js +25 -0
  7. package/dist/pools/lendingPool/getAdapterAddress.js +8 -10
  8. package/dist/pools/stabilityPool/getStabilityPoolInfo.js +0 -21
  9. package/dist/scripts/analyze_lending_positions.js +277 -0
  10. package/dist/scripts/analyze_transfers.js +247 -0
  11. package/dist/scripts/calculate_portfolio.js +167 -0
  12. package/dist/scripts/index.js +50 -38
  13. package/dist/scripts/run_analysis.js +115 -0
  14. package/dist/scripts/test_lending_analysis.js +105 -0
  15. package/dist/scripts/users.js +304 -0
  16. package/dist/types/RPCLibrary.d.ts +10 -0
  17. package/dist/types/oracles/houses/request.d.ts +2 -1
  18. package/dist/types/oracles/houses/simulate.d.ts +1 -0
  19. package/dist/types/oracles/prime-rate/config.d.ts +2 -1
  20. package/dist/types/oracles/prime-rate/request.d.ts +2 -1
  21. package/dist/types/oracles/prime-rate/simulate.d.ts +1 -0
  22. package/dist/types/pools/stabilityPool/getStabilityPoolInfo.d.ts +0 -1
  23. package/dist/types/scripts/analyze_lending_positions.d.ts +1 -0
  24. package/dist/types/scripts/analyze_transfers.d.ts +1 -0
  25. package/dist/types/scripts/calculate_portfolio.d.ts +1 -0
  26. package/dist/types/scripts/run_analysis.d.ts +1 -0
  27. package/dist/types/scripts/test_lending_analysis.d.ts +1 -0
  28. package/dist/types/scripts/users.d.ts +1 -0
  29. package/package.json +2 -1
@@ -121,6 +121,8 @@ const getWithdrawRequestInfo_2 = require("./pools/stabilityPool/getWithdrawReque
121
121
  const requestWithdraw_2 = require("./pools/stabilityPool/requestWithdraw");
122
122
  const artifacts_1 = require("./utils/artifacts");
123
123
  const errorDecoder_1 = require("./utils/errorDecoder");
124
+ const request_1 = require("./oracles/prime-rate/request");
125
+ const request_2 = require("./oracles/houses/request");
124
126
  class RPCLibrary {
125
127
  signer;
126
128
  isConnected;
@@ -131,6 +133,7 @@ class RPCLibrary {
131
133
  assets;
132
134
  // valid
133
135
  wallet;
136
+ oracles;
134
137
  nfts;
135
138
  contracts;
136
139
  pools;
@@ -291,6 +294,14 @@ class RPCLibrary {
291
294
  decodeErrorMessage: errorDecoder_1.decodeErrorMessage,
292
295
  attachDecodedErrorMessage: errorDecoder_1.attachDecodedErrorMessage,
293
296
  };
297
+ this.oracles = {
298
+ primeRate: {
299
+ request: request_1.request,
300
+ },
301
+ housePrices: {
302
+ request: request_2.request,
303
+ },
304
+ };
294
305
  }
295
306
  async getWallet(privateKey, provider) {
296
307
  // @ts-ignore
@@ -17,7 +17,7 @@ const subscriptionIds = {
17
17
  11155111: 4392,
18
18
  18453: 4392,
19
19
  };
20
- async function request(chainId, houseId, privateKey) {
20
+ async function request(chainId, houseId, signerV5) {
21
21
  const chain = (0, configs_1.getChainConfig)(chainId);
22
22
  const network = chainlink_networks_config_1.default[chainId];
23
23
  if (!chain) {
@@ -37,16 +37,6 @@ async function request(chainId, houseId, privateKey) {
37
37
  }
38
38
  console.log("Local simulation of source code completed...");
39
39
  }
40
- // Get the RPC endpoint from the chain config and derive a v5 provider
41
- const rpcUrl = chain.rpcs?.[0] ?? chain.rpc ?? undefined;
42
- if (!rpcUrl)
43
- throw new Error("No RPC URL found in chain configuration");
44
- const providerV5 = new ethers5_1.ethers.providers.JsonRpcProvider(rpcUrl);
45
- // Recreate the wallet in ethers v5 using the same private key
46
- if (!privateKey) {
47
- throw new Error("Signer passed to request() must expose a privateKey so an ethers v5 wallet can be derived.");
48
- }
49
- const signerV5 = new ethers5_1.ethers.Wallet(privateKey, providerV5);
50
40
  const contractAddress = (0, getContractAddress_1.default)(chainId, "raachousepriceoracle");
51
41
  const contractAbi = (0, artifacts_1.getABI)("raachousepriceoracle");
52
42
  const subscriptionId = subscriptionIds[chainId];
@@ -77,7 +67,7 @@ async function request(chainId, houseId, privateKey) {
77
67
  throw Error(`Consumer contract ${contractAddress} has not been added to subscription ${subscriptionId}`);
78
68
  }
79
69
  // Use the v5 provider for gas data (to avoid type mismatches)
80
- const { gasPrice } = await providerV5.getFeeData();
70
+ const { gasPrice } = await signerV5.provider.getFeeData();
81
71
  // @ts-ignore
82
72
  const gasPriceWei = BigInt(Math.ceil(ethers5_1.ethers.utils.formatUnits(gasPrice, "wei").toString()));
83
73
  const estimatedCostJuels = await subscriptionManager.estimateFunctionsRequestCost({
@@ -104,7 +94,7 @@ async function request(chainId, houseId, privateKey) {
104
94
  version,
105
95
  });
106
96
  const responseListener = new functions_toolkit_1.ResponseListener({
107
- provider: providerV5,
97
+ provider: signerV5.provider,
108
98
  functionsRouterAddress,
109
99
  });
110
100
  console.log(`Waiting for transaction for RAACHousePriceOracle contract ${contractAddress} on network ${network.name} to be confirmed...`);
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.simulate = simulate;
7
+ const functions_toolkit_1 = require("@chainlink/functions-toolkit");
8
+ const config_1 = __importDefault(require("./config"));
9
+ async function simulate(houseId) {
10
+ const { responseBytesHexstring, errorString } = await (0, functions_toolkit_1.simulateScript)({
11
+ ...config_1.default,
12
+ args: [houseId],
13
+ });
14
+ if (responseBytesHexstring) {
15
+ console.log(`\nResponse returned by script during local simulation: ${(0, functions_toolkit_1.decodeResult)(responseBytesHexstring, config_1.default.expectedReturnType).toString()}\n`);
16
+ }
17
+ if (errorString) {
18
+ console.log(`\nError returned by simulated script:\n${errorString}\n`);
19
+ }
20
+ console.log("Local simulation of source code completed...");
21
+ }
@@ -1,45 +1,12 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- const fs = __importStar(require("fs"));
37
3
  const functions_toolkit_1 = require("@chainlink/functions-toolkit");
38
4
  const requestConfig = {
39
- source: fs.readFileSync("./oracles/prime-rate/api.js").toString(),
5
+ name: "prime-rate",
6
+ sourceURL: "https://emerald-many-grasshopper-318.mypinata.cloud/ipfs/bafkreid3kubchc3f2sspzq25pnmnw3mppkhv46xvzg72xx7zqs2plvjbqu",
40
7
  codeLocation: functions_toolkit_1.Location["Inline"],
41
8
  // Optional
42
- secrets: { apiKey: process.env.RAAC_HOUSING_API_KEY ?? "" },
9
+ secrets: { apiKey: process.env.FRED_API_KEY ?? "" },
43
10
  // Optional
44
11
  secretsLocation: functions_toolkit_1.Location["DONHosted"],
45
12
  args: [],
@@ -17,17 +17,20 @@ const subscriptionIds = {
17
17
  11155111: 4392,
18
18
  18453: 4392,
19
19
  };
20
- async function request(chainId, countryCode = "us", privateKey) {
20
+ async function request(chainId, signerV5) {
21
21
  const chain = (0, configs_1.getChainConfig)(chainId);
22
22
  const network = chainlink_networks_config_1.default[chainId];
23
23
  if (!chain) {
24
24
  throw new Error(`Chain configuration not found for chainId: ${chainId}`);
25
25
  }
26
+ const sourceFetch = await fetch(config_1.default.sourceURL);
27
+ const source = await sourceFetch.text();
28
+ console.log("Reading source from URL: ", config_1.default.sourceURL);
26
29
  // Simulate the request first
27
30
  {
28
31
  const { responseBytesHexstring, errorString } = await (0, functions_toolkit_1.simulateScript)({
29
32
  ...config_1.default,
30
- args: [countryCode],
33
+ source,
31
34
  });
32
35
  if (responseBytesHexstring) {
33
36
  console.log(`\nResponse returned by script during local simulation: ${(0, functions_toolkit_1.decodeResult)(responseBytesHexstring, config_1.default.expectedReturnType).toString()}\n`);
@@ -41,12 +44,6 @@ async function request(chainId, countryCode = "us", privateKey) {
41
44
  const rpcUrl = chain.rpcs?.[0] ?? chain.rpc ?? undefined;
42
45
  if (!rpcUrl)
43
46
  throw new Error("No RPC URL found in chain configuration");
44
- const providerV5 = new ethers5_1.ethers.providers.JsonRpcProvider(rpcUrl);
45
- // Recreate the wallet in ethers v5 using the same private key
46
- if (!privateKey) {
47
- throw new Error("Signer passed to request() must expose a privateKey so an ethers v5 wallet can be derived.");
48
- }
49
- const signerV5 = new ethers5_1.ethers.Wallet(privateKey, providerV5);
50
47
  const contractAddress = (0, getContractAddress_1.default)(chainId, "raacprimerateoracle");
51
48
  const contractAbi = (0, artifacts_1.getABI)("raacprimerateoracle");
52
49
  const subscriptionId = subscriptionIds[chainId];
@@ -77,7 +74,7 @@ async function request(chainId, countryCode = "us", privateKey) {
77
74
  throw Error(`Consumer contract ${contractAddress} has not been added to subscription ${subscriptionId}`);
78
75
  }
79
76
  // Use the v5 provider for gas data (to avoid type mismatches)
80
- const { gasPrice } = await providerV5.getFeeData();
77
+ const { gasPrice } = await signerV5.provider.getFeeData();
81
78
  // @ts-ignore
82
79
  const gasPriceWei = BigInt(Math.ceil(ethers5_1.ethers.utils.formatUnits(gasPrice, "wei").toString()));
83
80
  const estimatedCostJuels = await subscriptionManager.estimateFunctionsRequestCost({
@@ -104,14 +101,14 @@ async function request(chainId, countryCode = "us", privateKey) {
104
101
  version,
105
102
  });
106
103
  const responseListener = new functions_toolkit_1.ResponseListener({
107
- provider: providerV5,
104
+ provider: signerV5.provider,
108
105
  functionsRouterAddress,
109
106
  });
110
107
  console.log(`Waiting for transaction for RAACPrimeRateOracle contract ${contractAddress} on network ${network.name} to be confirmed...`);
111
108
  const overrides = {
112
109
  gasLimit: 1_500_000,
113
110
  };
114
- const requestTx = await consumerContract.sendRequest(config_1.default.source, config_1.default.secretsLocation, encryptedSecretsReference, [countryCode], [], subscriptionId, callbackGasLimit, overrides);
111
+ const requestTx = await consumerContract.sendRequest(source, config_1.default.secretsLocation, encryptedSecretsReference, config_1.default.args, [], subscriptionId, callbackGasLimit, overrides);
115
112
  const requestTxReceipt = await requestTx.wait(1);
116
113
  console.log(`Transaction confirmed, see https://sepolia.etherscan.io/tx/${requestTx.hash} for more details.`);
117
114
  console.log(`Functions request has been initiated in transaction ${requestTx.hash} with request ID ${requestTxReceipt?.events[2]?.args?.id}. Note the request ID may change if a re-org occurs, but the transaction hash will remain constant.\nWaiting for fulfillment from the Decentralized Oracle Network...\n`);
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.simulate = simulate;
7
+ const functions_toolkit_1 = require("@chainlink/functions-toolkit");
8
+ const config_1 = __importDefault(require("./config"));
9
+ async function simulate() {
10
+ // Simulate the request first
11
+ const sourceFetch = await fetch(config_1.default.sourceURL);
12
+ const source = await sourceFetch.text();
13
+ console.log("Reading source from URL: ", config_1.default.sourceURL);
14
+ const { responseBytesHexstring, errorString } = await (0, functions_toolkit_1.simulateScript)({
15
+ ...config_1.default,
16
+ source,
17
+ });
18
+ if (responseBytesHexstring) {
19
+ console.log(`\nResponse returned by script during local simulation: ${(0, functions_toolkit_1.decodeResult)(responseBytesHexstring, config_1.default.expectedReturnType).toString()}\n`);
20
+ }
21
+ if (errorString) {
22
+ console.log(`\nError returned by simulated script:\n${errorString}\n`);
23
+ }
24
+ console.log("Local simulation of source code completed...");
25
+ }
@@ -18,16 +18,14 @@ function getAdapterAddress(chainId, adapterName) {
18
18
  throw new Error(`Chain configuration not found for chain ID ${chainId}`);
19
19
  }
20
20
  // Search through all adapters in the chain config
21
- for (const poolType in chainConfig.adapters) {
22
- const poolAdapters = chainConfig.adapters[poolType];
23
- for (const assetType in poolAdapters) {
24
- const adapter = poolAdapters[assetType];
25
- if (adapter.id === adapterName) {
26
- return {
27
- address: adapter.contract,
28
- type: adapter.type
29
- };
30
- }
21
+ const poolAdapters = chainConfig.adapters["lendingpool"];
22
+ for (const assetType in poolAdapters) {
23
+ const adapter = poolAdapters[assetType];
24
+ if (adapter.id === adapterName) {
25
+ return {
26
+ address: adapter.contract,
27
+ type: adapter.type
28
+ };
31
29
  }
32
30
  }
33
31
  return null;
@@ -15,7 +15,6 @@ async function getStabilityPoolInfo(chainId, address, provider) {
15
15
  }
16
16
  throw error;
17
17
  };
18
- console.log(await stabilityPoolContract.deToken());
19
18
  // Fetch data from StabilityPool
20
19
  const [totalDeposits] = await Promise.all([
21
20
  stabilityPoolContract
@@ -30,29 +29,9 @@ async function getStabilityPoolInfo(chainId, address, provider) {
30
29
  .catch(handleError("getUserDeposit")),
31
30
  ]);
32
31
  }
33
- // Fetch RAAC Minter information if needed for APY calculation
34
- const raacMinterAddress = (0, contracts_1.getContractAddress)(chainId, "raacminter");
35
- let apy = 0n;
36
- if (raacMinterAddress && raacMinterAddress !== ethers_1.ethers.ZeroAddress) {
37
- const raacMinterABI = (0, artifacts_1.getABI)("raacminter");
38
- const raacMinterContract = new ethers_1.ethers.Contract(raacMinterAddress, raacMinterABI, provider);
39
- const [emissionRate, totalSupply] = await Promise.all([
40
- raacMinterContract.emissionRate().catch(handleError("emissionRate")),
41
- raacMinterContract
42
- .getTotalSupply()
43
- .catch(handleError("getTotalSupply")),
44
- ]);
45
- // APY Calculation
46
- // Assuming emissionRate is per block and BLOCKS_PER_YEAR is known
47
- const BLOCKS_PER_YEAR = 2102400n; // Approximate number of blocks per year (Ethereum ~15 sec/block)
48
- const emissionRatePerYear = emissionRate * BLOCKS_PER_YEAR;
49
- apy =
50
- totalSupply > 0n ? (emissionRatePerYear * 10000n) / totalSupply : 0n;
51
- }
52
32
  const result = {
53
33
  totalDeposits: ethers_1.ethers.formatEther(totalDeposits ?? 0n),
54
34
  userDeposit: ethers_1.ethers.formatEther(userDeposit ?? 0n),
55
- apy: parseFloat((apy ?? 0n).toString()) / 100, // APY in percentage
56
35
  };
57
36
  return result;
58
37
  }
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const ethers_1 = require("ethers");
7
+ const fs_1 = require("fs");
8
+ const chains_1 = __importDefault(require("../configs/chains"));
9
+ const RPCLibrary_1 = __importDefault(require("../RPCLibrary"));
10
+ const artifacts_1 = require("../utils/artifacts");
11
+ const _helpers_1 = require("../pools/lendingPool/_helpers");
12
+ const BLOCK = 8718016;
13
+ const chainId = 11155111; // Sepolia
14
+ const config = chains_1.default[chainId];
15
+ // Health factor thresholds
16
+ const HEALTH_FACTOR_THRESHOLDS = {
17
+ HIGH_RISK: 1.1e18, // 1.1 in wei
18
+ MEDIUM_RISK: 1.3e18, // 1.3 in wei
19
+ LOW_RISK: 1.5e18, // 1.5 in wei
20
+ };
21
+ // RPC constraints
22
+ const MAX_BLOCK_RANGE = 500; // Maximum blocks per eth_getLogs request
23
+ async function getProvider() {
24
+ return new ethers_1.ethers.JsonRpcProvider(config.rpcs[0]);
25
+ }
26
+ async function getEventsInChunks(contract, filter, fromBlock, toBlock) {
27
+ const allEvents = [];
28
+ // Query events in chunks of MAX_BLOCK_RANGE
29
+ for (let startBlock = fromBlock; startBlock <= toBlock; startBlock += MAX_BLOCK_RANGE) {
30
+ const endBlock = Math.min(startBlock + MAX_BLOCK_RANGE - 1, toBlock);
31
+ console.log(` Querying blocks ${startBlock} to ${endBlock}...`);
32
+ try {
33
+ const events = await contract.queryFilter(filter, startBlock, endBlock);
34
+ allEvents.push(...events);
35
+ console.log(` Found ${events.length} events in this range`);
36
+ }
37
+ catch (error) {
38
+ console.error(` Error querying blocks ${startBlock}-${endBlock}:`, error);
39
+ // Continue with next chunk even if this one fails
40
+ }
41
+ }
42
+ return allEvents;
43
+ }
44
+ async function getAllDepositedUsers() {
45
+ const provider = await getProvider();
46
+ const rpc = new RPCLibrary_1.default();
47
+ rpc.provider = provider;
48
+ rpc.chainId = chainId;
49
+ const allUsers = [];
50
+ // Get current block number
51
+ const currentBlock = await provider.getBlockNumber();
52
+ console.log(`Current block number: ${currentBlock}`);
53
+ // Process each adapter
54
+ for (const adapterName of [
55
+ "raacnftadapter",
56
+ "rwaindextokenadapter"
57
+ ]) {
58
+ try {
59
+ // Get adapter info
60
+ const adapterInfo = rpc.pools.lendingPool.getAdapterAddress(chainId, adapterName);
61
+ if (!adapterInfo) {
62
+ console.log(`Skipping adapter ${adapterName} - not found in config`);
63
+ continue;
64
+ }
65
+ console.log(`Processing adapter: ${adapterInfo.address} (${adapterInfo.type})`);
66
+ if (adapterInfo.type === "ERC20") {
67
+ // For ERC20, we need to scan TokenDeposited events
68
+ const erc20AdapterABI = (0, artifacts_1.getABI)("erc20assetadapter");
69
+ const erc20AdapterContract = new ethers_1.ethers.Contract(adapterInfo.address, erc20AdapterABI, provider);
70
+ // Get all TokenDeposited events in chunks
71
+ const events = await getEventsInChunks(erc20AdapterContract, erc20AdapterContract.filters.TokenDeposited(), BLOCK, // from block
72
+ currentBlock // to block
73
+ );
74
+ for (const event of events) {
75
+ const { token, user, amount } = event.args;
76
+ allUsers.push({
77
+ user,
78
+ adapter: adapterInfo.address,
79
+ adapterType: "ERC20",
80
+ amount: amount.toString()
81
+ });
82
+ }
83
+ }
84
+ else if (adapterInfo.type === "ERC721") {
85
+ // For ERC721, we need to scan NFTDeposited events
86
+ const erc721AdapterABI = (0, artifacts_1.getABI)("erc721assetadapter");
87
+ const erc721AdapterContract = new ethers_1.ethers.Contract(adapterInfo.address, erc721AdapterABI, provider);
88
+ // Get all NFTDeposited events in chunks
89
+ const events = await getEventsInChunks(erc721AdapterContract, erc721AdapterContract.filters.NFTDeposited(), BLOCK, // from block
90
+ currentBlock // to block
91
+ );
92
+ for (const event of events) {
93
+ const { token, user, tokenId } = event.args;
94
+ allUsers.push({
95
+ user,
96
+ adapter: adapterInfo.address,
97
+ adapterType: "ERC721",
98
+ tokenId: tokenId.toString()
99
+ });
100
+ }
101
+ }
102
+ }
103
+ catch (error) {
104
+ console.error(`Error processing adapter ${adapterName}:`, error);
105
+ }
106
+ }
107
+ // Remove duplicates (same user, adapter, and tokenId/amount)
108
+ const uniqueUsers = allUsers.filter((user, index, self) => index === self.findIndex(u => u.user === user.user &&
109
+ u.adapter === user.adapter &&
110
+ u.tokenId === user.tokenId &&
111
+ u.amount === user.amount));
112
+ return uniqueUsers;
113
+ }
114
+ async function getPositionData(user, adapter, adapterType, tokenId, amount) {
115
+ const provider = await getProvider();
116
+ const rpc = new RPCLibrary_1.default();
117
+ rpc.provider = provider;
118
+ rpc.chainId = chainId;
119
+ try {
120
+ // Encode the data parameter based on adapter type
121
+ let data;
122
+ if (adapterType === "ERC20") {
123
+ // For ERC20, data is encoded as uint256 0
124
+ data = new ethers_1.ethers.AbiCoder().encode(["uint256"], [0]);
125
+ }
126
+ else {
127
+ // For ERC721, data is encoded as uint256 tokenId
128
+ data = new ethers_1.ethers.AbiCoder().encode(["uint256"], [tokenId || 0]);
129
+ }
130
+ // Get position view from lending pool
131
+ const lendingPoolContract = await (0, _helpers_1.getLendingPoolContract)(chainId, provider);
132
+ const positionView = await lendingPoolContract.getPositionView(adapter, user, data);
133
+ // Get asset value if available
134
+ let assetValue;
135
+ try {
136
+ if (adapterType === "ERC20") {
137
+ const erc20AdapterABI = (0, artifacts_1.getABI)("erc20assetadapter");
138
+ const erc20AdapterContract = new ethers_1.ethers.Contract(adapter, erc20AdapterABI, provider);
139
+ assetValue = (await erc20AdapterContract.getAssetValue(user, data)).toString();
140
+ }
141
+ else {
142
+ const erc721AdapterABI = (0, artifacts_1.getABI)("erc721assetadapter");
143
+ const erc721AdapterContract = new ethers_1.ethers.Contract(adapter, erc721AdapterABI, provider);
144
+ assetValue = (await erc721AdapterContract.getAssetValue(user, data)).toString();
145
+ }
146
+ }
147
+ catch (error) {
148
+ console.log(`Could not get asset value for user ${user}:`, error);
149
+ }
150
+ return {
151
+ user,
152
+ adapter,
153
+ adapterType,
154
+ asset: positionView.asset,
155
+ tokenId,
156
+ amount,
157
+ rawDebtBalance: positionView.rawDebtBalance.toString(),
158
+ scaledDebtBalance: positionView.scaledDebtBalance.toString(),
159
+ healthFactor: positionView.healthFactor.toString(),
160
+ isUnderLiquidation: positionView.isUnderLiquidation,
161
+ liquidationStartTime: positionView.liquidationStartTime.toString(),
162
+ positionIndex: positionView.positionIndex.toString(),
163
+ isInsured: positionView.isInsured,
164
+ assetValue
165
+ };
166
+ }
167
+ catch (error) {
168
+ console.error(`Error getting position data for user ${user}:`, error);
169
+ return null;
170
+ }
171
+ }
172
+ function determineRiskLevel(healthFactor) {
173
+ if (healthFactor < HEALTH_FACTOR_THRESHOLDS.HIGH_RISK) {
174
+ return "HIGH";
175
+ }
176
+ else if (healthFactor < HEALTH_FACTOR_THRESHOLDS.MEDIUM_RISK) {
177
+ return "MEDIUM";
178
+ }
179
+ else if (healthFactor < HEALTH_FACTOR_THRESHOLDS.LOW_RISK) {
180
+ return "LOW";
181
+ }
182
+ return "LOW"; // Safe
183
+ }
184
+ function convertToCSV(data, headers) {
185
+ const csvHeaders = headers.join(",");
186
+ const csvRows = data.map(row => headers.map(header => {
187
+ const value = row[header];
188
+ // Escape commas and quotes in CSV
189
+ if (typeof value === "string" && (value.includes(",") || value.includes('"'))) {
190
+ return `"${value.replace(/"/g, '""')}"`;
191
+ }
192
+ return value;
193
+ }).join(","));
194
+ return [csvHeaders, ...csvRows].join("\n");
195
+ }
196
+ async function analyzeLendingPositions() {
197
+ console.log("Starting lending position analysis...");
198
+ try {
199
+ // Get all users who have deposited collateral
200
+ console.log("Scanning for users with deposited collateral...");
201
+ const depositedUsers = await getAllDepositedUsers();
202
+ console.log(`Found ${depositedUsers.length} unique positions`);
203
+ // Get position data for each user
204
+ console.log("Fetching position data...");
205
+ const allPositions = [];
206
+ const lowHealthFactorPositions = [];
207
+ for (let i = 0; i < depositedUsers.length; i++) {
208
+ const user = depositedUsers[i];
209
+ console.log(`Processing position ${i + 1}/${depositedUsers.length}: ${user.user} on ${user.adapter}`);
210
+ const positionData = await getPositionData(user.user, user.adapter, user.adapterType, user.tokenId, user.amount);
211
+ if (positionData) {
212
+ allPositions.push(positionData);
213
+ // Check if health factor is below threshold
214
+ const healthFactor = BigInt(positionData.healthFactor);
215
+ if (healthFactor < HEALTH_FACTOR_THRESHOLDS.LOW_RISK) {
216
+ const riskLevel = determineRiskLevel(healthFactor);
217
+ lowHealthFactorPositions.push({
218
+ ...positionData,
219
+ riskLevel
220
+ });
221
+ }
222
+ }
223
+ }
224
+ // Generate CSV files
225
+ console.log("Generating CSV reports...");
226
+ // All positions CSV
227
+ const allPositionsHeaders = [
228
+ "user", "adapter", "adapterType", "asset", "tokenId", "amount",
229
+ "rawDebtBalance", "scaledDebtBalance", "healthFactor", "isUnderLiquidation",
230
+ "liquidationStartTime", "positionIndex", "isInsured", "assetValue"
231
+ ];
232
+ const allPositionsCSV = convertToCSV(allPositions, allPositionsHeaders);
233
+ (0, fs_1.writeFileSync)("all_lending_positions.csv", allPositionsCSV);
234
+ console.log(`All positions saved to all_lending_positions.csv (${allPositions.length} positions)`);
235
+ // Low health factor positions CSV
236
+ const lowHealthFactorHeaders = [
237
+ "user", "adapter", "adapterType", "asset", "tokenId", "amount",
238
+ "rawDebtBalance", "scaledDebtBalance", "healthFactor", "isUnderLiquidation",
239
+ "liquidationStartTime", "positionIndex", "isInsured", "assetValue", "riskLevel"
240
+ ];
241
+ const lowHealthFactorCSV = convertToCSV(lowHealthFactorPositions, lowHealthFactorHeaders);
242
+ (0, fs_1.writeFileSync)("low_health_factor_positions.csv", lowHealthFactorCSV);
243
+ console.log(`Low health factor positions saved to low_health_factor_positions.csv (${lowHealthFactorPositions.length} positions)`);
244
+ // Print summary
245
+ console.log("\n=== ANALYSIS SUMMARY ===");
246
+ console.log(`Total positions analyzed: ${allPositions.length}`);
247
+ console.log(`Positions with low health factor: ${lowHealthFactorPositions.length}`);
248
+ const highRisk = lowHealthFactorPositions.filter(p => p.riskLevel === "HIGH").length;
249
+ const mediumRisk = lowHealthFactorPositions.filter(p => p.riskLevel === "MEDIUM").length;
250
+ const lowRisk = lowHealthFactorPositions.filter(p => p.riskLevel === "LOW").length;
251
+ console.log(` - High risk (< 1.1): ${highRisk}`);
252
+ console.log(` - Medium risk (< 1.3): ${mediumRisk}`);
253
+ console.log(` - Low risk (< 1.5): ${lowRisk}`);
254
+ // Print details of high-risk positions
255
+ if (highRisk > 0) {
256
+ console.log("\n=== HIGH RISK POSITIONS ===");
257
+ lowHealthFactorPositions
258
+ .filter(p => p.riskLevel === "HIGH")
259
+ .forEach(p => {
260
+ console.log(`User: ${p.user}`);
261
+ console.log(` Adapter: ${p.adapter} (${p.adapterType})`);
262
+ console.log(` Health Factor: ${ethers_1.ethers.formatEther(p.healthFactor)}`);
263
+ console.log(` Debt: ${ethers_1.ethers.formatEther(p.rawDebtBalance)}`);
264
+ console.log(` Asset Value: ${p.assetValue ? ethers_1.ethers.formatEther(p.assetValue) : "N/A"}`);
265
+ console.log(` Under Liquidation: ${p.isUnderLiquidation}`);
266
+ console.log("---");
267
+ });
268
+ }
269
+ console.log("\nAnalysis complete! Check the CSV files for detailed data.");
270
+ }
271
+ catch (error) {
272
+ console.error("Error during analysis:", error);
273
+ throw error;
274
+ }
275
+ }
276
+ // Run the analysis
277
+ analyzeLendingPositions().catch(console.error);