Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/transactions/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}
65 changes: 62 additions & 3 deletions tests/unit/transactions/index.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});