@solana/transaction-confirmation 2.0.0-development.1532546
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/LICENSE +20 -0
- package/README.md +178 -0
- package/dist/index.browser.cjs +283 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.js +275 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.native.js +275 -0
- package/dist/index.native.js.map +1 -0
- package/dist/index.node.cjs +283 -0
- package/dist/index.node.cjs.map +1 -0
- package/dist/index.node.js +275 -0
- package/dist/index.node.js.map +1 -0
- package/dist/types/confirmation-strategy-blockheight.d.ts +14 -0
- package/dist/types/confirmation-strategy-blockheight.d.ts.map +1 -0
- package/dist/types/confirmation-strategy-nonce.d.ts +13 -0
- package/dist/types/confirmation-strategy-nonce.d.ts.map +1 -0
- package/dist/types/confirmation-strategy-racer.d.ts +14 -0
- package/dist/types/confirmation-strategy-racer.d.ts.map +1 -0
- package/dist/types/confirmation-strategy-recent-signature.d.ts +11 -0
- package/dist/types/confirmation-strategy-recent-signature.d.ts.map +1 -0
- package/dist/types/confirmation-strategy-timeout.d.ts +8 -0
- package/dist/types/confirmation-strategy-timeout.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/waiters.d.ts +29 -0
- package/dist/types/waiters.d.ts.map +1 -0
- package/package.json +106 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2023 Solana Labs, Inc
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
[![npm][npm-image]][npm-url]
|
|
2
|
+
[![npm-downloads][npm-downloads-image]][npm-url]
|
|
3
|
+
[![semantic-release][semantic-release-image]][semantic-release-url]
|
|
4
|
+
<br />
|
|
5
|
+
[![code-style-prettier][code-style-prettier-image]][code-style-prettier-url]
|
|
6
|
+
|
|
7
|
+
[code-style-prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square
|
|
8
|
+
[code-style-prettier-url]: https://github.com/prettier/prettier
|
|
9
|
+
[npm-downloads-image]: https://img.shields.io/npm/dm/@solana/transaction-confirmation/experimental.svg?style=flat
|
|
10
|
+
[npm-image]: https://img.shields.io/npm/v/@solana/transaction-confirmation/experimental.svg?style=flat
|
|
11
|
+
[npm-url]: https://www.npmjs.com/package/@solana/transaction-confirmation/v/experimental
|
|
12
|
+
[semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
|
13
|
+
[semantic-release-url]: https://github.com/semantic-release/semantic-release
|
|
14
|
+
|
|
15
|
+
# @solana/transaction-confirmation
|
|
16
|
+
|
|
17
|
+
This package contains utilities for confirming transactions and for building your own transaction confirmation strategies.
|
|
18
|
+
|
|
19
|
+
## Functions
|
|
20
|
+
|
|
21
|
+
### `createBlockHeightExceedencePromiseFactory()`
|
|
22
|
+
|
|
23
|
+
When a transaction's lifetime is tied to a blockhash, that transaction can be landed on the network until that blockhash expires. All blockhashes have a block height after which they are considered to have expired. A block height exceedence promise throws when the network progresses past that block height.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { isSolanaError, SolanaError } from '@solana/errors';
|
|
27
|
+
import { createBlockHeightExceedencePromiseFactory } from '@solana/transaction-confirmation';
|
|
28
|
+
|
|
29
|
+
const getBlockHeightExceedencePromise = createBlockHeightExceedencePromiseFactory({
|
|
30
|
+
rpc,
|
|
31
|
+
rpcSubscriptions,
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
await getBlockHeightExceedencePromise({ lastValidBlockHeight });
|
|
35
|
+
} catch (e) {
|
|
36
|
+
if (isSolanaError(e, SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED)) {
|
|
37
|
+
console.error(
|
|
38
|
+
`The block height of the network has exceeded ${e.context.lastValidBlockHeight}. ` +
|
|
39
|
+
`It is now ${e.context.currentBlockHeight}`,
|
|
40
|
+
);
|
|
41
|
+
// Re-sign and retry the transaction.
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `createNonceInvalidationPromiseFactory()`
|
|
49
|
+
|
|
50
|
+
When a transaction's lifetime is tied to the value stored in a nonce account, that transaction can be landed on the network until the nonce is advanced to a new value. A nonce invalidation promise throws when the value stored in a nonce account is not the expected one.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { isSolanaError, SolanaError } from '@solana/errors';
|
|
54
|
+
import { createNonceInvalidationPromiseFactory } from '@solana/transaction-confirmation';
|
|
55
|
+
|
|
56
|
+
const getNonceInvalidationPromise = createNonceInvalidationPromiseFactory({
|
|
57
|
+
rpc,
|
|
58
|
+
rpcSubscriptions,
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
await getNonceInvalidationPromise({
|
|
62
|
+
currentNonceValue,
|
|
63
|
+
nonceAccountAddress,
|
|
64
|
+
});
|
|
65
|
+
} catch (e) {
|
|
66
|
+
if (isSolanaError(e, SOLANA_ERROR__NONCE_INVALID)) {
|
|
67
|
+
console.error(`The nonce has advanced to ${e.context.actualNonceValue}`);
|
|
68
|
+
// Re-sign and retry the transaction.
|
|
69
|
+
return;
|
|
70
|
+
} else if (isSolanaError(e, SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND)) {
|
|
71
|
+
console.error(`No nonce account was found at ${nonceAccountAddress}`);
|
|
72
|
+
}
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `createRecentSignatureConfirmationPromiseFactory()`
|
|
78
|
+
|
|
79
|
+
The status of recently-landed transactions is available in the network's status cache. A recent signature confirmation promise resolves when a transaction achieves the target confirmation commitment, and throws when the transaction fails with an error.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { createRecentSignatureConfirmationPromiseFactory } from '@solana/transaction-confirmation';
|
|
83
|
+
|
|
84
|
+
const getRecentSignatureConfirmationPromise = createRecentSignatureConfirmationPromiseFactory({
|
|
85
|
+
rpc,
|
|
86
|
+
rpcSubscriptions,
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
await getRecentSignatureConfirmationPromise({
|
|
90
|
+
commitment,
|
|
91
|
+
signature,
|
|
92
|
+
});
|
|
93
|
+
console.log(`The transaction with signature \`${signature}\` has achieved a commitment level of \`${commitment}\``);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error(`The transaction with signature \`${signature}\` failed`, e.cause);
|
|
96
|
+
throw e;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `getTimeoutPromise()`
|
|
101
|
+
|
|
102
|
+
When no other heuristic exists to infer that a transaction has expired, you can use this promise factory with a commitment level. It throws after 30 seconds when the commitment is `processed`, and 60 seconds otherwise. You would typically race this with another confirmation strategy.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { getTimeoutPromise } from '@solana/transaction-confirmation';
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await Promise.race([getCustomTransactionConfirmationPromise(/* ... */), getTimeoutPromise({ commitment })]);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
if (e instanceof DOMException && e.name === 'TimeoutError') {
|
|
111
|
+
console.log('Could not confirm transaction after a timeout');
|
|
112
|
+
}
|
|
113
|
+
throw e;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `waitForDurableNonceTransactionConfirmation()`
|
|
118
|
+
|
|
119
|
+
Supply your own confirmation implementations to this function to create a custom nonce transaction confirmation strategy.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { waitForDurableNonceTransactionConfirmation } from '@solana/transaction-confirmation';
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await waitForDurableNonceTransactionConfirmation({
|
|
126
|
+
getNonceInvalidationPromise({ abortSignal, commitment, currentNonceValue, nonceAccountAddress }) {
|
|
127
|
+
// Return a promise that rejects when a nonce becomes invalid.
|
|
128
|
+
},
|
|
129
|
+
getRecentSignatureConfirmationPromise({ abortSignal, commitment, signature }) {
|
|
130
|
+
// Return a promise that resolves when a transaction achieves confirmation
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// Handle errors.
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `waitForRecentTransactionConfirmation()`
|
|
139
|
+
|
|
140
|
+
Supply your own confirmation implementations to this function to create a custom nonce transaction confirmation strategy.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { waitForRecentTransactionConfirmation } from '@solana/transaction-confirmation';
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
await waitForRecentTransactionConfirmation({
|
|
147
|
+
getBlockHeightExceedencePromise({ abortSignal, commitment, lastValidBlockHeight }) {
|
|
148
|
+
// Return a promise that rejects when the blockhash's block height has been exceeded
|
|
149
|
+
},
|
|
150
|
+
getRecentSignatureConfirmationPromise({ abortSignal, commitment, signature }) {
|
|
151
|
+
// Return a promise that resolves when a transaction achieves confirmation
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
} catch (e) {
|
|
155
|
+
// Handle errors.
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `waitForRecentTransactionConfirmationUntilTimeout()`
|
|
160
|
+
|
|
161
|
+
Supply your own confirmation implementations to this function to create a custom nonce transaction confirmation strategy.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { waitForRecentTransactionConfirmationUntilTimeout } from '@solana/transaction-confirmation';
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await waitForRecentTransactionConfirmationUntilTimeout({
|
|
168
|
+
getTimeoutPromise({ abortSignal, commitment }) {
|
|
169
|
+
// Return a promise that rejects after your chosen timeout
|
|
170
|
+
},
|
|
171
|
+
getRecentSignatureConfirmationPromise({ abortSignal, commitment, signature }) {
|
|
172
|
+
// Return a promise that resolves when a transaction achieves confirmation
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
} catch (e) {
|
|
176
|
+
// Handle errors.
|
|
177
|
+
}
|
|
178
|
+
```
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@solana/errors');
|
|
4
|
+
var codecsStrings = require('@solana/codecs-strings');
|
|
5
|
+
var rpcTypes = require('@solana/rpc-types');
|
|
6
|
+
var transactions = require('@solana/transactions');
|
|
7
|
+
|
|
8
|
+
// src/confirmation-strategy-blockheight.ts
|
|
9
|
+
function createBlockHeightExceedencePromiseFactory({
|
|
10
|
+
rpc,
|
|
11
|
+
rpcSubscriptions
|
|
12
|
+
}) {
|
|
13
|
+
return async function getBlockHeightExceedencePromise({
|
|
14
|
+
abortSignal: callerAbortSignal,
|
|
15
|
+
commitment,
|
|
16
|
+
lastValidBlockHeight
|
|
17
|
+
}) {
|
|
18
|
+
const abortController = new AbortController();
|
|
19
|
+
const handleAbort = () => {
|
|
20
|
+
abortController.abort();
|
|
21
|
+
};
|
|
22
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
23
|
+
async function getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight() {
|
|
24
|
+
const { absoluteSlot, blockHeight } = await rpc.getEpochInfo({ commitment }).send({ abortSignal: abortController.signal });
|
|
25
|
+
return {
|
|
26
|
+
blockHeight,
|
|
27
|
+
differenceBetweenSlotHeightAndBlockHeight: absoluteSlot - blockHeight
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const [slotNotifications, { blockHeight: initialBlockHeight, differenceBetweenSlotHeightAndBlockHeight }] = await Promise.all([
|
|
32
|
+
rpcSubscriptions.slotNotifications().subscribe({ abortSignal: abortController.signal }),
|
|
33
|
+
getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight()
|
|
34
|
+
]);
|
|
35
|
+
let currentBlockHeight = initialBlockHeight;
|
|
36
|
+
if (currentBlockHeight <= lastValidBlockHeight) {
|
|
37
|
+
let lastKnownDifferenceBetweenSlotHeightAndBlockHeight = differenceBetweenSlotHeightAndBlockHeight;
|
|
38
|
+
for await (const slotNotification of slotNotifications) {
|
|
39
|
+
const { slot } = slotNotification;
|
|
40
|
+
if (slot - lastKnownDifferenceBetweenSlotHeightAndBlockHeight > lastValidBlockHeight) {
|
|
41
|
+
const {
|
|
42
|
+
blockHeight: recheckedBlockHeight,
|
|
43
|
+
differenceBetweenSlotHeightAndBlockHeight: currentDifferenceBetweenSlotHeightAndBlockHeight
|
|
44
|
+
} = await getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight();
|
|
45
|
+
currentBlockHeight = recheckedBlockHeight;
|
|
46
|
+
if (currentBlockHeight > lastValidBlockHeight) {
|
|
47
|
+
break;
|
|
48
|
+
} else {
|
|
49
|
+
lastKnownDifferenceBetweenSlotHeightAndBlockHeight = currentDifferenceBetweenSlotHeightAndBlockHeight;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new errors.SolanaError(errors.SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, {
|
|
55
|
+
currentBlockHeight,
|
|
56
|
+
lastValidBlockHeight
|
|
57
|
+
});
|
|
58
|
+
} finally {
|
|
59
|
+
abortController.abort();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
var NONCE_VALUE_OFFSET = 4 + // version(u32)
|
|
64
|
+
4 + // state(u32)
|
|
65
|
+
32;
|
|
66
|
+
function createNonceInvalidationPromiseFactory(rpc, rpcSubscriptions) {
|
|
67
|
+
return async function getNonceInvalidationPromise({
|
|
68
|
+
abortSignal: callerAbortSignal,
|
|
69
|
+
commitment,
|
|
70
|
+
currentNonceValue: expectedNonceValue,
|
|
71
|
+
nonceAccountAddress
|
|
72
|
+
}) {
|
|
73
|
+
const abortController = new AbortController();
|
|
74
|
+
function handleAbort() {
|
|
75
|
+
abortController.abort();
|
|
76
|
+
}
|
|
77
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
78
|
+
const accountNotifications = await rpcSubscriptions.accountNotifications(nonceAccountAddress, { commitment, encoding: "base64" }).subscribe({ abortSignal: abortController.signal });
|
|
79
|
+
const base58Decoder = codecsStrings.getBase58Decoder();
|
|
80
|
+
const base64Encoder = codecsStrings.getBase64Encoder();
|
|
81
|
+
function getNonceFromAccountData([base64EncodedBytes]) {
|
|
82
|
+
const data = base64Encoder.encode(base64EncodedBytes);
|
|
83
|
+
const nonceValueBytes = data.slice(NONCE_VALUE_OFFSET, NONCE_VALUE_OFFSET + 32);
|
|
84
|
+
return base58Decoder.decode(nonceValueBytes);
|
|
85
|
+
}
|
|
86
|
+
const nonceAccountDidAdvancePromise = (async () => {
|
|
87
|
+
for await (const accountNotification of accountNotifications) {
|
|
88
|
+
const nonceValue = getNonceFromAccountData(accountNotification.value.data);
|
|
89
|
+
if (nonceValue !== expectedNonceValue) {
|
|
90
|
+
throw new errors.SolanaError(errors.SOLANA_ERROR__NONCE_INVALID, {
|
|
91
|
+
actualNonceValue: nonceValue,
|
|
92
|
+
expectedNonceValue
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
const nonceIsAlreadyInvalidPromise = (async () => {
|
|
98
|
+
const { value: nonceAccount } = await rpc.getAccountInfo(nonceAccountAddress, {
|
|
99
|
+
commitment,
|
|
100
|
+
dataSlice: { length: 32, offset: NONCE_VALUE_OFFSET },
|
|
101
|
+
encoding: "base58"
|
|
102
|
+
}).send({ abortSignal: abortController.signal });
|
|
103
|
+
if (!nonceAccount) {
|
|
104
|
+
throw new errors.SolanaError(errors.SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND, {
|
|
105
|
+
nonceAccountAddress
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const nonceValue = (
|
|
109
|
+
// This works because we asked for the exact slice of data representing the nonce
|
|
110
|
+
// value, and furthermore asked for it in `base58` encoding.
|
|
111
|
+
nonceAccount.data[0]
|
|
112
|
+
);
|
|
113
|
+
if (nonceValue !== expectedNonceValue) {
|
|
114
|
+
throw new errors.SolanaError(errors.SOLANA_ERROR__NONCE_INVALID, {
|
|
115
|
+
actualNonceValue: nonceValue,
|
|
116
|
+
expectedNonceValue
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
await new Promise(() => {
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
try {
|
|
124
|
+
return await Promise.race([nonceAccountDidAdvancePromise, nonceIsAlreadyInvalidPromise]);
|
|
125
|
+
} finally {
|
|
126
|
+
abortController.abort();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function createRecentSignatureConfirmationPromiseFactory(rpc, rpcSubscriptions) {
|
|
131
|
+
return async function getRecentSignatureConfirmationPromise({
|
|
132
|
+
abortSignal: callerAbortSignal,
|
|
133
|
+
commitment,
|
|
134
|
+
signature
|
|
135
|
+
}) {
|
|
136
|
+
const abortController = new AbortController();
|
|
137
|
+
function handleAbort() {
|
|
138
|
+
abortController.abort();
|
|
139
|
+
}
|
|
140
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
141
|
+
const signatureStatusNotifications = await rpcSubscriptions.signatureNotifications(signature, { commitment }).subscribe({ abortSignal: abortController.signal });
|
|
142
|
+
const signatureDidCommitPromise = (async () => {
|
|
143
|
+
for await (const signatureStatusNotification of signatureStatusNotifications) {
|
|
144
|
+
if (signatureStatusNotification.value.err) {
|
|
145
|
+
throw new Error(`The transaction with signature \`${signature}\` failed.`, {
|
|
146
|
+
cause: signatureStatusNotification.value.err
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
153
|
+
const signatureStatusLookupPromise = (async () => {
|
|
154
|
+
const { value: signatureStatusResults } = await rpc.getSignatureStatuses([signature]).send({ abortSignal: abortController.signal });
|
|
155
|
+
const signatureStatus = signatureStatusResults[0];
|
|
156
|
+
if (signatureStatus && signatureStatus.confirmationStatus && rpcTypes.commitmentComparator(signatureStatus.confirmationStatus, commitment) >= 0) {
|
|
157
|
+
return;
|
|
158
|
+
} else {
|
|
159
|
+
await new Promise(() => {
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
try {
|
|
164
|
+
return await Promise.race([signatureDidCommitPromise, signatureStatusLookupPromise]);
|
|
165
|
+
} finally {
|
|
166
|
+
abortController.abort();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/confirmation-strategy-timeout.ts
|
|
172
|
+
async function getTimeoutPromise({ abortSignal: callerAbortSignal, commitment }) {
|
|
173
|
+
return await new Promise((_, reject) => {
|
|
174
|
+
const handleAbort = (e) => {
|
|
175
|
+
clearTimeout(timeoutId);
|
|
176
|
+
const abortError = new DOMException(e.target.reason, "AbortError");
|
|
177
|
+
reject(abortError);
|
|
178
|
+
};
|
|
179
|
+
callerAbortSignal.addEventListener("abort", handleAbort);
|
|
180
|
+
const timeoutMs = commitment === "processed" ? 3e4 : 6e4;
|
|
181
|
+
const startMs = performance.now();
|
|
182
|
+
const timeoutId = (
|
|
183
|
+
// We use `setTimeout` instead of `AbortSignal.timeout()` because we want to measure
|
|
184
|
+
// elapsed time instead of active time.
|
|
185
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
const elapsedMs = performance.now() - startMs;
|
|
188
|
+
reject(new DOMException(`Timeout elapsed after ${elapsedMs} ms`, "TimeoutError"));
|
|
189
|
+
}, timeoutMs)
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/confirmation-strategy-racer.ts
|
|
195
|
+
async function raceStrategies(signature, config, getSpecificStrategiesForRace) {
|
|
196
|
+
const { abortSignal: callerAbortSignal, commitment, getRecentSignatureConfirmationPromise } = config;
|
|
197
|
+
callerAbortSignal?.throwIfAborted();
|
|
198
|
+
const abortController = new AbortController();
|
|
199
|
+
if (callerAbortSignal) {
|
|
200
|
+
const handleAbort = () => {
|
|
201
|
+
abortController.abort();
|
|
202
|
+
};
|
|
203
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const specificStrategies = getSpecificStrategiesForRace({
|
|
207
|
+
...config,
|
|
208
|
+
abortSignal: abortController.signal
|
|
209
|
+
});
|
|
210
|
+
return await Promise.race([
|
|
211
|
+
getRecentSignatureConfirmationPromise({
|
|
212
|
+
abortSignal: abortController.signal,
|
|
213
|
+
commitment,
|
|
214
|
+
signature
|
|
215
|
+
}),
|
|
216
|
+
...specificStrategies
|
|
217
|
+
]);
|
|
218
|
+
} finally {
|
|
219
|
+
abortController.abort();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/waiters.ts
|
|
224
|
+
async function waitForDurableNonceTransactionConfirmation(config) {
|
|
225
|
+
await raceStrategies(
|
|
226
|
+
transactions.getSignatureFromTransaction(config.transaction),
|
|
227
|
+
config,
|
|
228
|
+
function getSpecificStrategiesForRace({ abortSignal, commitment, getNonceInvalidationPromise, transaction }) {
|
|
229
|
+
return [
|
|
230
|
+
getNonceInvalidationPromise({
|
|
231
|
+
abortSignal,
|
|
232
|
+
commitment,
|
|
233
|
+
currentNonceValue: transaction.lifetimeConstraint.nonce,
|
|
234
|
+
nonceAccountAddress: transaction.instructions[0].accounts[0].address
|
|
235
|
+
})
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
async function waitForRecentTransactionConfirmation(config) {
|
|
241
|
+
await raceStrategies(
|
|
242
|
+
transactions.getSignatureFromTransaction(config.transaction),
|
|
243
|
+
config,
|
|
244
|
+
function getSpecificStrategiesForRace({
|
|
245
|
+
abortSignal,
|
|
246
|
+
commitment,
|
|
247
|
+
getBlockHeightExceedencePromise,
|
|
248
|
+
transaction
|
|
249
|
+
}) {
|
|
250
|
+
return [
|
|
251
|
+
getBlockHeightExceedencePromise({
|
|
252
|
+
abortSignal,
|
|
253
|
+
commitment,
|
|
254
|
+
lastValidBlockHeight: transaction.lifetimeConstraint.lastValidBlockHeight
|
|
255
|
+
})
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
async function waitForRecentTransactionConfirmationUntilTimeout(config) {
|
|
261
|
+
await raceStrategies(
|
|
262
|
+
config.signature,
|
|
263
|
+
config,
|
|
264
|
+
function getSpecificStrategiesForRace({ abortSignal, commitment, getTimeoutPromise: getTimeoutPromise2 }) {
|
|
265
|
+
return [
|
|
266
|
+
getTimeoutPromise2({
|
|
267
|
+
abortSignal,
|
|
268
|
+
commitment
|
|
269
|
+
})
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
exports.createBlockHeightExceedencePromiseFactory = createBlockHeightExceedencePromiseFactory;
|
|
276
|
+
exports.createNonceInvalidationPromiseFactory = createNonceInvalidationPromiseFactory;
|
|
277
|
+
exports.createRecentSignatureConfirmationPromiseFactory = createRecentSignatureConfirmationPromiseFactory;
|
|
278
|
+
exports.getTimeoutPromise = getTimeoutPromise;
|
|
279
|
+
exports.waitForDurableNonceTransactionConfirmation = waitForDurableNonceTransactionConfirmation;
|
|
280
|
+
exports.waitForRecentTransactionConfirmation = waitForRecentTransactionConfirmation;
|
|
281
|
+
exports.waitForRecentTransactionConfirmationUntilTimeout = waitForRecentTransactionConfirmationUntilTimeout;
|
|
282
|
+
//# sourceMappingURL=out.js.map
|
|
283
|
+
//# sourceMappingURL=index.browser.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/confirmation-strategy-blockheight.ts","../src/confirmation-strategy-nonce.ts","../src/confirmation-strategy-recent-signature.ts","../src/confirmation-strategy-timeout.ts","../src/waiters.ts","../src/confirmation-strategy-racer.ts"],"names":["SolanaError","getTimeoutPromise"],"mappings":";AAAA,SAAS,qCAAqC,mBAAmB;AAW1D,SAAS,0CAA0C;AAAA,EACtD;AAAA,EACA;AACJ,GAGuC;AACnC,SAAO,eAAe,gCAAgC;AAAA,IAClD,aAAa;AAAA,IACb;AAAA,IACA;AAAA,EACJ,GAAG;AACC,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,UAAM,cAAc,MAAM;AACtB,sBAAgB,MAAM;AAAA,IAC1B;AACA,sBAAkB,iBAAiB,SAAS,aAAa,EAAE,QAAQ,gBAAgB,OAAO,CAAC;AAC3F,mBAAe,6DAA6D;AACxE,YAAM,EAAE,cAAc,YAAY,IAAI,MAAM,IACvC,aAAa,EAAE,WAAW,CAAC,EAC3B,KAAK,EAAE,aAAa,gBAAgB,OAAO,CAAC;AACjD,aAAO;AAAA,QACH;AAAA,QACA,2CAA2C,eAAe;AAAA,MAC9D;AAAA,IACJ;AACA,QAAI;AACA,YAAM,CAAC,mBAAmB,EAAE,aAAa,oBAAoB,0CAA0C,CAAC,IACpG,MAAM,QAAQ,IAAI;AAAA,QACd,iBAAiB,kBAAkB,EAAE,UAAU,EAAE,aAAa,gBAAgB,OAAO,CAAC;AAAA,QACtF,2DAA2D;AAAA,MAC/D,CAAC;AACL,UAAI,qBAAqB;AACzB,UAAI,sBAAsB,sBAAsB;AAC5C,YAAI,qDAAqD;AACzD,yBAAiB,oBAAoB,mBAAmB;AACpD,gBAAM,EAAE,KAAK,IAAI;AACjB,cAAI,OAAO,qDAAqD,sBAAsB;AAElF,kBAAM;AAAA,cACF,aAAa;AAAA,cACb,2CAA2C;AAAA,YAC/C,IAAI,MAAM,2DAA2D;AACrE,iCAAqB;AACrB,gBAAI,qBAAqB,sBAAsB;AAE3C;AAAA,YACJ,OAAO;AAKH,mEACI;AAAA,YACR;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AACA,YAAM,IAAI,YAAY,qCAAqC;AAAA,QACvD;AAAA,QACA;AAAA,MACJ,CAAC;AAAA,IACL,UAAE;AACE,sBAAgB,MAAM;AAAA,IAC1B;AAAA,EACJ;AACJ;;;AC5EA,SAAS,kBAAkB,wBAAwB;AACnD,SAAS,uCAAuC,6BAA6B,eAAAA,oBAAmB;AAYhG,IAAM,qBACF;AACA;AACA;AAGG,SAAS,sCACZ,KACA,kBAC6B;AAC7B,SAAO,eAAe,4BAA4B;AAAA,IAC9C,aAAa;AAAA,IACb;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,EACJ,GAAG;AACC,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,aAAS,cAAc;AACnB,sBAAgB,MAAM;AAAA,IAC1B;AACA,sBAAkB,iBAAiB,SAAS,aAAa,EAAE,QAAQ,gBAAgB,OAAO,CAAC;AAI3F,UAAM,uBAAuB,MAAM,iBAC9B,qBAAqB,qBAAqB,EAAE,YAAY,UAAU,SAAS,CAAC,EAC5E,UAAU,EAAE,aAAa,gBAAgB,OAAO,CAAC;AACtD,UAAM,gBAAgB,iBAAiB;AACvC,UAAM,gBAAgB,iBAAiB;AACvC,aAAS,wBAAwB,CAAC,kBAAkB,GAAqC;AACrF,YAAM,OAAO,cAAc,OAAO,kBAAkB;AACpD,YAAM,kBAAkB,KAAK,MAAM,oBAAoB,qBAAqB,EAAE;AAC9E,aAAO,cAAc,OAAO,eAAe;AAAA,IAC/C;AACA,UAAM,iCAAiC,YAAY;AAC/C,uBAAiB,uBAAuB,sBAAsB;AAC1D,cAAM,aAAa,wBAAwB,oBAAoB,MAAM,IAAI;AACzE,YAAI,eAAe,oBAAoB;AACnC,gBAAM,IAAIA,aAAY,6BAA6B;AAAA,YAC/C,kBAAkB;AAAA,YAClB;AAAA,UACJ,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ,GAAG;AAKH,UAAM,gCAAgC,YAAY;AAC9C,YAAM,EAAE,OAAO,aAAa,IAAI,MAAM,IACjC,eAAe,qBAAqB;AAAA,QACjC;AAAA,QACA,WAAW,EAAE,QAAQ,IAAI,QAAQ,mBAAmB;AAAA,QACpD,UAAU;AAAA,MACd,CAAC,EACA,KAAK,EAAE,aAAa,gBAAgB,OAAO,CAAC;AACjD,UAAI,CAAC,cAAc;AACf,cAAM,IAAIA,aAAY,uCAAuC;AAAA,UACzD;AAAA,QACJ,CAAC;AAAA,MACL;AACA,YAAM;AAAA;AAAA;AAAA,QAGF,aAAa,KAAK,CAAC;AAAA;AACvB,UAAI,eAAe,oBAAoB;AACnC,cAAM,IAAIA,aAAY,6BAA6B;AAAA,UAC/C,kBAAkB;AAAA,UAClB;AAAA,QACJ,CAAC;AAAA,MACL,OAAO;AACH,cAAM,IAAI,QAAQ,MAAM;AAAA,QAExB,CAAC;AAAA,MACL;AAAA,IACJ,GAAG;AACH,QAAI;AACA,aAAO,MAAM,QAAQ,KAAK,CAAC,+BAA+B,4BAA4B,CAAC;AAAA,IAC3F,UAAE;AACE,sBAAgB,MAAM;AAAA,IAC1B;AAAA,EACJ;AACJ;;;AC/FA,SAA0B,4BAA6D;AAQhF,SAAS,gDACZ,KACA,kBACuC;AACvC,SAAO,eAAe,sCAAsC;AAAA,IACxD,aAAa;AAAA,IACb;AAAA,IACA;AAAA,EACJ,GAAG;AACC,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,aAAS,cAAc;AACnB,sBAAgB,MAAM;AAAA,IAC1B;AACA,sBAAkB,iBAAiB,SAAS,aAAa,EAAE,QAAQ,gBAAgB,OAAO,CAAC;AAI3F,UAAM,+BAA+B,MAAM,iBACtC,uBAAuB,WAAW,EAAE,WAAW,CAAC,EAChD,UAAU,EAAE,aAAa,gBAAgB,OAAO,CAAC;AACtD,UAAM,6BAA6B,YAAY;AAC3C,uBAAiB,+BAA+B,8BAA8B;AAC1E,YAAI,4BAA4B,MAAM,KAAK;AAEvC,gBAAM,IAAI,MAAM,oCAAoC,SAAS,cAAc;AAAA,YACvE,OAAO,4BAA4B,MAAM;AAAA,UAC7C,CAAC;AAAA,QACL,OAAO;AACH;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,GAAG;AAKH,UAAM,gCAAgC,YAAY;AAC9C,YAAM,EAAE,OAAO,uBAAuB,IAAI,MAAM,IAC3C,qBAAqB,CAAC,SAAS,CAAC,EAChC,KAAK,EAAE,aAAa,gBAAgB,OAAO,CAAC;AACjD,YAAM,kBAAkB,uBAAuB,CAAC;AAChD,UACI,mBACA,gBAAgB,sBAChB,qBAAqB,gBAAgB,oBAAoB,UAAU,KAAK,GAC1E;AACE;AAAA,MACJ,OAAO;AACH,cAAM,IAAI,QAAQ,MAAM;AAAA,QAExB,CAAC;AAAA,MACL;AAAA,IACJ,GAAG;AACH,QAAI;AACA,aAAO,MAAM,QAAQ,KAAK,CAAC,2BAA2B,4BAA4B,CAAC;AAAA,IACvF,UAAE;AACE,sBAAgB,MAAM;AAAA,IAC1B;AAAA,EACJ;AACJ;;;AC9DA,eAAsB,kBAAkB,EAAE,aAAa,mBAAmB,WAAW,GAAW;AAC5F,SAAO,MAAM,IAAI,QAAQ,CAAC,GAAG,WAAW;AACpC,UAAM,cAAc,CAAC,MAAoC;AACrD,mBAAa,SAAS;AACtB,YAAM,aAAa,IAAI,aAAc,EAAE,OAAuB,QAAQ,YAAY;AAClF,aAAO,UAAU;AAAA,IACrB;AACA,sBAAkB,iBAAiB,SAAS,WAAW;AACvD,UAAM,YAAY,eAAe,cAAc,MAAS;AACxD,UAAM,UAAU,YAAY,IAAI;AAChC,UAAM;AAAA;AAAA;AAAA;AAAA,MAIF,WAAW,MAAM;AACb,cAAM,YAAY,YAAY,IAAI,IAAI;AACtC,eAAO,IAAI,aAAa,yBAAyB,SAAS,OAAO,cAAc,CAAC;AAAA,MACpF,GAAG,SAAS;AAAA;AAAA,EACpB,CAAC;AACL;;;ACxBA;AAAA,EACI;AAAA,OAIG;;;ACMP,eAAsB,eAClB,WACA,QACA,8BACF;AACE,QAAM,EAAE,aAAa,mBAAmB,YAAY,sCAAsC,IAAI;AAC9F,qBAAmB,eAAe;AAClC,QAAM,kBAAkB,IAAI,gBAAgB;AAC5C,MAAI,mBAAmB;AACnB,UAAM,cAAc,MAAM;AACtB,sBAAgB,MAAM;AAAA,IAC1B;AACA,sBAAkB,iBAAiB,SAAS,aAAa,EAAE,QAAQ,gBAAgB,OAAO,CAAC;AAAA,EAC/F;AACA,MAAI;AACA,UAAM,qBAAqB,6BAA6B;AAAA,MACpD,GAAG;AAAA,MACH,aAAa,gBAAgB;AAAA,IACjC,CAAC;AACD,WAAO,MAAM,QAAQ,KAAK;AAAA,MACtB,sCAAsC;AAAA,QAClC,aAAa,gBAAgB;AAAA,QAC7B;AAAA,QACA;AAAA,MACJ,CAAC;AAAA,MACD,GAAG;AAAA,IACP,CAAC;AAAA,EACL,UAAE;AACE,oBAAgB,MAAM;AAAA,EAC1B;AACJ;;;ADNA,eAAsB,2CAClB,QACa;AACb,QAAM;AAAA,IACF,4BAA4B,OAAO,WAAW;AAAA,IAC9C;AAAA,IACA,SAAS,6BAA6B,EAAE,aAAa,YAAY,6BAA6B,YAAY,GAAG;AACzG,aAAO;AAAA,QACH,4BAA4B;AAAA,UACxB;AAAA,UACA;AAAA,UACA,mBAAmB,YAAY,mBAAmB;AAAA,UAClD,qBAAqB,YAAY,aAAa,CAAC,EAAE,SAAS,CAAC,EAAE;AAAA,QACjE,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,eAAsB,qCAClB,QACa;AACb,QAAM;AAAA,IACF,4BAA4B,OAAO,WAAW;AAAA,IAC9C;AAAA,IACA,SAAS,6BAA6B;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ,GAAG;AACC,aAAO;AAAA,QACH,gCAAgC;AAAA,UAC5B;AAAA,UACA;AAAA,UACA,sBAAsB,YAAY,mBAAmB;AAAA,QACzD,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ;AAGA,eAAsB,iDAClB,QACa;AACb,QAAM;AAAA,IACF,OAAO;AAAA,IACP;AAAA,IACA,SAAS,6BAA6B,EAAE,aAAa,YAAY,mBAAAC,mBAAkB,GAAG;AAClF,aAAO;AAAA,QACHA,mBAAkB;AAAA,UACd;AAAA,UACA;AAAA,QACJ,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ","sourcesContent":["import { SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, SolanaError } from '@solana/errors';\nimport type { GetEpochInfoApi, SlotNotificationsApi } from '@solana/rpc-core';\nimport type { Rpc, RpcSubscriptions } from '@solana/rpc-types';\nimport { Commitment } from '@solana/rpc-types';\n\ntype GetBlockHeightExceedencePromiseFn = (config: {\n abortSignal: AbortSignal;\n commitment?: Commitment;\n lastValidBlockHeight: bigint;\n}) => Promise<void>;\n\nexport function createBlockHeightExceedencePromiseFactory({\n rpc,\n rpcSubscriptions,\n}: Readonly<{\n rpc: Rpc<GetEpochInfoApi>;\n rpcSubscriptions: RpcSubscriptions<SlotNotificationsApi>;\n}>): GetBlockHeightExceedencePromiseFn {\n return async function getBlockHeightExceedencePromise({\n abortSignal: callerAbortSignal,\n commitment,\n lastValidBlockHeight,\n }) {\n const abortController = new AbortController();\n const handleAbort = () => {\n abortController.abort();\n };\n callerAbortSignal.addEventListener('abort', handleAbort, { signal: abortController.signal });\n async function getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight() {\n const { absoluteSlot, blockHeight } = await rpc\n .getEpochInfo({ commitment })\n .send({ abortSignal: abortController.signal });\n return {\n blockHeight,\n differenceBetweenSlotHeightAndBlockHeight: absoluteSlot - blockHeight,\n };\n }\n try {\n const [slotNotifications, { blockHeight: initialBlockHeight, differenceBetweenSlotHeightAndBlockHeight }] =\n await Promise.all([\n rpcSubscriptions.slotNotifications().subscribe({ abortSignal: abortController.signal }),\n getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight(),\n ]);\n let currentBlockHeight = initialBlockHeight;\n if (currentBlockHeight <= lastValidBlockHeight) {\n let lastKnownDifferenceBetweenSlotHeightAndBlockHeight = differenceBetweenSlotHeightAndBlockHeight;\n for await (const slotNotification of slotNotifications) {\n const { slot } = slotNotification;\n if (slot - lastKnownDifferenceBetweenSlotHeightAndBlockHeight > lastValidBlockHeight) {\n // Before making a final decision, recheck the actual block height.\n const {\n blockHeight: recheckedBlockHeight,\n differenceBetweenSlotHeightAndBlockHeight: currentDifferenceBetweenSlotHeightAndBlockHeight,\n } = await getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight();\n currentBlockHeight = recheckedBlockHeight;\n if (currentBlockHeight > lastValidBlockHeight) {\n // Verfied; the block height has been exceeded.\n break;\n } else {\n // The block height has not been exceeded, which implies that the\n // difference between the slot height and the block height has grown\n // (ie. some blocks have been skipped since we started). Recalibrate the\n // difference and keep waiting.\n lastKnownDifferenceBetweenSlotHeightAndBlockHeight =\n currentDifferenceBetweenSlotHeightAndBlockHeight;\n }\n }\n }\n }\n throw new SolanaError(SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, {\n currentBlockHeight,\n lastValidBlockHeight,\n });\n } finally {\n abortController.abort();\n }\n };\n}\n","import { Address } from '@solana/addresses';\nimport { getBase58Decoder, getBase64Encoder } from '@solana/codecs-strings';\nimport { SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND, SOLANA_ERROR__NONCE_INVALID, SolanaError } from '@solana/errors';\nimport type { AccountNotificationsApi, GetAccountInfoApi } from '@solana/rpc-core';\nimport type { Base64EncodedDataResponse, Commitment, Rpc, RpcSubscriptions } from '@solana/rpc-types';\nimport { Nonce } from '@solana/transactions';\n\ntype GetNonceInvalidationPromiseFn = (config: {\n abortSignal: AbortSignal;\n commitment: Commitment;\n currentNonceValue: Nonce;\n nonceAccountAddress: Address;\n}) => Promise<void>;\n\nconst NONCE_VALUE_OFFSET =\n 4 + // version(u32)\n 4 + // state(u32)\n 32; // nonce authority(pubkey)\n// Then comes the nonce value.\n\nexport function createNonceInvalidationPromiseFactory(\n rpc: Rpc<GetAccountInfoApi>,\n rpcSubscriptions: RpcSubscriptions<AccountNotificationsApi>,\n): GetNonceInvalidationPromiseFn {\n return async function getNonceInvalidationPromise({\n abortSignal: callerAbortSignal,\n commitment,\n currentNonceValue: expectedNonceValue,\n nonceAccountAddress,\n }) {\n const abortController = new AbortController();\n function handleAbort() {\n abortController.abort();\n }\n callerAbortSignal.addEventListener('abort', handleAbort, { signal: abortController.signal });\n /**\n * STEP 1: Set up a subscription for nonce account changes.\n */\n const accountNotifications = await rpcSubscriptions\n .accountNotifications(nonceAccountAddress, { commitment, encoding: 'base64' })\n .subscribe({ abortSignal: abortController.signal });\n const base58Decoder = getBase58Decoder();\n const base64Encoder = getBase64Encoder();\n function getNonceFromAccountData([base64EncodedBytes]: Base64EncodedDataResponse): Nonce {\n const data = base64Encoder.encode(base64EncodedBytes);\n const nonceValueBytes = data.slice(NONCE_VALUE_OFFSET, NONCE_VALUE_OFFSET + 32);\n return base58Decoder.decode(nonceValueBytes) as Nonce;\n }\n const nonceAccountDidAdvancePromise = (async () => {\n for await (const accountNotification of accountNotifications) {\n const nonceValue = getNonceFromAccountData(accountNotification.value.data);\n if (nonceValue !== expectedNonceValue) {\n throw new SolanaError(SOLANA_ERROR__NONCE_INVALID, {\n actualNonceValue: nonceValue,\n expectedNonceValue,\n });\n }\n }\n })();\n /**\n * STEP 2: Having subscribed for updates, make a one-shot request for the current nonce\n * value to check if it has already been advanced.\n */\n const nonceIsAlreadyInvalidPromise = (async () => {\n const { value: nonceAccount } = await rpc\n .getAccountInfo(nonceAccountAddress, {\n commitment,\n dataSlice: { length: 32, offset: NONCE_VALUE_OFFSET },\n encoding: 'base58',\n })\n .send({ abortSignal: abortController.signal });\n if (!nonceAccount) {\n throw new SolanaError(SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND, {\n nonceAccountAddress,\n });\n }\n const nonceValue =\n // This works because we asked for the exact slice of data representing the nonce\n // value, and furthermore asked for it in `base58` encoding.\n nonceAccount.data[0] as unknown as Nonce;\n if (nonceValue !== expectedNonceValue) {\n throw new SolanaError(SOLANA_ERROR__NONCE_INVALID, {\n actualNonceValue: nonceValue,\n expectedNonceValue,\n });\n } else {\n await new Promise(() => {\n /* never resolve */\n });\n }\n })();\n try {\n return await Promise.race([nonceAccountDidAdvancePromise, nonceIsAlreadyInvalidPromise]);\n } finally {\n abortController.abort();\n }\n };\n}\n","import { Signature } from '@solana/keys';\nimport type { GetSignatureStatusesApi, SignatureNotificationsApi } from '@solana/rpc-core';\nimport { type Commitment, commitmentComparator, type Rpc, type RpcSubscriptions } from '@solana/rpc-types';\n\ntype GetRecentSignatureConfirmationPromiseFn = (config: {\n abortSignal: AbortSignal;\n commitment: Commitment;\n signature: Signature;\n}) => Promise<void>;\n\nexport function createRecentSignatureConfirmationPromiseFactory(\n rpc: Rpc<GetSignatureStatusesApi>,\n rpcSubscriptions: RpcSubscriptions<SignatureNotificationsApi>,\n): GetRecentSignatureConfirmationPromiseFn {\n return async function getRecentSignatureConfirmationPromise({\n abortSignal: callerAbortSignal,\n commitment,\n signature,\n }) {\n const abortController = new AbortController();\n function handleAbort() {\n abortController.abort();\n }\n callerAbortSignal.addEventListener('abort', handleAbort, { signal: abortController.signal });\n /**\n * STEP 1: Set up a subscription for status changes to a signature.\n */\n const signatureStatusNotifications = await rpcSubscriptions\n .signatureNotifications(signature, { commitment })\n .subscribe({ abortSignal: abortController.signal });\n const signatureDidCommitPromise = (async () => {\n for await (const signatureStatusNotification of signatureStatusNotifications) {\n if (signatureStatusNotification.value.err) {\n // TODO: Coded error.\n throw new Error(`The transaction with signature \\`${signature}\\` failed.`, {\n cause: signatureStatusNotification.value.err,\n });\n } else {\n return;\n }\n }\n })();\n /**\n * STEP 2: Having subscribed for updates, make a one-shot request for the current status.\n * This will only yield a result if the signature is still in the status cache.\n */\n const signatureStatusLookupPromise = (async () => {\n const { value: signatureStatusResults } = await rpc\n .getSignatureStatuses([signature])\n .send({ abortSignal: abortController.signal });\n const signatureStatus = signatureStatusResults[0];\n if (\n signatureStatus &&\n signatureStatus.confirmationStatus &&\n commitmentComparator(signatureStatus.confirmationStatus, commitment) >= 0\n ) {\n return;\n } else {\n await new Promise(() => {\n /* never resolve */\n });\n }\n })();\n try {\n return await Promise.race([signatureDidCommitPromise, signatureStatusLookupPromise]);\n } finally {\n abortController.abort();\n }\n };\n}\n","import { Commitment } from '@solana/rpc-types';\n\ntype Config = Readonly<{\n abortSignal: AbortSignal;\n commitment: Commitment;\n}>;\n\nexport async function getTimeoutPromise({ abortSignal: callerAbortSignal, commitment }: Config) {\n return await new Promise((_, reject) => {\n const handleAbort = (e: AbortSignalEventMap['abort']) => {\n clearTimeout(timeoutId);\n const abortError = new DOMException((e.target as AbortSignal).reason, 'AbortError');\n reject(abortError);\n };\n callerAbortSignal.addEventListener('abort', handleAbort);\n const timeoutMs = commitment === 'processed' ? 30_000 : 60_000;\n const startMs = performance.now();\n const timeoutId =\n // We use `setTimeout` instead of `AbortSignal.timeout()` because we want to measure\n // elapsed time instead of active time.\n // See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static\n setTimeout(() => {\n const elapsedMs = performance.now() - startMs;\n reject(new DOMException(`Timeout elapsed after ${elapsedMs} ms`, 'TimeoutError'));\n }, timeoutMs);\n });\n}\n","import { Signature } from '@solana/keys';\nimport type { Slot } from '@solana/rpc-types';\nimport {\n getSignatureFromTransaction,\n IDurableNonceTransaction,\n ITransactionWithFeePayer,\n ITransactionWithSignatures,\n} from '@solana/transactions';\n\nimport { createBlockHeightExceedencePromiseFactory } from './confirmation-strategy-blockheight';\nimport { createNonceInvalidationPromiseFactory } from './confirmation-strategy-nonce';\nimport { BaseTransactionConfirmationStrategyConfig, raceStrategies } from './confirmation-strategy-racer';\nimport { getTimeoutPromise } from './confirmation-strategy-timeout';\n\ninterface WaitForDurableNonceTransactionConfirmationConfig extends BaseTransactionConfirmationStrategyConfig {\n getNonceInvalidationPromise: ReturnType<typeof createNonceInvalidationPromiseFactory>;\n transaction: ITransactionWithFeePayer & ITransactionWithSignatures & IDurableNonceTransaction;\n}\n\ninterface WaitForRecentTransactionWithBlockhashLifetimeConfirmationConfig\n extends BaseTransactionConfirmationStrategyConfig {\n getBlockHeightExceedencePromise: ReturnType<typeof createBlockHeightExceedencePromiseFactory>;\n transaction: ITransactionWithFeePayer &\n ITransactionWithSignatures &\n Readonly<{\n lifetimeConstraint: {\n lastValidBlockHeight: Slot;\n };\n }>;\n}\n\ninterface WaitForRecentTransactionWithTimeBasedLifetimeConfirmationConfig\n extends BaseTransactionConfirmationStrategyConfig {\n getTimeoutPromise: typeof getTimeoutPromise;\n signature: Signature;\n}\n\nexport async function waitForDurableNonceTransactionConfirmation(\n config: WaitForDurableNonceTransactionConfirmationConfig,\n): Promise<void> {\n await raceStrategies(\n getSignatureFromTransaction(config.transaction),\n config,\n function getSpecificStrategiesForRace({ abortSignal, commitment, getNonceInvalidationPromise, transaction }) {\n return [\n getNonceInvalidationPromise({\n abortSignal,\n commitment,\n currentNonceValue: transaction.lifetimeConstraint.nonce,\n nonceAccountAddress: transaction.instructions[0].accounts[0].address,\n }),\n ];\n },\n );\n}\n\nexport async function waitForRecentTransactionConfirmation(\n config: WaitForRecentTransactionWithBlockhashLifetimeConfirmationConfig,\n): Promise<void> {\n await raceStrategies(\n getSignatureFromTransaction(config.transaction),\n config,\n function getSpecificStrategiesForRace({\n abortSignal,\n commitment,\n getBlockHeightExceedencePromise,\n transaction,\n }) {\n return [\n getBlockHeightExceedencePromise({\n abortSignal,\n commitment,\n lastValidBlockHeight: transaction.lifetimeConstraint.lastValidBlockHeight,\n }),\n ];\n },\n );\n}\n\n/** @deprecated */\nexport async function waitForRecentTransactionConfirmationUntilTimeout(\n config: WaitForRecentTransactionWithTimeBasedLifetimeConfirmationConfig,\n): Promise<void> {\n await raceStrategies(\n config.signature,\n config,\n function getSpecificStrategiesForRace({ abortSignal, commitment, getTimeoutPromise }) {\n return [\n getTimeoutPromise({\n abortSignal,\n commitment,\n }),\n ];\n },\n );\n}\n","import { Signature } from '@solana/keys';\nimport { Commitment } from '@solana/rpc-types';\n\nimport { createRecentSignatureConfirmationPromiseFactory } from './confirmation-strategy-recent-signature';\n\nexport interface BaseTransactionConfirmationStrategyConfig {\n abortSignal?: AbortSignal;\n commitment: Commitment;\n getRecentSignatureConfirmationPromise: ReturnType<typeof createRecentSignatureConfirmationPromiseFactory>;\n}\n\ntype WithNonNullableAbortSignal<T> = Omit<T, 'abortSignal'> & Readonly<{ abortSignal: AbortSignal }>;\n\nexport async function raceStrategies<TConfig extends BaseTransactionConfirmationStrategyConfig>(\n signature: Signature,\n config: TConfig,\n getSpecificStrategiesForRace: (config: WithNonNullableAbortSignal<TConfig>) => readonly Promise<unknown>[],\n) {\n const { abortSignal: callerAbortSignal, commitment, getRecentSignatureConfirmationPromise } = config;\n callerAbortSignal?.throwIfAborted();\n const abortController = new AbortController();\n if (callerAbortSignal) {\n const handleAbort = () => {\n abortController.abort();\n };\n callerAbortSignal.addEventListener('abort', handleAbort, { signal: abortController.signal });\n }\n try {\n const specificStrategies = getSpecificStrategiesForRace({\n ...config,\n abortSignal: abortController.signal,\n });\n return await Promise.race([\n getRecentSignatureConfirmationPromise({\n abortSignal: abortController.signal,\n commitment,\n signature,\n }),\n ...specificStrategies,\n ]);\n } finally {\n abortController.abort();\n }\n}\n"]}
|