Skip to content

Commit 8a535fb

Browse files
authored
Merge pull request #15885 from ethereum/hotfix-torch
Hotfix torch
2 parents 2eb0f9c + 8d6ef09 commit 8a535fb

File tree

8 files changed

+116
-92
lines changed

8 files changed

+116
-92
lines changed

app/[locale]/10years/_components/CurrentTorchHolderCard.tsx

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
AvatarBase as Avatar,
3-
AvatarFallback,
4-
AvatarImage,
5-
} from "@/components/ui/avatar"
1+
import { Avatar } from "@/components/ui/avatar"
62
import { ButtonLink } from "@/components/ui/buttons/Button"
73
import {
84
Card,
@@ -18,14 +14,15 @@ import { cn } from "@/lib/utils/cn"
1814
import Curved10YearsText from "./10y.svg"
1915

2016
import {
17+
extractTwitterHandle,
2118
formatAddress,
22-
getAddressEtherscanUrl,
2319
getAvatarImage,
24-
type TorchHolderMetadata,
20+
getTxEtherscanUrl,
21+
type TorchHolderEvent,
2522
} from "@/lib/torch"
2623

2724
interface CurrentTorchHolderCardProps {
28-
currentHolder: TorchHolderMetadata | null
25+
currentHolder: TorchHolderEvent | null
2926
isBurned?: boolean
3027
className?: string
3128
}
@@ -78,15 +75,12 @@ const CurrentTorchHolderCard = ({
7875
<CardContent className="p-6">
7976
{currentHolder ? (
8077
<div className="flex items-start gap-4">
81-
<Avatar className="h-19 w-19 !shadow-none">
82-
<AvatarImage
83-
src={getAvatarImage(currentHolder)}
84-
alt={`Avatar for ${currentHolder.name || currentHolder.address}`}
85-
/>
86-
<AvatarFallback>
87-
{currentHolder.name || formatAddress(currentHolder.address)}
88-
</AvatarFallback>
89-
</Avatar>
78+
<Avatar
79+
className="h-19 w-19 !shadow-none"
80+
src={getAvatarImage(currentHolder)}
81+
href={`https://x.com/${extractTwitterHandle(currentHolder.twitter)}`}
82+
name={currentHolder.name || formatAddress(currentHolder.address)}
83+
/>
9084

9185
<div className="flex flex-col">
9286
{/* Name */}
@@ -98,7 +92,7 @@ const CurrentTorchHolderCard = ({
9892
{/* Verify onchain link */}
9993
<BaseLink
10094
className="mt-2 text-xs"
101-
href={getAddressEtherscanUrl(currentHolder.address)}
95+
href={getTxEtherscanUrl(currentHolder.event.transactionHash)}
10296
>
10397
View on Etherscan
10498
</BaseLink>

app/[locale]/10years/_components/TorchHistoryCard.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import React from "react"
22

3-
import {
4-
AvatarBase as Avatar,
5-
AvatarFallback,
6-
AvatarImage,
7-
} from "@/components/ui/avatar"
3+
import { Avatar } from "@/components/ui/avatar"
84
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
95
import { BaseLink } from "@/components/ui/Link"
106
import { Tag } from "@/components/ui/tag"
117

128
import { cn } from "@/lib/utils/cn"
139

14-
import { formatDate, getTxEtherscanUrl } from "@/lib/torch"
10+
import {
11+
extractTwitterHandle,
12+
formatDate,
13+
getTxEtherscanUrl,
14+
} from "@/lib/torch"
1515

1616
interface TorchHistoryCardProps {
1717
name: string
1818
role: string
1919
avatar: string
20+
twitter: string
2021
from: number
2122
to: number
2223
transactionHash: string
@@ -29,6 +30,7 @@ const TorchHistoryCard: React.FC<TorchHistoryCardProps> = ({
2930
name,
3031
role,
3132
avatar,
33+
twitter,
3234
from,
3335
to,
3436
transactionHash,
@@ -48,10 +50,12 @@ const TorchHistoryCard: React.FC<TorchHistoryCardProps> = ({
4850
>
4951
<CardHeader className="flex flex-col p-0">
5052
<div className="mb-4 flex flex-col items-center">
51-
<Avatar className="h-32 w-32 border-2 border-gray-100/50 !shadow-none">
52-
<AvatarImage src={avatar} alt={`Avatar for ${name}`} />
53-
<AvatarFallback>{name}</AvatarFallback>
54-
</Avatar>
53+
<Avatar
54+
className="h-32 w-32 border-2 border-gray-100/50 !shadow-none"
55+
src={avatar}
56+
href={`https://x.com/${extractTwitterHandle(twitter)}`}
57+
name={name}
58+
/>
5559
</div>
5660

5761
{isCurrentHolder && (

app/[locale]/10years/_components/TorchHistorySwiper/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const TorchHistorySwiper = ({
8989
? "/images/10-year-anniversary/torch-cover.webp"
9090
: getAvatarImage(card)
9191
}
92+
twitter={card.twitter}
9293
from={card.event.timestamp}
9394
to={card.event.timestamp}
9495
transactionHash={card.event.transactionHash}

app/[locale]/10years/page.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import {
4949
getTransferEvents,
5050
isAddressFiltered,
5151
isTorchBurned,
52-
TorchHolder,
52+
TorchHolderEvent,
5353
} from "@/lib/torch"
5454
import TenYearLogo from "@/public/images/10-year-anniversary/10-year-logo.png"
5555

@@ -101,24 +101,26 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
101101
{} as Record<string, (typeof allTorchHolders)[0]>
102102
)
103103

104+
const transferEvents = await getTransferEvents()
105+
const torchHoldersEvents = await getHolderEvents(
106+
torchHolderMap,
107+
transferEvents
108+
)
109+
104110
let isBurned = false
105-
let currentHolder: TorchHolder | null = null
111+
let currentHolder: TorchHolderEvent | null = null
106112
try {
107113
isBurned = await isTorchBurned()
108114
const currentHolderAddress = await getCurrentHolderAddress()
109115
const isFiltered = isAddressFiltered(currentHolderAddress)
116+
const currentHolderEvent = torchHoldersEvents.find(
117+
(holder) => holder.address === currentHolderAddress.toLowerCase()
118+
)
110119

111-
currentHolder = isFiltered
112-
? null
113-
: torchHolderMap[currentHolderAddress.toLowerCase()]
120+
currentHolder = !isFiltered ? (currentHolderEvent ?? null) : null
114121
} catch (error) {
115122
console.error("Error fetching torch data:", error)
116123
}
117-
const transferEvents = await getTransferEvents()
118-
const torchHoldersEvents = await getHolderEvents(
119-
torchHolderMap,
120-
transferEvents
121-
)
122124

123125
// Filter out events where the address is in the filtered list
124126
const torchHolders = torchHoldersEvents.filter(

next.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ module.exports = (phase, { defaultConfig }) => {
9393
protocol: "https",
9494
hostname: "coin-images.coingecko.com",
9595
},
96+
{
97+
protocol: "https",
98+
hostname: "unavatar.io",
99+
},
96100
],
97101
},
98102
async headers() {

src/components/ui/avatar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ const Avatar = React.forwardRef<
167167
{src ? (
168168
<Image
169169
className="object-fill"
170-
width={64}
171-
height={64}
170+
width={128}
171+
height={128}
172172
sizes="4rem"
173173
src={src}
174174
alt={name}
@@ -189,8 +189,8 @@ const Avatar = React.forwardRef<
189189
{src ? (
190190
<Image
191191
className="object-fill"
192-
width={64}
193-
height={64}
192+
width={128}
193+
height={128}
194194
sizes="4rem"
195195
src={src}
196196
alt={name}

src/lib/torch/etherscan.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { type Address } from "viem"
2+
3+
import Torch from "@/data/Torch.json"
4+
5+
const TORCH_CONTRACT_ADDRESS = Torch.address as Address
6+
const TORCH_BLOCK_NUMBER = Torch.blockNumber
7+
8+
export type TransferEvent = {
9+
from: Address
10+
to: Address
11+
blockNumber: number
12+
transactionHash: string
13+
timestamp: number
14+
}
15+
16+
// You'll need to get an API key from https://etherscan.io/apis
17+
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ""
18+
19+
export const fetchTorchTransfersFromEtherscan = async (): Promise<
20+
TransferEvent[]
21+
> => {
22+
if (!ETHERSCAN_API_KEY) {
23+
throw new Error("ETHERSCAN_API_KEY environment variable is required")
24+
}
25+
26+
try {
27+
// Get contract events from Etherscan
28+
const response = await fetch(
29+
[
30+
"https://api.etherscan.io/api",
31+
"?module=logs",
32+
"&action=getLogs",
33+
`&address=${TORCH_CONTRACT_ADDRESS}`,
34+
`&fromBlock=${TORCH_BLOCK_NUMBER}`,
35+
"&toBlock=latest",
36+
// ERC721 Transfer event signature
37+
"&topic0=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
38+
`&apikey=${ETHERSCAN_API_KEY}`,
39+
].join("")
40+
)
41+
42+
const data = await response.json()
43+
44+
if (data.status !== "1") {
45+
throw new Error(`Etherscan API error: ${data.message}`)
46+
}
47+
48+
return data.result.map(
49+
(log: {
50+
topics: string[]
51+
blockNumber: string
52+
transactionHash: string
53+
timeStamp: string
54+
}) => ({
55+
from: `0x${log.topics[1].slice(26)}` as Address, // Remove padding from topic1
56+
to: `0x${log.topics[2].slice(26)}` as Address, // Remove padding from topic2
57+
blockNumber: parseInt(log.blockNumber, 16),
58+
transactionHash: log.transactionHash,
59+
timestamp: parseInt(log.timeStamp, 16),
60+
})
61+
)
62+
} catch (error) {
63+
console.error("Failed to fetch torch transfers from Etherscan:", error)
64+
return []
65+
}
66+
}

src/lib/torch/index.ts

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { getPublicClient } from "@wagmi/core"
66
import Torch from "@/data/Torch.json"
77

88
import { config } from "./config"
9+
import { fetchTorchTransfersFromEtherscan } from "./etherscan"
910

1011
const TORCH_CONTRACT_ADDRESS = Torch.address as Address
1112
const TORCH_ABI = Torch.abi
12-
const TORCH_BLOCK_NUMBER = Torch.blockNumber
1313

1414
// Addresses to filter from the UI (show as "Unknown Holder")
1515
const FILTERED_ADDRESSES: string[] = [
@@ -50,54 +50,7 @@ export type TorchHolderEvent = TorchHolder & {
5050

5151
export const getTransferEvents = cache(
5252
async () => {
53-
const publicClient = getPublicClient(config)
54-
55-
// Get the current block number to ensure consistent results
56-
const currentBlock = await publicClient.getBlockNumber()
57-
58-
// Get Transfer events from the contract
59-
// ERC721 Transfer event signature: Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
60-
const logs = await publicClient.getLogs({
61-
address: TORCH_CONTRACT_ADDRESS,
62-
event: {
63-
type: "event",
64-
name: "Transfer",
65-
inputs: [
66-
{ name: "from", type: "address", indexed: true },
67-
{ name: "to", type: "address", indexed: true },
68-
{ name: "tokenId", type: "uint256", indexed: true },
69-
],
70-
},
71-
args: {
72-
tokenId: BigInt(1), // Torch NFT token ID is always 1
73-
},
74-
fromBlock: BigInt(TORCH_BLOCK_NUMBER) || "earliest",
75-
toBlock: currentBlock,
76-
})
77-
78-
// Process logs and get timestamps
79-
const transferEvents: TransferEvent[] = []
80-
81-
for (const log of logs) {
82-
if (log.args?.from && log.args?.to) {
83-
// Get block details to get timestamp
84-
const block = await publicClient.getBlock({
85-
blockNumber: log.blockNumber,
86-
})
87-
88-
transferEvents.push({
89-
from: log.args.from as Address,
90-
to: log.args.to as Address,
91-
blockNumber: Number(log.blockNumber),
92-
transactionHash: log.transactionHash,
93-
timestamp: Number(block.timestamp),
94-
})
95-
}
96-
}
97-
98-
// Sort by block number (oldest first)
99-
transferEvents.sort((a, b) => a.blockNumber - b.blockNumber)
100-
53+
const transferEvents = await fetchTorchTransfersFromEtherscan()
10154
return transferEvents
10255
},
10356
["torch-transfer-events"],
@@ -195,7 +148,7 @@ export const getAvatarImage = (holder: TorchHolderMetadata | null) => {
195148
return getBlockieImage(holder.address)
196149
}
197150

198-
const extractTwitterHandle = (twitterUrl: string): string | null => {
151+
export const extractTwitterHandle = (twitterUrl: string): string | null => {
199152
// Handle various Twitter URL formats
200153
const patterns = [
201154
/twitter\.com\/([^/?]+)/, // twitter.com/username

0 commit comments

Comments
 (0)