@solana/kit 6.3.1 → 6.3.2-canary-20260313143218
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/dist/index.development.js +6 -6
- package/dist/index.development.js.map +1 -1
- package/dist/index.production.min.js +32 -32
- package/package.json +27 -26
- package/src/airdrop-internal.ts +38 -0
- package/src/airdrop.ts +80 -0
- package/src/decompile-transaction-message-fetching-lookup-tables.ts +50 -0
- package/src/fetch-lookup-tables.ts +46 -0
- package/src/get-minimum-balance-for-rent-exemption.ts +26 -0
- package/src/index.ts +43 -0
- package/src/program-client-core.ts +1 -0
- package/src/send-and-confirm-durable-nonce-transaction.ts +172 -0
- package/src/send-and-confirm-transaction.ts +95 -0
- package/src/send-transaction-internal.ts +134 -0
- package/src/send-transaction-without-confirming.ts +52 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solana/kit",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.2-canary-20260313143218",
|
|
4
4
|
"description": "Solana Javascript API",
|
|
5
5
|
"homepage": "https://www.solanakit.com",
|
|
6
6
|
"exports": {
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"types": "./dist/types/index.d.ts",
|
|
61
61
|
"type": "commonjs",
|
|
62
62
|
"files": [
|
|
63
|
-
"./dist/"
|
|
63
|
+
"./dist/",
|
|
64
|
+
"./src/"
|
|
64
65
|
],
|
|
65
66
|
"sideEffects": false,
|
|
66
67
|
"keywords": [
|
|
@@ -82,30 +83,30 @@
|
|
|
82
83
|
"maintained node versions"
|
|
83
84
|
],
|
|
84
85
|
"dependencies": {
|
|
85
|
-
"@solana/accounts": "6.3.
|
|
86
|
-
"@solana/addresses": "6.3.
|
|
87
|
-
"@solana/
|
|
88
|
-
"@solana/
|
|
89
|
-
"@solana/
|
|
90
|
-
"@solana/
|
|
91
|
-
"@solana/
|
|
92
|
-
"@solana/
|
|
93
|
-
"@solana/
|
|
94
|
-
"@solana/plugin-interfaces": "6.3.
|
|
95
|
-
"@solana/
|
|
96
|
-
"@solana/program-client-core": "6.3.
|
|
97
|
-
"@solana/programs": "6.3.
|
|
98
|
-
"@solana/rpc": "6.3.
|
|
99
|
-
"@solana/rpc-parsed-types": "6.3.
|
|
100
|
-
"@solana/rpc-spec-types": "6.3.
|
|
101
|
-
"@solana/rpc-subscriptions": "6.3.
|
|
102
|
-
"@solana/rpc-
|
|
103
|
-
"@solana/
|
|
104
|
-
"@solana/
|
|
105
|
-
"@solana/
|
|
106
|
-
"@solana/transaction-confirmation": "6.3.
|
|
107
|
-
"@solana/transaction-messages": "6.3.
|
|
108
|
-
"@solana/transactions": "6.3.
|
|
86
|
+
"@solana/accounts": "6.3.2-canary-20260313143218",
|
|
87
|
+
"@solana/addresses": "6.3.2-canary-20260313143218",
|
|
88
|
+
"@solana/errors": "6.3.2-canary-20260313143218",
|
|
89
|
+
"@solana/functional": "6.3.2-canary-20260313143218",
|
|
90
|
+
"@solana/codecs": "6.3.2-canary-20260313143218",
|
|
91
|
+
"@solana/instructions": "6.3.2-canary-20260313143218",
|
|
92
|
+
"@solana/instruction-plans": "6.3.2-canary-20260313143218",
|
|
93
|
+
"@solana/offchain-messages": "6.3.2-canary-20260313143218",
|
|
94
|
+
"@solana/plugin-core": "6.3.2-canary-20260313143218",
|
|
95
|
+
"@solana/plugin-interfaces": "6.3.2-canary-20260313143218",
|
|
96
|
+
"@solana/keys": "6.3.2-canary-20260313143218",
|
|
97
|
+
"@solana/program-client-core": "6.3.2-canary-20260313143218",
|
|
98
|
+
"@solana/programs": "6.3.2-canary-20260313143218",
|
|
99
|
+
"@solana/rpc-api": "6.3.2-canary-20260313143218",
|
|
100
|
+
"@solana/rpc-parsed-types": "6.3.2-canary-20260313143218",
|
|
101
|
+
"@solana/rpc-spec-types": "6.3.2-canary-20260313143218",
|
|
102
|
+
"@solana/rpc-subscriptions": "6.3.2-canary-20260313143218",
|
|
103
|
+
"@solana/rpc-types": "6.3.2-canary-20260313143218",
|
|
104
|
+
"@solana/signers": "6.3.2-canary-20260313143218",
|
|
105
|
+
"@solana/sysvars": "6.3.2-canary-20260313143218",
|
|
106
|
+
"@solana/rpc": "6.3.2-canary-20260313143218",
|
|
107
|
+
"@solana/transaction-confirmation": "6.3.2-canary-20260313143218",
|
|
108
|
+
"@solana/transaction-messages": "6.3.2-canary-20260313143218",
|
|
109
|
+
"@solana/transactions": "6.3.2-canary-20260313143218"
|
|
109
110
|
},
|
|
110
111
|
"peerDependencies": {
|
|
111
112
|
"typescript": "^5.0.0"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Address } from '@solana/addresses';
|
|
2
|
+
import type { Signature } from '@solana/keys';
|
|
3
|
+
import type { RequestAirdropApi, Rpc } from '@solana/rpc';
|
|
4
|
+
import type { Commitment, Lamports } from '@solana/rpc-types';
|
|
5
|
+
import { waitForRecentTransactionConfirmationUntilTimeout } from '@solana/transaction-confirmation';
|
|
6
|
+
|
|
7
|
+
type RequestAndConfirmAirdropConfig = Readonly<{
|
|
8
|
+
abortSignal?: AbortSignal;
|
|
9
|
+
commitment: Commitment;
|
|
10
|
+
confirmSignatureOnlyTransaction: (
|
|
11
|
+
config: Omit<
|
|
12
|
+
Parameters<typeof waitForRecentTransactionConfirmationUntilTimeout>[0],
|
|
13
|
+
'getRecentSignatureConfirmationPromise' | 'getTimeoutPromise'
|
|
14
|
+
>,
|
|
15
|
+
) => Promise<void>;
|
|
16
|
+
lamports: Lamports;
|
|
17
|
+
recipientAddress: Address;
|
|
18
|
+
rpc: Rpc<RequestAirdropApi>;
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
export async function requestAndConfirmAirdrop_INTERNAL_ONLY_DO_NOT_EXPORT({
|
|
22
|
+
abortSignal,
|
|
23
|
+
commitment,
|
|
24
|
+
confirmSignatureOnlyTransaction,
|
|
25
|
+
lamports,
|
|
26
|
+
recipientAddress,
|
|
27
|
+
rpc,
|
|
28
|
+
}: RequestAndConfirmAirdropConfig): Promise<Signature> {
|
|
29
|
+
const airdropTransactionSignature = await rpc
|
|
30
|
+
.requestAirdrop(recipientAddress, lamports, { commitment })
|
|
31
|
+
.send({ abortSignal });
|
|
32
|
+
await confirmSignatureOnlyTransaction({
|
|
33
|
+
abortSignal,
|
|
34
|
+
commitment,
|
|
35
|
+
signature: airdropTransactionSignature,
|
|
36
|
+
});
|
|
37
|
+
return airdropTransactionSignature;
|
|
38
|
+
}
|
package/src/airdrop.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Signature } from '@solana/keys';
|
|
2
|
+
import type { GetSignatureStatusesApi, RequestAirdropApi, Rpc } from '@solana/rpc';
|
|
3
|
+
import type { RpcSubscriptions, SignatureNotificationsApi } from '@solana/rpc-subscriptions';
|
|
4
|
+
import {
|
|
5
|
+
createRecentSignatureConfirmationPromiseFactory,
|
|
6
|
+
getTimeoutPromise,
|
|
7
|
+
waitForRecentTransactionConfirmationUntilTimeout,
|
|
8
|
+
} from '@solana/transaction-confirmation';
|
|
9
|
+
|
|
10
|
+
import { requestAndConfirmAirdrop_INTERNAL_ONLY_DO_NOT_EXPORT } from './airdrop-internal';
|
|
11
|
+
|
|
12
|
+
type AirdropFunction = (
|
|
13
|
+
config: Omit<
|
|
14
|
+
Parameters<typeof requestAndConfirmAirdrop_INTERNAL_ONLY_DO_NOT_EXPORT>[0],
|
|
15
|
+
'confirmSignatureOnlyTransaction' | 'rpc'
|
|
16
|
+
>,
|
|
17
|
+
) => Promise<Signature>;
|
|
18
|
+
|
|
19
|
+
type AirdropFactoryConfig<TCluster> = {
|
|
20
|
+
/** An object that supports the {@link GetSignatureStatusesApi} and the {@link RequestAirdropApi} of the Solana RPC API */
|
|
21
|
+
rpc: Rpc<GetSignatureStatusesApi & RequestAirdropApi> & { '~cluster'?: TCluster };
|
|
22
|
+
/** An object that supports the {@link SignatureNotificationsApi} of the Solana RPC Subscriptions API */
|
|
23
|
+
rpcSubscriptions: RpcSubscriptions<SignatureNotificationsApi> & { '~cluster'?: TCluster };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns a function that you can call to airdrop a certain amount of {@link Lamports} to a Solana
|
|
28
|
+
* address.
|
|
29
|
+
*
|
|
30
|
+
* > [!NOTE] This only works on test clusters.
|
|
31
|
+
*
|
|
32
|
+
* @param config
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { address, airdropFactory, createSolanaRpc, createSolanaRpcSubscriptions, devnet, lamports } from '@solana/kit';
|
|
37
|
+
*
|
|
38
|
+
* const rpc = createSolanaRpc(devnet('http://127.0.0.1:8899'));
|
|
39
|
+
* const rpcSubscriptions = createSolanaRpcSubscriptions(devnet('ws://127.0.0.1:8900'));
|
|
40
|
+
*
|
|
41
|
+
* const airdrop = airdropFactory({ rpc, rpcSubscriptions });
|
|
42
|
+
*
|
|
43
|
+
* await airdrop({
|
|
44
|
+
* commitment: 'confirmed',
|
|
45
|
+
* recipientAddress: address('FnHyam9w4NZoWR6mKN1CuGBritdsEWZQa4Z4oawLZGxa'),
|
|
46
|
+
* lamports: lamports(10_000_000n),
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function airdropFactory({ rpc, rpcSubscriptions }: AirdropFactoryConfig<'devnet'>): AirdropFunction;
|
|
51
|
+
export function airdropFactory({ rpc, rpcSubscriptions }: AirdropFactoryConfig<'mainnet'>): AirdropFunction;
|
|
52
|
+
export function airdropFactory({ rpc, rpcSubscriptions }: AirdropFactoryConfig<'testnet'>): AirdropFunction;
|
|
53
|
+
export function airdropFactory<TCluster extends 'devnet' | 'mainnet' | 'testnet' | void = void>({
|
|
54
|
+
rpc,
|
|
55
|
+
rpcSubscriptions,
|
|
56
|
+
}: AirdropFactoryConfig<TCluster>): AirdropFunction {
|
|
57
|
+
const getRecentSignatureConfirmationPromise = createRecentSignatureConfirmationPromiseFactory({
|
|
58
|
+
rpc,
|
|
59
|
+
rpcSubscriptions,
|
|
60
|
+
} as Parameters<typeof createRecentSignatureConfirmationPromiseFactory>[0]);
|
|
61
|
+
async function confirmSignatureOnlyTransaction(
|
|
62
|
+
config: Omit<
|
|
63
|
+
Parameters<typeof waitForRecentTransactionConfirmationUntilTimeout>[0],
|
|
64
|
+
'getRecentSignatureConfirmationPromise' | 'getTimeoutPromise'
|
|
65
|
+
>,
|
|
66
|
+
) {
|
|
67
|
+
await waitForRecentTransactionConfirmationUntilTimeout({
|
|
68
|
+
...config,
|
|
69
|
+
getRecentSignatureConfirmationPromise,
|
|
70
|
+
getTimeoutPromise,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return async function airdrop(config) {
|
|
74
|
+
return await requestAndConfirmAirdrop_INTERNAL_ONLY_DO_NOT_EXPORT({
|
|
75
|
+
...config,
|
|
76
|
+
confirmSignatureOnlyTransaction,
|
|
77
|
+
rpc,
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type FetchAccountsConfig } from '@solana/accounts';
|
|
2
|
+
import type { GetMultipleAccountsApi, Rpc } from '@solana/rpc';
|
|
3
|
+
import {
|
|
4
|
+
CompiledTransactionMessage,
|
|
5
|
+
CompiledTransactionMessageWithLifetime,
|
|
6
|
+
decompileTransactionMessage,
|
|
7
|
+
TransactionMessage,
|
|
8
|
+
TransactionMessageWithFeePayer,
|
|
9
|
+
TransactionMessageWithLifetime,
|
|
10
|
+
} from '@solana/transaction-messages';
|
|
11
|
+
|
|
12
|
+
import { fetchAddressesForLookupTables } from './fetch-lookup-tables';
|
|
13
|
+
|
|
14
|
+
type DecompileTransactionMessageFetchingLookupTablesConfig = FetchAccountsConfig & {
|
|
15
|
+
lastValidBlockHeight?: bigint;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns a {@link TransactionMessage} from a {@link CompiledTransactionMessage}. If any of the
|
|
20
|
+
* accounts in the compiled message require an address lookup table to find their address, this
|
|
21
|
+
* function will use the supplied RPC instance to fetch the contents of the address lookup table
|
|
22
|
+
* from the network.
|
|
23
|
+
*
|
|
24
|
+
* @param rpc An object that supports the {@link GetMultipleAccountsApi} of the Solana RPC API
|
|
25
|
+
* @param config
|
|
26
|
+
*/
|
|
27
|
+
export async function decompileTransactionMessageFetchingLookupTables(
|
|
28
|
+
compiledTransactionMessage: CompiledTransactionMessage & CompiledTransactionMessageWithLifetime,
|
|
29
|
+
rpc: Rpc<GetMultipleAccountsApi>,
|
|
30
|
+
config?: DecompileTransactionMessageFetchingLookupTablesConfig,
|
|
31
|
+
): Promise<TransactionMessage & TransactionMessageWithFeePayer & TransactionMessageWithLifetime> {
|
|
32
|
+
const lookupTables =
|
|
33
|
+
'addressTableLookups' in compiledTransactionMessage &&
|
|
34
|
+
compiledTransactionMessage.addressTableLookups !== undefined &&
|
|
35
|
+
compiledTransactionMessage.addressTableLookups.length > 0
|
|
36
|
+
? compiledTransactionMessage.addressTableLookups
|
|
37
|
+
: [];
|
|
38
|
+
const lookupTableAddresses = lookupTables.map(l => l.lookupTableAddress);
|
|
39
|
+
|
|
40
|
+
const { lastValidBlockHeight, ...fetchAccountsConfig } = config ?? {};
|
|
41
|
+
const addressesByLookupTableAddress =
|
|
42
|
+
lookupTableAddresses.length > 0
|
|
43
|
+
? await fetchAddressesForLookupTables(lookupTableAddresses, rpc, fetchAccountsConfig)
|
|
44
|
+
: {};
|
|
45
|
+
|
|
46
|
+
return decompileTransactionMessage(compiledTransactionMessage, {
|
|
47
|
+
addressesByLookupTableAddress,
|
|
48
|
+
lastValidBlockHeight,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertAccountsDecoded,
|
|
3
|
+
assertAccountsExist,
|
|
4
|
+
type FetchAccountsConfig,
|
|
5
|
+
fetchJsonParsedAccounts,
|
|
6
|
+
} from '@solana/accounts';
|
|
7
|
+
import type { Address } from '@solana/addresses';
|
|
8
|
+
import type { GetMultipleAccountsApi, Rpc } from '@solana/rpc';
|
|
9
|
+
import { type AddressesByLookupTableAddress } from '@solana/transaction-messages';
|
|
10
|
+
|
|
11
|
+
type FetchedAddressLookup = {
|
|
12
|
+
addresses: Address[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Given a list of addresses belonging to address lookup tables, returns a map of lookup table
|
|
17
|
+
* addresses to an ordered array of the addresses they contain.
|
|
18
|
+
*
|
|
19
|
+
* @param rpc An object that supports the {@link GetMultipleAccountsApi} of the Solana RPC API
|
|
20
|
+
* @param config
|
|
21
|
+
*/
|
|
22
|
+
export async function fetchAddressesForLookupTables(
|
|
23
|
+
lookupTableAddresses: Address[],
|
|
24
|
+
rpc: Rpc<GetMultipleAccountsApi>,
|
|
25
|
+
config?: FetchAccountsConfig,
|
|
26
|
+
): Promise<AddressesByLookupTableAddress> {
|
|
27
|
+
if (lookupTableAddresses.length === 0) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fetchedLookupTables = await fetchJsonParsedAccounts<FetchedAddressLookup[]>(
|
|
32
|
+
rpc,
|
|
33
|
+
lookupTableAddresses,
|
|
34
|
+
config,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
assertAccountsDecoded(fetchedLookupTables);
|
|
38
|
+
assertAccountsExist(fetchedLookupTables);
|
|
39
|
+
|
|
40
|
+
return fetchedLookupTables.reduce<AddressesByLookupTableAddress>((acc, lookup) => {
|
|
41
|
+
return {
|
|
42
|
+
...acc,
|
|
43
|
+
[lookup.address]: lookup.data.addresses,
|
|
44
|
+
};
|
|
45
|
+
}, {});
|
|
46
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Lamports } from '@solana/rpc-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculates the minimum {@link Lamports | lamports} required to make an account rent exempt for a
|
|
5
|
+
* given data size, without performing an RPC call.
|
|
6
|
+
*
|
|
7
|
+
* Values are sourced from the on-chain rent parameters in the Solana runtime:
|
|
8
|
+
* https://github.com/anza-xyz/solana-sdk/blob/c07f692e41d757057c8700211a9300cdcd6d33b1/rent/src/lib.rs#L93-L97
|
|
9
|
+
*
|
|
10
|
+
* Note that this logic may change, or be incorrect depending on the cluster you are connected to.
|
|
11
|
+
* You can always use the RPC method `getMinimumBalanceForRentExemption` to get the current value.
|
|
12
|
+
*
|
|
13
|
+
* @param space The number of bytes of account data.
|
|
14
|
+
*/
|
|
15
|
+
export function getMinimumBalanceForRentExemption(space: bigint): Lamports {
|
|
16
|
+
const RENT = {
|
|
17
|
+
ACCOUNT_STORAGE_OVERHEAD: 128n,
|
|
18
|
+
DEFAULT_EXEMPTION_THRESHOLD: 2n,
|
|
19
|
+
DEFAULT_LAMPORTS_PER_BYTE_YEAR: 3_480n,
|
|
20
|
+
} as const;
|
|
21
|
+
const requiredLamports =
|
|
22
|
+
(RENT.ACCOUNT_STORAGE_OVERHEAD + space) *
|
|
23
|
+
RENT.DEFAULT_LAMPORTS_PER_BYTE_YEAR *
|
|
24
|
+
RENT.DEFAULT_EXEMPTION_THRESHOLD;
|
|
25
|
+
return requiredLamports as Lamports;
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the JavaScript SDK for building Solana apps for Node, web, and React Native.
|
|
3
|
+
*
|
|
4
|
+
* In addition to re-exporting functions from packages in the `@solana/*` namespace, this package
|
|
5
|
+
* offers additional helpers for building Solana applications, with sensible defaults.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export * from '@solana/accounts';
|
|
10
|
+
export * from '@solana/addresses';
|
|
11
|
+
export * from '@solana/codecs';
|
|
12
|
+
export * from '@solana/errors';
|
|
13
|
+
export * from '@solana/functional';
|
|
14
|
+
export * from '@solana/instructions';
|
|
15
|
+
export * from '@solana/instruction-plans';
|
|
16
|
+
export * from '@solana/keys';
|
|
17
|
+
export * from '@solana/offchain-messages';
|
|
18
|
+
export * from '@solana/plugin-core';
|
|
19
|
+
export type * from '@solana/plugin-interfaces';
|
|
20
|
+
export * from '@solana/programs';
|
|
21
|
+
export * from '@solana/rpc';
|
|
22
|
+
export * from '@solana/rpc-parsed-types';
|
|
23
|
+
export * from '@solana/rpc-subscriptions';
|
|
24
|
+
export * from '@solana/rpc-types';
|
|
25
|
+
export * from '@solana/signers';
|
|
26
|
+
export * from '@solana/transaction-messages';
|
|
27
|
+
export * from '@solana/transactions';
|
|
28
|
+
export * from './airdrop';
|
|
29
|
+
export * from './decompile-transaction-message-fetching-lookup-tables';
|
|
30
|
+
export * from './fetch-lookup-tables';
|
|
31
|
+
export * from './get-minimum-balance-for-rent-exemption';
|
|
32
|
+
export * from './send-and-confirm-durable-nonce-transaction';
|
|
33
|
+
export * from './send-and-confirm-transaction';
|
|
34
|
+
export * from './send-transaction-without-confirming';
|
|
35
|
+
|
|
36
|
+
export type {
|
|
37
|
+
RpcRequest,
|
|
38
|
+
RpcRequestTransformer,
|
|
39
|
+
RpcResponse,
|
|
40
|
+
RpcResponseData,
|
|
41
|
+
RpcResponseTransformer,
|
|
42
|
+
} from '@solana/rpc-spec-types';
|
|
43
|
+
export { createRpcMessage } from '@solana/rpc-spec-types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@solana/program-client-core';
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { getSolanaErrorFromTransactionError, isSolanaError, SOLANA_ERROR__INVALID_NONCE } from '@solana/errors';
|
|
2
|
+
import { Signature } from '@solana/keys';
|
|
3
|
+
import type { GetAccountInfoApi, GetSignatureStatusesApi, Rpc, SendTransactionApi } from '@solana/rpc';
|
|
4
|
+
import type { AccountNotificationsApi, RpcSubscriptions, SignatureNotificationsApi } from '@solana/rpc-subscriptions';
|
|
5
|
+
import { commitmentComparator } from '@solana/rpc-types';
|
|
6
|
+
import {
|
|
7
|
+
createNonceInvalidationPromiseFactory,
|
|
8
|
+
createRecentSignatureConfirmationPromiseFactory,
|
|
9
|
+
waitForDurableNonceTransactionConfirmation,
|
|
10
|
+
} from '@solana/transaction-confirmation';
|
|
11
|
+
import {
|
|
12
|
+
getSignatureFromTransaction,
|
|
13
|
+
SendableTransaction,
|
|
14
|
+
Transaction,
|
|
15
|
+
TransactionWithDurableNonceLifetime,
|
|
16
|
+
} from '@solana/transactions';
|
|
17
|
+
|
|
18
|
+
import { sendAndConfirmDurableNonceTransaction_INTERNAL_ONLY_DO_NOT_EXPORT } from './send-transaction-internal';
|
|
19
|
+
|
|
20
|
+
type SendAndConfirmDurableNonceTransactionFunction = (
|
|
21
|
+
transaction: SendableTransaction & Transaction & TransactionWithDurableNonceLifetime,
|
|
22
|
+
config: Omit<
|
|
23
|
+
Parameters<typeof sendAndConfirmDurableNonceTransaction_INTERNAL_ONLY_DO_NOT_EXPORT>[0],
|
|
24
|
+
'confirmDurableNonceTransaction' | 'rpc' | 'transaction'
|
|
25
|
+
>,
|
|
26
|
+
) => Promise<void>;
|
|
27
|
+
|
|
28
|
+
type SendAndConfirmDurableNonceTransactionFactoryConfig<TCluster> = {
|
|
29
|
+
/** An object that supports the {@link GetSignatureStatusesApi} and the {@link SendTransactionApi} of the Solana RPC API */
|
|
30
|
+
rpc: Rpc<GetAccountInfoApi & GetSignatureStatusesApi & SendTransactionApi> & { '~cluster'?: TCluster };
|
|
31
|
+
/** An object that supports the {@link AccountNotificationsApi} and the {@link SignatureNotificationsApi} of the Solana RPC Subscriptions API */
|
|
32
|
+
rpcSubscriptions: RpcSubscriptions<AccountNotificationsApi & SignatureNotificationsApi> & { '~cluster'?: TCluster };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns a function that you can call to send a nonce-based transaction to the network and to wait
|
|
37
|
+
* until it has been confirmed.
|
|
38
|
+
*
|
|
39
|
+
* @param config
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import {
|
|
44
|
+
* isSolanaError,
|
|
45
|
+
* sendAndConfirmDurableNonceTransactionFactory,
|
|
46
|
+
* SOLANA_ERROR__INVALID_NONCE,
|
|
47
|
+
* SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND,
|
|
48
|
+
* } from '@solana/kit';
|
|
49
|
+
*
|
|
50
|
+
* const sendAndConfirmNonceTransaction = sendAndConfirmDurableNonceTransactionFactory({ rpc, rpcSubscriptions });
|
|
51
|
+
*
|
|
52
|
+
* try {
|
|
53
|
+
* await sendAndConfirmNonceTransaction(transaction, { commitment: 'confirmed' });
|
|
54
|
+
* } catch (e) {
|
|
55
|
+
* if (isSolanaError(e, SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND)) {
|
|
56
|
+
* console.error(
|
|
57
|
+
* 'The lifetime specified by this transaction refers to a nonce account ' +
|
|
58
|
+
* `\`${e.context.nonceAccountAddress}\` that does not exist`,
|
|
59
|
+
* );
|
|
60
|
+
* } else if (isSolanaError(e, SOLANA_ERROR__INVALID_NONCE)) {
|
|
61
|
+
* console.error('This transaction depends on a nonce that is no longer valid');
|
|
62
|
+
* } else {
|
|
63
|
+
* throw e;
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function sendAndConfirmDurableNonceTransactionFactory({
|
|
69
|
+
rpc,
|
|
70
|
+
rpcSubscriptions,
|
|
71
|
+
}: SendAndConfirmDurableNonceTransactionFactoryConfig<'devnet'>): SendAndConfirmDurableNonceTransactionFunction;
|
|
72
|
+
export function sendAndConfirmDurableNonceTransactionFactory({
|
|
73
|
+
rpc,
|
|
74
|
+
rpcSubscriptions,
|
|
75
|
+
}: SendAndConfirmDurableNonceTransactionFactoryConfig<'testnet'>): SendAndConfirmDurableNonceTransactionFunction;
|
|
76
|
+
export function sendAndConfirmDurableNonceTransactionFactory({
|
|
77
|
+
rpc,
|
|
78
|
+
rpcSubscriptions,
|
|
79
|
+
}: SendAndConfirmDurableNonceTransactionFactoryConfig<'mainnet'>): SendAndConfirmDurableNonceTransactionFunction;
|
|
80
|
+
export function sendAndConfirmDurableNonceTransactionFactory<
|
|
81
|
+
TCluster extends 'devnet' | 'mainnet' | 'testnet' | void = void,
|
|
82
|
+
>({
|
|
83
|
+
rpc,
|
|
84
|
+
rpcSubscriptions,
|
|
85
|
+
}: SendAndConfirmDurableNonceTransactionFactoryConfig<TCluster>): SendAndConfirmDurableNonceTransactionFunction {
|
|
86
|
+
const getNonceInvalidationPromise = createNonceInvalidationPromiseFactory({ rpc, rpcSubscriptions } as Parameters<
|
|
87
|
+
typeof createNonceInvalidationPromiseFactory
|
|
88
|
+
>[0]);
|
|
89
|
+
const getRecentSignatureConfirmationPromise = createRecentSignatureConfirmationPromiseFactory({
|
|
90
|
+
rpc,
|
|
91
|
+
rpcSubscriptions,
|
|
92
|
+
} as Parameters<typeof createRecentSignatureConfirmationPromiseFactory>[0]);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates a wrapped version of getNonceInvalidationPromise that handles the race condition
|
|
96
|
+
* where the nonce account update notification arrives before the signature confirmation.
|
|
97
|
+
*
|
|
98
|
+
* When the nonce changes, we check if our transaction actually landed on-chain.
|
|
99
|
+
* If it did, we don't throw - letting the signature confirmation promise continue.
|
|
100
|
+
*/
|
|
101
|
+
function createNonceInvalidationPromiseHandlingRaceCondition(
|
|
102
|
+
signature: Signature,
|
|
103
|
+
): typeof getNonceInvalidationPromise {
|
|
104
|
+
return async function wrappedGetNonceInvalidationPromise(config) {
|
|
105
|
+
try {
|
|
106
|
+
return await getNonceInvalidationPromise(config);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// If nonce became invalid, check if our transaction actually landed
|
|
109
|
+
if (isSolanaError(e, SOLANA_ERROR__INVALID_NONCE)) {
|
|
110
|
+
let status;
|
|
111
|
+
try {
|
|
112
|
+
const { value: statuses } = await rpc
|
|
113
|
+
.getSignatureStatuses([signature])
|
|
114
|
+
.send({ abortSignal: config.abortSignal });
|
|
115
|
+
status = statuses[0];
|
|
116
|
+
} catch {
|
|
117
|
+
// RPC failed - propagate the original nonce error
|
|
118
|
+
throw e;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (status === null || status === undefined) {
|
|
122
|
+
// Transaction doesn't exist - nonce was truly invalid
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check if status meets required commitment
|
|
127
|
+
if (
|
|
128
|
+
status.confirmationStatus !== null &&
|
|
129
|
+
commitmentComparator(status.confirmationStatus, config.commitment) >= 0
|
|
130
|
+
) {
|
|
131
|
+
// Transaction failed on-chain, throw the error from the transaction
|
|
132
|
+
if (status.err !== null) {
|
|
133
|
+
throw getSolanaErrorFromTransactionError(status.err);
|
|
134
|
+
}
|
|
135
|
+
// Transaction succeeded, resolve the promise successfully
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Commitment not met yet - return a never-resolving promise
|
|
140
|
+
// This lets the signature confirmation promise continue
|
|
141
|
+
return await new Promise(() => {});
|
|
142
|
+
}
|
|
143
|
+
throw e;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function confirmDurableNonceTransaction(
|
|
149
|
+
config: Omit<
|
|
150
|
+
Parameters<typeof waitForDurableNonceTransactionConfirmation>[0],
|
|
151
|
+
'getNonceInvalidationPromise' | 'getRecentSignatureConfirmationPromise'
|
|
152
|
+
>,
|
|
153
|
+
) {
|
|
154
|
+
const wrappedGetNonceInvalidationPromise = createNonceInvalidationPromiseHandlingRaceCondition(
|
|
155
|
+
getSignatureFromTransaction(config.transaction),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
await waitForDurableNonceTransactionConfirmation({
|
|
159
|
+
...config,
|
|
160
|
+
getNonceInvalidationPromise: wrappedGetNonceInvalidationPromise,
|
|
161
|
+
getRecentSignatureConfirmationPromise,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return async function sendAndConfirmDurableNonceTransaction(transaction, config) {
|
|
165
|
+
await sendAndConfirmDurableNonceTransaction_INTERNAL_ONLY_DO_NOT_EXPORT({
|
|
166
|
+
...config,
|
|
167
|
+
confirmDurableNonceTransaction,
|
|
168
|
+
rpc,
|
|
169
|
+
transaction,
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { GetEpochInfoApi, GetSignatureStatusesApi, Rpc, SendTransactionApi } from '@solana/rpc';
|
|
2
|
+
import type { RpcSubscriptions, SignatureNotificationsApi, SlotNotificationsApi } from '@solana/rpc-subscriptions';
|
|
3
|
+
import {
|
|
4
|
+
createBlockHeightExceedencePromiseFactory,
|
|
5
|
+
createRecentSignatureConfirmationPromiseFactory,
|
|
6
|
+
TransactionWithLastValidBlockHeight,
|
|
7
|
+
waitForRecentTransactionConfirmation,
|
|
8
|
+
} from '@solana/transaction-confirmation';
|
|
9
|
+
import { SendableTransaction, Transaction } from '@solana/transactions';
|
|
10
|
+
|
|
11
|
+
import { sendAndConfirmTransactionWithBlockhashLifetime_INTERNAL_ONLY_DO_NOT_EXPORT } from './send-transaction-internal';
|
|
12
|
+
|
|
13
|
+
type SendAndConfirmTransactionWithBlockhashLifetimeFunction = (
|
|
14
|
+
transaction: SendableTransaction & Transaction & TransactionWithLastValidBlockHeight,
|
|
15
|
+
config: Omit<
|
|
16
|
+
Parameters<typeof sendAndConfirmTransactionWithBlockhashLifetime_INTERNAL_ONLY_DO_NOT_EXPORT>[0],
|
|
17
|
+
'confirmRecentTransaction' | 'rpc' | 'transaction'
|
|
18
|
+
>,
|
|
19
|
+
) => Promise<void>;
|
|
20
|
+
|
|
21
|
+
type SendAndConfirmTransactionWithBlockhashLifetimeFactoryConfig<TCluster> = {
|
|
22
|
+
/** An object that supports the {@link GetSignatureStatusesApi} and the {@link SendTransactionApi} of the Solana RPC API */
|
|
23
|
+
rpc: Rpc<GetEpochInfoApi & GetSignatureStatusesApi & SendTransactionApi> & { '~cluster'?: TCluster };
|
|
24
|
+
/** An object that supports the {@link SignatureNotificationsApi} and the {@link SlotNotificationsApi} of the Solana RPC Subscriptions API */
|
|
25
|
+
rpcSubscriptions: RpcSubscriptions<SignatureNotificationsApi & SlotNotificationsApi> & { '~cluster'?: TCluster };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns a function that you can call to send a blockhash-based transaction to the network and to
|
|
30
|
+
* wait until it has been confirmed.
|
|
31
|
+
*
|
|
32
|
+
* @param config
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { isSolanaError, sendAndConfirmTransactionFactory, SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED } from '@solana/kit';
|
|
37
|
+
*
|
|
38
|
+
* const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
|
|
39
|
+
*
|
|
40
|
+
* try {
|
|
41
|
+
* await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' });
|
|
42
|
+
* } catch (e) {
|
|
43
|
+
* if (isSolanaError(e, SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED)) {
|
|
44
|
+
* console.error('This transaction depends on a blockhash that has expired');
|
|
45
|
+
* } else {
|
|
46
|
+
* throw e;
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function sendAndConfirmTransactionFactory({
|
|
52
|
+
rpc,
|
|
53
|
+
rpcSubscriptions,
|
|
54
|
+
}: SendAndConfirmTransactionWithBlockhashLifetimeFactoryConfig<'devnet'>): SendAndConfirmTransactionWithBlockhashLifetimeFunction;
|
|
55
|
+
export function sendAndConfirmTransactionFactory({
|
|
56
|
+
rpc,
|
|
57
|
+
rpcSubscriptions,
|
|
58
|
+
}: SendAndConfirmTransactionWithBlockhashLifetimeFactoryConfig<'testnet'>): SendAndConfirmTransactionWithBlockhashLifetimeFunction;
|
|
59
|
+
export function sendAndConfirmTransactionFactory({
|
|
60
|
+
rpc,
|
|
61
|
+
rpcSubscriptions,
|
|
62
|
+
}: SendAndConfirmTransactionWithBlockhashLifetimeFactoryConfig<'mainnet'>): SendAndConfirmTransactionWithBlockhashLifetimeFunction;
|
|
63
|
+
export function sendAndConfirmTransactionFactory<TCluster extends 'devnet' | 'mainnet' | 'testnet' | void = void>({
|
|
64
|
+
rpc,
|
|
65
|
+
rpcSubscriptions,
|
|
66
|
+
}: SendAndConfirmTransactionWithBlockhashLifetimeFactoryConfig<TCluster>): SendAndConfirmTransactionWithBlockhashLifetimeFunction {
|
|
67
|
+
const getBlockHeightExceedencePromise = createBlockHeightExceedencePromiseFactory({
|
|
68
|
+
rpc,
|
|
69
|
+
rpcSubscriptions,
|
|
70
|
+
} as Parameters<typeof createBlockHeightExceedencePromiseFactory>[0]);
|
|
71
|
+
const getRecentSignatureConfirmationPromise = createRecentSignatureConfirmationPromiseFactory({
|
|
72
|
+
rpc,
|
|
73
|
+
rpcSubscriptions,
|
|
74
|
+
} as Parameters<typeof createRecentSignatureConfirmationPromiseFactory>[0]);
|
|
75
|
+
async function confirmRecentTransaction(
|
|
76
|
+
config: Omit<
|
|
77
|
+
Parameters<typeof waitForRecentTransactionConfirmation>[0],
|
|
78
|
+
'getBlockHeightExceedencePromise' | 'getRecentSignatureConfirmationPromise'
|
|
79
|
+
>,
|
|
80
|
+
) {
|
|
81
|
+
await waitForRecentTransactionConfirmation({
|
|
82
|
+
...config,
|
|
83
|
+
getBlockHeightExceedencePromise,
|
|
84
|
+
getRecentSignatureConfirmationPromise,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return async function sendAndConfirmTransaction(transaction, config) {
|
|
88
|
+
await sendAndConfirmTransactionWithBlockhashLifetime_INTERNAL_ONLY_DO_NOT_EXPORT({
|
|
89
|
+
...config,
|
|
90
|
+
confirmRecentTransaction,
|
|
91
|
+
rpc,
|
|
92
|
+
transaction,
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
}
|