Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/addressResolvers/ens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { capture } from '@snapshot-labs/snapshot-sentry';
import { ens_normalize } from '@adraffy/ens-normalize';
import {
provider as getProvider,
graphQlCall,
Address,
Handle,
isSilencedError,
FetchError,
isEvmAddress
} from './utils';
import { graphQlCall } from '../helpers/utils';

export const NAME = 'Ens';
const NETWORK = '1';
Expand Down
4 changes: 2 additions & 2 deletions src/addressResolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export async function lookupAddresses(addresses: Address[]): Promise<Record<Addr
MAX_LOOKUP_ADDRESSES
);

return mapOriginalInput(addresses, result);
return mapOriginalInput(addresses, result) as Record<Address, Handle>;
}

export async function resolveNames(handles: Handle[]): Promise<Record<Handle, Address>> {
Expand All @@ -72,5 +72,5 @@ export async function resolveNames(handles: Handle[]): Promise<Record<Handle, Ad
MAX_RESOLVE_NAMES
);

return mapOriginalInput(handles, result);
return mapOriginalInput(handles, result) as Record<Handle, Address>;
}
3 changes: 2 additions & 1 deletion src/addressResolvers/lens.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { capture } from '@snapshot-labs/snapshot-sentry';
import { graphQlCall, Address, Handle, FetchError, isSilencedError, isEvmAddress } from './utils';
import { Address, Handle, FetchError, isSilencedError, isEvmAddress } from './utils';
import { graphQlCall } from '../helpers/utils';

export const NAME = 'Lens';
const API_URL = 'https://api-v2.lens.dev/graphql';
Expand Down
19 changes: 2 additions & 17 deletions src/addressResolvers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import axios from 'axios';
import snapshot from '@snapshot-labs/snapshot.js';
import { getAddress } from '@ethersproject/address';

Expand All @@ -25,20 +24,6 @@ export function withoutEmptyValues(obj: Record<string, any>) {
return Object.fromEntries(Object.entries(obj).filter(([, value]) => value));
}

export function graphQlCall(url, query: string) {
return axios({
url: url,
method: 'post',
headers: {
'Content-Type': 'application/json'
},
timeout: 5e3,
data: {
query
}
});
}

export function normalizeAddresses(addresses: Address[]): Address[] {
return addresses
.map(a => {
Expand Down Expand Up @@ -69,8 +54,8 @@ export function isSilencedError(error: any): boolean {

export function mapOriginalInput(
input: string[],
results: Record<string, string>
): Record<string, string> {
results: Record<string, string | string[]>
): Record<string, string | string[]> {
const inputLc = input.map(i => i?.toLowerCase());
const resultLc = Object.fromEntries(
Object.entries(results).map(([key, value]) => [key.toLowerCase(), value])
Expand Down
16 changes: 12 additions & 4 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import resolvers from './resolvers';
import constants from './constants.json';
import { rpcError, rpcSuccess } from './helpers/utils';
import { lookupAddresses, resolveNames } from './addressResolvers';
import following from './following';

const router = express.Router();
const TYPE_CONSTRAINTS = Object.keys(constants.resolvers).join('|');
Expand All @@ -15,11 +16,18 @@ router.post('/', async (req, res) => {
if (!method) return rpcError(res, 400, 'missing method', id);
try {
let result: any = {};
if (!Array.isArray(params)) return rpcError(res, 400, 'params must be an array of string', id);

if (method === 'lookup_addresses') result = await lookupAddresses(params);
else if (method === 'resolve_names') result = await resolveNames(params);
else return rpcError(res, 400, 'invalid method', id);
if (['lookup_addresses', 'resolve_names'].includes(method)) {
if (!Array.isArray(params))
return rpcError(res, 400, 'params must be an array of string', id);

if (method === 'lookup_addresses') result = await lookupAddresses(params);
else if (method === 'resolve_names') result = await resolveNames(params);
} else if (method === 'following') {
if (Array.isArray(params)) return rpcError(res, 400, 'params must be a string', id);

result = await following(params);
} else return rpcError(res, 400, 'invalid method', id);

if (result?.error) return rpcError(res, result.code || 500, result.error, id);
return rpcSuccess(res, result, id);
Expand Down
23 changes: 23 additions & 0 deletions src/following/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Address, normalizeAddresses } from '../addressResolvers/utils';
import * as lensFollowing from './lens';

const PROVIDERS = [lensFollowing];

export default async function following(address: Address): Promise<Address[]> {
const normalizedAddress = normalizeAddresses([address])[0];

if (!normalizedAddress) return [];

// TODO: Add cache
const result = await Promise.all(
PROVIDERS.flatMap(provider => {
try {
return provider.following(normalizedAddress);
} catch (e) {
return [];
}
})
);

return result.flat();
}
69 changes: 69 additions & 0 deletions src/following/lens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { capture } from '@snapshot-labs/snapshot-sentry';
import { Address, FetchError, isSilencedError } from '../addressResolvers/utils';
import { graphQlCall } from '../helpers/utils';

export const NAME = 'Lens';
const API_URL = 'https://api-v2.lens.dev/graphql';

async function getProfileIds(address: Address): Promise<string[]> {
const {
data: {
data: {
profiles: { items }
}
}
} = await graphQlCall(
API_URL,
`query Profile {
profiles(request: { where: { ownedBy: "${address}" } }) {
items {
id
}
}
}`
);

return items.map(({ id }) => id);
}

export async function following(targetAddress: string): Promise<string[]> {
try {
const profileIds = await getProfileIds(targetAddress);

if (profileIds.length === 0) return [];

// TODO: Handle pagination, to fetch more than 50 followings per profile
const result = await Promise.all(
profileIds.flatMap(async profileId => {
const {
data: {
data: {
following: { items }
}
}
} = await graphQlCall(
API_URL,
`query Following {
following(request: { for: "${profileId}", limit: Fifty }) {
items {
handle {
ownedBy
}
}
}
}`
);

return items.map(({ handle: { ownedBy } }) => ownedBy) as string[];
})
);

return result.flat();
} catch (e) {
if (!isSilencedError(e)) {
capture(e, { input: { addresses: targetAddress } });
}

throw new FetchError();
}
}
16 changes: 16 additions & 0 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import axios from 'axios';

export function rpcSuccess(res, result, id) {
res.json({
jsonrpc: '2.0',
Expand All @@ -17,3 +19,17 @@ export function rpcError(res, code, e, id) {
id
});
}

export function graphQlCall(url: string, query: string) {
return axios({
url: url,
method: 'post',
headers: {
'Content-Type': 'application/json'
},
timeout: 5e3,
data: {
query
}
});
}