Skip to content

Commit

Permalink
Merge pull request #1315 from tapexyz/lemon
Browse files Browse the repository at this point in the history
feat: signups
  • Loading branch information
sasicodes authored Feb 24, 2024
2 parents d5ef474 + 34feeff commit 1b05dfe
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 28 deletions.
4 changes: 3 additions & 1 deletion apps/api/.dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ LOGTAIL_API_KEY=
INGEST_REST_ENDPOINT=
IP_API_KEY=
WALLET_PRIVATE_KEY= #pk without 0x
LIVEPEER_API_TOKEN=
LIVEPEER_API_TOKEN=
RELAYER_PRIVATE_KEYS=
LS_WEBHOOK_SECRET=
36 changes: 36 additions & 0 deletions apps/api/scripts/clickhouse.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- Events
CREATE TABLE events (
id UUID DEFAULT generateUUIDv4(),
name String,
actor Nullable(String),
properties Nullable(String),
url Nullable(String),
city Nullable(String),
country LowCardinality(String),
region Nullable(String),
referrer Nullable(String),
platform String,
browser Nullable(String),
browser_version Nullable(String),
os Nullable(String),
utm_source Nullable(String),
utm_medium Nullable(String),
utm_campaign Nullable(String),
utm_term Nullable(String),
utm_content Nullable(String),
fingerprint Nullable(String),
created DateTime DEFAULT now()
) ENGINE = MergeTree
ORDER BY created;

-- Signups
CREATE TABLE signups (
id UUID DEFAULT generateUUIDv4(),
handle String,
address String,
email String,
ls_txn_id String,
txn_hash String,
created DateTime DEFAULT now()
) ENGINE = MergeTree
ORDER BY created;
33 changes: 33 additions & 0 deletions apps/api/src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,36 @@ export const PUBLIC_ETHEREUM_NODE = 'https://ethereum.publicnode.com'
export const IRYS_NODE_URL = 'http://node2.irys.xyz/tx/matic'

export const ERROR_MESSAGE = 'Something went wrong'

export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
export const TAPE_SIGNUP_PROXY_ADDRESS =
'0xb9F635c498CdC2dBf95B3A916b007fD16c5506ED'
export const TAPE_SIGNUP_PROXY_ABI = [
{
inputs: [
{
components: [
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'address', name: 'followModule', type: 'address' },
{ internalType: 'bytes', name: 'followModuleInitData', type: 'bytes' }
],
internalType: 'struct CreateProfileParams',
name: 'createProfileParams',
type: 'tuple'
},
{ internalType: 'string', name: 'handle', type: 'string' },
{
internalType: 'address[]',
name: 'delegatedExecutors',
type: 'address[]'
}
],
name: 'createProfileWithHandle',
outputs: [
{ internalType: 'uint256', name: 'profileId', type: 'uint256' },
{ internalType: 'uint256', name: 'handleId', type: 'uint256' }
],
stateMutability: 'nonpayable',
type: 'function'
}
]
2 changes: 2 additions & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import gateway from './routes/gateway'
import metadata from './routes/metadata'
import oembed from './routes/oembed'
import recommendations from './routes/recommendations'
import signup from './routes/signup'
import sts from './routes/sts'
import tail from './routes/tail'
import toggles from './routes/toggles'
Expand All @@ -26,6 +27,7 @@ app.route('/tail', tail)
app.route('/tower', tower)
app.route('/views', views)
app.route('/oembed', oembed)
app.route('/signup', signup)
app.route('/gateway', gateway)
app.route('/toggles', toggles)
app.route('/metadata', metadata)
Expand Down
117 changes: 117 additions & 0 deletions apps/api/src/routes/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { zValidator } from '@hono/zod-validator'
import { Hono } from 'hono'
import type { Address } from 'viem'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { polygon, polygonMumbai } from 'viem/chains'
import type { z } from 'zod'
import { any, object } from 'zod'

import {
ERROR_MESSAGE,
TAPE_SIGNUP_PROXY_ABI,
TAPE_SIGNUP_PROXY_ADDRESS,
ZERO_ADDRESS
} from '@/helpers/constants'

type Bindings = {
RELAYER_PRIVATE_KEYS: string
INGEST_REST_ENDPOINT: string
LS_WEBHOOK_SECRET: string
}

const app = new Hono<{ Bindings: Bindings }>()

const validationSchema = object({
meta: any(),
data: any()
})
type RequestInput = z.infer<typeof validationSchema>

app.post('/', zValidator('json', validationSchema), async (c) => {
const body = await c.req.json<RequestInput>()
try {
const secret = c.env.LS_WEBHOOK_SECRET
const encoder = new TextEncoder()
const secretKey = encoder.encode(secret)
const data = encoder.encode(await c.req.text())
const providedSignature = c.req.header('X-Signature')
const key = await crypto.subtle.importKey(
'raw',
secretKey,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
)
const digest = await crypto.subtle.sign('HMAC', key, data)
const computedSignature = Array.from(new Uint8Array(digest))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
if (providedSignature !== computedSignature) {
throw new Error('Invalid signature.')
}
} catch (error) {
return c.json({ success: false, message: 'Invalid signature' })
}

try {
const { data, meta } = body
const { custom_data, test_mode } = meta
const { address, delegatedExecutor, handle } = custom_data
const { order_number, user_email: email } = data.attributes

// env is delimited by commas with no spaces
const privateKeys = c.env.RELAYER_PRIVATE_KEYS
const allPrivateKeys = privateKeys.split(',')
const randomPrivateKey =
allPrivateKeys[Math.floor(Math.random() * allPrivateKeys.length)]

const account = privateKeyToAccount(randomPrivateKey as Address)

const client = createWalletClient({
account,
chain: test_mode ? polygonMumbai : polygon,
transport: http()
})

const hash = await client.writeContract({
abi: TAPE_SIGNUP_PROXY_ABI,
address: TAPE_SIGNUP_PROXY_ADDRESS,
args: [[address, ZERO_ADDRESS, '0x'], handle, [delegatedExecutor]],
functionName: 'createProfileWithHandle'
})

if (!test_mode) {
const clickhouseBody = `
INSERT INTO signups (
address,
email,
handle,
hash,
order_number
) VALUES (
'${address}',
'${email}',
'${handle}',
'${hash}',
'${order_number}'
)
`
const clickhouseResponse = await fetch(c.env.INGEST_REST_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: clickhouseBody
})
if (clickhouseResponse.status !== 200) {
return c.json({ success: false, message: ERROR_MESSAGE })
}
}

return c.json({ success: true, hash })
} catch (error) {
console.log('🚀 ~ signup ~ app.post ~ error:', error)
return c.json({ success: false, message: ERROR_MESSAGE })
}
})

export default app
Loading

0 comments on commit 1b05dfe

Please sign in to comment.