feat: add a heatmap (#1022)

* add a heatmap

* styling and public env vars

* heatmap polish

* add support for sepolia deployments, add styling input

* update icon

* responsive heatmap

* improve url security

* heatmap-color-update (#1030)
This commit is contained in:
Jordan Frankfurt
2024-10-02 15:16:00 -05:00
committed by GitHub
parent 78ea42dac9
commit 182fd19312
10 changed files with 986 additions and 4 deletions

View File

@@ -23,4 +23,8 @@ DATADOG_CLIENT_ID=
NEYNAR_API_KEY=
FARCASTER_DEVELOPER_FID=
FARCASTER_DEVELOPER_MNEMONIC=
FARCASTER_DEVELOPER_MNEMONIC=
ETHERSCAN_API_KEY=
BASESCAN_API_KEY=
TALENT_PROTOCOL_API_KEY=

View File

@@ -22,11 +22,13 @@
"@heroicons/react": "^2.1.3",
"@lottiefiles/dotlottie-react": "^0.8.10",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@rainbow-me/rainbowkit": "^2.1.5",
"@tanstack/react-query": "^5",
"@types/jsonwebtoken": "^9.0.6",
"@types/react-calendar-heatmap": "^1.6.7",
"@vercel/blob": "^0.23.4",
"@vercel/kv": "^1.0.1",
"@vercel/og": "^0.6.2",
@@ -49,6 +51,7 @@
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-blockies": "^1.4.1",
"react-calendar-heatmap": "^1.9.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-intersection-observer": "^9.10.2",

View File

@@ -0,0 +1,70 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { isAddress } from 'viem';
// Make sure the environment variables are properly set
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;
const BASESCAN_API_KEY = process.env.BASESCAN_API_KEY;
const TALENT_PROTOCOL_API_KEY = process.env.TALENT_PROTOCOL_API_KEY;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { query, method } = req;
const address = query.address as string;
const apiType = query.apiType as string;
const body = req.body as Record<string, unknown>; // Explicitly type the body
if (!address || !isAddress(address)) {
return res.status(400).json({ error: 'Missing or invalid address parameter' });
}
let apiUrl: string;
try {
switch (apiType) {
case 'etherscan':
apiUrl = `https://api.etherscan.io/api?address=${address}&apikey=${ETHERSCAN_API_KEY}&module=account&action=txlist`;
break;
case 'base-sepolia':
apiUrl = `https://api-sepolia.basescan.org/api?address=${address}&apikey=${BASESCAN_API_KEY}&module=account&action=txlistinternal`;
break;
case 'basescan':
apiUrl = `https://api.basescan.org/api?address=${address}&apikey=${BASESCAN_API_KEY}&module=account&action=txlist`;
break;
case 'basescan-internal':
apiUrl = `https://api.basescan.org/api?address=${address}&apikey=${BASESCAN_API_KEY}&module=account&action=txlistinternal`;
break;
case 'talent':
apiUrl = `https://api.talentprotocol.com/api/v2/passports/${address}`;
break;
default:
return res.status(400).json({ error: 'Invalid apiType parameter' });
}
// Fetch from the external API using the constructed URL
const externalResponse = await fetch(apiUrl, {
method,
headers: {
'Content-Type': 'application/json',
'X-API-KEY': TALENT_PROTOCOL_API_KEY ?? '',
},
body: method !== 'GET' ? JSON.stringify(body) : undefined,
});
// Handle the content type of the response
const contentType = externalResponse.headers.get('content-type');
let responseData;
if (contentType?.includes('application/json')) {
responseData = await externalResponse.json();
} else {
responseData = await externalResponse.text();
}
if (externalResponse.ok) {
return res.status(200).json({ data: responseData });
} else {
return res.status(externalResponse.status).json({ error: responseData });
}
} catch (error) {
console.error('Error in API proxy:', error);
return res.status(500).json({ error: 'Internal server error' });
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -17,8 +17,8 @@ export default function UsernameProfile() {
);
return (
<div className="mx-auto flex min-h-screen flex-col justify-between gap-10 md:flex-row">
<div className="w-full md:max-w-[25rem]">
<div className="mx-auto grid min-h-screen grid-cols-1 gap-10 md:grid-cols-[25rem_minmax(0,1fr)]">
<div className="w-full">
<UsernameProfileSidebar />
</div>
<div className="w-full">

View File

@@ -1,5 +1,6 @@
import UsernameProfileSectionBadges from 'apps/web/src/components/Basenames/UsernameProfileSectionBadges';
import UsernameProfileSectionExplore from 'apps/web/src/components/Basenames/UsernameProfileSectionExplore';
import UsernameProfileSectionHeatmap from 'apps/web/src/components/Basenames/UsernameProfileSectionHeatmap';
import BadgeContextProvider from 'apps/web/src/components/Basenames/UsernameProfileSectionBadges/BadgeContext';
import UsernameProfileSectionFrames from 'apps/web/src/components/Basenames/UsernameProfileSectionFrames';
import UsernameProfileCasts from 'apps/web/src/components/Basenames/UsernameProfileCasts';
@@ -9,6 +10,7 @@ export default function UsernameProfileContent() {
return (
<div className="flex flex-col gap-4 rounded-2xl border border-[#EBEBEB] p-4 shadow-lg sm:gap-8 sm:p-8 md:gap-10 md:p-10 lg:gap-12 lg:p-12">
<UsernameProfileSectionFrames />
<UsernameProfileSectionHeatmap />
{USERNAMES_PINNED_CASTS_ENABLED && <UsernameProfileCasts />}
<BadgeContextProvider>
<UsernameProfileSectionBadges />

View File

@@ -0,0 +1,23 @@
.react-calendar-heatmap {
min-width: 780px;
height: auto;
width: 100%;
}
.react-calendar-heatmap text {
font-size: 10px;
fill: #aaa;
}
.react-calendar-heatmap .react-calendar-heatmap-small-text {
font-size: 5px;
}
.react-calendar-heatmap rect:hover {
stroke-width: 1;
stroke: #fff;
}
.react-calendar-heatmap rect {
stroke-width: 1;
stroke: #fff;
}

View File

@@ -0,0 +1,371 @@
export const bridges = new Set([
'0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0',
'0x0ac2d6f5f5afc669d3ca38f830dad2b4f238ad3f',
'0xa6baaed2053058a3c8f11e0c7a9716304454b09e',
'0x96e471b5945373de238963b4e032d3574be4d195',
'0x43298f9f91a4545df64748e78a2c777c580573d6',
'0x30b44c676a05f1264d1de9cc31db5f2a945186b6',
'0xdfe0ec39291e3b60aca122908f86809c9ee64e90',
'0x256c8919ce1ab0e33974cf6aa9c71561ef3017b6',
'0x02fbb64517e1c6ed69a6faa3abf37db0482f1152',
'0x7355efc63ae731f584380a9838292c7046c1e433',
'0xbbbd1bbb4f9b936c3604906d7592a644071de884',
'0x23122da8c581aa7e0d07a36ff1f16f799650232f',
'0xb2535b988dce19f9d71dfb22db6da744acac21bf',
'0xc840838bc438d73c16c2f8b22d2ce3669963cd48',
'0xe4e2121b479017955be0b175305b35f312330bae',
'0xcee284f754e854890e311e3280b767f80797180d',
'0xd3b5b60020504bc3489d6949d545893982ba3011',
'0xa3a7b6f88361f48403514059f1f16c8e78d60eec',
'0x72ce9c846789fdb6fc1f34ac4ad25dd9ef7031ef',
'0xd92023e9d9911199a6711321d1277285e6d4e2db',
'0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a',
'0xe5896783a2f463446e1f624e64aa6836be4c6f58',
'0xa10c7ce4b876998858b1a9e12b10092229539400',
'0x4dbd4fc535ac27206064b68ffcf827b0a60bab3f',
'0x667e23abd27e623c11d4cc00ca3ec4d0bd63337a',
'0x0b9857ae2d4a3dbe74ffe1d7df045bb7f96e4840',
'0x14797f5432f699cb4d4db04df599b74952d78d7b',
'0x1c479675ad559dc151f6ec7ed3fbf8cee79582b6',
'0xdac7bb7ce4ff441a235f08408e632fa1d799a147',
'0x8eb8a3b98659cce290402893d0123abb75e3ab28',
'0xe78388b4ce79068e89bf8aa7f218ef6b9ab0e9d0',
'0x1a2a1c938ce3ec39b6d47113c7955baa9dd454f2',
'0x64192819ac13ef72bf6b5ae239ac672b43a9af08',
'0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba',
'0xf78765bd14b4e8527d9e4e5c5a5c11a44ad12f47',
'0xecad1ab3464eccc7536af6afee414df873495616',
'0x841ce48f9446c8e281d3f1444cb859b4a6d0738c',
'0xc578cbaf5a411dfa9f0d227f97dadaa4074ad062',
'0x5427fefa711eff984124bfbb1ab6fbf5e3da1820',
'0xf1c1413096ff2278c3df198a28f8d54e0369cf3a',
'0x74af8a878317e0f6e72e302fbcdf5f3009186398',
'0x4add6ab943e7908bb51e7878755d0ca322c898d6',
'0x3be8a7d4aa3e9b723a718e1b83fe8a8b5c37871c',
'0x43de2d77bf8027e25dbd179b491e8d64f38398aa',
'0xd54f502e184b6b739d7d27a6410a67dc462d69c8',
'0x9280e0ffdfae4ec895fdf4d4978c9e1b869eb774',
'0x9a8c4bdcd75cfa1059a6e453ac5ce9d3f5c82a35',
'0x6880f6fd960d1581c2730a451a22eed1081cfd72',
'0x3014ca10b91cb3d0ad85fef7a3cb95bcac9c0f79',
'0x30f938fed5de6e06a9a7cd2ac3517131c317b1e7',
'0x75ace7a086ea0fb1a79e43cc6331ad053d8c67cb',
'0x88ad09518695c6c3712ac10a214be5109a655671',
'0x4aa42145aa6ebf72e164c9bbc74fbd3788045016',
'0x7e7669bdff02f2ee75b68b91fb81c2b38f9228c2',
'0x6ea6c65e14661c0bcab5bc862fe5e7d3b5630c2f',
'0x7301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6',
'0xa4108aa1ec4967f8b52220a4f7e94a8201f2d906',
'0xfd53b1b4af84d59b20bf2c20ca89a6beeaa2c628',
'0x2dccdb493827e15a5dc8f8b72147e6c4a5620857',
'0x426a61a2127fdd1318ec0edce02474f382fdad30',
'0xf9fb1c508ff49f78b60d3a96dea99fa5d7f3a8a6',
'0xfe601de9d4295274b9904d5a9ad7069f23ee2b32',
'0x4d34e61caf7a3622759d69e48ccdeb8dee5021e8',
'0x1bd0029385f95ad2584cdfaf5c19f3f20651def6',
'0xe2e3441004e7d377a2d97142e75d465e0dd36af9',
'0xa929022c9107643515f5c777ce9a910f0d1e490c',
'0x3d4cc8a61c7528fd86c55cfe061a78dcba48edd1',
'0xb8901acb165ed027e32754e0ffe830802919727f',
'0x22b1cbb8d98a01a3b71d034bb899775a76eb1cc2',
'0x3666f603cc164936c1b87e207f36beba4ac5f18a',
'0x3e4a3a4796d16c0cd582c382691998f7c06420b6',
'0xb98454270065a31d71bf635f6f7ee6a518dfb849',
'0x5fdcca53617f4d2b9134b29090c87d01058e27e9',
'0x37acfef331e6063c8507c2a69c97b4f78c770a5a',
'0x3307c46a1e9633025d2e89658c7502a683585450',
'0x5a1d63d3e1303e89503f2a1ecb553328f148909d',
'0x50002cdfe7ccb0c41f519c6eb0653158d11cd907',
'0xf86fd6735f88d5b6aa709b357ad5be22cedf1a05',
'0x66a71dcef29a0ffbdbe3c6a460a3b5bc225cd675',
'0x014f808b7d4b6f58be5fef88002d5028cd0ed14b',
'0x0baba1ad5be3a5c0a66e7ac838a129bf948f1ea4',
'0x674bdf20a0f284d710bc40872100128e2d66bd3f',
'0xec3cc6cf0252565b56fc7ac396017df5b9b78a31',
'0x3980c9ed79d2c191a89e02fa3529c60ed6e9c04b',
'0xc10ef9f491c9b59f936957026020c321651ac078',
'0xfc7cc7c7e7985316d23104b9689c511134f59bc8',
'0x13b432914a996b0a48695df9b2d701eda45ff264',
'0xc564ee9f21ed8a2d8e7e76c085740d5e4c5fafbe',
'0x87bcb3038988ca2a89605ffa8f237fb78df1c3ae',
'0x46290b0c3a234e3d538050d8f34421797532a827',
'0xd779967f8b511c5edf39115341b310022900efed',
'0x923e0a17f49fb03d936f2af2d59d379c615f5447',
'0xec4486a90371c9b66f499ff3936f29f0d5af8b7e',
'0x10c6b61dbf44a083aec3780acf769c77be747e23',
'0xe4cf417081a0ab3f964b44d904bc2b534351a9a7',
'0x533e3c0e6b48010873b947bddc4721b1bdff9648',
'0xe95fd76cf16008c12ff3b3a937cb16cd9cc20284',
'0x6b7a87899490ece95443e979ca9485cbe7e71522',
'0x765277eebeca2e31912c9946eae1021199b39c61',
'0xba8da9dcf11b50b03fd5284f164ef5cdef910705',
'0x4e67df0f232c3bc985f8a63326d80ce3d9a40400',
'0x8cc49fe67a4bd7a15674c4ffd4e969d94304bbbf',
'0x57ed6bd35a6ce815079855cd0b21331d1d5d0a0e',
'0xcdd83050f045ab31b884f0dc49581bc7b3e0b84c',
'0x23ddd3e3692d1861ed57ede224608875809e127f',
'0x2d6775c1673d4ce55e1f827a0d53e62c43d1f304',
'0x88a69b4e698a4b090df6cf5bd7b2d47325ad30a3',
'0x070cb1270a4b2ba53c81cef89d0fd584ed4f430b',
'0x3eed23ea148d356a72ca695dbce2fceb40a32ce0',
'0x48d7a6bbc428bca019a560cf3e8ea5364395aad3',
'0xdc1664458d2f0b6090bea60a8793a4e66c2f1c00',
'0x6a39909e805a3eadd2b61fff61147796ca6abb47',
'0x4fc16de11deac71e8b2db539d82d93be4b486892',
'0x2784a755690453035f32ac5e28c52524d127afe2',
'0x10e6593cdda8c58a1d0f14c5164b376352a55f2f',
'0x99c9fc46f92e8a1c0dec1b1747d010903e884be1',
'0x467194771dae2967aef3ecbedd3bf9a310c76c65',
'0x1bf68a9d1eaee7826b3593c20a0ca93293cb489a',
'0xeec0fb4913119567cdfc0c5fc2bf8f9f9b226c2d',
'0xcd38b15a419491c7c1238b0659f65c755792e257',
'0x8f92e7353b180937895e0c5937d616e8ea1a2bb9',
'0x2140ecdc45c89ca112523637824513bae72c8671',
'0x4c36d2919e407f0cc2ee3c993ccf8ac26d9ce64e',
'0xa0c68c638235ee32657e8f720a23cec1bfc77c77',
'0x40ec5b33f54e0e8a33a975908c5ba1c14e5bbbdf',
'0x8484ef722627bf18ca5ae6bcf031c23e6e922b30',
'0x401f6c983ea34274ec46f84d70b31c151321188b',
'0xa68d85df56e733a06443306a095646317b5fa633',
'0xf687e1481d85f8b9f4d1f4d4c15348cef8e5a762',
'0xe4b679400f0f267212d5d812b95f58c83243ee71',
'0x32666b64e9fd0f44916e1378efb2cfa3b3b96e80',
'0x5d22045daceab03b158031ecb7d9d06fad24609b',
'0x12ed69359919fc775bc2674860e8fe2d2b6a7b5d',
'0xd8b19613723215ef8cc80fc35a1428f8e8826940',
'0xf4b00c937b4ec4bb5ac051c3c719036c668a31ec',
'0xeae57ce9cc1984f202e15e038b964bb8bdf7229a',
'0xf5c9f957705bea56a7e806943f98f7777b995826',
'0x659a00c33263d9254fed382de81349426c795bb6',
'0xae0ee0a63a2ce6baeeffe56e7714fb4efe48d419',
'0xf6080d9fbeebcd44d89affbfd42f098cbff92816',
'0xbb3400f107804dfb482565ff1ec8d8ae66747605',
'0x283751a21eafbfcd52297820d27c1f1963d9b5b4',
'0x2796317b0ff8538f253012862c06787adfb8ceb6',
'0xa2569370a9d4841c9a62fc51269110f2eb7e0171',
'0x6571d6be3d8460cf5f7d6711cd9961860029d85f',
'0x045e507925d2e05d114534d0810a1abd94aca8d6',
'0xcd9d4988c0ae61887b075ba77f08cbfad2b65068',
'0x5fd79d46eba7f351fe49bff9e87cdea6c821ef9f',
'0xc145990e84155416144c532e31f89b840ca8c2ce',
'0x4103c267fba03a1df4fe84bc28092d629fa3f422',
'0x86ca49d37015a8541642b1b5a90af0115ec61994',
'0xf301d525da003e874df574bcdd309a6bf0535bb6',
'0x98f3c9e6e3face36baad05fe09d375ef1464288b',
'0x3ee18b2214aff97000d974cf647e7c347e8fa585',
'0xf92cd566ea4864356c5491c177a430c222d7e678',
'0x31efc4aeaa7c39e54a33fdc3c46ee2bd70ae0a09',
'0xe34b087bf3c99e664316a15b01e5295eb3512760',
'0x104b9b1c41c6764e88edf1207f498902d840fe2a',
'0x0dd1f24cf4ff96f197a795d02d0ba1eb53448bcc',
'0x8eca806aecc86ce90da803b080ca4e3a9b8097ad',
'0x6de5bdc580f55bc9dacafcb67b91674040a247e3',
'0x5cdaf83e077dbac2692b5864ca18b61d67453be8',
'0xabea9132b05a70803a4e85094fd0e1800777fbef',
'0xc30141b657f4216252dc59af2e7cdb9d8792e1b0',
'0x49048044d57e1c92a77f79988d21fa8faf74e97e',
'0x3154cf16ccdb4c6d922629664174b904d80f2c35',
'0x3a23f943181408eac424116af7b7790c94cb97a5',
'0x09aea4b2242abc8bb4bb78d537a67a245a7bec64',
]);
export const lendBorrowEarn = new Set([
'0x1e4b7a6b903680eab0c5dabcb8fd429cd2a9598c',
'0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2',
'0xcc9a0b7c43dc2a5f023bb9b738e45b0ef6b06e04',
'0xe65cdb6479bac1e22340e4e755fae7e509ecd06c',
'0x70e36f6bf80a52b3b46b3af8e106cc0ed743e8e4',
'0xface851a4921ce59e912d19329929ce6da6eb0c7',
'0x95b4ef2869ebd94beb4eee400a99824bf5dc325b',
'0xf5dce57282a584d2746faf1593d3121fcac444dc',
'0x4b0181102a0112a2ef11abee5563bb4a3176c9d7',
'0x12392f67bdf24fae0af363c24ac620a2f67dad86',
'0xccf4429db6322d5c611ee964527d42e5d685dd6a',
'0x80a2ae356fc9ef4305676f7a3e2ed04e12c33946',
'0x6d903f6003cca6255d85cca4d3b5e5146dc33925',
'0x3fda67f7583380e67ef93072294a7fac882fd7e7',
'0x6c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e',
'0x5d3a536e4d6dbd6114cc1ead35777bab948e3643',
'0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5',
'0x1c1853bc7c6bff0d276da53972c0b1a066db1ae7',
'0xc00e94cb662c3520282e6f5717214004a7f26888',
'0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b',
'0x316f9708bb98af7da9c68c1c3b5e79039cd336e3',
'0xcfc1fa6b7ca982176529899d99af6473ad80df4f',
'0xf859a1ad94bcf445a406b892ef0d3082f4174088',
'0x158079ee67fce2f58472a96584a73c7ab9ac95c1',
'0x35a18000230da775cac24873d00ff85bccded550',
'0x39aa39c021dfbae8fac545936693ac917d5e7563',
'0x285617313887d43256f852cae0ee4de4b68d45b0',
'0x42f9505a376761b180e27a01ba0554244ed1de7d',
'0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9',
'0xc11b1268c1a384e55c48c2391d8d480264a3a7f4',
'0xb3319f5d18bc0d84dd1b4825dcde5d5f7266d407',
'0xa7ff0d561cd15ed525e31bbe0af3fe34ac2059f6',
'0x1449e0687810bddd356ae6dd87789244a46d9adb',
'0xcec237e83a080f3225ab1562605ee6dedf5644cc',
'0xfe83af639f769ead20bad76067abc120245a06a9',
'0xc0da01a04c3f3e0be433606045bb7017a7323e38',
'0x1055be4bf7338c7606d9efdcf80593f180ba043e',
'0x02557a5e05defeffd4cae6d83ea3d173b272c904',
'0xba3d9687cf50fe253cd2e1cfeede1d6787344ed5',
'0xe1ba0fb44ccb0d11b80f92f4f8ed94ca3ff51d00',
'0x6ee0f7bb50a54ab5253da0667b0dc2ee526c30a8',
'0xfc1e690f61efd961294b3e1ce3313fbd8aa4f85d',
'0x9d91be44c06d373a8a226e1f3b146956083803eb',
'0xa64bd6c70cb9051f6a9ba1f163fdc07e0dfb5f84',
'0x6fce4a401b6b80ace52baaefe4421bd188e76f6f',
'0x7deb5e830be29f91e298ba5ff1356bb7f8146998',
'0x328c4c80bc7aca0834db37e6600a6c49e12da4de',
'0x625ae63000f46200499120b906716420bd059240',
'0x4da9b813057d04baef4e5800e36083717b4a0341',
'0xb124541127a0a657f056d9dd06188c4f1b0e5aab',
'0x9ba00d6856a4edf4665bca2c2309936572473b7e',
'0x71fc860f7d3a592a4a98740e39db31d25db65ae8',
'0xfc4b8ed459e00e5400be803a9bb3954234fd50e3',
'0x12e51e77daaa58aa0e9247db7510ea4b46f9bead',
'0x6fb0855c404e09c47c3fbca25f08d4e41f9f062f',
'0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9',
'0xa069e33994dcc24928d99f4bbeda83aaef00b5f3',
'0x93a62da5a14c80f265dabc077fcee437b1a0efde',
'0x29e240cfd7946ba20895a7a02edb25c210f9f324',
'0x04bc0ab673d88ae9dbc9da2380cb6b79c4bca9ae',
'0x5dbcf33d8c2e976c6b560249878e6f1491bca25c',
'0x975f1bc238303593efab00d63cf0fc5f519a8de0',
'0xed03415e5705c5abbf8e94c491b715df526cad55',
'0x2c3a2558e9b91e893e53bce94de3457a29f6b262',
'0x16de59092dae5ccf4a1e6439d611fd0653f0bd01',
'0xf61718057901f84c4eec4339ef8f0d86d2b45600',
'0x73a052500105205d34daf004eab301916da8190f',
'0xd6ad7a6750a7593e092a9b218d66c0a814a3436e',
'0x597ad1e0c13bfe8025993d9e79c69e1c0233522e',
'0x83f798e925bcd4017eb265844fddabb448f1707d',
'0xba2e7fed597fd0e3e70f5130bcdbbfe06bb94fe1',
'0x033e52f513f9b98e129381c6708f9faa2dee5db5',
'0xb01419e74d8a2abb1bbad82925b19c36c191a701',
'0x3a22df48d84957f907e67f4313e3d43179040d6e',
'0x0001fb050fe7312791bf6475b96569d83f695c9f',
'0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e',
'0x1cc481ce2bd2ec7bf67d1be64d4878b16078f309',
'0xbb3bf20822507c70eafdf11c7469c98fc752ccca',
'0x96e61422b6a9ba0e068b6c5add4ffabc6a4aae27',
'0xdf5e4e54d212f7a01cf94b3986f40933fcff589f',
'0x6903223578806940bd3ff0c51f87aa43968424c8',
'0x03403154afc09ce8e44c3b185c82c6ad5f86b9ab',
'0x46afc2dfbd1ea0c0760cad8262a5838e803a37e5',
'0xcb550a6d4c8e3517a939bc79d0c7093eb7cf56b5',
'0x0a61c2146a7800bdc278833f21ebf56cd660ee2a',
'0xc454f4e1ddb39c8de9663287d52b0e4feb4ca45e',
'0x35f5a420ef9bcc748329021fbd4ed0986abdf201',
'0x8e6741b456a074f0bc45b8b82a755d4af7e965df',
'0x2d407ddb06311396fe14d4b49da5f0471447d45c',
'0x710295b5f326c2e47e6dd2e7f6b5b0f7c5ac2f24',
'0x7d96ab1f847c3564b8f9a93f35e1027ada74aec2',
'0xe15461b18ee31b7379019dc523231c57d1cbc18c',
'0x50c1a2ea0a861a967d9d0ffe2ae4012c2e053804',
'0x0b634a8d61b09820e9f72f79cdcbc8a4d0aad26b',
'0xc97232527b62efb0d8ed38cf3ea103a6cca4037e',
'0x27b5739e22ad9033bcbf192059122d163b60349d',
'0xd0660cd418a64a1d44e9214ad8e459324d8157f1',
'0x9ca85572e6a3ebf24dedd195623f188735a5179f',
'0xfcc5c47be19d06bf83eb04298b026f81069ff65b',
'0x27b7b1ad7288079a66d12350c828d3c00a6f07d7',
'0x7047f90229a057c13bf847c0744d646cfb6c9e1a',
'0x5533ed0a3b83f70c3c4a1f69ef5546d3d4713e44',
'0xb8c3b7a2a618c552c23b1e4701109a9e756bab67',
'0xe625f5923303f1ce7a43acfefd11fd12f30dbca4',
'0xa8b1cb4ed612ee179bdea16cca6ba596321ae52d',
'0x2994529c0652d127b7842094103715ec5299bbed',
'0x629c759d1e83efbf63d84eb3868b564d9521c129',
'0x98b058b2cbacf5e99bc7012df757ea7cfebd35bc',
'0xcc7e70a958917cce67b4b87a8c30e6297451ae98',
'0x625b7df2fa8abe21b0a976736cda4775523aed1e',
'0x39546945695dcb1c037c836925b355262f551f55',
'0x96ea6af74af09522fcb4c28c269c26f59a31ced6',
'0x0fcdaedfb8a7dfda2e9838564c5a1665d856afdf',
'0x7f83935ecfe4729c4ea592ab2bc1a32588409797',
'0x123964ebe096a920dae00fb795ffbfa0c9ff4675',
'0x5334e150b938dd2b6bd040d9c4a03cff0ced3765',
'0x7ff566e1d69deff32a7b244ae7276b9f90e9d0f6',
'0xbacb69571323575c6a5a3b4f9eede1dc7d31fbc1',
'0xb4d1be44bff40ad6e506edf43156577a3f8672ec',
'0x8414db07a7f743debafb402070ab01a4e0d2e45e',
'0x986b4aff588a109c09b50a03f42e4110e29d353f',
'0xdcd90c7f6324cfa40d7169ef80b12031770b4325',
'0x07fb4756f67bd46b748b16119e802f1f880fb2cc',
'0xfe39ce91437c76178665d64d7a2694b0f6f17fe3',
'0x1b5eb1173d2bf770e50f10410c9a96f7a8eb6e75',
'0xf6c9e9af314982a4b38366f4abfaa00595c5a6fc',
'0xa9fe4601811213c340e850ea305481aff02f5b28',
'0xe14d13d8b3b85af791b2aadd661cdbd5e6097db1',
'0xc2cb1040220768554cf699b0d863a3cd4324ce32',
'0xacd43e627e64355f1861cec6d3a6688b31a6f952',
'0x19d3364a399d251e894ac732651be8b0e4e85001',
'0x020171085bcd43b6fd36ad8c95ad61848b1211a2',
'0xec0d8d3ed5477106c6d4ea27d90a60e594693c90',
'0x881b06da56bb5675c54e4ed311c21e54c5025298',
'0x37d19d1c4e1fa9dc47bd1ea12f742a0887eda74a',
'0x26ea744e5b887e5205727f55dfbe8685e3b21951',
'0x5f18c75abdae578b483e5f43f12a39cf75b973a9',
'0xe6354ed5bc4b393a5aad09f21c46e101e692d447',
'0x2f08119c6f07c006695e079aafc638b8789faf18',
'0x9d409a0a012cfba9b15f6d4b36ac57a46966ab9a',
'0xda816459f1ab5631232fe5e97a05bbbb94970c95',
'0xc5bddf9843308380375a611c18b50fb9341f502a',
'0xe11ba472f74869176652c35d30db89854b5ae84d',
'0xe0db48b4f71752c4bef16de1dbd042b82976b8c7',
'0xa354f35829ae975e850e23e9615b11da1b3dc4de',
'0xe1237aa7f535b0cc33fd973d66cbf830354d16c7',
'0x04aa51bbcb46541455ccf1b8bef2ebc5d3787ec9',
'0x9d25057e62939d3408406975ad75ffe834da4cdd',
'0x36324b8168f960a12a8fd01406c9c78143d41380',
'0xa2609b2b43ac0f5ebe27deb944d2a399c201e3da',
'0xa1787206d5b1be0f432c4c4f96dc4d1257a1dd14',
'0xe65cdb6479bac1e22340e4e755fae7e509ecd06c',
'0x70e36f6bf80a52b3b46b3af8e106cc0ed743e8e4',
'0xface851a4921ce59e912d19329929ce6da6eb0c7',
'0x95b4ef2869ebd94beb4eee400a99824bf5dc325b',
'0xf5dce57282a584d2746faf1593d3121fcac444dc',
'0x4b0181102a0112a2ef11abee5563bb4a3176c9d7',
'0x12392f67bdf24fae0af363c24ac620a2f67dad86',
'0xccf4429db6322d5c611ee964527d42e5d685dd6a',
'0x80a2ae356fc9ef4305676f7a3e2ed04e12c33946',
'0x6d903f6003cca6255d85cca4d3b5e5146dc33925',
'0x2e088a0a19dda628b4304301d1ea70b114e4accd',
'0xa1bc2cf69d474b39b91665e24e7f2606ed142991',
'0x25e12482a25cf36ec70fda2a09c1ed077fc21616',
'0xf403c135812408bfbe8713b5a23a04b3d48aae31',
'0x92cf9e5e4d1dfbf7da0d2bb3e884a68416a65070',
'0x5f465e9fcffc217c5849906216581a657cd60605',
'0x8014595f2ab54cd7c604b00e9fb932176fdc86ae',
'0xd18140b4b819b895a3dba5442f959fa44994af50',
'0xcf50b810e57ac33b91dcf525c6ddd9881b139332',
'0xe096ccec4a1d36f191189fe61e803d8b2044dfc3',
'0x3fe65692bfcd0e6cf84cb1e7d24108e434a7587e',
'0x947b7742c403f20e5faccdac5e092c943e7d0277',
'0xa3c5a1e09150b75ff251c1a7815a07182c3de2fb',
'0xae5f315a5b5dd4dbacd38862562a51490e500183',
'0xedccb35798fae4925718a43cc608ae136208aa8d',
'0x877288c4e6eba4f635ba7428706447353b47de75',
'0x3c995e43e6ddd551e226f4c5544c77bfed147ab9',
'0x1389388d01708118b497f59521f6943be2541bb7',
'0xe98984ad858075813ada4261af47e68a64e28fcc',
'0xdecc7d761496d30f30b92bdf764fb8803c79360d',
'0x989aeb4d175e16225e39e87d0d97a3360524ad80',
'0x989aeb4d175e16225e39e87d0d97a3360524ad80',
'0x794a61358d6845594f94dc1db02a252b5b4814ad',
'0x8dff5e27ea6b7ac08ebfdf9eb090f32ee9a30fcf',
'0xae7ab96520de3a18e5e111b5eaab095312d7fe84',
'0x5a98fcbea516cf06857215779fd812ca3bef1b32',
'0x9ee91f9f426fa633d227f7a9b000e28b9dfd8599',
'0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0',
'0xbeadf48d62acc944a06eeae0a9054a90e5a7dc97',
'0x18cd499e3d7ed42feba981ac9236a278e4cdc2ee',
'0x9c4ec768c28520b50860ea7a15bd7213a9ff58bf',
'0x78d0677032a35c63d142a48a2037048871212a8c',
'0xA238Dd80C259a72e81d7e4664a9801593F98d1c5',
'0x8be473dcfa93132658821e67cbeb684ec8ea2e74',
'0x03a520b32c04bf3beef7beb72e919cf822ed34f1',
'0x30a4aa1d14d44f0f5bfe887447ab6facc94a549f',
'0x70778cfcfc475c7ea0f24cc625baf6eae475d0c9',
]);

View File

@@ -0,0 +1,478 @@
import * as Collapsible from '@radix-ui/react-collapsible';
import { useUsernameProfile } from 'apps/web/src/components/Basenames/UsernameProfileContext';
import {
bridges,
lendBorrowEarn,
} from 'apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/contracts';
import { Icon } from 'apps/web/src/components/Icon/Icon';
import Image from 'next/image';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import CalendarHeatmap, { ReactCalendarHeatmapValue } from 'react-calendar-heatmap';
import { Address } from 'viem';
import './cal.css';
type HeatmapValue = {
date: string;
count: number;
};
type Transaction = {
timeStamp: string;
from: string;
to: string;
functionName?: string;
input: string;
hash: string;
};
export default function UsernameProfileSectionHeatmap() {
// The ref/effect here are a kinda jank approach to reaching into the heatmap library's rendered dom and modifying individual rect attributes.
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const pollForRects = () => {
const containerElement = containerRef.current;
if (!containerElement) return;
const rects = containerElement.querySelectorAll('rect');
if (rects.length > 0) {
rects.forEach((rect) => {
rect.setAttribute('rx', '2');
rect.setAttribute('ry', '2');
});
clearInterval(timerId);
// this line ensures that if the element is scrollable it will be all the way right (showing newest cal data)
containerElement.scrollLeft = containerElement.scrollWidth;
}
};
const timerId = setInterval(pollForRects, 100);
return () => {
clearInterval(timerId);
};
}, []);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isDataFetched, setIsDataFetched] = useState<boolean>(false);
const [totalTx, setTotalTx] = useState<number>(0);
const [tokenSwapCount, setTokenSwapCount] = useState<number>(0);
const [ensCount, setEnsCount] = useState<number>(0);
const [bridgeCount, setBridgeCount] = useState<number>(0);
const [lendCount, setLendCount] = useState<number>(0);
const [buildCount, setBuildCount] = useState<number>(0);
const [heatmapData, setHeatmapData] = useState<HeatmapValue[]>([]);
const [uniqueActiveDays, setUniqueActiveDays] = useState<number>(0);
const [longestStreak, setLongestStreak] = useState<number>(0);
const [currentStreak, setCurrentStreak] = useState<number>(0);
const [activityPeriod, setActivityPeriod] = useState<number>(0);
const [ethereumDeployments, setEthereumDeployments] = useState<string[]>([]);
const [baseDeployments, setBaseDeployments] = useState<string[]>([]);
const [totalTransactionsList, setTotalTransactionsList] = useState<Transaction[]>([]);
const classForValue = useCallback((value: ReactCalendarHeatmapValue<string> | undefined) => {
if (!value) return 'm-1 fill-[#F8F9FB]'; // empty
if (value.count >= 10) return 'm-1 fill-[#003EC1]'; // 4 - most
if (value.count >= 7) return 'm-1 fill-[#266EFF]'; // 3
if (value.count >= 4) return 'm-1 fill-[#92B6FF]'; // 2
if (value.count >= 1) return 'm-1 fill-[#D3E1FF]'; // 1
return 'm-1 fill-[#F8F9FB]'; // empty - least
}, []);
const titleForValue = useCallback((value: ReactCalendarHeatmapValue<string> | undefined) => {
return value ? `${value.date}: ${value.count} transactions` : '';
}, []);
const { profileAddress } = useUsernameProfile();
const generateHeatmapData = (transactions: Transaction[]): HeatmapValue[] => {
const dateMap: Record<string, HeatmapValue> = {};
transactions.forEach((tx) => {
const txDate = new Date(parseInt(tx.timeStamp) * 1000).toLocaleDateString();
dateMap[txDate] = dateMap[txDate]
? { date: txDate, count: dateMap[txDate].count + 1 }
: { date: txDate, count: 1 };
});
return Object.values(dateMap);
};
const calculateStreaksAndMetrics = (transactions: Transaction[], addrs: Address) => {
const filteredTransactions = transactions.filter(
(tx) => tx.from.toLowerCase() === addrs.toLowerCase(),
);
if (filteredTransactions.length === 0)
return { uniqueActiveDays: 0, longestStreakDays: 0, currentStreakDays: 0, activityPeriod: 0 };
const timestamps = filteredTransactions.map((tx) => parseInt(tx.timeStamp, 10));
const firstTransactionDate = new Date(Math.min(...timestamps) * 1000);
const lastTransactionDate = new Date(Math.max(...timestamps) * 1000);
const uniqueActiveDaysSet = new Set(
filteredTransactions.map((tx) => new Date(parseInt(tx.timeStamp, 10) * 1000).toDateString()),
);
const sortedDates = Array.from(uniqueActiveDaysSet)
.map((dateStr) => new Date(dateStr))
.sort((a, b) => a.getTime() - b.getTime());
let longestStreakDays = 0;
let streak = 0;
for (let i = 0; i < sortedDates.length; i++) {
if (
i === 0 ||
(sortedDates[i].getTime() - sortedDates[i - 1].getTime()) / (1000 * 60 * 60 * 24) === 1
) {
streak++;
} else {
longestStreakDays = Math.max(longestStreakDays, streak);
streak = 1;
}
}
longestStreakDays = Math.max(longestStreakDays, streak);
return {
uniqueActiveDays: uniqueActiveDaysSet.size,
longestStreakDays,
currentStreakDays:
sortedDates[sortedDates.length - 1].toDateString() === new Date().toDateString()
? streak
: 0,
activityPeriod: Math.max(
Math.ceil(
(lastTransactionDate.getTime() - firstTransactionDate.getTime()) / (1000 * 60 * 60 * 24),
),
1,
),
};
};
const fetchTransactions = useCallback(
async (apiUrl: string, retryCount = 3): Promise<Transaction[]> => {
try {
const response = await fetch(apiUrl);
const json = (await response.json()) as {
data: { result: Transaction[]; status: '1' | '0'; message: string };
};
if (json.data?.status === '1' && Array.isArray(json.data.result)) {
return json.data.result;
} else if (json.data?.status === '0' && json.data.message === 'No transactions found') {
return []; // Return an empty array for no transactions
} else if (json.data?.status === '0' && json.data.message === 'Exception') {
if (retryCount > 0) {
console.log(`API returned an exception. Retrying... (${retryCount} attempts left)`);
await new Promise((resolve) => setTimeout(resolve, 2000));
return await fetchTransactions(apiUrl, retryCount - 1);
} else {
throw new Error(`API Error: ${json.data.message}`);
}
} else {
console.error('Unexpected API response structure:', json);
return [];
}
} catch (e) {
console.error('Error fetching transactions:', e);
throw e;
}
},
[],
);
const filterTransactions = (transactions: Transaction[], addrs: string[]): Transaction[] => {
return transactions.filter((tx) =>
addrs.some((addr) => tx.from.toLowerCase() === addr.toLowerCase()),
);
};
const calculateScore = useCallback(
(completedTasks: number, transactions: Transaction[]): number => {
const taskScore = (Math.min(completedTasks, 6) / 6) * 35;
const txScore = (Math.min(totalTx, 100) / 100) * 20;
const daysScore = (Math.min(uniqueActiveDays, 100) / 100) * 15;
const longestStreakScore = (Math.min(longestStreak, 30) / 30) * 5;
const currentStreakScore = (Math.min(currentStreak, 5) / 5) * 5;
const activityPeriodScore = (Math.min(activityPeriod, 365) / 365) * 5;
const recencyScore = calculateRecencyScore(transactions);
const total = Math.round(
taskScore +
txScore +
daysScore +
longestStreakScore +
currentStreakScore +
activityPeriodScore +
recencyScore,
);
return total;
},
[activityPeriod, currentStreak, longestStreak, totalTx, uniqueActiveDays],
);
const calculateRecencyScore = (transactions: Transaction[]): number => {
const now = Date.now();
const ninetyDaysAgo = now - 90 * 24 * 60 * 60 * 1000;
const recentTransactions = transactions.filter(
(tx) => parseInt(tx.timeStamp) * 1000 > ninetyDaysAgo,
);
const recentTxCount = recentTransactions.length;
// Base score for up to 60 transactions (10 points)
const baseScore = (Math.min(recentTxCount, 60) / 60) * 10;
// Additional score for transactions beyond 60, up to 180 (5 points)
const additionalScore = recentTxCount > 60 ? (Math.min(recentTxCount - 60, 120) / 120) * 5 : 0;
// Total recency score (max 15 points)
return baseScore + additionalScore;
};
const fetchData = useCallback(
async (addrs: Address) => {
setIsLoading(true);
try {
const allTransactions: Transaction[] = [];
let allEthereumDeployments: string[] = [];
let allBaseDeployments: string[] = [];
let allSepoliaDeployments: string[] = [];
const [
ethereumTransactions,
baseTransactions,
baseInternalTransactions,
sepoliaTransactions,
] = await Promise.all([
fetchTransactions(`/api/proxy?apiType=etherscan&address=${addrs}`).catch(() => []),
fetchTransactions(`/api/proxy?apiType=basescan&address=${addrs}`).catch(() => []),
fetchTransactions(`/api/proxy?apiType=basescan-internal&address=${addrs}`).catch(
() => [],
),
fetchTransactions(`/api/proxy?apiType=base-sepolia&address=${addrs}`).catch(() => []),
]);
const filteredEthereumTransactions = filterTransactions(ethereumTransactions, [addrs]);
const filteredBaseTransactions = filterTransactions(baseTransactions, [addrs]);
const filteredSepoliaTransactions = filterTransactions(sepoliaTransactions, [addrs]);
// Filter and deduplicate internal Base transactions
const filteredBaseInternalTransactions = baseInternalTransactions
.filter((tx) => tx.from.toLowerCase() === addrs.toLowerCase())
.filter((tx) => !baseTransactions.some((baseTx) => baseTx.hash === tx.hash));
allTransactions.push(
...filteredEthereumTransactions,
...filteredBaseTransactions,
...filteredBaseInternalTransactions,
);
allEthereumDeployments = [
...allEthereumDeployments,
...filteredEthereumTransactions
.filter((tx) => tx.input?.startsWith('0x60806040'))
.map((tx) => tx.hash),
];
allBaseDeployments = [
...allBaseDeployments,
...filteredBaseTransactions
.filter((tx) => tx.input.includes('60806040'))
.map((tx) => tx.hash),
];
allSepoliaDeployments = [
...allSepoliaDeployments,
...filteredSepoliaTransactions
.filter((tx) => tx.input.includes('60806040'))
.map((tx) => tx.hash),
];
if (allTransactions.length === 0) {
return;
}
setTotalTransactionsList(allTransactions);
setTotalTx(allTransactions.length);
setHeatmapData(generateHeatmapData(allTransactions));
const {
uniqueActiveDays: activeDays,
longestStreakDays,
currentStreakDays,
activityPeriod: activity,
} = calculateStreaksAndMetrics(allTransactions, addrs);
setUniqueActiveDays(activeDays);
setLongestStreak(longestStreakDays);
setCurrentStreak(currentStreakDays);
setActivityPeriod(activity);
setTokenSwapCount(
allTransactions.filter(
(tx) =>
((tx.functionName &&
(tx.functionName.includes('swap') ||
tx.functionName.includes('fillOtcOrderWithEth') ||
tx.functionName.includes('proxiedSwap'))) ??
tx.to === '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad') || // uniswap - base
tx.to === '0x6cb442acf35158d5eda88fe602221b67b400be3e' || // aerodrome - base
tx.to === '0x1111111254eeb25477b68fb85ed929f73a960582', // 1inch - base
).length,
);
// ENS count calculation
setEnsCount(
allTransactions.filter((tx) =>
[
'0x283af0b28c62c092c9727f1ee09c02ca627eb7f5',
'0x253553366da8546fc250f225fe3d25d0c782303b',
'0x4ccb0bb02fcaba27e82a56646e81d8c5bc4119a5',
'0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda',
].includes(tx.to),
).length,
);
setBridgeCount(allTransactions.filter((tx) => bridges.has(tx.to)).length);
setLendCount(
allTransactions.filter(
(tx) =>
lendBorrowEarn.has(tx.to) || tx.from === '0x1382cff3cee10d283dcca55a30496187759e4caf',
).length,
);
setBuildCount(
allEthereumDeployments.length + allBaseDeployments.length + allSepoliaDeployments.length,
);
setEthereumDeployments(allEthereumDeployments);
setBaseDeployments(allBaseDeployments);
} catch (e) {
console.error('Error fetching data:', e);
} finally {
setIsLoading(false);
setIsDataFetched(true);
}
},
[fetchTransactions],
);
useEffect(() => {
if (!profileAddress) return;
if (!isDataFetched) {
void fetchData(profileAddress);
}
}, [fetchData, isDataFetched, profileAddress]);
const contractsDeployed = useMemo(() => {
return ethereumDeployments.length + baseDeployments.length;
}, [baseDeployments.length, ethereumDeployments.length]);
const tasksCompleted = useMemo(
() =>
[
totalTransactionsList.length > 0,
tokenSwapCount > 0,
bridgeCount > 0,
lendCount > 0,
ensCount > 0,
buildCount > 0,
].filter(Boolean).length,
[bridgeCount, buildCount, ensCount, lendCount, tokenSwapCount, totalTransactionsList.length],
);
const finalScore = useMemo(
() => calculateScore(tasksCompleted, totalTransactionsList),
[calculateScore, tasksCompleted, totalTransactionsList],
);
if (isLoading) {
return (
<div className="relative rounded-3xl border border-palette-line/20 p-10">
<div className="absolute inset-0 flex items-center justify-center">
<Image src="/images/base-loading.gif" alt="" width={22} height={22} />
</div>
</div>
);
}
return (
<Collapsible.Root className="rounded-3xl border border-palette-line/20">
<div className="mb-6 px-6 pt-6">
<div className="relative mb-6">
<h3 className="mb-1 text-sm font-medium text-gray-60">ONCHAIN SCORE</h3>
<p className="font-display text-3xl">{finalScore}/100</p>
<div className="absolute right-0 flex items-center gap-1 text-xs text-palette-foregroundMuted">
<p>Less</p>
<svg
xmlns="http://www.w3.org/2000/svg"
width="60"
height="10"
viewBox="0 0 60 10"
fill="none"
>
<rect x="0" width="10" height="10" rx="2" fill="#F8F9FB" />
<rect x="12" width="10" height="10" rx="2" fill="#D3E1FF" />
<rect x="24" width="10" height="10" rx="2" fill="#92B6FF" />
<rect x="36" width="10" height="10" rx="2" fill="#266EFF" />
<rect x="48" width="10" height="10" rx="2" fill="#003EC1" />
</svg>
<p>More</p>
</div>
</div>
<div
ref={containerRef}
className="w-full max-w-full overflow-x-auto overflow-y-hidden whitespace-nowrap"
>
<CalendarHeatmap
startDate={new Date(new Date().setFullYear(new Date().getFullYear() - 1))}
endDate={new Date()}
horizontal
values={heatmapData}
classForValue={classForValue}
titleForValue={titleForValue}
/>
</div>
</div>
<Collapsible.Trigger className="flex w-full flex-row items-center border-t border-palette-line/20 px-6 py-4">
<Icon name="caret" color="currentColor" width="1rem" height="1rem" />
<p className="ml-1">View details</p>
</Collapsible.Trigger>
<Collapsible.Content className="flex flex-row flex-wrap items-start justify-around gap-8 px-6 pb-9 data-[state=closed]:pb-0">
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{totalTx}</div>
<p className="text-xs text-palette-foregroundMuted">Transactions on Ethereum & Base</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{uniqueActiveDays}</div>
<p className="text-xs text-palette-foregroundMuted">Unique days active</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{longestStreak}</div>
<p className="text-xs text-palette-foregroundMuted">Day longest streak</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{currentStreak}</div>
<p className="text-xs text-palette-foregroundMuted">Day current streak</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{activityPeriod}</div>
<p className="text-xs text-palette-foregroundMuted">Day activity period</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{tokenSwapCount}</div>
<p className="text-xs text-palette-foregroundMuted">Token swaps performed</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{bridgeCount}</div>
<p className="text-xs text-palette-foregroundMuted">Bridge transactions</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{lendCount}</div>
<p className="text-xs text-palette-foregroundMuted">Lend/borrow/stake transactions</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{ensCount}</div>
<p className="text-xs text-palette-foregroundMuted">ENS contract interactions</p>
</div>
<div className="w-28">
<div className="text-xl font-medium text-palette-primary">{contractsDeployed}</div>
<p className="text-xs text-palette-foregroundMuted">Smart contracts deployed</p>
</div>
</Collapsible.Content>
</Collapsible.Root>
);
}

View File

@@ -363,6 +363,7 @@ __metadata:
"@heroicons/react": ^2.1.3
"@lottiefiles/dotlottie-react": ^0.8.10
"@radix-ui/react-accordion": ^1.2.0
"@radix-ui/react-collapsible": ^1.1.0
"@radix-ui/react-popover": ^1.1.1
"@radix-ui/react-tooltip": ^1.1.2
"@rainbow-me/rainbowkit": ^2.1.5
@@ -371,6 +372,7 @@ __metadata:
"@types/node": 18.11.18
"@types/pg": ^8.11.6
"@types/react": ^18
"@types/react-calendar-heatmap": ^1.6.7
"@types/react-copy-to-clipboard": ^5.0.7
"@types/react-dom": ^18
"@vercel/blob": ^0.23.4
@@ -401,6 +403,7 @@ __metadata:
qrcode.react: ^3.1.0
react: ^18.2.0
react-blockies: ^1.4.1
react-calendar-heatmap: ^1.9.0
react-copy-to-clipboard: ^5.1.0
react-dom: ^18.2.0
react-intersection-observer: ^9.10.2
@@ -5949,7 +5952,7 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-collapsible@npm:1.1.0":
"@radix-ui/react-collapsible@npm:1.1.0, @radix-ui/react-collapsible@npm:^1.1.0":
version: 1.1.0
resolution: "@radix-ui/react-collapsible@npm:1.1.0"
dependencies:
@@ -8286,6 +8289,15 @@ __metadata:
languageName: node
linkType: hard
"@types/react-calendar-heatmap@npm:^1.6.7":
version: 1.6.7
resolution: "@types/react-calendar-heatmap@npm:1.6.7"
dependencies:
"@types/react": "*"
checksum: 5a7734ad1253b550909302cbcf5b88fca6d05aa3aed07d80671312ff32a0aec90ddf9a6d382cdd63f055d305b0ebffe961c40e66b06940e627112ae837f330dd
languageName: node
linkType: hard
"@types/react-copy-to-clipboard@npm:^5.0.4, @types/react-copy-to-clipboard@npm:^5.0.7":
version: 5.0.7
resolution: "@types/react-copy-to-clipboard@npm:5.0.7"
@@ -18936,6 +18948,13 @@ __metadata:
languageName: node
linkType: hard
"memoize-one@npm:^5.0.0":
version: 5.2.1
resolution: "memoize-one@npm:5.2.1"
checksum: a3cba7b824ebcf24cdfcd234aa7f86f3ad6394b8d9be4c96ff756dafb8b51c7f71320785fbc2304f1af48a0467cbbd2a409efc9333025700ed523f254cb52e3d
languageName: node
linkType: hard
"meow@npm:^6.1.1":
version: 6.1.1
resolution: "meow@npm:6.1.1"
@@ -22103,6 +22122,18 @@ __metadata:
languageName: node
linkType: hard
"react-calendar-heatmap@npm:^1.9.0":
version: 1.9.0
resolution: "react-calendar-heatmap@npm:1.9.0"
dependencies:
memoize-one: ^5.0.0
prop-types: ^15.6.2
peerDependencies:
react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 00ee57373f9827c383f5060520ab57cce99960ca8c6e35fa7a9bd885ab98642560473d617e3696dc2f4116e5ca47a42bf9f9f6df69860b87249c1ce2d3f7a0b3
languageName: node
linkType: hard
"react-copy-to-clipboard@npm:^5.1.0":
version: 5.1.0
resolution: "react-copy-to-clipboard@npm:5.1.0"