jcwallet/src/standards/ERC1155Standard.ts
2023-02-03 14:32:36 +08:00

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,
})
);
}
}