Skip to content

Commit

Permalink
Various improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpolman committed Oct 28, 2024
1 parent ada096c commit ab2b0e5
Show file tree
Hide file tree
Showing 23 changed files with 204 additions and 83 deletions.
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"jochemvn <jochemvn@gmail.com>"
],
"license": "AGPL-3.0",
"version": "1.53.55",
"version": "1.53.63",
"scripts": {
"migrate": "yarn run migrate:db && yarn run migrate:post-migrate",
"migrate:post-migrate": "node post-migrate.js",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/scripts/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import db from '@thxnetwork/api/util/database';
// import main from './src/metamask';
// import main from './src/lottery';
// import main from './src/web3';
import main from './src/safe';
import main from './src/qr';

db.connect(process.env.MONGODB_URI_PROD);

Expand Down
36 changes: 19 additions & 17 deletions apps/api/scripts/src/qr.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { QRCodeEntry, RewardNFT } from '@thxnetwork/api/models';
import PoolService from '@thxnetwork/api/services/PoolService';
import { PromiseParser } from '@thxnetwork/api/util';

export default async function main() {
Expand All @@ -12,25 +11,28 @@ export default async function main() {
entries = await QRCodeEntry.find().skip(skip).limit(chunkSize).exec();
if (!entries.length) break;

const operations = await PromiseParser.parse(
entries.map(async (entry) => {
const reward = await RewardNFT.findById(entry.rewardId);
const pool = await PoolService.getById(reward.poolId);
const owner = await PoolService.findOwner(pool);
return {
updateOne: {
filter: { _id: entry._id },
update: {
$set: {
accountId: owner._id,
const operations = (
await PromiseParser.parse(
entries.map(async (entry) => {
const reward = await RewardNFT.findById(entry.rewardId);
if (!reward) return;

const update = {};
if (reward.erc721Id) update['erc721Id'] = reward.erc721Id;
if (reward.metadataId) update['erc721MetadataId'] = reward.metadataId;

return {
updateOne: {
filter: { _id: entry._id },
update: {
$set: update,
},
},
},
};
}),
);
};
}),
)
).filter((o) => !!o);

console.log(operations.length);
await QRCodeEntry.bulkWrite(operations);

skip += chunkSize;
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/controllers/qr-codes/list.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const controller = async (req: Request, res: Response) => {
account,
metadata,
erc721,
wallets: wallets.filter((wallet) => wallet.sub === entry.sub),
wallets: wallets.filter((wallet) => wallet.sub && wallet.sub === entry.sub),
});
});

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/controllers/wallet/wallet.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import ReadWalletScript from './js/get.controller';

const router: express.Router = express.Router();

router.get('/:id', assertRequestInput(ReadWallet.validation), ReadWallet.controller);
router.get('/js/:id.:ext', assertRequestInput(ReadWalletScript.validation), ReadWalletScript.controller);
router.get('/css/:id.:ext', assertRequestInput(ReadWalletCSS.validation), ReadWalletCSS.controller);
router.get('/:id', assertRequestInput(ReadWallet.validation), ReadWallet.controller);

export default router;
2 changes: 1 addition & 1 deletion apps/api/src/app/controllers/widget/js/get.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const validation = [

const controller = async (req: Request, res: Response) => {
const pool = await PoolService.getById(req.params.id);
if (!pool) throw new NotFoundError('Pool not found.');
if (!pool) return res.end('Faulty script src provided.');

// If there are QR codes for this campaign we redirect to it's wallet equivalent
const isQRCodeCampaign = await QRCodeEntry.exists({ accountId: pool.sub });
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MONGODB_URI, NODE_ENV, PORT, VERSION } from '@thxnetwork/api/config/secrets';
import { router } from '@thxnetwork/api/controllers/index';
import { errorLogger, errorNormalizer, errorOutput, notFoundHandler } from '@thxnetwork/api/middlewares';
import { corsHandler, errorLogger, errorNormalizer, errorOutput, notFoundHandler } from '@thxnetwork/api/middlewares';
import db from '@thxnetwork/api/util/database';
import axios from 'axios';
import axiosBetterStacktrace from 'axios-better-stacktrace';
Expand All @@ -21,6 +21,7 @@ db.connect(MONGODB_URI);

app.set('trust proxy', true);
app.set('port', PORT);
app.use(corsHandler);
app.use(lusca.xframe('SAMEORIGIN'));
app.use(lusca.xssProtection(true));
app.use(express.static(assetsPath));
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/app/middlewares/corsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import {
WIDGET_URL,
} from '@thxnetwork/api/config/secrets';
import cors from 'cors';
import { Widget } from '../models';

export const corsHandler = cors(async (req: any, callback: any) => {
const origin = req.header('Origin');
const domains = await Widget.find({ domain: { $exists: true, $ne: 'https://www.example.com' } }).distinct('domain');
const allowedOrigins = [
AUTH_URL,
API_URL,
Expand All @@ -34,6 +36,7 @@ export const corsHandler = cors(async (req: any, callback: any) => {
'https://dev-app.twinstory.io',
'https://admin.twinstory.io',
'https://dev-admin.twinstory.io',
...domains,
];

if (!origin || allowedOrigins.includes(origin)) {
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/app/models/QRCodeEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const QRCodeEntry = mongoose.model<QRCodeEntryDocument>(
transactionId: String,
erc721Id: String,
erc721MetadataId: String,
rewardId: String,
claimedAt: Date,
},
{ timestamps: true },
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/models/RewardNFT.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import mongoose from 'mongoose';
import { RewardVariant } from '@thxnetwork/common/enums';
import mongoose from 'mongoose';
import { rewardSchema } from './Reward';

export type RewardNFTDocument = mongoose.Document & TRewardNFT;
Expand Down
42 changes: 35 additions & 7 deletions apps/api/src/app/services/ERC721Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ 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 { ERC721TokenState, TransactionState, WalletVariant } from '@thxnetwork/common/enums';
import { Contract } from 'ethers';
import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
import { ObjectId } from 'mongodb';
import { TransactionReceipt } from 'web3-core';
import { toChecksumAddress } from 'web3-utils';
import { ADDRESS_ZERO } from '../config/secrets';
Expand Down Expand Up @@ -49,8 +51,11 @@ async function deployCallback({ erc721Id }: TERC721DeployCallbackArgs, receipt:
throw new ExpectedEventNotFound('Transfer or OwnershipTransferred');
}

const erc721 = await ERC721.findById(erc721Id);
await erc721.updateOne({ address: toChecksumAddress(receipt.contractAddress) });
const erc721 = await ERC721.findByIdAndUpdate(
erc721Id,
{ address: toChecksumAddress(receipt.contractAddress) },
{ new: true },
);

// Deploy a wallet and make it a minter
let safe = await Wallet.findOne({
Expand All @@ -72,7 +77,7 @@ async function deployCallback({ erc721Id }: TERC721DeployCallbackArgs, receipt:
}

// Add minter role for the address
if (!erc721.minters.includes(safe.address)) {
if (!erc721.minters.map((m) => m.address).includes(safe.address)) {
await addMinter(erc721, safe.address);
}
}
Expand All @@ -97,13 +102,31 @@ export async function findById(id: string): Promise<ERC721Document> {
}

export async function getMinters(erc721: ERC721Document, sub: string) {
const wallets = await Wallet.find({
// Fetch campaign safes
const safeConfig = {
sub,
chainId: erc721.chainId,
variant: WalletVariant.Safe,
owners: { $size: 1 },
});
};
const wallets = await Wallet.find(safeConfig);

// Return early if cached already
if (erc721.minters && erc721.minters.length) {
return {
wallets,
minters: await PromiseParser.parse(
erc721.minters.map((address) =>
Wallet.findOne({
...safeConfig,
address,
}),
),
),
};
}

// Test wallet addresses for minter roles
const minters = (
await PromiseParser.parse(
wallets.map(async (wallet: WalletDocument) => {
Expand All @@ -112,14 +135,17 @@ export async function getMinters(erc721: ERC721Document, sub: string) {
)
).filter((wallet: any) => wallet.isMinter);

// Cache results in db (not likely to change as UI does not allow for it)
await erc721.updateOne({ minters: minters.map((m) => m.address) });

return { wallets, minters };
}

export async function findBySub(sub: string): Promise<ERC721Document[]> {
const pools = await PoolService.getAllBySub(sub);
const nftRewards = await RewardNFT.find({ poolId: pools.map((p) => String(p._id)) });
const erc721Ids = nftRewards.map((c) => c.erc721Id);
const erc721s = await ERC721.find({ sub });
const erc721s = await ERC721.find({ sub, _id: { $nin: erc721Ids.map((id) => new ObjectId(id)) } });

return erc721s.concat(await ERC721.find({ _id: erc721Ids }));
}
Expand Down Expand Up @@ -240,7 +266,9 @@ export function parseAttributes(entry: ERC721MetadataDocument) {
}

async function isMinter(erc721: ERC721Document, address: string) {
return await erc721.contract.methods.hasRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address).call();
const provider = NetworkService.getProvider(erc721.chainId);
const contract = new Contract(erc721.address, erc721.contract.options.jsonInterface, provider.signer);
return await contract.hasRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address);
}

async function addMinter(erc721: ERC721Document, address: string) {
Expand Down
30 changes: 15 additions & 15 deletions apps/api/src/app/services/NetworkService.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { Relayer } from '@openzeppelin/defender-relay-client';
import { DefenderRelaySigner } from '@openzeppelin/defender-relay-client/lib/ethers';
import { DefenderRelayProvider } from '@openzeppelin/defender-relay-client/lib/web3';
import { EthersAdapter } from '@safe-global/protocol-kit';
import {
HARDHAT_RPC,
POLYGON_RELAYER,
POLYGON_RELAYER_API_KEY,
POLYGON_RELAYER_API_SECRET,
POLYGON_RPC,
LINEA_RELAYER,
LINEA_RELAYER_API_KEY,
LINEA_RELAYER_API_SECRET,
LINEA_RPC,
PRIVATE_KEY,
RELAYER_SPEED,
SAFE_TXS_SERVICE,
METIS_RELAYER,
METIS_RPC,
METIS_RELAYER_API_KEY,
METIS_RELAYER_API_SECRET,
METIS_RPC,
POLYGON_RELAYER,
POLYGON_RELAYER_API_KEY,
POLYGON_RELAYER_API_SECRET,
POLYGON_RPC,
PRIVATE_KEY,
RELAYER_SPEED,
SAFE_TXS_SERVICE,
} from '@thxnetwork/api/config/secrets';
import Web3 from 'web3';
import { ethers, Wallet } from 'ethers';
import { recoverAddress, hashMessage } from 'ethers/lib/utils';
import { DefenderRelaySigner } from '@openzeppelin/defender-relay-client/lib/ethers';
import { Relayer } from '@openzeppelin/defender-relay-client';
import { DefenderRelayProvider } from '@openzeppelin/defender-relay-client/lib/web3';
import { ChainId } from '@thxnetwork/common/enums';
import { EthersAdapter } from '@safe-global/protocol-kit';
import { ethers, Wallet } from 'ethers';
import { hashMessage, recoverAddress } from 'ethers/lib/utils';
import Web3 from 'web3';

class NetworkService {
config = {
Expand Down
2 changes: 1 addition & 1 deletion apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"jochemvn <jochemvn@gmail.com>"
],
"license": "AGPL-3.0",
"version": "1.2.93"
"version": "1.2.103"
}
2 changes: 1 addition & 1 deletion apps/app/src/views/SigninRedirect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
</div>
</template>
<script lang="ts">
import { mapStores } from 'pinia';
import { defineComponent } from 'vue';
import { supabase, useAccountStore } from '../stores/Account';
import { mapStores } from 'pinia';
export default defineComponent({
name: 'ViewSigninRedirect',
Expand Down
1 change: 1 addition & 0 deletions apps/studio/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
BAlert: typeof import('bootstrap-vue-next')['BAlert']
BaseButtonGroupMinterWallet: typeof import('./src/components/BaseButtonGroupMinterWallet.vue')['default']
BaseButtonMinterCreate: typeof import('./src/components/buttons/BaseButtonMinterCreate.vue')['default']
BaseButtonWalletCreate: typeof import('./src/components/buttons/BaseButtonWalletCreate.vue')['default']
BaseCardCollectible: typeof import('./src/components/cards/BaseCardCollectible.vue')['default']
Expand Down
70 changes: 70 additions & 0 deletions apps/studio/src/components/BaseButtonGroupMinterWallet.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<b-spinner v-if="!collection.wallets" small />
<BaseButtonWalletCreate v-else-if="!collection.wallets.length" :collection="collection" @submit="$emit('submit')" />
<BaseButtonMinterCreate
v-else-if="collection.minters && !collection.minters.length"
:collection="collection"
:wallet="collection.wallets[0]"
@submit="$emit('submit')"
/>
<b-link v-else-if="minter" :href="minter.url" target="_blank" class="text-decoration-none">
<code>
{{ minter.short }}
<BaseIcon icon="external-link-alt" />
</code>
</b-link>
<b-spinner v-else small />
</template>

<script lang="ts">
import { chainInfo } from '@thxnetwork/studio/utils/chains';
import { toast } from '@thxnetwork/studio/utils/toast';
import { mapStores } from 'pinia';
import { defineComponent, PropType } from 'vue';
import { useCollectionStore } from '../stores';
import { shortenAddress } from '../utils/address';
export default defineComponent({
name: 'BaseButtonGroupMinterWallet',
props: {
collection: { type: Object as PropType<TERC721>, required: true },
},
data() {
return {
isLoading: false,
};
},
computed: {
...mapStores(useCollectionStore),
minter() {
const { minters } = this.collection;
if (!minters || !minters.length) return;
return {
url: minters.length
? chainInfo[this.collection.chainId].blockExplorer + '/address/' + minters[0]?.address
: '',
long: minters.length ? minters[0].address : '',
short: minters.length ? shortenAddress(minters[0].address as `0x${string}`) : '',
};
},
},
mounted() {
this.getMinter();
},
methods: {
async getMinter() {
try {
this.isLoading = true;
await this.collectionStore.getMinter(this.collection._id);
} catch (error: any) {
toast(error.message, 'light', 3000, () => {
return;
});
} finally {
this.isLoading = false;
}
},
},
});
</script>
Loading

0 comments on commit ab2b0e5

Please sign in to comment.