Skip to content

Add TokenFactory module to support token management operations#62

Merged
zkCaleb-dev merged 1 commit intoTrustless-Work:developfrom
CoKeFish:feat/token-factory-module
Mar 12, 2026
Merged

Add TokenFactory module to support token management operations#62
zkCaleb-dev merged 1 commit intoTrustless-Work:developfrom
CoKeFish:feat/token-factory-module

Conversation

@CoKeFish
Copy link
Contributor

@CoKeFish CoKeFish commented Mar 12, 2026

Summary

  • Add token-factory NestJS module to apps/core exposing REST endpoints for the Token Factory
    (SEP-41) Soroban contract
  • 7 POST endpoints (writes): mint, set-admin, approve, transfer, transfer-from, burn, burn-from —
    return unsigned XDR for client-side signing
  • 6 GET endpoints (reads): balance, allowance, decimals, name, symbol, escrow-id
  • Follows the same pattern as the existing vault module (PR Feat/vault contract function calls endpoints #58)

Files

  • Created (10): DTOs, service, controller, and module under apps/core/src/token-factory/
  • Modified (1): apps/core/src/app.module.ts — registered TokenFactoryModule

Summary by CodeRabbit

  • New Features
    • Added Token Factory module enabling token contract operations including minting, burning, and transfer functionality
    • Introduced approval and allowance management for token transactions
    • Added query endpoints for token information (balance, allowance, decimals, name, symbol, escrow ID)

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

A new TokenFactory module is introduced with complete HTTP integration. The module includes service layer orchestration with SorobanService, controller endpoints for seven token operations (mint, burn, approve, transfer variants), corresponding DTOs for input validation, and registration in the main AppModule.

Changes

Cohort / File(s) Summary
Module Integration
apps/core/src/app.module.ts, apps/core/src/token-factory/token-factory.module.ts
Registers TokenFactoryModule in AppModule and defines the module structure with controller and service providers.
Data Transfer Objects
apps/core/src/token-factory/dto/approve.dto.ts, apps/core/src/token-factory/dto/burn.dto.ts, apps/core/src/token-factory/dto/burn-from.dto.ts, apps/core/src/token-factory/dto/mint.dto.ts, apps/core/src/token-factory/dto/set-admin.dto.ts, apps/core/src/token-factory/dto/transfer.dto.ts, apps/core/src/token-factory/dto/transfer-from.dto.ts
Defines validation schemas for token operations with class-validator decorators enforcing required string fields, non-empty constraints, and positive numeric amounts.
Controller and Service
apps/core/src/token-factory/token-factory.controller.ts, apps/core/src/token-factory/token-factory.service.ts
Exposes 13 HTTP endpoints (7 POST for write operations, 6 GET for read operations) and implements service methods delegating to SorobanService for contract interactions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as TokenFactory<br/>Controller
    participant Service as TokenFactory<br/>Service
    participant Soroban as Soroban<br/>Service
    participant Contract as Contract<br/>State

    rect rgba(100, 200, 150, 0.5)
    Note over Client,Contract: Write Operation (e.g., Mint)
    Client->>Controller: POST /mint (MintDto)
    Controller->>Service: mint(dto)
    Service->>Soroban: buildContractCallTransaction(payload)
    Soroban->>Contract: Invoke contract function
    Contract-->>Soroban: unsignedXdr
    Soroban-->>Service: unsignedXdr
    Service-->>Controller: unsignedXdr
    Controller-->>Client: { unsignedXdr }
    end

    rect rgba(150, 150, 200, 0.5)
    Note over Client,Contract: Read Operation (e.g., GetBalance)
    Client->>Controller: GET /balance?contractId=...&address=...
    Controller->>Service: getBalance(contractId, address, callerPublicKey)
    Service->>Soroban: readContractState(key, parameters)
    Soroban->>Contract: Query contract state
    Contract-->>Soroban: balance value
    Soroban-->>Service: result
    Service-->>Controller: balance
    Controller-->>Client: { balance: string }
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • zkCaleb-dev
  • JoelVR17
  • armandocodecr

Poem

🐰 Hops with glee through token streams,
Seven DTOs fulfill our dreams,
Controller talks, the service sings,
To Soroban the contract brings,
Balance checked, and coins now mint,
The TokenFactory leaves its print!

🚥 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 'Add TokenFactory module to support token management operations' directly and accurately describes the main change: introducing a new TokenFactory module with REST endpoints for token management operations.
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
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can scan for known vulnerabilities in your dependencies using OSV Scanner.

OSV Scanner will automatically detect and report security vulnerabilities in your project's dependencies. No additional configuration is required.

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 (2)
apps/core/src/token-factory/token-factory.service.ts (1)

2-9: Switch these imports to the @/ alias.

The new service uses relative imports instead of the repo-standard @/* mapping.

♻️ Suggested import cleanup
-import { SorobanService } from '../soroban/soroban.service';
-import { MintDto } from './dto/mint.dto';
-import { SetAdminDto } from './dto/set-admin.dto';
-import { ApproveDto } from './dto/approve.dto';
-import { TransferDto } from './dto/transfer.dto';
-import { TransferFromDto } from './dto/transfer-from.dto';
-import { BurnDto } from './dto/burn.dto';
-import { BurnFromDto } from './dto/burn-from.dto';
+import { SorobanService } from '@/soroban/soroban.service';
+import { MintDto } from '@/token-factory/dto/mint.dto';
+import { SetAdminDto } from '@/token-factory/dto/set-admin.dto';
+import { ApproveDto } from '@/token-factory/dto/approve.dto';
+import { TransferDto } from '@/token-factory/dto/transfer.dto';
+import { TransferFromDto } from '@/token-factory/dto/transfer-from.dto';
+import { BurnDto } from '@/token-factory/dto/burn.dto';
+import { BurnFromDto } from '@/token-factory/dto/burn-from.dto';

As per coding guidelines "Use path alias @/* mapping to ./src/* for imports".

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

In `@apps/core/src/token-factory/token-factory.service.ts` around lines 2 - 9,
Update the imports in token-factory.service.ts to use the project path-alias
instead of relative paths: replace occurrences importing SorobanService and all
DTOs (MintDto, SetAdminDto, ApproveDto, TransferDto, TransferFromDto, BurnDto,
BurnFromDto) so they use the `@/` mapping (e.g., import { SorobanService } from
'@/soroban/soroban.service' and DTOs from '@/token-factory/dto/...') ensuring
each import target matches the existing symbol names and directory structure.
apps/core/src/token-factory/token-factory.controller.ts (1)

2-9: Use the @/ alias for these controller imports.

This new controller is still using relative paths instead of the repo-standard alias.

♻️ Suggested import cleanup
-import { TokenFactoryService } from './token-factory.service';
-import { MintDto } from './dto/mint.dto';
-import { SetAdminDto } from './dto/set-admin.dto';
-import { ApproveDto } from './dto/approve.dto';
-import { TransferDto } from './dto/transfer.dto';
-import { TransferFromDto } from './dto/transfer-from.dto';
-import { BurnDto } from './dto/burn.dto';
-import { BurnFromDto } from './dto/burn-from.dto';
+import { TokenFactoryService } from '@/token-factory/token-factory.service';
+import { MintDto } from '@/token-factory/dto/mint.dto';
+import { SetAdminDto } from '@/token-factory/dto/set-admin.dto';
+import { ApproveDto } from '@/token-factory/dto/approve.dto';
+import { TransferDto } from '@/token-factory/dto/transfer.dto';
+import { TransferFromDto } from '@/token-factory/dto/transfer-from.dto';
+import { BurnDto } from '@/token-factory/dto/burn.dto';
+import { BurnFromDto } from '@/token-factory/dto/burn-from.dto';

As per coding guidelines "Use path alias @/* mapping to ./src/* for imports".

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

In `@apps/core/src/token-factory/token-factory.controller.ts` around lines 2 - 9,
Replace the relative imports in token-factory.controller.ts with the
repo-standard alias imports: change imports like TokenFactoryService and the
DTOs (MintDto, SetAdminDto, ApproveDto, TransferDto, TransferFromDto, BurnDto,
BurnFromDto) from './token-factory.service' and './dto/...` to the
'@/token-factory/token-factory.service' and '@/token-factory/dto/...`
equivalents so the controller uses the `@/` path alias consistently.
🤖 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/token-factory/dto/transfer-from.dto.ts`:
- Around line 20-22: The DTO currently uses amount: number which allows
fractions and loses precision; change the property on the TransferFrom DTO (the
amount field in transfer-from.dto.ts) to a string and validate it as a positive
integer string (e.g., replace `@IsNumber`()/@IsPositive() with `@IsString`() plus a
regex validator like `@Matches`(/^[1-9]\d*$/) or an equivalent custom validator)
so only integer strings are accepted; then, in the service code that handles
this DTO, convert to BigInt with BigInt(dto.amount) before building the Soroban
call.

In `@apps/core/src/token-factory/token-factory.controller.ts`:
- Around line 61-120: These controller GET handlers (getBalance, getAllowance,
getDecimals, getName, getSymbol, getEscrowId) are reading raw query strings so
class-validator + global ValidationPipe isn't applied; create small query DTOs
(e.g., GetBalanceQueryDto, GetAllowanceQueryDto, GetSimpleQueryDto) with
required fields annotated (IsString, IsNotEmpty, IsOptional where applicable)
and replace raw `@Query`(...) params with a single `@Query`() dto parameter for each
handler; use the DTO properties (dto.contractId, dto.address, dto.spender,
dto.callerPublicKey) when calling TokenFactoryService so missing/invalid queries
return 400s instead of downstream 500s.

In `@apps/core/src/token-factory/token-factory.service.ts`:
- Around line 49-55: The transfer method is passing the wrong field name to the
Soroban client; inside transfer(dto: TransferDto) replace the object property
to_muxed with the SEP-41 expected name to: use { from: dto.from, to: dto.to,
amount: dto.amount } so SorobanService.buildContractCallTransaction forwards the
correct parameter to the generated client call (update the transfer method in
TokenFactoryService accordingly).

---

Nitpick comments:
In `@apps/core/src/token-factory/token-factory.controller.ts`:
- Around line 2-9: Replace the relative imports in token-factory.controller.ts
with the repo-standard alias imports: change imports like TokenFactoryService
and the DTOs (MintDto, SetAdminDto, ApproveDto, TransferDto, TransferFromDto,
BurnDto, BurnFromDto) from './token-factory.service' and './dto/...` to the
'@/token-factory/token-factory.service' and '@/token-factory/dto/...`
equivalents so the controller uses the `@/` path alias consistently.

In `@apps/core/src/token-factory/token-factory.service.ts`:
- Around line 2-9: Update the imports in token-factory.service.ts to use the
project path-alias instead of relative paths: replace occurrences importing
SorobanService and all DTOs (MintDto, SetAdminDto, ApproveDto, TransferDto,
TransferFromDto, BurnDto, BurnFromDto) so they use the `@/` mapping (e.g.,
import { SorobanService } from '@/soroban/soroban.service' and DTOs from
'@/token-factory/dto/...') ensuring each import target matches the existing
symbol names and directory structure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c53fcda8-8038-4d27-a21a-a0cfaaa6b6e1

📥 Commits

Reviewing files that changed from the base of the PR and between 87c2857 and dde0234.

📒 Files selected for processing (11)
  • apps/core/src/app.module.ts
  • apps/core/src/token-factory/dto/approve.dto.ts
  • apps/core/src/token-factory/dto/burn-from.dto.ts
  • apps/core/src/token-factory/dto/burn.dto.ts
  • apps/core/src/token-factory/dto/mint.dto.ts
  • apps/core/src/token-factory/dto/set-admin.dto.ts
  • apps/core/src/token-factory/dto/transfer-from.dto.ts
  • apps/core/src/token-factory/dto/transfer.dto.ts
  • apps/core/src/token-factory/token-factory.controller.ts
  • apps/core/src/token-factory/token-factory.module.ts
  • apps/core/src/token-factory/token-factory.service.ts

Comment on lines +20 to +22
@IsNumber()
@IsPositive()
amount: number;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Use a bigint-safe representation for amount.

amount: number accepts fractional values and silently loses precision above 2^53 - 1, which is risky for SEP-41 token amounts. Prefer a positive integer string here, then convert with BigInt(dto.amount) in the service before building the Soroban call.

🔧 Suggested DTO change
-import { IsString, IsNumber, IsNotEmpty, IsPositive } from 'class-validator';
+import { Transform } from 'class-transformer';
+import { IsString, IsNotEmpty, Matches } from 'class-validator';
@@
-  `@IsNumber`()
-  `@IsPositive`()
-  amount: number;
+  `@Transform`(({ value }) => String(value))
+  `@IsString`()
+  `@Matches`(/^[1-9]\d*$/, { message: 'amount must be a positive integer string' })
+  amount: string;

As per coding guidelines "Use class-validator and class-transformer decorators for DTOs".

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

In `@apps/core/src/token-factory/dto/transfer-from.dto.ts` around lines 20 - 22,
The DTO currently uses amount: number which allows fractions and loses
precision; change the property on the TransferFrom DTO (the amount field in
transfer-from.dto.ts) to a string and validate it as a positive integer string
(e.g., replace `@IsNumber`()/@IsPositive() with `@IsString`() plus a regex validator
like `@Matches`(/^[1-9]\d*$/) or an equivalent custom validator) so only integer
strings are accepted; then, in the service code that handles this DTO, convert
to BigInt with BigInt(dto.amount) before building the Soroban call.

Comment on lines +61 to +120
@Get('balance')
async getBalance(
@Query('contractId') contractId: string,
@Query('address') address: string,
@Query('callerPublicKey') callerPublicKey: string,
) {
const balance = await this.tokenFactoryService.getBalance(contractId, address, callerPublicKey);
return { balance: String(balance) };
}

@Get('allowance')
async getAllowance(
@Query('contractId') contractId: string,
@Query('from') from: string,
@Query('spender') spender: string,
@Query('callerPublicKey') callerPublicKey: string,
) {
const allowance = await this.tokenFactoryService.getAllowance(
contractId,
from,
spender,
callerPublicKey,
);
return { allowance: String(allowance) };
}

@Get('decimals')
async getDecimals(
@Query('contractId') contractId: string,
@Query('callerPublicKey') callerPublicKey: string,
) {
const decimals = await this.tokenFactoryService.getDecimals(contractId, callerPublicKey);
return { decimals };
}

@Get('name')
async getName(
@Query('contractId') contractId: string,
@Query('callerPublicKey') callerPublicKey: string,
) {
const name = await this.tokenFactoryService.getName(contractId, callerPublicKey);
return { name: String(name) };
}

@Get('symbol')
async getSymbol(
@Query('contractId') contractId: string,
@Query('callerPublicKey') callerPublicKey: string,
) {
const symbol = await this.tokenFactoryService.getSymbol(contractId, callerPublicKey);
return { symbol: String(symbol) };
}

@Get('escrow-id')
async getEscrowId(
@Query('contractId') contractId: string,
@Query('callerPublicKey') callerPublicKey: string,
) {
const escrowId = await this.tokenFactoryService.getEscrowId(contractId, callerPublicKey);
return { escrowId: String(escrowId) };
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

Validate the GET query params before delegating to the service.

These handlers read raw query strings, so the global ValidationPipe never enforces required/non-empty values here. Missing contractId, address, spender, or callerPublicKey will fall through into TokenFactoryService and surface as downstream 500s instead of a clean 400. Please switch these read endpoints to query DTOs or explicit parameter pipes.

As per coding guidelines "Keep business logic in Services only; Controllers should handle HTTP concerns (params, delegation, response)".

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

In `@apps/core/src/token-factory/token-factory.controller.ts` around lines 61 -
120, These controller GET handlers (getBalance, getAllowance, getDecimals,
getName, getSymbol, getEscrowId) are reading raw query strings so
class-validator + global ValidationPipe isn't applied; create small query DTOs
(e.g., GetBalanceQueryDto, GetAllowanceQueryDto, GetSimpleQueryDto) with
required fields annotated (IsString, IsNotEmpty, IsOptional where applicable)
and replace raw `@Query`(...) params with a single `@Query`() dto parameter for each
handler; use the DTO properties (dto.contractId, dto.address, dto.spender,
dto.callerPublicKey) when calling TokenFactoryService so missing/invalid queries
return 400s instead of downstream 500s.

Comment on lines +49 to +55
transfer(dto: TransferDto): Promise<string> {
return this.soroban.buildContractCallTransaction(
dto.contractId,
'transfer',
{ from: dto.from, to_muxed: dto.to, amount: dto.amount },
dto.callerPublicKey,
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

What are the argument names for the Stellar Soroban SEP-41 token transfermethod? Is the destination parameter namedtoorto_muxed?

💡 Result:

For the SEP-41 (Soroban Token Interface) transfer method, the argument names are:

transfer(env: Env, from: Address, to: MuxedAddress, amount: i128) — i.e. the destination parameter is named to (its type is MuxedAddress). [1][2]

If you’re seeing to_muxed, that name shows up in some tooling/docs (e.g., a contract validator table) to emphasize that the destination is muxed-capable, but it’s not the SEP-41 trait’s parameter name. [3]

Answer: destination is to, not to_muxed. [1][2]

Sources: [1] [2] [3]


Use SEP-41 to parameter name instead of to_muxed.

Line 53 passes to_muxed to the Soroban client, but SEP-41 token interface defines the destination parameter as to, not to_muxed. Since SorobanService.buildContractCallTransaction() forwards this object directly to the generated client call, using the wrong parameter name will cause the contract invocation to fail.

🐛 Proposed fix
-      { from: dto.from, to_muxed: dto.to, amount: dto.amount },
+      { from: dto.from, to: dto.to, amount: dto.amount },
📝 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
transfer(dto: TransferDto): Promise<string> {
return this.soroban.buildContractCallTransaction(
dto.contractId,
'transfer',
{ from: dto.from, to_muxed: dto.to, amount: dto.amount },
dto.callerPublicKey,
);
transfer(dto: TransferDto): Promise<string> {
return this.soroban.buildContractCallTransaction(
dto.contractId,
'transfer',
{ from: dto.from, to: dto.to, amount: dto.amount },
dto.callerPublicKey,
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/core/src/token-factory/token-factory.service.ts` around lines 49 - 55,
The transfer method is passing the wrong field name to the Soroban client;
inside transfer(dto: TransferDto) replace the object property to_muxed with the
SEP-41 expected name to: use { from: dto.from, to: dto.to, amount: dto.amount }
so SorobanService.buildContractCallTransaction forwards the correct parameter to
the generated client call (update the transfer method in TokenFactoryService
accordingly).

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!!

@zkCaleb-dev zkCaleb-dev merged commit 2189e68 into Trustless-Work:develop Mar 12, 2026
1 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.

3 participants