From 57ac4e3bf22cc237d3a1207c42de275399848d80 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 15 Nov 2023 15:44:29 +0200 Subject: [PATCH] Add proxy deployer --- package-lock.json | 51 +++++- package.json | 2 +- schema.gql | 10 +- src/abis/proxy-deployer.abi.json | 150 ++++++++++++++++++ src/app.module.ts | 2 + src/modules/auctions/contractLoader.ts | 2 + src/modules/auctions/marketplaceUtils.ts | 1 + .../proxy-deployer/models/AddMinterArgs.ts | 14 ++ .../proxy-deployer/models/DeployMinterArgs.ts | 28 ++++ .../proxy-deployer/models/Minter.dto.ts | 24 +++ .../proxy-deployer/models/MinterFilters.ts | 18 +++ .../proxy-deployer/models/MintersResponse.ts | 6 + src/modules/proxy-deployer/models/index.ts | 2 + .../models/requests/DeployMinterRequest.ts | 34 ++++ .../models/requests/whitelistMinterRequest.ts | 16 ++ .../proxy-deployer-caching.service.ts | 18 +++ .../proxy-deployer-mutations.resolver.ts | 37 +++++ .../proxy-deployer.abi.service.ts | 79 +++++++++ .../proxy-deployer/proxy-deployer.module.ts | 17 ++ .../proxy-deployer/proxy-deployer.service.ts | 81 ++++++++++ 20 files changed, 586 insertions(+), 6 deletions(-) create mode 100644 src/abis/proxy-deployer.abi.json create mode 100644 src/modules/proxy-deployer/models/AddMinterArgs.ts create mode 100644 src/modules/proxy-deployer/models/DeployMinterArgs.ts create mode 100644 src/modules/proxy-deployer/models/Minter.dto.ts create mode 100644 src/modules/proxy-deployer/models/MinterFilters.ts create mode 100644 src/modules/proxy-deployer/models/MintersResponse.ts create mode 100644 src/modules/proxy-deployer/models/index.ts create mode 100644 src/modules/proxy-deployer/models/requests/DeployMinterRequest.ts create mode 100644 src/modules/proxy-deployer/models/requests/whitelistMinterRequest.ts create mode 100644 src/modules/proxy-deployer/proxy-deployer-caching.service.ts create mode 100644 src/modules/proxy-deployer/proxy-deployer-mutations.resolver.ts create mode 100644 src/modules/proxy-deployer/proxy-deployer.abi.service.ts create mode 100644 src/modules/proxy-deployer/proxy-deployer.module.ts create mode 100644 src/modules/proxy-deployer/proxy-deployer.service.ts diff --git a/package-lock.json b/package-lock.json index 093724a21..327e26c37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@aws-sdk/lib-storage": "^3.438.0", "@elastic/elasticsearch": "7.12.0", "@golevelup/nestjs-rabbitmq": "4.0.0", - "@multiversx/sdk-core": "12.6.1", + "@multiversx/sdk-core": "12.14.0", "@multiversx/sdk-native-auth-server": "1.0.7", "@multiversx/sdk-nestjs-cache": "2.0.0-beta.2", "@multiversx/sdk-nestjs-common": "2.0.0-beta.2", @@ -3665,9 +3665,9 @@ } }, "node_modules/@multiversx/sdk-core": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-12.6.1.tgz", - "integrity": "sha512-T21TMC3euAi3C0WtB6WOzNYQW4XbKTrH0Q1K7gxcNpLDqnQbMwh0SuVAossQRL/s1Ca829Zjt3zmuGQUtWAU1A==", + "version": "12.14.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-12.14.0.tgz", + "integrity": "sha512-Y5cSD+o//Ngj0VYgQraPWbpRFgD0W3+IeVLGGsPP2PCoojA40m4sjgDMZkNIxuvWuy5BUQyx/pGFASPFLpvpJw==", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", "bech32": "1.1.4", @@ -3797,6 +3797,49 @@ "@nestjs/swagger": "^7.x" } }, + "node_modules/@multiversx/sdk-nestjs-common/node_modules/@multiversx/sdk-core": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-12.6.1.tgz", + "integrity": "sha512-T21TMC3euAi3C0WtB6WOzNYQW4XbKTrH0Q1K7gxcNpLDqnQbMwh0SuVAossQRL/s1Ca829Zjt3zmuGQUtWAU1A==", + "dependencies": { + "@multiversx/sdk-transaction-decoder": "1.0.2", + "bech32": "1.1.4", + "bignumber.js": "9.0.1", + "blake2b": "2.1.3", + "buffer": "6.0.3", + "json-duplicate-key-handle": "1.0.0", + "keccak": "3.0.2", + "protobufjs": "7.2.4" + } + }, + "node_modules/@multiversx/sdk-nestjs-common/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/@multiversx/sdk-nestjs-common/node_modules/protobufjs": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@multiversx/sdk-nestjs-elastic": { "version": "2.0.0-beta.2", "resolved": "https://registry.npmjs.org/@multiversx/sdk-nestjs-elastic/-/sdk-nestjs-elastic-2.0.0-beta.2.tgz", diff --git a/package.json b/package.json index 7982b2992..67f240eba 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@aws-sdk/lib-storage": "^3.438.0", "@elastic/elasticsearch": "7.12.0", "@golevelup/nestjs-rabbitmq": "4.0.0", - "@multiversx/sdk-core": "12.6.1", + "@multiversx/sdk-core": "12.14.0", "@multiversx/sdk-native-auth-server": "1.0.7", "@multiversx/sdk-nestjs-cache": "2.0.0-beta.2", "@multiversx/sdk-nestjs-common": "2.0.0-beta.2", diff --git a/schema.gql b/schema.gql index 6d83c12a2..1c26ab5db 100644 --- a/schema.gql +++ b/schema.gql @@ -697,6 +697,13 @@ input DeployMinterArgs { royaltiesClaimAddress: String! } +input DeployMinterArgsP { + maxNftsPerTransaction: Int! + mintClaimAddress: String! + ownerAddress: String! + royaltiesClaimAddress: String! +} + type ExploreCollectionsStats { activeLast30DaysCount: Int! allCollectionsCount: Int! @@ -977,6 +984,7 @@ type Mutation { deployMinter(input: DeployMinterArgs!): TransactionNode! disableMarketplace(input: UpdateMarketplaceStateArgs!): Boolean! enableMarketplace(input: UpdateMarketplaceStateArgs!): Boolean! + deployMinterP(input: DeployMinterArgsP!): TransactionNode! endAuction(auctionId: Int!): TransactionNode! flagCollection(input: FlagCollectionInput!): Boolean! flagNft(input: FlagNftInput!): Boolean! @@ -1553,4 +1561,4 @@ input WhitelistMarketplaceArgs { input WhitelistMinterArgs { address: String! adminAddress: String! -} \ No newline at end of file +} diff --git a/src/abis/proxy-deployer.abi.json b/src/abis/proxy-deployer.abi.json new file mode 100644 index 000000000..05d7eba08 --- /dev/null +++ b/src/abis/proxy-deployer.abi.json @@ -0,0 +1,150 @@ +{ + "buildInfo": { + "rustc": { + "version": "1.71.0-nightly", + "commitHash": "7f94b314cead7059a71a265a8b64905ef2511796", + "commitDate": "2023-04-23", + "channel": "Nightly", + "short": "rustc 1.71.0-nightly (7f94b314c 2023-04-23)" + }, + "contractCrate": { + "name": "proxy-deployer", + "version": "0.0.0" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.43.4" + } + }, + "name": "ProxyDeployer", + "constructor": { + "inputs": [], + "outputs": [] + }, + "endpoints": [ + { + "name": "upgrade", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "contractDeploy", + "mutability": "mutable", + "inputs": [ + { + "name": "template_address_id", + "type": "u64" + }, + { + "name": "args", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "Address" + } + ] + }, + { + "name": "contractUpgrade", + "mutability": "mutable", + "inputs": [ + { + "name": "contract_address", + "type": "Address" + }, + { + "name": "template_address_id", + "type": "u64" + }, + { + "name": "args", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "callContractEndpoint", + "mutability": "mutable", + "inputs": [ + { + "name": "contract_address", + "type": "Address" + }, + { + "name": "function_name", + "type": "bytes" + }, + { + "name": "args", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "addContractTemplate", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "template_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "removeContractTemplate", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "address_id", + "type": "u64" + } + ], + "outputs": [] + }, + { + "name": "getAllDeployers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "getDeployerContractAddresses", + "mutability": "readonly", + "inputs": [ + { + "name": "deployer_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + } + ], + "events": [], + "hasCallback": false, + "types": {} +} diff --git a/src/app.module.ts b/src/app.module.ts index 9a3f2a66a..4cbb5743f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -40,6 +40,7 @@ import '@multiversx/sdk-nestjs-common/lib/utils/extensions/number.extensions'; import { TimescaleDbModule } from './common/persistence/timescaledb/timescaledb.module'; import { MintersModuleGraph } from './modules/minters/minters.module'; import { PersistenceModule } from './common/persistence/persistence.module'; +import { ProxyDeployerModuleGraph } from './modules/proxy-deployer/proxy-deployer.module'; @Module({ imports: [ @@ -99,6 +100,7 @@ import { PersistenceModule } from './common/persistence/persistence.module'; ExploreStatsModuleGraph, TimescaleDbModule, MintersModuleGraph, + ProxyDeployerModuleGraph, ], }) export class AppModule {} diff --git a/src/modules/auctions/contractLoader.ts b/src/modules/auctions/contractLoader.ts index daf5f42f3..ad883ac41 100644 --- a/src/modules/auctions/contractLoader.ts +++ b/src/modules/auctions/contractLoader.ts @@ -27,8 +27,10 @@ export class ContractLoader { try { const jsonContent: string = await fs.promises.readFile(this.abiPath, { encoding: 'utf8' }); const json = JSON.parse(jsonContent); + console.log({ json: JSON.stringify(json) }); const abiRegistry = AbiRegistry.create(json); + console.log(abiRegistry.getEndpoint('contractDeploy')); return abiRegistry; } catch (error) { diff --git a/src/modules/auctions/marketplaceUtils.ts b/src/modules/auctions/marketplaceUtils.ts index e1fbf9dc3..66956146a 100644 --- a/src/modules/auctions/marketplaceUtils.ts +++ b/src/modules/auctions/marketplaceUtils.ts @@ -6,6 +6,7 @@ export class MarketplaceUtils { public static readonly xoxnoMarketplaceAbiPath: string = './src/abis/xoxno-nft-marketplace.abi.json'; public static readonly deployerMintersAbiPath: string = './src/abis/nft-minter-deployer.abi.json'; + public static readonly proxyDeployerMintersAbiPath: string = './src/abis/proxy-deployer.abi.json'; static isExternalMarketplace(type: MarketplaceTypeEnum) { return type === MarketplaceTypeEnum.External; } diff --git a/src/modules/proxy-deployer/models/AddMinterArgs.ts b/src/modules/proxy-deployer/models/AddMinterArgs.ts new file mode 100644 index 000000000..a060dd3bc --- /dev/null +++ b/src/modules/proxy-deployer/models/AddMinterArgs.ts @@ -0,0 +1,14 @@ +import { Field, InputType, Int } from '@nestjs/graphql'; +import { Matches } from 'class-validator'; +import { ADDRESS_ERROR, ADDRESS_RGX } from 'src/utils/constants'; + +@InputType() +export class WhitelistMinterArgs { + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + address: string; + + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + adminAddress: string; +} diff --git a/src/modules/proxy-deployer/models/DeployMinterArgs.ts b/src/modules/proxy-deployer/models/DeployMinterArgs.ts new file mode 100644 index 000000000..f223d4771 --- /dev/null +++ b/src/modules/proxy-deployer/models/DeployMinterArgs.ts @@ -0,0 +1,28 @@ +import { Field, InputType, Int } from '@nestjs/graphql'; +import { Matches } from 'class-validator'; +import { ADDRESS_ERROR, ADDRESS_RGX } from 'src/utils/constants'; + +@InputType() +export class DeployMinterArgsP { + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + royaltiesClaimAddress: string; + + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + ownerAddress: string; + + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + mintClaimAddress: string; + + @Field(() => Int) + maxNftsPerTransaction: number; +} + +@InputType() +export class UpgradeMinterArgs { + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + minterAddress: string; +} diff --git a/src/modules/proxy-deployer/models/Minter.dto.ts b/src/modules/proxy-deployer/models/Minter.dto.ts new file mode 100644 index 000000000..7f15176ab --- /dev/null +++ b/src/modules/proxy-deployer/models/Minter.dto.ts @@ -0,0 +1,24 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; +import { MinterEntity } from 'src/db/minters'; + +@ObjectType() +export class Minter { + @Field(() => ID) + address!: string; + + @Field(() => ID) + adminAddress!: string; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + static fromEntity(minter: MinterEntity) { + return minter + ? new Minter({ + address: minter.address, + adminAddress: minter.adminAddress, + }) + : null; + } +} diff --git a/src/modules/proxy-deployer/models/MinterFilters.ts b/src/modules/proxy-deployer/models/MinterFilters.ts new file mode 100644 index 000000000..d5e7bc000 --- /dev/null +++ b/src/modules/proxy-deployer/models/MinterFilters.ts @@ -0,0 +1,18 @@ +import { Field, InputType, Int } from '@nestjs/graphql'; +import { Matches } from 'class-validator'; +import { ADDRESS_ERROR, ADDRESS_RGX } from 'src/utils/constants'; + +@InputType() +export class MinterFilters { + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + minterAddress: string; + + @Matches(RegExp(ADDRESS_RGX), { message: ADDRESS_ERROR }) + @Field() + minterAdminAddress: string; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/src/modules/proxy-deployer/models/MintersResponse.ts b/src/modules/proxy-deployer/models/MintersResponse.ts new file mode 100644 index 000000000..fee79fa0d --- /dev/null +++ b/src/modules/proxy-deployer/models/MintersResponse.ts @@ -0,0 +1,6 @@ +import { ObjectType } from '@nestjs/graphql'; +import relayTypes from 'src/modules/common/Relay.types'; +import { Minter } from './Minter.dto'; + +@ObjectType() +export class MintersResponse extends relayTypes(Minter) {} diff --git a/src/modules/proxy-deployer/models/index.ts b/src/modules/proxy-deployer/models/index.ts new file mode 100644 index 000000000..283d1f663 --- /dev/null +++ b/src/modules/proxy-deployer/models/index.ts @@ -0,0 +1,2 @@ +export * from './Minter.dto'; +export * from './AddMinterArgs'; diff --git a/src/modules/proxy-deployer/models/requests/DeployMinterRequest.ts b/src/modules/proxy-deployer/models/requests/DeployMinterRequest.ts new file mode 100644 index 000000000..8865dd921 --- /dev/null +++ b/src/modules/proxy-deployer/models/requests/DeployMinterRequest.ts @@ -0,0 +1,34 @@ +import { DeployMinterArgsP, UpgradeMinterArgs } from '../DeployMinterArgs'; + +export class DeployMinterRequest { + royaltiesClaimAddress: string; + mintClaimAddress: string; + ownerAddress: string; + maxNftsPerTransaction: number; + constructor(init?: Partial) { + Object.assign(this, init); + } + + static fromArgs(args: DeployMinterArgsP) { + return new DeployMinterRequest({ + royaltiesClaimAddress: args.royaltiesClaimAddress, + mintClaimAddress: args.mintClaimAddress, + maxNftsPerTransaction: args.maxNftsPerTransaction, + ownerAddress: args.ownerAddress, + }); + } +} + +export class UpgradeMinterRequest { + minterAddress: string; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + static fromArgs(args: UpgradeMinterArgs) { + return new UpgradeMinterRequest({ + minterAddress: args.minterAddress, + }); + } +} diff --git a/src/modules/proxy-deployer/models/requests/whitelistMinterRequest.ts b/src/modules/proxy-deployer/models/requests/whitelistMinterRequest.ts new file mode 100644 index 000000000..1cfc76bd0 --- /dev/null +++ b/src/modules/proxy-deployer/models/requests/whitelistMinterRequest.ts @@ -0,0 +1,16 @@ +import { WhitelistMinterArgs } from '../AddMinterArgs'; + +export class WhitelistMinterRequest { + address: string; + adminAddress: string; + constructor(init?: Partial) { + Object.assign(this, init); + } + + static fromArgs(args: WhitelistMinterArgs) { + return new WhitelistMinterRequest({ + address: args.address, + adminAddress: args.adminAddress, + }); + } +} diff --git a/src/modules/proxy-deployer/proxy-deployer-caching.service.ts b/src/modules/proxy-deployer/proxy-deployer-caching.service.ts new file mode 100644 index 000000000..f1f7b4cd0 --- /dev/null +++ b/src/modules/proxy-deployer/proxy-deployer-caching.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; +import '../../utils/extensions'; +import { RedisCacheService } from '@multiversx/sdk-nestjs-cache'; +import { CacheInfo } from 'src/common/services/caching/entities/cache.info'; +import { MinterEntity } from 'src/db/minters'; + +@Injectable() +export class MintersCachingService { + constructor(private redisCacheService: RedisCacheService) {} + + public async getMinters(getMinters: () => any): Promise { + return await this.redisCacheService.getOrSet(CacheInfo.Minters.key, () => getMinters(), CacheInfo.Minters.ttl); + } + + public async invalidateMinters() { + await this.redisCacheService.delete(CacheInfo.Minters.key); + } +} diff --git a/src/modules/proxy-deployer/proxy-deployer-mutations.resolver.ts b/src/modules/proxy-deployer/proxy-deployer-mutations.resolver.ts new file mode 100644 index 000000000..ea7bfd885 --- /dev/null +++ b/src/modules/proxy-deployer/proxy-deployer-mutations.resolver.ts @@ -0,0 +1,37 @@ +import { Resolver, Args, Mutation } from '@nestjs/graphql'; +import { BaseResolver } from '../common/base.resolver'; +import { UseGuards } from '@nestjs/common'; +import { JwtOrNativeAuthGuard } from '../auth/jwt.or.native.auth-guard'; +import { GqlAdminAuthGuard } from '../auth/gql-admin.auth-guard'; +import { DeployMinterArgsP, UpgradeMinterArgs } from './models/DeployMinterArgs'; +import { DeployMinterRequest, UpgradeMinterRequest } from './models/requests/DeployMinterRequest'; +import { ProxyDeployerAbiService } from './proxy-deployer.abi.service'; +import { TransactionNode } from '../common/transaction'; +import { AuthUser } from '../auth/authUser'; +import { UserAuthResult } from '../auth/userAuthResult'; +import { MintersResponse } from './models/MintersResponse'; + +@Resolver(() => MintersResponse) +export class ProxyDeployerMutationsResolver extends BaseResolver(MintersResponse) { + constructor(private minterDeployerService: ProxyDeployerAbiService) { + super(); + } + + @Mutation(() => TransactionNode) + // @UseGuards(JwtOrNativeAuthGuard, GqlAdminAuthGuard) + async deployMinterP(@Args('input') input: DeployMinterArgsP): Promise { + return await this.minterDeployerService.deployMinter(DeployMinterRequest.fromArgs(input)); + } + + // @Mutation(() => TransactionNode) + // @UseGuards(JwtOrNativeAuthGuard, GqlAdminAuthGuard) + // async pauseMinter(@Args('input') input: UpgradeMinterArgs, @AuthUser() user: UserAuthResult): Promise { + // return await this.minterDeployerService.pauseNftMinter(user.address, UpgradeMinterRequest.fromArgs(input)); + // } + + // @Mutation(() => TransactionNode) + // @UseGuards(JwtOrNativeAuthGuard, GqlAdminAuthGuard) + // async resumeMinter(@Args('input') input: UpgradeMinterArgs, @AuthUser() user: UserAuthResult): Promise { + // return await this.minterDeployerService.resumeNftMinter(user.address, UpgradeMinterRequest.fromArgs(input)); + // } +} diff --git a/src/modules/proxy-deployer/proxy-deployer.abi.service.ts b/src/modules/proxy-deployer/proxy-deployer.abi.service.ts new file mode 100644 index 000000000..5dc429f50 --- /dev/null +++ b/src/modules/proxy-deployer/proxy-deployer.abi.service.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@nestjs/common'; +import { + Address, + AddressValue, + BytesValue, + Interaction, + ResultsParser, + U32Value, + VariadicType, + VariadicValue, +} from '@multiversx/sdk-core/out'; +import { MarketplaceUtils } from '../auctions/marketplaceUtils'; +import { TransactionNode } from '../common/transaction'; +import { DeployMinterRequest, UpgradeMinterRequest } from './models/requests/DeployMinterRequest'; +import { mxConfig, gas } from 'src/config'; +import { MxProxyService } from 'src/common'; +import { ContractLoader } from '../auctions/contractLoader'; + +@Injectable() +export class ProxyDeployerAbiService { + private readonly parser: ResultsParser; + private contract = new ContractLoader(MarketplaceUtils.proxyDeployerMintersAbiPath); + + constructor(private mxProxyService: MxProxyService) { + this.parser = new ResultsParser(); + } + + async deployMinter(request: DeployMinterRequest): Promise { + const contract = await this.contract.getContract(process.env.DEPLOYER_ADDRESS); + + return contract.methods + .contractDeploy([1, request.royaltiesClaimAddress, request.mintClaimAddress, request.maxNftsPerTransaction]) + .check() + .withChainID(mxConfig.chainID) + .withGasLimit(gas.deployMinter) + .withSender(Address.fromString(request.ownerAddress)) + .buildTransaction() + .toPlainObject(); + } + + async pauseNftMinter(ownerAddress: string, request: UpgradeMinterRequest): Promise { + const contract = await this.contract.getContract(process.env.DEPLOYER_ADDRESS); + + return contract.methodsExplicit + .pauseNftMinter([new AddressValue(new Address(request.minterAddress))]) + .withChainID(mxConfig.chainID) + .withGasLimit(gas.deployMinter) + .withSender(Address.fromString(ownerAddress)) + .buildTransaction() + .toPlainObject(); + } + + async resumeNftMinter(ownerAddress: string, request: UpgradeMinterRequest): Promise { + const contract = await this.contract.getContract(process.env.DEPLOYER_ADDRESS); + + return contract.methodsExplicit + .resumeNftMinter([new AddressValue(new Address(request.minterAddress))]) + .withChainID(mxConfig.chainID) + .withSender(Address.fromString(ownerAddress)) + .withGasLimit(gas.deployMinter) + .buildTransaction() + .toPlainObject(); + } + + public async getMintersForAddress(address: string): Promise { + const contract = await this.contract.getContract(process.env.DEPLOYER_ADDRESS); + let getDataQuery = contract.methodsExplicit.getUserNftMinterContracts([new AddressValue(new Address(address))]); + + const response = await this.getFirstQueryResult(getDataQuery); + const contractAddresses: AddressValue[] = response?.firstValue?.valueOf(); + return contractAddresses.map((x) => x.valueOf().bech32()); + } + + private async getFirstQueryResult(interaction: Interaction) { + let queryResponse = await this.mxProxyService.getService().queryContract(interaction.buildQuery()); + let result = this.parser.parseQueryResponse(queryResponse, interaction.getEndpoint()); + return result; + } +} diff --git a/src/modules/proxy-deployer/proxy-deployer.module.ts b/src/modules/proxy-deployer/proxy-deployer.module.ts new file mode 100644 index 000000000..8df09d89f --- /dev/null +++ b/src/modules/proxy-deployer/proxy-deployer.module.ts @@ -0,0 +1,17 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { MxCommunicationModule } from 'src/common'; +import { ProxyDeployerMutationsResolver } from './proxy-deployer-mutations.resolver'; +import { MintersService } from './proxy-deployer.service'; +import { PubSubListenerModule } from 'src/pubsub/pub.sub.listener.module'; +import { CommonModule } from 'src/common.module'; +import { AuthModule } from '../auth/auth.module'; +import { MintersCachingService } from './proxy-deployer-caching.service'; +import { ProxyDeployerAbiService } from './proxy-deployer.abi.service'; +import { CacheEventsPublisherModule } from '../rabbitmq/cache-invalidation/cache-invalidation-publisher/change-events-publisher.module'; + +@Module({ + providers: [ProxyDeployerMutationsResolver, MintersService, MintersCachingService, ProxyDeployerAbiService], + imports: [PubSubListenerModule, MxCommunicationModule, CommonModule, forwardRef(() => AuthModule), CacheEventsPublisherModule], + exports: [MintersService, MintersCachingService], +}) +export class ProxyDeployerModuleGraph {} diff --git a/src/modules/proxy-deployer/proxy-deployer.service.ts b/src/modules/proxy-deployer/proxy-deployer.service.ts new file mode 100644 index 000000000..a84a23d3c --- /dev/null +++ b/src/modules/proxy-deployer/proxy-deployer.service.ts @@ -0,0 +1,81 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Minter } from './models'; +import { PersistenceService } from 'src/common/persistence/persistence.service'; +import { MinterEntity } from 'src/db/minters'; +import { UnableToLoadError } from 'src/common/models/errors/unable-to-load-error'; +import { MintersCachingService } from './proxy-deployer-caching.service'; +import { WhitelistMinterRequest } from './models/requests/whitelistMinterRequest'; +import { MinterFilters } from './models/MinterFilters'; +import { ProxyDeployerAbiService } from './proxy-deployer.abi.service'; +import { CacheEventsPublisherService } from '../rabbitmq/cache-invalidation/cache-invalidation-publisher/change-events-publisher.service'; +import { ChangedEvent, CacheEventTypeEnum } from '../rabbitmq/cache-invalidation/events/changed.event'; + +@Injectable() +export class MintersService { + constructor( + private persistenceService: PersistenceService, + private cacheService: MintersCachingService, + private minterDeployerService: ProxyDeployerAbiService, + private readonly cacheEventsPublisher: CacheEventsPublisherService, + private readonly logger: Logger, + ) {} + + async whitelistMinter(request: WhitelistMinterRequest): Promise { + try { + const contractAddresses = await this.minterDeployerService.getMintersForAddress(request.adminAddress); + if (contractAddresses.includes(request.address)) { + const savedMinter = await this.persistenceService.saveMinter(MinterEntity.fromRequest(request)); + await this.triggerCacheInvalidation(); + return savedMinter ? true : false; + } + return false; + } catch (error) { + this.logger.error('An error has occured while saving the minter ', { + path: this.whitelistMinter.name, + minterAddress: request?.address, + exception: error, + }); + throw new UnableToLoadError(`An error has ocurred while saving the minter: ${request?.address}`); + } + } + + async getMinters(filters?: MinterFilters): Promise { + try { + let minters = await this.cacheService.getMinters(() => this.persistenceService.getMinters()); + if (filters) { + if (filters.minterAddress) minters = minters.filter((m) => m.address === filters.minterAddress); + if (filters.minterAdminAddress) minters = minters.filter((m) => m.adminAddress === filters.minterAdminAddress); + } + + return minters?.map((minter) => Minter.fromEntity(minter)); + } catch (error) { + this.logger.error('An error has occured while getting minters', { + path: this.getMinters.name, + exception: error, + filters, + }); + return []; + } + } + + async getMintersAddresses(): Promise { + try { + const minters = await this.getMinters(); + return minters?.map((x) => x.address); + } catch (error) { + this.logger.error('An error has occured while getting minters addresses', { + path: this.getMintersAddresses.name, + exception: error, + }); + return []; + } + } + + private async triggerCacheInvalidation(): Promise { + await this.cacheEventsPublisher.publish( + new ChangedEvent({ + type: CacheEventTypeEnum.Minters, + }), + ); + } +}