@solana/react 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.browser.cjs.map +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.native.mjs.map +1 -1
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs.map +1 -1
- package/package.json +10 -9
- package/src/SelectedWalletAccountContextProvider.tsx +147 -0
- package/src/chain.ts +7 -0
- package/src/index.ts +14 -0
- package/src/selectedWalletAccountContext.ts +21 -0
- package/src/test-renderer.tsx +70 -0
- package/src/useSignAndSendTransaction.ts +168 -0
- package/src/useSignIn.ts +124 -0
- package/src/useSignMessage.ts +78 -0
- package/src/useSignTransaction.ts +164 -0
- package/src/useWalletAccountMessageSigner.ts +98 -0
- package/src/useWalletAccountTransactionSendingSigner.ts +101 -0
- package/src/useWalletAccountTransactionSigner.ts +146 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { address } from '@solana/addresses';
|
|
2
|
+
import { SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED, SolanaError } from '@solana/errors';
|
|
3
|
+
import { SignatureBytes } from '@solana/keys';
|
|
4
|
+
import { getAbortablePromise } from '@solana/promises';
|
|
5
|
+
import { TransactionSendingSigner } from '@solana/signers';
|
|
6
|
+
import { getTransactionEncoder } from '@solana/transactions';
|
|
7
|
+
import { UiWalletAccount } from '@wallet-standard/ui';
|
|
8
|
+
import { useMemo, useRef } from 'react';
|
|
9
|
+
|
|
10
|
+
import { OnlySolanaChains } from './chain';
|
|
11
|
+
import { useSignAndSendTransaction } from './useSignAndSendTransaction';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Use this to get a {@link TransactionSendingSigner} capable of signing a serialized transaction
|
|
15
|
+
* with the private key of a {@link UiWalletAccount} and sending it to the network for processing.
|
|
16
|
+
*
|
|
17
|
+
* @param chain The identifier of the chain the transaction is destined for. Wallets may use this to
|
|
18
|
+
* simulate the transaction for the user.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import { useWalletAccountTransactionSendingSigner } from '@solana/react';
|
|
23
|
+
* import {
|
|
24
|
+
* appendTransactionMessageInstruction,
|
|
25
|
+
* createSolanaRpc,
|
|
26
|
+
* getBase58Decoder,
|
|
27
|
+
* pipe,
|
|
28
|
+
* setTransactionMessageFeePayerSigner,
|
|
29
|
+
* setTransactionMessageLifetimeUsingBlockhash,
|
|
30
|
+
* signAndSendTransactionMessageWithSigners,
|
|
31
|
+
* } from '@solana/kit';
|
|
32
|
+
*
|
|
33
|
+
* function RecordMemoButton({ account, rpc, text }) {
|
|
34
|
+
* const signer = useWalletAccountTransactionSendingSigner(account, 'solana:devnet');
|
|
35
|
+
* return (
|
|
36
|
+
* <button
|
|
37
|
+
* onClick={async () => {
|
|
38
|
+
* try {
|
|
39
|
+
* const { value: latestBlockhash } = await createSolanaRpc('https://api.devnet.solana.com')
|
|
40
|
+
* .getLatestBlockhash()
|
|
41
|
+
* .send();
|
|
42
|
+
* const message = pipe(
|
|
43
|
+
* createTransactionMessage({ version: 'legacy' }),
|
|
44
|
+
* m => setTransactionMessageFeePayerSigner(signer, m),
|
|
45
|
+
* m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
|
|
46
|
+
* m => appendTransactionMessageInstruction(getAddMemoInstruction({ memo: text }), m),
|
|
47
|
+
* );
|
|
48
|
+
* const signatureBytes = await signAndSendTransactionMessageWithSigners(message);
|
|
49
|
+
* const base58Signature = getBase58Decoder().decode(signature);
|
|
50
|
+
* window.alert(`View transaction: https://explorer.solana.com/tx/${base58Signature}?cluster=devnet`);
|
|
51
|
+
* } catch (e) {
|
|
52
|
+
* console.error('Failed to record memo', e);
|
|
53
|
+
* }
|
|
54
|
+
* }}
|
|
55
|
+
* >
|
|
56
|
+
* Record Memo
|
|
57
|
+
* </button>
|
|
58
|
+
* );
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function useWalletAccountTransactionSendingSigner<TWalletAccount extends UiWalletAccount>(
|
|
63
|
+
uiWalletAccount: TWalletAccount,
|
|
64
|
+
chain: OnlySolanaChains<TWalletAccount['chains']>,
|
|
65
|
+
): TransactionSendingSigner<TWalletAccount['address']>;
|
|
66
|
+
export function useWalletAccountTransactionSendingSigner<TWalletAccount extends UiWalletAccount>(
|
|
67
|
+
uiWalletAccount: TWalletAccount,
|
|
68
|
+
chain: `solana:${string}`,
|
|
69
|
+
): TransactionSendingSigner<TWalletAccount['address']>;
|
|
70
|
+
export function useWalletAccountTransactionSendingSigner<TWalletAccount extends UiWalletAccount>(
|
|
71
|
+
uiWalletAccount: TWalletAccount,
|
|
72
|
+
chain: `solana:${string}`,
|
|
73
|
+
): TransactionSendingSigner<TWalletAccount['address']> {
|
|
74
|
+
const encoderRef = useRef<ReturnType<typeof getTransactionEncoder> | null>(null);
|
|
75
|
+
const signAndSendTransaction = useSignAndSendTransaction(uiWalletAccount, chain);
|
|
76
|
+
return useMemo(
|
|
77
|
+
() => ({
|
|
78
|
+
address: address(uiWalletAccount.address),
|
|
79
|
+
async signAndSendTransactions(transactions, config = {}) {
|
|
80
|
+
const { abortSignal, ...options } = config;
|
|
81
|
+
abortSignal?.throwIfAborted();
|
|
82
|
+
const transactionEncoder = (encoderRef.current ||= getTransactionEncoder());
|
|
83
|
+
if (transactions.length > 1) {
|
|
84
|
+
throw new SolanaError(SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED);
|
|
85
|
+
}
|
|
86
|
+
if (transactions.length === 0) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const [transaction] = transactions;
|
|
90
|
+
const wireTransactionBytes = transactionEncoder.encode(transaction);
|
|
91
|
+
const inputWithOptions = {
|
|
92
|
+
...options,
|
|
93
|
+
transaction: wireTransactionBytes as Uint8Array,
|
|
94
|
+
};
|
|
95
|
+
const { signature } = await getAbortablePromise(signAndSendTransaction(inputWithOptions), abortSignal);
|
|
96
|
+
return Object.freeze([signature as SignatureBytes]);
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
[signAndSendTransaction, uiWalletAccount.address],
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { address } from '@solana/addresses';
|
|
2
|
+
import { bytesEqual } from '@solana/codecs-core';
|
|
3
|
+
import { SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED, SolanaError } from '@solana/errors';
|
|
4
|
+
import { getAbortablePromise } from '@solana/promises';
|
|
5
|
+
import { TransactionModifyingSigner } from '@solana/signers';
|
|
6
|
+
import { getCompiledTransactionMessageDecoder } from '@solana/transaction-messages';
|
|
7
|
+
import {
|
|
8
|
+
assertIsTransactionWithinSizeLimit,
|
|
9
|
+
getTransactionCodec,
|
|
10
|
+
getTransactionLifetimeConstraintFromCompiledTransactionMessage,
|
|
11
|
+
Transaction,
|
|
12
|
+
TransactionWithinSizeLimit,
|
|
13
|
+
TransactionWithLifetime,
|
|
14
|
+
} from '@solana/transactions';
|
|
15
|
+
import { UiWalletAccount } from '@wallet-standard/ui';
|
|
16
|
+
import { useMemo, useRef } from 'react';
|
|
17
|
+
|
|
18
|
+
import { OnlySolanaChains } from './chain';
|
|
19
|
+
import { useSignTransaction } from './useSignTransaction';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Use this to get a {@link TransactionSigner} capable of signing serialized transactions with the
|
|
23
|
+
* private key of a {@link UiWalletAccount}
|
|
24
|
+
*
|
|
25
|
+
* @returns A {@link TransactionModifyingSigner}. This is a conservative assumption based on the
|
|
26
|
+
* fact that your application can not control whether or not the wallet will modify the transaction
|
|
27
|
+
* before signing it (eg. to add guard instructions, or a priority fee budget). Otherwise this
|
|
28
|
+
* method could more specifically return a {@link TransactionSigner} or a
|
|
29
|
+
* {@link TransactionPartialSigner}.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* import { useWalletAccountTransactionSigner } from '@solana/react';
|
|
34
|
+
*
|
|
35
|
+
* function SignTransactionButton({ account, transaction }) {
|
|
36
|
+
* const transactionSigner = useWalletAccountTransactionSigner(account, 'solana:devnet');
|
|
37
|
+
* return (
|
|
38
|
+
* <button
|
|
39
|
+
* onClick={async () => {
|
|
40
|
+
* try {
|
|
41
|
+
* const [{ signatures }] = await transactionSigner.modifyAndSignTransactions([transaction]);
|
|
42
|
+
* const signatureBytes = signatures[transactionSigner.address];
|
|
43
|
+
* window.alert(`Signature bytes: ${signatureBytes.toString()}`);
|
|
44
|
+
* } catch (e) {
|
|
45
|
+
* console.error('Failed to sign transaction', e);
|
|
46
|
+
* }
|
|
47
|
+
* }}
|
|
48
|
+
* >
|
|
49
|
+
* Sign Transaction
|
|
50
|
+
* </button>
|
|
51
|
+
* );
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function useWalletAccountTransactionSigner<TWalletAccount extends UiWalletAccount>(
|
|
56
|
+
uiWalletAccount: TWalletAccount,
|
|
57
|
+
chain: OnlySolanaChains<TWalletAccount['chains']>,
|
|
58
|
+
): TransactionModifyingSigner<TWalletAccount['address']>;
|
|
59
|
+
export function useWalletAccountTransactionSigner<TWalletAccount extends UiWalletAccount>(
|
|
60
|
+
uiWalletAccount: TWalletAccount,
|
|
61
|
+
chain: `solana:${string}`,
|
|
62
|
+
): TransactionModifyingSigner<TWalletAccount['address']>;
|
|
63
|
+
export function useWalletAccountTransactionSigner<TWalletAccount extends UiWalletAccount>(
|
|
64
|
+
uiWalletAccount: TWalletAccount,
|
|
65
|
+
chain: `solana:${string}`,
|
|
66
|
+
): TransactionModifyingSigner<TWalletAccount['address']> {
|
|
67
|
+
const encoderRef = useRef<ReturnType<typeof getTransactionCodec> | null>(null);
|
|
68
|
+
const signTransaction = useSignTransaction(uiWalletAccount, chain);
|
|
69
|
+
return useMemo(
|
|
70
|
+
() => ({
|
|
71
|
+
address: address(uiWalletAccount.address),
|
|
72
|
+
async modifyAndSignTransactions(transactions, config = {}) {
|
|
73
|
+
const { abortSignal, ...options } = config;
|
|
74
|
+
abortSignal?.throwIfAborted();
|
|
75
|
+
const transactionCodec = (encoderRef.current ||= getTransactionCodec());
|
|
76
|
+
if (transactions.length > 1) {
|
|
77
|
+
throw new SolanaError(SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED);
|
|
78
|
+
}
|
|
79
|
+
if (transactions.length === 0) {
|
|
80
|
+
return transactions as readonly (Transaction &
|
|
81
|
+
TransactionWithinSizeLimit &
|
|
82
|
+
TransactionWithLifetime)[];
|
|
83
|
+
}
|
|
84
|
+
const [transaction] = transactions;
|
|
85
|
+
const wireTransactionBytes = transactionCodec.encode(transaction);
|
|
86
|
+
const inputWithOptions = {
|
|
87
|
+
...options,
|
|
88
|
+
transaction: wireTransactionBytes as Uint8Array,
|
|
89
|
+
};
|
|
90
|
+
const { signedTransaction } = await getAbortablePromise(signTransaction(inputWithOptions), abortSignal);
|
|
91
|
+
const decodedSignedTransaction = transactionCodec.decode(
|
|
92
|
+
signedTransaction,
|
|
93
|
+
) as (typeof transactions)[number];
|
|
94
|
+
|
|
95
|
+
assertIsTransactionWithinSizeLimit(decodedSignedTransaction);
|
|
96
|
+
|
|
97
|
+
const existingLifetime =
|
|
98
|
+
'lifetimeConstraint' in transaction
|
|
99
|
+
? (transaction as TransactionWithLifetime).lifetimeConstraint
|
|
100
|
+
: undefined;
|
|
101
|
+
|
|
102
|
+
if (existingLifetime) {
|
|
103
|
+
if (bytesEqual(decodedSignedTransaction.messageBytes, transaction.messageBytes)) {
|
|
104
|
+
// If the transaction has identical bytes, the lifetime won't have changed
|
|
105
|
+
return Object.freeze([
|
|
106
|
+
{
|
|
107
|
+
...decodedSignedTransaction,
|
|
108
|
+
lifetimeConstraint: existingLifetime,
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// If the transaction has changed, check the lifetime constraint field
|
|
114
|
+
const compiledTransactionMessage = getCompiledTransactionMessageDecoder().decode(
|
|
115
|
+
decodedSignedTransaction.messageBytes,
|
|
116
|
+
);
|
|
117
|
+
const currentToken =
|
|
118
|
+
'blockhash' in existingLifetime ? existingLifetime.blockhash : existingLifetime.nonce;
|
|
119
|
+
|
|
120
|
+
if (compiledTransactionMessage.lifetimeToken === currentToken) {
|
|
121
|
+
return Object.freeze([
|
|
122
|
+
{
|
|
123
|
+
...decodedSignedTransaction,
|
|
124
|
+
lifetimeConstraint: existingLifetime,
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// If we get here then there is no existing lifetime, or the lifetime has changed. We need to attach a new lifetime
|
|
131
|
+
const compiledTransactionMessage = getCompiledTransactionMessageDecoder().decode(
|
|
132
|
+
decodedSignedTransaction.messageBytes,
|
|
133
|
+
);
|
|
134
|
+
const lifetimeConstraint =
|
|
135
|
+
await getTransactionLifetimeConstraintFromCompiledTransactionMessage(compiledTransactionMessage);
|
|
136
|
+
return Object.freeze([
|
|
137
|
+
{
|
|
138
|
+
...decodedSignedTransaction,
|
|
139
|
+
lifetimeConstraint,
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
[uiWalletAccount.address, signTransaction],
|
|
145
|
+
);
|
|
146
|
+
}
|