@certik/skynet 0.11.4 → 0.13.0
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/CHANGELOG.md +18 -0
- package/bun.lockb +0 -0
- package/kafka.js +1 -4
- package/package.json +14 -15
- package/slack.js +5 -60
- package/ably.d.ts +0 -4
- package/ably.js +0 -35
- package/block.d.ts +0 -49
- package/block.js +0 -82
- package/distributed-lock.d.ts +0 -1
- package/distributed-lock.js +0 -109
- package/metric.d.ts +0 -10
- package/metric.js +0 -60
- package/price.d.ts +0 -8
- package/price.js +0 -46
- package/primitive.d.ts +0 -29
- package/primitive.js +0 -78
- package/scan.d.ts +0 -47
- package/scan.js +0 -74
- package/token.d.ts +0 -5
- package/token.js +0 -46
- package/transaction.d.ts +0 -30
- package/transaction.js +0 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
- BREAKING: removed ably library (@certik/skynet/ably)
|
|
6
|
+
- BREAKING: removed metric library (@certik/skynet/metric)
|
|
7
|
+
- BREAKING: removed scan library (@certik/skynet/scan)
|
|
8
|
+
- BREAKING: redesigned slack library (@certik/skynet/slack)
|
|
9
|
+
- Upgraded: @aws-sdk to support new S3 directory bucket capability
|
|
10
|
+
- Deprecated: kafka / web3
|
|
11
|
+
|
|
12
|
+
## 0.12.0
|
|
13
|
+
|
|
14
|
+
- BREAKING: removed token module (@certik/skynet/token)
|
|
15
|
+
- BREAKING: removed block module (@certik/skynet/block)
|
|
16
|
+
- BREAKING: removed transaction module (@certik/skynet/transaction)
|
|
17
|
+
- BREAKING: removed primitive module (@certik/skynet/primitive)
|
|
18
|
+
- BREAKING: removed price module (@certik/skynet/price)
|
|
19
|
+
- BREAKING: removed distributed-lock module (@certik/skynet/distributed-lock)
|
|
20
|
+
|
|
3
21
|
## 0.11.4
|
|
4
22
|
|
|
5
23
|
- support api key in query string
|
package/bun.lockb
CHANGED
|
Binary file
|
package/kafka.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import meow from "meow";
|
|
2
2
|
import { Kafka, logLevel } from "kafkajs";
|
|
3
3
|
import { getEnvironment, getEnvOrThrow } from "./env.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getSelectorFlags, getSelectorDesc, toSelectorString } from "./selector.js";
|
|
5
5
|
import { createRecord, getRecordByKey, deleteRecordsByHashKey } from "./dynamodb.js";
|
|
6
6
|
import { wait, exponentialRetry } from "./availability.js";
|
|
7
|
-
import { useLock } from "./distributed-lock.js";
|
|
8
7
|
import { getBinaryName } from "./cli.js";
|
|
9
8
|
import { inline } from "./log.js";
|
|
10
9
|
|
|
@@ -219,8 +218,6 @@ ${getSelectorDesc(selector)}
|
|
|
219
218
|
process.exit(0);
|
|
220
219
|
}
|
|
221
220
|
|
|
222
|
-
await useLock({ name: getJobName(name, selectorFlags), ttl: 50, verbose });
|
|
223
|
-
|
|
224
221
|
if (!from) {
|
|
225
222
|
const prevId = await getProducerLatestId(name, selectorFlags);
|
|
226
223
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certik/skynet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Skynet Shared JS library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -15,13 +15,12 @@
|
|
|
15
15
|
"node": ">= 18"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
19
|
-
"@aws-sdk/client-s3": "^3.
|
|
20
|
-
"@aws-sdk/client-sqs": "^3.
|
|
21
|
-
"@aws-sdk/lib-dynamodb": "^3.
|
|
22
|
-
"@opensearch-project/opensearch": "^2.
|
|
23
|
-
"@slack/web-api": "^6.
|
|
24
|
-
"ably": "^1.2.44",
|
|
18
|
+
"@aws-sdk/client-dynamodb": "^3.478.0",
|
|
19
|
+
"@aws-sdk/client-s3": "^3.478.0",
|
|
20
|
+
"@aws-sdk/client-sqs": "^3.478.0",
|
|
21
|
+
"@aws-sdk/lib-dynamodb": "^3.478.0",
|
|
22
|
+
"@opensearch-project/opensearch": "^2.4.0",
|
|
23
|
+
"@slack/web-api": "^6.11.0",
|
|
25
24
|
"bottleneck": "^2.19.5",
|
|
26
25
|
"chalk": "^5.3.0",
|
|
27
26
|
"execa": "^8.0.1",
|
|
@@ -29,16 +28,16 @@
|
|
|
29
28
|
"kafkajs": "^2.2.4",
|
|
30
29
|
"md5": "^2.3.0",
|
|
31
30
|
"meow": "^12.1.1",
|
|
32
|
-
"snowflake-sdk": "^1.
|
|
33
|
-
"web3": "^4.
|
|
31
|
+
"snowflake-sdk": "^1.9.2",
|
|
32
|
+
"web3": "^4.3.0",
|
|
34
33
|
"which": "^4.0.0"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
|
37
|
-
"ava": "^
|
|
38
|
-
"eslint": "^8.
|
|
39
|
-
"eslint-plugin-import": "^2.
|
|
40
|
-
"prettier": "^3.
|
|
41
|
-
"sinon": "^
|
|
36
|
+
"ava": "^6.0.1",
|
|
37
|
+
"eslint": "^8.56.0",
|
|
38
|
+
"eslint-plugin-import": "^2.29.1",
|
|
39
|
+
"prettier": "^3.1.1",
|
|
40
|
+
"sinon": "^17.0.1"
|
|
42
41
|
},
|
|
43
42
|
"license": "MIT",
|
|
44
43
|
"publishConfig": {
|
package/slack.js
CHANGED
|
@@ -1,71 +1,16 @@
|
|
|
1
1
|
import { WebClient } from "@slack/web-api";
|
|
2
2
|
|
|
3
|
-
function
|
|
4
|
-
return process.env.SKYNET_SLACK_TOKEN;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function getClient(t) {
|
|
8
|
-
const token = t || getToken();
|
|
9
|
-
|
|
10
|
-
if (!token) {
|
|
11
|
-
throw new Error("Cannot communicate with slack due to missing slack token: process.env.SKYNET_SLACK_TOKEN");
|
|
12
|
-
}
|
|
13
|
-
|
|
3
|
+
function getClient(token) {
|
|
14
4
|
const client = new WebClient(token);
|
|
15
5
|
|
|
16
6
|
return client;
|
|
17
7
|
}
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let channels = [];
|
|
23
|
-
|
|
24
|
-
let result = await conversations.list({
|
|
25
|
-
types: "public_channel,private_channel",
|
|
26
|
-
limit: 1000,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
channels = channels.concat(result.channels);
|
|
30
|
-
|
|
31
|
-
while (result.response_metadata.next_cursor) {
|
|
32
|
-
result = await conversations.list({
|
|
33
|
-
types: "public_channel,private_channel",
|
|
34
|
-
cursor: result.response_metadata.next_cursor,
|
|
35
|
-
limit: 1000,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
channels = channels.concat(result.channels);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
for (const channel of channels) {
|
|
42
|
-
if (channel.name === name) {
|
|
43
|
-
const conversationId = channel.id;
|
|
44
|
-
|
|
45
|
-
return conversationId;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function isString(s) {
|
|
53
|
-
return typeof s === "string" || s instanceof String;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function postMessage(channel, message, verbose) {
|
|
9
|
+
// you can identify conversation ID at the bottom of the channel settings page
|
|
10
|
+
async function postMessageToConversation({ conversationId, message, token, verbose }) {
|
|
57
11
|
try {
|
|
58
|
-
const token = isString(channel) ? null : channel.token;
|
|
59
12
|
const client = getClient(token);
|
|
60
13
|
|
|
61
|
-
let conversationId = isString(channel) ? await findConversation(client, channel) : channel.id;
|
|
62
|
-
|
|
63
|
-
if (!conversationId) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`cannot find slack public/private channel: ${channel}, you may have to invite the @CertiK Skynet bot to the channel`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
14
|
let post = {};
|
|
70
15
|
|
|
71
16
|
if (typeof message === "string") {
|
|
@@ -75,7 +20,7 @@ async function postMessage(channel, message, verbose) {
|
|
|
75
20
|
}
|
|
76
21
|
|
|
77
22
|
if (verbose) {
|
|
78
|
-
console.log(`posting to slack
|
|
23
|
+
console.log(`posting to slack conversation ${conversationId}:`, JSON.stringify(post, null, 2));
|
|
79
24
|
}
|
|
80
25
|
|
|
81
26
|
await client.chat.postMessage({
|
|
@@ -89,4 +34,4 @@ async function postMessage(channel, message, verbose) {
|
|
|
89
34
|
}
|
|
90
35
|
}
|
|
91
36
|
|
|
92
|
-
export {
|
|
37
|
+
export { postMessageToConversation };
|
package/ably.d.ts
DELETED
package/ably.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import Ably from "ably";
|
|
2
|
-
import Bottleneck from "bottleneck/es5";
|
|
3
|
-
import { ensureAndGet } from "./env.js";
|
|
4
|
-
|
|
5
|
-
let ABLY;
|
|
6
|
-
|
|
7
|
-
async function getAbly() {
|
|
8
|
-
if (!ABLY) {
|
|
9
|
-
ABLY = await new Promise((resolve) => {
|
|
10
|
-
const ably = new Ably.Realtime(ensureAndGet("ABLY_ROOT_API_KEY"));
|
|
11
|
-
ably.connection.on("connected", () => {
|
|
12
|
-
console.log("ably connection successful");
|
|
13
|
-
resolve(ably);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return ABLY;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function publishMessage(channel, messageName, messageData) {
|
|
21
|
-
const ably = await getAbly();
|
|
22
|
-
const channelObj = ably.channels.get(channel);
|
|
23
|
-
|
|
24
|
-
const messageDataJson = JSON.stringify(messageData);
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
await channelObj.publish(messageName, messageDataJson);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error("error publishing to ably: ", error);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const rateLimitedPublishMessage = new Bottleneck({
|
|
34
|
-
minTime: 1000 / 40,
|
|
35
|
-
}).wrap(publishMessage);
|
package/block.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// See https://www.quicknode.com/docs/ethereum/eth_getBlockByNumber for a complete reference.
|
|
2
|
-
|
|
3
|
-
type Transaction = {
|
|
4
|
-
blockHash: string;
|
|
5
|
-
blockNumber: string;
|
|
6
|
-
hash: string;
|
|
7
|
-
to: string;
|
|
8
|
-
from: string;
|
|
9
|
-
transactionIndex: string;
|
|
10
|
-
value: string | undefined;
|
|
11
|
-
nonce: string;
|
|
12
|
-
gas: string;
|
|
13
|
-
gasPrice: string;
|
|
14
|
-
input: string;
|
|
15
|
-
type: string;
|
|
16
|
-
chainId: string | undefined;
|
|
17
|
-
v: string;
|
|
18
|
-
r: string;
|
|
19
|
-
s: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type BlockInfo = {
|
|
23
|
-
number: string;
|
|
24
|
-
timestamp: string;
|
|
25
|
-
difficulty: string;
|
|
26
|
-
extraData: string;
|
|
27
|
-
gasLimit: string;
|
|
28
|
-
gasUsed: string;
|
|
29
|
-
hash: string;
|
|
30
|
-
logsBloom: string;
|
|
31
|
-
miner: string;
|
|
32
|
-
mixHash: string;
|
|
33
|
-
nonce: string;
|
|
34
|
-
parentHash: string;
|
|
35
|
-
receiptsRoot: string;
|
|
36
|
-
sha3Uncles: string;
|
|
37
|
-
size: string;
|
|
38
|
-
stateRoot: string;
|
|
39
|
-
totalDifficulty: string;
|
|
40
|
-
transactions: Transaction[];
|
|
41
|
-
transactionsRoot: string;
|
|
42
|
-
uncles: string[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function getLatestBlockHeight(protocol: string): Promise<number>;
|
|
46
|
-
export function getBlock(protocol: string, height: number): Promise<BlockInfo>;
|
|
47
|
-
export function getBlockS3Path(protocol: string, height: number): string;
|
|
48
|
-
export function getLatestS3BlockHeight(protocol: string): Promise<number>;
|
|
49
|
-
export function getBlockFromS3(protocol: string, height: number): Promise<BlockInfo>;
|
package/block.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Web3 } from "web3";
|
|
2
|
-
import { getEnvironment } from "./env.js";
|
|
3
|
-
import { PROTOCOLS } from "./const.js";
|
|
4
|
-
import { readFile } from "./s3.js";
|
|
5
|
-
import { getRecordByKey } from "./dynamodb.js";
|
|
6
|
-
|
|
7
|
-
const S3_BUCKET_NAME =
|
|
8
|
-
getEnvironment() === "prd" ? "certik-skynet" : "certik-skynet-dev";
|
|
9
|
-
const INDEXER_STATE_TABLE = `skynet-${getEnvironment()}-indexer-state`;
|
|
10
|
-
|
|
11
|
-
function leftPad3(num) {
|
|
12
|
-
if (num === 0) {
|
|
13
|
-
return "000";
|
|
14
|
-
} else if (num < 10) {
|
|
15
|
-
return "00" + String(num);
|
|
16
|
-
} else if (num < 100) {
|
|
17
|
-
return "0" + String(num);
|
|
18
|
-
} else {
|
|
19
|
-
return String(num);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getBlockS3Path(protocol, height) {
|
|
24
|
-
return `chaindata/${protocol}/${Math.floor(height / 1_000_000)}/${leftPad3(
|
|
25
|
-
Math.floor(height / 1_000) % 1000
|
|
26
|
-
)}/${leftPad3(height % 1000)}.json`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// We use this function instead of web3 because the gas limits on bsc exceed
|
|
30
|
-
// the size supported by javascript numbers, and web3 does not support this
|
|
31
|
-
async function getBlock(protocol, height) {
|
|
32
|
-
const body = {
|
|
33
|
-
jsonrpc: "2.0",
|
|
34
|
-
method: "eth_getBlockByNumber",
|
|
35
|
-
params: [`0x${height.toString(16)}`, true],
|
|
36
|
-
id: 1
|
|
37
|
-
};
|
|
38
|
-
const response = await fetch(PROTOCOLS[protocol].endpoint, {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: {
|
|
41
|
-
"Content-Type": "application/json"
|
|
42
|
-
},
|
|
43
|
-
body: JSON.stringify(body)
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const { result: block } = await response.json();
|
|
47
|
-
|
|
48
|
-
return block;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function getLatestBlockHeight(protocol) {
|
|
52
|
-
const web3 = new Web3(PROTOCOLS[protocol].endpoint);
|
|
53
|
-
const isSyncing = await web3.eth.isSyncing();
|
|
54
|
-
|
|
55
|
-
if (isSyncing) {
|
|
56
|
-
return isSyncing.currentBlock;
|
|
57
|
-
} else {
|
|
58
|
-
const latestBlock = await web3.eth.getBlock("latest");
|
|
59
|
-
|
|
60
|
-
return latestBlock.number;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function getLatestS3BlockHeight(protocol) {
|
|
65
|
-
const name = `AMLBlockIndexerSince(protocol=${protocol})`;
|
|
66
|
-
const { value: height } = await getRecordByKey(INDEXER_STATE_TABLE, { name });
|
|
67
|
-
return height;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function getBlockFromS3(protocol, height) {
|
|
71
|
-
const body = await readFile(S3_BUCKET_NAME, getBlockS3Path(protocol, height));
|
|
72
|
-
|
|
73
|
-
return JSON.parse(body);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export {
|
|
77
|
-
getLatestBlockHeight,
|
|
78
|
-
getBlock,
|
|
79
|
-
getBlockS3Path,
|
|
80
|
-
getLatestS3BlockHeight,
|
|
81
|
-
getBlockFromS3
|
|
82
|
-
};
|
package/distributed-lock.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export function useLock(useLockParams: { name: string, ttl: string, verbose: boolean }): Promise<string | null>
|
package/distributed-lock.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
async function acquireLock(name, ttl) {
|
|
2
|
-
try {
|
|
3
|
-
const res = await fetch("http://localhost:8500/v1/session/create", {
|
|
4
|
-
method: "PUT",
|
|
5
|
-
body: JSON.stringify({
|
|
6
|
-
Name: name,
|
|
7
|
-
TTL: `${ttl}s`,
|
|
8
|
-
Behavior: "delete",
|
|
9
|
-
}),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
if (res.ok) {
|
|
13
|
-
const { ID: id } = await res.json();
|
|
14
|
-
|
|
15
|
-
return id;
|
|
16
|
-
} else {
|
|
17
|
-
console.log(res, await res.text());
|
|
18
|
-
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
} catch (fetchErr) {
|
|
22
|
-
// console.log("error fetching", fetchErr);
|
|
23
|
-
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function renewLock(uid) {
|
|
29
|
-
try {
|
|
30
|
-
const res = await fetch(`http://localhost:8500/v1/session/renew/${uid}`, {
|
|
31
|
-
method: "PUT",
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
if (res.ok) {
|
|
35
|
-
return uid;
|
|
36
|
-
} else {
|
|
37
|
-
console.log(res, await res.text());
|
|
38
|
-
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
} catch (fetchErr) {
|
|
42
|
-
// console.log("error fetching", fetchErr);
|
|
43
|
-
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function hasLock(name, uid = null) {
|
|
49
|
-
try {
|
|
50
|
-
const res = await fetch(`http://localhost:8500/v1/session/list`);
|
|
51
|
-
|
|
52
|
-
if (res.ok) {
|
|
53
|
-
const sessions = await res.json();
|
|
54
|
-
|
|
55
|
-
const sessionsWithTheSameName = sessions.filter((s) => s.Name === name);
|
|
56
|
-
|
|
57
|
-
if (!uid) {
|
|
58
|
-
return sessionsWithTheSameName.length === 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (sessionsWithTheSameName.length !== 1) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// only one should have the same uid
|
|
66
|
-
return sessionsWithTheSameName[0].ID === uid;
|
|
67
|
-
} else {
|
|
68
|
-
console.log(await res.text());
|
|
69
|
-
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
} catch (fetchErr) {
|
|
73
|
-
// console.log("error fetching", fetchErr);
|
|
74
|
-
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function useLock({ name, ttl, verbose }) {
|
|
80
|
-
const lockWarningMessage = `only one process with the same lock name ${name} should be running, terminate current process`;
|
|
81
|
-
|
|
82
|
-
const lockAvailable = await hasLock(name);
|
|
83
|
-
|
|
84
|
-
if (!lockAvailable) {
|
|
85
|
-
console.log(lockWarningMessage);
|
|
86
|
-
process.exit(0);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
let uid = await acquireLock(name, ttl);
|
|
90
|
-
|
|
91
|
-
setInterval(async () => {
|
|
92
|
-
if (!(await hasLock(name, uid))) {
|
|
93
|
-
console.log(lockWarningMessage);
|
|
94
|
-
process.exit(0);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (uid) {
|
|
98
|
-
if (verbose) {
|
|
99
|
-
console.log("renewing", name, uid);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
uid = await renewLock(uid);
|
|
103
|
-
} else {
|
|
104
|
-
uid = await acquireLock(name, ttl);
|
|
105
|
-
}
|
|
106
|
-
}, (ttl * 1000) / 2);
|
|
107
|
-
|
|
108
|
-
return uid;
|
|
109
|
-
}
|
package/metric.d.ts
DELETED
package/metric.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { getDocClient } from "./dynamodb.js";
|
|
2
|
-
|
|
3
|
-
/* Assume table has name/timestamp/value fields */
|
|
4
|
-
|
|
5
|
-
export async function getMetricAt(tableName, name, timestamp) {
|
|
6
|
-
const docClient = getDocClient();
|
|
7
|
-
|
|
8
|
-
const query = await docClient
|
|
9
|
-
.query({
|
|
10
|
-
TableName: tableName,
|
|
11
|
-
KeyConditionExpression: "#name = :name and #timestamp <= :timestamp",
|
|
12
|
-
ExpressionAttributeNames: {
|
|
13
|
-
"#name": "name",
|
|
14
|
-
"#timestamp": "timestamp"
|
|
15
|
-
},
|
|
16
|
-
ExpressionAttributeValues: {
|
|
17
|
-
":name": name,
|
|
18
|
-
":timestamp": timestamp
|
|
19
|
-
},
|
|
20
|
-
Limit: 1,
|
|
21
|
-
ScanIndexForward: false
|
|
22
|
-
})
|
|
23
|
-
.promise();
|
|
24
|
-
|
|
25
|
-
if (query.Count > 0) {
|
|
26
|
-
const { value } = query.Items[0];
|
|
27
|
-
|
|
28
|
-
return value;
|
|
29
|
-
} else {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function getMetricPreviousValue(tableName, name, timestamp) {
|
|
35
|
-
const docClient = getDocClient();
|
|
36
|
-
|
|
37
|
-
const query = await docClient
|
|
38
|
-
.query({
|
|
39
|
-
TableName: tableName,
|
|
40
|
-
KeyConditionExpression: "#name = :name and #timestamp < :timestamp",
|
|
41
|
-
ExpressionAttributeNames: {
|
|
42
|
-
"#timestamp": "timestamp",
|
|
43
|
-
"#name": "name"
|
|
44
|
-
},
|
|
45
|
-
ExpressionAttributeValues: {
|
|
46
|
-
":timestamp": timestamp,
|
|
47
|
-
":name": name
|
|
48
|
-
},
|
|
49
|
-
Limit: 1,
|
|
50
|
-
ScanIndexForward: false
|
|
51
|
-
})
|
|
52
|
-
.promise();
|
|
53
|
-
|
|
54
|
-
if (query.Count === 0) {
|
|
55
|
-
return 0;
|
|
56
|
-
} else {
|
|
57
|
-
const { value } = query.Items[0];
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
60
|
-
}
|
package/price.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
type PricePreviousRecord = {
|
|
2
|
-
address: string;
|
|
3
|
-
price: number;
|
|
4
|
-
timestamp: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function getPriceClosestTo(tableName: string, address: string, timestamp: number): Promise<number>;
|
|
8
|
-
export function getPricePreviousRecord(tableName: string, address: string, timestamp: number): Promise<PricePreviousRecord | null>;
|
package/price.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { QueryCommand } from "@aws-sdk/lib-dynamodb";
|
|
2
|
-
import { getDocClient } from "./dynamodb.js";
|
|
3
|
-
|
|
4
|
-
async function queryPrice(tableName, address, timestamp, op = "<") {
|
|
5
|
-
const docClient = getDocClient();
|
|
6
|
-
|
|
7
|
-
return await docClient.send(
|
|
8
|
-
new QueryCommand({
|
|
9
|
-
TableName: tableName,
|
|
10
|
-
KeyConditionExpression: `#address = :address and #timestamp ${op} :timestamp`,
|
|
11
|
-
ExpressionAttributeNames: {
|
|
12
|
-
"#timestamp": "timestamp",
|
|
13
|
-
"#address": "address",
|
|
14
|
-
},
|
|
15
|
-
ExpressionAttributeValues: {
|
|
16
|
-
":timestamp": timestamp,
|
|
17
|
-
":address": address,
|
|
18
|
-
},
|
|
19
|
-
Limit: 1,
|
|
20
|
-
ScanIndexForward: false,
|
|
21
|
-
}),
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function getPricePreviousRecord(tableName, address, timestamp) {
|
|
26
|
-
const query = await queryPrice(tableName, address, timestamp, "<");
|
|
27
|
-
|
|
28
|
-
if (query.Count === 0) {
|
|
29
|
-
return null;
|
|
30
|
-
} else {
|
|
31
|
-
return query.Items[0];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function getPriceClosestTo(tableName, address, timestamp) {
|
|
36
|
-
const query = await queryPrice(tableName, address, timestamp, "<=");
|
|
37
|
-
|
|
38
|
-
if (query.Count === 0) {
|
|
39
|
-
return 0;
|
|
40
|
-
} else {
|
|
41
|
-
const { price } = query.Items[0];
|
|
42
|
-
return price;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export { getPriceClosestTo, getPricePreviousRecord };
|
package/primitive.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { DynamoDB } from "aws-sdk";
|
|
2
|
-
|
|
3
|
-
type Issue = {
|
|
4
|
-
code: string;
|
|
5
|
-
link: string;
|
|
6
|
-
reduction: number;
|
|
7
|
-
title: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function getOverrides(
|
|
11
|
-
docClient: DynamoDB.DocumentClient,
|
|
12
|
-
projectId: string,
|
|
13
|
-
address: string,
|
|
14
|
-
log: (...data: any[]) => void,
|
|
15
|
-
primitive: string
|
|
16
|
-
): Promise<string[] | null>;
|
|
17
|
-
|
|
18
|
-
export function overrideScoreAndIssues(
|
|
19
|
-
docClient: DynamoDB.DocumentClient,
|
|
20
|
-
projectId: string,
|
|
21
|
-
address: string,
|
|
22
|
-
score: number,
|
|
23
|
-
issues: Issue[],
|
|
24
|
-
log: (...data: any[]) => void,
|
|
25
|
-
primitive: string
|
|
26
|
-
): Promise<{
|
|
27
|
-
score: number;
|
|
28
|
-
issues: Issue[];
|
|
29
|
-
}>
|
package/primitive.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
async function getOverrides(docClient, projectId, address, log, primitive) {
|
|
2
|
-
try {
|
|
3
|
-
console.log("ProjectId:" + projectId + " address:" + address);
|
|
4
|
-
|
|
5
|
-
const whitelist = [];
|
|
6
|
-
const queryProjectOverrides = await docClient
|
|
7
|
-
.get({
|
|
8
|
-
TableName: "skynet-prd-primitive-manual-overrides",
|
|
9
|
-
Key: {
|
|
10
|
-
primitive: primitive,
|
|
11
|
-
overrideId: "project/" + projectId,
|
|
12
|
-
},
|
|
13
|
-
})
|
|
14
|
-
.promise();
|
|
15
|
-
if (queryProjectOverrides.Item && queryProjectOverrides.Item.whitelist) {
|
|
16
|
-
whitelist.push(...queryProjectOverrides.Item.whitelist);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const queryAddressOverrides = await docClient
|
|
20
|
-
.get({
|
|
21
|
-
TableName: "skynet-prd-primitive-manual-overrides",
|
|
22
|
-
Key: {
|
|
23
|
-
primitive: primitive,
|
|
24
|
-
overrideId: "address/" + address,
|
|
25
|
-
},
|
|
26
|
-
})
|
|
27
|
-
.promise();
|
|
28
|
-
|
|
29
|
-
if (queryAddressOverrides.Item && queryAddressOverrides.Item.whitelist) {
|
|
30
|
-
console.log(queryAddressOverrides.Item.whitelist);
|
|
31
|
-
whitelist.push(...queryAddressOverrides.Item.whitelist);
|
|
32
|
-
}
|
|
33
|
-
if (whitelist.length === 0) {
|
|
34
|
-
log(
|
|
35
|
-
"ProjectId " + projectId + " ,Address " + address + " has no override"
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
console.log("Whitelist:" + whitelist);
|
|
39
|
-
return whitelist;
|
|
40
|
-
} catch (scanFeedErr) {
|
|
41
|
-
log("scan feed error, check dynamodb table", scanFeedErr);
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function overrideScoreAndIssues(
|
|
47
|
-
docClient,
|
|
48
|
-
projectId,
|
|
49
|
-
address,
|
|
50
|
-
score,
|
|
51
|
-
issues,
|
|
52
|
-
log,
|
|
53
|
-
primitive
|
|
54
|
-
) {
|
|
55
|
-
const overrides = await getOverrides(
|
|
56
|
-
docClient,
|
|
57
|
-
projectId,
|
|
58
|
-
address,
|
|
59
|
-
log,
|
|
60
|
-
primitive
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
issues = issues.filter((issue) => {
|
|
64
|
-
if (overrides.includes(issue.code)) {
|
|
65
|
-
score += issue.reduction;
|
|
66
|
-
return false;
|
|
67
|
-
} else return true;
|
|
68
|
-
});
|
|
69
|
-
return {
|
|
70
|
-
score,
|
|
71
|
-
issues,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export {
|
|
76
|
-
getOverrides,
|
|
77
|
-
overrideScoreAndIssues,
|
|
78
|
-
};
|
package/scan.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
type Transaction = {
|
|
2
|
-
blockNumber: string,
|
|
3
|
-
timeStamp: string,
|
|
4
|
-
hash: string,
|
|
5
|
-
nonce: string,
|
|
6
|
-
blockHash: string,
|
|
7
|
-
transactionIndex: string,
|
|
8
|
-
from: string,
|
|
9
|
-
to: string,
|
|
10
|
-
value: string | undefined,
|
|
11
|
-
gas: string,
|
|
12
|
-
gasPrice: string,
|
|
13
|
-
isError: string,
|
|
14
|
-
txreceipt_status: string,
|
|
15
|
-
input: string,
|
|
16
|
-
contractAddress: string,
|
|
17
|
-
cumulativeGasUsed: string,
|
|
18
|
-
gasUsed: string,
|
|
19
|
-
confirmations: string,
|
|
20
|
-
methodId: string,
|
|
21
|
-
functionName: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function streamTxs(
|
|
25
|
-
address: string,
|
|
26
|
-
since: number | undefined,
|
|
27
|
-
to: number | undefined,
|
|
28
|
-
callback: (tx: Transaction) => void,
|
|
29
|
-
verbose: boolean
|
|
30
|
-
): Promise<void>;
|
|
31
|
-
|
|
32
|
-
export function fetchTxs(
|
|
33
|
-
protocol: string,
|
|
34
|
-
addr: string,
|
|
35
|
-
since: number | undefined,
|
|
36
|
-
to: number | undefined,
|
|
37
|
-
offset: number | undefined,
|
|
38
|
-
): Promise<Transaction[] | null>;
|
|
39
|
-
|
|
40
|
-
export function fetchTxsWithRetry(
|
|
41
|
-
protocol: string,
|
|
42
|
-
addr: string,
|
|
43
|
-
since: number | undefined,
|
|
44
|
-
to: number | undefined,
|
|
45
|
-
offset: number | undefined,
|
|
46
|
-
verbose: boolean
|
|
47
|
-
): Promise<Transaction[] | null>
|
package/scan.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { exponentialRetry, wait } from "./availability.js";
|
|
2
|
-
import { PROTOCOLS } from "./const.js";
|
|
3
|
-
|
|
4
|
-
const BATCH_SIZE = 10_000; // Max number of transactions fetched from scan api at once
|
|
5
|
-
|
|
6
|
-
function getScanApiEndpoint(protocol, addr, startBlock = 0, endBlock = null, offset=BATCH_SIZE) {
|
|
7
|
-
const { endpoint, key } = PROTOCOLS[protocol].scanApi;
|
|
8
|
-
let url = `${endpoint}?module=account&action=txlist&address=${addr}&apikey=${key}&sort=asc&startblock=${startBlock}&page=1&offset=${offset}`;
|
|
9
|
-
|
|
10
|
-
if (endBlock) {
|
|
11
|
-
url += `&endblock=${endBlock}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return url;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function fetchTxs(protocol, addr, since, to, offset) {
|
|
18
|
-
const url = getScanApiEndpoint(protocol, addr, since, to, offset);
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const res = await fetch(url);
|
|
22
|
-
const scanJSON = await res.json();
|
|
23
|
-
return scanJSON.result;
|
|
24
|
-
} catch (err) {
|
|
25
|
-
console.log(`error fetching txs: ${err}`)
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function fetchTxsWithRetry(protocol, addr, since, to, offset, verbose) {
|
|
31
|
-
const txs = await exponentialRetry(
|
|
32
|
-
async () => fetchTxs(protocol, addr, since, to, offset),
|
|
33
|
-
{ maxRetry: 6, test: Array.isArray, verbose }
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
return txs;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function streamTxs(address, since, to, callback, verbose) {
|
|
40
|
-
let startBlock = since;
|
|
41
|
-
let hasMorePage = true;
|
|
42
|
-
let lastTxHash = null;
|
|
43
|
-
|
|
44
|
-
const [protocol, addr] = address.split(":");
|
|
45
|
-
|
|
46
|
-
while (hasMorePage) {
|
|
47
|
-
const txs = await fetchTxs(protocol, addr, startBlock, to, BATCH_SIZE, verbose);
|
|
48
|
-
console.log(`start block=${since}, total items=${txs.length}`);
|
|
49
|
-
|
|
50
|
-
// This will only ever happen if the total number of txs is a multiple of our batch size
|
|
51
|
-
if (txs.length === 0) {
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// If our start block overlaps with the last request's end block, then we may have some overlapping txs
|
|
56
|
-
// To avoid adding those txs to the db twice, we filter them out
|
|
57
|
-
const newTxsStartIdx = txs.indexOf(({ hash }) => hash === lastTxHash);
|
|
58
|
-
|
|
59
|
-
const newTxs = txs.slice(newTxsStartIdx !== -1 ? newTxsStartIdx : 0);
|
|
60
|
-
|
|
61
|
-
for (const tx of newTxs) {
|
|
62
|
-
callback(tx);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
hasMorePage = txs.length === BATCH_SIZE;
|
|
66
|
-
|
|
67
|
-
startBlock = parseInt(txs[txs.length - 1].blockNumber, 10) + 1;
|
|
68
|
-
lastTxHash = txs[txs.length - 1].hash;
|
|
69
|
-
|
|
70
|
-
await wait(2000);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export { streamTxs, fetchTxs, fetchTxsWithRetry };
|
package/token.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { BigNumber } from "bignumber.js";
|
|
2
|
-
|
|
3
|
-
export function getTokenPriceAt(tokenAddress: string, timestamp: number, useCache: boolean | undefined): Promise<number>;
|
|
4
|
-
export function toNativeDecimal(bigNumber: BigNumber, decimals: number): Promise<BigNumber>;
|
|
5
|
-
export function toHumanDecimal(bigNumber: BigNumber, decimals: number): Promise<BigNumber>;
|
package/token.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { getEnvironment } from "./env.js";
|
|
2
|
-
import { getPriceClosestTo } from "./price.js";
|
|
3
|
-
import BigNumber from "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
|
-
export {
|
|
43
|
-
getTokenPriceAt,
|
|
44
|
-
toNativeDecimal,
|
|
45
|
-
toHumanDecimal,
|
|
46
|
-
};
|
package/transaction.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
type Log = {
|
|
2
|
-
address: string,
|
|
3
|
-
topics: string[],
|
|
4
|
-
data: string,
|
|
5
|
-
blockNumber: string,
|
|
6
|
-
transactionHash: string,
|
|
7
|
-
transactionIndex: string,
|
|
8
|
-
blockHash: string,
|
|
9
|
-
logIndex: string,
|
|
10
|
-
removed: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
type TransactionReceipt = {
|
|
14
|
-
blockHash: string,
|
|
15
|
-
blockNumber: string,
|
|
16
|
-
contractAddress: string | null,
|
|
17
|
-
cumulativeGasUsed: string,
|
|
18
|
-
effectiveGasPrice: string,
|
|
19
|
-
from: string,
|
|
20
|
-
gasUsed: string,
|
|
21
|
-
logs: Log[],
|
|
22
|
-
logsBloom: string,
|
|
23
|
-
status: string,
|
|
24
|
-
to: string,
|
|
25
|
-
transactionHash: string,
|
|
26
|
-
transactionIndex: string,
|
|
27
|
-
type: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getTxReceipt(protocol: string, txHash: string, verbose: boolean): Promise<TransactionReceipt | null>;
|
package/transaction.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { PROTOCOLS } from "./const.js";
|
|
2
|
-
import { exponentialRetry } from "./availability.js";
|
|
3
|
-
|
|
4
|
-
async function getTxReceipt(protocol, txHash, verbose = false) {
|
|
5
|
-
const { endpoint } = PROTOCOLS[protocol];
|
|
6
|
-
|
|
7
|
-
console.log("got endpoint", protocol, endpoint);
|
|
8
|
-
|
|
9
|
-
const body = {
|
|
10
|
-
jsonrpc: "2.0",
|
|
11
|
-
method: "eth_getTransactionReceipt",
|
|
12
|
-
params: [txHash],
|
|
13
|
-
id: 1
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const response = await exponentialRetry(
|
|
17
|
-
() => {
|
|
18
|
-
return fetch(endpoint, {
|
|
19
|
-
method: "POST",
|
|
20
|
-
headers: { "Content-Type": "application/json" },
|
|
21
|
-
body: JSON.stringify(body)
|
|
22
|
-
});
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
maxRetry: 6,
|
|
26
|
-
test: r => r.ok,
|
|
27
|
-
verbose
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
if (!response.ok) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const { result } = await response.json();
|
|
36
|
-
|
|
37
|
-
return result;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export {
|
|
41
|
-
getTxReceipt
|
|
42
|
-
};
|