Skip to content

Feat/vault contract function calls endpoints#58

Merged
zkCaleb-dev merged 4 commits intoTrustless-Work:developfrom
miguelnietoa:feat/vault-contract-function-calls-endpoints
Mar 12, 2026
Merged

Feat/vault contract function calls endpoints#58
zkCaleb-dev merged 4 commits intoTrustless-Work:developfrom
miguelnietoa:feat/vault-contract-function-calls-endpoints

Conversation

@miguelnietoa
Copy link
Contributor

@miguelnietoa miguelnietoa commented Mar 11, 2026

Closes #17

Summary by CodeRabbit

  • New Features

    • Added Vault API with endpoints for availability-for-exchange, claim, overview, preview-claim, is-enabled, USDC balance, total redeemed, admin, ROI, token and USDC addresses.
    • Added contract-state read capability for querying on-chain data.
  • Chores

    • Minor formatting and trailing-newline cleanups across codebase.
  • Modules

    • Vault module registered with the application.

@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 538c1f57-bd05-4702-9709-8f48d54d8282

📥 Commits

Reviewing files that changed from the base of the PR and between dedabe8 and bd2111f.

⛔ Files ignored due to path filters (60)
  • apps/core/dist/app.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/app.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/app.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/app.module.js is excluded by !**/dist/**
  • apps/core/dist/app.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/app.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/app.service.js is excluded by !**/dist/**
  • apps/core/dist/app.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/campaigns.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/campaigns.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.module.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/campaigns.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.service.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/dto/create-campaign.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/create-campaign.dto.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/create-campaign.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/dto/update-campaign-status.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/update-campaign-status.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/dto/update-campaign.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/update-campaign.dto.js is excluded by !**/dist/**
  • apps/core/dist/campaigns/dto/update-campaign.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/deploy.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.controller.js is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/deploy.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/deploy.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.service.js is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/dto/deploy-participation-token.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-participation-token.dto.js is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-participation-token.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/dto/deploy-token-factory.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-token-factory.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/dto/create-investment.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/investments/dto/create-investment.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/investments.controller.js is excluded by !**/dist/**
  • apps/core/dist/investments/investments.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/investments.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/investments/investments.module.js is excluded by !**/dist/**
  • apps/core/dist/investments/investments.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/investments.service.js is excluded by !**/dist/**
  • apps/core/dist/investments/investments.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/main.d.ts is excluded by !**/dist/**
  • apps/core/dist/main.js is excluded by !**/dist/**
  • apps/core/dist/main.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/prisma/prisma.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/prisma/prisma.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/prisma/prisma.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/prisma/prisma.service.js is excluded by !**/dist/**
  • apps/core/dist/prisma/prisma.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/soroban/soroban.module.d.ts is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.module.js is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/soroban/soroban.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.service.js is excluded by !**/dist/**
  • apps/core/dist/soroban/soroban.service.js.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (22)
  • .gitignore
  • apps/core/src/app.module.ts
  • apps/core/src/campaigns/campaigns.controller.ts
  • apps/core/src/campaigns/campaigns.module.ts
  • apps/core/src/campaigns/campaigns.service.ts
  • apps/core/src/campaigns/dto/update-campaign.dto.ts
  • apps/core/src/deploy/deploy.controller.ts
  • apps/core/src/deploy/deploy.service.ts
  • apps/core/src/deploy/dto/deploy-participation-token.dto.ts
  • apps/core/src/investments/dto/create-investment.dto.ts
  • apps/core/src/investments/investments.controller.ts
  • apps/core/src/investments/investments.module.ts
  • apps/core/src/investments/investments.service.ts
  • apps/core/src/loans/loans.service.spec.ts
  • apps/core/src/prisma/prisma.module.ts
  • apps/core/src/soroban/soroban.module.ts
  • apps/core/src/soroban/soroban.service.ts
  • apps/core/src/vault/dto/availability-for-exchange.dto.ts
  • apps/core/src/vault/dto/claim.dto.ts
  • apps/core/src/vault/vault.controller.ts
  • apps/core/src/vault/vault.module.ts
  • apps/core/src/vault/vault.service.ts

📝 Walkthrough

Walkthrough

This PR adds a new Vault module (controller, service, module, DTOs), registers VaultModule in AppModule, extends SorobanService with readContractState for read-only contract calls, and includes miscellaneous formatting/newline adjustments across several core files and .gitignore.

Changes

Cohort / File(s) Summary
Vault Module Core
apps/core/src/vault/vault.module.ts, apps/core/src/vault/vault.controller.ts, apps/core/src/vault/vault.service.ts
New VaultModule with VaultController (11 endpoints: 2 POST writes, 9 GET reads) and VaultService delegating contract calls to SorobanService.
Vault DTOs
apps/core/src/vault/dto/availability-for-exchange.dto.ts, apps/core/src/vault/dto/claim.dto.ts, apps/core/src/vault/dto/availability-for-exchange.dto.ts
New DTOs with class-validator decorators (contractId, admin, enabled, beneficiary, callerPublicKey).
Soroban Service
apps/core/src/soroban/soroban.service.ts
Added readContractState(contractId, method, args, callerPublicKey) for read-only simulations; minor invocation formatting change in buildContractCallTransaction.
App Module Registration
apps/core/src/app.module.ts
Imported and registered VaultModule in AppModule imports.
Formatting / Newlines / Small Edits
apps/core/src/campaigns/campaigns.controller.ts, apps/core/src/campaigns/..., apps/core/src/deploy/..., apps/core/src/investments/..., apps/core/src/loans/loans.service.spec.ts, apps/core/src/prisma/prisma.module.ts, apps/core/src/soroban/soroban.module.ts, .gitignore
Whitespace and formatting tweaks (method signature formatting, trailing newlines), providers array formatting in tests, and added **/dist to .gitignore.
Tests / Specs
apps/core/src/loans/loans.service.spec.ts
Providers array compressed to single line (formatting only).

Sequence Diagram

sequenceDiagram
    participant Client
    participant VaultController
    participant VaultService
    participant SorobanService
    participant StellarContract

    rect rgba(200,150,255,0.5)
    Note over Client,StellarContract: Write operation (POST /vault/availability-for-exchange)
    Client->>VaultController: POST AvailabilityForExchangeDto
    VaultController->>VaultService: availabilityForExchange(dto)
    VaultService->>SorobanService: buildContractCallTransaction(contractId, "availability_for_exchange", {admin, enabled}, callerPublicKey)
    SorobanService->>StellarContract: construct unsigned transaction (simulate)
    SorobanService-->>VaultService: unsignedXdr
    VaultService-->>VaultController: { unsignedXdr }
    VaultController-->>Client: { unsignedXdr }
    end

    rect rgba(150,200,255,0.5)
    Note over Client,StellarContract: Read operation (GET /vault/overview)
    Client->>VaultController: GET ?contractId=...&callerPublicKey=...
    VaultController->>VaultService: getOverview(contractId, callerPublicKey)
    VaultService->>SorobanService: readContractState(contractId, "get_vault_overview", {}, callerPublicKey)
    SorobanService->>StellarContract: simulate read-only call
    StellarContract-->>SorobanService: result
    SorobanService-->>VaultService: result data
    VaultService-->>VaultController: result data
    VaultController-->>Client: VaultOverview JSON
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • zkCaleb-dev
  • JoelVR17

Poem

🐰 Hopping in code where vaults now sing,

Eleven doors that a contract can bring,
Read or write through Soroban's light,
Unsigned XDRs take flight at night,
A rabbit cheers: vaults work just right!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/vault contract function calls endpoints' directly describes the main changes: adding new vault contract endpoints (deployVault, availabilityForExchange) for contract function calls, which aligns with the PR's core additions of VaultModule, VaultService, VaultController, and related DTOs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan for PR comments
  • Generate coding plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@miguelnietoa miguelnietoa marked this pull request as draft March 11, 2026 22:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/core/src/vault/vault.service.ts (1)

9-18: Add a focused unit test for this Soroban call.

availability_for_exchange and its argument keys are all inline literals here, so a typo will only surface at runtime. A small service spec that asserts the exact buildContractCallTransaction(...) arguments would make this integration safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/core/src/vault/vault.service.ts` around lines 9 - 18, Add a focused unit
test for VaultService.availabilityForExchange that spies/mocks
SorobanService.buildContractCallTransaction and asserts it was called exactly
once with the expected literal contract function name
"availability_for_exchange", the exact argument object { admin: dto.admin,
enabled: dto.enabled }, dto.contractId and dto.callerPublicKey in the correct
parameter order; instantiate VaultService with a test double for soroban, call
availabilityForExchange with a sample AvailabilityForExchangeDto, and assert the
mock was invoked with those exact values (and that the method returns/forwards
the mock's Promise result).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/core/src/deploy/deploy.service.ts`:
- Around line 13-20: The constructor in DeployService reads
PARTICIPATION_TOKEN_WASM_HASH, TOKEN_FACTORY_WASM_HASH and VAULT_WASM_HASH with
non-null assertions which defers failure to runtime; change this to validate
those env vars at startup: either read and throw a NestJS exception (e.g.,
InternalServerErrorException) from the DeployService constructor if any of
participationTokenWasmHash, tokenFactoryWasmHash or vaultWasmHash are missing,
or better, migrate these settings to `@nestjs/config` with a Joi/zod schema so
missing values are rejected during bootstrap; reference the DeployService
constructor and the private fields participationTokenWasmHash,
tokenFactoryWasmHash and vaultWasmHash when implementing the fix.

In `@apps/core/src/deploy/dto/deploy-vault.dto.ts`:
- Around line 1-26: Update the DeployVaultDto (class DeployVaultDto) to include
class-transformer decorators: add `@Type`(() => Number) to roiPercentage to coerce
numeric strings to numbers and add appropriate string transformation decorators
(e.g., trim/normalize via `@Transform`) to admin, token, usdc, callerPublicKey and
to enabled if needed for boolean coercion so incoming string values are
normalized/converted before validation; then enable transformation globally by
updating the ValidationPipe instantiation (ValidationPipe in main.ts) to include
transform: true (e.g., { whitelist: true, transform: true }) so the
class-transformer decorators are executed during request validation.

In `@apps/core/src/soroban/soroban.service.ts`:
- Line 43: Validate that the requested contract method exists and is callable
before invoking dynamic calls like client[method](args) (applies to both
occurrences where client[method](args) is used); resolve the property from
client, check typeof resolved === 'function', and if not throw a NestJS
BadRequestException (or HttpException) with a clear message including the
supplied method name, otherwise call the function safely (e.g., const fn =
(client as any)[method]; return await fn(args)). Ensure you reference the
variables client and method and replace unguarded dynamic invocation sites with
this guarded pattern so typos or unsupported method names produce a proper HTTP
error instead of an unhandled runtime exception.

---

Nitpick comments:
In `@apps/core/src/vault/vault.service.ts`:
- Around line 9-18: Add a focused unit test for
VaultService.availabilityForExchange that spies/mocks
SorobanService.buildContractCallTransaction and asserts it was called exactly
once with the expected literal contract function name
"availability_for_exchange", the exact argument object { admin: dto.admin,
enabled: dto.enabled }, dto.contractId and dto.callerPublicKey in the correct
parameter order; instantiate VaultService with a test double for soroban, call
availabilityForExchange with a sample AvailabilityForExchangeDto, and assert the
mock was invoked with those exact values (and that the method returns/forwards
the mock's Promise result).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c795bb7e-00aa-44f0-9239-380f267a59c7

📥 Commits

Reviewing files that changed from the base of the PR and between 554b9c4 and dedabe8.

⛔ Files ignored due to path filters (15)
  • apps/core/dist/app.module.js is excluded by !**/dist/**
  • apps/core/dist/app.module.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/campaigns/campaigns.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/campaigns/campaigns.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.controller.js is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.controller.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/deploy.service.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.service.js is excluded by !**/dist/**
  • apps/core/dist/deploy/deploy.service.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/deploy/dto/deploy-vault.dto.d.ts is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-vault.dto.js is excluded by !**/dist/**
  • apps/core/dist/deploy/dto/deploy-vault.dto.js.map is excluded by !**/dist/**, !**/*.map
  • apps/core/dist/investments/investments.controller.d.ts is excluded by !**/dist/**
  • apps/core/dist/investments/investments.service.d.ts is excluded by !**/dist/**
📒 Files selected for processing (23)
  • apps/core/README.md
  • apps/core/package.json
  • apps/core/src/app.module.ts
  • apps/core/src/campaigns/campaigns.controller.ts
  • apps/core/src/campaigns/campaigns.module.ts
  • apps/core/src/campaigns/campaigns.service.ts
  • apps/core/src/campaigns/dto/update-campaign.dto.ts
  • apps/core/src/deploy/deploy.controller.ts
  • apps/core/src/deploy/deploy.service.ts
  • apps/core/src/deploy/dto/deploy-participation-token.dto.ts
  • apps/core/src/deploy/dto/deploy-vault.dto.ts
  • apps/core/src/investments/dto/create-investment.dto.ts
  • apps/core/src/investments/investments.controller.ts
  • apps/core/src/investments/investments.module.ts
  • apps/core/src/investments/investments.service.ts
  • apps/core/src/loans/loans.service.spec.ts
  • apps/core/src/prisma/prisma.module.ts
  • apps/core/src/soroban/soroban.module.ts
  • apps/core/src/soroban/soroban.service.ts
  • apps/core/src/vault/dto/availability-for-exchange.dto.ts
  • apps/core/src/vault/vault.controller.ts
  • apps/core/src/vault/vault.module.ts
  • apps/core/src/vault/vault.service.ts

Comment on lines +13 to 20
private readonly vaultWasmHash: string;

constructor(private readonly soroban: SorobanService) {
this.participationTokenWasmHash =
process.env.PARTICIPATION_TOKEN_WASM_HASH!;
this.tokenFactoryWasmHash = process.env.TOKEN_FACTORY_WASM_HASH!;
this.vaultWasmHash = process.env.VAULT_WASM_HASH!;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "VAULT_WASM_HASH|TOKEN_FACTORY_WASM_HASH|PARTICIPATION_TOKEN_WASM_HASH" apps/core/src apps/core/.env.example
rg -n "ConfigModule|ConfigService|validationSchema|Joi|zod|registerAs" apps/core/src apps/core

Repository: Trustless-Work/tokenization-private-credit

Length of output: 542


🏁 Script executed:

# Check how this service currently handles errors
head -50 apps/core/src/deploy/deploy.service.ts | tail -40

# Check for NestJS exception imports in this service
rg -n "BadRequestException|InternalServerErrorException|NotFoundException|throw" apps/core/src/deploy/deploy.service.ts | head -20

# Check app bootstrap for any env validation
rg -n "process.env|ConfigModule|validate" apps/core/src/main.ts apps/core/src/app.module.ts 2>/dev/null || true

Repository: Trustless-Work/tokenization-private-credit

Length of output: 1435


🏁 Script executed:

# Check if app.module.ts exists and has ConfigModule
cat apps/core/src/app.module.ts 2>/dev/null || echo "File not found"

# Check entire main.ts for bootstrap logic
cat apps/core/src/main.ts

# Search for any error class/exception definitions in the codebase
rg -n "class.*Error|class.*Exception" apps/core/src --max-count=10

Repository: Trustless-Work/tokenization-private-credit

Length of output: 1216


Validate VAULT_WASM_HASH and other required env vars at startup.

Lines 17–19 read three WASM hash env vars with non-null assertions. If any are missing, the app boots successfully and fails only on first request with an unclear error. This pattern is particularly risky for configuration that is essential to deployment functionality.

Per coding guidelines, NestJS services should use built-in exceptions (e.g., BadRequestException, InternalServerErrorException) for error handling. Two options:

  1. Immediate validation in constructor: Throw a NestJS exception if VAULT_WASM_HASH (and ideally the other two hashes) is missing.
  2. ConfigModule approach (recommended): Use @nestjs/config with a validation schema (Joi/zod) to enforce required env vars at bootstrap time, consistent with NestJS best practices.

The codebase currently has no centralized env validation, and PORT already shows an inconsistent pattern with a fallback while WASM hashes rely on assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/core/src/deploy/deploy.service.ts` around lines 13 - 20, The constructor
in DeployService reads PARTICIPATION_TOKEN_WASM_HASH, TOKEN_FACTORY_WASM_HASH
and VAULT_WASM_HASH with non-null assertions which defers failure to runtime;
change this to validate those env vars at startup: either read and throw a
NestJS exception (e.g., InternalServerErrorException) from the DeployService
constructor if any of participationTokenWasmHash, tokenFactoryWasmHash or
vaultWasmHash are missing, or better, migrate these settings to `@nestjs/config`
with a Joi/zod schema so missing values are rejected during bootstrap; reference
the DeployService constructor and the private fields participationTokenWasmHash,
tokenFactoryWasmHash and vaultWasmHash when implementing the fix.

Comment on lines +1 to +26
import { IsString, IsBoolean, IsNumber, IsNotEmpty } from 'class-validator';

export class DeployVaultDto {
@IsString()
@IsNotEmpty()
admin: string;

@IsBoolean()
@IsNotEmpty()
enabled: boolean;

@IsNumber()
@IsNotEmpty()
roiPercentage: number;

@IsString()
@IsNotEmpty()
token: string;

@IsString()
@IsNotEmpty()
usdc: string;

@IsString()
@IsNotEmpty()
callerPublicKey: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "ValidationPipe|transform:\s*true|enableImplicitConversion" apps/core/src/main.ts apps/core/src
rg -n "class-transformer|@Transform\(|@Type\(" apps/core/src --glob "*.dto.ts"

Repository: Trustless-Work/tokenization-private-credit

Length of output: 405


🏁 Script executed:

cat apps/core/package.json | grep -A 5 -B 5 "class-transformer"

Repository: Trustless-Work/tokenization-private-credit

Length of output: 418


🏁 Script executed:

fd "\.dto\.ts$" apps/core/src --exec head -20 {} \; -print

Repository: Trustless-Work/tokenization-private-credit

Length of output: 316


🏁 Script executed:

cat apps/core/src/main.ts

Repository: Trustless-Work/tokenization-private-credit

Length of output: 425


🏁 Script executed:

find apps/core/src -name "*.dto.ts" -type f

Repository: Trustless-Work/tokenization-private-credit

Length of output: 600


🏁 Script executed:

cat apps/core/src/deploy/dto/deploy-vault.dto.ts

Repository: Trustless-Work/tokenization-private-credit

Length of output: 503


🏁 Script executed:

cat apps/core/src/campaigns/dto/create-campaign.dto.ts

Repository: Trustless-Work/tokenization-private-credit

Length of output: 645


🏁 Script executed:

cat apps/core/src/investments/dto/create-investment.dto.ts

Repository: Trustless-Work/tokenization-private-credit

Length of output: 467


🏁 Script executed:

cat apps/core/src/loans/dto/create-loan.dto.ts

Repository: Trustless-Work/tokenization-private-credit

Length of output: 482


🏁 Script executed:

rg "@Transform|@Type" apps/core/src --glob "*.dto.ts"

Repository: Trustless-Work/tokenization-private-credit

Length of output: 68


Add class-transformer decorators to this DTO and enable transformation in ValidationPipe.

This DTO uses only class-validator decorators but lacks class-transformer per the coding guideline. Additionally, ValidationPipe in apps/core/src/main.ts is configured without transform: true, so even with decorators added, transformation won't execute. Both are required:

  1. Add @Type(() => Number) to roiPercentage for automatic string-to-number coercion
  2. Add transformation decorators for string normalization where appropriate
  3. Update ValidationPipe in apps/core/src/main.ts:7 to include { whitelist: true, transform: true }

This pattern applies across all DTOs in the project (10 total files found).

Minimal example for this DTO
+import { Transform, Type } from 'class-transformer';
 import { IsString, IsBoolean, IsNumber, IsNotEmpty } from 'class-validator';

 export class DeployVaultDto {
   `@IsString`()
   `@IsNotEmpty`()
   admin: string;

   `@IsBoolean`()
   `@IsNotEmpty`()
   enabled: boolean;

+  `@Type`(() => Number)
   `@IsNumber`()
   `@IsNotEmpty`()
   roiPercentage: number;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { IsString, IsBoolean, IsNumber, IsNotEmpty } from 'class-validator';
export class DeployVaultDto {
@IsString()
@IsNotEmpty()
admin: string;
@IsBoolean()
@IsNotEmpty()
enabled: boolean;
@IsNumber()
@IsNotEmpty()
roiPercentage: number;
@IsString()
@IsNotEmpty()
token: string;
@IsString()
@IsNotEmpty()
usdc: string;
@IsString()
@IsNotEmpty()
callerPublicKey: string;
import { Transform, Type } from 'class-transformer';
import { IsString, IsBoolean, IsNumber, IsNotEmpty } from 'class-validator';
export class DeployVaultDto {
`@IsString`()
`@IsNotEmpty`()
admin: string;
`@IsBoolean`()
`@IsNotEmpty`()
enabled: boolean;
`@Type`(() => Number)
`@IsNumber`()
`@IsNotEmpty`()
roiPercentage: number;
`@IsString`()
`@IsNotEmpty`()
token: string;
`@IsString`()
`@IsNotEmpty`()
usdc: string;
`@IsString`()
`@IsNotEmpty`()
callerPublicKey: string;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/core/src/deploy/dto/deploy-vault.dto.ts` around lines 1 - 26, Update the
DeployVaultDto (class DeployVaultDto) to include class-transformer decorators:
add `@Type`(() => Number) to roiPercentage to coerce numeric strings to numbers
and add appropriate string transformation decorators (e.g., trim/normalize via
`@Transform`) to admin, token, usdc, callerPublicKey and to enabled if needed for
boolean coercion so incoming string values are normalized/converted before
validation; then enable transformation globally by updating the ValidationPipe
instantiation (ValidationPipe in main.ts) to include transform: true (e.g., {
whitelist: true, transform: true }) so the class-transformer decorators are
executed during request validation.

});

const tx = await (client)[method](args);
const tx = await client[method](args);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In @stellar/stellar-sdk 14.6.1, what is the documented return shape of methods invoked on a client created with contract.Client.from({...}), and is calling them through client[method](args) with an arbitrary runtime string a supported pattern?

💡 Result:

  • Documented return shape (v14.6.1): contract.Client.from({...}) builds a Client whose contract functions are added as methods; each contract method returns an AssembledTransaction (in practice you await the call, since assembly/simulation is async). The AssembledTransaction is the “workhorse” object you use to inspect the call (result getter), serialize (toJSON()), and (for write calls) sign/send (signAndSend()SentTransaction, which also has a result getter). [1][2]

  • Is client[method](args) with an arbitrary runtime string supported? The docs describe calling named methods (e.g. client.myReadMethod(...)) that are generated from the contract spec. The implementation attaches methods dynamically under a sanitized identifier of the contract function name, not a general “invoke-by-string” API; so while bracket-access can work in JavaScript when the key matches the generated property name, calling via an arbitrary runtime string is not a documented/supported pattern (and is not type-safe, and can fail if the name isn’t exactly the generated/sanitized one). [1]

Sources: [1] [2]


🏁 Script executed:

cat -n apps/core/src/soroban/soroban.service.ts | head -80

Repository: Trustless-Work/tokenization-private-credit

Length of output: 2116


Validate the contract method name before invoking it.

client[method](args) with method: string bypasses type safety and ignores the @stellar/stellar-sdk documented pattern (calling named methods generated from the contract spec). A typo or unsupported method name will throw an unhandled runtime error instead of a proper HTTP error. Per coding guidelines, use a NestJS exception for invalid input.

Add validation to resolve and verify the method exists before invocation:

Suggested approach
-import { Injectable } from '@nestjs/common';
+import { BadRequestException, Injectable } from '@nestjs/common';
 import { contract, Networks } from '@stellar/stellar-sdk';

 `@Injectable`()
 export class SorobanService {
   private readonly rpcUrl: string;
   private readonly networkPassphrase: string;

   constructor() {
     this.rpcUrl = process.env.SOROBAN_RPC_URL!;
     this.networkPassphrase = Networks.TESTNET;
   }
+
+  private getContractMethod<TResult>(
+    client: object,
+    method: string,
+  ): (args: Record<string, unknown>) => Promise<TResult> {
+    const candidate: unknown = Reflect.get(client, method);
+    if (typeof candidate !== 'function') {
+      throw new BadRequestException(`Unsupported contract method: ${method}`);
+    }
+
+    return candidate.bind(client) as (
+      args: Record<string, unknown>,
+    ) => Promise<TResult>;
+  }

   async buildDeployTransaction(
     wasmHash: string,
     args: Record<string, unknown>,
     callerPublicKey: string,
@@
   async buildContractCallTransaction(
     contractId: string,
     method: string,
     args: Record<string, unknown>,
     callerPublicKey: string,
   ): Promise<string> {
@@
-    const tx = await client[method](args);
+    const invoke = this.getContractMethod<{ toXDR(): string }>(client, method);
+    const tx = await invoke(args);

     return tx.toXDR();
   }

   async readContractState(
@@
   ): Promise<unknown> {
@@
-    const result = await client[method](args);
+    const invoke = this.getContractMethod<{ result: unknown }>(client, method);
+    const result = await invoke(args);

     return result.result;
   }
 }

Applies to lines 43 and 61.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/core/src/soroban/soroban.service.ts` at line 43, Validate that the
requested contract method exists and is callable before invoking dynamic calls
like client[method](args) (applies to both occurrences where
client[method](args) is used); resolve the property from client, check typeof
resolved === 'function', and if not throw a NestJS BadRequestException (or
HttpException) with a clear message including the supplied method name,
otherwise call the function safely (e.g., const fn = (client as any)[method];
return await fn(args)). Ensure you reference the variables client and method and
replace unguarded dynamic invocation sites with this guarded pattern so typos or
unsupported method names produce a proper HTTP error instead of an unhandled
runtime exception.

@miguelnietoa miguelnietoa force-pushed the feat/vault-contract-function-calls-endpoints branch from dedabe8 to 29d9658 Compare March 11, 2026 23:18
Copy link
Contributor

@zkCaleb-dev zkCaleb-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greate job!!

- Add deploy endpoints for token-factory and vault contracts
- Add vault module with availability-for-exchange write endpoint
- Add readContractState method to SorobanService for view functions
- Add CI pipeline for lint, format, and build checks
- Update core README with required setup commands
- Add ClaimDto with contractId, beneficiary, and callerPublicKey validation
- Add POST /vault/claim endpoint returning { unsignedXdr }
- Implement VaultService.claim method using buildContractCallTransaction
- POST /vault/claim for token-to-USDC exchange
- GET endpoints for overview, preview-claim, is-enabled, usdc-balance,
  total-redeemed, admin, roi-percentage, token-address, usdc-address
@miguelnietoa miguelnietoa force-pushed the feat/vault-contract-function-calls-endpoints branch from cabcac6 to bd2111f Compare March 12, 2026 15:34
@miguelnietoa miguelnietoa marked this pull request as ready for review March 12, 2026 15:36
@zkCaleb-dev zkCaleb-dev merged commit a8d9874 into Trustless-Work:develop Mar 12, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(vault): implementar módulo con endpoints para interactuar con el Vault Contract

3 participants