Skip to content

Commit

Permalink
Adds wallet management to studio app
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpolman committed Sep 25, 2024
1 parent 39a9e3e commit 622ffd7
Show file tree
Hide file tree
Showing 42 changed files with 581 additions and 218 deletions.
6 changes: 3 additions & 3 deletions apps/api/scripts/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import db from '@thxnetwork/api/util/database';
// import main from './src/ipfs';
// import main from './src/invoices';
// import main from './src/demo';
import main from './src/multisend';
// import main from './src/multisend';
// import main from './src/preview';
// import main from './src/metamask';
// import main from './src/lottery';
// import main from './src/web3';
// import main from './src/safe';
import main from './src/safe';

db.connect(process.env.MONGODB_URI);
db.connect(process.env.MONGODB_URI_DEV);

main()
.then(() => process.exit(0))
Expand Down
27 changes: 16 additions & 11 deletions apps/api/scripts/src/safe.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { RewardCoin, Wallet } from '@thxnetwork/api/models';
import RewardCoinService from '@thxnetwork/api/services/RewardCoinService';
import { Wallet } from '@thxnetwork/api/models';
import SafeService from '@thxnetwork/api/services/SafeService';
import { PromiseParser } from '@thxnetwork/api/util';
import { WalletVariant } from '@thxnetwork/common/enums';

export default async function main() {
// const reward = await RewardCoin.findById('669126e1110e00291909e0e3'); // Polygon Reward
// const reward = await RewardCoin.findById('66952d939a90f7280b2d3164'); // Linea Reward
const reward = await RewardCoin.findById('6698033a03bf2db6c9a940f1'); // Linea Reward
const wallet = await Wallet.findById('669805738c683b6c4c506e97');
const service = new RewardCoinService();
const wallet = await Wallet.find({ variant: WalletVariant.Safe, poolId: { $exists: true } });
const chunkSize = 10;

await service.createPayment({
reward,
wallet,
});
for (let i = 0; i < wallet.length; i += chunkSize) {
await PromiseParser.parse(
wallet.slice(i, i + chunkSize).map(async (w) => {
const safe = await SafeService.getSafe(w);
const owners = await safe.getOwners();

await w.updateOne({ owners });
}),
);
}
}
48 changes: 25 additions & 23 deletions apps/api/src/app/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import { checkJwt, corsHandler } from '@thxnetwork/api/middlewares';
import express from 'express';
import RouterHealth from './health/health.router';
import RouterAccount from './account/account.router';
import RouterPools from './pools/pools.router';
import RouterToken from './token/token.router';
import RouterParticipants from './participants/participants.router';
import RouterMetadata from './metadata/metadata.router';
import RouterUpload from './upload/upload.router';
import RouterInvites from './invites/invites.router';
import RouterBrands from './brands/brands.router';
import RouterCoupons from './coupons/coupons.router';
import RouterData from './data/data.router';
import RouterPrices from './earn/earn.router';
import RouterERC1155 from './erc1155/erc1155.router';
import RouterERC20 from './erc20/erc20.router';
import RouterERC721 from './erc721/erc721.router';
import RouterERC1155 from './erc1155/erc1155.router';
import RouterEvents from './events/events.router';
import RouterHealth from './health/health.router';
import RouterIdentity from './identity/identity.router';
import RouterInvites from './invites/invites.router';
import RouterJobs from './jobs/jobs.router';
import RouterLeaderboards from './leaderboards/leaderboards.router';
import RouterLogin from './login/login.router';
import RouterLotteries from './lotteries/lotteries.router';
import RouterMetadata from './metadata/metadata.router';
import RouterOAuth from './oauth/oauth.router';
import RouterParticipants from './participants/participants.router';
import RouterPools from './pools/pools.router';
import RouterWallets from './pools/wallets/wallets.router';
import RouterQRCodes from './qr-codes/qr-codes.router';
import RouterBrands from './brands/brands.router';
import RouterWidget from './widget/widget.router';
import RouterWallet from './wallet/wallet.router';
import RouterQuests from './quests/quests.router';
import RouterRewards from './rewards/rewards.router';
import RouterLeaderboards from './leaderboards/leaderboards.router';
import RouterToken from './token/token.router';
import RouterUpload from './upload/upload.router';
import RouterVoteEscrow from './ve/ve.router';
import RouterWallet from './wallet/wallet.router';
import RouterWebhook from './webhook/webhook.router';
import RouterPrices from './earn/earn.router';
import RouterWidget from './widget/widget.router';
import RouterWidgets from './widgets/widgets.router';
import RouterIdentity from './identity/identity.router';
import RouterEvents from './events/events.router';
import RouterData from './data/data.router';
import RouterVoteEscrow from './ve/ve.router';
import RouterJobs from './jobs/jobs.router';
import RouterLotteries from './lotteries/lotteries.router';
import RouterCoupons from './coupons/coupons.router';
import RouterLogin from './login/login.router';
import RouterOAuth from './oauth/oauth.router';
import { checkJwt, corsHandler } from '@thxnetwork/api/middlewares';

const router: express.Router = express.Router({ mergeParams: true });

Expand Down Expand Up @@ -62,6 +63,7 @@ router.use('/coupons', RouterCoupons);
router.use('/participants', RouterParticipants);
router.use('/pools', RouterPools);
router.use('/widgets', RouterWidgets);
router.use('/wallets', RouterWallets);
router.use('/ve', RouterVoteEscrow);

router.use('/erc20', RouterERC20);
Expand Down
24 changes: 11 additions & 13 deletions apps/api/src/app/controllers/pools/pools.router.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { assertPayment, assertPoolAccess, assertRequestInput } from '@thxnetwork/api/middlewares';
import express from 'express';
import { assertRequestInput, assertPoolAccess, assertPayment } from '@thxnetwork/api/middlewares';

import * as ListController from './list.controller';
import * as ReadController from './get.controller';
import * as CreateController from './post.controller';
import * as UpdateController from './patch.controller';
import * as DeleteController from './delete.controller';
import * as CreateDuplicate from './duplicate/post.controller';
import * as ReadController from './get.controller';
import * as ListController from './list.controller';
import * as UpdateController from './patch.controller';
import * as CreateController from './post.controller';

import RouterAnalytics from './analytics/analytics.router';
import RouterCollaborators from './collaborators/collaborators.router';
import RouterER1155 from './erc1155/erc1155.router';
import RouterERC20 from './erc20/erc20.router';
import RouterGuilds from './guilds/guilds.router';
import RouterIntegrations from './integrations/integrations.router';
import RouterParticipants from './participants/participants.router';
import RouterAnalytics from './analytics/analytics.router';
import RouterPayments from './payments/payments.router';
import RouterQuests from './quests/quests.router';
import RouterRewards from './rewards/rewards.router';
import RouterGuilds from './guilds/guilds.router';
import RouterPayments from './payments/payments.router';
import RouterERC20 from './erc20/erc20.router';
import RouterER1155 from './erc1155/erc1155.router';
import RouterIntegrations from './integrations/integrations.router';
import RouterWallets from './wallets/wallets.router';

const router: express.Router = express.Router({ mergeParams: true });

Expand Down Expand Up @@ -48,6 +47,5 @@ router.use('/:id/participants', RouterParticipants);
router.use('/:id/guilds', RouterGuilds);
router.use('/:id/erc1155', RouterER1155);
router.use('/:id/integrations', RouterIntegrations);
router.use('/:id/wallets', RouterWallets);

export default router;
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Request, Response } from 'express';
import { param } from 'express-validator';
import { Wallet } from '@thxnetwork/api/models';
import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
import { Request, Response } from 'express';
import { param } from 'express-validator';

const validation = [param('id').isMongoId(), param('walletId').isMongoId()];
const validation = [param('walletId').isMongoId()];

const controller = async (req: Request, res: Response) => {
const wallet = await Wallet.findById(req.params.walletId);
Expand Down
13 changes: 4 additions & 9 deletions apps/api/src/app/controllers/pools/wallets/list.controller.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { Request, Response } from 'express';
import { param } from 'express-validator';
import { Pool, Transaction, Wallet } from '@thxnetwork/api/models';
import { NotFoundError } from '@thxnetwork/api/util/errors';
import { Transaction, Wallet } from '@thxnetwork/api/models';
import { PromiseParser } from '@thxnetwork/api/util';
import { Request, Response } from 'express';

const validation = [param('id').isMongoId()];
const validation = [];

const controller = async (req: Request, res: Response) => {
const pool = await Pool.findById(req.params.id);
if (!pool) throw new NotFoundError('Pool not found');

const wallets = await Wallet.find({ poolId: req.params.id, sub: pool.sub });
const wallets = await Wallet.find({ sub: req.auth.sub, owners: { $exists: true, $size: 1 } });
const response = await PromiseParser.parse(
wallets.map(async (wallet) => {
const transactions = await Transaction.find({ walletId: wallet.id }).sort({ createdAt: -1 }).limit(50);
Expand Down
17 changes: 11 additions & 6 deletions apps/api/src/app/controllers/pools/wallets/post.controller.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { Request, Response } from 'express';
import { body, param } from 'express-validator';
import { Wallet } from '@thxnetwork/api/models';
import { ForbiddenError } from '@thxnetwork/api/util/errors';
import { safeVersion } from '@thxnetwork/api/services/ContractService';
import NetworkService from '@thxnetwork/api/services/NetworkService';
import SafeService from '@thxnetwork/api/services/SafeService';
import { ForbiddenError } from '@thxnetwork/api/util/errors';
import { WalletVariant } from '@thxnetwork/common/enums';
import { Request, Response } from 'express';
import { body } from 'express-validator';
import { toChecksumAddress } from 'web3-utils';

const validation = [param('id').isMongoId(), body('chainId').isInt()];
const validation = [body('chainId').isInt()];

const controller = async (req: Request, res: Response) => {
const wallet = await Wallet.findOne({ poolId: req.params.id, chainId: req.body.chainId, sub: req.auth.sub });
const wallet = await Wallet.findOne({ variant: WalletVariant.Safe, chainId: req.body.chainId, sub: req.auth.sub });
if (wallet) throw new ForbiddenError('Wallet for this chain already exists');

const { defaultAccount } = NetworkService.getProvider(req.body.chainId);
const owners = [toChecksumAddress(defaultAccount)];
const safe = await SafeService.create({
sub: req.auth.sub,
chainId: req.body.chainId,
poolId: req.params.id,
safeVersion,
owners,
});

res.status(201).json(safe);
Expand Down
10 changes: 5 additions & 5 deletions apps/api/src/app/controllers/pools/wallets/wallets.router.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { assertRequestInput } from '@thxnetwork/api/middlewares';
import express from 'express';
import { assertRequestInput, assertPoolAccess } from '@thxnetwork/api/middlewares';
import * as RemoveWallets from './delete.controller';
import * as ListWallets from './list.controller';
import * as CreateWallets from './post.controller';
import * as RemoveWallets from './delete.controller';

const router: express.Router = express.Router({ mergeParams: true });

router.get('/', assertPoolAccess, assertRequestInput(ListWallets.validation), ListWallets.controller);
router.post('/', assertPoolAccess, assertRequestInput(CreateWallets.validation), CreateWallets.controller);
router.delete('/:walletId', assertPoolAccess, assertRequestInput(RemoveWallets.validation), RemoveWallets.controller);
router.get('/', assertRequestInput(ListWallets.validation), ListWallets.controller);
router.post('/', assertRequestInput(CreateWallets.validation), CreateWallets.controller);
router.delete('/:walletId', assertRequestInput(RemoveWallets.validation), RemoveWallets.controller);

export default router;
1 change: 1 addition & 0 deletions apps/api/src/app/models/Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const Wallet = mongoose.model<WalletDocument>(
version: String,
safeVersion: String,
variant: String,
owners: [String],
},
{ timestamps: true },
),
Expand Down
21 changes: 11 additions & 10 deletions apps/api/src/app/services/ERC721Service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
import { ERC721, ERC721Document } from '@thxnetwork/api/models/ERC721';
import { ERC721Metadata, ERC721MetadataDocument } from '@thxnetwork/api/models/ERC721Metadata';
import { ERC721Token, ERC721TokenDocument } from '@thxnetwork/api/models/ERC721Token';
import { Transaction, TransactionDocument } from '@thxnetwork/api/models/Transaction';
import { ERC721TokenState, TransactionState } from '@thxnetwork/common/enums';
import { assertEvent, ExpectedEventNotFound, findEvent, parseLogs } from '@thxnetwork/api/util/events';
import NetworkService from '@thxnetwork/api/services/NetworkService';
import { assertEvent, ExpectedEventNotFound, findEvent, parseLogs } from '@thxnetwork/api/util/events';
import { paginatedResults } from '@thxnetwork/api/util/pagination';
import { Wallet, WalletDocument } from '../models/Wallet';
import { RewardNFT } from '../models/RewardNFT';
import { getArtifact } from '../hardhat';
import PoolService from './PoolService';
import TransactionService from './TransactionService';
import IPFSService from './IPFSService';
import WalletService from './WalletService';
import { ERC721TokenState, TransactionState } from '@thxnetwork/common/enums';
import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
import { TransactionReceipt } from 'web3-core';
import { toChecksumAddress } from 'web3-utils';
import { ADDRESS_ZERO } from '../config/secrets';
import { getArtifact } from '../hardhat';
import { QRCodeEntry } from '../models';
import { RewardNFT } from '../models/RewardNFT';
import { Wallet, WalletDocument } from '../models/Wallet';
import IPFSService from './IPFSService';
import PoolService from './PoolService';
import TransactionService from './TransactionService';
import WalletService from './WalletService';

const contractName = 'THXERC721';

Expand Down Expand Up @@ -92,6 +92,7 @@ export async function mint(
metadata: ERC721MetadataDocument,
): Promise<TransactionDocument> {
const tokenUri = await IPFSService.getTokenURI(erc721, metadata.id);
console.log(tokenUri);
return await TransactionService.sendSafeAsync(
safe,
erc721.address,
Expand Down
26 changes: 7 additions & 19 deletions apps/api/src/app/services/SafeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,26 @@ import { logger } from '../util/logger';
import TransactionService from './TransactionService';

class SafeService {
async create(
data: { sub: string; chainId: ChainId; safeVersion: '1.3.0'; address?: string; poolId?: string },
userWalletAddress?: string,
) {
async create(data: { sub: string; chainId: ChainId; safeVersion: '1.3.0'; owners: string[]; address?: string }) {
const wallet = await Wallet.create({
variant: WalletVariant.Safe,
...data,
});
// Present address means Metamask account so do not deploy and return early
if (!safeVersion && wallet.address) return wallet;

// Add relayer address and consider this a campaign safe
const { defaultAccount } = NetworkService.getProvider(wallet.chainId);
const owners = [toChecksumAddress(defaultAccount)];

// 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)
const saltNonce = wallet.poolId && String(convertObjectIdToNumber(wallet.poolId));
const safeAddress = await this.deploy(wallet, owners, saltNonce);
// If campaign safe we provide a nonce based on the timestamp in the account sub to make it unique
const saltNonce = wallet.owners.length === 1 && String(convertObjectIdToNumber(wallet.sub));
const safeAddress = await this.deploy(wallet, saltNonce);

return await Wallet.findByIdAndUpdate(wallet.id, { address: safeAddress }, { new: true });
}

async deploy(wallet: WalletDocument, owners: string[], saltNonce?: string) {
async deploy(wallet: WalletDocument, saltNonce?: string) {
const { ethAdapter } = NetworkService.getProvider(wallet.chainId);
const safeAccountConfig: SafeAccountConfig = {
owners,
threshold: owners.length,
owners: wallet.owners,
threshold: wallet.owners.length,
};
const safeAddress = await this.predictAddress(wallet, safeAccountConfig, safeVersion, saltNonce);

Expand Down
15 changes: 8 additions & 7 deletions apps/api/src/app/services/TransactionService.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import NetworkService from '@thxnetwork/api/services/NetworkService';
import { ChainId, TransactionState, TransactionType } from '@thxnetwork/common/enums';
import { RelayerTransactionPayload } from '@openzeppelin/defender-relay-client';
import { MINIMUM_GAS_LIMIT, RELAYER_SPEED } from '@thxnetwork/api/config/secrets';
import { Transaction, TransactionDocument, Wallet, WalletDocument } from '@thxnetwork/api/models';
import NetworkService from '@thxnetwork/api/services/NetworkService';
import { paginatedResults } from '@thxnetwork/api/util/pagination';
import { toChecksumAddress } from 'web3-utils';
import { poll } from '@thxnetwork/api/util/polling';
import { RelayerTransactionPayload } from '@openzeppelin/defender-relay-client';
import { Transaction, TransactionDocument, Wallet, WalletDocument } from '@thxnetwork/api/models';
import { ChainId, TransactionState, TransactionType } from '@thxnetwork/common/enums';
import { TransactionReceipt } from 'web3-core';
import { toChecksumAddress } from 'web3-utils';
import { PromiseParser } from '../util';
import { logger } from '../util/logger';
import ERC1155Service from './ERC1155Service';
import ERC20Service from './ERC20Service';
import ERC721Service from './ERC721Service';
import ERC1155Service from './ERC1155Service';
import SafeService from './SafeService';
import { PromiseParser } from '../util';

class TransactionService {
/**
Expand Down Expand Up @@ -304,6 +304,7 @@ class TransactionService {

async sendSafeAsync(wallet: WalletDocument, to: string | null, fn: any, callback?: TTransactionCallback) {
const data = fn.encodeABI();
console.log(wallet, to, fn, callback);
return await this.proposeSafeAsync(wallet, to, data, callback);
}
}
Expand Down
7 changes: 6 additions & 1 deletion apps/api/src/app/services/WalletService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Transaction } from '@thxnetwork/api/models/Transaction';
import { Wallet } from '@thxnetwork/api/models/Wallet';
import { TransactionState, WalletVariant } from '@thxnetwork/common/enums';
import { toChecksumAddress } from 'web3-utils';
import { safeVersion } from './ContractService';
import NetworkService from './NetworkService';
import SafeService from './SafeService';

export default class WalletService {
Expand Down Expand Up @@ -53,7 +55,10 @@ export default class WalletService {
if (safeWallet) throw new Error('Already has a Safe.');

// Deploy a Safe with Web3Auth address and relayer as signers
await SafeService.create({ sub, chainId, safeVersion }, address);
const { defaultAccount } = NetworkService.getProvider(chainId);
const owners = [toChecksumAddress(defaultAccount), toChecksumAddress(address)];

await SafeService.create({ sub, chainId, safeVersion, owners });
}

static async createWalletConnect({ sub, address }: Partial<TWallet>) {
Expand Down
Loading

0 comments on commit 622ffd7

Please sign in to comment.