Skip to content

Commit

Permalink
Merge pull request #106 from kaleido-io/checkinterface
Browse files Browse the repository at this point in the history
Add /checkinterface API
  • Loading branch information
awrichar committed Jan 5, 2023
2 parents eab3a44 + ba1a628 commit 3c966fc
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/tokens/abimapper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class AbiMapperService {
return true;
}

private getAllMethods(abi: IAbiMethod[], signatures: MethodSignature[]) {
getAllMethods(abi: IAbiMethod[], signatures: MethodSignature[]) {
const methods: IAbiMethod[] = [];
for (const signature of signatures) {
for (const method of abi) {
Expand Down
29 changes: 20 additions & 9 deletions src/tokens/tokens.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Res } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Response } from 'express';
import { RequestContext } from '../request-context/request-context.decorator';
import { Context, RequestContext } from '../request-context/request-context.decorator';
import { EventStreamReply } from '../event-stream/event-stream.interfaces';
import { BlockchainConnectorService } from './blockchain.service';
import {
AsyncResponse,
CheckInterfaceRequest,
TokenApproval,
TokenBurn,
TokenMint,
Expand All @@ -39,7 +40,7 @@ export class TokensController {
@Post('init')
@HttpCode(204)
@ApiOperation({ summary: 'Perform one-time initialization (if not auto-initialized)' })
init(@RequestContext() ctx) {
init(@RequestContext() ctx: Context) {
return this.service.init(ctx);
}

Expand All @@ -53,7 +54,7 @@ export class TokensController {
@ApiResponse({ status: 200, type: TokenPoolEvent })
@ApiResponse({ status: 202, type: AsyncResponse })
async createPool(
@RequestContext() ctx,
@RequestContext() ctx: Context,
@Body() dto: TokenPool,
@Res({ passthrough: true }) res: Response,
) {
Expand All @@ -72,10 +73,20 @@ export class TokensController {
summary: 'Activate a token pool to begin receiving transfer events',
})
@ApiBody({ type: TokenPoolActivate })
activatePool(@RequestContext() ctx, @Body() dto: TokenPoolActivate) {
activatePool(@RequestContext() ctx: Context, @Body() dto: TokenPoolActivate) {
return this.service.activatePool(ctx, dto);
}

@Post('checkinterface')
@HttpCode(200)
@ApiOperation({
summary: 'Check which interface methods are supported by this connector',
})
@ApiBody({ type: CheckInterfaceRequest })
checkInterface(@Body() dto: CheckInterfaceRequest) {
return this.service.checkInterface(dto);
}

@Post('mint')
@HttpCode(202)
@ApiOperation({
Expand All @@ -85,7 +96,7 @@ export class TokensController {
})
@ApiBody({ type: TokenMint })
@ApiResponse({ status: 202, type: AsyncResponse })
mint(@RequestContext() ctx, @Body() dto: TokenMint) {
mint(@RequestContext() ctx: Context, @Body() dto: TokenMint) {
return this.service.mint(ctx, dto);
}

Expand All @@ -98,7 +109,7 @@ export class TokensController {
})
@ApiBody({ type: TokenTransfer })
@ApiResponse({ status: 202, type: AsyncResponse })
transfer(@RequestContext() ctx, @Body() dto: TokenTransfer) {
transfer(@RequestContext() ctx: Context, @Body() dto: TokenTransfer) {
return this.service.transfer(ctx, dto);
}

Expand All @@ -110,7 +121,7 @@ export class TokensController {
})
@ApiBody({ type: TokenApproval })
@ApiResponse({ status: 202, type: AsyncResponse })
approve(@RequestContext() ctx, @Body() dto: TokenApproval) {
approve(@RequestContext() ctx: Context, @Body() dto: TokenApproval) {
return this.service.approval(ctx, dto);
}

Expand All @@ -123,14 +134,14 @@ export class TokensController {
})
@ApiBody({ type: TokenBurn })
@ApiResponse({ status: 202, type: AsyncResponse })
burn(@RequestContext() ctx, @Body() dto: TokenBurn) {
burn(@RequestContext() ctx: Context, @Body() dto: TokenBurn) {
return this.service.burn(ctx, dto);
}

@Get('receipt/:id')
@ApiOperation({ summary: 'Retrieve the result of an async operation' })
@ApiResponse({ status: 200, type: EventStreamReply })
getReceipt(@RequestContext() ctx, @Param('id') id: string) {
getReceipt(@RequestContext() ctx: Context, @Param('id') id: string) {
return this.blockchain.getReceipt(ctx, id);
}
}
42 changes: 39 additions & 3 deletions src/tokens/tokens.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// limitations under the License.

import { ApiProperty, OmitType } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
import { Equals, IsDefined, IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
import { Event } from '../event-stream/event-stream.interfaces';

// Ethconnect interfaces
Expand Down Expand Up @@ -95,6 +95,11 @@ export enum TokenType {
NONFUNGIBLE = 'nonfungible',
}

export enum InterfaceFormat {
ABI = 'abi',
FFI = 'ffi',
}

export interface IPoolLocator {
address: string | null;
schema: string | null;
Expand Down Expand Up @@ -221,9 +226,37 @@ export class TokenPoolActivate {
}

export class TokenInterface {
@ApiProperty({ enum: InterfaceFormat })
@Equals(InterfaceFormat.ABI)
format: InterfaceFormat;

@ApiProperty({ isArray: true })
@IsOptional()
abi?: IAbiMethod[];
@IsDefined()
methods: IAbiMethod[];
}

export class CheckInterfaceRequest extends TokenInterface {
@ApiProperty()
@IsNotEmpty()
poolLocator: string;
}

type TokenAbi = {
[op in TokenOperation]: TokenInterface;
};

export class CheckInterfaceResponse implements TokenAbi {
@ApiProperty()
approval: TokenInterface;

@ApiProperty()
burn: TokenInterface;

@ApiProperty()
mint: TokenInterface;

@ApiProperty()
transfer: TokenInterface;
}

export class TokenTransfer {
Expand Down Expand Up @@ -353,6 +386,9 @@ export class TokenPoolEvent extends tokenEventBase {
@ApiProperty()
standard: string;

@ApiProperty()
interfaceFormat: InterfaceFormat;

@ApiProperty()
symbol: string;

Expand Down
2 changes: 2 additions & 0 deletions src/tokens/tokens.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
TokenTransferEvent,
TokenType,
TransferEvent,
InterfaceFormat,
} from './tokens.interfaces';
import {
decodeHex,
Expand Down Expand Up @@ -150,6 +151,7 @@ export class TokenListener implements EventListener {
event: 'token-pool',
data: <TokenPoolEvent>{
standard: type === TokenType.FUNGIBLE ? 'ERC20' : 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
poolLocator: packPoolLocator(poolLocator),
type,
signer: event.inputSigner,
Expand Down
11 changes: 11 additions & 0 deletions src/tokens/tokens.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
EthConnectMsgRequest,
EthConnectReturn,
IAbiMethod,
InterfaceFormat,
TokenBurn,
TokenMint,
TokenPool,
Expand Down Expand Up @@ -267,6 +268,7 @@ describe('TokensService', () => {
data: `{"tx":${TX}}`,
poolLocator: ERC20_NO_DATA_POOL_ID,
standard: 'ERC20',
interfaceFormat: InterfaceFormat.ABI,
type: 'fungible',
symbol: SYMBOL,
decimals: 18,
Expand Down Expand Up @@ -295,6 +297,7 @@ describe('TokensService', () => {
const response: TokenPoolEvent = {
poolLocator: ERC20_NO_DATA_POOL_ID,
standard: 'ERC20',
interfaceFormat: InterfaceFormat.ABI,
type: TokenType.FUNGIBLE,
symbol: SYMBOL,
decimals: 18,
Expand Down Expand Up @@ -458,6 +461,7 @@ describe('TokensService', () => {
data: `{"tx":${TX}}`,
poolLocator: ERC20_WITH_DATA_POOL_ID,
standard: 'ERC20',
interfaceFormat: InterfaceFormat.ABI,
type: 'fungible',
symbol: SYMBOL,
decimals: 18,
Expand Down Expand Up @@ -490,6 +494,7 @@ describe('TokensService', () => {
data: `{"tx":${TX}}`,
poolLocator: ERC20_WITH_DATA_POOL_ID,
standard: 'ERC20',
interfaceFormat: InterfaceFormat.ABI,
type: 'fungible',
symbol: SYMBOL,
decimals: 18,
Expand Down Expand Up @@ -518,6 +523,7 @@ describe('TokensService', () => {
const response: TokenPoolEvent = {
poolLocator: ERC20_WITH_DATA_POOL_ID,
standard: 'ERC20',
interfaceFormat: InterfaceFormat.ABI,
type: TokenType.FUNGIBLE,
symbol: SYMBOL,
decimals: 18,
Expand Down Expand Up @@ -680,6 +686,7 @@ describe('TokensService', () => {
data: `{"tx":${TX}}`,
poolLocator: ERC721_NO_DATA_POOL_ID,
standard: 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
type: 'nonfungible',
symbol: SYMBOL,
decimals: 0,
Expand Down Expand Up @@ -708,6 +715,7 @@ describe('TokensService', () => {
const response: TokenPoolEvent = {
poolLocator: ERC721_NO_DATA_POOL_ID,
standard: 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
type: TokenType.NONFUNGIBLE,
symbol: SYMBOL,
decimals: 0,
Expand Down Expand Up @@ -888,6 +896,7 @@ describe('TokensService', () => {
data: `{"tx":${TX}}`,
poolLocator: ERC721_WITH_DATA_POOL_ID,
standard: 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
type: 'nonfungible',
symbol: SYMBOL,
decimals: 0,
Expand Down Expand Up @@ -921,6 +930,7 @@ describe('TokensService', () => {
data: `{"tx":${TX}}`,
poolLocator: ERC721_WITH_DATA_POOL_ID,
standard: 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
type: 'nonfungible',
symbol: SYMBOL,
decimals: 0,
Expand Down Expand Up @@ -949,6 +959,7 @@ describe('TokensService', () => {
const response: TokenPoolEvent = {
poolLocator: ERC721_WITH_DATA_POOL_ID,
standard: 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
type: TokenType.NONFUNGIBLE,
symbol: SYMBOL,
decimals: 0,
Expand Down
36 changes: 32 additions & 4 deletions src/tokens/tokens.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ import { EventStreamProxyGateway } from '../eventstream-proxy/eventstream-proxy.
import { Context, newContext } from '../request-context/request-context.decorator';
import {
AsyncResponse,
CheckInterfaceRequest,
CheckInterfaceResponse,
IAbiMethod,
InterfaceFormat,
IPoolLocator,
IValidPoolLocator,
TokenApproval,
TokenBurn,
TokenInterface,
TokenMint,
TokenPool,
TokenPoolActivate,
Expand All @@ -48,13 +53,15 @@ import {
Transfer as ERC20Transfer,
Name as ERC20Name,
Symbol as ERC20Symbol,
DynamicMethods as ERC20Methods,
} from './erc20';
import {
Approval as ERC721Approval,
ApprovalForAll as ERC721ApprovalForAll,
Transfer as ERC721Transfer,
Name as ERC721Name,
Symbol as ERC721Symbol,
DynamicMethods as ERC721Methods,
} from './erc721';

@Injectable()
Expand Down Expand Up @@ -263,6 +270,7 @@ export class TokensService {
data: dto.data,
poolLocator: packPoolLocator(poolLocator),
standard: dto.type === TokenType.FUNGIBLE ? 'ERC20' : 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
type: dto.type,
symbol: poolInfo.symbol,
decimals: poolInfo.decimals,
Expand Down Expand Up @@ -363,6 +371,7 @@ export class TokensService {
const tokenPoolEvent: TokenPoolEvent = {
poolLocator: dto.poolLocator,
standard: poolLocator.type === TokenType.FUNGIBLE ? 'ERC20' : 'ERC721',
interfaceFormat: InterfaceFormat.ABI,
type: poolLocator.type,
symbol: poolInfo.symbol,
decimals: poolInfo.decimals,
Expand All @@ -376,6 +385,25 @@ export class TokensService {
return tokenPoolEvent;
}

checkInterface(dto: CheckInterfaceRequest): CheckInterfaceResponse {
const poolLocator = unpackPoolLocator(dto.poolLocator);
if (!validatePoolLocator(poolLocator)) {
throw new BadRequestException('Invalid pool locator');
}

const wrapMethods = (methods: IAbiMethod[]): TokenInterface => {
return { format: InterfaceFormat.ABI, methods };
};

const methods = poolLocator.type === TokenType.FUNGIBLE ? ERC20Methods : ERC721Methods;
return {
approval: wrapMethods(this.mapper.getAllMethods(dto.methods, methods.approval)),
burn: wrapMethods(this.mapper.getAllMethods(dto.methods, methods.burn)),
mint: wrapMethods(this.mapper.getAllMethods(dto.methods, methods.mint)),
transfer: wrapMethods(this.mapper.getAllMethods(dto.methods, methods.transfer)),
};
}

private async getAbiForMint(ctx: Context, poolLocator: IValidPoolLocator, dto: TokenMint) {
const supportsUri =
dto.uri !== undefined && (await this.mapper.supportsMintWithUri(ctx, poolLocator.address));
Expand All @@ -388,7 +416,7 @@ export class TokensService {
throw new BadRequestException('Invalid pool locator');
}

const abi = dto.interface?.abi || (await this.getAbiForMint(ctx, poolLocator, dto));
const abi = dto.interface?.methods || (await this.getAbiForMint(ctx, poolLocator, dto));
const { method, params } = this.mapper.getMethodAndParams(
abi,
poolLocator.type === TokenType.FUNGIBLE,
Expand All @@ -412,7 +440,7 @@ export class TokensService {
throw new BadRequestException('Invalid pool locator');
}

const abi = dto.interface?.abi || this.mapper.getAbi(poolLocator.schema);
const abi = dto.interface?.methods || this.mapper.getAbi(poolLocator.schema);
const { method, params } = this.mapper.getMethodAndParams(
abi,
poolLocator.type === TokenType.FUNGIBLE,
Expand All @@ -436,7 +464,7 @@ export class TokensService {
throw new BadRequestException('Invalid pool locator');
}

const abi = dto.interface?.abi || this.mapper.getAbi(poolLocator.schema);
const abi = dto.interface?.methods || this.mapper.getAbi(poolLocator.schema);
const { method, params } = this.mapper.getMethodAndParams(
abi,
poolLocator.type === TokenType.FUNGIBLE,
Expand All @@ -460,7 +488,7 @@ export class TokensService {
throw new BadRequestException('Invalid pool locator');
}

const abi = dto.interface?.abi || this.mapper.getAbi(poolLocator.schema);
const abi = dto.interface?.methods || this.mapper.getAbi(poolLocator.schema);
const { method, params } = this.mapper.getMethodAndParams(
abi,
poolLocator.type === TokenType.FUNGIBLE,
Expand Down

0 comments on commit 3c966fc

Please sign in to comment.