diff --git a/.changeset/wicked-cougars-matter.md b/.changeset/wicked-cougars-matter.md new file mode 100644 index 000000000000..14c52fd7366d --- /dev/null +++ b/.changeset/wicked-cougars-matter.md @@ -0,0 +1,5 @@ +--- +'@solana/signers': minor +--- + +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. diff --git a/packages/react/src/__tests__/useWalletAccountTransactionSendingSigner-test.ts b/packages/react/src/__tests__/useWalletAccountTransactionSendingSigner-test.ts index f7a4270db656..c271ebec7fd7 100644 --- a/packages/react/src/__tests__/useWalletAccountTransactionSendingSigner-test.ts +++ b/packages/react/src/__tests__/useWalletAccountTransactionSendingSigner-test.ts @@ -144,36 +144,10 @@ describe('useWalletAccountTransactionSendingSigner', () => { await expect(signAndSendTransactions([inputTransaction])).resolves.toEqual([mockSignatureResult]); } }); - it('calls `getOptions` with the transaction', () => { - const mockGetOptions = jest.fn(); - const { result } = renderHook(() => - useWalletAccountTransactionSendingSigner(mockUiWalletAccount, 'solana:danknet', { - getOptions: mockGetOptions, - }), - ); - // eslint-disable-next-line jest/no-conditional-in-test - if (result.__type === 'error' || !result.current) { - throw result.current; - } else { - const { signAndSendTransactions } = result.current; - const inputTransaction = { - messageBytes: new Uint8Array([1, 2, 3]) as unknown as TransactionMessageBytes, - signatures: { - '11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes, - }, - }; - signAndSendTransactions([inputTransaction]); - // eslint-disable-next-line jest/no-conditional-expect - expect(mockGetOptions).toHaveBeenCalledWith(inputTransaction); - } - }); - it('adds the options returned by `getOptions` to the call to `signTransaction`', () => { + it('calls `signAndSendTransaction` with all options except the `abortSignal`', () => { const mockOptions = { minContextSlot: 123n }; - const mockGetOptions = jest.fn().mockReturnValue(mockOptions); const { result } = renderHook(() => - useWalletAccountTransactionSendingSigner(mockUiWalletAccount, 'solana:danknet', { - getOptions: mockGetOptions, - }), + useWalletAccountTransactionSendingSigner(mockUiWalletAccount, 'solana:danknet'), ); // eslint-disable-next-line jest/no-conditional-in-test if (result.__type === 'error' || !result.current) { @@ -186,9 +160,12 @@ describe('useWalletAccountTransactionSendingSigner', () => { '11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes, }, }; - signAndSendTransactions([inputTransaction]); + signAndSendTransactions([inputTransaction], { + abortSignal: AbortSignal.timeout(1_000_000), + ...mockOptions, + }); // eslint-disable-next-line jest/no-conditional-expect - expect(mockSignAndSendTransaction).toHaveBeenCalledWith({ options: mockOptions }); + expect(mockSignAndSendTransaction).toHaveBeenCalledWith(mockOptions); } }); it('rejects when aborted', async () => { diff --git a/packages/react/src/__tests__/useWalletAccountTransactionSigner-test.ts b/packages/react/src/__tests__/useWalletAccountTransactionSigner-test.ts index f68525b5298c..96a9c6bcfa1b 100644 --- a/packages/react/src/__tests__/useWalletAccountTransactionSigner-test.ts +++ b/packages/react/src/__tests__/useWalletAccountTransactionSigner-test.ts @@ -144,33 +144,9 @@ describe('useWalletAccountTransactionSigner', () => { await expect(signPromise).resolves.toEqual([mockDecodedTransaction]); } }); - it('calls `getOptions` with the transaction', () => { - const mockGetOptions = jest.fn(); - const { result } = renderHook(() => - useWalletAccountTransactionSigner(mockUiWalletAccount, 'solana:danknet', { getOptions: mockGetOptions }), - ); - // eslint-disable-next-line jest/no-conditional-in-test - if (result.__type === 'error' || !result.current) { - throw result.current; - } else { - const { modifyAndSignTransactions } = result.current; - const inputTransaction = { - messageBytes: new Uint8Array([1, 2, 3]) as unknown as TransactionMessageBytes, - signatures: { - '11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes, - }, - }; - modifyAndSignTransactions([inputTransaction]); - // eslint-disable-next-line jest/no-conditional-expect - expect(mockGetOptions).toHaveBeenCalledWith(inputTransaction); - } - }); - it('adds the options returned by `getOptions` to the call to `signTransaction`', () => { + it('calls `signTransaction` with all options except the `abortSignal`', () => { const mockOptions = { minContextSlot: 123n }; - const mockGetOptions = jest.fn().mockReturnValue(mockOptions); - const { result } = renderHook(() => - useWalletAccountTransactionSigner(mockUiWalletAccount, 'solana:danknet', { getOptions: mockGetOptions }), - ); + const { result } = renderHook(() => useWalletAccountTransactionSigner(mockUiWalletAccount, 'solana:danknet')); // eslint-disable-next-line jest/no-conditional-in-test if (result.__type === 'error' || !result.current) { throw result.current; @@ -182,9 +158,12 @@ describe('useWalletAccountTransactionSigner', () => { '11111111111111111111111111111114': new Uint8Array(64).fill(2) as SignatureBytes, }, }; - modifyAndSignTransactions([inputTransaction]); + modifyAndSignTransactions([inputTransaction], { + abortSignal: AbortSignal.timeout(1_000_000), + ...mockOptions, + }); // eslint-disable-next-line jest/no-conditional-expect - expect(mockSignTransaction).toHaveBeenCalledWith({ options: mockOptions }); + expect(mockSignTransaction).toHaveBeenCalledWith(mockOptions); } }); it('rejects when aborted', async () => { diff --git a/packages/react/src/useWalletAccountTransactionSendingSigner.ts b/packages/react/src/useWalletAccountTransactionSendingSigner.ts index 8bdd986ffc9c..b2b8b1e5d126 100644 --- a/packages/react/src/useWalletAccountTransactionSendingSigner.ts +++ b/packages/react/src/useWalletAccountTransactionSendingSigner.ts @@ -2,7 +2,7 @@ import { address } from '@solana/addresses'; import { SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED, SolanaError } from '@solana/errors'; import { SignatureBytes } from '@solana/keys'; import { TransactionSendingSigner } from '@solana/signers'; -import { getTransactionEncoder, Transaction } from '@solana/transactions'; +import { getTransactionEncoder } from '@solana/transactions'; import { UiWalletAccount } from '@wallet-standard/ui'; import { useMemo, useRef } from 'react'; @@ -10,36 +10,29 @@ import { getAbortablePromise } from './abortable-promise'; import { OnlySolanaChains } from './chain'; import { useSignAndSendTransaction } from './useSignAndSendTransaction'; -type ExtraConfig = Readonly<{ - getOptions?(transaction: Transaction): Readonly<{ minContextSlot?: bigint }> | undefined; -}>; - /** * Returns an object that conforms to the `TransactionSendingSigner` interface of `@solana/signers`. */ export function useWalletAccountTransactionSendingSigner( uiWalletAccount: TWalletAccount, chain: OnlySolanaChains, - extraConfig?: ExtraConfig, ): TransactionSendingSigner; export function useWalletAccountTransactionSendingSigner( uiWalletAccount: TWalletAccount, chain: `solana:${string}`, - extraConfig?: ExtraConfig, ): TransactionSendingSigner; export function useWalletAccountTransactionSendingSigner( uiWalletAccount: TWalletAccount, chain: `solana:${string}`, - extraConfig?: ExtraConfig, ): TransactionSendingSigner { const encoderRef = useRef>(); const signAndSendTransaction = useSignAndSendTransaction(uiWalletAccount, chain); - const getOptions = extraConfig?.getOptions; return useMemo( () => ({ address: address(uiWalletAccount.address), - async signAndSendTransactions(transactions, config) { - config?.abortSignal?.throwIfAborted(); + async signAndSendTransactions(transactions, config = {}) { + const { abortSignal, ...options } = config; + abortSignal?.throwIfAborted(); const transactionEncoder = (encoderRef.current ||= getTransactionEncoder()); if (transactions.length > 1) { throw new SolanaError(SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED); @@ -49,18 +42,14 @@ export function useWalletAccountTransactionSendingSigner | undefined; -}>; - /** * Returns an object that conforms to the `TransactionModifyingSigner` interface of * `@solana/signers`. @@ -20,26 +16,23 @@ type ExtraConfig = Readonly<{ export function useWalletAccountTransactionSigner( uiWalletAccount: TWalletAccount, chain: OnlySolanaChains, - extraConfig?: ExtraConfig, ): TransactionModifyingSigner; export function useWalletAccountTransactionSigner( uiWalletAccount: TWalletAccount, chain: `solana:${string}`, - extraConfig?: ExtraConfig, ): TransactionModifyingSigner; export function useWalletAccountTransactionSigner( uiWalletAccount: TWalletAccount, chain: `solana:${string}`, - extraConfig?: ExtraConfig, ): TransactionModifyingSigner { const encoderRef = useRef>(); const signTransaction = useSignTransaction(uiWalletAccount, chain); - const getOptions = extraConfig?.getOptions; return useMemo( () => ({ address: address(uiWalletAccount.address), - async modifyAndSignTransactions(transactions, config) { - config?.abortSignal?.throwIfAborted(); + async modifyAndSignTransactions(transactions, config = {}) { + const { abortSignal, ...options } = config; + abortSignal?.throwIfAborted(); const transactionCodec = (encoderRef.current ||= getTransactionCodec()); if (transactions.length > 1) { throw new SolanaError(SOLANA_ERROR__SIGNER__WALLET_MULTISIGN_UNIMPLEMENTED); @@ -49,21 +42,17 @@ export function useWalletAccountTransactionSigner { signerB.signTransactions.mockResolvedValueOnce([{ '2222': '2222_signature' }]); // When we partially sign this transaction. - const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage); + const mockOptions = { + abortSignal: AbortSignal.timeout(1_000_000), + minContextSlot: 123n, + }; + const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage, mockOptions); // Then it contains the expected signatures. expect(signedTransaction.signatures).toStrictEqual({ @@ -71,10 +75,8 @@ describe('partiallySignTransactionMessageWithSigners', () => { }); // And the signers were called with the expected parameters. - expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], { - abortSignal: undefined, - }); - expect(signerB.signTransactions).toHaveBeenCalledWith([modifiedTransaction], { abortSignal: undefined }); + expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], mockOptions); + expect(signerB.signTransactions).toHaveBeenCalledWith([modifiedTransaction], mockOptions); }); it('signs modifying signers before partial signers', async () => { @@ -241,7 +243,7 @@ describe('partiallySignTransactionMessageWithSigners', () => { expect(signedTransaction.signatures).toStrictEqual({ '1111': null, '2222': '2222_signature' }); // And only the partial signer function was called. - expect(signerB.signTransactions).toHaveBeenCalledWith([unsignedTransaction], { abortSignal: undefined }); + expect(signerB.signTransactions).toHaveBeenCalledWith([unsignedTransaction], undefined /* config */); expect(signerA.signAndSendTransactions).not.toHaveBeenCalled(); expect(signerB.signAndSendTransactions).not.toHaveBeenCalled(); }); @@ -276,10 +278,8 @@ describe('partiallySignTransactionMessageWithSigners', () => { // Then signer A was used as a modifying signer. expect(signerA.signTransactions).not.toHaveBeenCalled(); - expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], { - abortSignal: undefined, - }); - expect(signerB.signTransactions).toHaveBeenCalledWith([modifiedTransaction], { abortSignal: undefined }); + expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], undefined /* config */); + expect(signerB.signTransactions).toHaveBeenCalledWith([modifiedTransaction], undefined /* config */); // And it contains the expected signatures. expect(signedTransaction.signatures).toStrictEqual({ @@ -317,11 +317,9 @@ describe('partiallySignTransactionMessageWithSigners', () => { const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage); // Then signer A was used as a partial signer. - expect(signerA.signTransactions).toHaveBeenCalledWith([modifiedTransaction], { abortSignal: undefined }); + expect(signerA.signTransactions).toHaveBeenCalledWith([modifiedTransaction], undefined /* config */); expect(signerA.modifyAndSignTransactions).not.toHaveBeenCalled(); - expect(signerB.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], { - abortSignal: undefined, - }); + expect(signerB.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], undefined /* config */); // And it contains the expected signatures. expect(signedTransaction.signatures).toStrictEqual({ @@ -430,7 +428,11 @@ describe('signTransactionMessageWithSigners', () => { signerB.signTransactions.mockResolvedValueOnce([{ '2222': '2222_signature' }]); // When we sign this transaction. - const signedTransaction = await signTransactionMessageWithSigners(transactionMessage); + const mockOptions = { + abortSignal: AbortSignal.timeout(1_000_000), + minContextSlot: 123n, + }; + const signedTransaction = await signTransactionMessageWithSigners(transactionMessage, mockOptions); // Then it contains the expected signatures. expect(signedTransaction.signatures).toStrictEqual({ @@ -442,10 +444,8 @@ describe('signTransactionMessageWithSigners', () => { signedTransaction satisfies FullySignedTransaction; // And the signers were called with the expected parameters. - expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], { - abortSignal: undefined, - }); - expect(signerB.signTransactions).toHaveBeenCalledWith([modifiedTransaction], { abortSignal: undefined }); + expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], mockOptions); + expect(signerB.signTransactions).toHaveBeenCalledWith([modifiedTransaction], mockOptions); }); it('asserts the transaction is fully signed', async () => { @@ -554,13 +554,17 @@ describe('signAndSendTransactionMessageWithSigners', () => { // When we sign and send this transaction. assertIsTransactionMessageWithSingleSendingSigner(transactionMessage); - const transactionSignature = await signAndSendTransactionMessageWithSigners(transactionMessage); + const mockOptions = { + abortSignal: AbortSignal.timeout(1_000_000), + minContextSlot: 123n, + }; + const transactionSignature = await signAndSendTransactionMessageWithSigners(transactionMessage, mockOptions); // Then the sending signer was used to send the transaction. - expect(signerA.signTransactions).toHaveBeenCalledWith([unsignedTransaction], { abortSignal: undefined }); + expect(signerA.signTransactions).toHaveBeenCalledWith([unsignedTransaction], mockOptions); expect(signerB.signAndSendTransactions).toHaveBeenCalledWith( [{ ...unsignedTransaction, signatures: { '1111': '1111_signature', '2222': null } }], - { abortSignal: undefined }, + mockOptions, ); // And the returned signature matches the one returned by the sending signer. @@ -622,7 +626,7 @@ describe('signAndSendTransactionMessageWithSigners', () => { // Then the composite signer was used as a sending signer. expect(signerA.signAndSendTransactions).toHaveBeenCalledWith( [{ ...unsignedTransaction, signatures: { '1111': null, '2222': '2222_signature' } }], - { abortSignal: undefined }, + undefined /* config */, ); expect(signerA.signTransactions).not.toHaveBeenCalled(); expect(signerA.modifyAndSignTransactions).not.toHaveBeenCalled(); @@ -658,17 +662,13 @@ describe('signAndSendTransactionMessageWithSigners', () => { const transactionSignature = await signAndSendTransactionMessageWithSigners(transaction); // Then the composite signer was used as a modifying signer. - expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], { - abortSignal: undefined, - }); + expect(signerA.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], undefined /* config */); expect(signerA.signTransactions).not.toHaveBeenCalled(); expect(signerA.signAndSendTransactions).not.toHaveBeenCalled(); // And the sending only signer was used to send the transaction. expect(transactionSignature).toStrictEqual(new Uint8Array([1, 2, 3])); - expect(signerB.signAndSendTransactions).toHaveBeenCalledWith([modifiedTransaction], { - abortSignal: undefined, - }); + expect(signerB.signAndSendTransactions).toHaveBeenCalledWith([modifiedTransaction], undefined /* config */); }); it('uses a composite signer as a partial signer when other sending and modifying signers exist', async () => { @@ -705,14 +705,12 @@ describe('signAndSendTransactionMessageWithSigners', () => { const transactionSignature = await signAndSendTransactionMessageWithSigners(transaction); // Then the composite signer was used as a partial signer. - expect(signerA.signTransactions).toHaveBeenCalledWith([modifiedTransaction], { abortSignal: undefined }); + expect(signerA.signTransactions).toHaveBeenCalledWith([modifiedTransaction], undefined /* config */); expect(signerA.modifyAndSignTransactions).not.toHaveBeenCalled(); expect(signerA.signAndSendTransactions).not.toHaveBeenCalled(); // And the other signers were used as expected. - expect(signerC.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], { - abortSignal: undefined, - }); + expect(signerC.modifyAndSignTransactions).toHaveBeenCalledWith([unsignedTransaction], undefined /* config */); expect(transactionSignature).toStrictEqual(new Uint8Array([1, 2, 3])); expect(signerB.signAndSendTransactions).toHaveBeenCalledWith( [ @@ -721,7 +719,7 @@ describe('signAndSendTransactionMessageWithSigners', () => { signatures: { '1111': '1111_signature', '2222': null, '3333': '3333_signature' }, }, ], - { abortSignal: undefined }, + undefined /* config */, ); }); diff --git a/packages/signers/src/sign-transaction.ts b/packages/signers/src/sign-transaction.ts index f64410a430b8..0c6c788c4481 100644 --- a/packages/signers/src/sign-transaction.ts +++ b/packages/signers/src/sign-transaction.ts @@ -17,9 +17,21 @@ import { import { getSignersFromTransactionMessage, ITransactionMessageWithSigners } from './account-signer-meta'; import { deduplicateSigners } from './deduplicate-signers'; -import { isTransactionModifyingSigner, TransactionModifyingSigner } from './transaction-modifying-signer'; -import { isTransactionPartialSigner, TransactionPartialSigner } from './transaction-partial-signer'; -import { isTransactionSendingSigner, TransactionSendingSigner } from './transaction-sending-signer'; +import { + isTransactionModifyingSigner, + TransactionModifyingSigner, + TransactionModifyingSignerConfig, +} from './transaction-modifying-signer'; +import { + isTransactionPartialSigner, + TransactionPartialSigner, + TransactionPartialSignerConfig, +} from './transaction-partial-signer'; +import { + isTransactionSendingSigner, + TransactionSendingSigner, + TransactionSendingSignerConfig, +} from './transaction-sending-signer'; import { isTransactionSigner, TransactionSigner } from './transaction-signer'; import { assertIsTransactionMessageWithSingleSendingSigner } from './transaction-with-single-sending-signer'; @@ -36,7 +48,7 @@ export async function partiallySignTransactionMessageWithSigners< TransactionMessageWithBlockhashLifetime, >( transactionMessage: TTransactionMessage, - config?: { abortSignal?: AbortSignal }, + config?: TransactionPartialSignerConfig, ): Promise; export async function partiallySignTransactionMessageWithSigners< @@ -45,21 +57,21 @@ export async function partiallySignTransactionMessageWithSigners< TransactionMessageWithDurableNonceLifetime, >( transactionMessage: TTransactionMessage, - config?: { abortSignal?: AbortSignal }, + config?: TransactionPartialSignerConfig, ): Promise>; export async function partiallySignTransactionMessageWithSigners< TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners, >( transactionMessage: TTransactionMessage, - config?: { abortSignal?: AbortSignal }, + config?: TransactionPartialSignerConfig, ): Promise>; export async function partiallySignTransactionMessageWithSigners< TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners, >( transactionMessage: TTransactionMessage, - config: { abortSignal?: AbortSignal } = {}, + config?: TransactionPartialSignerConfig, ): Promise> { const { partialSigners, modifyingSigners } = categorizeTransactionSigners( deduplicateSigners(getSignersFromTransactionMessage(transactionMessage).filter(isTransactionSigner)), @@ -70,7 +82,7 @@ export async function partiallySignTransactionMessageWithSigners< transactionMessage, modifyingSigners, partialSigners, - config.abortSignal, + config, ); } @@ -86,7 +98,7 @@ export async function signTransactionMessageWithSigners< TransactionMessageWithBlockhashLifetime, >( transactionMessage: TTransactionMessage, - config?: { abortSignal?: AbortSignal }, + config?: TransactionPartialSignerConfig, ): Promise>; export async function signTransactionMessageWithSigners< @@ -95,21 +107,21 @@ export async function signTransactionMessageWithSigners< TransactionMessageWithDurableNonceLifetime, >( transactionMessage: TTransactionMessage, - config?: { abortSignal?: AbortSignal }, + config?: TransactionPartialSignerConfig, ): Promise>; export async function signTransactionMessageWithSigners< TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners, >( transactionMessage: TTransactionMessage, - config?: { abortSignal?: AbortSignal }, + config?: TransactionPartialSignerConfig, ): Promise>; export async function signTransactionMessageWithSigners< TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners, >( transactionMessage: TTransactionMessage, - config: { abortSignal?: AbortSignal } = {}, + config?: TransactionPartialSignerConfig, ): Promise> { const signedTransaction = await partiallySignTransactionMessageWithSigners(transactionMessage, config); assertTransactionIsFullySigned(signedTransaction); @@ -124,10 +136,10 @@ export async function signTransactionMessageWithSigners< */ export async function signAndSendTransactionMessageWithSigners< TTransactionMessage extends CompilableTransactionMessageWithSigners = CompilableTransactionMessageWithSigners, ->(transaction: TTransactionMessage, config: { abortSignal?: AbortSignal } = {}): Promise { +>(transaction: TTransactionMessage, config?: TransactionSendingSignerConfig): Promise { assertIsTransactionMessageWithSingleSendingSigner(transaction); - const abortSignal = config.abortSignal; + const abortSignal = config?.abortSignal; const { partialSigners, modifyingSigners, sendingSigner } = categorizeTransactionSigners( deduplicateSigners(getSignersFromTransactionMessage(transaction).filter(isTransactionSigner)), ); @@ -137,7 +149,7 @@ export async function signAndSendTransactionMessageWithSigners< transaction, modifyingSigners, partialSigners, - abortSignal, + config, ); if (!sendingSigner) { @@ -145,7 +157,7 @@ export async function signAndSendTransactionMessageWithSigners< } abortSignal?.throwIfAborted(); - const [signature] = await sendingSigner.signAndSendTransactions([signedTransaction], { abortSignal }); + const [signature] = await sendingSigner.signAndSendTransactions([signedTransaction], config); abortSignal?.throwIfAborted(); return signature; @@ -234,7 +246,7 @@ async function signModifyingAndPartialTransactionSigners< transactionMessage: TTransactionMessage, modifyingSigners: readonly TransactionModifyingSigner[] = [], partialSigners: readonly TransactionPartialSigner[] = [], - abortSignal?: AbortSignal, + config?: TransactionModifyingSignerConfig, ): Promise> { // serialize the transaction const transaction = compileTransaction(transactionMessage); @@ -242,18 +254,18 @@ async function signModifyingAndPartialTransactionSigners< // Handle modifying signers sequentially. const modifiedTransaction = await modifyingSigners.reduce( async (transaction, modifyingSigner) => { - abortSignal?.throwIfAborted(); - const [tx] = await modifyingSigner.modifyAndSignTransactions([await transaction], { abortSignal }); + config?.abortSignal?.throwIfAborted(); + const [tx] = await modifyingSigner.modifyAndSignTransactions([await transaction], config); return Object.freeze(tx); }, Promise.resolve(transaction) as Promise>, ); // Handle partial signers in parallel. - abortSignal?.throwIfAborted(); + config?.abortSignal?.throwIfAborted(); const signatureDictionaries = await Promise.all( partialSigners.map(async partialSigner => { - const [signatures] = await partialSigner.signTransactions([modifiedTransaction], { abortSignal }); + const [signatures] = await partialSigner.signTransactions([modifiedTransaction], config); return signatures; }), ); diff --git a/packages/signers/src/transaction-modifying-signer.ts b/packages/signers/src/transaction-modifying-signer.ts index 765eeb190f08..2fede577a182 100644 --- a/packages/signers/src/transaction-modifying-signer.ts +++ b/packages/signers/src/transaction-modifying-signer.ts @@ -2,9 +2,9 @@ import { Address } from '@solana/addresses'; import { SOLANA_ERROR__SIGNER__EXPECTED_TRANSACTION_MODIFYING_SIGNER, SolanaError } from '@solana/errors'; import { Transaction } from '@solana/transactions'; -import { BaseSignerConfig } from './types'; +import { BaseTransactionSignerConfig } from './types'; -export type TransactionModifyingSignerConfig = BaseSignerConfig; +export type TransactionModifyingSignerConfig = BaseTransactionSignerConfig; /** Defines a signer capable of signing transactions. */ export type TransactionModifyingSigner = Readonly<{ diff --git a/packages/signers/src/transaction-partial-signer.ts b/packages/signers/src/transaction-partial-signer.ts index fa021f3151ce..678dad34cab5 100644 --- a/packages/signers/src/transaction-partial-signer.ts +++ b/packages/signers/src/transaction-partial-signer.ts @@ -2,9 +2,9 @@ import { Address } from '@solana/addresses'; import { SOLANA_ERROR__SIGNER__EXPECTED_TRANSACTION_PARTIAL_SIGNER, SolanaError } from '@solana/errors'; import { Transaction } from '@solana/transactions'; -import { BaseSignerConfig, SignatureDictionary } from './types'; +import { BaseTransactionSignerConfig, SignatureDictionary } from './types'; -export type TransactionPartialSignerConfig = BaseSignerConfig; +export type TransactionPartialSignerConfig = BaseTransactionSignerConfig; /** Defines a signer capable of signing transactions. */ export type TransactionPartialSigner = Readonly<{ diff --git a/packages/signers/src/transaction-sending-signer.ts b/packages/signers/src/transaction-sending-signer.ts index acfab84db736..8c91970f56df 100644 --- a/packages/signers/src/transaction-sending-signer.ts +++ b/packages/signers/src/transaction-sending-signer.ts @@ -3,9 +3,9 @@ import { SOLANA_ERROR__SIGNER__EXPECTED_TRANSACTION_SENDING_SIGNER, SolanaError import { SignatureBytes } from '@solana/keys'; import { Transaction } from '@solana/transactions'; -import { BaseSignerConfig } from './types'; +import { BaseTransactionSignerConfig } from './types'; -export type TransactionSendingSignerConfig = BaseSignerConfig; +export type TransactionSendingSignerConfig = BaseTransactionSignerConfig; /** Defines a signer capable of signing and sending transactions simultaneously. */ export type TransactionSendingSigner = Readonly<{ diff --git a/packages/signers/src/types.ts b/packages/signers/src/types.ts index 18cb7142fb8e..1a6aac6f65e7 100644 --- a/packages/signers/src/types.ts +++ b/packages/signers/src/types.ts @@ -1,6 +1,16 @@ import { Address } from '@solana/addresses'; import { SignatureBytes } from '@solana/keys'; +import { Slot } from '@solana/rpc-types'; export type SignatureDictionary = Readonly>; -export type BaseSignerConfig = Readonly<{ abortSignal?: AbortSignal }>; +export type BaseSignerConfig = Readonly<{ + abortSignal?: AbortSignal; +}>; + +export interface BaseTransactionSignerConfig extends BaseSignerConfig { + // Signers that simulate transactions (eg. wallets) might be interested in knowing which slot + // was current when the transaction was prepared. They can use this information to ensure that + // they don't run the simulation at too early a slot. + minContextSlot?: Slot; +}