@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
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, SolanaError, SOLANA_ERROR__NONCE_INVALID, SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND } from '@solana/errors';
|
|
2
|
+
import { getBase58Decoder, getBase64Encoder } from '@solana/codecs-strings';
|
|
3
|
+
import { commitmentComparator } from '@solana/rpc-types';
|
|
4
|
+
import { getSignatureFromTransaction } from '@solana/transactions';
|
|
5
|
+
|
|
6
|
+
// src/confirmation-strategy-blockheight.ts
|
|
7
|
+
function createBlockHeightExceedencePromiseFactory({
|
|
8
|
+
rpc,
|
|
9
|
+
rpcSubscriptions
|
|
10
|
+
}) {
|
|
11
|
+
return async function getBlockHeightExceedencePromise({
|
|
12
|
+
abortSignal: callerAbortSignal,
|
|
13
|
+
commitment,
|
|
14
|
+
lastValidBlockHeight
|
|
15
|
+
}) {
|
|
16
|
+
const abortController = new AbortController();
|
|
17
|
+
const handleAbort = () => {
|
|
18
|
+
abortController.abort();
|
|
19
|
+
};
|
|
20
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
21
|
+
async function getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight() {
|
|
22
|
+
const { absoluteSlot, blockHeight } = await rpc.getEpochInfo({ commitment }).send({ abortSignal: abortController.signal });
|
|
23
|
+
return {
|
|
24
|
+
blockHeight,
|
|
25
|
+
differenceBetweenSlotHeightAndBlockHeight: absoluteSlot - blockHeight
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const [slotNotifications, { blockHeight: initialBlockHeight, differenceBetweenSlotHeightAndBlockHeight }] = await Promise.all([
|
|
30
|
+
rpcSubscriptions.slotNotifications().subscribe({ abortSignal: abortController.signal }),
|
|
31
|
+
getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight()
|
|
32
|
+
]);
|
|
33
|
+
let currentBlockHeight = initialBlockHeight;
|
|
34
|
+
if (currentBlockHeight <= lastValidBlockHeight) {
|
|
35
|
+
let lastKnownDifferenceBetweenSlotHeightAndBlockHeight = differenceBetweenSlotHeightAndBlockHeight;
|
|
36
|
+
for await (const slotNotification of slotNotifications) {
|
|
37
|
+
const { slot } = slotNotification;
|
|
38
|
+
if (slot - lastKnownDifferenceBetweenSlotHeightAndBlockHeight > lastValidBlockHeight) {
|
|
39
|
+
const {
|
|
40
|
+
blockHeight: recheckedBlockHeight,
|
|
41
|
+
differenceBetweenSlotHeightAndBlockHeight: currentDifferenceBetweenSlotHeightAndBlockHeight
|
|
42
|
+
} = await getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight();
|
|
43
|
+
currentBlockHeight = recheckedBlockHeight;
|
|
44
|
+
if (currentBlockHeight > lastValidBlockHeight) {
|
|
45
|
+
break;
|
|
46
|
+
} else {
|
|
47
|
+
lastKnownDifferenceBetweenSlotHeightAndBlockHeight = currentDifferenceBetweenSlotHeightAndBlockHeight;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new SolanaError(SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, {
|
|
53
|
+
currentBlockHeight,
|
|
54
|
+
lastValidBlockHeight
|
|
55
|
+
});
|
|
56
|
+
} finally {
|
|
57
|
+
abortController.abort();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var NONCE_VALUE_OFFSET = 4 + // version(u32)
|
|
62
|
+
4 + // state(u32)
|
|
63
|
+
32;
|
|
64
|
+
function createNonceInvalidationPromiseFactory(rpc, rpcSubscriptions) {
|
|
65
|
+
return async function getNonceInvalidationPromise({
|
|
66
|
+
abortSignal: callerAbortSignal,
|
|
67
|
+
commitment,
|
|
68
|
+
currentNonceValue: expectedNonceValue,
|
|
69
|
+
nonceAccountAddress
|
|
70
|
+
}) {
|
|
71
|
+
const abortController = new AbortController();
|
|
72
|
+
function handleAbort() {
|
|
73
|
+
abortController.abort();
|
|
74
|
+
}
|
|
75
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
76
|
+
const accountNotifications = await rpcSubscriptions.accountNotifications(nonceAccountAddress, { commitment, encoding: "base64" }).subscribe({ abortSignal: abortController.signal });
|
|
77
|
+
const base58Decoder = getBase58Decoder();
|
|
78
|
+
const base64Encoder = getBase64Encoder();
|
|
79
|
+
function getNonceFromAccountData([base64EncodedBytes]) {
|
|
80
|
+
const data = base64Encoder.encode(base64EncodedBytes);
|
|
81
|
+
const nonceValueBytes = data.slice(NONCE_VALUE_OFFSET, NONCE_VALUE_OFFSET + 32);
|
|
82
|
+
return base58Decoder.decode(nonceValueBytes);
|
|
83
|
+
}
|
|
84
|
+
const nonceAccountDidAdvancePromise = (async () => {
|
|
85
|
+
for await (const accountNotification of accountNotifications) {
|
|
86
|
+
const nonceValue = getNonceFromAccountData(accountNotification.value.data);
|
|
87
|
+
if (nonceValue !== expectedNonceValue) {
|
|
88
|
+
throw new SolanaError(SOLANA_ERROR__NONCE_INVALID, {
|
|
89
|
+
actualNonceValue: nonceValue,
|
|
90
|
+
expectedNonceValue
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
const nonceIsAlreadyInvalidPromise = (async () => {
|
|
96
|
+
const { value: nonceAccount } = await rpc.getAccountInfo(nonceAccountAddress, {
|
|
97
|
+
commitment,
|
|
98
|
+
dataSlice: { length: 32, offset: NONCE_VALUE_OFFSET },
|
|
99
|
+
encoding: "base58"
|
|
100
|
+
}).send({ abortSignal: abortController.signal });
|
|
101
|
+
if (!nonceAccount) {
|
|
102
|
+
throw new SolanaError(SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND, {
|
|
103
|
+
nonceAccountAddress
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const nonceValue = (
|
|
107
|
+
// This works because we asked for the exact slice of data representing the nonce
|
|
108
|
+
// value, and furthermore asked for it in `base58` encoding.
|
|
109
|
+
nonceAccount.data[0]
|
|
110
|
+
);
|
|
111
|
+
if (nonceValue !== expectedNonceValue) {
|
|
112
|
+
throw new SolanaError(SOLANA_ERROR__NONCE_INVALID, {
|
|
113
|
+
actualNonceValue: nonceValue,
|
|
114
|
+
expectedNonceValue
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
await new Promise(() => {
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
try {
|
|
122
|
+
return await Promise.race([nonceAccountDidAdvancePromise, nonceIsAlreadyInvalidPromise]);
|
|
123
|
+
} finally {
|
|
124
|
+
abortController.abort();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function createRecentSignatureConfirmationPromiseFactory(rpc, rpcSubscriptions) {
|
|
129
|
+
return async function getRecentSignatureConfirmationPromise({
|
|
130
|
+
abortSignal: callerAbortSignal,
|
|
131
|
+
commitment,
|
|
132
|
+
signature
|
|
133
|
+
}) {
|
|
134
|
+
const abortController = new AbortController();
|
|
135
|
+
function handleAbort() {
|
|
136
|
+
abortController.abort();
|
|
137
|
+
}
|
|
138
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
139
|
+
const signatureStatusNotifications = await rpcSubscriptions.signatureNotifications(signature, { commitment }).subscribe({ abortSignal: abortController.signal });
|
|
140
|
+
const signatureDidCommitPromise = (async () => {
|
|
141
|
+
for await (const signatureStatusNotification of signatureStatusNotifications) {
|
|
142
|
+
if (signatureStatusNotification.value.err) {
|
|
143
|
+
throw new Error(`The transaction with signature \`${signature}\` failed.`, {
|
|
144
|
+
cause: signatureStatusNotification.value.err
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
const signatureStatusLookupPromise = (async () => {
|
|
152
|
+
const { value: signatureStatusResults } = await rpc.getSignatureStatuses([signature]).send({ abortSignal: abortController.signal });
|
|
153
|
+
const signatureStatus = signatureStatusResults[0];
|
|
154
|
+
if (signatureStatus && signatureStatus.confirmationStatus && commitmentComparator(signatureStatus.confirmationStatus, commitment) >= 0) {
|
|
155
|
+
return;
|
|
156
|
+
} else {
|
|
157
|
+
await new Promise(() => {
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
try {
|
|
162
|
+
return await Promise.race([signatureDidCommitPromise, signatureStatusLookupPromise]);
|
|
163
|
+
} finally {
|
|
164
|
+
abortController.abort();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/confirmation-strategy-timeout.ts
|
|
170
|
+
async function getTimeoutPromise({ abortSignal: callerAbortSignal, commitment }) {
|
|
171
|
+
return await new Promise((_, reject) => {
|
|
172
|
+
const handleAbort = (e) => {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
const abortError = new DOMException(e.target.reason, "AbortError");
|
|
175
|
+
reject(abortError);
|
|
176
|
+
};
|
|
177
|
+
callerAbortSignal.addEventListener("abort", handleAbort);
|
|
178
|
+
const timeoutMs = commitment === "processed" ? 3e4 : 6e4;
|
|
179
|
+
const startMs = performance.now();
|
|
180
|
+
const timeoutId = (
|
|
181
|
+
// We use `setTimeout` instead of `AbortSignal.timeout()` because we want to measure
|
|
182
|
+
// elapsed time instead of active time.
|
|
183
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
const elapsedMs = performance.now() - startMs;
|
|
186
|
+
reject(new DOMException(`Timeout elapsed after ${elapsedMs} ms`, "TimeoutError"));
|
|
187
|
+
}, timeoutMs)
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/confirmation-strategy-racer.ts
|
|
193
|
+
async function raceStrategies(signature, config, getSpecificStrategiesForRace) {
|
|
194
|
+
const { abortSignal: callerAbortSignal, commitment, getRecentSignatureConfirmationPromise } = config;
|
|
195
|
+
callerAbortSignal?.throwIfAborted();
|
|
196
|
+
const abortController = new AbortController();
|
|
197
|
+
if (callerAbortSignal) {
|
|
198
|
+
const handleAbort = () => {
|
|
199
|
+
abortController.abort();
|
|
200
|
+
};
|
|
201
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const specificStrategies = getSpecificStrategiesForRace({
|
|
205
|
+
...config,
|
|
206
|
+
abortSignal: abortController.signal
|
|
207
|
+
});
|
|
208
|
+
return await Promise.race([
|
|
209
|
+
getRecentSignatureConfirmationPromise({
|
|
210
|
+
abortSignal: abortController.signal,
|
|
211
|
+
commitment,
|
|
212
|
+
signature
|
|
213
|
+
}),
|
|
214
|
+
...specificStrategies
|
|
215
|
+
]);
|
|
216
|
+
} finally {
|
|
217
|
+
abortController.abort();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/waiters.ts
|
|
222
|
+
async function waitForDurableNonceTransactionConfirmation(config) {
|
|
223
|
+
await raceStrategies(
|
|
224
|
+
getSignatureFromTransaction(config.transaction),
|
|
225
|
+
config,
|
|
226
|
+
function getSpecificStrategiesForRace({ abortSignal, commitment, getNonceInvalidationPromise, transaction }) {
|
|
227
|
+
return [
|
|
228
|
+
getNonceInvalidationPromise({
|
|
229
|
+
abortSignal,
|
|
230
|
+
commitment,
|
|
231
|
+
currentNonceValue: transaction.lifetimeConstraint.nonce,
|
|
232
|
+
nonceAccountAddress: transaction.instructions[0].accounts[0].address
|
|
233
|
+
})
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
async function waitForRecentTransactionConfirmation(config) {
|
|
239
|
+
await raceStrategies(
|
|
240
|
+
getSignatureFromTransaction(config.transaction),
|
|
241
|
+
config,
|
|
242
|
+
function getSpecificStrategiesForRace({
|
|
243
|
+
abortSignal,
|
|
244
|
+
commitment,
|
|
245
|
+
getBlockHeightExceedencePromise,
|
|
246
|
+
transaction
|
|
247
|
+
}) {
|
|
248
|
+
return [
|
|
249
|
+
getBlockHeightExceedencePromise({
|
|
250
|
+
abortSignal,
|
|
251
|
+
commitment,
|
|
252
|
+
lastValidBlockHeight: transaction.lifetimeConstraint.lastValidBlockHeight
|
|
253
|
+
})
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
async function waitForRecentTransactionConfirmationUntilTimeout(config) {
|
|
259
|
+
await raceStrategies(
|
|
260
|
+
config.signature,
|
|
261
|
+
config,
|
|
262
|
+
function getSpecificStrategiesForRace({ abortSignal, commitment, getTimeoutPromise: getTimeoutPromise2 }) {
|
|
263
|
+
return [
|
|
264
|
+
getTimeoutPromise2({
|
|
265
|
+
abortSignal,
|
|
266
|
+
commitment
|
|
267
|
+
})
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { createBlockHeightExceedencePromiseFactory, createNonceInvalidationPromiseFactory, createRecentSignatureConfirmationPromiseFactory, getTimeoutPromise, waitForDurableNonceTransactionConfirmation, waitForRecentTransactionConfirmation, waitForRecentTransactionConfirmationUntilTimeout };
|
|
274
|
+
//# sourceMappingURL=out.js.map
|
|
275
|
+
//# sourceMappingURL=index.browser.js.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":"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","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"]}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, SolanaError, SOLANA_ERROR__NONCE_INVALID, SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND } from '@solana/errors';
|
|
2
|
+
import { getBase58Decoder, getBase64Encoder } from '@solana/codecs-strings';
|
|
3
|
+
import { commitmentComparator } from '@solana/rpc-types';
|
|
4
|
+
import { getSignatureFromTransaction } from '@solana/transactions';
|
|
5
|
+
|
|
6
|
+
// src/confirmation-strategy-blockheight.ts
|
|
7
|
+
function createBlockHeightExceedencePromiseFactory({
|
|
8
|
+
rpc,
|
|
9
|
+
rpcSubscriptions
|
|
10
|
+
}) {
|
|
11
|
+
return async function getBlockHeightExceedencePromise({
|
|
12
|
+
abortSignal: callerAbortSignal,
|
|
13
|
+
commitment,
|
|
14
|
+
lastValidBlockHeight
|
|
15
|
+
}) {
|
|
16
|
+
const abortController = new AbortController();
|
|
17
|
+
const handleAbort = () => {
|
|
18
|
+
abortController.abort();
|
|
19
|
+
};
|
|
20
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
21
|
+
async function getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight() {
|
|
22
|
+
const { absoluteSlot, blockHeight } = await rpc.getEpochInfo({ commitment }).send({ abortSignal: abortController.signal });
|
|
23
|
+
return {
|
|
24
|
+
blockHeight,
|
|
25
|
+
differenceBetweenSlotHeightAndBlockHeight: absoluteSlot - blockHeight
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const [slotNotifications, { blockHeight: initialBlockHeight, differenceBetweenSlotHeightAndBlockHeight }] = await Promise.all([
|
|
30
|
+
rpcSubscriptions.slotNotifications().subscribe({ abortSignal: abortController.signal }),
|
|
31
|
+
getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight()
|
|
32
|
+
]);
|
|
33
|
+
let currentBlockHeight = initialBlockHeight;
|
|
34
|
+
if (currentBlockHeight <= lastValidBlockHeight) {
|
|
35
|
+
let lastKnownDifferenceBetweenSlotHeightAndBlockHeight = differenceBetweenSlotHeightAndBlockHeight;
|
|
36
|
+
for await (const slotNotification of slotNotifications) {
|
|
37
|
+
const { slot } = slotNotification;
|
|
38
|
+
if (slot - lastKnownDifferenceBetweenSlotHeightAndBlockHeight > lastValidBlockHeight) {
|
|
39
|
+
const {
|
|
40
|
+
blockHeight: recheckedBlockHeight,
|
|
41
|
+
differenceBetweenSlotHeightAndBlockHeight: currentDifferenceBetweenSlotHeightAndBlockHeight
|
|
42
|
+
} = await getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight();
|
|
43
|
+
currentBlockHeight = recheckedBlockHeight;
|
|
44
|
+
if (currentBlockHeight > lastValidBlockHeight) {
|
|
45
|
+
break;
|
|
46
|
+
} else {
|
|
47
|
+
lastKnownDifferenceBetweenSlotHeightAndBlockHeight = currentDifferenceBetweenSlotHeightAndBlockHeight;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new SolanaError(SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, {
|
|
53
|
+
currentBlockHeight,
|
|
54
|
+
lastValidBlockHeight
|
|
55
|
+
});
|
|
56
|
+
} finally {
|
|
57
|
+
abortController.abort();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var NONCE_VALUE_OFFSET = 4 + // version(u32)
|
|
62
|
+
4 + // state(u32)
|
|
63
|
+
32;
|
|
64
|
+
function createNonceInvalidationPromiseFactory(rpc, rpcSubscriptions) {
|
|
65
|
+
return async function getNonceInvalidationPromise({
|
|
66
|
+
abortSignal: callerAbortSignal,
|
|
67
|
+
commitment,
|
|
68
|
+
currentNonceValue: expectedNonceValue,
|
|
69
|
+
nonceAccountAddress
|
|
70
|
+
}) {
|
|
71
|
+
const abortController = new AbortController();
|
|
72
|
+
function handleAbort() {
|
|
73
|
+
abortController.abort();
|
|
74
|
+
}
|
|
75
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
76
|
+
const accountNotifications = await rpcSubscriptions.accountNotifications(nonceAccountAddress, { commitment, encoding: "base64" }).subscribe({ abortSignal: abortController.signal });
|
|
77
|
+
const base58Decoder = getBase58Decoder();
|
|
78
|
+
const base64Encoder = getBase64Encoder();
|
|
79
|
+
function getNonceFromAccountData([base64EncodedBytes]) {
|
|
80
|
+
const data = base64Encoder.encode(base64EncodedBytes);
|
|
81
|
+
const nonceValueBytes = data.slice(NONCE_VALUE_OFFSET, NONCE_VALUE_OFFSET + 32);
|
|
82
|
+
return base58Decoder.decode(nonceValueBytes);
|
|
83
|
+
}
|
|
84
|
+
const nonceAccountDidAdvancePromise = (async () => {
|
|
85
|
+
for await (const accountNotification of accountNotifications) {
|
|
86
|
+
const nonceValue = getNonceFromAccountData(accountNotification.value.data);
|
|
87
|
+
if (nonceValue !== expectedNonceValue) {
|
|
88
|
+
throw new SolanaError(SOLANA_ERROR__NONCE_INVALID, {
|
|
89
|
+
actualNonceValue: nonceValue,
|
|
90
|
+
expectedNonceValue
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
const nonceIsAlreadyInvalidPromise = (async () => {
|
|
96
|
+
const { value: nonceAccount } = await rpc.getAccountInfo(nonceAccountAddress, {
|
|
97
|
+
commitment,
|
|
98
|
+
dataSlice: { length: 32, offset: NONCE_VALUE_OFFSET },
|
|
99
|
+
encoding: "base58"
|
|
100
|
+
}).send({ abortSignal: abortController.signal });
|
|
101
|
+
if (!nonceAccount) {
|
|
102
|
+
throw new SolanaError(SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND, {
|
|
103
|
+
nonceAccountAddress
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const nonceValue = (
|
|
107
|
+
// This works because we asked for the exact slice of data representing the nonce
|
|
108
|
+
// value, and furthermore asked for it in `base58` encoding.
|
|
109
|
+
nonceAccount.data[0]
|
|
110
|
+
);
|
|
111
|
+
if (nonceValue !== expectedNonceValue) {
|
|
112
|
+
throw new SolanaError(SOLANA_ERROR__NONCE_INVALID, {
|
|
113
|
+
actualNonceValue: nonceValue,
|
|
114
|
+
expectedNonceValue
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
await new Promise(() => {
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
try {
|
|
122
|
+
return await Promise.race([nonceAccountDidAdvancePromise, nonceIsAlreadyInvalidPromise]);
|
|
123
|
+
} finally {
|
|
124
|
+
abortController.abort();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function createRecentSignatureConfirmationPromiseFactory(rpc, rpcSubscriptions) {
|
|
129
|
+
return async function getRecentSignatureConfirmationPromise({
|
|
130
|
+
abortSignal: callerAbortSignal,
|
|
131
|
+
commitment,
|
|
132
|
+
signature
|
|
133
|
+
}) {
|
|
134
|
+
const abortController = new AbortController();
|
|
135
|
+
function handleAbort() {
|
|
136
|
+
abortController.abort();
|
|
137
|
+
}
|
|
138
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
139
|
+
const signatureStatusNotifications = await rpcSubscriptions.signatureNotifications(signature, { commitment }).subscribe({ abortSignal: abortController.signal });
|
|
140
|
+
const signatureDidCommitPromise = (async () => {
|
|
141
|
+
for await (const signatureStatusNotification of signatureStatusNotifications) {
|
|
142
|
+
if (signatureStatusNotification.value.err) {
|
|
143
|
+
throw new Error(`The transaction with signature \`${signature}\` failed.`, {
|
|
144
|
+
cause: signatureStatusNotification.value.err
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
const signatureStatusLookupPromise = (async () => {
|
|
152
|
+
const { value: signatureStatusResults } = await rpc.getSignatureStatuses([signature]).send({ abortSignal: abortController.signal });
|
|
153
|
+
const signatureStatus = signatureStatusResults[0];
|
|
154
|
+
if (signatureStatus && signatureStatus.confirmationStatus && commitmentComparator(signatureStatus.confirmationStatus, commitment) >= 0) {
|
|
155
|
+
return;
|
|
156
|
+
} else {
|
|
157
|
+
await new Promise(() => {
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
try {
|
|
162
|
+
return await Promise.race([signatureDidCommitPromise, signatureStatusLookupPromise]);
|
|
163
|
+
} finally {
|
|
164
|
+
abortController.abort();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/confirmation-strategy-timeout.ts
|
|
170
|
+
async function getTimeoutPromise({ abortSignal: callerAbortSignal, commitment }) {
|
|
171
|
+
return await new Promise((_, reject) => {
|
|
172
|
+
const handleAbort = (e) => {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
const abortError = new DOMException(e.target.reason, "AbortError");
|
|
175
|
+
reject(abortError);
|
|
176
|
+
};
|
|
177
|
+
callerAbortSignal.addEventListener("abort", handleAbort);
|
|
178
|
+
const timeoutMs = commitment === "processed" ? 3e4 : 6e4;
|
|
179
|
+
const startMs = performance.now();
|
|
180
|
+
const timeoutId = (
|
|
181
|
+
// We use `setTimeout` instead of `AbortSignal.timeout()` because we want to measure
|
|
182
|
+
// elapsed time instead of active time.
|
|
183
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
const elapsedMs = performance.now() - startMs;
|
|
186
|
+
reject(new DOMException(`Timeout elapsed after ${elapsedMs} ms`, "TimeoutError"));
|
|
187
|
+
}, timeoutMs)
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/confirmation-strategy-racer.ts
|
|
193
|
+
async function raceStrategies(signature, config, getSpecificStrategiesForRace) {
|
|
194
|
+
const { abortSignal: callerAbortSignal, commitment, getRecentSignatureConfirmationPromise } = config;
|
|
195
|
+
callerAbortSignal?.throwIfAborted();
|
|
196
|
+
const abortController = new AbortController();
|
|
197
|
+
if (callerAbortSignal) {
|
|
198
|
+
const handleAbort = () => {
|
|
199
|
+
abortController.abort();
|
|
200
|
+
};
|
|
201
|
+
callerAbortSignal.addEventListener("abort", handleAbort, { signal: abortController.signal });
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const specificStrategies = getSpecificStrategiesForRace({
|
|
205
|
+
...config,
|
|
206
|
+
abortSignal: abortController.signal
|
|
207
|
+
});
|
|
208
|
+
return await Promise.race([
|
|
209
|
+
getRecentSignatureConfirmationPromise({
|
|
210
|
+
abortSignal: abortController.signal,
|
|
211
|
+
commitment,
|
|
212
|
+
signature
|
|
213
|
+
}),
|
|
214
|
+
...specificStrategies
|
|
215
|
+
]);
|
|
216
|
+
} finally {
|
|
217
|
+
abortController.abort();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/waiters.ts
|
|
222
|
+
async function waitForDurableNonceTransactionConfirmation(config) {
|
|
223
|
+
await raceStrategies(
|
|
224
|
+
getSignatureFromTransaction(config.transaction),
|
|
225
|
+
config,
|
|
226
|
+
function getSpecificStrategiesForRace({ abortSignal, commitment, getNonceInvalidationPromise, transaction }) {
|
|
227
|
+
return [
|
|
228
|
+
getNonceInvalidationPromise({
|
|
229
|
+
abortSignal,
|
|
230
|
+
commitment,
|
|
231
|
+
currentNonceValue: transaction.lifetimeConstraint.nonce,
|
|
232
|
+
nonceAccountAddress: transaction.instructions[0].accounts[0].address
|
|
233
|
+
})
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
async function waitForRecentTransactionConfirmation(config) {
|
|
239
|
+
await raceStrategies(
|
|
240
|
+
getSignatureFromTransaction(config.transaction),
|
|
241
|
+
config,
|
|
242
|
+
function getSpecificStrategiesForRace({
|
|
243
|
+
abortSignal,
|
|
244
|
+
commitment,
|
|
245
|
+
getBlockHeightExceedencePromise,
|
|
246
|
+
transaction
|
|
247
|
+
}) {
|
|
248
|
+
return [
|
|
249
|
+
getBlockHeightExceedencePromise({
|
|
250
|
+
abortSignal,
|
|
251
|
+
commitment,
|
|
252
|
+
lastValidBlockHeight: transaction.lifetimeConstraint.lastValidBlockHeight
|
|
253
|
+
})
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
async function waitForRecentTransactionConfirmationUntilTimeout(config) {
|
|
259
|
+
await raceStrategies(
|
|
260
|
+
config.signature,
|
|
261
|
+
config,
|
|
262
|
+
function getSpecificStrategiesForRace({ abortSignal, commitment, getTimeoutPromise: getTimeoutPromise2 }) {
|
|
263
|
+
return [
|
|
264
|
+
getTimeoutPromise2({
|
|
265
|
+
abortSignal,
|
|
266
|
+
commitment
|
|
267
|
+
})
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { createBlockHeightExceedencePromiseFactory, createNonceInvalidationPromiseFactory, createRecentSignatureConfirmationPromiseFactory, getTimeoutPromise, waitForDurableNonceTransactionConfirmation, waitForRecentTransactionConfirmation, waitForRecentTransactionConfirmationUntilTimeout };
|
|
274
|
+
//# sourceMappingURL=out.js.map
|
|
275
|
+
//# sourceMappingURL=index.native.js.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":"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","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"]}
|