Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Display LXP-L score from OBL #93

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
223 changes: 223 additions & 0 deletions packages/functions/global-api-v3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import type { Activation } from '@consensys/linea-voyager/src/types';

const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Content-Type': 'application/json',
};

export const LXP_CONTRACT_ADDRESS =
'0xd83af4fbD77f3AB65C3B1Dc4B38D7e67AEcf599A';

const LINEASCAN_API_KEY = process.env.LINEASCAN_API_KEY;

/**
* This function is called on every network call.
* @param event - The event object.
* @param event.httpMethod - The HTTP method used by the caller.
* @param event.body - The HTTP request body.
* @returns The response object.
*/
export async function handler(event: {
queryStringParameters: { address: string; isLineascan: boolean };
body: string;
httpMethod: string;
}) {
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 204,
headers,
body: '',
};
}

if (event.httpMethod === 'GET') {
if (!LINEASCAN_API_KEY) {
return {
statusCode: 500,
body: JSON.stringify({
message: 'Lineascan API key not set',
}),
};
}

const { address, isLineascan } = event.queryStringParameters;

if (!address || address == '') {
return {
statusCode: 400,
body: JSON.stringify({
message: 'Missing address parameter',
}),
};
}

const [activations, pohStatus, openBlockScore, lxpBalance, name] =
await Promise.all([
getActivations(),
fetchPohStatus(address),
getOpenBlockScore(address.toLowerCase()),
isLineascan
? fetchBalanceFromLineascan(LXP_CONTRACT_ADDRESS, address)
: Promise.resolve('0'),
isLineascan ? fetchLineaEns(address.toLowerCase()) : undefined,
]);

return {
statusCode: 200,
headers,
body: JSON.stringify({
activations,
pohStatus,
openBlockScore,
lxpBalance,
name,
}),
};
}

return {
statusCode: 405,
headers,
body: JSON.stringify({
message: 'Method not allowed',
}),
};
}

async function getData(
url: string,
additionalHeaders?: Record<string, string>,
) {
const response = await fetch(url, {
method: 'GET',
headers: {
...additionalHeaders,
'Content-Type': 'application/json',
},
});

if (!response.ok) {
console.error(`Call to ${url} failed with status ${response.status}`);
throw new Error(`HTTP error! Status: ${response.status}`);
}

return response.json();
}

async function postData(url: string, data: Record<string, string>) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

if (!response.ok) {
console.error(`Call to ${url} failed with status ${response.status}`);
throw new Error(`HTTP error! Status: ${response.status}`);
}

return response.json();
}

/**
* Get current active activations from Contentful.
* @returns The activations object.
*/
async function getActivations() {
const { CONTENTFUL_API_KEY } = process.env;
const GET_XP_TAG = '4WJBpV24ju4wlbr6Kvi2pt';

if (!CONTENTFUL_API_KEY) {
return {
statusCode: 500,
body: JSON.stringify({
message: 'Contentful API key not set',
}),
};
}

try {
const res = await getData(
'https://api.contentful.com/spaces/64upluvbiuck/environments/master/entries/?content_type=activationsCard',
{
Authorization: CONTENTFUL_API_KEY,
},
);

const allActivations = res?.items ?? [];
return allActivations.filter((activation: Activation) => {
const isCurrent =
new Date(activation?.fields?.endDate?.['en-US']) > new Date();
const hasXpTag = activation?.fields?.tags?.['en-US']?.find(
(tag) => tag?.sys?.id === GET_XP_TAG,
);
return isCurrent && hasXpTag;
});
} catch (error) {
return [];
}
}

/**
* Get the current OpenBlock XP score for an address.
* @param address - The address to get the OpenBlock XP score for.
* @returns The OpenBlock XP score for the address.
*/
async function getOpenBlockScore(address: string) {
try {
const res = await getData(
`https://kx58j6x5me.execute-api.us-east-1.amazonaws.com/linea/userPointsSearchMetaMask?user=${address}`,
{
Origin: 'snap://linea-voyager',
},
);

return res[0].xp;
} catch (error) {
return 0;
}
}

async function fetchBalanceFromLineascan(
tokenBalance: string,
address: string,
) {
try {
const res = await getData(
`https://api.lineascan.build/api?module=account&action=tokenbalance&contractaddress=${tokenBalance}&address=${address}&tag=latest&apiKey=${LINEASCAN_API_KEY}`,
);

return res.result as string;
} catch (e) {
return '0';
}
}

async function fetchPohStatus(address: string) {
try {
const pohPayload = await getData(
` https://linea-xp-poh-api.linea.build/poh/${address}`,
);
return pohPayload.poh as boolean;
} catch (e) {
return false;
}
}

async function fetchLineaEns(address: string) {
try {
const res = await postData(
`https://api.studio.thegraph.com/query/69290/ens-linea-mainnet/version/latest`,
{
query: `query getNamesForAddress {domains(first: 1, where: {and: [{or: [{owner: \"${address}\"}, {registrant: \"${address}\"}, {wrappedOwner: \"${address}\"}]}, {parent_not: \"0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2\"}, {or: [{expiryDate_gt: \"1721033912\"}, {expiryDate: null}]}, {or: [{owner_not: \"0x0000000000000000000000000000000000000000\"}, {resolver_not: null}, {and: [{registrant_not: \"0x0000000000000000000000000000000000000000\"}, {registrant_not: null}]}]}]}) {...DomainDetailsWithoutParent}} fragment DomainDetailsWithoutParent on Domain {name}`,
},
);
return res.data.domains[0].name as string;
} catch (e) {
return undefined;
}
}
4 changes: 1 addition & 3 deletions packages/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
"allow-scripts": "yarn workspace root allow-scripts"
},
"dependencies": {
"google-auth-library": "^9.6.3",
"google-spreadsheet": "^4.1.1",
"viem": "^2.7.22"
"viem": "^2.21.50"
}
}
3 changes: 1 addition & 2 deletions packages/snap/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"lxp": "You have {count} LXP",
"balance": "LXP Balance",
"balanceLxpL": "LXP-L Balance",
"pendingBalanceLxpL": "Pending LXP-L",
"address": "Address",
"pohStatus": "POH Status",
"activations": {
Expand All @@ -31,7 +30,7 @@
},
"help": "Your LXP balance shows only completed onchain drops. It does not include activations pending drops.",
"viewBalance": "View LXP balance on Lineascan",
"viewLxpLBalance": "View LXP-L balance on Lineascan",
"viewLxpLBalance": "View LXP-L balance on OpenBlock",
"completePOH": "Complete Proof of Humanity",
"exploreAll": "Explore All Linea Activations",
"manageLineaEns": "Manage your Linea ENS domain",
Expand Down
3 changes: 1 addition & 2 deletions packages/snap/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"lxp": "Tienes {count} LXP",
"balance": "Balance LXP",
"balanceLxpL": "Balance LXP-L",
"pendingBalanceLxpL": "Pendiente LXP-L",
"address": "Cuenta",
"pohStatus": "Estado de POH",
"activations": {
Expand All @@ -31,7 +30,7 @@
},
"help": "Tu balance de LXP solo incluye entregas completadas. No incluye entregas pendientes de finalizar.",
"viewBalance": "Ve tu balance de LXP en Lineascan",
"viewLxpLBalance": "Ve tu balance de LXP-L en Lineascan",
"viewLxpLBalance": "Ve tu balance de LXP-L en OpenBlock",
"completePOH": "Verifica tu estado de POH",
"exploreAll": "Explora todas las activaciones",
"manageLineaEns": "Gestiona tu dominio Linea ENS",
Expand Down
3 changes: 1 addition & 2 deletions packages/snap/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"lxp": "Vous avez {count} LXP",
"balance": "Balance LXP",
"balanceLxpL": "Balance LXP-L",
"pendingBalanceLxpL": "LXP-L en attente",
"address": "Adresse",
"pohStatus": "Statut POH",
"activations": {
Expand All @@ -31,7 +30,7 @@
},
"help": "Votre balance de LXP ne montre que les drops onchain terminés. Elle n'inclut pas les activations en attente de drops.",
"viewBalance": "Voir votre balance de LXP sur Lineascan",
"viewLxpLBalance": "Voir votre balance de LXP-L sur Lineascan",
"viewLxpLBalance": "Voir votre balance de LXP-L sur OpenBlock",
"completePOH": "Réalisez votre POH",
"exploreAll": "Explorez les Activations Linea",
"manageLineaEns": "Gérez votre domaine Linea ENS",
Expand Down
6 changes: 3 additions & 3 deletions packages/snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
"test": "jest"
},
"dependencies": {
"@metamask/abi-utils": "^2.0.2",
"@metamask/snaps-sdk": "3.2.0",
"@metamask/utils": "^8.4.0",
"@metamask/abi-utils": "^2.0.4",
"@metamask/snaps-sdk": "^6.12.0",
"@metamask/utils": "^10.0.1",
"buffer": "^6.0.3"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/Consensys/linea-voyager-snap"
},
"source": {
"shasum": "oIZBQ1pqMiZJJvAIDJuYAHRvPR3AIB2Hlt99G27DP+E=",
"shasum": "Le2CMin20Ppf5bE8j7xVsXdIfGGSlTMd8CITAoggaRs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/snap/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const callGlobalApi = async (
isLineascan: boolean,
): Promise<UserData> => {
const response = await fetch(
`https://lxp-snap-api.netlify.app/.netlify/functions/global-api?address=${address}&isLineascan=${isLineascan}`,
`https://lxp-snap-api.netlify.app/.netlify/functions/global-api-v3?address=${address}&isLineascan=${isLineascan}`,
{
method: 'GET',
},
Expand Down
11 changes: 2 additions & 9 deletions packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,11 @@ export const onHomePage: OnHomePageHandler = async () => {

const myAccount = snapState.lxpAddress as string;

const {
lxpBalance,
lxpLBalance,
openBlockScore,
pohStatus,
activations,
name,
} = await getDataForUser(myAccount, chainId);
const { lxpBalance, openBlockScore, pohStatus, activations, name } =
await getDataForUser(myAccount, chainId);

await setState({
myLxpBalance: lxpBalance,
myLxpLBalance: lxpLBalance,
myOpenBlockScore: openBlockScore,
myPohStatus: pohStatus,
activations,
Expand Down
15 changes: 2 additions & 13 deletions packages/snap/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ import type { Hex } from '@metamask/utils';

import { callGlobalApi } from './api';
import type { UserData } from './types';
import {
convertBalanceToDisplay,
LXP_CONTRACT_ADDRESS,
LXP_L_CONTRACT_ADDRESS,
} from './utils';
import { convertBalanceToDisplay, LXP_CONTRACT_ADDRESS } from './utils';

/**
* Fetch all the relevant data for the user.
Expand All @@ -22,31 +18,24 @@ export async function getDataForUser(
try {
const isLineascan = chainId !== '0xe708';

const [userData, lxpBalanceRaw, lxpLBalanceRaw, name] = await Promise.all([
const [userData, lxpBalanceRaw, name] = await Promise.all([
callGlobalApi(address, isLineascan),
isLineascan
? Promise.resolve(0)
: getBalanceFromChain(LXP_CONTRACT_ADDRESS, address),
isLineascan
? Promise.resolve(0)
: getBalanceFromChain(LXP_L_CONTRACT_ADDRESS, address),
isLineascan ? Promise.resolve('') : getNameFromChain(address),
]);

userData.lxpBalance = isLineascan
? convertBalanceToDisplay(userData.lxpBalance.toString())
: lxpBalanceRaw;
userData.lxpLBalance = isLineascan
? convertBalanceToDisplay(userData.lxpLBalance.toString())
: lxpLBalanceRaw;
userData.name = isLineascan ? userData.name : name;

return userData;
} catch (error) {
return {
openBlockScore: 0,
lxpBalance: 0,
lxpLBalance: 0,
pohStatus: false,
activations: [],
name: '',
Expand Down
Loading
Loading