diff --git a/apps/anti-abuse-oracle/package.json b/apps/anti-abuse-oracle/package.json index cddd926..a2d9e35 100644 --- a/apps/anti-abuse-oracle/package.json +++ b/apps/anti-abuse-oracle/package.json @@ -15,39 +15,40 @@ "preset": "jest-presets/jest/node" }, "dependencies": { - "@audius/sdk": "4.1.1-beta.1", - "@hono/node-server": "1.19.1", + "@audius/sdk": "*", + "@audius/sdk-legacy": "npm:@audius/sdk@5.0.0", + "@hono/node-server": "^1.13.7", "@pedalboard/basekit": "*", "@pedalboard/logger": "*", "@pedalboard/storage": "*", - "body-parser": "1.20.3", - "cors": "2.8.5", - "dotenv": "16.6.1", - "express": "4.17.1", - "hono": "4.9.5", - "morgan": "1.10.1", - "pino": "9.0.0", - "postgres": "3.4.7", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.17.1", + "hono": "^4.6.17", + "morgan": "^1.10.0", + "postgres": "^3.4.5", "cross-fetch": "4.0.0" }, "devDependencies": { - "@types/body-parser": "1.19.6", + "@types/body-parser": "^1.19.0", "@types/cors": "2.8.10", "@types/express": "4.17.12", - "@types/jest": "26.0.24", - "@types/morgan": "1.9.10", - "@types/node": "15.14.9", - "@types/supertest": "2.0.16", - "esbuild": "0.14.38", - "esbuild-register": "3.3.2", + "@types/jest": "^26.0.22", + "@types/morgan": "^1.9.2", + "@types/node": "^15.12.2", + "@types/supertest": "^2.0.11", + "esbuild": "^0.14.38", + "esbuild-register": "^3.3.2", "eslint": "8.56.0", "eslint-config-custom-server": "*", + "ts-node": "10.9.2", "jest": "26.6.3", "jest-presets": "*", - "nodemon": "2.0.22", - "supertest": "6.3.4", + "nodemon": "^2.0.15", + "supertest": "^6.1.3", "tsconfig": "*", - "typescript": "5.0.4", + "typescript": "5.4.5", "envalid": "8.0.0" } } diff --git a/apps/anti-abuse-oracle/src/actionLog.ts b/apps/anti-abuse-oracle/src/actionLog.ts index 8fed690..c82979c 100644 --- a/apps/anti-abuse-oracle/src/actionLog.ts +++ b/apps/anti-abuse-oracle/src/actionLog.ts @@ -149,6 +149,7 @@ export async function getUserNormalizedScore(userId: number, wallet: string) { following_count, chat_block_count, is_audius_impersonator, + has_profile_picture, score: shadowban_score, is_blocked } = rows[0] @@ -185,6 +186,7 @@ export async function getUserNormalizedScore(userId: number, wallet: string) { chatBlockCount: chat_block_count, fingerprintCount: numberOfUserWithFingerprint, isAudiusImpersonator: is_audius_impersonator, + hasProfilePicture: has_profile_picture, isEmailDeliverable, isBlocked: is_blocked, shadowbanScore, diff --git a/apps/anti-abuse-oracle/src/config.ts b/apps/anti-abuse-oracle/src/config.ts index 5afc7c0..7658ea2 100644 --- a/apps/anti-abuse-oracle/src/config.ts +++ b/apps/anti-abuse-oracle/src/config.ts @@ -14,6 +14,8 @@ export const InstructionsProgram = new PublicKey( 'Sysvar1nstructions1111111111111111111111111' ) +export type Environment = 'dev' | 'prod' + // reads .env file based on environment const readDotEnv = () => { const environment = process.env.audius_discprov_env || 'dev' @@ -24,7 +26,7 @@ const readDotEnv = () => { } type Config = { - environment: string + environment: Environment discoveryDbConnectionString: string redisUrl: string serverHost: string @@ -34,7 +36,7 @@ type Config = { let cachedConfig: Config | null = null -const readConfig = (): Config => { +export const readConfig = (): Config => { if (cachedConfig !== null) return cachedConfig readDotEnv() @@ -44,14 +46,14 @@ const readConfig = (): Config => { default: 'dev' }), audius_discprov_url: str({ - default: 'http://audius-protocol-discovery-provider-1' + default: 'http://audius-discovery-provider-1' }), audius_db_url: str({ default: 'postgresql+psycopg2://postgres:postgres@db:5432/discovery_provider_1' }), audius_redis_url: str({ - default: 'redis://audius-protocol-discovery-provider-redis-1:6379/00' + default: 'redis://audius-discovery-redis-1:6379/00' }), anti_abuse_oracle_server_host: str({ default: '0.0.0.0' }), anti_abuse_oracle_server_port: num({ default: 6003 }), @@ -60,7 +62,7 @@ const readConfig = (): Config => { }) cachedConfig = { - environment: env.audius_discprov_env, + environment: env.audius_discprov_env as Environment, discoveryDbConnectionString: env.audius_db_url, redisUrl: env.audius_redis_url, serverHost: env.anti_abuse_oracle_server_host, diff --git a/apps/anti-abuse-oracle/src/identity.ts b/apps/anti-abuse-oracle/src/identity.ts index cd20c6f..174108e 100644 --- a/apps/anti-abuse-oracle/src/identity.ts +++ b/apps/anti-abuse-oracle/src/identity.ts @@ -54,3 +54,10 @@ export async function useEmailDeliverable(wallet: string) { ` return rows[0].isEmailDeliverable } + +export async function useEmail(userId: number) { + const rows = await sql` + select "email" from "Users" where "blockchainUserId" = ${userId} + ` + return rows[0].email +} diff --git a/apps/anti-abuse-oracle/src/sdk.ts b/apps/anti-abuse-oracle/src/sdk.ts new file mode 100644 index 0000000..7d7cc20 --- /dev/null +++ b/apps/anti-abuse-oracle/src/sdk.ts @@ -0,0 +1,23 @@ +import { AudiusSdk, sdk } from '@audius/sdk' +import { readConfig, Environment } from './config' + +const environmentToSdkEnvironment: Record< + Environment, + 'development' | 'production' +> = { + dev: 'development', + prod: 'production' +} + +let audiusSdk: AudiusSdk | undefined = undefined + +export const getAudiusSdk = () => { + if (audiusSdk === undefined) { + const config = readConfig() + audiusSdk = sdk({ + appName: 'anti-abuse-oracle', + environment: environmentToSdkEnvironment[config.environment] + }) + } + return audiusSdk +} diff --git a/apps/anti-abuse-oracle/src/server.tsx b/apps/anti-abuse-oracle/src/server.tsx index 169227f..91fbbe4 100644 --- a/apps/anti-abuse-oracle/src/server.tsx +++ b/apps/anti-abuse-oracle/src/server.tsx @@ -16,18 +16,15 @@ import { } from './actionLog' import { logger } from 'hono/logger' import { config } from './config' -import { SolanaUtils, Utils } from '@audius/sdk' +import { HashId } from '@audius/sdk' +import { SolanaUtils, Utils } from '@audius/sdk-legacy' import bn from 'bn.js' -import { userFingerprints } from './identity' +import { useEmail, userFingerprints } from './identity' import { cors } from 'hono/cors' +import { getAudiusSdk } from './sdk' -let CONTENT_NODE = 'https://creatornode2.audius.co' -let FRONTEND = 'https://audius.co' - -if (config.environment == 'stage') { - CONTENT_NODE = 'https://creatornode10.staging.audius.co' - FRONTEND = 'https://staging.audius.co' -} +const CONTENT_NODE = 'https://creatornode2.audius.co' +const FRONTEND = 'https://audius.co' let { AAO_AUTH_USER, AAO_AUTH_PASSWORD } = process.env if (!AAO_AUTH_USER) { @@ -42,6 +39,34 @@ if (!AAO_AUTH_PASSWORD) { ) } +const openRewards = [ + 'dvl', // daily volume rewards + 't', // tastemaker + 'tt', // trending + 'tut', // trending underground + 'b', // audio match buy (from verified user) + 'rd', // referred (by verified user) + 'w', // remix contest winner (from verified user) + 'cs', // cosign (from verified user) +] + +const verifiedRewards = [ + 'u', // uploads + 's', // audio match sell + 'r', // referral + 'c', // first comment + 'cp', // comment pin + 'e', // listen streak + 'fp', // first playlist + 'm', // mobile install + 'p', // profile completion + 'p1', // 250 plays + 'p2', // 1000 plays + 'p3', // 10000 plays +] + +const sdk = getAudiusSdk() + async function ensureTableExists() { try { await sql` @@ -167,19 +192,25 @@ app.post('/attestation/:handle', async (c) => { const handle = c.req.param('handle').toLowerCase() const { challengeId, challengeSpecifier, amount } = await c.req.json() - const users = - await sql`select user_id, wallet from users where handle_lc = ${handle}` - const user = users[0] - if (!user) return c.json({ error: `handle not found: ${handle}` }, 404) - - // pass / fail - const userScore = await getUserNormalizedScore(user.user_id, user.wallet) - - // Reward attestation proportional to user score confidence - if (userScore.overallScore < (amount as number) / 10) { - return c.json({ error: 'denied' }, 400) + const { data: user } = await sdk.users.getUserByHandle({ handle }) + if (!user) { + return c.json({ error: `handle not found: ${handle}` }, 404) } - + if (verifiedRewards.includes(challengeId)) { + if (!user.isVerified) { + return c.json({ error: 'denied' }, 400) + } + } + if (openRewards.includes(challengeId)) { + const userScore = await getUserNormalizedScore( + HashId.parse(user.id), + user.wallet + ) + if (userScore.overallScore < -1000) { + return c.json({ error: 'denied' }, 400) + } + } + try { const bnAmount = SolanaUtils.uiAudioToBNWaudio(amount) const identifier = SolanaUtils.constructTransferId( @@ -195,9 +226,9 @@ app.post('/attestation/:handle', async (c) => { Buffer.from(toSignStr), config.privateSignerAddress ) - const result = new bn(Uint8Array.of(...signature, recoveryId)).toString( - 'hex' - ) + const result = new bn(Uint8Array.of(...signature, recoveryId)) + .toString('hex') + .padStart(130, '0') return c.json({ result }) } catch (error) { @@ -321,6 +352,7 @@ app.get('/attestation/ui/user', async (c) => { const idOrHandle = c.req.query('q') || '1' const user = await getUser(idOrHandle) if (!user) return c.text(`user id not found: ${idOrHandle}`, 404) + const email = await useEmail(user.id) const signals = await getUserScore(user.id) const userScore = (await getUserNormalizedScore(user.id, user.wallet))! @@ -358,7 +390,7 @@ app.get('/attestation/ui/user', async (c) => {