Skip to content

Commit d14dd54

Browse files
authored
Merge pull request #28 from trustwallet/feat/looksrare_v2_support
Feat/looksrare v2 support
2 parents fbdc512 + c809398 commit d14dd54

File tree

5 files changed

+337
-1
lines changed

5 files changed

+337
-1
lines changed

src/types/looksrare-v2.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { BytesLike } from "ethers";
2+
3+
/**
4+
* @dev all types are taken from looksrare-sdk-v2
5+
* BigNumberish is casted to string as the EIP-712 doesn't have BigInt (all BigInt will be transformed with .toString())
6+
* @see https://github.com/LooksRare/sdk-v2/releases/tag/0.9.0
7+
*/
8+
9+
/** Type for maker order */
10+
export enum QuoteType {
11+
Bid = 0,
12+
Ask = 1,
13+
}
14+
15+
/** List of collection types supported by the protocol */
16+
export enum CollectionType {
17+
ERC721 = 0,
18+
ERC1155 = 1,
19+
}
20+
21+
/** List of trading strategies */
22+
export enum StrategyType {
23+
standard = 0,
24+
collection = 1,
25+
collectionWithMerkleTree = 2,
26+
}
27+
28+
export type LooksRareV2MakerOrder = {
29+
/** Bid (0) or Ask (1) */
30+
quoteType: QuoteType;
31+
/** User's current bid / ask nonce */
32+
globalNonce: string;
33+
/** Subset nonce used to group an arbitrary number of orders under the same nonce */
34+
subsetNonce: string;
35+
/** Nonce for this specific order */
36+
orderNonce: string;
37+
/** Strategy ID, 0: Standard, 1: Collection, etc*/
38+
strategyId: StrategyType;
39+
/** Asset type, 0: ERC-721, 1:ERC-1155, etc */
40+
collectionType: CollectionType;
41+
/** Collection address */
42+
collection: string;
43+
/** Currency address (zero address for ETH) */
44+
currency: string;
45+
/** Signer address */
46+
signer: string;
47+
/** Timestamp in second of the time when the order starts to be valid */
48+
startTime: string;
49+
/** Timestamp in second of the time when the order becomes invalid */
50+
endTime: string;
51+
/** Minimum price to be received after the trade */
52+
price: string;
53+
/** List of item IDS */
54+
itemIds: string[];
55+
/** List of amount for each item ID (1 for ERC721) */
56+
amounts: string[];
57+
/** Additional parameters for complex orders */
58+
additionalParameters: BytesLike;
59+
};

src/visualizer/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,25 @@ import { LooksrareMakerOrderWithEncodedParams } from "../types/looksrare";
77
import blurIo from "./blur-io";
88
import erc20Permit from "./erc20-permit";
99
import looksrare from "./looksrare";
10+
import looksrareV2 from "./looksrare-v2";
1011
import seaport from "./seaport";
1112
import { Domain, VisualizationResult } from "../types/visualizer";
1213
import { WizardError } from "../utils";
14+
import { LooksRareV2MakerOrder } from "../types/looksrare-v2";
1315

1416
export enum PROTOCOL_ID {
1517
OPENSEA_SEAPORT = "OPENSEA_SEAPORT",
1618
LOOKSRARE_EXCHANGE = "LOOKSRARE_EXCHANGE",
19+
LOOKSRARE_EXCHANGE_V2 = "LOOKSRARE_EXCHANGE_V2",
1720
BLUR_IO_MARKETPLACE = "BLUR_IO_MARKETPLACE",
1821
ERC20_PERMIT = "ERC20_PERMIT",
1922
}
2023

2124
export const getProtocolId = (domain: Domain): PROTOCOL_ID | undefined => {
2225
if (seaport.isCorrectDomain(domain)) return PROTOCOL_ID.OPENSEA_SEAPORT;
23-
if (looksrare.isCorrectDomain(domain)) return PROTOCOL_ID.LOOKSRARE_EXCHANGE;
2426
if (blurIo.isCorrectDomain(domain)) return PROTOCOL_ID.BLUR_IO_MARKETPLACE;
27+
if (looksrareV2.isCorrectDomain(domain)) return PROTOCOL_ID.LOOKSRARE_EXCHANGE_V2;
28+
if (looksrare.isCorrectDomain(domain)) return PROTOCOL_ID.LOOKSRARE_EXCHANGE;
2529

2630
return;
2731
};
@@ -42,6 +46,9 @@ export default async function visualize<T extends object>(
4246
case PROTOCOL_ID.OPENSEA_SEAPORT:
4347
return seaport.visualize(message as SeaPortPayload, domain);
4448

49+
case PROTOCOL_ID.LOOKSRARE_EXCHANGE_V2:
50+
return looksrareV2.visualize(message as LooksRareV2MakerOrder, domain);
51+
4552
case PROTOCOL_ID.LOOKSRARE_EXCHANGE:
4653
return looksrare.visualize(message as LooksrareMakerOrderWithEncodedParams, domain);
4754

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @see https://docs.looksrare.org/developers/protocol/order-types-v2
3+
*/
4+
5+
import { PROTOCOL_ID } from "..";
6+
import { ASSET_TYPE, AssetInOut } from "../../types";
7+
import { LooksRareV2MakerOrder } from "../../types/looksrare-v2";
8+
import { Domain, EIP712Protocol, VisualizationResult } from "../../types/visualizer";
9+
import { WizardError, getPaymentAssetType } from "../../utils";
10+
11+
const { ERC1155, ERC721 } = ASSET_TYPE;
12+
13+
export const isCorrectDomain = (domain: Domain) => {
14+
return (
15+
supportedChains.includes(Number(domain.chainId)) &&
16+
addressesBook.includes(domain.verifyingContract.toLocaleLowerCase())
17+
);
18+
};
19+
20+
export const visualize = (
21+
message: LooksRareV2MakerOrder,
22+
domain: Domain
23+
): VisualizationResult => {
24+
if (!isCorrectDomain(domain)) throw new Error("wrong looksrare-v2 domain");
25+
26+
const strategyId = Number(message.strategyId);
27+
if (strategyId > 2)
28+
throw new WizardError(`unknown looksrare-v2 strategy with ID: ${message.strategyId}`);
29+
30+
const nftAssets: AssetInOut[] = message.amounts.map((amount, index) => {
31+
const item: AssetInOut = {
32+
address: message.collection,
33+
type: Number(message.collectionType) === 0 ? ERC721 : ERC1155,
34+
id: message.itemIds[index],
35+
amounts: [amount.toString()],
36+
};
37+
38+
// If it's a collection offer, remove tokenId
39+
if (strategyId !== 0) delete item.id;
40+
return item;
41+
});
42+
43+
// insure tape safety if case param passed as string ("0" or "1")
44+
// Whether the order is an ask (sending a passive order to sell a NFT) or a bid (sending a passive order to buy an NFT).
45+
const isOrderAsk = Number(message.quoteType) === 1;
46+
47+
const paymentAsset: AssetInOut = {
48+
address: message.currency,
49+
type: getPaymentAssetType(message.currency),
50+
amounts: [message.price],
51+
};
52+
53+
return {
54+
protocol: PROTOCOL_ID.LOOKSRARE_EXCHANGE_V2,
55+
// If order is an ask, user is selling an NFT for an asset in return
56+
assetsIn: isOrderAsk ? [paymentAsset] : nftAssets,
57+
assetsOut: isOrderAsk ? nftAssets : [paymentAsset],
58+
liveness: {
59+
from: Number(message.startTime) * 1000,
60+
to: Number(message.endTime) * 1000,
61+
},
62+
approvals: [],
63+
};
64+
};
65+
66+
/**
67+
* @see https://docs.looksrare.org/developers/deployed-contract-addresses
68+
*/
69+
const supportedChains = [
70+
1, //Ethereum
71+
5, //Goerli
72+
];
73+
74+
/**
75+
* @see https://docs.looksrare.org/developers/deployed-contract-addresses
76+
*/
77+
const addressesBook = [
78+
"0x0000000000E655fAe4d56241588680F86E3b2377", // Mainnet Exchange
79+
"0x35C2215F2FFe8917B06454eEEaba189877F200cf", // Goerli Exchange
80+
].map((e) => e.toLocaleLowerCase());
81+
82+
const looksrare: EIP712Protocol<LooksRareV2MakerOrder> = {
83+
isCorrectDomain,
84+
visualize,
85+
};
86+
export default looksrare;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const looksrareV2NormalListing = {
2+
quoteType: 1,
3+
globalNonce: "0",
4+
subsetNonce: "0",
5+
orderNonce: "0",
6+
strategyId: 0,
7+
collectionType: 0,
8+
collection: "0x5f04D47D698F79d76F85E835930170Ff4c4EBdB7",
9+
currency: "0x0000000000000000000000000000000000000000",
10+
signer: "0x000075B45Dff84C00Cf597d5C3E766108CeA0000",
11+
startTime: "1684302184",
12+
endTime: "1686893895",
13+
price: "100000000000000000",
14+
itemIds: ["99117"],
15+
amounts: ["1"],
16+
additionalParameters: "0x",
17+
};
18+
19+
const looksrareV2CollectionBid = {
20+
quoteType: 0,
21+
globalNonce: "0",
22+
subsetNonce: "0",
23+
orderNonce: "0",
24+
strategyId: 1,
25+
collectionType: 0,
26+
collection: "0xED5AF388653567Af2F388E6224dC7C4b3241C544",
27+
currency: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
28+
signer: "0x000075B45Dff84C00Cf597d5C3E766108CeA0000",
29+
startTime: "1684302184",
30+
endTime: "1686893895",
31+
price: "100000000000000000",
32+
itemIds: [],
33+
amounts: ["1"],
34+
additionalParameters: "0x",
35+
};
36+
37+
const looksrareV2Bid = {
38+
quoteType: 0,
39+
globalNonce: "0",
40+
subsetNonce: "0",
41+
orderNonce: "0",
42+
strategyId: 0,
43+
collectionType: 2,
44+
collection: "0x005AF388653567Af2F388E6224dC7C4b3241C500",
45+
currency: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
46+
signer: "0x000075B45Dff84C00Cf597d5C3E766108CeA0000",
47+
startTime: "1684302184",
48+
endTime: "1686893895",
49+
price: "100000000000000000",
50+
itemIds: ["1500"],
51+
amounts: ["10"],
52+
additionalParameters: "0x",
53+
};
54+
55+
Object.freeze(looksrareV2NormalListing);
56+
Object.freeze(looksrareV2CollectionBid);
57+
Object.freeze(looksrareV2Bid);
58+
59+
export { looksrareV2NormalListing, looksrareV2CollectionBid, looksrareV2Bid };
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { LooksRareV2MakerOrder } from "../../../src/types/looksrare-v2";
2+
import { Domain } from "../../../src/types/visualizer";
3+
import visualize from "../../../src/visualizer";
4+
import looksrare from "../../../src/visualizer/looksrare-v2";
5+
import {
6+
looksrareV2Bid,
7+
looksrareV2CollectionBid,
8+
looksrareV2NormalListing,
9+
} from "./data";
10+
11+
describe("looksrare-v2", () => {
12+
const looksrareDomain: Domain = {
13+
verifyingContract: "0x0000000000E655fAe4d56241588680F86E3b2377",
14+
name: "LooksRareProtocol",
15+
version: "2",
16+
chainId: "1",
17+
};
18+
19+
it("should revert if domain is not recognized by SDK entry", async () => {
20+
await expect(
21+
visualize(looksrareV2NormalListing, { ...looksrareDomain, chainId: "11" })
22+
).rejects.toThrowError("Unrecognized/Unsupported EIP712Protocol Domain");
23+
});
24+
25+
it("should revert at looksrare module level if accessed directly with wrong domain", () => {
26+
expect(() => {
27+
looksrare.visualize(looksrareV2NormalListing, {
28+
...looksrareDomain,
29+
verifyingContract: "0x0",
30+
});
31+
}).toThrow("wrong looksrare-v2 domain");
32+
});
33+
34+
it("should throw for unknown strategy", async () => {
35+
await expect(
36+
visualize<LooksRareV2MakerOrder>(
37+
{ ...looksrareV2Bid, strategyId: 3 as any },
38+
looksrareDomain
39+
)
40+
).rejects.toThrowError("unknown looksrare-v2 strategy with ID: 3");
41+
});
42+
43+
it("should successfully visualize sell order", async () => {
44+
const result = await visualize<LooksRareV2MakerOrder>(
45+
looksrareV2NormalListing,
46+
looksrareDomain
47+
);
48+
49+
expect(result).toEqual({
50+
protocol: "LOOKSRARE_EXCHANGE_V2",
51+
assetsIn: [
52+
{
53+
address: "0x0000000000000000000000000000000000000000",
54+
type: "NATIVE",
55+
amounts: ["100000000000000000"],
56+
},
57+
],
58+
assetsOut: [
59+
{
60+
address: "0x5f04D47D698F79d76F85E835930170Ff4c4EBdB7",
61+
type: "ERC721",
62+
id: "99117",
63+
amounts: ["1"],
64+
},
65+
],
66+
approvals: [],
67+
liveness: { from: 1684302184000, to: 1686893895000 },
68+
});
69+
});
70+
71+
it("should successfully visualize collection bid", async () => {
72+
const result = await visualize<LooksRareV2MakerOrder>(
73+
looksrareV2CollectionBid,
74+
looksrareDomain
75+
);
76+
77+
expect(result).toEqual({
78+
protocol: "LOOKSRARE_EXCHANGE_V2",
79+
assetsIn: [
80+
{
81+
address: "0xED5AF388653567Af2F388E6224dC7C4b3241C544",
82+
type: "ERC721",
83+
amounts: ["1"],
84+
},
85+
],
86+
assetsOut: [
87+
{
88+
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
89+
type: "ERC20",
90+
amounts: ["100000000000000000"],
91+
},
92+
],
93+
approvals: [],
94+
liveness: { from: 1684302184000, to: 1686893895000 },
95+
});
96+
});
97+
98+
it("should successfully visualize an ER1155 bid order", async () => {
99+
const result = await visualize<LooksRareV2MakerOrder>(
100+
looksrareV2Bid,
101+
looksrareDomain
102+
);
103+
104+
expect(result).toEqual({
105+
protocol: "LOOKSRARE_EXCHANGE_V2",
106+
assetsIn: [
107+
{
108+
address: "0x005AF388653567Af2F388E6224dC7C4b3241C500",
109+
type: "ERC1155",
110+
id: "1500",
111+
amounts: ["10"],
112+
},
113+
],
114+
assetsOut: [
115+
{
116+
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
117+
type: "ERC20",
118+
amounts: ["100000000000000000"],
119+
},
120+
],
121+
approvals: [],
122+
liveness: { from: 1684302184000, to: 1686893895000 },
123+
});
124+
});
125+
});

0 commit comments

Comments
 (0)