aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts')
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts479
1 files changed, 479 insertions, 0 deletions
diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts
new file mode 100644
index 000000000..415415fd7
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts
@@ -0,0 +1,479 @@
+import { schemas } from '@0xproject/json-schemas';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { constants } from '../../test/utils/constants';
+import { artifacts } from '../artifacts';
+import { methodOptsSchema } from '../schemas/method_opts_schema';
+import { txOptsSchema } from '../schemas/tx_opts_schema';
+import {
+ BlockRange,
+ ContractWrappersError,
+ EventCallback,
+ IndexedFilterValues,
+ MethodOpts,
+ TransactionOpts,
+} from '../types';
+import { assert } from '../utils/assert';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ERC721ProxyWrapper } from './erc721_proxy_wrapper';
+import { ERC721TokenContract, ERC721TokenEventArgs, ERC721TokenEvents } from './generated/erc721_token';
+
+const removeUndefinedProperties = _.pickBy;
+
+/**
+ * This class includes all the functionality related to interacting with ERC721 token contracts.
+ * All ERC721 method calls are supported, along with some convenience methods for getting/setting allowances
+ * to the 0x ERC721 Proxy smart contract.
+ */
+export class ERC721TokenWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.ERC721Token.compilerOutput.abi;
+ private _tokenContractsByAddress: { [address: string]: ERC721TokenContract };
+ private _erc721ProxyWrapper: ERC721ProxyWrapper;
+ constructor(web3Wrapper: Web3Wrapper, networkId: number, erc721ProxyWrapper: ERC721ProxyWrapper) {
+ super(web3Wrapper, networkId);
+ this._tokenContractsByAddress = {};
+ this._erc721ProxyWrapper = erc721ProxyWrapper;
+ }
+ /**
+ * Count all NFTs assigned to an owner
+ * NFTs assigned to the zero address are considered invalid, and this function throws for queries about the zero address.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The number of NFTs owned by `ownerAddress`, possibly zero
+ */
+ public async getTokenCountAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ methodOpts?: MethodOpts,
+ ): Promise<BigNumber> {
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ let balance = await tokenContract.balanceOf.callAsync(normalizedOwnerAddress, txData, defaultBlock);
+ // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
+ balance = new BigNumber(balance);
+ return balance;
+ }
+ /**
+ * Find the owner of an NFT
+ * NFTs assigned to zero address are considered invalid, and queries about them do throw.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The address of the owner of the NFT
+ */
+ public async getOwnerOfAsync(tokenAddress: string, tokenId: BigNumber, methodOpts?: MethodOpts): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isBigNumber('tokenId', tokenId);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ try {
+ const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId, txData, defaultBlock);
+ return tokenOwner;
+ } catch (err) {
+ throw new Error(ContractWrappersError.ERC721OwnerNotFound);
+ }
+ }
+ /**
+ * Query if an address is an authorized operator for all NFT's of `ownerAddress`
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to check if approved.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return True if `operatorAddress` is an approved operator for `ownerAddress`, false otherwise
+ */
+ public async isApprovedForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ operatorAddress: string,
+ methodOpts?: MethodOpts,
+ ): Promise<boolean> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ assert.isETHAddressHex('operatorAddress', operatorAddress);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedOperatorAddress = operatorAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ const isApprovedForAll = await tokenContract.isApprovedForAll.callAsync(
+ normalizedOwnerAddress,
+ normalizedOperatorAddress,
+ txData,
+ defaultBlock,
+ );
+ return isApprovedForAll;
+ }
+ /**
+ * Query if 0x proxy is an authorized operator for all NFT's of `ownerAddress`
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param methodOpts Optional arguments this method accepts.
+ * @return True if `operatorAddress` is an approved operator for `ownerAddress`, false otherwise
+ */
+ public async isProxyApprovedForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ methodOpts?: MethodOpts,
+ ): Promise<boolean> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('ownerAddress', ownerAddress);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const isProxyApprovedForAll = await this.isApprovedForAllAsync(
+ normalizedTokenAddress,
+ normalizedOwnerAddress,
+ proxyAddress,
+ methodOpts,
+ );
+ return isProxyApprovedForAll;
+ }
+ /**
+ * Get the approved address for a single NFT. Returns undefined if no approval was set
+ * Throws if `_tokenId` is not a valid NFT
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param methodOpts Optional arguments this method accepts.
+ * @return The approved address for this NFT, or the undefined if there is none
+ */
+ public async getApprovedIfExistsAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ methodOpts?: MethodOpts,
+ ): Promise<string | undefined> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isBigNumber('tokenId', tokenId);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
+ const txData = {};
+ const approvedAddress = await tokenContract.getApproved.callAsync(tokenId, txData, defaultBlock);
+ if (approvedAddress === constants.NULL_ADDRESS) {
+ return undefined;
+ }
+ return approvedAddress;
+ }
+ /**
+ * Checks if 0x proxy is approved for a single NFT
+ * Throws if `_tokenId` is not a valid NFT
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param methodOpts Optional arguments this method accepts.
+ * @return True if 0x proxy is approved
+ */
+ public async isProxyApprovedAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ methodOpts?: MethodOpts,
+ ): Promise<boolean> {
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const approvedAddress = await this.getApprovedIfExistsAsync(tokenAddress, tokenId, methodOpts);
+ const isProxyApproved = approvedAddress === proxyAddress;
+ return isProxyApproved;
+ }
+ /**
+ * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the ApprovalForAll event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to set approval for.
+ * @param isApproved The boolean variable to set the approval to.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setApprovalForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ operatorAddress: string,
+ isApproved: boolean,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
+ assert.isETHAddressHex('operatorAddress', operatorAddress);
+ assert.isBoolean('isApproved', isApproved);
+ if (!_.isUndefined(txOpts)) {
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedOwnerAddress = ownerAddress.toLowerCase();
+ const normalizedOperatorAddress = operatorAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const txHash = await tokenContract.setApprovalForAll.sendTransactionAsync(
+ normalizedOperatorAddress,
+ isApproved,
+ removeUndefinedProperties({
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ from: normalizedOwnerAddress,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the ApprovalForAll event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param ownerAddress The hex encoded user Ethereum address of the token owner.
+ * @param operatorAddress The hex encoded user Ethereum address of the operator you'd like to set approval for.
+ * @param isApproved The boolean variable to set the approval to.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setProxyApprovalForAllAsync(
+ tokenAddress: string,
+ ownerAddress: string,
+ isApproved: boolean,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const txHash = await this.setApprovalForAllAsync(tokenAddress, ownerAddress, proxyAddress, isApproved, txOpts);
+ return txHash;
+ }
+ /**
+ * Set or reaffirm the approved address for an NFT
+ * The zero address indicates there is no approved address. Throws unless `msg.sender` is the current NFT owner,
+ * or an authorized operator of the current owner.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the Approval event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param approvedAddress The hex encoded user Ethereum address you'd like to set approval for.
+ * @param tokenId The identifier for an NFT
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setApprovalAsync(
+ tokenAddress: string,
+ approvedAddress: string,
+ tokenId: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('approvedAddress', approvedAddress);
+ assert.isBigNumber('tokenId', tokenId);
+ if (!_.isUndefined(txOpts)) {
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedApprovedAddress = approvedAddress.toLowerCase();
+
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const tokenOwnerAddress = await tokenContract.ownerOf.callAsync(tokenId);
+ await assert.isSenderAddressAsync('tokenOwnerAddress', tokenOwnerAddress, this._web3Wrapper);
+ const txHash = await tokenContract.approve.sendTransactionAsync(
+ normalizedApprovedAddress,
+ tokenId,
+ removeUndefinedProperties({
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ from: tokenOwnerAddress,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Set or reaffirm 0x proxy as an approved address for an NFT
+ * Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the Approval event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param tokenId The identifier for an NFT
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async setProxyApprovalAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ const proxyAddress = this._erc721ProxyWrapper.getContractAddress();
+ const txHash = await this.setApprovalAsync(tokenAddress, proxyAddress, tokenId, txOpts);
+ return txHash;
+ }
+ /**
+ * Enable or disable approval for a third party ("operator") to manage all of `ownerAddress`'s assets.
+ * Throws if `_tokenId` is not a valid NFT
+ * Emits the ApprovalForAll event.
+ * @param tokenAddress The hex encoded contract Ethereum address where the ERC721 token is deployed.
+ * @param receiverAddress The hex encoded Ethereum address of the user to send the NFT to.
+ * @param senderAddress The hex encoded Ethereum address of the user to send the NFT to.
+ * @param tokenId The identifier for an NFT
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async transferFromAsync(
+ tokenAddress: string,
+ receiverAddress: string,
+ senderAddress: string,
+ tokenId: BigNumber,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ assert.isETHAddressHex('receiverAddress', receiverAddress);
+ await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
+ if (!_.isUndefined(txOpts)) {
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ }
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ const normalizedReceiverAddress = receiverAddress.toLowerCase();
+ const normalizedSenderAddress = senderAddress.toLowerCase();
+ const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress);
+ const ownerAddress = await this.getOwnerOfAsync(tokenAddress, tokenId);
+ const isApprovedForAll = await this.isApprovedForAllAsync(
+ normalizedTokenAddress,
+ ownerAddress,
+ normalizedSenderAddress,
+ );
+ if (!isApprovedForAll) {
+ const approvedAddress = await this.getApprovedIfExistsAsync(normalizedTokenAddress, tokenId);
+ if (approvedAddress !== senderAddress) {
+ throw new Error(ContractWrappersError.ERC721NoApproval);
+ }
+ }
+ const txHash = await tokenContract.transferFrom.sendTransactionAsync(
+ ownerAddress,
+ normalizedReceiverAddress,
+ tokenId,
+ removeUndefinedProperties({
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ from: normalizedSenderAddress,
+ }),
+ );
+ return txHash;
+ }
+ /**
+ * Subscribe to an event type emitted by the Token contract.
+ * @param tokenAddress The hex encoded address where the ERC721 token is deployed.
+ * @param eventName The token contract event you would like to subscribe to.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
+ * @param callback Callback that gets called when a log is added/removed
+ * @return Subscription token used later to unsubscribe
+ */
+ public subscribe<ArgsType extends ERC721TokenEventArgs>(
+ tokenAddress: string,
+ eventName: ERC721TokenEvents,
+ indexFilterValues: IndexedFilterValues,
+ callback: EventCallback<ArgsType>,
+ ): string {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, ERC721TokenEvents);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ assert.isFunction('callback', callback);
+ const subscriptionToken = this._subscribe<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ indexFilterValues,
+ artifacts.ERC721Token.compilerOutput.abi,
+ callback,
+ );
+ return subscriptionToken;
+ }
+ /**
+ * Cancel a subscription
+ * @param subscriptionToken Subscription token returned by `subscribe()`
+ */
+ public unsubscribe(subscriptionToken: string): void {
+ assert.isValidSubscriptionToken('subscriptionToken', subscriptionToken);
+ this._unsubscribe(subscriptionToken);
+ }
+ /**
+ * Cancels all existing subscriptions
+ */
+ public unsubscribeAll(): void {
+ super._unsubscribeAll();
+ }
+ /**
+ * Gets historical logs without creating a subscription
+ * @param tokenAddress An address of the token that emitted the logs.
+ * @param eventName The token contract event you would like to subscribe to.
+ * @param blockRange Block range to get logs from.
+ * @param indexFilterValues An object where the keys are indexed args returned by the event and
+ * the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
+ * @return Array of logs that match the parameters
+ */
+ public async getLogsAsync<ArgsType extends ERC721TokenEventArgs>(
+ tokenAddress: string,
+ eventName: ERC721TokenEvents,
+ blockRange: BlockRange,
+ indexFilterValues: IndexedFilterValues,
+ ): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
+ assert.isETHAddressHex('tokenAddress', tokenAddress);
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ assert.doesBelongToStringEnum('eventName', eventName, ERC721TokenEvents);
+ assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
+ assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
+ const logs = await this._getLogsAsync<ArgsType>(
+ normalizedTokenAddress,
+ eventName,
+ blockRange,
+ indexFilterValues,
+ artifacts.ERC721Token.compilerOutput.abi,
+ );
+ return logs;
+ }
+ // HACK: We don't want this method to be visible to the other units within that package but not to the end user.
+ // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstances(): void {
+ this.unsubscribeAll();
+ this._tokenContractsByAddress = {};
+ }
+ private async _getTokenContractAsync(tokenAddress: string): Promise<ERC721TokenContract> {
+ const normalizedTokenAddress = tokenAddress.toLowerCase();
+ let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress];
+ if (!_.isUndefined(tokenContract)) {
+ return tokenContract;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.ERC721Token,
+ normalizedTokenAddress,
+ );
+ const contractInstance = new ERC721TokenContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ tokenContract = contractInstance;
+ this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract;
+ return tokenContract;
+ }
+}