-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added a backend package that will read indexer data and forward it. ### Endpoints #### Deploy - /deploy - Returns all deploys - /deploy/:token - Returns specific token deploy - /deploy/owner/:owner - Returns token deploys of an owner #### Launch - /launch - Returns all launches - /launch/:token - Returns specific token launch #### Transfer - /transfer - Returns all transfers - /transfer/:token - Returns transfers of a specific token
- Loading branch information
Showing
18 changed files
with
770 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
PORT=3001 | ||
INDEXER_DB_CONNECTION_STRING=postgresql://admin:password@localhost:5432/indexer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
extends: ['../../.eslintrc.js', '@uniswap/eslint-config/node'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"name": "backend", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
"scripts": { | ||
"start": "tsx src/index.ts", | ||
"build": "pkgroll", | ||
"lint": "eslint . --max-warnings=0 --ignore-path ../../.eslintignore", | ||
"lint:fix": "eslint src --fix" | ||
}, | ||
"dependencies": { | ||
"cors": "^2.8.5", | ||
"drizzle-orm": "^0.32.0", | ||
"express": "^4.19.2", | ||
"helmet": "^7.1.0", | ||
"pg": "^8.12.0" | ||
}, | ||
"devDependencies": { | ||
"@types/cors": "^2.8.17", | ||
"@types/express": "^4.17.21", | ||
"@types/node": "^20.14.10", | ||
"@types/pg": "^8.11.6", | ||
"@uniswap/eslint-config": "^1.2.0", | ||
"dotenv": "^16.4.5", | ||
"drizzle-kit": "^0.23.0", | ||
"eslint": "^8.0.0", | ||
"pkgroll": "^2.0.2", | ||
"tsx": "^4.7.3", | ||
"typescript": "^5.4.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import 'dotenv/config' | ||
|
||
import cors from 'cors' | ||
import express from 'express' | ||
import helmet from 'helmet' | ||
|
||
import router from './router' | ||
|
||
const app = express() | ||
|
||
app.use(cors()) | ||
app.use(helmet()) | ||
app.use(express.urlencoded({ extended: false })) | ||
app.use(express.json()) | ||
|
||
app.use('/', router) | ||
|
||
const PORT = Number(process.env.PORT) || 3001 | ||
|
||
app.listen(PORT, () => { | ||
console.info(`Express server started listening on port ${PORT}`) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import express from 'express' | ||
|
||
import deploy from './routes/deploy' | ||
import health from './routes/health' | ||
import launch from './routes/launch' | ||
import transfer from './routes/transfer' | ||
|
||
const Router = express.Router() | ||
|
||
Router.use('/health', health) | ||
|
||
Router.use('/deploy', deploy) | ||
Router.use('/launch', launch) | ||
Router.use('/transfer', transfer) | ||
|
||
export default Router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { eq } from 'drizzle-orm' | ||
import express from 'express' | ||
|
||
import { db, deploy } from '../services/db' | ||
import { ErrorCode } from '../utils/error' | ||
import { isValidStarknetAddress } from '../utils/helpers' | ||
import { HTTPStatus } from '../utils/http' | ||
|
||
const Router = express.Router() | ||
|
||
Router.get('/', async (req, res) => { | ||
try { | ||
const deploys = await db.select().from(deploy) | ||
|
||
res.status(HTTPStatus.OK).send(deploys) | ||
} catch (error) { | ||
res.status(HTTPStatus.InternalServerError).send(error) | ||
} | ||
}) | ||
|
||
Router.get('/:token', async (req, res) => { | ||
try { | ||
const { token } = req.params | ||
|
||
if (!isValidStarknetAddress(token)) { | ||
res.status(HTTPStatus.BadRequest).send({ code: ErrorCode.BAD_REQUEST, message: 'Invalid token address' }) | ||
return | ||
} | ||
|
||
const deploys = await db.select().from(deploy).where(eq(deploy.token, token)).limit(1) | ||
|
||
if (!deploys[0]) { | ||
res.status(HTTPStatus.NotFound).send({ code: ErrorCode.TOKEN_NOT_FOUND, message: 'Token not found' }) | ||
return | ||
} | ||
|
||
res.status(HTTPStatus.OK).send(deploys[0]) | ||
} catch (error) { | ||
res.status(HTTPStatus.InternalServerError).send(error) | ||
} | ||
}) | ||
|
||
Router.get('/owner/:owner', async (req, res) => { | ||
try { | ||
const { owner } = req.params | ||
|
||
if (!isValidStarknetAddress(owner)) { | ||
res.status(HTTPStatus.BadRequest).send({ code: ErrorCode.BAD_REQUEST, message: 'Invalid owner address' }) | ||
return | ||
} | ||
|
||
const deploys = await db.select().from(deploy).where(eq(deploy.owner, owner)) | ||
|
||
res.status(HTTPStatus.OK).send(deploys) | ||
} catch (error) { | ||
res.status(HTTPStatus.InternalServerError).send(error) | ||
} | ||
}) | ||
|
||
export default Router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import express from 'express' | ||
|
||
import { HTTPStatus } from '../utils/http' | ||
|
||
const Router = express.Router() | ||
|
||
Router.get('/', async (req, res) => { | ||
res.status(HTTPStatus.OK).send() | ||
}) | ||
|
||
export default Router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { eq } from 'drizzle-orm' | ||
import express from 'express' | ||
|
||
import { db, launch } from '../services/db' | ||
import { ErrorCode } from '../utils/error' | ||
import { isValidStarknetAddress } from '../utils/helpers' | ||
import { HTTPStatus } from '../utils/http' | ||
|
||
const Router = express.Router() | ||
|
||
Router.get('/', async (req, res) => { | ||
try { | ||
const launches = await db.select().from(launch) | ||
|
||
res.status(HTTPStatus.OK).send(launches) | ||
} catch (error) { | ||
res.status(HTTPStatus.InternalServerError).send(error) | ||
} | ||
}) | ||
|
||
Router.get('/:token', async (req, res) => { | ||
try { | ||
const { token } = req.params | ||
|
||
if (!isValidStarknetAddress(token)) { | ||
res.status(HTTPStatus.BadRequest).send({ code: ErrorCode.BAD_REQUEST, message: 'Invalid token address' }) | ||
return | ||
} | ||
|
||
const launches = await db.select().from(launch).where(eq(launch.token, token)).limit(1) | ||
|
||
if (!launches[0]) { | ||
res.status(HTTPStatus.NotFound).send({ code: ErrorCode.TOKEN_NOT_FOUND, message: 'Token not found' }) | ||
return | ||
} | ||
|
||
res.status(HTTPStatus.OK).send(launches[0]) | ||
} catch (error) { | ||
res.status(HTTPStatus.InternalServerError).send(error) | ||
} | ||
}) | ||
|
||
export default Router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { eq } from 'drizzle-orm' | ||
import express from 'express' | ||
|
||
import { db, transfer } from '../services/db' | ||
import { ErrorCode } from '../utils/error' | ||
import { isValidStarknetAddress } from '../utils/helpers' | ||
import { HTTPStatus } from '../utils/http' | ||
|
||
const Router = express.Router() | ||
|
||
Router.get('/', async (req, res) => { | ||
try { | ||
const transfers = await db.select().from(transfer) | ||
|
||
res.status(HTTPStatus.OK).send(transfers) | ||
} catch (error) { | ||
res.status(HTTPStatus.InternalServerError).send(error) | ||
} | ||
}) | ||
|
||
Router.get('/:token', async (req, res) => { | ||
try { | ||
const { token } = req.params | ||
|
||
if (!isValidStarknetAddress(token)) { | ||
res.status(HTTPStatus.BadRequest).send({ code: ErrorCode.BAD_REQUEST, message: 'Invalid token address' }) | ||
return | ||
} | ||
|
||
const transfers = await db.select().from(transfer).where(eq(transfer.token, token)) | ||
|
||
res.status(HTTPStatus.OK).send(transfers) | ||
} catch (error) { | ||
res.status(HTTPStatus.InternalServerError).send(error) | ||
} | ||
}) | ||
|
||
export default Router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { drizzle } from 'drizzle-orm/node-postgres' | ||
import { bigint, pgTable, text, timestamp } from 'drizzle-orm/pg-core' | ||
import { Pool } from 'pg' | ||
|
||
if (!process.env.INDEXER_DB_CONNECTION_STRING) { | ||
throw new Error('INDEXER_DB_CONNECTION_STRING environment variable is not set') | ||
} | ||
|
||
const pool = new Pool({ | ||
connectionString: process.env.INDEXER_DB_CONNECTION_STRING, | ||
}) | ||
|
||
export const db = drizzle(pool) | ||
|
||
const commonSchema = { | ||
cursor: bigint('_cursor', { mode: 'number' }), | ||
createdAt: timestamp('created_at', { mode: 'date', withTimezone: false }), | ||
|
||
network: text('network'), | ||
blockHash: text('block_hash'), | ||
blockNumber: bigint('block_number', { mode: 'number' }), | ||
blockTimestamp: timestamp('block_timestamp', { mode: 'date', withTimezone: false }), | ||
transactionHash: text('transaction_hash'), | ||
} | ||
|
||
export const deploy = pgTable('unrugmeme_deploy', { | ||
...commonSchema, | ||
|
||
token: text('memecoin_address').primaryKey(), | ||
owner: text('owner_address'), | ||
name: text('name'), | ||
symbol: text('symbol'), | ||
initialSupply: text('initial_supply'), | ||
}) | ||
|
||
export const launch = pgTable('unrugmeme_launch', { | ||
...commonSchema, | ||
|
||
token: text('memecoin_address').primaryKey(), | ||
quoteToken: text('quote_token'), | ||
exchangeName: text('exchange_name'), | ||
}) | ||
|
||
export const transfer = pgTable('unrugmeme_transfers', { | ||
...commonSchema, | ||
|
||
transferId: text('transfer_id').primaryKey(), | ||
from: text('from_address'), | ||
to: text('to_address'), | ||
token: text('memecoin_address'), | ||
amount: text('amount'), | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
const ErrorCodesArray = ['BAD_REQUEST', 'TOKEN_NOT_FOUND'] as const | ||
|
||
export type ErrorCode = (typeof ErrorCodesArray)[number] | ||
|
||
export const ErrorCode = Object.fromEntries(ErrorCodesArray.map((code) => [code, code])) as { | ||
[code in ErrorCode]: code | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Checks if a given string is a valid StarkNet address. | ||
* | ||
* A valid StarkNet address must start with '0x' followed by 63 or 64 hexadecimal characters. | ||
* | ||
* @param address - The string to be tested against the StarkNet address format. | ||
* @returns `true` if the string is a valid StarkNet address, otherwise `false`. | ||
*/ | ||
export function isValidStarknetAddress(address: string): boolean { | ||
const regex = /^0x[0-9a-fA-F]{50,64}$/ | ||
return regex.test(address) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export const HTTPStatus = { | ||
OK: 200, | ||
Created: 201, | ||
Accepted: 202, | ||
NoContent: 204, | ||
ResetContent: 205, | ||
PartialContent: 206, | ||
|
||
MovedPermanently: 301, | ||
Found: 302, | ||
SeeOther: 303, | ||
TemporaryRedirect: 307, | ||
PermanentRedirect: 308, | ||
|
||
BadRequest: 400, | ||
Unauthorized: 401, | ||
Forbidden: 403, | ||
NotFound: 404, | ||
NotAcceptable: 406, | ||
Timeout: 408, | ||
Gone: 410, | ||
TooManyRequests: 429, | ||
|
||
InternalServerError: 500, | ||
NotImplemented: 501, | ||
BadGateway: 502, | ||
ServiceUnavailable: 503, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"compilerOptions": { | ||
"rootDir": ".", | ||
"baseUrl": "." | ||
}, | ||
"include": ["src/**/*"], | ||
"exclude": ["node_modules"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.