244 lines
6.5 KiB
TypeScript
244 lines
6.5 KiB
TypeScript
import Web3 from "web3";
|
|
import { abiERC1155 } from "../abis/abiERC1155";
|
|
import { universalChainCb } from "../util/chain.util";
|
|
import { timeoutFetch } from "../util/net.util";
|
|
import { getFormattedIpfsUrl } from "../util/wallet.util";
|
|
|
|
export const ERC1155 = "ERC1155";
|
|
export const ERC1155_INTERFACE_ID = "0xd9b67a26";
|
|
export const ERC1155_METADATA_URI_INTERFACE_ID = "0x0e89341c";
|
|
export const ERC1155_TOKEN_RECEIVER_INTERFACE_ID = "0x4e2312e0";
|
|
|
|
export class ERC1155Standard {
|
|
private web3: Web3;
|
|
|
|
constructor(web3: Web3) {
|
|
this.web3 = web3;
|
|
}
|
|
|
|
/**
|
|
* Query if contract implements ERC1155 URI Metadata interface.
|
|
*
|
|
* @param address - ERC1155 asset contract address.
|
|
* @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface.
|
|
*/
|
|
contractSupportsURIMetadataInterface = async (
|
|
address: string
|
|
): Promise<boolean> => {
|
|
return this.contractSupportsInterface(
|
|
address,
|
|
ERC1155_METADATA_URI_INTERFACE_ID
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Query if contract implements ERC1155 Token Receiver interface.
|
|
*
|
|
* @param address - ERC1155 asset contract address.
|
|
* @returns Promise resolving to whether the contract implements ERC1155 Token Receiver interface.
|
|
*/
|
|
contractSupportsTokenReceiverInterface = async (
|
|
address: string
|
|
): Promise<boolean> => {
|
|
return this.contractSupportsInterface(
|
|
address,
|
|
ERC1155_TOKEN_RECEIVER_INTERFACE_ID
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Query if contract implements ERC1155 interface.
|
|
*
|
|
* @param address - ERC1155 asset contract address.
|
|
* @returns Promise resolving to whether the contract implements the base ERC1155 interface.
|
|
*/
|
|
contractSupportsBase1155Interface = async (
|
|
address: string
|
|
): Promise<boolean> => {
|
|
return this.contractSupportsInterface(address, ERC1155_INTERFACE_ID);
|
|
};
|
|
|
|
/**
|
|
* Query for tokenURI for a given asset.
|
|
*
|
|
* @param address - ERC1155 asset contract address.
|
|
* @param tokenId - ERC1155 asset identifier.
|
|
* @returns Promise resolving to the 'tokenURI'.
|
|
*/
|
|
getTokenURI = async (address: string, tokenId: string): Promise<string> => {
|
|
const contract = new this.web3.eth.Contract(abiERC1155, address);
|
|
return new Promise<string>((resolve, reject) => {
|
|
contract.methods
|
|
.tokenURI(tokenId)
|
|
.call((error: Error, result: string) => {
|
|
/* istanbul ignore if */
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
resolve(result);
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Query for balance of a given ERC1155 token.
|
|
*
|
|
* @param contractAddress - ERC1155 asset contract address.
|
|
* @param address - Wallet public address.
|
|
* @param tokenId - ERC1155 asset identifier.
|
|
* @returns Promise resolving to the 'balanceOf'.
|
|
*/
|
|
getBalanceOf = async (
|
|
contractAddress: string,
|
|
address: string,
|
|
tokenId: string
|
|
): Promise<number> => {
|
|
const contract = new this.web3.eth.Contract(abiERC1155, contractAddress);
|
|
return new Promise<number>((resolve, reject) => {
|
|
contract.methods.balanceOf(
|
|
address,
|
|
tokenId,
|
|
(error: Error, result: number) => {
|
|
/* istanbul ignore if */
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
resolve(result);
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Query if a contract implements an interface.
|
|
*
|
|
* @param address - ERC1155 asset contract address.
|
|
* @param interfaceId - Interface identifier.
|
|
* @returns Promise resolving to whether the contract implements `interfaceID`.
|
|
*/
|
|
private contractSupportsInterface = async (
|
|
address: string,
|
|
interfaceId: string
|
|
): Promise<boolean> => {
|
|
const contract = new this.web3.eth.Contract(abiERC1155, address);
|
|
return new Promise<boolean>((resolve, reject) => {
|
|
contract.methods.supportsInterface(
|
|
interfaceId,
|
|
(error: Error, result: boolean) => {
|
|
/* istanbul ignore if */
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
resolve(result);
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Query if a contract implements an interface.
|
|
*
|
|
* @param address - Asset contract address.
|
|
* @param ipfsGateway - The user's preferred IPFS gateway.
|
|
* @param tokenId - tokenId of a given token in the contract.
|
|
* @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair.
|
|
*/
|
|
getDetails = async (
|
|
address: string,
|
|
ipfsGateway: string,
|
|
tokenId?: string
|
|
): Promise<{
|
|
standard: string;
|
|
tokenURI: string | undefined;
|
|
image: string | undefined;
|
|
}> => {
|
|
const isERC1155 = await this.contractSupportsBase1155Interface(address);
|
|
|
|
if (!isERC1155) {
|
|
throw new Error("This isn't a valid ERC1155 contract");
|
|
}
|
|
let tokenURI, image;
|
|
|
|
if (tokenId) {
|
|
tokenURI = await this.getTokenURI(address, tokenId);
|
|
if (tokenURI.startsWith("ipfs://")) {
|
|
tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true);
|
|
}
|
|
|
|
try {
|
|
const response = await timeoutFetch(tokenURI);
|
|
const object = await response.json();
|
|
image = object?.image;
|
|
if (image?.startsWith("ipfs://")) {
|
|
image = getFormattedIpfsUrl(ipfsGateway, image, true);
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
// TODO consider querying to the metadata to get name.
|
|
return {
|
|
standard: ERC1155,
|
|
tokenURI,
|
|
image,
|
|
};
|
|
};
|
|
|
|
async transferBatch({
|
|
address,
|
|
from,
|
|
to,
|
|
tokenIds,
|
|
amounts,
|
|
gas,
|
|
estimate,
|
|
}: {
|
|
address: string;
|
|
from: string;
|
|
to: string;
|
|
tokenIds: string[];
|
|
amounts: string[];
|
|
gas?: number;
|
|
estimate: number;
|
|
}) {
|
|
const contract = new this.web3.eth.Contract(abiERC1155, address);
|
|
if (!gas) {
|
|
gas = await contract.methods
|
|
.safeBatchTransferFrom(from, to, tokenIds, amounts, [])
|
|
.estimateGas({ gas: 1000000 });
|
|
}
|
|
gas = (gas * 1.1) | 1;
|
|
if (estimate) {
|
|
return jc.wallet.generateGasShow(gas);
|
|
}
|
|
const detailArr = [];
|
|
for (let i = 0, l = tokenIds.length; i < l; i++) {
|
|
detailArr.push({
|
|
address,
|
|
from,
|
|
to,
|
|
id: tokenIds[i],
|
|
amount: amounts[i],
|
|
});
|
|
}
|
|
const logData = {
|
|
gas,
|
|
title: "transfer",
|
|
details: detailArr,
|
|
};
|
|
return universalChainCb(
|
|
logData,
|
|
contract.methods
|
|
.safeBatchTransferFrom(from, to, tokenIds, amounts, [])
|
|
.send({
|
|
from,
|
|
gas,
|
|
})
|
|
);
|
|
}
|
|
}
|