Skip to content

Commit 1529216

Browse files
authored
Adds support to batch buy NFTs (#67)
* ADDS batch buy nfts * ADDS tests for batch fill
1 parent 33cca7e commit 1529216

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

src/sdk/v4/NftSwapV4.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ import {
3434
import type {
3535
AddressesForChainV4,
3636
ApprovalOverrides,
37+
ERC721OrderStruct,
3738
FillOrderOverrides,
3839
NftOrderV4,
3940
NftOrderV4Serialized,
4041
OrderStructOptionsCommonStrict,
42+
SignedERC1155OrderStruct,
43+
SignedERC1155OrderStructSerialized,
44+
SignedERC721OrderStruct,
45+
SignedERC721OrderStructSerialized,
4146
SignedNftOrderV4,
4247
SigningOptionsV4,
4348
SwappableAssetV4,
@@ -63,6 +68,7 @@ import { DIRECTION_MAPPING, OrderStatusV4, TradeDirection } from './enums';
6368
import { CONTRACT_ORDER_VALIDATOR } from './properties';
6469
import { ETH_ADDRESS_AS_ERC20 } from './constants';
6570
import { ZERO_AMOUNT } from '../../utils/eth';
71+
import { arrayify } from '@ethersproject/bytes';
6672

6773
export enum SupportedChainIdsV4 {
6874
Mainnet = 1,
@@ -276,6 +282,84 @@ class NftSwapV4 implements INftSwapV4 {
276282
throw new Error('unsupport order');
277283
};
278284

285+
/**
286+
* Batch fill NFT sell orders
287+
* Can be used by taker to fill multiple NFT sell orders atomically.
288+
* E.g. A taker has a shopping cart full of NFTs to buy, can call this method to fill them all.
289+
* Requires a valid signer to execute transaction
290+
* @param signedOrders Signed 0x NFT sell orders
291+
* @param revertIfIncomplete Revert if we don't fill _all_ orders (defaults to false)
292+
* @param transacitonOverrides Ethers transaciton overrides
293+
* @returns
294+
*/
295+
batchBuyNfts = (
296+
signedOrders: Array<SignedNftOrderV4>,
297+
revertIfIncomplete: boolean = false,
298+
transacitonOverrides?: PayableOverrides
299+
) => {
300+
const allSellOrders = signedOrders.every((signedOrder) => {
301+
if (signedOrder.direction === 0) {
302+
return true;
303+
}
304+
return false;
305+
});
306+
307+
invariant(
308+
allSellOrders,
309+
`batchBuyNfts: All orders must be of type sell order (order direction == 0)`
310+
);
311+
312+
const allErc721 = signedOrders.every((signedOrder) => {
313+
if ('erc721Token' in signedOrder) {
314+
return true;
315+
}
316+
return false;
317+
});
318+
319+
const allErc1155 = signedOrders.every((signedOrder) => {
320+
if ('erc1155Token' in signedOrder) {
321+
return true;
322+
}
323+
return false;
324+
});
325+
326+
const eitherAllErc721OrErc1155Orders = allErc721 || allErc1155;
327+
328+
invariant(
329+
eitherAllErc721OrErc1155Orders,
330+
`Batch buy is only available for tokens of the same ERC type.`
331+
);
332+
333+
if (allErc721) {
334+
const erc721SignedOrders: SignedERC721OrderStruct[] =
335+
signedOrders as SignedERC721OrderStruct[];
336+
return this.exchangeProxy.batchBuyERC721s(
337+
erc721SignedOrders,
338+
erc721SignedOrders.map((so) => so.signature),
339+
erc721SignedOrders.map((_) => '0x'),
340+
revertIfIncomplete,
341+
{
342+
...transacitonOverrides,
343+
}
344+
);
345+
} else if (allErc1155) {
346+
const erc1155SignedOrders: SignedERC1155OrderStruct[] =
347+
signedOrders as SignedERC1155OrderStruct[];
348+
return this.exchangeProxy.batchBuyERC1155s(
349+
erc1155SignedOrders,
350+
erc1155SignedOrders.map((so) => so.signature),
351+
erc1155SignedOrders.map((so) => so.erc1155TokenAmount),
352+
erc1155SignedOrders.map((_) => '0x'),
353+
revertIfIncomplete,
354+
{
355+
...transacitonOverrides,
356+
}
357+
);
358+
} else {
359+
throw Error('batchBuyNfts: Incompatible state');
360+
}
361+
};
362+
279363
/**
280364
* Derives order hash from order (currently requires a provider to derive)
281365
* @param order A 0x v4 order (signed or unsigned)

test/v4/batch-erc721-fill.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { ethers } from 'ethers';
2+
import { NftSwapV4 } from '../../src/sdk/v4/NftSwapV4';
3+
4+
import {
5+
SignedERC721OrderStruct,
6+
SwappableAssetV4,
7+
} from '../../src/sdk/v4/types';
8+
9+
jest.setTimeout(120 * 1000);
10+
11+
const MAKER_WALLET_ADDRESS = '0xabc23F70Df4F45dD3Df4EC6DA6827CB05853eC9b';
12+
const MAKER_PRIVATE_KEY =
13+
'fc5db508b0a52da8fbcac3ab698088715595f8de9cccf2467d51952eec564ec9';
14+
// NOTE(johnrjj) - NEVER use these private keys for anything of value, testnets only!
15+
16+
const DAI_TOKEN_ADDRESS_TESTNET = '0x31f42841c2db5173425b5223809cf3a38fede360';
17+
const TEST_NFT_CONTRACT_ADDRESS = '0xf5de760f2e916647fd766b4ad9e85ff943ce3a2b'; // https://ropsten.etherscan.io/token/0xf5de760f2e916647fd766b4ad9e85ff943ce3a2b?a=0xabc23F70Df4F45dD3Df4EC6DA6827CB05853eC9b
18+
19+
const RPC_TESTNET =
20+
'https://eth-ropsten.alchemyapi.io/v2/is1WqyAFM1nNFFx2aCozhTep7IxHVNGo';
21+
22+
const MAKER_WALLET = new ethers.Wallet(MAKER_PRIVATE_KEY);
23+
// const TAKER_WALLET = new ethers.Wallet(TAKER_PRIVATE_KEY);
24+
25+
const PROVIDER = new ethers.providers.StaticJsonRpcProvider(RPC_TESTNET);
26+
27+
const MAKER_SIGNER = MAKER_WALLET.connect(PROVIDER);
28+
// const TAKER_PROVIDER = TAKER_WALLET.connect(PROVIDER);
29+
30+
const ROPSTEN_CHAIN_ID = 3;
31+
32+
const nftSwapperMaker = new NftSwapV4(
33+
MAKER_SIGNER as any,
34+
MAKER_SIGNER,
35+
ROPSTEN_CHAIN_ID
36+
);
37+
// const nftSwapperTaker = new NftSwap(TAKER_PROVIDER as any, 4);
38+
39+
const TAKER_ASSET: SwappableAssetV4 = {
40+
type: 'ERC20',
41+
tokenAddress: DAI_TOKEN_ADDRESS_TESTNET,
42+
amount: '100000000000', // 1 USDC
43+
};
44+
const MAKER_ASSET_1: SwappableAssetV4 = {
45+
type: 'ERC721',
46+
tokenAddress: TEST_NFT_CONTRACT_ADDRESS,
47+
tokenId: '11045',
48+
};
49+
50+
const MAKER_ASSET_2: SwappableAssetV4 = {
51+
type: 'ERC721',
52+
tokenAddress: TEST_NFT_CONTRACT_ADDRESS,
53+
tokenId: '171353',
54+
};
55+
56+
describe('NFTSwapV4', () => {
57+
it('v4 erc721 test', async () => {
58+
// NOTE(johnrjj) - Assumes USDC and DAI are already approved w/ the ExchangeProxy
59+
60+
const v4Erc721Order_1 = nftSwapperMaker.buildOrder(
61+
MAKER_ASSET_1,
62+
TAKER_ASSET,
63+
MAKER_WALLET_ADDRESS
64+
// {
65+
// // Fix dates and salt so we have reproducible tests
66+
// expiration: new Date(3000, 10, 1),
67+
// }
68+
);
69+
70+
const v4Erc721Order_2 = nftSwapperMaker.buildOrder(
71+
MAKER_ASSET_2,
72+
TAKER_ASSET,
73+
MAKER_WALLET_ADDRESS
74+
// {
75+
// // Fix dates and salt so we have reproducible tests
76+
// expiration: new Date(3000, 10, 1),
77+
// }
78+
);
79+
80+
// console.log('v4Erc721Order.nonce', v4Erc721Order.nonce.toString());
81+
82+
// expect(v4Erc721Order.nonce.toString().includes('-')).toBeFalsy();
83+
84+
// const makerapprovalTx = await nftSwapperMaker.approveTokenOrNftByAsset(
85+
// MAKER_ASSET,
86+
// MAKER_WALLET_ADDRESS,
87+
// )
88+
// const makerApprovalTxHash = await (await makerapprovalTx.wait()).transactionHash
89+
// console.log('maker approval tx hash', makerApprovalTxHash)
90+
91+
// const takerApprovalTx = await nftSwapperMaker.approveTokenOrNftByAsset(
92+
// TAKER_ASSET,
93+
// MAKER_WALLET_ADDRESS,
94+
// )
95+
96+
// const takerApprovalTxHash = await (await takerApprovalTx.wait()).transactionHash
97+
// console.log('taker approval tx hash', takerApprovalTxHash)
98+
99+
const signedOrder_1 = await nftSwapperMaker.signOrder(v4Erc721Order_1);
100+
const signedOrder_2 = await nftSwapperMaker.signOrder(v4Erc721Order_2);
101+
102+
// const fillTx = await nftSwapperMaker.batchBuyNfts([
103+
// signedOrder_1,
104+
// signedOrder_2,
105+
// ]);
106+
107+
// const txReceipt = await fillTx.wait();
108+
// console.log('erc721 batch fill fill tx', txReceipt.transactionHash);
109+
110+
// expect(txReceipt.transactionHash).toBeTruthy();
111+
112+
// console.log(
113+
// `Swapped batch nft fill on Ropsten (txHAsh: ${txReceipt.transactionIndex})`
114+
// );
115+
});
116+
});

0 commit comments

Comments
 (0)