@solana/web3.js 1.41.7 → 1.41.10
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/lib/index.browser.cjs.js +138 -42
- package/lib/index.browser.cjs.js.map +1 -1
- package/lib/index.browser.esm.js +139 -43
- package/lib/index.browser.esm.js.map +1 -1
- package/lib/index.cjs.js +138 -42
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +44 -10
- package/lib/index.esm.js +139 -43
- package/lib/index.esm.js.map +1 -1
- package/lib/index.iife.js +138 -42
- package/lib/index.iife.js.map +1 -1
- package/lib/index.iife.min.js +2 -2
- package/lib/index.iife.min.js.map +1 -1
- package/package.json +2 -2
- package/src/connection.ts +120 -41
- package/src/transaction.ts +54 -6
- package/src/util/send-and-confirm-transaction.ts +19 -6
- package/src/util/tx-expiry-custom-errors.ts +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solana/web3.js",
|
|
3
|
-
"version": "1.41.
|
|
3
|
+
"version": "1.41.10",
|
|
4
4
|
"description": "Solana Javascript API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"sinon": "^13.0.2",
|
|
130
130
|
"sinon-chai": "^3.7.0",
|
|
131
131
|
"start-server-and-test": "^1.12.0",
|
|
132
|
-
"ts-mocha": "^
|
|
132
|
+
"ts-mocha": "^10.0.0",
|
|
133
133
|
"ts-node": "^10.0.0",
|
|
134
134
|
"tslib": "^2.1.0",
|
|
135
135
|
"typedoc": "^0.22.2",
|
package/src/connection.ts
CHANGED
|
@@ -32,12 +32,15 @@ import {NonceAccount} from './nonce-account';
|
|
|
32
32
|
import {PublicKey} from './publickey';
|
|
33
33
|
import {Signer} from './keypair';
|
|
34
34
|
import {MS_PER_SLOT} from './timing';
|
|
35
|
-
import {Transaction} from './transaction';
|
|
35
|
+
import {Transaction, TransactionStatus} from './transaction';
|
|
36
36
|
import {Message} from './message';
|
|
37
37
|
import assert from './util/assert';
|
|
38
38
|
import {sleep} from './util/sleep';
|
|
39
|
-
import {promiseTimeout} from './util/promise-timeout';
|
|
40
39
|
import {toBuffer} from './util/to-buffer';
|
|
40
|
+
import {
|
|
41
|
+
TransactionExpiredBlockheightExceededError,
|
|
42
|
+
TransactionExpiredTimeoutError,
|
|
43
|
+
} from './util/tx-expiry-custom-errors';
|
|
41
44
|
import {makeWebsocketUrl} from './util/url';
|
|
42
45
|
import type {Blockhash} from './blockhash';
|
|
43
46
|
import type {FeeCalculator} from './fee-calculator';
|
|
@@ -281,6 +284,16 @@ export type RpcResponseAndContext<T> = {
|
|
|
281
284
|
value: T;
|
|
282
285
|
};
|
|
283
286
|
|
|
287
|
+
/**
|
|
288
|
+
* A strategy for confirming transactions that uses the last valid
|
|
289
|
+
* block height for a given blockhash to check for transaction expiration.
|
|
290
|
+
*/
|
|
291
|
+
export type BlockheightBasedTransactionConfimationStrategy = {
|
|
292
|
+
signature: TransactionSignature;
|
|
293
|
+
blockhash: Blockhash;
|
|
294
|
+
lastValidBlockHeight: number;
|
|
295
|
+
};
|
|
296
|
+
|
|
284
297
|
/**
|
|
285
298
|
* @internal
|
|
286
299
|
*/
|
|
@@ -2825,38 +2838,64 @@ export class Connection {
|
|
|
2825
2838
|
return res.result;
|
|
2826
2839
|
}
|
|
2827
2840
|
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2841
|
+
confirmTransaction(
|
|
2842
|
+
strategy: BlockheightBasedTransactionConfimationStrategy,
|
|
2843
|
+
commitment?: Commitment,
|
|
2844
|
+
): Promise<RpcResponseAndContext<SignatureResult>>;
|
|
2845
|
+
|
|
2846
|
+
/** @deprecated Instead, call `confirmTransaction` using a `TransactionConfirmationConfig` */
|
|
2847
|
+
// eslint-disable-next-line no-dupe-class-members
|
|
2848
|
+
confirmTransaction(
|
|
2849
|
+
strategy: TransactionSignature,
|
|
2850
|
+
commitment?: Commitment,
|
|
2851
|
+
): Promise<RpcResponseAndContext<SignatureResult>>;
|
|
2852
|
+
|
|
2853
|
+
// eslint-disable-next-line no-dupe-class-members
|
|
2831
2854
|
async confirmTransaction(
|
|
2832
|
-
|
|
2855
|
+
strategy:
|
|
2856
|
+
| BlockheightBasedTransactionConfimationStrategy
|
|
2857
|
+
| TransactionSignature,
|
|
2833
2858
|
commitment?: Commitment,
|
|
2834
2859
|
): Promise<RpcResponseAndContext<SignatureResult>> {
|
|
2860
|
+
let rawSignature: string;
|
|
2861
|
+
|
|
2862
|
+
if (typeof strategy == 'string') {
|
|
2863
|
+
rawSignature = strategy;
|
|
2864
|
+
} else {
|
|
2865
|
+
const config = strategy as BlockheightBasedTransactionConfimationStrategy;
|
|
2866
|
+
rawSignature = config.signature;
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2835
2869
|
let decodedSignature;
|
|
2870
|
+
|
|
2836
2871
|
try {
|
|
2837
|
-
decodedSignature = bs58.decode(
|
|
2872
|
+
decodedSignature = bs58.decode(rawSignature);
|
|
2838
2873
|
} catch (err) {
|
|
2839
|
-
throw new Error('signature must be base58 encoded: ' +
|
|
2874
|
+
throw new Error('signature must be base58 encoded: ' + rawSignature);
|
|
2840
2875
|
}
|
|
2841
2876
|
|
|
2842
2877
|
assert(decodedSignature.length === 64, 'signature has invalid length');
|
|
2843
2878
|
|
|
2844
|
-
const start = Date.now();
|
|
2845
2879
|
const subscriptionCommitment = commitment || this.commitment;
|
|
2846
|
-
|
|
2880
|
+
let timeoutId;
|
|
2847
2881
|
let subscriptionId;
|
|
2848
|
-
let
|
|
2849
|
-
|
|
2882
|
+
let done = false;
|
|
2883
|
+
|
|
2884
|
+
const confirmationPromise = new Promise<{
|
|
2885
|
+
__type: TransactionStatus.PROCESSED;
|
|
2886
|
+
response: RpcResponseAndContext<SignatureResult>;
|
|
2887
|
+
}>((resolve, reject) => {
|
|
2850
2888
|
try {
|
|
2851
2889
|
subscriptionId = this.onSignature(
|
|
2852
|
-
|
|
2890
|
+
rawSignature,
|
|
2853
2891
|
(result: SignatureResult, context: Context) => {
|
|
2854
2892
|
subscriptionId = undefined;
|
|
2855
|
-
response = {
|
|
2893
|
+
const response = {
|
|
2856
2894
|
context,
|
|
2857
2895
|
value: result,
|
|
2858
2896
|
};
|
|
2859
|
-
|
|
2897
|
+
done = true;
|
|
2898
|
+
resolve({__type: TransactionStatus.PROCESSED, response});
|
|
2860
2899
|
},
|
|
2861
2900
|
subscriptionCommitment,
|
|
2862
2901
|
);
|
|
@@ -2865,40 +2904,78 @@ export class Connection {
|
|
|
2865
2904
|
}
|
|
2866
2905
|
});
|
|
2867
2906
|
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
case 'singleGossip': {
|
|
2875
|
-
timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
|
|
2876
|
-
break;
|
|
2907
|
+
const checkBlockHeight = async () => {
|
|
2908
|
+
try {
|
|
2909
|
+
const blockHeight = await this.getBlockHeight(commitment);
|
|
2910
|
+
return blockHeight;
|
|
2911
|
+
} catch (_e) {
|
|
2912
|
+
return -1;
|
|
2877
2913
|
}
|
|
2878
|
-
|
|
2879
|
-
case 'finalized':
|
|
2880
|
-
case 'max':
|
|
2881
|
-
case 'root':
|
|
2882
|
-
}
|
|
2914
|
+
};
|
|
2883
2915
|
|
|
2916
|
+
const expiryPromise = new Promise<
|
|
2917
|
+
| {__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED}
|
|
2918
|
+
| {__type: TransactionStatus.TIMED_OUT; timeoutMs: number}
|
|
2919
|
+
>(resolve => {
|
|
2920
|
+
if (typeof strategy === 'string') {
|
|
2921
|
+
let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
|
|
2922
|
+
switch (subscriptionCommitment) {
|
|
2923
|
+
case 'processed':
|
|
2924
|
+
case 'recent':
|
|
2925
|
+
case 'single':
|
|
2926
|
+
case 'confirmed':
|
|
2927
|
+
case 'singleGossip': {
|
|
2928
|
+
timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
|
|
2929
|
+
break;
|
|
2930
|
+
}
|
|
2931
|
+
// exhaust enums to ensure full coverage
|
|
2932
|
+
case 'finalized':
|
|
2933
|
+
case 'max':
|
|
2934
|
+
case 'root':
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
timeoutId = setTimeout(
|
|
2938
|
+
() => resolve({__type: TransactionStatus.TIMED_OUT, timeoutMs}),
|
|
2939
|
+
timeoutMs,
|
|
2940
|
+
);
|
|
2941
|
+
} else {
|
|
2942
|
+
let config = strategy as BlockheightBasedTransactionConfimationStrategy;
|
|
2943
|
+
(async () => {
|
|
2944
|
+
let currentBlockHeight = await checkBlockHeight();
|
|
2945
|
+
if (done) return;
|
|
2946
|
+
while (currentBlockHeight <= config.lastValidBlockHeight) {
|
|
2947
|
+
await sleep(1000);
|
|
2948
|
+
if (done) return;
|
|
2949
|
+
currentBlockHeight = await checkBlockHeight();
|
|
2950
|
+
if (done) return;
|
|
2951
|
+
}
|
|
2952
|
+
resolve({__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED});
|
|
2953
|
+
})();
|
|
2954
|
+
}
|
|
2955
|
+
});
|
|
2956
|
+
|
|
2957
|
+
let result: RpcResponseAndContext<SignatureResult>;
|
|
2884
2958
|
try {
|
|
2885
|
-
await
|
|
2959
|
+
const outcome = await Promise.race([confirmationPromise, expiryPromise]);
|
|
2960
|
+
switch (outcome.__type) {
|
|
2961
|
+
case TransactionStatus.BLOCKHEIGHT_EXCEEDED:
|
|
2962
|
+
throw new TransactionExpiredBlockheightExceededError(rawSignature);
|
|
2963
|
+
case TransactionStatus.PROCESSED:
|
|
2964
|
+
result = outcome.response;
|
|
2965
|
+
break;
|
|
2966
|
+
case TransactionStatus.TIMED_OUT:
|
|
2967
|
+
throw new TransactionExpiredTimeoutError(
|
|
2968
|
+
rawSignature,
|
|
2969
|
+
outcome.timeoutMs / 1000,
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2886
2972
|
} finally {
|
|
2973
|
+
clearTimeout(timeoutId);
|
|
2887
2974
|
if (subscriptionId) {
|
|
2888
2975
|
this.removeSignatureListener(subscriptionId);
|
|
2889
2976
|
}
|
|
2890
2977
|
}
|
|
2891
|
-
|
|
2892
|
-
if (response === null) {
|
|
2893
|
-
const duration = (Date.now() - start) / 1000;
|
|
2894
|
-
throw new Error(
|
|
2895
|
-
`Transaction was not confirmed in ${duration.toFixed(
|
|
2896
|
-
2,
|
|
2897
|
-
)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`,
|
|
2898
|
-
);
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
|
-
return response;
|
|
2978
|
+
return result;
|
|
2902
2979
|
}
|
|
2903
2980
|
|
|
2904
2981
|
/**
|
|
@@ -4174,6 +4251,7 @@ export class Connection {
|
|
|
4174
4251
|
* @internal
|
|
4175
4252
|
*/
|
|
4176
4253
|
_wsOnError(err: Error) {
|
|
4254
|
+
this._rpcWebSocketConnected = false;
|
|
4177
4255
|
console.error('ws error:', err.message);
|
|
4178
4256
|
}
|
|
4179
4257
|
|
|
@@ -4181,6 +4259,7 @@ export class Connection {
|
|
|
4181
4259
|
* @internal
|
|
4182
4260
|
*/
|
|
4183
4261
|
_wsOnClose(code: number) {
|
|
4262
|
+
this._rpcWebSocketConnected = false;
|
|
4184
4263
|
this._rpcWebSocketGeneration++;
|
|
4185
4264
|
if (this._rpcWebSocketHeartbeat) {
|
|
4186
4265
|
clearInterval(this._rpcWebSocketHeartbeat);
|
package/src/transaction.ts
CHANGED
|
@@ -21,6 +21,12 @@ import type {CompiledInstruction} from './message';
|
|
|
21
21
|
*/
|
|
22
22
|
export type TransactionSignature = string;
|
|
23
23
|
|
|
24
|
+
export const enum TransactionStatus {
|
|
25
|
+
BLOCKHEIGHT_EXCEEDED,
|
|
26
|
+
PROCESSED,
|
|
27
|
+
TIMED_OUT,
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
/**
|
|
25
31
|
* Default (empty) signature
|
|
26
32
|
*/
|
|
@@ -124,17 +130,30 @@ export type SignaturePubkeyPair = {
|
|
|
124
130
|
|
|
125
131
|
/**
|
|
126
132
|
* List of Transaction object fields that may be initialized at construction
|
|
127
|
-
*
|
|
128
133
|
*/
|
|
129
|
-
export type
|
|
130
|
-
/** A recent blockhash */
|
|
131
|
-
recentBlockhash?: Blockhash | null;
|
|
134
|
+
export type TransactionCtorFields_DEPRECATED = {
|
|
132
135
|
/** Optional nonce information used for offline nonce'd transactions */
|
|
133
136
|
nonceInfo?: NonceInformation | null;
|
|
134
137
|
/** The transaction fee payer */
|
|
135
138
|
feePayer?: PublicKey | null;
|
|
136
139
|
/** One or more signatures */
|
|
137
140
|
signatures?: Array<SignaturePubkeyPair>;
|
|
141
|
+
/** A recent blockhash */
|
|
142
|
+
recentBlockhash?: Blockhash;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* List of Transaction object fields that may be initialized at construction
|
|
147
|
+
*/
|
|
148
|
+
export type TransactionBlockhashCtor = {
|
|
149
|
+
/** The transaction fee payer */
|
|
150
|
+
feePayer?: PublicKey | null;
|
|
151
|
+
/** One or more signatures */
|
|
152
|
+
signatures?: Array<SignaturePubkeyPair>;
|
|
153
|
+
/** A recent blockhash */
|
|
154
|
+
blockhash: Blockhash;
|
|
155
|
+
/** the last block chain can advance to before tx is declared expired */
|
|
156
|
+
lastValidBlockHeight: number;
|
|
138
157
|
};
|
|
139
158
|
|
|
140
159
|
/**
|
|
@@ -196,6 +215,11 @@ export class Transaction {
|
|
|
196
215
|
*/
|
|
197
216
|
recentBlockhash?: Blockhash;
|
|
198
217
|
|
|
218
|
+
/**
|
|
219
|
+
* the last block chain can advance to before tx is declared expired
|
|
220
|
+
* */
|
|
221
|
+
lastValidBlockHeight?: number;
|
|
222
|
+
|
|
199
223
|
/**
|
|
200
224
|
* Optional Nonce information. If populated, transaction will use a durable
|
|
201
225
|
* Nonce hash instead of a recentBlockhash. Must be populated by the caller
|
|
@@ -212,11 +236,35 @@ export class Transaction {
|
|
|
212
236
|
*/
|
|
213
237
|
_json?: TransactionJSON;
|
|
214
238
|
|
|
239
|
+
// Construct a transaction with a blockhash and lastValidBlockHeight
|
|
240
|
+
constructor(opts?: TransactionBlockhashCtor);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @deprecated `TransactionCtorFields` has been deprecated and will be removed in a future version.
|
|
244
|
+
* Please supply a `TransactionBlockhashCtor` instead.
|
|
245
|
+
*/
|
|
246
|
+
constructor(opts?: TransactionCtorFields_DEPRECATED);
|
|
247
|
+
|
|
215
248
|
/**
|
|
216
249
|
* Construct an empty Transaction
|
|
217
250
|
*/
|
|
218
|
-
constructor(
|
|
219
|
-
opts
|
|
251
|
+
constructor(
|
|
252
|
+
opts?: TransactionBlockhashCtor | TransactionCtorFields_DEPRECATED,
|
|
253
|
+
) {
|
|
254
|
+
if (!opts) {
|
|
255
|
+
return;
|
|
256
|
+
} else if (
|
|
257
|
+
Object.prototype.hasOwnProperty.call(opts, 'lastValidBlockHeight')
|
|
258
|
+
) {
|
|
259
|
+
const newOpts = opts as TransactionBlockhashCtor;
|
|
260
|
+
Object.assign(this, newOpts);
|
|
261
|
+
this.recentBlockhash = newOpts.blockhash;
|
|
262
|
+
this.lastValidBlockHeight = newOpts.lastValidBlockHeight;
|
|
263
|
+
} else {
|
|
264
|
+
const oldOpts = opts as TransactionCtorFields_DEPRECATED;
|
|
265
|
+
Object.assign(this, oldOpts);
|
|
266
|
+
this.recentBlockhash = oldOpts.recentBlockhash;
|
|
267
|
+
}
|
|
220
268
|
}
|
|
221
269
|
|
|
222
270
|
/**
|
|
@@ -33,12 +33,25 @@ export async function sendAndConfirmTransaction(
|
|
|
33
33
|
sendOptions,
|
|
34
34
|
);
|
|
35
35
|
|
|
36
|
-
const status =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const status =
|
|
37
|
+
transaction.recentBlockhash != null &&
|
|
38
|
+
transaction.lastValidBlockHeight != null
|
|
39
|
+
? (
|
|
40
|
+
await connection.confirmTransaction(
|
|
41
|
+
{
|
|
42
|
+
signature: signature,
|
|
43
|
+
blockhash: transaction.recentBlockhash,
|
|
44
|
+
lastValidBlockHeight: transaction.lastValidBlockHeight,
|
|
45
|
+
},
|
|
46
|
+
options && options.commitment,
|
|
47
|
+
)
|
|
48
|
+
).value
|
|
49
|
+
: (
|
|
50
|
+
await connection.confirmTransaction(
|
|
51
|
+
signature,
|
|
52
|
+
options && options.commitment,
|
|
53
|
+
)
|
|
54
|
+
).value;
|
|
42
55
|
|
|
43
56
|
if (status.err) {
|
|
44
57
|
throw new Error(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class TransactionExpiredBlockheightExceededError extends Error {
|
|
2
|
+
signature: string;
|
|
3
|
+
|
|
4
|
+
constructor(signature: string) {
|
|
5
|
+
super(`Signature ${signature} has expired: block height exceeded.`);
|
|
6
|
+
this.signature = signature;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Object.defineProperty(
|
|
11
|
+
TransactionExpiredBlockheightExceededError.prototype,
|
|
12
|
+
'name',
|
|
13
|
+
{
|
|
14
|
+
value: 'TransactionExpiredBlockheightExceededError',
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export class TransactionExpiredTimeoutError extends Error {
|
|
19
|
+
signature: string;
|
|
20
|
+
|
|
21
|
+
constructor(signature: string, timeoutSeconds: number) {
|
|
22
|
+
super(
|
|
23
|
+
`Transaction was not confirmed in ${timeoutSeconds.toFixed(
|
|
24
|
+
2,
|
|
25
|
+
)} seconds. It is ` +
|
|
26
|
+
'unknown if it succeeded or failed. Check signature ' +
|
|
27
|
+
`${signature} using the Solana Explorer or CLI tools.`,
|
|
28
|
+
);
|
|
29
|
+
this.signature = signature;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Object.defineProperty(TransactionExpiredTimeoutError.prototype, 'name', {
|
|
34
|
+
value: 'TransactionExpiredTimeoutError',
|
|
35
|
+
});
|