@dignetwork/dig-sdk 0.0.1-alpha.15 → 0.0.1-alpha.150
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/DataIntegrityTree/DataIntegrityTree.d.ts +11 -2
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.js +117 -58
- package/dist/DigNetwork/ContentServer.d.ts +12 -5
- package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
- package/dist/DigNetwork/ContentServer.js +172 -24
- package/dist/DigNetwork/DigNetwork.d.ts +15 -9
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +146 -189
- package/dist/DigNetwork/DigPeer.d.ts +8 -3
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +68 -115
- package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
- package/dist/DigNetwork/IncentiveServer.js +6 -5
- package/dist/DigNetwork/PropagationServer.d.ts +83 -33
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +605 -356
- package/dist/blockchain/DataStore.d.ts +25 -7
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +114 -159
- package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
- package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.d.ts +94 -10
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +325 -100
- package/dist/blockchain/ServerCoin.d.ts +13 -5
- package/dist/blockchain/ServerCoin.d.ts.map +1 -1
- package/dist/blockchain/ServerCoin.js +90 -28
- package/dist/blockchain/StoreMonitorRegistry.d.ts +85 -0
- package/dist/blockchain/StoreMonitorRegistry.d.ts.map +1 -0
- package/dist/blockchain/StoreMonitorRegistry.js +241 -0
- package/dist/blockchain/Wallet.d.ts +2 -2
- package/dist/blockchain/Wallet.d.ts.map +1 -1
- package/dist/blockchain/Wallet.js +50 -18
- package/dist/blockchain/coins.d.ts +1 -2
- package/dist/blockchain/coins.d.ts.map +1 -1
- package/dist/blockchain/coins.js +1 -55
- package/dist/blockchain/index.d.ts +1 -0
- package/dist/blockchain/index.d.ts.map +1 -1
- package/dist/blockchain/index.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/ContentScanner.d.ts +63 -0
- package/dist/utils/ContentScanner.d.ts.map +1 -0
- package/dist/utils/ContentScanner.js +175 -0
- package/dist/utils/Environment.d.ts +21 -0
- package/dist/utils/Environment.d.ts.map +1 -0
- package/dist/utils/Environment.js +92 -0
- package/dist/utils/FileCache.d.ts +1 -1
- package/dist/utils/FileCache.d.ts.map +1 -1
- package/dist/utils/FileCache.js +2 -2
- package/dist/utils/FileTransfer.d.ts +47 -0
- package/dist/utils/FileTransfer.d.ts.map +1 -0
- package/dist/utils/FileTransfer.js +209 -0
- package/dist/utils/NconfManager.js +2 -1
- package/dist/utils/PeerRanker.d.ts +53 -0
- package/dist/utils/PeerRanker.d.ts.map +1 -0
- package/dist/utils/PeerRanker.js +171 -0
- package/dist/utils/StoreArchiveManager.d.ts +45 -0
- package/dist/utils/StoreArchiveManager.d.ts.map +1 -0
- package/dist/utils/StoreArchiveManager.js +153 -0
- package/dist/utils/config.d.ts +4 -3
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -7
- package/dist/utils/directoryUtils.d.ts.map +1 -1
- package/dist/utils/directoryUtils.js +9 -12
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/merkle.d.ts +2 -0
- package/dist/utils/merkle.d.ts.map +1 -0
- package/dist/utils/merkle.js +28 -0
- package/dist/utils/network.d.ts +1 -0
- package/dist/utils/network.d.ts.map +1 -1
- package/dist/utils/network.js +38 -11
- package/dist/utils/promiseUtils.d.ts +19 -0
- package/dist/utils/promiseUtils.d.ts.map +1 -0
- package/dist/utils/promiseUtils.js +43 -0
- package/dist/utils/ssl.js +2 -2
- package/package.json +17 -5
- package/dist/utils/deltaUtils.d.ts +0 -2
- package/dist/utils/deltaUtils.d.ts.map +0 -1
- package/dist/utils/deltaUtils.js +0 -83
|
@@ -5,27 +5,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ServerCoin = void 0;
|
|
7
7
|
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
-
const datalayer_driver_1 = require("datalayer-driver");
|
|
8
|
+
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
9
9
|
const FullNodePeer_1 = require("./FullNodePeer");
|
|
10
|
-
const coins_1 = require("./coins");
|
|
11
10
|
const Wallet_1 = require("./Wallet");
|
|
12
11
|
const NconfManager_1 = require("../utils/NconfManager");
|
|
13
12
|
const DataStore_1 = require("./DataStore");
|
|
13
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
14
|
+
const network_1 = require("../utils/network");
|
|
15
|
+
const Environment_1 = require("../utils/Environment");
|
|
14
16
|
const serverCoinCollateral = 300000000;
|
|
17
|
+
// Initialize the cache with a TTL of 300 seconds (5 minutes)
|
|
18
|
+
const serverCoinPeersCache = new node_cache_1.default({ stdTTL: 300 });
|
|
15
19
|
class ServerCoin {
|
|
16
20
|
constructor(storeId) {
|
|
17
21
|
this.storeId = storeId;
|
|
18
22
|
}
|
|
19
23
|
// Create a new server coin for the current epoch
|
|
20
|
-
async createForEpoch(peerIp) {
|
|
24
|
+
async createForEpoch(peerIp, rootHash) {
|
|
21
25
|
try {
|
|
22
26
|
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
23
27
|
const wallet = await Wallet_1.Wallet.load("default");
|
|
24
28
|
const publicSyntheticKey = await wallet.getPublicSyntheticKey();
|
|
25
|
-
const serverCoinCreationCoins = await
|
|
26
|
-
const currentEpoch = ServerCoin.getCurrentEpoch();
|
|
29
|
+
const serverCoinCreationCoins = await wallet.selectUnspentCoins(peer, BigInt(serverCoinCollateral), BigInt(1000000));
|
|
30
|
+
const { epoch: currentEpoch } = ServerCoin.getCurrentEpoch();
|
|
27
31
|
const epochBasedHint = (0, datalayer_driver_1.morphLauncherId)(Buffer.from(this.storeId, "hex"), BigInt(currentEpoch));
|
|
28
|
-
const newServerCoin = (0, datalayer_driver_1.createServerCoin)(publicSyntheticKey, serverCoinCreationCoins, epochBasedHint, [peerIp], BigInt(serverCoinCollateral), BigInt(1000000));
|
|
32
|
+
const newServerCoin = (0, datalayer_driver_1.createServerCoin)(publicSyntheticKey, serverCoinCreationCoins, epochBasedHint, [peerIp, publicSyntheticKey.toString("hex")], BigInt(serverCoinCollateral), BigInt(1000000));
|
|
29
33
|
const combinedCoinSpends = [...newServerCoin.coinSpends];
|
|
30
34
|
const sig = (0, datalayer_driver_1.signCoinSpends)(combinedCoinSpends, [await wallet.getPrivateSyntheticKey()], false);
|
|
31
35
|
const err = await peer.broadcastSpend(combinedCoinSpends, [sig]);
|
|
@@ -33,12 +37,12 @@ class ServerCoin {
|
|
|
33
37
|
if (err.includes("no spendable coins")) {
|
|
34
38
|
console.log("No coins available. Will try again in 5 seconds...");
|
|
35
39
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
36
|
-
return this.createForEpoch(peerIp);
|
|
40
|
+
return this.createForEpoch(peerIp, rootHash);
|
|
37
41
|
}
|
|
38
42
|
throw new Error(err);
|
|
39
43
|
}
|
|
40
44
|
// Cache the new server coin in the NconfManager
|
|
41
|
-
await this.saveServerCoinData(newServerCoin.serverCoin, currentEpoch, peerIp);
|
|
45
|
+
await this.saveServerCoinData(newServerCoin.serverCoin, currentEpoch, peerIp, rootHash);
|
|
42
46
|
return newServerCoin.serverCoin;
|
|
43
47
|
}
|
|
44
48
|
catch (error) {
|
|
@@ -46,7 +50,7 @@ class ServerCoin {
|
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
// Save the server coin data to NconfManager
|
|
49
|
-
async saveServerCoinData(serverCoin, epoch, peerIp) {
|
|
53
|
+
async saveServerCoinData(serverCoin, epoch, peerIp, rootHash) {
|
|
50
54
|
const newServerCoinData = {
|
|
51
55
|
coin: {
|
|
52
56
|
amount: serverCoin.coin.amount.toString(),
|
|
@@ -54,7 +58,8 @@ class ServerCoin {
|
|
|
54
58
|
parentCoinInfo: serverCoin.coin.parentCoinInfo.toString("hex"),
|
|
55
59
|
},
|
|
56
60
|
createdAt: new Date().toISOString(),
|
|
57
|
-
epoch
|
|
61
|
+
epoch,
|
|
62
|
+
rootHash,
|
|
58
63
|
};
|
|
59
64
|
const serverCoins = await this.getServerCoinsForStore(peerIp);
|
|
60
65
|
serverCoins.push(newServerCoinData);
|
|
@@ -70,7 +75,7 @@ class ServerCoin {
|
|
|
70
75
|
if (!serverCoin) {
|
|
71
76
|
throw new Error(`No server coin found for epoch ${epoch} in store ${this.storeId}.`);
|
|
72
77
|
}
|
|
73
|
-
const feeCoins = await
|
|
78
|
+
const feeCoins = await wallet.selectUnspentCoins(peer, BigInt(0), BigInt(1000000));
|
|
74
79
|
const coin = {
|
|
75
80
|
amount: BigInt(serverCoin.coin.amount),
|
|
76
81
|
puzzleHash: Buffer.from(serverCoin.coin.puzzleHash, "hex"),
|
|
@@ -94,13 +99,13 @@ class ServerCoin {
|
|
|
94
99
|
serverCoins = serverCoins.filter((coin) => coin.coin.parentCoinInfo !== serverCoin.parentCoinInfo);
|
|
95
100
|
await ServerCoin.serverCoinManager.setConfigValue(`${this.storeId}:${peerIp}`, serverCoins);
|
|
96
101
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
async getAllEpochPeers(epoch, blacklist = []) {
|
|
103
|
+
const cacheKey = `serverCoinPeers-${this.storeId}-${epoch}`;
|
|
104
|
+
// Check if the result is already cached
|
|
105
|
+
const cachedPeers = serverCoinPeersCache.get(cacheKey);
|
|
106
|
+
if (cachedPeers) {
|
|
107
|
+
return cachedPeers;
|
|
108
|
+
}
|
|
104
109
|
const epochBasedHint = (0, datalayer_driver_1.morphLauncherId)(Buffer.from(this.storeId, "hex"), BigInt(epoch));
|
|
105
110
|
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
106
111
|
const maxClvmCost = BigInt(11000000000);
|
|
@@ -111,33 +116,83 @@ class ServerCoin {
|
|
|
111
116
|
for (const coinState of filteredCoinStates) {
|
|
112
117
|
const serverCoin = await peer.fetchServerCoin(coinState, maxClvmCost);
|
|
113
118
|
const peerUrl = serverCoin.memoUrls[0];
|
|
119
|
+
// The second memo URL is the public key
|
|
120
|
+
// We will utilize this for future features
|
|
121
|
+
const publicKey = serverCoin.memoUrls[1];
|
|
114
122
|
if (!blacklist.includes(peerUrl)) {
|
|
115
123
|
serverCoinPeers.add(peerUrl);
|
|
116
124
|
}
|
|
117
125
|
}
|
|
118
|
-
if (
|
|
126
|
+
if (Environment_1.Environment.DEBUG) {
|
|
127
|
+
console.log("Server Coin Peers: ", serverCoinPeers);
|
|
128
|
+
}
|
|
129
|
+
const peerList = Array.from(serverCoinPeers);
|
|
130
|
+
// Cache the result
|
|
131
|
+
serverCoinPeersCache.set(cacheKey, peerList);
|
|
132
|
+
return peerList;
|
|
133
|
+
}
|
|
134
|
+
async getActiveEpochPeers(blacklist = []) {
|
|
135
|
+
const { epoch } = ServerCoin.getCurrentEpoch();
|
|
136
|
+
return this.getAllEpochPeers(epoch, blacklist);
|
|
137
|
+
}
|
|
138
|
+
// Sample server coins for the current epoch
|
|
139
|
+
async sampleCurrentEpoch(sampleSize = 5, blacklist = []) {
|
|
140
|
+
const { epoch } = ServerCoin.getCurrentEpoch();
|
|
141
|
+
return this.sampleServerCoinsByEpoch(epoch, sampleSize, blacklist);
|
|
142
|
+
}
|
|
143
|
+
// Sample server coins by epoch
|
|
144
|
+
async sampleServerCoinsByEpoch(epoch, sampleSize = 5, blacklist = []) {
|
|
145
|
+
// We dont want our own IP to be included
|
|
146
|
+
const myIp = await (0, network_1.getPublicIpAddress)();
|
|
147
|
+
if (myIp) {
|
|
148
|
+
blacklist.push(myIp);
|
|
149
|
+
}
|
|
150
|
+
const serverCoinPeers = await this.getAllEpochPeers(epoch, blacklist);
|
|
151
|
+
if (Environment_1.Environment.DEBUG) {
|
|
119
152
|
console.log("Server Coin Peers: ", serverCoinPeers);
|
|
120
153
|
}
|
|
121
|
-
|
|
122
|
-
return lodash_1.default.sampleSize(Array.from(serverCoinPeers), sampleSize);
|
|
154
|
+
return lodash_1.default.sampleSize(serverCoinPeers, sampleSize);
|
|
123
155
|
}
|
|
124
156
|
// Get the current epoch based on the current timestamp
|
|
125
157
|
static getCurrentEpoch() {
|
|
126
|
-
return ServerCoin.
|
|
158
|
+
return ServerCoin.calculateEpochAndRound(new Date());
|
|
127
159
|
}
|
|
128
160
|
// Ensure server coin exists for the current epoch
|
|
129
161
|
async ensureServerCoinExists(peerIp) {
|
|
130
162
|
try {
|
|
131
163
|
console.log(`Ensuring server coin exists for store ${this.storeId}...`);
|
|
132
|
-
const currentEpoch = ServerCoin.getCurrentEpoch();
|
|
164
|
+
const { epoch: currentEpoch } = ServerCoin.getCurrentEpoch();
|
|
133
165
|
const serverCoins = await this.getServerCoinsForStore(peerIp);
|
|
134
166
|
// Check if a server coin already exists for the current epoch
|
|
135
167
|
const existingCoin = serverCoins.find((coin) => coin.epoch === currentEpoch);
|
|
168
|
+
const dataStore = DataStore_1.DataStore.from(this.storeId);
|
|
169
|
+
const rootHistory = await dataStore.getRootHistory(true);
|
|
170
|
+
const lastRoot = lodash_1.default.last(rootHistory);
|
|
171
|
+
if (!lastRoot) {
|
|
172
|
+
throw new Error('Cant get the last root on chain, something is wrong.');
|
|
173
|
+
}
|
|
136
174
|
if (existingCoin) {
|
|
175
|
+
// nothing to do
|
|
176
|
+
if (lastRoot?.synced && lastRoot?.root_hash === existingCoin.rootHash) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// everything is fine, lets just update the roothash for tracking
|
|
180
|
+
if (lastRoot?.synced && lastRoot?.root_hash !== existingCoin.rootHash) {
|
|
181
|
+
existingCoin.rootHash = lastRoot.root_hash;
|
|
182
|
+
// Update the conf with the modified server coin
|
|
183
|
+
await ServerCoin.serverCoinManager.setConfigValue(`${this.storeId}:${peerIp}`, serverCoins);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// If not synced, melt the coin, a new one will be created when synced up
|
|
187
|
+
// this helps prevent penalties for not having a valid peer registered
|
|
188
|
+
await this.melt(currentEpoch, peerIp);
|
|
189
|
+
}
|
|
190
|
+
// Don't create a server coin until you're synced up
|
|
191
|
+
if (!lodash_1.default.last(rootHistory)?.synced) {
|
|
137
192
|
return;
|
|
138
193
|
}
|
|
139
194
|
console.log(`No server coin found for epoch ${currentEpoch}. Creating new server coin...`);
|
|
140
|
-
const serverCoin = await this.createForEpoch(peerIp);
|
|
195
|
+
const serverCoin = await this.createForEpoch(peerIp, lastRoot?.root_hash);
|
|
141
196
|
const newServerCoinData = {
|
|
142
197
|
coin: {
|
|
143
198
|
amount: serverCoin.coin.amount.toString(),
|
|
@@ -146,6 +201,7 @@ class ServerCoin {
|
|
|
146
201
|
},
|
|
147
202
|
createdAt: new Date().toISOString(),
|
|
148
203
|
epoch: currentEpoch,
|
|
204
|
+
rootHash: lodash_1.default.last(rootHistory)?.root_hash || "",
|
|
149
205
|
};
|
|
150
206
|
await FullNodePeer_1.FullNodePeer.waitForConfirmation(serverCoin.coin.parentCoinInfo);
|
|
151
207
|
serverCoins.push(newServerCoinData);
|
|
@@ -160,7 +216,7 @@ class ServerCoin {
|
|
|
160
216
|
// Melt outdated server coins
|
|
161
217
|
async meltOutdatedEpochs(peerIp) {
|
|
162
218
|
try {
|
|
163
|
-
const currentEpoch = ServerCoin.getCurrentEpoch();
|
|
219
|
+
const { epoch: currentEpoch } = ServerCoin.getCurrentEpoch();
|
|
164
220
|
let serverCoins = await this.getServerCoinsForStore(peerIp);
|
|
165
221
|
// Filter out coins that are not in the current epoch
|
|
166
222
|
const outdatedCoins = serverCoins.filter((coin) => coin.epoch < currentEpoch);
|
|
@@ -244,17 +300,23 @@ class ServerCoin {
|
|
|
244
300
|
}
|
|
245
301
|
}
|
|
246
302
|
// Static method to calculate the current epoch
|
|
247
|
-
static
|
|
303
|
+
static calculateEpochAndRound(currentTimestampUTC) {
|
|
248
304
|
const firstEpochStart = new Date(Date.UTC(2024, 8, 3, 0, 0)); // Sept 3, 2024, 00:00 UTC
|
|
249
305
|
// Convert the current timestamp to milliseconds
|
|
250
306
|
const currentTimestampMillis = currentTimestampUTC.getTime();
|
|
251
307
|
// Calculate the number of milliseconds in one epoch (7 days)
|
|
252
|
-
const millisecondsInEpoch = 7 * 24 * 60 * 60 * 1000;
|
|
308
|
+
const millisecondsInEpoch = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
|
|
253
309
|
// Calculate the difference in milliseconds between the current timestamp and the first epoch start
|
|
254
310
|
const differenceMillis = currentTimestampMillis - firstEpochStart.getTime();
|
|
255
311
|
// Calculate the current epoch number
|
|
256
312
|
const epochNumber = Math.floor(differenceMillis / millisecondsInEpoch) + 1;
|
|
257
|
-
|
|
313
|
+
// Calculate the milliseconds elapsed since the start of the current epoch
|
|
314
|
+
const elapsedMillisInCurrentEpoch = differenceMillis % millisecondsInEpoch;
|
|
315
|
+
// Calculate the number of milliseconds in a round (10 minutes)
|
|
316
|
+
const millisecondsInRound = 10 * 60 * 1000; // 10 minutes in milliseconds
|
|
317
|
+
// Calculate the current round number
|
|
318
|
+
const roundNumber = Math.floor(elapsedMillisInCurrentEpoch / millisecondsInRound) + 1;
|
|
319
|
+
return { epoch: epochNumber, round: roundNumber };
|
|
258
320
|
}
|
|
259
321
|
}
|
|
260
322
|
exports.ServerCoin = ServerCoin;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { DataStoreSerializer } from "./DataStoreSerializer";
|
|
2
|
+
/**
|
|
3
|
+
* Represents the structure of cached store information.
|
|
4
|
+
*/
|
|
5
|
+
interface StoreCacheEntry {
|
|
6
|
+
latestStore: ReturnType<DataStoreSerializer["serialize"]>;
|
|
7
|
+
latestHeight: number;
|
|
8
|
+
latestHash: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Callback type to be invoked when the store is updated.
|
|
12
|
+
*/
|
|
13
|
+
type StoreUpdateCallback = (storeId: string, updatedStore: StoreCacheEntry) => void;
|
|
14
|
+
/**
|
|
15
|
+
* StoreMonitorRegistry manages monitoring of multiple storeIds.
|
|
16
|
+
* It encapsulates cache management and ensures only one monitor runs per storeId.
|
|
17
|
+
*/
|
|
18
|
+
export declare class StoreMonitorRegistry {
|
|
19
|
+
private static instance;
|
|
20
|
+
private activeMonitors;
|
|
21
|
+
private storeCoinCache;
|
|
22
|
+
private cachePopulationPromises;
|
|
23
|
+
/**
|
|
24
|
+
* Private constructor to enforce singleton pattern.
|
|
25
|
+
*/
|
|
26
|
+
private constructor();
|
|
27
|
+
/**
|
|
28
|
+
* Retrieves the singleton instance of the registry.
|
|
29
|
+
* @returns {StoreMonitorRegistry} The singleton instance.
|
|
30
|
+
*/
|
|
31
|
+
static getInstance(): StoreMonitorRegistry;
|
|
32
|
+
/**
|
|
33
|
+
* Registers a storeId with a callback to be invoked upon updates.
|
|
34
|
+
* Immediately updates the cache and invokes the callback before starting the monitor.
|
|
35
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
36
|
+
* @param {StoreUpdateCallback} callback - The callback to execute when the store is updated.
|
|
37
|
+
*/
|
|
38
|
+
registerStore(storeId: string, callback: StoreUpdateCallback): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Retrieves the latest cached entry for a given storeId.
|
|
41
|
+
* If no cache exists, it fetches and caches the store info before returning.
|
|
42
|
+
* @param {string} storeId - The store identifier.
|
|
43
|
+
* @returns {Promise<StoreCacheEntry>} The latest cached entry.
|
|
44
|
+
*/
|
|
45
|
+
getLatestCache(storeId: string): Promise<StoreCacheEntry>;
|
|
46
|
+
/**
|
|
47
|
+
* Starts the monitor for a specific storeId.
|
|
48
|
+
* Ensures that the monitor restarts upon any error.
|
|
49
|
+
* Implements an exponential backoff strategy for retries.
|
|
50
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
51
|
+
* @param {StoreUpdateCallback} callback - The callback to invoke upon updates.
|
|
52
|
+
*/
|
|
53
|
+
private startMonitor;
|
|
54
|
+
/**
|
|
55
|
+
* Monitors a single store indefinitely.
|
|
56
|
+
* Executes a single iteration of the monitoring logic.
|
|
57
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
58
|
+
* @param {StoreUpdateCallback} callback - The callback to invoke upon updates.
|
|
59
|
+
*/
|
|
60
|
+
private monitorStore;
|
|
61
|
+
/**
|
|
62
|
+
* Fetches the latest store information and updates the cache.
|
|
63
|
+
* @param {string} storeId - The store identifier.
|
|
64
|
+
* @returns {Promise<StoreCacheEntry>} The latest store information.
|
|
65
|
+
*/
|
|
66
|
+
private fetchAndCacheStoreInfo;
|
|
67
|
+
/**
|
|
68
|
+
* Utility method to introduce a delay.
|
|
69
|
+
* @param {number} ms - The delay duration in milliseconds.
|
|
70
|
+
* @returns {Promise<void>} Resolves after the specified delay.
|
|
71
|
+
*/
|
|
72
|
+
private delay;
|
|
73
|
+
/**
|
|
74
|
+
* Unregisters a storeId, stopping its monitor.
|
|
75
|
+
* @param {string} storeId - The store identifier to stop monitoring.
|
|
76
|
+
*/
|
|
77
|
+
unregisterStore(storeId: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Stops all active monitors and clears the registry.
|
|
80
|
+
* Useful for graceful shutdowns.
|
|
81
|
+
*/
|
|
82
|
+
stopAllMonitors(): void;
|
|
83
|
+
}
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=StoreMonitorRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StoreMonitorRegistry.d.ts","sourceRoot":"","sources":["../../src/blockchain/StoreMonitorRegistry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAK5D;;GAEG;AACH,UAAU,eAAe;IACvB,WAAW,EAAE,UAAU,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1D,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,KAAK,mBAAmB,GAAG,CACzB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,eAAe,KAC1B,IAAI,CAAC;AAEV;;;GAGG;AACH,qBAAa,oBAAoB;IAE/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAuB;IAG9C,OAAO,CAAC,cAAc,CAAmC;IAGzD,OAAO,CAAC,cAAc,CAA6B;IAGnD,OAAO,CAAC,uBAAuB,CAAwC;IAEvE;;OAEG;IACH,OAAO;IASP;;;OAGG;WACW,WAAW,IAAI,oBAAoB;IAOjD;;;;;OAKG;IACU,aAAa,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAgChB;;;;;OAKG;IACU,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAkCtE;;;;;;OAMG;YACW,YAAY;IA4B1B;;;;;OAKG;YACW,YAAY;IAuG1B;;;;OAIG;YACW,sBAAsB;IAkDpC;;;;OAIG;YACW,KAAK;IAInB;;;OAGG;IACI,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAS7C;;;OAGG;IACI,eAAe,IAAI,IAAI;CAI/B"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StoreMonitorRegistry = void 0;
|
|
4
|
+
const FullNodePeer_1 = require("./FullNodePeer");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const DataStoreSerializer_1 = require("./DataStoreSerializer");
|
|
7
|
+
const DataStore_1 = require("./DataStore");
|
|
8
|
+
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
9
|
+
const utils_2 = require("../utils");
|
|
10
|
+
/**
|
|
11
|
+
* StoreMonitorRegistry manages monitoring of multiple storeIds.
|
|
12
|
+
* It encapsulates cache management and ensures only one monitor runs per storeId.
|
|
13
|
+
*/
|
|
14
|
+
class StoreMonitorRegistry {
|
|
15
|
+
/**
|
|
16
|
+
* Private constructor to enforce singleton pattern.
|
|
17
|
+
*/
|
|
18
|
+
constructor() {
|
|
19
|
+
this.activeMonitors = new Map();
|
|
20
|
+
this.storeCoinCache = new utils_1.FileCache("stores", utils_1.USER_DIR_PATH);
|
|
21
|
+
this.cachePopulationPromises = new Map();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Retrieves the singleton instance of the registry.
|
|
25
|
+
* @returns {StoreMonitorRegistry} The singleton instance.
|
|
26
|
+
*/
|
|
27
|
+
static getInstance() {
|
|
28
|
+
if (!StoreMonitorRegistry.instance) {
|
|
29
|
+
StoreMonitorRegistry.instance = new StoreMonitorRegistry();
|
|
30
|
+
}
|
|
31
|
+
return StoreMonitorRegistry.instance;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Registers a storeId with a callback to be invoked upon updates.
|
|
35
|
+
* Immediately updates the cache and invokes the callback before starting the monitor.
|
|
36
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
37
|
+
* @param {StoreUpdateCallback} callback - The callback to execute when the store is updated.
|
|
38
|
+
*/
|
|
39
|
+
async registerStore(storeId, callback) {
|
|
40
|
+
if (this.activeMonitors.has(storeId)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log(`Registry: Registering monitor for storeId: ${storeId}`);
|
|
44
|
+
this.activeMonitors.set(storeId, callback);
|
|
45
|
+
try {
|
|
46
|
+
// Immediately fetch and cache the latest store info
|
|
47
|
+
const initialStore = await this.fetchAndCacheStoreInfo(storeId);
|
|
48
|
+
// Invoke the callback with the initial store info
|
|
49
|
+
callback(storeId, initialStore);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(`Registry: Failed to perform initial cache update for storeId: ${storeId} - ${error.message}`);
|
|
53
|
+
// Decide whether to unregister the monitor or proceed
|
|
54
|
+
// For this example, we'll proceed to start the monitor
|
|
55
|
+
}
|
|
56
|
+
// No monitoring in cli mode
|
|
57
|
+
if (!utils_2.Environment.CLI_MODE) {
|
|
58
|
+
// Start monitoring asynchronously
|
|
59
|
+
this.startMonitor(storeId, callback).catch((error) => {
|
|
60
|
+
console.error(`Registry: Unexpected error in startMonitor for storeId: ${storeId} - ${error.message}`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Retrieves the latest cached entry for a given storeId.
|
|
66
|
+
* If no cache exists, it fetches and caches the store info before returning.
|
|
67
|
+
* @param {string} storeId - The store identifier.
|
|
68
|
+
* @returns {Promise<StoreCacheEntry>} The latest cached entry.
|
|
69
|
+
*/
|
|
70
|
+
async getLatestCache(storeId) {
|
|
71
|
+
// if in CLI mode, always fetch the latest store info directly
|
|
72
|
+
if (!utils_2.Environment.CLI_MODE) {
|
|
73
|
+
let cachedInfo = this.storeCoinCache.get(storeId);
|
|
74
|
+
if (cachedInfo) {
|
|
75
|
+
return cachedInfo;
|
|
76
|
+
}
|
|
77
|
+
// If cache is missing, fetch and cache it
|
|
78
|
+
console.log(`getLatestCache: No cache found for storeId: ${storeId}. Fetching...`);
|
|
79
|
+
// Prevent duplicate fetches for the same storeId
|
|
80
|
+
if (this.cachePopulationPromises.has(storeId)) {
|
|
81
|
+
return this.cachePopulationPromises.get(storeId);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const fetchPromise = this.fetchAndCacheStoreInfo(storeId)
|
|
85
|
+
.then((store) => {
|
|
86
|
+
this.cachePopulationPromises.delete(storeId);
|
|
87
|
+
return store;
|
|
88
|
+
})
|
|
89
|
+
.catch((error) => {
|
|
90
|
+
this.cachePopulationPromises.delete(storeId);
|
|
91
|
+
throw error;
|
|
92
|
+
});
|
|
93
|
+
this.cachePopulationPromises.set(storeId, fetchPromise);
|
|
94
|
+
return fetchPromise;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Starts the monitor for a specific storeId.
|
|
98
|
+
* Ensures that the monitor restarts upon any error.
|
|
99
|
+
* Implements an exponential backoff strategy for retries.
|
|
100
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
101
|
+
* @param {StoreUpdateCallback} callback - The callback to invoke upon updates.
|
|
102
|
+
*/
|
|
103
|
+
async startMonitor(storeId, callback) {
|
|
104
|
+
let retryCount = 0;
|
|
105
|
+
const maxRetryDelay = 60000; // 60 seconds
|
|
106
|
+
while (this.activeMonitors.has(storeId)) {
|
|
107
|
+
try {
|
|
108
|
+
await this.monitorStore(storeId, callback);
|
|
109
|
+
retryCount = 0; // Reset on success
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error(`Registry: Monitor for storeId: ${storeId} encountered an error: ${error.message}. Restarting monitor...`);
|
|
113
|
+
const delayTime = maxRetryDelay;
|
|
114
|
+
console.log(`Registry: Waiting for ${delayTime / 1000} seconds before retrying...`);
|
|
115
|
+
await this.delay(delayTime);
|
|
116
|
+
retryCount++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.log(`Registry: Monitor for storeId: ${storeId} has been stopped.`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Monitors a single store indefinitely.
|
|
123
|
+
* Executes a single iteration of the monitoring logic.
|
|
124
|
+
* @param {string} storeId - The store identifier to monitor.
|
|
125
|
+
* @param {StoreUpdateCallback} callback - The callback to invoke upon updates.
|
|
126
|
+
*/
|
|
127
|
+
async monitorStore(storeId, callback) {
|
|
128
|
+
console.log(`Monitor: Starting monitor iteration for storeId: ${storeId}`);
|
|
129
|
+
try {
|
|
130
|
+
console.log(`Monitor: Connecting to peer for storeId: ${storeId}`);
|
|
131
|
+
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
132
|
+
const cachedInfo = this.storeCoinCache.get(storeId);
|
|
133
|
+
if (cachedInfo) {
|
|
134
|
+
// Log cached store info retrieval
|
|
135
|
+
console.log(`Monitor: Cached store info found for storeId: ${storeId}, syncing...`);
|
|
136
|
+
// Deserialize cached info and wait for the coin to be spent
|
|
137
|
+
const previousStore = DataStoreSerializer_1.DataStoreSerializer.deserialize({
|
|
138
|
+
latestStore: cachedInfo.latestStore,
|
|
139
|
+
latestHeight: cachedInfo.latestHeight.toString(),
|
|
140
|
+
latestHash: cachedInfo.latestHash,
|
|
141
|
+
});
|
|
142
|
+
console.log(`Monitor: Waiting for coin to be spent for storeId: ${storeId}...`);
|
|
143
|
+
const dataStore = DataStore_1.DataStore.from(storeId);
|
|
144
|
+
const { createdAtHeight, createdAtHash } = await dataStore.getCreationHeight();
|
|
145
|
+
await peer.waitForCoinToBeSpent((0, datalayer_driver_1.getCoinId)(previousStore.latestStore.coin), createdAtHeight, createdAtHash);
|
|
146
|
+
// Sync store and get updated details
|
|
147
|
+
console.log(`Monitor: Syncing store for storeId: ${storeId}`);
|
|
148
|
+
const { latestStore, latestHeight } = await peer.syncStore(previousStore.latestStore, createdAtHeight, createdAtHash, false);
|
|
149
|
+
const latestHash = await peer.getHeaderHash(latestHeight);
|
|
150
|
+
console.log(`Monitor: Store synced for storeId: ${storeId}, coin id: ${(0, datalayer_driver_1.getCoinId)(latestStore.coin).toString("hex")}`);
|
|
151
|
+
// Serialize and cache the updated store info
|
|
152
|
+
const serializedLatestStore = new DataStoreSerializer_1.DataStoreSerializer(latestStore, latestHeight, latestHash).serialize();
|
|
153
|
+
console.log(`Monitor: Caching updated store info for storeId: ${storeId}`);
|
|
154
|
+
this.storeCoinCache.set(storeId, {
|
|
155
|
+
latestStore: serializedLatestStore,
|
|
156
|
+
latestHeight,
|
|
157
|
+
latestHash: latestHash.toString("hex"),
|
|
158
|
+
});
|
|
159
|
+
// Invoke the callback with updated store info
|
|
160
|
+
const updatedStore = this.storeCoinCache.get(storeId);
|
|
161
|
+
if (updatedStore) {
|
|
162
|
+
callback(storeId, updatedStore);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.warn(`Monitor: Updated store info for storeId: ${storeId} is missing after caching.`);
|
|
166
|
+
}
|
|
167
|
+
return; // Successful iteration
|
|
168
|
+
}
|
|
169
|
+
// If no cached info exists, fetch initial store info
|
|
170
|
+
console.log(`Monitor: No cached info found for storeId: ${storeId}. Fetching initial store info.`);
|
|
171
|
+
// Fetch and cache the latest store info
|
|
172
|
+
const newStore = await this.fetchAndCacheStoreInfo(storeId);
|
|
173
|
+
// Invoke the callback with the new store info
|
|
174
|
+
callback(storeId, newStore);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.error(`Monitor: Error monitoring storeId: ${storeId} - ${error.message}`);
|
|
178
|
+
// Propagate the error to trigger a restart in startMonitor
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Fetches the latest store information and updates the cache.
|
|
184
|
+
* @param {string} storeId - The store identifier.
|
|
185
|
+
* @returns {Promise<StoreCacheEntry>} The latest store information.
|
|
186
|
+
*/
|
|
187
|
+
async fetchAndCacheStoreInfo(storeId) {
|
|
188
|
+
console.log(`Monitor: Fetching and caching latest store info for storeId: ${storeId}`);
|
|
189
|
+
const peer = await FullNodePeer_1.FullNodePeer.connect();
|
|
190
|
+
const dataStore = DataStore_1.DataStore.from(storeId);
|
|
191
|
+
const { createdAtHeight, createdAtHash } = await dataStore.getCreationHeight();
|
|
192
|
+
// Sync store from the peer using launcher ID
|
|
193
|
+
console.log(`Monitor: Syncing store from launcher ID for storeId: ${storeId}`);
|
|
194
|
+
const { latestStore, latestHeight } = await peer.syncStoreFromLauncherId(Buffer.from(storeId, "hex"), createdAtHeight, createdAtHash, false);
|
|
195
|
+
const latestHash = await peer.getHeaderHash(latestHeight);
|
|
196
|
+
// Serialize and cache the new store info
|
|
197
|
+
const serializedLatestStore = new DataStoreSerializer_1.DataStoreSerializer(latestStore, latestHeight, latestHash).serialize();
|
|
198
|
+
console.log(`Monitor: Caching new store info for storeId: ${storeId}`);
|
|
199
|
+
this.storeCoinCache.set(storeId, {
|
|
200
|
+
latestStore: serializedLatestStore,
|
|
201
|
+
latestHeight,
|
|
202
|
+
latestHash: latestHash.toString("hex"),
|
|
203
|
+
});
|
|
204
|
+
// Return the cached store info
|
|
205
|
+
const newStore = this.storeCoinCache.get(storeId);
|
|
206
|
+
if (!newStore) {
|
|
207
|
+
throw new Error(`Failed to cache store info for storeId: ${storeId} after fetching.`);
|
|
208
|
+
}
|
|
209
|
+
return newStore;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Utility method to introduce a delay.
|
|
213
|
+
* @param {number} ms - The delay duration in milliseconds.
|
|
214
|
+
* @returns {Promise<void>} Resolves after the specified delay.
|
|
215
|
+
*/
|
|
216
|
+
async delay(ms) {
|
|
217
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Unregisters a storeId, stopping its monitor.
|
|
221
|
+
* @param {string} storeId - The store identifier to stop monitoring.
|
|
222
|
+
*/
|
|
223
|
+
unregisterStore(storeId) {
|
|
224
|
+
if (this.activeMonitors.has(storeId)) {
|
|
225
|
+
this.activeMonitors.delete(storeId);
|
|
226
|
+
console.log(`Registry: Unregistered monitor for storeId: ${storeId}`);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
console.log(`Registry: No active monitor found for storeId: ${storeId}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Stops all active monitors and clears the registry.
|
|
234
|
+
* Useful for graceful shutdowns.
|
|
235
|
+
*/
|
|
236
|
+
stopAllMonitors() {
|
|
237
|
+
this.activeMonitors.clear();
|
|
238
|
+
console.log(`Registry: All monitors have been stopped.`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.StoreMonitorRegistry = StoreMonitorRegistry;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
|
-
import { Peer, Coin, CoinSpend } from "datalayer-driver";
|
|
2
|
+
import { Peer, Coin, CoinSpend } from "@dignetwork/datalayer-driver";
|
|
3
3
|
export declare const DEFAULT_FEE_COIN_COST = 64000000;
|
|
4
4
|
export declare class Wallet {
|
|
5
5
|
private mnemonic;
|
|
@@ -20,7 +20,7 @@ export declare class Wallet {
|
|
|
20
20
|
private static getWalletFromKeyring;
|
|
21
21
|
private static saveWalletToKeyring;
|
|
22
22
|
createKeyOwnershipSignature(nonce: string): Promise<string>;
|
|
23
|
-
verifyKeyOwnershipSignature(nonce: string, signature: string, publicKey: string): Promise<boolean>;
|
|
23
|
+
static verifyKeyOwnershipSignature(nonce: string, signature: string, publicKey: string): Promise<boolean>;
|
|
24
24
|
selectUnspentCoins(peer: Peer, coinAmount: bigint, feeBigInt: bigint, omitCoins?: Coin[]): Promise<Coin[]>;
|
|
25
25
|
static calculateFeeForCoinSpends(peer: Peer, coinSpends: CoinSpend[] | null): Promise<bigint>;
|
|
26
26
|
static isCoinSpendable(peer: Peer, coinId: Buffer): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Wallet.d.ts","sourceRoot":"","sources":["../../src/blockchain/Wallet.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EASL,IAAI,EACJ,IAAI,EAEJ,SAAS,EAEV,MAAM,
|
|
1
|
+
{"version":3,"file":"Wallet.d.ts","sourceRoot":"","sources":["../../src/blockchain/Wallet.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EASL,IAAI,EACJ,IAAI,EAEJ,SAAS,EAEV,MAAM,8BAA8B,CAAC;AAQtC,eAAO,MAAM,qBAAqB,WAAa,CAAC;AAOhD,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO;WAKa,IAAI,CACtB,UAAU,GAAE,MAAkB,EAC9B,iBAAiB,GAAE,OAAc,GAChC,OAAO,CAAC,MAAM,CAAC;IAsBX,WAAW,IAAI,MAAM;WAOR,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;WAMpD,YAAY,CAC9B,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC;WASE,oBAAoB,CACtC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;IA6BL,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKrC,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC;IAMxC,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKzC,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAMrC,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;WAK7B,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;WASlD,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;mBAU/B,oBAAoB;mBAcpB,mBAAmB;IAS3B,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;WAUpD,2BAA2B,CAC7C,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IASN,kBAAkB,CAC7B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,IAAI,EAAO,GACrB,OAAO,CAAC,IAAI,EAAE,CAAC;WAiFE,yBAAyB,CAC3C,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,SAAS,EAAE,GAAG,IAAI,GAC7B,OAAO,CAAC,MAAM,CAAC;WAIE,eAAe,CACjC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;CAWpB"}
|