Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
caafc43
Add AAO plugin with basic attest (#11453)
Feb 25, 2025
127318d
Fix aao plugin build (#11461)
Feb 25, 2025
d4b1f1c
Setup AAO plugin routing (#11472)
Feb 26, 2025
8cbafd9
AAO UI (#11499)
stereosteve Mar 4, 2025
aac03f7
Use cross fetch in AAO plugin (#11505)
Mar 4, 2025
ee28cf3
Add cors to attestation plugin (#11509)
Mar 5, 2025
dcaac1a
Use score in aao response (#11522)
stereosteve Mar 6, 2025
e4743df
fix attest endpoint (#11552)
stereosteve Mar 10, 2025
bc81da8
Add user scoring for in app notif (#11570)
Mar 11, 2025
19cc00f
Improve AAO tool UI (#11613)
Mar 13, 2025
01b93f9
Manual block/allow from AAO (#11654)
Mar 26, 2025
b666f99
Add chat block to AAO (#11728)
Apr 4, 2025
5a992a6
Update anti abuse UI to include impersonator (#11770)
Apr 7, 2025
1b0d858
Fix AAO error on missing timestamp (#11809)
Apr 15, 2025
810abfa
Add deliverable email to AAO and optimize perf (#11943)
Apr 25, 2025
d99796e
Fix AAO on local dev by querying for wallet instead of user ID (#12089)
rickyrombo May 9, 2025
7448a17
AAO attestation threshold proportional to reward + karma (#12238)
May 30, 2025
9838982
Increase aao override score (#12383)
raymondjacobson Jun 23, 2025
a9aa34e
[API-299] Add AAO check in solana-relay (#12660)
raymondjacobson Aug 5, 2025
3f59e15
Move audius-protocol to apps (#13078)
raymondjacobson Oct 2, 2025
f298f48
Update aao listen streak (#13286)
raymondjacobson Oct 22, 2025
5849f64
[API-369] Skip ratio check for dvl challenge (#13349)
schottra Oct 31, 2025
6038774
Silver tier for endless streak (#13319)
raymondjacobson Nov 6, 2025
4a1d281
Update user score calculation for users without a profile picture (#1…
Kyle-Shanks Nov 12, 2025
5c238e0
Update 'e' challenge to require verification (#13471)
raymondjacobson Dec 2, 2025
7c1adbe
Drop staging (#13540)
dylanjeffers Jan 2, 2026
23ff083
pad aao signature to 130 (#13546)
raymondjacobson Jan 5, 2026
4c2ce0f
Update rewards UI (#13637)
raymondjacobson Feb 3, 2026
e3db4e6
Drop trending playlist reward and notification (#13684)
dylanjeffers Feb 10, 2026
0ee4db4
Use non-full user endpoints (#13685)
dylanjeffers Feb 11, 2026
8a9d67e
Revert apis (#13700)
dylanjeffers Feb 13, 2026
fbe7ea8
Migrate to v1 methods and types (#13728)
dylanjeffers Feb 19, 2026
6a6e07b
Remove reward amount ratio from claim message (#14035)
raymondjacobson Apr 1, 2026
a300a46
Migrate anti-abuse-oracle from discovery-provider/apps repo with full…
dylanjeffers Apr 5, 2026
fe3923b
Fix getUser
dylanjeffers Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions apps/anti-abuse-oracle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
2 changes: 2 additions & 0 deletions apps/anti-abuse-oracle/src/actionLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions apps/anti-abuse-oracle/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -24,7 +26,7 @@ const readDotEnv = () => {
}

type Config = {
environment: string
environment: Environment
discoveryDbConnectionString: string
redisUrl: string
serverHost: string
Expand All @@ -34,7 +36,7 @@ type Config = {

let cachedConfig: Config | null = null

const readConfig = (): Config => {
export const readConfig = (): Config => {
if (cachedConfig !== null) return cachedConfig
readDotEnv()

Expand All @@ -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 }),
Expand All @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions apps/anti-abuse-oracle/src/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
23 changes: 23 additions & 0 deletions apps/anti-abuse-oracle/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -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
}
93 changes: 68 additions & 25 deletions apps/anti-abuse-oracle/src/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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`
Expand Down Expand Up @@ -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(
Expand All @@ -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) {
Expand Down Expand Up @@ -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))!

Expand Down Expand Up @@ -358,7 +390,7 @@ app.get('/attestation/ui/user', async (c) => {
<div class='flex gap-4 items-end'>
<div>
<a href={`${FRONTEND}/${user.handle}`} target='_blank'>
{user.handle}
{user.handle} ({email})
</a>
</div>
<div>{user.id}</div>
Expand Down Expand Up @@ -390,6 +422,7 @@ app.get('/attestation/ui/user', async (c) => {
<th class='text-left'>Fast Challenge Count</th>
<th class='text-left'>Following Count</th>
<th class='text-left'>Chat Block Count</th>
<th class='text-left'>Has Profile Picture</th>
<th class='text-left'>Audius Impersonator</th>
</tr>
</thead>
Expand Down Expand Up @@ -422,6 +455,16 @@ app.get('/attestation/ui/user', async (c) => {
>
{userScore.chatBlockCount}
</td>
<td
class={
!userScore.hasProfilePicture
? 'text-red-500'
: 'text-green-500'
}
>
{userScore.hasProfilePicture.toString()}{' '}
{!userScore.hasProfilePicture ? '(-100)' : ''}
</td>
<td
class={
userScore.isAudiusImpersonator
Expand Down
Loading