@voidifydao/sdk 1.0.0 → 2.0.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.
Files changed (59) hide show
  1. package/README.md +0 -0
  2. package/dist/cli/config/init.js +1 -7
  3. package/dist/cli/config/loader.js +0 -1
  4. package/dist/cli/config/types.d.ts +0 -1
  5. package/dist/cli/deposit.js +45 -29
  6. package/dist/cli/helpers.js +0 -3
  7. package/dist/cli/progress.d.ts +5 -0
  8. package/dist/cli/progress.js +37 -0
  9. package/dist/cli/relayer.js +16 -7
  10. package/dist/cli/withdraw.js +20 -6
  11. package/dist/context.d.ts +1 -4
  12. package/dist/context.js +14 -12
  13. package/dist/idl/voidify/idl.d.ts +1 -1
  14. package/dist/idl/voidify/idl.json +1 -1
  15. package/dist/index.d.ts +2 -3
  16. package/dist/index.js +0 -2
  17. package/dist/relayer/server/server.js +29 -13
  18. package/dist/relayer/server/switchboard.js +15 -6
  19. package/dist/relayer/types.d.ts +1 -0
  20. package/dist/substream/chain/index.d.ts +10 -3
  21. package/dist/substream/chain/index.js +14 -7
  22. package/dist/substream/chain/registry.d.ts +2 -2
  23. package/dist/substream/chain/utils.d.ts +2 -1
  24. package/dist/substream/chain/utils.js +35 -11
  25. package/dist/substream/client.d.ts +6 -1
  26. package/dist/substream/database/indexeddb.js +3 -0
  27. package/dist/substream/database/sqlite.d.ts +1 -0
  28. package/dist/substream/database/sqlite.js +6 -0
  29. package/dist/substream/modules/deposit.d.ts +2 -1
  30. package/dist/substream/modules/deposit.js +24 -20
  31. package/dist/substream/modules/relayer.d.ts +2 -1
  32. package/dist/substream/modules/relayer.js +39 -33
  33. package/dist/substream/runtime.d.ts +19 -4
  34. package/dist/substream/runtime.js +216 -16
  35. package/dist/substream/server/server.d.ts +2 -0
  36. package/dist/substream/server/server.js +42 -8
  37. package/dist/substream/types.d.ts +21 -0
  38. package/dist/types/index.d.ts +0 -1
  39. package/dist/types/index.js +1 -1
  40. package/dist/voidify/deposit.d.ts +2 -0
  41. package/dist/voidify/deposit.js +1 -1
  42. package/dist/voidify/program.d.ts +0 -4
  43. package/dist/voidify/program.js +0 -10
  44. package/dist/voidify/relayer/list.d.ts +2 -0
  45. package/dist/voidify/relayer/list.js +1 -1
  46. package/dist/voidify/withdraw.d.ts +7 -2
  47. package/dist/voidify/withdraw.js +68 -10
  48. package/package.json +5 -4
  49. package/dist/idl/voidify-staking/idl.d.ts +0 -93
  50. package/dist/idl/voidify-staking/idl.js +0 -1
  51. package/dist/idl/voidify-staking/idl.json +0 -87
  52. package/dist/staking/commands.d.ts +0 -3
  53. package/dist/staking/commands.js +0 -13
  54. package/dist/staking/index.d.ts +0 -2
  55. package/dist/staking/index.js +0 -2
  56. package/dist/staking/program.d.ts +0 -18
  57. package/dist/staking/program.js +0 -40
  58. package/dist/types/errors.d.ts +0 -1
  59. package/dist/types/errors.js +0 -16
@@ -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 syncTransactions(connection: Connection, address: PublicKey, lastSignature?: string, batchSize?: number): Promise<TransactionEvent[]>;
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 syncTransactions(connection, address, lastSignature, batchSize = 1000) {
21
- const signatures = await fetchSignaturesForAddress(connection, address, lastSignature, batchSize);
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 connection.getTransaction(sigInfo.signature, {
28
- maxSupportedTransactionVersion: 0,
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
- if (!tx || !tx.meta) {
31
- continue;
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: tx.meta.logMessages || [],
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 { syncTransactions } from "../../substream/chain/utils.js";
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
- collect: async (lastSignature) => {
69
- const transactions = await syncTransactions(ctx.connection, address, lastSignature);
70
- const records = [];
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 { syncTransactions } from "../../substream/chain/utils.js";
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
- collect: async (lastSignature) => {
79
- const transactions = await syncTransactions(ctx.connection, ctx.programId, lastSignature);
80
- const records = [];
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
- substreamLogger.warn({ err, relayerPubkey }, "fetchRelayerConfig failed");
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
- record.stakeAmount = 0n;
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;