Skip to content

Commit

Permalink
Merge pull request #79 from yuki-wtf/fetching-nfts-starkscan
Browse files Browse the repository at this point in the history
Fetching nfts starkscan
  • Loading branch information
lorcan-codes authored Jan 25, 2024
2 parents 62c2c39 + ff7cbf8 commit 91bfd6f
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 12 deletions.
10 changes: 6 additions & 4 deletions app/components/Snapshot/SnapshotMint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useAccount, useProvider } from '@starknet-react/core'
import { CallData, cairo } from 'starknet'
import Button from '../Button'
import { useRootLoaderData } from '~/hooks/useRootLoaderData'
import { useFetcher } from '@remix-run/react'

const MintedAddressContainer = styled.div`
display: flex;
Expand Down Expand Up @@ -37,7 +38,8 @@ export const SnapshotMint = ({ generation, nft }: { generation: string }) => {
const { account } = useAccount()
const { provider } = useProvider()
const { env } = useRootLoaderData()
const voyagerUrl = env.USE_MAINNET ? 'https://voyager.online' : 'https://goerli.voyager.online'
const starkscan = env.USE_MAINNET ? 'https://starkscan.co' : 'https://testnet.starkscan.co'
const fetcher = useFetcher()

const mintGame = async (generation: string) => {
if (!account) {
Expand All @@ -57,8 +59,7 @@ export const SnapshotMint = ({ generation, nft }: { generation: string }) => {
calldata: CallData.compile([generation]),
},
])
const res = await provider.waitForTransaction(multiCall.transaction_hash)
console.log('res', res)
return provider.waitForTransaction(multiCall.transaction_hash)
}

const isLoading = false
Expand All @@ -73,6 +74,7 @@ export const SnapshotMint = ({ generation, nft }: { generation: string }) => {
onClick={(e) => {
mintGame(generation).then((minted) => {
console.log('minted', minted)
fetcher.load('/snapshots')
})
e.stopPropagation()
}}
Expand All @@ -87,7 +89,7 @@ export const SnapshotMint = ({ generation, nft }: { generation: string }) => {
<div className="address">
MINTED ON CHAIN:
<a
href={`${voyagerUrl}/tx/${nft.transactionHash}`}
href={`${starkscan}/nft/${nft.contractAddress}/${nft.gameGeneration}`}
target="_blank"
rel="noreferrer"
style={{
Expand Down
18 changes: 18 additions & 0 deletions app/helpers/getUserNFTsStarkscan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export async function getUserNFTs(owner: string) {
const contractAddress = process.env.NFT_CONTRACT_ADDRESS ?? ''
const baseUrl = process.env.USE_MAINNET === 'true' ? 'https://api.starkscan.co' : 'https://api-testnet.starkscan.co'
const url = `${baseUrl}/api/v0/nfts?contract_address=${contractAddress}&owner_address=${owner}&limit=100`

const res = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.STARKSCAN_API_KEY ?? '',
},
})
const resJson = await res.json()
if (resJson && resJson.status === 404) {
throw new Error(resJson.message)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return resJson.data
}
60 changes: 60 additions & 0 deletions app/helpers/mergeSnapshotsWithStarkscanNFTs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
const targetContractAddress = process.env.NFT_CONTRACT_ADDRESS

function transformNfts(nfts) {
const nftsByGeneration = {}
nfts.forEach((nft) => {
if (nft.token_id) {
nftsByGeneration[nft.token_id] = nft
}
})
return nftsByGeneration
}

export function mergeSnapshotsWithNFTs(snapshots, nfts) {
const nftsByGeneration = transformNfts(nfts)

const snapshotsWithNfts = snapshots.map((snapshot) => {
const snapshotWithNft = { ...snapshot }
const nft = nftsByGeneration[snapshot.gameGeneration]

if (nft && nft.contract_address.toLowerCase() === targetContractAddress.toLowerCase()) {
const gameModeAttribute = nft.attributes.find((attr) => attr.trait_type === 'Game Mode')

const nftData = {
...nft.raw?.metadata,
gameGeneration: snapshot.gameGeneration,
transactionHash: nft.minted_at_transaction_hash,
contractAddress: nft.contract_address,
}
if (gameModeAttribute) {
nftData.gameMode = gameModeAttribute.value
}
delete nftData.attributes
snapshotWithNft.nft = nftData
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return snapshotWithNft
})

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return snapshotsWithNfts
}

// Sample data
// nft_id: '0x00f4ad66ea867300f97ec6e47c2d0aaa201367c1265a00b6b643fab77531bb82_5',
// contract_address: '0x00f4ad66ea867300f97ec6e47c2d0aaa201367c1265a00b6b643fab77531bb82',
// token_id: '5',
// name: 'GoL2 #5',
// description: 'Snapshot of GoL2 Game at Generation #5',
// external_url: 'https://gol2.io',
// attributes: [ [Object], [Object], [Object], [Object], [Object] ],
// image_url: 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%22910%22%20height=%22910%22%20viewBox=%220%200%20910%20910%22%3E%3Cg%20transform=%22translate(5%205)%22%3E%3Crect%20width=%22900%22%20height=%22900%22%20fill=%22%231e222b%22/%3E%3Cg%20stroke=%22%235e6266%22%20stroke-width=%221%22%3E%3Cline%20x1=%220%22%20y1=%2260%22%20x2=%22900%22%20y2=%2260%22/%3E%3Cline%20x1=%2260%22%20y1=%220%22%20x2=%2260%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22120%22%20x2=%22900%22%20y2=%22120%22/%3E%3Cline%20x1=%22120%22%20y1=%220%22%20x2=%22120%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22180%22%20x2=%22900%22%20y2=%22180%22/%3E%3Cline%20x1=%22180%22%20y1=%220%22%20x2=%22180%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22240%22%20x2=%22900%22%20y2=%22240%22/%3E%3Cline%20x1=%22240%22%20y1=%220%22%20x2=%22240%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22300%22%20x2=%22900%22%20y2=%22300%22/%3E%3Cline%20x1=%22300%22%20y1=%220%22%20x2=%22300%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22360%22%20x2=%22900%22%20y2=%22360%22/%3E%3Cline%20x1=%22360%22%20y1=%220%22%20x2=%22360%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22420%22%20x2=%22900%22%20y2=%22420%22/%3E%3Cline%20x1=%22420%22%20y1=%220%22%20x2=%22420%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22480%22%20x2=%22900%22%20y2=%22480%22/%3E%3Cline%20x1=%22480%22%20y1=%220%22%20x2=%22480%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22540%22%20x2=%22900%22%20y2=%22540%22/%3E%3Cline%20x1=%22540%22%20y1=%220%22%20x2=%22540%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22600%22%20x2=%22900%22%20y2=%22600%22/%3E%3Cline%20x1=%22600%22%20y1=%220%22%20x2=%22600%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22660%22%20x2=%22900%22%20y2=%22660%22/%3E%3Cline%20x1=%22660%22%20y1=%220%22%20x2=%22660%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22720%22%20x2=%22900%22%20y2=%22720%22/%3E%3Cline%20x1=%22720%22%20y1=%220%22%20x2=%22720%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22780%22%20x2=%22900%22%20y2=%22780%22/%3E%3Cline%20x1=%22780%22%20y1=%220%22%20x2=%22780%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22840%22%20x2=%22900%22%20y2=%22840%22/%3E%3Cline%20x1=%22840%22%20y1=%220%22%20x2=%22840%22%20y2=%22900%22/%3E%3C/g%3E%3Cg%20fill=%22%23dff17b%22%20stroke=%22%23dff17b%22%20stroke-width=%220.5%22%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(540%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(600%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(720%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(540%20420)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20420)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(600%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(660%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(840%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(720%20540)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20540)%22/%3E%3C/g%3E%3Crect%20width=%22900%22%20height=%22900%22%20fill=%22none%22%20stroke=%22%230a0c10%22%20stroke-width=%225%22/%3E%3C/g%3E%3C/svg%3E',
// image_small_url: 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%22910%22%20height=%22910%22%20viewBox=%220%200%20910%20910%22%3E%3Cg%20transform=%22translate(5%205)%22%3E%3Crect%20width=%22900%22%20height=%22900%22%20fill=%22%231e222b%22/%3E%3Cg%20stroke=%22%235e6266%22%20stroke-width=%221%22%3E%3Cline%20x1=%220%22%20y1=%2260%22%20x2=%22900%22%20y2=%2260%22/%3E%3Cline%20x1=%2260%22%20y1=%220%22%20x2=%2260%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22120%22%20x2=%22900%22%20y2=%22120%22/%3E%3Cline%20x1=%22120%22%20y1=%220%22%20x2=%22120%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22180%22%20x2=%22900%22%20y2=%22180%22/%3E%3Cline%20x1=%22180%22%20y1=%220%22%20x2=%22180%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22240%22%20x2=%22900%22%20y2=%22240%22/%3E%3Cline%20x1=%22240%22%20y1=%220%22%20x2=%22240%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22300%22%20x2=%22900%22%20y2=%22300%22/%3E%3Cline%20x1=%22300%22%20y1=%220%22%20x2=%22300%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22360%22%20x2=%22900%22%20y2=%22360%22/%3E%3Cline%20x1=%22360%22%20y1=%220%22%20x2=%22360%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22420%22%20x2=%22900%22%20y2=%22420%22/%3E%3Cline%20x1=%22420%22%20y1=%220%22%20x2=%22420%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22480%22%20x2=%22900%22%20y2=%22480%22/%3E%3Cline%20x1=%22480%22%20y1=%220%22%20x2=%22480%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22540%22%20x2=%22900%22%20y2=%22540%22/%3E%3Cline%20x1=%22540%22%20y1=%220%22%20x2=%22540%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22600%22%20x2=%22900%22%20y2=%22600%22/%3E%3Cline%20x1=%22600%22%20y1=%220%22%20x2=%22600%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22660%22%20x2=%22900%22%20y2=%22660%22/%3E%3Cline%20x1=%22660%22%20y1=%220%22%20x2=%22660%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22720%22%20x2=%22900%22%20y2=%22720%22/%3E%3Cline%20x1=%22720%22%20y1=%220%22%20x2=%22720%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22780%22%20x2=%22900%22%20y2=%22780%22/%3E%3Cline%20x1=%22780%22%20y1=%220%22%20x2=%22780%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22840%22%20x2=%22900%22%20y2=%22840%22/%3E%3Cline%20x1=%22840%22%20y1=%220%22%20x2=%22840%22%20y2=%22900%22/%3E%3C/g%3E%3Cg%20fill=%22%23dff17b%22%20stroke=%22%23dff17b%22%20stroke-width=%220.5%22%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(540%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(600%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(720%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(540%20420)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20420)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(600%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(660%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(840%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(720%20540)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20540)%22/%3E%3C/g%3E%3Crect%20width=%22900%22%20height=%22900%22%20fill=%22none%22%20stroke=%22%230a0c10%22%20stroke-width=%225%22/%3E%3C/g%3E%3C/svg%3E',
// image_medium_url: 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%22910%22%20height=%22910%22%20viewBox=%220%200%20910%20910%22%3E%3Cg%20transform=%22translate(5%205)%22%3E%3Crect%20width=%22900%22%20height=%22900%22%20fill=%22%231e222b%22/%3E%3Cg%20stroke=%22%235e6266%22%20stroke-width=%221%22%3E%3Cline%20x1=%220%22%20y1=%2260%22%20x2=%22900%22%20y2=%2260%22/%3E%3Cline%20x1=%2260%22%20y1=%220%22%20x2=%2260%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22120%22%20x2=%22900%22%20y2=%22120%22/%3E%3Cline%20x1=%22120%22%20y1=%220%22%20x2=%22120%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22180%22%20x2=%22900%22%20y2=%22180%22/%3E%3Cline%20x1=%22180%22%20y1=%220%22%20x2=%22180%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22240%22%20x2=%22900%22%20y2=%22240%22/%3E%3Cline%20x1=%22240%22%20y1=%220%22%20x2=%22240%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22300%22%20x2=%22900%22%20y2=%22300%22/%3E%3Cline%20x1=%22300%22%20y1=%220%22%20x2=%22300%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22360%22%20x2=%22900%22%20y2=%22360%22/%3E%3Cline%20x1=%22360%22%20y1=%220%22%20x2=%22360%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22420%22%20x2=%22900%22%20y2=%22420%22/%3E%3Cline%20x1=%22420%22%20y1=%220%22%20x2=%22420%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22480%22%20x2=%22900%22%20y2=%22480%22/%3E%3Cline%20x1=%22480%22%20y1=%220%22%20x2=%22480%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22540%22%20x2=%22900%22%20y2=%22540%22/%3E%3Cline%20x1=%22540%22%20y1=%220%22%20x2=%22540%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22600%22%20x2=%22900%22%20y2=%22600%22/%3E%3Cline%20x1=%22600%22%20y1=%220%22%20x2=%22600%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22660%22%20x2=%22900%22%20y2=%22660%22/%3E%3Cline%20x1=%22660%22%20y1=%220%22%20x2=%22660%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22720%22%20x2=%22900%22%20y2=%22720%22/%3E%3Cline%20x1=%22720%22%20y1=%220%22%20x2=%22720%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22780%22%20x2=%22900%22%20y2=%22780%22/%3E%3Cline%20x1=%22780%22%20y1=%220%22%20x2=%22780%22%20y2=%22900%22/%3E%3Cline%20x1=%220%22%20y1=%22840%22%20x2=%22900%22%20y2=%22840%22/%3E%3Cline%20x1=%22840%22%20y1=%220%22%20x2=%22840%22%20y2=%22900%22/%3E%3C/g%3E%3Cg%20fill=%22%23dff17b%22%20stroke=%22%23dff17b%22%20stroke-width=%220.5%22%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(540%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(600%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(720%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20360)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(540%20420)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20420)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(600%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(660%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(840%20480)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(720%20540)%22/%3E%3Crect%20width=%2260%22%20height=%2260%22%20transform=%22translate(780%20540)%22/%3E%3C/g%3E%3Crect%20width=%22900%22%20height=%22900%22%20fill=%22none%22%20stroke=%22%230a0c10%22%20stroke-width=%225%22/%3E%3C/g%3E%3C/svg%3E',
// animation_url: null,
// minted_by_address: '0x03a33a013cc7a9b632c685b7e7df3c318beb1cc8e9d08e0e54a3fd34b88abeba',
// minted_at_transaction_hash: '0x06da7e4ae39d153b658ea7eb3ab6948db71eeb3c69e826ab0eb6e489a8520e66',
// minted_at_timestamp: 1705573232
4 changes: 2 additions & 2 deletions app/hooks/useGamePlayback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ export function useGamePlayback({
})
)
})
// }, [fetchFrames, state.currentFrame, state.maxFrame])
}, [fetchFrames, state.currentFrame, state.frames, state.maxFrame])
}, [fetchFrames, state.currentFrame, state.maxFrame])
// }, [fetchFrames, state.currentFrame, state.frames, state.maxFrame])

return [
state,
Expand Down
28 changes: 22 additions & 6 deletions app/routes/snapshots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import { json } from '@remix-run/node'
import type { Infinite } from '~/db.server'
import { sql } from '~/db.server'
import { getUserId } from '~/session.server'
import { useLoaderData } from '@remix-run/react'
import { useFetcher } from '@remix-run/react'
import { num } from 'starknet'
import { useUser } from '~/hooks/useUser'
import { useRootLoaderData } from '~/hooks/useRootLoaderData'
import { mergeSnapshotsWithNFTs } from '~/helpers/mergeSnapshotsWithNFTs'
import { getUserNFTs } from '~/helpers/getUserNFTs'
// import { mergeSnapshotsWithNFTs } from '~/helpers/mergeSnapshotsWithNFTs'
import { mergeSnapshotsWithNFTs } from '~/helpers/mergeSnapshotsWithStarkscanNFTs'
import { getUserNFTs } from '~/helpers/getUserNFTsStarkscan'
import { useEffect, useState } from 'react'
import { useUpdateEffect } from 'react-use'

const hexToDecimalString = num.hexToDecimalString

Expand Down Expand Up @@ -45,7 +48,7 @@ export async function loader({ request }: LoaderArgs): Promise<TypedResponse<Inf

if (userId == null) return json(null)

const { data } = await getUserNFTs(userId)
const userNfts = await getUserNFTs(userId)

const result = await sql<Infinite>`
select *
Expand All @@ -54,14 +57,27 @@ export async function loader({ request }: LoaderArgs): Promise<TypedResponse<Inf
and "transactionOwner" = ${hexToDecimalString(userId)}
`

const snapshotsWithNfts = mergeSnapshotsWithNFTs(result.rows, data.ownedNfts)
const snapshotsWithNfts = mergeSnapshotsWithNFTs(result.rows, userNfts)
return json(snapshotsWithNfts)
}

export default function Snapshots() {
const user = useUser()
const data = useLoaderData<typeof loader>()
const { load, data: fetcherData } = useFetcher()
const { env } = useRootLoaderData()
const [data, setData] = useState(null)

useEffect(() => {
load('/snapshots')
}, [load])

useUpdateEffect(() => {
console.log('fetcherData', fetcherData)
if (fetcherData) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
setData(fetcherData)
}
}, [fetcherData])

return (
<ContainerInner maxWidth={1000} paddingBottom={64}>
Expand Down

0 comments on commit 91bfd6f

Please sign in to comment.