opnet 1.8.5 → 1.8.7
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/CHANGELOG.md +22 -0
- package/browser/_version.d.ts +1 -1
- package/browser/block/BlockWitness.d.ts +0 -1
- package/browser/block/interfaces/IBlockWitness.d.ts +0 -2
- package/browser/index.js +39 -45
- package/browser/providers/AbstractRpcProvider.d.ts +1 -1
- package/browser/utxos/UTXOsManager.d.ts +1 -1
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/abi/shared/json/opnet/OP_721_ABI.js +32 -28
- package/build/block/BlockWitness.d.ts +0 -1
- package/build/block/BlockWitness.js +0 -2
- package/build/block/interfaces/IBlockWitness.d.ts +0 -2
- package/build/providers/AbstractRpcProvider.d.ts +1 -1
- package/build/providers/AbstractRpcProvider.js +1 -3
- package/build/providers/WebsocketRpcProvider.js +0 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/utxos/UTXOsManager.d.ts +1 -1
- package/build/utxos/UTXOsManager.js +13 -10
- package/docs/abi-reference/op721-abi.md +50 -25
- package/package.json +4 -4
- package/src/_version.ts +1 -1
- package/src/abi/shared/json/opnet/OP_721_ABI.ts +32 -28
- package/src/block/BlockWitness.ts +0 -2
- package/src/block/interfaces/IBlockWitness.ts +0 -2
- package/src/providers/AbstractRpcProvider.ts +1 -4
- package/src/providers/WebsocketRpcProvider.ts +1 -2
- package/src/utxos/UTXOsManager.ts +13 -10
- package/test/mempool.test.ts +16 -15
- package/test/utxos-manager.test.ts +55 -2
|
@@ -7,7 +7,7 @@ export declare class UTXOsManager {
|
|
|
7
7
|
constructor(provider: IProviderForUTXO);
|
|
8
8
|
spentUTXO(address: string, spent: UTXOs, newUTXOs: UTXOs): void;
|
|
9
9
|
getPendingUTXOs(address: string): UTXOs;
|
|
10
|
-
clean(address?: string): void;
|
|
10
|
+
clean(address?: string, threshold?: bigint): void;
|
|
11
11
|
getUTXOs({ address, isCSV, optimize, mergePendingUTXOs, filterSpentUTXOs, olderThan, }: RequestUTXOsParams): Promise<UTXOs>;
|
|
12
12
|
getUTXOsForAmount({ address, amount, csvAddress, optimize, mergePendingUTXOs, filterSpentUTXOs, throwErrors, olderThan, maxUTXOs, throwIfUTXOsLimitReached, }: RequestUTXOsParamsWithAmount): Promise<UTXOs>;
|
|
13
13
|
getMultipleUTXOs({ requests, mergePendingUTXOs, filterSpentUTXOs, }: RequestMultipleUTXOsParams): Promise<Record<string, UTXOs>>;
|
|
@@ -42,9 +42,9 @@ export class UTXOsManager {
|
|
|
42
42
|
const addressData = this.getAddressData(address);
|
|
43
43
|
return addressData.pendingUTXOs;
|
|
44
44
|
}
|
|
45
|
-
clean(address) {
|
|
45
|
+
clean(address, threshold) {
|
|
46
46
|
if (address) {
|
|
47
|
-
const addressData = this.getAddressData(address);
|
|
47
|
+
const addressData = this.getAddressData(address, threshold);
|
|
48
48
|
addressData.spentUTXOs = [];
|
|
49
49
|
addressData.pendingUTXOs = [];
|
|
50
50
|
addressData.pendingUtxoDepth = {};
|
|
@@ -57,7 +57,7 @@ export class UTXOsManager {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
async getUTXOs({ address, isCSV = false, optimize = true, mergePendingUTXOs = true, filterSpentUTXOs = true, olderThan, }) {
|
|
60
|
-
const addressData = this.getAddressData(address);
|
|
60
|
+
const addressData = this.getAddressData(address, olderThan);
|
|
61
61
|
const fetchedData = await this.maybeFetchUTXOs(address, optimize, olderThan, isCSV);
|
|
62
62
|
const utxoKey = (utxo) => `${utxo.transactionId}:${utxo.outputIndex}`;
|
|
63
63
|
const spentRefKey = (ref) => `${ref.transactionId}:${ref.outputIndex}`;
|
|
@@ -244,9 +244,10 @@ export class UTXOsManager {
|
|
|
244
244
|
}
|
|
245
245
|
return result;
|
|
246
246
|
}
|
|
247
|
-
getAddressData(address) {
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
getAddressData(address, threshold) {
|
|
248
|
+
const addressWithThreshold = threshold ? `${address}_${threshold}` : address;
|
|
249
|
+
if (!this.dataByAddress[addressWithThreshold]) {
|
|
250
|
+
this.dataByAddress[addressWithThreshold] = {
|
|
250
251
|
spentUTXOs: [],
|
|
251
252
|
pendingUTXOs: [],
|
|
252
253
|
pendingUtxoDepth: {},
|
|
@@ -255,21 +256,23 @@ export class UTXOsManager {
|
|
|
255
256
|
lastFetchedData: null,
|
|
256
257
|
};
|
|
257
258
|
}
|
|
258
|
-
return this.dataByAddress[
|
|
259
|
+
return this.dataByAddress[addressWithThreshold];
|
|
259
260
|
}
|
|
260
261
|
async maybeFetchUTXOs(address, optimize, olderThan, isCSV = false) {
|
|
261
|
-
const addressData = this.getAddressData(address);
|
|
262
|
+
const addressData = this.getAddressData(address, olderThan);
|
|
262
263
|
const now = Date.now();
|
|
263
264
|
const age = now - addressData.lastFetchTimestamp;
|
|
264
265
|
if (now - addressData.lastCleanup > AUTO_PURGE_AFTER) {
|
|
265
|
-
this.clean(address);
|
|
266
|
+
this.clean(address, olderThan);
|
|
266
267
|
}
|
|
267
268
|
if (addressData.lastFetchedData && age < FETCH_COOLDOWN) {
|
|
268
269
|
return addressData.lastFetchedData;
|
|
269
270
|
}
|
|
270
271
|
addressData.lastFetchedData = await this.fetchUTXOs(address, optimize, olderThan, isCSV);
|
|
271
272
|
addressData.lastFetchTimestamp = now;
|
|
272
|
-
|
|
273
|
+
if (!olderThan) {
|
|
274
|
+
this.syncPendingDepthWithFetched(address);
|
|
275
|
+
}
|
|
273
276
|
return addressData.lastFetchedData;
|
|
274
277
|
}
|
|
275
278
|
async fetchUTXOs(address, optimize = false, olderThan, isCSV = false) {
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
| `name()` | - | `string` | Collection name |
|
|
31
31
|
| `symbol()` | - | `string` | Collection symbol |
|
|
32
32
|
| `maxSupply()` | - | `bigint` | Maximum supply |
|
|
33
|
-
| `collectionInfo()` | - | `{ icon, banner, description, website }` | Collection metadata |
|
|
34
33
|
| `tokenURI(tokenId)` | `bigint` | `string` | Token metadata URI |
|
|
35
34
|
| `metadata()` | - | Full metadata object | All metadata in one call |
|
|
36
35
|
|
|
@@ -79,7 +78,7 @@ import {
|
|
|
79
78
|
| `changeMetadata()` | - | - | Trigger metadata change |
|
|
80
79
|
| `setBaseURI(baseURI)` | `string` | - | Set base URI for tokens |
|
|
81
80
|
| `domainSeparator()` | - | `Uint8Array` | EIP-712 domain separator |
|
|
82
|
-
| `
|
|
81
|
+
| `nonceOf(owner)` | `Address` | `bigint` | Get nonce for signatures |
|
|
83
82
|
|
|
84
83
|
---
|
|
85
84
|
|
|
@@ -94,7 +93,7 @@ interface TransferredEventNFT {
|
|
|
94
93
|
operator: Address; // Who initiated the transfer
|
|
95
94
|
from: Address; // Sender
|
|
96
95
|
to: Address; // Recipient
|
|
97
|
-
|
|
96
|
+
tokenId: bigint; // Token ID
|
|
98
97
|
}
|
|
99
98
|
```
|
|
100
99
|
|
|
@@ -105,8 +104,8 @@ Emitted when single token approval changes.
|
|
|
105
104
|
```typescript
|
|
106
105
|
interface ApprovedEventNFT {
|
|
107
106
|
owner: Address; // Token owner
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
operator: Address; // Approved operator
|
|
108
|
+
tokenId: bigint; // Token ID
|
|
110
109
|
}
|
|
111
110
|
```
|
|
112
111
|
|
|
@@ -122,6 +121,28 @@ interface ApprovedForAllEventNFT {
|
|
|
122
121
|
}
|
|
123
122
|
```
|
|
124
123
|
|
|
124
|
+
### Burned
|
|
125
|
+
|
|
126
|
+
Emitted when a token is burned.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
interface BurnedEventNFT {
|
|
130
|
+
from: Address; // Token owner
|
|
131
|
+
tokenId: bigint; // Token ID
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Minted
|
|
136
|
+
|
|
137
|
+
Emitted when a new token is minted.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
interface MintedEventNFT {
|
|
141
|
+
to: Address; // Recipient
|
|
142
|
+
tokenId: bigint; // Token ID
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
125
146
|
### URI
|
|
126
147
|
|
|
127
148
|
Emitted when token URI changes.
|
|
@@ -165,10 +186,10 @@ console.log('Collection:', name.properties.name);
|
|
|
165
186
|
console.log('Symbol:', symbol.properties.symbol);
|
|
166
187
|
console.log('Max Supply:', maxSupply.properties.maxSupply);
|
|
167
188
|
|
|
168
|
-
// Get full collection metadata
|
|
169
|
-
const
|
|
170
|
-
console.log('Description:',
|
|
171
|
-
console.log('Website:',
|
|
189
|
+
// Get full collection metadata in one call
|
|
190
|
+
const metadata = await nft.metadata();
|
|
191
|
+
console.log('Description:', metadata.properties.description);
|
|
192
|
+
console.log('Website:', metadata.properties.website);
|
|
172
193
|
```
|
|
173
194
|
|
|
174
195
|
### Check Ownership
|
|
@@ -363,18 +384,6 @@ export const OP_721_ABI: BitcoinInterfaceAbi = [
|
|
|
363
384
|
outputs: [{ name: 'maxSupply', type: ABIDataTypes.UINT256 }],
|
|
364
385
|
type: BitcoinAbiTypes.Function,
|
|
365
386
|
},
|
|
366
|
-
{
|
|
367
|
-
name: 'collectionInfo',
|
|
368
|
-
constant: true,
|
|
369
|
-
inputs: [],
|
|
370
|
-
outputs: [
|
|
371
|
-
{ name: 'icon', type: ABIDataTypes.STRING },
|
|
372
|
-
{ name: 'banner', type: ABIDataTypes.STRING },
|
|
373
|
-
{ name: 'description', type: ABIDataTypes.STRING },
|
|
374
|
-
{ name: 'website', type: ABIDataTypes.STRING },
|
|
375
|
-
],
|
|
376
|
-
type: BitcoinAbiTypes.Function,
|
|
377
|
-
},
|
|
378
387
|
{
|
|
379
388
|
name: 'tokenURI',
|
|
380
389
|
constant: true,
|
|
@@ -549,7 +558,7 @@ export const OP_721_ABI: BitcoinInterfaceAbi = [
|
|
|
549
558
|
type: BitcoinAbiTypes.Function,
|
|
550
559
|
},
|
|
551
560
|
{
|
|
552
|
-
name: '
|
|
561
|
+
name: 'nonceOf',
|
|
553
562
|
constant: true,
|
|
554
563
|
inputs: [{ name: 'owner', type: ABIDataTypes.ADDRESS }],
|
|
555
564
|
outputs: [{ name: 'nonce', type: ABIDataTypes.UINT256 }],
|
|
@@ -563,7 +572,7 @@ export const OP_721_ABI: BitcoinInterfaceAbi = [
|
|
|
563
572
|
{ name: 'operator', type: ABIDataTypes.ADDRESS },
|
|
564
573
|
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
565
574
|
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
566
|
-
{ name: '
|
|
575
|
+
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
567
576
|
],
|
|
568
577
|
type: BitcoinAbiTypes.Event,
|
|
569
578
|
},
|
|
@@ -571,8 +580,8 @@ export const OP_721_ABI: BitcoinInterfaceAbi = [
|
|
|
571
580
|
name: 'Approved',
|
|
572
581
|
values: [
|
|
573
582
|
{ name: 'owner', type: ABIDataTypes.ADDRESS },
|
|
574
|
-
{ name: '
|
|
575
|
-
{ name: '
|
|
583
|
+
{ name: 'operator', type: ABIDataTypes.ADDRESS },
|
|
584
|
+
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
576
585
|
],
|
|
577
586
|
type: BitcoinAbiTypes.Event,
|
|
578
587
|
},
|
|
@@ -585,6 +594,22 @@ export const OP_721_ABI: BitcoinInterfaceAbi = [
|
|
|
585
594
|
],
|
|
586
595
|
type: BitcoinAbiTypes.Event,
|
|
587
596
|
},
|
|
597
|
+
{
|
|
598
|
+
name: 'Burned',
|
|
599
|
+
values: [
|
|
600
|
+
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
601
|
+
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
602
|
+
],
|
|
603
|
+
type: BitcoinAbiTypes.Event,
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: 'Minted',
|
|
607
|
+
values: [
|
|
608
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
609
|
+
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
610
|
+
],
|
|
611
|
+
type: BitcoinAbiTypes.Event,
|
|
612
|
+
},
|
|
588
613
|
{
|
|
589
614
|
name: 'URI',
|
|
590
615
|
values: [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opnet",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.8.
|
|
4
|
+
"version": "1.8.7",
|
|
5
5
|
"author": "OP_NET",
|
|
6
6
|
"description": "The perfect library for building Bitcoin-based applications.",
|
|
7
7
|
"engines": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
103
|
"@babel/core": "^7.29.0",
|
|
104
|
-
"@babel/preset-env": "^7.29.
|
|
104
|
+
"@babel/preset-env": "^7.29.2",
|
|
105
105
|
"@babel/preset-typescript": "^7.28.5",
|
|
106
106
|
"@eslint/js": "^10.0.1",
|
|
107
107
|
"@microsoft/api-extractor": "7.57.7",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"stream-browserify": "^3.0.0",
|
|
117
117
|
"typedoc": "^0.28.17",
|
|
118
118
|
"typescript": "^5.9.3",
|
|
119
|
-
"typescript-eslint": "^8.57.
|
|
119
|
+
"typescript-eslint": "^8.57.1",
|
|
120
120
|
"vite": "^8.0.0",
|
|
121
121
|
"vite-plugin-dts": "4.5.4",
|
|
122
122
|
"vite-plugin-node-polyfills": "^0.25.0",
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"p-limit": "^7.3.0",
|
|
144
144
|
"pako": "^2.1.0",
|
|
145
145
|
"protobufjs": "^8.0.0",
|
|
146
|
-
"undici": "^7.24.
|
|
146
|
+
"undici": "^7.24.4"
|
|
147
147
|
},
|
|
148
148
|
"overrides": {
|
|
149
149
|
"vite-plugin-node-polyfills": {
|
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.8.
|
|
1
|
+
export const version = '1.8.7';
|
|
@@ -23,7 +23,7 @@ export const OP721Events: BitcoinInterfaceAbi = [
|
|
|
23
23
|
type: ABIDataTypes.ADDRESS,
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
name: '
|
|
26
|
+
name: 'tokenId',
|
|
27
27
|
type: ABIDataTypes.UINT256,
|
|
28
28
|
},
|
|
29
29
|
],
|
|
@@ -37,11 +37,11 @@ export const OP721Events: BitcoinInterfaceAbi = [
|
|
|
37
37
|
type: ABIDataTypes.ADDRESS,
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
name: '
|
|
40
|
+
name: 'operator',
|
|
41
41
|
type: ABIDataTypes.ADDRESS,
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
|
-
name: '
|
|
44
|
+
name: 'tokenId',
|
|
45
45
|
type: ABIDataTypes.UINT256,
|
|
46
46
|
},
|
|
47
47
|
],
|
|
@@ -65,6 +65,34 @@ export const OP721Events: BitcoinInterfaceAbi = [
|
|
|
65
65
|
],
|
|
66
66
|
type: BitcoinAbiTypes.Event,
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
name: 'Burned',
|
|
70
|
+
values: [
|
|
71
|
+
{
|
|
72
|
+
name: 'from',
|
|
73
|
+
type: ABIDataTypes.ADDRESS
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'tokenId',
|
|
77
|
+
type: ABIDataTypes.UINT256
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
type: BitcoinAbiTypes.Event,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'Minted',
|
|
84
|
+
values: [
|
|
85
|
+
{
|
|
86
|
+
name: 'to',
|
|
87
|
+
type: ABIDataTypes.ADDRESS,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'tokenId',
|
|
91
|
+
type: ABIDataTypes.UINT256,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
type: BitcoinAbiTypes.Event,
|
|
95
|
+
},
|
|
68
96
|
{
|
|
69
97
|
name: 'URI',
|
|
70
98
|
values: [
|
|
@@ -121,30 +149,6 @@ export const OP_721_ABI: BitcoinInterfaceAbi = [
|
|
|
121
149
|
},
|
|
122
150
|
],
|
|
123
151
|
},
|
|
124
|
-
{
|
|
125
|
-
name: 'collectionInfo',
|
|
126
|
-
type: BitcoinAbiTypes.Function,
|
|
127
|
-
constant: true,
|
|
128
|
-
inputs: [],
|
|
129
|
-
outputs: [
|
|
130
|
-
{
|
|
131
|
-
name: 'icon',
|
|
132
|
-
type: ABIDataTypes.STRING,
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: 'banner',
|
|
136
|
-
type: ABIDataTypes.STRING,
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: 'description',
|
|
140
|
-
type: ABIDataTypes.STRING,
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'website',
|
|
144
|
-
type: ABIDataTypes.STRING,
|
|
145
|
-
},
|
|
146
|
-
],
|
|
147
|
-
},
|
|
148
152
|
{
|
|
149
153
|
name: 'tokenURI',
|
|
150
154
|
type: BitcoinAbiTypes.Function,
|
|
@@ -426,7 +430,7 @@ export const OP_721_ABI: BitcoinInterfaceAbi = [
|
|
|
426
430
|
],
|
|
427
431
|
},
|
|
428
432
|
{
|
|
429
|
-
name: '
|
|
433
|
+
name: 'nonceOf',
|
|
430
434
|
type: BitcoinAbiTypes.Function,
|
|
431
435
|
constant: true,
|
|
432
436
|
inputs: [
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
} from './interfaces/IBlockWitness.js';
|
|
9
9
|
|
|
10
10
|
export class BlockWitnessAPI implements IBlockWitnessAPI {
|
|
11
|
-
public readonly trusted: boolean;
|
|
12
11
|
public readonly signature: Uint8Array;
|
|
13
12
|
public readonly timestamp: number;
|
|
14
13
|
public readonly proofs: readonly Uint8Array[];
|
|
@@ -16,7 +15,6 @@ export class BlockWitnessAPI implements IBlockWitnessAPI {
|
|
|
16
15
|
public readonly publicKey?: Address;
|
|
17
16
|
|
|
18
17
|
constructor(data: RawBlockWitnessAPI) {
|
|
19
|
-
this.trusted = data.trusted;
|
|
20
18
|
this.signature = stringBase64ToBuffer(data.signature);
|
|
21
19
|
this.timestamp = data.timestamp;
|
|
22
20
|
this.proofs = Object.freeze(data.proofs.map((proof) => stringBase64ToBuffer(proof)));
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Address } from '@btc-vision/transaction';
|
|
2
2
|
|
|
3
3
|
export interface IBlockWitnessAPI {
|
|
4
|
-
readonly trusted: boolean;
|
|
5
4
|
readonly signature: Uint8Array;
|
|
6
5
|
readonly timestamp: number;
|
|
7
6
|
|
|
@@ -12,7 +11,6 @@ export interface IBlockWitnessAPI {
|
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export interface RawBlockWitnessAPI {
|
|
15
|
-
readonly trusted: boolean;
|
|
16
14
|
readonly signature: string;
|
|
17
15
|
readonly timestamp: number;
|
|
18
16
|
|
|
@@ -759,7 +759,6 @@ export abstract class AbstractRpcProvider {
|
|
|
759
759
|
* Get block witnesses.
|
|
760
760
|
* @description This method is used to get the witnesses of a block. This proves that the actions executed inside a block are valid and confirmed by the network. If the minimum number of witnesses are not met, the block is considered as potentially invalid.
|
|
761
761
|
* @param {BlockTag} height The block number or hash, use -1 for latest block
|
|
762
|
-
* @param {boolean} [trusted] Whether to trust the witnesses or not
|
|
763
762
|
* @param {number} [limit] The maximum number of witnesses to return
|
|
764
763
|
* @param {number} [page] The page number of the witnesses
|
|
765
764
|
* @returns {Promise<BlockWitnesses>} The witnesses of the block
|
|
@@ -768,13 +767,11 @@ export abstract class AbstractRpcProvider {
|
|
|
768
767
|
*/
|
|
769
768
|
public async getBlockWitness(
|
|
770
769
|
height: BigNumberish = -1,
|
|
771
|
-
trusted?: boolean,
|
|
772
770
|
limit?: number,
|
|
773
771
|
page?: number,
|
|
774
772
|
): Promise<BlockWitnesses> {
|
|
775
|
-
const params: [BigNumberish,
|
|
773
|
+
const params: [BigNumberish, number?, number?] = [height.toString()];
|
|
776
774
|
|
|
777
|
-
if (trusted !== undefined && trusted !== null) params.push(trusted);
|
|
778
775
|
if (limit !== undefined && limit !== null) params.push(limit);
|
|
779
776
|
if (page !== undefined && page !== null) params.push(page);
|
|
780
777
|
|
|
@@ -398,12 +398,11 @@ export class WebSocketRpcProvider extends AbstractRpcProvider {
|
|
|
398
398
|
};
|
|
399
399
|
|
|
400
400
|
case JSONRpcMethods.BLOCK_WITNESS:
|
|
401
|
-
// GetBlockWitnessRequest: requestId=1, height=2,
|
|
401
|
+
// GetBlockWitnessRequest: requestId=1, height=2, limit=3, page=4
|
|
402
402
|
return {
|
|
403
403
|
2: Long.fromString(String(params[0] ?? -1)),
|
|
404
404
|
3: params[1],
|
|
405
405
|
4: params[2],
|
|
406
|
-
5: params[3],
|
|
407
406
|
};
|
|
408
407
|
|
|
409
408
|
case JSONRpcMethods.GAS:
|
|
@@ -116,10 +116,10 @@ export class UTXOsManager {
|
|
|
116
116
|
/**
|
|
117
117
|
* Clean (reset) the data for a particular address or for all addresses if none is passed.
|
|
118
118
|
*/
|
|
119
|
-
public clean(address?: string): void {
|
|
119
|
+
public clean(address?: string, threshold?: bigint): void {
|
|
120
120
|
if (address) {
|
|
121
121
|
// Reset a single address
|
|
122
|
-
const addressData = this.getAddressData(address);
|
|
122
|
+
const addressData = this.getAddressData(address, threshold);
|
|
123
123
|
addressData.spentUTXOs = [];
|
|
124
124
|
addressData.pendingUTXOs = [];
|
|
125
125
|
addressData.pendingUtxoDepth = {};
|
|
@@ -156,7 +156,7 @@ export class UTXOsManager {
|
|
|
156
156
|
filterSpentUTXOs = true,
|
|
157
157
|
olderThan,
|
|
158
158
|
}: RequestUTXOsParams): Promise<UTXOs> {
|
|
159
|
-
const addressData = this.getAddressData(address);
|
|
159
|
+
const addressData = this.getAddressData(address, olderThan);
|
|
160
160
|
const fetchedData = await this.maybeFetchUTXOs(address, optimize, olderThan, isCSV);
|
|
161
161
|
|
|
162
162
|
const utxoKey = (utxo: UTXO) => `${utxo.transactionId}:${utxo.outputIndex}`;
|
|
@@ -493,9 +493,10 @@ export class UTXOsManager {
|
|
|
493
493
|
/**
|
|
494
494
|
* Return the AddressData object for a given address. Initializes it if nonexistent.
|
|
495
495
|
*/
|
|
496
|
-
private getAddressData(address: string): AddressData {
|
|
497
|
-
|
|
498
|
-
|
|
496
|
+
private getAddressData(address: string, threshold?: bigint): AddressData {
|
|
497
|
+
const addressWithThreshold = threshold ? `${address}_${threshold}` : address;
|
|
498
|
+
if (!this.dataByAddress[addressWithThreshold]) {
|
|
499
|
+
this.dataByAddress[addressWithThreshold] = {
|
|
499
500
|
spentUTXOs: [],
|
|
500
501
|
pendingUTXOs: [],
|
|
501
502
|
pendingUtxoDepth: {},
|
|
@@ -504,7 +505,7 @@ export class UTXOsManager {
|
|
|
504
505
|
lastFetchedData: null,
|
|
505
506
|
};
|
|
506
507
|
}
|
|
507
|
-
return this.dataByAddress[
|
|
508
|
+
return this.dataByAddress[addressWithThreshold];
|
|
508
509
|
}
|
|
509
510
|
|
|
510
511
|
/**
|
|
@@ -516,13 +517,13 @@ export class UTXOsManager {
|
|
|
516
517
|
olderThan: bigint | undefined,
|
|
517
518
|
isCSV: boolean = false,
|
|
518
519
|
): Promise<IUTXOsData> {
|
|
519
|
-
const addressData = this.getAddressData(address);
|
|
520
|
+
const addressData = this.getAddressData(address, olderThan);
|
|
520
521
|
const now = Date.now();
|
|
521
522
|
const age = now - addressData.lastFetchTimestamp;
|
|
522
523
|
|
|
523
524
|
// Purge if it's been too long for this address
|
|
524
525
|
if (now - addressData.lastCleanup > AUTO_PURGE_AFTER) {
|
|
525
|
-
this.clean(address); // Clean only this address data
|
|
526
|
+
this.clean(address, olderThan); // Clean only this address data
|
|
526
527
|
}
|
|
527
528
|
|
|
528
529
|
// If it's been less than FETCH_COOLDOWN ms, return cached data if available
|
|
@@ -535,7 +536,9 @@ export class UTXOsManager {
|
|
|
535
536
|
addressData.lastFetchTimestamp = now;
|
|
536
537
|
|
|
537
538
|
// Remove any pending UTXOs that have become confirmed or known spent
|
|
538
|
-
|
|
539
|
+
if (!olderThan) {
|
|
540
|
+
this.syncPendingDepthWithFetched(address);
|
|
541
|
+
}
|
|
539
542
|
|
|
540
543
|
return addressData.lastFetchedData;
|
|
541
544
|
}
|
package/test/mempool.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { AddressTypes } from '@btc-vision/transaction';
|
|
|
5
5
|
import type { JsonRpcPayload } from '../src/providers/interfaces/JSONRpc.js';
|
|
6
6
|
import type { JsonRpcCallResult } from '../src/providers/interfaces/JSONRpcResult.js';
|
|
7
7
|
import type { MempoolInfo } from '../src/providers/interfaces/mempool/MempoolInfo.js';
|
|
8
|
-
import type { IMempoolTransactionData } from '../src/providers/interfaces/mempool/MempoolTransactionData.js';
|
|
8
|
+
import type { IMempoolTransactionData, IMempoolOPNetTransactionData } from '../src/providers/interfaces/mempool/MempoolTransactionData.js';
|
|
9
9
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Mock provider that intercepts _send
|
|
@@ -146,7 +146,7 @@ describe('Mempool API - Unit Tests', () => {
|
|
|
146
146
|
// ========================================================================
|
|
147
147
|
|
|
148
148
|
describe('getPendingTransaction', () => {
|
|
149
|
-
const mockTx:
|
|
149
|
+
const mockTx: IMempoolOPNetTransactionData = {
|
|
150
150
|
id: 'abc123def456abc123def456abc123def456abc123def456abc123def456abc1',
|
|
151
151
|
firstSeen: '2023-11-14T22:13:20.000Z',
|
|
152
152
|
blockHeight: '0xcf080',
|
|
@@ -270,13 +270,14 @@ describe('Mempool API - Unit Tests', () => {
|
|
|
270
270
|
|
|
271
271
|
it('should handle Generic transaction', async () => {
|
|
272
272
|
const nonOPNetTx: IMempoolTransactionData = {
|
|
273
|
-
|
|
273
|
+
id: mockTx.id,
|
|
274
|
+
firstSeen: mockTx.firstSeen,
|
|
275
|
+
blockHeight: mockTx.blockHeight,
|
|
274
276
|
transactionType: 'Generic',
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
calldata: undefined,
|
|
277
|
+
psbt: mockTx.psbt,
|
|
278
|
+
inputs: mockTx.inputs,
|
|
279
|
+
outputs: mockTx.outputs,
|
|
280
|
+
raw: mockTx.raw,
|
|
280
281
|
};
|
|
281
282
|
|
|
282
283
|
provider.mockSend.mockResolvedValue([
|
|
@@ -350,7 +351,7 @@ describe('Mempool API - Unit Tests', () => {
|
|
|
350
351
|
// ========================================================================
|
|
351
352
|
|
|
352
353
|
describe('getLatestPendingTransactions', () => {
|
|
353
|
-
const mockTx1:
|
|
354
|
+
const mockTx1: IMempoolOPNetTransactionData = {
|
|
354
355
|
id: 'a000000000000000000000000000000000000000000000000000000000000001',
|
|
355
356
|
firstSeen: '2023-11-14T22:13:20.000Z',
|
|
356
357
|
blockHeight: '0xcf080',
|
|
@@ -603,7 +604,7 @@ describe('Mempool API - Unit Tests', () => {
|
|
|
603
604
|
// ========================================================================
|
|
604
605
|
|
|
605
606
|
describe('getLatestPendingTransactionsByAddresses', () => {
|
|
606
|
-
const mockTx1:
|
|
607
|
+
const mockTx1: IMempoolOPNetTransactionData = {
|
|
607
608
|
id: 'a000000000000000000000000000000000000000000000000000000000000001',
|
|
608
609
|
firstSeen: '2023-11-14T22:13:20.000Z',
|
|
609
610
|
blockHeight: '0xcf080',
|
|
@@ -710,12 +711,12 @@ describe('Mempool API - Unit Tests', () => {
|
|
|
710
711
|
// Integration Tests - Real Regtest Network
|
|
711
712
|
// ============================================================================
|
|
712
713
|
|
|
713
|
-
describe('Mempool API - Integration Tests (
|
|
714
|
-
const
|
|
714
|
+
describe('Mempool API - Integration Tests (testnet.opnet.org)', () => {
|
|
715
|
+
const TESTNET_URL = 'https://testnet.opnet.org';
|
|
715
716
|
let provider: JSONRpcProvider;
|
|
716
717
|
|
|
717
718
|
beforeEach(() => {
|
|
718
|
-
provider = new JSONRpcProvider({ url:
|
|
719
|
+
provider = new JSONRpcProvider({ url: TESTNET_URL, network: networks.opnetTestnet });
|
|
719
720
|
});
|
|
720
721
|
|
|
721
722
|
describe('getMempoolInfo', () => {
|
|
@@ -745,8 +746,8 @@ describe('Mempool API - Integration Tests (regtest.opnet.org)', () => {
|
|
|
745
746
|
if (txs.length > 0) {
|
|
746
747
|
const tx = txs[0];
|
|
747
748
|
expect(typeof tx.id).toBe('string');
|
|
748
|
-
expect(
|
|
749
|
-
expect(
|
|
749
|
+
expect(tx.firstSeen).toBeDefined();
|
|
750
|
+
expect(tx.blockHeight).toBeDefined();
|
|
750
751
|
expect(typeof tx.transactionType).toBe('string');
|
|
751
752
|
expect(typeof tx.psbt).toBe('boolean');
|
|
752
753
|
expect(Array.isArray(tx.inputs)).toBe(true);
|
|
@@ -83,7 +83,7 @@ function createMockProvider(): IProviderForUTXO & {
|
|
|
83
83
|
} {
|
|
84
84
|
const mockCallPayloadSingle = vi.fn<(payload: JsonRpcPayload) => Promise<JsonRpcResult>>();
|
|
85
85
|
const mockCallMultiplePayloads = vi.fn<(payloads: JsonRpcPayload[]) => Promise<JsonRpcCallResult>>();
|
|
86
|
-
const mockBuildJsonRpcPayload = vi.fn((method, params) => ({
|
|
86
|
+
const mockBuildJsonRpcPayload = vi.fn((method: unknown, params: unknown) => ({
|
|
87
87
|
method,
|
|
88
88
|
params,
|
|
89
89
|
id: 1,
|
|
@@ -91,6 +91,7 @@ function createMockProvider(): IProviderForUTXO & {
|
|
|
91
91
|
}));
|
|
92
92
|
|
|
93
93
|
return {
|
|
94
|
+
// @ts-expect-error - This is a mockup for mockBuildJsonRpcPayload
|
|
94
95
|
buildJsonRpcPayload: mockBuildJsonRpcPayload,
|
|
95
96
|
callPayloadSingle: mockCallPayloadSingle,
|
|
96
97
|
callMultiplePayloads: mockCallMultiplePayloads,
|
|
@@ -1159,7 +1160,7 @@ describe('UTXOsManager - Ultra Complex Tests', () => {
|
|
|
1159
1160
|
expect(manager.getPendingUTXOs(address)).toHaveLength(1);
|
|
1160
1161
|
});
|
|
1161
1162
|
|
|
1162
|
-
it('should handle no fetched data gracefully',
|
|
1163
|
+
it('should handle no fetched data gracefully', () => {
|
|
1163
1164
|
const address = 'bc1qtest...';
|
|
1164
1165
|
|
|
1165
1166
|
// Add pending UTXO
|
|
@@ -1488,6 +1489,58 @@ describe('UTXOsManager - Ultra Complex Tests', () => {
|
|
|
1488
1489
|
// Note: First call fetches, subsequent use cache
|
|
1489
1490
|
expect(callCount).toBeGreaterThanOrEqual(1);
|
|
1490
1491
|
});
|
|
1492
|
+
|
|
1493
|
+
it('should do real getUTXOs fetch for calls with different threshold', async () => {
|
|
1494
|
+
const address = 'bc1qconcurrent...';
|
|
1495
|
+
|
|
1496
|
+
let callCount = 0;
|
|
1497
|
+
mockProvider.mockCallPayloadSingle.mockImplementation(async (data: unknown) => {
|
|
1498
|
+
++callCount;
|
|
1499
|
+
|
|
1500
|
+
const mockData = createMockRawUTXOsData([
|
|
1501
|
+
{ txId: `tx${callCount}`, index: 0, value: '1000' },
|
|
1502
|
+
]);
|
|
1503
|
+
|
|
1504
|
+
// Simulate network delay
|
|
1505
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
1506
|
+
return { result: mockData, jsonrpc: '2.0', id: callCount };
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
// First call without threshold
|
|
1510
|
+
const promises = [
|
|
1511
|
+
manager.getUTXOs({ address })
|
|
1512
|
+
];
|
|
1513
|
+
vi.advanceTimersByTime(50);
|
|
1514
|
+
const first = await Promise.all(promises);
|
|
1515
|
+
expect(first).toHaveLength(1);
|
|
1516
|
+
expect(first[0][0]).toHaveProperty('transactionId', 'tx1');
|
|
1517
|
+
|
|
1518
|
+
// Fire multiple concurrent requests
|
|
1519
|
+
const promises2 = [
|
|
1520
|
+
manager.getUTXOs({ address, olderThan: 1n }),
|
|
1521
|
+
manager.getUTXOs({ address }),
|
|
1522
|
+
manager.getUTXOs({ address, olderThan: 2n }),
|
|
1523
|
+
];
|
|
1524
|
+
|
|
1525
|
+
// Advance timers to complete all requests
|
|
1526
|
+
vi.advanceTimersByTime(50);
|
|
1527
|
+
|
|
1528
|
+
const results = await Promise.all(promises2);
|
|
1529
|
+
const [csv1, all, csv2] = results;
|
|
1530
|
+
|
|
1531
|
+
// All should return same data
|
|
1532
|
+
for (const result of results) {
|
|
1533
|
+
expect(result).toHaveLength(1);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
expect(csv1[0]).toHaveProperty('transactionId', 'tx2');
|
|
1537
|
+
expect(all[0]).toHaveProperty('transactionId', 'tx1'); // Fetched from cache
|
|
1538
|
+
expect(csv2[0]).toHaveProperty('transactionId', 'tx3');
|
|
1539
|
+
|
|
1540
|
+
// But only 1 actual fetch should have happened (cache)
|
|
1541
|
+
// Note: First call fetches, subsequent use cache
|
|
1542
|
+
expect(callCount).toBe(3); // Only one call cached
|
|
1543
|
+
});
|
|
1491
1544
|
});
|
|
1492
1545
|
|
|
1493
1546
|
// ==========================================
|