import { BigNumber, BigNumberish, ethers } from 'ethers'; import { Axelar } from './Axelar'; import { CollectionCreator } from './CollectionCreator'; import config from './config/config'; import { ChainId } from './config/constants'; import { FacetId } from './contracts/constants'; import { ERC721Native } from './contracts/ERC721Native'; import { OmniteAxelarBridgeSender } from './contracts/OmniteAxelarBridgeSender'; import { OmniteLayerZeroBridgeSender } from './contracts/OmniteLayerZeroBridgeSender'; import { ipfsFetchMetadataContent } from './external/pinataApiClient'; import { LayerZero } from './LayerZero'; import { BridgeBroker } from './types'; import { translateChainIdToAxelarChainId, translateChainIdToLayer0ChainId, } from './utils/chainUtils'; export class Collection { /** * Collection manager constructor * * @param {ethers.providers.JsonRpcSigner|undefined} _signer - ethers signer * @param {string} address - collection address */ constructor( private readonly _signer: ethers.providers.JsonRpcSigner | undefined, private readonly address: string ) {} get signer(): ethers.providers.JsonRpcSigner { if (!this._signer) { throw new Error('Signer is not defined'); } return this._signer; } /** * Mint token on the collection * * @param {{tokenId:string;photo:string;title:string;description:string;chainId:string;}} mintDetails * @param {string} metadataCID * @param {string} collectionId - optional, collection id, if not provided it'll be fetched automagically */ mint = async ( mintDetails: { tokenId: string; photo: string; title: string; description: string; chainId: string; }, metadataCID: string, collectionId?: string ) => { if (!collectionId) { collectionId = await ERC721Native.collectionId( mintDetails.chainId, this.address ); } if (!collectionId) { throw new Error('Collection does not exist'); } const tokenUri = metadataCID ? `ipfs://${metadataCID}` : ''; return await ( await ERC721Native.create(this.signer, this.address) ).mintToWithUri( await this.signer.getAddress(), mintDetails.tokenId, tokenUri ); }; mintOnTargetChain = async ( targetChainId: ChainId, broker: BridgeBroker, mintDetails: { tokenId: BigNumber; }, metadataCID: string, collectionId?: string ) => { const sourceChainId = '0x' + (await this.signer.getChainId()).toString(16); if (!collectionId) { collectionId = await ERC721Native.collectionId( sourceChainId, this.address ); } if (!collectionId) { throw new Error('Collection does not exist'); } const tokenUri = metadataCID ? `ipfs://${metadataCID}` : ''; const userAddress = await this.signer.getAddress(); const targetChainConfig = config.getChainConfig(targetChainId); const targetChainSystemData = config.getSystemData(targetChainId); switch (broker) { case BridgeBroker.Axelar: throw new Error('not implemented'); case BridgeBroker.LayerZero: const erc721Native = await ERC721Native.create( this.signer, this.address ); const payload = await OmniteLayerZeroBridgeSender.mintOnTargetChainEncode( targetChainId, collectionId, userAddress, mintDetails.tokenId, tokenUri ); const estimatedGasLimit = await LayerZero.estimateReceiveGas( sourceChainId, userAddress, targetChainId, payload ); const fee = await LayerZero.estimateFees( sourceChainId, targetChainId, this.address, payload, estimatedGasLimit ); return erc721Native.mintOnViaLayerZero( targetChainConfig.layerZeroChainId, targetChainSystemData.omniteLayerZeroBridgeReceiver, mintDetails.tokenId, userAddress, estimatedGasLimit, fee ); default: throw new Error('Unsupported broker ' + broker); } }; /** * Move token to another chain. Deployed collection contract on target chain is required. * * @param {ChainId} sourceChainId * @param {string} targetChainId * @param {string} collectionId * @param {BigNumberish} tokenId * @param {BridgeBroker} broker */ moveTo = async ( sourceChainId: ChainId, targetChainId: string, collectionId: string, tokenId: BigNumberish, broker: BridgeBroker ) => { const userAddress = await this.signer.getAddress(); const payload = broker === BridgeBroker.LayerZero ? await OmniteLayerZeroBridgeSender.mintOnTargetChainEncode( sourceChainId, collectionId, userAddress, tokenId, '' ) : await OmniteAxelarBridgeSender.mintOnTargetChainEncode( sourceChainId, collectionId, userAddress, tokenId, '' ); const estimatedGasLimit = broker === BridgeBroker.LayerZero ? await LayerZero.estimateReceiveGas( sourceChainId, userAddress, targetChainId, payload ) : await Axelar.estimateReceiveGas( sourceChainId, userAddress, targetChainId, payload ); const fee = broker === BridgeBroker.LayerZero ? await LayerZero.estimateFees( sourceChainId, targetChainId, this.address, payload, estimatedGasLimit ) : await Axelar.estimateFees( sourceChainId, targetChainId, estimatedGasLimit ); const collection = await ERC721Native.create(this.signer, this.address); if (broker === BridgeBroker.LayerZero) { return collection.moveToViaLayerZero( translateChainIdToLayer0ChainId(targetChainId), config.getSystemData(targetChainId) .omniteLayerZeroBridgeReceiver, tokenId, estimatedGasLimit, fee ); } else { return collection.moveToViaAxelar( translateChainIdToAxelarChainId(targetChainId), config.getSystemData(targetChainId).omniteAxelarBridgeReceiver, tokenId, fee ); } }; /** * Add diamond facet to the collection on the current chain * * @param {FacetId} facet */ addFacet = async (facet: FacetId) => { const erc721native = await ERC721Native.create( this.signer, this.address ); return erc721native.addFacet(facet); }; /** * Remove diamond facet from the collection on the current chain * * @param {FacetId} facet */ removeFacet = async (facet: FacetId) => { const erc721native = await ERC721Native.create( this.signer, this.address ); return erc721native.removeFacet(facet); }; static getName = async (chainId: string, address: string) => { // compatible interface so we can use ERC721Native here return ERC721Native.getName(chainId, address); }; static getTokenUri = async ( chainId: string, address: string, tokenId: string ) => { // compatible interface so we can use ERC721Native here return ERC721Native.tokenURI(chainId, address, tokenId); }; static getNativeCollectionDetails = async ( chainId: string, address: string ) => { const collectionName = await ERC721Native.getName(chainId, address); const collectionMetadata = await Collection.getCollectionMetadata( chainId, address ); if (!collectionMetadata) { return null; } return { collectionName, name: collectionMetadata.name || '', description: collectionMetadata.description || '', imageUrl: collectionMetadata.image || '', }; }; // TODO add cache (indexed db?) static getCollectionMetadata = async (chainId: string, address: string) => { let contractURI = await ERC721Native.contractURI(chainId, address); return ipfsFetchMetadataContent(contractURI); }; static getSlotRange = async (address: string, chainId: string) => { const { slotStart, slotEnd } = await ERC721Native.getSlots( chainId, address ); return [ BigNumber.from(slotStart).toNumber(), BigNumber.from(slotEnd).toNumber(), ]; }; }