Skip to content

Commit

Permalink
feat: presale token issuance part 3 (#325)
Browse files Browse the repository at this point in the history
* chore(alchemy): export types

* chore(indexer): log process event id

* chore(indexer): log process event id

* chore: prod network conig, validate networks

* wip(indexer): validate before triggering

* chore: add try catch

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
gaboesquivel and coderabbitai[bot] authored Aug 23, 2024
1 parent 85ff357 commit 4235767
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 76 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
},
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit"
}
},
"editor.insertSpaces": true,
"editor.tabSize": 2
}
5 changes: 3 additions & 2 deletions apps/alchemy/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"name": "alchemy-hooks",
"name": "@repo/alchemy",
"module": "src/index.ts",
"types": "src/index.d.ts",
"type": "module",
"scripts": {
"push": "bun src/index.ts"
"create": "bun src/create.ts"
},
"devDependencies": {
"@types/bun": "latest"
Expand Down
30 changes: 30 additions & 0 deletions apps/alchemy/src/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Alchemy, Network, WebhookType } from 'alchemy-sdk'
import { appConfig } from './config'

async function createAddressActivityNotification() {
try {
const settings = {
authToken: appConfig.alchemyNotifyToken,
network: Network.MATIC_MAINNET, // Replace with your network.
}

const alchemy = new Alchemy(settings)
const addressActivityWebhook = await alchemy.notify.createWebhook(
appConfig.alchemyActivityWebhookUrl,
WebhookType.ADDRESS_ACTIVITY,
{
addresses: [appConfig.presaleAddress],
network: Network.MATIC_MAINNET,
},
)
console.log('Address Activity Webhook Details:')
console.log(JSON.stringify(addressActivityWebhook, null, 2))
console.log(
'Alchemy Notify address activity notification created, go to https://dashboard.alchemy.com/notify to see details of your custom hook.',
)
} catch (error) {
console.error('Failed to create address activity notification:', error)
}
}

createAddressActivityNotification()
27 changes: 1 addition & 26 deletions apps/alchemy/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1 @@
import { Alchemy, Network, WebhookType } from 'alchemy-sdk'
import { appConfig } from './config'

async function createAddressActivityNotification() {
const settings = {
authToken: appConfig.alchemyNotifyToken,
network: Network.MATIC_MAINNET, // Replace with your network.
}

const alchemy = new Alchemy(settings)
const addressActivityWebhook = await alchemy.notify.createWebhook(
appConfig.alchemyActivityWebhookUrl,
WebhookType.ADDRESS_ACTIVITY,
{
addresses: [appConfig.presaleAddress],
network: Network.MATIC_MAINNET,
},
)
console.log('Address Activity Webhook Details:')
console.log(JSON.stringify(addressActivityWebhook, null, 2))
console.log(
'Alchemy Notify address activity notification created, go to https://dashboard.alchemy.com/notify to see details of your custom hook.',
)
}

createAddressActivityNotification()
export * from './types'
32 changes: 32 additions & 0 deletions apps/alchemy/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Network } from 'alchemy-sdk'
export interface AlchemyWebhookEvent {
webhookId: string
id: string
createdAt: Date
type: AlchemyWebhookType
event: Record<any, any>
}

export type AlchemyWebhookType =
| 'MINED_TRANSACTION'
| 'DROPPED_TRANSACTION'
| 'ADDRESS_ACTIVITY'

export interface AlchemyActivity {
fromAddress: string
toAddress: string
blockNum: string
hash: string
value: number
asset: string
category: string
rawContract: {
rawValue: string
decimals: number
}
}

export interface AlchemyActivityEvent {
network: Network
activity: AlchemyActivity[]
}
1 change: 1 addition & 0 deletions apps/indexer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@dfuse/client": "^0.3.21",
"@repo/supabase": "workspace:*",
"@repo/trigger": "workspace:*",
"@repo/alchemy": "workspace:*",
"@sentry/integrations": "^7.114.0",
"@sentry/node": "^8.19.0",
"@sentry/profiling-node": "^8.26.0",
Expand Down
8 changes: 8 additions & 0 deletions apps/indexer/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ const envSchema = z.object({
(value): value is Address => isAddress(value),
'Invalid issuer address',
),
PRESALE_ADDRESS: z
.string()
.refine(
(value): value is Address => isAddress(value),
'Invalid presale address',
),

ALCHEMY_ACTIVITY_SIGNING_KEY: z.string().min(1),
})

Expand All @@ -33,6 +40,7 @@ if (!parsedEnv.success) {
}

export const appConfig = {
presaleAddress: parsedEnv.data.PRESALE_ADDRESS,
sentry: {
dsn: parsedEnv.data.SENTRY_DSN,
},
Expand Down
53 changes: 37 additions & 16 deletions apps/indexer/src/routes/alchemy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import crypto from 'crypto'
import type { AlchemyWebhookEvent } from '@repo/alchemy'
import { addressActivityTask } from '@repo/trigger'
import { Network } from 'alchemy-sdk'
import { prodChains } from 'app-env'
import type { Request, Response } from 'express'
import { appConfig } from '~/config'
import { logger } from '~/lib/logger'
import {AlchemyActivityEvent} from '@/Users/gaboesquivel/Code/smartsale/apps/alchemy/src/types';

const chainIdToNetwork: Record<number, Network> = {
1: Network.ETH_MAINNET,
137: Network.MATIC_MAINNET,
42161: Network.ARB_MAINNET,
10: Network.OPT_MAINNET,
8453: Network.BASE_MAINNET,
43114: Network.AVAX_MAINNET,
56: Network.BNB_MAINNET,
}

const networks: Network[] = prodChains.map((chain) => {
const network = chainIdToNetwork[chain.id]
if (!network) throw new Error(`Unsupported chain ID: ${chain.id}`)
return network
})

/**
* Handles incoming Alchemy webhook requests.
Expand All @@ -13,15 +33,29 @@ import { logger } from '~/lib/logger'
export async function alchemyWebhook(req: Request, res: Response) {
const evt = req.body as AlchemyWebhookEvent
logger.info(`Alchemy webhook received: ${evt.id}`)
// TODO: restore alchemy signature validation
// TODO: restore alchemy signature validation
// if (!validateAlchemySignature(req)) return res.status(401).send('Unauthorized')
// logger.info('Validated Alchemy webhook 😀')
// TODO: validate user is whitelisted

const {network, activity} = evt.event

// Validate before triggering
if (
evt.type !== 'ADDRESS_ACTIVITY' ||
!networks.includes(network) ||
(activity.asset !== 'USDC' && activity.asset !== 'USDT') ||
activity.toAddress === appConfig.presaleAddress
) {
return res.status(401).send('Unauthorized')
}


// TODO: validate addres is whitelisted

const handle = await addressActivityTask.trigger(req.body)
logger.info(`Triggered address activity task: ${JSON.stringify(handle)}`)

res.status(200).send('Webhook processed')
res.status(200).send(`Webhook ${evt.id} processed`)
}

/**
Expand All @@ -39,16 +73,3 @@ function validateAlchemySignature(req: Request): boolean {
hmac.update(payload)
return alchemySignature === hmac.digest('hex')
}

export interface AlchemyWebhookEvent {
webhookId: string
id: string
createdAt: Date
type: AlchemyWebhookType
event: Record<any, any>
}

export type AlchemyWebhookType =
| 'MINED_TRANSACTION'
| 'DROPPED_TRANSACTION'
| 'ADDRESS_ACTIVITY'
4 changes: 3 additions & 1 deletion apps/trigger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"deploy:prod": "bunx trigger.dev@beta deploy --env prod"
},
"dependencies": {
"@repo/alchemy": "workspace:*",
"@trigger.dev/sdk": "3.0.0-beta.55",
"alchemy-sdk": "^3.4.1",
"app-contracts": "workspace:*",
"app-env": "workspace:*",
"app-lib": "workspace:*",
"@trigger.dev/sdk": "3.0.0-beta.55",
"viem": "latest"
}
}
10 changes: 6 additions & 4 deletions apps/trigger/src/trigger/activity.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { AlchemyWebhookEvent, AlchemyActivity } from '@repo/alchemy'
import { logger, task } from '@trigger.dev/sdk/v3'
import { getErrorMessage } from 'app-lib'

// AlchemyWebhookEvent
export const addressActivityTask = task({
id: 'address-activity',
run: async (payload: any, { ctx }) => {
run: async (payload: AlchemyWebhookEvent) => {
try {
logger.log('Address activity', { payload, ctx })
const activity: AlchemyActivity = payload.event.activity[0]
console.log(activity)
} catch (error) {
logger.error('Error processing address activity', {
error: getErrorMessage(error),
error: (error as Error).message,
})
throw error
}
},
})

Binary file modified bun.lockb
Binary file not shown.
35 changes: 9 additions & 26 deletions packages/app-env/src/chains.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import type { Chain } from 'viem'
import {
arbitrum,
aurora,
avalanche,
base,
bsc,
celo,
cronos,
fantom,
gnosis,
harmonyOne,
kava,
mainnet,
metis,
moonbeam,
optimism,
polygon,
sepolia,
zkSync,
} from 'viem/chains'

export const eosEvmTestnet: Chain = {
Expand All @@ -41,26 +31,19 @@ export const eosEvmTestnet: Chain = {
testnet: true,
}

const prodChains: Chain[] = [
arbitrum,
avalanche,
export const prodChains: Chain[] = [
base,
celo,
mainnet,
arbitrum,
optimism,
polygon,
zkSync,
bsc,
fantom,
moonbeam,
cronos,
kava,
metis,
gnosis,
aurora,
harmonyOne,
mainnet, // Ethereum
avalanche,
bsc, // BNB Chain
]
const devChains: Chain[] = [eosEvmTestnet, sepolia]

// Note: Solana is not included as it's not an EVM-compatible chain and not supported by viem

export const devChains: Chain[] = [eosEvmTestnet, sepolia]

// note: use .entries() to get an array
export const appChains = {
Expand Down

0 comments on commit 4235767

Please sign in to comment.