Skip to content

Commit

Permalink
start init dynamic + otp email and jwt backend
Browse files Browse the repository at this point in the history
  • Loading branch information
MSghais committed Oct 8, 2024
1 parent d5d74fb commit 0469c3e
Show file tree
Hide file tree
Showing 16 changed files with 409 additions and 54 deletions.
4 changes: 3 additions & 1 deletion apps/data-backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ WEBHOOK_DOMAIN="https://data-backend.xyz"
BACKEND_DATABASE_URL=
APP_URL_WEB="http://localhost:8081"

FUNKIT_API_KEY=
FUNKIT_API_KEY=

JWT_SECRET=
4 changes: 4 additions & 0 deletions apps/data-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,20 @@
"@prisma/client": "^5.18.0",
"@telegraf/types": "^7.1.0",
"apollo-server": "^3.13.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"fastify": "^4.28.1",
"fastify-plugin": "^4.5.1",
"graphql": "^16.9.0",
"helmet": "^7.1.0",
"indexer-prisma": "workspace:*",
"jsonwebtoken": "^9.0.2",
"nodemailer": "^6.9.15",
"pg": "^8.12.0",
"pg-promise": "^11.9.1",
"telegraf": "^4.16.3",
"twilio": "^5.3.3",
"typescript": "^5.5.4"
},
"devDependencies": {
Expand Down
7 changes: 6 additions & 1 deletion apps/data-backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,13 @@ model unrugmeme_transfers {
}

model registration {
contract_addres String @id
contract_address String @id
nickname String?
created_at DateTime? @default(now()) @db.Timestamp(6)
is_confirmed Boolean? @default(false)
phoneNumber String?
email String?
starknetAddress String?
evmAddress String?
}
37 changes: 37 additions & 0 deletions apps/data-backend/src/constants/contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { constants } from 'starknet'

type SupportedChainId = Exclude<constants.StarknetChainId, typeof constants.StarknetChainId.SN_GOERLI>

type AddressesMap = Record<SupportedChainId, string>

export const BLANK_ACCOUNT_CLASS_HASH = '0x1fa186ff7ea06307ded0baa1eb7648afc43618b92084da1110a9c0bd2b6bf56'

export enum Entrypoint {
DEPLOY_ACCOUNT = 'deploy_account',
EXECUTE_FROM_OUTSIDE = 'execute_from_outside_v2',
}

export const VAULT_FACTORY_ADDRESSES: AddressesMap = {
[constants.StarknetChainId.SN_MAIN]: '0x410da9af28e654fa93354430841ce7c5f0c2c17cc92971fb23d3d4f826d9834',
[constants.StarknetChainId.SN_SEPOLIA]: '0x33498f0d9e6ebef71b3d8dfa56501388cfe5ce96cba81503cd8572be92bd77c',
}

export const DEFAULT_NETWORK_NAME = constants.NetworkName.SN_SEPOLIA

// eslint-disable-next-line import/no-unused-modules
export const SN_CHAIN_ID = (constants.StarknetChainId[(process.env.SN_NETWORK ?? '') as constants.NetworkName] ??
constants.StarknetChainId[DEFAULT_NETWORK_NAME]) as SupportedChainId

// const NODE_URLS = {
// [constants.StarknetChainId.SN_MAIN]: (apiKey: string) => `https://rpc.nethermind.io/mainnet-juno/?apikey=${apiKey}`,
// [constants.StarknetChainId.SN_SEPOLIA]: (apiKey: string) =>
// `https://rpc.nethermind.io/sepolia-juno/?apikey=${apiKey}`,
// }

const NODE_URLS = {
[constants.StarknetChainId.SN_MAIN]: (apiKey: string) => `https://starknet-sepolia.g.alchemy.com/v2/${apiKey}`,
[constants.StarknetChainId.SN_SEPOLIA]: (apiKey: string) =>
`https://starknet-sepolia.g.alchemy.com/v2/${apiKey}`,
}

export const NODE_URL = NODE_URLS[SN_CHAIN_ID](process.env.RPC_NODE_API_KEY!)
33 changes: 30 additions & 3 deletions apps/data-backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import Fastify from "fastify";
import fastifyCors from "@fastify/cors";
import declareRoutes from "./router";
import { launchBot, sendWebAppButton } from "./services/telegram-app";

import jwt from "jsonwebtoken"
import { launchBot } from "./services/telegram-app";
import { NODE_URL } from "./constants/contracts";
import nodemailer from "nodemailer"
import bcrypt from "bcryptjs"
import { Account } from 'starknet'
import twilio from 'twilio'
/**
* @type {import('fastify').FastifyInstance} Instance of Fastify
*/
Expand All @@ -15,8 +20,30 @@ fastify.register(fastifyCors, {
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true
});
declareRoutes(fastify);

const JWT_SECRET= process.env.JWT_SECRET

// const deployer = new Account({ nodeUrl: NODE_URL }, process.env.ACCOUNT_ADDRESS ?? "", process.env.ACCOUNT_PRIVATE_KEY ?? "")
// const twilio_services = twilio(process.env.TWILIO_ACCOUNT_SSID, process.env.TWILIO_AUTH_TOKEN).
// verify.v2.services(
// process.env.TWILIO_SERVICE_ID ?? '',
// )

declareRoutes(fastify,
// deployer,
// twilio_services
);

// Middleware to verify JWT
fastify.decorate('verifyJWT', async (request, reply) => {
try {
const token = request.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, JWT_SECRET);
request.user = decoded;
} catch (error) {
reply.code(401).send({ error: 'Unauthorized' });
}
});
const port = Number(process.env.PORT) || 5050;
const host = process.env.NODE_ENV == "production" ? "0.0.0.0" : "127.0.0.1";

Expand Down
13 changes: 12 additions & 1 deletion apps/data-backend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ import allTransactionsRoute from "./routes/indexer/all-transactions";
import createFunkitStripeCheckout from "./routes/funkit/create_funkit_stripe_checkout";
import getFunkitStripeCheckoutQuote from "./routes/funkit/get_funkit_stripe_checkout_quote";
import getFunkitStripeCheckoutStatus from "./routes/funkit/get_funkit_stripe_checkout_status";
// import getOtp from "./routes/otp/getOtp";
// import verifyOtp from "./routes/otp/verifyOtp";
// import type { Account } from 'starknet'
// import { ServiceContext } from 'twilio/lib/rest/verify/v2/service'

function declareRoutes(fastify: FastifyInstance) {
function declareRoutes(
fastify: FastifyInstance,
// deployer: Account,
// twilio_services: ServiceContext
) {
fastify.register(buyCoinRoute);
fastify.register(deployLaunchRoute);
fastify.register(deployTokenRoute);
Expand All @@ -24,6 +32,9 @@ function declareRoutes(fastify: FastifyInstance) {
fastify.register(createFunkitStripeCheckout);
fastify.register(getFunkitStripeCheckoutQuote);
fastify.register(getFunkitStripeCheckoutStatus);
// fastify.register(getOtp, twilio_services?.verifications);
// fastify.register(verifyOtp, deployer, twilio_services?.verificationChecks);
// fastify.register(verifyOtp, [deployer, twilio_services?.verificationChecks]);
}

export default declareRoutes;
82 changes: 82 additions & 0 deletions apps/data-backend/src/routes/otp/getOtp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { FastifyInstance } from 'fastify'
import { VerificationListInstance } from 'twilio/lib/rest/verify/v2/service/verification'
import { prisma } from "@prisma/client";

interface GetOtpRequestBody {
phone_number: string
nickname: string
}

async function getOtp(fastify: FastifyInstance, twilio_verification: VerificationListInstance) {
fastify.post<{ Body: GetOtpRequestBody }>(
'/get_otp',

{
schema: {
body: {
type: 'object',
required: ['phone_number', 'nickname'],
properties: {
phone_number: { type: 'string', pattern: '^\\+[1-9]\\d{1,14}$' },
nickname: { type: 'string', pattern: '^[A-Za-z]{1,20}$' },
},
},
},
},

async (request, reply) => {
try {
const { phone_number, nickname } = request.body

// validating if phone number exists in db

const record_by_phone_number = await prisma.registration.findFirst({
where:{
phoneNumber:phone_number
},
orderBy: { created_at: "desc" },

})

if (!record_by_phone_number.length) {
try {
await prisma.registration.create({
data:{
phoneNumber:phone_number,
nickname
},
})


} catch (error: any) {
fastify.log.error(error)
if (error.code === '23505') {
return reply.code(409).send({
message: 'A user with the given phone number already exists.',
})
}
return reply.code(500).send({ message: 'Internal server error' })
}
}

const send_msg_res = await twilio_verification.create({
to: phone_number,
channel: 'sms',
})
if (send_msg_res.status != 'pending') {
fastify.log.error('Error sending message to phone number')
return reply.code(500).send({
message: 'We are facing some issues. Please try again later',
})
}

return reply.code(200).send({ ok: true })
} catch (error) {
fastify.log.error(error)
return reply.code(500).send({ message: 'Internal Server Error' })
}
},
)
}

export default getOtp
137 changes: 137 additions & 0 deletions apps/data-backend/src/routes/otp/verifyOtp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import type { FastifyInstance } from 'fastify'
import { type Account, addAddressPadding, uint256 } from 'starknet'
import { VerificationCheckListInstance } from 'twilio/lib/rest/verify/v2/service/verificationCheck'
import { Entrypoint, SN_CHAIN_ID, VAULT_FACTORY_ADDRESSES } from '../../constants/contracts'
import { hashPhoneNumber } from '../../utils/format'
import { computeAddress } from '../../utils/address'
import { prisma } from "@prisma/client";


interface VerifyOtpRequestBody {
phone_number: string
sent_otp: string
public_key_x: string
public_key_y: string
}

async function verifyOtp(
fastify: FastifyInstance,
deployer: Account,
twilio_verification: VerificationCheckListInstance,
) {
fastify.post<{
Body: VerifyOtpRequestBody
}>(
'/verify_otp',

{
schema: {
body: {
type: 'object',
required: ['phone_number', 'public_key_x', 'public_key_y', 'sent_otp'],
properties: {
phone_number: { type: 'string', pattern: '^\\+[1-9]\\d{1,14}$' },
sent_otp: { type: 'string', pattern: '^[0-9]{6}$' },
public_key_x: { type: 'string', pattern: '^0x[0-9a-fA-F]+$' },
public_key_y: { type: 'string', pattern: '^0x[0-9a-fA-F]+$' },
},
},
},
},

async (request, reply) => {
try {
const { phone_number, sent_otp, public_key_x, public_key_y } = request.body

// Create a verification request to twilio
const response = await twilio_verification
.create({
to: phone_number,
code: sent_otp,
})
.catch((error) => {
fastify.log.error(error)
return { status: 'unrequested' }
})

// The status of the verification. Can be: `pending`, `approved`, `canceled`, `max_attempts_reached`, `deleted`, `failed` or `expired`.
if (response.status != 'approved') {
return reply.code(400).send({
message: `Otp is ${response.status}.`,
})
}

// check if user is already registered
const user = prisma.registration.findFirst({
where: {
phoneNumber: phone_number,
}
})

// user is already registered
if (user?.is_confirmed) {
return reply.code(200).send({
contract_address: user.contract_address,
})
}

// public key, approver, limit
const { transaction_hash } = await deployer.execute({
contractAddress: VAULT_FACTORY_ADDRESSES[SN_CHAIN_ID],
calldata: [
hashPhoneNumber(phone_number),
uint256.bnToUint256(public_key_x),
uint256.bnToUint256(public_key_y),
],
entrypoint: Entrypoint.DEPLOY_ACCOUNT,
})

const contractAddress = addAddressPadding(computeAddress(phone_number))

fastify.log.info(
'Deploying account: ',
contractAddress,
' for: ',
phone_number,
' with tx hash: ',
transaction_hash,
)

if (!transaction_hash) {
return reply.code(500).send({
message: 'Error in deploying smart contract. Please try again later',
})
}

// update the user record as confirmed and add the account address

const userUpdated = await prisma.registration.update({
where: {
phoneNumber: phone_number
},
data: {
is_confirmed: true,
contract_address: contractAddress
}
})
// await fastify.db
// .update(registration)
// .set({
// is_confirmed: true,
// contract_address: contractAddress,
// })
// .where(eq(registration.phone_number, phone_number))

return reply.code(200).send({
contract_address: contractAddress,
})
} catch (error) {
console.log(error)
fastify.log.error(error)
return reply.code(500).send({ message: 'Internal Server Error' })
}
},
)
}

export default verifyOtp
12 changes: 12 additions & 0 deletions apps/data-backend/src/utils/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { hash } from 'starknet'
import { hashPhoneNumber } from './format'
import { BLANK_ACCOUNT_CLASS_HASH, SN_CHAIN_ID, VAULT_FACTORY_ADDRESSES } from '../constants/contracts'

export function computeAddress(phoneNumber: string) {
return hash.calculateContractAddressFromHash(
hashPhoneNumber(phoneNumber),
BLANK_ACCOUNT_CLASS_HASH,
[],
VAULT_FACTORY_ADDRESSES[SN_CHAIN_ID],
)
}
Loading

0 comments on commit 0469c3e

Please sign in to comment.