diff --git a/src/transactions/index.ts b/src/transactions/index.ts index 04d4874..979b73c 100644 --- a/src/transactions/index.ts +++ b/src/transactions/index.ts @@ -1,2 +1,25 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function buildMultisigTransaction(..._args: unknown[]): unknown { return undefined; } +import { Keypair, Transaction } from '@stellar/stellar-sdk'; +import { ValidationError } from '../utils/errors'; +import { isValidSecretKey } from '../utils/validation'; + +export function buildMultisigTransaction( + transaction: Transaction, + secretKeys: string[], +): Transaction { + if (!Array.isArray(secretKeys) || secretKeys.length === 0) { + throw new ValidationError('secretKeys', 'At least one secret key is required'); + } + + for (const secretKey of secretKeys) { + if (!isValidSecretKey(secretKey)) { + throw new ValidationError('secretKeys', 'Invalid secret key provided'); + } + } + + for (const secretKey of secretKeys) { + const signer = Keypair.fromSecret(secretKey); + transaction.sign(signer); + } + + return transaction; +} diff --git a/tests/unit/transactions/index.test.ts b/tests/unit/transactions/index.test.ts index e3de91c..245494e 100644 --- a/tests/unit/transactions/index.test.ts +++ b/tests/unit/transactions/index.test.ts @@ -1,8 +1,67 @@ import { buildMultisigTransaction } from '../../../src/transactions'; +import { ValidationError } from '../../../src/utils/errors'; +import { + Account, + BASE_FEE, + Keypair, + Networks, + Operation, + Transaction, + TransactionBuilder, +} from '@stellar/stellar-sdk'; -describe('transactions module placeholders', () => { - it('exports callable placeholder function', () => { - expect(buildMultisigTransaction()).toBeUndefined(); +function buildUnsignedTransaction(): Transaction { + const source = Keypair.random(); + const account = new Account(source.publicKey(), '1'); + + return new TransactionBuilder(account, { + fee: BASE_FEE, + networkPassphrase: Networks.TESTNET, + }) + .addOperation(Operation.bumpSequence({ bumpTo: '2' })) + .setTimeout(30) + .build(); +} + +describe('buildMultisigTransaction', () => { + it('signs transaction with a single secret key', () => { + const signer = Keypair.random(); + const transaction = buildUnsignedTransaction(); + + const signed = buildMultisigTransaction(transaction, [signer.secret()]); + + expect(signed).toBe(transaction); + expect(signed.signatures).toHaveLength(1); + }); + + it('signs transaction with multiple secret keys', () => { + const signerA = Keypair.random(); + const signerB = Keypair.random(); + const transaction = buildUnsignedTransaction(); + + const signed = buildMultisigTransaction(transaction, [signerA.secret(), signerB.secret()]); + + expect(signed).toBe(transaction); + expect(signed.signatures).toHaveLength(2); + }); + + it('throws ValidationError when any secret key is invalid', () => { + const signer = Keypair.random(); + const transaction = buildUnsignedTransaction(); + + expect(() => { + buildMultisigTransaction(transaction, [signer.secret(), 'SINVALID']); + }).toThrow(ValidationError); + expect(transaction.signatures).toHaveLength(0); + }); + + it('throws ValidationError when secret keys array is empty', () => { + const transaction = buildUnsignedTransaction(); + + expect(() => { + buildMultisigTransaction(transaction, []); + }).toThrow(ValidationError); + expect(transaction.signatures).toHaveLength(0); }); });