264 lines
7.1 KiB
TypeScript
264 lines
7.1 KiB
TypeScript
import Web3 from "web3";
|
|
import { abiERC1155 } from "../abis/abiERC1155";
|
|
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);
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Transfer single ERC1155 token.
|
|
* When minting/creating tokens, the from arg MUST be set to 0x0 (i.e. zero address).
|
|
* When burning/destroying tokens, the to arg MUST be set to 0x0 (i.e. zero address).
|
|
*
|
|
* @param operator - ERC1155 token address.
|
|
* @param from - ERC1155 token holder.
|
|
* @param to - ERC1155 token recipient.
|
|
* @param id - ERC1155 token id.
|
|
* @param value - Number of tokens to be sent.
|
|
* @returns Promise resolving to the 'transferSingle'.
|
|
*/
|
|
transferSingle = async (
|
|
operator: string,
|
|
from: string,
|
|
to: string,
|
|
id: string,
|
|
value: string
|
|
): Promise<void> => {
|
|
const contract = new this.web3.eth.Contract(abiERC1155, operator);
|
|
return new Promise<void>((resolve, reject) => {
|
|
contract.methods.transferSingle(
|
|
operator,
|
|
from,
|
|
to,
|
|
id,
|
|
value,
|
|
(error: Error, result: void) => {
|
|
/* 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);
|
|
}
|
|
return contract.methods
|
|
.safeBatchTransferFrom(from, to, tokenIds, amounts, [])
|
|
.send({
|
|
from,
|
|
gas,
|
|
});
|
|
}
|
|
}
|