|
1 | | -import { Signer, TypedDataSigner } from '@ethersproject/abstract-signer'; |
2 | | -import { BaseProvider, TransactionReceipt } from '@ethersproject/providers'; |
3 | | -import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; |
4 | | -import { IZeroEx, IZeroEx__factory } from '../../contracts'; |
| 1 | +import type { Signer, TypedDataSigner } from '@ethersproject/abstract-signer'; |
| 2 | +import type { |
| 3 | + BaseProvider, |
| 4 | + TransactionReceipt, |
| 5 | +} from '@ethersproject/providers'; |
| 6 | +import type { BigNumberish, ContractTransaction } from 'ethers'; |
| 7 | +import { Interface } from '@ethersproject/abi'; |
| 8 | +import invariant from 'tiny-invariant'; |
| 9 | +import { |
| 10 | + ERC1155__factory, |
| 11 | + ERC721__factory, |
| 12 | + IZeroEx, |
| 13 | + IZeroEx__factory, |
| 14 | +} from '../../contracts'; |
5 | 15 | import type { |
6 | 16 | ApprovalStatus, |
7 | 17 | BaseNftSwap, |
@@ -36,9 +46,11 @@ import type { |
36 | 46 | SignedNftOrderV4, |
37 | 47 | SigningOptions, |
38 | 48 | } from './types'; |
| 49 | +import { |
| 50 | + ERC1155_TRANSFER_FROM_DATA, |
| 51 | + ERC721_TRANSFER_FROM_DATA, |
| 52 | +} from './nft-safe-transfer-from-data'; |
39 | 53 | import addresses from './addresses.json'; |
40 | | -import invariant from 'tiny-invariant'; |
41 | | -import { NULL_ADDRESS } from '../../utils/eth'; |
42 | 54 |
|
43 | 55 | export enum SupportedChainIdsV4 { |
44 | 56 | Ropsten = 3, |
@@ -364,6 +376,95 @@ class NftSwapV4 implements INftSwapV4 { |
364 | 376 | return signedOrder; |
365 | 377 | }; |
366 | 378 |
|
| 379 | + /** |
| 380 | + * Fill a 'Buy NFT' order (e.g. taker would be selling'their NFT to fill this order) without needing an approval |
| 381 | + * Use case: Users can accept offers/bids for their NFTs without needing to approve their NFT! 🤯 |
| 382 | + * @param signedOrder Signed Buy Nft order (e.g. direction = 1) |
| 383 | + * @param tokenId NFT token id that taker of trade will sell |
| 384 | + * @param fillOrderOverrides Trade specific (SDK-level) overrides |
| 385 | + * @param transactionOverrides General transaction overrides from ethers (gasPrice, gasLimit, etc) |
| 386 | + * @returns |
| 387 | + */ |
| 388 | + fillBuyNftOrderWithoutApproval = async ( |
| 389 | + signedOrder: SignedNftOrderV4, |
| 390 | + tokenId: string, |
| 391 | + fillOrderOverrides?: Partial<FillOrderOverrides>, |
| 392 | + transactionOverrides?: Partial<PayableOverrides> |
| 393 | + ) => { |
| 394 | + if (!this.signer) { |
| 395 | + throw new Error( |
| 396 | + 'Signer undefined. Signer must be provided to fill order' |
| 397 | + ); |
| 398 | + } |
| 399 | + if (signedOrder.direction !== TradeDirection.BuyNFT) { |
| 400 | + throw new Error( |
| 401 | + 'Only filling Buy NFT orders (direction=1) is valid for skipping approvals' |
| 402 | + ); |
| 403 | + } |
| 404 | + |
| 405 | + const signerAddress = await this.signer.getAddress(); |
| 406 | + const unwrapWeth = |
| 407 | + fillOrderOverrides?.fillOrderWithNativeTokenInsteadOfWrappedToken ?? |
| 408 | + false; |
| 409 | + |
| 410 | + // Handle ERC721 |
| 411 | + if ('erc721Token' in signedOrder) { |
| 412 | + const erc721Contract = ERC721__factory.connect( |
| 413 | + signedOrder.erc721Token, |
| 414 | + this.signer |
| 415 | + ); |
| 416 | + |
| 417 | + const encodingIface = new Interface(ERC721_TRANSFER_FROM_DATA); |
| 418 | + |
| 419 | + const fragment = encodingIface.getFunction('safeTransferFromErc721Data'); |
| 420 | + const data = encodingIface._encodeParams(fragment.inputs, [ |
| 421 | + signedOrder, |
| 422 | + signedOrder.signature, |
| 423 | + unwrapWeth, |
| 424 | + ]); |
| 425 | + |
| 426 | + const transferFromTx = await erc721Contract[ |
| 427 | + 'safeTransferFrom(address,address,uint256,bytes)' |
| 428 | + ]( |
| 429 | + signerAddress, |
| 430 | + this.exchangeProxy.address, |
| 431 | + fillOrderOverrides?.tokenIdToSellForCollectionOrder ?? tokenId, |
| 432 | + data, |
| 433 | + transactionOverrides ?? {} |
| 434 | + ); |
| 435 | + return transferFromTx; |
| 436 | + } |
| 437 | + |
| 438 | + // Handle ERC1155 |
| 439 | + if ('erc1155Token' in signedOrder) { |
| 440 | + const erc1155Contract = ERC1155__factory.connect( |
| 441 | + signedOrder.erc1155Token, |
| 442 | + this.signer |
| 443 | + ); |
| 444 | + const encodingIface = new Interface(ERC1155_TRANSFER_FROM_DATA); |
| 445 | + |
| 446 | + const fragment = encodingIface.getFunction('safeTransferFromErc1155Data'); |
| 447 | + const data = encodingIface._encodeParams(fragment.inputs, [ |
| 448 | + signedOrder, |
| 449 | + signedOrder.signature, |
| 450 | + unwrapWeth, |
| 451 | + ]); |
| 452 | + |
| 453 | + const transferFromTx = await erc1155Contract.safeTransferFrom( |
| 454 | + signerAddress, |
| 455 | + this.exchangeProxy.address, |
| 456 | + fillOrderOverrides?.tokenIdToSellForCollectionOrder ?? tokenId, |
| 457 | + signedOrder.erc1155TokenAmount ?? '1', |
| 458 | + data, |
| 459 | + transactionOverrides ?? {} |
| 460 | + ); |
| 461 | + return transferFromTx; |
| 462 | + } |
| 463 | + |
| 464 | + // Unknown format (NFT neither ERC721 or ERC1155) |
| 465 | + throw new Error('unknown order type'); |
| 466 | + }; |
| 467 | + |
367 | 468 | fillSignedCollectionOrder = async ( |
368 | 469 | signedOrder: SignedNftOrderV4, |
369 | 470 | tokenId: BigNumberish, |
|
0 commit comments