Skip to content

Commit 6219921

Browse files
Merge pull request #24 from RoboVault/utils/timestamp
Utils/timestamp
2 parents d602cc5 + 66257cf commit 6219921

File tree

6 files changed

+406
-771
lines changed

6 files changed

+406
-771
lines changed

deno.lock

+268-722
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/arkiver/manifest-builder/manifest.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,13 @@ export class Manifest<
131131
public build() {
132132
const { problems } = parseArkiveManifest.manifest(this.manifest)
133133
if (problems) {
134-
throw new Error(`Invalid manifest: ${problems}`)
134+
throw new Error(
135+
`Invalid manifest: \n\t${
136+
problems.map((p) =>
137+
`${p.message} at: ${p.path?.map((p) => p.key).join('.')}`
138+
).join('\n\t')
139+
}`,
140+
)
135141
}
136142
return this.manifest
137143
}

src/arkiver/manifest-validator.ts

+84-47
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,86 @@
1-
import { supportedChains } from '../chains.ts'
2-
import { scope } from '../deps.ts'
1+
import {
2+
any,
3+
array,
4+
bigint,
5+
boolean,
6+
literal,
7+
object,
8+
optional,
9+
record,
10+
regex,
11+
safeParse,
12+
special,
13+
string,
14+
union,
15+
url,
16+
} from 'https://deno.land/x/valibot@v0.8.0/mod.ts'
317

4-
export const parseArkiveManifest = scope({
5-
manifest: {
6-
name: 'string',
7-
'version?': /^v\d+$/,
8-
dataSources: Object.fromEntries(
9-
Object.keys(supportedChains).map((chain) => [`${chain}?`, 'dataSource']),
10-
) as Record<`${keyof typeof supportedChains}?`, 'dataSource'>,
11-
entities: 'entity[]',
12-
'schemaComposerCustomizer?': 'Function',
13-
},
14-
dataSource: {
15-
options: 'chainOptions',
16-
'contracts?': 'contract[]',
17-
'blockHandlers?': 'blockHandler[]',
18-
},
19-
entity: {
20-
model: 'Function',
21-
list: 'boolean',
22-
name: 'string',
23-
},
24-
chainOptions: {
25-
blockRange: 'bigint',
26-
rpcUrl: 'string',
27-
},
28-
contract: {
29-
id: 'string',
30-
abi: 'any[]',
31-
sources: 'source[]',
32-
events: 'eventSource[]',
33-
'factorySources?': 'any',
34-
},
35-
blockHandler: {
36-
handler: 'Function',
37-
startBlockHeight: 'bigint|"live"',
38-
blockInterval: 'bigint',
39-
name: 'string',
40-
},
41-
source: {
42-
address: '/^0x[a-fA-F0-9]{40}$/ | "*"',
43-
startBlockHeight: 'bigint',
44-
},
45-
eventSource: {
46-
name: 'string',
47-
handler: 'Function',
18+
const manifestSchema = object({
19+
name: string('Name must be a string'),
20+
version: optional(string('Invalid version', [regex(/^v\d+$/)])),
21+
dataSources: record(object({
22+
options: object({
23+
blockRange: bigint('Block range must be a bigint'),
24+
rpcUrl: string('RPC URL must be a string', [
25+
url('RPC URL must be a valid URL'),
26+
]),
27+
}),
28+
contracts: optional(array(object({
29+
id: string('Contract ID must be a string'),
30+
abi: array(any()),
31+
sources: array(object({
32+
address: union([
33+
string(
34+
'Address must either be a valid hexstring or a wildcard (\'*\')',
35+
[
36+
regex(/^0x[a-fA-F0-9]{40}$/, 'Address must be a valid hexstring'),
37+
],
38+
),
39+
literal('*', 'Address can be \'*\''),
40+
]),
41+
startBlockHeight: bigint('Start block height must be a bigint'),
42+
})),
43+
events: array(object({
44+
name: string('Event name must be a string'),
45+
handler: special((input) => typeof input === 'function'),
46+
})),
47+
factorySources: optional(
48+
record(
49+
record(
50+
string('Factory sources must be a record of records of strings'),
51+
),
52+
),
53+
),
54+
}))),
55+
blockHandlers: optional(array(object({
56+
handler: special((input) => typeof input === 'function'),
57+
startBlockHeight: union(
58+
[
59+
bigint('Start block height must be a bigint'),
60+
literal('live', 'Start block height can be \'live\''),
61+
],
62+
),
63+
blockInterval: bigint('Block interval must be a bigint'),
64+
name: string('Block handler name must be a string'),
65+
}))),
66+
})),
67+
entities: array(object({
68+
model: special((input) => typeof input === 'function'),
69+
list: boolean('Entity\'s list property must be a boolean'),
70+
name: string('Entity\'s name property must be a string'),
71+
})),
72+
schemaComposerCustomizer: optional(
73+
special((input) => typeof input === 'function'),
74+
),
75+
})
76+
77+
export const parseArkiveManifest = {
78+
manifest: (manifest: unknown) => {
79+
const result = safeParse(manifestSchema, manifest)
80+
if (result.success) {
81+
return { data: result.data }
82+
} else {
83+
return { problems: result.error.issues }
84+
}
4885
},
49-
}).compile()
86+
}

src/deps.ts

-1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,4 @@ export {
3939
ConsoleHandler,
4040
} from 'https://deno.land/std@0.181.0/log/handlers.ts'
4141
export { crypto } from 'https://deno.land/std@0.186.0/crypto/mod.ts'
42-
export { instanceOf, scope } from 'npm:arktype'
4342
export { GraphQLError } from 'npm:graphql'

src/utils/timestamp.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Store } from "../arkiver/store.ts";
2+
import { PublicClient } from "../deps.ts";
3+
4+
/**
5+
* Returns the closest timestamp to the given timestamp that is divisible by the given interval (in ms) rounded down.
6+
* @param timestamp
7+
* @param interval
8+
* @returns
9+
*/
10+
export const getClosestTimestamp = (timestamp: number, interval: number) => {
11+
return timestamp - (timestamp % interval);
12+
};
13+
14+
export const getTimestampFromBlockNumber = async (
15+
params: {
16+
client: PublicClient;
17+
store: Store;
18+
blockNumber: bigint;
19+
group?: {
20+
blockTimeMs: number;
21+
groupTimeMs: number;
22+
};
23+
},
24+
) => {
25+
let adjustedBlockNumber = params.blockNumber;
26+
27+
if (params.group) {
28+
const blocks = Math.floor(
29+
params.group.groupTimeMs / params.group.blockTimeMs,
30+
);
31+
adjustedBlockNumber = params.blockNumber -
32+
(params.blockNumber % BigInt(blocks));
33+
}
34+
35+
return Number(
36+
await params.store.retrieve(
37+
`blockNumberTimestamp:${adjustedBlockNumber}`,
38+
async () => {
39+
const block = await params.client.getBlock({
40+
blockNumber: adjustedBlockNumber,
41+
});
42+
return block.timestamp;
43+
},
44+
),
45+
) * 1000;
46+
};

utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { bigIntDivToFloat, bigIntToFloat } from "./src/utils/bigint.ts";
2+
export { getClosestTimestamp, getTimestampFromBlockNumber } from "./src/utils/timestamp.ts"

0 commit comments

Comments
 (0)