@aztec/pxe 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dest/database/deferred_note_dao.d.ts +2 -2
  2. package/dest/database/deferred_note_dao.d.ts.map +1 -1
  3. package/dest/database/deferred_note_dao.js +2 -2
  4. package/dest/database/incoming_note_dao.d.ts +73 -0
  5. package/dest/database/incoming_note_dao.d.ts.map +1 -0
  6. package/dest/database/incoming_note_dao.js +92 -0
  7. package/dest/database/kv_pxe_database.d.ts +10 -7
  8. package/dest/database/kv_pxe_database.d.ts.map +1 -1
  9. package/dest/database/kv_pxe_database.js +149 -78
  10. package/dest/database/{note_dao.d.ts → outgoing_note_dao.d.ts} +7 -12
  11. package/dest/database/outgoing_note_dao.d.ts.map +1 -0
  12. package/dest/database/outgoing_note_dao.js +83 -0
  13. package/dest/database/pxe_database.d.ts +21 -9
  14. package/dest/database/pxe_database.d.ts.map +1 -1
  15. package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
  16. package/dest/database/pxe_database_test_suite.js +71 -24
  17. package/dest/kernel_oracle/index.d.ts +1 -1
  18. package/dest/note_processor/note_processor.d.ts +23 -20
  19. package/dest/note_processor/note_processor.d.ts.map +1 -1
  20. package/dest/note_processor/note_processor.js +123 -76
  21. package/dest/note_processor/produce_note_dao.d.ts +13 -4
  22. package/dest/note_processor/produce_note_dao.d.ts.map +1 -1
  23. package/dest/note_processor/produce_note_dao.js +88 -31
  24. package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
  25. package/dest/pxe_service/create_pxe_service.js +3 -1
  26. package/dest/pxe_service/pxe_service.d.ts +9 -4
  27. package/dest/pxe_service/pxe_service.d.ts.map +1 -1
  28. package/dest/pxe_service/pxe_service.js +106 -28
  29. package/dest/simulator_oracle/index.js +2 -2
  30. package/dest/synchronizer/synchronizer.d.ts +2 -2
  31. package/dest/synchronizer/synchronizer.d.ts.map +1 -1
  32. package/dest/synchronizer/synchronizer.js +37 -36
  33. package/package.json +14 -14
  34. package/src/database/deferred_note_dao.ts +1 -1
  35. package/src/database/{note_dao.ts → incoming_note_dao.ts} +10 -7
  36. package/src/database/kv_pxe_database.ts +127 -29
  37. package/src/database/outgoing_note_dao.ts +90 -0
  38. package/src/database/pxe_database.ts +23 -9
  39. package/src/database/pxe_database_test_suite.ts +93 -29
  40. package/src/note_processor/note_processor.ts +191 -121
  41. package/src/note_processor/produce_note_dao.ts +164 -50
  42. package/src/pxe_service/create_pxe_service.ts +2 -0
  43. package/src/pxe_service/pxe_service.ts +170 -42
  44. package/src/simulator_oracle/index.ts +1 -1
  45. package/src/synchronizer/synchronizer.ts +48 -52
  46. package/dest/database/note_dao.d.ts.map +0 -1
  47. package/dest/database/note_dao.js +0 -89
@@ -78,7 +78,7 @@ export class SimulatorOracle implements DBOracle {
78
78
  }
79
79
 
80
80
  async getNotes(contractAddress: AztecAddress, storageSlot: Fr, status: NoteStatus) {
81
- const noteDaos = await this.db.getNotes({
81
+ const noteDaos = await this.db.getIncomingNotes({
82
82
  contractAddress,
83
83
  storageSlot,
84
84
  status,
@@ -1,4 +1,4 @@
1
- import { type AztecNode, type L2Block, L2BlockL2Logs, MerkleTreeId, type TxHash } from '@aztec/circuit-types';
1
+ import { type AztecNode, type L2Block, MerkleTreeId, type TxHash } from '@aztec/circuit-types';
2
2
  import { type NoteProcessorCaughtUpStats } from '@aztec/circuit-types/stats';
3
3
  import { type AztecAddress, type Fr, INITIAL_L2_BLOCK_NUM, type PublicKey } from '@aztec/circuits.js';
4
4
  import { type SerialQueue } from '@aztec/foundation/fifo';
@@ -7,8 +7,9 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
7
7
  import { type KeyStore } from '@aztec/key-store';
8
8
 
9
9
  import { type DeferredNoteDao } from '../database/deferred_note_dao.js';
10
+ import { type IncomingNoteDao } from '../database/incoming_note_dao.js';
10
11
  import { type PxeDatabase } from '../database/index.js';
11
- import { type NoteDao } from '../database/note_dao.js';
12
+ import { type OutgoingNoteDao } from '../database/outgoing_note_dao.js';
12
13
  import { NoteProcessor } from '../note_processor/index.js';
13
14
 
14
15
  /**
@@ -99,19 +100,13 @@ export class Synchronizer {
99
100
  return false;
100
101
  }
101
102
 
102
- const noteEncryptedLogs = blocks.flatMap(block => block.body.noteEncryptedLogs);
103
-
104
103
  // Update latest tree roots from the most recent block
105
104
  const latestBlock = blocks[blocks.length - 1];
106
105
  await this.setHeaderFromBlock(latestBlock);
107
106
 
108
- const logCount = L2BlockL2Logs.getTotalLogCount(noteEncryptedLogs);
109
- this.log.debug(
110
- `Forwarding ${logCount} encrypted logs and blocks to ${this.noteProcessors.length} note processors`,
111
- );
107
+ this.log.debug(`Forwarding ${blocks.length} blocks to ${this.noteProcessors.length} note processors`);
112
108
  for (const noteProcessor of this.noteProcessors) {
113
- // TODO(#6830): pass in only the blocks
114
- await noteProcessor.process(blocks, noteEncryptedLogs);
109
+ await noteProcessor.process(blocks);
115
110
  }
116
111
  return true;
117
112
  } catch (err) {
@@ -177,11 +172,6 @@ export class Synchronizer {
177
172
  throw new Error('No blocks in processor catch up mode');
178
173
  }
179
174
 
180
- const noteEncryptedLogs = blocks.flatMap(block => block.body.noteEncryptedLogs);
181
-
182
- const logCount = L2BlockL2Logs.getTotalLogCount(noteEncryptedLogs);
183
- this.log.debug(`Forwarding ${logCount} encrypted logs and blocks to note processors in catch up mode`);
184
-
185
175
  for (const noteProcessor of catchUpGroup) {
186
176
  // find the index of the first block that the note processor is not yet synced to
187
177
  const index = blocks.findIndex(block => block.number > noteProcessor.status.syncedToBlock);
@@ -193,27 +183,24 @@ export class Synchronizer {
193
183
  }
194
184
 
195
185
  this.log.debug(
196
- `Catching up note processor ${noteProcessor.masterIncomingViewingPublicKey.toString()} by processing ${
186
+ `Catching up note processor ${noteProcessor.account.toString()} by processing ${
197
187
  blocks.length - index
198
188
  } blocks`,
199
189
  );
200
- await noteProcessor.process(blocks.slice(index), noteEncryptedLogs.slice(index));
190
+ await noteProcessor.process(blocks.slice(index));
201
191
 
202
192
  if (noteProcessor.status.syncedToBlock === toBlockNumber) {
203
193
  // Note processor caught up, move it to `noteProcessors` from `noteProcessorsToCatchUp`.
204
- this.log.debug(
205
- `Note processor for ${noteProcessor.masterIncomingViewingPublicKey.toString()} has caught up`,
206
- {
207
- eventName: 'note-processor-caught-up',
208
- publicKey: noteProcessor.masterIncomingViewingPublicKey.toString(),
209
- duration: noteProcessor.timer.ms(),
210
- dbSize: this.db.estimateSize(),
211
- ...noteProcessor.stats,
212
- } satisfies NoteProcessorCaughtUpStats,
213
- );
194
+ this.log.debug(`Note processor for ${noteProcessor.account.toString()} has caught up`, {
195
+ eventName: 'note-processor-caught-up',
196
+ account: noteProcessor.account.toString(),
197
+ duration: noteProcessor.timer.ms(),
198
+ dbSize: await this.db.estimateSize(),
199
+ ...noteProcessor.stats,
200
+ } satisfies NoteProcessorCaughtUpStats);
214
201
 
215
202
  this.noteProcessorsToCatchUp = this.noteProcessorsToCatchUp.filter(
216
- np => !np.masterIncomingViewingPublicKey.equals(noteProcessor.masterIncomingViewingPublicKey),
203
+ np => !np.account.equals(noteProcessor.account),
217
204
  );
218
205
  this.noteProcessors.push(noteProcessor);
219
206
  }
@@ -257,14 +244,14 @@ export class Synchronizer {
257
244
  * @param startingBlock - The block where to start scanning for notes for this accounts.
258
245
  * @returns A promise that resolves once the account is added to the Synchronizer.
259
246
  */
260
- public addAccount(publicKey: PublicKey, keyStore: KeyStore, startingBlock: number) {
261
- const predicate = (x: NoteProcessor) => x.masterIncomingViewingPublicKey.equals(publicKey);
247
+ public async addAccount(account: AztecAddress, keyStore: KeyStore, startingBlock: number) {
248
+ const predicate = (x: NoteProcessor) => x.account.equals(account);
262
249
  const processor = this.noteProcessors.find(predicate) ?? this.noteProcessorsToCatchUp.find(predicate);
263
250
  if (processor) {
264
251
  return;
265
252
  }
266
253
 
267
- this.noteProcessorsToCatchUp.push(new NoteProcessor(publicKey, keyStore, this.db, this.node, startingBlock));
254
+ this.noteProcessorsToCatchUp.push(await NoteProcessor.create(account, keyStore, this.db, this.node, startingBlock));
268
255
  }
269
256
 
270
257
  /**
@@ -280,9 +267,9 @@ export class Synchronizer {
280
267
  if (!completeAddress) {
281
268
  throw new Error(`Checking if account is synched is not possible for ${account} because it is not registered.`);
282
269
  }
283
- const findByPublicKey = (x: NoteProcessor) =>
284
- x.masterIncomingViewingPublicKey.equals(completeAddress.publicKeys.masterIncomingViewingPublicKey);
285
- const processor = this.noteProcessors.find(findByPublicKey) ?? this.noteProcessorsToCatchUp.find(findByPublicKey);
270
+ const findByAccountAddress = (x: NoteProcessor) => x.account.equals(completeAddress.address);
271
+ const processor =
272
+ this.noteProcessors.find(findByAccountAddress) ?? this.noteProcessorsToCatchUp.find(findByAccountAddress);
286
273
  if (!processor) {
287
274
  throw new Error(
288
275
  `Checking if account is synched is not possible for ${account} because it is only registered as a recipient.`,
@@ -314,9 +301,7 @@ export class Synchronizer {
314
301
  const lastBlockNumber = this.getSynchedBlockNumber();
315
302
  return {
316
303
  blocks: lastBlockNumber,
317
- notes: Object.fromEntries(
318
- this.noteProcessors.map(n => [n.masterIncomingViewingPublicKey.toString(), n.status.syncedToBlock]),
319
- ),
304
+ notes: Object.fromEntries(this.noteProcessors.map(n => [n.account.toString(), n.status.syncedToBlock])),
320
305
  };
321
306
  }
322
307
 
@@ -325,7 +310,7 @@ export class Synchronizer {
325
310
  * @returns The note processor stats for notes for each public key being tracked.
326
311
  */
327
312
  public getSyncStats() {
328
- return Object.fromEntries(this.noteProcessors.map(n => [n.masterIncomingViewingPublicKey.toString(), n.stats]));
313
+ return Object.fromEntries(this.noteProcessors.map(n => [n.account.toString(), n.stats]));
329
314
  }
330
315
 
331
316
  /**
@@ -348,40 +333,51 @@ export class Synchronizer {
348
333
  }
349
334
 
350
335
  // keep track of decoded notes
351
- const newNotes: NoteDao[] = [];
336
+ const incomingNotes: IncomingNoteDao[] = [];
337
+ const outgoingNotes: OutgoingNoteDao[] = [];
338
+
352
339
  // now process each txHash
353
340
  for (const deferredNotes of txHashToDeferredNotes.values()) {
354
341
  // to be safe, try each note processor in case the deferred notes are for different accounts.
355
342
  for (const processor of this.noteProcessors) {
356
- const decodedNotes = await processor.decodeDeferredNotes(
357
- deferredNotes.filter(n => n.publicKey.equals(processor.masterIncomingViewingPublicKey)),
358
- );
359
- newNotes.push(...decodedNotes);
343
+ const { incomingNotes: inNotes, outgoingNotes: outNotes } = await processor.decodeDeferredNotes(deferredNotes);
344
+ incomingNotes.push(...inNotes);
345
+ outgoingNotes.push(...outNotes);
360
346
  }
361
347
  }
362
348
 
363
349
  // now drop the deferred notes, and add the decoded notes
364
350
  await this.db.removeDeferredNotesByContract(contractAddress);
365
- await this.db.addNotes(newNotes);
351
+ await this.db.addNotes(incomingNotes, outgoingNotes);
366
352
 
367
- newNotes.forEach(noteDao => {
353
+ incomingNotes.forEach(noteDao => {
368
354
  this.log.debug(
369
- `Decoded deferred note for contract ${noteDao.contractAddress} at slot ${
355
+ `Decoded deferred incoming note for contract ${noteDao.contractAddress} at slot ${
370
356
  noteDao.storageSlot
371
357
  } with nullifier ${noteDao.siloedNullifier.toString()}`,
372
358
  );
373
359
  });
374
360
 
375
- // now group the decoded notes by public key
376
- const publicKeyToNotes: Map<PublicKey, NoteDao[]> = new Map();
377
- for (const noteDao of newNotes) {
378
- const notesForPublicKey = publicKeyToNotes.get(noteDao.publicKey) ?? [];
361
+ outgoingNotes.forEach(noteDao => {
362
+ this.log.debug(
363
+ `Decoded deferred outgoing note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`,
364
+ );
365
+ });
366
+
367
+ await this.#removeNullifiedNotes(incomingNotes);
368
+ }
369
+
370
+ async #removeNullifiedNotes(notes: IncomingNoteDao[]) {
371
+ // now group the decoded incoming notes by public key
372
+ const publicKeyToIncomingNotes: Map<PublicKey, IncomingNoteDao[]> = new Map();
373
+ for (const noteDao of notes) {
374
+ const notesForPublicKey = publicKeyToIncomingNotes.get(noteDao.ivpkM) ?? [];
379
375
  notesForPublicKey.push(noteDao);
380
- publicKeyToNotes.set(noteDao.publicKey, notesForPublicKey);
376
+ publicKeyToIncomingNotes.set(noteDao.ivpkM, notesForPublicKey);
381
377
  }
382
378
 
383
379
  // now for each group, look for the nullifiers in the nullifier tree
384
- for (const [publicKey, notes] of publicKeyToNotes.entries()) {
380
+ for (const [publicKey, notes] of publicKeyToIncomingNotes.entries()) {
385
381
  const nullifiers = notes.map(n => n.siloedNullifier);
386
382
  const relevantNullifiers: Fr[] = [];
387
383
  for (const nullifier of nullifiers) {
@@ -1 +0,0 @@
1
- {"version":3,"file":"note_dao.d.ts","sourceRoot":"","sources":["../../src/database/note_dao.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,EAAE,EAAS,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,YAAY,EAAqB,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD;;GAEG;AACH,qBAAa,OAAQ,YAAW,QAAQ;IAEpC,kDAAkD;IAC3C,IAAI,EAAE,IAAI;IACjB,oDAAoD;IAC7C,eAAe,EAAE,YAAY;IACpC,iEAAiE;IAC1D,WAAW,EAAE,EAAE;IACtB,iDAAiD;IAC1C,UAAU,EAAE,EAAE;IACrB,kDAAkD;IAC3C,MAAM,EAAE,MAAM;IACrB,6BAA6B;IACtB,KAAK,EAAE,EAAE;IAChB;;;OAGG;IACI,aAAa,EAAE,EAAE;IACxB,8DAA8D;IACvD,eAAe,EAAE,EAAE;IAC1B,+DAA+D;IACxD,KAAK,EAAE,MAAM;IACpB,wDAAwD;IACjD,SAAS,EAAE,SAAS;;IAtB3B,kDAAkD;IAC3C,IAAI,EAAE,IAAI;IACjB,oDAAoD;IAC7C,eAAe,EAAE,YAAY;IACpC,iEAAiE;IAC1D,WAAW,EAAE,EAAE;IACtB,iDAAiD;IAC1C,UAAU,EAAE,EAAE;IACrB,kDAAkD;IAC3C,MAAM,EAAE,MAAM;IACrB,6BAA6B;IACtB,KAAK,EAAE,EAAE;IAChB;;;OAGG;IACI,aAAa,EAAE,EAAE;IACxB,8DAA8D;IACvD,eAAe,EAAE,EAAE;IAC1B,+DAA+D;IACxD,KAAK,EAAE,MAAM;IACpB,wDAAwD;IACjD,SAAS,EAAE,SAAS;IAG7B,QAAQ,IAAI,MAAM;IAclB,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IA4B/C,QAAQ;IAIR,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM;IAK7B;;;OAGG;IACI,OAAO;CAKf"}
@@ -1,89 +0,0 @@
1
- import { Note, TxHash } from '@aztec/circuit-types';
2
- import { AztecAddress, Fr, Point } from '@aztec/circuits.js';
3
- import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
4
- import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
5
- /**
6
- * A note with contextual data.
7
- */
8
- export class NoteDao {
9
- constructor(
10
- /** The note as emitted from the Noir contract. */
11
- note,
12
- /** The contract address this note is created in. */
13
- contractAddress,
14
- /** The specific storage location of the note on the contract. */
15
- storageSlot,
16
- /** The note type identifier for the contract. */
17
- noteTypeId,
18
- /** The hash of the tx the note was created in. */
19
- txHash,
20
- /** The nonce of the note. */
21
- nonce,
22
- /**
23
- * Inner note hash of the note. This is customizable by the app circuit.
24
- * We can use this value to compute siloedNoteHash and uniqueSiloedNoteHash.
25
- */
26
- innerNoteHash,
27
- /** The nullifier of the note (siloed by contract address). */
28
- siloedNullifier,
29
- /** The location of the relevant note in the note hash tree. */
30
- index,
31
- /** The public key with which the note was encrypted. */
32
- publicKey) {
33
- this.note = note;
34
- this.contractAddress = contractAddress;
35
- this.storageSlot = storageSlot;
36
- this.noteTypeId = noteTypeId;
37
- this.txHash = txHash;
38
- this.nonce = nonce;
39
- this.innerNoteHash = innerNoteHash;
40
- this.siloedNullifier = siloedNullifier;
41
- this.index = index;
42
- this.publicKey = publicKey;
43
- }
44
- toBuffer() {
45
- return serializeToBuffer([
46
- this.note,
47
- this.contractAddress,
48
- this.storageSlot,
49
- this.noteTypeId,
50
- this.txHash.buffer,
51
- this.nonce,
52
- this.innerNoteHash,
53
- this.siloedNullifier,
54
- this.index,
55
- this.publicKey,
56
- ]);
57
- }
58
- static fromBuffer(buffer) {
59
- const reader = BufferReader.asReader(buffer);
60
- const note = Note.fromBuffer(reader);
61
- const contractAddress = AztecAddress.fromBuffer(reader);
62
- const storageSlot = Fr.fromBuffer(reader);
63
- const noteTypeId = Fr.fromBuffer(reader);
64
- const txHash = new TxHash(reader.readBytes(TxHash.SIZE));
65
- const nonce = Fr.fromBuffer(reader);
66
- const innerNoteHash = Fr.fromBuffer(reader);
67
- const siloedNullifier = Fr.fromBuffer(reader);
68
- const index = toBigIntBE(reader.readBytes(32));
69
- const publicKey = Point.fromBuffer(reader);
70
- return new NoteDao(note, contractAddress, storageSlot, noteTypeId, txHash, nonce, innerNoteHash, siloedNullifier, index, publicKey);
71
- }
72
- toString() {
73
- return '0x' + this.toBuffer().toString('hex');
74
- }
75
- static fromString(str) {
76
- const hex = str.replace(/^0x/, '');
77
- return NoteDao.fromBuffer(Buffer.from(hex, 'hex'));
78
- }
79
- /**
80
- * Returns the size in bytes of the Note Dao.
81
- * @returns - Its size in bytes.
82
- */
83
- getSize() {
84
- const indexSize = Math.ceil(Math.log2(Number(this.index)));
85
- const noteSize = 4 + this.note.items.length * Fr.SIZE_IN_BYTES;
86
- return noteSize + AztecAddress.SIZE_IN_BYTES + Fr.SIZE_IN_BYTES * 4 + TxHash.SIZE + Point.SIZE_IN_BYTES + indexSize;
87
- }
88
- }
89
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm90ZV9kYW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGF0YWJhc2Uvbm90ZV9kYW8udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUsWUFBWSxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQWtCLE1BQU0sb0JBQW9CLENBQUM7QUFDN0UsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzdELE9BQU8sRUFBRSxZQUFZLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUc5RTs7R0FFRztBQUNILE1BQU0sT0FBTyxPQUFPO0lBQ2xCO0lBQ0Usa0RBQWtEO0lBQzNDLElBQVU7SUFDakIsb0RBQW9EO0lBQzdDLGVBQTZCO0lBQ3BDLGlFQUFpRTtJQUMxRCxXQUFlO0lBQ3RCLGlEQUFpRDtJQUMxQyxVQUFjO0lBQ3JCLGtEQUFrRDtJQUMzQyxNQUFjO0lBQ3JCLDZCQUE2QjtJQUN0QixLQUFTO0lBQ2hCOzs7T0FHRztJQUNJLGFBQWlCO0lBQ3hCLDhEQUE4RDtJQUN2RCxlQUFtQjtJQUMxQiwrREFBK0Q7SUFDeEQsS0FBYTtJQUNwQix3REFBd0Q7SUFDakQsU0FBb0I7UUFyQnBCLFNBQUksR0FBSixJQUFJLENBQU07UUFFVixvQkFBZSxHQUFmLGVBQWUsQ0FBYztRQUU3QixnQkFBVyxHQUFYLFdBQVcsQ0FBSTtRQUVmLGVBQVUsR0FBVixVQUFVLENBQUk7UUFFZCxXQUFNLEdBQU4sTUFBTSxDQUFRO1FBRWQsVUFBSyxHQUFMLEtBQUssQ0FBSTtRQUtULGtCQUFhLEdBQWIsYUFBYSxDQUFJO1FBRWpCLG9CQUFlLEdBQWYsZUFBZSxDQUFJO1FBRW5CLFVBQUssR0FBTCxLQUFLLENBQVE7UUFFYixjQUFTLEdBQVQsU0FBUyxDQUFXO0lBQzFCLENBQUM7SUFFSixRQUFRO1FBQ04sT0FB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