@ercworldio/blockchain-shared 1.0.0-dev.9 → 1.0.1-dev.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/build/chains/networks_dev.json +43 -10
- package/build/chains/networks_prod-bu.json +1 -1
- package/build/chains/networks_prod-dz.json +1 -1
- package/build/chains/networks_stg-bu.json +2 -2
- package/build/chains/networks_stg-dz.json +12 -1
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +13 -2
- package/build/interfaces/config.d.ts +1 -0
- package/build/interfaces/config.d.ts.map +1 -1
- package/build/interfaces.d.ts +2 -1
- package/build/interfaces.d.ts.map +1 -1
- package/build/services/AlchemyService.d.ts +1 -0
- package/build/services/AlchemyService.d.ts.map +1 -1
- package/build/services/AlchemyService.js +6 -2
- package/build/services/AlchemyWebhookAddressManager.d.ts +38 -0
- package/build/services/AlchemyWebhookAddressManager.d.ts.map +1 -0
- package/build/services/AlchemyWebhookAddressManager.js +123 -0
- package/build/services/BalanceService.d.ts +5 -1
- package/build/services/BalanceService.d.ts.map +1 -1
- package/build/services/BalanceService.js +32 -11
- package/build/services/ClaimJobService.d.ts.map +1 -1
- package/build/services/ClaimJobService.js +4 -1
- package/build/services/DepositAddressService.d.ts +8 -0
- package/build/services/DepositAddressService.d.ts.map +1 -1
- package/build/services/DepositAddressService.js +30 -0
- package/build/services/SweepJobService.d.ts.map +1 -1
- package/build/services/SweepJobService.js +4 -1
- package/build/services/WalletManager.d.ts +3 -1
- package/build/services/WalletManager.d.ts.map +1 -1
- package/build/services/WalletManager.js +11 -7
- package/build/services/WalletManagerHelper.d.ts +15 -0
- package/build/services/WalletManagerHelper.d.ts.map +1 -0
- package/build/services/WalletManagerHelper.js +65 -0
- package/build/services/quicknode-notifications/QnNotificationsApi.d.ts +20 -0
- package/build/services/quicknode-notifications/QnNotificationsApi.d.ts.map +1 -0
- package/build/services/quicknode-notifications/QnNotificationsApi.js +134 -0
- package/build/services/quicknode-notifications/QnWebhookAddressManager.d.ts +47 -0
- package/build/services/quicknode-notifications/QnWebhookAddressManager.d.ts.map +1 -0
- package/build/services/quicknode-notifications/QnWebhookAddressManager.js +265 -0
- package/build/services/quicknode-notifications/SetupNotifications.d.ts +40 -0
- package/build/services/quicknode-notifications/SetupNotifications.d.ts.map +1 -0
- package/build/services/quicknode-notifications/SetupNotifications.js +253 -0
- package/build/services/quicknode-notifications/types/index.d.ts +2 -0
- package/build/services/quicknode-notifications/types/index.d.ts.map +1 -0
- package/build/services/quicknode-notifications/types/index.js +17 -0
- package/build/services/quicknode-notifications/types/notification_types.d.ts +154 -0
- package/build/services/quicknode-notifications/types/notification_types.d.ts.map +1 -0
- package/build/services/quicknode-notifications/types/notification_types.js +2 -0
- package/build/services/solana/escrow/idl/escrow.json +110 -1
- package/build/services/solana/escrow/types/escrow.d.ts +110 -1
- package/build/services/solana/escrow/types/escrow.d.ts.map +1 -1
- package/build/services/types/chain_manager.d.ts +5 -0
- package/build/services/types/chain_manager.d.ts.map +1 -1
- package/build/services/types/quicknode.d.ts +1 -0
- package/build/services/types/quicknode.d.ts.map +1 -1
- package/build/utils/AsyncTTLCache.d.ts +1 -0
- package/build/utils/AsyncTTLCache.d.ts.map +1 -1
- package/build/utils/AsyncTTLCache.js +3 -0
- package/build/utils/solana.d.ts +1 -0
- package/build/utils/solana.d.ts.map +1 -1
- package/build/utils/solana.js +12 -5
- package/package.json +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const ChainManager_1 = __importDefault(require("../ChainManager"));
|
|
16
|
+
const QnNotificationsApi_1 = __importDefault(require("./QnNotificationsApi"));
|
|
17
|
+
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
18
|
+
class QnWebhookAddressManager {
|
|
19
|
+
constructor(config, rlc) {
|
|
20
|
+
this.cache = new Map();
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.api = QnNotificationsApi_1.default.getInstance(config, rlc);
|
|
23
|
+
}
|
|
24
|
+
static getInstance(config, rlc) {
|
|
25
|
+
if (!this.instance) {
|
|
26
|
+
this.instance = new QnWebhookAddressManager(config, rlc);
|
|
27
|
+
}
|
|
28
|
+
return this.instance;
|
|
29
|
+
}
|
|
30
|
+
get_blockchain_for_chain(chain_id) {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const cfg = (_a = ChainManager_1.default.getInstance(this.config).contractsMap) === null || _a === void 0 ? void 0 : _a[chain_id.toString()];
|
|
33
|
+
return (_b = cfg === null || cfg === void 0 ? void 0 : cfg.blockchainType) !== null && _b !== void 0 ? _b : null;
|
|
34
|
+
}
|
|
35
|
+
/** Returns the { name, network } pairs for the notification configs of a given chain. */
|
|
36
|
+
get_webhook_keys_for_chain(chain_id) {
|
|
37
|
+
var _a, _b, _c;
|
|
38
|
+
const cfg = (_a = ChainManager_1.default.getInstance(this.config).contractsMap) === null || _a === void 0 ? void 0 : _a[chain_id.toString()];
|
|
39
|
+
return (_c = (_b = cfg === null || cfg === void 0 ? void 0 : cfg.quicknodeNotificationConfig) === null || _b === void 0 ? void 0 : _b.map(n => ({
|
|
40
|
+
name: n.name.toLowerCase(),
|
|
41
|
+
network: n.network.toLowerCase(),
|
|
42
|
+
}))) !== null && _c !== void 0 ? _c : [];
|
|
43
|
+
}
|
|
44
|
+
get_notif_configs(blockchain) {
|
|
45
|
+
const chain_manager = ChainManager_1.default.getInstance(this.config);
|
|
46
|
+
if (!chain_manager.contractsMap)
|
|
47
|
+
return [];
|
|
48
|
+
return Object.entries(chain_manager.contractsMap)
|
|
49
|
+
.filter(([_, cfg]) => {
|
|
50
|
+
var _a;
|
|
51
|
+
return cfg.blockchainType === blockchain &&
|
|
52
|
+
((_a = cfg.quicknodeNotificationConfig) === null || _a === void 0 ? void 0 : _a.length) > 0;
|
|
53
|
+
})
|
|
54
|
+
.flatMap(([_, cfg]) => cfg.quicknodeNotificationConfig.map(n => (Object.assign(Object.assign({}, n), { blockchain: cfg.blockchainType }))))
|
|
55
|
+
.filter((n, i, arr) => arr.findIndex(x => x.name === n.name) === i);
|
|
56
|
+
}
|
|
57
|
+
is_stale(blockchain) {
|
|
58
|
+
const cached = this.cache.get(blockchain);
|
|
59
|
+
if (!cached)
|
|
60
|
+
return true;
|
|
61
|
+
return Date.now() - cached.loaded_at > CACHE_TTL_MS;
|
|
62
|
+
}
|
|
63
|
+
load(blockchain) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
var _a;
|
|
66
|
+
const notif_configs = this.get_notif_configs(blockchain);
|
|
67
|
+
if (notif_configs.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
let all_webhooks = [];
|
|
70
|
+
try {
|
|
71
|
+
const res = yield this.api.get_all_webhooks();
|
|
72
|
+
all_webhooks = res.data || [];
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error(`QnWebhookAddressManager: Failed to fetch webhooks for blockchain=${blockchain}`, error);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const entries = [];
|
|
79
|
+
for (const notif_config of notif_configs) {
|
|
80
|
+
const existing = all_webhooks.find(w => {
|
|
81
|
+
var _a, _b;
|
|
82
|
+
return ((_a = w.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === notif_config.name.toLowerCase() &&
|
|
83
|
+
((_b = w.network) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === notif_config.network.toLowerCase();
|
|
84
|
+
});
|
|
85
|
+
if (!existing) {
|
|
86
|
+
console.warn(`QnWebhookAddressManager: No webhook found for config=${notif_config.name} network=${notif_config.network}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const arg_key = notif_config.template === 'solanaWalletFilter' ? 'accounts' : 'wallets';
|
|
90
|
+
const addresses = ((_a = existing.templateArgs) === null || _a === void 0 ? void 0 : _a[arg_key]) || [];
|
|
91
|
+
entries.push({
|
|
92
|
+
webhook_id: existing.id,
|
|
93
|
+
name: notif_config.name.toLowerCase(),
|
|
94
|
+
template: notif_config.template,
|
|
95
|
+
network: notif_config.network.toLowerCase(),
|
|
96
|
+
address_set: new Set(addresses),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
this.cache.set(blockchain, { entries, loaded_at: Date.now() });
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
ensure_loaded(blockchain) {
|
|
103
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
var _a;
|
|
105
|
+
if (this.is_stale(blockchain)) {
|
|
106
|
+
yield this.load(blockchain);
|
|
107
|
+
}
|
|
108
|
+
return ((_a = this.cache.get(blockchain)) === null || _a === void 0 ? void 0 : _a.entries) || [];
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
matches_key(entry, key) {
|
|
112
|
+
return entry.name === key.name && entry.network === key.network;
|
|
113
|
+
}
|
|
114
|
+
add_address(blockchain, address) {
|
|
115
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
116
|
+
const entries = yield this.ensure_loaded(blockchain);
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
if (entry.address_set.has(address))
|
|
119
|
+
continue;
|
|
120
|
+
entry.address_set.add(address);
|
|
121
|
+
try {
|
|
122
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
123
|
+
console.log(`QnWebhookAddressManager: Added ${address} to ${entry.name} webhook`);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
entry.address_set.delete(address);
|
|
127
|
+
console.error(`QnWebhookAddressManager: Failed to add ${address} to ${entry.name}`, error);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
remove_address(blockchain, address) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
const entries = yield this.ensure_loaded(blockchain);
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
if (!entry.address_set.has(address))
|
|
139
|
+
continue;
|
|
140
|
+
entry.address_set.delete(address);
|
|
141
|
+
try {
|
|
142
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
143
|
+
console.log(`QnWebhookAddressManager: Removed ${address} from ${entry.name} webhook`);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
entry.address_set.add(address);
|
|
147
|
+
console.error(`QnWebhookAddressManager: Failed to remove ${address} from ${entry.name}`, error);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Add an address to the webhook(s) for a single chain only.
|
|
156
|
+
* Matches by both name and network to avoid cross-service contamination.
|
|
157
|
+
* Use this for the immediate await before responding to the caller.
|
|
158
|
+
*/
|
|
159
|
+
add_address_to_chain(chain_id, address) {
|
|
160
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
161
|
+
const blockchain = this.get_blockchain_for_chain(chain_id);
|
|
162
|
+
if (!blockchain)
|
|
163
|
+
return false;
|
|
164
|
+
const keys = this.get_webhook_keys_for_chain(chain_id);
|
|
165
|
+
const entries = (yield this.ensure_loaded(blockchain))
|
|
166
|
+
.filter(e => keys.some(k => this.matches_key(e, k)));
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
if (entry.address_set.has(address))
|
|
169
|
+
continue;
|
|
170
|
+
entry.address_set.add(address);
|
|
171
|
+
try {
|
|
172
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
173
|
+
console.log(`QnWebhookAddressManager: Added ${address} to ${entry.name} webhook (chain ${chain_id})`);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
entry.address_set.delete(address);
|
|
177
|
+
console.error(`QnWebhookAddressManager: Failed to add ${address} to ${entry.name}`, error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Remove an address from the webhook(s) for a single chain only.
|
|
186
|
+
* Matches by both name and network to avoid cross-service contamination.
|
|
187
|
+
* Use this for the immediate await before responding to the caller.
|
|
188
|
+
*/
|
|
189
|
+
remove_address_from_chain(chain_id, address) {
|
|
190
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
191
|
+
const blockchain = this.get_blockchain_for_chain(chain_id);
|
|
192
|
+
if (!blockchain)
|
|
193
|
+
return false;
|
|
194
|
+
const keys = this.get_webhook_keys_for_chain(chain_id);
|
|
195
|
+
const entries = (yield this.ensure_loaded(blockchain))
|
|
196
|
+
.filter(e => keys.some(k => this.matches_key(e, k)));
|
|
197
|
+
for (const entry of entries) {
|
|
198
|
+
if (!entry.address_set.has(address))
|
|
199
|
+
continue;
|
|
200
|
+
entry.address_set.delete(address);
|
|
201
|
+
try {
|
|
202
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
203
|
+
console.log(`QnWebhookAddressManager: Removed ${address} from ${entry.name} webhook (chain ${chain_id})`);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
entry.address_set.add(address);
|
|
207
|
+
console.error(`QnWebhookAddressManager: Failed to remove ${address} from ${entry.name}`, error);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Add an address to all webhooks for a blockchain except the already-updated chain.
|
|
216
|
+
* Excludes by both name and network so only the exact webhooks for that chain are skipped.
|
|
217
|
+
* Call this fire-and-forget after responding to the caller.
|
|
218
|
+
*/
|
|
219
|
+
sync_add_to_remaining(blockchain, address, exclude_chain_id) {
|
|
220
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
221
|
+
const exclude_keys = this.get_webhook_keys_for_chain(exclude_chain_id);
|
|
222
|
+
const entries = (yield this.ensure_loaded(blockchain))
|
|
223
|
+
.filter(e => !exclude_keys.some(k => this.matches_key(e, k)));
|
|
224
|
+
for (const entry of entries) {
|
|
225
|
+
if (entry.address_set.has(address))
|
|
226
|
+
continue;
|
|
227
|
+
entry.address_set.add(address);
|
|
228
|
+
try {
|
|
229
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
230
|
+
console.log(`QnWebhookAddressManager: Synced add ${address} to ${entry.name} webhook`);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
entry.address_set.delete(address);
|
|
234
|
+
console.error(`QnWebhookAddressManager: Sync add failed for ${entry.name}`, error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Remove an address from all webhooks for a blockchain except the already-updated chain.
|
|
241
|
+
* Excludes by both name and network so only the exact webhooks for that chain are skipped.
|
|
242
|
+
* Call this fire-and-forget after responding to the caller.
|
|
243
|
+
*/
|
|
244
|
+
sync_remove_from_remaining(blockchain, address, exclude_chain_id) {
|
|
245
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
246
|
+
const exclude_keys = this.get_webhook_keys_for_chain(exclude_chain_id);
|
|
247
|
+
const entries = (yield this.ensure_loaded(blockchain))
|
|
248
|
+
.filter(e => !exclude_keys.some(k => this.matches_key(e, k)));
|
|
249
|
+
for (const entry of entries) {
|
|
250
|
+
if (!entry.address_set.has(address))
|
|
251
|
+
continue;
|
|
252
|
+
entry.address_set.delete(address);
|
|
253
|
+
try {
|
|
254
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
255
|
+
console.log(`QnWebhookAddressManager: Synced remove ${address} from ${entry.name} webhook`);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
entry.address_set.add(address);
|
|
259
|
+
console.error(`QnWebhookAddressManager: Sync remove failed for ${entry.name}`, error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
exports.default = QnWebhookAddressManager;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import BaseErrors from "../../errors/errors";
|
|
2
|
+
import { BlockchainType } from "../../interfaces";
|
|
3
|
+
import { IConfig } from "../../interfaces/config";
|
|
4
|
+
import { IDatabasePool } from "../../interfaces/database";
|
|
5
|
+
import { RateLimiterOptions } from "../../utils/AsyncRateLimiter";
|
|
6
|
+
export interface NotificationWebhookEntry {
|
|
7
|
+
webhook_id: string;
|
|
8
|
+
template: string;
|
|
9
|
+
network: string;
|
|
10
|
+
blockchain: BlockchainType;
|
|
11
|
+
address_set: Set<string>;
|
|
12
|
+
}
|
|
13
|
+
declare class SetupNotifications extends BaseErrors {
|
|
14
|
+
private static instance;
|
|
15
|
+
private api;
|
|
16
|
+
private webhook_map;
|
|
17
|
+
private by_blockchain;
|
|
18
|
+
private pending_configs;
|
|
19
|
+
private config;
|
|
20
|
+
private db;
|
|
21
|
+
private constructor();
|
|
22
|
+
static getInstance(config: IConfig, db: IDatabasePool, rlc: RateLimiterOptions): SetupNotifications;
|
|
23
|
+
get_all_webhook_ids(): string[];
|
|
24
|
+
get_entry_by_webhook_id(id: string): NotificationWebhookEntry | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Fetch all deposit addresses for a blockchain from the DB (paginated).
|
|
27
|
+
* The `blockchain` filter value in DepositAddressService is the string stored
|
|
28
|
+
* in the DB — e.g. "solana", "evm", "ethereum", etc.
|
|
29
|
+
*/
|
|
30
|
+
private fetch_all_db_addresses;
|
|
31
|
+
/**
|
|
32
|
+
* Register a newly-created webhook entry in the internal maps.
|
|
33
|
+
*/
|
|
34
|
+
private register_entry;
|
|
35
|
+
initialize(): Promise<void>;
|
|
36
|
+
add_address(blockchain: BlockchainType, address: string): Promise<void>;
|
|
37
|
+
remove_address(blockchain: BlockchainType, address: string): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export default SetupNotifications;
|
|
40
|
+
//# sourceMappingURL=SetupNotifications.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SetupNotifications.d.ts","sourceRoot":"","sources":["../../../src/services/quicknode-notifications/SetupNotifications.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAiB,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAQlE,MAAM,WAAW,wBAAwB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,cAAc,CAAC;IAC3B,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAWD,cAAM,kBAAmB,SAAQ,UAAU;IACvC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAqB;IAC5C,OAAO,CAAC,GAAG,CAAqB;IAEhC,OAAO,CAAC,WAAW,CAAoD;IAEvE,OAAO,CAAC,aAAa,CAA8D;IAEnF,OAAO,CAAC,eAAe,CAAqD;IAC5E,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO;IAOP,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,kBAAkB,GAAG,kBAAkB;IAO5F,mBAAmB,IAAI,MAAM,EAAE;IAI/B,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS;IAOhF;;;;OAIG;YACW,sBAAsB;IAuBpC;;OAEG;IACH,OAAO,CAAC,cAAc;IAOT,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAwH3B,WAAW,CAAC,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyDvE,cAAc,CAAC,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAe1F;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const errors_1 = __importDefault(require("../../errors/errors"));
|
|
16
|
+
const ChainManager_1 = __importDefault(require("../ChainManager"));
|
|
17
|
+
const DepositAddressService_1 = __importDefault(require("../DepositAddressService"));
|
|
18
|
+
const QuicknodeWebhookSignature_1 = __importDefault(require("../QuicknodeWebhookSignature"));
|
|
19
|
+
const QnNotificationsApi_1 = __importDefault(require("./QnNotificationsApi"));
|
|
20
|
+
const ADDRESS_SYNC_BATCH_SIZE = 500;
|
|
21
|
+
const ADDRESS_LOG_THRESHOLD = 1000;
|
|
22
|
+
class SetupNotifications extends errors_1.default {
|
|
23
|
+
constructor(config, db, rlc) {
|
|
24
|
+
super();
|
|
25
|
+
// keyed by notification config name (unique per network)
|
|
26
|
+
this.webhook_map = new Map();
|
|
27
|
+
// keyed by blockchain type → list of notification entries for that blockchain
|
|
28
|
+
this.by_blockchain = new Map();
|
|
29
|
+
// configs deferred because no addresses existed at startup
|
|
30
|
+
this.pending_configs = new Map();
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.db = db;
|
|
33
|
+
this.api = QnNotificationsApi_1.default.getInstance(config, rlc);
|
|
34
|
+
}
|
|
35
|
+
static getInstance(config, db, rlc) {
|
|
36
|
+
if (!this.instance) {
|
|
37
|
+
this.instance = new SetupNotifications(config, db, rlc);
|
|
38
|
+
}
|
|
39
|
+
return this.instance;
|
|
40
|
+
}
|
|
41
|
+
get_all_webhook_ids() {
|
|
42
|
+
return Array.from(this.webhook_map.values()).map(e => e.webhook_id);
|
|
43
|
+
}
|
|
44
|
+
get_entry_by_webhook_id(id) {
|
|
45
|
+
for (const entry of this.webhook_map.values()) {
|
|
46
|
+
if (entry.webhook_id === id)
|
|
47
|
+
return entry;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Fetch all deposit addresses for a blockchain from the DB (paginated).
|
|
53
|
+
* The `blockchain` filter value in DepositAddressService is the string stored
|
|
54
|
+
* in the DB — e.g. "solana", "evm", "ethereum", etc.
|
|
55
|
+
*/
|
|
56
|
+
fetch_all_db_addresses(blockchain) {
|
|
57
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
const service = new DepositAddressService_1.default(this.db);
|
|
59
|
+
const addresses = [];
|
|
60
|
+
let page = 1;
|
|
61
|
+
const page_size = ADDRESS_SYNC_BATCH_SIZE;
|
|
62
|
+
while (true) {
|
|
63
|
+
const result = yield service.getDepositAddressesPaginated(page, page_size, { blockchain });
|
|
64
|
+
for (const item of result.items) {
|
|
65
|
+
addresses.push(item.address);
|
|
66
|
+
}
|
|
67
|
+
if (!result.has_next)
|
|
68
|
+
break;
|
|
69
|
+
page++;
|
|
70
|
+
}
|
|
71
|
+
if (addresses.length > ADDRESS_LOG_THRESHOLD) {
|
|
72
|
+
console.warn(`SetupNotifications: ${addresses.length} addresses fetched for blockchain=${blockchain} — ensure QN webhook limit is sufficient`);
|
|
73
|
+
}
|
|
74
|
+
return addresses;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register a newly-created webhook entry in the internal maps.
|
|
79
|
+
*/
|
|
80
|
+
register_entry(config_name, entry) {
|
|
81
|
+
this.webhook_map.set(config_name, entry);
|
|
82
|
+
const existing_by_bc = this.by_blockchain.get(entry.blockchain) || [];
|
|
83
|
+
existing_by_bc.push(entry);
|
|
84
|
+
this.by_blockchain.set(entry.blockchain, existing_by_bc);
|
|
85
|
+
}
|
|
86
|
+
initialize() {
|
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
var _a;
|
|
89
|
+
try {
|
|
90
|
+
const chain_manager = ChainManager_1.default.getInstance(this.config);
|
|
91
|
+
if (!chain_manager.contractsMap)
|
|
92
|
+
return;
|
|
93
|
+
// Collect all required notification configs from chain config
|
|
94
|
+
const required_notifications = Object.entries(chain_manager.contractsMap)
|
|
95
|
+
.filter(([_, cfg]) => { var _a; return ((_a = cfg.quicknodeNotificationConfig) === null || _a === void 0 ? void 0 : _a.length) > 0; })
|
|
96
|
+
.flatMap(([_, cfg]) => cfg.quicknodeNotificationConfig.map(n => (Object.assign(Object.assign({}, n), { blockchain: cfg.blockchainType }))));
|
|
97
|
+
if (required_notifications.length === 0) {
|
|
98
|
+
console.log(`SetupNotifications: No quicknodeNotifications config found. Skipping.`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Deduplicate by name (multiple chains may share the same notification config name)
|
|
102
|
+
const unique_notifications = required_notifications.filter((n, i, arr) => arr.findIndex(x => x.name === n.name) === i);
|
|
103
|
+
// Fetch existing webhooks
|
|
104
|
+
let existing_webhooks = [];
|
|
105
|
+
try {
|
|
106
|
+
const res = yield this.api.get_all_webhooks();
|
|
107
|
+
existing_webhooks = res.data || [];
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error(`SetupNotifications: Failed to fetch existing webhooks`, error);
|
|
111
|
+
}
|
|
112
|
+
const host = this.config.config.useTunnel ? this.config.config.tunnel : this.config.config.host;
|
|
113
|
+
const webhook_url = `${host}/webhook/quicknode/notifications`;
|
|
114
|
+
for (const notif_config of unique_notifications) {
|
|
115
|
+
const existing = existing_webhooks.find(w => {
|
|
116
|
+
var _a, _b;
|
|
117
|
+
return ((_a = w.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === notif_config.name.toLowerCase() &&
|
|
118
|
+
((_b = w.network) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === notif_config.network.toLowerCase();
|
|
119
|
+
});
|
|
120
|
+
// Fetch current addresses from the DB for this blockchain
|
|
121
|
+
const db_addresses = yield this.fetch_all_db_addresses(notif_config.blockchain);
|
|
122
|
+
console.log(`SetupNotifications: ${db_addresses.length} DB addresses for blockchain=${notif_config.blockchain}`);
|
|
123
|
+
let webhook_id;
|
|
124
|
+
if (existing) {
|
|
125
|
+
webhook_id = existing.id;
|
|
126
|
+
console.log(`SetupNotifications: Found existing webhook for ${notif_config.name} (id: ${webhook_id})`);
|
|
127
|
+
// Sync the webhook with the full DB address list to fix any drift
|
|
128
|
+
if (db_addresses.length > 0) {
|
|
129
|
+
try {
|
|
130
|
+
yield this.api.update_webhook_addresses(webhook_id, notif_config.template, db_addresses);
|
|
131
|
+
console.log(`SetupNotifications: Synced ${db_addresses.length} addresses to existing webhook ${notif_config.name}`);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error(`SetupNotifications: Failed to sync addresses to webhook ${notif_config.name}`, err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const entry = {
|
|
138
|
+
webhook_id,
|
|
139
|
+
template: notif_config.template,
|
|
140
|
+
network: notif_config.network,
|
|
141
|
+
blockchain: notif_config.blockchain,
|
|
142
|
+
address_set: new Set(db_addresses),
|
|
143
|
+
};
|
|
144
|
+
this.register_entry(notif_config.name, entry);
|
|
145
|
+
}
|
|
146
|
+
else if (db_addresses.length === 0) {
|
|
147
|
+
// QN requires at least one address to create a webhook — defer until first address arrives
|
|
148
|
+
console.log(`SetupNotifications: No addresses for ${notif_config.name} — deferring webhook creation until first address is added`);
|
|
149
|
+
this.pending_configs.set(notif_config.name, { notif_config, webhook_url });
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Create webhook with DB addresses as the initial set
|
|
153
|
+
console.log(`SetupNotifications: Creating webhook for ${notif_config.name} on ${notif_config.network} with ${db_addresses.length} initial addresses`);
|
|
154
|
+
try {
|
|
155
|
+
const res = yield this.api.create_webhook(notif_config.template, notif_config.name, notif_config.network, webhook_url, db_addresses);
|
|
156
|
+
webhook_id = res.id;
|
|
157
|
+
if ((_a = res.destination_attributes) === null || _a === void 0 ? void 0 : _a.security_token) {
|
|
158
|
+
yield QuicknodeWebhookSignature_1.default.getInstance().createSigningKey(this.config.config.keyVaultName, webhook_id, res.destination_attributes.security_token);
|
|
159
|
+
console.log(`SetupNotifications: Stored signing key for webhook ${webhook_id}`);
|
|
160
|
+
}
|
|
161
|
+
console.log(`SetupNotifications: Created webhook ${notif_config.name} (id: ${webhook_id})`);
|
|
162
|
+
const entry = {
|
|
163
|
+
webhook_id,
|
|
164
|
+
template: notif_config.template,
|
|
165
|
+
network: notif_config.network,
|
|
166
|
+
blockchain: notif_config.blockchain,
|
|
167
|
+
address_set: new Set(db_addresses),
|
|
168
|
+
};
|
|
169
|
+
this.register_entry(notif_config.name, entry);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
console.error(`SetupNotifications: Failed to create webhook for ${notif_config.name}`, err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
console.log(`SetupNotifications: Initialized ${this.webhook_map.size} notification webhooks, ${this.pending_configs.size} deferred.`);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error(`SetupNotifications: Error during initialization`, error);
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
add_address(blockchain, address) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
var _a;
|
|
187
|
+
// If any pending configs exist for this blockchain, create the webhook now
|
|
188
|
+
for (const [config_name, pending] of this.pending_configs.entries()) {
|
|
189
|
+
if (pending.notif_config.blockchain !== blockchain)
|
|
190
|
+
continue;
|
|
191
|
+
console.log(`SetupNotifications: Lazily creating deferred webhook ${config_name} for first address ${address}`);
|
|
192
|
+
try {
|
|
193
|
+
const res = yield this.api.create_webhook(pending.notif_config.template, pending.notif_config.name, pending.notif_config.network, pending.webhook_url, [address]);
|
|
194
|
+
if ((_a = res.destination_attributes) === null || _a === void 0 ? void 0 : _a.security_token) {
|
|
195
|
+
yield QuicknodeWebhookSignature_1.default.getInstance().createSigningKey(this.config.config.keyVaultName, res.id, res.destination_attributes.security_token);
|
|
196
|
+
}
|
|
197
|
+
const entry = {
|
|
198
|
+
webhook_id: res.id,
|
|
199
|
+
template: pending.notif_config.template,
|
|
200
|
+
network: pending.notif_config.network,
|
|
201
|
+
blockchain: pending.notif_config.blockchain,
|
|
202
|
+
address_set: new Set([address]),
|
|
203
|
+
};
|
|
204
|
+
this.register_entry(config_name, entry);
|
|
205
|
+
this.pending_configs.delete(config_name);
|
|
206
|
+
console.log(`SetupNotifications: Lazily created webhook ${config_name} (id: ${res.id})`);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
console.error(`SetupNotifications: Failed to lazily create webhook ${config_name}`, err);
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
// After lazy creation the address is already in the entry — continue to next pending
|
|
213
|
+
// but skip the update_webhook_addresses call below for this entry since it's already set
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const entries = this.by_blockchain.get(blockchain) || [];
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
if (entry.address_set.has(address))
|
|
219
|
+
continue;
|
|
220
|
+
entry.address_set.add(address);
|
|
221
|
+
try {
|
|
222
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
223
|
+
console.log(`SetupNotifications: Added ${address} to ${entry.network} webhook`);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
entry.address_set.delete(address); // rollback on failure
|
|
227
|
+
console.error(`SetupNotifications: Failed to add ${address} to ${entry.network}`, error);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
remove_address(blockchain, address) {
|
|
234
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
const entries = this.by_blockchain.get(blockchain) || [];
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
if (!entry.address_set.has(address))
|
|
238
|
+
continue;
|
|
239
|
+
entry.address_set.delete(address);
|
|
240
|
+
try {
|
|
241
|
+
yield this.api.update_webhook_addresses(entry.webhook_id, entry.template, Array.from(entry.address_set));
|
|
242
|
+
console.log(`SetupNotifications: Removed ${address} from ${entry.network} webhook`);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
entry.address_set.add(address); // rollback on failure
|
|
246
|
+
console.error(`SetupNotifications: Failed to remove ${address} from ${entry.network}`, error);
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
exports.default = SetupNotifications;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/services/quicknode-notifications/types/index.ts"],"names":[],"mappings":"AACA,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./notification_types"), exports);
|