Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CryptoPunks: fetch images on chain #155

Merged
merged 2 commits into from
Aug 19, 2021
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
20 changes: 20 additions & 0 deletions src/fetchers/ethereum/cryptopunks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ContractMethod } from "../../types"
import type { EthereumFetcherConfig } from "./types"

import { decodeString, ethCall, uint256Hex } from "./utils"

export function cryptoPunksImage(config: EthereumFetcherConfig) {
return async function cryptoPunksImage(
index: string,
method: ContractMethod
): Promise<string> {
if (config.ethereum === undefined) {
throw new Error("No Ethereum provider")
}
return ethCall(
config.ethereum,
method.address,
method.methodHash + uint256Hex(BigInt(index))
).then(decodeString)
}
}
9 changes: 5 additions & 4 deletions src/fetchers/ethereum/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
decentralandParcelMetadata,
} from "../shared/decentraland-parcel"
import { moonCatsMetadata, isMoonCats } from "../shared/mooncats"
import { cryptoPunksImage } from "./cryptopunks"
import { moonCatsCatId } from "./mooncats"
import { fetchStandardNftContractData } from "./standard-nft"

Expand All @@ -39,14 +40,14 @@ async function fetchNftMetadata(
return decentralandEstateMetadata(tokenId)
}

if (isCryptoPunks(contractAddress)) {
return cryptoPunksMetadata(tokenId)
}

if (isCryptoKitties(contractAddress)) {
return cryptoKittiesMetadata(tokenId, fetchContext)
}

if (isCryptoPunks(contractAddress)) {
return cryptoPunksMetadata(tokenId, cryptoPunksImage(config))
}

if (isMoonCats(contractAddress)) {
return moonCatsMetadata(tokenId, moonCatsCatId(config), fetchContext)
}
Expand Down
5 changes: 2 additions & 3 deletions src/fetchers/ethereum/mooncats.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { Address, ContractMethod } from "../../types"
import type { ContractMethod } from "../../types"
import type { EthereumFetcherConfig } from "./types"

import { ethCall, uint256Hex } from "./utils"

export function moonCatsCatId(config: EthereumFetcherConfig) {
return async function moonCatsCatId(
contractAddress: Address,
tokenId: string,
method: ContractMethod
): Promise<string> {
Expand All @@ -14,7 +13,7 @@ export function moonCatsCatId(config: EthereumFetcherConfig) {
}
const result = await ethCall(
config.ethereum,
contractAddress,
method.address,
method.methodHash + uint256Hex(BigInt(tokenId))
)
return result.slice(0, 12) // 12 = 0x prefix + 5 bytes
Expand Down
19 changes: 19 additions & 0 deletions src/fetchers/ethers/cryptopunks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Contract, ContractFunction } from "@ethersproject/contracts"
import type { ContractMethod } from "../../types"
import type { EthersFetcherConfigEthersLoaded } from "./types"

export function cryptoPunksImage(config: EthersFetcherConfigEthersLoaded) {
return async function cryptoPunksImage(
index: string,
method: ContractMethod
): Promise<string> {
const contract = new config.ethers.Contract(
method.address,
method.humanReadableAbi,
config.provider
) as InstanceType<typeof Contract> & {
punkImageSvg: ContractFunction<string>
}
return contract.punkImageSvg(index)
}
}
12 changes: 8 additions & 4 deletions src/fetchers/ethers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
decentralandParcelMetadata,
} from "../shared/decentraland-parcel"
import { moonCatsMetadata, isMoonCats } from "../shared/mooncats"
import { cryptoPunksImage } from "./cryptopunks"
import { moonCatsCatId } from "./mooncats"
import { fetchStandardNftContractData } from "./standard-nft"

Expand Down Expand Up @@ -62,16 +63,19 @@ async function fetchNftMetadata(
return decentralandEstateMetadata(tokenId)
}

if (isCryptoPunks(contractAddress)) {
return cryptoPunksMetadata(tokenId)
}

if (isCryptoKitties(contractAddress)) {
return cryptoKittiesMetadata(tokenId, fetchContext)
}

const configWithEthersLoaded = await loadEthers(config)

if (isCryptoPunks(contractAddress)) {
return cryptoPunksMetadata(
tokenId,
cryptoPunksImage(configWithEthersLoaded)
)
}

if (isMoonCats(contractAddress)) {
return moonCatsMetadata(
tokenId,
Expand Down
5 changes: 2 additions & 3 deletions src/fetchers/ethers/mooncats.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { Contract, ContractFunction } from "@ethersproject/contracts"
import type { Address, ContractMethod } from "../../types"
import type { ContractMethod } from "../../types"
import type { EthersFetcherConfigEthersLoaded } from "./types"

export function moonCatsCatId(config: EthersFetcherConfigEthersLoaded) {
return async function moonCatsCatId(
contractAddress: Address,
tokenId: string,
method: ContractMethod
): Promise<string> {
const wrappedContract = new config.ethers.Contract(
contractAddress,
method.address,
method.humanReadableAbi,
config.provider
) as InstanceType<typeof Contract> & {
Expand Down
30 changes: 26 additions & 4 deletions src/fetchers/shared/cryptopunks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Address, NftMetadata } from "../../types"
import type { Address, ContractMethod, NftMetadata } from "../../types"

import { CRYPTOPUNKS, CRYPTOPUNKS_IMAGES } from "../../known-contracts"
import { addressesEqual } from "../../utils"
import { CRYPTOPUNKS } from "../../known-contracts"

const CRYPTOPUNKS_DESCRIPTION = `
10,000 unique collectible characters with proof of ownership stored on the
Expand All @@ -10,10 +10,32 @@ const CRYPTOPUNKS_DESCRIPTION = `
standard that powers most digital art and collectibles.
`

export function cryptoPunksMetadata(index: string): NftMetadata {
const CRYPTOPUNKS_IMAGE_SVG: ContractMethod = {
address: CRYPTOPUNKS_IMAGES,
methodName: "punkImageSvg",
methodHash: "0x74beb047",
humanReadableAbi: [
"function punkImageSvg(uint16 index) view returns (string svg)",
],
}

function encodeUriData(dataUri: string): string {
const dataStart = dataUri.indexOf(",") + 1
return (
dataUri.slice(0, dataStart) +
encodeURIComponent(dataUri.slice(dataStart)) ?? ""
)
}

export async function cryptoPunksMetadata(
index: string,
cryptoPunksImage: (tokenId: string, method: ContractMethod) => Promise<string>
): Promise<NftMetadata> {
const image = await cryptoPunksImage(index, CRYPTOPUNKS_IMAGE_SVG)

return {
description: CRYPTOPUNKS_DESCRIPTION,
image: `https://www.larvalabs.com/cryptopunks/cryptopunk${index}.png`,
image: encodeUriData(image),
imageType: "image",
metadataUrl: "",
name: `CryptoPunk ${index}`,
Expand Down
12 changes: 2 additions & 10 deletions src/fetchers/shared/mooncats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,10 @@ export async function imageUrl(

export async function moonCatsMetadata(
tokenId: string,
getCatId: (
contractAddress: Address,
tokenId: string,
method: ContractMethod
) => Promise<string>,
getCatId: (tokenId: string, method: ContractMethod) => Promise<string>,
fetchContext: FetchContext
): Promise<NftMetadata> {
const catId = await getCatId(
MOONCATS_WRAPPED.address,
tokenId,
MOONCATS_WRAPPED
)
const catId = await getCatId(tokenId, MOONCATS_WRAPPED)

const image = (await imageUrl(catId, fetchContext.ipfsUrl)) ?? ""
return {
Expand Down
1 change: 1 addition & 0 deletions src/known-contracts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const CRYPTOKITTIES = "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d"
export const CRYPTOPUNKS = "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb"
export const CRYPTOPUNKS_IMAGES = "0x16F5A35647D6F03D5D3da7b35409D65ba03aF3B2"
export const CRYPTOVOXELS = "0x79986aF15539de2db9A5086382daEdA917A9CF0C"
export const DECENTRALAND_ESTATE = "0x959e104E1a4dB6317fA58F8295F586e1A978c297"
export const DECENTRALAND_PARCEL = "0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d"
7 changes: 7 additions & 0 deletions website/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2499,7 +2499,14 @@ typescript@^4.3.5:
dependencies:
swr: ^1.0.0-beta.10
peerDependencies:
"@ethersproject/contracts": ">=5.2.0 <6"
ethers: ">=5.2.0 <6"
react: ">=17"
peerDependenciesMeta:
"@ethersproject/contracts":
optional: true
ethers:
optional: true
languageName: node
linkType: soft

Expand Down