@raac/rpc 1.1.0-beta.56 → 1.1.0-beta.58
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/dist/RPCLibrary.js +18 -0
- package/dist/oracles/houses/request.js +3 -13
- package/dist/oracles/houses/simulate.js +21 -0
- package/dist/oracles/prime-rate/config.js +3 -36
- package/dist/oracles/prime-rate/request.js +8 -11
- package/dist/oracles/prime-rate/simulate.js +25 -0
- package/dist/pools/lendingPool/getAdapterAddress.js +8 -10
- package/dist/scripts/analyze_lending_positions.js +277 -0
- package/dist/scripts/analyze_transfers.js +247 -0
- package/dist/scripts/calculate_portfolio.js +167 -0
- package/dist/scripts/index.js +50 -38
- package/dist/scripts/run_analysis.js +115 -0
- package/dist/scripts/test_lending_analysis.js +105 -0
- package/dist/scripts/users.js +304 -0
- package/dist/types/oracles/houses/request.d.ts +2 -1
- package/dist/types/oracles/houses/simulate.d.ts +1 -0
- package/dist/types/oracles/prime-rate/config.d.ts +2 -1
- package/dist/types/oracles/prime-rate/request.d.ts +2 -1
- package/dist/types/oracles/prime-rate/simulate.d.ts +1 -0
- package/dist/types/scripts/analyze_lending_positions.d.ts +1 -0
- package/dist/types/scripts/analyze_transfers.d.ts +1 -0
- package/dist/types/scripts/calculate_portfolio.d.ts +1 -0
- package/dist/types/scripts/run_analysis.d.ts +1 -0
- package/dist/types/scripts/test_lending_analysis.d.ts +1 -0
- package/dist/types/scripts/users.d.ts +1 -0
- package/package.json +2 -1
package/dist/RPCLibrary.js
CHANGED
|
@@ -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
|
+
// import { request as requestPrimeRate } from "./oracles/prime-rate/request";
|
|
125
|
+
// import { request as requestHousePrices } from "./oracles/houses/request";
|
|
124
126
|
class RPCLibrary {
|
|
125
127
|
signer;
|
|
126
128
|
isConnected;
|
|
@@ -131,6 +133,14 @@ class RPCLibrary {
|
|
|
131
133
|
assets;
|
|
132
134
|
// valid
|
|
133
135
|
wallet;
|
|
136
|
+
// oracles: {
|
|
137
|
+
// primeRate: {
|
|
138
|
+
// request: typeof requestPrimeRate;
|
|
139
|
+
// };
|
|
140
|
+
// housePrices: {
|
|
141
|
+
// request: typeof requestHousePrices;
|
|
142
|
+
// };
|
|
143
|
+
// };
|
|
134
144
|
nfts;
|
|
135
145
|
contracts;
|
|
136
146
|
pools;
|
|
@@ -291,6 +301,14 @@ class RPCLibrary {
|
|
|
291
301
|
decodeErrorMessage: errorDecoder_1.decodeErrorMessage,
|
|
292
302
|
attachDecodedErrorMessage: errorDecoder_1.attachDecodedErrorMessage,
|
|
293
303
|
};
|
|
304
|
+
// this.oracles = {
|
|
305
|
+
// primeRate: {
|
|
306
|
+
// request: requestPrimeRate,
|
|
307
|
+
// },
|
|
308
|
+
// housePrices: {
|
|
309
|
+
// request: requestHousePrices,
|
|
310
|
+
// },
|
|
311
|
+
// };
|
|
294
312
|
}
|
|
295
313
|
async getWallet(privateKey, provider) {
|
|
296
314
|
// @ts-ignore
|
|
@@ -17,7 +17,7 @@ const subscriptionIds = {
|
|
|
17
17
|
11155111: 4392,
|
|
18
18
|
18453: 4392,
|
|
19
19
|
};
|
|
20
|
-
async function request(chainId, houseId,
|
|
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
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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;
|
|
@@ -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);
|