import type { AssetType, CollectionId, ItemId, Order, TezosFTAssetType, TezosMTAssetType, TezosNFTAssetType, TezosXTZAssetType, UnionAddress, } from "@rarible/api-client" import { Blockchain, CollectionType } from "@rarible/api-client" import type { Asset as TezosLibAsset, AssetType as TezosAssetType, Config, Provider, TezosNetwork, TezosProvider, } from "@rarible/tezos-sdk" // eslint-disable-next-line camelcase import { AssetTypeV2, get_public_key, pk_to_pkh } from "@rarible/tezos-sdk" import type { Part } from "@rarible/tezos-common" // eslint-disable-next-line camelcase import { get_ft_type } from "@rarible/tezos-common" import BigNumber from "bignumber.js" import type { Asset as TezosClientAsset, AssetType as TezosClientAssetType } from "tezos-api-client/build" import { Configuration, NftCollectionControllerApi, NftItemControllerApi, NftOwnershipControllerApi, OrderControllerApi, } from "tezos-api-client/build" import type { Maybe } from "@rarible/types/build/maybe" import type { ContractAddress, OrderId } from "@rarible/types" import { toCollectionId, toContractAddress, toItemId, toOrderId, toUnionAddress } from "@rarible/types" import type { BigNumber as RaribleBigNumber } from "@rarible/types/build/big-number" import { toBigNumber as toRaribleBigNumber } from "@rarible/types/build/big-number" // import type { Part as TezosPart } from "@rarible/tezos-sdk/dist/order/utils" import type { OrderForm } from "@rarible/tezos-sdk/dist/order" import type { Payout } from "@rarible/api-client/build/models/Payout" import axios from "axios" import type { UnionPart } from "../../../types/order/common" import type { CurrencyType } from "../../../common/domain" import type { RaribleSdkConfig } from "../../../config/domain" export interface ITezosAPI { collection: NftCollectionControllerApi, item: NftItemControllerApi, ownership: NftOwnershipControllerApi, order: OrderControllerApi, } export type MaybeProvider

= { tezos: Maybe

config: Config } export type PreparedOrder = OrderForm & { makeStock: RaribleBigNumber } export type TezosMetadataResponse = { name: string description?: string artifactUri?: string decimals: number displayUri?: string externalUri?: string formats?: Array attributes: Array } export type TezosMetaContent = { uri: string hash?: string mimeType?: string fileSize?: number fileName?: string duration?: string dimensions?: { value: string unit: string } dataRate?: { value: number unit: string } } export type TezosMetaAttribute = { name: string value?: string type?: string } export const XTZ_DECIMALS = 6 export function getTezosAPIs(network: TezosNetwork): ITezosAPI { const config = new Configuration({ basePath: getTezosBasePath(network), }) return { collection: new NftCollectionControllerApi(config), item: new NftItemControllerApi(config), ownership: new NftOwnershipControllerApi(config), order: new OrderControllerApi(config), } } export function getTezosBasePath(network: TezosNetwork): string { switch (network) { case "testnet": { return "https://test-tezos-api.rarible.org" } case "dev": { return "http://dev-tezos-api.rarible.int" } case "mainnet": { return "https://tezos-api.rarible.org" } default: { throw new Error("Unsupported tezos network") } } } export function isExistedTezosProvider(provider: MaybeProvider): provider is Provider { return provider.tezos !== undefined } export function getMaybeTezosProvider( provider: Maybe, network: TezosNetwork, config: RaribleSdkConfig ): MaybeProvider { const unionApiBaseUrl = `${config.basePath}/v0.1` switch (network) { case "testnet": { return { tezos: provider, config: { exchange: "KT1S6H2FWxrpaD7aPRSW1cTTE1xPucXBSTL5", transfer_proxy: "KT1WbVjXdmBpzzVoYSSUiNt6QFnSC3W768d1", fees: new BigNumber(0), nft_public: "", mt_public: "", auction: "KT1CB5JBSC7kTxRV3ir2xsooMA1FLieiD4Mt", auction_storage: "KT1KWAPPjuDq4ZeX67rzZWsf6eAeqwtuAfSP", node_url: "https://rpc.tzkt.io/ithacanet", chain_id: "NetXnHfVqm9iesp", sales: "KT1NcKyhPnomH9PKGeDfvMiGH2PDgKCd5YuM", sales_storage: "KT1GDUG3AQpaKmFjFHVn6PYT4Tprf7ccwPa3", transfer_manager: "KT1LQPAi4w2h9GQ61S8NkENcNe3aH5vYEzjP", bid: "KT1MwKGYWWbXtfYdnQfwspwz5ZGfqGwiJuQF", bid_storage: "KT1ENB6j6uMJn7MtDV4VBE1AAAwCXmMtzjUd", sig_checker: "KT1Fbvkq4sMawS4rdNXswoN7ELgkNV1ooLB7", tzkt: "https://api.ghostnet.tzkt.io", dipdup: "https://testnet-tezos-indexer.rarible.org/v1/graphql", union_api: unionApiBaseUrl, objkt_sales_v1: "KT1Ax5fm2UNxjXGmrMDytREfqvYoCXoBB4Jo", objkt_sales_v2: "KT1GiZuR6TdkgxZGQGZSdbC3Jox9JTSbqTB6", royalties_provider: "KT1F68vtdE2HHhZa3jBNT1kCkMjaQAWCShXB", hen_marketplace: "KT1XYgjgFQutFfgEiD7RuppSKZsawZbkpKxL", hen_objkts: "KT1P2VyFd61A3ukizJoX37nFF9fqZnihv7Lw", teia_marketplace: "KT1Anx515N2PK8A2ZX5uGNn7Gckh4WytLJmK", versum_marketplace: "KT1B1Wz7jPH23EqKUpDwFDkw3A1yLxGZ4uJy", versum_nfts: "KT1UH5RSbomuV1o6UuDB9yeACbqRMup3utGu", fxhash_sales_v1: "KT1BEc3m6yxN856Y4zfArpDqQ1uZZ1HkDTRh", fxhash_sales_v2: "KT1GCLoBSwUaNjaGXq5RtiP8CXTL3cEeMNDs", fxhash_nfts_v1: "KT1VEXkw6rw6pJDP9APGsMneFafArijmM96j", fxhash_nfts_v2: "KT1WSwXCWPPAxAy4ibPmFyCm4NhmSJT9UuxQ", }, } } case "dev": { return { tezos: provider, config: { exchange: "KT1S6H2FWxrpaD7aPRSW1cTTE1xPucXBSTL5", transfer_proxy: "KT1WbVjXdmBpzzVoYSSUiNt6QFnSC3W768d1", fees: new BigNumber(0), nft_public: "", mt_public: "", chain_id: "NetXnHfVqm9iesp", auction: "KT1CB5JBSC7kTxRV3ir2xsooMA1FLieiD4Mt", auction_storage: "KT1KWAPPjuDq4ZeX67rzZWsf6eAeqwtuAfSP", node_url: "https://rpc.tzkt.io/ghostnet", sales: "KT1NcKyhPnomH9PKGeDfvMiGH2PDgKCd5YuM", sales_storage: "KT1GDUG3AQpaKmFjFHVn6PYT4Tprf7ccwPa3", transfer_manager: "KT1LQPAi4w2h9GQ61S8NkENcNe3aH5vYEzjP", bid: "KT1MwKGYWWbXtfYdnQfwspwz5ZGfqGwiJuQF", bid_storage: "KT1ENB6j6uMJn7MtDV4VBE1AAAwCXmMtzjUd", sig_checker: "KT1Fbvkq4sMawS4rdNXswoN7ELgkNV1ooLB7", tzkt: "https://api.ghostnet.tzkt.io", dipdup: "https://dev-tezos-indexer.rarible.org/v1/graphql", union_api: "https://dev-api.rarible.org/v0.1", objkt_sales_v1: "KT1Ax5fm2UNxjXGmrMDytREfqvYoCXoBB4Jo", objkt_sales_v2: "KT1GiZuR6TdkgxZGQGZSdbC3Jox9JTSbqTB6", royalties_provider: "KT1F68vtdE2HHhZa3jBNT1kCkMjaQAWCShXB", hen_marketplace: "KT1XYgjgFQutFfgEiD7RuppSKZsawZbkpKxL", hen_objkts: "KT1P2VyFd61A3ukizJoX37nFF9fqZnihv7Lw", teia_marketplace: "KT1Anx515N2PK8A2ZX5uGNn7Gckh4WytLJmK", versum_marketplace: "KT1B1Wz7jPH23EqKUpDwFDkw3A1yLxGZ4uJy", versum_nfts: "KT1UH5RSbomuV1o6UuDB9yeACbqRMup3utGu", fxhash_sales_v1: "KT1BEc3m6yxN856Y4zfArpDqQ1uZZ1HkDTRh", fxhash_sales_v2: "KT1GCLoBSwUaNjaGXq5RtiP8CXTL3cEeMNDs", fxhash_nfts_v1: "KT1VEXkw6rw6pJDP9APGsMneFafArijmM96j", fxhash_nfts_v2: "KT1WSwXCWPPAxAy4ibPmFyCm4NhmSJT9UuxQ", }, } } case "mainnet": { return { tezos: provider, config: { exchange: "KT198mqFKkiWerXLmMCw69YB1i6yzYtmGVrC", transfer_proxy: "KT1N2oby9tYmv5tjkGD1KyVzkDRCmgDkXgSD", fees: new BigNumber(0), nft_public: "", mt_public: "", auction: "", auction_storage: "", node_url: "https://rpc.tzkt.io/mainnet", chain_id: "NetXdQprcVkpaWU", sales: "KT1N4Rrm6BU6229drs6scrH3vard1pPngMyA", sales_storage: "KT1BEZNm3E25rZtXfPPKr5Jxygbi2kL2cCEW", transfer_manager: "KT1ViAbsAM5rp89yVydEkbQozp1S12zqirwS", bid: "", bid_storage: "", sig_checker: "KT1VAmfDTkcYKMZZQhwuxtCGoD1hx7v5bjZ9", tzkt: "https://api.mainnet.tzkt.io", dipdup: "https://tezos-indexer.rarible.org/v1/graphql", union_api: unionApiBaseUrl, objkt_sales_v2: "KT1WvzYHCNBvDSdwafTHv7nJ1dWmZ8GCYuuC", objkt_sales_v1: "KT1FvqJwEDWb1Gwc55Jd1jjTHRVWbYKUUpyq", royalties_provider: "KT1HNNrmCk1fpqveRDz8Fvww2GM4gPzmA7fo", hen_marketplace: "KT1HbQepzV1nVGg8QVznG7z4RcHseD5kwqBn", hen_objkts: "KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton", teia_marketplace: "KT1PHubm9HtyQEJ4BBpMTVomq6mhbfNZ9z5w", versum_marketplace: "KT1GyRAJNdizF1nojQz62uGYkx8WFRUJm9X5", versum_nfts: "KT1LjmAdYQCLBjwv4S2oFkEzyHVkomAf5MrW", fxhash_sales_v1: "KT1Xo5B7PNBAeynZPmca4bRh6LQow4og1Zb9", fxhash_sales_v2: "KT1GbyoDi7H1sfXmimXpptZJuCdHMh66WS9u", fxhash_nfts_v1: "KT1KEa8z6vWXDJrVqtMrAeDVzsvxat3kHaCE", fxhash_nfts_v2: "KT1U6EHmNxJTkvaWJ4ThczG4FSDaHC21ssvi", }, } } default: { throw new Error("Unsupported tezos network for config") } } } const checkChainIdCache: Map = new Map() export async function checkChainId(provider: MaybeProvider) { let walletChainId = checkChainIdCache.get(provider.tezos!) if (!walletChainId) { walletChainId = await provider.tezos?.chain_id() checkChainIdCache.set(provider.tezos!, walletChainId!) } if (walletChainId !== provider.config.chain_id) { throw new Error(`Config chainId=${provider.config.chain_id}, but wallet chainId=${walletChainId}`) } } export function getRequiredProvider(provider: MaybeProvider): Provider { if (!isExistedTezosProvider(provider)) { throw new Error("Tezos provider is required") } return provider } export function getTezosOrderId(orderId: OrderId): string { if (!orderId) { throw new Error("OrderId has not been specified") } const [blockchain, id] = orderId.split(":") if (blockchain !== Blockchain.TEZOS) { throw new Error("Not an TEZOS order") } return id } export function getTezosItemData(itemId: ItemId) { const [domain, contract, tokenId] = itemId.split(":") if (domain !== Blockchain.TEZOS) { throw new Error(`Not an tezos item: ${itemId}`) } return { itemId: `${contract}:${tokenId}`, contract, tokenId, domain, } } export function getTezosAddress(address: UnionAddress): string { const [blockchain, tezosAddress] = address.split(":") if (blockchain !== Blockchain.TEZOS) { throw new Error(`Not an tezos item: ${address}`) } return tezosAddress } export async function getMakerPublicKey(provider: Provider): Promise { const maker = await get_public_key(provider) if (!maker) { throw new Error("Maker does not exist") } return maker } export async function getPayouts(provider: Provider, requestPayouts?: UnionPart[]): Promise { let payouts = requestPayouts || [] if (!Array.isArray(payouts) || payouts.length === 0) { return [{ account: pk_to_pkh(await getMakerPublicKey(provider)), value: new BigNumber(10000), }] } return convertUnionParts(payouts) } export function getSupportedCurrencies(): CurrencyType[] { return [ { blockchain: Blockchain.TEZOS, type: "NATIVE" }, { blockchain: Blockchain.TEZOS, type: "TEZOS_FT" }, ] } export function convertOrderToFillOrder(order: Order): PreparedOrder { return { ...convertOrderToOrderForm(order), makeStock: toRaribleBigNumber(order.makeStock), } } export function convertOrderToOrderForm(order: Order): OrderForm { if (order.data["@type"] !== "TEZOS_RARIBLE_V2") { throw new Error("Unsupported order data type") } return { type: "RARIBLE_V2", maker: order.maker, maker_edpk: order.data.makerEdpk!, taker: order.taker, taker_edpk: order.data.takerEdpk, make: { asset_type: getTezosAssetType(order.make.type), value: new BigNumber(order.make.value), }, take: { asset_type: getTezosAssetType(order.take.type), value: new BigNumber(order.take.value), }, salt: order.salt, start: order.startedAt ? parseInt(order.startedAt) : undefined, end: order.endedAt ? parseInt(order.endedAt) : undefined, signature: order.signature, data: { data_type: "V1", payouts: convertUnionParts(order.data.payouts), origin_fees: convertUnionParts(order.data.originFees), }, } } export function getTezosAssetType(type: AssetType): TezosAssetType { switch (type["@type"]) { case "XTZ": { return { asset_class: "XTZ", } } case "TEZOS_FT": { return { asset_class: "FT", contract: convertFromContractAddress(type.contract), token_id: type.tokenId ? new BigNumber(type.tokenId) : undefined, } } case "TEZOS_NFT": { return { asset_class: "NFT", contract: convertFromContractAddress(type.contract), token_id: new BigNumber(type.tokenId), } } case "TEZOS_MT": { return { asset_class: "MT", contract: convertFromContractAddress(type.contract), token_id: new BigNumber(type.tokenId), } } default: { throw new Error("Invalid take asset type") } } } export function covertToLibAsset(a: TezosClientAsset): TezosLibAsset { switch (a.assetType.assetClass) { case "XTZ": { return { asset_type: { asset_class: a.assetType.assetClass }, value: new BigNumber(a.value), } } case "FT": { return { asset_type: { asset_class: a.assetType.assetClass, contract: a.assetType.contract, token_id: (a.assetType.tokenId === undefined) ? undefined : new BigNumber(a.assetType.tokenId), }, value: new BigNumber(a.value), } } case "NFT": case "MT": return { asset_type: { asset_class: a.assetType.assetClass, contract: a.assetType.contract, token_id: new BigNumber(a.assetType.tokenId), }, value: new BigNumber(a.value), } default: throw new Error("Unknown Asset Class") } } export function convertTezosToUnionAsset(assetType: TezosClientAssetType): AssetType { switch (assetType.assetClass) { case "XTZ": { return { "@type": "XTZ" } } case "FT": { return { "@type": "TEZOS_FT", contract: convertTezosToContractAddress(assetType.contract), tokenId: assetType.tokenId ? toRaribleBigNumber(assetType.tokenId) : undefined, } } case "NFT": { return { "@type": "TEZOS_NFT", contract: convertTezosToContractAddress(assetType.contract), tokenId: toRaribleBigNumber(assetType.tokenId), } } case "MT": { return { "@type": "TEZOS_MT", contract: convertTezosToContractAddress(assetType.contract), tokenId: toRaribleBigNumber(assetType.tokenId), } } default: { throw new Error("Invalid asset type") } } } export function getCollectionTypeAssetClass(type: CollectionType.TEZOS_NFT | CollectionType.TEZOS_MT): "MT" | "NFT" { switch (type) { case CollectionType.TEZOS_MT: return "MT" case CollectionType.TEZOS_NFT: return "NFT" default: throw new Error("Unrecognized NFT collection type") } } export function convertUnionParts(parts?: Array): Array { return parts?.map(p => ({ account: getTezosAddress(p.account), value: new BigNumber(p.value), })) || [] } export function convertFromContractAddress(contract: ContractAddress): string { const [blockchain, tezosAddress] = contract.split(":") if (blockchain !== Blockchain.TEZOS) { throw new Error(`Not a tezos contract address: ${contract}`) } return tezosAddress } export function convertUnionAddress(address: UnionAddress): string { const [blockchain, tezosAddress] = address.split(":") if (blockchain !== Blockchain.TEZOS) { throw new Error(`Not a tezos address: ${address}`) } return tezosAddress } export function convertTezosOrderId(hash: string): OrderId { return toOrderId(`${Blockchain.TEZOS}:${hash}`) } export function convertTezosItemId(itemId: string): ItemId { return toItemId(`${Blockchain.TEZOS}:${itemId}`) } export function convertTezosToContractAddress(address: string): ContractAddress { return toContractAddress(`${Blockchain.TEZOS}:${address}`) } export function convertTezosToCollectionAddress(address: string): CollectionId { return toCollectionId(`${Blockchain.TEZOS}:${address}`) } export function convertTezosToUnionAddress(address: string): UnionAddress { return toUnionAddress(`${Blockchain.TEZOS}:${address}`) } export type CurrencyV2 = { type: AssetTypeV2 // eslint-disable-next-line camelcase asset_contract: string | undefined // eslint-disable-next-line camelcase asset_token_id: BigNumber | undefined } export async function getTezosAssetTypeV2(config: Config, type: AssetType): Promise { switch (type["@type"]) { case "XTZ": { return { type: AssetTypeV2.XTZ, asset_contract: undefined, asset_token_id: undefined, } } case "TEZOS_FT": { const contract = convertFromContractAddress(type.contract) let ftType: AssetTypeV2 | undefined = AssetTypeV2.FA2 try { ftType = await get_ft_type(config, contract) } catch (e) { console.log("error get_ft_type", e, contract) } if (ftType === AssetTypeV2.FA2) { return { type: AssetTypeV2.FA2, asset_contract: contract, asset_token_id: new BigNumber(type.tokenId || 0), } } else if (ftType === AssetTypeV2.FA12) { return { type: AssetTypeV2.FA12, asset_contract: contract, asset_token_id: undefined, } } else { throw new Error("Unrecognized FT contract type, check contract and network") } } default: { throw new Error("Invalid asset type") } } } export function getTokenIdString(tokenId: BigNumber | string | undefined): string | undefined { return tokenId !== undefined ? tokenId.toString(): undefined } export function isNftAssetType(assetType: AssetType): assetType is TezosNFTAssetType { return assetType["@type"] === "TEZOS_NFT" } export function isMTAssetType(assetType: AssetType): assetType is TezosMTAssetType { return assetType["@type"] === "TEZOS_MT" } export function isXtzAssetType(assetType: AssetType): assetType is TezosXTZAssetType { return assetType["@type"] === "XTZ" } export function isFTAssetType(assetType: AssetType): assetType is TezosFTAssetType { return assetType["@type"] === "TEZOS_FT" } export async function getCollectionType( provider: MaybeProvider, collection: string ): Promise { let response try { const { data } = await axios.get(`${provider.config.tzkt}/v1/contracts/${collection}/storage/schema`) response = data } catch (e) { console.error(e) throw new Error("Getting tezos collection data error") } const schema = response["schema:object"] if ("ledger:big_map:object:nat" in schema) { return CollectionType.TEZOS_MT } else if ("ledger:big_map_flat:nat:address" in schema) { return CollectionType.TEZOS_NFT } else { throw new Error("Unrecognized tezos collection") } }