@certik/skynet 0.12.0 → 0.13.1
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 +13 -0
- package/bun.lockb +0 -0
- package/package.json +13 -14
- package/s3.js +13 -1
- package/slack.js +5 -60
- package/ably.d.ts +0 -4
- package/ably.js +0 -35
- package/metric.d.ts +0 -10
- package/metric.js +0 -60
- package/scan.d.ts +0 -47
- package/scan.js +0 -74
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.1
|
|
4
|
+
|
|
5
|
+
- Added: s3 getFileSize (@certik/skynet/s3)
|
|
6
|
+
|
|
7
|
+
## 0.13.0
|
|
8
|
+
|
|
9
|
+
- BREAKING: removed ably library (@certik/skynet/ably)
|
|
10
|
+
- BREAKING: removed metric library (@certik/skynet/metric)
|
|
11
|
+
- BREAKING: removed scan library (@certik/skynet/scan)
|
|
12
|
+
- BREAKING: redesigned slack library (@certik/skynet/slack)
|
|
13
|
+
- Upgraded: @aws-sdk to support new S3 directory bucket capability
|
|
14
|
+
- Deprecated: kafka / web3
|
|
15
|
+
|
|
3
16
|
## 0.12.0
|
|
4
17
|
|
|
5
18
|
- BREAKING: removed token module (@certik/skynet/token)
|
package/bun.lockb
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certik/skynet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
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.
|
|
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
22
|
"@opensearch-project/opensearch": "^2.4.0",
|
|
23
|
-
"@slack/web-api": "^6.
|
|
24
|
-
"ably": "^1.2.47",
|
|
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.9.
|
|
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/s3.js
CHANGED
|
@@ -46,6 +46,18 @@ async function hasFile(bucketName, key) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
async function getFileSize(bucketName, key) {
|
|
50
|
+
const s3 = getS3();
|
|
51
|
+
|
|
52
|
+
const result = await s3.send(
|
|
53
|
+
new HeadObjectCommand({
|
|
54
|
+
Bucket: bucketName,
|
|
55
|
+
Key: key,
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
return result.ContentLength;
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
async function writeFile(bucketName, key, body, options = {}) {
|
|
50
62
|
const s3 = getS3();
|
|
51
63
|
|
|
@@ -116,4 +128,4 @@ async function listKeys(bucketname, prefix, continuationToken) {
|
|
|
116
128
|
return res;
|
|
117
129
|
}
|
|
118
130
|
|
|
119
|
-
export { getS3, hasFile, readFile, writeFile, deleteFile, listKeys };
|
|
131
|
+
export { getS3, hasFile, readFile, writeFile, deleteFile, getFileSize, listKeys };
|
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/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/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 };
|