feat: dev environment backend setup (#278)

* feat: add anvil (qanet) to hardhat config networks.

* chore: deploy contract to anvil testnet.

* chore: sepolia deployment.

* merge: authentication sls.

* feat: separate the issignaturevalid function from handlers.

* feat: update subgraph config to match the qa network.

* feat: add app specific prisma schema to deploy script copy command.

* fix: merge conflict

* feat: remove unnecessary conditions.

---------

Co-authored-by: Nima Rasooli <nimarasooli1@gmail.com>
This commit is contained in:
Shredder 2023-06-26 18:13:08 +03:30 committed by GitHub
parent 948f926c92
commit 86907836ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 5594 additions and 2448 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
{
"FleekERC721": [
{
"address": "0x1CfD8455F189c56a4FBd81EB7D4118DB04616BA8",
"timestamp": "6/16/2023, 8:51:33 AM"
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
{
"FleekERC721": [
{
"address": "0x40208b6aFfCc39CD42A25EC47B410Cfe117837D6",
"timestamp": "6/16/2023, 12:21:27 PM"
}
]
}

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,7 @@ const {
ETH_GOERLI_API_URL, ETH_GOERLI_API_URL,
MAINNET_API_KEY, MAINNET_API_KEY,
COINMARKETCAP_KEY, COINMARKETCAP_KEY,
QANET_RPC_URL,
} = process.env; } = process.env;
const config: HardhatUserConfig = { const config: HardhatUserConfig = {
@ -59,6 +60,11 @@ const config: HardhatUserConfig = {
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
chainId: 1, chainId: 1,
}, },
qanet: {
url: QANET_RPC_URL ? QANET_RPC_URL : '',
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
chainId: 31337,
},
local: { local: {
url: 'http://localhost:8545', url: 'http://localhost:8545',
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [], accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],

View File

@ -5,12 +5,11 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "yarn tsc", "build": "yarn tsc",
"invoke:build": "yarn build && serverless invoke local --function submitBuildInfo",
"prisma:generate": "npx prisma generate", "prisma:generate": "npx prisma generate",
"prisma:pull": "npx prisma db pull --force", "prisma:pull": "npx prisma db pull --force",
"start": "yarn build && serverless offline", "start": "yarn build && serverless offline",
"generate:layers": "./scripts/prepare-prisma-client-lambda-layer.sh && ./scripts/prepare-libs-lambda-layer.sh && ./scripts/prepare-node-modules-lambda-layer.sh", "deploy:dev": "sh ./scripts/deploy.sh dev",
"deploy:dev": "yarn build && yarn generate:layers && yarn sls deploy --stage dev" "deploy:prd": "sh ./scripts/deploy.sh prd"
}, },
"author": "fleek", "author": "fleek",
"license": "MIT", "license": "MIT",

View File

@ -79,12 +79,16 @@ echo "${bold}Copying the Prisma schema file to function directories${normal}"
cp prisma/schema.prisma dist/src/functions/builds/ cp prisma/schema.prisma dist/src/functions/builds/
cp prisma/schema.prisma dist/src/functions/mints/ cp prisma/schema.prisma dist/src/functions/mints/
echo "${bold}Generating Prisma Client${normal}"
yarn prisma:generate
echo "${bold}Running the build command${normal}" echo "${bold}Running the build command${normal}"
yarn build yarn build
echo "${bold}Copying the rhel openssl engine to dist/${normal}" echo "${bold}Copying the rhel openssl engine to dist/${normal}"
cp node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node dist/src/functions/mints cp node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node dist/src/functions/mints
cp node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node dist/src/functions/builds cp node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node dist/src/functions/builds
cp node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node dist/src/functions/apps
echo "${bold}Copying the .env file to dist/${normal}" echo "${bold}Copying the .env file to dist/${normal}"
cp .env src/ cp .env src/
@ -95,9 +99,7 @@ cp src/libs/FleekERC721.json dist/src/libs/
echo "${bold}Copying the Prisma schema file to function directories${normal}" echo "${bold}Copying the Prisma schema file to function directories${normal}"
cp prisma/schema.prisma dist/src/functions/builds/ cp prisma/schema.prisma dist/src/functions/builds/
cp prisma/schema.prisma dist/src/functions/mints/ cp prisma/schema.prisma dist/src/functions/mints/
cp prisma/schema.prisma dist/src/functions/apps/
echo "${bold}Generating Prisma Client${normal}"
yarn prisma:generate
echo "${bold}Creating layer zip files${normal}" echo "${bold}Creating layer zip files${normal}"
/bin/bash ./scripts/prepare-libs-lambda-layer.sh /bin/bash ./scripts/prepare-libs-lambda-layer.sh

View File

@ -11,7 +11,6 @@ provider:
runtime: nodejs18.x runtime: nodejs18.x
stage: ${opt:stage, 'prd'} stage: ${opt:stage, 'prd'}
region: ${opt:region, 'us-west-2'} region: ${opt:region, 'us-west-2'}
timeout: 40
apiGateway: apiGateway:
minimumCompressionSize: 1024 minimumCompressionSize: 1024
shouldStartNameWithService: true shouldStartNameWithService: true
@ -88,7 +87,7 @@ functions:
- { Ref: TopicPrismaAwsPrismaClientLambdaLayer } - { Ref: TopicPrismaAwsPrismaClientLambdaLayer }
verifyAccessPoint: verifyAccessPoint:
handler: src/functions/apps/handler.verifyApp handler: ./dist/src/functions/apps/handler.verifyApp
events: events:
- http: - http:
path: verifyApp path: verifyApp
@ -96,7 +95,7 @@ functions:
cors: true cors: true
submitAppInfo: submitAppInfo:
handler: src/functions/apps/handler.submitAppInfo handler: ./dist/src/functions/apps/handler.submitAppInfo
events: events:
- http: - http:
path: app path: app

View File

@ -9,24 +9,7 @@ import {
CreatePullZoneMethodArgs, CreatePullZoneMethodArgs,
LoadFreeCertificateMethodArgs, LoadFreeCertificateMethodArgs,
} from '@libs/bunnyCDN'; } from '@libs/bunnyCDN';
import * as crypto from "crypto"; import { isTheSignatureValid } from '@libs/verify-signature';
function isTheSignatureValid(
body: string, // must be raw string body, not json transformed version of the body
signature: string, // the "lambda-signature" from header
signingKey: string, // signing secret key for front-end
) {
const hmac = crypto.createHmac("sha256", signingKey); // Create a HMAC SHA256 hash using the signing key
hmac.update(body, "utf8"); // Update the token hash with the request body using utf8
const digest = hmac.digest("hex");
if (signature !== digest) {
// the request is not valid
return formatJSONResponse({
status: 401,
message: 'Unauthorized',
});
}
}
export const verifyApp = async ( export const verifyApp = async (
event: APIGatewayEvent event: APIGatewayEvent
@ -34,7 +17,7 @@ export const verifyApp = async (
try { try {
// Check the parameters and environment variables // Check the parameters and environment variables
dotenv.config(); dotenv.config();
if (event.body === null || process.env.BUNNY_CDN_ACCESS_KEY == undefined) { if (event.body === null || process.env.BUNNY_CDN_ACCESS_KEY === undefined) {
return formatJSONResponse({ return formatJSONResponse({
status: 422, status: 422,
message: 'Required parameters were not passed.', message: 'Required parameters were not passed.',
@ -43,10 +26,23 @@ export const verifyApp = async (
// Check the lambda-signature and confirm the value of the FE_SIGNING_KEY env variable. // Check the lambda-signature and confirm the value of the FE_SIGNING_KEY env variable.
// If both are valid, verify the authenticity of the request. // If both are valid, verify the authenticity of the request.
if (event.headers["lambda-signature"] === undefined) throw Error("Header field 'lambda-signature' was not found."); if (event.headers['lambda-signature'] === undefined)
throw Error("Header field 'lambda-signature' was not found.");
if (process.env.FE_SIGNING_KEY === undefined) throw Error("FE_SIGNING_KEY env variable not found.");
else { isTheSignatureValid(event.body, event.headers["lambda-signature"], process.env.FE_SIGNING_KEY); }; if (process.env.FE_SIGNING_KEY === undefined)
throw Error('FE_SIGNING_KEY env variable not found.');
else if (
!isTheSignatureValid(
event.body,
event.headers['lambda-signature'],
process.env.FE_SIGNING_KEY
)
) {
return formatJSONResponse({
status: 401,
message: 'Unauthorized',
});
}
// Set up constants // Set up constants
const bunnyCdn = new BunnyCdn(process.env.BUNNY_CDN_ACCESS_KEY); const bunnyCdn = new BunnyCdn(process.env.BUNNY_CDN_ACCESS_KEY);
@ -75,7 +71,7 @@ export const submitAppInfo = async (
try { try {
// Check the parameters and environment variables // Check the parameters and environment variables
dotenv.config(); dotenv.config();
if (event.body === null || process.env.BUNNY_CDN_ACCESS_KEY == undefined || event.headers.originUrl === undefined) { if (event.body === null || process.env.BUNNY_CDN_ACCESS_KEY === undefined) {
return formatJSONResponse({ return formatJSONResponse({
status: 422, status: 422,
message: 'Required parameters were not passed.', message: 'Required parameters were not passed.',
@ -84,10 +80,23 @@ export const submitAppInfo = async (
// Check the lambda-signature and confirm the value of the FE_SIGNING_KEY env variable. // Check the lambda-signature and confirm the value of the FE_SIGNING_KEY env variable.
// If both are valid, verify the authenticity of the request. // If both are valid, verify the authenticity of the request.
if (event.headers["lambda-signature"] === undefined) throw Error("Header field 'lambda-signature' was not found."); if (event.headers['lambda-signature'] === undefined)
throw Error("Header field 'lambda-signature' was not found.");
if (process.env.FE_SIGNING_KEY === undefined) throw Error("FE_SIGNING_KEY env variable not found.");
else { isTheSignatureValid(event.body, event.headers["lambda-signature"], process.env.FE_SIGNING_KEY); }; if (process.env.FE_SIGNING_KEY === undefined)
throw Error('FE_SIGNING_KEY env variable not found.');
else if (
!isTheSignatureValid(
event.body,
event.headers['lambda-signature'],
process.env.FE_SIGNING_KEY
)
) {
return formatJSONResponse({
status: 401,
message: 'Unauthorized',
});
}
// Set up constants // Set up constants
const bunnyCdn = new BunnyCdn(process.env.BUNNY_CDN_ACCESS_KEY); const bunnyCdn = new BunnyCdn(process.env.BUNNY_CDN_ACCESS_KEY);

View File

@ -27,7 +27,7 @@ export const submitBuildInfo = async (
domain: data.domain, domain: data.domain,
verificationTransactionHash: 'Not verified.', verificationTransactionHash: 'Not verified.',
}; };
// Add build record to the database, if it's not already added // Add build record to the database, if it's not already added
const buildRecord = await prisma.builds.findMany({ const buildRecord = await prisma.builds.findMany({
where: { where: {
@ -37,10 +37,9 @@ export const submitBuildInfo = async (
domain: buildInfo.domain, domain: buildInfo.domain,
}, },
}); });
if (buildRecord.length == 0) { if (buildRecord.length == 0) {
await prisma.builds.create({ await prisma.builds.create({
data: { data: {
githubRepository: buildInfo.githubRepository, githubRepository: buildInfo.githubRepository,

View File

@ -7,24 +7,8 @@ import { formatJSONResponse } from '@libs/api-gateway';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { initPrisma, prisma } from '@libs/prisma'; import { initPrisma, prisma } from '@libs/prisma';
import { contractInstance, web3 } from '@libs/nfa-contract'; import { contractInstance, web3 } from '@libs/nfa-contract';
import * as crypto from "crypto"; import { isTheSignatureValid } from '@libs/verify-signature';
import { ethers } from 'ethers';
function isTheSignatureValid(
body: string, // must be raw string body, not json transformed version of the body
signature: string, // the "x-alchemy-signature" from header
signingKey: string, // taken from dashboard for specific webhook
) {
const hmac = crypto.createHmac("sha256", signingKey); // Create a HMAC SHA256 hash using the signing key
hmac.update(body, "utf8"); // Update the token hash with the request body using utf8
const digest = hmac.digest("hex");
if (signature !== digest) {
// the request is not valid
return formatJSONResponse({
status: 401,
message: 'Unauthorized',
});
}
}
export const submitMintInfo = async ( export const submitMintInfo = async (
event: APIGatewayEvent event: APIGatewayEvent
@ -40,14 +24,39 @@ export const submitMintInfo = async (
// Check the alchemy signature and confirm the value of the ALCHEMY_SIGNING_KEY env variable. // Check the alchemy signature and confirm the value of the ALCHEMY_SIGNING_KEY env variable.
// If both are valid, verify the authenticity of the request. // If both are valid, verify the authenticity of the request.
if (event.headers["x-alchemy-signature"] === undefined) throw Error("Header field 'x-alchemy-signature' was not found."); if (event.headers['x-alchemy-signature'] === undefined)
throw Error("Header field 'x-alchemy-signature' was not found.");
if (process.env.ALCHEMY_SIGNING_KEY === undefined) throw Error("ALCHEMY_SIGNING_KEY env variable not found.");
else { isTheSignatureValid(event.body, event.headers["x-alchemy-signature"], process.env.ALCHEMY_SIGNING_KEY); }; if (process.env.ALCHEMY_SIGNING_KEY === undefined)
throw Error('ALCHEMY_SIGNING_KEY env variable not found.');
else if (
!isTheSignatureValid(
event.body,
event.headers['x-alchemy-signature'],
process.env.ALCHEMY_SIGNING_KEY
)
) {
return formatJSONResponse({
status: 401,
message: 'Unauthorized',
});
}
const id = v4(); const id = v4();
const eventBody = JSON.parse(event.body); const eventBody = JSON.parse(event.body);
if (
eventBody.event.data.block.logs[1].topics[0] !=
ethers.utils.id(
'NewMint(uint256,string,string,string,string,string,string,string,string,uint24,bool,address,address,address)'
) // The first topic should be equal to the hash of the event name and its parameter types
) {
throw Error(
'The emitted event is not `NewMint`. This request is ignored.'
);
}
const topics = eventBody.event.data.block.logs[1].topics.slice(1, 4); const topics = eventBody.event.data.block.logs[1].topics.slice(1, 4);
const hexCalldata = eventBody.event.data.block.logs[1].data; const hexCalldata = eventBody.event.data.block.logs[1].data;
const decodedLogs = web3.eth.abi.decodeLog( const decodedLogs = web3.eth.abi.decodeLog(
@ -150,7 +159,7 @@ export const submitMintInfo = async (
owner: decodedLogs.owner, owner: decodedLogs.owner,
ipfsHash: decodedLogs.ipfsHash, ipfsHash: decodedLogs.ipfsHash,
domain: decodedLogs.externalURL, domain: decodedLogs.externalURL,
verificationTransactionHash: 'Not verified' verificationTransactionHash: 'Not verified',
}; };
initPrisma(); initPrisma();

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig, AxiosError } from 'axios'; import axios, { AxiosRequestConfig } from 'axios';
type BunnyCdnErrorOptions = { type BunnyCdnErrorOptions = {
name: string; name: string;

View File

@ -0,0 +1,12 @@
import * as crypto from 'crypto';
export function isTheSignatureValid(
body: string, // must be raw string body, not json transformed version of the body
signature: string, // the "lambda-signature" from header
signingKey: string // signing secret key for front-end
): boolean {
const hmac = crypto.createHmac('sha256', signingKey); // Create a HMAC SHA256 hash using the signing key
hmac.update(body, 'utf8'); // Update the token hash with the request body using utf8
const digest = hmac.digest('hex');
return signature === digest; // returns true for valid and false for invalid
}

View File

@ -3,11 +3,11 @@ schema:
dataSources: dataSources:
- kind: ethereum - kind: ethereum
name: FleekNFA name: FleekNFA
network: goerli network: mainnet # Works with the Anvil QA network also
source: source:
address: "0x8795608346Eb475E42e69F1281008AEAa522479D" # <- Proxy Contract address: "0x1CfD8455F189c56a4FBd81EB7D4118DB04616BA8" # <- Proxy Contract
abi: FleekNFA abi: FleekNFA
startBlock: 8671990 # startBlock: 8671990
mapping: mapping:
kind: ethereum/events kind: ethereum/events
apiVersion: 0.0.7 apiVersion: 0.0.7
@ -32,7 +32,7 @@ dataSources:
- ChangeAccessPointAutoApproval - ChangeAccessPointAutoApproval
abis: abis:
- name: FleekNFA - name: FleekNFA
file: ../contracts/artifacts/contracts/FleekERC721.sol/FleekERC721.json file: ../contracts/deployments/qanet/FleekERC721.json
eventHandlers: eventHandlers:
- event: Approval(indexed address,indexed address,indexed uint256) - event: Approval(indexed address,indexed address,indexed uint256)
handler: handleApproval handler: handleApproval