From 182fd19312fafed7aea97191c5ae8ba60a005fa4 Mon Sep 17 00:00:00 2001 From: Jordan Frankfurt Date: Wed, 2 Oct 2024 15:16:00 -0500 Subject: [PATCH] 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) --- apps/web/.env.local.example | 6 +- apps/web/package.json | 3 + apps/web/pages/api/proxy/index.ts | 70 +++ apps/web/public/images/base-loading.gif | Bin 0 -> 74268 bytes .../Basenames/UsernameProfile/index.tsx | 4 +- .../UsernameProfileContent/index.tsx | 2 + .../UsernameProfileSectionHeatmap/cal.css | 23 + .../contracts.ts | 371 ++++++++++++++ .../UsernameProfileSectionHeatmap/index.tsx | 478 ++++++++++++++++++ yarn.lock | 33 +- 10 files changed, 986 insertions(+), 4 deletions(-) create mode 100644 apps/web/pages/api/proxy/index.ts create mode 100644 apps/web/public/images/base-loading.gif create mode 100644 apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/cal.css create mode 100644 apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/contracts.ts create mode 100644 apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx diff --git a/apps/web/.env.local.example b/apps/web/.env.local.example index 42cb83ac44..06298bfcf3 100644 --- a/apps/web/.env.local.example +++ b/apps/web/.env.local.example @@ -23,4 +23,8 @@ DATADOG_CLIENT_ID= NEYNAR_API_KEY= FARCASTER_DEVELOPER_FID= -FARCASTER_DEVELOPER_MNEMONIC= \ No newline at end of file +FARCASTER_DEVELOPER_MNEMONIC= + +ETHERSCAN_API_KEY= +BASESCAN_API_KEY= +TALENT_PROTOCOL_API_KEY= \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index ef0e29fc91..1ac7a07893 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -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", diff --git a/apps/web/pages/api/proxy/index.ts b/apps/web/pages/api/proxy/index.ts new file mode 100644 index 0000000000..e22633dbdc --- /dev/null +++ b/apps/web/pages/api/proxy/index.ts @@ -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; // 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' }); + } +} diff --git a/apps/web/public/images/base-loading.gif b/apps/web/public/images/base-loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..84e1b0309f7ab6eb18b5cd5ab26a452a22faaba2 GIT binary patch literal 74268 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 s^7MbTJMNWNAK54*0J_cp2gaxfjsO4v literal 0 HcmV?d00001 diff --git a/apps/web/src/components/Basenames/UsernameProfile/index.tsx b/apps/web/src/components/Basenames/UsernameProfile/index.tsx index b96ac34cad..066018fe5f 100644 --- a/apps/web/src/components/Basenames/UsernameProfile/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfile/index.tsx @@ -17,8 +17,8 @@ export default function UsernameProfile() { ); return ( -
-
+
+
diff --git a/apps/web/src/components/Basenames/UsernameProfileContent/index.tsx b/apps/web/src/components/Basenames/UsernameProfileContent/index.tsx index 323d83e86e..e98900ffad 100644 --- a/apps/web/src/components/Basenames/UsernameProfileContent/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileContent/index.tsx @@ -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 (
+ {USERNAMES_PINNED_CASTS_ENABLED && } diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/cal.css b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/cal.css new file mode 100644 index 0000000000..10f16cb374 --- /dev/null +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/cal.css @@ -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; +} diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/contracts.ts b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/contracts.ts new file mode 100644 index 0000000000..694038f391 --- /dev/null +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/contracts.ts @@ -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', +]); diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx new file mode 100644 index 0000000000..42013e5441 --- /dev/null +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx @@ -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(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(true); + const [isDataFetched, setIsDataFetched] = useState(false); + const [totalTx, setTotalTx] = useState(0); + const [tokenSwapCount, setTokenSwapCount] = useState(0); + const [ensCount, setEnsCount] = useState(0); + const [bridgeCount, setBridgeCount] = useState(0); + const [lendCount, setLendCount] = useState(0); + const [buildCount, setBuildCount] = useState(0); + const [heatmapData, setHeatmapData] = useState([]); + const [uniqueActiveDays, setUniqueActiveDays] = useState(0); + const [longestStreak, setLongestStreak] = useState(0); + const [currentStreak, setCurrentStreak] = useState(0); + const [activityPeriod, setActivityPeriod] = useState(0); + const [ethereumDeployments, setEthereumDeployments] = useState([]); + const [baseDeployments, setBaseDeployments] = useState([]); + const [totalTransactionsList, setTotalTransactionsList] = useState([]); + + const classForValue = useCallback((value: ReactCalendarHeatmapValue | 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 | undefined) => { + return value ? `${value.date}: ${value.count} transactions` : ''; + }, []); + + const { profileAddress } = useUsernameProfile(); + + const generateHeatmapData = (transactions: Transaction[]): HeatmapValue[] => { + const dateMap: Record = {}; + 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 => { + 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 ( +
+
+ +
+
+ ); + } + + return ( + +
+
+

ONCHAIN SCORE

+

{finalScore}/100

+
+

Less

+ + + + + + + +

More

+
+
+
+ +
+
+ + +

View details

+
+ +
+
{totalTx}
+

Transactions on Ethereum & Base

+
+
+
{uniqueActiveDays}
+

Unique days active

+
+
+
{longestStreak}
+

Day longest streak

+
+
+
{currentStreak}
+

Day current streak

+
+
+
{activityPeriod}
+

Day activity period

+
+
+
{tokenSwapCount}
+

Token swaps performed

+
+
+
{bridgeCount}
+

Bridge transactions

+
+
+
{lendCount}
+

Lend/borrow/stake transactions

+
+
+
{ensCount}
+

ENS contract interactions

+
+
+
{contractsDeployed}
+

Smart contracts deployed

+
+
+
+ ); +} diff --git a/yarn.lock b/yarn.lock index a4d84c6466..479e481b3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"