Skip to content

Commit

Permalink
Merge branch 'roles-0.1.0/root' into prepare-edit-hat-proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
adamgall committed Jul 3, 2024
2 parents e89c914 + 160f2a2 commit eb1471e
Show file tree
Hide file tree
Showing 68 changed files with 1,311 additions and 1,675 deletions.
9 changes: 5 additions & 4 deletions .env
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# CoinGecko API key
COINGECKO_API_KEY=""
# Minutes to cache prices for token addresses
TOKEN_PRICE_CACHE_INTERVAL_MINUTES=""
# Minutes to cache token balances for address
BALANCES_CACHE_INTERVAL_MINUTES=""

# Moralis API key
MORALIS_API_KEY=""

# Configuration for Netlify Sentry plugin, not needed in development
SENTRY_ORG=""
Expand Down
4 changes: 2 additions & 2 deletions docs/TRANSLATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@

- Some English text in the app uses ALL CAPS for emphasis. If your language does not have capitalized letters but does have some equivalent way to emphasize text, please do so.

- Gnosis Safe, Gnosis, Safe{Wallet}, and the capitalized word Safe are products of [Gnosis](https://gnosis.io/) and should not be translated.
- Safe{Wallet} and the capitalized word Safe are products of [Safe](https://safe.global/) and should not be translated.

- Etherscan, CoinGecko, and ENS (Ethereum Name Service) are additional web3 products that are mentioned, and should not be translated.
- Etherscan, Moralis, and ENS (Ethereum Name Service) are additional web3 products that are mentioned, and should not be translated.

- The acronyms / abbreviation DAO (Decentralized Autonomous Organizatino), NFT (Non-Fungible Token), and ETH (Ether), should not be translated.

Expand Down
100 changes: 100 additions & 0 deletions netlify/functions/nftBalances.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { getStore } from '@netlify/blobs';
import Moralis from 'moralis';
import { isAddress } from 'viem';
import { moralisSupportedChainIds } from '../../src/providers/NetworkConfig/NetworkConfigProvider';
import type { NFTBalance } from '../../src/types';
import { camelCaseKeys } from '../../src/utils/dataFormatter';

type NFTBalancesWithMetadata = {
data: NFTBalance[];
metadata: {
fetched: number;
};
};

export default async function getNftBalances(request: Request) {
if (!process.env.MORALIS_API_KEY) {
console.error('Moralis API key is missing');
return Response.json({ error: 'Error while fetching token balances' }, { status: 503 });
}

if (!process.env.BALANCES_CACHE_INTERVAL_MINUTES) {
console.error('BALANCES_CACHE_INTERVAL_MINUTES is not set');
return Response.json({ error: 'Error while fetching prices' }, { status: 503 });
}

const requestSearchParams = new URL(request.url).searchParams;
const addressParam = requestSearchParams.get('address');

if (!addressParam) {
return Response.json({ error: 'Address missing from request' }, { status: 400 });
}

if (!isAddress(addressParam)) {
return Response.json({ error: 'Provided address is not a valid address' }, { status: 400 });
}

const networkParam = requestSearchParams.get('network');
if (!networkParam) {
return Response.json({ error: 'Network missing from request' }, { status: 400 });
}

const chainId = parseInt(networkParam);
if (!moralisSupportedChainIds.includes(chainId)) {
return Response.json({ error: 'Requested network is not supported' }, { status: 400 });
}

const nftsStore = getStore(`moralis-balances-nfts-${networkParam}`);
const nowSeconds = Math.floor(Date.now() / 1000);
const cacheTimeSeconds = parseInt(process.env.BALANCES_CACHE_INTERVAL_MINUTES) * 60;
const config = { nowSeconds, cacheTimeSeconds };
const storeKey = `${networkParam}/${addressParam}`;
try {
const balances = await (nftsStore.getWithMetadata(storeKey, {
type: 'json',
}) as Promise<NFTBalancesWithMetadata> | null);

if (
balances?.metadata.fetched &&
balances.metadata.fetched + config.cacheTimeSeconds > config.nowSeconds
) {
return Response.json({ data: balances.data });
} else {
if (!Moralis.Core.isStarted) {
await Moralis.start({
apiKey: process.env.MORALIS_API_KEY,
});
}
let mappedNftsData: NFTBalance[] = [];

let nftsFetched = false;
try {
const nftsResponse = await Moralis.EvmApi.nft.getWalletNFTs({
chain: chainId.toString(),
address: addressParam,
});
mappedNftsData = nftsResponse.result.map(nftBalance =>
camelCaseKeys<ReturnType<typeof nftBalance.toJSON>>(nftBalance.toJSON()),
);
nftsFetched = true;
} catch (e) {
console.error('Error while fetching address NFTs', e);
nftsFetched = false;
}

if (nftsFetched) {
await nftsStore.setJSON(storeKey, mappedNftsData, {
metadata: { fetched: config.nowSeconds },
});
}

return Response.json({ data: mappedNftsData });
}
} catch (e) {
console.error(e);
return Response.json(
{ error: 'Unexpected error while fetching NFTs balances' },
{ status: 503 },
);
}
}
107 changes: 107 additions & 0 deletions netlify/functions/tokenBalances.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { getStore } from '@netlify/blobs';
import Moralis from 'moralis';
import { isAddress } from 'viem';
import { moralisSupportedChainIds } from '../../src/providers/NetworkConfig/NetworkConfigProvider';
import type { TokenBalance } from '../../src/types';
import { camelCaseKeys } from '../../src/utils/dataFormatter';

type TokenBalancesWithMetadata = {
data: TokenBalance[];
metadata: {
fetched: number;
};
};

export default async function getTokenBalancesWithPrices(request: Request) {
if (!process.env.MORALIS_API_KEY) {
console.error('Moralis API key is missing');
return Response.json({ error: 'Error while fetching token balances' }, { status: 503 });
}

if (!process.env.BALANCES_CACHE_INTERVAL_MINUTES) {
console.error('BALANCES_CACHE_INTERVAL_MINUTES is not set');
return Response.json({ error: 'Error while fetching prices' }, { status: 503 });
}

const requestSearchParams = new URL(request.url).searchParams;
const addressParam = requestSearchParams.get('address');

if (!addressParam) {
return Response.json({ error: 'Address missing from request' }, { status: 400 });
}

if (!isAddress(addressParam)) {
return Response.json({ error: 'Provided address is not a valid address' }, { status: 400 });
}

const networkParam = requestSearchParams.get('network');
if (!networkParam) {
return Response.json({ error: 'Network missing from request' }, { status: 400 });
}

const chainId = parseInt(networkParam);
if (!moralisSupportedChainIds.includes(chainId)) {
return Response.json({ error: 'Requested network is not supported' }, { status: 400 });
}

const tokensStore = getStore(`moralis-balances-tokens-${networkParam}`);
const nowSeconds = Math.floor(Date.now() / 1000);
const cacheTimeSeconds = parseInt(process.env.BALANCES_CACHE_INTERVAL_MINUTES) * 60;
const config = { nowSeconds, cacheTimeSeconds };
const storeKey = `${networkParam}/${addressParam}`;
try {
const balances = await (tokensStore.getWithMetadata(storeKey, {
type: 'json',
}) as Promise<TokenBalancesWithMetadata> | null);

if (
balances?.metadata.fetched &&
balances.metadata.fetched + config.cacheTimeSeconds > config.nowSeconds
) {
return Response.json({ data: balances.data });
} else {
if (!Moralis.Core.isStarted) {
await Moralis.start({
apiKey: process.env.MORALIS_API_KEY,
});
}

let tokensFetched = false;
let mappedTokensData: TokenBalance[] = [];
try {
const tokensResponse = await Moralis.EvmApi.wallets.getWalletTokenBalancesPrice({
chain: chainId.toString(),
address: addressParam,
});

mappedTokensData = tokensResponse.result
.filter(tokenBalance => tokenBalance.balance.value.toBigInt() > 0n)
.map(
tokenBalance =>
({
...camelCaseKeys(tokenBalance.toJSON()),
decimals: Number(tokenBalance.decimals),
}) as unknown as TokenBalance,
);
tokensFetched = true;
} catch (e) {
console.error('Unexpected error while fetching address token balances', e);
tokensFetched = false;
}

if (tokensFetched) {
await tokensStore.setJSON(storeKey, mappedTokensData, {
metadata: { fetched: config.nowSeconds },
});
}

return Response.json({ data: mappedTokensData });
}
} catch (e) {
console.error(e);
return Response.json(
{ error: 'Unexpected error while fetching token balances' },
{ status: 503 },
);
}
}
Loading

0 comments on commit eb1471e

Please sign in to comment.