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
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ export class TransactionStatusCheckerService {
};
}

if (functionName === 'repay_loan') {
if (functionName === 'repay_installment') {
const loanId = nativeArgs[1] as string;
const rawAmount = nativeArgs[2];
const amount = typeof rawAmount === 'bigint' ? Number(rawAmount) / 10_000_000 :
Expand Down
2 changes: 1 addition & 1 deletion src/modules/loans/dto/loan-payment-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class LoanPaymentPreviewDto {
*/
export class LoanPaymentResponseDto {
@ApiProperty({
description: 'Unsigned XDR transaction for the repay_loan() Soroban call',
description: 'Unsigned XDR transaction for the repay_installment() Soroban call',
example: 'AAAAAgAAAAA...',
})
unsignedXdr: string;
Expand Down
2 changes: 1 addition & 1 deletion src/modules/loans/loans.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export class LoansController {
@ApiOperation({
summary: 'Make a loan repayment',
description:
'Validates the payment, constructs an unsigned Soroban repay_loan() transaction, and returns it alongside a payment preview. The mobile app must sign the XDR and submit the signed transaction back to the network. Requires JWT authentication.',
'Validates the payment, constructs an unsigned Soroban repay_installment() transaction, and returns it alongside a payment preview. The mobile app must sign the XDR and submit the signed transaction back to the network. Requires JWT authentication.',
})
@ApiResponse({
status: 200,
Expand Down
2 changes: 1 addition & 1 deletion src/modules/loans/loans.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export class LoansService {
});
}

return this.creditLineContractClient.buildRepayLoanTx(
return this.creditLineContractClient.buildRepayInstallmentTx(
wallet,
loan.loan_id,
amount,
Expand Down
4 changes: 2 additions & 2 deletions src/stellar/contracts/clients/creditline.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class CreditLineContractClient {
return prepared.toXDR();
}

async buildRepayLoanTx(userWallet: string, loanId: string, amount: number): Promise<string> {
async buildRepayInstallmentTx(userWallet: string, loanId: string, amount: number): Promise<string> {
this.ensureConfigured();

try {
Expand Down Expand Up @@ -97,7 +97,7 @@ export class CreditLineContractClient {
if (error instanceof ContractNotConfiguredError) {
throw error;
}
this.logger.error(`Failed to build repay_loan transaction: ${error.message}`);
this.logger.error(`Failed to build repay_installment transaction: ${error.message}`);
throw new ContractTxBuildError('repayment');
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/stellar/contracts/interfaces/creditline.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface ICreditLineClient {
params: CreateLoanParams,
): Promise<string>;

buildRepayLoanTx(
buildRepayInstallmentTx(
userWallet: string,
loanId: string,
amount: number,
Expand Down
2 changes: 1 addition & 1 deletion src/stellar/contracts/mocks/creditline.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class MockCreditLineContractClient {
},
);

buildRepayLoanTx = jest.fn(
buildRepayInstallmentTx = jest.fn(
async (_userWallet: string, _loanId: string, _amount: number): Promise<string> => {
return 'AAAAAgAAAQAAAAAAAAAAiZ3TgwAAAAAyMZyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==';
},
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/helpers/test-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export async function buildTestApp(): Promise<{
buildCreateLoanTransaction: jest
.fn()
.mockResolvedValue('AAAAAgAAAQAAAAAAAAAAiZ3TgwAAAAAyMZyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='),
buildRepayLoanTx: jest
buildRepayInstallmentTx: jest
.fn()
.mockResolvedValue('AAAAAgAAAQAAAAAAAAAAiZ3TgwAAAAAyMZyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='),
getScore: jest.fn().mockResolvedValue(75),
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/modules/loans/loan-lifecycle.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('Loan Lifecycle Flow (e2e)', () => {

const mockCreditLineContract = {
buildCreateLoanTransaction: jest.fn(),
buildRepayLoanTx: jest.fn(),
buildRepayInstallmentTx: jest.fn(),
};

const mockTransactionsService = {
Expand Down Expand Up @@ -288,7 +288,7 @@ describe('Loan Lifecycle Flow (e2e)', () => {
return `xdr-loan-create-${payload.loanId}`;
});

mockCreditLineContract.buildRepayLoanTx.mockImplementation(async (_wallet, loanId, amount) => {
mockCreditLineContract.buildRepayInstallmentTx.mockImplementation(async (_wallet, loanId, amount) => {
return `xdr-loan-repay-${loanId}-${amount}`;
});

Expand Down
4 changes: 2 additions & 2 deletions test/unit/modules/loans/loans.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('LoansService', () => {
mockSupabaseFrom.insert.mockResolvedValue({ error: null });
mockSupabaseFrom.update.mockReturnThis();
mockCreditLineContractClient.buildCreateLoanTransaction.mockResolvedValue('AAAAAgAAAAC...');
mockCreditLineContractClient.buildRepayLoanTx.mockResolvedValue('AAAAAgAAAAA...');
mockCreditLineContractClient.buildRepayInstallmentTx.mockResolvedValue('AAAAAgAAAAA...');
});

afterEach(() => {
Expand Down Expand Up @@ -386,7 +386,7 @@ describe('LoansService', () => {
willComplete: false,
},
});
expect(mockCreditLineContractClient.buildRepayLoanTx).toHaveBeenCalledWith(
expect(mockCreditLineContractClient.buildRepayInstallmentTx).toHaveBeenCalledWith(
validWallet,
'chain-loan-1',
108.33,
Expand Down
66 changes: 66 additions & 0 deletions test/unit/stellar/contract-function-name.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
jest.unmock('stellar-sdk');

import * as StellarSdk from 'stellar-sdk';
import { CreditLineContractClient } from '../../../src/stellar/contracts/clients/creditline.client';
import { SorobanService } from '../../../src/blockchain/soroban/soroban.service';
import { ConfigService } from '@nestjs/config';

describe('Repayment function name alignment', () => {
let client: CreditLineContractClient;
let mockServer: { prepareTransaction: jest.Mock; getAccount: jest.Mock };

const testContractId = 'CC4C3VI4FS6J5PP7OQN4ZC3WWBYMAISPRN4VYTCZ2J7CHI3255OM32LT';
const testNetwork = 'Test SDF Network ; September 2015';
const testWallet = 'GAZBSUBUXTD6YVYXECZRW2R6K7MOU5FGU2SVYXKYWOLT5H3JIRD54EZL';

beforeEach(() => {
mockServer = {
prepareTransaction: jest.fn(async (tx: StellarSdk.Transaction) => {
return { toXDR: () => tx.toXDR() };
}),
getAccount: jest.fn().mockResolvedValue({}),
};

const mockSorobanService = {
getServer: jest.fn().mockReturnValue(mockServer),
getNetworkPassphrase: jest.fn().mockReturnValue(testNetwork),
};

const mockConfigService = {
get: jest.fn((key: string) => {
if (key === 'CREDIT_LINE_CONTRACT_ID' || key === 'CREDITLINE_CONTRACT_ID') {
return testContractId;
}
return undefined;
}),
};

client = new CreditLineContractClient(
mockSorobanService as unknown as SorobanService,
mockConfigService as unknown as ConfigService,
);
});

it('XDR builder and transaction checker use the same function name: repay_installment with 3 args', async () => {
const xdr = await client.buildRepayInstallmentTx(testWallet, 'test-loan-123', 100);

const tx = StellarSdk.TransactionBuilder.fromXDR(xdr, testNetwork);
const innerTx =
tx instanceof StellarSdk.FeeBumpTransaction ? tx.innerTransaction : tx;
const op = innerTx.operations[0];

expect(op.type).toBe('invokeHostFunction');

const invocation = ((op as unknown as { func: unknown }).func as {
_value?: { _attributes?: { functionName?: { toString?: () => string }; args?: unknown[] } };
})?._value?._attributes;

expect(invocation).toBeDefined();

const functionName = invocation!.functionName?.toString?.();
expect(functionName).toBe('repay_installment');

const args = invocation!.args as unknown[];
expect(args).toHaveLength(3);
});
});
Loading