Skip to content

Commit 48ff538

Browse files
committed
Support bypassing approvals when taker sells NFT by accepting a bid
1 parent fb617c0 commit 48ff538

File tree

6 files changed

+523
-4
lines changed

6 files changed

+523
-4
lines changed

src/sdk/v4/NftSwapV4.ts

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Signer, TypedDataSigner } from '@ethersproject/abstract-signer';
22
import { BaseProvider, TransactionReceipt } from '@ethersproject/providers';
33
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';
510
import type {
611
ApprovalStatus,
712
BaseNftSwap,
@@ -30,15 +35,22 @@ import {
3035
import type {
3136
AddressesForChain,
3237
ApprovalOverrides,
38+
FeeStruct,
3339
FillOrderOverrides,
3440
NftOrderV4,
3541
OrderStructOptionsCommonStrict,
42+
PropertyStruct,
3643
SignedNftOrderV4,
3744
SigningOptions,
3845
} from './types';
3946
import addresses from './addresses.json';
4047
import invariant from 'tiny-invariant';
4148
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';
4254

4355
export enum SupportedChainIdsV4 {
4456
Ropsten = 3,
@@ -106,6 +118,21 @@ export interface INftSwapV4 extends BaseNftSwap {
106118
// };
107119
}
108120

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+
109136
export interface AdditionalSdkConfig {
110137
zeroExExchangeProxyContractAddress: string;
111138
}
@@ -364,6 +391,97 @@ class NftSwapV4 implements INftSwapV4 {
364391
return signedOrder;
365392
};
366393

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+
367485
fillSignedCollectionOrder = async (
368486
signedOrder: SignedNftOrderV4,
369487
tokenId: BigNumberish,

0 commit comments

Comments
 (0)