opnet 1.2.20 → 1.2.22
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/browser/index.js +1 -1
- package/browser/providers/AbstractRpcProvider.d.ts +5 -3
- package/browser/transactions/Transaction.d.ts +1 -0
- package/browser/utxos/UTXOsManager.d.ts +5 -0
- package/build/providers/AbstractRpcProvider.d.ts +5 -3
- package/build/providers/AbstractRpcProvider.js +56 -11
- package/build/transactions/Transaction.d.ts +1 -0
- package/build/transactions/Transaction.js +2 -0
- package/build/utxos/UTXOsManager.d.ts +5 -0
- package/build/utxos/UTXOsManager.js +70 -20
- package/eslint.config.js +1 -0
- package/package.json +3 -3
- package/src/providers/AbstractRpcProvider.ts +111 -40
- package/src/transactions/Transaction.ts +7 -0
- package/src/utxos/UTXOsManager.ts +149 -42
|
@@ -25,10 +25,10 @@ export declare abstract class AbstractRpcProvider {
|
|
|
25
25
|
readonly network: Network;
|
|
26
26
|
private nextId;
|
|
27
27
|
private chainId;
|
|
28
|
-
protected constructor(network: Network);
|
|
29
|
-
private _utxoManager;
|
|
30
28
|
private gasCache;
|
|
31
29
|
private lastFetchedGas;
|
|
30
|
+
protected constructor(network: Network);
|
|
31
|
+
private _utxoManager;
|
|
32
32
|
get utxoManager(): UTXOsManager;
|
|
33
33
|
getPublicKeyInfo(address: string): Promise<Address>;
|
|
34
34
|
validateAddress(addr: string | Address, network: Network): AddressTypes | null;
|
|
@@ -37,6 +37,7 @@ export declare abstract class AbstractRpcProvider {
|
|
|
37
37
|
getBlocks(blockNumbers: BlockTag[], prefetchTxs?: boolean): Promise<Block[]>;
|
|
38
38
|
getBlockByHash(blockHash: string): Promise<Block>;
|
|
39
39
|
getBalance(addressLike: string, filterOrdinals?: boolean): Promise<bigint>;
|
|
40
|
+
getBalances(addressesLike: string[], filterOrdinals?: boolean): Promise<Record<string, bigint>>;
|
|
40
41
|
getTransaction(txHash: string): Promise<TransactionBase<OPNetTransactionTypes>>;
|
|
41
42
|
getTransactionReceipt(txHash: string): Promise<TransactionReceipt>;
|
|
42
43
|
getNetwork(): Network;
|
|
@@ -45,8 +46,8 @@ export declare abstract class AbstractRpcProvider {
|
|
|
45
46
|
getStorageAt(address: string | Address, rawPointer: bigint | string, proofs?: boolean, height?: BigNumberish): Promise<StoredValue>;
|
|
46
47
|
call(to: string | Address, data: Buffer | string, from?: Address, height?: BigNumberish, simulatedTransaction?: ParsedSimulatedTransaction, accessList?: IAccessList): Promise<CallResult | ICallRequestError>;
|
|
47
48
|
gasParameters(): Promise<BlockGasParameters>;
|
|
48
|
-
private _gasParameters;
|
|
49
49
|
sendRawTransaction(tx: string, psbt: boolean): Promise<BroadcastedTransaction>;
|
|
50
|
+
sendRawTransactions(txs: string[]): Promise<BroadcastedTransaction[]>;
|
|
50
51
|
getBlockWitness(height?: BigNumberish, trusted?: boolean, limit?: number, page?: number): Promise<BlockWitnesses>;
|
|
51
52
|
getReorg(fromBlock?: BigNumberish, toBlock?: BigNumberish): Promise<ReorgInformation[]>;
|
|
52
53
|
abstract _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<JsonRpcCallResult>;
|
|
@@ -55,6 +56,7 @@ export declare abstract class AbstractRpcProvider {
|
|
|
55
56
|
buildJsonRpcPayload<T extends JSONRpcMethods>(method: T, params: unknown[]): JsonRpcPayload;
|
|
56
57
|
getPublicKeysInfo(addresses: string | string[] | Address | Address[]): Promise<AddressesInfo>;
|
|
57
58
|
protected abstract providerUrl(url: string): string;
|
|
59
|
+
private _gasParameters;
|
|
58
60
|
private parseSimulatedTransaction;
|
|
59
61
|
private bufferToHex;
|
|
60
62
|
private bigintToBase64;
|
|
@@ -12,6 +12,7 @@ export declare abstract class TransactionBase<T extends OPNetTransactionTypes> e
|
|
|
12
12
|
readonly index: number;
|
|
13
13
|
readonly burnedBitcoin: BigNumberish;
|
|
14
14
|
readonly priorityFee: BigNumberish;
|
|
15
|
+
readonly maxGasSat: BigNumberish;
|
|
15
16
|
readonly inputs: TransactionInput[];
|
|
16
17
|
readonly outputs: TransactionOutput[];
|
|
17
18
|
readonly OPNetType: T;
|
|
@@ -5,11 +5,16 @@ export declare class UTXOsManager {
|
|
|
5
5
|
private readonly provider;
|
|
6
6
|
private spentUTXOs;
|
|
7
7
|
private pendingUTXOs;
|
|
8
|
+
private pendingUtxoDepth;
|
|
8
9
|
private lastCleanup;
|
|
10
|
+
private lastFetchTimestamp;
|
|
11
|
+
private lastFetchedData;
|
|
9
12
|
constructor(provider: AbstractRpcProvider);
|
|
10
13
|
spentUTXO(spent: UTXOs, newUTXOs: UTXOs): void;
|
|
11
14
|
clean(): void;
|
|
12
15
|
getUTXOs({ address, optimize, mergePendingUTXOs, filterSpentUTXOs, }: RequestUTXOsParams): Promise<UTXOs>;
|
|
13
16
|
getUTXOsForAmount({ address, amount, optimize, mergePendingUTXOs, filterSpentUTXOs, throwErrors, }: RequestUTXOsParamsWithAmount): Promise<UTXOs>;
|
|
17
|
+
private maybeFetchUTXOs;
|
|
14
18
|
private fetchUTXOs;
|
|
19
|
+
private syncPendingDepthWithFetched;
|
|
15
20
|
}
|
|
@@ -25,10 +25,10 @@ export declare abstract class AbstractRpcProvider {
|
|
|
25
25
|
readonly network: Network;
|
|
26
26
|
private nextId;
|
|
27
27
|
private chainId;
|
|
28
|
-
protected constructor(network: Network);
|
|
29
|
-
private _utxoManager;
|
|
30
28
|
private gasCache;
|
|
31
29
|
private lastFetchedGas;
|
|
30
|
+
protected constructor(network: Network);
|
|
31
|
+
private _utxoManager;
|
|
32
32
|
get utxoManager(): UTXOsManager;
|
|
33
33
|
getPublicKeyInfo(address: string): Promise<Address>;
|
|
34
34
|
validateAddress(addr: string | Address, network: Network): AddressTypes | null;
|
|
@@ -37,6 +37,7 @@ export declare abstract class AbstractRpcProvider {
|
|
|
37
37
|
getBlocks(blockNumbers: BlockTag[], prefetchTxs?: boolean): Promise<Block[]>;
|
|
38
38
|
getBlockByHash(blockHash: string): Promise<Block>;
|
|
39
39
|
getBalance(addressLike: string, filterOrdinals?: boolean): Promise<bigint>;
|
|
40
|
+
getBalances(addressesLike: string[], filterOrdinals?: boolean): Promise<Record<string, bigint>>;
|
|
40
41
|
getTransaction(txHash: string): Promise<TransactionBase<OPNetTransactionTypes>>;
|
|
41
42
|
getTransactionReceipt(txHash: string): Promise<TransactionReceipt>;
|
|
42
43
|
getNetwork(): Network;
|
|
@@ -45,8 +46,8 @@ export declare abstract class AbstractRpcProvider {
|
|
|
45
46
|
getStorageAt(address: string | Address, rawPointer: bigint | string, proofs?: boolean, height?: BigNumberish): Promise<StoredValue>;
|
|
46
47
|
call(to: string | Address, data: Buffer | string, from?: Address, height?: BigNumberish, simulatedTransaction?: ParsedSimulatedTransaction, accessList?: IAccessList): Promise<CallResult | ICallRequestError>;
|
|
47
48
|
gasParameters(): Promise<BlockGasParameters>;
|
|
48
|
-
private _gasParameters;
|
|
49
49
|
sendRawTransaction(tx: string, psbt: boolean): Promise<BroadcastedTransaction>;
|
|
50
|
+
sendRawTransactions(txs: string[]): Promise<BroadcastedTransaction[]>;
|
|
50
51
|
getBlockWitness(height?: BigNumberish, trusted?: boolean, limit?: number, page?: number): Promise<BlockWitnesses>;
|
|
51
52
|
getReorg(fromBlock?: BigNumberish, toBlock?: BigNumberish): Promise<ReorgInformation[]>;
|
|
52
53
|
abstract _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<JsonRpcCallResult>;
|
|
@@ -55,6 +56,7 @@ export declare abstract class AbstractRpcProvider {
|
|
|
55
56
|
buildJsonRpcPayload<T extends JSONRpcMethods>(method: T, params: unknown[]): JsonRpcPayload;
|
|
56
57
|
getPublicKeysInfo(addresses: string | string[] | Address | Address[]): Promise<AddressesInfo>;
|
|
57
58
|
protected abstract providerUrl(url: string): string;
|
|
59
|
+
private _gasParameters;
|
|
58
60
|
private parseSimulatedTransaction;
|
|
59
61
|
private bufferToHex;
|
|
60
62
|
private bigintToBase64;
|
|
@@ -13,12 +13,12 @@ export class AbstractRpcProvider {
|
|
|
13
13
|
network;
|
|
14
14
|
nextId = 0;
|
|
15
15
|
chainId;
|
|
16
|
+
gasCache;
|
|
17
|
+
lastFetchedGas = 0;
|
|
16
18
|
constructor(network) {
|
|
17
19
|
this.network = network;
|
|
18
20
|
}
|
|
19
21
|
_utxoManager = new UTXOsManager(this);
|
|
20
|
-
gasCache;
|
|
21
|
-
lastFetchedGas = 0;
|
|
22
22
|
get utxoManager() {
|
|
23
23
|
return this._utxoManager;
|
|
24
24
|
}
|
|
@@ -101,6 +101,32 @@ export class AbstractRpcProvider {
|
|
|
101
101
|
}
|
|
102
102
|
return BigInt(result);
|
|
103
103
|
}
|
|
104
|
+
async getBalances(addressesLike, filterOrdinals = true) {
|
|
105
|
+
const payloads = addressesLike.map((address) => {
|
|
106
|
+
return this.buildJsonRpcPayload(JSONRpcMethods.GET_BALANCE, [address, filterOrdinals]);
|
|
107
|
+
});
|
|
108
|
+
const balances = await this.callMultiplePayloads(payloads);
|
|
109
|
+
if ('error' in balances) {
|
|
110
|
+
const error = balances.error;
|
|
111
|
+
throw new Error(`Error fetching block: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
const resultBalance = {};
|
|
114
|
+
for (let i = 0; i < balances.length; i++) {
|
|
115
|
+
const balance = balances[i];
|
|
116
|
+
const address = addressesLike[i];
|
|
117
|
+
if (!address)
|
|
118
|
+
throw new Error('Impossible index.');
|
|
119
|
+
if ('error' in balance) {
|
|
120
|
+
throw new Error(`Error fetching block: ${balance.error}`);
|
|
121
|
+
}
|
|
122
|
+
const result = balance.result;
|
|
123
|
+
if (!result || (result && !result.startsWith('0x'))) {
|
|
124
|
+
throw new Error(`Invalid balance returned from provider: ${result}`);
|
|
125
|
+
}
|
|
126
|
+
resultBalance[address] = BigInt(result);
|
|
127
|
+
}
|
|
128
|
+
return resultBalance;
|
|
129
|
+
}
|
|
104
130
|
async getTransaction(txHash) {
|
|
105
131
|
const payload = this.buildJsonRpcPayload(JSONRpcMethods.GET_TRANSACTION_BY_HASH, [txHash]);
|
|
106
132
|
const rawTransaction = await this.callPayloadSingle(payload);
|
|
@@ -217,15 +243,6 @@ export class AbstractRpcProvider {
|
|
|
217
243
|
}
|
|
218
244
|
return this.gasCache;
|
|
219
245
|
}
|
|
220
|
-
async _gasParameters() {
|
|
221
|
-
const payload = this.buildJsonRpcPayload(JSONRpcMethods.GAS, []);
|
|
222
|
-
const rawCall = await this.callPayloadSingle(payload);
|
|
223
|
-
if ('error' in rawCall) {
|
|
224
|
-
throw new Error(`Error fetching gas parameters: ${rawCall.error}`);
|
|
225
|
-
}
|
|
226
|
-
const result = rawCall.result;
|
|
227
|
-
return new BlockGasParameters(result);
|
|
228
|
-
}
|
|
229
246
|
async sendRawTransaction(tx, psbt) {
|
|
230
247
|
if (!/^[0-9A-Fa-f]+$/.test(tx)) {
|
|
231
248
|
throw new Error('sendRawTransaction: Invalid hex string');
|
|
@@ -241,6 +258,25 @@ export class AbstractRpcProvider {
|
|
|
241
258
|
}
|
|
242
259
|
return result;
|
|
243
260
|
}
|
|
261
|
+
async sendRawTransactions(txs) {
|
|
262
|
+
const payloads = txs.map((tx) => {
|
|
263
|
+
return this.buildJsonRpcPayload(JSONRpcMethods.BROADCAST_TRANSACTION, [tx, false]);
|
|
264
|
+
});
|
|
265
|
+
const rawTxs = await this.callMultiplePayloads(payloads);
|
|
266
|
+
if ('error' in rawTxs) {
|
|
267
|
+
throw new Error(`Error sending transactions: ${rawTxs.error}`);
|
|
268
|
+
}
|
|
269
|
+
return rawTxs.map((rawTx) => {
|
|
270
|
+
const result = rawTx.result;
|
|
271
|
+
if (result && result.identifier) {
|
|
272
|
+
return {
|
|
273
|
+
...result,
|
|
274
|
+
identifier: BigInt(result.identifier),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
244
280
|
async getBlockWitness(height = -1, trusted, limit, page) {
|
|
245
281
|
const params = [height.toString()];
|
|
246
282
|
if (trusted !== undefined && trusted !== null)
|
|
@@ -332,6 +368,15 @@ export class AbstractRpcProvider {
|
|
|
332
368
|
}
|
|
333
369
|
return response;
|
|
334
370
|
}
|
|
371
|
+
async _gasParameters() {
|
|
372
|
+
const payload = this.buildJsonRpcPayload(JSONRpcMethods.GAS, []);
|
|
373
|
+
const rawCall = await this.callPayloadSingle(payload);
|
|
374
|
+
if ('error' in rawCall) {
|
|
375
|
+
throw new Error(`Error fetching gas parameters: ${rawCall.error}`);
|
|
376
|
+
}
|
|
377
|
+
const result = rawCall.result;
|
|
378
|
+
return new BlockGasParameters(result);
|
|
379
|
+
}
|
|
335
380
|
parseSimulatedTransaction(transaction) {
|
|
336
381
|
return {
|
|
337
382
|
inputs: transaction.inputs.map((input) => {
|
|
@@ -12,6 +12,7 @@ export declare abstract class TransactionBase<T extends OPNetTransactionTypes> e
|
|
|
12
12
|
readonly index: number;
|
|
13
13
|
readonly burnedBitcoin: BigNumberish;
|
|
14
14
|
readonly priorityFee: BigNumberish;
|
|
15
|
+
readonly maxGasSat: BigNumberish;
|
|
15
16
|
readonly inputs: TransactionInput[];
|
|
16
17
|
readonly outputs: TransactionOutput[];
|
|
17
18
|
readonly OPNetType: T;
|
|
@@ -7,6 +7,7 @@ export class TransactionBase extends TransactionReceipt {
|
|
|
7
7
|
index;
|
|
8
8
|
burnedBitcoin;
|
|
9
9
|
priorityFee;
|
|
10
|
+
maxGasSat;
|
|
10
11
|
inputs;
|
|
11
12
|
outputs;
|
|
12
13
|
OPNetType;
|
|
@@ -31,6 +32,7 @@ export class TransactionBase extends TransactionReceipt {
|
|
|
31
32
|
if (transaction.pow) {
|
|
32
33
|
this.pow = this.decodeProofOfWorkChallenge(transaction.pow);
|
|
33
34
|
}
|
|
35
|
+
this.maxGasSat = this.burnedBitcoin + (this.pow?.reward || 0n) - this.priorityFee;
|
|
34
36
|
}
|
|
35
37
|
decodeProofOfWorkChallenge(challenge) {
|
|
36
38
|
return {
|
|
@@ -5,11 +5,16 @@ export declare class UTXOsManager {
|
|
|
5
5
|
private readonly provider;
|
|
6
6
|
private spentUTXOs;
|
|
7
7
|
private pendingUTXOs;
|
|
8
|
+
private pendingUtxoDepth;
|
|
8
9
|
private lastCleanup;
|
|
10
|
+
private lastFetchTimestamp;
|
|
11
|
+
private lastFetchedData;
|
|
9
12
|
constructor(provider: AbstractRpcProvider);
|
|
10
13
|
spentUTXO(spent: UTXOs, newUTXOs: UTXOs): void;
|
|
11
14
|
clean(): void;
|
|
12
15
|
getUTXOs({ address, optimize, mergePendingUTXOs, filterSpentUTXOs, }: RequestUTXOsParams): Promise<UTXOs>;
|
|
13
16
|
getUTXOsForAmount({ address, amount, optimize, mergePendingUTXOs, filterSpentUTXOs, throwErrors, }: RequestUTXOsParamsWithAmount): Promise<UTXOs>;
|
|
17
|
+
private maybeFetchUTXOs;
|
|
14
18
|
private fetchUTXOs;
|
|
19
|
+
private syncPendingDepthWithFetched;
|
|
15
20
|
}
|
|
@@ -1,27 +1,57 @@
|
|
|
1
1
|
import { UTXO } from '../bitcoin/UTXOs.js';
|
|
2
2
|
import { JSONRpcMethods } from '../providers/interfaces/JSONRpcMethods.js';
|
|
3
3
|
const AUTO_PURGE_AFTER = 1000 * 60;
|
|
4
|
+
const FETCH_COOLDOWN = 10000;
|
|
5
|
+
const MEMPOOL_CHAIN_LIMIT = 25;
|
|
4
6
|
export class UTXOsManager {
|
|
5
7
|
provider;
|
|
6
8
|
spentUTXOs = [];
|
|
7
9
|
pendingUTXOs = [];
|
|
10
|
+
pendingUtxoDepth = {};
|
|
8
11
|
lastCleanup = Date.now();
|
|
12
|
+
lastFetchTimestamp = 0;
|
|
13
|
+
lastFetchedData = null;
|
|
9
14
|
constructor(provider) {
|
|
10
15
|
this.provider = provider;
|
|
11
16
|
}
|
|
12
17
|
spentUTXO(spent, newUTXOs) {
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
const utxoKey = (u) => `${u.transactionId}:${u.outputIndex}`;
|
|
19
|
+
this.pendingUTXOs = this.pendingUTXOs.filter((utxo) => {
|
|
20
|
+
return !spent.some((spentUtxo) => spentUtxo.transactionId === utxo.transactionId &&
|
|
21
|
+
spentUtxo.outputIndex === utxo.outputIndex);
|
|
22
|
+
});
|
|
23
|
+
for (const spentUtxo of spent) {
|
|
24
|
+
const key = utxoKey(spentUtxo);
|
|
25
|
+
delete this.pendingUtxoDepth[key];
|
|
26
|
+
}
|
|
15
27
|
this.spentUTXOs.push(...spent);
|
|
16
|
-
|
|
28
|
+
let maxParentDepth = 0;
|
|
29
|
+
for (const spentUtxo of spent) {
|
|
30
|
+
const key = utxoKey(spentUtxo);
|
|
31
|
+
const parentDepth = this.pendingUtxoDepth[key] ?? 0;
|
|
32
|
+
if (parentDepth > maxParentDepth) {
|
|
33
|
+
maxParentDepth = parentDepth;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const newDepth = maxParentDepth + 1;
|
|
37
|
+
if (newDepth > MEMPOOL_CHAIN_LIMIT) {
|
|
38
|
+
throw new Error(`too-long-mempool-chain, too many descendants for tx ... [limit: ${MEMPOOL_CHAIN_LIMIT}]`);
|
|
39
|
+
}
|
|
40
|
+
for (const nu of newUTXOs) {
|
|
41
|
+
this.pendingUTXOs.push(nu);
|
|
42
|
+
this.pendingUtxoDepth[utxoKey(nu)] = newDepth;
|
|
43
|
+
}
|
|
17
44
|
}
|
|
18
45
|
clean() {
|
|
19
46
|
this.spentUTXOs = [];
|
|
20
47
|
this.pendingUTXOs = [];
|
|
48
|
+
this.pendingUtxoDepth = {};
|
|
21
49
|
this.lastCleanup = Date.now();
|
|
50
|
+
this.lastFetchTimestamp = 0;
|
|
51
|
+
this.lastFetchedData = null;
|
|
22
52
|
}
|
|
23
53
|
async getUTXOs({ address, optimize = true, mergePendingUTXOs = true, filterSpentUTXOs = true, }) {
|
|
24
|
-
const fetchedData = await this.
|
|
54
|
+
const fetchedData = await this.maybeFetchUTXOs(address, optimize);
|
|
25
55
|
const utxoKey = (utxo) => `${utxo.transactionId}:${utxo.outputIndex}`;
|
|
26
56
|
const pendingUTXOKeys = new Set(this.pendingUTXOs.map(utxoKey));
|
|
27
57
|
const spentUTXOKeys = new Set(this.spentUTXOs.map(utxoKey));
|
|
@@ -67,8 +97,8 @@ export class UTXOsManager {
|
|
|
67
97
|
const utxoUntilAmount = [];
|
|
68
98
|
let currentValue = 0n;
|
|
69
99
|
for (const utxo of combinedUTXOs) {
|
|
70
|
-
currentValue += utxo.value;
|
|
71
100
|
utxoUntilAmount.push(utxo);
|
|
101
|
+
currentValue += utxo.value;
|
|
72
102
|
if (currentValue >= amount) {
|
|
73
103
|
break;
|
|
74
104
|
}
|
|
@@ -78,31 +108,51 @@ export class UTXOsManager {
|
|
|
78
108
|
}
|
|
79
109
|
return utxoUntilAmount;
|
|
80
110
|
}
|
|
81
|
-
async
|
|
82
|
-
|
|
111
|
+
async maybeFetchUTXOs(address, optimize) {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const age = now - this.lastFetchTimestamp;
|
|
114
|
+
if (now - this.lastCleanup > AUTO_PURGE_AFTER) {
|
|
83
115
|
this.clean();
|
|
84
116
|
}
|
|
117
|
+
if (this.lastFetchedData && age < FETCH_COOLDOWN) {
|
|
118
|
+
return this.lastFetchedData;
|
|
119
|
+
}
|
|
120
|
+
this.lastFetchedData = await this.fetchUTXOs(address, optimize);
|
|
121
|
+
this.lastFetchTimestamp = now;
|
|
122
|
+
this.syncPendingDepthWithFetched();
|
|
123
|
+
return this.lastFetchedData;
|
|
124
|
+
}
|
|
125
|
+
async fetchUTXOs(address, optimize = false) {
|
|
85
126
|
const addressStr = address.toString();
|
|
86
127
|
const payload = this.provider.buildJsonRpcPayload(JSONRpcMethods.GET_UTXOS, [addressStr, optimize]);
|
|
87
|
-
const
|
|
88
|
-
if ('error' in
|
|
89
|
-
throw new Error(`Error fetching block: ${
|
|
128
|
+
const rawUTXOs = await this.provider.callPayloadSingle(payload);
|
|
129
|
+
if ('error' in rawUTXOs) {
|
|
130
|
+
throw new Error(`Error fetching block: ${rawUTXOs.error}`);
|
|
90
131
|
}
|
|
91
|
-
const result =
|
|
132
|
+
const result = rawUTXOs.result || {
|
|
92
133
|
confirmed: [],
|
|
93
134
|
pending: [],
|
|
94
135
|
spentTransactions: [],
|
|
95
136
|
};
|
|
96
137
|
return {
|
|
97
|
-
confirmed: result.confirmed.map((utxo) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
pending: result.pending.map((utxo) => {
|
|
101
|
-
return new UTXO(utxo);
|
|
102
|
-
}),
|
|
103
|
-
spentTransactions: result.spentTransactions.map((utxo) => {
|
|
104
|
-
return new UTXO(utxo);
|
|
105
|
-
}),
|
|
138
|
+
confirmed: result.confirmed.map((utxo) => new UTXO(utxo)),
|
|
139
|
+
pending: result.pending.map((utxo) => new UTXO(utxo)),
|
|
140
|
+
spentTransactions: result.spentTransactions.map((utxo) => new UTXO(utxo)),
|
|
106
141
|
};
|
|
107
142
|
}
|
|
143
|
+
syncPendingDepthWithFetched() {
|
|
144
|
+
if (!this.lastFetchedData) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const confirmedKeys = new Set(this.lastFetchedData.confirmed.map((u) => `${u.transactionId}:${u.outputIndex}`));
|
|
148
|
+
const spentKeys = new Set(this.lastFetchedData.spentTransactions.map((u) => `${u.transactionId}:${u.outputIndex}`));
|
|
149
|
+
this.pendingUTXOs = this.pendingUTXOs.filter((u) => {
|
|
150
|
+
const key = `${u.transactionId}:${u.outputIndex}`;
|
|
151
|
+
if (confirmedKeys.has(key) || spentKeys.has(key)) {
|
|
152
|
+
delete this.pendingUtxoDepth[key];
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
108
158
|
}
|
package/eslint.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opnet",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.22",
|
|
5
5
|
"author": "OP_NET",
|
|
6
6
|
"description": "The perfect library for building Bitcoin-based applications.",
|
|
7
7
|
"engines": {
|
|
@@ -100,9 +100,9 @@
|
|
|
100
100
|
},
|
|
101
101
|
"dependencies": {
|
|
102
102
|
"@bitcoinerlab/secp256k1": "^1.2.0",
|
|
103
|
-
"@btc-vision/bitcoin": "^6.3.
|
|
103
|
+
"@btc-vision/bitcoin": "^6.3.5",
|
|
104
104
|
"@btc-vision/bitcoin-rpc": "^1.0.0",
|
|
105
|
-
"@btc-vision/transaction": "^1.2.
|
|
105
|
+
"@btc-vision/transaction": "^1.2.9",
|
|
106
106
|
"@noble/hashes": "^1.7.0",
|
|
107
107
|
"bignumber.js": "^9.1.2",
|
|
108
108
|
"buffer": "^6.0.3",
|
|
@@ -12,10 +12,7 @@ import { ContractData } from '../contracts/ContractData.js';
|
|
|
12
12
|
import { IAccessList } from '../contracts/interfaces/IAccessList.js';
|
|
13
13
|
import { ICallRequestError, ICallResult } from '../contracts/interfaces/ICallResult.js';
|
|
14
14
|
import { IRawContract } from '../contracts/interfaces/IRawContract.js';
|
|
15
|
-
import {
|
|
16
|
-
ParsedSimulatedTransaction,
|
|
17
|
-
SimulatedTransaction,
|
|
18
|
-
} from '../contracts/interfaces/SimulatedTransaction.js';
|
|
15
|
+
import { ParsedSimulatedTransaction, SimulatedTransaction } from '../contracts/interfaces/SimulatedTransaction.js';
|
|
19
16
|
import { OPNetTransactionTypes } from '../interfaces/opnet/OPNetTransactionTypes.js';
|
|
20
17
|
import { IStorageValue } from '../storage/interfaces/IStorageValue.js';
|
|
21
18
|
import { StoredValue } from '../storage/StoredValue.js';
|
|
@@ -45,14 +42,13 @@ import { ReorgInformation } from './interfaces/ReorgInformation.js';
|
|
|
45
42
|
export abstract class AbstractRpcProvider {
|
|
46
43
|
private nextId: number = 0;
|
|
47
44
|
private chainId: bigint | undefined;
|
|
45
|
+
private gasCache: BlockGasParameters | undefined;
|
|
46
|
+
private lastFetchedGas: number = 0;
|
|
48
47
|
|
|
49
48
|
protected constructor(public readonly network: Network) {}
|
|
50
49
|
|
|
51
50
|
private _utxoManager: UTXOsManager = new UTXOsManager(this);
|
|
52
51
|
|
|
53
|
-
private gasCache: BlockGasParameters | undefined;
|
|
54
|
-
private lastFetchedGas: number = 0;
|
|
55
|
-
|
|
56
52
|
/**
|
|
57
53
|
* Get the UTXO manager.
|
|
58
54
|
*/
|
|
@@ -220,6 +216,51 @@ export abstract class AbstractRpcProvider {
|
|
|
220
216
|
return BigInt(result);
|
|
221
217
|
}
|
|
222
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Get the bitcoin balances of multiple addresses.
|
|
221
|
+
* @param {string[]} addressesLike The addresses to get the balances of
|
|
222
|
+
* @param {boolean} filterOrdinals Whether to filter ordinals or not
|
|
223
|
+
* @description This method is used to get the balance of a bitcoin address.
|
|
224
|
+
* @returns {Record<string, bigint>} The balance of the address
|
|
225
|
+
* @example await getBalances(['bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq']);
|
|
226
|
+
*/
|
|
227
|
+
public async getBalances(
|
|
228
|
+
addressesLike: string[],
|
|
229
|
+
filterOrdinals: boolean = true,
|
|
230
|
+
): Promise<Record<string, bigint>> {
|
|
231
|
+
const payloads: JsonRpcPayload[] = addressesLike.map((address: string) => {
|
|
232
|
+
return this.buildJsonRpcPayload(JSONRpcMethods.GET_BALANCE, [address, filterOrdinals]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const balances: JsonRpcCallResult = await this.callMultiplePayloads(payloads);
|
|
236
|
+
if ('error' in balances) {
|
|
237
|
+
const error = balances.error as JSONRpcResultError<JSONRpcMethods.GET_BALANCE>;
|
|
238
|
+
|
|
239
|
+
throw new Error(`Error fetching block: ${error.message}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const resultBalance: Record<string, bigint> = {};
|
|
243
|
+
for (let i = 0; i < balances.length; i++) {
|
|
244
|
+
const balance = balances[i];
|
|
245
|
+
const address = addressesLike[i];
|
|
246
|
+
|
|
247
|
+
if (!address) throw new Error('Impossible index.');
|
|
248
|
+
|
|
249
|
+
if ('error' in balance) {
|
|
250
|
+
throw new Error(`Error fetching block: ${balance.error}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const result = balance.result as string;
|
|
254
|
+
if (!result || (result && !result.startsWith('0x'))) {
|
|
255
|
+
throw new Error(`Invalid balance returned from provider: ${result}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
resultBalance[address] = BigInt(result);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return resultBalance;
|
|
262
|
+
}
|
|
263
|
+
|
|
223
264
|
/**
|
|
224
265
|
* Get a transaction by its hash or hash id.
|
|
225
266
|
* @description This method is used to get a transaction by its hash or hash id.
|
|
@@ -464,18 +505,6 @@ export abstract class AbstractRpcProvider {
|
|
|
464
505
|
return this.gasCache;
|
|
465
506
|
}
|
|
466
507
|
|
|
467
|
-
private async _gasParameters(): Promise<BlockGasParameters> {
|
|
468
|
-
const payload: JsonRpcPayload = this.buildJsonRpcPayload(JSONRpcMethods.GAS, []);
|
|
469
|
-
const rawCall: JsonRpcResult = await this.callPayloadSingle(payload);
|
|
470
|
-
|
|
471
|
-
if ('error' in rawCall) {
|
|
472
|
-
throw new Error(`Error fetching gas parameters: ${rawCall.error}`);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const result: IBlockGasParametersInput = rawCall.result as IBlockGasParametersInput;
|
|
476
|
-
return new BlockGasParameters(result);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
508
|
/**
|
|
480
509
|
* Send a raw transaction.
|
|
481
510
|
* @description This method is used to send a raw transaction.
|
|
@@ -508,6 +537,36 @@ export abstract class AbstractRpcProvider {
|
|
|
508
537
|
return result;
|
|
509
538
|
}
|
|
510
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Bulk send transactions.
|
|
542
|
+
* @description This method is used to send multiple transactions at the same time.
|
|
543
|
+
* @param {string[]} txs The raw transactions to send as hex string
|
|
544
|
+
* @returns {Promise<BroadcastedTransaction[]>} The result of the transaction
|
|
545
|
+
* @throws {Error} If something went wrong while sending the transaction
|
|
546
|
+
*/
|
|
547
|
+
public async sendRawTransactions(txs: string[]): Promise<BroadcastedTransaction[]> {
|
|
548
|
+
const payloads: JsonRpcPayload[] = txs.map((tx) => {
|
|
549
|
+
return this.buildJsonRpcPayload(JSONRpcMethods.BROADCAST_TRANSACTION, [tx, false]);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const rawTxs: JsonRpcCallResult = await this.callMultiplePayloads(payloads);
|
|
553
|
+
if ('error' in rawTxs) {
|
|
554
|
+
throw new Error(`Error sending transactions: ${rawTxs.error}`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return rawTxs.map((rawTx) => {
|
|
558
|
+
const result: BroadcastedTransaction = rawTx.result as BroadcastedTransaction;
|
|
559
|
+
if (result && result.identifier) {
|
|
560
|
+
return {
|
|
561
|
+
...result,
|
|
562
|
+
identifier: BigInt(result.identifier),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return result;
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
511
570
|
/**
|
|
512
571
|
* Get block witnesses.
|
|
513
572
|
* @description This method is used to get the witnesses of a block. This proves that the action 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.
|
|
@@ -587,6 +646,27 @@ export abstract class AbstractRpcProvider {
|
|
|
587
646
|
*/
|
|
588
647
|
public abstract _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<JsonRpcCallResult>;
|
|
589
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Send a single payload. This method is used to send a single payload.
|
|
651
|
+
* @param {JsonRpcPayload} payload The payload to send
|
|
652
|
+
* @returns {Promise<JsonRpcResult>} The result of the payload
|
|
653
|
+
* @throws {Error} If no data is returned
|
|
654
|
+
* @private
|
|
655
|
+
*/
|
|
656
|
+
public async callPayloadSingle(payload: JsonRpcPayload): Promise<JsonRpcResult> {
|
|
657
|
+
const rawData: JsonRpcCallResult = await this._send(payload);
|
|
658
|
+
if (!rawData.length) {
|
|
659
|
+
throw new Error('No data returned');
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const data = rawData.shift();
|
|
663
|
+
if (!data) {
|
|
664
|
+
throw new Error('Block not found');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return data as JSONRpc2ResponseResult<JSONRpcMethods>;
|
|
668
|
+
}
|
|
669
|
+
|
|
590
670
|
/*
|
|
591
671
|
* Generate parameters needed to wrap bitcoin.
|
|
592
672
|
* @description This method is used to generate the parameters needed to wrap bitcoin.
|
|
@@ -611,27 +691,6 @@ export abstract class AbstractRpcProvider {
|
|
|
611
691
|
return new WrappedGeneration(result);
|
|
612
692
|
}*/
|
|
613
693
|
|
|
614
|
-
/**
|
|
615
|
-
* Send a single payload. This method is used to send a single payload.
|
|
616
|
-
* @param {JsonRpcPayload} payload The payload to send
|
|
617
|
-
* @returns {Promise<JsonRpcResult>} The result of the payload
|
|
618
|
-
* @throws {Error} If no data is returned
|
|
619
|
-
* @private
|
|
620
|
-
*/
|
|
621
|
-
public async callPayloadSingle(payload: JsonRpcPayload): Promise<JsonRpcResult> {
|
|
622
|
-
const rawData: JsonRpcCallResult = await this._send(payload);
|
|
623
|
-
if (!rawData.length) {
|
|
624
|
-
throw new Error('No data returned');
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const data = rawData.shift();
|
|
628
|
-
if (!data) {
|
|
629
|
-
throw new Error('Block not found');
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
return data as JSONRpc2ResponseResult<JSONRpcMethods>;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
694
|
/**
|
|
636
695
|
* Send multiple payloads. This method is used to send multiple payloads.
|
|
637
696
|
* @param {JsonRpcPayload[]} payloads The payloads to send
|
|
@@ -719,6 +778,18 @@ export abstract class AbstractRpcProvider {
|
|
|
719
778
|
|
|
720
779
|
protected abstract providerUrl(url: string): string;
|
|
721
780
|
|
|
781
|
+
private async _gasParameters(): Promise<BlockGasParameters> {
|
|
782
|
+
const payload: JsonRpcPayload = this.buildJsonRpcPayload(JSONRpcMethods.GAS, []);
|
|
783
|
+
const rawCall: JsonRpcResult = await this.callPayloadSingle(payload);
|
|
784
|
+
|
|
785
|
+
if ('error' in rawCall) {
|
|
786
|
+
throw new Error(`Error fetching gas parameters: ${rawCall.error}`);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const result: IBlockGasParametersInput = rawCall.result as IBlockGasParametersInput;
|
|
790
|
+
return new BlockGasParameters(result);
|
|
791
|
+
}
|
|
792
|
+
|
|
722
793
|
private parseSimulatedTransaction(
|
|
723
794
|
transaction: ParsedSimulatedTransaction,
|
|
724
795
|
): SimulatedTransaction {
|
|
@@ -44,6 +44,11 @@ export abstract class TransactionBase<T extends OPNetTransactionTypes>
|
|
|
44
44
|
*/
|
|
45
45
|
public readonly priorityFee: BigNumberish;
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @description The maximum amount of gas that can be spent by the transaction.
|
|
49
|
+
*/
|
|
50
|
+
public readonly maxGasSat: BigNumberish;
|
|
51
|
+
|
|
47
52
|
/**
|
|
48
53
|
* @description The inputs of the transaction.
|
|
49
54
|
*/
|
|
@@ -98,6 +103,8 @@ export abstract class TransactionBase<T extends OPNetTransactionTypes>
|
|
|
98
103
|
if (transaction.pow) {
|
|
99
104
|
this.pow = this.decodeProofOfWorkChallenge(transaction.pow as RawProofOfWorkChallenge);
|
|
100
105
|
}
|
|
106
|
+
|
|
107
|
+
this.maxGasSat = this.burnedBitcoin + (this.pow?.reward || 0n) - this.priorityFee;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
private decodeProofOfWorkChallenge(challenge: RawProofOfWorkChallenge): ProofOfWorkChallenge {
|