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 0492482 + 1866f88 commit b676b78
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/tokens/abimapper.service.ts
Expand Up @@ -77,7 +77,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
11 changes: 11 additions & 0 deletions src/tokens/tokens.controller.ts
Expand Up @@ -20,6 +20,7 @@ import { EventStreamReply } from '../event-stream/event-stream.interfaces';
import { BlockchainConnectorService } from './blockchain.service';
import {
AsyncResponse,
CheckInterfaceRequest,
TokenApproval,
TokenBalance,
TokenBalanceQuery,
Expand Down Expand Up @@ -79,6 +80,16 @@ export class TokensController {
return this.service.mint(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('approval')
@HttpCode(202)
@ApiOperation({
Expand Down
42 changes: 39 additions & 3 deletions src/tokens/tokens.interfaces.ts
Expand Up @@ -15,7 +15,7 @@
// limitations under the License.

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

// Internal types
Expand Down Expand Up @@ -89,6 +89,11 @@ export enum TokenType {
NONFUNGIBLE = 'nonfungible',
}

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

const requestIdDescription =
'Optional ID to identify this request. Must be unique for every request. ' +
'If none is provided, one will be assigned and returned in the 202 response.';
Expand Down Expand Up @@ -213,9 +218,37 @@ export class TokenBalance {
}

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 @@ -333,6 +366,9 @@ export class TokenPoolEvent extends tokenEventBase {
@ApiProperty()
standard: string;

@ApiProperty()
interfaceFormat: InterfaceFormat;

@ApiProperty()
@IsOptional()
symbol?: string;
Expand Down
2 changes: 2 additions & 0 deletions src/tokens/tokens.listener.ts
Expand Up @@ -30,6 +30,7 @@ import {
TransferBatchEvent,
TransferSingleEvent,
TokenPoolEventInfo,
InterfaceFormat,
} from './tokens.interfaces';
import {
decodeHex,
Expand Down Expand Up @@ -142,6 +143,7 @@ export class TokenListener implements EventListener {
event: 'token-pool',
data: <TokenPoolEvent>{
standard: TOKEN_STANDARD,
interfaceFormat: InterfaceFormat.ABI,
poolLocator: packedPoolLocator,
type: unpackedId.isFungible ? TokenType.FUNGIBLE : TokenType.NONFUNGIBLE,
signer: output.operator,
Expand Down
35 changes: 30 additions & 5 deletions src/tokens/tokens.service.ts
Expand Up @@ -20,10 +20,15 @@ import { EventStream, EventStreamSubscription } from '../event-stream/event-stre
import { EventStreamProxyGateway } from '../eventstream-proxy/eventstream-proxy.gateway';
import {
AsyncResponse,
CheckInterfaceRequest,
CheckInterfaceResponse,
IAbiMethod,
InterfaceFormat,
TokenApproval,
TokenBalance,
TokenBalanceQuery,
TokenBurn,
TokenInterface,
TokenMint,
TokenPool,
TokenPoolActivate,
Expand All @@ -39,7 +44,14 @@ import {
import { TokenListener } from './tokens.listener';
import { BlockchainConnectorService } from './blockchain.service';
import { AbiMapperService } from './abimapper.service';
import { AllEvents, ApprovalForAll, BalanceOf, TransferBatch, TransferSingle } from './erc1155';
import {
AllEvents,
ApprovalForAll,
BalanceOf,
DynamicMethods,
TransferBatch,
TransferSingle,
} from './erc1155';

export const BASE_SUBSCRIPTION_NAME = 'base';

Expand Down Expand Up @@ -306,6 +318,19 @@ export class TokensService {
await Promise.all(promises);
}

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

return {
approval: wrapMethods(this.mapper.getAllMethods(dto.methods, DynamicMethods.approval)),
burn: wrapMethods(this.mapper.getAllMethods(dto.methods, DynamicMethods.burn)),
mint: wrapMethods(this.mapper.getAllMethods(dto.methods, DynamicMethods.mint)),
transfer: wrapMethods(this.mapper.getAllMethods(dto.methods, DynamicMethods.transfer)),
};
}

private async getAbiForMint(address: string, dto: TokenMint) {
const supportsUri = dto.uri !== undefined && (await this.mapper.supportsMintWithUri(address));
return this.mapper.getAbi(supportsUri);
Expand All @@ -314,7 +339,7 @@ export class TokensService {
async mint(dto: TokenMint): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const abi = dto.interface?.abi || (await this.getAbiForMint(address, dto));
const abi = dto.interface?.methods || (await this.getAbiForMint(address, dto));
const { method, params } = this.mapper.getMethodAndParams(abi, poolLocator, 'mint', dto);
const response = await this.blockchain.sendTransaction(
dto.signer,
Expand All @@ -329,7 +354,7 @@ export class TokensService {
async transfer(dto: TokenTransfer): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const abi = dto.interface?.abi || this.mapper.getAbi();
const abi = dto.interface?.methods || this.mapper.getAbi();
const { method, params } = this.mapper.getMethodAndParams(abi, poolLocator, 'transfer', dto);
const response = await this.blockchain.sendTransaction(
dto.signer,
Expand All @@ -344,7 +369,7 @@ export class TokensService {
async burn(dto: TokenBurn): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const abi = dto.interface?.abi || this.mapper.getAbi();
const abi = dto.interface?.methods || this.mapper.getAbi();
const { method, params } = this.mapper.getMethodAndParams(abi, poolLocator, 'burn', dto);
const response = await this.blockchain.sendTransaction(
dto.signer,
Expand All @@ -360,7 +385,7 @@ export class TokensService {
async approval(dto: TokenApproval): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const abi = dto.interface?.abi || this.mapper.getAbi();
const abi = dto.interface?.methods || this.mapper.getAbi();
const { method, params } = this.mapper.getMethodAndParams(abi, poolLocator, 'approval', dto);
const response = await this.blockchain.sendTransaction(
dto.signer,
Expand Down
39 changes: 39 additions & 0 deletions test/suites/api.ts
Expand Up @@ -9,6 +9,9 @@ import {
EthConnectReturn,
TokenBalance,
TokenBalanceQuery,
CheckInterfaceRequest,
InterfaceFormat,
CheckInterfaceResponse,
} from '../../src/tokens/tokens.interfaces';
import { TestContext, FakeObservable, BASE_URL, CONTRACT_ADDRESS } from '../app.e2e-context';
import { abi as ERC1155MixedFungibleAbi } from '../../src/abi/ERC1155MixedFungible.json';
Expand Down Expand Up @@ -357,4 +360,40 @@ export default (context: TestContext) => {
{},
);
});

it('Check interface', async () => {
const request: CheckInterfaceRequest = {
poolLocator: 'F1',
format: InterfaceFormat.ABI,
methods: ERC1155MixedFungibleAbi,
};

const response: CheckInterfaceResponse = {
approval: {
format: InterfaceFormat.ABI,
methods: [
...ERC1155MixedFungibleAbi.filter(m => m.name === 'setApprovalForAllWithData'),
...ERC1155MixedFungibleAbi.filter(m => m.name === 'setApprovalForAll'),
],
},
burn: {
format: InterfaceFormat.ABI,
methods: ERC1155MixedFungibleAbi.filter(m => m.name === 'burn'),
},
mint: {
format: InterfaceFormat.ABI,
methods: [
...ERC1155MixedFungibleAbi.filter(m => m.name === 'mintFungible'),
...ERC1155MixedFungibleAbi.filter(m => m.name === 'mintNonFungibleWithURI'),
...ERC1155MixedFungibleAbi.filter(m => m.name === 'mintNonFungible'),
],
},
transfer: {
format: InterfaceFormat.ABI,
methods: ERC1155MixedFungibleAbi.filter(m => m.name === 'safeTransferFrom'),
},
};

await context.server.post('/checkinterface').send(request).expect(200).expect(response);
});
};
3 changes: 3 additions & 0 deletions test/suites/websocket.ts
Expand Up @@ -81,6 +81,7 @@ export default (context: TestContext) => {
event: 'token-pool',
data: <TokenPoolEvent>{
standard: 'ERC1155',
interfaceFormat: 'abi',
poolLocator: 'F1',
type: 'fungible',
signer: 'bob',
Expand Down Expand Up @@ -150,6 +151,7 @@ export default (context: TestContext) => {
event: 'token-pool',
data: <TokenPoolEvent>{
standard: 'ERC1155',
interfaceFormat: 'abi',
poolLocator: 'address=0x00001&id=F1&block=1',
type: 'fungible',
signer: 'bob',
Expand Down Expand Up @@ -219,6 +221,7 @@ export default (context: TestContext) => {
event: 'token-pool',
data: <TokenPoolEvent>{
standard: 'ERC1155',
interfaceFormat: 'abi',
poolLocator: 'address=0x00001&id=F1&block=1',
type: 'fungible',
signer: 'bob',
Expand Down

0 comments on commit b676b78

Please sign in to comment.