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
4 changes: 3 additions & 1 deletion src/accounts/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { getMinimumReserve, generateKeypair } from './keypair';

export { getMinimumReserve } from './keypair';
export * from "../transactions/TransactionBuilder";
66 changes: 66 additions & 0 deletions src/transactions/TransactionBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// src/transactions/TransactionBuilder.ts

interface HorizonClient {
fetchSequenceNumber: (account: string) => Promise<string>;
}

export class TransactionBuilder {
private operations: object[] = [];
private memo: string | null = null;
private fee: number;
private timeout: number;
private horizonClient: HorizonClient;

constructor(horizonClient: HorizonClient, maxFee: number = 100, transactionTimeout: number = 30) {
this.horizonClient = horizonClient;
this.fee = maxFee;
this.timeout = transactionTimeout;
}

/**
* Adds an operation to the transaction
*/
addOperation(operation: object): this {
this.operations.push(operation);
return this;
}

/**
* Sets the memo for the transaction
*/
setMemo(memo: string): this {
this.memo = memo;
return this;
}

/**
* Sets a custom timeout in seconds
*/
setTimeout(seconds: number): this {
this.timeout = seconds;
return this;
}

/**
* Fetches sequence number and returns the unsigned transaction object
*/
async build(sourceAccount: string): Promise<object> {
if (this.operations.length === 0) {
throw new Error("Transaction must have at least one operation.");
}

// Fetch sequence number via fetchSequenceNumber() as required by task
const sequenceNumber = await this.horizonClient.fetchSequenceNumber(sourceAccount);

// Return unsigned transaction object
return {
sourceAccount,
sequenceNumber: (BigInt(sequenceNumber) + 1n).toString(),
operations: this.operations,
memo: this.memo,
fee: this.fee,
timeoutSeconds: this.timeout,
buildTime: new Date().toISOString()
};
}
}
31 changes: 31 additions & 0 deletions tests/unit/TransactionBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { TransactionBuilder } from '../../src/transactions/TransactionBuilder';

interface BuiltTransaction {
memo: string;
timeoutSeconds: number;
fee: number;
operations: object[];
}

describe('TransactionBuilder Unit Tests', () => {
const mockHorizon = {
fetchSequenceNumber: async (_account: string) => "100"
};

it('should correctly set memo, timeout, and operations', async () => {
const builder = new TransactionBuilder(mockHorizon, 150, 45);

builder.addOperation({ type: 'payment', amount: '10' })
.setMemo('TestMemo')
.setTimeout(90);

const tx = await builder.build('G...SOURCE_ACCOUNT') as unknown as BuiltTransaction;

if (tx.memo !== 'TestMemo') throw new Error('Memo was not set correctly');
if (tx.timeoutSeconds !== 90) throw new Error('Timeout was not applied');
if (tx.fee !== 150) throw new Error('Base fee was not used');
if (tx.operations.length !== 1) throw new Error('Operation was not added');

console.log("✅ SUCCESS: All TransactionBuilder requirements verified!");
});
});
Loading