diff --git a/apps/api/src/app/controllers/account/get.controller.ts b/apps/api/src/app/controllers/account/get.controller.ts index d8c5d18dc..d53d7167f 100644 --- a/apps/api/src/app/controllers/account/get.controller.ts +++ b/apps/api/src/app/controllers/account/get.controller.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { WalletVariant, AccountVariant } from '@thxnetwork/common/enums'; +import { WalletVariant, AccountVariant, ChainId } from '@thxnetwork/common/enums'; import AccountProxy from '@thxnetwork/api/proxies/AccountProxy'; import WalletService from '@thxnetwork/api/services/WalletService'; import THXService from '@thxnetwork/api/services/THXService'; @@ -20,13 +20,16 @@ const controller = async (req: Request, res: Response) => { metadata, })) as TToken[]; - // If account variant is metamask and no wallet is found then create it + // If account variant is metamask and no wallet is found then create it for all networks if (account.variant === AccountVariant.Metamask) { const wallet = await WalletService.findOne({ sub: req.auth.sub, variant: WalletVariant.WalletConnect }); if (!wallet) { - await WalletService.createWalletConnect({ - sub: req.auth.sub, - address: account.address, + Object.keys(ChainId).map(async (chainId) => { + await WalletService.createWalletConnect({ + chainId, + sub: req.auth.sub, + address: account.address, + }); }); } } diff --git a/apps/api/src/app/controllers/erc721/transfer/erc721-transfer.test.ts b/apps/api/src/app/controllers/erc721/transfer/erc721-transfer.test.ts index 1da874a24..fb0b9c3ec 100644 --- a/apps/api/src/app/controllers/erc721/transfer/erc721-transfer.test.ts +++ b/apps/api/src/app/controllers/erc721/transfer/erc721-transfer.test.ts @@ -47,7 +47,7 @@ describe('ERC721 Transfer', () => { it('Deploy Campaign Safe', async () => { const { web3 } = NetworkService.getProvider(chainId); pool = await PoolService.deploy(sub, 'My Reward Campaign'); - const safe = await SafeService.create({ sub, safeVersion, poolId: String(pool._id) }); + const safe = await SafeService.create({ sub, safeVersion, chainId: ChainId.Hardhat, poolId: String(pool._id) }); // Wait for safe address to return code await poll( @@ -96,7 +96,7 @@ describe('ERC721 Transfer', () => { it('Add ERC721 minter', async () => { pool = await Pool.findById(pool._id); - const safe = await SafeService.findOneByPool(pool); + const safe = await SafeService.findOneByPool(pool, ChainId.Hardhat); erc721 = await ERC721.findById(erc721._id); await ERC721Service.addMinter(erc721, safe.address); @@ -122,7 +122,7 @@ describe('ERC721 Transfer', () => { description: metadataDescription, externalUrl: metadataExternalUrl, }); - const safe = await SafeService.findOneByPool(pool); + const safe = await SafeService.findOneByPool(pool, ChainId.Hardhat); // Wait for safe address to return code const { web3 } = NetworkService.getProvider(chainId); diff --git a/apps/api/src/app/controllers/pools/duplicate/post.controller.ts b/apps/api/src/app/controllers/pools/duplicate/post.controller.ts index 9325ff537..238557bc0 100644 --- a/apps/api/src/app/controllers/pools/duplicate/post.controller.ts +++ b/apps/api/src/app/controllers/pools/duplicate/post.controller.ts @@ -1,29 +1,21 @@ import { param } from 'express-validator'; import { Request, Response } from 'express'; -import { safeVersion } from '@thxnetwork/api/services/ContractService'; import { Pool } from '@thxnetwork/api/models'; import PoolService from '@thxnetwork/api/services/PoolService'; -import SafeService from '@thxnetwork/api/services/SafeService'; const validation = [param('id').isMongoId()]; const controller = async (req: Request, res: Response) => { const pool = await Pool.findById(req.params.id); - const duplicatePool = await PoolService.deploy(req.auth.sub, `${pool.settings.title} (clone)`); - // Deploy a Safe for the campaign - const safe = await SafeService.create({ sub: req.auth.sub, safeVersion, poolId: duplicatePool.id }); - // Update predicted safe address for pool - await pool.updateOne({ safeAddress: safe.address }); - // Duplicate Quests // TODO // Duplicate Rewards // TODO - res.status(201).json({ ...duplicatePool.toJSON(), safeAddress: safe.address, safe }); + res.status(201).json(duplicatePool.toJSON()); }; export { controller, validation }; diff --git a/apps/api/src/app/controllers/pools/erc1155/balance/get.controller.ts b/apps/api/src/app/controllers/pools/erc1155/balance/get.controller.ts index f8c0d37a5..38870e863 100644 --- a/apps/api/src/app/controllers/pools/erc1155/balance/get.controller.ts +++ b/apps/api/src/app/controllers/pools/erc1155/balance/get.controller.ts @@ -5,13 +5,17 @@ import { Pool } from '@thxnetwork/api/models'; import ContractService from '@thxnetwork/api/services/ContractService'; import SafeService from '@thxnetwork/api/services/SafeService'; -const validation = [query('contractAddress').isEthereumAddress(), query('tokenId').isInt()]; +const validation = [ + query('contractAddress').isEthereumAddress(), + query('tokenId').isInt(), + query('walletId').isMongoId(), +]; const controller = async (req: Request, res: Response) => { const pool = await Pool.findById(req.params.id); if (!pool) throw new NotFoundError('Pool not found'); - const safe = await SafeService.findOneByPool(pool, pool.chainId); + const safe = await SafeService.findById(req.query.walletId as string); if (!safe) throw new NotFoundError('Safe not found'); const contract = ContractService.getContract('THXERC1155', pool.chainId, req.query.contractAddress as string); diff --git a/apps/api/src/app/controllers/pools/erc20/allowance/get.controller.ts b/apps/api/src/app/controllers/pools/erc20/allowance/get.controller.ts index 3c880a287..321a7982a 100644 --- a/apps/api/src/app/controllers/pools/erc20/allowance/get.controller.ts +++ b/apps/api/src/app/controllers/pools/erc20/allowance/get.controller.ts @@ -9,6 +9,7 @@ const validation = [ param('id').isMongoId(), query('tokenAddress').isEthereumAddress(), query('spender').isEthereumAddress(), + query('walletId').isMongoId(), ]; const controller = async (req: Request, res: Response) => { @@ -16,8 +17,8 @@ const controller = async (req: Request, res: Response) => { const pool = await PoolService.getById(poolId); if (!pool) throw new NotFoundError('Pool not found'); - const safe = await SafeService.findOneByPool(pool); - if (!safe) throw new NotFoundError('Wallet not found'); + const safe = await SafeService.findById(req.query.walletId as string); + if (!safe) throw new NotFoundError('Safe not found'); const contract = ContractService.getContract( 'THXERC20_LimitedSupply', diff --git a/apps/api/src/app/controllers/pools/erc20/allowance/post.controller.ts b/apps/api/src/app/controllers/pools/erc20/allowance/post.controller.ts index 0b0184041..075f6903a 100644 --- a/apps/api/src/app/controllers/pools/erc20/allowance/post.controller.ts +++ b/apps/api/src/app/controllers/pools/erc20/allowance/post.controller.ts @@ -12,6 +12,7 @@ const validation = [ body('tokenAddress').isEthereumAddress(), body('spender').isEthereumAddress(), body('amountInWei').isString(), + body('chainId').isInt(), ]; const controller = async (req: Request, res: Response) => { @@ -19,7 +20,7 @@ const controller = async (req: Request, res: Response) => { const pool = await PoolService.getById(poolId); if (!pool) throw new NotFoundError('Pool not found'); - const safe = await SafeService.findOneByPool(pool); + const safe = await SafeService.findOneByPool(pool, req.body.chainId); if (!safe) throw new NotFoundError('Wallet not found'); const { web3 } = NetworkService.getProvider(safe.chainId); diff --git a/apps/api/src/app/controllers/pools/erc20/balance/get.controller.ts b/apps/api/src/app/controllers/pools/erc20/balance/get.controller.ts index e93e318c8..44c689294 100644 --- a/apps/api/src/app/controllers/pools/erc20/balance/get.controller.ts +++ b/apps/api/src/app/controllers/pools/erc20/balance/get.controller.ts @@ -5,13 +5,13 @@ import SafeService from '@thxnetwork/api/services/SafeService'; import { NotFoundError } from '@thxnetwork/api/util/errors'; import ContractService from '@thxnetwork/api/services/ContractService'; -const validation = [param('id').isMongoId(), query('tokenAddress').isEthereumAddress()]; +const validation = [param('id').isMongoId(), query('tokenAddress').isEthereumAddress(), query('chainId').isInt()]; const controller = async (req: Request, res: Response) => { const pool = await PoolService.getById(req.params.id); if (!pool) throw new NotFoundError('Campaign not found.'); - const safe = await SafeService.findOneByPool(pool, pool.chainId); + const safe = await SafeService.findOneByPool(pool, Number(req.query.chainId)); if (!safe) throw new NotFoundError('Campaign Safe not found.'); const contract = ContractService.getContract( diff --git a/apps/api/src/app/controllers/pools/get.controller.ts b/apps/api/src/app/controllers/pools/get.controller.ts index 7a62adfc6..73a37f1e1 100644 --- a/apps/api/src/app/controllers/pools/get.controller.ts +++ b/apps/api/src/app/controllers/pools/get.controller.ts @@ -1,29 +1,14 @@ import { Request, Response } from 'express'; import { param } from 'express-validator'; import { Participant, Widget, Wallet, Event, Identity } from '@thxnetwork/api/models'; -import { safeVersion } from '@thxnetwork/api/services/ContractService'; -import { logger } from '@thxnetwork/api/util/logger'; import { ethers } from 'ethers'; import PoolService from '@thxnetwork/api/services/PoolService'; import BrandService from '@thxnetwork/api/services/BrandService'; -import SafeService from '@thxnetwork/api/services/SafeService'; -import PaymentService from '@thxnetwork/api/services/PaymentService'; const validation = [param('id').isMongoId()]; const controller = async (req: Request, res: Response) => { const pool = await PoolService.getById(req.params.id); - let safe = await SafeService.findOneByPool(pool); - - // Deploy a Safe if none is found - if (!safe) { - safe = await SafeService.create({ - sub: pool.sub, - safeVersion, - poolId: req.params.id, - }); - logger.info(`[${req.params.id}] Deployed Campaign Safe ${safe.address}`); - } // Create a galachain private key if none exists if (!pool.settings.galachainPrivateKey) { @@ -32,24 +17,20 @@ const controller = async (req: Request, res: Response) => { } // Fetch all other campaign entities - const [widget, brand, wallets, collaborators, owner, events, identities, subscriberCount, balance] = - await Promise.all([ - Widget.findOne({ poolId: req.params.id }), - BrandService.get(req.params.id), - Wallet.find({ poolId: req.params.id }), - PoolService.findCollaborators(pool), - PoolService.findOwner(pool), - Event.find({ poolId: pool._id }).distinct('name'), // Seperate list (many) - Identity.find({ poolId: pool._id }), // Seperate list (many) - Participant.countDocuments({ poolId: req.params.id, isSubscribed: true }), - PaymentService.balanceOf(safe), - ]); + const [widget, brand, wallets, collaborators, owner, events, identities, subscriberCount] = await Promise.all([ + Widget.findOne({ poolId: req.params.id }), + BrandService.get(req.params.id), + Wallet.find({ poolId: req.params.id }), + PoolService.findCollaborators(pool), + PoolService.findOwner(pool), + Event.find({ poolId: pool._id }).distinct('name'), // Seperate list (many) + Identity.find({ poolId: pool._id }), // Seperate list (many) + Participant.countDocuments({ poolId: req.params.id, isSubscribed: true }), + ]); res.json({ ...pool.toJSON(), - balance, address: pool.safeAddress, - safe, identities, events, wallets, diff --git a/apps/api/src/app/controllers/pools/payments/post.controller.ts b/apps/api/src/app/controllers/pools/payments/post.controller.ts index 9e94fb36d..50119df3f 100644 --- a/apps/api/src/app/controllers/pools/payments/post.controller.ts +++ b/apps/api/src/app/controllers/pools/payments/post.controller.ts @@ -8,7 +8,12 @@ import PoolService from '@thxnetwork/api/services/PoolService'; import SafeService from '@thxnetwork/api/services/SafeService'; import PaymentService from '@thxnetwork/api/services/PaymentService'; -const validation = [param('id').isMongoId(), body('amountInWei').exists(), body('planType').isInt()]; +const validation = [ + param('id').isMongoId(), + body('chainId').isInt(), + body('amountInWei').exists(), + body('planType').isInt(), +]; // TODO // 1. Customer approves USDC for Campaign Safe for x allowance @@ -23,7 +28,7 @@ const controller = async (req: Request, res: Response) => { const pool = await PoolService.getById(req.params.id); if (!pool) throw new NotFoundError('Could not find campaign'); - const safe = await SafeService.findOneByPool(pool, pool.chainId); + const safe = await SafeService.findOneByPool(pool, req.body.chainId); if (!safe) throw new NotFoundError('Could not find campaign Safe'); const amountInWei = BigNumber.from(req.body.amountInWei); diff --git a/apps/api/src/app/controllers/pools/post.controller.ts b/apps/api/src/app/controllers/pools/post.controller.ts index a98e80265..6e83a8b74 100644 --- a/apps/api/src/app/controllers/pools/post.controller.ts +++ b/apps/api/src/app/controllers/pools/post.controller.ts @@ -1,8 +1,6 @@ import { Request, Response } from 'express'; import { body } from 'express-validator'; -import { safeVersion } from '@thxnetwork/api/services/ContractService'; import PoolService from '@thxnetwork/api/services/PoolService'; -import SafeService from '@thxnetwork/api/services/SafeService'; const validation = [body('settings.title').optional().isString().trim().escape().isLength({ max: 50 })]; @@ -10,14 +8,7 @@ const controller = async (req: Request, res: Response) => { const { title } = req.body; const pool = await PoolService.deploy(req.auth.sub, title || 'My Quest Campaign'); - // Deploy a Safe for the campaign - const poolId = String(pool._id); - const safe = await SafeService.create({ sub: req.auth.sub, safeVersion, poolId }); - - // Update predicted safe address for pool - await pool.updateOne({ safeAddress: safe.address }); - - res.status(201).json({ ...pool.toJSON(), safeAddress: safe.address, safe }); + res.status(201).json(pool.toJSON()); }; export { controller, validation }; diff --git a/apps/api/src/app/controllers/qr-codes/collect/patch.controller.ts b/apps/api/src/app/controllers/qr-codes/collect/patch.controller.ts index 47724cec9..75bce64dc 100644 --- a/apps/api/src/app/controllers/qr-codes/collect/patch.controller.ts +++ b/apps/api/src/app/controllers/qr-codes/collect/patch.controller.ts @@ -23,9 +23,6 @@ const controller = async (req: Request, res: Response) => { const pool = await PoolService.getById(reward.poolId); if (!pool) throw new BadRequestError('Campaign not found.'); - const safe = await SafeService.findOneByPool(pool); - if (!safe) throw new BadRequestError('Safe not found.'); - // Find wallet for the authenticated user const wallet = await WalletService.findById(req.query.walletId as string); if (!wallet) throw new NotFoundError('Wallet not found'); @@ -37,6 +34,10 @@ const controller = async (req: Request, res: Response) => { const erc721 = await ERC721Service.findById(metadata.erc721Id); if (!erc721) throw new NotFoundError('ERC721 not found'); + // Get the pool Safe for the token network + const safe = await SafeService.findOneByPool(pool, erc721.chainId); + if (!safe) throw new BadRequestError('Safe not found.'); + // Mint the NFT const token = await ERC721Service.mint(safe, erc721, wallet, metadata); diff --git a/apps/api/src/app/controllers/rewards/payments/post.controller.ts b/apps/api/src/app/controllers/rewards/payments/post.controller.ts index 13957387b..7ddffb3ec 100644 --- a/apps/api/src/app/controllers/rewards/payments/post.controller.ts +++ b/apps/api/src/app/controllers/rewards/payments/post.controller.ts @@ -2,12 +2,11 @@ import { Request, Response } from 'express'; import { body, param } from 'express-validator'; import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors'; import { JobType, RewardVariant } from '@thxnetwork/common/enums'; +import { agenda } from '@thxnetwork/api/util/agenda'; +import { Wallet } from '@thxnetwork/api/models'; import PoolService from '@thxnetwork/api/services/PoolService'; import AccountProxy from '@thxnetwork/api/proxies/AccountProxy'; import RewardService from '@thxnetwork/api/services/RewardService'; -import SafeService from '@thxnetwork/api/services/SafeService'; -import { agenda } from '@thxnetwork/api/util/agenda'; -import { Wallet } from '@thxnetwork/api/models'; const validation = [param('variant').isInt(), param('rewardId').isMongoId(), body('walletId').optional().isMongoId()]; @@ -21,14 +20,11 @@ const controller = async (req: Request, res: Response) => { const pool = await PoolService.getById(reward.poolId); if (!pool) throw new NotFoundError('Campaign not found'); - const safe = await SafeService.findOneByPool(pool); - if (!safe) throw new NotFoundError('Campaign Safe not found'); - const account = await AccountProxy.findById(req.auth.sub); if (!account) throw new NotFoundError('Account not found'); const wallet = req.body.walletId ? await Wallet.findById(req.body.walletId) : null; - const validationResult = await RewardService.getValidationResult({ reward, account, safe, wallet }); + const validationResult = await RewardService.getValidationResult({ reward, account, wallet }); if (!validationResult.result) { throw new ForbiddenError(validationResult.reason); } diff --git a/apps/api/src/app/middlewares/assertPayment.ts b/apps/api/src/app/middlewares/assertPayment.ts index 27aca009d..1dfe53f97 100644 --- a/apps/api/src/app/middlewares/assertPayment.ts +++ b/apps/api/src/app/middlewares/assertPayment.ts @@ -4,6 +4,7 @@ import { logger } from '../util/logger'; import SafeService from '../services/SafeService'; import PoolService from '../services/PoolService'; import PaymentService from '../services/PaymentService'; +import { ChainId } from '@thxnetwork/common/enums'; /* * This middleware function is used to assert payments of the pool owner. @@ -11,7 +12,7 @@ import PaymentService from '../services/PaymentService'; */ export async function assertPayment(req: Request, res: Response, next: NextFunction) { const pool = await PoolService.getById(req.params.id); - const safe = await SafeService.findOneByPool(pool); + const safe = await SafeService.findOneByPool(pool, ChainId.Polygon); const balanceInWei = await PaymentService.balanceOf(safe); // If pool.createdAt + 2 weeks is larger than now there should be a payment diff --git a/apps/api/src/app/services/PaymentService.ts b/apps/api/src/app/services/PaymentService.ts index 3797a86bb..0e6d540ab 100644 --- a/apps/api/src/app/services/PaymentService.ts +++ b/apps/api/src/app/services/PaymentService.ts @@ -88,7 +88,7 @@ export default class PaymentService { const pools = await Pool.find({ trialEndsAt: { $exists: true, $lt: subWeeks(new Date(), 1) } }); for (const pool of pools) { // Get campaing safe - const safe = await SafeService.findOneByPool(pool); + const safe = await SafeService.findOneByPool(pool, ChainId.Polygon); const timeLeftInSeconds = await this.getTimeLeftInSeconds(safe, pool); // Insufficient payments diff --git a/apps/api/src/app/services/PoolService.ts b/apps/api/src/app/services/PoolService.ts index 19ab6d382..3a9124e23 100644 --- a/apps/api/src/app/services/PoolService.ts +++ b/apps/api/src/app/services/PoolService.ts @@ -53,11 +53,7 @@ async function isSubjectAllowed(sub: string, poolId: string) { } async function getById(id: string) { - const pool = await Pool.findById(id); - const chainId = ContractService.getChainId(); - const safe = await SafeService.findOneByPool(pool, chainId); - pool.safe = safe; - return pool; + return await Pool.findById(id); } function getByAddress(address: string) { @@ -145,14 +141,13 @@ async function getAllBySub(sub: string): Promise { return await Promise.all( pools.map(async (pool) => { const brand = await Brand.findOne({ poolId: pool.id }); - const safe = await SafeService.findOneByPool(pool); const participantCount = await Participant.countDocuments({ poolId: pool.id }); const account = accounts.find((a) => a.sub === pool.sub); const author = account && { username: account.username, }; - return { ...pool.toJSON(), participantCount, author, brand, safe }; + return { ...pool.toJSON(), participantCount, author, brand }; }), ); } diff --git a/apps/api/src/app/services/RewardCoinService.ts b/apps/api/src/app/services/RewardCoinService.ts index 7ca2bee1b..dad0daff3 100644 --- a/apps/api/src/app/services/RewardCoinService.ts +++ b/apps/api/src/app/services/RewardCoinService.ts @@ -16,6 +16,7 @@ import ERC20Service from './ERC20Service'; import MailService from './MailService'; import PoolService from './PoolService'; import { toWei } from 'web3-utils'; +import SafeService from './SafeService'; export default class RewardCoinService implements IRewardService { models = { @@ -54,20 +55,16 @@ export default class RewardCoinService implements IRewardService { await this.models.reward.findOneAndDelete(reward._id); } - async createPayment({ - reward, - safe, - wallet, - }: { - reward: TRewardCoin; - safe: WalletDocument; - wallet?: WalletDocument; - }) { + async createPayment({ reward, wallet }: { reward: TRewardCoin; wallet?: WalletDocument }) { if (!wallet) return { result: false, reason: 'Wallet not found' }; const erc20 = await ERC20.findById(reward.erc20Id); if (!erc20) return { result: false, reason: 'ERC20 not found' }; + const pool = await PoolService.getById(reward.poolId); + const safe = await SafeService.findOneByPool(pool, erc20.chainId); + if (!safe) return { result: false, reason: 'Safe not found' }; + // TODO Wei should be determined in the FE const amount = toWei(reward.amount as string); @@ -84,15 +81,7 @@ export default class RewardCoinService implements IRewardService { }); } - async getValidationResult({ - reward, - safe, - wallet, - }: { - reward: RewardCoinDocument; - safe: WalletDocument; - wallet: WalletDocument; - }) { + async getValidationResult({ reward, wallet }: { reward: RewardCoinDocument; wallet: WalletDocument }) { if (!wallet) return { result: false, reason: `No wallet provided for this reward transfer.` }; // Check if wallet exists @@ -103,6 +92,10 @@ export default class RewardCoinService implements IRewardService { const erc20 = await ERC20.findById(reward.erc20Id); if (!erc20) return { result: false, reason: `ERC20 not found.` }; + const pool = await PoolService.getById(reward.poolId); + const safe = await SafeService.findOneByPool(pool, erc20.chainId); + if (!safe) return { result: false, reason: 'Campaign Safe is no longer available for this network' }; + // Check if there are pending transactions that are not mined or failed. const txs = await Transaction.find({ walletId: safe.id, diff --git a/apps/api/src/app/services/RewardGalachainService.ts b/apps/api/src/app/services/RewardGalachainService.ts index 11df2f49e..d6042ebbc 100644 --- a/apps/api/src/app/services/RewardGalachainService.ts +++ b/apps/api/src/app/services/RewardGalachainService.ts @@ -97,8 +97,6 @@ export default class RewardGalachainService implements IRewardService { wallet, }: { reward: TRewardGalachain; - account: TAccount; - safe: WalletDocument; wallet?: WalletDocument; }): Promise { const token = this.getToken(reward); diff --git a/apps/api/src/app/services/RewardNFTService.ts b/apps/api/src/app/services/RewardNFTService.ts index d5ab830ab..a3df54397 100644 --- a/apps/api/src/app/services/RewardNFTService.ts +++ b/apps/api/src/app/services/RewardNFTService.ts @@ -41,19 +41,14 @@ export default class RewardNFTService implements IRewardService { return payment.toJSON(); } - async getValidationResult({ - reward, - safe, - wallet, - }: { - reward: TRewardNFT; - safe?: WalletDocument; - wallet?: WalletDocument; - account?: TAccount; - }) { + async getValidationResult({ reward, wallet }: { reward: TRewardNFT; wallet?: WalletDocument; account?: TAccount }) { const nft = await this.findNFT(reward); if (!nft) return { result: false, reason: 'NFT contract is no longer available' }; + const pool = await PoolService.getById(reward.poolId); + const safe = await SafeService.findOneByPool(pool, nft.chainId); + if (!safe) return { result: false, reason: 'Campaign Safe is no longer available for this network' }; + // This will require a transfer if (reward.tokenId) { // Check if Safe is the owner @@ -107,19 +102,15 @@ export default class RewardNFTService implements IRewardService { await this.models.reward.findOneAndDelete(reward._id); } - async createPayment({ - reward, - safe, - wallet, - }: { - reward: RewardNFTDocument; - safe: WalletDocument; - wallet?: WalletDocument; - }) { + async createPayment({ reward, wallet }: { reward: RewardNFTDocument; wallet?: WalletDocument }) { const erc1155Amount = reward.erc1155Amount && String(reward.erc1155Amount); const nft = await this.findNFT(reward); if (!nft) throw new Error('NFT not found'); + const pool = await PoolService.getById(reward.poolId); + const safe = await SafeService.findOneByPool(pool, nft.chainId); + if (!safe) return { result: false, reason: 'Campaign Safe is no longer available for this network' }; + // Get token and metadata for either ERC721 or ERC1155 based contracts // and mint if metadataId is present or transfer if tokenId is present let token: ERC721TokenDocument | ERC1155TokenDocument, diff --git a/apps/api/src/app/services/RewardService.ts b/apps/api/src/app/services/RewardService.ts index 0fca1a797..9fbbc099a 100644 --- a/apps/api/src/app/services/RewardService.ts +++ b/apps/api/src/app/services/RewardService.ts @@ -193,16 +193,16 @@ export default class RewardService { const wallet = walletId && (await Wallet.findById(walletId)); // Validate supply, expiry, locked and reward specific validation - const validationResult = await this.getValidationResult({ reward, account, wallet, safe: pool.safe }); + const validationResult = await this.getValidationResult({ reward, account, wallet }); if (!validationResult.result) return validationResult.reason; - // @TODO Should create payment and update after point subtraction and reward distribution etc + // @TODO Should create payment with state and update after point subtraction and reward distribution etc // Subtract points for account await PointBalanceService.subtract(pool, account, reward.pointPrice); // Create the payment - await serviceMap[variant].createPayment({ reward, account, safe: pool.safe, wallet }); + await serviceMap[variant].createPayment({ reward, account, wallet }); // Send email notification let html = `

Congratulations!🚀

`; @@ -256,12 +256,10 @@ export default class RewardService { static async getValidationResult({ reward, account, - safe, wallet, }: { reward: TReward; account: TAccount; - safe?: WalletDocument; wallet?: WalletDocument; }) { const participant = await Participant.findOne({ sub: account.sub, poolId: reward.poolId }); @@ -281,7 +279,7 @@ export default class RewardService { const isLimitReached = await this.isLimitReached({ reward, account }); if (isLimitReached) return { result: false, reason: 'This reward has reached your personal limit.' }; - return serviceMap[reward.variant].getValidationResult({ reward, account, wallet, safe }); + return serviceMap[reward.variant].getValidationResult({ reward, account, wallet }); } // Checks if the account has reached the max amount of payments for this reward diff --git a/apps/api/src/app/services/SafeService.ts b/apps/api/src/app/services/SafeService.ts index ce40bcc6c..b60432dd6 100644 --- a/apps/api/src/app/services/SafeService.ts +++ b/apps/api/src/app/services/SafeService.ts @@ -1,8 +1,7 @@ import { Wallet, WalletDocument, Pool, PoolDocument, Transaction } from '@thxnetwork/api/models'; import { ChainId, WalletVariant } from '@thxnetwork/common/enums'; -import NetworkService from '@thxnetwork/api/services/NetworkService'; import { contractNetworks } from '@thxnetwork/api/hardhat'; -import ContractService, { safeVersion } from '@thxnetwork/api/services/ContractService'; +import { safeVersion } from '@thxnetwork/api/services/ContractService'; import { toChecksumAddress } from 'web3-utils'; import Safe, { SafeAccountConfig, SafeFactory } from '@safe-global/protocol-kit'; import SafeApiKit from '@safe-global/api-kit'; @@ -16,6 +15,7 @@ import { agenda, JobType } from '@thxnetwork/api/util/agenda'; import { Job } from '@hokify/agenda'; import { convertObjectIdToNumber } from '../util'; import TransactionService from './TransactionService'; +import NetworkService from '@thxnetwork/api/services/NetworkService'; function getSafeApiKit(chainId: ChainId) { const { txServiceUrl, ethAdapter } = NetworkService.getProvider(chainId); @@ -28,13 +28,19 @@ function reset(wallet: WalletDocument, userWalletAddress: string) { } async function create( - data: { sub: string; safeVersion?: SafeVersion; address?: string; poolId?: string }, + data: { sub: string; chainId: ChainId; safeVersion?: SafeVersion; address?: string; poolId?: string }, userWalletAddress?: string, ) { const { safeVersion, sub, address, poolId } = data; - const chainId = ContractService.getChainId(); - const { defaultAccount } = NetworkService.getProvider(chainId); - const wallet = await Wallet.create({ variant: WalletVariant.Safe, sub, chainId, address, safeVersion, poolId }); + const { defaultAccount } = NetworkService.getProvider(data.chainId); + const wallet = await Wallet.create({ + variant: WalletVariant.Safe, + chainId: data.chainId, + sub, + address, + safeVersion, + poolId, + }); // Concerns a Metamask account so we do not deploy and return early if (!safeVersion && address) return wallet; @@ -44,7 +50,7 @@ async function create( // Add user address as a signer and consider this a participant safe if (userWalletAddress) owners.push(toChecksumAddress(userWalletAddress)); - // If campaign safe we provide a nonce based on the timestamp in the MongoID the pool (poolId value) + // If campaign safe we provide a nonce based on the timestamp in the MongoID of the pool (poolId value) const nonce = wallet.poolId && String(convertObjectIdToNumber(wallet.poolId)); return await deploy(wallet, owners, nonce); @@ -119,11 +125,11 @@ function findOneByAddress(address: string) { return Wallet.findOne({ address: toChecksumAddress(address) }); } -async function findOneByPool(pool: PoolDocument, chainId?: ChainId) { +async function findOneByPool(pool: PoolDocument, chainId: ChainId) { if (!pool) return; return await Wallet.findOne({ poolId: pool.id, - chainId: chainId || ContractService.getChainId(), + chainId, sub: pool.sub, safeVersion, }); diff --git a/apps/api/src/app/services/WalletService.ts b/apps/api/src/app/services/WalletService.ts index 58ca8d1d4..2ca046ac4 100644 --- a/apps/api/src/app/services/WalletService.ts +++ b/apps/api/src/app/services/WalletService.ts @@ -1,7 +1,7 @@ import { Wallet } from '@thxnetwork/api/models/Wallet'; import { TransactionState, WalletVariant } from '@thxnetwork/common/enums'; import { Transaction } from '@thxnetwork/api/models/Transaction'; -import ContractService, { safeVersion } from './ContractService'; +import { safeVersion } from './ContractService'; import SafeService from './SafeService'; export default class WalletService { @@ -48,17 +48,16 @@ export default class WalletService { return map[variant]({ ...(data as TWallet) }); } - static async createSafe({ sub, address }) { + static async createSafe({ sub, chainId, address }) { const safeWallet = await SafeService.findOne({ sub }); // An account can have max 1 Safe if (safeWallet) throw new Error('Already has a Safe.'); // Deploy a Safe with Web3Auth address and relayer as signers - await SafeService.create({ sub, safeVersion }, address); + await SafeService.create({ sub, chainId, safeVersion }, address); } - static async createWalletConnect({ sub, address }) { - const chainId = ContractService.getChainId(); + static async createWalletConnect({ sub, chainId, address }) { const data: Partial = { variant: WalletVariant.WalletConnect, sub, address, chainId }; await Wallet.findOneAndUpdate({ sub, address, chainId }, data, { upsert: true }); diff --git a/apps/api/src/app/services/interfaces/IRewardService.ts b/apps/api/src/app/services/interfaces/IRewardService.ts index 0ffd58ac3..d54f0a441 100644 --- a/apps/api/src/app/services/interfaces/IRewardService.ts +++ b/apps/api/src/app/services/interfaces/IRewardService.ts @@ -21,12 +21,10 @@ export interface IRewardService { createPayment({ reward, account, - safe, wallet, }: { reward: TReward; account: TAccount; - safe: WalletDocument; wallet?: WalletDocument; }): Promise; }