@develit-services/blockchain 0.8.1 → 0.8.3
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/dist/database/schema.cjs +5 -1
- package/dist/database/schema.d.cts +1 -1
- package/dist/database/schema.d.mts +1 -1
- package/dist/database/schema.d.ts +1 -1
- package/dist/database/schema.mjs +3 -1
- package/dist/export/worker.cjs +20 -103
- package/dist/export/worker.d.cts +2 -2
- package/dist/export/worker.d.mts +2 -2
- package/dist/export/worker.d.ts +2 -2
- package/dist/export/worker.mjs +20 -103
- package/dist/export/workflows.cjs +88 -82
- package/dist/export/workflows.mjs +83 -77
- package/dist/shared/{blockchain.Bq5JL2Nn.d.cts → blockchain.7cRY-udH.d.ts} +33 -5
- package/dist/shared/blockchain.BUbQBxqd.cjs +335 -0
- package/dist/shared/blockchain.BhDiNGPI.mjs +332 -0
- package/dist/shared/{blockchain.B_9YwgFd.mjs → blockchain.CKh8Fs7w.mjs} +42 -20
- package/dist/shared/{blockchain.BFesyGyk.d.mts → blockchain.CaPQzwns.d.cts} +33 -5
- package/dist/shared/blockchain.CbmnjjFl.cjs +70 -0
- package/dist/shared/{blockchain.C0wB1XUE.cjs → blockchain.Cjq9eH7Z.cjs} +44 -19
- package/dist/shared/{blockchain.C1uxxlk3.d.ts → blockchain.CvNI0YTF.d.mts} +33 -5
- package/dist/shared/blockchain.DUhjXgba.d.cts +1107 -0
- package/dist/shared/blockchain.DUhjXgba.d.mts +1107 -0
- package/dist/shared/blockchain.DUhjXgba.d.ts +1107 -0
- package/dist/shared/blockchain._nv3VxNA.mjs +64 -0
- package/dist/types.cjs +4 -1
- package/dist/types.d.cts +12 -4
- package/dist/types.d.mts +12 -4
- package/dist/types.d.ts +12 -4
- package/dist/types.mjs +1 -1
- package/package.json +1 -1
- package/dist/shared/blockchain.0tUJ62WT.mjs +0 -6
- package/dist/shared/blockchain.BBvwu2_7.cjs +0 -39
- package/dist/shared/blockchain.Cx60lJ0c.d.cts +0 -566
- package/dist/shared/blockchain.Cx60lJ0c.d.mts +0 -566
- package/dist/shared/blockchain.Cx60lJ0c.d.ts +0 -566
- package/dist/shared/blockchain.DN735AwB.cjs +0 -8
- package/dist/shared/blockchain._wwKu1qP.mjs +0 -35
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { uuidv4, asNonEmpty } from '@develit-io/backend-sdk';
|
|
2
|
-
import { t as tables } from '../shared/blockchain.
|
|
2
|
+
import { t as tables, i as initiateConnector } from '../shared/blockchain.BhDiNGPI.mjs';
|
|
3
3
|
import { eq } from 'drizzle-orm';
|
|
4
4
|
import { WorkflowEntrypoint } from 'cloudflare:workers';
|
|
5
5
|
import { NonRetryableError } from 'cloudflare:workflows';
|
|
6
6
|
import { drizzle } from 'drizzle-orm/d1';
|
|
7
|
-
import
|
|
8
|
-
import { gnosisChiado } from 'viem/chains';
|
|
9
|
-
import '../shared/blockchain._wwKu1qP.mjs';
|
|
7
|
+
import '../shared/blockchain._nv3VxNA.mjs';
|
|
10
8
|
import 'drizzle-orm/sqlite-core';
|
|
9
|
+
import '../shared/blockchain.CKh8Fs7w.mjs';
|
|
10
|
+
import 'zod';
|
|
11
|
+
import 'viem';
|
|
12
|
+
import 'viem/accounts';
|
|
13
|
+
|
|
14
|
+
const updateAddressLastSyncBlockCommand = (db, {
|
|
15
|
+
lastSyncBlock,
|
|
16
|
+
addressId
|
|
17
|
+
}) => {
|
|
18
|
+
const command = db.update(tables.address).set({
|
|
19
|
+
lastSyncBlock
|
|
20
|
+
}).where(eq(tables.address.id, addressId)).returning();
|
|
21
|
+
return {
|
|
22
|
+
command
|
|
23
|
+
};
|
|
24
|
+
};
|
|
11
25
|
|
|
12
26
|
const updateAddressLastSyncCommand = (db, {
|
|
13
27
|
lastSyncAt,
|
|
@@ -28,6 +42,11 @@ const getAddressById = async (db, id) => {
|
|
|
28
42
|
return address;
|
|
29
43
|
};
|
|
30
44
|
|
|
45
|
+
const getContractAddresses = async (db, { connector }) => {
|
|
46
|
+
const addresses = await db.select().from(tables.tokens).where(eq(tables.tokens.connector, connector)).all();
|
|
47
|
+
return addresses;
|
|
48
|
+
};
|
|
49
|
+
|
|
31
50
|
const createTransactionCommand = (db, { transaction }) => {
|
|
32
51
|
const id = uuidv4();
|
|
33
52
|
const command = db.insert(tables.transaction).values({ ...transaction, id }).returning();
|
|
@@ -62,7 +81,9 @@ class BlockchainSyncAddressTransactions extends WorkflowEntrypoint {
|
|
|
62
81
|
return address2;
|
|
63
82
|
});
|
|
64
83
|
if (!address.lastSyncAt) {
|
|
65
|
-
throw new
|
|
84
|
+
throw new NonRetryableError(
|
|
85
|
+
`lastSyncedAt is not set for address: ${address}`
|
|
86
|
+
);
|
|
66
87
|
}
|
|
67
88
|
const transactions = await step.do(
|
|
68
89
|
"fetch address transactions",
|
|
@@ -71,86 +92,63 @@ class BlockchainSyncAddressTransactions extends WorkflowEntrypoint {
|
|
|
71
92
|
timeout: "30 seconds"
|
|
72
93
|
},
|
|
73
94
|
async () => {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
})).data?.secretValue || "";
|
|
77
|
-
const client = createPublicClient({
|
|
78
|
-
chain: gnosisChiado,
|
|
79
|
-
transport: http(rpcUrl)
|
|
95
|
+
const tokens = await getContractAddresses(db, {
|
|
96
|
+
connector: address.connector
|
|
80
97
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const blocksToScan = Math.min(estimatedBlocksPerInterval, 1e3);
|
|
86
|
-
const startBlock = currentBlock - BigInt(blocksToScan);
|
|
87
|
-
const endBlock = currentBlock;
|
|
88
|
-
const lastSyncedTimestamp = address.lastSyncAt || 0;
|
|
89
|
-
const allTransactions = [];
|
|
90
|
-
for (let blockNum = startBlock; blockNum <= endBlock; blockNum++) {
|
|
91
|
-
const block = await client.getBlock({
|
|
92
|
-
blockNumber: blockNum,
|
|
93
|
-
includeTransactions: true
|
|
94
|
-
});
|
|
95
|
-
const blockTimestamp = Number(block.timestamp);
|
|
96
|
-
const lastSyncedTimestampNum = typeof lastSyncedTimestamp === "number" ? lastSyncedTimestamp : Number(lastSyncedTimestamp);
|
|
97
|
-
if (blockTimestamp <= lastSyncedTimestampNum) {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
for (const tx of block.transactions) {
|
|
101
|
-
if (typeof tx === "string" || !tx.to) continue;
|
|
102
|
-
if (tx.from.toLowerCase() === address.number.toLowerCase()) {
|
|
103
|
-
const receipt = await client.getTransactionReceipt({
|
|
104
|
-
hash: tx.hash
|
|
105
|
-
});
|
|
106
|
-
allTransactions.push({
|
|
107
|
-
hash: tx.hash,
|
|
108
|
-
from: tx.from,
|
|
109
|
-
to: tx.to,
|
|
110
|
-
value: tx.value.toString(),
|
|
111
|
-
blockNumber: block.number.toString(),
|
|
112
|
-
blockHash: block.hash,
|
|
113
|
-
gasUsed: receipt.gasUsed.toString(),
|
|
114
|
-
gasPrice: tx.gasPrice?.toString() ?? "",
|
|
115
|
-
timestamp: blockTimestamp,
|
|
116
|
-
status: receipt.status
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
98
|
+
if (!tokens || tokens.length === 0) {
|
|
99
|
+
throw new NonRetryableError(
|
|
100
|
+
`No contract addresses found for connector: ${address.connector}`
|
|
101
|
+
);
|
|
120
102
|
}
|
|
121
|
-
|
|
103
|
+
const connector = await initiateConnector({
|
|
104
|
+
chain: address.connector,
|
|
105
|
+
env: this.env,
|
|
106
|
+
tokens,
|
|
107
|
+
connectedAddresses: [
|
|
108
|
+
{
|
|
109
|
+
id: address.id,
|
|
110
|
+
number: address.number,
|
|
111
|
+
connector: address.connector
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
return await connector.getAllAddressTransactions({
|
|
116
|
+
address: address.number,
|
|
117
|
+
filter: { fromBlock: address.lastSyncBlock }
|
|
118
|
+
});
|
|
122
119
|
}
|
|
123
120
|
);
|
|
124
|
-
if (transactions.length > 0) {
|
|
121
|
+
if (transactions.transactions.length > 0) {
|
|
125
122
|
await step.do(
|
|
126
123
|
"process new transactions and update lastSyncAt",
|
|
127
124
|
async () => {
|
|
128
|
-
const createCommands = transactions.map(
|
|
125
|
+
const createCommands = transactions.transactions.map(
|
|
129
126
|
(tx) => createTransactionCommand(db, { transaction: tx }).command
|
|
130
127
|
);
|
|
131
|
-
const eventsToEmit = transactions.map(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
128
|
+
const eventsToEmit = transactions.transactions.map((tx) => ({
|
|
129
|
+
eventType: "BLOCKCHAIN_TRANSACTION",
|
|
130
|
+
blockchainTransaction: {
|
|
131
|
+
address: address.number,
|
|
132
|
+
hash: tx.hash,
|
|
133
|
+
from: tx.from,
|
|
134
|
+
to: tx.to,
|
|
135
|
+
value: tx.value,
|
|
136
|
+
blockNumber: tx.blockNumber,
|
|
137
|
+
blockHash: tx.blockHash,
|
|
138
|
+
gasUsed: tx.gasUsed,
|
|
139
|
+
gasPrice: tx.gasPrice,
|
|
140
|
+
timestamp: tx.timestamp,
|
|
141
|
+
status: tx.status,
|
|
142
|
+
decimals: tx.decimals,
|
|
143
|
+
connector: address.connector,
|
|
144
|
+
ticker: tx.ticker
|
|
145
|
+
},
|
|
146
|
+
metadata: {
|
|
147
|
+
correlationId: uuidv4(),
|
|
148
|
+
entityId: uuidv4(),
|
|
149
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
154
152
|
const lastSyncMetadata = {
|
|
155
153
|
eventsEmitted: eventsToEmit.length
|
|
156
154
|
};
|
|
@@ -159,9 +157,17 @@ class BlockchainSyncAddressTransactions extends WorkflowEntrypoint {
|
|
|
159
157
|
lastSyncAt: now,
|
|
160
158
|
lastSyncMetadata
|
|
161
159
|
}).command;
|
|
160
|
+
const updateLastSyncBlockCommand = updateAddressLastSyncBlockCommand(db, {
|
|
161
|
+
addressId: address.id,
|
|
162
|
+
lastSyncBlock: transactions.latestBlock
|
|
163
|
+
}).command;
|
|
162
164
|
if (eventsToEmit.length) {
|
|
163
165
|
await db.batch(
|
|
164
|
-
asNonEmpty([
|
|
166
|
+
asNonEmpty([
|
|
167
|
+
updateLastSyncCommand,
|
|
168
|
+
updateLastSyncBlockCommand,
|
|
169
|
+
...createCommands
|
|
170
|
+
])
|
|
165
171
|
);
|
|
166
172
|
await pushToQueue(
|
|
167
173
|
this.env.QUEUE_BUS_QUEUE,
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { s as schema } from './blockchain.
|
|
2
|
+
import { s as schema } from './blockchain.DUhjXgba.js';
|
|
3
3
|
|
|
4
4
|
declare const getBlockInputSchema: z.ZodObject<{
|
|
5
|
+
chain: z.ZodEnum<{
|
|
6
|
+
ARBITRUM: "ARBITRUM";
|
|
7
|
+
AVALANCHE: "AVALANCHE";
|
|
8
|
+
BASE: "BASE";
|
|
9
|
+
ETHEREUM: "ETHEREUM";
|
|
10
|
+
GNOSIS: "GNOSIS";
|
|
11
|
+
OPTIMISM: "OPTIMISM";
|
|
12
|
+
SCROLL: "SCROLL";
|
|
13
|
+
}>;
|
|
5
14
|
blockTag: z.ZodOptional<z.ZodEnum<{
|
|
15
|
+
pending: "pending";
|
|
6
16
|
latest: "latest";
|
|
7
17
|
earliest: "earliest";
|
|
8
|
-
pending: "pending";
|
|
9
18
|
safe: "safe";
|
|
10
19
|
finalized: "finalized";
|
|
11
20
|
}>>;
|
|
@@ -50,7 +59,26 @@ interface GetBlockInput extends z.infer<typeof getBlockInputSchema> {
|
|
|
50
59
|
}
|
|
51
60
|
type GetBlockOutput = z.infer<typeof getBlockOutputSchema>;
|
|
52
61
|
|
|
62
|
+
declare const chainSchema: z.ZodEnum<{
|
|
63
|
+
ARBITRUM: "ARBITRUM";
|
|
64
|
+
AVALANCHE: "AVALANCHE";
|
|
65
|
+
BASE: "BASE";
|
|
66
|
+
ETHEREUM: "ETHEREUM";
|
|
67
|
+
GNOSIS: "GNOSIS";
|
|
68
|
+
OPTIMISM: "OPTIMISM";
|
|
69
|
+
SCROLL: "SCROLL";
|
|
70
|
+
}>;
|
|
71
|
+
type Chain = z.infer<typeof chainSchema>;
|
|
53
72
|
declare const getTransactionInputSchema: z.ZodObject<{
|
|
73
|
+
chain: z.ZodEnum<{
|
|
74
|
+
ARBITRUM: "ARBITRUM";
|
|
75
|
+
AVALANCHE: "AVALANCHE";
|
|
76
|
+
BASE: "BASE";
|
|
77
|
+
ETHEREUM: "ETHEREUM";
|
|
78
|
+
GNOSIS: "GNOSIS";
|
|
79
|
+
OPTIMISM: "OPTIMISM";
|
|
80
|
+
SCROLL: "SCROLL";
|
|
81
|
+
}>;
|
|
54
82
|
txHash: z.ZodString;
|
|
55
83
|
}, z.core.$strip>;
|
|
56
84
|
declare const getTransactionOutputSchema: z.ZodObject<{
|
|
@@ -65,9 +93,9 @@ declare const getTransactionOutputSchema: z.ZodObject<{
|
|
|
65
93
|
nonce: z.ZodNumber;
|
|
66
94
|
transactionIndex: z.ZodNullable<z.ZodNumber>;
|
|
67
95
|
status: z.ZodEnum<{
|
|
68
|
-
pending: "pending";
|
|
69
96
|
success: "success";
|
|
70
97
|
reverted: "reverted";
|
|
98
|
+
pending: "pending";
|
|
71
99
|
}>;
|
|
72
100
|
gasUsed: z.ZodNullable<z.ZodString>;
|
|
73
101
|
timestamp: z.ZodNullable<z.ZodNumber>;
|
|
@@ -106,5 +134,5 @@ type SyncAddressOutput = z.infer<typeof syncAddressOutputSchema>;
|
|
|
106
134
|
|
|
107
135
|
declare const tables: typeof schema;
|
|
108
136
|
|
|
109
|
-
export {
|
|
110
|
-
export type { GetTransactionInput as G, SyncAddressInput as S, SyncAddressOutput as a, GetTransactionOutput as b, GetBlockInput as c, GetBlockOutput as d };
|
|
137
|
+
export { chainSchema as e, getBlockOutputSchema as f, getBlockInputSchema as g, getTransactionInputSchema as h, getTransactionOutputSchema as i, syncAddressOutputSchema as j, syncAddressInputSchema as s, tables as t };
|
|
138
|
+
export type { Chain as C, GetTransactionInput as G, SyncAddressInput as S, SyncAddressOutput as a, GetTransactionOutput as b, GetBlockInput as c, GetBlockOutput as d };
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const database_schema = require('./blockchain.CbmnjjFl.cjs');
|
|
4
|
+
require('drizzle-orm');
|
|
5
|
+
const backendSdk = require('@develit-io/backend-sdk');
|
|
6
|
+
const viem = require('viem');
|
|
7
|
+
const accounts = require('viem/accounts');
|
|
8
|
+
|
|
9
|
+
const tables = database_schema.schema;
|
|
10
|
+
|
|
11
|
+
class IBlockchainConnector {
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TRANSFER_EVENT_ABI = [
|
|
15
|
+
{
|
|
16
|
+
type: "event",
|
|
17
|
+
name: "Transfer",
|
|
18
|
+
inputs: [
|
|
19
|
+
{ indexed: true, name: "from", type: "address" },
|
|
20
|
+
{ indexed: true, name: "to", type: "address" },
|
|
21
|
+
{ indexed: false, name: "value", type: "uint256" }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
const ERC20_TRANSFER_ABI = [
|
|
26
|
+
{
|
|
27
|
+
type: "function",
|
|
28
|
+
name: "transfer",
|
|
29
|
+
inputs: [
|
|
30
|
+
{ name: "to", type: "address" },
|
|
31
|
+
{ name: "amount", type: "uint256" }
|
|
32
|
+
],
|
|
33
|
+
outputs: [{ name: "", type: "bool" }],
|
|
34
|
+
stateMutability: "nonpayable"
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
class EvmConnector extends IBlockchainConnector {
|
|
38
|
+
constructor(config) {
|
|
39
|
+
super();
|
|
40
|
+
this.client = null;
|
|
41
|
+
this.rpcUrl = config.RPC_URL;
|
|
42
|
+
this.tokens = config.tokens;
|
|
43
|
+
this.connectedAddresses = config.connectedAddresses;
|
|
44
|
+
this.resolveCredentials = config.resolveCredentials;
|
|
45
|
+
}
|
|
46
|
+
createClient() {
|
|
47
|
+
if (!this.client) {
|
|
48
|
+
this.client = viem.createPublicClient({
|
|
49
|
+
transport: viem.http(this.rpcUrl)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return this.client;
|
|
53
|
+
}
|
|
54
|
+
async getTransaction(txHash) {
|
|
55
|
+
const client = this.createClient();
|
|
56
|
+
const [tx, txError] = await backendSdk.useResult(
|
|
57
|
+
client.getTransaction({ hash: txHash })
|
|
58
|
+
);
|
|
59
|
+
if (txError || !tx) {
|
|
60
|
+
throw backendSdk.createInternalError(txError);
|
|
61
|
+
}
|
|
62
|
+
let status = "pending";
|
|
63
|
+
let gasUsed = null;
|
|
64
|
+
let timestamp = null;
|
|
65
|
+
if (tx.blockNumber) {
|
|
66
|
+
const [receipt, receiptError] = await backendSdk.useResult(
|
|
67
|
+
client.getTransactionReceipt({ hash: txHash })
|
|
68
|
+
);
|
|
69
|
+
if (receiptError || !receipt) {
|
|
70
|
+
throw backendSdk.createInternalError(receiptError);
|
|
71
|
+
}
|
|
72
|
+
const [block, blockError] = await backendSdk.useResult(
|
|
73
|
+
client.getBlock({ blockNumber: tx.blockNumber })
|
|
74
|
+
);
|
|
75
|
+
if (blockError || !block) {
|
|
76
|
+
throw backendSdk.createInternalError(blockError);
|
|
77
|
+
}
|
|
78
|
+
status = receipt.status === "success" ? "success" : "reverted";
|
|
79
|
+
gasUsed = receipt.gasUsed.toString();
|
|
80
|
+
timestamp = Number(block.timestamp) * 1e3;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
hash: tx.hash,
|
|
84
|
+
from: tx.from,
|
|
85
|
+
to: tx.to,
|
|
86
|
+
value: tx.value.toString(),
|
|
87
|
+
blockNumber: tx.blockNumber?.toString() ?? null,
|
|
88
|
+
blockHash: tx.blockHash ?? null,
|
|
89
|
+
gasPrice: tx.gasPrice?.toString() ?? null,
|
|
90
|
+
gas: tx.gas.toString(),
|
|
91
|
+
nonce: tx.nonce,
|
|
92
|
+
transactionIndex: tx.transactionIndex,
|
|
93
|
+
status,
|
|
94
|
+
gasUsed,
|
|
95
|
+
timestamp
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async getTransactionReceipt(txHash) {
|
|
99
|
+
const client = this.createClient();
|
|
100
|
+
const [receipt, error] = await backendSdk.useResult(
|
|
101
|
+
client.getTransactionReceipt({ hash: txHash })
|
|
102
|
+
);
|
|
103
|
+
if (error || !receipt) {
|
|
104
|
+
throw backendSdk.createInternalError(error);
|
|
105
|
+
}
|
|
106
|
+
return receipt;
|
|
107
|
+
}
|
|
108
|
+
async getBlock({
|
|
109
|
+
blockHash,
|
|
110
|
+
blockNumber,
|
|
111
|
+
blockTag
|
|
112
|
+
}) {
|
|
113
|
+
const client = this.createClient();
|
|
114
|
+
const [block, error] = await backendSdk.useResult(
|
|
115
|
+
blockHash !== void 0 ? client.getBlock({ blockHash }) : blockNumber !== void 0 ? client.getBlock({ blockNumber }) : client.getBlock({ blockTag: blockTag ?? "finalized" })
|
|
116
|
+
);
|
|
117
|
+
if (error || !block) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return block;
|
|
121
|
+
}
|
|
122
|
+
async getAllAddressTransactions({
|
|
123
|
+
address,
|
|
124
|
+
filter
|
|
125
|
+
}) {
|
|
126
|
+
const client = this.createClient();
|
|
127
|
+
const latestBlock = await client.getBlockNumber();
|
|
128
|
+
const transactions = [];
|
|
129
|
+
const allTransactionsFromContracts = await Promise.all(
|
|
130
|
+
this.tokens.map(async (token) => {
|
|
131
|
+
const events = await client.getContractEvents({
|
|
132
|
+
abi: TRANSFER_EVENT_ABI,
|
|
133
|
+
address: token.smartContractAddress,
|
|
134
|
+
eventName: "Transfer",
|
|
135
|
+
args: {
|
|
136
|
+
to: address
|
|
137
|
+
},
|
|
138
|
+
fromBlock: filter.fromBlock ?? latestBlock - 900n,
|
|
139
|
+
toBlock: "latest"
|
|
140
|
+
});
|
|
141
|
+
return { events, token };
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
for (const { events, token } of allTransactionsFromContracts) {
|
|
145
|
+
for (const tx of events) {
|
|
146
|
+
const [receipt, block] = await Promise.all([
|
|
147
|
+
client.getTransactionReceipt({ hash: tx.transactionHash }),
|
|
148
|
+
client.getBlock({ blockNumber: tx.blockNumber })
|
|
149
|
+
]);
|
|
150
|
+
transactions.push({
|
|
151
|
+
hash: tx.transactionHash,
|
|
152
|
+
from: tx.args.from ?? "",
|
|
153
|
+
to: tx.args.to ?? "",
|
|
154
|
+
value: (tx.args.value ?? 0n).toString(),
|
|
155
|
+
blockNumber: tx.blockNumber.toString(),
|
|
156
|
+
blockHash: tx.blockHash,
|
|
157
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
158
|
+
gasPrice: receipt.effectiveGasPrice?.toString() ?? "",
|
|
159
|
+
timestamp: Number(block.timestamp),
|
|
160
|
+
status: receipt.status,
|
|
161
|
+
decimals: token.decimals,
|
|
162
|
+
connector: this.connectorKey,
|
|
163
|
+
ticker: token.ticker
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { transactions, latestBlock };
|
|
168
|
+
}
|
|
169
|
+
async sendTransaction({
|
|
170
|
+
from,
|
|
171
|
+
to,
|
|
172
|
+
value,
|
|
173
|
+
token
|
|
174
|
+
}) {
|
|
175
|
+
const address = this.connectedAddresses.find(
|
|
176
|
+
(a) => a.number.toLowerCase() === from.toLowerCase()
|
|
177
|
+
);
|
|
178
|
+
if (!address) {
|
|
179
|
+
throw backendSdk.createInternalError(null, {
|
|
180
|
+
message: `Address not found: ${from}`
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (!this.resolveCredentials) {
|
|
184
|
+
throw backendSdk.createInternalError(null, {
|
|
185
|
+
message: `Credentials resolver not provided`
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const credentials = await this.resolveCredentials(address.id);
|
|
189
|
+
if (!credentials) {
|
|
190
|
+
throw backendSdk.createInternalError(null, {
|
|
191
|
+
message: `No private key found for address: ${from}`
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const account = accounts.privateKeyToAccount(credentials.value);
|
|
195
|
+
const walletClient = viem.createWalletClient({
|
|
196
|
+
account,
|
|
197
|
+
transport: viem.http(this.rpcUrl)
|
|
198
|
+
});
|
|
199
|
+
let hash;
|
|
200
|
+
if (token) {
|
|
201
|
+
const data = viem.encodeFunctionData({
|
|
202
|
+
abi: ERC20_TRANSFER_ABI,
|
|
203
|
+
functionName: "transfer",
|
|
204
|
+
args: [to, viem.parseUnits(value, token.decimals)]
|
|
205
|
+
});
|
|
206
|
+
hash = await walletClient.sendTransaction({
|
|
207
|
+
chain: null,
|
|
208
|
+
to: token.smartContractAddress,
|
|
209
|
+
data
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
hash = await walletClient.sendTransaction({
|
|
213
|
+
chain: null,
|
|
214
|
+
to,
|
|
215
|
+
value: BigInt(value)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return { hash };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
class ArbitrumConnector extends EvmConnector {
|
|
223
|
+
constructor(config) {
|
|
224
|
+
super(config);
|
|
225
|
+
this.connectorKey = "ARBITRUM";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
class AvalancheConnector extends EvmConnector {
|
|
230
|
+
constructor(config) {
|
|
231
|
+
super(config);
|
|
232
|
+
this.connectorKey = "AVALANCHE";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
class BaseConnector extends EvmConnector {
|
|
237
|
+
constructor(config) {
|
|
238
|
+
super(config);
|
|
239
|
+
this.connectorKey = "BASE";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
class EthereumConnector extends EvmConnector {
|
|
244
|
+
constructor(config) {
|
|
245
|
+
super(config);
|
|
246
|
+
this.connectorKey = "ETHEREUM";
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
class GnosisConnector extends EvmConnector {
|
|
251
|
+
constructor(config) {
|
|
252
|
+
super(config);
|
|
253
|
+
this.connectorKey = "GNOSIS";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
class OptimismConnector extends EvmConnector {
|
|
258
|
+
constructor(config) {
|
|
259
|
+
super(config);
|
|
260
|
+
this.connectorKey = "OPTIMISM";
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
class ScrollConnector extends EvmConnector {
|
|
265
|
+
constructor(config) {
|
|
266
|
+
super(config);
|
|
267
|
+
this.connectorKey = "SCROLL";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const initiateConnector = async ({
|
|
272
|
+
chain,
|
|
273
|
+
env,
|
|
274
|
+
tokens,
|
|
275
|
+
connectedAddresses,
|
|
276
|
+
resolveCredentials
|
|
277
|
+
}) => {
|
|
278
|
+
const getRpcUrl = async (secretName) => (await env.SECRETS_STORE.get({ secretName })).data?.secretValue || "";
|
|
279
|
+
switch (chain) {
|
|
280
|
+
case "ETHEREUM":
|
|
281
|
+
return new EthereumConnector({
|
|
282
|
+
RPC_URL: await getRpcUrl("BLOCKCHAIN_SERVICE_ETHEREUM_RPC_URL"),
|
|
283
|
+
tokens,
|
|
284
|
+
connectedAddresses,
|
|
285
|
+
resolveCredentials
|
|
286
|
+
});
|
|
287
|
+
case "ARBITRUM":
|
|
288
|
+
return new ArbitrumConnector({
|
|
289
|
+
RPC_URL: await getRpcUrl("BLOCKCHAIN_SERVICE_ARBITRUM_RPC_URL"),
|
|
290
|
+
tokens,
|
|
291
|
+
connectedAddresses,
|
|
292
|
+
resolveCredentials
|
|
293
|
+
});
|
|
294
|
+
case "AVALANCHE":
|
|
295
|
+
return new AvalancheConnector({
|
|
296
|
+
RPC_URL: await getRpcUrl("BLOCKCHAIN_SERVICE_AVALANCHE_RPC_URL"),
|
|
297
|
+
tokens,
|
|
298
|
+
connectedAddresses,
|
|
299
|
+
resolveCredentials
|
|
300
|
+
});
|
|
301
|
+
case "BASE":
|
|
302
|
+
return new BaseConnector({
|
|
303
|
+
RPC_URL: await getRpcUrl("BLOCKCHAIN_SERVICE_BASE_RPC_URL"),
|
|
304
|
+
tokens,
|
|
305
|
+
connectedAddresses,
|
|
306
|
+
resolveCredentials
|
|
307
|
+
});
|
|
308
|
+
case "GNOSIS":
|
|
309
|
+
return new GnosisConnector({
|
|
310
|
+
RPC_URL: await getRpcUrl("BLOCKCHAIN_SERVICE_GNOSIS_RPC_URL"),
|
|
311
|
+
tokens,
|
|
312
|
+
connectedAddresses,
|
|
313
|
+
resolveCredentials
|
|
314
|
+
});
|
|
315
|
+
case "OPTIMISM":
|
|
316
|
+
return new OptimismConnector({
|
|
317
|
+
RPC_URL: await getRpcUrl("BLOCKCHAIN_SERVICE_OPTIMISM_RPC_URL"),
|
|
318
|
+
tokens,
|
|
319
|
+
connectedAddresses,
|
|
320
|
+
resolveCredentials
|
|
321
|
+
});
|
|
322
|
+
case "SCROLL":
|
|
323
|
+
return new ScrollConnector({
|
|
324
|
+
RPC_URL: await getRpcUrl("BLOCKCHAIN_SERVICE_SCROLL_RPC_URL"),
|
|
325
|
+
tokens,
|
|
326
|
+
connectedAddresses,
|
|
327
|
+
resolveCredentials
|
|
328
|
+
});
|
|
329
|
+
default:
|
|
330
|
+
throw new Error(`Unsupported chain: ${chain}`);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
exports.initiateConnector = initiateConnector;
|
|
335
|
+
exports.tables = tables;
|