-
Notifications
You must be signed in to change notification settings - Fork 12
Add TokenFactory module to support token management operations #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { IsString, IsNumber, IsNotEmpty, IsPositive } from 'class-validator'; | ||
|
|
||
| export class ApproveDto { | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| contractId: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| from: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| spender: string; | ||
|
|
||
| @IsNumber() | ||
| @IsPositive() | ||
| amount: number; | ||
|
|
||
| @IsNumber() | ||
| @IsPositive() | ||
| expirationLedger: number; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| callerPublicKey: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { IsString, IsNumber, IsNotEmpty, IsPositive } from 'class-validator'; | ||
|
|
||
| export class BurnFromDto { | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| contractId: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| spender: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| from: string; | ||
|
|
||
| @IsNumber() | ||
| @IsPositive() | ||
| amount: number; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| callerPublicKey: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { IsString, IsNumber, IsNotEmpty, IsPositive } from 'class-validator'; | ||
|
|
||
| export class BurnDto { | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| contractId: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| from: string; | ||
|
|
||
| @IsNumber() | ||
| @IsPositive() | ||
| amount: number; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| callerPublicKey: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { IsString, IsNumber, IsNotEmpty, IsPositive } from 'class-validator'; | ||
|
|
||
| export class MintDto { | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| contractId: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| to: string; | ||
|
|
||
| @IsNumber() | ||
| @IsPositive() | ||
| amount: number; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| callerPublicKey: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { IsString, IsNotEmpty } from 'class-validator'; | ||
|
|
||
| export class SetAdminDto { | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| contractId: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| newAdmin: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| callerPublicKey: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { IsString, IsNumber, IsNotEmpty, IsPositive } from 'class-validator'; | ||
|
|
||
| export class TransferFromDto { | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| contractId: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| spender: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| from: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| to: string; | ||
|
|
||
| @IsNumber() | ||
| @IsPositive() | ||
| amount: number; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| callerPublicKey: string; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { IsString, IsNumber, IsNotEmpty, IsPositive } from 'class-validator'; | ||
|
|
||
| export class TransferDto { | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| contractId: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| from: string; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| to: string; | ||
|
|
||
| @IsNumber() | ||
| @IsPositive() | ||
| amount: number; | ||
|
|
||
| @IsString() | ||
| @IsNotEmpty() | ||
| callerPublicKey: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import { Controller, Post, Get, Body, Query } from '@nestjs/common'; | ||
| 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'; | ||
|
|
||
| @Controller('token-factory') | ||
| export class TokenFactoryController { | ||
| constructor(private readonly tokenFactoryService: TokenFactoryService) {} | ||
|
|
||
| // ── POST endpoints (writes) ── | ||
|
|
||
| @Post('mint') | ||
| async mint(@Body() dto: MintDto) { | ||
| const unsignedXdr = await this.tokenFactoryService.mint(dto); | ||
| return { unsignedXdr }; | ||
| } | ||
|
|
||
| @Post('set-admin') | ||
| async setAdmin(@Body() dto: SetAdminDto) { | ||
| const unsignedXdr = await this.tokenFactoryService.setAdmin(dto); | ||
| return { unsignedXdr }; | ||
| } | ||
|
|
||
| @Post('approve') | ||
| async approve(@Body() dto: ApproveDto) { | ||
| const unsignedXdr = await this.tokenFactoryService.approve(dto); | ||
| return { unsignedXdr }; | ||
| } | ||
|
|
||
| @Post('transfer') | ||
| async transfer(@Body() dto: TransferDto) { | ||
| const unsignedXdr = await this.tokenFactoryService.transfer(dto); | ||
| return { unsignedXdr }; | ||
| } | ||
|
|
||
| @Post('transfer-from') | ||
| async transferFrom(@Body() dto: TransferFromDto) { | ||
| const unsignedXdr = await this.tokenFactoryService.transferFrom(dto); | ||
| return { unsignedXdr }; | ||
| } | ||
|
|
||
| @Post('burn') | ||
| async burn(@Body() dto: BurnDto) { | ||
| const unsignedXdr = await this.tokenFactoryService.burn(dto); | ||
| return { unsignedXdr }; | ||
| } | ||
|
|
||
| @Post('burn-from') | ||
| async burnFrom(@Body() dto: BurnFromDto) { | ||
| const unsignedXdr = await this.tokenFactoryService.burnFrom(dto); | ||
| return { unsignedXdr }; | ||
| } | ||
|
|
||
| // ── GET endpoints (reads) ── | ||
|
|
||
| @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) }; | ||
|
Comment on lines
+61
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate the GET query params before delegating to the service. These handlers read raw query strings, so the global As per coding guidelines "Keep business logic in Services only; Controllers should handle HTTP concerns (params, delegation, response)". 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Module } from '@nestjs/common'; | ||
| import { TokenFactoryController } from './token-factory.controller'; | ||
| import { TokenFactoryService } from './token-factory.service'; | ||
|
|
||
| @Module({ | ||
| controllers: [TokenFactoryController], | ||
| providers: [TokenFactoryService], | ||
| }) | ||
| export class TokenFactoryModule {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a bigint-safe representation for
amount.amount: numberaccepts fractional values and silently loses precision above2^53 - 1, which is risky for SEP-41 token amounts. Prefer a positive integer string here, then convert withBigInt(dto.amount)in the service before building the Soroban call.🔧 Suggested DTO change
As per coding guidelines "Use class-validator and class-transformer decorators for DTOs".
🤖 Prompt for AI Agents