@subsquid/portal-client 0.0.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.
@@ -0,0 +1,180 @@
1
+ import type { Select, Selector, Trues, Hex, Simplify, PortalQuery } from './common';
2
+ /**
3
+ * @example 'Balances.Transfer'
4
+ */
5
+ export type QualifiedName = string & {};
6
+ export type BlockHeaderFields = {
7
+ /**
8
+ * Block height
9
+ */
10
+ number: number;
11
+ /**
12
+ * Block hash
13
+ */
14
+ hash: Hex;
15
+ /**
16
+ * Hash of the parent block
17
+ */
18
+ parentHash: Hex;
19
+ /**
20
+ * Root hash of the state merkle tree
21
+ */
22
+ stateRoot: Hex;
23
+ /**
24
+ * Root hash of the extrinsics merkle tree
25
+ */
26
+ extrinsicsRoot: Hex;
27
+ digest: {
28
+ logs: Hex[];
29
+ };
30
+ specName: string;
31
+ specVersion: number;
32
+ implName: string;
33
+ implVersion: number;
34
+ /**
35
+ * Block timestamp as set by `timestamp.now()` (unix epoch ms, compatible with `Date`).
36
+ */
37
+ timestamp?: number;
38
+ /**
39
+ * Account address of block validator
40
+ */
41
+ validator?: Hex;
42
+ };
43
+ export type ExtrinsicSignatureFields = {
44
+ address: unknown;
45
+ signature: unknown;
46
+ signedExtensions: unknown;
47
+ };
48
+ export type ExtrinsicFields = {
49
+ /**
50
+ * Ordinal index in the extrinsics array of the current block
51
+ */
52
+ index: number;
53
+ version: number;
54
+ signature?: ExtrinsicSignatureFields;
55
+ fee?: bigint;
56
+ tip?: bigint;
57
+ error?: unknown;
58
+ success?: boolean;
59
+ /**
60
+ * Blake2b 128-bit hash of the raw extrinsic
61
+ */
62
+ hash?: Hex;
63
+ };
64
+ export type CallFields = {
65
+ extrinsicIndex: number;
66
+ address: number[];
67
+ name: QualifiedName;
68
+ args: unknown;
69
+ origin?: unknown;
70
+ /**
71
+ * Call error.
72
+ *
73
+ * Absence of error doesn't imply that the call was executed successfully,
74
+ * check {@link success} property for that.
75
+ */
76
+ error?: unknown;
77
+ success?: boolean;
78
+ _ethereumTransactTo?: Hex;
79
+ _ethereumTransactSighash?: Hex;
80
+ };
81
+ export type EventFields = {
82
+ /**
83
+ * Ordinal index in the event array of the current block
84
+ */
85
+ index: number;
86
+ /**
87
+ * Event name
88
+ */
89
+ name: QualifiedName;
90
+ args: unknown;
91
+ phase: 'Initialization' | 'ApplyExtrinsic' | 'Finalization';
92
+ extrinsicIndex?: number;
93
+ callAddress?: number[];
94
+ /**
95
+ * This field is not supported by all currently deployed archives.
96
+ * Requesting it may cause internal error.
97
+ */
98
+ topics: Hex[];
99
+ _evmLogAddress?: Hex;
100
+ _evmLogTopics?: Hex[];
101
+ _contractAddress?: Hex;
102
+ _gearProgramId?: Hex;
103
+ };
104
+ export type BlockHeaderFieldSelection = Simplify<Selector<keyof BlockHeaderFields> & {
105
+ number: true;
106
+ hash: true;
107
+ }>;
108
+ export type BlockHeader<T extends BlockHeaderFieldSelection = Trues<BlockHeaderFieldSelection>> = Select<BlockHeaderFields, T>;
109
+ export type ExtrinsicFieldSelection = Selector<keyof ExtrinsicFields>;
110
+ export type Extrinsic<T extends ExtrinsicFieldSelection = Trues<ExtrinsicFieldSelection>> = Select<ExtrinsicFields, T>;
111
+ export type CallFieldSelection = Selector<keyof CallFields>;
112
+ export type Call<T extends CallFieldSelection = Trues<CallFieldSelection>> = Select<CallFields, T>;
113
+ export type EventFieldSelection = Selector<keyof EventFields>;
114
+ export type Event<T extends EventFieldSelection = Trues<CallFieldSelection>> = Select<EventFields, T>;
115
+ export type FieldSelection = {
116
+ block?: BlockHeaderFieldSelection;
117
+ extrinsic?: ExtrinsicFieldSelection;
118
+ call?: CallFieldSelection;
119
+ event?: EventFieldSelection;
120
+ };
121
+ export type EventRelations = {
122
+ extrinsic?: boolean;
123
+ call?: boolean;
124
+ stack?: boolean;
125
+ };
126
+ export type EventRequest = Simplify<{
127
+ name?: QualifiedName[];
128
+ } & EventRelations>;
129
+ export type CallRelations = {
130
+ extrinsic?: boolean;
131
+ stack?: boolean;
132
+ events?: boolean;
133
+ };
134
+ export type CallRequest = Simplify<{
135
+ name?: QualifiedName[];
136
+ } & CallRelations>;
137
+ export type EvmLogRequest = Simplify<{
138
+ address?: Hex[];
139
+ } & EventRelations>;
140
+ export type EthereumLogRequest = Simplify<{
141
+ address?: Hex[];
142
+ topic0?: Hex[];
143
+ topic1?: Hex[];
144
+ topic2?: Hex[];
145
+ topic3?: Hex[];
146
+ } & EventRelations>;
147
+ export type EthereumTransactRequest = Simplify<{
148
+ to?: Hex[];
149
+ sighash?: Hex[];
150
+ } & CallRelations>;
151
+ export type ContractsContractEmittedRequest = Simplify<{
152
+ address?: Hex[];
153
+ } & EventRelations>;
154
+ export type GearMessageQueuedRequest = Simplify<{
155
+ programId?: Hex[];
156
+ } & EventRelations>;
157
+ export type GearUserMessageSentRequest = Simplify<{
158
+ programId?: Hex[];
159
+ } & EventRelations>;
160
+ export type DataRequest = {
161
+ includeAllBlocks?: boolean;
162
+ events?: EventRequest[];
163
+ calls?: CallRequest[];
164
+ evmLogs?: EvmLogRequest[];
165
+ ethereumTransactions?: EthereumTransactRequest[];
166
+ contractsEvents?: ContractsContractEmittedRequest[];
167
+ gearMessagesQueued?: GearMessageQueuedRequest[];
168
+ gearUserMessagesSent?: GearUserMessageSentRequest[];
169
+ };
170
+ export type Query = Simplify<PortalQuery & {
171
+ type: 'substrate';
172
+ fields: FieldSelection;
173
+ } & DataRequest>;
174
+ export type Block<F extends FieldSelection> = Simplify<{
175
+ header: BlockHeader<F['block'] & {}>;
176
+ events?: Event<F['event'] & {}>[];
177
+ calls?: Call<F['call'] & {}>[];
178
+ extrinsics?: Extrinsic<F['extrinsic'] & {}>[];
179
+ }>;
180
+ //# sourceMappingURL=substrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"substrate.d.ts","sourceRoot":"","sources":["../../src/query/substrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAe,WAAW,EAAC,MAAM,UAAU,CAAA;AAE9F;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,EAAE,CAAA;AAEvC,MAAM,MAAM,iBAAiB,GAAG;IAC5B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;OAEG;IACH,IAAI,EAAE,GAAG,CAAA;IACT;;OAEG;IACH,UAAU,EAAE,GAAG,CAAA;IACf;;OAEG;IACH,SAAS,EAAE,GAAG,CAAA;IACd;;OAEG;IACH,cAAc,EAAE,GAAG,CAAA;IACnB,MAAM,EAAE;QAAC,IAAI,EAAE,GAAG,EAAE,CAAA;KAAC,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,GAAG,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB,gBAAgB,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC1B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,wBAAwB,CAAA;IACpC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;OAEG;IACH,IAAI,CAAC,EAAE,GAAG,CAAA;CACb,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mBAAmB,CAAC,EAAE,GAAG,CAAA;IACzB,wBAAwB,CAAC,EAAE,GAAG,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACtB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,cAAc,CAAA;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB;;;OAGG;IACH,MAAM,EAAE,GAAG,EAAE,CAAA;IACb,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,aAAa,CAAC,EAAE,GAAG,EAAE,CAAA;IACrB,gBAAgB,CAAC,EAAE,GAAG,CAAA;IACtB,cAAc,CAAC,EAAE,GAAG,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,iBAAiB,CAAC,GAAG;IAAC,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAC,CAAC,CAAA;AAChH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,yBAAyB,GAAG,KAAK,CAAC,yBAAyB,CAAC,IAAI,MAAM,CACpG,iBAAiB,EACjB,CAAC,CACJ,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC,MAAM,eAAe,CAAC,CAAA;AACrE,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,uBAAuB,GAAG,KAAK,CAAC,uBAAuB,CAAC,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAA;AAEtH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,UAAU,CAAC,CAAA;AAC3D,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS,kBAAkB,GAAG,KAAK,CAAC,kBAAkB,CAAC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAA;AAElG,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,MAAM,WAAW,CAAC,CAAA;AAC7D,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,mBAAmB,GAAG,KAAK,CAAC,kBAAkB,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;AAErG,MAAM,MAAM,cAAc,GAAG;IACzB,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,SAAS,CAAC,EAAE,uBAAuB,CAAA;IACnC,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB,KAAK,CAAC,EAAE,mBAAmB,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IACzB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,CAC/B;IACI,IAAI,CAAC,EAAE,aAAa,EAAE,CAAA;CACzB,GAAG,cAAc,CACrB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IAAC,IAAI,CAAC,EAAE,aAAa,EAAE,CAAA;CAAC,GAAG,aAAa,CAAC,CAAA;AAE5E,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;CAAC,GAAG,cAAc,CAAC,CAAA;AAExE,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CACrC;IACI,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;CACjB,GAAG,cAAc,CACrB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;CAAC,GAAG,aAAa,CAAC,CAAA;AAE7F,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;CAAC,GAAG,cAAc,CAAC,CAAA;AAE1F,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAA;CAAC,GAAG,cAAc,CAAC,CAAA;AAErF,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAA;CAAC,GAAG,cAAc,CAAC,CAAA;AAEvF,MAAM,MAAM,WAAW,GAAG;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;IACvB,KAAK,CAAC,EAAE,WAAW,EAAE,CAAA;IACrB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;IACzB,oBAAoB,CAAC,EAAE,uBAAuB,EAAE,CAAA;IAChD,eAAe,CAAC,EAAE,+BAA+B,EAAE,CAAA;IACnD,kBAAkB,CAAC,EAAE,wBAAwB,EAAE,CAAA;IAC/C,oBAAoB,CAAC,EAAE,0BAA0B,EAAE,CAAA;CACtD,CAAA;AAED,MAAM,MAAM,KAAK,GAAG,QAAQ,CACxB,WAAW,GAAG;IACV,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,cAAc,CAAA;CACzB,GAAG,WAAW,CAClB,CAAA;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,cAAc,IAAI,QAAQ,CAAC;IACnD,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACpC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAA;IACjC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAA;IAC9B,UAAU,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,CAAA;CAChD,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=substrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"substrate.js","sourceRoot":"","sources":["../../src/query/substrate.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@subsquid/portal-client",
3
+ "version": "0.0.0",
4
+ "description": "SQD Portal API",
5
+ "license": "GPL-3.0-or-later",
6
+ "repository": "git@github.com:subsquid/squid.git",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "main": "lib/client.js",
11
+ "files": [
12
+ "lib",
13
+ "src",
14
+ "!src/*.test.*",
15
+ "!lib/*.test.*"
16
+ ],
17
+ "dependencies": {
18
+ "@subsquid/util-internal": "^3.2.0"
19
+ },
20
+ "peerDependencies": {
21
+ "@subsquid/http-client": "^1.6.1"
22
+ },
23
+ "peerDependenciesMeta": {},
24
+ "devDependencies": {
25
+ "@subsquid/http-client": "^1.6.1",
26
+ "@types/node": "^18.18.14",
27
+ "typescript": "5.5.4"
28
+ },
29
+ "scripts": {
30
+ "build": "rm -rf lib && tsc"
31
+ }
32
+ }
@@ -0,0 +1,167 @@
1
+ import {BlockRef, createQuery, isForkException, PortalClient} from './client'
2
+
3
+ const portalUrls = {
4
+ evm: 'https://portal.sqd.dev/datasets/ethereum-mainnet',
5
+ solana: 'https://portal.sqd.dev/datasets/solana-mainnet',
6
+ }
7
+
8
+ const queries = {
9
+ evm: createQuery({
10
+ type: 'evm',
11
+ fromBlock: 23_000_000,
12
+ fields: {
13
+ block: {
14
+ number: true,
15
+ hash: true,
16
+ timestamp: true,
17
+ },
18
+ transaction: {
19
+ from: true,
20
+ to: true,
21
+ hash: true,
22
+ },
23
+ log: {
24
+ address: true,
25
+ topics: true,
26
+ data: true,
27
+ transactionHash: true,
28
+ logIndex: true,
29
+ transactionIndex: true,
30
+ },
31
+ stateDiff: {
32
+ kind: true,
33
+ next: true,
34
+ prev: true,
35
+ },
36
+ },
37
+ logs: [
38
+ {
39
+ address: ['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'],
40
+ topic0: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'],
41
+ },
42
+ ],
43
+ }),
44
+ solana: createQuery({
45
+ type: 'solana',
46
+ fromBlock: 358_600_000,
47
+ fields: {
48
+ block: {number: true, timestamp: true, hash: true, parentHash: true},
49
+ transaction: {signatures: true, err: true, transactionIndex: true},
50
+ instruction: {
51
+ programId: true,
52
+ accounts: true,
53
+ data: true,
54
+ isCommitted: true,
55
+ transactionIndex: true,
56
+ instructionAddress: true,
57
+ },
58
+ },
59
+ instructions: [
60
+ {
61
+ programId: ['whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc'],
62
+ d8: ['0xf8c69e91e17587c8'],
63
+ isCommitted: true,
64
+ innerInstructions: true,
65
+ },
66
+ ],
67
+ }),
68
+ }
69
+
70
+ async function main() {
71
+ const queryType = 'evm'
72
+ const query = queries[queryType]
73
+
74
+ let portal = new PortalClient({
75
+ url: portalUrls[queryType],
76
+ http: {
77
+ retryAttempts: Infinity,
78
+ },
79
+ minBytes: 50 * 1024 * 1024,
80
+ })
81
+
82
+ let coldHead: BlockRef | undefined = undefined
83
+ let hotHeads: BlockRef[] = []
84
+ while (true) {
85
+ const currentQuery = {...query}
86
+
87
+ let head = hotHeads[hotHeads.length - 1] ?? coldHead
88
+ if (head != null && head.number > currentQuery.fromBlock) {
89
+ currentQuery.fromBlock = head.number + 1
90
+ currentQuery.parentBlockHash = head.hash
91
+ }
92
+
93
+ try {
94
+ for await (let {blocks, finalizedHead} of portal.getStream(query)) {
95
+ if (head && blocks.length > 0 && head.number >= blocks[0].header.number) {
96
+ throw new Error('Data is not continuous')
97
+ }
98
+
99
+ let unfinalizedIndex = 0
100
+ if (finalizedHead) {
101
+ unfinalizedIndex = blocks.findIndex((b) => b.header.number > finalizedHead?.number)
102
+ }
103
+
104
+ // all new blocks are finalized
105
+ if (unfinalizedIndex < 0) {
106
+ const finalizedRef = blocks[blocks.length - 1].header
107
+ coldHead = {number: finalizedRef.number, hash: finalizedRef.hash}
108
+ // finalize all hot heads
109
+ hotHeads = []
110
+ } else {
111
+ const finalizedRef = finalizedHead ?? blocks[unfinalizedIndex - 1]?.header
112
+ coldHead = finalizedRef ?? coldHead
113
+
114
+ // finalize all hot heads that are older than the cold head
115
+ if (coldHead) {
116
+ let finalizeIndex = hotHeads.findIndex((h) => h.number > coldHead!.number)
117
+ hotHeads = finalizeIndex < 0 ? [] : hotHeads.slice(finalizeIndex)
118
+ }
119
+
120
+ // process unfinalized blocks
121
+ for (let i = unfinalizedIndex; i < blocks.length; i++) {
122
+ hotHeads.push({number: blocks[i].header.number, hash: blocks[i].header.hash})
123
+ }
124
+ }
125
+
126
+ head = hotHeads[hotHeads.length - 1] ?? coldHead
127
+ let portalHead = Math.max(head.number, finalizedHead?.number ?? -1)
128
+ console.log(`progress: ${head.number} / ${portalHead}` + `, blocks: ${blocks.length}`)
129
+ console.log(` \u001b[2mcold head: ${coldHead ? formatRef(coldHead) : 'N/A'}\u001b[0m`)
130
+ console.log(` \u001b[2mhot heads: ${hotHeads.map((h) => formatRef(h)).join(', ') || 'N/A'}\u001b[0m`)
131
+ }
132
+ break
133
+ } catch (e) {
134
+ if (!isForkException(e)) throw e
135
+
136
+ let chain = coldHead ? [coldHead, ...hotHeads] : hotHeads
137
+ let rollbackIndex = findRollbackIndex(chain, e.lastBlocks)
138
+ if (rollbackIndex === -1) throw new Error('Unable to process fork')
139
+
140
+ const rollbackHead = chain[rollbackIndex]
141
+ console.warn(`detected fork at block ${rollbackHead.number} (${e.head.number - rollbackHead.number} depth)`)
142
+
143
+ hotHeads = chain.slice(1, rollbackIndex + 1)
144
+ head = hotHeads[hotHeads.length - 1] ?? coldHead
145
+ }
146
+ }
147
+ }
148
+
149
+ function findRollbackIndex(chainA: BlockRef[], chainB: BlockRef[]) {
150
+ let i = 0
151
+ let j = 0
152
+ for (; i < chainA.length; i++) {
153
+ const blockA = chainA[i]
154
+ for (; j < chainB.length; j++) {
155
+ let blockB = chainB[j]
156
+ if (blockB.number > blockA.number) break
157
+ if (blockB.number === blockA.number && blockB.hash !== blockA.hash) return i - 1
158
+ }
159
+ }
160
+ return i - 1
161
+ }
162
+
163
+ function formatRef(ref: BlockRef) {
164
+ return `${ref.number}#${ref.hash.slice(2, 8)}`
165
+ }
166
+
167
+ main()