From b8f006d6fe1837231b854ea8d3054ac6a630f305 Mon Sep 17 00:00:00 2001 From: Callum McIntyre Date: Thu, 18 Apr 2024 08:41:04 +0100 Subject: [PATCH] Delete lots of transactions code! (#2494) --- .../src/__tests__/blockhash-test.ts | 162 ----- .../src/__tests__/compile-transaction-test.ts | 63 -- .../src/__tests__/create-transaction-test.ts | 22 - .../src/__tests__/durable-nonce-test.ts | 284 -------- .../src/__tests__/fee-payer-test.ts | 67 -- .../src/__tests__/instructions-test.ts | 158 ----- .../src/__tests__/message-test.ts | 119 ---- .../src/__tests__/new-signatures-test.ts | 2 - .../src/__tests__/signatures-test.ts | 611 ------------------ .../src/__tests__/wire-transaction-test.ts | 46 -- packages/transactions/src/blockhash.ts | 72 --- .../src/compilable-transaction.ts | 13 - .../transactions/src/compile-transaction.ts | 30 - .../transactions/src/create-transaction.ts | 19 - packages/transactions/src/durable-nonce.ts | 204 ------ packages/transactions/src/fee-payer.ts | 41 -- packages/transactions/src/index.ts | 2 - packages/transactions/src/instructions.ts | 43 -- packages/transactions/src/lifetime.ts | 6 - packages/transactions/src/message.ts | 54 -- .../src/serializers/__tests__/message-test.ts | 353 ---------- .../serializers/__tests__/transaction-test.ts | 265 -------- .../transactions/src/serializers/index.ts | 2 - .../transactions/src/serializers/message.ts | 120 ---- .../src/serializers/transaction.ts | 97 --- packages/transactions/src/signatures.ts | 93 --- packages/transactions/src/types.ts | 19 - .../transactions/src/unsigned-transaction.ts | 16 - packages/transactions/src/wire-transaction.ts | 11 - 29 files changed, 2994 deletions(-) delete mode 100644 packages/transactions/src/__tests__/blockhash-test.ts delete mode 100644 packages/transactions/src/__tests__/compile-transaction-test.ts delete mode 100644 packages/transactions/src/__tests__/create-transaction-test.ts delete mode 100644 packages/transactions/src/__tests__/durable-nonce-test.ts delete mode 100644 packages/transactions/src/__tests__/fee-payer-test.ts delete mode 100644 packages/transactions/src/__tests__/instructions-test.ts delete mode 100644 packages/transactions/src/__tests__/message-test.ts delete mode 100644 packages/transactions/src/__tests__/signatures-test.ts delete mode 100644 packages/transactions/src/__tests__/wire-transaction-test.ts delete mode 100644 packages/transactions/src/blockhash.ts delete mode 100644 packages/transactions/src/compilable-transaction.ts delete mode 100644 packages/transactions/src/compile-transaction.ts delete mode 100644 packages/transactions/src/create-transaction.ts delete mode 100644 packages/transactions/src/durable-nonce.ts delete mode 100644 packages/transactions/src/fee-payer.ts delete mode 100644 packages/transactions/src/instructions.ts delete mode 100644 packages/transactions/src/message.ts delete mode 100644 packages/transactions/src/serializers/__tests__/message-test.ts delete mode 100644 packages/transactions/src/serializers/__tests__/transaction-test.ts delete mode 100644 packages/transactions/src/serializers/index.ts delete mode 100644 packages/transactions/src/serializers/message.ts delete mode 100644 packages/transactions/src/serializers/transaction.ts delete mode 100644 packages/transactions/src/signatures.ts delete mode 100644 packages/transactions/src/types.ts delete mode 100644 packages/transactions/src/unsigned-transaction.ts delete mode 100644 packages/transactions/src/wire-transaction.ts diff --git a/packages/transactions/src/__tests__/blockhash-test.ts b/packages/transactions/src/__tests__/blockhash-test.ts deleted file mode 100644 index 107ba5f22d13..000000000000 --- a/packages/transactions/src/__tests__/blockhash-test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import '@solana/test-matchers/toBeFrozenObject'; - -import { getBase58Encoder } from '@solana/codecs-strings'; -import type { Blockhash } from '@solana/rpc-types'; - -import { - assertIsTransactionWithBlockhashLifetime, - ITransactionWithBlockhashLifetime, - setTransactionLifetimeUsingBlockhash, -} from '../blockhash'; -import { ITransactionWithSignatures } from '../signatures'; -import { BaseTransaction } from '../types'; - -jest.mock('@solana/codecs-strings', () => ({ - ...jest.requireActual('@solana/codecs-strings'), - getBase58Encoder: jest.fn(), -})); - -// real implementations -const originalBase58Module = jest.requireActual('@solana/codecs-strings'); -const originalGetBase58Encoder = originalBase58Module.getBase58Encoder(); - -describe('assertIsBlockhashLifetimeTransaction', () => { - beforeEach(() => { - // use real implementation - jest.mocked(getBase58Encoder).mockReturnValue(originalGetBase58Encoder); - }); - it('throws for a transaction with no lifetime constraint', () => { - const transaction: BaseTransaction = { - instructions: [], - version: 0, - }; - expect(() => assertIsTransactionWithBlockhashLifetime(transaction)).toThrow(); - }); - it('throws for a transaction with a durable nonce constraint', () => { - const transaction = { - instructions: [], - lifetimeConstraint: { - nonce: 'abcd', - }, - version: 0, - } as unknown as BaseTransaction; - expect(() => assertIsTransactionWithBlockhashLifetime(transaction)).toThrow(); - }); - it('throws for a transaction with a blockhash but no lastValidBlockHeight in lifetimeConstraint', () => { - const transaction = { - instructions: [], - lifetimeConstraint: { - blockhash: '11111111111111111111111111111111', - }, - version: 0, - } as unknown as BaseTransaction; - expect(() => assertIsTransactionWithBlockhashLifetime(transaction)).toThrow(); - }); - it('throws for a transaction with a lastValidBlockHeight but no blockhash in lifetimeConstraint', () => { - const transaction = { - instructions: [], - lifetimeConstraint: { - lastValidBlockHeight: 1234n, - }, - version: 0, - } as unknown as BaseTransaction; - expect(() => assertIsTransactionWithBlockhashLifetime(transaction)).toThrow(); - }); - it('throws for a transaction with a blockhash lifetime but an invalid blockhash value', () => { - const transaction = { - instructions: [], - lifetimeConstraint: { - blockhash: 'not a valid blockhash value', - }, - version: 0, - } as unknown as BaseTransaction; - expect(() => assertIsTransactionWithBlockhashLifetime(transaction)).toThrow(); - }); - it('does not throw for a transaction with a valid blockhash lifetime constraint', () => { - const transaction = { - instructions: [], - lifetimeConstraint: { - blockhash: '11111111111111111111111111111111', - lastValidBlockHeight: 1234n, - }, - version: 0, - } as unknown as BaseTransaction; - expect(() => assertIsTransactionWithBlockhashLifetime(transaction)).not.toThrow(); - }); -}); - -describe('setTransactionLifetimeUsingBlockhash', () => { - let baseTx: BaseTransaction; - const BLOCKHASH_CONSTRAINT_A = { - blockhash: 'F7vmkY3DTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7' as Blockhash, - lastValidBlockHeight: 123n, - }; - const BLOCKHASH_CONSTRAINT_B = { - blockhash: '6bjroqDcZgTv6Vavhqf81oBHTv3aMnX19UTB51YhAZnN' as Blockhash, - lastValidBlockHeight: 123n, - }; - beforeEach(() => { - baseTx = { - instructions: [], - version: 0, - }; - }); - it('sets the lifetime constraint on the transaction to the supplied blockhash lifetime constraint', () => { - const txWithBlockhashLifetimeConstraint = setTransactionLifetimeUsingBlockhash(BLOCKHASH_CONSTRAINT_A, baseTx); - expect(txWithBlockhashLifetimeConstraint).toHaveProperty('lifetimeConstraint', BLOCKHASH_CONSTRAINT_A); - }); - describe('given a transaction with a blockhash lifetime already set', () => { - let txWithBlockhashLifetimeConstraint: BaseTransaction & ITransactionWithBlockhashLifetime; - beforeEach(() => { - txWithBlockhashLifetimeConstraint = { - ...baseTx, - lifetimeConstraint: BLOCKHASH_CONSTRAINT_A, - }; - }); - it('sets the new blockhash lifetime constraint on the transaction when it differs from the existing one', () => { - const txWithBlockhashLifetimeConstraintB = setTransactionLifetimeUsingBlockhash( - BLOCKHASH_CONSTRAINT_B, - txWithBlockhashLifetimeConstraint, - ); - expect(txWithBlockhashLifetimeConstraintB).toHaveProperty('lifetimeConstraint', BLOCKHASH_CONSTRAINT_B); - }); - it('returns the original transaction when trying to set the same blockhash lifetime constraint again', () => { - const txWithSameBlockhashLifetimeConstraint = setTransactionLifetimeUsingBlockhash( - BLOCKHASH_CONSTRAINT_A, - txWithBlockhashLifetimeConstraint, - ); - expect(txWithBlockhashLifetimeConstraint).toBe(txWithSameBlockhashLifetimeConstraint); - }); - describe('given that transaction also has signatures', () => { - let txWithBlockhashLifetimeConstraintAndSignatures: BaseTransaction & - ITransactionWithBlockhashLifetime & - ITransactionWithSignatures; - beforeEach(() => { - txWithBlockhashLifetimeConstraintAndSignatures = { - ...txWithBlockhashLifetimeConstraint, - signatures: {}, - }; - }); - it('does not clear the signatures when the blockhash lifetime constraint is the same as the current one', () => { - expect( - setTransactionLifetimeUsingBlockhash( - BLOCKHASH_CONSTRAINT_A, - txWithBlockhashLifetimeConstraintAndSignatures, - ), - ).toHaveProperty('signatures', txWithBlockhashLifetimeConstraintAndSignatures.signatures); - }); - it('clears the signatures when the blockhash lifetime constraint is different than the current one', () => { - expect( - setTransactionLifetimeUsingBlockhash( - BLOCKHASH_CONSTRAINT_B, - txWithBlockhashLifetimeConstraintAndSignatures, - ), - ).not.toHaveProperty('signatures'); - }); - }); - }); - it('freezes the object', () => { - const txWithBlockhashLifetimeConstraint = setTransactionLifetimeUsingBlockhash(BLOCKHASH_CONSTRAINT_A, baseTx); - expect(txWithBlockhashLifetimeConstraint).toBeFrozenObject(); - }); -}); diff --git a/packages/transactions/src/__tests__/compile-transaction-test.ts b/packages/transactions/src/__tests__/compile-transaction-test.ts deleted file mode 100644 index 99bf588393c1..000000000000 --- a/packages/transactions/src/__tests__/compile-transaction-test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Address } from '@solana/addresses'; - -import { getCompiledTransaction } from '../compile-transaction'; -import { CompiledMessage, compileTransactionMessage } from '../message'; -import { ITransactionWithSignatures } from '../signatures'; - -jest.mock('../message'); - -let _nextMockAddress = 0; -function getMockAddress() { - return `${_nextMockAddress++}` as Address; -} - -describe('getCompiledTransaction', () => { - let addressA: Address; - let addressB: Address; - let mockCompiledMessage: CompiledMessage; - beforeEach(() => { - addressA = getMockAddress(); - addressB = getMockAddress(); - mockCompiledMessage = { - header: { - numReadonlyNonSignerAccounts: 0, - numReadonlySignerAccounts: 0, - numSignerAccounts: 2, - }, - staticAccounts: [addressB, addressA], - } as CompiledMessage; - (compileTransactionMessage as jest.Mock).mockReturnValue(mockCompiledMessage); - }); - it('compiles the transaction message', () => { - const compiledTransaction = getCompiledTransaction({} as Parameters[0]); - expect(compiledTransaction).toHaveProperty('compiledMessage', mockCompiledMessage); - }); - it('compiles an array of signatures the length of the number of signers', () => { - const compiledTransaction = getCompiledTransaction({} as Parameters[0]); - expect(compiledTransaction.signatures).toHaveLength(mockCompiledMessage.header.numSignerAccounts); - }); - it("compiles signatures into the correct position in the signatures' array", () => { - const mockSignatureA = new Uint8Array(64).fill(1); - const mockSignatureB = new Uint8Array(64).fill(2); - const transactionWithSignatures = { - signatures: { [addressA]: mockSignatureA, [addressB]: mockSignatureB }, - } as ITransactionWithSignatures & Parameters[0]; - const compiledTransaction = getCompiledTransaction(transactionWithSignatures); - expect(compiledTransaction).toHaveProperty('signatures', [ - // Two signers, in the order they're found in `mockCompiledMessage.staticAccounts` - mockSignatureB, - mockSignatureA, - ]); - }); - it('compiles a null signature into the compiled signatures array when a signature is missing', () => { - const mockSignatureA = new Uint8Array(64).fill(1); - const transactionWithSignatures = { - signatures: { [addressA]: mockSignatureA }, - } as ITransactionWithSignatures & Parameters[0]; - const compiledTransaction = getCompiledTransaction(transactionWithSignatures); - expect(compiledTransaction).toHaveProperty('signatures', [ - new Uint8Array(Array(64).fill(0)), // Missing signature for account B - mockSignatureA, - ]); - }); -}); diff --git a/packages/transactions/src/__tests__/create-transaction-test.ts b/packages/transactions/src/__tests__/create-transaction-test.ts deleted file mode 100644 index a515012168a4..000000000000 --- a/packages/transactions/src/__tests__/create-transaction-test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import '@solana/test-matchers/toBeFrozenObject'; - -import { createTransaction } from '../create-transaction'; - -describe('createTransaction', () => { - it('creates a legacy transaction', () => { - expect(createTransaction({ version: 'legacy' })).toMatchObject({ - instructions: [], - version: 'legacy', - }); - }); - it('creates a v0 transaction', () => { - expect(createTransaction({ version: 0 })).toMatchObject({ - instructions: [], - version: 0, - }); - }); - it('freezes the object', () => { - const tx = createTransaction({ version: 0 }); - expect(tx).toBeFrozenObject(); - }); -}); diff --git a/packages/transactions/src/__tests__/durable-nonce-test.ts b/packages/transactions/src/__tests__/durable-nonce-test.ts deleted file mode 100644 index 2cb392b1cb1d..000000000000 --- a/packages/transactions/src/__tests__/durable-nonce-test.ts +++ /dev/null @@ -1,284 +0,0 @@ -import '@solana/test-matchers/toBeFrozenObject'; - -import { Address } from '@solana/addresses'; -import { AccountRole, IInstruction, ReadonlySignerAccount, WritableAccount } from '@solana/instructions'; -import type { Blockhash } from '@solana/rpc-types'; - -import { ITransactionWithBlockhashLifetime } from '../blockhash'; -import { - assertIsDurableNonceTransaction, - IDurableNonceTransaction, - Nonce, - setTransactionLifetimeUsingDurableNonce, -} from '../durable-nonce'; -import { ITransactionWithSignatures } from '../signatures'; -import { BaseTransaction } from '../types'; - -function createMockAdvanceNonceAccountInstruction< - TNonceAccountAddress extends string = string, - TNonceAuthorityAddress extends string = string, ->({ - nonceAccountAddress, - nonceAuthorityAddress, -}: { - nonceAccountAddress: Address; - nonceAuthorityAddress: Address; -}): IDurableNonceTransaction['instructions'][0] { - return { - accounts: [ - { address: nonceAccountAddress, role: AccountRole.WRITABLE } as WritableAccount, - { - address: - 'SysvarRecentB1ockHashes11111111111111111111' as Address<'SysvarRecentB1ockHashes11111111111111111111'>, - role: AccountRole.READONLY, - }, - { - address: nonceAuthorityAddress, - role: AccountRole.READONLY_SIGNER, - } as ReadonlySignerAccount, - ], - data: new Uint8Array([4, 0, 0, 0]) as IDurableNonceTransaction['instructions'][0]['data'], - programAddress: '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>, - }; -} - -describe('assertIsDurableNonceTransaction()', () => { - let durableNonceTx: BaseTransaction & IDurableNonceTransaction; - const NONCE_CONSTRAINT = { - nonce: '123' as Nonce, - nonceAccountAddress: '123' as Address, - nonceAuthorityAddress: '123' as Address, - }; - beforeEach(() => { - durableNonceTx = { - instructions: [createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT)], - lifetimeConstraint: { nonce: NONCE_CONSTRAINT.nonce } as IDurableNonceTransaction['lifetimeConstraint'], - version: 0, - } as const; - }); - it('throws when supplied a transaction with a nonce lifetime constraint but no instructions', () => { - expect(() => { - assertIsDurableNonceTransaction({ - ...durableNonceTx, - instructions: [], - }); - }).toThrow(); - }); - it('throws when supplied a transaction with a nonce lifetime constraint but an instruction at index 0 for a program other than the system program', () => { - expect(() => { - assertIsDurableNonceTransaction({ - ...durableNonceTx, - instructions: [ - { - ...durableNonceTx.instructions[0], - programAddress: '32JTd9jz5xGuLegzVouXxfzAVTiJYWMLrg6p8RxbV5xc' as Address, - }, - ], - lifetimeConstraint: { nonce: NONCE_CONSTRAINT.nonce } as IDurableNonceTransaction['lifetimeConstraint'], - }); - }).toThrow(); - }); - it('throws when supplied a transaction with a nonce lifetime constraint but a system program instruction at index 0 for something other than the `AdvanceNonceAccount` instruction', () => { - expect(() => { - assertIsDurableNonceTransaction({ - ...durableNonceTx, - instructions: [ - { - ...durableNonceTx.instructions[0], - data: new Uint8Array([2, 0, 0, 0]), // Transfer instruction - }, - ], - lifetimeConstraint: { nonce: NONCE_CONSTRAINT.nonce } as IDurableNonceTransaction['lifetimeConstraint'], - }); - }).toThrow(); - }); - it('throws when supplied a transaction with a nonce lifetime constraint but a system program instruction at index 0 with malformed accounts', () => { - expect(() => { - assertIsDurableNonceTransaction({ - ...durableNonceTx, - instructions: [ - { - ...durableNonceTx.instructions[0], - accounts: [], - }, - ], - lifetimeConstraint: { nonce: NONCE_CONSTRAINT.nonce } as IDurableNonceTransaction['lifetimeConstraint'], - }); - }).toThrow(); - }); - it('throws when supplied a transaction with an `AdvanceNonceAccount` instruction at index 0 but no lifetime constraint', () => { - expect(() => { - assertIsDurableNonceTransaction({ - ...durableNonceTx, - lifetimeConstraint: undefined, - }); - }).toThrow(); - }); - it('throws when supplied a transaction with an `AdvanceNonceAccount` instruction at index 0 but a blockhash lifetime constraint', () => { - expect(() => { - assertIsDurableNonceTransaction({ - ...durableNonceTx, - lifetimeConstraint: { - blockhash: '123' as Blockhash, - lastValidBlockHeight: 123n, - } as ITransactionWithBlockhashLifetime['lifetimeConstraint'], - } as BaseTransaction); - }).toThrow(); - }); - it('does not throw when supplied a durable nonce transaction', () => { - expect(() => { - assertIsDurableNonceTransaction({ ...durableNonceTx }); - }).not.toThrow(); - }); - it('does not throw when the nonce authority is a writable signer', () => { - const advanceDurableNonceInstruction = createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT); - const { accounts } = advanceDurableNonceInstruction; - const updatedInstruction: IInstruction = { - ...advanceDurableNonceInstruction, - accounts: [ - accounts[0], - accounts[1], - { - ...accounts[2], - role: AccountRole.WRITABLE_SIGNER, - }, - ], - }; - const transaction = { - instructions: [updatedInstruction], - lifetimeConstraint: { nonce: NONCE_CONSTRAINT.nonce } as IDurableNonceTransaction['lifetimeConstraint'], - version: 0, - } as const; - expect(() => { - assertIsDurableNonceTransaction({ ...transaction }); - }).not.toThrow(); - }); -}); - -describe('setTransactionLifetimeUsingDurableNonce', () => { - let baseTx: BaseTransaction; - const NONCE_CONSTRAINT_A = { - nonce: '123' as Nonce, - nonceAccountAddress: '123' as Address, - nonceAuthorityAddress: '123' as Address, - }; - const NONCE_CONSTRAINT_B = { - nonce: '456' as Nonce, - nonceAccountAddress: '456' as Address, - nonceAuthorityAddress: '456' as Address, - }; - beforeEach(() => { - baseTx = { - instructions: [{ programAddress: '32JTd9jz5xGuLegzVouXxfzAVTiJYWMLrg6p8RxbV5xc' as Address }], - version: 0, - }; - }); - it('sets the lifetime constraint on the transaction to the supplied durable nonce constraint', () => { - const durableNonceTxWithConstraintA = setTransactionLifetimeUsingDurableNonce(NONCE_CONSTRAINT_A, baseTx); - expect(durableNonceTxWithConstraintA).toHaveProperty('lifetimeConstraint', { nonce: NONCE_CONSTRAINT_A.nonce }); - }); - it('appends an `AdvanceNonceAccount` instruction', () => { - const durableNonceTxWithConstraintA = setTransactionLifetimeUsingDurableNonce(NONCE_CONSTRAINT_A, baseTx); - expect(durableNonceTxWithConstraintA.instructions).toEqual([ - createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT_A), - baseTx.instructions[0], - ]); - }); - describe('given a transaction with an advance nonce account instruction but no nonce lifetime constraint', () => { - it('does not modify an `AdvanceNonceAccount` instruction if the existing one matches the constraint added', () => { - const instruction = createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT_A); - instruction.accounts[2].role = AccountRole.WRITABLE_SIGNER; - const transaction: BaseTransaction = { ...baseTx, instructions: [instruction, baseTx.instructions[0]] }; - const durableNonceTxWithConstraintA = setTransactionLifetimeUsingDurableNonce( - NONCE_CONSTRAINT_A, - transaction, - ); - expect(durableNonceTxWithConstraintA.instructions).toEqual([instruction, baseTx.instructions[0]]); - }); - it('replaces an `AdvanceNonceAccount` instruction if the existing one does not match the constraint added', () => { - const transaction: BaseTransaction = { - ...baseTx, - instructions: [createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT_B), baseTx.instructions[0]], - }; - const durableNonceTxWithConstraintA = setTransactionLifetimeUsingDurableNonce( - NONCE_CONSTRAINT_A, - transaction, - ); - expect(durableNonceTxWithConstraintA.instructions).toEqual([ - createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT_A), - baseTx.instructions[0], - ]); - }); - }); - describe('given a durable nonce transaction', () => { - let durableNonceTxWithConstraintA: BaseTransaction & IDurableNonceTransaction; - beforeEach(() => { - durableNonceTxWithConstraintA = { - ...baseTx, - instructions: [ - createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT_A), - { programAddress: '32JTd9jz5xGuLegzVouXxfzAVTiJYWMLrg6p8RxbV5xc' as Address }, - ], - lifetimeConstraint: { nonce: NONCE_CONSTRAINT_A.nonce }, - version: 0, - }; - }); - it('sets the new durable nonce constraint on the transaction when it differs from the existing one', () => { - const durableNonceTxWithConstraintB = setTransactionLifetimeUsingDurableNonce( - NONCE_CONSTRAINT_B, - durableNonceTxWithConstraintA, - ); - expect(durableNonceTxWithConstraintB).toHaveProperty('lifetimeConstraint', { - nonce: NONCE_CONSTRAINT_B.nonce, - }); - }); - it('replaces the advance nonce account instruction when it differs from the existing one', () => { - const durableNonceTxWithConstraintB = setTransactionLifetimeUsingDurableNonce( - NONCE_CONSTRAINT_B, - durableNonceTxWithConstraintA, - ); - expect(durableNonceTxWithConstraintB.instructions).toEqual([ - createMockAdvanceNonceAccountInstruction(NONCE_CONSTRAINT_B), - durableNonceTxWithConstraintA.instructions[1], - ]); - }); - it('returns the original transaction when trying to set the same durable nonce constraint again', () => { - const txWithSameNonceLifetimeConstraint = setTransactionLifetimeUsingDurableNonce( - NONCE_CONSTRAINT_A, - durableNonceTxWithConstraintA, - ); - expect(durableNonceTxWithConstraintA).toBe(txWithSameNonceLifetimeConstraint); - }); - describe('given that transaction also has signatures', () => { - let durableNonceTxWithConstraintAAndSignatures: BaseTransaction & - IDurableNonceTransaction & - ITransactionWithSignatures; - beforeEach(() => { - durableNonceTxWithConstraintAAndSignatures = { - ...durableNonceTxWithConstraintA, - signatures: {}, - }; - }); - it('does not clear the signatures when the durable nonce constraint is the same as the current one', () => { - expect( - setTransactionLifetimeUsingDurableNonce( - NONCE_CONSTRAINT_A, - durableNonceTxWithConstraintAAndSignatures, - ), - ).toHaveProperty('signatures', durableNonceTxWithConstraintAAndSignatures.signatures); - }); - it('clears the signatures when the durable nonce constraint is different than the current one', () => { - expect( - setTransactionLifetimeUsingDurableNonce( - NONCE_CONSTRAINT_B, - durableNonceTxWithConstraintAAndSignatures, - ), - ).not.toHaveProperty('signatures'); - }); - }); - }); - it('freezes the object', () => { - const durableNonceTx = setTransactionLifetimeUsingDurableNonce(NONCE_CONSTRAINT_A, baseTx); - expect(durableNonceTx).toBeFrozenObject(); - }); -}); diff --git a/packages/transactions/src/__tests__/fee-payer-test.ts b/packages/transactions/src/__tests__/fee-payer-test.ts deleted file mode 100644 index be6dc1a1fafc..000000000000 --- a/packages/transactions/src/__tests__/fee-payer-test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import '@solana/test-matchers/toBeFrozenObject'; - -import { Address } from '@solana/addresses'; - -import { ITransactionWithFeePayer, setTransactionFeePayer } from '../fee-payer'; -import { ITransactionWithSignatures } from '../signatures'; -import { BaseTransaction } from '../types'; - -const EXAMPLE_FEE_PAYER_A = - '7mvYAxeCui21xYkAyQSjh6iBVZPpgVyt7PYv9km8V5mE' as Address<'7mvYAxeCui21xYkAyQSjh6iBVZPpgVyt7PYv9km8V5mE'>; -const EXAMPLE_FEE_PAYER_B = - '5LHng8dLBxCYyR3jdDbobLiRQ6pR74pYtxKohY93RbZN' as Address<'5LHng8dLBxCYyR3jdDbobLiRQ6pR74pYtxKohY93RbZN'>; - -describe('setTransactionFeePayer', () => { - let baseTx: BaseTransaction; - beforeEach(() => { - baseTx = { - instructions: [], - version: 0, - }; - }); - it('sets the fee payer on the transaction', () => { - const txWithFeePayerA = setTransactionFeePayer(EXAMPLE_FEE_PAYER_A, baseTx); - expect(txWithFeePayerA).toHaveProperty('feePayer', EXAMPLE_FEE_PAYER_A); - }); - describe('given a transaction with a fee payer already set', () => { - let txWithFeePayerA: BaseTransaction & ITransactionWithFeePayer; - beforeEach(() => { - txWithFeePayerA = { - ...baseTx, - feePayer: EXAMPLE_FEE_PAYER_A, - }; - }); - it('sets the new fee payer on the transaction when it differs from the existing one', () => { - const txWithFeePayerB = setTransactionFeePayer(EXAMPLE_FEE_PAYER_B, txWithFeePayerA); - expect(txWithFeePayerB).toHaveProperty('feePayer', EXAMPLE_FEE_PAYER_B); - }); - it('returns the original transaction when trying to set the same fee payer again', () => { - const txWithSameFeePayer = setTransactionFeePayer(EXAMPLE_FEE_PAYER_A, txWithFeePayerA); - expect(txWithFeePayerA).toBe(txWithSameFeePayer); - }); - describe('given that transaction also has signatures', () => { - let txWithFeePayerAndSignatures: BaseTransaction & ITransactionWithFeePayer & ITransactionWithSignatures; - beforeEach(() => { - txWithFeePayerAndSignatures = { - ...txWithFeePayerA, - signatures: {}, - }; - }); - it('does not clear the signatures when the fee payer is the same as the current one', () => { - expect(setTransactionFeePayer(EXAMPLE_FEE_PAYER_A, txWithFeePayerAndSignatures)).toHaveProperty( - 'signatures', - txWithFeePayerAndSignatures.signatures, - ); - }); - it('clears the signatures when the fee payer is different than the current one', () => { - expect(setTransactionFeePayer(EXAMPLE_FEE_PAYER_B, txWithFeePayerAndSignatures)).not.toHaveProperty( - 'signatures', - ); - }); - }); - }); - it('freezes the object', () => { - const txWithFeePayer = setTransactionFeePayer(EXAMPLE_FEE_PAYER_A, baseTx); - expect(txWithFeePayer).toBeFrozenObject(); - }); -}); diff --git a/packages/transactions/src/__tests__/instructions-test.ts b/packages/transactions/src/__tests__/instructions-test.ts deleted file mode 100644 index 4423748a3e62..000000000000 --- a/packages/transactions/src/__tests__/instructions-test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import '@solana/test-matchers/toBeFrozenObject'; - -import { Address } from '@solana/addresses'; -import { IInstruction } from '@solana/instructions'; - -import { - appendTransactionInstruction, - appendTransactionInstructions, - prependTransactionInstruction, - prependTransactionInstructions, -} from '../instructions'; -import { ITransactionWithSignatures } from '../signatures'; -import { BaseTransaction } from '../types'; - -const PROGRAM_A = - 'AALQD2dt1k43Acrkp4SvdhZaN4S115Ff2Bi7rHPti3sL' as Address<'AALQD2dt1k43Acrkp4SvdhZaN4S115Ff2Bi7rHPti3sL'>; -const PROGRAM_B = - 'DNAbkMkoMLRXF7wuLCrTzouMyzi25krr3B94yW87VvxU' as Address<'DNAbkMkoMLRXF7wuLCrTzouMyzi25krr3B94yW87VvxU'>; -const PROGRAM_C = - '6Bkt4j67rxzFF6s9DaMRyfitftRrGxe4oYHPRHuFChzi' as Address<'6Bkt4j67rxzFF6s9DaMRyfitftRrGxe4oYHPRHuFChzi'>; - -describe('Transaction instruction helpers', () => { - let baseTx: BaseTransaction; - let exampleInstruction: IInstruction; - let secondExampleInstruction: IInstruction; - beforeEach(() => { - baseTx = { - instructions: [ - { - programAddress: PROGRAM_A, - }, - ], - version: 0, - }; - exampleInstruction = { - programAddress: PROGRAM_B, - }; - secondExampleInstruction = { - programAddress: PROGRAM_C, - }; - }); - describe('appendTransactionInstruction', () => { - it('adds the instruction to the end of the list', () => { - const txWithAddedInstruction = appendTransactionInstruction(exampleInstruction, baseTx); - expect(txWithAddedInstruction.instructions).toMatchObject([...baseTx.instructions, exampleInstruction]); - }); - describe('given a transaction with signatures', () => { - let txWithSignatures: BaseTransaction & ITransactionWithSignatures; - beforeEach(() => { - txWithSignatures = { - ...baseTx, - signatures: {}, - }; - }); - it('clears the transaction signatures', () => { - expect(appendTransactionInstruction(exampleInstruction, txWithSignatures)).not.toHaveProperty( - 'signatures', - ); - }); - }); - it('freezes the object', () => { - const txWithAddedInstruction = appendTransactionInstruction(exampleInstruction, baseTx); - expect(txWithAddedInstruction).toBeFrozenObject(); - }); - }); - describe('appendTransactionInstructions', () => { - it('adds the instructions to the end of the list', () => { - const txWithAddedInstructions = appendTransactionInstructions( - [exampleInstruction, secondExampleInstruction], - baseTx, - ); - expect(txWithAddedInstructions.instructions).toMatchObject([ - ...baseTx.instructions, - exampleInstruction, - secondExampleInstruction, - ]); - }); - describe('given a transaction with signatures', () => { - let txWithSignatures: BaseTransaction & ITransactionWithSignatures; - beforeEach(() => { - txWithSignatures = { - ...baseTx, - signatures: {}, - }; - }); - it('clears the transaction signatures', () => { - expect( - appendTransactionInstructions([exampleInstruction, secondExampleInstruction], txWithSignatures), - ).not.toHaveProperty('signatures'); - }); - }); - it('freezes the object', () => { - const txWithAddedInstruction = appendTransactionInstructions( - [exampleInstruction, secondExampleInstruction], - baseTx, - ); - expect(txWithAddedInstruction).toBeFrozenObject(); - }); - }); - describe('prependTransactionInstruction', () => { - it('adds the instruction to the beginning of the list', () => { - const txWithAddedInstruction = prependTransactionInstruction(exampleInstruction, baseTx); - expect(txWithAddedInstruction.instructions).toMatchObject([exampleInstruction, ...baseTx.instructions]); - }); - describe('given a transaction with signatures', () => { - let txWithSignatures: BaseTransaction & ITransactionWithSignatures; - beforeEach(() => { - txWithSignatures = { - ...baseTx, - signatures: {}, - }; - }); - it('clears the transaction signatures', () => { - expect(prependTransactionInstruction(exampleInstruction, txWithSignatures)).not.toHaveProperty( - 'signatures', - ); - }); - }); - it('freezes the object', () => { - const txWithAddedInstruction = prependTransactionInstruction(exampleInstruction, baseTx); - expect(txWithAddedInstruction).toBeFrozenObject(); - }); - }); - describe('prependTransactionInstructions', () => { - it('adds the instructions to the beginning of the list', () => { - const txWithAddedInstruction = prependTransactionInstructions( - [exampleInstruction, secondExampleInstruction], - baseTx, - ); - expect(txWithAddedInstruction.instructions).toMatchObject([ - exampleInstruction, - secondExampleInstruction, - ...baseTx.instructions, - ]); - }); - describe('given a transaction with signatures', () => { - let txWithSignatures: BaseTransaction & ITransactionWithSignatures; - beforeEach(() => { - txWithSignatures = { - ...baseTx, - signatures: {}, - }; - }); - it('clears the transaction signatures', () => { - expect( - prependTransactionInstructions([exampleInstruction, secondExampleInstruction], txWithSignatures), - ).not.toHaveProperty('signatures'); - }); - }); - it('freezes the object', () => { - const txWithAddedInstruction = prependTransactionInstructions( - [exampleInstruction, secondExampleInstruction], - baseTx, - ); - expect(txWithAddedInstruction).toBeFrozenObject(); - }); - }); -}); diff --git a/packages/transactions/src/__tests__/message-test.ts b/packages/transactions/src/__tests__/message-test.ts deleted file mode 100644 index eaf81a0dcbc9..000000000000 --- a/packages/transactions/src/__tests__/message-test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Address } from '@solana/addresses'; -import { - getCompiledAddressTableLookups, - getCompiledInstructions, - getCompiledLifetimeToken, - getCompiledMessageHeader, - getCompiledStaticAccounts, -} from '@solana/transaction-messages'; - -import { ITransactionWithBlockhashLifetime } from '../blockhash'; -import { ITransactionWithFeePayer } from '../fee-payer'; -import { compileTransactionMessage } from '../message'; -import { BaseTransaction } from '../types'; - -jest.mock('@solana/transaction-messages', () => ({ - ...jest.requireActual('@solana/transaction-messages'), - getCompiledAddressTableLookups: jest.fn(), - getCompiledInstructions: jest.fn(), - getCompiledLifetimeToken: jest.fn(), - getCompiledMessageHeader: jest.fn(), - getCompiledStaticAccounts: jest.fn(), -})); - -const MOCK_LIFETIME_CONSTRAINT = - 'SOME_CONSTRAINT' as unknown as ITransactionWithBlockhashLifetime['lifetimeConstraint']; - -describe('compileTransactionMessage', () => { - let baseTx: BaseTransaction & ITransactionWithBlockhashLifetime & ITransactionWithFeePayer; - beforeEach(() => { - baseTx = { - feePayer: 'abc' as Address<'abc'>, - instructions: [], - lifetimeConstraint: MOCK_LIFETIME_CONSTRAINT, - version: 0, - }; - }); - describe('address table lookups', () => { - const expectedAddressTableLookups = [] as ReturnType; - beforeEach(() => { - jest.mocked(getCompiledAddressTableLookups).mockReturnValue(expectedAddressTableLookups); - }); - describe("when the transaction version is `'legacy'`", () => { - let legacyBaseTx: Readonly<{ version: 'legacy' }> & typeof baseTx; - beforeEach(() => { - legacyBaseTx = { ...baseTx, version: 'legacy' }; - }); - it('does not set `addressTableLookups`', () => { - const message = compileTransactionMessage(legacyBaseTx); - expect(message).not.toHaveProperty('addressTableLookups'); - }); - it('does not call `getCompiledAddressTableLookups`', () => { - compileTransactionMessage(legacyBaseTx); - expect(getCompiledAddressTableLookups).not.toHaveBeenCalled(); - }); - }); - it('sets `addressTableLookups` to the return value of `getCompiledAddressTableLookups`', () => { - const message = compileTransactionMessage(baseTx); - expect(getCompiledAddressTableLookups).toHaveBeenCalled(); - expect(message.addressTableLookups).toBe(expectedAddressTableLookups); - }); - }); - describe('message header', () => { - const expectedCompiledMessageHeader = { - numReadonlyNonSignerAccounts: 0, - numReadonlySignerAccounts: 0, - numSignerAccounts: 1, - } as const; - beforeEach(() => { - jest.mocked(getCompiledMessageHeader).mockReturnValue(expectedCompiledMessageHeader); - }); - it('sets `header` to the return value of `getCompiledMessageHeader`', () => { - const message = compileTransactionMessage(baseTx); - expect(getCompiledMessageHeader).toHaveBeenCalled(); - expect(message.header).toBe(expectedCompiledMessageHeader); - }); - }); - describe('instructions', () => { - const expectedInstructions = [] as ReturnType; - beforeEach(() => { - jest.mocked(getCompiledInstructions).mockReturnValue(expectedInstructions); - }); - it('sets `instructions` to the return value of `getCompiledInstructions`', () => { - const message = compileTransactionMessage(baseTx); - console.log({ message }); - expect(getCompiledInstructions).toHaveBeenCalledWith( - baseTx.instructions, - expect.any(Array) /* orderedAccounts */, - ); - expect(message.instructions).toBe(expectedInstructions); - }); - }); - describe('lifetime constraints', () => { - beforeEach(() => { - jest.mocked(getCompiledLifetimeToken).mockReturnValue('abc'); - }); - it('sets `lifetimeToken` to the return value of `getCompiledLifetimeToken`', () => { - const message = compileTransactionMessage(baseTx); - expect(getCompiledLifetimeToken).toHaveBeenCalledWith('SOME_CONSTRAINT'); - expect(message.lifetimeToken).toBe('abc'); - }); - }); - describe('static accounts', () => { - const expectedStaticAccounts = [] as ReturnType; - beforeEach(() => { - jest.mocked(getCompiledStaticAccounts).mockReturnValue(expectedStaticAccounts); - }); - it('sets `staticAccounts` to the return value of `getCompiledStaticAccounts`', () => { - const message = compileTransactionMessage(baseTx); - expect(getCompiledStaticAccounts).toHaveBeenCalled(); - expect(message.staticAccounts).toBe(expectedStaticAccounts); - }); - }); - describe('versions', () => { - it('compiles the version', () => { - const message = compileTransactionMessage(baseTx); - expect(message).toHaveProperty('version', 0); - }); - }); -}); diff --git a/packages/transactions/src/__tests__/new-signatures-test.ts b/packages/transactions/src/__tests__/new-signatures-test.ts index 918858e774ce..4bccc94a59aa 100644 --- a/packages/transactions/src/__tests__/new-signatures-test.ts +++ b/packages/transactions/src/__tests__/new-signatures-test.ts @@ -20,8 +20,6 @@ import { NewTransaction, OrderedMap, TransactionMessageBytes } from '../transact jest.mock('@solana/addresses'); jest.mock('@solana/keys'); -jest.mock('../message'); -jest.mock('../serializers/message'); describe('getSignatureFromTransaction', () => { it("returns the signature associated with a transaction's fee payer", () => { diff --git a/packages/transactions/src/__tests__/signatures-test.ts b/packages/transactions/src/__tests__/signatures-test.ts deleted file mode 100644 index a1659360bbef..000000000000 --- a/packages/transactions/src/__tests__/signatures-test.ts +++ /dev/null @@ -1,611 +0,0 @@ -import '@solana/test-matchers/toBeFrozenObject'; - -import { - Address, - getAddressCodec, - getAddressDecoder, - getAddressEncoder, - getAddressFromPublicKey, -} from '@solana/addresses'; -import { - SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING, - SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, - SolanaError, -} from '@solana/errors'; -import { AccountRole } from '@solana/instructions'; -import { SignatureBytes, signBytes } from '@solana/keys'; -import type { Blockhash } from '@solana/rpc-types'; - -import { CompilableTransaction } from '../compilable-transaction'; -import { CompiledMessage, compileTransactionMessage } from '../message'; -import { - assertTransactionIsFullySigned, - getSignatureFromTransaction, - ITransactionWithSignatures, - partiallySignTransaction, - signTransaction, -} from '../signatures'; - -jest.mock('@solana/addresses'); -jest.mock('@solana/keys'); -jest.mock('../message'); - -describe('getSignatureFromTransaction', () => { - it("returns the signature associated with a transaction's fee payer", () => { - const transactionWithoutFeePayerSignature = { - feePayer: '123' as Address, - signatures: { - ['123' as Address]: new Uint8Array(new Array(64).fill(9)) as SignatureBytes, - } as const, - }; - expect(getSignatureFromTransaction(transactionWithoutFeePayerSignature)).toBe( - 'BUguQsv2ZuHus54HAFzjdJHzZBkygAjKhEeYwSG19tUfUyvvz3worsdQCdAXDNjakJHioSiyxhFiDJrm8XpSXRA', - ); - }); - it('throws when supplied a transaction that has not been signed by the fee payer', () => { - const transactionWithoutFeePayerSignature = { - feePayer: '123' as Address, - signatures: { - // No signature by the fee payer. - ['456' as Address]: new Uint8Array(new Array(64).fill(9)) as SignatureBytes, - } as const, - }; - expect(() => { - getSignatureFromTransaction(transactionWithoutFeePayerSignature); - }).toThrow(new SolanaError(SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING)); - }); -}); - -describe('partiallySignTransaction', () => { - const MOCK_TRANSACTION = {} as unknown as Parameters[1]; - const MOCK_SIGNATURE_A = new Uint8Array(Array(64).fill(1)); - const MOCK_SIGNATURE_B = new Uint8Array(Array(64).fill(2)); - const MOCK_SIGNATURE_C = new Uint8Array(Array(64).fill(3)); - const mockKeyPairA = { privateKey: {} as CryptoKey, publicKey: {} as CryptoKey } as CryptoKeyPair; - const mockKeyPairB = { privateKey: {} as CryptoKey, publicKey: {} as CryptoKey } as CryptoKeyPair; - const mockKeyPairC = { privateKey: {} as CryptoKey, publicKey: {} as CryptoKey } as CryptoKeyPair; - const mockPublicKeyAddressA = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' as Address<'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'>; - const mockPublicKeyAddressB = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' as Address<'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'>; - const mockPublicKeyAddressC = 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC' as Address<'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC'>; - beforeEach(() => { - (compileTransactionMessage as jest.Mock).mockReturnValue({ - header: { - numReadonlyNonSignerAccounts: 2, - numReadonlySignerAccounts: 1, - numSignerAccounts: 2, - }, - instructions: [ - { - accountIndices: [/* mockPublicKeyAddressB */ 1, /* mockPublicKeyAddressC */ 2], - programAddressIndex: 3 /* system program */, - }, - ], - lifetimeToken: 'fBrpLg4qfyVH8e3z4zbjAXy4kCZP2jCFdqy113vndcj' as Blockhash, - staticAccounts: [ - /* 0: fee payer */ mockPublicKeyAddressA, - /* 1: read-only instruction signer address */ mockPublicKeyAddressB, - /* 2: readonly address */ mockPublicKeyAddressC, - /* 3: system program */ '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>, - ], - version: 0, - } as CompiledMessage); - (getAddressFromPublicKey as jest.Mock).mockImplementation(publicKey => { - switch (publicKey) { - case mockKeyPairA.publicKey: - return mockPublicKeyAddressA; - case mockKeyPairB.publicKey: - return mockPublicKeyAddressB; - case mockKeyPairC.publicKey: - return mockPublicKeyAddressC; - default: - return '99999999999999999999999999999999' as Address<'99999999999999999999999999999999'>; - } - }); - (signBytes as jest.Mock).mockImplementation(secretKey => { - switch (secretKey) { - case mockKeyPairA.privateKey: - return MOCK_SIGNATURE_A; - case mockKeyPairB.privateKey: - return MOCK_SIGNATURE_B; - case mockKeyPairC.privateKey: - return MOCK_SIGNATURE_C; - default: - return new Uint8Array(Array(64).fill(0xff)); - } - }); - (getAddressEncoder as jest.Mock).mockReturnValue({ - getSizeFromValue: jest.fn().mockReturnValue(42), - write: jest.fn().mockImplementation((_value, _bytes: Uint8Array, offset: number) => offset + 42), - }); - (getAddressDecoder as jest.Mock).mockReturnValue({}); - (getAddressCodec as jest.Mock).mockReturnValue({ - getSizeFromValue: jest.fn().mockReturnValue(42), - write: jest.fn().mockImplementation((_value, _bytes: Uint8Array, offset: number) => offset + 42), - }); - }); - it("returns a transaction object having the first signer's signature", async () => { - expect.assertions(1); - const partiallySignedTransactionPromise = partiallySignTransaction([mockKeyPairA], MOCK_TRANSACTION); - await expect(partiallySignedTransactionPromise).resolves.toHaveProperty( - 'signatures', - expect.objectContaining({ [mockPublicKeyAddressA]: MOCK_SIGNATURE_A }), - ); - }); - it("returns a transaction object having the second signer's signature", async () => { - expect.assertions(1); - const partiallySignedTransactionPromise = partiallySignTransaction([mockKeyPairB], MOCK_TRANSACTION); - await expect(partiallySignedTransactionPromise).resolves.toHaveProperty( - 'signatures', - expect.objectContaining({ [mockPublicKeyAddressB]: MOCK_SIGNATURE_B }), - ); - }); - it('returns a transaction object having multiple signatures', async () => { - expect.assertions(1); - const partiallySignedTransactionPromise = partiallySignTransaction( - [mockKeyPairA, mockKeyPairB, mockKeyPairC], - MOCK_TRANSACTION, - ); - await expect(partiallySignedTransactionPromise).resolves.toHaveProperty( - 'signatures', - expect.objectContaining({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - [mockPublicKeyAddressC]: MOCK_SIGNATURE_C, - }), - ); - }); - it('returns a transaction object without overwriting the existing signatures', async () => { - expect.assertions(1); - const mockTransactionWithSignatureForSignerA = { - ...MOCK_TRANSACTION, - signatures: { [mockPublicKeyAddressB]: MOCK_SIGNATURE_B }, - }; - const partiallySignedTransactionPromise = partiallySignTransaction( - [mockKeyPairA], - mockTransactionWithSignatureForSignerA, - ); - await expect(partiallySignedTransactionPromise).resolves.toHaveProperty( - 'signatures', - expect.objectContaining({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - }), - ); - }); - it("does not mutate the original signatures when updating a transaction's signatures", async () => { - expect.assertions(2); - const startingSignatures = { [mockPublicKeyAddressB]: MOCK_SIGNATURE_B } as const; - const mockTransactionWithSignatureForSignerA = { - ...MOCK_TRANSACTION, - signatures: startingSignatures, - }; - const { signatures } = await partiallySignTransaction([mockKeyPairA], mockTransactionWithSignatureForSignerA); - expect(signatures).not.toBe(startingSignatures); - expect(signatures).toMatchObject({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - }); - }); - it("does not mutate the original signatures when updating a transaction's signatures with multiple signers", async () => { - expect.assertions(2); - const startingSignatures = { [mockPublicKeyAddressB]: MOCK_SIGNATURE_B } as const; - const mockTransactionWithSignatureForSignerA = { - ...MOCK_TRANSACTION, - signatures: startingSignatures, - }; - const { signatures } = await partiallySignTransaction( - [mockKeyPairA, mockKeyPairC], - mockTransactionWithSignatureForSignerA, - ); - expect(signatures).not.toBe(startingSignatures); - expect(signatures).toMatchObject({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - [mockPublicKeyAddressC]: MOCK_SIGNATURE_C, - }); - }); - it('freezes the object', async () => { - expect.assertions(1); - await expect(partiallySignTransaction([mockKeyPairA], MOCK_TRANSACTION)).resolves.toBeFrozenObject(); - }); -}); - -describe('signTransaction', () => { - const mockPublicKeyAddressA = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' as Address<'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'>; - const mockPublicKeyAddressB = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' as Address<'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'>; - const MOCK_TRANSACTION = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [{ address: mockPublicKeyAddressB, role: AccountRole.READONLY_SIGNER }], - programAddress: '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>, - }, - ], - } as unknown as Parameters[1]; - const MOCK_SIGNATURE_A = new Uint8Array(Array(64).fill(1)); - const MOCK_SIGNATURE_B = new Uint8Array(Array(64).fill(2)); - const mockKeyPairA = { privateKey: {} as CryptoKey, publicKey: {} as CryptoKey } as CryptoKeyPair; - const mockKeyPairB = { privateKey: {} as CryptoKey, publicKey: {} as CryptoKey } as CryptoKeyPair; - beforeEach(() => { - (compileTransactionMessage as jest.Mock).mockReturnValue({ - header: { - numReadonlyNonSignerAccounts: 1, - numReadonlySignerAccounts: 1, - numSignerAccounts: 2, - }, - instructions: [ - { - accountIndices: [/* mockPublicKeyAddressB */ 1], - programAddressIndex: 2 /* system program */, - }, - ], - lifetimeToken: 'fBrpLg4qfyVH8e3z4zbjAXy4kCZP2jCFdqy113vndcj' as Blockhash, - staticAccounts: [ - /* 0: fee payer */ mockPublicKeyAddressA, - /* 1: read-only instruction signer address */ mockPublicKeyAddressB, - /* 2: system program */ '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>, - ], - version: 0, - } as CompiledMessage); - (getAddressFromPublicKey as jest.Mock).mockImplementation(publicKey => { - switch (publicKey) { - case mockKeyPairA.publicKey: - return mockPublicKeyAddressA; - case mockKeyPairB.publicKey: - return mockPublicKeyAddressB; - default: - return '99999999999999999999999999999999' as Address<'99999999999999999999999999999999'>; - } - }); - (signBytes as jest.Mock).mockImplementation(secretKey => { - switch (secretKey) { - case mockKeyPairA.privateKey: - return MOCK_SIGNATURE_A; - case mockKeyPairB.privateKey: - return MOCK_SIGNATURE_B; - default: - return new Uint8Array(Array(64).fill(0xff)); - } - }); - (getAddressEncoder as jest.Mock).mockReturnValue({ - getSizeFromValue: jest.fn().mockReturnValue(42), - write: jest.fn().mockImplementation((_value, _bytes: Uint8Array, offset: number) => offset + 42), - }); - (getAddressDecoder as jest.Mock).mockReturnValue({}); - (getAddressCodec as jest.Mock).mockReturnValue({ - getSizeFromValue: jest.fn().mockReturnValue(42), - write: jest.fn().mockImplementation((_value, _bytes: Uint8Array, offset: number) => offset + 42), - }); - }); - it('fatals when missing a signer', async () => { - expect.assertions(1); - const signedTransactionPromise = signTransaction([mockKeyPairA], MOCK_TRANSACTION); - await expect(signedTransactionPromise).rejects.toThrow( - new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { - addresses: [mockPublicKeyAddressB], - }), - ); - }); - it('returns a transaction object having multiple signatures', async () => { - expect.assertions(1); - const signedTransactionPromise = signTransaction([mockKeyPairA, mockKeyPairB], MOCK_TRANSACTION); - await expect(signedTransactionPromise).resolves.toHaveProperty( - 'signatures', - expect.objectContaining({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - }), - ); - }); - it('returns a transaction object without overwriting the existing signatures', async () => { - expect.assertions(1); - const mockTransactionWithSignatureForSignerA = { - ...MOCK_TRANSACTION, - signatures: { [mockPublicKeyAddressB]: MOCK_SIGNATURE_B }, - }; - const signedTransactionPromise = signTransaction([mockKeyPairA], mockTransactionWithSignatureForSignerA); - await expect(signedTransactionPromise).resolves.toHaveProperty( - 'signatures', - expect.objectContaining({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - }), - ); - }); - it("does not mutate the original signatures when updating a transaction's signatures", async () => { - expect.assertions(2); - const startingSignatures = { [mockPublicKeyAddressB]: MOCK_SIGNATURE_B } as const; - const mockTransactionWithSignatureForSignerA = { - ...MOCK_TRANSACTION, - signatures: startingSignatures, - }; - const { signatures } = await signTransaction([mockKeyPairA], mockTransactionWithSignatureForSignerA); - expect(signatures).not.toBe(startingSignatures); - expect(signatures).toMatchObject({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - }); - }); - it("does not mutate the original signatures when updating a transaction's signatures with multiple signers", async () => { - expect.assertions(2); - const startingSignatures = { [mockPublicKeyAddressB]: MOCK_SIGNATURE_B } as const; - const mockTransactionWithSignatureForSignerA = { - ...MOCK_TRANSACTION, - signatures: startingSignatures, - }; - const { signatures } = await signTransaction([mockKeyPairA], mockTransactionWithSignatureForSignerA); - expect(signatures).not.toBe(startingSignatures); - expect(signatures).toMatchObject({ - [mockPublicKeyAddressA]: MOCK_SIGNATURE_A, - [mockPublicKeyAddressB]: MOCK_SIGNATURE_B, - }); - }); - it('freezes the object', async () => { - expect.assertions(1); - await expect(signTransaction([mockKeyPairA, mockKeyPairB], MOCK_TRANSACTION)).resolves.toBeFrozenObject(); - }); -}); - -describe('assertTransactionIsFullySigned', () => { - type SignedTransaction = CompilableTransaction & ITransactionWithSignatures; - - const mockProgramAddress = 'program' as Address; - const mockPublicKeyAddressA = 'A' as Address; - const mockSignatureA = new Uint8Array(0) as SignatureBytes; - const mockPublicKeyAddressB = 'B' as Address; - const mockSignatureB = new Uint8Array(1) as SignatureBytes; - const mockPublicKeyAddressC = 'C' as Address; - const mockSignatureC = new Uint8Array(2) as SignatureBytes; - - const mockBlockhashConstraint = { - blockhash: 'a' as Blockhash, - lastValidBlockHeight: 100n, - }; - - it('throws if the transaction has no signature for the fee payer', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [], - lifetimeConstraint: mockBlockhashConstraint, - signatures: {}, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).toThrow( - new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { - addresses: [mockPublicKeyAddressA], - }), - ); - }); - - it('throws all missing signers if the transaction has no signature for multiple signers', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [{ address: mockPublicKeyAddressB, role: AccountRole.READONLY_SIGNER }], - programAddress: '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: {}, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).toThrow( - new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { - addresses: [mockPublicKeyAddressA, mockPublicKeyAddressB], - }), - ); - }); - - it('throws if the transaction has no signature for an instruction readonly signer', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [ - { - address: mockPublicKeyAddressB, - role: AccountRole.READONLY_SIGNER, - }, - ], - programAddress: mockProgramAddress, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).toThrow( - new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { - addresses: [mockPublicKeyAddressB], - }), - ); - }); - - it('throws if the transaction has no signature for an instruction writable signer', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [ - { - address: mockPublicKeyAddressB, - role: AccountRole.WRITABLE_SIGNER, - }, - ], - programAddress: mockProgramAddress, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).toThrow( - new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { - addresses: [mockPublicKeyAddressB], - }), - ); - }); - - it('throws if the transaction has multiple instructions and one is missing a signer', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [ - { - address: mockPublicKeyAddressB, - role: AccountRole.WRITABLE_SIGNER, - }, - ], - programAddress: mockProgramAddress, - }, - { - accounts: [ - { - address: mockPublicKeyAddressC, - role: AccountRole.WRITABLE_SIGNER, - }, - ], - programAddress: mockProgramAddress, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - [mockPublicKeyAddressB]: mockSignatureB, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).toThrow( - new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { - addresses: [mockPublicKeyAddressC], - }), - ); - }); - - it('does not throw if the transaction has no instructions and is signed by the fee payer', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow(); - }); - - it('does not throw if the transaction has an instruction and is signed by the fee payer and instruction signer', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [ - { - address: mockPublicKeyAddressB, - role: AccountRole.WRITABLE_SIGNER, - }, - ], - programAddress: mockProgramAddress, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - [mockPublicKeyAddressB]: mockSignatureB, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow(); - }); - - it('does not throw if the transaction has multiple instructions and is signed by all signers', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [ - { - address: mockPublicKeyAddressB, - role: AccountRole.WRITABLE_SIGNER, - }, - ], - programAddress: mockProgramAddress, - }, - { - accounts: [ - { - address: mockPublicKeyAddressC, - role: AccountRole.WRITABLE_SIGNER, - }, - ], - programAddress: mockProgramAddress, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - [mockPublicKeyAddressB]: mockSignatureB, - [mockPublicKeyAddressC]: mockSignatureC, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow(); - }); - - it('does not throw if the transaction has an instruction with a non-signer account', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - accounts: [ - { - address: mockPublicKeyAddressB, - role: AccountRole.WRITABLE, - }, - ], - programAddress: mockProgramAddress, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow(); - }); - - it('does not throw if the transaction has an instruction with no accounts', () => { - const transaction: SignedTransaction = { - feePayer: mockPublicKeyAddressA, - instructions: [ - { - programAddress: mockProgramAddress, - }, - ], - lifetimeConstraint: mockBlockhashConstraint, - signatures: { - [mockPublicKeyAddressA]: mockSignatureA, - }, - version: 0, - }; - - expect(() => assertTransactionIsFullySigned(transaction)).not.toThrow(); - }); -}); diff --git a/packages/transactions/src/__tests__/wire-transaction-test.ts b/packages/transactions/src/__tests__/wire-transaction-test.ts deleted file mode 100644 index 0cb14badecf8..000000000000 --- a/packages/transactions/src/__tests__/wire-transaction-test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Address } from '@solana/addresses'; -import { AccountRole } from '@solana/instructions'; -import { SignatureBytes } from '@solana/keys'; -import type { Blockhash } from '@solana/rpc-types'; - -import { getBase64EncodedWireTransaction } from '../wire-transaction'; - -describe('getBase64EncodedWireTransaction', () => { - it('serializes a transaction to wire format', () => { - const tx: Parameters[0] = { - 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, - }; - expect(getBase64EncodedWireTransaction(tx)) - // 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/blockhash.ts b/packages/transactions/src/blockhash.ts deleted file mode 100644 index 822b2e469e7b..000000000000 --- a/packages/transactions/src/blockhash.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { SOLANA_ERROR__TRANSACTION__EXPECTED_BLOCKHASH_LIFETIME, SolanaError } from '@solana/errors'; -import { assertIsBlockhash, type Blockhash } from '@solana/rpc-types'; - -import { IDurableNonceTransaction } from './durable-nonce'; -import { ITransactionWithSignatures } from './signatures'; -import { BaseTransaction } from './types'; -import { getUnsignedTransaction } from './unsigned-transaction'; - -type BlockhashLifetimeConstraint = Readonly<{ - blockhash: Blockhash; - lastValidBlockHeight: bigint; -}>; - -export interface ITransactionWithBlockhashLifetime { - readonly lifetimeConstraint: BlockhashLifetimeConstraint; -} - -function isTransactionWithBlockhashLifetime( - transaction: BaseTransaction | (BaseTransaction & ITransactionWithBlockhashLifetime), -): transaction is BaseTransaction & ITransactionWithBlockhashLifetime { - const lifetimeConstraintShapeMatches = - 'lifetimeConstraint' in transaction && - typeof transaction.lifetimeConstraint.blockhash === 'string' && - typeof transaction.lifetimeConstraint.lastValidBlockHeight === 'bigint'; - if (!lifetimeConstraintShapeMatches) return false; - try { - assertIsBlockhash(transaction.lifetimeConstraint.blockhash); - return true; - } catch { - return false; - } -} - -export function assertIsTransactionWithBlockhashLifetime( - transaction: BaseTransaction | (BaseTransaction & ITransactionWithBlockhashLifetime), -): asserts transaction is BaseTransaction & ITransactionWithBlockhashLifetime { - if (!isTransactionWithBlockhashLifetime(transaction)) { - throw new SolanaError(SOLANA_ERROR__TRANSACTION__EXPECTED_BLOCKHASH_LIFETIME); - } -} - -export function setTransactionLifetimeUsingBlockhash( - blockhashLifetimeConstraint: BlockhashLifetimeConstraint, - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): ITransactionWithBlockhashLifetime & Omit; - -export function setTransactionLifetimeUsingBlockhash< - TTransaction extends BaseTransaction | (BaseTransaction & ITransactionWithBlockhashLifetime), ->( - blockhashLifetimeConstraint: BlockhashLifetimeConstraint, - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): ITransactionWithBlockhashLifetime & Omit; - -export function setTransactionLifetimeUsingBlockhash( - blockhashLifetimeConstraint: BlockhashLifetimeConstraint, - transaction: BaseTransaction | (BaseTransaction & ITransactionWithBlockhashLifetime), -) { - if ( - 'lifetimeConstraint' in transaction && - transaction.lifetimeConstraint.blockhash === blockhashLifetimeConstraint.blockhash && - transaction.lifetimeConstraint.lastValidBlockHeight === blockhashLifetimeConstraint.lastValidBlockHeight - ) { - return transaction; - } - const out = { - // A change in lifetime constraint implies that any existing signatures are invalid. - ...getUnsignedTransaction(transaction), - lifetimeConstraint: blockhashLifetimeConstraint, - }; - Object.freeze(out); - return out; -} diff --git a/packages/transactions/src/compilable-transaction.ts b/packages/transactions/src/compilable-transaction.ts deleted file mode 100644 index 91a0d0ccdb2c..000000000000 --- a/packages/transactions/src/compilable-transaction.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IInstruction } from '@solana/instructions'; - -import { ITransactionWithBlockhashLifetime } from './blockhash'; -import { IDurableNonceTransaction } from './durable-nonce'; -import { ITransactionWithFeePayer } from './fee-payer'; -import { BaseTransaction, TransactionVersion } from './types'; - -export type CompilableTransaction< - TVersion extends TransactionVersion = TransactionVersion, - TInstruction extends IInstruction = IInstruction, -> = BaseTransaction & - ITransactionWithFeePayer & - (IDurableNonceTransaction | ITransactionWithBlockhashLifetime); diff --git a/packages/transactions/src/compile-transaction.ts b/packages/transactions/src/compile-transaction.ts deleted file mode 100644 index 1e8041e32608..000000000000 --- a/packages/transactions/src/compile-transaction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { SignatureBytes } from '@solana/keys'; - -import { CompilableTransaction } from './compilable-transaction'; -import { CompiledMessage, compileTransactionMessage } from './message'; -import { ITransactionWithSignatures } from './signatures'; - -export type CompiledTransaction = Readonly<{ - compiledMessage: CompiledMessage; - signatures: SignatureBytes[]; -}>; - -export function getCompiledTransaction( - transaction: CompilableTransaction | (CompilableTransaction & ITransactionWithSignatures), -): CompiledTransaction { - const compiledMessage = compileTransactionMessage(transaction); - let signatures; - if ('signatures' in transaction) { - signatures = []; - for (let ii = 0; ii < compiledMessage.header.numSignerAccounts; ii++) { - signatures[ii] = - transaction.signatures[compiledMessage.staticAccounts[ii]] ?? new Uint8Array(Array(64).fill(0)); - } - } else { - signatures = Array(compiledMessage.header.numSignerAccounts).fill(new Uint8Array(Array(64).fill(0))); - } - return { - compiledMessage, - signatures, - }; -} diff --git a/packages/transactions/src/create-transaction.ts b/packages/transactions/src/create-transaction.ts deleted file mode 100644 index 05ecdbdc65ab..000000000000 --- a/packages/transactions/src/create-transaction.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Transaction, TransactionVersion } from './types'; - -type TransactionConfig = Readonly<{ - version: TVersion; -}>; - -export function createTransaction( - config: TransactionConfig, -): Extract; -export function createTransaction({ - version, -}: TransactionConfig): Transaction { - const out: Transaction = { - instructions: [], - version, - }; - Object.freeze(out); - return out; -} diff --git a/packages/transactions/src/durable-nonce.ts b/packages/transactions/src/durable-nonce.ts deleted file mode 100644 index 53ed10576f43..000000000000 --- a/packages/transactions/src/durable-nonce.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { Address } from '@solana/addresses'; -import { SOLANA_ERROR__TRANSACTION__EXPECTED_NONCE_LIFETIME, SolanaError } from '@solana/errors'; -import { - AccountRole, - IInstruction, - IInstructionWithAccounts, - IInstructionWithData, - isSignerRole, - ReadonlyAccount, - ReadonlySignerAccount, - WritableAccount, - WritableSignerAccount, -} from '@solana/instructions'; - -import { ITransactionWithSignatures } from './signatures'; -import { BaseTransaction } from './types'; -import { getUnsignedTransaction } from './unsigned-transaction'; - -type AdvanceNonceAccountInstruction< - TNonceAccountAddress extends string = string, - TNonceAuthorityAddress extends string = string, -> = IInstruction<'11111111111111111111111111111111'> & - IInstructionWithAccounts< - readonly [ - WritableAccount, - ReadonlyAccount<'SysvarRecentB1ockHashes11111111111111111111'>, - ReadonlySignerAccount | WritableSignerAccount, - ] - > & - IInstructionWithData; -type AdvanceNonceAccountInstructionData = Uint8Array & { - readonly __brand: unique symbol; -}; -type DurableNonceConfig< - TNonceAccountAddress extends string = string, - TNonceAuthorityAddress extends string = string, - TNonceValue extends string = string, -> = Readonly<{ - readonly nonce: Nonce; - readonly nonceAccountAddress: Address; - readonly nonceAuthorityAddress: Address; -}>; -export type Nonce = TNonceValue & { readonly __brand: unique symbol }; -type NonceLifetimeConstraint = Readonly<{ - nonce: Nonce; -}>; - -const RECENT_BLOCKHASHES_SYSVAR_ADDRESS = - 'SysvarRecentB1ockHashes11111111111111111111' as Address<'SysvarRecentB1ockHashes11111111111111111111'>; -const SYSTEM_PROGRAM_ADDRESS = '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>; - -export interface IDurableNonceTransaction< - TNonceAccountAddress extends string = string, - TNonceAuthorityAddress extends string = string, - TNonceValue extends string = string, -> { - readonly instructions: readonly [ - // The first instruction *must* be the system program's `AdvanceNonceAccount` instruction. - AdvanceNonceAccountInstruction, - ...IInstruction[], - ]; - readonly lifetimeConstraint: NonceLifetimeConstraint; -} - -export function assertIsDurableNonceTransaction( - transaction: BaseTransaction | (BaseTransaction & IDurableNonceTransaction), -): asserts transaction is BaseTransaction & IDurableNonceTransaction { - if (!isDurableNonceTransaction(transaction)) { - throw new SolanaError(SOLANA_ERROR__TRANSACTION__EXPECTED_NONCE_LIFETIME); - } -} - -function createAdvanceNonceAccountInstruction< - TNonceAccountAddress extends string = string, - TNonceAuthorityAddress extends string = string, ->( - nonceAccountAddress: Address, - nonceAuthorityAddress: Address, -): AdvanceNonceAccountInstruction { - return { - accounts: [ - { address: nonceAccountAddress, role: AccountRole.WRITABLE }, - { - address: RECENT_BLOCKHASHES_SYSVAR_ADDRESS, - role: AccountRole.READONLY, - }, - { address: nonceAuthorityAddress, role: AccountRole.READONLY_SIGNER }, - ], - data: new Uint8Array([4, 0, 0, 0]) as AdvanceNonceAccountInstructionData, - programAddress: SYSTEM_PROGRAM_ADDRESS, - }; -} - -export function isAdvanceNonceAccountInstruction( - instruction: IInstruction, -): instruction is AdvanceNonceAccountInstruction { - return ( - instruction.programAddress === SYSTEM_PROGRAM_ADDRESS && - // Test for `AdvanceNonceAccount` instruction data - instruction.data != null && - isAdvanceNonceAccountInstructionData(instruction.data) && - // Test for exactly 3 accounts - instruction.accounts?.length === 3 && - // First account is nonce account address - instruction.accounts[0].address != null && - instruction.accounts[0].role === AccountRole.WRITABLE && - // Second account is recent blockhashes sysvar - instruction.accounts[1].address === RECENT_BLOCKHASHES_SYSVAR_ADDRESS && - instruction.accounts[1].role === AccountRole.READONLY && - // Third account is nonce authority account - instruction.accounts[2].address != null && - isSignerRole(instruction.accounts[2].role) - ); -} - -function isAdvanceNonceAccountInstructionData(data: Uint8Array): data is AdvanceNonceAccountInstructionData { - // AdvanceNonceAccount is the fifth instruction in the System Program (index 4) - return data.byteLength === 4 && data[0] === 4 && data[1] === 0 && data[2] === 0 && data[3] === 0; -} - -function isDurableNonceTransaction( - transaction: BaseTransaction | (BaseTransaction & IDurableNonceTransaction), -): transaction is BaseTransaction & IDurableNonceTransaction { - return ( - 'lifetimeConstraint' in transaction && - typeof transaction.lifetimeConstraint.nonce === 'string' && - transaction.instructions[0] != null && - isAdvanceNonceAccountInstruction(transaction.instructions[0]) - ); -} - -function isAdvanceNonceAccountInstructionForNonce< - TNonceAccountAddress extends Address = Address, - TNonceAuthorityAddress extends Address = Address, ->( - instruction: AdvanceNonceAccountInstruction, - nonceAccountAddress: TNonceAccountAddress, - nonceAuthorityAddress: TNonceAuthorityAddress, -): instruction is AdvanceNonceAccountInstruction { - return ( - instruction.accounts[0].address === nonceAccountAddress && - instruction.accounts[2].address === nonceAuthorityAddress - ); -} - -export function setTransactionLifetimeUsingDurableNonce< - TTransaction extends BaseTransaction, - TNonceAccountAddress extends string = string, - TNonceAuthorityAddress extends string = string, - TNonceValue extends string = string, ->( - { - nonce, - nonceAccountAddress, - nonceAuthorityAddress, - }: DurableNonceConfig, - transaction: TTransaction | (IDurableNonceTransaction & TTransaction), -): IDurableNonceTransaction & - Omit { - let newInstructions: [ - AdvanceNonceAccountInstruction, - ...IInstruction[], - ]; - - const firstInstruction = transaction.instructions[0]; - if (firstInstruction && isAdvanceNonceAccountInstruction(firstInstruction)) { - if (isAdvanceNonceAccountInstructionForNonce(firstInstruction, nonceAccountAddress, nonceAuthorityAddress)) { - if (isDurableNonceTransaction(transaction) && transaction.lifetimeConstraint.nonce === nonce) { - return transaction as IDurableNonceTransaction< - TNonceAccountAddress, - TNonceAuthorityAddress, - TNonceValue - > & - TTransaction; - } else { - // we already have the right first instruction, leave it as-is - newInstructions = [firstInstruction, ...transaction.instructions.slice(1)]; - } - } else { - // we have a different advance nonce instruction as the first instruction, replace it - newInstructions = [ - createAdvanceNonceAccountInstruction(nonceAccountAddress, nonceAuthorityAddress), - ...transaction.instructions.slice(1), - ]; - } - } else { - // we don't have an existing advance nonce instruction as the first instruction, prepend one - newInstructions = [ - createAdvanceNonceAccountInstruction(nonceAccountAddress, nonceAuthorityAddress), - ...transaction.instructions, - ]; - } - - const out = { - // A change in lifetime constraint implies that any existing signatures are invalid. - ...getUnsignedTransaction(transaction), - instructions: newInstructions, - lifetimeConstraint: { - nonce, - }, - } as IDurableNonceTransaction & TTransaction; - Object.freeze(out); - return out; -} diff --git a/packages/transactions/src/fee-payer.ts b/packages/transactions/src/fee-payer.ts deleted file mode 100644 index 9fda53a73596..000000000000 --- a/packages/transactions/src/fee-payer.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Address } from '@solana/addresses'; - -import { ITransactionWithSignatures } from './signatures'; -import { BaseTransaction } from './types'; -import { getUnsignedTransaction } from './unsigned-transaction'; - -export interface ITransactionWithFeePayer { - readonly feePayer: Address; -} - -export function setTransactionFeePayer( - feePayer: Address, - transaction: - | (ITransactionWithFeePayer & ITransactionWithSignatures & TTransaction) - | (ITransactionWithSignatures & TTransaction), -): ITransactionWithFeePayer & Omit; - -export function setTransactionFeePayer( - feePayer: Address, - transaction: TTransaction | (ITransactionWithFeePayer & TTransaction), -): ITransactionWithFeePayer & TTransaction; - -export function setTransactionFeePayer( - feePayer: Address, - transaction: - | TTransaction - | (ITransactionWithFeePayer & ITransactionWithSignatures & TTransaction) - | (ITransactionWithFeePayer & TTransaction) - | (ITransactionWithSignatures & TTransaction), -) { - if ('feePayer' in transaction && feePayer === transaction.feePayer) { - return transaction; - } - const out = { - // A change in fee payer implies that any existing signatures are invalid. - ...getUnsignedTransaction(transaction), - feePayer, - }; - Object.freeze(out); - return out; -} diff --git a/packages/transactions/src/index.ts b/packages/transactions/src/index.ts index 5ad6d501751d..aff00b88ca19 100644 --- a/packages/transactions/src/index.ts +++ b/packages/transactions/src/index.ts @@ -1,6 +1,4 @@ export * from './codecs'; -export * from './create-transaction'; // will be removed soon, only used by programs code that is removed in an open PR -export * from './instructions'; // will be removed soon, only used by programs code that is removed in an open PR export * from './lifetime'; export * from './new-compile-transaction'; export * from './new-signatures'; diff --git a/packages/transactions/src/instructions.ts b/packages/transactions/src/instructions.ts deleted file mode 100644 index 59455d999429..000000000000 --- a/packages/transactions/src/instructions.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ITransactionWithSignatures } from './signatures'; -import { BaseTransaction } from './types'; -import { getUnsignedTransaction } from './unsigned-transaction'; - -export function appendTransactionInstruction( - instruction: TTransaction['instructions'][number], - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): Omit | TTransaction { - return appendTransactionInstructions([instruction], transaction); -} - -export function appendTransactionInstructions( - instructions: ReadonlyArray, - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): Omit | TTransaction { - const out = { - // The addition of an instruction implies that any existing signatures are invalid. - ...getUnsignedTransaction(transaction), - instructions: [...transaction.instructions, ...instructions], - }; - Object.freeze(out); - return out; -} - -export function prependTransactionInstruction( - instruction: TTransaction['instructions'][number], - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): Omit | TTransaction { - return prependTransactionInstructions([instruction], transaction); -} - -export function prependTransactionInstructions( - instructions: ReadonlyArray, - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): Omit | TTransaction { - const out = { - // The addition of an instruction implies that any existing signatures are invalid. - ...getUnsignedTransaction(transaction), - instructions: [...instructions, ...transaction.instructions], - }; - Object.freeze(out); - return out; -} diff --git a/packages/transactions/src/lifetime.ts b/packages/transactions/src/lifetime.ts index 7d2ff9b31176..6bb31bfcf083 100644 --- a/packages/transactions/src/lifetime.ts +++ b/packages/transactions/src/lifetime.ts @@ -23,9 +23,3 @@ export type TransactionWithBlockhashLifetime = { export type TransactionWithDurableNonceLifetime = { readonly lifetimeConstraint: TransactionDurableNonceLifetime; }; - -export function isTransactionBlockhashLifetime( - lifetime: TransactionWithLifetime['lifetimeConstraint'], -): lifetime is TransactionBlockhashLifetime { - return 'blockhash' in lifetime; -} diff --git a/packages/transactions/src/message.ts b/packages/transactions/src/message.ts deleted file mode 100644 index d20113853394..000000000000 --- a/packages/transactions/src/message.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - getAddressMapFromInstructions, - getCompiledAddressTableLookups, - getCompiledInstructions, - getCompiledLifetimeToken, - getCompiledMessageHeader, - getCompiledStaticAccounts, - getOrderedAccountsFromAddressMap, -} from '@solana/transaction-messages'; - -import { CompilableTransaction } from './compilable-transaction'; - -type BaseCompiledMessage = Readonly<{ - header: ReturnType; - instructions: ReturnType; - lifetimeToken: ReturnType; - staticAccounts: ReturnType; -}>; - -export type CompiledMessage = LegacyCompiledMessage | VersionedCompiledMessage; - -type LegacyCompiledMessage = BaseCompiledMessage & - Readonly<{ - version: 'legacy'; - }>; - -type VersionedCompiledMessage = BaseCompiledMessage & - Readonly<{ - addressTableLookups?: ReturnType; - version: number; - }>; - -export function compileTransactionMessage( - transaction: CompilableTransaction & Readonly<{ version: 'legacy' }>, -): LegacyCompiledMessage; -export function compileTransactionMessage(transaction: CompilableTransaction): VersionedCompiledMessage; -export function compileTransactionMessage(transaction: CompilableTransaction): CompiledMessage { - const addressMap = getAddressMapFromInstructions(transaction.feePayer, transaction.instructions); - const orderedAccounts = getOrderedAccountsFromAddressMap(addressMap); - return { - ...(transaction.version !== 'legacy' - ? { addressTableLookups: getCompiledAddressTableLookups(orderedAccounts) } - : null), - header: getCompiledMessageHeader(orderedAccounts), - instructions: getCompiledInstructions(transaction.instructions, orderedAccounts), - // TODO later: remove this cast, gross hack because of renamed types - // won't apply when the input is from transaction-messages - lifetimeToken: getCompiledLifetimeToken( - transaction.lifetimeConstraint as Parameters[0], - ), - staticAccounts: getCompiledStaticAccounts(orderedAccounts), - version: transaction.version, - }; -} diff --git a/packages/transactions/src/serializers/__tests__/message-test.ts b/packages/transactions/src/serializers/__tests__/message-test.ts deleted file mode 100644 index f1c6b8566ca2..000000000000 --- a/packages/transactions/src/serializers/__tests__/message-test.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { Address } from '@solana/addresses'; -import { Decoder, Encoder } from '@solana/codecs-core'; - -import { CompiledMessage } from '../../message'; -import { getCompiledMessageCodec, getCompiledMessageDecoder, getCompiledMessageEncoder } from '../message'; - -describe.each([getCompiledMessageCodec, getCompiledMessageEncoder])( - 'Transaction message serializer %p', - serializerFactory => { - let compiledMessage: Encoder; - beforeEach(() => { - compiledMessage = serializerFactory(); - }); - it('serializes a transaction according to the spec', () => { - const byteArray = compiledMessage.encode({ - addressTableLookups: [ - { - lookupTableAddress: '3yS1JFVT284y8z1LC9MRoWxZjzFrdoD5axKsZiyMsfC7' as Address, // decodes to [44{32}] - readableIndices: [77], - writableIndices: [66, 55], - }, - ], - header: { - numReadonlyNonSignerAccounts: 1, - numReadonlySignerAccounts: 2, - numSignerAccounts: 3, - }, - instructions: [ - { programAddressIndex: 44 }, - { accountIndices: [77, 66], data: new Uint8Array([7, 8, 9]), programAddressIndex: 55 }, - ], - lifetimeToken: '3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL', // decodes to [33{32}] - staticAccounts: [ - 'k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn', // decodes to [11{32}] - '2VDW9dFE1ZXz4zWAbaBDQFynNVdRpQ73HyfSHMzBSL6Z', // decodes to [22{32}] - ] as Address[], - version: 0, - }); - expect(byteArray).toStrictEqual( - // prettier-ignore - new Uint8Array([ - /** VERSION HEADER */ - 128, // 0 + version mask - - /** MESSAGE HEADER */ - 3, // numSignerAccounts - 2, // numReadonlySignerAccount - 1, // numReadonlyNonSignerAccounts - - /** STATIC ADDRESSES */ - 2, // Number of static accounts - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, // 2VDW9dFE1ZXz4zWAbaBDQFynNVdRpQ73HyfSHMzBSL6Z - - /** TRANSACTION LIFETIME TOKEN (ie. the blockhash) */ - 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, // 3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL - - /* INSTRUCTIONS */ - 2, // Number of instructions - - // First instruction - 44, // Program address index - 0, // Number of address indices - 0, // Length of instruction data - - // Second instruction - 55, // Program address index - 2, // Number of address indices - 77, 66, // Address indices - 3, // Length of instruction data - 7, 8, 9, // Instruction data itself - - /** ADDRESS TABLE LOOKUPS */ - 1, // Number of address table lookups - - // First address table lookup - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, // Address of lookup table 3yS1JFVT284y8z1LC9MRoWxZjzFrdoD5axKsZiyMsfC7 - 2, // Number of writable indices - 66, 55, // Writable indices - 1, // Number of readonly indices - 77, // Readonly indices - ]), - ); - }); - it('serializes a versioned transaction with `undefined` address table lookups', () => { - const byteArray = compiledMessage.encode({ - /** `addressTableLookups` is not defined */ - header: { - numReadonlyNonSignerAccounts: 1, - numReadonlySignerAccounts: 2, - numSignerAccounts: 3, - }, - instructions: [], - lifetimeToken: 'k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn', // decodes to [11{32}] - staticAccounts: [], - version: 0, - }); - expect(byteArray).toStrictEqual( - // prettier-ignore - new Uint8Array([ - /** VERSION HEADER */ - 128, // 0 + version mask - - /** MESSAGE HEADER */ - 3, // numSignerAccounts - 2, // numReadonlySignerAccount - 1, // numReadonlyNonSignerAccounts - - /** STATIC ADDRESSES */ - 0, // Number of static accounts - - /** TRANSACTION LIFETIME TOKEN (ie. the blockhash) */ - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // 3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL - - /* INSTRUCTIONS */ - 0, // Number of instructions - - /** ADDRESS TABLE LOOKUPS get serialized despite not being in the source object */ - 0, // Number of address table lookups - ]), - ); - }); - it('omits the version header for `legacy` transactions', () => { - expect( - compiledMessage.encode({ - header: { - numReadonlyNonSignerAccounts: 1, - numReadonlySignerAccounts: 2, - numSignerAccounts: 3, - }, - instructions: [], - lifetimeToken: 'k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn', // decodes to [11{32}] - staticAccounts: [], - version: 'legacy', - }), - ).toStrictEqual( - // prettier-ignore - new Uint8Array([ - /** NO VERSION HEADER */ - - /** MESSAGE HEADER */ - 3, // numSignerAccounts - 2, // numReadonlySignerAccount - 1, // numReadonlyNonSignerAccounts - - /** STATIC ADDRESSES */ - 0, // Number of static accounts - - /** TRANSACTION LIFETIME TOKEN (ie. the blockhash) */ - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn - - /* INSTRUCTIONS */ - 0, // Number of instructions - ]), - ); - }); - it('omits the address table lookups for `legacy` transactions', () => { - expect( - getCompiledMessageCodec().encode({ - header: { - numReadonlyNonSignerAccounts: 1, - numReadonlySignerAccounts: 2, - numSignerAccounts: 3, - }, - instructions: [], - lifetimeToken: 'k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn', // decodes to [11{32}] - staticAccounts: [], - version: 'legacy', - }), - ).toStrictEqual( - // prettier-ignore - new Uint8Array([ - /** MESSAGE HEADER */ - 3, // numSignerAccounts - 2, // numReadonlySignerAccount - 1, // numReadonlyNonSignerAccounts - - /** STATIC ADDRESSES */ - 0, // Number of static accounts - - /** TRANSACTION LIFETIME TOKEN (ie. the blockhash) */ - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn - - /* INSTRUCTIONS */ - 0, // Number of instructions - - /** NO ADDRESS TABLE LOOKUPS */ - ]), - ); - }); - }, -); - -describe.each([getCompiledMessageCodec, getCompiledMessageDecoder])( - 'Transaction message deserializer %p', - serializerFactory => { - let compiledMessage: Decoder; - beforeEach(() => { - compiledMessage = serializerFactory(); - }); - it('deserializes a version 0 transaction according to the spec', () => { - const byteArray = - // prettier-ignore - new Uint8Array([ - /** VERSION HEADER */ - 128, // 0 + version mask - - /** MESSAGE HEADER */ - 3, // numSignerAccounts - 2, // numReadonlySignerAccount - 1, // numReadonlyNonSignerAccounts - - /** STATIC ADDRESSES */ - 2, // Number of static accounts - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, // 2VDW9dFE1ZXz4zWAbaBDQFynNVdRpQ73HyfSHMzBSL6Z - - /** TRANSACTION LIFETIME TOKEN (ie. the blockhash) */ - 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, // 3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL - - /* INSTRUCTIONS */ - 2, // Number of instructions - - // First instruction - 44, // Program address index - 0, // Number of address indices - 0, // Length of instruction data - - // Second instruction - 55, // Program address index - 2, // Number of address indices - 77, 66, // Address indices - 3, // Length of instruction data - 7, 8, 9, // Instruction data itself - - /** ADDRESS TABLE LOOKUPS */ - 1, // Number of address table lookups - - // First address table lookup - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, // Address of lookup table 3yS1JFVT284y8z1LC9MRoWxZjzFrdoD5axKsZiyMsfC7 - 2, // Number of writable indices - 66, 55, // Writable indices - 1, // Number of readonly indices - 77, // Readonly indices - ]); - const [message, offset] = compiledMessage.read(byteArray, 0); - expect(message).toStrictEqual({ - addressTableLookups: [ - { - lookupTableAddress: '3yS1JFVT284y8z1LC9MRoWxZjzFrdoD5axKsZiyMsfC7' as Address, // decodes to [44{32}] - readableIndices: [77], - writableIndices: [66, 55], - }, - ], - header: { - numReadonlyNonSignerAccounts: 1, - numReadonlySignerAccounts: 2, - numSignerAccounts: 3, - }, - instructions: [ - { programAddressIndex: 44 }, - { accountIndices: [77, 66], data: new Uint8Array([7, 8, 9]), programAddressIndex: 55 }, - ], - lifetimeToken: '3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL', // decodes to [33{32}] - staticAccounts: [ - 'k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn', // decodes to [11{32}] - '2VDW9dFE1ZXz4zWAbaBDQFynNVdRpQ73HyfSHMzBSL6Z', // decodes to [22{32}] - ] as Address[], - version: 0, - }); - // Expect the entire byte array to have been consumed. - expect(offset).toBe(byteArray.byteLength); - }); - it('omits the `addressTableLookups` property of a versioned transaction when the address table lookups are zero-length', () => { - expect( - compiledMessage.decode( - // prettier-ignore - new Uint8Array([ - /** VERSION HEADER */ - 128, // 0 + version mask - - /** MESSAGE HEADER */ - 3, // numSignerAccounts - 2, // numReadonlySignerAccount - 1, // numReadonlyNonSignerAccounts - - /** STATIC ADDRESSES */ - 0, // Number of static accounts - - /** TRANSACTION LIFETIME TOKEN (ie. the blockhash) */ - 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, // 3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL - - /* INSTRUCTIONS */ - 0, // Number of instructions - ]), - ), - ).not.toHaveProperty('addressTableLookups'); - }); - it('deserializes a legacy transaction according to the spec', () => { - const byteArray = - // prettier-ignore - new Uint8Array([ - /** MESSAGE HEADER */ - 3, // numSignerAccounts - 2, // numReadonlySignerAccount - 1, // numReadonlyNonSignerAccounts - - /** STATIC ADDRESSES */ - 2, // Number of static accounts - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, // 2VDW9dFE1ZXz4zWAbaBDQFynNVdRpQ73HyfSHMzBSL6Z - - /** TRANSACTION LIFETIME TOKEN (ie. the blockhash) */ - 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, // 3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL - - /* INSTRUCTIONS */ - 2, // Number of instructions - - // First instruction - 44, // Program address index - 0, // Number of address indices - 0, // Length of instruction data - - // Second instruction - 55, // Program address index - 2, // Number of address indices - 77, 66, // Address indices - 3, // Length of instruction data - 7, 8, 9, // Instruction data itself - ]); - const [message, offset] = compiledMessage.read(byteArray, 0); - expect(message).toStrictEqual({ - header: { - numReadonlyNonSignerAccounts: 1, - numReadonlySignerAccounts: 2, - numSignerAccounts: 3, - }, - instructions: [ - { programAddressIndex: 44 }, - { accountIndices: [77, 66], data: new Uint8Array([7, 8, 9]), programAddressIndex: 55 }, - ], - lifetimeToken: '3EKkiwNLWqoUbzFkPrmKbtUB4EweE6f4STzevYUmezeL', // decodes to [33{32}] - staticAccounts: [ - 'k7FaK87WHGVXzkaoHb7CdVPgkKDQhZ29VLDeBVbDfYn', // decodes to [11{32}] - '2VDW9dFE1ZXz4zWAbaBDQFynNVdRpQ73HyfSHMzBSL6Z', // decodes to [22{32}] - ] as Address[], - version: 'legacy', - }); - // Expect the entire byte array to have been consumed. - expect(offset).toBe(byteArray.byteLength); - }); - }, -); diff --git a/packages/transactions/src/serializers/__tests__/transaction-test.ts b/packages/transactions/src/serializers/__tests__/transaction-test.ts deleted file mode 100644 index 6484d8ddf895..000000000000 --- a/packages/transactions/src/serializers/__tests__/transaction-test.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { Address } from '@solana/addresses'; -import { AccountRole } from '@solana/instructions'; -import { AddressesByLookupTableAddress, decompileTransactionMessage } from '@solana/transaction-messages'; - -import { CompiledMessage, compileTransactionMessage } from '../../message'; -import { getCompiledMessageDecoder, getCompiledMessageEncoder } from '../message'; -import { getTransactionCodec, getTransactionDecoder, getTransactionEncoder } from '../transaction'; - -jest.mock('../../message'); -jest.mock('../message'); -jest.mock('@solana/transaction-messages', () => ({ - ...jest.requireActual('@solana/transaction-messages'), - decompileTransactionMessage: jest.fn(), -})); - -let _nextMockAddress = 0; -function getMockAddress() { - return `${_nextMockAddress++}` as Address; -} - -describe.each([getTransactionEncoder, getTransactionCodec])('Transaction serializer %p', serializerFactory => { - let transaction: ReturnType; - - let addressA: Address; - let addressB: Address; - let mockCompiledMessage: CompiledMessage; - let mockCompiledWireMessage: Uint8Array; - beforeEach(() => { - addressA = getMockAddress(); - addressB = getMockAddress(); - mockCompiledMessage = { - header: { - numReadonlyNonSignerAccounts: 0, - numReadonlySignerAccounts: 1, - numSignerAccounts: 2, - }, - staticAccounts: [addressB, addressA], - } as CompiledMessage; - mockCompiledWireMessage = new Uint8Array([1, 2, 3]); - (getCompiledMessageEncoder as jest.Mock).mockReturnValue({ - getSizeFromValue: jest.fn().mockReturnValue(mockCompiledWireMessage.length), - write: jest.fn().mockImplementation((_value, bytes: Uint8Array, offset: number) => { - bytes.set(mockCompiledWireMessage, offset); - return offset + mockCompiledWireMessage.length; - }), - }); - (getCompiledMessageDecoder as jest.Mock).mockReturnValue({ - read: jest.fn().mockReturnValue([mockCompiledMessage, 0]), - }); - (compileTransactionMessage as jest.Mock).mockReturnValue(mockCompiledMessage); - transaction = serializerFactory({}); - }); - it('serializes a transaction with no signatures', () => { - expect(transaction.encode({} as Parameters[0])).toStrictEqual( - new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...Array(64).fill(0), // null signature for `addressB` - ...Array(64).fill(0), // null signature for `addressA` - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]), - ); - }); - it('serializes a partially signed transaction', () => { - const mockSignatureA = new Uint8Array(Array(64).fill(1)); - expect( - transaction.encode({ signatures: { [addressA]: mockSignatureA } } as Parameters< - typeof transaction.encode - >[0]), - ).toStrictEqual( - new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...Array(64).fill(0), // null signature for `addressB` - ...mockSignatureA, - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]), - ); - }); - it('serializes a fully signed transaction', () => { - const mockSignatureA = new Uint8Array(Array(64).fill(1)); - const mockSignatureB = new Uint8Array(Array(64).fill(2)); - expect( - transaction.encode({ - signatures: { [addressA]: mockSignatureA, [addressB]: mockSignatureB }, - } as Parameters[0]), - ).toStrictEqual( - new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...mockSignatureB, - ...mockSignatureA, - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]), - ); - }); -}); - -describe.each([getTransactionDecoder, getTransactionCodec])('Transaction deserializer %p', deserializerFactory => { - let transaction: ReturnType; - - let addressA: Address; - let addressB: Address; - let mockCompiledMessage: CompiledMessage; - let mockCompiledWireMessage: Uint8Array; - let mockDecompiledTransactionMessage: ReturnType; - - beforeEach(() => { - addressA = getMockAddress(); - addressB = getMockAddress(); - mockCompiledMessage = { - header: { - numReadonlyNonSignerAccounts: 0, - numReadonlySignerAccounts: 1, - numSignerAccounts: 2, - }, - staticAccounts: [addressB, addressA], - } as CompiledMessage; - mockCompiledWireMessage = new Uint8Array([1, 2, 3]); - mockDecompiledTransactionMessage = { - instructions: [ - { - accounts: [ - { - address: addressA, - role: AccountRole.WRITABLE_SIGNER, - }, - { - address: addressB, - role: AccountRole.WRITABLE_SIGNER, - }, - ], - }, - ], - } as unknown as ReturnType; - - (getCompiledMessageEncoder as jest.Mock).mockReturnValue({ - getSizeFromValue: jest.fn().mockReturnValue(mockCompiledWireMessage.length), - write: jest.fn().mockImplementation((_value, bytes: Uint8Array, offset: number) => { - bytes.set(mockCompiledWireMessage, offset); - return offset + mockCompiledWireMessage.length; - }), - }); - (getCompiledMessageDecoder as jest.Mock).mockReturnValue({ - read: jest.fn().mockReturnValue([mockCompiledMessage, 0]), - }); - (decompileTransactionMessage as jest.Mock).mockReturnValue(mockDecompiledTransactionMessage); - - transaction = deserializerFactory(); - }); - it('deserializes a transaction with no signatures', () => { - const noSignature = new Uint8Array(Array(64).fill(0)); - const bytes = new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...noSignature, // null signature for `addressB` - ...noSignature, // null signature for `addressA` - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]); - - const decodedTransaction = transaction.decode(bytes); - expect(decodedTransaction).toStrictEqual(mockDecompiledTransactionMessage); - expect(decompileTransactionMessage).toHaveBeenCalledWith(mockCompiledMessage, undefined); - }); - it('deserializes a partially signed transaction', () => { - const noSignature = new Uint8Array(Array(64).fill(0)); - const mockSignatureA = new Uint8Array(Array(64).fill(1)); - - const bytes = new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...noSignature, // null signature for `addressB` - ...mockSignatureA, - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]); - - const decodedTransaction = transaction.decode(bytes); - const expected = { - ...mockDecompiledTransactionMessage, - signatures: { - [addressA]: mockSignatureA, - }, - }; - expect(decodedTransaction).toStrictEqual(expected); - expect(decompileTransactionMessage).toHaveBeenCalledWith(mockCompiledMessage, undefined); - }); - it('deserializes a fully signed transaction', () => { - const mockSignatureA = new Uint8Array(Array(64).fill(1)); - const mockSignatureB = new Uint8Array(Array(64).fill(2)); - - const bytes = new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...mockSignatureB, - ...mockSignatureA, - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]); - - const decodedTransaction = transaction.decode(bytes); - const expected = { - ...mockDecompiledTransactionMessage, - signatures: { - [addressA]: mockSignatureA, - [addressB]: mockSignatureB, - }, - }; - expect(decodedTransaction).toStrictEqual(expected); - expect(decompileTransactionMessage).toHaveBeenCalledWith(mockCompiledMessage, undefined); - }); - it('passes lastValidBlockHeight to decompileTransactionMessage', () => { - const noSignature = new Uint8Array(Array(64).fill(0)); - const bytes = new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...noSignature, // null signature for `addressB` - ...noSignature, // null signature for `addressA` - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]); - - const transaction = deserializerFactory({ lastValidBlockHeight: 100n }); - const decodedTransaction = transaction.decode(bytes); - expect(decodedTransaction).toStrictEqual(mockDecompiledTransactionMessage); - expect(decompileTransactionMessage).toHaveBeenCalledWith(mockCompiledMessage, { - lastValidBlockHeight: 100n, - }); - }); - it('passes lookupTables to decompileTransactionMessage', () => { - const noSignature = new Uint8Array(Array(64).fill(0)); - const bytes = new Uint8Array([ - /** SIGNATURES */ - 2, // Length of signatures array - ...noSignature, // null signature for `addressB` - ...noSignature, // null signature for `addressA` - - /** COMPILED MESSAGE */ - ...mockCompiledWireMessage, - ]); - - const lookupTables: AddressesByLookupTableAddress = { - ['1111' as Address]: ['2222' as Address, '3333' as Address], - ['4444' as Address]: ['5555' as Address, '6666' as Address], - }; - - const transaction = deserializerFactory({ addressesByLookupTableAddress: lookupTables }); - const decodedTransaction = transaction.decode(bytes); - expect(decodedTransaction).toStrictEqual(mockDecompiledTransactionMessage); - expect(decompileTransactionMessage).toHaveBeenCalledWith(mockCompiledMessage, { - addressesByLookupTableAddress: lookupTables, - }); - }); -}); diff --git a/packages/transactions/src/serializers/index.ts b/packages/transactions/src/serializers/index.ts deleted file mode 100644 index 2b2782cdb188..000000000000 --- a/packages/transactions/src/serializers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './message'; -export * from './transaction'; diff --git a/packages/transactions/src/serializers/message.ts b/packages/transactions/src/serializers/message.ts deleted file mode 100644 index 7b1d97e781ae..000000000000 --- a/packages/transactions/src/serializers/message.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { getAddressDecoder, getAddressEncoder } from '@solana/addresses'; -import { - combineCodec, - createEncoder, - Decoder, - fixDecoderSize, - fixEncoderSize, - transformDecoder, - transformEncoder, - VariableSizeCodec, - VariableSizeDecoder, - VariableSizeEncoder, -} from '@solana/codecs-core'; -import { getArrayDecoder, getArrayEncoder, getStructDecoder, getStructEncoder } from '@solana/codecs-data-structures'; -import { getShortU16Decoder, getShortU16Encoder } from '@solana/codecs-numbers'; -import { getBase58Decoder, getBase58Encoder } from '@solana/codecs-strings'; -import type { getCompiledAddressTableLookups } from '@solana/transaction-messages'; -import { - getAddressTableLookupDecoder, - getAddressTableLookupEncoder, - getInstructionDecoder, - getInstructionEncoder, - getMessageHeaderDecoder, - getMessageHeaderEncoder, - getTransactionVersionDecoder, - getTransactionVersionEncoder, -} from '@solana/transaction-messages'; - -import { CompiledMessage } from '../message'; - -function getCompiledMessageLegacyEncoder(): VariableSizeEncoder { - return getStructEncoder(getPreludeStructEncoderTuple()) as VariableSizeEncoder; -} - -function getCompiledMessageVersionedEncoder(): VariableSizeEncoder { - return transformEncoder( - getStructEncoder([ - ...getPreludeStructEncoderTuple(), - ['addressTableLookups', getAddressTableLookupArrayEncoder()], - ]) as VariableSizeEncoder, - (value: CompiledMessage) => { - if (value.version === 'legacy') { - return value; - } - return { - ...value, - addressTableLookups: value.addressTableLookups ?? [], - } as Exclude; - }, - ) as VariableSizeEncoder; -} - -function getPreludeStructEncoderTuple() { - return [ - ['version', getTransactionVersionEncoder()], - ['header', getMessageHeaderEncoder()], - ['staticAccounts', getArrayEncoder(getAddressEncoder(), { size: getShortU16Encoder() })], - ['lifetimeToken', fixEncoderSize(getBase58Encoder(), 32)], - ['instructions', getArrayEncoder(getInstructionEncoder(), { size: getShortU16Encoder() })], - ] as const; -} - -function getPreludeStructDecoderTuple() { - return [ - ['version', getTransactionVersionDecoder() as Decoder], - ['header', getMessageHeaderDecoder()], - ['staticAccounts', getArrayDecoder(getAddressDecoder(), { size: getShortU16Decoder() })], - ['lifetimeToken', fixDecoderSize(getBase58Decoder(), 32)], - ['instructions', getArrayDecoder(getInstructionDecoder(), { size: getShortU16Decoder() })], - ['addressTableLookups', getAddressTableLookupArrayDecoder()], - ] as const; -} - -function getAddressTableLookupArrayEncoder() { - return getArrayEncoder(getAddressTableLookupEncoder(), { size: getShortU16Encoder() }); -} - -function getAddressTableLookupArrayDecoder() { - return getArrayDecoder(getAddressTableLookupDecoder(), { size: getShortU16Decoder() }); -} - -export function getCompiledMessageEncoder(): VariableSizeEncoder { - return createEncoder({ - getSizeFromValue: (compiledMessage: CompiledMessage) => { - if (compiledMessage.version === 'legacy') { - return getCompiledMessageLegacyEncoder().getSizeFromValue(compiledMessage); - } else { - return getCompiledMessageVersionedEncoder().getSizeFromValue(compiledMessage); - } - }, - write: (compiledMessage, bytes, offset) => { - if (compiledMessage.version === 'legacy') { - return getCompiledMessageLegacyEncoder().write(compiledMessage, bytes, offset); - } else { - return getCompiledMessageVersionedEncoder().write(compiledMessage, bytes, offset); - } - }, - }); -} - -export function getCompiledMessageDecoder(): VariableSizeDecoder { - return transformDecoder( - getStructDecoder(getPreludeStructDecoderTuple()) as VariableSizeDecoder< - CompiledMessage & { addressTableLookups?: ReturnType } - >, - ({ addressTableLookups, ...restOfMessage }) => { - if (restOfMessage.version === 'legacy' || !addressTableLookups?.length) { - return restOfMessage; - } - return { ...restOfMessage, addressTableLookups } as Exclude< - CompiledMessage, - { readonly version: 'legacy' } - >; - }, - ); -} - -export function getCompiledMessageCodec(): VariableSizeCodec { - return combineCodec(getCompiledMessageEncoder(), getCompiledMessageDecoder()); -} diff --git a/packages/transactions/src/serializers/transaction.ts b/packages/transactions/src/serializers/transaction.ts deleted file mode 100644 index 2f0b74494781..000000000000 --- a/packages/transactions/src/serializers/transaction.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - combineCodec, - fixDecoderSize, - FixedSizeDecoder, - fixEncoderSize, - transformDecoder, - transformEncoder, - VariableSizeCodec, - VariableSizeDecoder, - VariableSizeEncoder, -} from '@solana/codecs-core'; -import { - getArrayDecoder, - getArrayEncoder, - getBytesDecoder, - getBytesEncoder, - getStructDecoder, - getStructEncoder, -} from '@solana/codecs-data-structures'; -import { getShortU16Decoder, getShortU16Encoder } from '@solana/codecs-numbers'; -import { SignatureBytes } from '@solana/keys'; -import { decompileTransactionMessage, DecompileTransactionMessageConfig } from '@solana/transaction-messages'; - -import { CompilableTransaction } from '../compilable-transaction'; -import { CompiledTransaction, getCompiledTransaction } from '../compile-transaction'; -import { ITransactionWithSignatures } from '../signatures'; -import { getCompiledMessageDecoder, getCompiledMessageEncoder } from './message'; - -function getCompiledTransactionEncoder(): VariableSizeEncoder { - return getStructEncoder([ - ['signatures', getArrayEncoder(fixEncoderSize(getBytesEncoder(), 64), { size: getShortU16Encoder() })], - ['compiledMessage', getCompiledMessageEncoder()], - ]); -} - -export function getCompiledTransactionDecoder(): VariableSizeDecoder { - return getStructDecoder([ - [ - 'signatures', - getArrayDecoder(fixDecoderSize(getBytesDecoder(), 64) as FixedSizeDecoder, { - size: getShortU16Decoder(), - }), - ], - ['compiledMessage', getCompiledMessageDecoder()], - ]); -} - -export function getTransactionEncoder(): VariableSizeEncoder< - CompilableTransaction | (CompilableTransaction & ITransactionWithSignatures) -> { - return transformEncoder(getCompiledTransactionEncoder(), getCompiledTransaction); -} - -export function getTransactionDecoder( - config?: DecompileTransactionMessageConfig, -): VariableSizeDecoder { - return transformDecoder(getCompiledTransactionDecoder(), compiledTransaction => - tempDecompileTransaction(compiledTransaction, config), - ); -} - -export function getTransactionCodec( - config?: DecompileTransactionMessageConfig, -): VariableSizeCodec { - return combineCodec(getTransactionEncoder(), getTransactionDecoder(config)); -} - -// temporary adapter from decompileMessage to our old decompileTransaction -// temporary because this serializer will be removed eventually! -function tempDecompileTransaction( - compiledTransaction: CompiledTransaction, - config?: DecompileTransactionMessageConfig, -): CompilableTransaction | (CompilableTransaction & ITransactionWithSignatures) { - const message = decompileTransactionMessage(compiledTransaction.compiledMessage, config); - const signatures = convertSignatures(compiledTransaction); - - const out = Object.keys(signatures).length > 0 ? { ...message, signatures } : message; - return out as CompilableTransaction | (CompilableTransaction & ITransactionWithSignatures); -} - -// copied from decompile-transaction, -// which has moved to transaction-messages as decompile-message -function convertSignatures(compiledTransaction: CompiledTransaction): ITransactionWithSignatures['signatures'] { - const { - compiledMessage: { staticAccounts }, - signatures, - } = compiledTransaction; - return signatures.reduce((acc, sig, index) => { - // compiled transaction includes a fake all 0 signature if it hasn't been signed - // we don't store those for the ITransactionWithSignatures model. So just skip if it's all 0s - const allZeros = sig.every(byte => byte === 0); - if (allZeros) return acc; - - const address = staticAccounts[index]; - return { ...acc, [address]: sig as SignatureBytes }; - }, {}); -} diff --git a/packages/transactions/src/signatures.ts b/packages/transactions/src/signatures.ts deleted file mode 100644 index 7af4e42d5a92..000000000000 --- a/packages/transactions/src/signatures.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Address, getAddressFromPublicKey } from '@solana/addresses'; -import { Decoder } from '@solana/codecs-core'; -import { getBase58Decoder } from '@solana/codecs-strings'; -import { - SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING, - SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, - SolanaError, -} from '@solana/errors'; -import { isSignerRole } from '@solana/instructions'; -import { Signature, SignatureBytes, signBytes } from '@solana/keys'; - -import { CompilableTransaction } from './compilable-transaction'; -import { ITransactionWithFeePayer } from './fee-payer'; -import { compileTransactionMessage } from './message'; -import { getCompiledMessageEncoder } from './serializers/message'; - -export interface IFullySignedTransaction extends ITransactionWithSignatures { - readonly __brand: unique symbol; -} -export interface ITransactionWithSignatures { - readonly signatures: Readonly>; -} - -let base58Decoder: Decoder | undefined; - -export function getSignatureFromTransaction( - transaction: ITransactionWithFeePayer & ITransactionWithSignatures, -): Signature { - if (!base58Decoder) base58Decoder = getBase58Decoder(); - - const signatureBytes = transaction.signatures[transaction.feePayer]; - if (!signatureBytes) { - throw new SolanaError(SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING); - } - const transactionSignature = base58Decoder.decode(signatureBytes); - return transactionSignature as Signature; -} - -export async function partiallySignTransaction( - keyPairs: CryptoKeyPair[], - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): Promise { - const compiledMessage = compileTransactionMessage(transaction); - const nextSignatures: Record = - 'signatures' in transaction ? { ...transaction.signatures } : {}; - const wireMessageBytes = getCompiledMessageEncoder().encode(compiledMessage); - const publicKeySignaturePairs = await Promise.all( - keyPairs.map(keyPair => - Promise.all([getAddressFromPublicKey(keyPair.publicKey), signBytes(keyPair.privateKey, wireMessageBytes)]), - ), - ); - for (const [signerPublicKey, signature] of publicKeySignaturePairs) { - nextSignatures[signerPublicKey] = signature; - } - const out = { - ...transaction, - signatures: nextSignatures, - }; - Object.freeze(out); - return out; -} - -export async function signTransaction( - keyPairs: CryptoKeyPair[], - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): Promise { - const out = await partiallySignTransaction(keyPairs, transaction); - assertTransactionIsFullySigned(out); - Object.freeze(out); - return out; -} - -export function assertTransactionIsFullySigned( - transaction: ITransactionWithSignatures & TTransaction, -): asserts transaction is IFullySignedTransaction & TTransaction { - const signerAddressesFromInstructions = transaction.instructions - .flatMap(i => i.accounts?.filter(a => isSignerRole(a.role)) ?? []) - .map(a => a.address); - const requiredSigners = new Set([transaction.feePayer, ...signerAddressesFromInstructions]); - - const missingSigs: Address[] = []; - requiredSigners.forEach(address => { - if (!transaction.signatures[address]) { - missingSigs.push(address); - } - }); - - if (missingSigs.length > 0) { - throw new SolanaError(SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING, { - addresses: missingSigs, - }); - } -} diff --git a/packages/transactions/src/types.ts b/packages/transactions/src/types.ts deleted file mode 100644 index 345ac18cc4c2..000000000000 --- a/packages/transactions/src/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IAccountMeta, IInstruction } from '@solana/instructions'; - -export type BaseTransaction< - TVersion extends TransactionVersion = TransactionVersion, - TInstruction extends IInstruction = IInstruction, -> = Readonly<{ - instructions: readonly TInstruction[]; - version: TVersion; -}>; - -type ILegacyInstruction = IInstruction< - TProgramAddress, - readonly IAccountMeta[] ->; -type LegacyTransaction = BaseTransaction<'legacy', ILegacyInstruction>; -type V0Transaction = BaseTransaction<0, IInstruction>; - -export type Transaction = LegacyTransaction | V0Transaction; -export type TransactionVersion = 'legacy' | 0; diff --git a/packages/transactions/src/unsigned-transaction.ts b/packages/transactions/src/unsigned-transaction.ts deleted file mode 100644 index 42bdf59f7bc9..000000000000 --- a/packages/transactions/src/unsigned-transaction.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ITransactionWithSignatures } from './signatures'; -import { BaseTransaction } from './types'; - -export function getUnsignedTransaction( - transaction: TTransaction | (ITransactionWithSignatures & TTransaction), -): Omit | TTransaction { - if ('signatures' in transaction) { - const { - signatures: _, // eslint-disable-line @typescript-eslint/no-unused-vars - ...unsignedTransaction - } = transaction; - return unsignedTransaction; - } else { - return transaction; - } -} diff --git a/packages/transactions/src/wire-transaction.ts b/packages/transactions/src/wire-transaction.ts deleted file mode 100644 index 94c8c9bb5a9a..000000000000 --- a/packages/transactions/src/wire-transaction.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getBase64Decoder } from '@solana/codecs-strings'; - -import { Base64EncodedWireTransaction } from './new-wire-transaction'; -import { getTransactionEncoder } from './serializers/transaction'; - -export function getBase64EncodedWireTransaction( - transaction: Parameters['encode']>[0], -): Base64EncodedWireTransaction { - const wireTransactionBytes = getTransactionEncoder().encode(transaction); - return getBase64Decoder().decode(wireTransactionBytes) as Base64EncodedWireTransaction; -}