Skip to content

Commit ee304c3

Browse files
committed
Add minContextSlot as an option to Transaction{Sending}SignerConfig
1 parent f66e3dd commit ee304c3

11 files changed

+115
-156
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@solana/signers': minor
3+
---
4+
5+
Transaction signers' methods now take `minContextSlot` as an option. This is important for signers that simulate transactions, like wallets. They might be interested in knowing the slot at which the transaction was prepared, lest they run simulation at too early a slot.

packages/react/src/__tests__/useWalletAccountTransactionSendingSigner-test.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -144,36 +144,10 @@ describe('useWalletAccountTransactionSendingSigner', () => {
144144
await expect(signAndSendTransactions([inputTransaction])).resolves.toEqual([mockSignatureResult]);
145145
}
146146
});
147-
it('calls `getOptions` with the transaction', () => {
148-
const mockGetOptions = jest.fn();
149-
const { result } = renderHook(() =>
150-
useWalletAccountTransactionSendingSigner(mockUiWalletAccount, 'solana:danknet', {
151-
getOptions: mockGetOptions,
152-
}),
153-
);
154-
// eslint-disable-next-line jest/no-conditional-in-test
155-
if (result.__type === 'error' || !result.current) {
156-
throw result.current;
157-
} else {
158-
const { signAndSendTransactions } = result.current;
159-
const inputTransaction = {
160-
messageBytes: new Uint8Array([1, 2, 3]) as unknown as TransactionMessageBytes,
161-
signatures: {
162-
'11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes,
163-
},
164-
};
165-
signAndSendTransactions([inputTransaction]);
166-
// eslint-disable-next-line jest/no-conditional-expect
167-
expect(mockGetOptions).toHaveBeenCalledWith(inputTransaction);
168-
}
169-
});
170-
it('adds the options returned by `getOptions` to the call to `signTransaction`', () => {
147+
it('calls `signAndSendTransaction` with all options except the `abortSignal`', () => {
171148
const mockOptions = { minContextSlot: 123n };
172-
const mockGetOptions = jest.fn().mockReturnValue(mockOptions);
173149
const { result } = renderHook(() =>
174-
useWalletAccountTransactionSendingSigner(mockUiWalletAccount, 'solana:danknet', {
175-
getOptions: mockGetOptions,
176-
}),
150+
useWalletAccountTransactionSendingSigner(mockUiWalletAccount, 'solana:danknet'),
177151
);
178152
// eslint-disable-next-line jest/no-conditional-in-test
179153
if (result.__type === 'error' || !result.current) {
@@ -186,9 +160,12 @@ describe('useWalletAccountTransactionSendingSigner', () => {
186160
'11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes,
187161
},
188162
};
189-
signAndSendTransactions([inputTransaction]);
163+
signAndSendTransactions([inputTransaction], {
164+
abortSignal: AbortSignal.timeout(1_000_000),
165+
...mockOptions,
166+
});
190167
// eslint-disable-next-line jest/no-conditional-expect
191-
expect(mockSignAndSendTransaction).toHaveBeenCalledWith({ options: mockOptions });
168+
expect(mockSignAndSendTransaction).toHaveBeenCalledWith(mockOptions);
192169
}
193170
});
194171
it('rejects when aborted', async () => {

packages/react/src/__tests__/useWalletAccountTransactionSigner-test.ts

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -144,33 +144,9 @@ describe('useWalletAccountTransactionSigner', () => {
144144
await expect(signPromise).resolves.toEqual([mockDecodedTransaction]);
145145
}
146146
});
147-
it('calls `getOptions` with the transaction', () => {
148-
const mockGetOptions = jest.fn();
149-
const { result } = renderHook(() =>
150-
useWalletAccountTransactionSigner(mockUiWalletAccount, 'solana:danknet', { getOptions: mockGetOptions }),
151-
);
152-
// eslint-disable-next-line jest/no-conditional-in-test
153-
if (result.__type === 'error' || !result.current) {
154-
throw result.current;
155-
} else {
156-
const { modifyAndSignTransactions } = result.current;
157-
const inputTransaction = {
158-
messageBytes: new Uint8Array([1, 2, 3]) as unknown as TransactionMessageBytes,
159-
signatures: {
160-
'11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes,
161-
},
162-
};
163-
modifyAndSignTransactions([inputTransaction]);
164-
// eslint-disable-next-line jest/no-conditional-expect
165-
expect(mockGetOptions).toHaveBeenCalledWith(inputTransaction);
166-
}
167-
});
168-
it('adds the options returned by `getOptions` to the call to `signTransaction`', () => {
147+
it('calls `signTransaction` with all options except the `abortSignal`', () => {
169148
const mockOptions = { minContextSlot: 123n };
170-
const mockGetOptions = jest.fn().mockReturnValue(mockOptions);
171-
const { result } = renderHook(() =>
172-
useWalletAccountTransactionSigner(mockUiWalletAccount, 'solana:danknet', { getOptions: mockGetOptions }),
173-
);
149+
const { result } = renderHook(() => useWalletAccountTransactionSigner(mockUiWalletAccount, 'solana:danknet'));
174150
// eslint-disable-next-line jest/no-conditional-in-test
175151
if (result.__type === 'error' || !result.current) {
176152
throw result.current;
@@ -182,9 +158,12 @@ describe('useWalletAccountTransactionSigner', () => {
182158
'11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes,
183159
},
184160
};
185-
modifyAndSignTransactions([inputTransaction]);
161+
modifyAndSignTransactions([inputTransaction], {
162+
abortSignal: AbortSignal.timeout(1_000_000),
163+
...mockOptions,
164+
});
186165
// eslint-disable-next-line jest/no-conditional-expect
187-
expect(mockSignTransaction).toHaveBeenCalledWith({ options: mockOptions });
166+
expect(mockSignTransaction).toHaveBeenCalledWith(mockOptions);
188167
}
189168
});
190169
it('rejects when aborted', async () => {

packages/react/src/useWalletAccountTransactionSendingSigner.ts

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,37 @@ import { address } from '@solana/addresses';
22
import { SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED, SolanaError } from '@solana/errors';
33
import { SignatureBytes } from '@solana/keys';
44
import { TransactionSendingSigner } from '@solana/signers';
5-
import { getTransactionEncoder, Transaction } from '@solana/transactions';
5+
import { getTransactionEncoder } from '@solana/transactions';
66
import { UiWalletAccount } from '@wallet-standard/ui';
77
import { useMemo, useRef } from 'react';
88

99
import { getAbortablePromise } from './abortable-promise';
1010
import { OnlySolanaChains } from './chain';
1111
import { useSignAndSendTransaction } from './useSignAndSendTransaction';
1212

13-
type ExtraConfig = Readonly<{
14-
getOptions?(transaction: Transaction): Readonly<{ minContextSlot?: bigint }> | undefined;
15-
}>;
16-
1713
/**
1814
* Returns an object that conforms to the `TransactionSendingSigner` interface of `@solana/signers`.
1915
*/
2016
export function useWalletAccountTransactionSendingSigner<TWalletAccount extends UiWalletAccount>(
2117
uiWalletAccount: TWalletAccount,
2218
chain: OnlySolanaChains<TWalletAccount['chains']>,
23-
extraConfig?: ExtraConfig,
2419
): TransactionSendingSigner<TWalletAccount['address']>;
2520
export function useWalletAccountTransactionSendingSigner<TWalletAccount extends UiWalletAccount>(
2621
uiWalletAccount: TWalletAccount,
2722
chain: `solana:${string}`,
28-
extraConfig?: ExtraConfig,
2923
): TransactionSendingSigner<TWalletAccount['address']>;
3024
export function useWalletAccountTransactionSendingSigner<TWalletAccount extends UiWalletAccount>(
3125
uiWalletAccount: TWalletAccount,
3226
chain: `solana:${string}`,
33-
extraConfig?: ExtraConfig,
3427
): TransactionSendingSigner<TWalletAccount['address']> {
3528
const encoderRef = useRef<ReturnType<typeof getTransactionEncoder>>();
3629
const signAndSendTransaction = useSignAndSendTransaction(uiWalletAccount, chain);
37-
const getOptions = extraConfig?.getOptions;
3830
return useMemo(
3931
() => ({
4032
address: address(uiWalletAccount.address),
41-
async signAndSendTransactions(transactions, config) {
42-
config?.abortSignal?.throwIfAborted();
33+
async signAndSendTransactions(transactions, config = {}) {
34+
const { abortSignal, ...options } = config;
35+
abortSignal?.throwIfAborted();
4336
const transactionEncoder = (encoderRef.current ||= getTransactionEncoder());
4437
if (transactions.length > 1) {
4538
throw new SolanaError(SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED);
@@ -49,18 +42,14 @@ export function useWalletAccountTransactionSendingSigner<TWalletAccount extends
4942
}
5043
const [transaction] = transactions;
5144
const wireTransactionBytes = transactionEncoder.encode(transaction);
52-
const options = getOptions ? getOptions(transaction) : undefined;
5345
const inputWithOptions = {
54-
...(options ? { options } : null),
46+
...options,
5547
transaction: wireTransactionBytes as Uint8Array,
5648
};
57-
const { signature } = await getAbortablePromise(
58-
signAndSendTransaction(inputWithOptions),
59-
config?.abortSignal,
60-
);
49+
const { signature } = await getAbortablePromise(signAndSendTransaction(inputWithOptions), abortSignal);
6150
return Object.freeze([signature as SignatureBytes]);
6251
},
6352
}),
64-
[getOptions, signAndSendTransaction, uiWalletAccount.address],
53+
[signAndSendTransaction, uiWalletAccount.address],
6554
);
6655
}
Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,38 @@
11
import { address } from '@solana/addresses';
22
import { SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED, SolanaError } from '@solana/errors';
33
import { TransactionModifyingSigner } from '@solana/signers';
4-
import { getTransactionCodec, Transaction } from '@solana/transactions';
4+
import { getTransactionCodec } from '@solana/transactions';
55
import { UiWalletAccount } from '@wallet-standard/ui';
66
import { useMemo, useRef } from 'react';
77

88
import { getAbortablePromise } from './abortable-promise';
99
import { OnlySolanaChains } from './chain';
1010
import { useSignTransaction } from './useSignTransaction';
1111

12-
type ExtraConfig = Readonly<{
13-
getOptions?(transaction: Transaction): Readonly<{ minContextSlot?: bigint }> | undefined;
14-
}>;
15-
1612
/**
1713
* Returns an object that conforms to the `TransactionModifyingSigner` interface of
1814
* `@solana/signers`.
1915
*/
2016
export function useWalletAccountTransactionSigner<TWalletAccount extends UiWalletAccount>(
2117
uiWalletAccount: TWalletAccount,
2218
chain: OnlySolanaChains<TWalletAccount['chains']>,
23-
extraConfig?: ExtraConfig,
2419
): TransactionModifyingSigner<TWalletAccount['address']>;
2520
export function useWalletAccountTransactionSigner<TWalletAccount extends UiWalletAccount>(
2621
uiWalletAccount: TWalletAccount,
2722
chain: `solana:${string}`,
28-
extraConfig?: ExtraConfig,
2923
): TransactionModifyingSigner<TWalletAccount['address']>;
3024
export function useWalletAccountTransactionSigner<TWalletAccount extends UiWalletAccount>(
3125
uiWalletAccount: TWalletAccount,
3226
chain: `solana:${string}`,
33-
extraConfig?: ExtraConfig,
3427
): TransactionModifyingSigner<TWalletAccount['address']> {
3528
const encoderRef = useRef<ReturnType<typeof getTransactionCodec>>();
3629
const signTransaction = useSignTransaction(uiWalletAccount, chain);
37-
const getOptions = extraConfig?.getOptions;
3830
return useMemo(
3931
() => ({
4032
address: address(uiWalletAccount.address),
41-
async modifyAndSignTransactions(transactions, config) {
42-
config?.abortSignal?.throwIfAborted();
33+
async modifyAndSignTransactions(transactions, config = {}) {
34+
const { abortSignal, ...options } = config;
35+
abortSignal?.throwIfAborted();
4336
const transactionCodec = (encoderRef.current ||= getTransactionCodec());
4437
if (transactions.length > 1) {
4538
throw new SolanaError(SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED);
@@ -49,21 +42,17 @@ export function useWalletAccountTransactionSigner<TWalletAccount extends UiWalle
4942
}
5043
const [transaction] = transactions;
5144
const wireTransactionBytes = transactionCodec.encode(transaction);
52-
const options = getOptions ? getOptions(transaction) : undefined;
5345
const inputWithOptions = {
54-
...(options ? { options } : null),
46+
...options,
5547
transaction: wireTransactionBytes as Uint8Array,
5648
};
57-
const { signedTransaction } = await getAbortablePromise(
58-
signTransaction(inputWithOptions),
59-
config?.abortSignal,
60-
);
49+
const { signedTransaction } = await getAbortablePromise(signTransaction(inputWithOptions), abortSignal);
6150
const decodedSignedTransaction = transactionCodec.decode(
6251
signedTransaction,
6352
) as (typeof transactions)[number];
6453
return Object.freeze([decodedSignedTransaction]);
6554
},
6655
}),
67-
[uiWalletAccount.address, getOptions, signTransaction],
56+
[uiWalletAccount.address, signTransaction],
6857
);
6958
}

0 commit comments

Comments
 (0)