@voidifydao/sdk 1.0.0 → 2.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.
- package/README.md +105 -0
- package/dist/cli/config/init.js +1 -7
- package/dist/cli/config/loader.js +0 -1
- package/dist/cli/config/types.d.ts +0 -1
- package/dist/cli/deposit.js +45 -29
- package/dist/cli/helpers.js +0 -3
- package/dist/cli/progress.d.ts +5 -0
- package/dist/cli/progress.js +37 -0
- package/dist/cli/relayer.js +16 -7
- package/dist/cli/withdraw.js +20 -6
- package/dist/context.d.ts +1 -4
- package/dist/context.js +14 -12
- package/dist/idl/voidify/idl.d.ts +1 -1
- package/dist/idl/voidify/idl.json +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +0 -2
- package/dist/relayer/server/server.js +29 -13
- package/dist/relayer/server/switchboard.js +15 -6
- package/dist/relayer/types.d.ts +1 -0
- package/dist/substream/chain/index.d.ts +10 -3
- package/dist/substream/chain/index.js +14 -7
- package/dist/substream/chain/registry.d.ts +2 -2
- package/dist/substream/chain/utils.d.ts +2 -1
- package/dist/substream/chain/utils.js +35 -11
- package/dist/substream/client.d.ts +6 -1
- package/dist/substream/database/indexeddb.js +3 -0
- package/dist/substream/database/sqlite.d.ts +1 -0
- package/dist/substream/database/sqlite.js +6 -0
- package/dist/substream/modules/deposit.d.ts +2 -1
- package/dist/substream/modules/deposit.js +24 -20
- package/dist/substream/modules/relayer.d.ts +2 -1
- package/dist/substream/modules/relayer.js +39 -33
- package/dist/substream/runtime.d.ts +19 -4
- package/dist/substream/runtime.js +216 -16
- package/dist/substream/server/server.d.ts +2 -0
- package/dist/substream/server/server.js +42 -8
- package/dist/substream/types.d.ts +21 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +1 -1
- package/dist/voidify/deposit.d.ts +2 -0
- package/dist/voidify/deposit.js +1 -1
- package/dist/voidify/program.d.ts +0 -4
- package/dist/voidify/program.js +0 -10
- package/dist/voidify/relayer/list.d.ts +2 -0
- package/dist/voidify/relayer/list.js +1 -1
- package/dist/voidify/withdraw.d.ts +7 -2
- package/dist/voidify/withdraw.js +68 -10
- package/package.json +5 -4
- package/dist/idl/voidify-staking/idl.d.ts +0 -93
- package/dist/idl/voidify-staking/idl.js +0 -1
- package/dist/idl/voidify-staking/idl.json +0 -87
- package/dist/staking/commands.d.ts +0 -3
- package/dist/staking/commands.js +0 -13
- package/dist/staking/index.d.ts +0 -2
- package/dist/staking/index.js +0 -2
- package/dist/staking/program.d.ts +0 -18
- package/dist/staking/program.js +0 -40
- package/dist/types/errors.d.ts +0 -1
- package/dist/types/errors.js +0 -16
|
@@ -2,12 +2,19 @@ import * as sb from "@switchboard-xyz/on-demand";
|
|
|
2
2
|
import { CrossbarClient } from "@switchboard-xyz/common";
|
|
3
3
|
import { signAndSend } from "../../utils/tx.js";
|
|
4
4
|
import { VoidifyProgram } from "../../voidify/program.js";
|
|
5
|
+
import { relayerLogger as logger } from "../../utils/logger.js";
|
|
5
6
|
async function getQuoteSlot(ctx, quotePDA) {
|
|
7
|
+
const SWITCHBOARD_QUOTE_ACCOUNT_PAYLOAD_OFFSET = 42;
|
|
8
|
+
const SWITCHBOARD_QUOTE_TAIL_DISCRIMINATOR = "SBOD";
|
|
6
9
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const account = await ctx.connection.getAccountInfo(quotePDA, "confirmed");
|
|
11
|
+
if (!account)
|
|
12
|
+
return 0;
|
|
13
|
+
const quote = sb.OracleQuote.decode(Buffer.from(account.data).subarray(SWITCHBOARD_QUOTE_ACCOUNT_PAYLOAD_OFFSET));
|
|
14
|
+
if (quote.tailDiscriminator !== SWITCHBOARD_QUOTE_TAIL_DISCRIMINATOR) {
|
|
15
|
+
throw new Error(`Invalid Switchboard quote discriminator: ${quote.tailDiscriminator}`);
|
|
16
|
+
}
|
|
17
|
+
return quote.slot;
|
|
11
18
|
}
|
|
12
19
|
catch (error) {
|
|
13
20
|
throw new Error("Failed to fetch quote slot from chain", {
|
|
@@ -18,13 +25,15 @@ async function getQuoteSlot(ctx, quotePDA) {
|
|
|
18
25
|
export async function updateQuote(ctx, feedID) {
|
|
19
26
|
const voidifyProgram = new VoidifyProgram(ctx.connection, ctx.programId);
|
|
20
27
|
const oracleConfig = await voidifyProgram.program.account.oracleConfig.fetch(voidifyProgram.oracleConfig());
|
|
21
|
-
const maxPriceAgeSlots = (BigInt(oracleConfig.maxPriceAgeSecs.toString()) *
|
|
28
|
+
const maxPriceAgeSlots = (BigInt(oracleConfig.maxPriceAgeSecs.toString()) * 5n) / 2n;
|
|
22
29
|
const queue = await sb.Queue.loadDefault(await sb.AnchorUtils.loadProgramFromConnection(ctx.connection));
|
|
23
30
|
const crossbar = CrossbarClient.default();
|
|
24
31
|
const currentSlot = await ctx.connection.getSlot("confirmed");
|
|
25
32
|
const [quotePDA] = sb.OracleQuote.getCanonicalPubkey(queue.pubkey, [feedID]);
|
|
26
33
|
const quoteSlot = await getQuoteSlot(ctx, quotePDA);
|
|
27
|
-
|
|
34
|
+
const currentAgeSlots = BigInt(currentSlot - quoteSlot);
|
|
35
|
+
if (currentAgeSlots >= maxPriceAgeSlots) {
|
|
36
|
+
logger.info({ currentAgeSlots, maxPriceAgeSlots }, "Updating switchboard oracle");
|
|
28
37
|
const ixs = await queue.fetchManagedUpdateIxs(crossbar, [feedID], {
|
|
29
38
|
variableOverrides: {},
|
|
30
39
|
instructionIdx: 0,
|
package/dist/relayer/types.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { PublicKey } from "@solana/web3.js";
|
|
2
|
-
import type { ChainEventRecord, EventProjection, EventScope, EventStore } from "../../substream/types.js";
|
|
2
|
+
import type { ChainEventRecord, EventProjection, EventScope, EventStore, SyncProgress, SyncStatusReporter } from "../../substream/types.js";
|
|
3
|
+
export interface ChainSyncOptions {
|
|
4
|
+
reporter?: SyncStatusReporter;
|
|
5
|
+
}
|
|
6
|
+
export interface ChainSyncRun {
|
|
7
|
+
updateProgress(progress: SyncProgress): void;
|
|
8
|
+
}
|
|
3
9
|
export interface EventStreamSpec {
|
|
4
10
|
id: string;
|
|
5
11
|
scope: EventScope;
|
|
6
12
|
address: PublicKey;
|
|
7
13
|
getChainLastIndex(): Promise<bigint>;
|
|
8
|
-
|
|
14
|
+
collectBatches(lastSignature?: string, run?: ChainSyncRun): AsyncIterable<ChainEventRecord[]>;
|
|
9
15
|
}
|
|
10
16
|
export declare class ProjectionRegistry {
|
|
11
17
|
private projections;
|
|
@@ -18,7 +24,8 @@ export declare class ChainEventSyncer {
|
|
|
18
24
|
private projections;
|
|
19
25
|
constructor(events: EventStore, projections?: ProjectionRegistry);
|
|
20
26
|
registerProjection(projection: EventProjection): void;
|
|
21
|
-
checkAndSync(spec: EventStreamSpec): Promise<boolean>;
|
|
27
|
+
checkAndSync(spec: EventStreamSpec, run?: ChainSyncRun): Promise<boolean>;
|
|
22
28
|
applyLiveEvent(spec: EventStreamSpec, event: ChainEventRecord): Promise<void>;
|
|
23
29
|
private sync;
|
|
30
|
+
private applyRecords;
|
|
24
31
|
}
|
|
@@ -31,7 +31,7 @@ export class ChainEventSyncer {
|
|
|
31
31
|
registerProjection(projection) {
|
|
32
32
|
this.projections.register(projection);
|
|
33
33
|
}
|
|
34
|
-
async checkAndSync(spec) {
|
|
34
|
+
async checkAndSync(spec, run) {
|
|
35
35
|
const cursor = await this.events.getCursor(spec.scope);
|
|
36
36
|
const localIndex = cursor?.lastIndex ?? -1n;
|
|
37
37
|
let chainLastIndex;
|
|
@@ -40,12 +40,12 @@ export class ChainEventSyncer {
|
|
|
40
40
|
}
|
|
41
41
|
catch (error) {
|
|
42
42
|
substreamLogger.warn({ err: error, streamId: spec.id }, "substream alignment check failed");
|
|
43
|
-
await this.sync(spec);
|
|
43
|
+
await this.sync(spec, run);
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
46
|
if (localIndex >= chainLastIndex)
|
|
47
47
|
return true;
|
|
48
|
-
await this.sync(spec);
|
|
48
|
+
await this.sync(spec, run);
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
async applyLiveEvent(spec, event) {
|
|
@@ -58,11 +58,17 @@ export class ChainEventSyncer {
|
|
|
58
58
|
await this.projections.apply([event]);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
async sync(spec) {
|
|
61
|
+
async sync(spec, run) {
|
|
62
62
|
const cursor = await this.events.getCursor(spec.scope);
|
|
63
|
-
const records
|
|
63
|
+
for await (const records of spec.collectBatches(cursor?.lastSignature ?? undefined, run)) {
|
|
64
|
+
const shouldContinue = await this.applyRecords(spec, records);
|
|
65
|
+
if (!shouldContinue)
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async applyRecords(spec, records) {
|
|
64
70
|
if (records.length === 0)
|
|
65
|
-
return;
|
|
71
|
+
return true;
|
|
66
72
|
const outcome = await this.events.applyBatch(spec.scope, records);
|
|
67
73
|
if (outcome.kind === "gap") {
|
|
68
74
|
substreamLogger.warn({
|
|
@@ -72,8 +78,9 @@ export class ChainEventSyncer {
|
|
|
72
78
|
expected: outcome.expected.toString(),
|
|
73
79
|
got: outcome.got.toString(),
|
|
74
80
|
}, "chain event batch apply found a gap; RPC history window may be exhausted");
|
|
75
|
-
return;
|
|
81
|
+
return false;
|
|
76
82
|
}
|
|
77
83
|
await this.projections.apply(records);
|
|
84
|
+
return true;
|
|
78
85
|
}
|
|
79
86
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context } from "../../context.js";
|
|
2
2
|
import type { ChainEventRecord, EventProjection, EventScope, EventScopeType, EventStore, ProjectionStore } from "../../substream/types.js";
|
|
3
3
|
import type { VoidifyProgram } from "../../voidify/program.js";
|
|
4
|
-
import type { EventStreamSpec } from "./index.js";
|
|
4
|
+
import type { ChainSyncOptions, EventStreamSpec } from "./index.js";
|
|
5
5
|
export interface LiveEventContext {
|
|
6
6
|
ctx: Context;
|
|
7
7
|
voidify: VoidifyProgram;
|
|
@@ -18,7 +18,7 @@ export interface SubstreamModuleRuntime {
|
|
|
18
18
|
voidify: VoidifyProgram;
|
|
19
19
|
events: EventStore;
|
|
20
20
|
projections: ProjectionStore;
|
|
21
|
-
sync(scope: EventScope): Promise<void>;
|
|
21
|
+
sync(scope: EventScope, options?: ChainSyncOptions): Promise<void>;
|
|
22
22
|
rebuildProjection(scope: EventScope, projectionId: string): Promise<void>;
|
|
23
23
|
}
|
|
24
24
|
export interface SubstreamModule {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Connection, ConfirmedSignatureInfo, PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { SyncProgress } from "../../substream/types.js";
|
|
2
3
|
export declare function fetchSignaturesForAddress(connection: Connection, address: PublicKey, lastSignature?: string, batchSize?: number): Promise<ConfirmedSignatureInfo[]>;
|
|
3
4
|
export interface TransactionEvent {
|
|
4
5
|
signature: string;
|
|
@@ -6,4 +7,4 @@ export interface TransactionEvent {
|
|
|
6
7
|
slot: number | null;
|
|
7
8
|
blockTime: number | null;
|
|
8
9
|
}
|
|
9
|
-
export declare function
|
|
10
|
+
export declare function syncTransactionBatches(connection: Connection, address: PublicKey, lastSignature?: string, signatureBatchSize?: number, transactionBatchSize?: number, updateProgress?: (progress: SyncProgress) => void): AsyncGenerator<TransactionEvent[]>;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import pRetry from "p-retry";
|
|
2
|
+
const retryConnection = (operation) => pRetry(operation, {
|
|
3
|
+
minTimeout: 1000,
|
|
4
|
+
retries: 5,
|
|
5
|
+
});
|
|
1
6
|
export async function fetchSignaturesForAddress(connection, address, lastSignature, batchSize = 1000) {
|
|
2
7
|
const signatures = [];
|
|
3
8
|
let beforeSignature = undefined;
|
|
4
9
|
while (true) {
|
|
5
|
-
const batch = await connection.getSignaturesForAddress(address, {
|
|
10
|
+
const batch = await retryConnection(() => connection.getSignaturesForAddress(address, {
|
|
6
11
|
limit: batchSize,
|
|
7
12
|
before: beforeSignature,
|
|
8
13
|
until: lastSignature,
|
|
9
|
-
});
|
|
14
|
+
}));
|
|
10
15
|
if (batch.length === 0)
|
|
11
16
|
break;
|
|
12
17
|
signatures.push(...batch);
|
|
@@ -17,25 +22,44 @@ export async function fetchSignaturesForAddress(connection, address, lastSignatu
|
|
|
17
22
|
signatures.reverse();
|
|
18
23
|
return signatures;
|
|
19
24
|
}
|
|
20
|
-
export async function
|
|
21
|
-
const signatures = await fetchSignaturesForAddress(connection, address, lastSignature,
|
|
25
|
+
export async function* syncTransactionBatches(connection, address, lastSignature, signatureBatchSize = 1000, transactionBatchSize = 50, updateProgress) {
|
|
26
|
+
const signatures = await fetchSignaturesForAddress(connection, address, lastSignature, signatureBatchSize);
|
|
22
27
|
if (signatures.length === 0) {
|
|
23
|
-
return
|
|
28
|
+
return;
|
|
24
29
|
}
|
|
25
30
|
const events = [];
|
|
31
|
+
let current = 0;
|
|
26
32
|
for (const sigInfo of signatures) {
|
|
27
|
-
const tx = await
|
|
28
|
-
|
|
33
|
+
const tx = await retryConnection(async () => {
|
|
34
|
+
const transaction = await connection.getTransaction(sigInfo.signature, {
|
|
35
|
+
maxSupportedTransactionVersion: 0,
|
|
36
|
+
});
|
|
37
|
+
if (!transaction?.meta) {
|
|
38
|
+
throw new Error(`transaction unavailable: ${sigInfo.signature}`);
|
|
39
|
+
}
|
|
40
|
+
return transaction;
|
|
29
41
|
});
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
const meta = tx.meta;
|
|
43
|
+
if (!meta) {
|
|
44
|
+
throw new Error(`transaction unavailable: ${sigInfo.signature}`);
|
|
32
45
|
}
|
|
33
46
|
events.push({
|
|
34
47
|
signature: sigInfo.signature,
|
|
35
|
-
logs:
|
|
48
|
+
logs: meta.logMessages || [],
|
|
36
49
|
slot: tx.slot ?? null,
|
|
37
50
|
blockTime: tx.blockTime ?? null,
|
|
38
51
|
});
|
|
52
|
+
current += 1;
|
|
53
|
+
updateProgress?.({
|
|
54
|
+
current,
|
|
55
|
+
total: signatures.length,
|
|
56
|
+
signature: sigInfo.signature,
|
|
57
|
+
});
|
|
58
|
+
if (events.length >= transactionBatchSize) {
|
|
59
|
+
yield events.splice(0, events.length);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (events.length > 0) {
|
|
63
|
+
yield events.splice(0, events.length);
|
|
39
64
|
}
|
|
40
|
-
return events;
|
|
41
65
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Context, SubstreamMode } from "../context.js";
|
|
2
|
-
import type { ChainEventWire, SubstreamRepos } from "../substream/types.js";
|
|
2
|
+
import type { ChainEventWire, SubstreamRepos, SyncStatus } from "../substream/types.js";
|
|
3
3
|
import type { BuiltinSubstreamModuleApis } from "../substream/modules/index.js";
|
|
4
4
|
export interface SubstreamCliConfig {
|
|
5
5
|
timeout?: number;
|
|
@@ -7,6 +7,8 @@ export interface SubstreamCliConfig {
|
|
|
7
7
|
healthCacheMs?: number;
|
|
8
8
|
}
|
|
9
9
|
export interface CursorWire {
|
|
10
|
+
scopeType: string;
|
|
11
|
+
scopeKey: string;
|
|
10
12
|
lastIndex: string | null;
|
|
11
13
|
lastSignature: string | null;
|
|
12
14
|
lastSyncAt: number;
|
|
@@ -15,6 +17,9 @@ export interface EventsApiResponse {
|
|
|
15
17
|
events: ChainEventWire[];
|
|
16
18
|
total: number;
|
|
17
19
|
cursor: CursorWire | null;
|
|
20
|
+
syncStatus: (Omit<SyncStatus, "cursor"> & {
|
|
21
|
+
cursor: CursorWire | null;
|
|
22
|
+
}) | null;
|
|
18
23
|
}
|
|
19
24
|
export declare class SubstreamCliClient {
|
|
20
25
|
private readonly runtime;
|
|
@@ -198,6 +198,9 @@ class IndexedDBProjectionStore {
|
|
|
198
198
|
async put(record) {
|
|
199
199
|
await this.getDb().projection_states.put(projectionStateToRow(record));
|
|
200
200
|
}
|
|
201
|
+
async delete(projectionId, key) {
|
|
202
|
+
await this.getDb().projection_states.delete([projectionId, key]);
|
|
203
|
+
}
|
|
201
204
|
async list(projectionId) {
|
|
202
205
|
const rows = await this.getDb()
|
|
203
206
|
.projection_states.where("projection_id")
|
|
@@ -20,6 +20,7 @@ export declare class SQLiteProjectionStore implements ProjectionStore {
|
|
|
20
20
|
initialize(): Promise<void>;
|
|
21
21
|
get(projectionId: string, key: string): Promise<ProjectionStateRecord | null>;
|
|
22
22
|
put(record: ProjectionStateRecord): Promise<void>;
|
|
23
|
+
delete(projectionId: string, key: string): Promise<void>;
|
|
23
24
|
list(projectionId: string): Promise<ProjectionStateRecord[]>;
|
|
24
25
|
clear(projectionId: string): Promise<void>;
|
|
25
26
|
}
|
|
@@ -241,6 +241,12 @@ export class SQLiteProjectionStore {
|
|
|
241
241
|
? null
|
|
242
242
|
: eventIndexToNumber(record.lastEventIndex));
|
|
243
243
|
}
|
|
244
|
+
async delete(projectionId, key) {
|
|
245
|
+
this.db
|
|
246
|
+
.prepare(`DELETE FROM projection_states
|
|
247
|
+
WHERE projection_id = ? AND entity_key = ?`)
|
|
248
|
+
.run(projectionId, key);
|
|
249
|
+
}
|
|
244
250
|
async list(projectionId) {
|
|
245
251
|
const rows = this.db
|
|
246
252
|
.prepare(`SELECT * FROM projection_states
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { DepositRecord, EventCursor, EventScope } from "../../substream/types.js";
|
|
2
2
|
import type { SubstreamModule } from "../../substream/chain/registry.js";
|
|
3
|
+
import type { ChainSyncOptions } from "../../substream/chain/index.js";
|
|
3
4
|
export interface DepositModuleApi {
|
|
4
|
-
sync(denomination: bigint): Promise<void>;
|
|
5
|
+
sync(denomination: bigint, options?: ChainSyncOptions): Promise<void>;
|
|
5
6
|
list(denomination: bigint, opts?: {
|
|
6
7
|
offset?: number;
|
|
7
8
|
limit?: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { toBigInt } from "../../utils/anchor-events.js";
|
|
2
2
|
import { bytesToBigInt, bytesToHex } from "../../utils/bytes.js";
|
|
3
3
|
import { parseEventsFromLogs } from "../../utils/anchor-events.js";
|
|
4
|
-
import {
|
|
4
|
+
import { syncTransactionBatches, } from "../../substream/chain/utils.js";
|
|
5
5
|
export function depositScope(denomination) {
|
|
6
6
|
return { scopeType: "deposit", scopeKey: denomination.toString() };
|
|
7
7
|
}
|
|
@@ -35,8 +35,8 @@ export const depositModule = {
|
|
|
35
35
|
],
|
|
36
36
|
createClientApi(runtime) {
|
|
37
37
|
return {
|
|
38
|
-
async sync(denomination) {
|
|
39
|
-
await runtime.sync(depositScope(denomination));
|
|
38
|
+
async sync(denomination, options) {
|
|
39
|
+
await runtime.sync(depositScope(denomination), options);
|
|
40
40
|
},
|
|
41
41
|
async list(denomination, opts) {
|
|
42
42
|
const rows = await runtime.events.list(depositScope(denomination), opts);
|
|
@@ -65,27 +65,31 @@ function createDepositStream(ctx, voidify, denomination) {
|
|
|
65
65
|
const poolAccount = await voidify.program.account.pool.fetch(address);
|
|
66
66
|
return BigInt(poolAccount.merkleTree.nextIndex) - 1n;
|
|
67
67
|
},
|
|
68
|
-
|
|
69
|
-
const transactions
|
|
70
|
-
|
|
71
|
-
for (const tx of transactions) {
|
|
72
|
-
const decoded = parseEventsFromLogs(tx.logs, voidify.program.coder.events);
|
|
73
|
-
for (const item of decoded) {
|
|
74
|
-
if (item.name !== "depositEvent")
|
|
75
|
-
continue;
|
|
76
|
-
records.push(depositEventToChainEvent({
|
|
77
|
-
event: item.data,
|
|
78
|
-
signature: tx.signature,
|
|
79
|
-
slot: tx.slot,
|
|
80
|
-
blockTime: tx.blockTime,
|
|
81
|
-
address,
|
|
82
|
-
}));
|
|
83
|
-
}
|
|
68
|
+
collectBatches: async function* (lastSignature, run) {
|
|
69
|
+
for await (const transactions of syncTransactionBatches(ctx.connection, address, lastSignature, undefined, undefined, run?.updateProgress)) {
|
|
70
|
+
yield collectDepositRecords(transactions, voidify, address);
|
|
84
71
|
}
|
|
85
|
-
return records;
|
|
86
72
|
},
|
|
87
73
|
};
|
|
88
74
|
}
|
|
75
|
+
function collectDepositRecords(transactions, voidify, address) {
|
|
76
|
+
const records = [];
|
|
77
|
+
for (const tx of transactions) {
|
|
78
|
+
const decoded = parseEventsFromLogs(tx.logs, voidify.program.coder.events);
|
|
79
|
+
for (const item of decoded) {
|
|
80
|
+
if (item.name !== "depositEvent")
|
|
81
|
+
continue;
|
|
82
|
+
records.push(depositEventToChainEvent({
|
|
83
|
+
event: item.data,
|
|
84
|
+
signature: tx.signature,
|
|
85
|
+
slot: tx.slot,
|
|
86
|
+
blockTime: tx.blockTime,
|
|
87
|
+
address,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return records;
|
|
92
|
+
}
|
|
89
93
|
function depositEventToChainEvent(args) {
|
|
90
94
|
const event = args.event;
|
|
91
95
|
const denomination = toBigInt(event.denomination);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { EventScope, RelayerRecord } from "../../substream/types.js";
|
|
2
2
|
import type { SubstreamModule } from "../../substream/chain/registry.js";
|
|
3
|
+
import type { ChainSyncOptions } from "../../substream/chain/index.js";
|
|
3
4
|
export interface RelayerModuleApi {
|
|
4
|
-
sync(): Promise<void>;
|
|
5
|
+
sync(options?: ChainSyncOptions): Promise<void>;
|
|
5
6
|
list(): Promise<RelayerRecord[]>;
|
|
6
7
|
get(pubkey: string): Promise<RelayerRecord | null>;
|
|
7
8
|
rebuild(): Promise<void>;
|
|
@@ -2,7 +2,7 @@ import { PublicKey } from "@solana/web3.js";
|
|
|
2
2
|
import { normalizePayload } from "../../substream/chain/events.js";
|
|
3
3
|
import { parseEventsFromLogs, } from "../../utils/anchor-events.js";
|
|
4
4
|
import { substreamLogger } from "../../utils/logger.js";
|
|
5
|
-
import {
|
|
5
|
+
import { syncTransactionBatches, } from "../../substream/chain/utils.js";
|
|
6
6
|
export const RELAYER_SCOPE = {
|
|
7
7
|
scopeType: "relayer",
|
|
8
8
|
scopeKey: "global",
|
|
@@ -32,7 +32,7 @@ export const relayerModule = {
|
|
|
32
32
|
const decoded = { name: eventName, data: event };
|
|
33
33
|
const pubkey = decoded.data.relayer.toBase58();
|
|
34
34
|
const cfg = decoded.name === "relayerRegisteredEvent"
|
|
35
|
-
? await fetchRelayerConfig(voidify, pubkey)
|
|
35
|
+
? await fetchRelayerConfig(voidify, pubkey, false)
|
|
36
36
|
: null;
|
|
37
37
|
return relayerEventToChainEvent({
|
|
38
38
|
event: decoded,
|
|
@@ -48,8 +48,8 @@ export const relayerModule = {
|
|
|
48
48
|
},
|
|
49
49
|
createClientApi(runtime) {
|
|
50
50
|
return {
|
|
51
|
-
async sync() {
|
|
52
|
-
await runtime.sync(RELAYER_SCOPE);
|
|
51
|
+
async sync(options) {
|
|
52
|
+
await runtime.sync(RELAYER_SCOPE, options);
|
|
53
53
|
},
|
|
54
54
|
async list() {
|
|
55
55
|
const rows = await runtime.projections.list("relayer");
|
|
@@ -75,33 +75,37 @@ function createRelayerStream(ctx, voidify) {
|
|
|
75
75
|
const counter = await voidify.program.account.relayerEventCounter.fetch(counterPda);
|
|
76
76
|
return BigInt(counter.count.toString()) - 1n;
|
|
77
77
|
},
|
|
78
|
-
|
|
79
|
-
const transactions
|
|
80
|
-
|
|
81
|
-
for (const tx of transactions) {
|
|
82
|
-
const decoded = parseEventsFromLogs(tx.logs, voidify.program.coder.events);
|
|
83
|
-
for (const item of decoded) {
|
|
84
|
-
if (!isIndexedRelayerEventName(item.name))
|
|
85
|
-
continue;
|
|
86
|
-
const event = item;
|
|
87
|
-
const pubkey = event.data.relayer.toBase58();
|
|
88
|
-
const cfg = event.name === "relayerRegisteredEvent"
|
|
89
|
-
? await fetchRelayerConfig(voidify, pubkey)
|
|
90
|
-
: null;
|
|
91
|
-
records.push(relayerEventToChainEvent({
|
|
92
|
-
event,
|
|
93
|
-
signature: tx.signature,
|
|
94
|
-
slot: tx.slot,
|
|
95
|
-
blockTime: tx.blockTime,
|
|
96
|
-
address: ctx.programId,
|
|
97
|
-
config: cfg,
|
|
98
|
-
}));
|
|
99
|
-
}
|
|
78
|
+
collectBatches: async function* (lastSignature, run) {
|
|
79
|
+
for await (const transactions of syncTransactionBatches(ctx.connection, ctx.programId, lastSignature, undefined, undefined, run?.updateProgress)) {
|
|
80
|
+
yield await collectRelayerRecords(transactions, voidify, ctx.programId);
|
|
100
81
|
}
|
|
101
|
-
return records;
|
|
102
82
|
},
|
|
103
83
|
};
|
|
104
84
|
}
|
|
85
|
+
async function collectRelayerRecords(transactions, voidify, address) {
|
|
86
|
+
const records = [];
|
|
87
|
+
for (const tx of transactions) {
|
|
88
|
+
const decoded = parseEventsFromLogs(tx.logs, voidify.program.coder.events);
|
|
89
|
+
for (const item of decoded) {
|
|
90
|
+
if (!isIndexedRelayerEventName(item.name))
|
|
91
|
+
continue;
|
|
92
|
+
const event = item;
|
|
93
|
+
const pubkey = event.data.relayer.toBase58();
|
|
94
|
+
const cfg = event.name === "relayerRegisteredEvent"
|
|
95
|
+
? await fetchRelayerConfig(voidify, pubkey, true)
|
|
96
|
+
: null;
|
|
97
|
+
records.push(relayerEventToChainEvent({
|
|
98
|
+
event,
|
|
99
|
+
signature: tx.signature,
|
|
100
|
+
slot: tx.slot,
|
|
101
|
+
blockTime: tx.blockTime,
|
|
102
|
+
address,
|
|
103
|
+
config: cfg,
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return records;
|
|
108
|
+
}
|
|
105
109
|
function relayerEventToChainEvent(args) {
|
|
106
110
|
const payload = normalizePayload(args.event.data);
|
|
107
111
|
if (args.event.name === "relayerRegisteredEvent") {
|
|
@@ -122,14 +126,16 @@ function relayerEventToChainEvent(args) {
|
|
|
122
126
|
function isIndexedRelayerEventName(name) {
|
|
123
127
|
return INDEXED_RELAYER_EVENT_NAMES.has(name);
|
|
124
128
|
}
|
|
125
|
-
async function fetchRelayerConfig(voidify, relayerPubkey) {
|
|
129
|
+
async function fetchRelayerConfig(voidify, relayerPubkey, quietNotFound = false) {
|
|
126
130
|
try {
|
|
127
131
|
const cfgKey = voidify.relayerConfig(new PublicKey(relayerPubkey));
|
|
128
132
|
const cfg = await voidify.program.account.relayerConfig.fetch(cfgKey);
|
|
129
133
|
return { name: cfg.name, url: cfg.url, feeBps: cfg.feeBps };
|
|
130
134
|
}
|
|
131
135
|
catch (err) {
|
|
132
|
-
|
|
136
|
+
if (!quietNotFound) {
|
|
137
|
+
substreamLogger.warn({ err, relayerPubkey }, "fetchRelayerConfig failed");
|
|
138
|
+
}
|
|
133
139
|
return null;
|
|
134
140
|
}
|
|
135
141
|
}
|
|
@@ -151,8 +157,10 @@ class RelayerProjection {
|
|
|
151
157
|
const existing = await this.store.get(this.id, pubkey);
|
|
152
158
|
const current = existing ? valueToRelayerState(existing.value) : null;
|
|
153
159
|
const next = reduceRelayerEvent(current, event);
|
|
154
|
-
if (!next)
|
|
160
|
+
if (!next) {
|
|
161
|
+
await this.store.delete(this.id, pubkey);
|
|
155
162
|
continue;
|
|
163
|
+
}
|
|
156
164
|
await this.store.put({
|
|
157
165
|
projectionId: this.id,
|
|
158
166
|
key: pubkey,
|
|
@@ -202,9 +210,7 @@ function reduceRelayerEvent(existing, event) {
|
|
|
202
210
|
}
|
|
203
211
|
case "relayerSlashedEvent":
|
|
204
212
|
case "relayerUnregisteredEvent": {
|
|
205
|
-
|
|
206
|
-
record.isActive = false;
|
|
207
|
-
break;
|
|
213
|
+
return null;
|
|
208
214
|
}
|
|
209
215
|
case "relayerUpdatedEvent": {
|
|
210
216
|
if (event.payload.feeBps !== null && event.payload.feeBps !== undefined) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Context, SubstreamMode } from "../context.js";
|
|
2
|
-
import type { EventScope, EventStore, ProjectionStore, SubstreamStores } from "../substream/types.js";
|
|
3
|
-
import { ChainEventSyncer, ProjectionRegistry } from "../substream/chain/index.js";
|
|
2
|
+
import type { EventScope, EventStore, ProjectionStore, SyncStatus, SubstreamStores } from "../substream/types.js";
|
|
3
|
+
import { type ChainSyncOptions, ChainEventSyncer, ProjectionRegistry } from "../substream/chain/index.js";
|
|
4
4
|
import { SubstreamModuleRegistry, type SubstreamModule, type SubstreamModuleRuntime } from "../substream/chain/registry.js";
|
|
5
5
|
import { VoidifyProgram } from "../voidify/program.js";
|
|
6
6
|
export interface SubstreamRuntimeConfig {
|
|
@@ -19,20 +19,35 @@ export declare class SubstreamRuntime implements SubstreamModuleRuntime {
|
|
|
19
19
|
private readonly mode;
|
|
20
20
|
private readonly timeout;
|
|
21
21
|
private readonly healthCacheMs;
|
|
22
|
+
private readonly syncInFlight;
|
|
23
|
+
private readonly syncStatuses;
|
|
22
24
|
private healthState;
|
|
23
25
|
private initialized;
|
|
24
26
|
constructor(ctx: Context, stores: SubstreamStores, config?: SubstreamRuntimeConfig, modules?: readonly SubstreamModule[]);
|
|
25
27
|
initialize(): Promise<void>;
|
|
26
28
|
module(id: string): unknown;
|
|
27
|
-
sync(scope: EventScope): Promise<void>;
|
|
28
|
-
syncLocal(scope: EventScope): Promise<void>;
|
|
29
|
+
sync(scope: EventScope, options?: ChainSyncOptions): Promise<void>;
|
|
30
|
+
syncLocal(scope: EventScope, options?: ChainSyncOptions): Promise<void>;
|
|
31
|
+
syncLocalInBackground(scope: EventScope): SyncStatus;
|
|
29
32
|
applyLiveEvent(eventScope: EventScope): Promise<void>;
|
|
30
33
|
applyLiveRecord(scope: EventScope, record: Parameters<ChainEventSyncer["applyLiveEvent"]>[1]): Promise<void>;
|
|
31
34
|
rebuildProjection(scope: EventScope, projectionId: string): Promise<void>;
|
|
35
|
+
getSyncStatus(scope: EventScope, mode?: "local" | "remote"): SyncStatus;
|
|
32
36
|
private get baseUrl();
|
|
33
37
|
private resolveMode;
|
|
34
38
|
private healthCheck;
|
|
35
39
|
private syncRemote;
|
|
40
|
+
private applyRemoteEvents;
|
|
41
|
+
private runScopedSync;
|
|
42
|
+
private syncKey;
|
|
43
|
+
private ensureSyncStatus;
|
|
44
|
+
private setSyncStatus;
|
|
45
|
+
private updateSyncStatus;
|
|
46
|
+
private waitForRemoteSync;
|
|
47
|
+
private sleep;
|
|
36
48
|
private fetchRemoteEvents;
|
|
49
|
+
private remoteEventsUrl;
|
|
50
|
+
private fetchWithTimeout;
|
|
51
|
+
private fetchRemoteSyncStatus;
|
|
37
52
|
}
|
|
38
53
|
export declare function createSubstreamRuntime(ctx: Context, stores: SubstreamStores, config?: SubstreamRuntimeConfig, modules?: readonly SubstreamModule[]): SubstreamRuntime;
|