Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/common/src/utils/coinLogo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest'

import { getCoinLogoUrl } from './coinLogo'

const CID = 'baeaaaiqsecwikb3rxnjr4txcptwvtlpdj6et6bkkz7vo5giyy35xnl5vxxbxk'
const NODE_URL = `https://creatornode.audius.co/content/${CID}`

describe('getCoinLogoUrl', () => {
it('requests a variant sized for the display at 3x', () => {
expect(getCoinLogoUrl(NODE_URL, 16)).toBe(`${NODE_URL}/150x150.jpg`)
expect(getCoinLogoUrl(NODE_URL, 50)).toBe(`${NODE_URL}/150x150.jpg`)
expect(getCoinLogoUrl(NODE_URL, 64)).toBe(`${NODE_URL}/480x480.jpg`)
expect(getCoinLogoUrl(NODE_URL, 200)).toBe(`${NODE_URL}/1000x1000.jpg`)
})

it('tolerates a trailing slash', () => {
expect(getCoinLogoUrl(`${NODE_URL}/`, 24)).toBe(`${NODE_URL}/150x150.jpg`)
})

it('leaves URLs it cannot resize unchanged', () => {
const irys =
'https://gateway.irys.xyz/FRdFgDGxVZC9HsdRkWQ3CKCyi6icxvxT1ENWgnk1By4y'
const asset = 'https://audius.co/assets/TokenAUDIO-B9fxmOIn.png'
const variant = `${NODE_URL}/480x480.jpg`
expect(getCoinLogoUrl(irys, 16)).toBe(irys)
expect(getCoinLogoUrl(asset, 16)).toBe(asset)
expect(getCoinLogoUrl(variant, 16)).toBe(variant)
})

it('passes through empty values', () => {
expect(getCoinLogoUrl(undefined, 16)).toBeUndefined()
expect(getCoinLogoUrl(null, 16)).toBeNull()
expect(getCoinLogoUrl('', 16)).toBe('')
})
})
33 changes: 33 additions & 0 deletions packages/common/src/utils/coinLogo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { SquareSizes } from '~/models/ImageSizes'

// A bare validator node content URL, e.g. https://creatornode.audius.co/content/<cid>.
// Coin logos launched after the Irys -> Audius migration (and existing logos
// once they are re-hosted) are stored this way, and the node resizes any image
// it stores on demand at /content/<cid>/<size>.jpg.
const CONTENT_NODE_URL_REGEX = /^(https?:\/\/[^/?#]+\/content\/[^/?#]+)\/?$/

// Smallest variant that stays sharp at 3x pixel density
const getSquareSizeForDisplay = (displaySize: number) => {
const pixels = displaySize * 3
if (pixels <= 150) return SquareSizes.SIZE_150_BY_150
if (pixels <= 480) return SquareSizes.SIZE_480_BY_480
return SquareSizes.SIZE_1000_BY_1000
}

/**
* Returns a resized variant of a coin's `logo_uri` for drawing it at
* `displaySize` CSS pixels. Coin logos are uploaded at up to 1000px and often
* weigh 0.5-2 MB, so badges should never load the original.
*
* Only validator node URLs can be resized. Other URLs (Irys/Arweave, static
* assets) are returned unchanged.
*/
export const getCoinLogoUrl = <T extends string | null | undefined>(
logoUri: T,
displaySize: number
): T | string => {
if (!logoUri) return logoUri
const match = CONTENT_NODE_URL_REGEX.exec(logoUri)
if (!match) return logoUri
return `${match[1]}/${getSquareSizeForDisplay(displaySize)}.jpg`
}
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export * from './handle'
export * from './comments'
export * from './quickSearch'
export * from './coinMetrics'
export * from './coinLogo'
export * from './convertHexToRGBA'
export * from './socialLinks'
export * from './videoUtils'
3 changes: 2 additions & 1 deletion packages/mobile/src/components/core/TokenIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getCoinLogoUrl } from '@audius/common/utils'
import { Image } from 'react-native'

import { HexagonalIcon, useTheme } from '@audius/harmony-native'
Expand All @@ -19,7 +20,7 @@ export const TokenIcon = ({ logoURI, size = 's' }: TokenIconProps) => {
return (
<HexagonalIcon size={iconSize}>
<Image
source={{ uri: logoURI }}
source={{ uri: getCoinLogoUrl(logoURI, iconSize) }}
style={{
width: iconSize,
height: iconSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@audius/common/models'
import type { AccessConditions, User } from '@audius/common/models'
import type { PurchaseableContentType } from '@audius/common/store'
import { getCoinLogoUrl } from '@audius/common/utils'
import { USDC } from '@audius/fixed-decimal'
import type { ViewStyle } from 'react-native'
import { Image, View } from 'react-native'
Expand Down Expand Up @@ -141,7 +142,7 @@ const DetailsTileOwnerSection = ({
<Flex row alignItems='center' gap='xs'>
<HexagonalIcon size={24}>
<Image
source={{ uri: token?.logoUri }}
source={{ uri: getCoinLogoUrl(token?.logoUri, 24) ?? undefined }}
style={{ width: 24, height: 24 }}
/>
</HexagonalIcon>
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/components/CoinSuccessModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
TrackMetadataForUpload,
useCoinSuccessModal
} from '@audius/common/store'
import { route } from '@audius/common/utils'
import { getCoinLogoUrl, route } from '@audius/common/utils'
import {
Artwork,
Button,
Expand Down Expand Up @@ -107,7 +107,7 @@ export const CoinSuccessModal = () => {
>
<Flex alignItems='center' gap='m' w='100%'>
<Artwork
src={logoUri}
src={getCoinLogoUrl(logoUri, 48)}
w='48px'
h='48px'
hex
Expand Down
7 changes: 6 additions & 1 deletion packages/web/src/components/buy-sell-modal/TokenIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ComponentType } from 'react'

import { getCoinLogoUrl } from '@audius/common/utils'
import { Artwork, ArtworkProps, IconProps, useTheme } from '@audius/harmony'

export type TokenIconProps = {
Expand Down Expand Up @@ -41,7 +42,11 @@ export const TokenIcon = ({

return (
<Artwork
src={logoURI}
src={
typeof dimensions?.w === 'number'
? getCoinLogoUrl(logoURI, dimensions.w)
: logoURI
}
{...dimensions}
borderWidth={0}
hex={hex}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
EditCollectionValues,
useEditAccessConfirmationModal
} from '@audius/common/store'
import { getUsersMayLoseAccess } from '@audius/common/utils'
import { getCoinLogoUrl, getUsersMayLoseAccess } from '@audius/common/utils'
import {
IconCart,
IconVisibilityHidden as IconHidden,
Expand Down Expand Up @@ -416,7 +416,13 @@ export const PriceAndAudienceField = (props: PriceAndAudienceFieldProps) => {
{
label: messages.coinGated,
startAdornment: (
<Artwork src={token?.logoUri} hex h={20} w={20} borderWidth={0} />
<Artwork
src={getCoinLogoUrl(token?.logoUri, 20)}
hex
h={20}
w={20}
borderWidth={0}
/>
)
}
]
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/components/hover-card/FanClubHoverCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'

import { useFanClub, useCoinBalance, useUser } from '@audius/common/api'
import { ID } from '@audius/common/models'
import { formatCount, route } from '@audius/common/utils'
import { formatCount, getCoinLogoUrl, route } from '@audius/common/utils'
import {
Artwork,
Flex,
Expand Down Expand Up @@ -143,7 +143,7 @@ export const FanClubHoverCard = ({
{/* Token Icon */}
{fanClubBadge?.logo_uri ? (
<Artwork
src={fanClubBadge.logo_uri}
src={getCoinLogoUrl(fanClubBadge.logo_uri, spacing.unit12)}
hex
w={spacing.unit12}
h={spacing.unit12}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback } from 'react'

import { useUser } from '@audius/common/api'
import { FanClubTextPostNotification as FanClubTextPostNotificationType } from '@audius/common/store'
import { route } from '@audius/common/utils'
import { getCoinLogoUrl, route } from '@audius/common/utils'
import { Artwork, Flex, IconFanClub } from '@audius/harmony'
import { useDispatch } from 'react-redux'

Expand Down Expand Up @@ -50,7 +50,12 @@ export const FanClubTextPostNotification = (
<ProfilePicture user={user} />
</Flex>
{user.fan_club_badge?.logo_uri ? (
<Artwork src={user.fan_club_badge.logo_uri} hex w={40} h={40} />
<Artwork
src={getCoinLogoUrl(user.fan_club_badge.logo_uri, 40)}
hex
w={40}
h={40}
/>
) : null}
</Flex>
</NotificationHeader>
Expand Down
6 changes: 4 additions & 2 deletions packages/web/src/components/user-badges/UserBadges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { useUser } from '@audius/common/api'
import { BadgeTier, ID } from '@audius/common/models'
import { useTierAndVerifiedForUser } from '@audius/common/store'
import { Nullable } from '@audius/common/utils'
import { getCoinLogoUrl, Nullable } from '@audius/common/utils'
import {
Artwork,
Box,
Expand Down Expand Up @@ -199,7 +199,9 @@ const UserBadges = ({
}}
>
<Artwork
src={userFanClubBadge?.logo_uri ?? ''}
src={
getCoinLogoUrl(userFanClubBadge?.logo_uri, iconSizes[size]) ?? ''
}
hex
w={iconSizes[size]}
h={iconSizes[size]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Name, WidthSizes } from '@audius/common/models'
import { useClaimVestedCoinsModal } from '@audius/common/store'
import {
formatCurrencyWithSubscript,
getCoinLogoUrl,
getTokenDecimalPlaces,
removeNullable,
route
Expand Down Expand Up @@ -335,7 +336,7 @@ export const FanClubBannerSection = ({ mint }: FanClubBannerSectionProps) => {
>
{coin.logoUri ? (
<Artwork
src={coin.logoUri}
src={getCoinLogoUrl(coin.logoUri, spacing.unit24)}
hex
w={spacing.unit24}
h={spacing.unit24}
Expand Down
Loading