Skip to content

Commit

Permalink
feat: add BUNDLE in subgraph
Browse files Browse the repository at this point in the history
  • Loading branch information
levalleux-ludo committed Jan 22, 2024
1 parent 636fcd7 commit 86d2fd2
Show file tree
Hide file tree
Showing 8 changed files with 387 additions and 9 deletions.
86 changes: 86 additions & 0 deletions packages/subgraph/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
enum MetadataType {
BASE
PRODUCT_V1
BUNDLE
}

enum ItemMetadataType {
ITEM_PRODUCT_V1
ITEM_NFT
}

interface MetadataInterface {
Expand Down Expand Up @@ -37,6 +43,13 @@ interface MetadataInterface {
numberOfRedemptions: BigInt!
}

interface ItemMetadataInterface {
id: ID!
schemaUrl: String! # JSON schema URL
type: ItemMetadataType!
bundle: BundleMetadataEntity!
}

type SellerContactLink @entity {
id: ID!
url: String!
Expand Down Expand Up @@ -195,6 +208,79 @@ type ProductV1MetadataEntity implements MetadataInterface @entity {
productOverrides: ProductV1ProductOverrides
}

type BundleMetadataEntity implements MetadataInterface @entity {
"""
Fields compliant to metadata standard
"""
id: ID!
name: String!
description: String!
externalUrl: String! # Arbitrary URL which is linked to metadata
animationUrl: String # Arbitrary URL which is linked to metadata
animationMetadata: AnimationMetadata
licenseUrl: String! # Arbitrary URL which is linked to the rNFT license
condition: String
schemaUrl: String! # JSON schema URL
"MetadataType.BUNDLE"
type: MetadataType!
image: String!
attributes: [MetadataAttribute!]
"""
References to entities
"""
offer: Offer!
seller: Seller!
exchangeToken: ExchangeToken!
"""
Enriched fields from offer entity to allow nested query workaround
"""
createdAt: BigInt!
voided: Boolean!
validFromDate: BigInt!
validUntilDate: BigInt!
quantityAvailable: BigInt!
numberOfCommits: BigInt!
numberOfRedemptions: BigInt!
"""
BUNDLE specific fields
"""
productV1Seller: ProductV1Seller!
items: [ItemMetadataInterface!]! @derivedFrom(field: "bundle")
}

type ProductV1ItemMetadataEntity implements ItemMetadataInterface @entity {
id: ID!
schemaUrl: String! # JSON schema URL
type: ItemMetadataType!
uuid: String! # Item unique ID
productUuid: String! # Product unique ID (unique for all variants of the same product)
productVersion: Int! # Product version
product: ProductV1Product!
variations: [ProductV1Variation!]
productV1Seller: ProductV1Seller!
shipping: ProductV1ShippingOption
exchangePolicy: ProductV1ExchangePolicy!
productOverrides: ProductV1ProductOverrides
bundle: BundleMetadataEntity!
}

type NftItemMetadataEntity implements ItemMetadataInterface @entity {
id: ID!
schemaUrl: String! # JSON schema URL
type: ItemMetadataType!
name: String!
description: String
image: String
externalUrl: String
animationUrl: String
chainId: Int
contract: String
tokenId: String
quantity: Int
attributes: [MetadataAttribute!]
bundle: BundleMetadataEntity!
}

type ProductV1Product @entity {
id: ID!
uuid: String!
Expand Down
136 changes: 136 additions & 0 deletions packages/subgraph/src/entities/metadata/bundle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { JSONValue, TypedMap, BigInt, log } from "@graphprotocol/graph-ts";
import { BundleMetadataEntity, Offer } from "../../../../generated/schema";
import {
convertToString,
convertToObject,
convertToObjectArray
} from "../../../utils/json";
import { saveAnimationMetadata } from "../animationMetadata";
import { saveProductV1Seller } from "../product-v1/seller";
import { getMetadataEntityId, saveMetadataAttributes } from "../utils";
import { getIpfsMetadataObject, parseIpfsHash } from "../../../utils/ipfs";
import { saveNftItemMetadata } from "../nft-item";
import { saveProductV1ItemMetadata } from "../product-v1";

export function saveBundleMetadata(
offer: Offer,
metadataObj: TypedMap<string, JSONValue>,
// eslint-disable-next-line @typescript-eslint/ban-types
timestamp: BigInt
): string {
const offerId = offer.id.toString();
const metadataId = getMetadataEntityId(offerId);

const name = convertToString(metadataObj.get("name"));
const description = convertToString(metadataObj.get("description"));
const externalUrl = convertToString(metadataObj.get("externalUrl"));
const animationUrl = convertToString(metadataObj.get("animationUrl"));
const animationMetadata = convertToObject(
metadataObj.get("animationMetadata")
);
const licenseUrl = convertToString(metadataObj.get("licenseUrl"));
const condition = convertToString(metadataObj.get("condition"));
const schemaUrl = convertToString(metadataObj.get("schemaUrl"));
const image = convertToString(metadataObj.get("image"));
const attributes = convertToObjectArray(metadataObj.get("attributes"));
const items = convertToObjectArray(metadataObj.get("items"));

const savedAnimationMetadataId = saveAnimationMetadata(animationMetadata);
const savedMetadataAttributeIds = saveMetadataAttributes(attributes);
const savedProductV1SellerId = saveProductV1Seller(
convertToObject(metadataObj.get("seller")),
offer.sellerId.toString()
);

if (savedProductV1SellerId === null) {
return metadataId;
}

let bundleMetadataEntity = BundleMetadataEntity.load(metadataId);

if (!bundleMetadataEntity) {
bundleMetadataEntity = new BundleMetadataEntity(metadataId);
}

bundleMetadataEntity.name = name;
bundleMetadataEntity.description = description;
bundleMetadataEntity.externalUrl = externalUrl;
bundleMetadataEntity.animationUrl = animationUrl;
bundleMetadataEntity.animationMetadata = savedAnimationMetadataId;
bundleMetadataEntity.licenseUrl = licenseUrl;
bundleMetadataEntity.schemaUrl = schemaUrl;
bundleMetadataEntity.condition = condition;
bundleMetadataEntity.type = "BUNDLE";
bundleMetadataEntity.image = image;
bundleMetadataEntity.attributes = savedMetadataAttributeIds;

bundleMetadataEntity.offer = offerId;
bundleMetadataEntity.seller = offer.sellerId.toString();
bundleMetadataEntity.exchangeToken = offer.exchangeToken;

bundleMetadataEntity.createdAt = timestamp;
bundleMetadataEntity.voided = offer.voided;
bundleMetadataEntity.validFromDate = offer.validFromDate;
bundleMetadataEntity.validUntilDate = offer.validUntilDate;
bundleMetadataEntity.quantityAvailable = offer.quantityAvailable;
bundleMetadataEntity.numberOfCommits = offer.numberOfCommits;
bundleMetadataEntity.numberOfRedemptions = offer.numberOfRedemptions;

bundleMetadataEntity.productV1Seller = savedProductV1SellerId;

bundleMetadataEntity.save();
for (let i = 0; i < items.length; i++) {
const itemObj = items[i];
const itemMetadataUri = convertToString(itemObj.get("url"));
saveItemMetadata(
itemMetadataUri,
offer,
i.toString(),
metadataId,
savedProductV1SellerId
);
}
return metadataId;
}

function saveItemMetadata(
itemMetadataUri: string,
offer: Offer,
index: string,
bundleId: string,
productV1SellerId: string
): string | null {
const ipfsHash = parseIpfsHash(itemMetadataUri);

if (ipfsHash === null) {
log.warning("Metadata URI does not contain supported CID: {}", [
itemMetadataUri
]);
return null;
}

const metadataObj = getIpfsMetadataObject(ipfsHash);

if (metadataObj === null) {
log.warning("Could not load metadata with ipfsHash: {}", [ipfsHash]);
return null;
}

const metadataType = convertToString(metadataObj.get("type"));

if (metadataType == "ITEM_PRODUCT_V1") {
return saveProductV1ItemMetadata(
offer,
metadataObj,
index,
bundleId,
productV1SellerId
);
}

if (metadataType == "ITEM_NFT") {
return saveNftItemMetadata(offer, metadataObj, index, bundleId);
}

return null;
}
5 changes: 5 additions & 0 deletions packages/subgraph/src/entities/metadata/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getIpfsMetadataObject, parseIpfsHash } from "../../utils/ipfs";
import { convertToString } from "../../utils/json";
import { saveInnerSellerMetadata } from "./seller";
import { saveInnerNftContractMetadata } from "./nft-contract";
import { saveBundleMetadata } from "./bundle";

// eslint-disable-next-line @typescript-eslint/ban-types
export function saveMetadata(offer: Offer, timestamp: BigInt): string | null {
Expand Down Expand Up @@ -38,6 +39,10 @@ export function saveMetadata(offer: Offer, timestamp: BigInt): string | null {
return saveProductV1Metadata(offer, metadataObj, timestamp);
}

if (metadataType == "BUNDLE") {
return saveBundleMetadata(offer, metadataObj, timestamp);
}

return null;
}

Expand Down
54 changes: 54 additions & 0 deletions packages/subgraph/src/entities/metadata/nft-item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { JSONValue, TypedMap } from "@graphprotocol/graph-ts";
import { getItemMetadataEntityId, saveMetadataAttributes } from "../utils";
import {
convertToInt,
convertToObjectArray,
convertToString
} from "../../../utils/json";
import { NftItemMetadataEntity, Offer } from "../../../../generated/schema";

export function saveNftItemMetadata(
offer: Offer,
metadataObj: TypedMap<string, JSONValue>,
index: string,
bundleId: string
): string {
const offerId = offer.id.toString();
const metadataId = getItemMetadataEntityId(offerId, index);

const schemaUrl = convertToString(metadataObj.get("schemaUrl"));
const name = convertToString(metadataObj.get("name"));
const description = convertToString(metadataObj.get("description"));
const image = convertToString(metadataObj.get("image"));
const externalUrl = convertToString(metadataObj.get("externalUrl"));
const animationUrl = convertToString(metadataObj.get("animationUrl"));
const chainId = convertToInt(metadataObj.get("chainId"));
const contract = convertToString(metadataObj.get("contract"));
const tokenId = convertToString(metadataObj.get("tokenId"));
const quantity = convertToInt(metadataObj.get("quantity"));

const attributes = convertToObjectArray(metadataObj.get("attributes"));
const savedMetadataAttributeIds = saveMetadataAttributes(attributes);

let nftItemMetadataEntity = NftItemMetadataEntity.load(metadataId);
if (!nftItemMetadataEntity) {
nftItemMetadataEntity = new NftItemMetadataEntity(metadataId);
}

nftItemMetadataEntity.type = "ITEM_NFT";
nftItemMetadataEntity.schemaUrl = schemaUrl;
nftItemMetadataEntity.name = name;
nftItemMetadataEntity.description = description;
nftItemMetadataEntity.image = image;
nftItemMetadataEntity.externalUrl = externalUrl;
nftItemMetadataEntity.animationUrl = animationUrl;
nftItemMetadataEntity.chainId = chainId;
nftItemMetadataEntity.contract = contract;
nftItemMetadataEntity.tokenId = tokenId;
nftItemMetadataEntity.quantity = quantity;
nftItemMetadataEntity.attributes = savedMetadataAttributeIds;
nftItemMetadataEntity.bundle = bundleId;

nftItemMetadataEntity.save();
return metadataId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ export function saveProductV1ExchangePolicy(
const version = convertToInt(exchangePolicyObj.get("version"));
const label = convertToString(exchangePolicyObj.get("label"));
const template = convertToString(exchangePolicyObj.get("template"));
const sellerContactMethod = convertToString(exchangePolicyObj.get("sellerContactMethod"));
const disputeResolverContactMethod = convertToString(exchangePolicyObj.get("disputeResolverContactMethod"));
const sellerContactMethod = convertToString(
exchangePolicyObj.get("sellerContactMethod")
);
const disputeResolverContactMethod = convertToString(
exchangePolicyObj.get("disputeResolverContactMethod")
);

const exchangePolicyId = getExchangePolicyId(productV1MetadataEntityId);

Expand Down
Loading

0 comments on commit 86d2fd2

Please sign in to comment.