Skip to content
This repository was archived by the owner on Aug 30, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions etc/sdk.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,8 @@ export class Erc721<T extends Multiwrap_2 | SignatureDrop_2 | DropERC721 | Token
query: Erc721Supply | undefined;
// @internal
setApprovalForAll(operator: string, approved: boolean): Promise<TransactionResult>;
// @internal
setApprovalForToken(operator: string, tokenId: BigNumberish): Promise<TransactionResult>;
// (undocumented)
protected storage: IStorage;
transfer(to: string, tokenId: BigNumberish): Promise<TransactionResult>;
Expand Down
18 changes: 18 additions & 0 deletions src/common/currency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,24 @@ export async function approveErc20Allowance(
}
}

export async function hasERC20Allowance(
contractToApprove: ContractWrapper<any>,
currencyAddress: string,
value: BigNumber,
) {
const provider = contractToApprove.getProvider();
const erc20 = new ContractWrapper<IERC20>(
provider,
currencyAddress,
ERC20Abi,
{},
);
const owner = await contractToApprove.getSignerAddress();
const spender = contractToApprove.readContract.address;
const allowance = await erc20.readContract.allowance(owner, spender);
return allowance.gte(value);
}

export async function normalizeAmount(
contractWrapper: ContractWrapper<BaseERC20>,
amount: Amount,
Expand Down
26 changes: 14 additions & 12 deletions src/common/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,23 @@ import ERC721Abi from "../../abis/IERC721.json";
import ERC165Abi from "../../abis/IERC165.json";

/**
* This method checks if the given token is approved for the marketplace contract.
* This is particularly useful for direct listings where the token
* being listed may be moved before the listing is actually closed.
* This method checks if the given token is approved for the transferrerContractAddress contract.
* This is particularly useful for contracts that need to transfer NFTs on the users' behalf
*
* @internal
* @param provider - The connected provider
* @param marketplaceAddress - The address of the marketplace contract
* @param transferrerContractAddress - The address of the marketplace contract
* @param assetContract - The address of the asset contract.
* @param tokenId - The token id of the token.
* @param from - The address of the account that owns the token.
* @returns - True if the marketplace is approved on the token, false otherwise.
* @param owner - The address of the account that owns the token.
* @returns - True if the transferrerContractAddress is approved on the token, false otherwise.
*/
export async function isTokenApprovedForMarketplace(
export async function isTokenApprovedForTransfer(
provider: providers.Provider,
marketplaceAddress: string,
transferrerContractAddress: string,
assetContract: string,
tokenId: BigNumberish,
from: string,
owner: string,
): Promise<boolean> {
try {
const erc165 = new Contract(assetContract, ERC165Abi, provider) as IERC165;
Expand All @@ -45,21 +44,24 @@ export async function isTokenApprovedForMarketplace(
if (isERC721) {
const asset = new Contract(assetContract, ERC721Abi, provider) as IERC721;

const approved = await asset.isApprovedForAll(from, marketplaceAddress);
const approved = await asset.isApprovedForAll(
owner,
transferrerContractAddress,
);
if (approved) {
return true;
}
return (
(await asset.getApproved(tokenId)).toLowerCase() ===
marketplaceAddress.toLowerCase()
transferrerContractAddress.toLowerCase()
);
} else if (isERC1155) {
const asset = new Contract(
assetContract,
ERC1155Abi,
provider,
) as IERC1155;
return await asset.isApprovedForAll(from, marketplaceAddress);
return await asset.isApprovedForAll(owner, transferrerContractAddress);
} else {
console.error("Contract does not implement ERC 1155 or ERC 721.");
return false;
Expand Down
66 changes: 60 additions & 6 deletions src/contracts/multiwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ import {
TokensToWrap,
WrappedTokens,
} from "../types/multiwrap";
import { normalizePriceValue } from "../common/currency";
import { hasERC20Allowance, normalizePriceValue } from "../common/currency";
import { ITokenBundle, TokensWrappedEvent } from "contracts/Multiwrap";
import { MultiwrapContractSchema } from "../schema/contracts/multiwrap";
import { BigNumberish, ethers } from "ethers";
import TokenStruct = ITokenBundle.TokenStruct;
import { QueryAllParams } from "../types";
import { isTokenApprovedForTransfer } from "../common/marketplace";

/**
* Multiwrap lets you wrap any number of ERC20, ERC721 and ERC1155 tokens you own into a single wrapped token bundle.
Expand Down Expand Up @@ -293,16 +294,32 @@ export class Multiwrap extends Erc721<MultiwrapContract> {
const tokens: TokenStruct[] = [];

const provider = this.contractWrapper.getProvider();
const owner = await this.contractWrapper.getSignerAddress();

if (contents.erc20Tokens) {
for (const erc20 of contents.erc20Tokens) {
const normalizedQuantity = await normalizePriceValue(
provider,
erc20.quantity,
erc20.contractAddress,
);
const hasAllowance = await hasERC20Allowance(
this.contractWrapper,
erc20.contractAddress,
normalizedQuantity,
);
if (!hasAllowance) {
throw new Error(
`ERC20 token with contract address "${
erc20.contractAddress
}" does not have enough allowance to transfer.\n\nYou can set allowance to the multiwrap contract to transfer these tokens by running:\n\nawait sdk.getToken("${
erc20.contractAddress
}").setAllowance("${this.getAddress()}", ${erc20.quantity});\n\n`,
);
}
tokens.push({
assetContract: erc20.contractAddress,
totalAmount: await normalizePriceValue(
provider,
erc20.quantity,
erc20.contractAddress,
),
totalAmount: normalizedQuantity,
tokenId: 0,
tokenType: 0,
});
Expand All @@ -311,6 +328,26 @@ export class Multiwrap extends Erc721<MultiwrapContract> {

if (contents.erc721Tokens) {
for (const erc721 of contents.erc721Tokens) {
const isApproved = await isTokenApprovedForTransfer(
this.contractWrapper.getProvider(),
this.getAddress(),
erc721.contractAddress,
erc721.tokenId,
owner,
);

if (!isApproved) {
throw new Error(
`ERC721 token "${erc721.tokenId}" with contract address "${
erc721.contractAddress
}" is not approved for transfer.\n\nYou can give approval the multiwrap contract to transfer this token by running:\n\nawait sdk.getNFTCollection("${
erc721.contractAddress
}").setApprovalForToken("${this.getAddress()}", ${
erc721.tokenId
});\n\n`,
);
}

tokens.push({
assetContract: erc721.contractAddress,
totalAmount: 0,
Expand All @@ -322,6 +359,23 @@ export class Multiwrap extends Erc721<MultiwrapContract> {

if (contents.erc1155Tokens) {
for (const erc1155 of contents.erc1155Tokens) {
const isApproved = await isTokenApprovedForTransfer(
this.contractWrapper.getProvider(),
this.getAddress(),
erc1155.contractAddress,
erc1155.tokenId,
owner,
);

if (!isApproved) {
throw new Error(
`ERC1155 token "${erc1155.tokenId}" with contract address "${
erc1155.contractAddress
}" is not approved for transfer.\n\nYou can give approval the multiwrap contract to transfer this token by running:\n\nawait sdk.getEdition("${
erc1155.contractAddress
}").setApprovalForAll("${this.getAddress()}", true);\n\n`,
);
}
tokens.push({
assetContract: erc1155.contractAddress,
totalAmount: erc1155.quantity,
Expand Down
19 changes: 19 additions & 0 deletions src/core/classes/erc-721.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,25 @@ export class Erc721<
};
}

/**
* Approve an operator for the NFT owner. Operators can call transferFrom or safeTransferFrom for the specified token.
* @param operator - the operator's address
* @param tokenId - the tokenId to give approval for
*
* @internal
*/
public async setApprovalForToken(
operator: string,
tokenId: BigNumberish,
): Promise<TransactionResult> {
return {
receipt: await this.contractWrapper.sendTransaction("approve", [
operator,
tokenId,
]),
};
}

/** ******************************
* PRIVATE FUNCTIONS
*******************************/
Expand Down
4 changes: 2 additions & 2 deletions src/core/classes/marketplace-direct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
} from "../../constants/contract";
import {
handleTokenApproval,
isTokenApprovedForMarketplace,
isTokenApprovedForTransfer,
mapOffer,
validateNewListingParam,
} from "../../common/marketplace";
Expand Down Expand Up @@ -465,7 +465,7 @@ export class MarketplaceDirect {
listing: DirectListing,
quantity?: BigNumberish,
): Promise<boolean> {
const approved = await isTokenApprovedForMarketplace(
const approved = await isTokenApprovedForTransfer(
this.contractWrapper.getProvider(),
this.getAddress(),
listing.assetContractAddress,
Expand Down