From 1344fa79afeb12b9b888435105fd86a0414ec70b Mon Sep 17 00:00:00 2001 From: Callum McIntyre Date: Thu, 18 Apr 2024 07:06:25 +0100 Subject: [PATCH] Refactor transaction-confirmation to use new transaction lifetimes (#2485) * add lastValidBlockHeight to transaction blockhash lifetime * add nonceAccountAddress to Transaction durable nonce lifetime * refactor transaction-confirmation to use new lifetimes --- .../send-transaction-internal-test.ts | 36 +++++----- .../library/src/send-transaction-internal.ts | 15 ++-- packages/library/src/send-transaction.ts | 15 ++-- .../src/__tests__/sign-transaction-test.ts | 42 +++++------ .../transaction-confirmation/package.json | 5 +- .../confirmation-strategy-nonce-test.ts | 26 +++---- .../src/__tests__/waiters-test.ts | 54 ++++++-------- .../src/confirmation-strategy-nonce.ts | 10 +-- .../transaction-confirmation/src/waiters.ts | 26 +++---- .../__tests__/new-compile-transaction-test.ts | 22 +++++- .../__tests__/new-wire-transaction-test.ts | 71 +++++++++++++++++++ packages/transactions/src/index.ts | 2 + packages/transactions/src/lifetime.ts | 5 +- .../src/new-compile-transaction.ts | 3 +- .../transactions/src/new-wire-transaction.ts | 13 ++++ packages/transactions/src/wire-transaction.ts | 5 +- pnpm-lock.yaml | 3 + 17 files changed, 218 insertions(+), 135 deletions(-) create mode 100644 packages/transactions/src/__tests__/new-wire-transaction-test.ts create mode 100644 packages/transactions/src/new-wire-transaction.ts diff --git a/packages/library/src/__tests__/send-transaction-internal-test.ts b/packages/library/src/__tests__/send-transaction-internal-test.ts index 94ee6c2b2da..42afb431a30 100644 --- a/packages/library/src/__tests__/send-transaction-internal-test.ts +++ b/packages/library/src/__tests__/send-transaction-internal-test.ts @@ -3,12 +3,10 @@ import type { Rpc, SendTransactionApi } from '@solana/rpc'; import type { Commitment } from '@solana/rpc-types'; import { Base64EncodedWireTransaction, - BaseTransaction, - getBase64EncodedWireTransaction, - IDurableNonceTransaction, - IFullySignedTransaction, - ITransactionWithBlockhashLifetime, - ITransactionWithFeePayer, + FullySignedTransaction, + newGetBase64EncodedWireTransaction, + TransactionBlockhashLifetime, + TransactionDurableNonceLifetime, } from '@solana/transactions'; import { @@ -23,10 +21,9 @@ const FOREVER_PROMISE = new Promise(() => { }); describe('sendAndConfirmTransaction', () => { - const MOCK_TRANSACTION = {} as unknown as BaseTransaction & - IFullySignedTransaction & - ITransactionWithBlockhashLifetime & - ITransactionWithFeePayer; + const MOCK_TRANSACTION = {} as unknown as FullySignedTransaction & { + lifetimeConstraint: TransactionBlockhashLifetime; + }; let confirmRecentTransaction: jest.Mock; let createPendingRequest: jest.Mock; let rpc: Rpc; @@ -39,7 +36,7 @@ describe('sendAndConfirmTransaction', () => { rpc = { sendTransaction: createPendingRequest, }; - jest.mocked(getBase64EncodedWireTransaction).mockReturnValue( + jest.mocked(newGetBase64EncodedWireTransaction).mockReturnValue( 'MOCK_WIRE_TRANSACTION' as Base64EncodedWireTransaction, ); }); @@ -51,7 +48,7 @@ describe('sendAndConfirmTransaction', () => { rpc, transaction: MOCK_TRANSACTION, }); - expect(getBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_TRANSACTION); + expect(newGetBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_TRANSACTION); expect(createPendingRequest).toHaveBeenCalledWith('MOCK_WIRE_TRANSACTION', expect.anything()); }); it('calls `sendTransaction` with the expected inputs', () => { @@ -69,7 +66,7 @@ describe('sendAndConfirmTransaction', () => { rpc, transaction: MOCK_TRANSACTION, }); - expect(getBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_TRANSACTION); + expect(newGetBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_TRANSACTION); expect(createPendingRequest).toHaveBeenCalledWith('MOCK_WIRE_TRANSACTION', { ...sendTransactionConfig, encoding: 'base64', @@ -180,10 +177,9 @@ describe('sendAndConfirmTransaction', () => { }); describe('sendAndConfirmDurableNonceTransaction', () => { - const MOCK_DURABLE_NONCE_TRANSACTION = {} as unknown as BaseTransaction & - IDurableNonceTransaction & - IFullySignedTransaction & - ITransactionWithFeePayer; + const MOCK_DURABLE_NONCE_TRANSACTION = {} as unknown as FullySignedTransaction & { + lifetimeConstraint: TransactionDurableNonceLifetime; + }; let confirmDurableNonceTransaction: jest.Mock; let createPendingRequest: jest.Mock; let rpc: Rpc; @@ -196,7 +192,7 @@ describe('sendAndConfirmDurableNonceTransaction', () => { rpc = { sendTransaction: createPendingRequest, }; - jest.mocked(getBase64EncodedWireTransaction).mockReturnValue( + jest.mocked(newGetBase64EncodedWireTransaction).mockReturnValue( 'MOCK_WIRE_TRANSACTION' as Base64EncodedWireTransaction, ); }); @@ -208,7 +204,7 @@ describe('sendAndConfirmDurableNonceTransaction', () => { rpc, transaction: MOCK_DURABLE_NONCE_TRANSACTION, }); - expect(getBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_DURABLE_NONCE_TRANSACTION); + expect(newGetBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_DURABLE_NONCE_TRANSACTION); expect(createPendingRequest).toHaveBeenCalledWith('MOCK_WIRE_TRANSACTION', expect.anything()); }); it('calls `sendTransaction` with the expected inputs', () => { @@ -226,7 +222,7 @@ describe('sendAndConfirmDurableNonceTransaction', () => { rpc, transaction: MOCK_DURABLE_NONCE_TRANSACTION, }); - expect(getBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_DURABLE_NONCE_TRANSACTION); + expect(newGetBase64EncodedWireTransaction).toHaveBeenCalledWith(MOCK_DURABLE_NONCE_TRANSACTION); expect(createPendingRequest).toHaveBeenCalledWith('MOCK_WIRE_TRANSACTION', { ...sendTransactionConfig, encoding: 'base64', diff --git a/packages/library/src/send-transaction-internal.ts b/packages/library/src/send-transaction-internal.ts index acf756ae4b6..814db1fcf20 100644 --- a/packages/library/src/send-transaction-internal.ts +++ b/packages/library/src/send-transaction-internal.ts @@ -7,12 +7,17 @@ import { } from '@solana/transaction-confirmation'; import { BaseTransaction, - getBase64EncodedWireTransaction, + FullySignedTransaction, IDurableNonceTransaction, IFullySignedTransaction, ITransactionWithBlockhashLifetime, ITransactionWithFeePayer, + newGetBase64EncodedWireTransaction, } from '@solana/transactions'; +import { + TransactionBlockhashLifetime, + TransactionDurableNonceLifetime, +} from '@solana/transactions/dist/types/lifetime'; interface SendAndConfirmDurableNonceTransactionConfig extends SendTransactionBaseConfig, @@ -23,7 +28,7 @@ interface SendAndConfirmDurableNonceTransactionConfig 'getNonceInvalidationPromise' | 'getRecentSignatureConfirmationPromise' >, ) => Promise; - transaction: IDurableNonceTransaction & SendableTransaction; + transaction: FullySignedTransaction & { lifetimeConstraint: TransactionDurableNonceLifetime }; } interface SendAndConfirmTransactionWithBlockhashLifetimeConfig @@ -35,14 +40,14 @@ interface SendAndConfirmTransactionWithBlockhashLifetimeConfig 'getBlockHeightExceedencePromise' | 'getRecentSignatureConfirmationPromise' >, ) => Promise; - transaction: ITransactionWithBlockhashLifetime & SendableTransaction; + transaction: FullySignedTransaction & { lifetimeConstraint: TransactionBlockhashLifetime }; } interface SendTransactionBaseConfig extends SendTransactionConfigWithoutEncoding { abortSignal?: AbortSignal; commitment: Commitment; rpc: Rpc; - transaction: SendableTransaction; + transaction: FullySignedTransaction; } interface SendTransactionConfigWithoutEncoding @@ -84,7 +89,7 @@ export async function sendTransaction_INTERNAL_ONLY_DO_NOT_EXPORT({ transaction, ...sendTransactionConfig }: SendTransactionBaseConfig): Promise { - const base64EncodedWireTransaction = getBase64EncodedWireTransaction(transaction); + const base64EncodedWireTransaction = newGetBase64EncodedWireTransaction(transaction); return await rpc .sendTransaction(base64EncodedWireTransaction, { ...getSendTransactionConfigWithAdjustedPreflightCommitment(commitment, sendTransactionConfig), diff --git a/packages/library/src/send-transaction.ts b/packages/library/src/send-transaction.ts index 8a54ab8a507..987ceb4501a 100644 --- a/packages/library/src/send-transaction.ts +++ b/packages/library/src/send-transaction.ts @@ -13,15 +13,12 @@ import { waitForRecentTransactionConfirmation, } from '@solana/transaction-confirmation'; import { - BaseTransaction, - IDurableNonceTransaction, - IFullySignedTransaction, - ITransactionWithBlockhashLifetime, - ITransactionWithFeePayer, + FullySignedTransaction, + TransactionBlockhashLifetime, + TransactionDurableNonceLifetime, } from '@solana/transactions'; import { - SendableTransaction, sendAndConfirmDurableNonceTransaction_INTERNAL_ONLY_DO_NOT_EXPORT, sendAndConfirmTransactionWithBlockhashLifetime_INTERNAL_ONLY_DO_NOT_EXPORT, sendTransaction_INTERNAL_ONLY_DO_NOT_EXPORT, @@ -42,7 +39,7 @@ interface SendTransactionWithoutConfirmingFactoryConfig { } type SendAndConfirmTransactionWithBlockhashLifetimeFunction = ( - transaction: ITransactionWithBlockhashLifetime & SendableTransaction, + transaction: FullySignedTransaction & { lifetimeConstraint: TransactionBlockhashLifetime }, config: Omit< Parameters[0], 'confirmRecentTransaction' | 'rpc' | 'transaction' @@ -50,7 +47,7 @@ type SendAndConfirmTransactionWithBlockhashLifetimeFunction = ( ) => Promise; type SendAndConfirmDurableNonceTransactionFunction = ( - transaction: BaseTransaction & IDurableNonceTransaction & IFullySignedTransaction & ITransactionWithFeePayer, + transaction: FullySignedTransaction & { lifetimeConstraint: TransactionDurableNonceLifetime }, config: Omit< Parameters[0], 'confirmDurableNonceTransaction' | 'rpc' | 'transaction' @@ -58,7 +55,7 @@ type SendAndConfirmDurableNonceTransactionFunction = ( ) => Promise; type SendTransactionWithoutConfirmingFunction = ( - transaction: SendableTransaction, + transaction: FullySignedTransaction, config: Omit[0], 'rpc' | 'transaction'>, ) => Promise; diff --git a/packages/signers/src/__tests__/sign-transaction-test.ts b/packages/signers/src/__tests__/sign-transaction-test.ts index c7fec019325..8eb2d6b81b7 100644 --- a/packages/signers/src/__tests__/sign-transaction-test.ts +++ b/packages/signers/src/__tests__/sign-transaction-test.ts @@ -47,7 +47,7 @@ describe('partiallySignTransactionWithSigners', () => { const signerB = createMockTransactionPartialSigner('2222' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -98,7 +98,7 @@ describe('partiallySignTransactionWithSigners', () => { // And given a transaction that contains theses signers in its account metas (in any order). const transactionMessage = createMockTransactionMessageWithSigners([signerB, signerA]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -144,7 +144,7 @@ describe('partiallySignTransactionWithSigners', () => { // And given a transaction that contains theses two signers in its account metas. const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -188,7 +188,7 @@ describe('partiallySignTransactionWithSigners', () => { // And given a transaction that contains theses two signers in its account metas. const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -222,7 +222,7 @@ describe('partiallySignTransactionWithSigners', () => { }; const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -257,7 +257,7 @@ describe('partiallySignTransactionWithSigners', () => { const signerB = createMockTransactionPartialSigner('2222' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -299,7 +299,7 @@ describe('partiallySignTransactionWithSigners', () => { const signerB = createMockTransactionModifyingSigner('2222' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -337,7 +337,7 @@ describe('partiallySignTransactionWithSigners', () => { const signer = createMockTransactionPartialSigner('1111' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signer]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -364,7 +364,7 @@ describe('partiallySignTransactionWithSigners', () => { signer.signTransactions.mockResolvedValueOnce([{ '1111': '1111_signature' }]); const transactionMessage = createMockTransactionMessageWithSigners([signer]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -392,7 +392,7 @@ describe('partiallySignTransactionWithSigners', () => { // Given a transaction const transactionMessage = createMockTransactionMessageWithSigners([]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: {}, }; @@ -415,7 +415,7 @@ describe('signTransactionWithSigners', () => { const signerB = createMockTransactionPartialSigner('2222' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -456,7 +456,7 @@ describe('signTransactionWithSigners', () => { const signerB = createMockTransactionSendingSigner('2222' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -488,7 +488,7 @@ describe('signTransactionWithSigners', () => { signer.signTransactions.mockResolvedValueOnce([{ '1111': '1111_signature' }]); const transactionMessage = createMockTransactionMessageWithSigners([signer]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -516,7 +516,7 @@ describe('signTransactionWithSigners', () => { // Given a transaction const transactionMessage = createMockTransactionMessageWithSigners([]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: {}, }; @@ -539,7 +539,7 @@ describe('signAndSendTransactionWithSigners', () => { const signerB = createMockTransactionSendingSigner('2222' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -575,7 +575,7 @@ describe('signAndSendTransactionWithSigners', () => { signer.signTransactions.mockResolvedValueOnce([{ '1111': '1111_signature' }]); const transactionMessage = createMockTransactionMessageWithSigners([signer]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -603,7 +603,7 @@ describe('signAndSendTransactionWithSigners', () => { const signerB = createMockTransactionPartialSigner('2222' as Address); const transactionMessage = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -639,7 +639,7 @@ describe('signAndSendTransactionWithSigners', () => { const signerB = createMockTransactionSendingSigner('2222' as Address); const transaction = createMockTransactionMessageWithSigners([signerA, signerB]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -681,7 +681,7 @@ describe('signAndSendTransactionWithSigners', () => { const signerC = createMockTransactionModifyingSigner('3333' as Address); const transaction = createMockTransactionMessageWithSigners([signerA, signerB, signerC]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -733,7 +733,7 @@ describe('signAndSendTransactionWithSigners', () => { signer.signAndSendTransactions.mockResolvedValueOnce([new Uint8Array([1, 2, 3])]); const transactionMessage = createMockTransactionMessageWithSigners([signer]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, @@ -763,7 +763,7 @@ describe('signAndSendTransactionWithSigners', () => { signer.signAndSendTransactions.mockResolvedValueOnce([new Uint8Array([1, 2, 3])]); const transactionMessage = createMockTransactionMessageWithSigners([signer]); const unsignedTransaction: NewTransaction & TransactionWithLifetime = { - lifetimeConstraint: { blockhash: 'a' as Blockhash }, + lifetimeConstraint: { blockhash: 'a' as Blockhash, lastValidBlockHeight: 1n }, messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['1111' as Address]: null, diff --git a/packages/transaction-confirmation/package.json b/packages/transaction-confirmation/package.json index 7415b947d9b..30c7b048a0f 100644 --- a/packages/transaction-confirmation/package.json +++ b/packages/transaction-confirmation/package.json @@ -69,10 +69,11 @@ "@solana/codecs-strings": "workspace:*", "@solana/errors": "workspace:*", "@solana/keys": "workspace:*", - "@solana/transactions": "workspace:*", "@solana/rpc": "workspace:*", "@solana/rpc-subscriptions": "workspace:*", - "@solana/rpc-types": "workspace:*" + "@solana/rpc-types": "workspace:*", + "@solana/transaction-messages": "workspace:*", + "@solana/transactions": "workspace:*" }, "devDependencies": { "@solana/instructions": "workspace:*" diff --git a/packages/transaction-confirmation/src/__tests__/confirmation-strategy-nonce-test.ts b/packages/transaction-confirmation/src/__tests__/confirmation-strategy-nonce-test.ts index 654a3bee793..05ad745655b 100644 --- a/packages/transaction-confirmation/src/__tests__/confirmation-strategy-nonce-test.ts +++ b/packages/transaction-confirmation/src/__tests__/confirmation-strategy-nonce-test.ts @@ -1,7 +1,7 @@ import { Address } from '@solana/addresses'; import { getBase58Encoder, getBase64Decoder } from '@solana/codecs-strings'; import { SOLANA_ERROR__INVALID_NONCE, SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND, SolanaError } from '@solana/errors'; -import { Nonce } from '@solana/transactions'; +import { NewNonce } from '@solana/transaction-messages'; import { createNonceInvalidationPromiseFactory } from '../confirmation-strategy-nonce'; @@ -10,7 +10,7 @@ const FOREVER_PROMISE = new Promise(() => { }); describe('createNonceInvalidationPromiseFactory', () => { - function getBase64EncodedNonceAccountData(nonceValue: Nonce) { + function getBase64EncodedNonceAccountData(nonceValue: NewNonce) { // This is mostly fake; we just put the nonce value in the correct spot in the byte buffer // without actually implementing the rest. const NONCE_VALUE_OFFSET = @@ -56,7 +56,7 @@ describe('createNonceInvalidationPromiseFactory', () => { getNonceInvalidationPromise({ abortSignal: abortController.signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await jest.runAllTimersAsync(); @@ -73,7 +73,7 @@ describe('createNonceInvalidationPromiseFactory', () => { getNonceInvalidationPromise({ abortSignal: abortController.signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); expect(createSubscriptionIterable).toHaveBeenCalledWith({ @@ -89,7 +89,7 @@ describe('createNonceInvalidationPromiseFactory', () => { getNonceInvalidationPromise({ abortSignal: new AbortController().signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await jest.runAllTimersAsync(); @@ -110,7 +110,7 @@ describe('createNonceInvalidationPromiseFactory', () => { getNonceInvalidationPromise({ abortSignal: new AbortController().signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await jest.runAllTimersAsync(); @@ -134,7 +134,7 @@ describe('createNonceInvalidationPromiseFactory', () => { const invalidationPromise = getNonceInvalidationPromise({ abortSignal: new AbortController().signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await jest.runAllTimersAsync(); @@ -146,7 +146,7 @@ describe('createNonceInvalidationPromiseFactory', () => { const invalidationPromise = getNonceInvalidationPromise({ abortSignal: new AbortController().signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await expect(invalidationPromise).rejects.toThrow( @@ -165,7 +165,7 @@ describe('createNonceInvalidationPromiseFactory', () => { const invalidationPromise = getNonceInvalidationPromise({ abortSignal: new AbortController().signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await expect(invalidationPromise).rejects.toThrow( @@ -180,7 +180,7 @@ describe('createNonceInvalidationPromiseFactory', () => { accountNotificationGenerator.mockImplementation(async function* () { yield { value: { - data: getBase64EncodedNonceAccountData('4'.repeat(44) as Nonce), + data: getBase64EncodedNonceAccountData('4'.repeat(44) as NewNonce), }, }; yield FOREVER_PROMISE; @@ -188,7 +188,7 @@ describe('createNonceInvalidationPromiseFactory', () => { const invalidationPromise = getNonceInvalidationPromise({ abortSignal: new AbortController().signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await jest.runAllTimersAsync(); @@ -197,13 +197,13 @@ describe('createNonceInvalidationPromiseFactory', () => { it('fatals when the nonce value returned by the account subscription is different than the expected one', async () => { expect.assertions(1); accountNotificationGenerator.mockImplementation(async function* () { - yield { value: { data: getBase64EncodedNonceAccountData('5'.repeat(44) as Nonce) } }; + yield { value: { data: getBase64EncodedNonceAccountData('5'.repeat(44) as NewNonce) } }; yield FOREVER_PROMISE; }); const invalidationPromise = getNonceInvalidationPromise({ abortSignal: new AbortController().signal, commitment: 'finalized', - currentNonceValue: '4'.repeat(44) as Nonce, + currentNonceValue: '4'.repeat(44) as NewNonce, nonceAccountAddress: '9'.repeat(44) as Address, }); await expect(invalidationPromise).rejects.toThrow( diff --git a/packages/transaction-confirmation/src/__tests__/waiters-test.ts b/packages/transaction-confirmation/src/__tests__/waiters-test.ts index f1b38c61216..055146b06b5 100644 --- a/packages/transaction-confirmation/src/__tests__/waiters-test.ts +++ b/packages/transaction-confirmation/src/__tests__/waiters-test.ts @@ -1,10 +1,15 @@ import { Address } from '@solana/addresses'; import { SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING, SolanaError } from '@solana/errors'; -import { AccountRole, ReadonlySignerAccount, WritableAccount } from '@solana/instructions'; import { Signature, SignatureBytes } from '@solana/keys'; import type { Blockhash } from '@solana/rpc-types'; -import { IDurableNonceTransaction, Nonce } from '@solana/transactions'; +import { NewNonce } from '@solana/transaction-messages'; +import { NewTransaction, TransactionMessageBytes } from '@solana/transactions'; +import { + TransactionBlockhashLifetime, + TransactionDurableNonceLifetime, +} from '@solana/transactions/dist/types/lifetime'; +import { ReadonlyUint8Array } from '../../../codecs-core/dist/types'; import { waitForDurableNonceTransactionConfirmation, waitForRecentTransactionConfirmation, @@ -16,32 +21,13 @@ const FOREVER_PROMISE = new Promise(() => { }); describe('waitForDurableNonceTransactionConfirmation', () => { - const MOCK_DURABLE_NONCE_TRANSACTION = { - feePayer: '9'.repeat(44) as Address, - instructions: [ - // Mock AdvanceNonce instruction. - { - accounts: [ - { address: '5'.repeat(44), role: AccountRole.WRITABLE } as WritableAccount, - { - address: - 'SysvarRecentB1ockHashes11111111111111111111' as Address<'SysvarRecentB1ockHashes11111111111111111111'>, - role: AccountRole.READONLY, - }, - { - address: '6'.repeat(44), - role: AccountRole.READONLY_SIGNER, - } as ReadonlySignerAccount, - ], - data: new Uint8Array([4, 0, 0, 0]) as IDurableNonceTransaction['instructions'][0]['data'], - programAddress: '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>, - }, - ], - lifetimeConstraint: { nonce: 'xyz' as Nonce }, + const MOCK_DURABLE_NONCE_TRANSACTION: NewTransaction & { lifetimeConstraint: TransactionDurableNonceLifetime } = { + lifetimeConstraint: { nonce: 'xyz' as NewNonce, nonceAccountAddress: '5'.repeat(44) as Address }, + messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['9'.repeat(44) as Address]: new Uint8Array(new Array(64).fill(0)) as SignatureBytes, - } as const, - } as const; + }, + }; let getNonceInvalidationPromise: jest.Mock>; let getRecentSignatureConfirmationPromise: jest.Mock>; beforeEach(() => { @@ -129,8 +115,8 @@ describe('waitForDurableNonceTransactionConfirmation', () => { const transactionWithoutFeePayerSignature = { ...MOCK_DURABLE_NONCE_TRANSACTION, signatures: { - // Signature by someone other than the fee payer. - ['456' as Address]: new Uint8Array(new Array(64).fill(0)) as SignatureBytes, + // No signature for fee payer. + ['9'.repeat(44) as Address]: null, } as const, }; const commitmentPromise = waitForDurableNonceTransactionConfirmation({ @@ -184,13 +170,13 @@ describe('waitForDurableNonceTransactionConfirmation', () => { }); describe('waitForRecentTransactionConfirmation', () => { - const MOCK_TRANSACTION = { - feePayer: '9'.repeat(44) as Address, + const MOCK_TRANSACTION: NewTransaction & { lifetimeConstraint: TransactionBlockhashLifetime } = { lifetimeConstraint: { blockhash: '4'.repeat(44) as Blockhash, lastValidBlockHeight: 123n }, + messageBytes: new Uint8Array() as ReadonlyUint8Array as TransactionMessageBytes, signatures: { ['9'.repeat(44) as Address]: new Uint8Array(new Array(64).fill(0)) as SignatureBytes, - } as const, - } as const; + }, + }; let getBlockHeightExceedencePromise: jest.Mock>; let getRecentSignatureConfirmationPromise: jest.Mock>; beforeEach(() => { @@ -243,8 +229,8 @@ describe('waitForRecentTransactionConfirmation', () => { const transactionWithoutFeePayerSignature = { ...MOCK_TRANSACTION, signatures: { - // Signature by someone other than the fee payer. - ['456' as Address]: new Uint8Array(new Array(64).fill(0)) as SignatureBytes, + // No signature for fee payer + ['9'.repeat(44) as Address]: null, } as const, }; const commitmentPromise = waitForRecentTransactionConfirmation({ diff --git a/packages/transaction-confirmation/src/confirmation-strategy-nonce.ts b/packages/transaction-confirmation/src/confirmation-strategy-nonce.ts index 31e23ad94b0..d1c4b593874 100644 --- a/packages/transaction-confirmation/src/confirmation-strategy-nonce.ts +++ b/packages/transaction-confirmation/src/confirmation-strategy-nonce.ts @@ -4,12 +4,12 @@ import { SOLANA_ERROR__INVALID_NONCE, SOLANA_ERROR__NONCE_ACCOUNT_NOT_FOUND, Sol import type { GetAccountInfoApi, Rpc } from '@solana/rpc'; import type { AccountNotificationsApi, RpcSubscriptions } from '@solana/rpc-subscriptions'; import type { Base64EncodedDataResponse, Commitment } from '@solana/rpc-types'; -import type { Nonce } from '@solana/transactions'; +import { NewNonce } from '@solana/transaction-messages'; type GetNonceInvalidationPromiseFn = (config: { abortSignal: AbortSignal; commitment: Commitment; - currentNonceValue: Nonce; + currentNonceValue: NewNonce; nonceAccountAddress: Address; }) => Promise; @@ -42,10 +42,10 @@ export function createNonceInvalidationPromiseFactory( .subscribe({ abortSignal: abortController.signal }); const base58Decoder = getBase58Decoder(); const base64Encoder = getBase64Encoder(); - function getNonceFromAccountData([base64EncodedBytes]: Base64EncodedDataResponse): Nonce { + function getNonceFromAccountData([base64EncodedBytes]: Base64EncodedDataResponse): NewNonce { const data = base64Encoder.encode(base64EncodedBytes); const nonceValueBytes = data.slice(NONCE_VALUE_OFFSET, NONCE_VALUE_OFFSET + 32); - return base58Decoder.decode(nonceValueBytes) as Nonce; + return base58Decoder.decode(nonceValueBytes) as NewNonce; } const nonceAccountDidAdvancePromise = (async () => { for await (const accountNotification of accountNotifications) { @@ -78,7 +78,7 @@ export function createNonceInvalidationPromiseFactory( const nonceValue = // This works because we asked for the exact slice of data representing the nonce // value, and furthermore asked for it in `base58` encoding. - nonceAccount.data[0] as unknown as Nonce; + nonceAccount.data[0] as unknown as NewNonce; if (nonceValue !== expectedNonceValue) { throw new SolanaError(SOLANA_ERROR__INVALID_NONCE, { actualNonceValue: nonceValue, diff --git a/packages/transaction-confirmation/src/waiters.ts b/packages/transaction-confirmation/src/waiters.ts index b236a3d2528..f2f55707765 100644 --- a/packages/transaction-confirmation/src/waiters.ts +++ b/packages/transaction-confirmation/src/waiters.ts @@ -1,11 +1,9 @@ import { Signature } from '@solana/keys'; -import type { Slot } from '@solana/rpc-types'; +import { newGetSignatureFromTransaction, NewTransaction } from '@solana/transactions'; import { - getSignatureFromTransaction, - IDurableNonceTransaction, - ITransactionWithFeePayer, - ITransactionWithSignatures, -} from '@solana/transactions'; + TransactionBlockhashLifetime, + TransactionDurableNonceLifetime, +} from '@solana/transactions/dist/types/lifetime'; import { createBlockHeightExceedencePromiseFactory } from './confirmation-strategy-blockheight'; import { createNonceInvalidationPromiseFactory } from './confirmation-strategy-nonce'; @@ -14,19 +12,13 @@ import { getTimeoutPromise } from './confirmation-strategy-timeout'; interface WaitForDurableNonceTransactionConfirmationConfig extends BaseTransactionConfirmationStrategyConfig { getNonceInvalidationPromise: ReturnType; - transaction: IDurableNonceTransaction & ITransactionWithFeePayer & ITransactionWithSignatures; + transaction: NewTransaction & { lifetimeConstraint: TransactionDurableNonceLifetime }; } interface WaitForRecentTransactionWithBlockhashLifetimeConfirmationConfig extends BaseTransactionConfirmationStrategyConfig { getBlockHeightExceedencePromise: ReturnType; - transaction: ITransactionWithFeePayer & - ITransactionWithSignatures & - Readonly<{ - lifetimeConstraint: { - lastValidBlockHeight: Slot; - }; - }>; + transaction: NewTransaction & { lifetimeConstraint: TransactionBlockhashLifetime }; } interface WaitForRecentTransactionWithTimeBasedLifetimeConfirmationConfig @@ -39,7 +31,7 @@ export async function waitForDurableNonceTransactionConfirmation( config: WaitForDurableNonceTransactionConfirmationConfig, ): Promise { await raceStrategies( - getSignatureFromTransaction(config.transaction), + newGetSignatureFromTransaction(config.transaction), config, function getSpecificStrategiesForRace({ abortSignal, commitment, getNonceInvalidationPromise, transaction }) { return [ @@ -47,7 +39,7 @@ export async function waitForDurableNonceTransactionConfirmation( abortSignal, commitment, currentNonceValue: transaction.lifetimeConstraint.nonce, - nonceAccountAddress: transaction.instructions[0].accounts[0].address, + nonceAccountAddress: transaction.lifetimeConstraint.nonceAccountAddress, }), ]; }, @@ -58,7 +50,7 @@ export async function waitForRecentTransactionConfirmation( config: WaitForRecentTransactionWithBlockhashLifetimeConfirmationConfig, ): Promise { await raceStrategies( - getSignatureFromTransaction(config.transaction), + newGetSignatureFromTransaction(config.transaction), config, function getSpecificStrategiesForRace({ abortSignal, diff --git a/packages/transactions/src/__tests__/new-compile-transaction-test.ts b/packages/transactions/src/__tests__/new-compile-transaction-test.ts index 360071b2f6a..147c0fa9b48 100644 --- a/packages/transactions/src/__tests__/new-compile-transaction-test.ts +++ b/packages/transactions/src/__tests__/new-compile-transaction-test.ts @@ -42,7 +42,10 @@ describe('compileTransactionMessage', () => { }); const emptyMockTransactionMessage = { - lifetimeConstraint: { blockhash: 'b' as Blockhash }, + lifetimeConstraint: { + blockhash: '4'.repeat(44) as Blockhash, + lastValidBlockHeight: 1n, + }, } as TransactionMessage; it('compiles the supplied `TransactionMessage` and sets the `messageBytes` property to the result', () => { @@ -80,16 +83,29 @@ describe('compileTransactionMessage', () => { const transaction = compileTransaction(transactionMessage); expect(transaction.lifetimeConstraint).toStrictEqual({ blockhash: 'D5vmAVFNZFaBBZNJ17tMaVrcsQ9DZViL9bAZn1n1Kxer' as Blockhash, + lastValidBlockHeight: 1n, }); }); it('returns a durable nonce lifetime constraint when the transaction message has a nonce constraint', () => { const transactionMessage = { + instructions: [ + { + accounts: [ + { + address: 'nonceAddress' as Address, + }, + ], + }, + ], lifetimeConstraint: { nonce: 'b' as NewNonce, }, - } as TransactionMessage; + } as unknown as TransactionMessage; const transaction = compileTransaction(transactionMessage); - expect(transaction.lifetimeConstraint).toStrictEqual({ nonce: 'b' as NewNonce }); + expect(transaction.lifetimeConstraint).toStrictEqual({ + nonce: 'b' as NewNonce, + nonceAccountAddress: 'nonceAddress', + }); }); }); diff --git a/packages/transactions/src/__tests__/new-wire-transaction-test.ts b/packages/transactions/src/__tests__/new-wire-transaction-test.ts new file mode 100644 index 00000000000..edbaf906e2a --- /dev/null +++ b/packages/transactions/src/__tests__/new-wire-transaction-test.ts @@ -0,0 +1,71 @@ +import { Address } from '@solana/addresses'; +import { ReadonlyUint8Array } from '@solana/codecs-core'; +import { SignatureBytes } from '@solana/keys'; + +import { newGetBase64EncodedWireTransaction } from '../new-wire-transaction'; +import { NewTransaction, TransactionMessageBytes } from '../transaction'; + +describe('getBase64EncodedWireTransaction', () => { + it('serializes a transaction to wire format', () => { + // Based on the following transaction message: + /* + { + feePayer: '22222222222222222222222222222222222222222222' as Address, + instructions: [ + { + accounts: [ + { + address: '44444444444444444444444444444444444444444444' as Address, + role: AccountRole.READONLY_SIGNER, + }, + ], + programAddress: '55555555555555555555555555555555555555555555' as Address, + }, + ], + lifetimeConstraint: { + blockhash: '33333333333333333333333333333333333333333333' as Blockhash, + lastValidBlockHeight: 123n, + }, + signatures: { + // No signature for fee payer + ['44444444444444444444444444444444444444444444' as Address]: + // Base58 encoded signature: `3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333` + new Uint8Array([ + 0x65, 0xc9, 0xfa, 0x89, 0xe6, 0xab, 0xdb, 0x8b, 0x62, 0x79, 0xaf, 0x58, 0x43, 0x82, 0x48, 0xa6, + 0x7f, 0xbc, 0x40, 0xb2, 0x37, 0x06, 0x76, 0xe0, 0xed, 0xa6, 0xef, 0x73, 0x7d, 0x39, 0xfc, 0x30, + 0x6c, 0x80, 0x80, 0xc0, 0x66, 0x2d, 0x32, 0x7a, 0x56, 0xb5, 0xb9, 0xd3, 0xc1, 0x20, 0xd7, 0x15, + 0xa4, 0x34, 0x3f, 0x93, 0x8a, 0x23, 0xee, 0x08, 0xfb, 0x82, 0x3e, 0xe0, 0x8f, 0xb8, 0x23, 0xee, + ]) as SignatureBytes, + }, + version: 0, + } + */ + + const transaction: NewTransaction = { + messageBytes: new Uint8Array([ + 128, 2, 1, 1, 3, 15, 30, 107, 20, 33, 192, 74, 7, 4, 49, 38, 92, 25, 197, 187, 238, 25, 146, 186, 232, + 175, 209, 205, 7, 142, 248, 175, 112, 71, 220, 17, 247, 45, 91, 65, 60, 101, 64, 222, 21, 12, 147, 115, + 20, 77, 81, 51, 202, 76, 184, 48, 186, 15, 117, 103, 22, 172, 234, 14, 80, 215, 148, 53, 229, 60, 121, + 172, 80, 135, 1, 40, 28, 16, 196, 153, 112, 103, 22, 239, 184, 102, 74, 235, 162, 191, 71, 52, 30, 59, + 226, 189, 193, 31, 112, 71, 220, 30, 60, 214, 40, 67, 128, 148, 14, 8, 98, 76, 184, 51, 139, 119, 220, + 51, 37, 117, 209, 95, 163, 154, 15, 29, 241, 94, 224, 143, 184, 35, 238, 1, 2, 1, 1, 0, 0, + ]) as ReadonlyUint8Array as TransactionMessageBytes, + signatures: { + ['22222222222222222222222222222222222222222222' as Address]: null, + // Base58 encoded signature: `3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333` + ['44444444444444444444444444444444444444444444' as Address]: new Uint8Array([ + 0x65, 0xc9, 0xfa, 0x89, 0xe6, 0xab, 0xdb, 0x8b, 0x62, 0x79, 0xaf, 0x58, 0x43, 0x82, 0x48, 0xa6, + 0x7f, 0xbc, 0x40, 0xb2, 0x37, 0x06, 0x76, 0xe0, 0xed, 0xa6, 0xef, 0x73, 0x7d, 0x39, 0xfc, 0x30, + 0x6c, 0x80, 0x80, 0xc0, 0x66, 0x2d, 0x32, 0x7a, 0x56, 0xb5, 0xb9, 0xd3, 0xc1, 0x20, 0xd7, 0x15, + 0xa4, 0x34, 0x3f, 0x93, 0x8a, 0x23, 0xee, 0x08, 0xfb, 0x82, 0x3e, 0xe0, 0x8f, 0xb8, 0x23, 0xee, + ]) as SignatureBytes, + }, + }; + + expect(newGetBase64EncodedWireTransaction(transaction)) + // Copy and paste this string into the Solana Explorer at https://explorer.solana.com/tx/inspector + .toBe( + 'AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlyfqJ5qvbi2J5r1hDgkimf7xAsjcGduDtpu9zfTn8MGyAgMBmLTJ6VrW508Eg1xWkND+TiiPuCPuCPuCPuCPugAIBAQMPHmsUIcBKBwQxJlwZxbvuGZK66K/RzQeO+K9wR9wR9y1bQTxlQN4VDJNzFE1RM8pMuDC6D3VnFqzqDlDXlDXlPHmsUIcBKBwQxJlwZxbvuGZK66K/RzQeO+K9wR9wR9wePNYoQ4CUDghiTLgzi3fcMyV10V+jmg8d8V7gj7gj7gECAQEAAA==', + ); + }); +}); diff --git a/packages/transactions/src/index.ts b/packages/transactions/src/index.ts index 2fdc87aaa9e..af574ed46b7 100644 --- a/packages/transactions/src/index.ts +++ b/packages/transactions/src/index.ts @@ -5,9 +5,11 @@ export * from './create-transaction'; export * from './durable-nonce'; export * from './fee-payer'; export * from './instructions'; +export * from './lifetime'; export * from './message'; export * from './new-compile-transaction'; export * from './new-signatures'; +export * from './new-wire-transaction'; export * from './serializers'; export * from './signatures'; export * from './transaction'; diff --git a/packages/transactions/src/lifetime.ts b/packages/transactions/src/lifetime.ts index e880b55d923..a3aaeb996da 100644 --- a/packages/transactions/src/lifetime.ts +++ b/packages/transactions/src/lifetime.ts @@ -1,12 +1,15 @@ -import { Blockhash } from '@solana/rpc-types'; +import { Address } from '@solana/addresses'; +import { Blockhash, Slot } from '@solana/rpc-types'; import { NewNonce } from '@solana/transaction-messages'; export type TransactionBlockhashLifetime = { blockhash: Blockhash; + lastValidBlockHeight: Slot; }; export type TransactionDurableNonceLifetime = { nonce: NewNonce; + nonceAccountAddress: Address; }; export type TransactionWithLifetime = { diff --git a/packages/transactions/src/new-compile-transaction.ts b/packages/transactions/src/new-compile-transaction.ts index ead520936cd..10e3f9dc347 100644 --- a/packages/transactions/src/new-compile-transaction.ts +++ b/packages/transactions/src/new-compile-transaction.ts @@ -40,14 +40,15 @@ export function compileTransaction( } let lifetimeConstraint: TransactionWithLifetime['lifetimeConstraint']; - console.log({ transactionMessage }); if (isTransactionMessageWithBlockhashLifetime(transactionMessage)) { lifetimeConstraint = { blockhash: transactionMessage.lifetimeConstraint.blockhash, + lastValidBlockHeight: transactionMessage.lifetimeConstraint.lastValidBlockHeight, }; } else { lifetimeConstraint = { nonce: transactionMessage.lifetimeConstraint.nonce, + nonceAccountAddress: transactionMessage.instructions[0].accounts[0].address, }; } diff --git a/packages/transactions/src/new-wire-transaction.ts b/packages/transactions/src/new-wire-transaction.ts new file mode 100644 index 00000000000..c152ac7563a --- /dev/null +++ b/packages/transactions/src/new-wire-transaction.ts @@ -0,0 +1,13 @@ +import { getBase64Decoder } from '@solana/codecs-strings'; + +import { getNewTransactionEncoder } from './codecs'; +import { NewTransaction } from './transaction'; + +export type Base64EncodedWireTransaction = string & { + readonly __brand: unique symbol; +}; + +export function newGetBase64EncodedWireTransaction(transaction: NewTransaction): Base64EncodedWireTransaction { + const wireTransactionBytes = getNewTransactionEncoder().encode(transaction); + return getBase64Decoder().decode(wireTransactionBytes) as Base64EncodedWireTransaction; +} diff --git a/packages/transactions/src/wire-transaction.ts b/packages/transactions/src/wire-transaction.ts index 5ef6a36097e..94c8c9bb5a9 100644 --- a/packages/transactions/src/wire-transaction.ts +++ b/packages/transactions/src/wire-transaction.ts @@ -1,11 +1,8 @@ import { getBase64Decoder } from '@solana/codecs-strings'; +import { Base64EncodedWireTransaction } from './new-wire-transaction'; import { getTransactionEncoder } from './serializers/transaction'; -export type Base64EncodedWireTransaction = string & { - readonly __brand: unique symbol; -}; - export function getBase64EncodedWireTransaction( transaction: Parameters['encode']>[0], ): Base64EncodedWireTransaction { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8327fc0d75..84eb8f1e317 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -959,6 +959,9 @@ importers: '@solana/rpc-types': specifier: workspace:* version: link:../rpc-types + '@solana/transaction-messages': + specifier: workspace:* + version: link:../transaction-messages '@solana/transactions': specifier: workspace:* version: link:../transactions