From 20f884affe0b84e42669172d87b6e9d4d67b50bc Mon Sep 17 00:00:00 2001 From: Kaylee George Date: Tue, 4 Jun 2024 13:21:12 -0700 Subject: [PATCH 1/5] generate metadata and image preview --- .../[blockNumber]/[logIndex]/apiGetLog.ts | 2 +- app/components/image-gen/LinkPreview.tsx | 106 +++++++++++ .../[blockNumber]/[logIndex]/page.tsx | 32 ++-- app/preview/route.tsx | 48 +++++ app/utils/getAbsoluteUrl.ts | 16 ++ app/utils/getLogData.ts | 28 +++ app/utils/linkMetaTags.ts | 90 ++++++++++ app/utils/serialization.ts | 4 + package.json | 1 + pnpm-lock.yaml | 165 +++++++++++++++++- 10 files changed, 464 insertions(+), 28 deletions(-) create mode 100644 app/components/image-gen/LinkPreview.tsx create mode 100644 app/preview/route.tsx create mode 100644 app/utils/getAbsoluteUrl.ts create mode 100644 app/utils/getLogData.ts create mode 100644 app/utils/linkMetaTags.ts diff --git a/app/api/[chainId]/[blockNumber]/[logIndex]/apiGetLog.ts b/app/api/[chainId]/[blockNumber]/[logIndex]/apiGetLog.ts index 499a579..cb46461 100644 --- a/app/api/[chainId]/[blockNumber]/[logIndex]/apiGetLog.ts +++ b/app/api/[chainId]/[blockNumber]/[logIndex]/apiGetLog.ts @@ -48,7 +48,7 @@ export async function apiGetLog(params: { ...details, }; console.log(`[API] loaded log ${chainId}/${blockNumber}/${logIndex}: ${JSON.stringify(ret)}`); - return Response.json(ret); + return new Response(JSON.stringify(ret)); } async function fetchEventLogFromViem( diff --git a/app/components/image-gen/LinkPreview.tsx b/app/components/image-gen/LinkPreview.tsx new file mode 100644 index 0000000..579ef72 --- /dev/null +++ b/app/components/image-gen/LinkPreview.tsx @@ -0,0 +1,106 @@ +import { formatValue, truncateAddress } from '@/app/utils/formatting'; +import { getAbsoluteUrl } from '@/app/utils/getAbsoluteUrl'; +import stablecoinsAddresses from '@/app/utils/tokens/stablecoins'; +import { AddressProfile, EventLog, Transfer } from '@/app/utils/types'; + +export function LinkPreviewImg({ + transferData, + addressProfileFrom, + addressProfileTo, + eventLogData, + latestFinalizedBlockNumber, +}: { + transferData: Transfer; + addressProfileFrom: AddressProfile; + addressProfileTo: AddressProfile; + eventLogData: EventLog; + latestFinalizedBlockNumber: number; +}) { + console.log(`[LINK PREVIEW] ${JSON.stringify(addressProfileFrom)}`); + return ( +
+
+ +
+
+
+ ); +} + +function Content({ + transferData, + addressProfileFrom, + addressProfileTo, +}: { + transferData: Transfer; + addressProfileFrom: AddressProfile; + addressProfileTo: AddressProfile; +}) { + // Format token value + const { tokenSymbol, tokenDecimal, value: tokenValue } = transferData; + const value = formatValue(Number(tokenValue) / Number(10 ** Number(tokenDecimal))); + + const isStablecoin = stablecoinsAddresses.includes(transferData.contractAddress); + const amountStr = `${isStablecoin ? '$' : ''}${value}`; + + // Format addresses + const sender = + addressProfileFrom.account?.name || truncateAddress(addressProfileFrom.accountAddress); + const receiver = + addressProfileTo.account?.name || truncateAddress(addressProfileTo.accountAddress); + + const text = `${sender} sent ${amountStr} ${tokenSymbol} to ${receiver}`; + return
{text}
; +} + +function Footer({ + eventLogData, + latestFinalizedBlockNumber, +}: { + eventLogData: EventLog; + latestFinalizedBlockNumber: number; +}) { + const isFinalized = eventLogData.blockNumber <= latestFinalizedBlockNumber; + const status = isFinalized ? 'FINALIZED' : 'NOT FINALIZED'; + const chain = eventLogData.chainId; + return ( +
+ CHAIN: {chain} | STATUS: {status} +
+ ); +} diff --git a/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx b/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx index cb7ea53..d59496a 100644 --- a/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx +++ b/app/l/[chainId]/[blockNumber]/[logIndex]/page.tsx @@ -1,25 +1,13 @@ -import { apiGetLog } from '@/app/api/[chainId]/[blockNumber]/[logIndex]/apiGetLog'; import ERC20TransferSection from '@/app/components/logs/ERC20TransferSection'; import EventLogSection from '@/app/components/logs/EventLogSection'; import UnsupportedLogSection from '@/app/components/logs/UnsupportedLogSection'; import { Wiggle } from '@/app/components/shared/Wiggle'; +import { getLogData, LogData } from '@/app/utils/getLogData'; +import { createMetadataForTransfer } from '@/app/utils/linkMetaTags'; +import { Metadata } from 'next'; -/** - * Fetch log data from API. - * - * @param {string} chainId - The chain ID. - * @param {string} blockNumber - The block number. - * @param {string} logIndex - The log index. - * @returns {Object} The result from API fetch. - */ -async function getLogData(chainId: string, blockNumber: string, logIndex: string) { - // Revalidate every 10 minutes. - const res = await apiGetLog({ chainId, blockNumber, logIndex }); - if (!res.ok) { - console.error('Failed to fetch log', res.status); - return null; - } - return res.json(); +interface LinkProps { + params: { chainId: string; blockNumber: string; logIndex: string }; } /** @@ -37,7 +25,7 @@ export default async function Page({ params: { chainId: string; blockNumber: string; logIndex: string }; }) { console.log(`[LOG PAGE] chainId: ${chainId}, blockNumber: ${blockNumber}, logIndex: ${logIndex}`); - const logData = await getLogData(chainId, blockNumber, logIndex); + const logData: LogData = await getLogData(chainId, blockNumber, logIndex); return (
@@ -65,3 +53,11 @@ export default async function Page({
); } + +// Generate metadata for a transfer log. +export async function generateMetadata(props: LinkProps): Promise { + const { chainId, blockNumber, logIndex } = props.params; + const logData: LogData = await getLogData(chainId, blockNumber, logIndex); + const metadata = createMetadataForTransfer(logData); + return metadata; +} diff --git a/app/preview/route.tsx b/app/preview/route.tsx new file mode 100644 index 0000000..e5cee0b --- /dev/null +++ b/app/preview/route.tsx @@ -0,0 +1,48 @@ +import { ImageResponse } from '@vercel/og'; +import { LinkPreviewImg } from '../components/image-gen/LinkPreview'; +import { getLogData } from '../utils/getLogData'; + +export const runtime = 'edge'; + +// Generate link preview image. +// Note: tailwind CSS is not supported in Vercel previews. +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + + // Transfer preview parameters. + if ( + !searchParams.has('chainId') || + !searchParams.has('blockNumber') || + !searchParams.has('logIndex') + ) { + throw new Error('Invalid preview parameters'); + } + const chainId = searchParams.get('chainId')!; + const blockNumber = searchParams.get('blockNumber')!; + const logIndex = searchParams.get('logIndex')!; + console.log( + `[PREVIEW] generating preview for transfer (chainId: ${chainId}, blockNumber: ${blockNumber}, logIndex: ${logIndex})`, + ); + + const logData = await getLogData(chainId, blockNumber, logIndex); + if (!logData) { + console.log('Transfer log not found'); + // TODO: show transfer log not found page. + } + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + }, + ); +} diff --git a/app/utils/getAbsoluteUrl.ts b/app/utils/getAbsoluteUrl.ts new file mode 100644 index 0000000..73b3a81 --- /dev/null +++ b/app/utils/getAbsoluteUrl.ts @@ -0,0 +1,16 @@ +const publicUrl = (function () { + if (process.env.NEXT_PUBLIC_URL) { + return process.env.NEXT_PUBLIC_URL; + } else if (process.env.VERCEL_URL) { + return `https://${process.env.VERCEL_URL}`; + } else { + return 'https://ethreceipts.org'; + } +})(); + +export function getAbsoluteUrl(path: string) { + if (!path.startsWith('/')) { + path = `/${path}`; + } + return `${publicUrl}${path}`; +} diff --git a/app/utils/getLogData.ts b/app/utils/getLogData.ts new file mode 100644 index 0000000..7789ea9 --- /dev/null +++ b/app/utils/getLogData.ts @@ -0,0 +1,28 @@ +import { apiGetLog } from '../api/[chainId]/[blockNumber]/[logIndex]/apiGetLog'; +import { Transfer, AddressProfile, EventLog } from './types'; + +export type LogData = { + transferData: Transfer; + fromAddressProfile: AddressProfile; + toAddressProfile: AddressProfile; + eventLogData: EventLog; + latestFinalizedBlockNumber: number; +}; + +/** + * Fetch log data from API. + * + * @param {string} chainId - The chain ID. + * @param {string} blockNumber - The block number. + * @param {string} logIndex - The log index. + * @returns {Object} The result from API fetch. + */ +export async function getLogData(chainId: string, blockNumber: string, logIndex: string) { + // Revalidate every 10 minutes. + const res = await apiGetLog({ chainId, blockNumber, logIndex }); + if (!res.ok) { + console.error('Failed to fetch log', res.status); + return null; + } + return res.json(); +} diff --git a/app/utils/linkMetaTags.ts b/app/utils/linkMetaTags.ts new file mode 100644 index 0000000..ba5ea0c --- /dev/null +++ b/app/utils/linkMetaTags.ts @@ -0,0 +1,90 @@ +import { Metadata } from 'next'; +import { getChainExplorerByChainId } from './getExplorerURL'; +import stablecoinsAddresses from './tokens/stablecoins'; +import { getAbsoluteUrl } from './getAbsoluteUrl'; +import { formatValue } from './formatting'; +import { LogData } from './getLogData'; + +// Creates a metadata object for a transfer log. +export function createMetadataForTransfer(logData: LogData): Metadata { + const { + transferData, + fromAddressProfile, + toAddressProfile, + eventLogData, + latestFinalizedBlockNumber, + } = logData; + + // Create title + const sender = fromAddressProfile.account?.name || fromAddressProfile.accountAddress; + const receiver = toAddressProfile.account?.name || toAddressProfile.accountAddress; + const title = `Eth Receipts Transaction Receipt`; + + // Create description + const chainName = getChainExplorerByChainId(eventLogData.chainId) ?? eventLogData.chainId; + const status = latestFinalizedBlockNumber >= eventLogData?.blockNumber ? 'Finalized' : 'Pending'; + + const { tokenSymbol, tokenDecimal, value: tokenValue } = transferData; + const value = formatValue(Number(tokenValue) / Number(10 ** Number(tokenDecimal))); + const isStablecoin = stablecoinsAddresses.includes(transferData.contractAddress); + const amountStr = `${isStablecoin ? '$' : ''}${value}`; + + const description = `Transaction Receipt: ${sender} sent ${tokenSymbol} ${amountStr} to ${receiver} on ${chainName} (${status})`; + + // Create preview URL + const previewUrl = getPreviewURL( + eventLogData?.chainId.toString() ?? undefined, + eventLogData?.blockNumber.toString() ?? undefined, + eventLogData?.logIndex.toString() ?? undefined, + ); + + // Create metadata + const metadata = createMetadata(title, description, previewUrl); + return metadata; +} + +// Creates a metadata object. +function createMetadata(title: string, description: string, previewUrl: string): Metadata { + return { + title, + description, + icons: { + icon: '/logo-web-favicon.png', // TODO: change to icon + }, + openGraph: { + title, + description, + siteName: title, + images: [ + { + url: previewUrl, + alt: 'Eth Receipts', + width: 1200, + height: 630, + }, + ], + type: 'website', + }, + }; +} + +// Generates an OpenGraph preview link image URL. +// Also generates dynamically generated image preview (see preview/route.tsx). +function getPreviewURL( + chainId: string | undefined, + blockNumber: string | undefined, + logIndex: string | undefined, +) { + if (!chainId) { + return `${getAbsoluteUrl('/assets/eth-receipts-header.png')}`; + } + + let previewUrl = `/preview?chainId${chainId}`; + if (blockNumber) { + previewUrl += `&blockNumber=${blockNumber}`; + } + if (logIndex) { + previewUrl += `&logIndex=${logIndex}`; + } + return `${getAbsoluteUrl(previewUrl)}`; +} diff --git a/app/utils/serialization.ts b/app/utils/serialization.ts index ad7b420..1ad9fc7 100644 --- a/app/utils/serialization.ts +++ b/app/utils/serialization.ts @@ -10,3 +10,7 @@ interface BigInt { BigInt.prototype.toJSON = function () { return this.toString(); }; + +(BigInt.prototype as any).toJSON = function () { + return this.toString(); +}; diff --git a/package.json b/package.json index 2cb37ae..a40ead1 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@trpc/next": "11.0.0-rc.330", "@trpc/react-query": "11.0.0-rc.330", "@trpc/server": "11.0.0-rc.330", + "@vercel/og": "^0.6.2", "add": "^2.0.6", "next": "14.1.4", "pg": "^8.11.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1d7a9d..3ae297f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@trpc/server': specifier: 11.0.0-rc.330 version: 11.0.0-rc.330 + '@vercel/og': + specifier: ^0.6.2 + version: 0.6.2 add: specifier: ^2.0.6 version: 2.0.6 @@ -394,6 +397,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@resvg/resvg-wasm@2.4.0': + resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} + engines: {node: '>= 10'} + '@rushstack/eslint-patch@1.10.1': resolution: {integrity: sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==} @@ -406,6 +413,11 @@ packages: '@scure/bip39@1.2.1': resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} + '@shuding/opentype.js@1.4.0-beta.0': + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + '@swc/helpers@0.5.2': resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} @@ -514,6 +526,10 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vercel/og@0.6.2': + resolution: {integrity: sha512-OTe0KE37F5Y2eTys6eMnfopC+P4qr2ooXUTFyFPTplYSPwowmFk/HLD1FXtbKLjqsIH0SgekcJWad+C5uX4nkg==} + engines: {node: '>=16'} + abitype@1.0.0: resolution: {integrity: sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==} peerDependencies: @@ -642,6 +658,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -677,6 +697,9 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + caniuse-lite@1.0.30001600: resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==} @@ -712,6 +735,19 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + css-background-parser@0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + + css-box-shadow@1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -795,6 +831,9 @@ packages: electron-to-chromium@1.4.722: resolution: {integrity: sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ==} + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -845,6 +884,9 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -966,6 +1008,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fflate@0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1092,6 +1137,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hex-rgb@4.3.0: + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} + engines: {node: '>=6'} + ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -1290,6 +1339,9 @@ packages: resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} engines: {node: '>=14'} + linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -1441,10 +1493,16 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-css-color@0.2.1: + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1692,6 +1750,10 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + satori@0.10.9: + resolution: {integrity: sha512-XU9EELUEZuioT4acLIpCXxHcFzrsC8muvg0MY28d+TlqwxbkTzBmWbw+3+hnCzXT7YZ0Qm8k3eXktDaEu+qmEw==} + engines: {node: '>=16'} + scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} @@ -1752,6 +1814,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + string.prototype.matchall@4.0.11: resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} engines: {node: '>= 0.4'} @@ -1828,6 +1893,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1901,6 +1969,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + update-browserslist-db@1.0.13: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -1987,6 +2058,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoga-wasm-web@0.3.3: + resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} + zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} @@ -2204,6 +2278,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@resvg/resvg-wasm@2.4.0': {} + '@rushstack/eslint-patch@1.10.1': {} '@scure/base@1.1.6': {} @@ -2219,6 +2295,11 @@ snapshots: '@noble/hashes': 1.3.2 '@scure/base': 1.1.6 + '@shuding/opentype.js@1.4.0-beta.0': + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + '@swc/helpers@0.5.2': dependencies: tslib: 2.6.2 @@ -2332,6 +2413,12 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vercel/og@0.6.2': + dependencies: + '@resvg/resvg-wasm': 2.4.0 + satori: 0.10.9 + yoga-wasm-web: 0.3.3 + abitype@1.0.0(typescript@5.4.3)(zod@3.22.4): optionalDependencies: typescript: 5.4.3 @@ -2481,6 +2568,8 @@ snapshots: balanced-match@1.0.2: {} + base64-js@0.0.8: {} + binary-extensions@2.3.0: {} brace-expansion@1.1.11: @@ -2519,6 +2608,8 @@ snapshots: camelcase-css@2.0.1: {} + camelize@1.0.1: {} + caniuse-lite@1.0.30001600: {} chalk@4.1.2: @@ -2559,6 +2650,18 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-background-parser@0.1.0: {} + + css-box-shadow@1.0.0-3: {} + + css-color-keywords@1.0.0: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -2630,6 +2733,8 @@ snapshots: electron-to-chromium@1.4.722: {} + emoji-regex@10.3.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -2759,6 +2864,8 @@ snapshots: escalade@3.1.2: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-config-next@14.1.4(eslint@8.57.0)(typescript@5.4.3): @@ -2768,8 +2875,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -2787,13 +2894,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 4.3.4 enhanced-resolve: 5.16.0 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -2804,18 +2911,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -2825,7 +2932,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -2974,6 +3081,8 @@ snapshots: dependencies: reusify: 1.0.4 + fflate@0.7.4: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -3118,6 +3227,8 @@ snapshots: dependencies: function-bind: 1.1.2 + hex-rgb@4.3.0: {} + ignore@5.3.1: {} import-fresh@3.3.0: @@ -3304,6 +3415,11 @@ snapshots: lilconfig@3.1.1: {} + linebreak@1.1.0: + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + lines-and-columns@1.2.4: {} locate-path@6.0.0: @@ -3462,10 +3578,17 @@ snapshots: dependencies: p-limit: 3.1.0 + pako@0.2.9: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-css-color@0.2.1: + dependencies: + color-name: 1.1.4 + hex-rgb: 4.3.0 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -3696,6 +3819,19 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 + satori@0.10.9: + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-to-react-native: 3.2.0 + emoji-regex: 10.3.0 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-wasm-web: 0.3.3 + scheduler@0.23.0: dependencies: loose-envify: 1.4.0 @@ -3757,6 +3893,8 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string.prototype.codepointat@0.2.1: {} + string.prototype.matchall@4.0.11: dependencies: call-bind: 1.0.7 @@ -3863,6 +4001,8 @@ snapshots: dependencies: any-promise: 1.3.0 + tiny-inflate@1.0.3: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -3957,6 +4097,11 @@ snapshots: undici-types@5.26.5: {} + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + update-browserslist-db@1.0.13(browserslist@4.23.0): dependencies: browserslist: 4.23.0 @@ -4058,4 +4203,6 @@ snapshots: yocto-queue@0.1.0: {} + yoga-wasm-web@0.3.3: {} + zod@3.22.4: {} From 4126c1a6e126d68dc9165e8a93fdc816d912d53e Mon Sep 17 00:00:00 2001 From: Kaylee George Date: Tue, 4 Jun 2024 17:17:24 -0700 Subject: [PATCH 2/5] add /preview styling --- app/components/image-gen/LinkPreview.tsx | 116 ++++++++++++++++++----- app/components/image-gen/UserBubble.tsx | 63 ++++++++++++ app/components/shared/Wiggle.tsx | 4 +- public/assets/eth-receipts-one-liner.png | Bin 0 -> 21949 bytes 4 files changed, 159 insertions(+), 24 deletions(-) create mode 100644 app/components/image-gen/UserBubble.tsx create mode 100644 public/assets/eth-receipts-one-liner.png diff --git a/app/components/image-gen/LinkPreview.tsx b/app/components/image-gen/LinkPreview.tsx index 579ef72..cd6f9cf 100644 --- a/app/components/image-gen/LinkPreview.tsx +++ b/app/components/image-gen/LinkPreview.tsx @@ -1,7 +1,9 @@ -import { formatValue, truncateAddress } from '@/app/utils/formatting'; +/* eslint-disable @next/next/no-img-element */ +import { formatValue, getDateDifference, truncateAddress } from '@/app/utils/formatting'; import { getAbsoluteUrl } from '@/app/utils/getAbsoluteUrl'; import stablecoinsAddresses from '@/app/utils/tokens/stablecoins'; import { AddressProfile, EventLog, Transfer } from '@/app/utils/types'; +import { UserBubble } from './UserBubble'; export function LinkPreviewImg({ transferData, @@ -16,26 +18,29 @@ export function LinkPreviewImg({ eventLogData: EventLog; latestFinalizedBlockNumber: number; }) { - console.log(`[LINK PREVIEW] ${JSON.stringify(addressProfileFrom)}`); return (
+
+ {'Profile'} +
); } @@ -68,14 +80,71 @@ function Content({ const isStablecoin = stablecoinsAddresses.includes(transferData.contractAddress); const amountStr = `${isStablecoin ? '$' : ''}${value}`; - // Format addresses - const sender = - addressProfileFrom.account?.name || truncateAddress(addressProfileFrom.accountAddress); - const receiver = - addressProfileTo.account?.name || truncateAddress(addressProfileTo.accountAddress); + const memo = transferData.memo ?? ''; - const text = `${sender} sent ${amountStr} ${tokenSymbol} to ${receiver}`; - return
{text}
; + return ( +
+
+ + {amountStr} {tokenSymbol} + + {memo} +
+ +
+
+ FROM + +
+
+ TO + +
+
+
+ ); } function Footer({ @@ -85,22 +154,25 @@ function Footer({ eventLogData: EventLog; latestFinalizedBlockNumber: number; }) { - const isFinalized = eventLogData.blockNumber <= latestFinalizedBlockNumber; - const status = isFinalized ? 'FINALIZED' : 'NOT FINALIZED'; - const chain = eventLogData.chainId; + const time = new Date(Number(eventLogData.timestamp) * 1000); + const dateDifferenceStr = getDateDifference(time); + + const chain = eventLogData.chainName; + const chainName = chain[0].toUpperCase() + chain.slice(1); + return (
- CHAIN: {chain} | STATUS: {status} + {dateDifferenceStr} on {chainName}
); } diff --git a/app/components/image-gen/UserBubble.tsx b/app/components/image-gen/UserBubble.tsx new file mode 100644 index 0000000..3a2bb5c --- /dev/null +++ b/app/components/image-gen/UserBubble.tsx @@ -0,0 +1,63 @@ +/* eslint-disable @next/next/no-img-element */ +import { truncateAddress } from '@/app/utils/formatting'; +import { AddressProfile } from '@/app/utils/types'; + +export function UserBubble({ addressProfile }: { addressProfile: AddressProfile }) { + const address = truncateAddress(addressProfile.accountAddress); + const name = addressProfile.account?.name ?? undefined; + + return ( +
+ +
+ {name &&
{name}
} + {
{address}
} +
+
+ ); +} + +function UserProfilePicture({ name }: { name: string | undefined }) { + const nameInitial = name ? name[0].toUpperCase() : '0x'; + + return ( +
+
+ {nameInitial} +
+
+ ); +} diff --git a/app/components/shared/Wiggle.tsx b/app/components/shared/Wiggle.tsx index 1aa811f..fc4bdde 100644 --- a/app/components/shared/Wiggle.tsx +++ b/app/components/shared/Wiggle.tsx @@ -1,8 +1,8 @@ import { useMemo } from 'react'; +export const wiggleSvgDataUri = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzkiIGhlaWdodD0iNyIgdmlld0JveD0iMCAwIDM5IDciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xNDJfNSkiPgo8cGF0aCBkPSJNMCA2TDUgMUwxMCA2TDE1IDFMMTkuNSA2TDI0IDFMMjkgNkwzNCAxTDM5IDYiIHN0cm9rZT0iI0YzRjNGMyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzE0Ml81Ij4KPHJlY3Qgd2lkdGg9IjM5IiBoZWlnaHQ9IjciIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg=='; export function Wiggle() { - const wiggleSvgDataUri = - 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzkiIGhlaWdodD0iNyIgdmlld0JveD0iMCAwIDM5IDciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xNDJfNSkiPgo8cGF0aCBkPSJNMCA2TDUgMUwxMCA2TDE1IDFMMTkuNSA2TDI0IDFMMjkgNkwzNCAxTDM5IDYiIHN0cm9rZT0iI0YzRjNGMyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzE0Ml81Ij4KPHJlY3Qgd2lkdGg9IjM5IiBoZWlnaHQ9IjciIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg=='; const style = useMemo( () => ({ width: '100%', diff --git a/public/assets/eth-receipts-one-liner.png b/public/assets/eth-receipts-one-liner.png new file mode 100644 index 0000000000000000000000000000000000000000..7fa19ab30298124853f987990eefb0c47bc0b4fb GIT binary patch literal 21949 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 Date: Tue, 4 Jun 2024 17:27:13 -0700 Subject: [PATCH 3/5] minor changes --- app/preview/route.tsx | 1 - app/utils/linkMetaTags.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/app/preview/route.tsx b/app/preview/route.tsx index e5cee0b..91ba2c0 100644 --- a/app/preview/route.tsx +++ b/app/preview/route.tsx @@ -27,7 +27,6 @@ export async function GET(request: Request) { const logData = await getLogData(chainId, blockNumber, logIndex); if (!logData) { console.log('Transfer log not found'); - // TODO: show transfer log not found page. } return new ImageResponse( diff --git a/app/utils/linkMetaTags.ts b/app/utils/linkMetaTags.ts index ba5ea0c..8a824e5 100644 --- a/app/utils/linkMetaTags.ts +++ b/app/utils/linkMetaTags.ts @@ -48,9 +48,6 @@ function createMetadata(title: string, description: string, previewUrl: string): return { title, description, - icons: { - icon: '/logo-web-favicon.png', // TODO: change to icon - }, openGraph: { title, description, From 4085dd5cb8501af1939ac3083decd294cb9255e8 Mon Sep 17 00:00:00 2001 From: Kaylee George Date: Tue, 4 Jun 2024 17:31:06 -0700 Subject: [PATCH 4/5] change text --- app/utils/linkMetaTags.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/utils/linkMetaTags.ts b/app/utils/linkMetaTags.ts index 8a824e5..78bf387 100644 --- a/app/utils/linkMetaTags.ts +++ b/app/utils/linkMetaTags.ts @@ -2,7 +2,7 @@ import { Metadata } from 'next'; import { getChainExplorerByChainId } from './getExplorerURL'; import stablecoinsAddresses from './tokens/stablecoins'; import { getAbsoluteUrl } from './getAbsoluteUrl'; -import { formatValue } from './formatting'; +import { formatValue, truncateAddress } from './formatting'; import { LogData } from './getLogData'; // Creates a metadata object for a transfer log. @@ -16,11 +16,14 @@ export function createMetadataForTransfer(logData: LogData): Metadata { } = logData; // Create title - const sender = fromAddressProfile.account?.name || fromAddressProfile.accountAddress; - const receiver = toAddressProfile.account?.name || toAddressProfile.accountAddress; const title = `Eth Receipts Transaction Receipt`; // Create description + const sender = + fromAddressProfile.account?.name || truncateAddress(fromAddressProfile.accountAddress); + const receiver = + toAddressProfile.account?.name || truncateAddress(toAddressProfile.accountAddress); + const chainName = getChainExplorerByChainId(eventLogData.chainId) ?? eventLogData.chainId; const status = latestFinalizedBlockNumber >= eventLogData?.blockNumber ? 'Finalized' : 'Pending'; @@ -29,7 +32,7 @@ export function createMetadataForTransfer(logData: LogData): Metadata { const isStablecoin = stablecoinsAddresses.includes(transferData.contractAddress); const amountStr = `${isStablecoin ? '$' : ''}${value}`; - const description = `Transaction Receipt: ${sender} sent ${tokenSymbol} ${amountStr} to ${receiver} on ${chainName} (${status})`; + const description = `${sender} sent ${amountStr} ${tokenSymbol} to ${receiver} on ${chainName}.`; // Create preview URL const previewUrl = getPreviewURL( From 4241a264512ca02204d16d72cfa75ab8355c2577 Mon Sep 17 00:00:00 2001 From: Kaylee George Date: Tue, 4 Jun 2024 17:33:17 -0700 Subject: [PATCH 5/5] small metadata changes --- app/utils/linkMetaTags.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/utils/linkMetaTags.ts b/app/utils/linkMetaTags.ts index 78bf387..74ac58e 100644 --- a/app/utils/linkMetaTags.ts +++ b/app/utils/linkMetaTags.ts @@ -36,9 +36,9 @@ export function createMetadataForTransfer(logData: LogData): Metadata { // Create preview URL const previewUrl = getPreviewURL( - eventLogData?.chainId.toString() ?? undefined, - eventLogData?.blockNumber.toString() ?? undefined, - eventLogData?.logIndex.toString() ?? undefined, + eventLogData.chainId.toString() ?? undefined, + eventLogData.blockNumber.toString() ?? undefined, + eventLogData.logIndex.toString() ?? undefined, ); // Create metadata @@ -79,7 +79,7 @@ function getPreviewURL( return `${getAbsoluteUrl('/assets/eth-receipts-header.png')}`; } - let previewUrl = `/preview?chainId${chainId}`; + let previewUrl = `/preview?chainId=${chainId}`; if (blockNumber) { previewUrl += `&blockNumber=${blockNumber}`; }