@certik/skynet 0.10.7 → 0.10.10
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/.editorconfig +5 -5
- package/.eslintrc.js +13 -13
- package/.prettierrc.js +3 -3
- package/CHANGELOG.md +389 -372
- package/README.md +23 -23
- package/abi.js +353 -353
- package/ably.js +29 -29
- package/address.js +18 -18
- package/api.js +128 -105
- package/app.js +718 -709
- package/availability.js +58 -58
- package/block.js +83 -83
- package/cli.js +53 -53
- package/const.js +92 -92
- package/deploy.js +676 -676
- package/dynamodb.js +444 -444
- package/env.js +90 -90
- package/examples/api +70 -73
- package/examples/consumer +47 -47
- package/examples/indexer +65 -65
- package/examples/mode-indexer +82 -82
- package/examples/producer +80 -80
- package/indexer.js +596 -595
- package/inquiry.js +14 -14
- package/kafka.js +444 -443
- package/labelling.js +90 -90
- package/log.js +46 -29
- package/metric.js +65 -65
- package/monitor.js +196 -191
- package/opsgenie.js +55 -55
- package/package.json +37 -37
- package/price.js +48 -48
- package/primitive.js +77 -77
- package/proxy.js +157 -157
- package/rateLimit.js +21 -21
- package/s3.js +122 -122
- package/scan.js +74 -74
- package/selector.js +53 -53
- package/slack.js +87 -87
- package/snowflake.js +36 -36
- package/sqs.js +12 -12
- package/token.js +46 -46
- package/transaction.js +41 -41
- package/util.js +67 -67
- package/web3.js +117 -117
package/snowflake.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
const snowflake = require("snowflake-sdk");
|
|
2
|
-
const { promisify } = require("util");
|
|
3
|
-
const { ensureAndGet } = require("./env");
|
|
4
|
-
|
|
5
|
-
async function getConnection(options) {
|
|
6
|
-
const connection = snowflake.createConnection({
|
|
7
|
-
account: ensureAndGet("SKYNET_SNOWFLAKE_ACCOUNT"),
|
|
8
|
-
username: ensureAndGet("SKYNET_SNOWFLAKE_USERNAME"),
|
|
9
|
-
password: ensureAndGet("SKYNET_SNOWFLAKE_PASSWORD"),
|
|
10
|
-
...options,
|
|
11
|
-
});
|
|
12
|
-
return await promisify(connection.connect).bind(connection)();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function executeSql(options, sql, binds) {
|
|
16
|
-
const connection = await getConnection(options);
|
|
17
|
-
return await new Promise((resolve, reject) => {
|
|
18
|
-
connection.execute({
|
|
19
|
-
sqlText: sql,
|
|
20
|
-
binds: binds,
|
|
21
|
-
complete: (err, statement, rows) => {
|
|
22
|
-
// console.log(statement.getSqlText());
|
|
23
|
-
if (err) {
|
|
24
|
-
reject(err);
|
|
25
|
-
} else {
|
|
26
|
-
resolve(rows);
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
module.exports = {
|
|
34
|
-
getConnection,
|
|
35
|
-
executeSql,
|
|
36
|
-
};
|
|
1
|
+
const snowflake = require("snowflake-sdk");
|
|
2
|
+
const { promisify } = require("util");
|
|
3
|
+
const { ensureAndGet } = require("./env");
|
|
4
|
+
|
|
5
|
+
async function getConnection(options) {
|
|
6
|
+
const connection = snowflake.createConnection({
|
|
7
|
+
account: ensureAndGet("SKYNET_SNOWFLAKE_ACCOUNT"),
|
|
8
|
+
username: ensureAndGet("SKYNET_SNOWFLAKE_USERNAME"),
|
|
9
|
+
password: ensureAndGet("SKYNET_SNOWFLAKE_PASSWORD"),
|
|
10
|
+
...options,
|
|
11
|
+
});
|
|
12
|
+
return await promisify(connection.connect).bind(connection)();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function executeSql(options, sql, binds) {
|
|
16
|
+
const connection = await getConnection(options);
|
|
17
|
+
return await new Promise((resolve, reject) => {
|
|
18
|
+
connection.execute({
|
|
19
|
+
sqlText: sql,
|
|
20
|
+
binds: binds,
|
|
21
|
+
complete: (err, statement, rows) => {
|
|
22
|
+
// console.log(statement.getSqlText());
|
|
23
|
+
if (err) {
|
|
24
|
+
reject(err);
|
|
25
|
+
} else {
|
|
26
|
+
resolve(rows);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
getConnection,
|
|
35
|
+
executeSql,
|
|
36
|
+
};
|
package/sqs.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
const { SQS } = require("aws-sdk");
|
|
2
|
-
const { getAWSAccessKeyId, getAWSSecretAccessKey, getAWSRegion } = require("./env");
|
|
3
|
-
|
|
4
|
-
function getSQS() {
|
|
5
|
-
return new SQS({
|
|
6
|
-
accessKeyId: getAWSAccessKeyId(),
|
|
7
|
-
secretAccessKey: getAWSSecretAccessKey(),
|
|
8
|
-
region: getAWSRegion(),
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
module.exports = { getSQS };
|
|
1
|
+
const { SQS } = require("aws-sdk");
|
|
2
|
+
const { getAWSAccessKeyId, getAWSSecretAccessKey, getAWSRegion } = require("./env");
|
|
3
|
+
|
|
4
|
+
function getSQS() {
|
|
5
|
+
return new SQS({
|
|
6
|
+
accessKeyId: getAWSAccessKeyId(),
|
|
7
|
+
secretAccessKey: getAWSSecretAccessKey(),
|
|
8
|
+
region: getAWSRegion(),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { getSQS };
|
package/token.js
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
const { getEnvironment } = require("./env");
|
|
2
|
-
const { getPriceClosestTo } = require("./price");
|
|
3
|
-
const BigNumber = require("bignumber.js");
|
|
4
|
-
|
|
5
|
-
const TOKEN_PRICE_TABLE_NAME = `skynet-${getEnvironment()}-token-prices`;
|
|
6
|
-
const TOKEN_PRICE_CACHE = {};
|
|
7
|
-
|
|
8
|
-
async function getTokenPriceAt(tokenAddress, timestamp, useCache = true) {
|
|
9
|
-
// to avoid huge amount of dynamodb query
|
|
10
|
-
// "ceil" timestamp to the closest proximate timestamp
|
|
11
|
-
// 100_000 seconds ~= a bit more than 1 day 86_400 seconds
|
|
12
|
-
// and use cache
|
|
13
|
-
const proximateTimestamp = timestamp + 100_000 - (timestamp % 100_000);
|
|
14
|
-
|
|
15
|
-
if (useCache && TOKEN_PRICE_CACHE[tokenAddress] && TOKEN_PRICE_CACHE[tokenAddress][proximateTimestamp]) {
|
|
16
|
-
return TOKEN_PRICE_CACHE[tokenAddress][proximateTimestamp];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const price = await getPriceClosestTo(TOKEN_PRICE_TABLE_NAME, tokenAddress, proximateTimestamp);
|
|
20
|
-
|
|
21
|
-
if (useCache) {
|
|
22
|
-
if (!TOKEN_PRICE_CACHE[tokenAddress]) {
|
|
23
|
-
TOKEN_PRICE_CACHE[tokenAddress] = {};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
TOKEN_PRICE_CACHE[tokenAddress][proximateTimestamp] = price;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return price;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function toNativeDecimal(bigNumber, decimals) {
|
|
33
|
-
const dividend = new BigNumber(10).exponentiatedBy(new BigNumber(decimals));
|
|
34
|
-
return new BigNumber(bigNumber).multipliedBy(dividend);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function toHumanDecimal(bigNumber, decimals) {
|
|
38
|
-
const dividend = new BigNumber(10).exponentiatedBy(new BigNumber(decimals));
|
|
39
|
-
return new BigNumber(bigNumber).dividedBy(dividend);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
module.exports = {
|
|
43
|
-
getTokenPriceAt,
|
|
44
|
-
toNativeDecimal,
|
|
45
|
-
toHumanDecimal,
|
|
46
|
-
};
|
|
1
|
+
const { getEnvironment } = require("./env");
|
|
2
|
+
const { getPriceClosestTo } = require("./price");
|
|
3
|
+
const BigNumber = require("bignumber.js");
|
|
4
|
+
|
|
5
|
+
const TOKEN_PRICE_TABLE_NAME = `skynet-${getEnvironment()}-token-prices`;
|
|
6
|
+
const TOKEN_PRICE_CACHE = {};
|
|
7
|
+
|
|
8
|
+
async function getTokenPriceAt(tokenAddress, timestamp, useCache = true) {
|
|
9
|
+
// to avoid huge amount of dynamodb query
|
|
10
|
+
// "ceil" timestamp to the closest proximate timestamp
|
|
11
|
+
// 100_000 seconds ~= a bit more than 1 day 86_400 seconds
|
|
12
|
+
// and use cache
|
|
13
|
+
const proximateTimestamp = timestamp + 100_000 - (timestamp % 100_000);
|
|
14
|
+
|
|
15
|
+
if (useCache && TOKEN_PRICE_CACHE[tokenAddress] && TOKEN_PRICE_CACHE[tokenAddress][proximateTimestamp]) {
|
|
16
|
+
return TOKEN_PRICE_CACHE[tokenAddress][proximateTimestamp];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const price = await getPriceClosestTo(TOKEN_PRICE_TABLE_NAME, tokenAddress, proximateTimestamp);
|
|
20
|
+
|
|
21
|
+
if (useCache) {
|
|
22
|
+
if (!TOKEN_PRICE_CACHE[tokenAddress]) {
|
|
23
|
+
TOKEN_PRICE_CACHE[tokenAddress] = {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
TOKEN_PRICE_CACHE[tokenAddress][proximateTimestamp] = price;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return price;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toNativeDecimal(bigNumber, decimals) {
|
|
33
|
+
const dividend = new BigNumber(10).exponentiatedBy(new BigNumber(decimals));
|
|
34
|
+
return new BigNumber(bigNumber).multipliedBy(dividend);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toHumanDecimal(bigNumber, decimals) {
|
|
38
|
+
const dividend = new BigNumber(10).exponentiatedBy(new BigNumber(decimals));
|
|
39
|
+
return new BigNumber(bigNumber).dividedBy(dividend);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
getTokenPriceAt,
|
|
44
|
+
toNativeDecimal,
|
|
45
|
+
toHumanDecimal,
|
|
46
|
+
};
|
package/transaction.js
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
const fetch = require("node-fetch");
|
|
2
|
-
const { PROTOCOLS } = require("./const");
|
|
3
|
-
const { exponentialRetry } = require("./availability");
|
|
4
|
-
|
|
5
|
-
async function getTxReceipt(protocol, txHash, verbose = false) {
|
|
6
|
-
let { endpoint } = PROTOCOLS[protocol];
|
|
7
|
-
|
|
8
|
-
const body = {
|
|
9
|
-
jsonrpc: "2.0",
|
|
10
|
-
method: "eth_getTransactionReceipt",
|
|
11
|
-
params: [txHash],
|
|
12
|
-
id: 1
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const response = await exponentialRetry(
|
|
16
|
-
() => {
|
|
17
|
-
return fetch(endpoint, {
|
|
18
|
-
method: "POST",
|
|
19
|
-
headers: { "Content-Type": "application/json" },
|
|
20
|
-
body: JSON.stringify(body)
|
|
21
|
-
});
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
maxRetry: 6,
|
|
25
|
-
test: r => r.ok,
|
|
26
|
-
verbose
|
|
27
|
-
}
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const { result } = await response.json();
|
|
35
|
-
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
module.exports = {
|
|
40
|
-
getTxReceipt
|
|
41
|
-
};
|
|
1
|
+
const fetch = require("node-fetch");
|
|
2
|
+
const { PROTOCOLS } = require("./const");
|
|
3
|
+
const { exponentialRetry } = require("./availability");
|
|
4
|
+
|
|
5
|
+
async function getTxReceipt(protocol, txHash, verbose = false) {
|
|
6
|
+
let { endpoint } = PROTOCOLS[protocol];
|
|
7
|
+
|
|
8
|
+
const body = {
|
|
9
|
+
jsonrpc: "2.0",
|
|
10
|
+
method: "eth_getTransactionReceipt",
|
|
11
|
+
params: [txHash],
|
|
12
|
+
id: 1
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const response = await exponentialRetry(
|
|
16
|
+
() => {
|
|
17
|
+
return fetch(endpoint, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify(body)
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
maxRetry: 6,
|
|
25
|
+
test: r => r.ok,
|
|
26
|
+
verbose
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { result } = await response.json();
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
getTxReceipt
|
|
41
|
+
};
|
package/util.js
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
// Inclusive endAt
|
|
2
|
-
function partition(startAt, endAt, numGroups) {
|
|
3
|
-
if (numGroups === 0 || startAt > endAt) {
|
|
4
|
-
return [];
|
|
5
|
-
} else if (startAt === endAt) {
|
|
6
|
-
return [[startAt, endAt]];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const span = endAt - startAt + 1;
|
|
10
|
-
const step = Math.max(Math.floor(span / numGroups), 1);
|
|
11
|
-
const adjustedStep = span % step === 0 ? step : step + 1;
|
|
12
|
-
|
|
13
|
-
return [[startAt, startAt + adjustedStep - 1], ...partition(startAt + adjustedStep, endAt, numGroups - 1)];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Inclusive range
|
|
17
|
-
function range(startAt, endAt, step) {
|
|
18
|
-
let arr = [];
|
|
19
|
-
|
|
20
|
-
for (let i = startAt; i <= endAt; i += step) {
|
|
21
|
-
arr.push([i, Math.min(endAt, i + step - 1)]);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return arr;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function arrayGroup(array, groupSize) {
|
|
28
|
-
const groups = [];
|
|
29
|
-
|
|
30
|
-
for (let i = 0; i < array.length; i += groupSize) {
|
|
31
|
-
groups.push(array.slice(i, i + groupSize));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return groups;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// return an array with numbers of given inclusive range
|
|
38
|
-
// given 1, 5
|
|
39
|
-
// return [1, 2, 3, 4, 5]
|
|
40
|
-
function fillRange(start, end) {
|
|
41
|
-
let result = [];
|
|
42
|
-
|
|
43
|
-
for (let i = start; i <= end; i++) {
|
|
44
|
-
result.push(i);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function chunk(array, count) {
|
|
51
|
-
if (count == null || count < 1) return [];
|
|
52
|
-
var result = [];
|
|
53
|
-
var i = 0,
|
|
54
|
-
length = array.length;
|
|
55
|
-
while (i < length) {
|
|
56
|
-
result.push(array.slice(i, (i += count)));
|
|
57
|
-
}
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
module.exports = {
|
|
62
|
-
arrayGroup,
|
|
63
|
-
partition,
|
|
64
|
-
range,
|
|
65
|
-
fillRange,
|
|
66
|
-
chunk
|
|
67
|
-
};
|
|
1
|
+
// Inclusive endAt
|
|
2
|
+
function partition(startAt, endAt, numGroups) {
|
|
3
|
+
if (numGroups === 0 || startAt > endAt) {
|
|
4
|
+
return [];
|
|
5
|
+
} else if (startAt === endAt) {
|
|
6
|
+
return [[startAt, endAt]];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const span = endAt - startAt + 1;
|
|
10
|
+
const step = Math.max(Math.floor(span / numGroups), 1);
|
|
11
|
+
const adjustedStep = span % step === 0 ? step : step + 1;
|
|
12
|
+
|
|
13
|
+
return [[startAt, startAt + adjustedStep - 1], ...partition(startAt + adjustedStep, endAt, numGroups - 1)];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Inclusive range
|
|
17
|
+
function range(startAt, endAt, step) {
|
|
18
|
+
let arr = [];
|
|
19
|
+
|
|
20
|
+
for (let i = startAt; i <= endAt; i += step) {
|
|
21
|
+
arr.push([i, Math.min(endAt, i + step - 1)]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return arr;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function arrayGroup(array, groupSize) {
|
|
28
|
+
const groups = [];
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < array.length; i += groupSize) {
|
|
31
|
+
groups.push(array.slice(i, i + groupSize));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return groups;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// return an array with numbers of given inclusive range
|
|
38
|
+
// given 1, 5
|
|
39
|
+
// return [1, 2, 3, 4, 5]
|
|
40
|
+
function fillRange(start, end) {
|
|
41
|
+
let result = [];
|
|
42
|
+
|
|
43
|
+
for (let i = start; i <= end; i++) {
|
|
44
|
+
result.push(i);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function chunk(array, count) {
|
|
51
|
+
if (count == null || count < 1) return [];
|
|
52
|
+
var result = [];
|
|
53
|
+
var i = 0,
|
|
54
|
+
length = array.length;
|
|
55
|
+
while (i < length) {
|
|
56
|
+
result.push(array.slice(i, (i += count)));
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
arrayGroup,
|
|
63
|
+
partition,
|
|
64
|
+
range,
|
|
65
|
+
fillRange,
|
|
66
|
+
chunk
|
|
67
|
+
};
|
package/web3.js
CHANGED
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
const Web3 = require("web3");
|
|
2
|
-
const { PROTOCOLS } = require("./const");
|
|
3
|
-
const { MULTICALL } = require("./abi");
|
|
4
|
-
const { chunk } = require("./util");
|
|
5
|
-
const MULTICALL_CHUNK_SIZE = 400;
|
|
6
|
-
|
|
7
|
-
function newWeb3ByProtocol(protocol) {
|
|
8
|
-
if (!Object.keys(PROTOCOLS).includes(protocol)) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return new Web3(PROTOCOLS[protocol].endpoint);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function normalizeCallParams(params) {
|
|
16
|
-
if (params === undefined) {
|
|
17
|
-
return [];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (Array.isArray(params)) {
|
|
21
|
-
return params;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return [params];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function singleCall(protocol, abi, target, params) {
|
|
28
|
-
const web3 = newWeb3ByProtocol(protocol);
|
|
29
|
-
|
|
30
|
-
if (!web3) {
|
|
31
|
-
throw new Error(`unsupported protocol`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const contract = new web3.eth.Contract([abi], target);
|
|
35
|
-
const functionSignature = web3.eth.abi.encodeFunctionSignature(abi);
|
|
36
|
-
const method = contract.methods[functionSignature];
|
|
37
|
-
|
|
38
|
-
const result = await method(...normalizeCallParams(params)).call();
|
|
39
|
-
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// limiter if provided, should be an instance of Bottleneck, which is part of bottleneck package
|
|
44
|
-
async function multiCall({ protocol, limiter = null, target, abi, calls }) {
|
|
45
|
-
const web3 = newWeb3ByProtocol(protocol);
|
|
46
|
-
|
|
47
|
-
if (!web3) {
|
|
48
|
-
throw new Error(`unsupported protocol`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const multiCallProviderAddress = PROTOCOLS[protocol].multiCallProvider;
|
|
52
|
-
|
|
53
|
-
if (!multiCallProviderAddress) {
|
|
54
|
-
throw new Error("protocol doesn't support multicall yet");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (calls.length === 0) return { callCount: 0, output: [] };
|
|
58
|
-
|
|
59
|
-
const multiCallContract = new web3.eth.Contract(MULTICALL, multiCallProviderAddress);
|
|
60
|
-
|
|
61
|
-
let maybeRateLimitedCallChunk = async (callChunk) => {
|
|
62
|
-
const txs = callChunk.map((call) => ({
|
|
63
|
-
delegateCall: false,
|
|
64
|
-
revertOnError: false,
|
|
65
|
-
gasLimit: 0,
|
|
66
|
-
target: call.target || target,
|
|
67
|
-
value: 0,
|
|
68
|
-
data: web3.eth.abi.encodeFunctionCall(abi, normalizeCallParams(call.params)),
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
const response = await multiCallContract.methods.multiCall(txs).call();
|
|
72
|
-
|
|
73
|
-
return response._results.map((res, idx) => {
|
|
74
|
-
const input = {
|
|
75
|
-
target: callChunk[idx].target || target,
|
|
76
|
-
params: normalizeCallParams(callChunk[idx].params),
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const callResult = web3.eth.abi.decodeParameters(abi.outputs, res);
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
input,
|
|
84
|
-
success: response._successes[idx],
|
|
85
|
-
// according to SDK doc, if there's only one, return result directly
|
|
86
|
-
output: abi.outputs.length === 1 ? callResult[0] : callResult,
|
|
87
|
-
};
|
|
88
|
-
} catch (decodeErr) {
|
|
89
|
-
console.log("decode err", abi.name, callChunk[idx].target || target, callChunk[idx].params, decodeErr.message);
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
input,
|
|
93
|
-
success: false,
|
|
94
|
-
output: null,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (limiter) {
|
|
101
|
-
maybeRateLimitedCallChunk = limiter.wrap(maybeRateLimitedCallChunk);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const callChunks = chunk(calls, MULTICALL_CHUNK_SIZE);
|
|
105
|
-
|
|
106
|
-
const responses = (
|
|
107
|
-
await Promise.all(callChunks.map(async (callChunk) => await maybeRateLimitedCallChunk(callChunk)))
|
|
108
|
-
).flat();
|
|
109
|
-
|
|
110
|
-
return { actualCallCount: callChunks.length, output: responses };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
module.exports = {
|
|
114
|
-
newWeb3ByProtocol,
|
|
115
|
-
singleCall,
|
|
116
|
-
multiCall,
|
|
117
|
-
};
|
|
1
|
+
const Web3 = require("web3");
|
|
2
|
+
const { PROTOCOLS } = require("./const");
|
|
3
|
+
const { MULTICALL } = require("./abi");
|
|
4
|
+
const { chunk } = require("./util");
|
|
5
|
+
const MULTICALL_CHUNK_SIZE = 400;
|
|
6
|
+
|
|
7
|
+
function newWeb3ByProtocol(protocol) {
|
|
8
|
+
if (!Object.keys(PROTOCOLS).includes(protocol)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return new Web3(PROTOCOLS[protocol].endpoint);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeCallParams(params) {
|
|
16
|
+
if (params === undefined) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(params)) {
|
|
21
|
+
return params;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return [params];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function singleCall(protocol, abi, target, params) {
|
|
28
|
+
const web3 = newWeb3ByProtocol(protocol);
|
|
29
|
+
|
|
30
|
+
if (!web3) {
|
|
31
|
+
throw new Error(`unsupported protocol`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const contract = new web3.eth.Contract([abi], target);
|
|
35
|
+
const functionSignature = web3.eth.abi.encodeFunctionSignature(abi);
|
|
36
|
+
const method = contract.methods[functionSignature];
|
|
37
|
+
|
|
38
|
+
const result = await method(...normalizeCallParams(params)).call();
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// limiter if provided, should be an instance of Bottleneck, which is part of bottleneck package
|
|
44
|
+
async function multiCall({ protocol, limiter = null, target, abi, calls }) {
|
|
45
|
+
const web3 = newWeb3ByProtocol(protocol);
|
|
46
|
+
|
|
47
|
+
if (!web3) {
|
|
48
|
+
throw new Error(`unsupported protocol`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const multiCallProviderAddress = PROTOCOLS[protocol].multiCallProvider;
|
|
52
|
+
|
|
53
|
+
if (!multiCallProviderAddress) {
|
|
54
|
+
throw new Error("protocol doesn't support multicall yet");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (calls.length === 0) return { callCount: 0, output: [] };
|
|
58
|
+
|
|
59
|
+
const multiCallContract = new web3.eth.Contract(MULTICALL, multiCallProviderAddress);
|
|
60
|
+
|
|
61
|
+
let maybeRateLimitedCallChunk = async (callChunk) => {
|
|
62
|
+
const txs = callChunk.map((call) => ({
|
|
63
|
+
delegateCall: false,
|
|
64
|
+
revertOnError: false,
|
|
65
|
+
gasLimit: 0,
|
|
66
|
+
target: call.target || target,
|
|
67
|
+
value: 0,
|
|
68
|
+
data: web3.eth.abi.encodeFunctionCall(abi, normalizeCallParams(call.params)),
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
const response = await multiCallContract.methods.multiCall(txs).call();
|
|
72
|
+
|
|
73
|
+
return response._results.map((res, idx) => {
|
|
74
|
+
const input = {
|
|
75
|
+
target: callChunk[idx].target || target,
|
|
76
|
+
params: normalizeCallParams(callChunk[idx].params),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const callResult = web3.eth.abi.decodeParameters(abi.outputs, res);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
input,
|
|
84
|
+
success: response._successes[idx],
|
|
85
|
+
// according to SDK doc, if there's only one, return result directly
|
|
86
|
+
output: abi.outputs.length === 1 ? callResult[0] : callResult,
|
|
87
|
+
};
|
|
88
|
+
} catch (decodeErr) {
|
|
89
|
+
console.log("decode err", abi.name, callChunk[idx].target || target, callChunk[idx].params, decodeErr.message);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
input,
|
|
93
|
+
success: false,
|
|
94
|
+
output: null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (limiter) {
|
|
101
|
+
maybeRateLimitedCallChunk = limiter.wrap(maybeRateLimitedCallChunk);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const callChunks = chunk(calls, MULTICALL_CHUNK_SIZE);
|
|
105
|
+
|
|
106
|
+
const responses = (
|
|
107
|
+
await Promise.all(callChunks.map(async (callChunk) => await maybeRateLimitedCallChunk(callChunk)))
|
|
108
|
+
).flat();
|
|
109
|
+
|
|
110
|
+
return { actualCallCount: callChunks.length, output: responses };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
newWeb3ByProtocol,
|
|
115
|
+
singleCall,
|
|
116
|
+
multiCall,
|
|
117
|
+
};
|