@develit-services/blockchain 0.7.0 → 0.8.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.
Files changed (43) hide show
  1. package/dist/database/schema.cjs +2 -1
  2. package/dist/database/schema.d.cts +1 -1
  3. package/dist/database/schema.d.mts +1 -1
  4. package/dist/database/schema.d.ts +1 -1
  5. package/dist/database/schema.mjs +1 -1
  6. package/dist/export/worker.cjs +10 -8
  7. package/dist/export/worker.d.cts +3 -5
  8. package/dist/export/worker.d.mts +3 -5
  9. package/dist/export/worker.d.ts +3 -5
  10. package/dist/export/worker.mjs +9 -7
  11. package/dist/export/workflows.cjs +124 -45
  12. package/dist/export/workflows.mjs +125 -46
  13. package/dist/export/wrangler.cjs +8 -0
  14. package/dist/export/wrangler.d.cts +7 -1
  15. package/dist/export/wrangler.d.mts +7 -1
  16. package/dist/export/wrangler.d.ts +7 -1
  17. package/dist/export/wrangler.mjs +8 -0
  18. package/dist/shared/blockchain.0tUJ62WT.mjs +6 -0
  19. package/dist/shared/{blockchain.DskfTOau.d.cts → blockchain.4Hzh__vM.d.ts} +26 -3
  20. package/dist/shared/{blockchain.AQv3PaVU.cjs → blockchain.B49xpPZ0.cjs} +3 -2
  21. package/dist/shared/blockchain.BBvwu2_7.cjs +39 -0
  22. package/dist/shared/{blockchain.DskfTOau.d.mts → blockchain.BDDFE27V.d.mts} +26 -3
  23. package/dist/shared/{blockchain.CNVF0KXj.mjs → blockchain.B_Tqqlia.mjs} +3 -2
  24. package/dist/shared/{blockchain.DQEKAvxx.d.ts → blockchain.C1Jdisxn.d.cts} +9 -1
  25. package/dist/shared/{blockchain.DQEKAvxx.d.cts → blockchain.C1Jdisxn.d.mts} +9 -1
  26. package/dist/shared/{blockchain.DQEKAvxx.d.mts → blockchain.C1Jdisxn.d.ts} +9 -1
  27. package/dist/shared/blockchain.Cx60lJ0c.d.cts +566 -0
  28. package/dist/shared/blockchain.Cx60lJ0c.d.mts +566 -0
  29. package/dist/shared/blockchain.Cx60lJ0c.d.ts +566 -0
  30. package/dist/shared/blockchain.DN735AwB.cjs +8 -0
  31. package/dist/shared/{blockchain.DskfTOau.d.ts → blockchain.DbsOmkkX.d.cts} +26 -3
  32. package/dist/shared/blockchain._wwKu1qP.mjs +35 -0
  33. package/dist/types.cjs +2 -1
  34. package/dist/types.d.cts +35 -2
  35. package/dist/types.d.mts +35 -2
  36. package/dist/types.d.ts +35 -2
  37. package/dist/types.mjs +2 -1
  38. package/package.json +5 -5
  39. package/dist/shared/blockchain.DmhmTTL_.mjs +0 -17
  40. package/dist/shared/blockchain.JxC5JMLI.cjs +0 -20
  41. package/dist/shared/blockchain.f4eN2PFQ.d.cts +0 -97
  42. package/dist/shared/blockchain.f4eN2PFQ.d.mts +0 -97
  43. package/dist/shared/blockchain.f4eN2PFQ.d.ts +0 -97
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const database_schema = require('../shared/blockchain.JxC5JMLI.cjs');
3
+ const database_schema = require('../shared/blockchain.BBvwu2_7.cjs');
4
4
  require('@develit-io/backend-sdk');
5
5
  require('drizzle-orm/sqlite-core');
6
6
 
7
7
 
8
8
 
9
9
  exports.address = database_schema.address;
10
+ exports.transaction = database_schema.transaction;
@@ -1,2 +1,2 @@
1
- export { a as address } from '../shared/blockchain.f4eN2PFQ.cjs';
1
+ export { a as address, t as transaction } from '../shared/blockchain.Cx60lJ0c.cjs';
2
2
  import 'drizzle-orm/sqlite-core';
@@ -1,2 +1,2 @@
1
- export { a as address } from '../shared/blockchain.f4eN2PFQ.mjs';
1
+ export { a as address, t as transaction } from '../shared/blockchain.Cx60lJ0c.mjs';
2
2
  import 'drizzle-orm/sqlite-core';
@@ -1,2 +1,2 @@
1
- export { a as address } from '../shared/blockchain.f4eN2PFQ.js';
1
+ export { a as address, t as transaction } from '../shared/blockchain.Cx60lJ0c.js';
2
2
  import 'drizzle-orm/sqlite-core';
@@ -1,3 +1,3 @@
1
- export { a as address } from '../shared/blockchain.DmhmTTL_.mjs';
1
+ export { a as address, t as transaction } from '../shared/blockchain._wwKu1qP.mjs';
2
2
  import '@develit-io/backend-sdk';
3
3
  import 'drizzle-orm/sqlite-core';
@@ -4,12 +4,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  const backendSdk = require('@develit-io/backend-sdk');
6
6
  const viem = require('viem');
7
- const database_schema = require('../shared/blockchain.JxC5JMLI.cjs');
8
- require('drizzle-orm');
9
- const syncAddress = require('../shared/blockchain.AQv3PaVU.cjs');
7
+ const drizzle = require('../shared/blockchain.DN735AwB.cjs');
8
+ const syncAddress = require('../shared/blockchain.B49xpPZ0.cjs');
10
9
  const cloudflare_workers = require('cloudflare:workers');
11
10
  const d1 = require('drizzle-orm/d1');
11
+ require('../shared/blockchain.BBvwu2_7.cjs');
12
12
  require('drizzle-orm/sqlite-core');
13
+ require('drizzle-orm');
13
14
  require('zod');
14
15
 
15
16
  class IBlockchainConnector {
@@ -86,8 +87,6 @@ class AlchemyConnector extends IBlockchainConnector {
86
87
  }
87
88
  }
88
89
 
89
- const tables = database_schema.schema;
90
-
91
90
  var __defProp = Object.defineProperty;
92
91
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
93
92
  var __decorateClass = (decorators, target, key, kind) => {
@@ -103,20 +102,23 @@ let BlockchainServiceBase = class extends backendSdk.develitWorker(
103
102
  ) {
104
103
  constructor(ctx, env) {
105
104
  super(ctx, env);
106
- this.db = d1.drizzle(this.env.BLOCKCHAIN_D1, { schema: tables });
105
+ this.db = d1.drizzle(this.env.BLOCKCHAIN_D1, { schema: drizzle.tables });
107
106
  }
108
107
  async syncAddress(input) {
109
108
  return this.handleAction(
110
109
  { data: input, schema: syncAddress.syncAddressInputSchema },
111
110
  { successMessage: "Address sync workflow started" },
112
111
  async ({ addressId }) => {
113
- await this.env.SYNC_ADDRESS_TRANSACTIONS_WORKFLOW.create({
112
+ const instance = await this.env.SYNC_ADDRESS_TRANSACTIONS_WORKFLOW.create({
114
113
  id: addressId,
115
114
  params: {
116
115
  addressId
117
116
  }
118
117
  });
119
- return {};
118
+ return {
119
+ instanceId: instance.id,
120
+ details: await instance.status()
121
+ };
120
122
  }
121
123
  );
122
124
  }
@@ -1,13 +1,11 @@
1
1
  import * as _develit_io_backend_sdk from '@develit-io/backend-sdk';
2
2
  import { IRPCResponse } from '@develit-io/backend-sdk';
3
- import { s as schema } from '../shared/blockchain.f4eN2PFQ.cjs';
4
- import { S as SyncAddressInput, b as SyncAddressOutput, G as GetTransactionInput, a as GetTransactionOutput } from '../shared/blockchain.DskfTOau.cjs';
3
+ import { t as tables, S as SyncAddressInput, a as SyncAddressOutput, G as GetTransactionInput, b as GetTransactionOutput } from '../shared/blockchain.DbsOmkkX.cjs';
5
4
  import { WorkerEntrypoint } from 'cloudflare:workers';
6
5
  import { DrizzleD1Database } from 'drizzle-orm/d1';
7
- import 'drizzle-orm/sqlite-core';
8
6
  import 'zod';
9
-
10
- declare const tables: typeof schema;
7
+ import '../shared/blockchain.Cx60lJ0c.cjs';
8
+ import 'drizzle-orm/sqlite-core';
11
9
 
12
10
  declare const BlockchainServiceBase_base: (abstract new (ctx: ExecutionContext, env: BlockchainEnv) => WorkerEntrypoint<BlockchainEnv, {}>) & (abstract new (...args: any[]) => _develit_io_backend_sdk.DevelitWorkerMethods);
13
11
  declare class BlockchainServiceBase extends BlockchainServiceBase_base {
@@ -1,13 +1,11 @@
1
1
  import * as _develit_io_backend_sdk from '@develit-io/backend-sdk';
2
2
  import { IRPCResponse } from '@develit-io/backend-sdk';
3
- import { s as schema } from '../shared/blockchain.f4eN2PFQ.mjs';
4
- import { S as SyncAddressInput, b as SyncAddressOutput, G as GetTransactionInput, a as GetTransactionOutput } from '../shared/blockchain.DskfTOau.mjs';
3
+ import { t as tables, S as SyncAddressInput, a as SyncAddressOutput, G as GetTransactionInput, b as GetTransactionOutput } from '../shared/blockchain.BDDFE27V.mjs';
5
4
  import { WorkerEntrypoint } from 'cloudflare:workers';
6
5
  import { DrizzleD1Database } from 'drizzle-orm/d1';
7
- import 'drizzle-orm/sqlite-core';
8
6
  import 'zod';
9
-
10
- declare const tables: typeof schema;
7
+ import '../shared/blockchain.Cx60lJ0c.mjs';
8
+ import 'drizzle-orm/sqlite-core';
11
9
 
12
10
  declare const BlockchainServiceBase_base: (abstract new (ctx: ExecutionContext, env: BlockchainEnv) => WorkerEntrypoint<BlockchainEnv, {}>) & (abstract new (...args: any[]) => _develit_io_backend_sdk.DevelitWorkerMethods);
13
11
  declare class BlockchainServiceBase extends BlockchainServiceBase_base {
@@ -1,13 +1,11 @@
1
1
  import * as _develit_io_backend_sdk from '@develit-io/backend-sdk';
2
2
  import { IRPCResponse } from '@develit-io/backend-sdk';
3
- import { s as schema } from '../shared/blockchain.f4eN2PFQ.js';
4
- import { S as SyncAddressInput, b as SyncAddressOutput, G as GetTransactionInput, a as GetTransactionOutput } from '../shared/blockchain.DskfTOau.js';
3
+ import { t as tables, S as SyncAddressInput, a as SyncAddressOutput, G as GetTransactionInput, b as GetTransactionOutput } from '../shared/blockchain.4Hzh__vM.js';
5
4
  import { WorkerEntrypoint } from 'cloudflare:workers';
6
5
  import { DrizzleD1Database } from 'drizzle-orm/d1';
7
- import 'drizzle-orm/sqlite-core';
8
6
  import 'zod';
9
-
10
- declare const tables: typeof schema;
7
+ import '../shared/blockchain.Cx60lJ0c.js';
8
+ import 'drizzle-orm/sqlite-core';
11
9
 
12
10
  declare const BlockchainServiceBase_base: (abstract new (ctx: ExecutionContext, env: BlockchainEnv) => WorkerEntrypoint<BlockchainEnv, {}>) & (abstract new (...args: any[]) => _develit_io_backend_sdk.DevelitWorkerMethods);
13
11
  declare class BlockchainServiceBase extends BlockchainServiceBase_base {
@@ -1,11 +1,12 @@
1
1
  import { useResult, createInternalError, develitWorker, action, service } from '@develit-io/backend-sdk';
2
2
  import { createPublicClient, http } from 'viem';
3
- import { s as schema } from '../shared/blockchain.DmhmTTL_.mjs';
4
- import 'drizzle-orm';
5
- import { s as syncAddressInputSchema, g as getTransactionInputSchema } from '../shared/blockchain.CNVF0KXj.mjs';
3
+ import { t as tables } from '../shared/blockchain.0tUJ62WT.mjs';
4
+ import { s as syncAddressInputSchema, g as getTransactionInputSchema } from '../shared/blockchain.B_Tqqlia.mjs';
6
5
  import { WorkerEntrypoint } from 'cloudflare:workers';
7
6
  import { drizzle } from 'drizzle-orm/d1';
7
+ import '../shared/blockchain._wwKu1qP.mjs';
8
8
  import 'drizzle-orm/sqlite-core';
9
+ import 'drizzle-orm';
9
10
  import 'zod';
10
11
 
11
12
  class IBlockchainConnector {
@@ -82,8 +83,6 @@ class AlchemyConnector extends IBlockchainConnector {
82
83
  }
83
84
  }
84
85
 
85
- const tables = schema;
86
-
87
86
  var __defProp = Object.defineProperty;
88
87
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
89
88
  var __decorateClass = (decorators, target, key, kind) => {
@@ -106,13 +105,16 @@ let BlockchainServiceBase = class extends develitWorker(
106
105
  { data: input, schema: syncAddressInputSchema },
107
106
  { successMessage: "Address sync workflow started" },
108
107
  async ({ addressId }) => {
109
- await this.env.SYNC_ADDRESS_TRANSACTIONS_WORKFLOW.create({
108
+ const instance = await this.env.SYNC_ADDRESS_TRANSACTIONS_WORKFLOW.create({
110
109
  id: addressId,
111
110
  params: {
112
111
  addressId
113
112
  }
114
113
  });
115
- return {};
114
+ return {
115
+ instanceId: instance.id,
116
+ details: await instance.status()
117
+ };
116
118
  }
117
119
  );
118
120
  }
@@ -1,42 +1,61 @@
1
1
  'use strict';
2
2
 
3
+ const backendSdk = require('@develit-io/backend-sdk');
4
+ const drizzle = require('../shared/blockchain.DN735AwB.cjs');
5
+ const drizzleOrm = require('drizzle-orm');
3
6
  const cloudflare_workers = require('cloudflare:workers');
4
7
  const cloudflare_workflows = require('cloudflare:workflows');
8
+ const d1 = require('drizzle-orm/d1');
5
9
  const viem = require('viem');
6
10
  const chains = require('viem/chains');
11
+ require('../shared/blockchain.BBvwu2_7.cjs');
12
+ require('drizzle-orm/sqlite-core');
7
13
 
8
- const TEST_ADDRESSES = {
9
- "faucet-1": {
10
- id: "faucet-1",
11
- address: "0xc959483dba39aa9e78757139af0e9a2edeb3f42d",
12
- blockchain: "ethereum",
13
- network: "sepolia",
14
- syncIntervalS: 60,
15
- lastSyncedBlock: null
16
- },
17
- "faucet-2": {
18
- id: "faucet-2",
19
- address: "0x1f885520b7bd528e46b390040f12e753dce43004",
20
- blockchain: "ethereum",
21
- network: "sepolia",
22
- syncIntervalS: 90,
23
- lastSyncedBlock: null
24
- },
25
- "vitalik-test": {
26
- id: "vitalik-test",
27
- address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
28
- blockchain: "ethereum",
29
- network: "sepolia",
30
- syncIntervalS: 120,
31
- lastSyncedBlock: null
32
- }
14
+ const updateAddressLastSyncCommand = (db, {
15
+ lastSyncAt,
16
+ addressId,
17
+ lastSyncMetadata
18
+ }) => {
19
+ const command = db.update(drizzle.tables.address).set({
20
+ lastSyncAt,
21
+ lastSyncMetadata
22
+ }).where(drizzleOrm.eq(drizzle.tables.address.id, addressId)).returning();
23
+ return {
24
+ command
25
+ };
26
+ };
27
+
28
+ const getAddressById = async (db, id) => {
29
+ const address = await db.select().from(drizzle.tables.address).where(drizzleOrm.eq(drizzle.tables.address.id, id)).get();
30
+ return address;
31
+ };
32
+
33
+ const createTransactionCommand = (db, { transaction }) => {
34
+ const id = backendSdk.uuidv4();
35
+ const command = db.insert(drizzle.tables.transaction).values({ ...transaction, id }).returning();
36
+ return { command };
33
37
  };
38
+
39
+ function pushToQueue(queue, message) {
40
+ if (!Array.isArray(message)) return queue.send(message, { contentType: "v8" });
41
+ return queue.sendBatch(
42
+ message.map((m) => ({
43
+ body: m,
44
+ contentType: "v8"
45
+ }))
46
+ );
47
+ }
34
48
  class BlockchainSyncAddressTransactions extends cloudflare_workers.WorkflowEntrypoint {
35
49
  async run(event, step) {
36
50
  const { addressId } = event.payload;
51
+ const db = d1.drizzle(this.env.BLOCKCHAIN_D1, { schema: drizzle.tables });
52
+ if (!addressId) {
53
+ throw new cloudflare_workflows.NonRetryableError(`Haven't obtained addressId to load.`);
54
+ }
37
55
  while (true) {
56
+ const now = /* @__PURE__ */ new Date();
38
57
  const address = await step.do("load address", async () => {
39
- const address2 = TEST_ADDRESSES[addressId];
58
+ const address2 = await getAddressById(db, addressId);
40
59
  if (!address2) {
41
60
  throw new cloudflare_workflows.NonRetryableError(
42
61
  `Blockchain address not found: ${addressId}.`
@@ -44,32 +63,45 @@ class BlockchainSyncAddressTransactions extends cloudflare_workers.WorkflowEntry
44
63
  }
45
64
  return address2;
46
65
  });
47
- await step.do(
66
+ if (!address.lastSyncAt) {
67
+ throw new Error(`lastSyncedAt is not set for address: ${address}`);
68
+ }
69
+ const transactions = await step.do(
48
70
  "fetch address transactions",
49
71
  {
50
72
  retries: { limit: 5, delay: 2e3, backoff: "constant" },
51
73
  timeout: "30 seconds"
52
74
  },
53
75
  async () => {
76
+ const rpcUrl = (await this.env.SECRETS_STORE.get({
77
+ secretName: "BLOCKCHAIN_SERVICE_RPC_URL"
78
+ })).data?.secretValue || "";
54
79
  const client = viem.createPublicClient({
55
- chain: chains.sepolia,
56
- transport: viem.http()
80
+ chain: chains.gnosisChiado,
81
+ transport: viem.http(rpcUrl)
57
82
  });
58
83
  const currentBlock = await client.getBlockNumber();
59
- const startBlock = address.lastSyncedBlock ? BigInt(address.lastSyncedBlock) + BigInt(1) : currentBlock - BigInt(10);
60
- const endBlock = currentBlock;
61
- console.log(
62
- `\u{1F50D} Scanning blocks ${startBlock} to ${endBlock} for address ${address.address}`
84
+ const estimatedBlocksPerInterval = Math.ceil(
85
+ address.syncIntervalS * 1.5 / 5
63
86
  );
87
+ const blocksToScan = Math.min(estimatedBlocksPerInterval, 1e3);
88
+ const startBlock = currentBlock - BigInt(blocksToScan);
89
+ const endBlock = currentBlock;
90
+ const lastSyncedTimestamp = address.lastSyncAt || 0;
64
91
  const allTransactions = [];
65
92
  for (let blockNum = startBlock; blockNum <= endBlock; blockNum++) {
66
93
  const block = await client.getBlock({
67
94
  blockNumber: blockNum,
68
95
  includeTransactions: true
69
96
  });
97
+ const blockTimestamp = Number(block.timestamp);
98
+ const lastSyncedTimestampNum = typeof lastSyncedTimestamp === "number" ? lastSyncedTimestamp : Number(lastSyncedTimestamp);
99
+ if (blockTimestamp <= lastSyncedTimestampNum) {
100
+ continue;
101
+ }
70
102
  for (const tx of block.transactions) {
71
- if (typeof tx === "string") continue;
72
- if (tx.from.toLowerCase() === address.address.toLowerCase() || tx.to?.toLowerCase() === address.address.toLowerCase()) {
103
+ if (typeof tx === "string" || !tx.to) continue;
104
+ if (tx.from.toLowerCase() === address.number.toLowerCase()) {
73
105
  const receipt = await client.getTransactionReceipt({
74
106
  hash: tx.hash
75
107
  });
@@ -81,23 +113,70 @@ class BlockchainSyncAddressTransactions extends cloudflare_workers.WorkflowEntry
81
113
  blockNumber: block.number.toString(),
82
114
  blockHash: block.hash,
83
115
  gasUsed: receipt.gasUsed.toString(),
84
- gasPrice: tx.gasPrice?.toString(),
85
- timestamp: Number(block.timestamp),
86
- status: receipt.status === "success" ? "success" : "reverted"
116
+ gasPrice: tx.gasPrice?.toString() ?? "",
117
+ timestamp: blockTimestamp,
118
+ status: receipt.status
87
119
  });
88
120
  }
89
121
  }
90
122
  }
91
- console.log(`\u2705 Sync complete for ${address.address}`);
92
- console.log(` Found ${allTransactions.length} transactions`);
93
- console.log(` Blocks scanned: ${startBlock} to ${endBlock}`);
94
- if (allTransactions.length > 0) {
95
- console.log("Transactions:");
96
- console.log(JSON.stringify(allTransactions));
97
- }
98
123
  return allTransactions;
99
124
  }
100
125
  );
126
+ if (transactions.length > 0) {
127
+ await step.do(
128
+ "process new transactions and update lastSyncAt",
129
+ async () => {
130
+ const createCommands = transactions.map(
131
+ (tx) => createTransactionCommand(db, { transaction: tx }).command
132
+ );
133
+ const eventsToEmit = transactions.map(
134
+ (tx) => ({
135
+ eventType: "BLOCKCHAIN_TRANSACTION",
136
+ blockchainTransaction: {
137
+ address: address.number,
138
+ hash: tx.hash,
139
+ from: tx.from,
140
+ to: tx.to,
141
+ value: tx.value,
142
+ blockNumber: tx.blockNumber,
143
+ blockHash: tx.blockHash,
144
+ gasUsed: tx.gasUsed,
145
+ gasPrice: tx.gasPrice,
146
+ timestamp: tx.timestamp,
147
+ status: tx.status
148
+ },
149
+ metadata: {
150
+ correlationId: backendSdk.uuidv4(),
151
+ entityId: backendSdk.uuidv4(),
152
+ timestamp: (/* @__PURE__ */ new Date()).toDateString()
153
+ }
154
+ })
155
+ );
156
+ const lastSyncMetadata = {
157
+ eventsEmitted: eventsToEmit.length
158
+ };
159
+ const updateLastSyncCommand = updateAddressLastSyncCommand(db, {
160
+ addressId: address.id,
161
+ lastSyncAt: now,
162
+ lastSyncMetadata
163
+ }).command;
164
+ if (eventsToEmit.length) {
165
+ await db.batch(
166
+ backendSdk.asNonEmpty([updateLastSyncCommand, ...createCommands])
167
+ );
168
+ await pushToQueue(
169
+ this.env.QUEUE_BUS_QUEUE,
170
+ eventsToEmit
171
+ );
172
+ }
173
+ return {
174
+ ...lastSyncMetadata,
175
+ newLastSyncAt: now
176
+ };
177
+ }
178
+ );
179
+ }
101
180
  await step.sleep(
102
181
  "sleep until next sync",
103
182
  `${address.syncIntervalS} seconds`
@@ -1,40 +1,59 @@
1
+ import { uuidv4, asNonEmpty } from '@develit-io/backend-sdk';
2
+ import { t as tables } from '../shared/blockchain.0tUJ62WT.mjs';
3
+ import { eq } from 'drizzle-orm';
1
4
  import { WorkflowEntrypoint } from 'cloudflare:workers';
2
5
  import { NonRetryableError } from 'cloudflare:workflows';
6
+ import { drizzle } from 'drizzle-orm/d1';
3
7
  import { createPublicClient, http } from 'viem';
4
- import { sepolia } from 'viem/chains';
8
+ import { gnosisChiado } from 'viem/chains';
9
+ import '../shared/blockchain._wwKu1qP.mjs';
10
+ import 'drizzle-orm/sqlite-core';
5
11
 
6
- const TEST_ADDRESSES = {
7
- "faucet-1": {
8
- id: "faucet-1",
9
- address: "0xc959483dba39aa9e78757139af0e9a2edeb3f42d",
10
- blockchain: "ethereum",
11
- network: "sepolia",
12
- syncIntervalS: 60,
13
- lastSyncedBlock: null
14
- },
15
- "faucet-2": {
16
- id: "faucet-2",
17
- address: "0x1f885520b7bd528e46b390040f12e753dce43004",
18
- blockchain: "ethereum",
19
- network: "sepolia",
20
- syncIntervalS: 90,
21
- lastSyncedBlock: null
22
- },
23
- "vitalik-test": {
24
- id: "vitalik-test",
25
- address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
26
- blockchain: "ethereum",
27
- network: "sepolia",
28
- syncIntervalS: 120,
29
- lastSyncedBlock: null
30
- }
12
+ const updateAddressLastSyncCommand = (db, {
13
+ lastSyncAt,
14
+ addressId,
15
+ lastSyncMetadata
16
+ }) => {
17
+ const command = db.update(tables.address).set({
18
+ lastSyncAt,
19
+ lastSyncMetadata
20
+ }).where(eq(tables.address.id, addressId)).returning();
21
+ return {
22
+ command
23
+ };
24
+ };
25
+
26
+ const getAddressById = async (db, id) => {
27
+ const address = await db.select().from(tables.address).where(eq(tables.address.id, id)).get();
28
+ return address;
29
+ };
30
+
31
+ const createTransactionCommand = (db, { transaction }) => {
32
+ const id = uuidv4();
33
+ const command = db.insert(tables.transaction).values({ ...transaction, id }).returning();
34
+ return { command };
31
35
  };
36
+
37
+ function pushToQueue(queue, message) {
38
+ if (!Array.isArray(message)) return queue.send(message, { contentType: "v8" });
39
+ return queue.sendBatch(
40
+ message.map((m) => ({
41
+ body: m,
42
+ contentType: "v8"
43
+ }))
44
+ );
45
+ }
32
46
  class BlockchainSyncAddressTransactions extends WorkflowEntrypoint {
33
47
  async run(event, step) {
34
48
  const { addressId } = event.payload;
49
+ const db = drizzle(this.env.BLOCKCHAIN_D1, { schema: tables });
50
+ if (!addressId) {
51
+ throw new NonRetryableError(`Haven't obtained addressId to load.`);
52
+ }
35
53
  while (true) {
54
+ const now = /* @__PURE__ */ new Date();
36
55
  const address = await step.do("load address", async () => {
37
- const address2 = TEST_ADDRESSES[addressId];
56
+ const address2 = await getAddressById(db, addressId);
38
57
  if (!address2) {
39
58
  throw new NonRetryableError(
40
59
  `Blockchain address not found: ${addressId}.`
@@ -42,32 +61,45 @@ class BlockchainSyncAddressTransactions extends WorkflowEntrypoint {
42
61
  }
43
62
  return address2;
44
63
  });
45
- await step.do(
64
+ if (!address.lastSyncAt) {
65
+ throw new Error(`lastSyncedAt is not set for address: ${address}`);
66
+ }
67
+ const transactions = await step.do(
46
68
  "fetch address transactions",
47
69
  {
48
70
  retries: { limit: 5, delay: 2e3, backoff: "constant" },
49
71
  timeout: "30 seconds"
50
72
  },
51
73
  async () => {
74
+ const rpcUrl = (await this.env.SECRETS_STORE.get({
75
+ secretName: "BLOCKCHAIN_SERVICE_RPC_URL"
76
+ })).data?.secretValue || "";
52
77
  const client = createPublicClient({
53
- chain: sepolia,
54
- transport: http()
78
+ chain: gnosisChiado,
79
+ transport: http(rpcUrl)
55
80
  });
56
81
  const currentBlock = await client.getBlockNumber();
57
- const startBlock = address.lastSyncedBlock ? BigInt(address.lastSyncedBlock) + BigInt(1) : currentBlock - BigInt(10);
58
- const endBlock = currentBlock;
59
- console.log(
60
- `\u{1F50D} Scanning blocks ${startBlock} to ${endBlock} for address ${address.address}`
82
+ const estimatedBlocksPerInterval = Math.ceil(
83
+ address.syncIntervalS * 1.5 / 5
61
84
  );
85
+ const blocksToScan = Math.min(estimatedBlocksPerInterval, 1e3);
86
+ const startBlock = currentBlock - BigInt(blocksToScan);
87
+ const endBlock = currentBlock;
88
+ const lastSyncedTimestamp = address.lastSyncAt || 0;
62
89
  const allTransactions = [];
63
90
  for (let blockNum = startBlock; blockNum <= endBlock; blockNum++) {
64
91
  const block = await client.getBlock({
65
92
  blockNumber: blockNum,
66
93
  includeTransactions: true
67
94
  });
95
+ const blockTimestamp = Number(block.timestamp);
96
+ const lastSyncedTimestampNum = typeof lastSyncedTimestamp === "number" ? lastSyncedTimestamp : Number(lastSyncedTimestamp);
97
+ if (blockTimestamp <= lastSyncedTimestampNum) {
98
+ continue;
99
+ }
68
100
  for (const tx of block.transactions) {
69
- if (typeof tx === "string") continue;
70
- if (tx.from.toLowerCase() === address.address.toLowerCase() || tx.to?.toLowerCase() === address.address.toLowerCase()) {
101
+ if (typeof tx === "string" || !tx.to) continue;
102
+ if (tx.from.toLowerCase() === address.number.toLowerCase()) {
71
103
  const receipt = await client.getTransactionReceipt({
72
104
  hash: tx.hash
73
105
  });
@@ -79,23 +111,70 @@ class BlockchainSyncAddressTransactions extends WorkflowEntrypoint {
79
111
  blockNumber: block.number.toString(),
80
112
  blockHash: block.hash,
81
113
  gasUsed: receipt.gasUsed.toString(),
82
- gasPrice: tx.gasPrice?.toString(),
83
- timestamp: Number(block.timestamp),
84
- status: receipt.status === "success" ? "success" : "reverted"
114
+ gasPrice: tx.gasPrice?.toString() ?? "",
115
+ timestamp: blockTimestamp,
116
+ status: receipt.status
85
117
  });
86
118
  }
87
119
  }
88
120
  }
89
- console.log(`\u2705 Sync complete for ${address.address}`);
90
- console.log(` Found ${allTransactions.length} transactions`);
91
- console.log(` Blocks scanned: ${startBlock} to ${endBlock}`);
92
- if (allTransactions.length > 0) {
93
- console.log("Transactions:");
94
- console.log(JSON.stringify(allTransactions));
95
- }
96
121
  return allTransactions;
97
122
  }
98
123
  );
124
+ if (transactions.length > 0) {
125
+ await step.do(
126
+ "process new transactions and update lastSyncAt",
127
+ async () => {
128
+ const createCommands = transactions.map(
129
+ (tx) => createTransactionCommand(db, { transaction: tx }).command
130
+ );
131
+ const eventsToEmit = transactions.map(
132
+ (tx) => ({
133
+ eventType: "BLOCKCHAIN_TRANSACTION",
134
+ blockchainTransaction: {
135
+ address: address.number,
136
+ hash: tx.hash,
137
+ from: tx.from,
138
+ to: tx.to,
139
+ value: tx.value,
140
+ blockNumber: tx.blockNumber,
141
+ blockHash: tx.blockHash,
142
+ gasUsed: tx.gasUsed,
143
+ gasPrice: tx.gasPrice,
144
+ timestamp: tx.timestamp,
145
+ status: tx.status
146
+ },
147
+ metadata: {
148
+ correlationId: uuidv4(),
149
+ entityId: uuidv4(),
150
+ timestamp: (/* @__PURE__ */ new Date()).toDateString()
151
+ }
152
+ })
153
+ );
154
+ const lastSyncMetadata = {
155
+ eventsEmitted: eventsToEmit.length
156
+ };
157
+ const updateLastSyncCommand = updateAddressLastSyncCommand(db, {
158
+ addressId: address.id,
159
+ lastSyncAt: now,
160
+ lastSyncMetadata
161
+ }).command;
162
+ if (eventsToEmit.length) {
163
+ await db.batch(
164
+ asNonEmpty([updateLastSyncCommand, ...createCommands])
165
+ );
166
+ await pushToQueue(
167
+ this.env.QUEUE_BUS_QUEUE,
168
+ eventsToEmit
169
+ );
170
+ }
171
+ return {
172
+ ...lastSyncMetadata,
173
+ newLastSyncAt: now
174
+ };
175
+ }
176
+ );
177
+ }
99
178
  await step.sleep(
100
179
  "sleep until next sync",
101
180
  `${address.syncIntervalS} seconds`
@@ -30,6 +30,14 @@ function defineBlockchainServiceWrangler(config) {
30
30
  class_name: "BlockchainSyncAddressTransactions"
31
31
  }
32
32
  ],
33
+ queues: {
34
+ producers: [
35
+ {
36
+ binding: envs.local.queues.producers.bus.binding,
37
+ queue: `${project}-${envs.local.queues.producers.bus.queue}`
38
+ }
39
+ ]
40
+ },
33
41
  env: {}
34
42
  };
35
43
  for (const [envName, envCfg] of Object.entries(envs).filter(
@@ -1,4 +1,4 @@
1
- import { b as BlockchainServiceWranglerConfig } from '../shared/blockchain.DQEKAvxx.cjs';
1
+ import { B as BlockchainServiceWranglerConfig } from '../shared/blockchain.C1Jdisxn.cjs';
2
2
 
3
3
  declare function defineBlockchainServiceWrangler(config: BlockchainServiceWranglerConfig): {
4
4
  services: {
@@ -16,6 +16,12 @@ declare function defineBlockchainServiceWrangler(config: BlockchainServiceWrangl
16
16
  name: string;
17
17
  class_name: string;
18
18
  }[];
19
+ queues: {
20
+ producers: {
21
+ binding: string;
22
+ queue: string;
23
+ }[];
24
+ };
19
25
  env: Record<string, unknown>;
20
26
  $schema: string;
21
27
  name: string;