Skip to content

Commit

Permalink
Merge pull request #100 from kaleido-io/address
Browse files Browse the repository at this point in the history
Support specifying a non-default contract address for a pool
  • Loading branch information
awrichar authored Sep 22, 2022
2 parents dfa3bb0 + 14d4de8 commit 1cb03af
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 55 deletions.
3 changes: 1 addition & 2 deletions samples/solidity/contracts/ERC1155MixedFungible.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ contract ERC1155MixedFungible is Context, ERC1155, IERC1155MixedFungible {
}

function _setNonFungibleURI(uint256 type_id, uint256 id, string memory _uri)
public
virtual
private
creatorOnly(type_id)
{
require(isNonFungible(type_id), "ERC1155MixedFungible: id does not represent a non-fungible type");
Expand Down
13 changes: 12 additions & 1 deletion src/tokens/tokens.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Event } from '../event-stream/event-stream.interfaces';
export interface PoolLocator {
poolId: string;
blockNumber?: string;
address?: string;
}

// Ethconnect interfaces
Expand Down Expand Up @@ -98,6 +99,16 @@ const approvalConfigDescription =
const transferConfigDescription =
'Optional configuration info for the token transfer. Reserved for future use.';

export class TokenPoolConfig {
@ApiProperty()
@IsOptional()
address?: string;

@ApiProperty()
@IsOptional()
blockNumber?: string;
}

export class TokenPool {
@ApiProperty({ enum: TokenType })
@IsDefined()
Expand All @@ -117,7 +128,7 @@ export class TokenPool {

@ApiProperty({ description: poolConfigDescription })
@IsOptional()
config?: any;
config?: TokenPoolConfig;
}

export class TokenApproval {
Expand Down
109 changes: 68 additions & 41 deletions src/tokens/tokens.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
TransferBatchEvent,
TransferSingleEvent,
TokenPoolEventInfo,
TokenPoolConfig,
} from './tokens.interfaces';
import {
decodeHex,
Expand Down Expand Up @@ -148,22 +149,23 @@ export class TokensService {
* One-time initialization of event stream and base subscription.
*/
async init() {
const stream = await this.getStream();
await this.createPoolSubscription(await this.getContractAddress());
}

private async createPoolSubscription(address: string, blockNumber?: string) {
const stream = await this.getStream();
const eventABI = ERC1155MixedFungibleAbi.find(m => m.name === tokenCreateEvent);
const methodABI = ERC1155MixedFungibleAbi.find(m => m.name === tokenCreateFunctionName);

if (eventABI !== undefined && methodABI !== undefined) {
const contractAddress = await this.getContractAddress();
await this.eventstream.getOrCreateSubscription(
this.baseUrl,
eventABI,
stream.id,
tokenCreateEvent,
packSubscriptionName(this.instancePath, BASE_SUBSCRIPTION_NAME, tokenCreateEvent),
contractAddress,
packSubscriptionName(address, BASE_SUBSCRIPTION_NAME, tokenCreateEvent),
address,
[methodABI],
'0',
blockNumber ?? '0',
);
}
}
Expand All @@ -187,10 +189,11 @@ export class TokensService {
return this.contractAddress;
}

async isCustomUriSupported() {
async isCustomUriSupported(address: string) {
if (this.supportsCustomUri === undefined) {
try {
const result = await this.query(
address,
ERC1155MixedFungibleAbi.find(m => m.name === 'supportsInterface'),
[CUSTOM_URI_IID],
);
Expand All @@ -208,9 +211,10 @@ export class TokensService {
return this.supportsCustomUri;
}

async queryBaseUri() {
async queryBaseUri(address: string) {
try {
const result = await this.query(
address,
ERC1155MixedFungibleAbi.find(m => m.name === 'baseTokenUri'),
[CUSTOM_URI_IID],
);
Expand Down Expand Up @@ -315,22 +319,6 @@ export class TokensService {
return basicAuth(this.username, this.password);
}

private postOptions(signer: string, requestId?: string) {
const from = `${this.shortPrefix}-from`;
const sync = `${this.shortPrefix}-sync`;
const id = `${this.shortPrefix}-id`;

const requestOptions: AxiosRequestConfig = {
params: {
[from]: signer,
[sync]: 'false',
[id]: requestId,
},
...basicAuth(this.username, this.password),
};

return requestOptions;
}
private async wrapError<T>(response: Promise<AxiosResponse<T>>) {
return response.catch(err => {
if (axios.isAxiosError(err)) {
Expand All @@ -346,28 +334,34 @@ export class TokensService {
});
}

async query(method?: IAbiMethod, params?: any[]) {
async query(to: string, method?: IAbiMethod, params?: any[]) {
const response = await this.wrapError(
lastValueFrom(
this.http.post<EthConnectReturn>(
this.baseUrl,
{ headers: { type: queryHeader }, to: await this.getContractAddress(), method, params },
{ headers: { type: queryHeader }, to, method, params },
this.requestOptions(),
),
),
);
return response.data;
}

async sendTransaction(from: string, id?: string, method?: IAbiMethod, params?: any[]) {
async sendTransaction(
from: string,
to: string,
id?: string,
method?: IAbiMethod,
params?: any[],
) {
const response = await this.wrapError(
lastValueFrom(
this.http.post<EthConnectAsyncResponse>(
this.baseUrl,
{
headers: { id, type: sendTransactionHeader },
from,
to: await this.getContractAddress(),
to,
method,
params,
},
Expand All @@ -394,8 +388,18 @@ export class TokensService {
}

async createPool(dto: TokenPool): Promise<AsyncResponse> {
if (dto.config?.address !== undefined && dto.config.address !== '') {
await this.createPoolSubscription(dto.config.address, dto.config.blockNumber);
return this.createWithAddress(dto.config.address, dto);
}
return this.createWithAddress(await this.getContractAddress(), dto);
}

async createWithAddress(address: string, dto: TokenPool) {
this.logger.log(`Create token pool from contract: '${address}'`);
const response = await this.sendTransaction(
dto.signer,
address,
dto.requestId,
ERC1155MixedFungibleAbi.find(m => m.name === tokenCreateFunctionName),
[dto.type === TokenType.FUNGIBLE, encodeHex(dto.data ?? '')],
Expand All @@ -406,6 +410,7 @@ export class TokensService {
async activatePool(dto: TokenPoolActivate) {
const stream = await this.getStream();
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());

const tokenCreateEventABI = ERC1155MixedFungibleAbi.find(m => m.name === tokenCreateEvent);
const tokenCreateFunctionABI = ERC1155MixedFungibleAbi.find(
Expand Down Expand Up @@ -436,15 +441,14 @@ export class TokensService {
transferBatchEventABI !== undefined &&
approvalForAllEventABI !== undefined
) {
const contractAddress = await this.getContractAddress();
await Promise.all([
this.eventstream.getOrCreateSubscription(
this.baseUrl,
tokenCreateEventABI,
stream.id,
tokenCreateEvent,
packSubscriptionName(this.instancePath, dto.poolLocator, tokenCreateEvent, dto.poolData),
contractAddress,
address,
[tokenCreateFunctionABI],
poolLocator.blockNumber ?? '0',
),
Expand All @@ -459,7 +463,7 @@ export class TokensService {
transferSingleEvent,
dto.poolData,
),
contractAddress,
address,
transferFunctionABIs,
poolLocator.blockNumber ?? '0',
),
Expand All @@ -474,7 +478,7 @@ export class TokensService {
transferBatchEvent,
dto.poolData,
),
contractAddress,
address,
transferFunctionABIs,
poolLocator.blockNumber ?? '0',
),
Expand All @@ -489,7 +493,7 @@ export class TokensService {
approvalForAllEvent,
dto.poolData,
),
contractAddress,
address,
approvalFunctionABIs,
// Block number is 0 because it is important to receive all approval events,
// so existing approvals will be reflected in the newly created pool
Expand All @@ -501,10 +505,12 @@ export class TokensService {

async mint(dto: TokenMint): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const typeId = packTokenId(poolLocator.poolId);
if (isFungible(poolLocator.poolId)) {
const response = await this.sendTransaction(
dto.signer,
address,
dto.requestId,
ERC1155MixedFungibleAbi.find(m => m.name === 'mintFungible'),
[typeId, [dto.to], [dto.amount], encodeHex(dto.data ?? '')],
Expand All @@ -520,9 +526,10 @@ export class TokensService {
to.push(dto.to);
}

if (dto.uri !== undefined && (await this.isCustomUriSupported())) {
if (dto.uri !== undefined && (await this.isCustomUriSupported(address))) {
const response = await this.sendTransaction(
dto.signer,
address,
dto.requestId,
ERC1155MixedFungibleAbi.find(m => m.name === 'mintNonFungibleWithURI'),
[typeId, to, encodeHex(dto.data ?? ''), dto.uri],
Expand All @@ -531,6 +538,7 @@ export class TokensService {
} else {
const response = await this.sendTransaction(
dto.signer,
address,
dto.requestId,
ERC1155MixedFungibleAbi.find(m => m.name === 'mintNonFungible'),
[typeId, to, encodeHex(dto.data ?? '')],
Expand All @@ -541,8 +549,11 @@ export class TokensService {
}

async approval(dto: TokenApproval): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const response = await this.sendTransaction(
dto.signer,
address,
dto.requestId,
ERC1155MixedFungibleAbi.find(m => m.name === 'setApprovalForAllWithData'),
[dto.operator, dto.approved, encodeHex(dto.data ?? '')],
Expand All @@ -552,8 +563,10 @@ export class TokensService {

async transfer(dto: TokenTransfer): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const response = await this.sendTransaction(
dto.signer,
address,
dto.requestId,
ERC1155MixedFungibleAbi.find(m => m.name === 'safeTransferFrom'),
[
Expand All @@ -569,8 +582,10 @@ export class TokensService {

async burn(dto: TokenBurn): Promise<AsyncResponse> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const response = await this.sendTransaction(
dto.signer,
address,
dto.requestId,
ERC1155MixedFungibleAbi.find(m => m.name === 'burn'),
[
Expand All @@ -586,7 +601,9 @@ export class TokensService {

async balance(dto: TokenBalanceQuery): Promise<TokenBalance> {
const poolLocator = unpackPoolLocator(dto.poolLocator);
const address = poolLocator.address ?? (await this.getContractAddress());
const response = await this.query(
address,
ERC1155MixedFungibleAbi.find(m => m.name === 'balanceOf'),
[dto.account, packTokenId(poolLocator.poolId, dto.tokenIndex)],
);
Expand Down Expand Up @@ -663,8 +680,15 @@ class TokenListener implements EventListener {
return undefined;
}

const poolLocator = unpackPoolLocator(unpackedSub.poolLocator);
if (poolLocator.poolId !== BASE_SUBSCRIPTION_NAME && poolLocator.poolId !== unpackedId.poolId) {
let packedPoolLocator = unpackedSub.poolLocator;
const poolLocator = unpackPoolLocator(packedPoolLocator);
if (poolLocator.poolId === BASE_SUBSCRIPTION_NAME) {
packedPoolLocator = packPoolLocator(
event.address.toLowerCase(),
unpackedId.poolId,
event.blockNumber,
);
} else if (poolLocator.poolId !== unpackedId.poolId) {
return undefined;
}

Expand All @@ -673,15 +697,15 @@ class TokenListener implements EventListener {
typeId: '0x' + encodeHexIDForURI(output.type_id),
};

if (await this.service.isCustomUriSupported()) {
eventInfo.baseUri = await this.service.queryBaseUri();
if (await this.service.isCustomUriSupported(event.address)) {
eventInfo.baseUri = await this.service.queryBaseUri(event.address);
}

return {
event: 'token-pool',
data: <TokenPoolEvent>{
standard: TOKEN_STANDARD,
poolLocator: packPoolLocator(unpackedId.poolId, event.blockNumber),
poolLocator: packedPoolLocator,
type: unpackedId.isFungible ? TokenType.FUNGIBLE : TokenType.NONFUNGIBLE,
signer: output.operator,
data: decodedData,
Expand Down Expand Up @@ -731,7 +755,9 @@ class TokenListener implements EventListener {
return undefined;
}

const uri = unpackedId.isFungible ? undefined : await this.getTokenUri(output.id);
const uri = unpackedId.isFungible
? undefined
: await this.getTokenUri(event.address, output.id);
const eventId = this.formatBlockchainEventId(event);
const transferId =
eventIndex === undefined ? eventId : eventId + '/' + eventIndex.toString(10).padStart(6, '0');
Expand Down Expand Up @@ -858,9 +884,10 @@ class TokenListener implements EventListener {
};
}

private async getTokenUri(id: string): Promise<string> {
private async getTokenUri(address: string, id: string): Promise<string> {
try {
const response = await this.service.query(
address,
ERC1155MixedFungibleAbi.find(m => m.name === 'uri'),
[id],
);
Expand Down
2 changes: 1 addition & 1 deletion src/tokens/tokens.util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('Util', () => {
});

it('packPoolLocator', () => {
expect(packPoolLocator('N1', '5')).toEqual('id=N1&block=5');
expect(packPoolLocator('0x123', 'N1', '5')).toEqual('address=0x123&id=N1&block=5');
});

it('unpackPoolLocator', () => {
Expand Down
Loading

0 comments on commit 1cb03af

Please sign in to comment.