Skip to content

Commit

Permalink
chore: Update wallet check (#266)
Browse files Browse the repository at this point in the history
* chore: Update wallet check with new provider

* chore: Update address

* chore: Replace env vars
  • Loading branch information
garethfuller authored May 22, 2024
1 parent a4da9a7 commit 565eb61
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 119 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
INFURA_PROJECT_ID: ${{ secrets.INFURA_PROJECT_ID }}
ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }}
SANCTIONS_API_KEY: ${{ secrets.SANCTIONS_API_KEY }}
HYPERNATIVE_EMAIL: ${{ secrets.HYPERNATIVE_EMAIL }}
HYPERNATIVE_PASSWORD: ${{ secrets.HYPERNATIVE_PASSWORD }}
TENDERLY_USER: ${{ secrets.TENDERLY_USER }}
TENDERLY_PROJECT: ${{ secrets.TENDERLY_PROJECT }}
TENDERLY_ACCESS_KEY: ${{ secrets.TENDERLY_ACCESS_KEY }}
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,6 @@ You can customize your deployment with env variables. See .env.example for all p
#### Additional Settings - Rarely used

- DOMAIN_NAME - The domain that API Gateway will run on. If specified a random AWS domain will be created.
- SANCTIONS_API_KEY - TRM API key for running sanction checks.
- TENDERLY_USER - Your Tenderly user id, used by the `/tenderly` endpoints.
- TENDERLY_PROJECT - Your Tenderly project id, used by the `/tenderly` endpoints.
- TENDERLY_ACCESS_KEY - Your tenderly access key, used by the `/tenderly` endpoints.
Expand Down
6 changes: 4 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ const {
UPDATE_POOLS_INTERVAL_IN_MINUTES,
DECORATE_POOLS_INTERVAL_IN_MINUTES,
DOMAIN_NAME,
SANCTIONS_API_KEY,
HYPERNATIVE_EMAIL,
HYPERNATIVE_PASSWORD,
NETWORKS,
TENDERLY_USER,
TENDERLY_PROJECT,
Expand Down Expand Up @@ -373,7 +374,8 @@ export class BalancerPoolsAPI extends Stack {
const checkWalletLambda = new NodejsFunction(this, 'checkWalletFunction', {
entry: join(__dirname, 'src', 'lambdas', 'check-wallet.ts'),
environment: {
SANCTIONS_API_KEY: SANCTIONS_API_KEY || '',
HYPERNATIVE_EMAIL: HYPERNATIVE_EMAIL || '',
HYPERNATIVE_PASSWORD: HYPERNATIVE_PASSWORD || '',
},
runtime: Runtime.NODEJS_14_X,
timeout: Duration.seconds(15),
Expand Down
10 changes: 7 additions & 3 deletions scripts/test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
console.log('Running test.ts!');
import { fetchPoolsFromChain } from '../src/modules/chain-data/onchain';
import { handler } from '../src/lambdas/check-wallet';

(async () => {
try {
console.log('Fetching pools...');
const pools = await fetchPoolsFromChain(1);
console.log('Fetched', pools.length, 'pools');
const response = await handler({
queryStringParameters: {
address: '0x7f367cc41522ce07553e823bf3be79a889debe1b',
},
});
console.log('handler response:', response);
} catch (e) {
console.log(e);
}
Expand Down
136 changes: 58 additions & 78 deletions src/lambdas/check-wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,80 @@
import nock from 'nock';
import { handler } from './check-wallet';
import { TRMAccountDetails } from '@/modules/trm';

nock.disableNetConnect();

let trmResponse: TRMAccountDetails[] = [];

const request = {
queryStringParameters: {
address: '0x0000000000000000000000000000000000000000',
},
};

const goodResponse = {
data: [
{
address: '0x0000000000000000000000000000000000000000',
recommendation: 'Approve',
flags: [],
},
],
};

const badResponse = {
data: [
{
address: '0x0000000000000000000000000000000000000000',
recommendation: 'Deny',
flags: [
{
title: 'Blacklisted by circle (USDC)',
valence: 'Negative',
flagId: 'F-1401',
chain: 'ethereum',
lastUpdate: '2018-12-28T15:36:24Z',
events: [Array],
},
{
title: 'Received from OFAC-sanctioned',
valence: 'Negative',
flagId: 'F-1102',
chain: 'gnosis',
lastUpdate: '2024-03-19T22:06:55Z',
events: [Array],
},
{
title: 'Sent to OFAC-sanctioned',
valence: 'Negative',
flagId: 'F-1103',
chain: 'gnosis',
lastUpdate: '2024-01-22T09:57:45Z',
events: [Array],
},
],
},
],
};

describe('Wallet Check Lambda', () => {
it('Should return false for an address with no issues', async () => {
trmResponse = [
{
accountExternalId: null,
address: '0x0000000000000000000000000000000000000000',
addressRiskIndicators: [],
addressSubmitted: '0x0000000000000000000000000000000000000000',
chain: 'ethereum',
entities: [],
trmAppUrl:
'https://my.trmlabs.com/address/0x0000000000000000000000000000000000000000/eth',
},
];
nock('https://api.trmlabs.com')
.post('/public/v2/screening/addresses')
.reply(200, trmResponse);
nock('https://api.hypernative.xyz')
.post('/auth/login')
.reply(200, { data: { token: 'token' } });
nock('https://api.hypernative.xyz')
.post('/assets/reputation/addresses')
.reply(200, goodResponse);
const response = await handler(request);
console.log('Response:', response);
const body = JSON.parse(response.body);
expect(body.is_blocked).toBe(false);
});

it('Should return blocked for an address that has a Severe risk', async () => {
trmResponse = [
{
accountExternalId: null,
address: '0x0000000000000000000000000000000000000000',
addressRiskIndicators: [
{
category: 'Sanctions',
categoryId: '69',
categoryRiskScoreLevel: 15,
categoryRiskScoreLevelLabel: 'Severe',
incomingVolumeUsd:
'570037717.3324722239717737602882028941260267337',
outgoingVolumeUsd:
'573357789.82550046115536928143858188991303929335',
riskType: 'OWNERSHIP',
totalVolumeUsd: '1143395507.15797268512714304172678478403906602705',
},
{
category: 'Sanctions',
categoryId: '69',
categoryRiskScoreLevel: 15,
categoryRiskScoreLevelLabel: 'Severe',
incomingVolumeUsd: '0',
outgoingVolumeUsd:
'428172699.46703217102356766551565669942647220227',
riskType: 'COUNTERPARTY',
totalVolumeUsd: '428172699.46703217102356766551565669942647220227',
},
],
addressSubmitted: '0x0000000000000000000000000000000000000000',
chain: 'ethereum',
entities: [
{
category: 'Sanctions',
categoryId: '69',
entity: 'Lazarus Group',
riskScoreLevel: 15,
riskScoreLevelLabel: 'Severe',
trmAppUrl:
'https://my.trmlabs.com/entities/trm/75624c42-157e-4a63-8f32-070b1d1fa4d4',
trmUrn: '/entity/manual/75624c42-157e-4a63-8f32-070b1d1fa4d4',
},
{
category: 'Hacked or Stolen Funds',
categoryId: '34',
entity: 'Ronin Bridge Hack - March 2022',
riskScoreLevel: 15,
riskScoreLevelLabel: 'Severe',
trmAppUrl:
'https://my.trmlabs.com/entities/trm/9145c0ff-1544-475f-8403-40840cb051e0',
trmUrn: '/entity/manual/9145c0ff-1544-475f-8403-40840cb051e0',
},
],
trmAppUrl:
'https://my.trmlabs.com/address/0x0000000000000000000000000000000000000000/eth',
},
];
nock('https://api.trmlabs.com')
.post('/public/v2/screening/addresses')
.reply(200, trmResponse);
nock('https://api.hypernative.xyz')
.post('/auth/login')
.reply(200, { data: { token: 'token' } });
nock('https://api.hypernative.xyz')
.post('/assets/reputation/addresses')
.reply(200, badResponse);
const response = await handler(request);
const body = JSON.parse(response.body);
expect(body.is_blocked).toBe(true);
Expand Down
83 changes: 49 additions & 34 deletions src/lambdas/check-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
import { wrapHandler } from '@/modules/sentry';
import { captureException } from '@sentry/serverless';
import fetch from 'isomorphic-fetch';
import { TRMAccountDetails, TRMEntity, TRMRiskIndicator } from '@/modules/trm';
import { formatResponse } from './utils';

const SANCTIONS_ENDPOINT =
'https://api.trmlabs.com/public/v2/screening/addresses';
const { SANCTIONS_API_KEY } = process.env;
const { HYPERNATIVE_EMAIL, HYPERNATIVE_PASSWORD } = process.env;

type ReputationResponse = {
data: Array<{ flags: string[]; address: string; recommendation: string }>;
};

async function getAuthKey(): Promise<string | null> {
try {
const res = await fetch('https://api.hypernative.xyz/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: HYPERNATIVE_EMAIL || '',
password: HYPERNATIVE_PASSWORD || '',
}),
});
const {
data: { token },
} = await res.json();

return token;
} catch (err) {
captureException(err);
return null;
}
}

export const handler = wrapHandler(async (event: any = {}): Promise<any> => {
const address = event.queryStringParameters.address;
Expand All @@ -17,39 +41,30 @@ export const handler = wrapHandler(async (event: any = {}): Promise<any> => {
);
}

const apiKey = await getAuthKey();
if (!apiKey) return formatResponse(500, 'Unable to perform sanctions check');

try {
const response = await fetch(SANCTIONS_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization:
'Basic ' +
Buffer.from(`${SANCTIONS_API_KEY}:${SANCTIONS_API_KEY}`).toString(
'base64'
),
},
body: JSON.stringify([
{
address: address.toLowerCase(),
chain: 'ethereum',
const response = await fetch(
'https://api.hypernative.xyz/assets/reputation/addresses',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
]),
});

const result: TRMAccountDetails[] = await response.json();

const riskIndicators: TRMRiskIndicator[] =
result[0]?.addressRiskIndicators || [];
const entities: TRMEntity[] = result[0]?.entities || [];

const hasSevereRisk = riskIndicators.some(
indicator => indicator.categoryRiskScoreLevelLabel === 'Severe'
);
const hasSevereEntity = entities.some(
entity => entity.riskScoreLevelLabel === 'Severe'
body: JSON.stringify({
addresses: [address],
expandDetails: true,
}),
}
);

const isBlocked = hasSevereEntity || hasSevereRisk;
const {
data: [check],
}: ReputationResponse = await response.json();

const isBlocked = check.recommendation === 'Deny';

return formatResponse(
200,
Expand All @@ -61,7 +76,7 @@ export const handler = wrapHandler(async (event: any = {}): Promise<any> => {
console.log(
`Received error performing wallet check on address ${address}: ${e}`
);
captureException(e, { extra: { address } })
captureException(e, { extra: { address } });
return formatResponse(500, 'Unable to perform sanctions check');
}
});

0 comments on commit 565eb61

Please sign in to comment.