Add TokenFactory module to support token management operations#62
Conversation
📝 WalkthroughWalkthroughA 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 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. Comment 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. |
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
apps/core/src/app.module.tsapps/core/src/token-factory/dto/approve.dto.tsapps/core/src/token-factory/dto/burn-from.dto.tsapps/core/src/token-factory/dto/burn.dto.tsapps/core/src/token-factory/dto/mint.dto.tsapps/core/src/token-factory/dto/set-admin.dto.tsapps/core/src/token-factory/dto/transfer-from.dto.tsapps/core/src/token-factory/dto/transfer.dto.tsapps/core/src/token-factory/token-factory.controller.tsapps/core/src/token-factory/token-factory.module.tsapps/core/src/token-factory/token-factory.service.ts
| @IsNumber() | ||
| @IsPositive() | ||
| amount: number; |
There was a problem hiding this comment.
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.
| @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) }; |
There was a problem hiding this comment.
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.
| transfer(dto: TransferDto): Promise<string> { | ||
| return this.soroban.buildContractCallTransaction( | ||
| dto.contractId, | ||
| 'transfer', | ||
| { from: dto.from, to_muxed: dto.to, amount: dto.amount }, | ||
| dto.callerPublicKey, | ||
| ); |
There was a problem hiding this comment.
🧩 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.
| 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).
Summary
token-factoryNestJS module toapps/coreexposing REST endpoints for the Token Factory(SEP-41) Soroban contract
return unsigned XDR for client-side signing
vaultmodule (PR Feat/vault contract function calls endpoints #58)Files
apps/core/src/token-factory/apps/core/src/app.module.ts— registeredTokenFactoryModuleSummary by CodeRabbit