Skip to content

Commit

Permalink
add erc721 urn (#166)
Browse files Browse the repository at this point in the history
* add erc721 urn

* final touches
  • Loading branch information
menduz authored May 11, 2023
1 parent 011b40e commit 64b7018
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 73 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ const parsed = await parseUrn("urn:decentraland:goerli:LAND:-10,-13?atBlock=1512
# Registered routes

- `decentraland:off-chain:{registry}:{name}`: Resolve static offchain assets (i.e. base wearables, not in any blockchain)
- `decentraland:{protocol}:collections-v1:{contract(0x[a-fA-F0-9]+)}:{name}`: Resolve an ethereum wearables collection asset by contract address (v1)
- `decentraland:{protocol}:collections-v1:{collection-name}:{name}`: Resolve an ethereum wearables collection asset by collection name (wearables API) (v1)
- `decentraland:{protocol}:collections-v2:{contract(0x[a-fA-F0-9]+)}:{id}`: Resolve an ethereum wearables collection asset by contract address (v2)
- `decentraland:{protocol}:LAND:{x},{y}`: Resolves the ethereum asset of a LAND position.
- `decentraland:{protocol}:LAND:{tokenId}`: Resolves the ethereum asset of a LAND by tokenId.
- `decentraland:{protocol}:collections-thirdparty:{thirdPartyName}:{collectionId}:{itemId}`: Resolves the ethereum asset of an item of a third party collection, currently only supported on polygon
- `decentraland:{protocol}:collections-thirdparty:{thirdPartyName}:{collectionId}`: Resolves the ethereum asset of a third party collection, currently only supported on polygon
- `decentraland:{protocol}:collections-thirdparty:{thirdPartyName}`: Resolves the ethereum asset of all collections from a third party, currently only supported on polygon
- `decentraland:{network}:collections-v1:{contract(0x[a-fA-F0-9]+)}:{name}`: Resolve an ethereum wearables collection asset by contract address (v1)
- `decentraland:{network}:collections-v1:{collection-name}:{name}`: Resolve an ethereum wearables collection asset by collection name (wearables API) (v1)
- `decentraland:{network}:collections-v2:{contract(0x[a-fA-F0-9]+)}:{id}`: Resolve an ethereum wearables collection asset by contract address (v2)
- `decentraland:{network}:LAND:{x},{y}`: Resolves the ethereum asset of a LAND position.
- `decentraland:{network}:LAND:{tokenId}`: Resolves the ethereum asset of a LAND by tokenId.
- `decentraland:{network}:collections-thirdparty:{thirdPartyName}:{collectionId}:{itemId}`: Resolves the ethereum asset of an item of a third party collection, currently only supported on polygon
- `decentraland:{network}:collections-thirdparty:{thirdPartyName}:{collectionId}`: Resolves the ethereum asset of a third party collection, currently only supported on polygon
- `decentraland:{network}:collections-thirdparty:{thirdPartyName}`: Resolves the ethereum asset of all collections from a third party, currently only supported on polygon

# DecentralandAssetIdentifier

Expand Down
8 changes: 4 additions & 4 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ const collections: Collection[] = []

const lowerCasedContracts: Record<string, Record<string, string>> = {}

const validProtocols = new Set(["ethereum", "kovan", "rinkeby", "goerli", "matic", "mumbai"])
const validNetworks = new Set(["ethereum", "kovan", "rinkeby", "goerli", "matic", "mumbai"])

for (let network in contracts) {
lowerCasedContracts[network] = Object.create(null)
const c = lowerCasedContracts[network]
if (network.toLowerCase() != "mainnet") {
validProtocols.add(network.toLowerCase())
validNetworks.add(network.toLowerCase())
}
Object.keys(contracts[network]).forEach((key) => {
c[key.toLowerCase()] = contracts[network][key]
Expand Down Expand Up @@ -58,8 +58,8 @@ export async function getContract(network: string, contractNameOrAddress: string
return mapContract(network.toLowerCase(), contractNameOrAddress.toLowerCase())
}

export function isValidProtocol(protocol: string): boolean {
return validProtocols.has(protocol.toLowerCase())
export function isValidNetwork(protocol: string): boolean {
return validNetworks.has(protocol.toLowerCase())
}

export type ParserFunction = (original: URL, captures: RegExpExecArray) => Promise<{ url: URL } | undefined>
Expand Down
138 changes: 79 additions & 59 deletions src/resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { createParser, getCollection, getContract, isValidProtocol, RouteMap } from "./helpers"
import { createParser, getCollection, getContract, isValidNetwork, RouteMap } from "./helpers"
import { LandUtils } from "./land-utils"
import {
BlockchainAsset,
Expand All @@ -26,38 +26,40 @@ export const resolvers: RouteMap<DecentralandAssetIdentifier> = {
// Resolver for deployed entities. Deployed entities are used to specify portable experience identifiers that may be deployed anywhere in the web.
"decentraland:entity:{cid}": resolveEntityV3,
// collections v1 asset (by contract)
"decentraland:{protocol}:collections-v1:{contract(0x[a-fA-F0-9]+)}:{name}": resolveCollectionV1Asset,
"decentraland:{network}:collections-v1:{contract(0x[a-fA-F0-9]+)}:{name}": resolveCollectionV1Asset,
// collections v1 asset (by name)
"decentraland:{protocol}:collections-v1:{collectionName}:{name}": resolveCollectionV1AssetByCollectionName,
"decentraland:{network}:collections-v1:{collectionName}:{name}": resolveCollectionV1AssetByCollectionName,
// collections v2 asset (hex)
"decentraland:{protocol}:collections-v2:{contract(0x[a-fA-F0-9]+)}:{id(0x[a-fA-F0-9]+)}": resolveCollectionV2Asset,
"decentraland:{network}:collections-v2:{contract(0x[a-fA-F0-9]+)}:{id(0x[a-fA-F0-9]+)}": resolveCollectionV2Asset,
// collections v2 asset (id)
"decentraland:{protocol}:collections-v2:{contract(0x[a-fA-F0-9]+)}:{id([0-9]+)}": resolveCollectionV2Asset,
"decentraland:{network}:collections-v2:{contract(0x[a-fA-F0-9]+)}:{id([0-9]+)}": resolveCollectionV2Asset,
// collections v1 (by contract)
"decentraland:{protocol}:collections-v1:{contract(0x[a-fA-F0-9]+)}": resolveCollectionV1,
"decentraland:{network}:collections-v1:{contract(0x[a-fA-F0-9]+)}": resolveCollectionV1,
// collections v1 (by name)
"decentraland:{protocol}:collections-v1:{collectionName}": resolveCollectionV1ByCollectionName,
"decentraland:{network}:collections-v1:{collectionName}": resolveCollectionV1ByCollectionName,
// collections v2
"decentraland:{protocol}:collections-v2:{contract(0x[a-fA-F0-9]+)}": resolveCollectionV2,
"decentraland:{network}:collections-v2:{contract(0x[a-fA-F0-9]+)}": resolveCollectionV2,
// resolve LAND by position
"decentraland:{protocol}:LAND:{position}": resolveLandAsset,
"decentraland:{network}:LAND:{position}": resolveLandAsset,
// resolve third party names
"decentraland:{protocol}:collections-thirdparty:{thirdPartyName}": resolveThirdPartyCollectionName,
"decentraland:{network}:collections-thirdparty:{thirdPartyName}": resolveThirdPartyCollectionName,
// resolve third party collections
"decentraland:{protocol}:collections-thirdparty:{thirdPartyName}:{collectionId}": resolveThirdPartyCollectionOnlyCollection,
"decentraland:{network}:collections-thirdparty:{thirdPartyName}:{collectionId}": resolveThirdPartyCollectionOnlyCollection,
// resolve third party assets
"decentraland:{protocol}:collections-thirdparty:{thirdPartyName}:{collectionId}:{itemId}": resolveThirdPartyCollection
"decentraland:{network}:collections-thirdparty:{thirdPartyName}:{collectionId}:{itemId}": resolveThirdPartyCollection,
// resolve 721 assets
"decentraland:{network}:erc721:{contract(0x[a-fA-F0-9]+)}:{tokenId}": resolveErc721Asset,
}

export const internalResolver = createParser(resolvers)

export async function resolveLandAsset(
uri: URL,
groups: Record<"protocol" | "position", string>
groups: Record<"network" | "position", string>
): Promise<BlockchainLandAsset | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, "LandProxy")
const contract = await getContract(groups.network, "LandProxy")

let { x, y } = LandUtils.parseParcelPosition(groups.position)

Expand All @@ -74,7 +76,7 @@ export async function resolveLandAsset(
if (contract) {
const r = await resolveEthereumAsset(uri, {
contract,
protocol: groups.protocol.toLowerCase(),
network: groups.network.toLowerCase(),
tokenId: "0x" + tokenId.toString(16),
})

Expand All @@ -87,6 +89,24 @@ export async function resolveLandAsset(
}
}

export async function resolveErc721Asset(
uri: URL,
groups: Record<"network" | "contract" | "tokenId", string>
): Promise<BlockchainAsset | void> {
if (!isValidNetwork(groups.network)) return

const r = await resolveEthereumAsset(uri, {
contract: groups.contract,
network: groups.network.toLowerCase(),
tokenId: groups.tokenId,
})

if (r)
return {
...r,
}
}

export async function resolveLegacyDclUrl(uri: URL) {
let host: string
let path: string[]
Expand All @@ -111,19 +131,19 @@ export async function resolveLegacyDclUrl(uri: URL) {

export async function resolveEthereumAsset(
uri: URL,
groups: Record<"protocol" | "contract" | "tokenId", string>
groups: Record<"network" | "contract" | "tokenId", string>
): Promise<BlockchainAsset | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, groups.contract)
const contract = await getContract(groups.network, groups.contract)

if (contract)
return {
namespace: "decentraland",
uri,
blockchain: "ethereum",
type: "blockchain-asset",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
contractAddress: contract,
id: groups.tokenId,
}
Expand All @@ -148,7 +168,7 @@ export async function resolveEntityV3(
): Promise<EntityV3Asset | void> {
let baseUrl: string | undefined

if (uri.searchParams.has('baseUrl')){
if (uri.searchParams.has('baseUrl')) {
baseUrl = uri.searchParams.get('baseUrl')!
}

Expand All @@ -163,10 +183,10 @@ export async function resolveEntityV3(

export async function resolveCollectionV1AssetByCollectionName(
uri: URL,
groups: Record<"protocol" | "collectionName" | "name", string>
groups: Record<"network" | "collectionName" | "name", string>
): Promise<BlockchainCollectionV1Asset | void> {
// this only works in mainnet
if (groups.protocol != "ethereum") return
if (groups.network != "ethereum") return

const collection = await getCollection(groups.collectionName)

Expand All @@ -184,11 +204,11 @@ export async function resolveCollectionV1AssetByCollectionName(

export async function resolveCollectionV1Asset(
uri: URL,
groups: Record<"protocol" | "contract" | "name", string>
groups: Record<"network" | "contract" | "name", string>
): Promise<BlockchainCollectionV1Asset | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, groups.contract)
const contract = await getContract(groups.network, groups.contract)

if (contract) {
const collection = await getCollection(contract)
Expand All @@ -198,7 +218,7 @@ export async function resolveCollectionV1Asset(
uri,
blockchain: "ethereum",
type: "blockchain-collection-v1-asset",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
contractAddress: contract,
id: groups.name,
collectionName: collection ? collection.collectionId : null,
Expand All @@ -208,31 +228,31 @@ export async function resolveCollectionV1Asset(

export async function resolveCollectionV2Asset(
uri: URL,
groups: Record<"protocol" | "contract" | "id", string>
groups: Record<"network" | "contract" | "id", string>
): Promise<BlockchainCollectionV2Asset | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, groups.contract)
const contract = await getContract(groups.network, groups.contract)

if (contract)
return {
namespace: "decentraland",
uri,
blockchain: "ethereum",
type: "blockchain-collection-v2-asset",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
contractAddress: contract,
id: groups.id,
}
}

export async function resolveCollectionV1(
uri: URL,
groups: Record<"protocol" | "contract", string>
groups: Record<"network" | "contract", string>
): Promise<BlockchainCollectionV1 | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, groups.contract)
const contract = await getContract(groups.network, groups.contract)

if (contract) {
const collection = await getCollection(contract)
Expand All @@ -242,7 +262,7 @@ export async function resolveCollectionV1(
uri,
blockchain: "ethereum",
type: "blockchain-collection-v1",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
id: contract,
collectionName: collection ? collection.collectionId : null
}
Expand All @@ -251,10 +271,10 @@ export async function resolveCollectionV1(

export async function resolveCollectionV1ByCollectionName(
uri: URL,
groups: Record<"protocol" | "collectionName", string>
groups: Record<"network" | "collectionName", string>
): Promise<BlockchainCollectionV1 | void> {
// this only works in mainnet
if (groups.protocol != "ethereum") return
if (groups.network != "ethereum") return

const collection = await getCollection(groups.collectionName)

Expand All @@ -273,85 +293,85 @@ export async function resolveCollectionV1ByCollectionName(

export async function resolveCollectionV2(
uri: URL,
groups: Record<"protocol" | "contract", string>
groups: Record<"network" | "contract", string>
): Promise<BlockchainCollectionV2 | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, groups.contract)
const contract = await getContract(groups.network, groups.contract)

if (contract)
return {
namespace: "decentraland",
uri,
blockchain: "ethereum",
type: "blockchain-collection-v2",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
contractAddress: contract,
id: contract,
}
}

export async function resolveThirdPartyCollection(
uri: URL,
groups: Record<"protocol" | "thirdPartyName" | "collectionId" | "itemId", string>
groups: Record<"network" | "thirdPartyName" | "collectionId" | "itemId", string>
): Promise<BlockchainCollectionThirdParty | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, "TPR")
const contract = await getContract(groups.network, "TPR")

if (contract) {
return {
namespace: "decentraland",
uri,
blockchain: "ethereum",
type: "blockchain-collection-third-party",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
thirdPartyName: groups.thirdPartyName,
collectionId: groups.collectionId,
itemId: groups.itemId,
contractAddress: contract
}
contractAddress: contract
}
}
}

export async function resolveThirdPartyCollectionName(
uri: URL,
groups: Record<"protocol" | "thirdPartyName" , string>): Promise<BlockchainCollectionThirdPartyName | void> {
if (!isValidProtocol(groups.protocol)) return
groups: Record<"network" | "thirdPartyName", string>): Promise<BlockchainCollectionThirdPartyName | void> {
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, "TPR")
const contract = await getContract(groups.network, "TPR")

if (contract) {
return {
namespace: "decentraland",
uri,
blockchain: "ethereum",
type: "blockchain-collection-third-party-name",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
thirdPartyName: groups.thirdPartyName,
contractAddress: contract
}
contractAddress: contract
}
}
}

export async function resolveThirdPartyCollectionOnlyCollection(
uri: URL,
groups: Record<"protocol" | "thirdPartyName" | "collectionId", string>
groups: Record<"network" | "thirdPartyName" | "collectionId", string>
): Promise<BlockchainCollectionThirdPartyCollection | void> {
if (!isValidProtocol(groups.protocol)) return
if (!isValidNetwork(groups.network)) return

const contract = await getContract(groups.protocol, "TPR")
const contract = await getContract(groups.network, "TPR")

if (contract) {
return {
namespace: "decentraland",
uri,
blockchain: "ethereum",
type: "blockchain-collection-third-party-collection",
network: groups.protocol == "ethereum" ? "mainnet" : groups.protocol.toLowerCase(),
network: groups.network == "ethereum" ? "mainnet" : groups.network.toLowerCase(),
thirdPartyName: groups.thirdPartyName,
collectionId: groups.collectionId,
contractAddress: contract
}
contractAddress: contract
}
}
}
Loading

0 comments on commit 64b7018

Please sign in to comment.