feat: dynamically generated images for rich link previews (#6902)
* feat: add token and nft injection * feat: basic tests * fix: get jest configured properly * fix: change timeout * fix: uninstall port ready * fix: readd port ready * fix: local tests work * Update yarn.lock * add lint disable for setup files * fix: update dependencies * fix: basic test suite for nfts/tokens * feat: collection data * fix: make tests more comprehensive * fix: change matches to contains * fix: tests for twitter alt image tag * fix: image gen * fix: add patch-package * fix: update yarn install * feat: basic image gen for nfts and collections * fix: remove vibrant attempt * use watermark asset * dynamically grab color * modularize code and prototype for token preview * refactor code * finalize css * fix color grabber * update tests * fix up css * refactor code a bit more * remove console logs * tests * update tests * update images based on design feedback * network logos * update lint * slight refactoring * more refactoring * fix packages * Update yarn.lock * remove dynamically generated image stuff * Revert "remove dynamically generated image stuff" This reverts commit a80241edb3a970a724b9a07ce36e492ff8a1c2af. * change image reference and revamp tests * cleanup return values * Create README.md * Revert "Create README.md" This reverts commit 7a91c98d384995fba914c9bf9a2fb3072793621f. * First round of feedback * comments * feat: cache * Update test.yml * Update test.yml * Update test.yml * feedback round 2 * final feedback * final final feedback * add coverage and other options * Update test.yml * start typecheck * update cache * update snapshots? * Update jest.config.json * Update jest.config.json * give timeout some buffer * update import * upgrade ts * fix typing for apollo deps * finalize typechecks * downgrade typescript to original version * add cache directory to jest * remove coverage * remove google analytics from tests * merge main * remove timeout * update tests * update graphql queries * review changes * try cache setup * Update cache.test.ts * make cache helper function * cache test * remove unneeded test causing issues * feat: parallelize cache (#6930) * feat: parallelize cache? * remove graph query from concurrency await * most of feedback * move tests * update token tests * singleton cache * restructuring res and cache promise * abstract away repeated graph logic * update tests and functions * refactor * update typing, parallelize, and start tests * fix one tsc issue * final feedback * Update yarn.lock * final final feedback * add svgs * try and setup svg * stashing changes * cleanup! * prepare for start of feedback? * LESS GOO * modify versioning * fix: update wrangler version * Update yarn.lock * downgrade wrangler * Update yarn.lock * Update yarn.lock * fix type error * update github test * cleanup tests * Delete custom.d.ts * fix: cloudfunctions * update tests * final touchups * lint * change github action * Update yarn.lock * styling updates * nate's feedback * feedback p1 * typing feedback * update yarn * Create wrangler.toml * move wrangler.toml location * last try * Delete wrangler.toml * use 2.20? * remove comment * Update yarn.lock * change compatibility date * update wrangler and fix bugs * Update colorthief+2.4.0.patch * build: cleanup flags * cleaner patches * update compatibility date * quick tweeks * cleanup rendering and lint * final color update --------- Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
This commit is contained in:
parent
9fbdc3cab1
commit
9954f9502d
4
.github/actions/setup/action.yml
vendored
4
.github/actions/setup/action.yml
vendored
@ -23,6 +23,10 @@ runs:
|
|||||||
- if: steps.install-cache.outputs.cache-hit != 'true'
|
- if: steps.install-cache.outputs.cache-hit != 'true'
|
||||||
run: yarn install --frozen-lockfile --ignore-scripts
|
run: yarn install --frozen-lockfile --ignore-scripts
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
# Run patch-package to apply patches to dependencies.
|
||||||
|
- run: yarn patch-package
|
||||||
|
shell: bash
|
||||||
|
|
||||||
# Contracts are compiled from source. If source hasn't changed, the contracts do not need to be re-compiled.
|
# Contracts are compiled from source. If source hasn't changed, the contracts do not need to be re-compiled.
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
|
71
functions/api/image/nfts/asset/[[index]].tsx
Normal file
71
functions/api/image/nfts/asset/[[index]].tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/* eslint-disable import/no-unused-modules */
|
||||||
|
import { ImageResponse } from '@vercel/og'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { WATERMARK_URL } from '../../../../constants'
|
||||||
|
import getAsset from '../../../../utils/getAsset'
|
||||||
|
import getFont from '../../../../utils/getFont'
|
||||||
|
import { getRequest } from '../../../../utils/getRequest'
|
||||||
|
|
||||||
|
export const onRequest: PagesFunction = async ({ params, request }) => {
|
||||||
|
try {
|
||||||
|
const origin = new URL(request.url).origin
|
||||||
|
const { index } = params
|
||||||
|
const collectionAddress = index[0]?.toString()
|
||||||
|
const tokenId = index[1]?.toString()
|
||||||
|
const cacheUrl = origin + '/nfts/asset/' + collectionAddress + '/' + tokenId
|
||||||
|
|
||||||
|
const data = await getRequest(
|
||||||
|
cacheUrl,
|
||||||
|
() => getAsset(collectionAddress, tokenId, cacheUrl),
|
||||||
|
(data): data is NonNullable<Awaited<ReturnType<typeof getAsset>>> => Boolean(data.ogImage)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return new Response('Asset not found.', { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const fontData = await getFont()
|
||||||
|
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '1200px',
|
||||||
|
height: '630px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={data.ogImage} alt={data.title} width="1200px" />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '72px',
|
||||||
|
right: '72px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '24px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={WATERMARK_URL} alt="Uniswap" height="72px" width="324px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'Inter',
|
||||||
|
data: fontData,
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
) as Response
|
||||||
|
} catch (error: any) {
|
||||||
|
return new Response(error.message || error.toString(), { status: 500 })
|
||||||
|
}
|
||||||
|
}
|
21
functions/api/image/nfts/asset/nftImage.test.ts
Normal file
21
functions/api/image/nfts/asset/nftImage.test.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const assetImageUrl = [
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/804',
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947',
|
||||||
|
]
|
||||||
|
|
||||||
|
test.each(assetImageUrl)('assetImageUrl', async (url) => {
|
||||||
|
const response = await fetch(new Request(url))
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.headers.get('content-type')).toBe('image/png')
|
||||||
|
})
|
||||||
|
|
||||||
|
const invalidAssetImageUrl = [
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/100000',
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||||
|
]
|
||||||
|
|
||||||
|
test.each(invalidAssetImageUrl)('invalidAssetImageUrl', async (url) => {
|
||||||
|
const response = await fetch(new Request(url))
|
||||||
|
expect(response.status).toBe(404)
|
||||||
|
})
|
117
functions/api/image/nfts/collection/[index].tsx
Normal file
117
functions/api/image/nfts/collection/[index].tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/* eslint-disable import/no-unused-modules */
|
||||||
|
import { ImageResponse } from '@vercel/og'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { CHECK_URL, WATERMARK_URL } from '../../../../constants'
|
||||||
|
import getCollection from '../../../../utils/getCollection'
|
||||||
|
import getColor from '../../../../utils/getColor'
|
||||||
|
import getFont from '../../../../utils/getFont'
|
||||||
|
import { getRequest } from '../../../../utils/getRequest'
|
||||||
|
|
||||||
|
export const onRequest: PagesFunction = async ({ params, request }) => {
|
||||||
|
try {
|
||||||
|
const origin = new URL(request.url).origin
|
||||||
|
const { index } = params
|
||||||
|
const collectionAddress = index?.toString()
|
||||||
|
const cacheUrl = origin + '/nfts/collection/' + collectionAddress
|
||||||
|
|
||||||
|
const data = await getRequest(
|
||||||
|
cacheUrl,
|
||||||
|
() => getCollection(collectionAddress, cacheUrl),
|
||||||
|
(data): data is NonNullable<Awaited<ReturnType<typeof getCollection>>> =>
|
||||||
|
Boolean(data.ogImage && data.name && data.isVerified)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return new Response('Asset not found.', { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const [fontData, palette] = await Promise.all([getFont(), getColor(data.ogImage)])
|
||||||
|
|
||||||
|
// Split name into words to wrap them since satori does not support inline text wrapping
|
||||||
|
const words = data.name.split(' ')
|
||||||
|
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'black',
|
||||||
|
display: 'flex',
|
||||||
|
width: '1200px',
|
||||||
|
height: '630px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: `rgba(${palette[0]}, ${palette[1]}, ${palette[2]}, 0.75)`,
|
||||||
|
padding: '72px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: '48px',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={data.ogImage}
|
||||||
|
alt={data.name}
|
||||||
|
width="500px"
|
||||||
|
height="500px"
|
||||||
|
style={{
|
||||||
|
borderRadius: '60px',
|
||||||
|
objectFit: 'cover',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '32px',
|
||||||
|
width: '45%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
gap: '12px',
|
||||||
|
fontSize: '72px',
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: 'white',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{words.map((word: string) => (
|
||||||
|
<text key={word + index}>{word}</text>
|
||||||
|
))}
|
||||||
|
{data.isVerified && <img src={CHECK_URL} height="54px" />}
|
||||||
|
</div>
|
||||||
|
<img src={WATERMARK_URL} alt="Uniswap" height="72px" width="324px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'Inter',
|
||||||
|
data: fontData,
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
) as Response
|
||||||
|
} catch (error: any) {
|
||||||
|
return new Response(error.message || error.toString(), { status: 500 })
|
||||||
|
}
|
||||||
|
}
|
20
functions/api/image/nfts/collection/collectionImage.test.ts
Normal file
20
functions/api/image/nfts/collection/collectionImage.test.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const collectionImageUrl = [
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
|
||||||
|
]
|
||||||
|
|
||||||
|
test.each(collectionImageUrl)('collectionImageUrl', async (url) => {
|
||||||
|
const response = await fetch(new Request(url))
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.headers.get('content-type')).toBe('image/png')
|
||||||
|
})
|
||||||
|
|
||||||
|
const invalidCollectionImageUrl = [
|
||||||
|
'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c545',
|
||||||
|
]
|
||||||
|
|
||||||
|
test.each(invalidCollectionImageUrl)('invalidAssetImageUrl', async (url) => {
|
||||||
|
const response = await fetch(new Request(url))
|
||||||
|
expect(response.status).toBe(404)
|
||||||
|
})
|
174
functions/api/image/tokens/[[index]].tsx
Normal file
174
functions/api/image/tokens/[[index]].tsx
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/* eslint-disable import/no-unused-modules */
|
||||||
|
import { ImageResponse } from '@vercel/og'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { WATERMARK_URL } from '../../../constants'
|
||||||
|
import getColor from '../../../utils/getColor'
|
||||||
|
import getFont from '../../../utils/getFont'
|
||||||
|
import getNetworkLogoUrl from '../../../utils/getNetworkLogoURL'
|
||||||
|
import { getRequest } from '../../../utils/getRequest'
|
||||||
|
import getToken from '../../../utils/getToken'
|
||||||
|
|
||||||
|
export const onRequest: PagesFunction = async ({ params, request }) => {
|
||||||
|
try {
|
||||||
|
const origin = new URL(request.url).origin
|
||||||
|
const { index } = params
|
||||||
|
const networkName = String(index[0])
|
||||||
|
const tokenAddress = String(index[1])
|
||||||
|
|
||||||
|
const cacheUrl = origin + '/tokens/' + networkName + '/' + tokenAddress
|
||||||
|
|
||||||
|
const data = await getRequest(
|
||||||
|
cacheUrl,
|
||||||
|
() => getToken(networkName, tokenAddress, cacheUrl),
|
||||||
|
(data): data is NonNullable<Awaited<ReturnType<typeof getToken>>> =>
|
||||||
|
Boolean(data.symbol && data.ogImage && data.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return new Response('Asset not found.', { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const [fontData, palette] = await Promise.all([getFont(), getColor(data.ogImage)])
|
||||||
|
|
||||||
|
const networkLogo = getNetworkLogoUrl(networkName.toUpperCase())
|
||||||
|
|
||||||
|
// Capitalize name such that each word starts with a capital letter
|
||||||
|
let words = data.name.split(' ')
|
||||||
|
words = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||||
|
let name = words.join(' ')
|
||||||
|
name = name.trim()
|
||||||
|
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'black',
|
||||||
|
display: 'flex',
|
||||||
|
width: '1200px',
|
||||||
|
height: '630px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
backgroundColor: `rgba(${palette[0]}, ${palette[1]}, ${palette[2]}, 0.8)`,
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
padding: '72px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.ogImage != '' ? (
|
||||||
|
<img src={data.ogImage} width="144px" style={{ borderRadius: '100%' }}>
|
||||||
|
{networkLogo != '' && (
|
||||||
|
<img
|
||||||
|
src={networkLogo}
|
||||||
|
width="48px"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '2px',
|
||||||
|
bottom: '0px',
|
||||||
|
borderRadius: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</img>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '144px',
|
||||||
|
height: '144px',
|
||||||
|
borderRadius: '100%',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.12)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: '48px',
|
||||||
|
lineHeight: '58px',
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.name.slice(0, 3).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
{networkLogo != '' && (
|
||||||
|
<img
|
||||||
|
src={networkLogo}
|
||||||
|
width="48px"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '2px',
|
||||||
|
bottom: '0px',
|
||||||
|
borderRadius: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: '72px',
|
||||||
|
lineHeight: '58px',
|
||||||
|
marginLeft: '-5px',
|
||||||
|
marginTop: '24px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: '168px',
|
||||||
|
lineHeight: '133px',
|
||||||
|
marginLeft: '-13px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.symbol}
|
||||||
|
</div>
|
||||||
|
<img src={WATERMARK_URL} alt="Uniswap" height="72px" width="324px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'Inter',
|
||||||
|
data: fontData,
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
) as Response
|
||||||
|
} catch (error: any) {
|
||||||
|
return new Response(error.message || error.toString(), { status: 500 })
|
||||||
|
}
|
||||||
|
}
|
23
functions/api/image/tokens/tokenImage.test.ts
Normal file
23
functions/api/image/tokens/tokenImage.test.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const tokenImageUrl = [
|
||||||
|
'http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||||
|
'http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE',
|
||||||
|
]
|
||||||
|
|
||||||
|
test.each(tokenImageUrl)('tokenImageUrl', async (url) => {
|
||||||
|
const response = await fetch(new Request(url))
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.headers.get('content-type')).toBe('image/png')
|
||||||
|
})
|
||||||
|
|
||||||
|
const invalidTokenImageUrl = [
|
||||||
|
'http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb49',
|
||||||
|
'http://127.0.0.1:3000/api/image/tokens/ethereum',
|
||||||
|
'http://127.0.0.1:3000/api/image/tokens/ethereun',
|
||||||
|
'http://127.0.0.1:3000/api/image/tokens/ethereum/0x0',
|
||||||
|
'http://127.0.0.1:3000/api/image/tokens/potato/?potato=1',
|
||||||
|
]
|
||||||
|
|
||||||
|
test.each(invalidTokenImageUrl)('invalidAssetImageUrl', async (url) => {
|
||||||
|
const response = await fetch(new Request(url))
|
||||||
|
expect(response.status).toBe(404)
|
||||||
|
})
|
4
functions/constants.ts
Normal file
4
functions/constants.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const WATERMARK_URL = 'https://app.uniswap.org/images/324x74_App_Watermark.png'
|
||||||
|
export const CHECK_URL = 'https://app.uniswap.org/images/54x54_Verified_Check.svg'
|
||||||
|
|
||||||
|
export const DEFAULT_COLOR = [35, 43, 43]
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable import/no-unused-modules */
|
/* eslint-disable import/no-unused-modules */
|
||||||
import getAsset from '../../utils/getAsset'
|
import getAsset from '../../utils/getAsset'
|
||||||
import getRequest from '../../utils/getRequest'
|
import { getMetadataRequest } from '../../utils/getRequest'
|
||||||
|
|
||||||
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
||||||
const res = next()
|
const res = next()
|
||||||
@ -8,7 +8,7 @@ export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
|||||||
const { index } = params
|
const { index } = params
|
||||||
const collectionAddress = index[0]?.toString()
|
const collectionAddress = index[0]?.toString()
|
||||||
const tokenId = index[1]?.toString()
|
const tokenId = index[1]?.toString()
|
||||||
return getRequest(res, request.url, () => getAsset(collectionAddress, tokenId, request.url))
|
return getMetadataRequest(res, request.url, () => getAsset(collectionAddress, tokenId, request.url))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ exports[`should inject metadata for valid assets 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Azuki #2550"/><meta property="og:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki #2550"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki #2550"/><meta property="twitter:image" content="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png"/><meta property="twitter:image:alt" content="Azuki #2550"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Azuki #2550"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki #2550"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki #2550"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550"/><meta property="twitter:image:alt" content="Azuki #2550"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -248,7 +248,7 @@ exports[`should inject metadata for valid assets 2`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club #3735"/><meta property="og:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club #3735"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club #3735"/><meta property="twitter:image" content="https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club #3735"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club #3735"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club #3735"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club #3735"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club #3735"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -380,7 +380,7 @@ exports[`should inject metadata for valid assets 3`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="CryptoPunk #3947"/><meta property="og:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CryptoPunk #3947"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CryptoPunk #3947"/><meta property="twitter:image" content="https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png"/><meta property="twitter:image:alt" content="CryptoPunk #3947"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="CryptoPunk #3947"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CryptoPunk #3947"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CryptoPunk #3947"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947"/><meta property="twitter:image:alt" content="CryptoPunk #3947"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
@ -3,22 +3,19 @@ const assets = [
|
|||||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||||
assetId: '2550',
|
assetId: '2550',
|
||||||
collectionName: 'Azuki',
|
collectionName: 'Azuki',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/2550',
|
||||||
'https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/2550/d268b7f60a56306ced68b9762709ceaff4f1ee939f3150e7363fae300a59da12.png',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
||||||
assetId: '3735',
|
assetId: '3735',
|
||||||
collectionName: 'Bored Ape Yacht Club',
|
collectionName: 'Bored Ape Yacht Club',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/nfts/asset/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/3735',
|
||||||
'https://cdn.center.app/v2/1/697f69bb495aaa24c66638cae921977354f0b8274fc2e2814e455f355e67f01d/88c2ac6b73288e41051d3fd58ff3cef1f4908403f05f4a7d2a8435d003758529.png',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb',
|
address: '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb',
|
||||||
assetId: '3947',
|
assetId: '3947',
|
||||||
collectionName: 'CryptoPunk',
|
collectionName: 'CryptoPunk',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/nfts/asset/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/3947',
|
||||||
'https://cdn.center.app/1/0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB/3947/62319d784e7a816d190aa184ffe58550d6ed8eb2e117b218e2ac02f126538ee6.png',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
/* eslint-disable import/no-unused-modules */
|
/* eslint-disable import/no-unused-modules */
|
||||||
import getCollection from '../../utils/getCollection'
|
import getCollection from '../../utils/getCollection'
|
||||||
import getRequest from '../../utils/getRequest'
|
import { getMetadataRequest } from '../../utils/getRequest'
|
||||||
|
|
||||||
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
||||||
const res = next()
|
const res = next()
|
||||||
try {
|
try {
|
||||||
const { index } = params
|
const { index } = params
|
||||||
const collectionAddress = index?.toString()
|
const collectionAddress = index?.toString()
|
||||||
return getRequest(res, request.url, () => getCollection(collectionAddress, request.url))
|
return getMetadataRequest(res, request.url, () => getCollection(collectionAddress, request.url))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ exports[`should inject metadata for valid collections 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Azuki on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format"/><meta property="twitter:image:alt" content="Azuki on Uniswap"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Azuki on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Azuki on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Azuki on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544"/><meta property="twitter:image:alt" content="Azuki on Uniswap"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -248,7 +248,7 @@ exports[`should inject metadata for valid collections 2`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club on Uniswap"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Bored Ape Yacht Club on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Bored Ape Yacht Club on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"/><meta property="twitter:image:alt" content="Bored Ape Yacht Club on Uniswap"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -380,7 +380,7 @@ exports[`should inject metadata for valid collections 3`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="twitter:image" content="https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format"/><meta property="twitter:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b"/><meta property="twitter:image:alt" content="CLONE X - X TAKASHI MURAKAMI on Uniswap"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
@ -2,20 +2,17 @@ const collections = [
|
|||||||
{
|
{
|
||||||
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
address: '0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||||
collectionName: 'Azuki',
|
collectionName: 'Azuki',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544',
|
||||||
'https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
address: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
||||||
collectionName: 'Bored Ape Yacht Club',
|
collectionName: 'Bored Ape Yacht Club',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d',
|
||||||
'https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: '0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
|
address: '0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
|
||||||
collectionName: 'CLONE X - X TAKASHI MURAKAMI',
|
collectionName: 'CLONE X - X TAKASHI MURAKAMI',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b',
|
||||||
'https://i.seadn.io/gae/XN0XuD8Uh3jyRWNtPTFeXJg_ht8m5ofDx6aHklOiy4amhFuWUa0JaR6It49AH8tlnYS386Q0TW_-Lmedn0UET_ko1a3CbJGeu5iHMg?w=500&auto=format',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,33 +1,17 @@
|
|||||||
/* eslint-disable import/no-unused-modules */
|
/* eslint-disable import/no-unused-modules */
|
||||||
import { Chain } from '../../src/graphql/data/__generated__/types-and-hooks'
|
import { getMetadataRequest } from '../utils/getRequest'
|
||||||
import getRequest from '../utils/getRequest'
|
|
||||||
import getToken from '../utils/getToken'
|
import getToken from '../utils/getToken'
|
||||||
|
|
||||||
const convertTokenAddress = (tokenAddress: string, networkName: string) => {
|
|
||||||
if (tokenAddress === 'NATIVE') {
|
|
||||||
switch (networkName) {
|
|
||||||
case Chain.Celo:
|
|
||||||
return '0x471EcE3750Da237f93B8E339c536989b8978a438'
|
|
||||||
case Chain.Polygon:
|
|
||||||
return '0x0000000000000000000000000000000000001010'
|
|
||||||
default:
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tokenAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
export const onRequest: PagesFunction = async ({ params, request, next }) => {
|
||||||
const res = next()
|
const res = next()
|
||||||
try {
|
try {
|
||||||
const { index } = params
|
const { index } = params
|
||||||
const networkName = index[0]?.toString().toUpperCase()
|
const networkName = index[0]?.toString()
|
||||||
const tokenString = index[1]?.toString()
|
const tokenAddress = index[1]?.toString()
|
||||||
if (!tokenString) {
|
if (!tokenAddress) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
const tokenAddress = convertTokenAddress(tokenString, networkName)
|
return getMetadataRequest(res, request.url, () => getToken(networkName, tokenAddress, request.url))
|
||||||
return getRequest(res, request.url, () => getToken(networkName, tokenAddress, request.url))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ exports[`should inject metadata for valid tokens 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get USDC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get USDC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get USDC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"/><meta property="twitter:image:alt" content="Get USDC on Uniswap"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get USDC on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get USDC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get USDC on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"/><meta property="twitter:image:alt" content="Get USDC on Uniswap"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -248,7 +248,7 @@ exports[`should inject metadata for valid tokens 2`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get ETH on Uniswap"/><meta property="og:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get ETH on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get ETH on Uniswap"/><meta property="twitter:image" content="https://token-icons.s3.amazonaws.com/eth.png"/><meta property="twitter:image:alt" content="Get ETH on Uniswap"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get ETH on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get ETH on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get ETH on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE"/><meta property="twitter:image:alt" content="Get ETH on Uniswap"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -380,7 +380,7 @@ exports[`should inject metadata for valid tokens 3`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get MATIC on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get MATIC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/polygon/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get MATIC on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"/><meta property="twitter:image:alt" content="Get MATIC on Uniswap"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get MATIC on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get MATIC on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/polygon/NATIVE"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get MATIC on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE"/><meta property="twitter:image:alt" content="Get MATIC on Uniswap"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -512,7 +512,7 @@ exports[`should inject metadata for valid tokens 4`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get PEPE on Uniswap"/><meta property="og:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get PEPE on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get PEPE on Uniswap"/><meta property="twitter:image" content="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png"/><meta property="twitter:image:alt" content="Get PEPE on Uniswap"/></head>
|
<script defer src="/static/js/bundle.js"></script><meta property="og:title" content="Get PEPE on Uniswap"/><meta property="og:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:image:alt" content="Get PEPE on Uniswap"/><meta property="og:type" content="website"/><meta property="og:url" content="http://127.0.0.1:3000/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:title" content="Get PEPE on Uniswap"/><meta property="twitter:image" content="http://127.0.0.1:3000/api/image/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933"/><meta property="twitter:image:alt" content="Get PEPE on Uniswap"/></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
@ -3,28 +3,25 @@ const tokens = [
|
|||||||
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||||
network: 'ethereum',
|
network: 'ethereum',
|
||||||
symbol: 'USDC',
|
symbol: 'USDC',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||||
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: 'NATIVE',
|
address: 'NATIVE',
|
||||||
network: 'ethereum',
|
network: 'ethereum',
|
||||||
symbol: 'ETH',
|
symbol: 'ETH',
|
||||||
image: 'https://token-icons.s3.amazonaws.com/eth.png',
|
image: 'http://127.0.0.1:3000/api/image/tokens/ethereum/NATIVE',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: 'NATIVE',
|
address: 'NATIVE',
|
||||||
network: 'polygon',
|
network: 'polygon',
|
||||||
symbol: 'MATIC',
|
symbol: 'MATIC',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/tokens/polygon/NATIVE',
|
||||||
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
address: '0x6982508145454ce325ddbe47a25d4ec3d2311933',
|
address: '0x6982508145454ce325ddbe47a25d4ec3d2311933',
|
||||||
network: 'ethereum',
|
network: 'ethereum',
|
||||||
symbol: 'PEPE',
|
symbol: 'PEPE',
|
||||||
image:
|
image: 'http://127.0.0.1:3000/api/image/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933',
|
||||||
'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6982508145454Ce325dDbE47a25d4ec3d2311933/logo.png',
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/functions", // avoid clobbering the build tsbuildinfo
|
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/functions", // avoid clobbering the build tsbuildinfo
|
||||||
"types": ["jest", "node", "@cloudflare/workers-types"],
|
"types": ["jest", "node", "@cloudflare/workers-types"],
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", ".ts"],
|
"include": ["**/*.ts", ".ts", "**/*.tsx"],
|
||||||
}
|
}
|
1
functions/types.d.ts
vendored
Normal file
1
functions/types.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module 'colorthief/src/color-thief-node'
|
@ -1,7 +1,11 @@
|
|||||||
interface Data {
|
export interface Data {
|
||||||
title: string
|
title: string
|
||||||
image: string
|
image: string
|
||||||
url: string
|
url: string
|
||||||
|
name?: string
|
||||||
|
ogImage?: string
|
||||||
|
isVerified?: boolean
|
||||||
|
symbol?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const CACHE_NAME = 'functions-cache' as const
|
const CACHE_NAME = 'functions-cache' as const
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AssetDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
|
import { AssetDocument, AssetQuery } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||||
import client from '../client'
|
import client from '../client'
|
||||||
|
|
||||||
function formatTitleName(name: string, collectionName: string, tokenId: string) {
|
function formatTitleName(name: string | undefined, collectionName: string | undefined, tokenId: string) {
|
||||||
if (name) {
|
if (name) {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
@ -15,7 +15,9 @@ function formatTitleName(name: string, collectionName: string, tokenId: string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function getAsset(collectionAddress: string, tokenId: string, url: string) {
|
export default async function getAsset(collectionAddress: string, tokenId: string, url: string) {
|
||||||
const { data } = await client.query({
|
const origin = new URL(url).origin
|
||||||
|
const image = origin + '/api/image/nfts/asset/' + collectionAddress + '/' + tokenId
|
||||||
|
const { data } = await client.query<AssetQuery>({
|
||||||
query: AssetDocument,
|
query: AssetDocument,
|
||||||
variables: {
|
variables: {
|
||||||
address: collectionAddress,
|
address: collectionAddress,
|
||||||
@ -31,8 +33,9 @@ export default async function getAsset(collectionAddress: string, tokenId: strin
|
|||||||
const title = formatTitleName(asset.name, asset.collection?.name, asset.tokenId)
|
const title = formatTitleName(asset.name, asset.collection?.name, asset.tokenId)
|
||||||
const formattedAsset = {
|
const formattedAsset = {
|
||||||
title,
|
title,
|
||||||
image: asset.image?.url,
|
image,
|
||||||
url,
|
url,
|
||||||
|
ogImage: asset.image?.url ?? origin + '/images/192x192_App_Icon.png',
|
||||||
}
|
}
|
||||||
return formattedAsset
|
return formattedAsset
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { CollectionDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
|
import { CollectionDocument, CollectionQuery } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||||
import client from '../client'
|
import client from '../client'
|
||||||
|
|
||||||
export default async function getCollection(collectionAddress: string, url: string) {
|
export default async function getCollection(collectionAddress: string, url: string) {
|
||||||
const { data } = await client.query({
|
const origin = new URL(url).origin
|
||||||
|
const image = origin + '/api/image/nfts/collection/' + collectionAddress
|
||||||
|
const { data } = await client.query<CollectionQuery>({
|
||||||
query: CollectionDocument,
|
query: CollectionDocument,
|
||||||
variables: {
|
variables: {
|
||||||
addresses: collectionAddress,
|
addresses: collectionAddress,
|
||||||
@ -14,8 +16,11 @@ export default async function getCollection(collectionAddress: string, url: stri
|
|||||||
}
|
}
|
||||||
const formattedAsset = {
|
const formattedAsset = {
|
||||||
title: collection.name + ' on Uniswap',
|
title: collection.name + ' on Uniswap',
|
||||||
image: collection.image?.url,
|
image,
|
||||||
url,
|
url,
|
||||||
|
name: collection.name ?? 'Collection',
|
||||||
|
ogImage: collection.image?.url ?? origin + '/images/192x192_App_Icon.png',
|
||||||
|
isVerified: collection.isVerified ?? false,
|
||||||
}
|
}
|
||||||
return formattedAsset
|
return formattedAsset
|
||||||
}
|
}
|
||||||
|
16
functions/utils/getColor.ts
Normal file
16
functions/utils/getColor.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import ColorThief from 'colorthief/src/color-thief-node'
|
||||||
|
|
||||||
|
import { DEFAULT_COLOR } from '../constants'
|
||||||
|
|
||||||
|
export default async function getColor(image: string) {
|
||||||
|
try {
|
||||||
|
const data = await fetch(image)
|
||||||
|
const buffer = await data.arrayBuffer()
|
||||||
|
const arrayBuffer = Buffer.from(buffer)
|
||||||
|
|
||||||
|
const palette = await ColorThief.getPalette(arrayBuffer, 5)
|
||||||
|
return palette[0] ?? DEFAULT_COLOR
|
||||||
|
} catch (e) {
|
||||||
|
return DEFAULT_COLOR
|
||||||
|
}
|
||||||
|
}
|
6
functions/utils/getFont.ts
Normal file
6
functions/utils/getFont.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const FONT_URL = 'https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fAZFhjQ.ttf'
|
||||||
|
|
||||||
|
export default async function getFont() {
|
||||||
|
const font = await fetch(FONT_URL)
|
||||||
|
return font.arrayBuffer()
|
||||||
|
}
|
16
functions/utils/getNetworkLogoURL.ts
Normal file
16
functions/utils/getNetworkLogoURL.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Chain } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||||
|
|
||||||
|
export default function getNetworkLogoUrl(network: string) {
|
||||||
|
switch (network) {
|
||||||
|
case Chain.Polygon:
|
||||||
|
return 'https://assets.coingecko.com/coins/images/4713/small/matic-token-icon.png?1624446912'
|
||||||
|
case Chain.Arbitrum:
|
||||||
|
return 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0x912CE59144191C1204E64559FE8253a0e49E6548/logo.png'
|
||||||
|
case Chain.Optimism:
|
||||||
|
return 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/optimism/assets/0x4200000000000000000000000000000000000042/logo.png'
|
||||||
|
case Chain.Celo:
|
||||||
|
return 'https://assets.coingecko.com/coins/images/11090/small/InjXBNx9_400x400.jpg?1674707499'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,8 @@ import * as matchers from 'jest-extended'
|
|||||||
expect.extend(matchers)
|
expect.extend(matchers)
|
||||||
|
|
||||||
import { mocked } from '../../src/test-utils/mocked'
|
import { mocked } from '../../src/test-utils/mocked'
|
||||||
import Cache from './cache'
|
import Cache, { Data } from './cache'
|
||||||
import getRequest from './getRequest'
|
import { getRequest } from './getRequest'
|
||||||
|
|
||||||
jest.mock('./cache', () => ({
|
jest.mock('./cache', () => ({
|
||||||
match: jest.fn(),
|
match: jest.fn(),
|
||||||
@ -17,7 +17,7 @@ test('should call Cache.match before calling getData when request is not cached'
|
|||||||
image: 'testImage',
|
image: 'testImage',
|
||||||
url: 'testUrl',
|
url: 'testUrl',
|
||||||
})
|
})
|
||||||
await getRequest(Promise.resolve(new Response()), url, getData)
|
await getRequest(url, getData, (data): data is Data => true)
|
||||||
expect(Cache.match).toHaveBeenCalledWith(url)
|
expect(Cache.match).toHaveBeenCalledWith(url)
|
||||||
expect(getData).toHaveBeenCalled()
|
expect(getData).toHaveBeenCalled()
|
||||||
expect(Cache.match).toHaveBeenCalledBefore(getData)
|
expect(Cache.match).toHaveBeenCalledBefore(getData)
|
||||||
@ -32,7 +32,7 @@ test('getData should not be called when request is cached', async () => {
|
|||||||
url: 'testUrl',
|
url: 'testUrl',
|
||||||
})
|
})
|
||||||
const getData = jest.fn()
|
const getData = jest.fn()
|
||||||
await getRequest(Promise.resolve(new Response()), url, getData)
|
await getRequest(url, getData, (data): data is Data => true)
|
||||||
expect(Cache.match).toHaveBeenCalledWith(url)
|
expect(Cache.match).toHaveBeenCalledWith(url)
|
||||||
expect(getData).not.toHaveBeenCalled()
|
expect(getData).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
@ -1,31 +1,42 @@
|
|||||||
import { MetaTagInjector } from '../components/metaTagInjector'
|
import { MetaTagInjector } from '../components/metaTagInjector'
|
||||||
import Cache from './cache'
|
import Cache from './cache'
|
||||||
|
import { Data } from './cache'
|
||||||
|
|
||||||
export default async function getRequest(
|
export async function getMetadataRequest(
|
||||||
res: Promise<Response>,
|
res: Promise<Response>,
|
||||||
url: string,
|
url: string,
|
||||||
getData: () => Promise<
|
getData: () => Promise<Data | undefined>
|
||||||
| {
|
|
||||||
title: string
|
|
||||||
image: string
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
>
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const cachedData = await Cache.match(url)
|
const cachedData = await getRequest(url, getData, (data): data is Data => true)
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
return new HTMLRewriter().on('head', new MetaTagInjector(cachedData)).transform(await res)
|
return new HTMLRewriter().on('head', new MetaTagInjector(cachedData)).transform(await res)
|
||||||
} else {
|
} else {
|
||||||
const data = await getData()
|
return res
|
||||||
if (!data) {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
await Cache.put(data, url)
|
|
||||||
return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(await res)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getRequest<T extends Data>(
|
||||||
|
url: string,
|
||||||
|
getData: () => Promise<T | undefined>,
|
||||||
|
validateData: (data: Data) => data is T
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
try {
|
||||||
|
const cachedData = await Cache.match(url)
|
||||||
|
if (cachedData && validateData(cachedData)) {
|
||||||
|
return cachedData
|
||||||
|
} else {
|
||||||
|
const data = await getData()
|
||||||
|
if (!data) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
await Cache.put(data, url)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { TokenDocument } from '../../src/graphql/data/__generated__/types-and-hooks'
|
import { TokenDocument, TokenQuery } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||||
|
import { Chain } from '../../src/graphql/data/__generated__/types-and-hooks'
|
||||||
import client from '../client'
|
import client from '../client'
|
||||||
|
|
||||||
function formatTitleName(symbol: string, name: string) {
|
function formatTitleName(symbol: string | undefined, name: string | undefined) {
|
||||||
if (symbol) {
|
if (symbol) {
|
||||||
return 'Get ' + symbol + ' on Uniswap'
|
return 'Get ' + symbol + ' on Uniswap'
|
||||||
}
|
}
|
||||||
@ -11,23 +12,46 @@ function formatTitleName(symbol: string, name: string) {
|
|||||||
return 'View Token on Uniswap'
|
return 'View Token on Uniswap'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function getToken(networkName: string, tokenAddress: string | undefined, url: string) {
|
const convertTokenAddress = (networkName: string, tokenAddress: string) => {
|
||||||
const { data } = await client.query({
|
if (tokenAddress === 'NATIVE') {
|
||||||
|
switch (networkName) {
|
||||||
|
case Chain.Celo:
|
||||||
|
return '0x471EcE3750Da237f93B8E339c536989b8978a438'
|
||||||
|
case Chain.Polygon:
|
||||||
|
return '0x0000000000000000000000000000000000001010'
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokenAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function getToken(networkName: string, tokenAddress: string, url: string) {
|
||||||
|
const origin = new URL(url).origin
|
||||||
|
const image = origin + '/api/image/tokens/' + networkName + '/' + tokenAddress
|
||||||
|
const uppercaseNetworkName = networkName.toUpperCase()
|
||||||
|
const convertedTokenAddress = convertTokenAddress(uppercaseNetworkName, tokenAddress)
|
||||||
|
const { data } = await client.query<TokenQuery>({
|
||||||
query: TokenDocument,
|
query: TokenDocument,
|
||||||
variables: {
|
variables: {
|
||||||
chain: networkName,
|
chain: uppercaseNetworkName,
|
||||||
address: tokenAddress,
|
address: convertedTokenAddress,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const asset = data?.token
|
const asset = data?.token
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = formatTitleName(asset.symbol, asset.name)
|
const title = formatTitleName(asset.symbol, asset.name)
|
||||||
|
|
||||||
const formattedAsset = {
|
const formattedAsset = {
|
||||||
title,
|
title,
|
||||||
image: asset.project?.logoUrl,
|
image,
|
||||||
url,
|
url,
|
||||||
|
symbol: asset.symbol ?? 'UNK',
|
||||||
|
ogImage: asset.project?.logoUrl ?? '',
|
||||||
|
name: asset.name ?? 'Token',
|
||||||
}
|
}
|
||||||
return formattedAsset
|
return formattedAsset
|
||||||
}
|
}
|
||||||
|
13
package.json
13
package.json
@ -18,7 +18,7 @@
|
|||||||
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
|
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
|
||||||
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
|
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
|
||||||
"start": "craco start",
|
"start": "craco start",
|
||||||
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --node-compat --compatibility-date=2023-08-04 --proxy=3001 --port=3000 -- yarn start",
|
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start",
|
||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js' --only-mapped",
|
"analyze": "source-map-explorer 'build/static/js/*.js' --only-mapped",
|
||||||
"serve": "serve build -l 3000",
|
"serve": "serve build -l 3000",
|
||||||
@ -30,7 +30,8 @@
|
|||||||
"test:cloud": "NODE_OPTIONS=--experimental-vm-modules yarn jest functions --config=functions/jest.config.json",
|
"test:cloud": "NODE_OPTIONS=--experimental-vm-modules yarn jest functions --config=functions/jest.config.json",
|
||||||
"cypress:open": "cypress open --browser chrome --e2e",
|
"cypress:open": "cypress open --browser chrome --e2e",
|
||||||
"cypress:run": "cypress run --browser chrome --e2e",
|
"cypress:run": "cypress run --browser chrome --e2e",
|
||||||
"deduplicate": "yarn-deduplicate --strategy=highest"
|
"deduplicate": "yarn-deduplicate --strategy=highest",
|
||||||
|
"postinstall": "yarn patch-package"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
@ -108,10 +109,12 @@
|
|||||||
"@uniswap/eslint-config": "^1.2.0",
|
"@uniswap/eslint-config": "^1.2.0",
|
||||||
"@vanilla-extract/jest-transform": "^1.1.1",
|
"@vanilla-extract/jest-transform": "^1.1.1",
|
||||||
"@vanilla-extract/webpack-plugin": "^2.2.0",
|
"@vanilla-extract/webpack-plugin": "^2.2.0",
|
||||||
|
"@vercel/og": "0.5.8",
|
||||||
"@walletconnect/types": "^2.8.6",
|
"@walletconnect/types": "^2.8.6",
|
||||||
"babel-jest": "^29.6.1",
|
"babel-jest": "^29.6.1",
|
||||||
"browser-cache-mock": "^0.1.7",
|
"browser-cache-mock": "^0.1.7",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"colorthief": "^2.4.0",
|
||||||
"concurrently": "^8.0.1",
|
"concurrently": "^8.0.1",
|
||||||
"cypress": "12.12.0",
|
"cypress": "12.12.0",
|
||||||
"cypress-hardhat": "^2.5.0",
|
"cypress-hardhat": "^2.5.0",
|
||||||
@ -127,7 +130,9 @@
|
|||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"jest-styled-components": "^7.0.8",
|
"jest-styled-components": "^7.0.8",
|
||||||
"mini-css-extract-plugin": "^2.7.6",
|
"mini-css-extract-plugin": "^2.7.6",
|
||||||
|
"patch-package": "^7.0.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
@ -142,8 +147,7 @@
|
|||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"webpack": "^5.88.2",
|
"webpack": "^5.88.2",
|
||||||
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
||||||
"//": "downgraded-because-vercel-og-incompatible",
|
"wrangler": "^3.5.0",
|
||||||
"wrangler": "0.0.0-6ccc4fa6",
|
|
||||||
"yarn-deduplicate": "^6.0.0"
|
"yarn-deduplicate": "^6.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -222,6 +226,7 @@
|
|||||||
"copy-to-clipboard": "^3.2.0",
|
"copy-to-clipboard": "^3.2.0",
|
||||||
"d3": "^7.6.1",
|
"d3": "^7.6.1",
|
||||||
"ethers": "^5.7.2",
|
"ethers": "^5.7.2",
|
||||||
|
"ext-name": "^5.0.0",
|
||||||
"focus-visible": "^5.2.0",
|
"focus-visible": "^5.2.0",
|
||||||
"get-graphql-schema": "^2.1.2",
|
"get-graphql-schema": "^2.1.2",
|
||||||
"graphql": "^16.5.0",
|
"graphql": "^16.5.0",
|
||||||
|
50
patches/@vercel+og+0.5.8.patch
Normal file
50
patches/@vercel+og+0.5.8.patch
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
diff --git a/node_modules/@vercel/og/dist/index.edge.js b/node_modules/@vercel/og/dist/index.edge.js
|
||||||
|
index 5187f88..c4a1c41 100644
|
||||||
|
--- a/node_modules/@vercel/og/dist/index.edge.js
|
||||||
|
+++ b/node_modules/@vercel/og/dist/index.edge.js
|
||||||
|
@@ -18673,8 +18673,8 @@ var Resvg2 = class extends Resvg {
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/index.edge.ts
|
||||||
|
-import resvg_wasm from "./resvg.wasm?module";
|
||||||
|
-import yoga_wasm from "./yoga.wasm?module";
|
||||||
|
+import resvg_wasm from "./resvg.wasm";
|
||||||
|
+import yoga_wasm from "./yoga.wasm";
|
||||||
|
|
||||||
|
// src/emoji/index.ts
|
||||||
|
var U200D = String.fromCharCode(8205);
|
||||||
|
@@ -18809,18 +18809,18 @@ async function render(satori, resvg, opts, defaultFonts, element) {
|
||||||
|
// src/index.edge.ts
|
||||||
|
var initializedResvg = initWasm(resvg_wasm);
|
||||||
|
var initializedYoga = initYoga(yoga_wasm).then((yoga2) => Ll(yoga2));
|
||||||
|
-var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());
|
||||||
|
+// var fallbackFont = fetch(new URL("https://fonts.gstatic.com/s/notosans/v28/o-0IIpQlx3QUlC5A4PNr6zRF.ttf", import.meta.url)).then((res) => res.arrayBuffer());
|
||||||
|
var ImageResponse = class {
|
||||||
|
constructor(element, options = {}) {
|
||||||
|
const result = new ReadableStream({
|
||||||
|
async start(controller) {
|
||||||
|
await initializedYoga;
|
||||||
|
await initializedResvg;
|
||||||
|
- const fontData = await fallbackFont;
|
||||||
|
+ // const fontData = await fallbackFont;
|
||||||
|
const fonts = [
|
||||||
|
{
|
||||||
|
name: "sans serif",
|
||||||
|
- data: fontData,
|
||||||
|
+ // data: fontData,
|
||||||
|
weight: 700,
|
||||||
|
style: "normal"
|
||||||
|
}
|
||||||
|
diff --git a/node_modules/@vercel/og/dist/types.d.ts b/node_modules/@vercel/og/dist/types.d.ts
|
||||||
|
index dde26cc..eb59ff4 100644
|
||||||
|
--- a/node_modules/@vercel/og/dist/types.d.ts
|
||||||
|
+++ b/node_modules/@vercel/og/dist/types.d.ts
|
||||||
|
@@ -30,7 +30,7 @@ declare type ImageOptions = {
|
||||||
|
* @type {{ data: ArrayBuffer; name: string; weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; style?: 'normal' | 'italic' }[]}
|
||||||
|
* @default Noto Sans Latin Regular.
|
||||||
|
*/
|
||||||
|
- fonts?: SatoriOptions['fonts'];
|
||||||
|
+ fonts: SatoriOptions['fonts'];
|
||||||
|
/**
|
||||||
|
* Using a specific Emoji style. Defaults to `twemoji`.
|
||||||
|
*
|
28
patches/get-pixels+3.3.3.patch
Normal file
28
patches/get-pixels+3.3.3.patch
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
diff --git a/node_modules/get-pixels/dom-pixels.js b/node_modules/get-pixels/dom-pixels.js
|
||||||
|
index 7714528..64e8db3 100644
|
||||||
|
--- a/node_modules/get-pixels/dom-pixels.js
|
||||||
|
+++ b/node_modules/get-pixels/dom-pixels.js
|
||||||
|
@@ -1,10 +1,8 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
-var path = require('path')
|
||||||
|
+var extname = require('ext-name')
|
||||||
|
var ndarray = require('ndarray')
|
||||||
|
var GifReader = require('omggif').GifReader
|
||||||
|
-var pack = require('ndarray-pack')
|
||||||
|
-var through = require('through')
|
||||||
|
var parseDataURI = require('data-uri-to-buffer')
|
||||||
|
|
||||||
|
function defaultImage(url, cb) {
|
||||||
|
@@ -117,9 +115,9 @@ module.exports = function getPixels(url, type, cb) {
|
||||||
|
cb = type
|
||||||
|
type = ''
|
||||||
|
}
|
||||||
|
- var ext = path.extname(url)
|
||||||
|
+ var ext = extname(url).ext
|
||||||
|
switch(type || ext.toUpperCase()) {
|
||||||
|
- case '.GIF':
|
||||||
|
+ case 'GIF':
|
||||||
|
httpGif(url, cb)
|
||||||
|
break
|
||||||
|
default:
|
Loading…
Reference in New Issue
Block a user