|
1 | 1 | import { Signer, TypedDataSigner } from '@ethersproject/abstract-signer'; |
2 | 2 | import { BaseProvider, TransactionReceipt } from '@ethersproject/providers'; |
3 | 3 | import { BigNumber, BigNumberish, ContractTransaction } from 'ethers'; |
4 | | -import { IZeroEx, IZeroEx__factory } from '../../contracts'; |
| 4 | +import { |
| 5 | + ERC1155__factory, |
| 6 | + ERC721__factory, |
| 7 | + IZeroEx, |
| 8 | + IZeroEx__factory, |
| 9 | +} from '../../contracts'; |
5 | 10 | import type { |
6 | 11 | ApprovalStatus, |
7 | 12 | BaseNftSwap, |
@@ -30,15 +35,22 @@ import { |
30 | 35 | import type { |
31 | 36 | AddressesForChain, |
32 | 37 | ApprovalOverrides, |
| 38 | + FeeStruct, |
33 | 39 | FillOrderOverrides, |
34 | 40 | NftOrderV4, |
35 | 41 | OrderStructOptionsCommonStrict, |
| 42 | + PropertyStruct, |
36 | 43 | SignedNftOrderV4, |
37 | 44 | SigningOptions, |
38 | 45 | } from './types'; |
39 | 46 | import addresses from './addresses.json'; |
40 | 47 | import invariant from 'tiny-invariant'; |
41 | 48 | import { NULL_ADDRESS } from '../../utils/eth'; |
| 49 | +import { defaultAbiCoder, Interface } from '@ethersproject/abi'; |
| 50 | +import { |
| 51 | + ERC1155_ENCODED_ORDER_DATA, |
| 52 | + ERC721_ENCODED_ORDER_DATA, |
| 53 | +} from './nft-safe-transfer-from-data'; |
42 | 54 |
|
43 | 55 | export enum SupportedChainIdsV4 { |
44 | 56 | Ropsten = 3, |
@@ -106,6 +118,21 @@ export interface INftSwapV4 extends BaseNftSwap { |
106 | 118 | // }; |
107 | 119 | } |
108 | 120 |
|
| 121 | +export type ERC1155OrderStruct = { |
| 122 | + direction: BigNumberish; |
| 123 | + maker: string; |
| 124 | + taker: string; |
| 125 | + expiry: BigNumberish; |
| 126 | + nonce: BigNumberish; |
| 127 | + erc20Token: string; |
| 128 | + erc20TokenAmount: BigNumberish; |
| 129 | + fees: FeeStruct[]; |
| 130 | + erc1155Token: string; |
| 131 | + erc1155TokenId: BigNumberish; |
| 132 | + erc1155TokenProperties: PropertyStruct[]; |
| 133 | + erc1155TokenAmount: BigNumberish; |
| 134 | +}; |
| 135 | + |
109 | 136 | export interface AdditionalSdkConfig { |
110 | 137 | zeroExExchangeProxyContractAddress: string; |
111 | 138 | } |
@@ -364,6 +391,97 @@ class NftSwapV4 implements INftSwapV4 { |
364 | 391 | return signedOrder; |
365 | 392 | }; |
366 | 393 |
|
| 394 | + /** |
| 395 | + * Fill a 'Buy NFT' order (e.g. taker would be selling'their NFT to fill this order) without needing an approval |
| 396 | + * Use case: Users can accept offers/bids for their NFTs without needing to approve their NFT! 🤯 |
| 397 | + * @param signedOrder Signed Buy Nft order (e.g. direction = 1) |
| 398 | + * @param tokenId NFT token id that taker of trade will sell |
| 399 | + * @param fillOrderOverrides Trade specific (SDK-level) overrides |
| 400 | + * @param transactionOverrides General transaction overrides from ethers (gasPrice, gasLimit, etc) |
| 401 | + * @returns |
| 402 | + */ |
| 403 | + fillBuyNftOrderWithoutApproval = async ( |
| 404 | + signedOrder: SignedNftOrderV4, |
| 405 | + tokenId: string, |
| 406 | + fillOrderOverrides?: Partial<FillOrderOverrides>, |
| 407 | + transactionOverrides?: Partial<PayableOverrides> |
| 408 | + ) => { |
| 409 | + if (!this.signer) { |
| 410 | + throw new Error( |
| 411 | + 'Signer undefined. Signer must be provided to fill order' |
| 412 | + ); |
| 413 | + } |
| 414 | + if (signedOrder.direction !== TradeDirection.BuyNFT) { |
| 415 | + throw new Error( |
| 416 | + 'Only filling Buy NFT orders (direction=1) is valid for skipping approvals' |
| 417 | + ); |
| 418 | + } |
| 419 | + |
| 420 | + const signerAddress = await this.signer.getAddress(); |
| 421 | + const unwrapWeth = |
| 422 | + fillOrderOverrides?.fillOrderWithNativeTokenInsteadOfWrappedToken ?? |
| 423 | + false; |
| 424 | + |
| 425 | + // Handle ERC721 |
| 426 | + if ('erc721Token' in signedOrder) { |
| 427 | + const erc721Contract = ERC721__factory.connect( |
| 428 | + signedOrder.erc721Token, |
| 429 | + this.signer |
| 430 | + ); |
| 431 | + |
| 432 | + const encodingIface = new Interface(ERC721_ENCODED_ORDER_DATA); |
| 433 | + |
| 434 | + const fragment = encodingIface.getFunction('safeTransferFromErc721Data'); |
| 435 | + const data = encodingIface._encodeParams(fragment.inputs, [ |
| 436 | + signedOrder, |
| 437 | + signedOrder.signature, |
| 438 | + unwrapWeth, |
| 439 | + ]); |
| 440 | + |
| 441 | + const transferFromTx = await erc721Contract[ |
| 442 | + 'safeTransferFrom(address,address,uint256,bytes)' |
| 443 | + ]( |
| 444 | + signerAddress, |
| 445 | + this.exchangeProxy.address, |
| 446 | + fillOrderOverrides?.tokenIdToSellForCollectionOrder ?? tokenId, |
| 447 | + data, |
| 448 | + transactionOverrides ?? {} |
| 449 | + ); |
| 450 | + return transferFromTx; |
| 451 | + } |
| 452 | + |
| 453 | + // Handle ERC1155 |
| 454 | + if ('erc1155Token' in signedOrder) { |
| 455 | + const erc1155Contract = ERC1155__factory.connect( |
| 456 | + signedOrder.erc1155Token, |
| 457 | + this.signer |
| 458 | + ); |
| 459 | + const encodingIface = new Interface(ERC1155_ENCODED_ORDER_DATA); |
| 460 | + |
| 461 | + const fragment = encodingIface.getFunction('safeTransferFromErc1155Data'); |
| 462 | + const data = encodingIface._encodeParams(fragment.inputs, [ |
| 463 | + signedOrder, |
| 464 | + signedOrder.signature, |
| 465 | + unwrapWeth, |
| 466 | + ]); |
| 467 | + |
| 468 | + const transferFromTx = await erc1155Contract.safeTransferFrom( |
| 469 | + signerAddress, |
| 470 | + this.exchangeProxy.address, |
| 471 | + tokenId, |
| 472 | + fillOrderOverrides?.tokenIdToSellForCollectionOrder ?? |
| 473 | + signedOrder.erc1155TokenAmount ?? |
| 474 | + '1', |
| 475 | + data, |
| 476 | + transactionOverrides ?? {} |
| 477 | + ); |
| 478 | + return transferFromTx; |
| 479 | + } |
| 480 | + |
| 481 | + // Unknown format (NFT neither ERC721 or ERC1155) |
| 482 | + throw new Error('unknown order type'); |
| 483 | + }; |
| 484 | + |
367 | 485 | fillSignedCollectionOrder = async ( |
368 | 486 | signedOrder: SignedNftOrderV4, |
369 | 487 | tokenId: BigNumberish, |
|
0 commit comments