Skip to content

Commit

Permalink
Remove dependencies to single Safe pools and auto deployment upon cam…
Browse files Browse the repository at this point in the history
…paign creation
  • Loading branch information
peterpolman committed Jul 8, 2024
1 parent 6c5876d commit 416efad
Show file tree
Hide file tree
Showing 22 changed files with 99 additions and 145 deletions.
13 changes: 8 additions & 5 deletions apps/api/src/app/controllers/account/get.controller.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
});
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
10 changes: 1 addition & 9 deletions apps/api/src/app/controllers/pools/duplicate/post.controller.ts
Original file line number Diff line number Diff line change
@@ -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 };
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ const validation = [
param('id').isMongoId(),
query('tokenAddress').isEthereumAddress(),
query('spender').isEthereumAddress(),
query('walletId').isMongoId(),
];

const controller = async (req: Request, res: Response) => {
const poolId = req.params.id as string;
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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ const validation = [
body('tokenAddress').isEthereumAddress(),
body('spender').isEthereumAddress(),
body('amountInWei').isString(),
body('chainId').isInt(),
];

const controller = async (req: Request, res: Response) => {
const poolId = req.params.id as string;
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
39 changes: 10 additions & 29 deletions apps/api/src/app/controllers/pools/get.controller.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
11 changes: 1 addition & 10 deletions apps/api/src/app/controllers/pools/post.controller.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
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 })];

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 };
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);

Expand Down
10 changes: 3 additions & 7 deletions apps/api/src/app/controllers/rewards/payments/post.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()];

Expand All @@ -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);
}
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app/middlewares/assertPayment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ 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.
* @dev Assumes that the poolId is available as 'id' param in the request
*/
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
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/services/PaymentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 2 additions & 7 deletions apps/api/src/app/services/PoolService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -145,14 +141,13 @@ async function getAllBySub(sub: string): Promise<PoolDocument[]> {
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 };
}),
);
}
Expand Down
Loading

0 comments on commit 416efad

Please sign in to comment.