├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── README.md
├── example
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── AppContext.js
│ ├── assets
│ ├── css
│ │ └── index.css
│ └── images
│ │ ├── coinbaseWallet.svg
│ │ ├── metamask.png
│ │ ├── wallet-metamask-fox.svg
│ │ ├── walletconnect-logo.svg
│ │ └── walletconnect.png
│ ├── index.js
│ ├── js
│ ├── config.js
│ ├── helper.js
│ └── walletAction.js
│ └── page
│ ├── SeaportBuy.js
│ ├── SeaportSell.js
│ ├── WalletList.js
│ └── index.js
├── index.ts
├── package-lock.json
├── package.json
├── src
├── api
│ ├── config.ts
│ ├── seaport.ts
│ ├── types.ts
│ └── utils.ts
├── constants.ts
├── contracts
│ ├── abi
│ │ ├── aggtrade
│ │ │ └── ExSwap.json
│ │ └── seaport
│ │ │ ├── Conduit.json
│ │ │ ├── ConduitController.json
│ │ │ ├── PausableZone.json
│ │ │ └── Seaport.json
│ └── index.ts
├── index.ts
├── seaport.ts
├── swapEx
│ └── swapEx.ts
├── types.ts
└── utils
│ ├── assert.ts
│ ├── balanceAndApprovalCheck.ts
│ ├── criteria.ts
│ ├── fulfill.ts
│ ├── gcd.ts
│ ├── item.ts
│ ├── merkletree.ts
│ ├── order.ts
│ └── usecase.ts
├── test
├── abi
│ ├── Seaport.ts
│ ├── fulfillAdvancedOrder.ts
│ ├── fulfillBasicOrder.ts
│ └── gem.fulfillAvailableAdvancedOrders.ts
├── data
│ ├── gcd.test.ts
│ └── orders.ts
├── seaport
│ ├── api.test.ts
│ ├── assert.test.ts
│ ├── create.adjust.test.ts
│ ├── create.listing.test.ts
│ ├── create.offer.test.ts
│ ├── fulfillOrders.test.ts
│ ├── getOrderHash.test.ts
│ ├── post.order.ts
│ └── swap.test.ts
└── utisl.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:@typescript-eslint/recommended"
9 | ],
10 | "parser": "@typescript-eslint/parser",
11 | "parserOptions": {
12 | "ecmaVersion": 12,
13 | "sourceType": "module"
14 | },
15 | "plugins": [
16 | "@typescript-eslint"
17 | ],
18 | "rules": {
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 | lib/
106 | .idea/*
107 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /test/
3 | /src/
4 | /index.ts
5 | /dist/
6 | /page/
7 | /proxy/
8 | /wallet/
9 | /config/
10 | /0x-js/
11 | /element-ex/
12 | .eslintignore
13 | .eslintrc.json
14 | .gitignore
15 | .prettierignore
16 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /static/_dist
3 | /server/_dist
4 | /static/orders
5 | /_temp
6 | /logs
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true,
6 | "endOfLine": "lf",
7 | "printWidth": 120,
8 | "bracketSpacing": true,
9 | "arrowParens": "always"
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # seaport-js
2 |
3 | SDK for the Seaport protocol v1.1
4 | https://web3w.github.io/seaport-js/
5 |
6 | ## Installation
7 |
8 | In your project, run:
9 |
10 | ```bash
11 | npm i seaport-js
12 | ```
13 |
14 | ## Getting Started
15 |
16 | To get started, create a new Seaport client, using your chainId and address:
17 |
18 | ```JavaScript
19 | import {SeaportSDK} from 'seaport-js'
20 | import {Web3Wallets} from 'web3-wallets'
21 |
22 | const {chainId, address} = new Web3Wallets({name:'metamask'})
23 | const seaport = new SeaportSDK({chainId, address})
24 | ```
25 |
26 | In the browser environment, only the chainId and address need to be configured,If you want to use the bash environment,
27 | configure the private key and RPC
28 |
29 | ```ts
30 | type WalletInfo = {
31 | chainId: number;
32 | address: string;
33 | privateKeys?: string[];
34 | rpcUrl?: RpcInfo; // User-defined RPC information of the provider
35 | }
36 |
37 | type APIConfig = {
38 | apiKey?: string; //opensea api key
39 | }
40 | const rpcUrl = {url: "https://..."}
41 | const wallet = {chainId, address, privateKeys: ["0x..."]}
42 | const config = {apiKey: "xx-xxx"}
43 | const seaport = new SeaportSDK(wallet, config)
44 | ```
45 |
46 |
47 | ### Fetching Assets
48 |
49 | Assets are items on OpenSea. They can be non-fungible (conforming to standards like ERC721), semi-fungible (like ERC1155
50 | assets), and even fungible (ERC20).
51 |
52 | Assets are represented by the `Asset` type, defined in TypeScript:
53 |
54 | ```TypeScript
55 | /**
56 | * Simple, unannotated non-fungible asset spec
57 | */
58 | export interface Asset {
59 | // The asset's token ID, or null if ERC-20
60 | tokenId: string | undefined;
61 | // The asset's contract address
62 | tokenAddress: string;
63 | // 'erc20' | 'erc721' | 'erc1155' | 'cryptokitties' | 'ensshortnameauction' | 'cryptopunks'
64 | schemaName: string;
65 | // Optional for fungible items
66 | name?: string;
67 | data?: string;
68 | decimals?: number;
69 | chainId?: number;
70 | collection?: {
71 | "royaltyFeePoints": 500,
72 | "royaltyFeeAddress": "0x9F7A946d935c8Efc7A8329C0d894A69bA241345A"
73 | };
74 | }
75 |
76 | ```
77 |
78 | The `Asset` type is the minimal type you need for most marketplace actions. `SchemaName` is optional. If omitted, most
79 | actions will assume you're referring to a non-fungible, ERC721 asset. Other options include 'ERC20' and 'ERC1155'.
80 |
81 | You can fetch an asset using the `OpenSeaAPI`, which will return an `OpenSeaAsset` for you (`OpenSeaAsset`
82 | extends `Asset`):
83 |
84 | Owner Asset
85 |
86 | ```TypeScript
87 |
88 | const ownerAssets = (await seaport.getOwnerAssets({limit: 1}))
89 |
90 | ```
91 |
92 | Query Asset
93 |
94 | ```ts
95 |
96 | const assetsQuery = {
97 | assets: [{
98 | asset_contract_addresses, // string
99 | token_ids //string | number | null
100 | }],
101 | include_orders: true,
102 | } as AssetsQueryParams
103 |
104 | const assetFee = await seaport.getAssetsFees([asset_contract_addresses])
105 |
106 | const asset = await seaport.api.getAssets(assetsQuery)
107 | ```
108 |
109 | ### Making Offers
110 |
111 | Once you have your asset, you can do this to make an offer on it:
112 |
113 | ```JavaScript
114 | // Token ID and smart contract address for a non-fungible token:
115 | const {tokenId, tokenAddress} = YOUR_ASSET
116 |
117 | const offer = await seaport.createBuyOrder({
118 | asset: {
119 | tokenId,
120 | tokenAddress,
121 | schemaName // WyvernSchemaName. If omitted, defaults to 'ERC721'. Other options include 'ERC20' and 'ERC1155'
122 | },
123 | // Value of the offer, in units of the payment token (or wrapped ETH if none is specified):
124 | startAmount: 1.2,
125 | })
126 | ```
127 |
128 | ### Making Listings / Selling Items
129 |
130 | To sell an asset, call `createSellOrder`. You can do a fixed-price listing, where `startAmount` is equal to `endAmount`,
131 | or a declining [Dutch auction](https://en.wikipedia.org/wiki/Dutch_auction), where `endAmount` is lower and the price
132 | declines until `expirationTime` is hit:
133 |
134 | ```JavaScript
135 | // Expire this auction one day from now.
136 | // Note that we convert from the JavaScript timestamp (milliseconds):
137 | const expirationTime = Math.round(Date.now() / 1000 + 60 * 60 * 24)
138 |
139 | const listing = await seaport.createSellOrder({
140 | asset: {
141 | tokenId,
142 | tokenAddress,
143 | schemaName,
144 | "collection": {
145 | royaltyFeeAddress,
146 | royaltyFeePoints
147 | }
148 | },
149 | startAmount: 3,
150 | // If `endAmount` is specified, the order will decline in value to that amount until `expirationTime`. Otherwise, it's a fixed-price order:
151 | endAmount: 0.1,
152 | expirationTime
153 | })
154 | ```
155 |
156 | ### Posting Order
157 |
158 | ```ts
159 | // const orderStr = JSON.stringify(offer)
160 | const orderStr = JSON.stringify(listing)
161 | const order = await seaport.postOrder(orderStr).catch((err: any) => {
162 | throw err
163 | })
164 | ```
165 |
166 | ### Fetching Orders
167 |
168 | To retrieve a list of offers and auction on an asset, you can use an instance of the `OpenSeaAPI` exposed on the client.
169 | Parameters passed into API filter objects are underscored instead of camel-cased, similar to the
170 | main [OpenSea API parameters](https://docs.opensea.io/v1.0/reference):
171 |
172 | ```JavaScript
173 | import {OrderSide} from 'seaport-js'
174 |
175 | // Get offers (bids), a.k.a. orders where `side == 0`
176 | const query = {
177 | asset_contract_address: tokenAddress, //
178 | token_ids: [tokenId]
179 | }
180 | const {orders, count} = await seaport.api.getOrders(query)
181 |
182 | // Get page 2 of all auctions, a.k.a. orders where `side == 1`
183 | const {orders, count} = await seaport.api.getOrders({
184 | asset_contract_address: tokenAddress,
185 | token_ids: [tokenId],
186 | side: OrderSide.Sell
187 | }, 2)
188 |
189 | // Get Owner Orders
190 | const {orders, count} = await seaport.getOwnerOrders()
191 | ```
192 |
193 | Note that the listing price of an asset is equal to the `currentPrice` of the **lowest valid sell order** on the asset.
194 | Users can lower their listing price without invalidating previous sell orders, so all get shipped down until they're
195 | cancelled or one is fulfilled.
196 |
197 | To learn more about signatures, makers, takers, listingTime vs createdTime and other kinds of order terminology, please
198 | read the [**Terminology Section**](https://docs.opensea.io/reference#terminology) of the API Docs.
199 |
200 | The available API filters for the orders endpoint is documented in the `OrderJSON` interface below, but see the
201 | main [API Docs](https://docs.opensea.io/reference#reference-getting-started) for a playground, along with more
202 | up-to-date and detailed explanantions.
203 |
204 | ```TypeScript
205 | /**
206 | * Attrs used by orderbook to make queries easier
207 | * More to come soon!
208 | */
209 | maker ? : string, // Address of the order's creator
210 | taker ? : string, // The null address if anyone is allowed to take the order
211 | side ? : OrderSide, // 0 for offers, 1 for auctions
212 | owner ? : string, // Address of owner of the order's asset
213 | asset_contract_address ? : string, // Contract address for order's asset
214 | token_ids ? : Array < number | string >
215 |
216 | // For pagination
217 | limit ? : number,
218 | offset ? : number
219 | ```
220 |
221 | ### Buying Items
222 |
223 | To buy an item , you need to **fulfill a sell order**. To do that, it's just one call:
224 |
225 | ```JavaScript
226 | const orders = await seaport.api.getOrders({side: OrderSide.Sell, ...})
227 | const tx = await seaport.fulfillOrder(JSON.stringify(orders[0]))
228 | console.log(tx.hash)
229 | await tx.wait()
230 |
231 | const orderList = [{orderStr: JSON.stringify(order[0])}, {orderStr: JSON.stringify(order[0])}]
232 | const res = await sdk.fulfillOrders({orderList})
233 | console.log(res.hash)
234 | await res.wait()
235 | ```
236 |
237 | Note that the `fulfillOrder` promise resolves when the transaction has been confirmed and mined to the blockchain. To
238 | get the transaction hash before this happens, add an event listener (see [Listening to Events](#listening-to-events))
239 | for the `TransactionCreated` event.
240 |
241 | If the order is a sell order (`order.side === OrderSide.Sell`), the taker is the *buyer* and this will prompt the buyer
242 | to pay for the item(s).
243 |
244 | ### Accepting Offers
245 |
246 | Similar to fulfilling sell orders above, you need to fulfill a buy order on an item you own to receive the tokens in the
247 | offer.
248 |
249 | ```JavaScript
250 | const orders = await seaport.api.getOrders({side: OrderSide.Buy, ...})
251 | const tx = await seaport.fulfillOrder(JSON.stringify(orders[0]))
252 | console.log(tx.hash)
253 | await tx.wait()
254 | ```
255 |
256 | If the order is a buy order (`order.side === OrderSide.Buy`), then the taker is the *owner* and this will prompt the
257 | owner to exchange their item(s) for whatever is being offered in return. See [Listening to Events](#listening-to-events)
258 | below to respond to the setup transactions that occur the first time a user accepts a bid.
259 |
260 | ### Transferring Items or Coins (Gifting)
261 |
262 | A handy feature in OpenSea.js is the ability to transfer any supported asset (fungible or non-fungible tokens) in one
263 | line of JavaScript.
264 |
265 | To transfer an ERC-721 asset or an ERC-1155 asset, it's just one call:
266 |
267 | ```JavaScript
268 |
269 | const transactionHash = await seaport.transfer({
270 | asset: {tokenId, tokenAddress},
271 | fromAddress, // Must own the asset
272 | toAddress
273 | })
274 | ```
275 |
276 | For fungible ERC-1155 assets, you can set `schemaName` to "ERC1155" and pass a `quantity` in to transfer multiple at
277 | once:
278 |
279 | ```JavaScript
280 |
281 | const transactionHash = await seaport.transfer({
282 | asset: {
283 | tokenId,
284 | tokenAddress,
285 | schemaName: "ERC1155"
286 | },
287 | toAddress,
288 | quantity: 2,
289 | })
290 | ```
291 |
292 | #### Checking Balances and Ownerships
293 |
294 | The nice thing about the `Asset` type is that it unifies logic between fungibles, non-fungibles, and semi-fungibles.
295 |
296 | Once you have an `Asset`, you can see how many any account owns, regardless of whether it's an ERC-20 token or a
297 | non-fungible good:
298 |
299 | ```JavaScript
300 |
301 | const asset = {
302 | tokenId: '9',
303 | tokenAddress: '0xb556f251eacbec4badbcddc4a146906f2c095bee',
304 | schemaName: 'ERC721'
305 | }
306 |
307 | const balance = await seaport.getAssetBalances(asset, accountAddress)
308 |
309 | ```
310 |
311 | You can use this same method for fungible ERC-20 tokens like wrapped ETH (WETH). As a convenience, you can use this
312 | fungible wrapper for checking fungible balances:
313 |
314 | ```JavaScript
315 | const balanceOfWETH = await seaport.getTokenBalance({
316 | accountAddress, // string
317 | tokenAddress: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
318 | })
319 | ```
320 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | [webpack](./node_modules/react-scripts/config/webpack.config.js)
4 |
5 |
6 |
7 | ``ts
8 | resolve:{
9 | fallback: {
10 | "crypto": require.resolve("crypto-browserify"),
11 | },
12 | }
13 |
14 | ``
15 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "seaport-js",
3 | "version": "0.0.0",
4 | "private": true,
5 | "homepage": "https://web3w.github.io/seaport-js",
6 | "dependencies": {
7 | "@ant-design/icons": "^4.7.0",
8 | "ahooks": "^3.5.2",
9 | "antd": "^4.21.6",
10 | "moment": "^2.29.4",
11 | "react": "^18.2.0",
12 | "react-dom": "^18.2.0",
13 | "react-scripts": "5.0.1",
14 | "web3-accounts": "^1.2.12",
15 | "web3-qrcode-modal": "^1.0.6",
16 | "web3-wallets": "^2.2.5"
17 | },
18 | "scripts": {
19 | "start": "GENERATE_SOURCEMAP=false react-scripts --openssl-legacy-provider start",
20 | "build": "GENERATE_SOURCEMAP=false react-scripts build",
21 | "link": "sudo npm link ..",
22 | "deploy": "npm run build && gh-pages -d build"
23 | },
24 | "devDependencies": {
25 | "assert": "^2.0.0",
26 | "gh-pages": "^4.0.0",
27 | "https-browserify": "^1.0.0",
28 | "net": "^1.0.2",
29 | "os-browserify": "^0.3.0",
30 | "stream-http": "^3.2.0",
31 | "tls": "^0.0.1",
32 | "typescript": "~4.6.4",
33 | "url": "^0.11.0",
34 | "web3": "^1.7.3"
35 | },
36 | "eslintConfig": {
37 | "extends": [
38 | "react-app",
39 | "react-app/jest"
40 | ]
41 | },
42 | "browserslist": {
43 | "production": [
44 | ">0.2%",
45 | "not dead",
46 | "not op_mini all"
47 | ],
48 | "development": [
49 | "last 1 chrome version",
50 | "last 1 firefox version",
51 | "last 1 safari version"
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/web3w/seaport-js/e614fe5e3794ea0c1dd40c80dd67b1a590245cb7/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
17 |
18 |
27 | Seaport
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/example/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/example/src/AppContext.js:
--------------------------------------------------------------------------------
1 | import React, {createContext, useEffect, useState} from "react";
2 | import {Web3Wallets} from "web3-wallets";
3 | import {SeaportSDK} from "seaport-js";
4 |
5 | export const Context = createContext();
6 | export const AppContext = ({children}) => {
7 | const [wallet, setWallet] = useState({});
8 | const [sdk, setSdk] = useState({});
9 | useEffect(() => {
10 | // setLoading(true);
11 | async function init() {
12 | console.log("AppContext: wallet change")
13 | const wallet = new Web3Wallets('metamask')
14 | await wallet.connect()
15 | setWallet(wallet)
16 | const {chainId, address} = wallet
17 | const sdk = new SeaportSDK({chainId, address})
18 | setSdk(sdk)
19 | }
20 |
21 | init()
22 | }, [])
23 | return (
24 | {children}
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/example/src/assets/css/index.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | height: 30px;
3 | margin: 16px;
4 | font-size: 16px;
5 | color: #f0f2f5;
6 | }
7 |
8 | .site-layout .site-layout-background {
9 | background: #fff;
10 | }
11 |
--------------------------------------------------------------------------------
/example/src/assets/images/coinbaseWallet.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/example/src/assets/images/metamask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/web3w/seaport-js/e614fe5e3794ea0c1dd40c80dd67b1a590245cb7/example/src/assets/images/metamask.png
--------------------------------------------------------------------------------
/example/src/assets/images/wallet-metamask-fox.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/src/assets/images/walletconnect-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/example/src/assets/images/walletconnect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/web3w/seaport-js/e614fe5e3794ea0c1dd40c80dd67b1a590245cb7/example/src/assets/images/walletconnect.png
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import "antd/dist/antd.css";
3 | import {createRoot} from 'react-dom/client';
4 | import {MainLayout} from './page/index'
5 | import {AppContext} from "./AppContext";
6 |
7 | const rootDiv = document.getElementById('root');
8 | const root = createRoot(rootDiv);
9 | // 装载
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/example/src/js/config.js:
--------------------------------------------------------------------------------
1 | import walletConnectIcon from "../assets/images/walletconnect.png";
2 | import metamaskIcon from "../assets/images/metamask.png";
3 | import coinbaseIcon from "../assets/images/coinbaseWallet.svg";
4 |
5 | export {walletConnectIcon, metamaskIcon, coinbaseIcon}
6 |
7 | export const msg712sign = {
8 | types: {
9 | EIP712Domain: [
10 | {name: 'name', type: 'string'},
11 | {name: 'version', type: 'string'},
12 | {name: 'chainId', type: 'uint256'},
13 | {name: 'verifyingContract', type: 'address'},
14 | ],
15 | Person: [
16 | {name: 'name', type: 'string'},
17 | {name: 'wallet', type: 'address'},
18 | ],
19 | Mail: [
20 | {name: 'from', type: 'Person'},
21 | {name: 'to', type: 'Person'},
22 | {name: 'contents', type: 'string'},
23 | ],
24 | },
25 | primaryType: 'Mail',
26 | domain: {
27 | name: 'Ether Mail',
28 | version: '1',
29 | chainId: '1',
30 | verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
31 | },
32 | message: {
33 | from: {
34 | name: 'Cow',
35 | wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
36 | },
37 | to: {
38 | name: 'Bob',
39 | wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
40 | },
41 | contents: 'Hello, Bob!',
42 | },
43 | };
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/example/src/js/helper.js:
--------------------------------------------------------------------------------
1 |
2 | function addZero(m) {
3 | return m < 10 ? '0' + m : m;
4 | }
5 |
6 | export function transformTime(timestamp = +new Date()) {
7 | if (timestamp) {
8 | const time = new Date(timestamp);
9 | const y = time.getFullYear();
10 | const M = time.getMonth() + 1;
11 | const d = time.getDate();
12 | const h = time.getHours();
13 | const m = time.getMinutes();
14 | const s = time.getSeconds();
15 | return y + '-' + addZero(M) + '-' + addZero(d) + ' ' + addZero(h) + ':' + addZero(m) + ':' + addZero(s);
16 | } else {
17 | return '';
18 | }
19 | }
20 |
21 | export const transformDate = (seconds) => {
22 | const dateFormat = 'YY-MM-DD HH:mm';
23 | return transformTime(Number(seconds) * 1000)
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/example/src/js/walletAction.js:
--------------------------------------------------------------------------------
1 | import {message, notification} from "antd";
2 |
3 | import {utils} from "web3-wallets";
4 | import {msg712sign} from "./config";
5 |
6 | import {Web3Accounts, MockContract} from 'web3-accounts';
7 |
8 |
9 | export const walletAction = async (wallet, action) => {
10 | const account = new Web3Accounts({address: wallet.address, chainId: wallet.chainId})
11 |
12 | const mock = new MockContract(wallet)
13 | if (!wallet) {
14 | message.error('Please select wallet');
15 | return
16 | }
17 | const walletName = wallet.walletName
18 | const {walletSigner, walletProvider} = wallet
19 |
20 | if (action == 'Connect') {
21 | if (walletProvider.walletName == 'wallet_connect') {
22 | const {walletName, wc} = walletProvider
23 | const {clientMeta, peerId, peerMeta, uri, session} = wc
24 | const {name, description} = peerMeta
25 | // debugger
26 | notification['info']({
27 | message: walletName,
28 | description: `Please open ${description} App`,
29 | });
30 | } else {
31 | const addresses = await wallet.connect()
32 | notification["info"]({
33 | message: `Connect ${walletName}`,
34 | description: addresses
35 | });
36 | }
37 | }
38 |
39 | if (action == "DisConnect") {
40 | if (walletProvider.walletName == 'wallet_connect') {
41 | const {walletName, wc} = walletProvider
42 | debugger
43 | const {clientMeta, peerId, peerMeta, uri, session} = wc
44 | if (!peerMeta) return
45 | const {name, description} = peerMeta
46 |
47 | notification['info']({
48 | message: walletName + "-" + action,
49 | description: `DisConnect ${description} App`,
50 | });
51 | walletProvider.close()
52 | }
53 | }
54 |
55 | if (action == 'SignMessage') {
56 | const signInfo = prompt("Sign Info")
57 | const signature = await wallet.signMessage(signInfo)
58 | notification["info"]({
59 | message: `SignMessage ${walletName}`,
60 | description: signature
61 | });
62 | }
63 |
64 | if (action == 'SignTypedData') {
65 | const signature = await wallet.signTypedData(msg712sign)
66 | notification["info"]({
67 | message: `SignTypedData ${walletName}`,
68 | description: signature
69 | });
70 | }
71 |
72 | if (action == 'GetBalance') {
73 | const balance = await walletSigner.getBalance()
74 | const eth = utils.formatEther(balance)
75 |
76 | const msg = `Address: ${wallet.address}
77 | ChainId: ${wallet.chainId}
78 | Balance: ${eth}ETH`
79 | notification["info"]({
80 | message: `GetBalance ${walletName}`,
81 | description: msg
82 | });
83 | }
84 |
85 | if (action == 'wethDeposit') {
86 | // const iface = new ethers.utils.Interface(['function migrate()']);
87 | // const callData = iface.encodeFunctionData('migrate', []);
88 | // console.log('callData: ', callData.toString());
89 |
90 | if (walletProvider.walletName == 'wallet_connect') {
91 | const {walletName, wc} = walletProvider
92 | const {clientMeta, peerId, peerMeta, uri, session} = wc
93 | if (!peerMeta) return
94 | const {name, description} = peerMeta
95 | notification['info']({
96 | message: walletName,
97 | description: `Please open ${description} App accept WethDeposit`,
98 | });
99 | }
100 | debugger
101 | const ethBal = await account.getGasBalances()
102 | debugger
103 | if (ethBal == "0") {
104 | message.error("WETH balance eq 0")
105 | return
106 | } else {
107 | await account.wethDeposit(ethBal)
108 | }
109 |
110 | }
111 |
112 | if (action == 'wethWithdraw') {
113 | // const iface = new ethers.utils.Interface(['function migrate()']);
114 | // const callData = iface.encodeFunctionData('migrate', []);
115 | // console.log('callData: ', callData.toString());
116 |
117 | if (walletProvider.walletName == 'wallet_connect') {
118 | const {walletName, wc} = walletProvider
119 |
120 | const {clientMeta, peerId, peerMeta, uri, session} = wc
121 | if (!peerMeta) return
122 | const {name, description} = peerMeta
123 | notification['info']({
124 | message: walletName,
125 | description: `Please open ${description} App accept WethWithdraw`,
126 | });
127 | }
128 | // const wethBal = await account.getTokenBalances({tokenAddr: account.GasWarpperContract.address})
129 |
130 | const wethBal = await account.wethBalances()
131 | if (wethBal == "0") {
132 | message.error("WETH balance eq 0")
133 | return
134 | } else {
135 | debugger
136 | await account.wethWithdraw("1")
137 |
138 | debugger
139 | }
140 | }
141 |
142 | if (action == 'wethBalances') {
143 | // const iface = new ethers.utils.Interface(['function migrate()']);
144 | // const callData = iface.encodeFunctionData('migrate', []);
145 | // console.log('callData: ', callData.toString());
146 |
147 | const tokenAddr = account.GasWarpperContract.address
148 | const wethBal = await account.getTokenBalances({tokenAddr})
149 |
150 | const msg = `WETH_Address: ${tokenAddr}
151 | Balance: ${wethBal} WETH`
152 | notification["info"]({
153 | message: `GetBalance ${walletName}`,
154 | description: msg
155 | });
156 | }
157 |
158 | if (action == 'mint') {
159 | const mint721 = await mock.Mock721.mint()
160 | const mint721Tx = await mint721.wait()
161 | console.log("tokenId:", mint721Tx.events[0].args.tokenId.toString())
162 | }
163 |
164 | if (action == 'transfer') {
165 |
166 | }
167 |
168 | if (action == 'AddChain') {
169 | walletProvider.addChainId(56)
170 | }
171 |
172 | if (action == 'AddToken') {
173 | walletProvider.addToken()
174 | }
175 |
176 | if (action == "SwitchChain") {
177 | walletProvider.switchBSCTEST()
178 | }
179 |
180 | // if(action =="scanQRCode"){
181 | // walletProvider.scanQRCode()
182 | // }
183 |
184 | };
185 |
186 | // if (action == 'Get1559Fee') {
187 | // const {walletSigner, walletProvider} = wallet
188 | // const {chainId} = walletProvider
189 | // console.log(await walletSigner.getBalance())
190 | // console.log(RPC_PROVIDER[chainId])
191 | // if (chainId.toString() == '56' || chainId.toString() == '97') {
192 | // return
193 | // }
194 | // // console.log(await walletSDK.get1559Fee())
195 | // }
196 |
--------------------------------------------------------------------------------
/example/src/page/SeaportBuy.js:
--------------------------------------------------------------------------------
1 | import {Button, Col, Form, Input, Row, Table, Select, DatePicker, Tag, Space} from 'antd';
2 | import React, {useContext, useEffect, useState} from "react";
3 | import {Context} from '../AppContext'
4 | import moment from "moment";
5 | import {useAntdTable} from "ahooks";
6 | import {utils} from 'web3-wallets'
7 | import Avatar from "antd/es/avatar/avatar";
8 | import {transformDate, transformTime} from "../js/helper";
9 |
10 | const {RangePicker} = DatePicker;
11 | const {Option} = Select;
12 |
13 |
14 | export function SeaportBuy() {
15 | const {sdk} = useContext(Context);
16 | const [form] = Form.useForm();
17 | const userAccount = "userAccountuserAccount"//sdk.walletInfo.address
18 | const dateFormat = 'YYYY-MM-DD HH:mm';
19 | const defaultDateRange = [
20 | moment(transformTime(new Date().getTime() - 24 * 60 * 60 * 1000), dateFormat),
21 | moment(transformTime(Date.now()), dateFormat)
22 | ]
23 |
24 | const [orders, setOrders] = useState([])
25 |
26 | const defaultExpandable = {
27 | expandedRowRender: (record) => (
28 | [,
29 | TokenId:{record.takerAssetBundle.assets[0].token_id}])
30 | };
31 | const [expandable, setExpandable] = useState(
32 | defaultExpandable,
33 | );
34 |
35 | const handleExpandChange = (enable) => {
36 | setExpandable(enable ? defaultExpandable : undefined);
37 | };
38 |
39 | const seaportBuyOrder = async (item) => {
40 |
41 | await sdk.fulfillOrder(JSON.stringify(item))
42 | }
43 |
44 | const columns = [
45 | {
46 | title: 'side',
47 | dataIndex: 'side'
48 | },
49 | {
50 | title: 'makerAsset',
51 | dataIndex: 'makerAssetBundle',
52 | render: (text, record) => ({text.asset_contract.name})
53 | }, {
54 | title: 'takerAsset',
55 | dataIndex: 'takerAssetBundle',
56 | render: (text, record) => ([
57 | {text.asset_contract.name},
58 | {text.asset_contract.schema_name},
59 | Fee({text.asset_contract.seller_fee_basis_points})
60 | ])
61 | },
62 | {
63 | title: 'currentPrice',
64 | dataIndex: 'currentPrice',
65 | render: (text, record) => ({utils.formatEther(text)})
66 | },
67 | {
68 | title: 'expirationTime',
69 | dataIndex: 'expirationTime',
70 | render: (text, record) => ({transformDate(text)})
71 | },
72 | {
73 | title: 'Action',
74 | dataIndex: 'state',
75 | render: (text, record) => ()
76 | }
77 | ];
78 |
79 | // useEffect(() => {
80 | // async function fetchOrder() {
81 | // const orders = await sdk.api.getOrders({side: 1, limit: 5})
82 | // debugger
83 | // console.log(orders)
84 | // setOrders(orders)
85 | // }
86 | //
87 | // fetchOrder().catch(err => {
88 | // // message.error(err);
89 | // console.log(err)
90 | // })
91 | // }, [sdk]);
92 | const getEventLogs = async (page, formData, host) => {
93 | if (!formData.account) throw new Error("eventLogs account undefined")
94 | const res = await sdk.api.getOrders({side: 0, limit: 20})
95 |
96 | const orders = res.orders.filter(val=>val.makerAssetBundle.asset_contract.address !="0x495f947276749ce646f68ac8c248420045cb7b5e")
97 | // debugger
98 |
99 | return {
100 | total: orders.length < 50 ? orders.length : 50,
101 | list: orders,
102 | }
103 | }
104 | const {tableProps, search, loading} =
105 | useAntdTable((page, formData) => getEventLogs(page, formData),
106 | {
107 | defaultPageSize: 10,
108 | form
109 | });
110 |
111 | // const {type, changeType, submit, reset} = search;
112 | const {submit} = search;
113 |
114 |
115 | if (!loading) {
116 | console.log("Data", loading, tableProps)
117 | }
118 | const onChange = (value, dateString) => {
119 | console.log('Selected Time: ', value);
120 | console.log('Formatted Selected Time: ', dateString);
121 | };
122 |
123 | const onOk = (value) => {
124 | console.log('onOk: ', value);
125 | };
126 |
127 | const advanceSearchForm = (
128 |
159 | );
160 |
161 | return (
162 |
163 | {advanceSearchForm}
164 | {/*//expandable={expandable}*/}
165 |
167 |
168 | );
169 | }
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/example/src/page/SeaportSell.js:
--------------------------------------------------------------------------------
1 | import {Button, Col, Divider, List, message, Row, Space, Table} from "antd";
2 | import React, {useContext, useEffect, useState} from "react";
3 | import {Context} from '../AppContext'
4 | import Avatar from "antd/es/avatar/avatar";
5 | import {coinbaseIcon, metamaskIcon, walletConnectIcon} from "../js/config";
6 | import {utils} from "web3-wallets";
7 | import {transformDate} from "../js/helper";
8 |
9 | export function SeaportSell() {
10 | const {sdk} = useContext(Context);
11 | const [assets, setAssets] = useState([])
12 | const [orders, setOrders] = useState([])
13 |
14 | const seaportGetOrder = async (item) => {
15 | const order = await sdk.api.getOrders({
16 | asset_contract_address: item.address,
17 | token_ids: [item.token_id]
18 | })
19 | console.log(order)
20 | setOrders(order.orders)
21 | message.success("Seaport post order success")
22 | }
23 |
24 | const columns = [
25 | {
26 | title: 'side',
27 | dataIndex: 'side'
28 | },
29 | {
30 | title: 'currentPrice',
31 | dataIndex: 'currentPrice',
32 | render: (text, record) => ({utils.formatEther(text)})
33 | },
34 | {
35 | title: 'expirationTime',
36 | dataIndex: 'expirationTime',
37 | render: (text, record) => ({transformDate(text)})
38 | },
39 | {
40 | title: 'Action',
41 | dataIndex: 'state',
42 | render: (text, record) => ()
43 | }
44 | ];
45 | const seaportCreateSellOrder = async (item) => {
46 | const order = await sdk.createSellOrder({
47 | asset: {
48 | tokenAddress: item.address,
49 | tokenId: item.token_id,
50 | schemaName: item.schema_name,
51 | collection: {
52 | royaltyFeeAddress: item.royaltyFeeAddress,
53 | royaltyFeePoints: item.royaltyFeePoints
54 | }
55 | },
56 | startAmount: 0.99
57 | }
58 | )
59 | const orderStr = JSON.stringify(order)
60 | const orderRes = await sdk.postOrder(orderStr)
61 | message.success("Seaport post sell order success")
62 | }
63 |
64 | const seaportCreateBuyOrder = async (item) => {
65 | const order = await sdk.createBuyOrder({
66 | asset: {
67 | tokenAddress: item.address,
68 | tokenId: item.token_id,
69 | schemaName: item.schema_name,
70 | collection: {
71 | royaltyFeeAddress: item.royaltyFeeAddress,
72 | royaltyFeePoints: item.royaltyFeePoints
73 | }
74 | },
75 | startAmount: 0.001
76 | }
77 | )
78 | const orderStr = JSON.stringify(order)
79 | const orderRes = await sdk.postOrder(orderStr)
80 | message.success("Seaport post buy order success")
81 | }
82 | const seaportCancelOrder = async (item) => {
83 | await sdk.contracts.cancelOrders([item.protocolData.parameters])
84 | }
85 | useEffect(() => {
86 | async function fetchOrder() {
87 | const assets = await sdk.getOwnerAssets()
88 | // console.log(orders)
89 | setAssets(assets)
90 | }
91 |
92 | fetchOrder().catch(err => {
93 | // message.error(err);
94 | console.log(err)
95 | })
96 | }, [sdk]);
97 |
98 | const walletItems = [
99 | {symbol: 'MetaMask', key: 'metamask', icon: metamaskIcon, description: "Popular wallet"},
100 | {symbol: 'WalletConnect', key: 'wallet_connect', icon: walletConnectIcon, description: "mobile only"},
101 | {symbol: 'CoinBase', key: 'coinbase', icon: coinbaseIcon, description: "coinbase wallet"}
102 | ];
103 | return (
104 | <>
105 | {assets.length > 0 && (
111 |
112 | }
114 | title={{item.name}}
115 | description={item.description}
116 | />
117 |
118 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | {/*{()=>actions(item)}*/}
127 |
128 | )}
129 | />}
130 |
131 |
132 | >
133 | )
134 | }
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/example/src/page/WalletList.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useState} from "react";
2 | import {Button, Divider, List, Space} from "antd";
3 | import {Context} from "../AppContext";
4 | import Avatar from "antd/es/avatar/avatar";
5 | import QRCodeModal from "web3-qrcode-modal";
6 | import {Web3Wallets} from 'web3-wallets';
7 |
8 | import {metamaskIcon, coinbaseIcon, walletConnectIcon} from "../js/config"
9 |
10 | import {walletAction} from "../js/walletAction";
11 | import {Buffer} from "buffer";
12 | window.Buffer = Buffer;
13 | export function WalletList() {
14 | const {setWallet} = useContext(Context);
15 |
16 | const selectWallet = async (item, action) => {
17 | const newWallet = new Web3Wallets({name: item.key, qrcodeModal: QRCodeModal})
18 | if (item.key == 'metamask') {
19 | const provider = newWallet.walletProvider
20 | const accounts = await provider.connect() // enable ethereum
21 | setWallet(newWallet)
22 | provider.on('chainChanged', async (walletChainId) => {
23 | console.log('Matemask chainChanged', walletChainId)
24 | })
25 |
26 | provider.on('accountsChanged', async (accounts) => {
27 | console.log('Matemask accountsChanged', accounts)
28 |
29 | })
30 |
31 | }
32 | if (item.key == "wallet_connect") {
33 | const provider = newWallet.walletProvider
34 |
35 | if (provider.connected) {
36 | setWallet(newWallet)
37 | } else {
38 | if(action != "DisConnect"){
39 | await provider.open()
40 | }
41 | }
42 | provider.on('connect', async (error, payload) => {
43 | if (error) {
44 | throw error
45 | }
46 | debugger
47 | const {} = payload
48 | setWallet(newWallet)
49 | })
50 | provider.on('disconnect', async (error) => {
51 | if (error) {
52 | throw error
53 | }
54 | setWallet({})
55 | })
56 | }
57 |
58 | if (item.key == 'coinbase') {
59 | const accounts = await newWallet.walletProvider.connect() // enable ethereum
60 | setWallet(newWallet)
61 | }
62 |
63 | if (newWallet.chainId) {
64 | await walletAction(newWallet, action)
65 | }
66 |
67 | };
68 |
69 |
70 | const walletItems = [
71 | {title: 'MetaMask', key: 'metamask', icon: metamaskIcon, desc: "Popular wallet"},
72 | {title: 'WalletConnect', key: 'wallet_connect', icon: walletConnectIcon, desc: "mobile only"},
73 | {title: 'CoinBase', key: 'coinbase', icon: coinbaseIcon, desc: "coinbase wallet"}
74 | ];
75 |
76 | const accountFun = [
77 | {title: 'SignMessage', key: 'SignMessage', disabled: ['']},
78 | {title: 'SignTypedData', key: 'SignTypedData', disabled: ['']},
79 | {title: 'GetBalance', key: 'GetBalance', disabled: ['']}
80 | ];
81 |
82 | const contractFun = [
83 | {title: 'DepositWETH', key: 'wethDeposit', disabled: ['']},
84 | {title: 'WithdrawWETH', key: 'wethWithdraw', disabled: ['']},
85 | // {title: 'Transfer', key: 'transfer', disabled: ['']},
86 | ]
87 |
88 | const walletFun = [
89 | {title: 'Connect', key: 'Connect', disabled: ['']},
90 | {title: 'DisConnect', key: 'DisConnect', disabled: ['metamask']},
91 | {title: 'AddChain', key: 'AddChain', disabled: ['wallet_connect']},
92 | {title: 'AddToken', key: 'AddToken', disabled: ['wallet_connect', 'coinbase']},
93 | {title: 'SwitchChain', key: 'SwitchChain', disabled: ['wallet_connect']}
94 | ]
95 |
96 | const accountActions = (item) => accountFun.map(val => {
97 | return
99 | })
100 |
101 | const contractActions = (item) => contractFun.map(val => {
102 | return
104 | })
105 |
106 | const walletActions = (item) => walletFun.map(val => {
107 | return
109 | })
110 |
111 | return (
112 | <>
113 | (
119 |
120 | }
122 | title={{item.title}}
123 | description={item.desc}
124 | />
125 | {/*{"ddddd"}*/}
126 |
127 | Account Actions {accountActions(item)}
128 |
129 |
130 |
131 | Contract Actions {contractActions(item)}
132 |
133 |
134 |
135 | Wallet Actions {walletActions(item)}
136 |
137 |
138 |
139 | {/*{()=>actions(item)}*/}
140 |
141 | )}
142 | />
143 | >
144 | )
145 | }
146 |
--------------------------------------------------------------------------------
/example/src/page/index.js:
--------------------------------------------------------------------------------
1 | import {message, Layout, Descriptions, Menu} from 'antd';
2 | import React, {useContext, useState} from "react";
3 | import "../assets/css/index.css"
4 | import {Context} from '../AppContext'
5 | import {SeaportSell} from "./SeaportSell";
6 | import {SeaportBuy} from "./SeaportBuy";
7 | import {WalletList} from "./WalletList";
8 | // import {WalletFunc} from "./WalletFunc";
9 | import pkg from "../../package.json"
10 |
11 |
12 | const {Header, Content, Footer, Sider} = Layout;
13 |
14 | export function MainLayout() {
15 | const {wallet, sdk} = useContext(Context);
16 | const [collapsed, setCollapsed] = useState(false);
17 | const [page, setPage] = useState("seaport_sell");
18 |
19 |
20 | const items = [
21 | {label: 'SeaportSell', key: 'seaport_sell'},
22 | {label: 'SeaportBuy', key: 'seaport_buy'},
23 | {label: 'Wallets', key: 'wallets'},
24 | ];
25 | return (
26 | //
27 |
28 |
29 | {`${pkg.name}@${sdk.version}`}
30 |
36 |
37 |
38 | {wallet.walletName &&
39 | {wallet.walletName}
40 |
41 | {wallet.walletProvider.chainId}
42 |
43 | {wallet.walletProvider.address}
44 | {wallet.walletProvider.peerMetaName &&
45 | {wallet.walletProvider.peerMetaName}}
47 | }
48 |
49 | {page == "wallets" && }
50 | {page == "seaport_sell" && }
51 | {page == "seaport_buy" && }
52 |
53 |
54 | //
55 | )
56 | }
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | export {SwapEx} from './src/swapEx/swapEx'
2 | export {SeaportSDK} from './src/index'
3 | export {Seaport} from './src/seaport'
4 | export {SeaportAPI} from "./src/api/seaport"
5 | export type {OrderV2} from "./src/api/types"
6 | export {seaportAssert} from "./src/utils/assert"
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "seaport-js",
3 | "version": "1.2.0",
4 | "description": "Javascript SDK for the seaport protocol",
5 | "main": "lib/index.js",
6 | "type": "lib/index.d.ts",
7 | "author": "Web3 Project Developers",
8 | "license": "MIT",
9 | "homepage": "https://www.npmjs.com/package/seaport-js",
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/web3w/seaport-js.git"
13 | },
14 | "bugs": {
15 | "url": "https://github.com/web3w/seaport-js/issues"
16 | },
17 | "files": [
18 | "lib"
19 | ],
20 | "scripts": {
21 | "release": "tsc && npm publish && git tag $npm_package_version && git push --tag",
22 | "start": "tsc -w -p tsconfig.json",
23 | "link": "sudo npm link seaport-js"
24 | },
25 | "dependencies": {
26 | "merkletreejs": "^0.2.32",
27 | "query-string": "^7.1.1",
28 | "web3-assert": "^1.0.5",
29 | "web3-accounts": "1.4.0",
30 | "web3-wallets": "^2.4.1"
31 | },
32 | "devDependencies": {
33 | "typescript": "4.8.2"
34 | },
35 | "publishConfig": {
36 | "access": "public"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/api/config.ts:
--------------------------------------------------------------------------------
1 | export const OPENSEA_API_KEY = "2f6f419a083c46de9d83ce3dbe7db601" // common
2 | //Api Timeout
3 | export const OPENSEA_API_TIMEOUT = 20000
4 |
5 | export const CHAIN_PATH: { [key: string]: string } = {
6 | 1: 'ethereum',
7 | 4: 'rinkeby'
8 | }
9 |
10 | //has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
11 | export const OPENSEA_API_CONFIG = {
12 | 1: {apiBaseUrl: 'https://api.opensea.io'},
13 | 4: {apiBaseUrl: 'https://testnets-api.opensea.io'}
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/api/seaport.ts:
--------------------------------------------------------------------------------
1 | import {sleep} from "web3-wallets";
2 | import QueryString from "querystring";
3 | import {
4 | APIConfig
5 | } from "../types";
6 |
7 | import {
8 | AssetCollection,
9 | OrdersQueryParams,
10 | AssetsQueryParams
11 | } from "./types"
12 |
13 | import {OPENSEA_API_TIMEOUT, OPENSEA_API_CONFIG, OPENSEA_API_KEY, CHAIN_PATH} from "./config";
14 | import {OrderSide, BaseFetch} from "web3-accounts"
15 | import {ItemType} from "../constants";
16 | import {OrderV2} from "./types";
17 | import {deserializeOrder} from "./utils";
18 | import {seaportAssert} from "../utils/assert";
19 |
20 | const {validateOrderV2, validateOrderWithCounter} = seaportAssert
21 |
22 | export class SeaportAPI extends BaseFetch {
23 | public chainPath: string
24 |
25 | constructor(
26 | config?: APIConfig
27 | ) {
28 | const chainId = config?.chainId || 1
29 | const apiBaseUrl = config?.apiBaseUrl || OPENSEA_API_CONFIG[chainId].apiBaseUrl
30 | super({
31 | apiBaseUrl,
32 | apiKey: config?.apiKey || OPENSEA_API_KEY
33 | })
34 | this.chainPath = CHAIN_PATH[chainId]
35 | if (OPENSEA_API_CONFIG[chainId]) {
36 | this.proxyUrl = config?.proxyUrl
37 | this.apiTimeout = config?.apiTimeout || OPENSEA_API_TIMEOUT
38 | } else {
39 | throw 'OpenseaAPI unsport chainId:' + config?.chainId
40 | }
41 | }
42 |
43 | //https://docs.opensea.io/reference/getting-assets
44 | // https://api.opensea.io/api/v1/assets?asset_contract_address=0xbd3531da5cf5857e7cfaa92426877b022e612cf8
45 | public async getAssets(queryParams: AssetsQueryParams, retries = 2): Promise {
46 | const {owner, include_orders, limit, assets} = queryParams
47 | const list = assets ? assets.map((val: any) => {
48 | return QueryString.stringify(val)
49 | }) : []
50 | const assetList = list.join('&')
51 | const query = {
52 | include_orders: include_orders || false,
53 | limit: limit || 1
54 | }
55 | if (owner) {
56 | query['owner'] = owner
57 | }
58 | const queryUrl = list.length > 0
59 | ? `${QueryString.stringify(query)}&${assetList}`
60 | : QueryString.stringify(query)
61 |
62 | try {
63 | //https://testnets-api.opensea.io/api/v1/assets?include_orders=false&limit=1&owner=0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401
64 | console.log("getAssets", `${this.apiBaseUrl}/api/v1/assets?${queryUrl}`)
65 | const json = await this.getQueryString('/api/v1/assets', queryUrl)
66 |
67 | // json.assets.collection.dev_seller_fee_basis_points
68 | // json.assets.asset_contract.dev_seller_fee_basis_points
69 | return json.assets.map(val => ({
70 | ...val.asset_contract,
71 | royaltyFeePoints: Number(val.collection?.dev_seller_fee_basis_points),
72 | protocolFeePoints: Number(val.collection?.opensea_seller_fee_basis_points),
73 | royaltyFeeAddress: val.collection?.payout_address,
74 | sell_orders: val.sell_orders,
75 | token_id: val.token_id,
76 | supports_wyvern: val.supports_wyvern
77 | }))
78 | } catch (error: any) {
79 | this.throwOrContinue(error, retries)
80 | await sleep(3000)
81 | return this.getAssets(queryParams, retries - 1)
82 | }
83 | }
84 |
85 | public async getOrders(queryParams: OrdersQueryParams, retries = 2): Promise<{ orders: OrderV2[], count: number }> {
86 | // const {token_ids, asset_contract_address} = queryParams
87 | try {
88 | queryParams.limit = queryParams.limit || 2
89 | queryParams.order_by = queryParams.order_by || 'created_date'
90 |
91 | // if (!queryParams.limit) throw new Error("GetOrders params error")
92 |
93 |
94 | const headers = {
95 | "X-API-KEY": this.apiKey || OPENSEA_API_KEY
96 | }
97 |
98 | const reqList: any[] = []
99 |
100 | const apiPath = `/v2/orders/${this.chainPath}/seaport/`
101 | if (queryParams.side == OrderSide.All) {
102 | delete queryParams.side
103 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
104 | // @ts-ignore
105 | const query = QueryString.stringify(queryParams)
106 | const apiPathOffer = apiPath + 'offers'
107 | console.log(`${this.apiBaseUrl}${apiPathOffer}?${query}`)
108 | reqList.push(await this.get(apiPathOffer, queryParams, {headers}))
109 | const apiPathListing = apiPath + 'listings'
110 | console.log(`${this.apiBaseUrl}${apiPathListing}?${query}`)
111 | reqList.push(await this.get(apiPathListing, queryParams, {headers}))
112 |
113 | } else {
114 | const orderSide = queryParams.side ? 'offers' : 'listings'
115 | delete queryParams.side
116 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
117 | // @ts-ignore
118 | const query = QueryString.stringify(queryParams)
119 | console.log(`${this.apiBaseUrl}${apiPath + orderSide}?${query}`)
120 | reqList.push(await this.get(apiPath + orderSide, queryParams, {headers}))
121 |
122 | }
123 | const orders: OrderV2[] = []
124 |
125 | for (let i = 0; i < reqList.length; i++) {
126 | const orderList = reqList[i].orders
127 | if (!orderList) {
128 | throw new Error('Not found: no matching order found')
129 | }
130 |
131 | for (let j = 0; j < orderList.length; j++) {
132 | const order = deserializeOrder(orderList[j])
133 | if (validateOrderV2(order)) {
134 | orders.push(order)
135 | } else {
136 | console.log(validateOrderV2.errors)
137 | }
138 | }
139 | }
140 | return {
141 | orders,
142 | count: orders.length
143 | }
144 | } catch (error: any) {
145 | this.throwOrContinue(error, retries)
146 | await sleep(3000)
147 | return this.getOrders(queryParams, retries - 1)
148 | }
149 | }
150 |
151 |
152 | public async postOrder(orderStr: string, retries = 2): Promise<{ order: OrderV2 }> {
153 | const order = JSON.parse(orderStr)
154 | if (!validateOrderWithCounter(order)) throw validateOrderWithCounter.errors
155 | const {parameters, signature} = order
156 | // const order_parameters = converToPost(parameters)
157 | try {
158 | const opts = {
159 | headers: {
160 | 'X-API-KEY': this.apiKey || OPENSEA_API_KEY
161 | }
162 | }
163 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
164 | // @ts-ignore
165 | parameters.totalOriginalConsiderationItems = parameters.consideration.length
166 | // itemType = 1 ERC20 "itemType" = 2, ERC721..
167 | const offer = parameters.offer[0]
168 | const sidePath = offer.itemType == ItemType.ERC20 && offer.identifierOrCriteria == "0" ? 'offers' : 'listings'
169 |
170 | // const orderSide = side == OrderSide.Buy ? 'offers' : 'listings'
171 | const apiPath = `/v2/orders/${this.chainPath}/seaport/${sidePath}`
172 |
173 | // console.log(`${this.apiBaseUrl}${apiPath}`)
174 | // console.log(order)
175 | // console.log(signature)
176 | const result = await this.post(
177 | apiPath,
178 | order,
179 | opts
180 | ).catch((e: any) => {
181 | console.log(e)
182 | throw e
183 | })
184 | return result
185 | } catch (error: any) {
186 | this.throwOrContinue(error, retries)
187 | await sleep(3000)
188 | return this.postOrder(orderStr, retries)
189 | }
190 | }
191 |
192 | }
193 |
194 | // success
195 | // {
196 | // "parameters": {
197 | // "offerer": "0x32f4b63a46c1d12ad82cabc778d75abf9889821a",
198 | // "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00",
199 | // "orderType": 2,
200 | // "startTime": "1658844163",
201 | // "endTime": "1658847761",
202 | // "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
203 | // "salt": "63671428114909039",
204 | // "offer": [
205 | // {
206 | // "itemType": 2,
207 | // "token": "0x52f687b1c6aacc92b47da5209cf25d987c876628",
208 | // "identifierOrCriteria": "1",
209 | // "startAmount": "1",
210 | // "endAmount": "1"
211 | // }
212 | // ],
213 | // "consideration": [
214 | // {
215 | // "itemType": 0,
216 | // "token": "0x0000000000000000000000000000000000000000",
217 | // "identifierOrCriteria": "0",
218 | // "startAmount": "925000000000000000",
219 | // "endAmount": "925000000000000000",
220 | // "recipient": "0x32f4b63a46c1d12ad82cabc778d75abf9889821a"
221 | // },
222 | // {
223 | // "itemType": 0,
224 | // "token": "0x0000000000000000000000000000000000000000",
225 | // "identifierOrCriteria": "0",
226 | // "startAmount": "25000000000000000",
227 | // "endAmount": "25000000000000000",
228 | // "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90"
229 | // },
230 | // {
231 | // "itemType": 0,
232 | // "token": "0x0000000000000000000000000000000000000000",
233 | // "identifierOrCriteria": "0",
234 | // "startAmount": "50000000000000000",
235 | // "endAmount": "50000000000000000",
236 | // "recipient": "0x32f4b63a46c1d12ad82cabc778d75abf9889821a"
237 | // }
238 | // ],
239 | // "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000",
240 | // "counter": 0,
241 | // "totalOriginalConsiderationItems": 3
242 | // },
243 | // "signature": "0x88d4b8510925d34b49513ee78d5be83cb2602a4cb812e322a0e073238dbc880928259da9c963bdd5081d5cf0909fb84289030a1933fa5fb0695a6861752b764a1c"
244 | // }
245 |
246 |
247 | // {
248 | // "parameters": {
249 | // "offerer": "0x32f4b63a46c1d12ad82cabc778d75abf9889821a",
250 | // "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00",
251 | // "orderType": 2,
252 | // "startTime": "1658843602",
253 | // "endTime": "1658847201",
254 | // "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
255 | // "salt": "23144606790842543",
256 | // "offer": [
257 | // {
258 | // "itemType": 2,
259 | // "token": "0xb840ec0db3b9ab7b920710d6fc21a9d206f994aa",
260 | // "identifierOrCriteria": "686",
261 | // "startAmount": "1",
262 | // "endAmount": "1"
263 | // }
264 | // ],
265 | // "consideration": [
266 | // {
267 | // "itemType": 0,
268 | // "token": "0x0000000000000000000000000000000000000000",
269 | // "identifierOrCriteria": "0",
270 | // "startAmount": "832500000000000000",
271 | // "endAmount": "832500000000000000",
272 | // "recipient": "0x32f4b63a46c1d12ad82cabc778d75abf9889821a"
273 | // },
274 | // {
275 | // "itemType": 0,
276 | // "token": "0x0000000000000000000000000000000000000000",
277 | // "identifierOrCriteria": "0",
278 | // "startAmount": "22500000000000000",
279 | // "endAmount": "22500000000000000",
280 | // "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90"
281 | // },
282 | // {
283 | // "itemType": 0,
284 | // "token": "0x0000000000000000000000000000000000000000",
285 | // "identifierOrCriteria": "0",
286 | // "startAmount": "45000000000000000",
287 | // "endAmount": "45000000000000000",
288 | // "recipient": "0xb0e9fe550ca345daa529c603ed28ef869e2882cd"
289 | // }
290 | // ],
291 | // "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000",
292 | // "counter": 0,
293 | // "totalOriginalConsiderationItems": 3
294 | // },
295 | // "signature": "0x388e1d5e78090cb2175524bcc179b9996acbf5951bebb5967fae4c398f0c0b0761904d83310c48897d542af310a65ea20c83e3b801e7462d2be125c4d7bf36a21c"
296 | // }
297 |
--------------------------------------------------------------------------------
/src/api/types.ts:
--------------------------------------------------------------------------------
1 | //--------- Api ---------------------
2 |
3 | import {FeesInfo} from "web3-accounts";
4 |
5 | import {OpenSeaAccount, OrderWithCounter} from "../types";
6 |
7 | export interface OrdersQueryParams {
8 | token_ids?: string[]
9 | asset_contract_address?: string
10 | payment_token_address?: string
11 | include_bundled?: boolean
12 | maker?: string
13 | taker?: string
14 | side?: number
15 | owner?: string
16 | order_by?: string
17 | limit?: number
18 | offset?: number
19 | }
20 |
21 | export interface AssetsQueryParams {
22 | assets?: {
23 | asset_contract_addresses: string
24 | token_ids?: string
25 | }[],
26 | owner?: string
27 | limit?: number
28 | include_orders?: boolean
29 | }
30 |
31 | export interface AssetCollection extends FeesInfo {
32 | name: string
33 | symbol: string
34 | address?: string
35 | token_id?: string
36 | schema_name?: string
37 | nft_version?: string
38 | created_date?: string
39 | supports_wyvern?: boolean
40 | sell_orders?: any
41 | }
42 |
43 | // Protocol data
44 | type OrderProtocolToProtocolData = {
45 | seaport: OrderWithCounter;
46 | };
47 | export type OrderProtocol = keyof OrderProtocolToProtocolData;
48 | export type ProtocolData = OrderProtocolToProtocolData[OrderProtocol];
49 |
50 | // Protocol agnostic order data
51 | type OrderType = "basic" | "dutch" | "english" | "criteria";
52 | export type OrderSide = "ask" | "bid";
53 | export type OrderFee = {
54 | account: OpenSeaAccount;
55 | basisPoints: string;
56 | };
57 |
58 |
59 | export type OrderV2 = {
60 | createdDate: string;
61 | closingDate: string | null;
62 | listingTime: number;
63 | expirationTime: number;
64 | orderHash: string | null;
65 | maker: OpenSeaAccount;
66 | taker: OpenSeaAccount | null;
67 | protocolData: ProtocolData;
68 | protocolAddress: string;
69 | currentPrice: string;
70 | makerFees: OrderFee[];
71 | takerFees: OrderFee[];
72 | side: OrderSide;
73 | orderType: OrderType;
74 | cancelled: boolean;
75 | finalized: boolean;
76 | markedInvalid: boolean;
77 | clientSignature: string | null;
78 | makerAssetBundle: any;
79 | takerAssetBundle: any;
80 | };
81 |
82 |
83 | // API query types
84 | type OpenOrderOrderingOption = "created_date" | "eth_price";
85 | type OrderByDirection = "asc" | "desc";
86 |
87 | export type OrderAPIOptions = {
88 | protocol: OrderProtocol;
89 | side: OrderSide;
90 | };
91 |
92 | export type OrdersQueryOptions = OrderAPIOptions & {
93 | limit: number;
94 | cursor?: string;
95 |
96 | paymentTokenAddress?: string;
97 | maker?: string;
98 | taker?: string;
99 | owner?: string;
100 | bundled?: boolean;
101 | includeBundled?: boolean;
102 | listedAfter?: number | string;
103 | listedBefore?: number | string;
104 | tokenIds?: string[];
105 | assetContractAddress?: string;
106 | orderBy?: OpenOrderOrderingOption;
107 | orderDirection?: OrderByDirection;
108 | onlyEnglish?: boolean;
109 | };
110 |
111 | export type SerializedOrderV2 = {
112 | created_date: string;
113 | closing_date: string | null;
114 | listing_time: number;
115 | expiration_time: number;
116 | order_hash: string | null;
117 | maker: unknown;
118 | taker: unknown | null;
119 | protocol_data: ProtocolData;
120 | protocol_address: string;
121 | current_price: string;
122 | maker_fees: {
123 | account: unknown;
124 | basis_points: string;
125 | }[];
126 | taker_fees: {
127 | account: unknown;
128 | basis_points: string;
129 | }[];
130 | side: OrderSide;
131 | order_type: OrderType;
132 | cancelled: boolean;
133 | finalized: boolean;
134 | marked_invalid: boolean;
135 | client_signature: string | null;
136 | maker_asset_bundle: unknown;
137 | taker_asset_bundle: unknown;
138 | };
139 |
--------------------------------------------------------------------------------
/src/api/utils.ts:
--------------------------------------------------------------------------------
1 | import {ConsiderationItem, OfferItem, OpenSeaAccount, OpenSeaUser, OrderParameters} from "../types";
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
4 | export const accountFromJSON = (account: any): OpenSeaAccount => {
5 | return {
6 | address: account.address,
7 | config: account.config,
8 | profileImgUrl: account.profile_img_url,
9 | user: account.user ? userFromJSON(account.user) : null,
10 | };
11 | };
12 |
13 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
14 | export const userFromJSON = (user: any): OpenSeaUser => {
15 | return {
16 | username: user.username,
17 | };
18 | };
19 |
20 | import {
21 | OrderProtocol,
22 | OrdersQueryOptions,
23 | OrderSide,
24 | OrderV2,
25 | SerializedOrderV2,
26 | } from "./types";
27 |
28 | const NETWORK_TO_CHAIN = {
29 | 1: "ethereum",
30 | 4: "rinkeby",
31 | };
32 |
33 | export const getOrdersAPIPath = (
34 | chainId: number,
35 | protocol: OrderProtocol,
36 | side: OrderSide
37 | ) => {
38 | const chain = NETWORK_TO_CHAIN[chainId];
39 | const sidePath = side === "ask" ? "listings" : "offers";
40 | return `/api/v2/orders/${chain}/${protocol}/${sidePath}`;
41 | };
42 |
43 | type OrdersQueryPathOptions = "protocol" | "side";
44 | export const serializeOrdersQueryOptions = (
45 | options: Omit
46 | ) => {
47 | return {
48 | limit: options.limit,
49 | cursor: options.cursor,
50 |
51 | payment_token_address: options.paymentTokenAddress,
52 | maker: options.maker,
53 | taker: options.taker,
54 | owner: options.owner,
55 | bundled: options.bundled,
56 | include_bundled: options.includeBundled,
57 | listed_after: options.listedAfter,
58 | listed_before: options.listedBefore,
59 | token_ids: options.tokenIds,
60 | asset_contract_address: options.assetContractAddress,
61 | order_by: options.orderBy,
62 | order_direction: options.orderDirection,
63 | only_english: options.onlyEnglish,
64 | };
65 | };
66 |
67 | export const deserializeOrder = (order: SerializedOrderV2): OrderV2 => {
68 | return {
69 | createdDate: order.created_date,
70 | closingDate: order.closing_date,
71 | listingTime: order.listing_time,
72 | expirationTime: order.expiration_time,
73 | orderHash: order.order_hash,
74 | maker: accountFromJSON(order.maker),
75 | taker: order.taker ? accountFromJSON(order.taker) : null,
76 | protocolData: order.protocol_data,
77 | protocolAddress: order.protocol_address,
78 | currentPrice: order.current_price,
79 | makerFees: order.maker_fees.map(({ account, basis_points }) => ({
80 | account: accountFromJSON(account),
81 | basisPoints: basis_points,
82 | })),
83 | takerFees: order.taker_fees.map(({ account, basis_points }) => ({
84 | account: accountFromJSON(account),
85 | basisPoints: basis_points,
86 | })),
87 | side: order.side,
88 | orderType: order.order_type,
89 | cancelled: order.cancelled,
90 | finalized: order.finalized,
91 | markedInvalid: order.marked_invalid,
92 | clientSignature: order.client_signature,
93 | makerAssetBundle: order.maker_asset_bundle,
94 | takerAssetBundle: order.taker_asset_bundle,
95 | };
96 | };
97 |
98 |
99 | //
100 | //
101 | // export interface OfferItemModel {
102 | // item_type: number
103 | // token: string
104 | // identifier_or_criteria: string
105 | // startAmount: number
106 | // endAmount: number
107 | // }
108 | //
109 | // export interface OfferModel {
110 | // offer_item: OfferItemModel
111 | // }
112 | //
113 | // export interface ConsiderationItemModel extends OfferItemModel {
114 | // recipient: string
115 | // }
116 | //
117 | // export interface ConsiderationModel {
118 | // consideration_item: ConsiderationItemModel
119 | // }
120 | //
121 | //
122 | // export type OrderParametersModel = {
123 | // offerer: string
124 | // zone: string
125 | // zone_hash: string
126 | // start_time: number
127 | // end_time: number
128 | // order_type: number
129 | // salt: string
130 | // conduitKey: string
131 | // nonce: string,
132 | // offer: OfferItemModel[],
133 | // consideration: ConsiderationItemModel[]
134 | // }
135 | //
136 | // export function converToPost(order: OrderParameters): OrderParametersModel {
137 | //
138 | // // const {parameters: order_parameters, signature} = order721
139 | // const {offerer, zone, zoneHash, startTime, endTime, orderType, salt, conduitKey, offer, consideration} = order
140 | // const offerItem: OfferItemModel[] = offer.map((val: OfferItem) => ({
141 | // item_type: val.itemType,
142 | // token: val.token,
143 | // identifier_or_criteria: val.identifierOrCriteria,
144 | // startAmount: Number(val.startAmount),
145 | // endAmount: Number(val.endAmount)
146 | // }))
147 | // const considerationItme: ConsiderationItemModel[] = consideration.map((val: ConsiderationItem) => ({
148 | // item_type: val.itemType,
149 | // token: val.token,
150 | // identifier_or_criteria: val.identifierOrCriteria,
151 | // startAmount: Number(val.startAmount),
152 | // endAmount: Number(val.endAmount),
153 | // recipient: val.recipient
154 | // }))
155 | // return {
156 | // offerer,
157 | // zone,
158 | // zone_hash: zoneHash,
159 | // start_time: Number(startTime),
160 | // end_time: Number(endTime),
161 | // order_type: orderType,
162 | // salt,
163 | // conduitKey,
164 | // nonce: "0",
165 | // offer: offerItem,
166 | // consideration: considerationItme
167 | // }
168 | // }
169 |
170 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | import {BigNumber} from "ethers";
2 |
3 | export const SEAPORT_CONTRACT_NAME = "Seaport";
4 | export const SEAPORT_CONTRACT_VERSION = "1.1";
5 | export const EIP_712_PRIMARY_TYPE = "OrderComponents"
6 | export const EIP_712_ORDER_TYPE = {
7 | OrderComponents: [
8 | {name: "offerer", type: "address"},
9 | {name: "zone", type: "address"},
10 | {name: "offer", type: "OfferItem[]"},
11 | {name: "consideration", type: "ConsiderationItem[]"},
12 | {name: "orderType", type: "uint8"},
13 | {name: "startTime", type: "uint256"},
14 | {name: "endTime", type: "uint256"},
15 | {name: "zoneHash", type: "bytes32"},
16 | {name: "salt", type: "uint256"},
17 | {name: "conduitKey", type: "bytes32"},
18 | {name: "counter", type: "uint256"},
19 | ],
20 | OfferItem: [
21 | {name: "itemType", type: "uint8"},
22 | {name: "token", type: "address"},
23 | {name: "identifierOrCriteria", type: "uint256"},
24 | {name: "startAmount", type: "uint256"},
25 | {name: "endAmount", type: "uint256"},
26 | ],
27 | ConsiderationItem: [
28 | {name: "itemType", type: "uint8"},
29 | {name: "token", type: "address"},
30 | {name: "identifierOrCriteria", type: "uint256"},
31 | {name: "startAmount", type: "uint256"},
32 | {name: "endAmount", type: "uint256"},
33 | {name: "recipient", type: "address"},
34 | ],
35 | };
36 |
37 | export enum OrderType {
38 | FULL_OPEN = 0, // No partial fills, anyone can execute
39 | PARTIAL_OPEN = 1, // Partial fills supported, anyone can execute
40 | FULL_RESTRICTED = 2, // No partial fills, only offerer or zone can execute
41 | PARTIAL_RESTRICTED = 3, // Partial fills supported, only offerer or zone can execute
42 | }
43 |
44 | export enum ItemType {
45 | NATIVE = 0,
46 | ERC20 = 1,
47 | ERC721 = 2,
48 | ERC1155 = 3,
49 | ERC721_WITH_CRITERIA = 4,
50 | ERC1155_WITH_CRITERIA = 5,
51 | }
52 |
53 | export enum Side {
54 | OFFER = 0,
55 | CONSIDERATION = 1,
56 | }
57 |
58 | export type NftItemType =
59 | | ItemType.ERC721
60 | | ItemType.ERC1155
61 | | ItemType.ERC721_WITH_CRITERIA
62 | | ItemType.ERC1155_WITH_CRITERIA;
63 |
64 | export enum BasicOrderRouteType {
65 | ETH_TO_ERC721,
66 | ETH_TO_ERC1155,
67 | ERC20_TO_ERC721,
68 | ERC20_TO_ERC1155,
69 | ERC721_TO_ERC20,
70 | ERC1155_TO_ERC20,
71 | }
72 |
73 | export const offerAndConsiderationFulfillmentMapping: {
74 | [_key in ItemType]?: { [_key in ItemType]?: BasicOrderRouteType };
75 | } = {
76 | [ItemType.ERC20]: {
77 | [ItemType.ERC721]: BasicOrderRouteType.ERC721_TO_ERC20,
78 | [ItemType.ERC1155]: BasicOrderRouteType.ERC1155_TO_ERC20,
79 | },
80 | [ItemType.ERC721]: {
81 | [ItemType.NATIVE]: BasicOrderRouteType.ETH_TO_ERC721,
82 | [ItemType.ERC20]: BasicOrderRouteType.ERC20_TO_ERC721,
83 | },
84 | [ItemType.ERC1155]: {
85 | [ItemType.NATIVE]: BasicOrderRouteType.ETH_TO_ERC1155,
86 | [ItemType.ERC20]: BasicOrderRouteType.ERC20_TO_ERC1155,
87 | },
88 | } as const;
89 |
90 | export const MAX_INT = BigNumber.from(
91 | "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
92 | );
93 | export const ONE_HUNDRED_PERCENT_BP = 10000;
94 | export const NO_CONDUIT =
95 | "0x0000000000000000000000000000000000000000000000000000000000000000";
96 |
97 | // Supply here any known conduit keys as well as their conduits
98 | export const KNOWN_CONDUIT_KEYS_TO_CONDUIT = {};
99 |
100 |
101 | // export const MERKLE_VALIDATOR_MAINNET =
102 | // "0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7";
103 | // export const MERKLE_VALIDATOR_RINKEBY =
104 | // "0x45b594792a5cdc008d0de1c1d69faa3d16b3ddc1";
105 | //
106 | // export const CROSS_CHAIN_DEFAULT_CONDUIT_KEY =
107 | // "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000";
108 | // const CROSS_CHAIN_DEFAULT_CONDUIT =
109 | // "0x1e0049783f008a0085193e00003d00cd54003c71";
110 | //
111 | // export const CONDUIT_KEYS_TO_CONDUIT = {
112 | // [CROSS_CHAIN_DEFAULT_CONDUIT_KEY]: CROSS_CHAIN_DEFAULT_CONDUIT,
113 | // };
114 | //
115 | // export const WETH_ADDRESS_BY_NETWORK = {
116 | // [Network.Main]: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
117 | // [Network.Rinkeby]: "0xc778417e063141139fce010982780140aa0cd5ab",
118 | // } as const;
119 | //
120 | // export const DEFAULT_ZONE_BY_NETWORK = {
121 | // [Network.Main]: "0x004c00500000ad104d7dbd00e3ae0a5c00560c00",
122 | // [Network.Rinkeby]: "0x9b814233894cd227f561b78cc65891aa55c62ad2",
123 | // } as const;
124 |
--------------------------------------------------------------------------------
/src/contracts/abi/seaport/Conduit.json:
--------------------------------------------------------------------------------
1 | {
2 | "contractName": "Conduit",
3 | "abi": [
4 | {
5 | "inputs": [],
6 | "stateMutability": "nonpayable",
7 | "type": "constructor"
8 | },
9 | {
10 | "inputs": [
11 | {
12 | "internalType": "address",
13 | "name": "token",
14 | "type": "address"
15 | },
16 | {
17 | "internalType": "address",
18 | "name": "from",
19 | "type": "address"
20 | },
21 | {
22 | "internalType": "address",
23 | "name": "to",
24 | "type": "address"
25 | },
26 | {
27 | "internalType": "uint256",
28 | "name": "amount",
29 | "type": "uint256"
30 | }
31 | ],
32 | "name": "BadReturnValueFromERC20OnTransfer",
33 | "type": "error"
34 | },
35 | {
36 | "inputs": [
37 | {
38 | "internalType": "address",
39 | "name": "channel",
40 | "type": "address"
41 | }
42 | ],
43 | "name": "ChannelClosed",
44 | "type": "error"
45 | },
46 | {
47 | "inputs": [
48 | {
49 | "internalType": "address",
50 | "name": "channel",
51 | "type": "address"
52 | },
53 | {
54 | "internalType": "bool",
55 | "name": "isOpen",
56 | "type": "bool"
57 | }
58 | ],
59 | "name": "ChannelStatusAlreadySet",
60 | "type": "error"
61 | },
62 | {
63 | "inputs": [
64 | {
65 | "internalType": "address",
66 | "name": "token",
67 | "type": "address"
68 | },
69 | {
70 | "internalType": "address",
71 | "name": "from",
72 | "type": "address"
73 | },
74 | {
75 | "internalType": "address",
76 | "name": "to",
77 | "type": "address"
78 | },
79 | {
80 | "internalType": "uint256[]",
81 | "name": "identifiers",
82 | "type": "uint256[]"
83 | },
84 | {
85 | "internalType": "uint256[]",
86 | "name": "amounts",
87 | "type": "uint256[]"
88 | }
89 | ],
90 | "name": "ERC1155BatchTransferGenericFailure",
91 | "type": "error"
92 | },
93 | {
94 | "inputs": [],
95 | "name": "Invalid1155BatchTransferEncoding",
96 | "type": "error"
97 | },
98 | {
99 | "inputs": [],
100 | "name": "InvalidController",
101 | "type": "error"
102 | },
103 | {
104 | "inputs": [],
105 | "name": "InvalidERC721TransferAmount",
106 | "type": "error"
107 | },
108 | {
109 | "inputs": [],
110 | "name": "InvalidItemType",
111 | "type": "error"
112 | },
113 | {
114 | "inputs": [],
115 | "name": "MissingItemAmount",
116 | "type": "error"
117 | },
118 | {
119 | "inputs": [
120 | {
121 | "internalType": "address",
122 | "name": "account",
123 | "type": "address"
124 | }
125 | ],
126 | "name": "NoContract",
127 | "type": "error"
128 | },
129 | {
130 | "inputs": [
131 | {
132 | "internalType": "address",
133 | "name": "token",
134 | "type": "address"
135 | },
136 | {
137 | "internalType": "address",
138 | "name": "from",
139 | "type": "address"
140 | },
141 | {
142 | "internalType": "address",
143 | "name": "to",
144 | "type": "address"
145 | },
146 | {
147 | "internalType": "uint256",
148 | "name": "identifier",
149 | "type": "uint256"
150 | },
151 | {
152 | "internalType": "uint256",
153 | "name": "amount",
154 | "type": "uint256"
155 | }
156 | ],
157 | "name": "TokenTransferGenericFailure",
158 | "type": "error"
159 | },
160 | {
161 | "inputs": [],
162 | "name": "UnusedItemParameters",
163 | "type": "error"
164 | },
165 | {
166 | "anonymous": false,
167 | "inputs": [
168 | {
169 | "indexed": true,
170 | "internalType": "address",
171 | "name": "channel",
172 | "type": "address"
173 | },
174 | {
175 | "indexed": false,
176 | "internalType": "bool",
177 | "name": "open",
178 | "type": "bool"
179 | }
180 | ],
181 | "name": "ChannelUpdated",
182 | "type": "event"
183 | },
184 | {
185 | "inputs": [
186 | {
187 | "components": [
188 | {
189 | "internalType": "enum ConduitItemType",
190 | "name": "itemType",
191 | "type": "uint8"
192 | },
193 | {
194 | "internalType": "address",
195 | "name": "token",
196 | "type": "address"
197 | },
198 | {
199 | "internalType": "address",
200 | "name": "from",
201 | "type": "address"
202 | },
203 | {
204 | "internalType": "address",
205 | "name": "to",
206 | "type": "address"
207 | },
208 | {
209 | "internalType": "uint256",
210 | "name": "identifier",
211 | "type": "uint256"
212 | },
213 | {
214 | "internalType": "uint256",
215 | "name": "amount",
216 | "type": "uint256"
217 | }
218 | ],
219 | "internalType": "struct ConduitTransfer[]",
220 | "name": "transfers",
221 | "type": "tuple[]"
222 | }
223 | ],
224 | "name": "execute",
225 | "outputs": [
226 | {
227 | "internalType": "bytes4",
228 | "name": "magicValue",
229 | "type": "bytes4"
230 | }
231 | ],
232 | "stateMutability": "nonpayable",
233 | "type": "function"
234 | },
235 | {
236 | "inputs": [
237 | {
238 | "components": [
239 | {
240 | "internalType": "address",
241 | "name": "token",
242 | "type": "address"
243 | },
244 | {
245 | "internalType": "address",
246 | "name": "from",
247 | "type": "address"
248 | },
249 | {
250 | "internalType": "address",
251 | "name": "to",
252 | "type": "address"
253 | },
254 | {
255 | "internalType": "uint256[]",
256 | "name": "ids",
257 | "type": "uint256[]"
258 | },
259 | {
260 | "internalType": "uint256[]",
261 | "name": "amounts",
262 | "type": "uint256[]"
263 | }
264 | ],
265 | "internalType": "struct ConduitBatch1155Transfer[]",
266 | "name": "batchTransfers",
267 | "type": "tuple[]"
268 | }
269 | ],
270 | "name": "executeBatch1155",
271 | "outputs": [
272 | {
273 | "internalType": "bytes4",
274 | "name": "magicValue",
275 | "type": "bytes4"
276 | }
277 | ],
278 | "stateMutability": "nonpayable",
279 | "type": "function"
280 | },
281 | {
282 | "inputs": [
283 | {
284 | "components": [
285 | {
286 | "internalType": "enum ConduitItemType",
287 | "name": "itemType",
288 | "type": "uint8"
289 | },
290 | {
291 | "internalType": "address",
292 | "name": "token",
293 | "type": "address"
294 | },
295 | {
296 | "internalType": "address",
297 | "name": "from",
298 | "type": "address"
299 | },
300 | {
301 | "internalType": "address",
302 | "name": "to",
303 | "type": "address"
304 | },
305 | {
306 | "internalType": "uint256",
307 | "name": "identifier",
308 | "type": "uint256"
309 | },
310 | {
311 | "internalType": "uint256",
312 | "name": "amount",
313 | "type": "uint256"
314 | }
315 | ],
316 | "internalType": "struct ConduitTransfer[]",
317 | "name": "standardTransfers",
318 | "type": "tuple[]"
319 | },
320 | {
321 | "components": [
322 | {
323 | "internalType": "address",
324 | "name": "token",
325 | "type": "address"
326 | },
327 | {
328 | "internalType": "address",
329 | "name": "from",
330 | "type": "address"
331 | },
332 | {
333 | "internalType": "address",
334 | "name": "to",
335 | "type": "address"
336 | },
337 | {
338 | "internalType": "uint256[]",
339 | "name": "ids",
340 | "type": "uint256[]"
341 | },
342 | {
343 | "internalType": "uint256[]",
344 | "name": "amounts",
345 | "type": "uint256[]"
346 | }
347 | ],
348 | "internalType": "struct ConduitBatch1155Transfer[]",
349 | "name": "batchTransfers",
350 | "type": "tuple[]"
351 | }
352 | ],
353 | "name": "executeWithBatch1155",
354 | "outputs": [
355 | {
356 | "internalType": "bytes4",
357 | "name": "magicValue",
358 | "type": "bytes4"
359 | }
360 | ],
361 | "stateMutability": "nonpayable",
362 | "type": "function"
363 | },
364 | {
365 | "inputs": [
366 | {
367 | "internalType": "address",
368 | "name": "channel",
369 | "type": "address"
370 | },
371 | {
372 | "internalType": "bool",
373 | "name": "isOpen",
374 | "type": "bool"
375 | }
376 | ],
377 | "name": "updateChannel",
378 | "outputs": [],
379 | "stateMutability": "nonpayable",
380 | "type": "function"
381 | }
382 | ]
383 | }
384 |
--------------------------------------------------------------------------------
/src/contracts/abi/seaport/ConduitController.json:
--------------------------------------------------------------------------------
1 | {
2 | "contractName": "ConduitController",
3 | "abi": [
4 | {
5 | "inputs": [],
6 | "stateMutability": "nonpayable",
7 | "type": "constructor"
8 | },
9 | {
10 | "inputs": [
11 | {
12 | "internalType": "address",
13 | "name": "conduit",
14 | "type": "address"
15 | }
16 | ],
17 | "name": "CallerIsNotNewPotentialOwner",
18 | "type": "error"
19 | },
20 | {
21 | "inputs": [
22 | {
23 | "internalType": "address",
24 | "name": "conduit",
25 | "type": "address"
26 | }
27 | ],
28 | "name": "CallerIsNotOwner",
29 | "type": "error"
30 | },
31 | {
32 | "inputs": [
33 | {
34 | "internalType": "address",
35 | "name": "conduit",
36 | "type": "address"
37 | }
38 | ],
39 | "name": "ChannelOutOfRange",
40 | "type": "error"
41 | },
42 | {
43 | "inputs": [
44 | {
45 | "internalType": "address",
46 | "name": "conduit",
47 | "type": "address"
48 | }
49 | ],
50 | "name": "ConduitAlreadyExists",
51 | "type": "error"
52 | },
53 | {
54 | "inputs": [],
55 | "name": "InvalidCreator",
56 | "type": "error"
57 | },
58 | {
59 | "inputs": [],
60 | "name": "InvalidInitialOwner",
61 | "type": "error"
62 | },
63 | {
64 | "inputs": [
65 | {
66 | "internalType": "address",
67 | "name": "conduit",
68 | "type": "address"
69 | },
70 | {
71 | "internalType": "address",
72 | "name": "newPotentialOwner",
73 | "type": "address"
74 | }
75 | ],
76 | "name": "NewPotentialOwnerAlreadySet",
77 | "type": "error"
78 | },
79 | {
80 | "inputs": [
81 | {
82 | "internalType": "address",
83 | "name": "conduit",
84 | "type": "address"
85 | }
86 | ],
87 | "name": "NewPotentialOwnerIsZeroAddress",
88 | "type": "error"
89 | },
90 | {
91 | "inputs": [],
92 | "name": "NoConduit",
93 | "type": "error"
94 | },
95 | {
96 | "inputs": [
97 | {
98 | "internalType": "address",
99 | "name": "conduit",
100 | "type": "address"
101 | }
102 | ],
103 | "name": "NoPotentialOwnerCurrentlySet",
104 | "type": "error"
105 | },
106 | {
107 | "anonymous": false,
108 | "inputs": [
109 | {
110 | "indexed": false,
111 | "internalType": "address",
112 | "name": "conduit",
113 | "type": "address"
114 | },
115 | {
116 | "indexed": false,
117 | "internalType": "bytes32",
118 | "name": "conduitKey",
119 | "type": "bytes32"
120 | }
121 | ],
122 | "name": "NewConduit",
123 | "type": "event"
124 | },
125 | {
126 | "anonymous": false,
127 | "inputs": [
128 | {
129 | "indexed": true,
130 | "internalType": "address",
131 | "name": "conduit",
132 | "type": "address"
133 | },
134 | {
135 | "indexed": true,
136 | "internalType": "address",
137 | "name": "previousOwner",
138 | "type": "address"
139 | },
140 | {
141 | "indexed": true,
142 | "internalType": "address",
143 | "name": "newOwner",
144 | "type": "address"
145 | }
146 | ],
147 | "name": "OwnershipTransferred",
148 | "type": "event"
149 | },
150 | {
151 | "anonymous": false,
152 | "inputs": [
153 | {
154 | "indexed": true,
155 | "internalType": "address",
156 | "name": "newPotentialOwner",
157 | "type": "address"
158 | }
159 | ],
160 | "name": "PotentialOwnerUpdated",
161 | "type": "event"
162 | },
163 | {
164 | "inputs": [
165 | {
166 | "internalType": "address",
167 | "name": "conduit",
168 | "type": "address"
169 | }
170 | ],
171 | "name": "acceptOwnership",
172 | "outputs": [],
173 | "stateMutability": "nonpayable",
174 | "type": "function"
175 | },
176 | {
177 | "inputs": [
178 | {
179 | "internalType": "address",
180 | "name": "conduit",
181 | "type": "address"
182 | }
183 | ],
184 | "name": "cancelOwnershipTransfer",
185 | "outputs": [],
186 | "stateMutability": "nonpayable",
187 | "type": "function"
188 | },
189 | {
190 | "inputs": [
191 | {
192 | "internalType": "bytes32",
193 | "name": "conduitKey",
194 | "type": "bytes32"
195 | },
196 | {
197 | "internalType": "address",
198 | "name": "initialOwner",
199 | "type": "address"
200 | }
201 | ],
202 | "name": "createConduit",
203 | "outputs": [
204 | {
205 | "internalType": "address",
206 | "name": "conduit",
207 | "type": "address"
208 | }
209 | ],
210 | "stateMutability": "nonpayable",
211 | "type": "function"
212 | },
213 | {
214 | "inputs": [
215 | {
216 | "internalType": "address",
217 | "name": "conduit",
218 | "type": "address"
219 | },
220 | {
221 | "internalType": "uint256",
222 | "name": "channelIndex",
223 | "type": "uint256"
224 | }
225 | ],
226 | "name": "getChannel",
227 | "outputs": [
228 | {
229 | "internalType": "address",
230 | "name": "channel",
231 | "type": "address"
232 | }
233 | ],
234 | "stateMutability": "view",
235 | "type": "function"
236 | },
237 | {
238 | "inputs": [
239 | {
240 | "internalType": "address",
241 | "name": "conduit",
242 | "type": "address"
243 | },
244 | {
245 | "internalType": "address",
246 | "name": "channel",
247 | "type": "address"
248 | }
249 | ],
250 | "name": "getChannelStatus",
251 | "outputs": [
252 | {
253 | "internalType": "bool",
254 | "name": "isOpen",
255 | "type": "bool"
256 | }
257 | ],
258 | "stateMutability": "view",
259 | "type": "function"
260 | },
261 | {
262 | "inputs": [
263 | {
264 | "internalType": "address",
265 | "name": "conduit",
266 | "type": "address"
267 | }
268 | ],
269 | "name": "getChannels",
270 | "outputs": [
271 | {
272 | "internalType": "address[]",
273 | "name": "channels",
274 | "type": "address[]"
275 | }
276 | ],
277 | "stateMutability": "view",
278 | "type": "function"
279 | },
280 | {
281 | "inputs": [
282 | {
283 | "internalType": "bytes32",
284 | "name": "conduitKey",
285 | "type": "bytes32"
286 | }
287 | ],
288 | "name": "getConduit",
289 | "outputs": [
290 | {
291 | "internalType": "address",
292 | "name": "conduit",
293 | "type": "address"
294 | },
295 | {
296 | "internalType": "bool",
297 | "name": "exists",
298 | "type": "bool"
299 | }
300 | ],
301 | "stateMutability": "view",
302 | "type": "function"
303 | },
304 | {
305 | "inputs": [],
306 | "name": "getConduitCodeHashes",
307 | "outputs": [
308 | {
309 | "internalType": "bytes32",
310 | "name": "creationCodeHash",
311 | "type": "bytes32"
312 | },
313 | {
314 | "internalType": "bytes32",
315 | "name": "runtimeCodeHash",
316 | "type": "bytes32"
317 | }
318 | ],
319 | "stateMutability": "view",
320 | "type": "function"
321 | },
322 | {
323 | "inputs": [
324 | {
325 | "internalType": "address",
326 | "name": "conduit",
327 | "type": "address"
328 | }
329 | ],
330 | "name": "getKey",
331 | "outputs": [
332 | {
333 | "internalType": "bytes32",
334 | "name": "conduitKey",
335 | "type": "bytes32"
336 | }
337 | ],
338 | "stateMutability": "view",
339 | "type": "function"
340 | },
341 | {
342 | "inputs": [
343 | {
344 | "internalType": "address",
345 | "name": "conduit",
346 | "type": "address"
347 | }
348 | ],
349 | "name": "getPotentialOwner",
350 | "outputs": [
351 | {
352 | "internalType": "address",
353 | "name": "potentialOwner",
354 | "type": "address"
355 | }
356 | ],
357 | "stateMutability": "view",
358 | "type": "function"
359 | },
360 | {
361 | "inputs": [
362 | {
363 | "internalType": "address",
364 | "name": "conduit",
365 | "type": "address"
366 | }
367 | ],
368 | "name": "getTotalChannels",
369 | "outputs": [
370 | {
371 | "internalType": "uint256",
372 | "name": "totalChannels",
373 | "type": "uint256"
374 | }
375 | ],
376 | "stateMutability": "view",
377 | "type": "function"
378 | },
379 | {
380 | "inputs": [
381 | {
382 | "internalType": "address",
383 | "name": "conduit",
384 | "type": "address"
385 | }
386 | ],
387 | "name": "ownerOf",
388 | "outputs": [
389 | {
390 | "internalType": "address",
391 | "name": "owner",
392 | "type": "address"
393 | }
394 | ],
395 | "stateMutability": "view",
396 | "type": "function"
397 | },
398 | {
399 | "inputs": [
400 | {
401 | "internalType": "address",
402 | "name": "conduit",
403 | "type": "address"
404 | },
405 | {
406 | "internalType": "address",
407 | "name": "newPotentialOwner",
408 | "type": "address"
409 | }
410 | ],
411 | "name": "transferOwnership",
412 | "outputs": [],
413 | "stateMutability": "nonpayable",
414 | "type": "function"
415 | },
416 | {
417 | "inputs": [
418 | {
419 | "internalType": "address",
420 | "name": "conduit",
421 | "type": "address"
422 | },
423 | {
424 | "internalType": "address",
425 | "name": "channel",
426 | "type": "address"
427 | },
428 | {
429 | "internalType": "bool",
430 | "name": "isOpen",
431 | "type": "bool"
432 | }
433 | ],
434 | "name": "updateChannel",
435 | "outputs": [],
436 | "stateMutability": "nonpayable",
437 | "type": "function"
438 | }
439 | ]
440 | }
441 |
--------------------------------------------------------------------------------
/src/contracts/index.ts:
--------------------------------------------------------------------------------
1 | import exSwap from './abi/aggtrade/ExSwap.json'
2 | import seaport from './abi/seaport/Seaport.json'
3 | import conduit from './abi/seaport/Conduit.json'
4 | import conduitController from './abi/seaport/ConduitController.json'
5 |
6 | export interface AbiInfo {
7 | contractName: string
8 | sourceName?: string
9 | abi: any
10 | }
11 |
12 | export const SeaportABI = {
13 | seaport: seaport as AbiInfo,
14 | conduit: conduit as AbiInfo,
15 | conduitController: conduitController as AbiInfo
16 | }
17 |
18 | export const ContractABI = {
19 | swapEx: exSwap as AbiInfo
20 | }
21 | // https://etherscan.com/address/0x00000000006c3852cbef3e08e8df289169ede581#code
22 | // https://etherscan.com/address/0x1e0049783f008a0085193e00003d00cd54003c71#code
23 | export const SEAPORT_CONTRACTS_ADDRESSES = {
24 | 1: {
25 | Exchange: "0x00000000006c3852cbef3e08e8df289169ede581",
26 | ConduitController: "0x00000000F9490004C11Cef243f5400493c00Ad63",
27 | Conduit: "0x1e0049783f008a0085193e00003d00cd54003c71",
28 | Zone: "0x00000000E88FE2628EbC5DA81d2b3CeaD633E89e",
29 | PausableZone: "0x004C00500000aD104D7DBd00e3ae0A5C00560C00",
30 | FeeRecipientAddress: '0x0000a26b00c1F0DF003000390027140000fAa719',
31 | GasToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
32 | },
33 | 4: {
34 | Exchange: "0x00000000006c3852cbef3e08e8df289169ede581",
35 | ConduitController: "0x00000000F9490004C11Cef243f5400493c00Ad63",
36 | Conduit: "0x1e0049783f008a0085193e00003d00cd54003c71",
37 | Zone: "0x00000000E88FE2628EbC5DA81d2b3CeaD633E89e",
38 | PausableZone: "0x004C00500000aD104D7DBd00e3ae0A5C00560C00",
39 | FeeRecipientAddress: '0x0000a26b00c1F0DF003000390027140000fAa719',
40 | GasToken: '0xc778417e063141139fce010982780140aa0cd5ab',
41 | }
42 | }
43 |
44 | export const EXSWAP_CONTRACTS_ADDRESSES = {
45 | 1: {
46 | ExSwap: "0x69Cf8871F61FB03f540bC519dd1f1D4682Ea0bF6",
47 | 0: '0x7f268357A8c2552623316e2562D90e642bB538E5', // opensea
48 | 1: '0x00000000006c3852cbef3e08e8df289169ede581', //seaport
49 | 2: '0x20F780A973856B93f63670377900C1d2a50a77c4' // //elementV3
50 | },
51 | 4: {
52 | ExSwap: "0x1A365EC4d192F7ddE7c5c638DD4871653D93Ee06",
53 | 0: '0xdD54D660178B28f6033a953b0E55073cFA7e3744', // opensea
54 | 1: '0x00000000006c3852cbef3e08e8df289169ede581',//seaport
55 | 2: '0x8D6022B8A421B08E9E4cEf45E46f1c83C85d402F',//elementV3
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'events'
2 | import {Seaport} from "./seaport";
3 | import {SeaportAPI} from "./api/seaport";
4 | import {SwapEx} from "./swapEx/swapEx";
5 |
6 | import {
7 | Asset,
8 | FeesInfo,
9 | APIConfig,
10 | Web3Accounts,
11 | ExchangetAgent,
12 | OrderSide,
13 | CreateOrderParams,
14 | MatchParams,
15 | SellOrderParams,
16 | BuyOrderParams,
17 | MatchOrderOption,
18 | transactionToCallData,
19 | AdjustOrderParams, ETHToken
20 | } from "web3-accounts"
21 |
22 | import {
23 | AssetsQueryParams,
24 | AssetCollection, OrdersQueryParams, OrderV2
25 | } from "./api/types"
26 | import {WalletInfo, utils} from "web3-wallets";
27 | import {OrderComponents, OrderWithCounter, MatchOrdersParams, Order} from "./types";
28 | import {seaportAssert} from "./utils/assert";
29 | import pkg from "../package.json"
30 |
31 | const {validateOrder, validateOrderWithCounter} = seaportAssert
32 |
33 | export class SeaportSDK extends EventEmitter implements ExchangetAgent {
34 | public walletInfo: WalletInfo
35 | public contracts: Seaport
36 | public swap: SwapEx
37 | public api: SeaportAPI
38 | public user: Web3Accounts
39 | public version = pkg.version
40 |
41 | constructor(wallet: WalletInfo, config?: APIConfig) {
42 | super()
43 | const {chainId, address} = wallet
44 | let conf: APIConfig = {chainId}
45 | if (config) {
46 | conf = {...conf, ...config}
47 | }
48 | this.contracts = new Seaport(wallet, conf)
49 | this.api = new SeaportAPI(conf)
50 | this.swap = new SwapEx(wallet)
51 | this.user = new Web3Accounts(wallet)
52 | this.walletInfo = wallet
53 | }
54 |
55 | async getOrderApprove(params: CreateOrderParams, side: OrderSide) {
56 | return this.contracts.getOrderApprove(params, side)
57 | }
58 |
59 | async getMatchCallData(params: MatchParams): Promise {
60 | const {orderStr, takerAmount} = params
61 | if (!validateOrderWithCounter(orderStr)) throw validateOrderWithCounter.errors
62 | return this.contracts.getMatchCallData({order: JSON.parse(orderStr) as OrderWithCounter})
63 | }
64 |
65 | async createSellOrder(params: SellOrderParams): Promise {
66 | return this.contracts.createSellOrder(params)
67 | }
68 |
69 | async adjustOrder(params: AdjustOrderParams) {
70 | return this.contracts.adjustOrder(params)
71 | }
72 |
73 |
74 | async createBuyOrder(params: BuyOrderParams): Promise {
75 | return this.contracts.createBuyOrder(params)
76 | }
77 |
78 | async fulfillOrder(orderStr: string, options?: MatchOrderOption) {
79 | let order = JSON.parse(orderStr)
80 | if (order.protocolData) {
81 | order = order.protocolData
82 | }
83 | if (!validateOrder(order)) throw validateOrder.errors
84 |
85 | const {takerAmount, taker} = options || {}
86 | let data
87 | if (takerAmount) {
88 | data = await this.contracts.fulfillAdvancedOrder({order, takerAmount, recipient: taker})
89 | } else {
90 | data = await this.contracts.fulfillBasicOrder({order})
91 | }
92 | return this.contracts.ethSend(transactionToCallData(data))
93 | }
94 |
95 | async fulfillOrders(orders: MatchOrdersParams) {
96 | const {orderList} = orders
97 | if (orderList.length == 0) {
98 | throw 'Seaport fulfill orders eq 0'
99 | }
100 | if (orderList.length == 1) {
101 | const {orderStr, metadata, takerAmount, taker} = orderList[0]
102 | const oneOption: MatchOrderOption = {
103 | metadata,
104 | takerAmount,
105 | taker
106 | }
107 | return this.fulfillOrder(orderStr, oneOption)
108 | } else {
109 | const availableOrders: Order[] = []
110 | for (const {orderStr} of orderList) {
111 | let order = JSON.parse(orderStr)
112 | if (order.protocolData) {
113 | order = order.protocolData
114 | }
115 | if (!validateOrder(order)) throw validateOrder.errors
116 | availableOrders.push(order)
117 | }
118 | const data = await this.contracts.fulfillAvailableAdvancedOrders({orders: availableOrders})
119 |
120 | return this.contracts.ethSend(transactionToCallData(data))
121 | }
122 |
123 | }
124 |
125 | async cancelOrders(orders: string[]) {
126 | if (orders.length == 0) throw new Error("cancelOrders error")
127 | const orderComp = orders.map((val) => {
128 | const order = JSON.parse(val) as OrderWithCounter
129 | if (!validateOrderWithCounter(order)) throw validateOrderWithCounter.errors
130 | const {parameters} = order;
131 | return parameters as OrderComponents
132 | })
133 | return this.contracts.cancelOrders(orderComp)
134 |
135 | }
136 |
137 | async cancelAllOrders(nonce?: string) {
138 | return this.contracts.bulkCancelOrders()
139 | }
140 |
141 | async getAssetBalances(asset: Asset, account?: string): Promise {
142 | return this.user.getAssetBalances(asset, account)
143 | }
144 |
145 | async getTokenBalances(params: {
146 | tokenAddress: string;
147 | accountAddress?: string;
148 | rpcUrl?: string;
149 | }): Promise {
150 | return this.user.getTokenBalances({
151 | tokenAddr: params.tokenAddress,
152 | account: params.accountAddress,
153 | rpcUrl: params.rpcUrl
154 | })
155 | }
156 |
157 |
158 | async transfer(asset: Asset, to: string, quantity: number) {
159 | return this.user.transfer(asset, to, quantity)
160 | }
161 |
162 | async getOwnerAssets(tokens?: AssetsQueryParams): Promise {
163 | if (tokens) {
164 | tokens.owner = tokens.owner || this.walletInfo.address
165 | } else {
166 | tokens = {
167 | owner: this.walletInfo.address,
168 | limit: 1,
169 | }
170 | }
171 | return this.api.getAssets(tokens)
172 | }
173 |
174 | async getOwnerOrders(tokens?: OrdersQueryParams): Promise<{ orders: OrderV2[], count: number }> {
175 | if (tokens) {
176 | tokens.owner = tokens.owner || this.walletInfo.address
177 | } else {
178 | tokens = {
179 | owner: this.walletInfo.address,
180 | limit: 10,
181 | side: OrderSide.All
182 | }
183 | }
184 | return this.api.getOrders(tokens)
185 | }
186 |
187 | async postOrder(orderStr: string) {
188 | return this.api.postOrder(orderStr)
189 | }
190 |
191 | async getAssetsFees(assetAddresses: string[]): Promise {
192 | const assets = assetAddresses.map(val => ({asset_contract_addresses: val}))
193 | const tokens: AssetsQueryParams = {assets, limit: 1}
194 | const collections: AssetCollection[] = await this.api.getAssets(tokens)
195 | return collections.map(val => ({
196 | royaltyFeeAddress: val.royaltyFeeAddress,
197 | royaltyFeePoints: val.royaltyFeePoints,
198 | protocolFeePoints: val.protocolFeePoints,
199 | protocolFeeAddress: this.contracts.protocolFeeAddress
200 | }))
201 | }
202 |
203 | }
204 |
205 |
--------------------------------------------------------------------------------
/src/swapEx/swapEx.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'events'
2 | import {Contract, ethers} from 'ethers'
3 | import {
4 | EXSWAP_CONTRACTS_ADDRESSES,
5 | ContractABI
6 | } from '../contracts'
7 |
8 |
9 | import {
10 | ethSend, LimitedCallSpec,
11 | WalletInfo, getEstimateGas, CHAIN_CONFIG, getChainRpcUrl
12 | } from 'web3-wallets'
13 |
14 | import {Web3Accounts} from "web3-accounts"
15 |
16 |
17 | export interface SimpleTrades {
18 | value: string
19 | tradeData: string
20 | }
21 |
22 | export interface TradeDetails extends SimpleTrades {
23 | marketId: string
24 | }
25 |
26 | function getValidSwaps(intData: number, swaps: Array) {
27 | let bData = intData.toString(2)
28 |
29 | if (bData.length != swaps.length) {
30 | const diffLen = swaps.length - bData.length
31 | if (bData.length > 200) throw 'GetValidSwaps error'
32 | const b0 = Array(diffLen).fill(0).join('')
33 | bData = `${b0}${bData}`
34 | }
35 | let allValue = ethers.BigNumber.from(0)
36 | const swapValid: Array = []
37 | const swapIsValid = swaps.map((val, index) => {
38 | const isValid = bData[swaps.length - index - 1] == '1' ? true : false
39 | if (isValid) {
40 | allValue = allValue.add(val.value)
41 | swapValid.push(val)
42 | }
43 | return {
44 | index,
45 | isValid,
46 | swap: val
47 | }
48 | })
49 | return {swapIsValid, swaps: swapValid, value: allValue.toString(), bData}
50 | }
51 |
52 | function getSwapsValue(swaps: Array) {
53 | let value = ethers.BigNumber.from(0)
54 | swaps.forEach(val => {
55 | value = value.add(val.value)
56 | })
57 | return value
58 | }
59 |
60 | export class SwapEx extends EventEmitter {
61 | public swapExContract: Contract
62 | public walletInfo: WalletInfo
63 | public userAccount: Web3Accounts
64 | public contractAddr
65 |
66 | constructor(wallet: WalletInfo) {
67 | super()
68 | this.walletInfo = {...wallet, rpcUrl: CHAIN_CONFIG[wallet.chainId].rpcs[0]}
69 | this.userAccount = new Web3Accounts(wallet)
70 | const contractAddr = EXSWAP_CONTRACTS_ADDRESSES[this.walletInfo.chainId]
71 | if (!contractAddr) throw 'ExSwap config error ' + this.walletInfo.chainId
72 | this.swapExContract = new ethers.Contract(contractAddr.ExSwap, ContractABI.swapEx.abi, this.userAccount.signer)
73 | this.contractAddr = contractAddr
74 | }
75 |
76 | public async batchBuyWithETHSimulate(swaps: Array): Promise {
77 | if (swaps.length == 0) return {swaps: [], value: '0'}// throw 'BatchBuyWithETHSimulate swaps is null'
78 | // if (swaps.find(val => !val.tradeData) || swaps.find(val => !val.value)) throw 'BatchBuyWithETHSimulate swaps tradeData or value is undefined'
79 | for (const val of swaps) {
80 | if (!val.tradeData || !val.value) throw 'BatchBuyWithETHSimulate swaps tradeData or value is undefined'
81 | const funcID = val.tradeData.substring(0, 10)
82 | //markId 0 opensea 0xab834bab atomicMatch_(address[14],uint256[18],uint8[8],bytes,bytes,bytes,bytes,bytes,bytes,uint8[2],bytes32[5])
83 | if (this.walletInfo.chainId == 1 || this.walletInfo.chainId == 4) {
84 | //markId 0 opensea 0xab834bab atomicMatch_(address[14],uint256[18],uint8[8],bytes,bytes,bytes,bytes,bytes,bytes,uint8[2],bytes32[5])
85 | if (val.marketId == "0" && funcID != '0xab834bab') throw 'Opensea match function encode error'
86 | //markId 1 seaport 0xe7acab24 fulfillAdvancedOrder 0xe7acab24
87 | if (val.marketId == "1" && funcID != '0xe7acab24') throw 'Seaport match function encode error'
88 | }
89 | }
90 | const value = getSwapsValue(swaps)
91 | // eslint-disable-next-line no-async-promise-executor
92 | return new Promise(async (resolve, reject) => {
93 | const callData = await this.swapExContract.populateTransaction.batchBuyWithETHSimulate(swaps, {value})
94 | const rpcUrl = await getChainRpcUrl(this.walletInfo.chainId)
95 | return getEstimateGas(rpcUrl, {
96 | ...callData,
97 | value: value.toString()
98 | } as LimitedCallSpec).catch(async (err: any) => {
99 | if (err.code == '-32000') {
100 | console.log(value.toString())
101 | const bal = await this.userAccount.getGasBalances({})
102 | console.log(bal)
103 | reject(err.message)
104 | } else {
105 | //0x4e487b71
106 | if (err.data.substring(0, 10) == '0x4e487b71') {
107 | console.log('Panic(uint256)', err.data)
108 | throw 'BatchBuyWithETHSimulate Panic'
109 | }
110 |
111 | const intData = parseInt(err.data, 16)
112 | if (intData == 0) reject('No valid swaps data by batchBuyWithETHSimulate')
113 | const swapData = getValidSwaps(intData, swaps)
114 | resolve(swapData)
115 | }
116 | })
117 | })
118 | }
119 |
120 | public async buyOneWithETH(swap: TradeDetails) {
121 | const value = ethers.BigNumber.from(swap.value)
122 | const marketProxy = this.contractAddr[swap.marketId]
123 | if (!marketProxy) `The Market id ${swap.marketId} is invalid in the buy one of eth`
124 | const tradeDetail: SimpleTrades = {
125 | value: swap.value,
126 | tradeData: swap.tradeData
127 | }
128 | const tx = await this.swapExContract.populateTransaction.buyOneWithETH(marketProxy, tradeDetail, {value})
129 | const callData = {...tx, value: tx.value?.toString()} as LimitedCallSpec
130 | return ethSend(this.walletInfo, callData)
131 | }
132 |
133 | public async batchBuyFromSingleMarketWithETH(swaps: Array) {
134 | const value = getSwapsValue(swaps)
135 | const marketProxy = this.contractAddr[swaps[0].marketId]
136 | if (!marketProxy) `The Market id ${swaps[0].marketId} is invalid in the batch buy`
137 | const tradeDetails: SimpleTrades[] = swaps.map((val: TradeDetails) => {
138 | return {
139 | value: val.value,
140 | tradeData: val.tradeData
141 | }
142 | })
143 | // return this.swapExContract.batchBuyFromSingleMarketWithETH(marketProxy, tradeDetails, {value})
144 |
145 | const tx = await this.swapExContract.populateTransaction.batchBuyFromSingleMarketWithETH(marketProxy, tradeDetails, {value})
146 | const callData = {...tx, value: tx.value?.toString()} as LimitedCallSpec
147 | return ethSend(this.walletInfo, callData)
148 |
149 | }
150 |
151 | public async batchBuyWithETH(swaps: Array) {
152 | const value = getSwapsValue(swaps)
153 | const tx = await this.swapExContract.populateTransaction.batchBuyWithETH(swaps, {value})
154 | const callData = {...tx, value: tx.value?.toString()} as LimitedCallSpec
155 | return ethSend(this.walletInfo, callData)
156 |
157 | // console.log("batchBuyWithETH", swaps.length, callData.value)
158 | // swaps.map(val => {
159 | // console.log(val.marketId, val.value)
160 | // })
161 | //
162 | // return getEstimateGas(this.walletInfo.rpcUrl || "", callData)
163 | //
164 |
165 | }
166 |
167 | public async buyNFTsWithETH(swaps: Array): Promise {
168 | if (!swaps || swaps.length == 0) throw 'No valid swap data'
169 | if (swaps.length == 1) {
170 | const swap = swaps[0]
171 | return this.buyOneWithETH(swap)
172 | }
173 | if (swaps.length > 1) {
174 | const marktIds = swaps.map(val => val.marketId)
175 | // if(this.walletInfo.chainId != 1 ||this.walletInfo.chainId != 4)
176 | const elementsAreEqual = array => array.every(el => el === array[0])
177 | if (elementsAreEqual(marktIds)) {
178 | // 单一市场
179 | if (this.walletInfo.chainId == 1 || this.walletInfo.chainId == 4) {
180 | return this.batchBuyFromSingleMarketWithETH(swaps)
181 | }
182 | } else {
183 | // 跨市场
184 | return this.batchBuyWithETH(swaps)
185 | }
186 | }
187 | }
188 |
189 | }
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BigNumber,
3 | BigNumberish,
4 | Contract,
5 | ContractTransaction,
6 | Overrides,
7 | PayableOverrides,
8 | PopulatedTransaction,
9 | } from "ethers";
10 | import {
11 | Asset,
12 | Token,
13 | ExchangeMetadata,
14 | APIConfig,
15 | MatchOrdersParams,
16 | } from "web3-accounts"
17 | import {ItemType, OrderType} from "./constants";
18 |
19 | export {OrderType}
20 | export {
21 | NULL_BLOCK_HASH,
22 | NULL_ADDRESS, getProvider, getEstimateGas,
23 | ethSend,
24 | BigNumber,
25 | ETH_TOKEN_ADDRESS,
26 | CHAIN_CONFIG,
27 | getChainRpcUrl,
28 | hexUtils,
29 | getEIP712DomainHash,
30 | createEIP712TypedData,
31 | } from 'web3-wallets'
32 | export type {Signature, WalletInfo, LimitedCallSpec, EIP712TypedData, EIP712Domain} from 'web3-wallets'
33 |
34 | export type {Asset, Token, APIConfig, ExchangeMetadata,MatchOrdersParams}
35 |
36 | export type SeaportConfig = {
37 | // Used because fulfillments may be invalid if confirmations take too long. Default buffer is 5 minutes
38 | ascendingAmountFulfillmentBuffer?: number;
39 |
40 | // Allow users to optionally skip balance and approval checks on order creation
41 | balanceAndApprovalChecksOnOrderCreation?: boolean;
42 |
43 | // A mapping of conduit key to conduit
44 | conduitKeyToConduit?: Record;
45 |
46 | overrides?: {
47 | contractAddress?: string;
48 | // A default conduit key to use when creating and fulfilling orders
49 | defaultConduitKey?: string;
50 | };
51 | };
52 |
53 | export type InputCriteria = {
54 | identifier: string;
55 | proof: string[];
56 | };
57 |
58 | export type NftItemType =
59 | | ItemType.ERC721
60 | | ItemType.ERC1155
61 | | ItemType.ERC721_WITH_CRITERIA
62 | | ItemType.ERC1155_WITH_CRITERIA;
63 |
64 | export enum BasicOrderRouteType {
65 | ETH_TO_ERC721,
66 | ETH_TO_ERC1155,
67 | ERC20_TO_ERC721,
68 | ERC20_TO_ERC1155,
69 | ERC721_TO_ERC20,
70 | ERC1155_TO_ERC20,
71 | }
72 |
73 | export type OfferItem = {
74 | itemType: ItemType;
75 | token: string;
76 | identifierOrCriteria: string;
77 | startAmount: string;
78 | endAmount: string;
79 | };
80 |
81 | export type ConsiderationItem = {
82 | itemType: ItemType;
83 | token: string;
84 | identifierOrCriteria: string;
85 | startAmount: string;
86 | endAmount: string;
87 | recipient: string;
88 | };
89 |
90 | export type Item = OfferItem | ConsiderationItem;
91 |
92 | export type OrderParameters = {
93 | offerer: string
94 | offer: OfferItem[]
95 | consideration: ConsiderationItem[]
96 | startTime: BigNumberish
97 | endTime: BigNumberish
98 | orderType: OrderType
99 | zone: string
100 | zoneHash: string
101 | salt: string
102 | conduitKey: string
103 | };
104 |
105 | export type OrderComponents = OrderParameters & { counter: number };
106 |
107 | export type Order = {
108 | parameters: OrderParameters;
109 | signature: string;
110 | };
111 |
112 | export type OrderWithCounter = {
113 | parameters: OrderComponents;
114 | signature: string;
115 | };
116 |
117 | export type OrderStatus = {
118 | isValidated: boolean;
119 | isCancelled: boolean;
120 | totalFilled: BigNumber;
121 | totalSize: BigNumber;
122 | };
123 |
124 |
125 |
126 | //----------- Item-------------
127 | export type BasicErc721Item = {
128 | itemType: ItemType.ERC721;
129 | token: string;
130 | identifier: string;
131 | };
132 |
133 | export type Erc721ItemWithCriteria = {
134 | itemType: ItemType.ERC721;
135 | token: string;
136 | identifiers: string[];
137 | // Used for criteria based items i.e. offering to buy 5 NFTs for a collection
138 | amount?: string;
139 | endAmount?: string;
140 | };
141 |
142 | type Erc721Item = BasicErc721Item | Erc721ItemWithCriteria;
143 |
144 | export type BasicErc1155Item = {
145 | itemType: ItemType.ERC1155;
146 | token: string;
147 | identifier: string;
148 | amount: string;
149 | endAmount?: string;
150 | };
151 |
152 | export type Erc1155ItemWithCriteria = {
153 | itemType: ItemType.ERC1155;
154 | token: string;
155 | identifiers: string[];
156 | amount: string;
157 | endAmount?: string;
158 | };
159 |
160 | type Erc1155Item = BasicErc1155Item | Erc1155ItemWithCriteria;
161 |
162 | export type CurrencyItem = {
163 | token?: string;
164 | amount: string;
165 | endAmount?: string;
166 | };
167 | export type CreateInputItem = Erc721Item | Erc1155Item | CurrencyItem;
168 | export type TipInputItem = CreateInputItem & { recipient: string };
169 |
170 | export type ConsiderationInputItem = CreateInputItem & { recipient?: string };
171 |
172 | export type CreateOrderInput = {
173 | conduitKey?: string;
174 | zone?: string;
175 | startTime?: string;
176 | endTime?: string;
177 | offer: readonly CreateInputItem[];
178 | consideration: readonly ConsiderationInputItem[];
179 | counter?: number;
180 | fees?: readonly Fee[];
181 | allowPartialFills?: boolean;
182 | restrictedByZone?: boolean;
183 | useProxy?: boolean;
184 | salt?: string;
185 | };
186 |
187 | export type AdvancedOrder = Order & {
188 | numerator: BigNumber;
189 | denominator: BigNumber;
190 | extraData: string;
191 | };
192 |
193 | export type FulfillOrdersMetadata = {
194 | order: Order;
195 | unitsToFill?: BigNumberish;
196 | orderStatus: OrderStatus;
197 | offerCriteria: InputCriteria[];
198 | considerationCriteria: InputCriteria[];
199 | tips: ConsiderationItem[];
200 | extraData: string;
201 | offererBalancesAndApprovals: any;
202 | offererOperator: string;
203 | }[];
204 |
205 |
206 |
207 | export type TransactionMethods = {
208 | buildTransaction: (overrides?: Overrides) => Promise;
209 | callStatic: (overrides?: Overrides) => Promise;
210 | estimateGas: (overrides?: Overrides) => Promise;
211 | transact: (overrides?: Overrides) => Promise;
212 | };
213 |
214 | export type ExchangeAction = {
215 | type: "exchange";
216 | transactionMethods: TransactionMethods;
217 | };
218 |
219 |
220 | export type Fee = {
221 | recipient: string;
222 | basisPoints: number;
223 | };
224 | export type InsufficientApprovals = {
225 | token: string;
226 | identifierOrCriteria: string;
227 | approvedAmount: BigNumber;
228 | requiredApprovedAmount: BigNumber;
229 | operator: string;
230 | itemType: ItemType;
231 | }[];
232 |
233 | export type CreateOrderAction = {
234 | type: "create";
235 | getMessageToSign: () => Promise;
236 | createOrder: () => Promise;
237 | };
238 |
239 | export type ApprovalAction = {
240 | type: "approval";
241 | token: string;
242 | identifierOrCriteria: string;
243 | itemType: ItemType;
244 | operator: string;
245 | transactionMethods: any
246 | };
247 |
248 | export type CreateOrderActions = readonly [
249 | ...ApprovalAction[],
250 | CreateOrderAction
251 | ];
252 |
253 | export type OrderExchangeActions = readonly [
254 | ...ApprovalAction[],
255 | ExchangeAction
256 | ];
257 |
258 | export type OrderUseCase = {
259 | actions: T extends CreateOrderAction
260 | ? CreateOrderActions
261 | : OrderExchangeActions ? U : never>;
262 | executeAllActions: () => Promise;
263 | };
264 |
265 | /**
266 | * The OpenSea account object appended to orders, providing extra metadata, profile images and usernames
267 | */
268 | export interface OpenSeaAccount {
269 | // Wallet address for this account
270 | address: string;
271 | // Public configuration info, including "affiliate" for users who are in the OpenSea affiliate program
272 | config: string;
273 |
274 | // This account's profile image - by default, randomly generated by the server
275 | profileImgUrl: string;
276 |
277 | // More information explicitly set by this account's owner on OpenSea
278 | user: OpenSeaUser | null;
279 | }
280 |
281 | export interface OpenSeaUser {
282 | // Username for this user
283 | username?: string;
284 | }
285 |
--------------------------------------------------------------------------------
/src/utils/assert.ts:
--------------------------------------------------------------------------------
1 | //
2 | import {Web3Assert, Schema} from "web3-assert";
3 |
4 | const userSchema = {
5 | type: "object",
6 | properties: {
7 | username: {type: "string", nullable: true},
8 | },
9 | };
10 |
11 | const accountSchema = {
12 | type: "object",
13 | properties: {
14 | address: {type: "string"},
15 | config: {type: "string"},
16 | profileImgUrl: {type: "string"},
17 | user: {...userSchema, nullable: true},
18 | },
19 | required: ["address", "config", "profileImgUrl", "user"],
20 | };
21 |
22 | const feeSchema = {
23 | type: "object",
24 | properties: {
25 | account: accountSchema,
26 | basisPoints: {type: "string"},
27 | },
28 | required: ["account", "basisPoints"],
29 | };
30 |
31 | // type PartialOrderV2Type = Omit & {
32 | // makerAssetBundle: object;
33 | // takerAssetBundle: object;
34 | // protocolData: object;
35 | // };
36 |
37 | const orderV2Schema = {
38 | type: "object",
39 | properties: {
40 | createdDate: {type: "string"},
41 | closingDate: {type: "string", nullable: true},
42 | listingTime: {type: "number"},
43 | expirationTime: {type: "number"},
44 | orderHash: {type: "string", nullable: true},
45 | maker: accountSchema,
46 | taker: {...accountSchema, nullable: true},
47 | protocolData: {type: "object"},
48 | protocolAddress: {type: "string"},
49 | currentPrice: {type: "string"},
50 | makerFees: {type: "array", items: feeSchema},
51 | takerFees: {type: "array", items: feeSchema},
52 | side: {type: "string"},
53 | orderType: {type: "string"},
54 | cancelled: {type: "boolean"},
55 | finalized: {type: "boolean"},
56 | markedInvalid: {type: "boolean"},
57 | clientSignature: {type: "string", nullable: true},
58 | makerAssetBundle: {type: "object"},
59 | takerAssetBundle: {type: "object"},
60 | },
61 | required: [
62 | "createdDate",
63 | "closingDate",
64 | "listingTime",
65 | "expirationTime",
66 | "orderHash",
67 | "maker",
68 | "taker",
69 | "protocolData",
70 | "protocolAddress",
71 | "currentPrice",
72 | "makerFees",
73 | "takerFees",
74 | "side",
75 | "orderType",
76 | "cancelled",
77 | "finalized",
78 | "markedInvalid",
79 | "clientSignature"
80 | ],
81 | };
82 |
83 | const offerItemSchama = {
84 | type: "object",
85 | properties: {
86 | itemType: {
87 | "type": "number",
88 | "enum": [0, 1, 2, 3]
89 | },
90 | token: {type: "string"},
91 | identifierOrCriteria: {type: "string"},
92 | startAmount: {type: "string"},
93 | endAmount: {type: "string"},
94 | },
95 | required: ["itemType", "token", "identifierOrCriteria", "startAmount", "endAmount"]
96 | }
97 |
98 | const considerationItemSchama = {
99 | type: "object",
100 | properties: {
101 | itemType: {
102 | "type": "number",
103 | "enum": [0, 1, 2, 3]
104 | },
105 | token: {type: "string"},
106 | identifierOrCriteria: {type: "string"},
107 | startAmount: {type: "string"},
108 | endAmount: {type: "string"},
109 | recipient: {type: "string"}
110 | },
111 | required: ["itemType", "token", "identifierOrCriteria", "startAmount", "endAmount", "recipient"],
112 | }
113 | const orderComponentsSchama = {
114 | type: "object",
115 | properties: {
116 | offerer: {type: "string"},
117 | offer: {"type": "array", "items": offerItemSchama},
118 | consideration: {"type": "array", "items": considerationItemSchama},
119 | startTime: {type: "string"},
120 | endTime: {type: "string"},
121 | orderType: {type: "number", "enum": [0, 1, 2, 3]},
122 | zone: {type: "string"},
123 | zoneHash: {type: "string"},
124 | salt: {type: "string"},
125 | conduitKey: {type: "string"},
126 | counter: {type: "number"}
127 | },
128 | required: [
129 | "offerer",
130 | "offer",
131 | "consideration",
132 | "startTime",
133 | "endTime",
134 | "orderType",
135 | "zone",
136 | "zoneHash",
137 | "salt",
138 | "conduitKey",
139 | "counter"
140 | ]
141 | }
142 |
143 | const orderParametersSchama = {
144 | type: "object",
145 | properties: {
146 | offerer: {type: "string"},
147 | offer: {"type": "array", "items": offerItemSchama},
148 | consideration: {"type": "array", "items": considerationItemSchama},
149 | startTime: {type: "string"},
150 | endTime: {type: "string"},
151 | orderType: {type: "number", "enum": [0, 1, 2, 3]},
152 | zone: {type: "string"},
153 | zoneHash: {type: "string"},
154 | salt: {type: "string"},
155 | conduitKey: {type: "string"}
156 | },
157 | required: [
158 | "offerer",
159 | "offer",
160 | "consideration",
161 | "startTime",
162 | "endTime",
163 | "orderType",
164 | "zone",
165 | "zoneHash",
166 | "salt",
167 | "conduitKey"
168 | ]
169 | }
170 | const orderWithCounterSchema = {
171 | type: "object",
172 | properties: {
173 | parameters: {...orderComponentsSchama, nullable: true},
174 | signature: {type: "string", nullable: true}
175 | },
176 | required: [
177 | "parameters",
178 | "signature",
179 | ],
180 | };
181 |
182 | const orderSchema = {
183 | type: "object",
184 | properties: {
185 | parameters: {...orderParametersSchama, nullable: true},
186 | signature: {type: "string", nullable: true}
187 | },
188 | required: [
189 | "parameters",
190 | "signature",
191 | ],
192 | };
193 |
194 | const assert = new Web3Assert()
195 | export const seaportAssert = {
196 | validateOrder: assert.compile(orderSchema as Schema),
197 | validateOrderV2: assert.compile(orderV2Schema as Schema),
198 | validateOrderWithCounter: assert.compile(orderWithCounterSchema as Schema)
199 | }
200 | //
201 |
--------------------------------------------------------------------------------
/src/utils/criteria.ts:
--------------------------------------------------------------------------------
1 | import { Side } from "../constants";
2 | import { InputCriteria, Item, Order } from "../types";
3 | import { isCriteriaItem } from "./item";
4 |
5 | export const generateCriteriaResolvers = ({
6 | orders,
7 | offerCriterias = [[]],
8 | considerationCriterias = [[]],
9 | }: {
10 | orders: Order[];
11 | offerCriterias?: InputCriteria[][];
12 | considerationCriterias?: InputCriteria[][];
13 | }) => {
14 |
15 | const offerCriteriaItems = orders.flatMap((order, orderIndex) =>
16 | order.parameters.offer
17 | .map(
18 | (item, index) =>
19 | ({
20 | orderIndex,
21 | item,
22 | index,
23 | side: Side.OFFER,
24 | } as const)
25 | )
26 | .filter(({ item }) => isCriteriaItem(item.itemType))
27 | );
28 |
29 | const considerationCriteriaItems = orders.flatMap((order, orderIndex) =>
30 | order.parameters.consideration
31 | .map(
32 | (item, index) =>
33 | ({
34 | orderIndex,
35 | item,
36 | index,
37 | side: Side.CONSIDERATION,
38 | } as const)
39 | )
40 | .filter(({ item }) => isCriteriaItem(item.itemType))
41 | );
42 |
43 | const mapCriteriaItemsToResolver = (
44 | criteriaItems:
45 | | typeof offerCriteriaItems
46 | | typeof considerationCriteriaItems,
47 | criterias: InputCriteria[][]
48 | ) =>
49 | criteriaItems.map(({ orderIndex, item, index, side }, i) => {
50 | const merkleRoot = item.identifierOrCriteria || "0";
51 | const inputCriteria: InputCriteria = criterias[orderIndex][i];
52 |
53 | return {
54 | orderIndex,
55 | index,
56 | side,
57 | identifier: inputCriteria.identifier,
58 | criteriaProof: merkleRoot === "0" ? [] : inputCriteria.proof,
59 | };
60 | });
61 |
62 | const criteriaResolvers = [
63 | ...mapCriteriaItemsToResolver(offerCriteriaItems, offerCriterias),
64 | ...mapCriteriaItemsToResolver(
65 | considerationCriteriaItems,
66 | considerationCriterias
67 | ),
68 | ];
69 |
70 | return criteriaResolvers;
71 | };
72 |
73 | export const getItemToCriteriaMap = (
74 | items: Item[],
75 | criterias: InputCriteria[]
76 | ) => {
77 | const criteriasCopy = [...criterias];
78 |
79 | return items.reduce((map, item) => {
80 | if (isCriteriaItem(item.itemType)) {
81 | map.set(item, criteriasCopy.shift() as InputCriteria);
82 | }
83 | return map;
84 | }, new Map- ());
85 | };
86 |
--------------------------------------------------------------------------------
/src/utils/fulfill.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BigNumber,
3 | BigNumberish,
4 | ethers,
5 | } from "ethers";
6 | import {ItemType} from "../constants";
7 | import type {
8 | Order,
9 | OrderParameters,
10 | OrderStatus,
11 | FulfillOrdersMetadata,
12 | } from "../types";
13 | import { getItemToCriteriaMap} from "./criteria";
14 | import {gcd} from "./gcd";
15 | import {
16 | getMaximumSizeForOrder,
17 | isCriteriaItem,
18 | isCurrencyItem,
19 | isErc721Item,
20 | isNativeCurrencyItem,
21 | } from "./item";
22 | import {
23 | areAllCurrenciesSame,
24 | totalItemsAmount,
25 | } from "./order";
26 |
27 |
28 | /**
29 | * We should use basic fulfill order if the order adheres to the following criteria:
30 | * 1. The order should not be partially filled.
31 | * 2. The order only contains a single offer item and contains at least one consideration item
32 | * 3. The order does not offer an item with Ether (or other native tokens) as its item type.
33 | * 4. The order only contains a single ERC721 or ERC1155 item and that item is not criteria-based
34 | * 5. All other items have the same Native or ERC20 item type and token
35 | * 6. All items have the same startAmount and endAmount
36 | * 7. First consideration item must contain the offerer as the recipient
37 | * 8. If the order has multiple consideration items and all consideration items other than the
38 | * first consideration item have the same item type as the offered item, the offered item
39 | * amount is not less than the sum of all consideration item amounts excluding the
40 | * first consideration item amount
41 | * 9. The token on native currency items needs to be set to the null address and the identifier on
42 | * currencies needs to be zero, and the amounts on the 721 item need to be 1
43 | */
44 | export const shouldUseBasicFulfill = (
45 | {offer, consideration, offerer}: OrderParameters,
46 | totalFilled: OrderStatus["totalFilled"]
47 | ) => {
48 | // 1. The order must not be partially filled
49 | if (!totalFilled.eq(0)) {
50 | return false;
51 | }
52 |
53 | // 2. Must be single offer and at least one consideration
54 | if (offer.length > 1 || consideration.length === 0) {
55 | return false;
56 | }
57 |
58 | const allItems = [...offer, ...consideration];
59 |
60 | const nfts = allItems.filter(({itemType}) =>
61 | [ItemType.ERC721, ItemType.ERC1155].includes(itemType)
62 | );
63 |
64 | const nftsWithCriteria = allItems.filter(({itemType}) =>
65 | isCriteriaItem(itemType)
66 | );
67 |
68 | const offersNativeCurrency = isNativeCurrencyItem(offer[0]);
69 |
70 | // 3. The order does not offer an item with Ether (or other native tokens) as its item type.
71 | if (offersNativeCurrency) {
72 | return false;
73 | }
74 |
75 | // 4. The order only contains a single ERC721 or ERC1155 item and that item is not criteria-based
76 | if (nfts.length !== 1 || nftsWithCriteria.length !== 0) {
77 | return false;
78 | }
79 |
80 | // 5. All currencies need to have the same address and item type (Native, ERC20)
81 | if (!areAllCurrenciesSame({offer, consideration})) {
82 | return false;
83 | }
84 |
85 | // 6. All individual items need to have the same startAmount and endAmount
86 | const differentStartAndEndAmount = allItems.some(
87 | ({startAmount, endAmount}) => startAmount !== endAmount
88 | );
89 |
90 | if (differentStartAndEndAmount) {
91 | return false;
92 | }
93 |
94 | const [firstConsideration, ...restConsideration] = consideration;
95 |
96 | // 7. First consideration item must contain the offerer as the recipient
97 | const firstConsiderationRecipientIsNotOfferer =
98 | firstConsideration.recipient.toLowerCase() !== offerer.toLowerCase();
99 |
100 | if (firstConsiderationRecipientIsNotOfferer) {
101 | return false;
102 | }
103 |
104 | // 8. If the order has multiple consideration items and all consideration items other than the
105 | // first consideration item have the same item type as the offered item, the offered item
106 | // amount is not less than the sum of all consideration item amounts excluding the
107 | // first consideration item amount
108 | if (
109 | consideration.length > 1 &&
110 | restConsideration.every((item) => item.itemType === offer[0].itemType) &&
111 | totalItemsAmount(restConsideration).endAmount.gt(offer[0].endAmount)
112 | ) {
113 | return false;
114 | }
115 |
116 | const currencies = allItems.filter(isCurrencyItem);
117 |
118 | // 9. The token on native currency items needs to be set to the null address and the identifier on
119 | // currencies needs to be zero, and the amounts on the 721 item need to be 1
120 | const nativeCurrencyIsZeroAddress = currencies
121 | .filter(({itemType}) => itemType === ItemType.NATIVE)
122 | .every(({token}) => token === ethers.constants.AddressZero);
123 |
124 | const currencyIdentifiersAreZero = currencies.every(
125 | ({identifierOrCriteria}) => BigNumber.from(identifierOrCriteria).eq(0)
126 | );
127 |
128 | const erc721sAreSingleAmount = nfts
129 | .filter(({itemType}) => itemType === ItemType.ERC721)
130 | .every(({endAmount}) => endAmount === "1");
131 |
132 | return (
133 | nativeCurrencyIsZeroAddress &&
134 | currencyIdentifiersAreZero &&
135 | erc721sAreSingleAmount
136 | );
137 | };
138 |
139 |
140 | export function generateFulfillOrdersFulfillments(
141 | ordersMetadata: FulfillOrdersMetadata
142 | ): {
143 | offerFulfillments: any[];
144 | considerationFulfillments: any[];
145 | } {
146 | const hashAggregateKey = ({
147 | sourceOrDestination,
148 | operator = "",
149 | token,
150 | identifier,
151 | }: {
152 | sourceOrDestination: string;
153 | operator?: string;
154 | token: string;
155 | identifier: string;
156 | }) => `${sourceOrDestination}-${operator}-${token}-${identifier}`;
157 |
158 | const offerAggregatedFulfillments: Record = {};
159 |
160 | const considerationAggregatedFulfillments: Record = {};
161 |
162 | ordersMetadata.forEach(({order, offererOperator, offerCriteria}, orderIndex) => {
163 | const itemToCriteria = getItemToCriteriaMap(
164 | order.parameters.offer,
165 | offerCriteria
166 | );
167 |
168 | return order.parameters.offer.forEach((item, itemIndex) => {
169 | const aggregateKey = `${hashAggregateKey({
170 | sourceOrDestination: order.parameters.offerer,
171 | operator: offererOperator,
172 | token: item.token,
173 | identifier:
174 | itemToCriteria.get(item)?.identifier ?? item.identifierOrCriteria,
175 | // We tack on the index to ensure that erc721s can never be aggregated and instead must be in separate arrays
176 | })}${isErc721Item(item.itemType) ? itemIndex : ""}`;
177 |
178 | offerAggregatedFulfillments[aggregateKey] = [
179 | ...(offerAggregatedFulfillments[aggregateKey] ?? []),
180 | {orderIndex, itemIndex},
181 | ];
182 | });
183 | }
184 | );
185 |
186 | ordersMetadata.forEach(({order, considerationCriteria, tips}, orderIndex) => {
187 | const itemToCriteria = getItemToCriteriaMap(
188 | order.parameters.consideration,
189 | considerationCriteria
190 | );
191 | return [...order.parameters.consideration, ...tips].forEach(
192 | (item, itemIndex) => {
193 | const aggregateKey = `${hashAggregateKey({
194 | sourceOrDestination: item.recipient,
195 | token: item.token,
196 | identifier:itemToCriteria.get(item)?.identifier ?? item.identifierOrCriteria,
197 | // We tack on the index to ensure that erc721s can never be aggregated and instead must be in separate arrays
198 | })}${isErc721Item(item.itemType) ? itemIndex : ""}`;
199 |
200 | considerationAggregatedFulfillments[aggregateKey] = [
201 | ...(considerationAggregatedFulfillments[aggregateKey] ?? []),
202 | {orderIndex, itemIndex},
203 | ];
204 | }
205 | );
206 | }
207 | );
208 |
209 | return {
210 | offerFulfillments: Object.values(offerAggregatedFulfillments),
211 | considerationFulfillments: Object.values(considerationAggregatedFulfillments),
212 | };
213 | }
214 |
215 | export const getAdvancedOrderNumeratorDenominator = (
216 | order: Order,
217 | unitsToFill?: BigNumberish
218 | ) => {
219 | // Used for advanced order cases
220 | const maxUnits = getMaximumSizeForOrder(order);
221 | const unitsToFillBn = BigNumber.from(unitsToFill);
222 |
223 | // Reduce the numerator/denominator as optimization
224 | const unitsGcd = gcd(unitsToFillBn, maxUnits);
225 |
226 | const numerator = unitsToFill
227 | ? unitsToFillBn.div(unitsGcd)
228 | : BigNumber.from(1);
229 | const denominator = unitsToFill ? maxUnits.div(unitsGcd) : BigNumber.from(1);
230 |
231 | return {numerator, denominator};
232 | };
233 |
--------------------------------------------------------------------------------
/src/utils/gcd.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber, BigNumberish } from "ethers";
2 |
3 | export const gcd = (a: BigNumberish, b: BigNumberish): BigNumber => {
4 | const bnA = BigNumber.from(a);
5 | const bnB = BigNumber.from(b);
6 |
7 | if (bnA.eq(0)) {
8 | return bnB;
9 | }
10 |
11 | return gcd(bnB.mod(a), bnA);
12 | };
13 |
14 | export const findGcd = (elements: BigNumberish[]) => {
15 | let result = BigNumber.from(elements[0]);
16 |
17 | for (let i = 1; i < elements.length; i++) {
18 | result = gcd(elements[i], result);
19 |
20 | if (result.eq(1)) {
21 | return result;
22 | }
23 | }
24 |
25 | return result;
26 | };
27 |
--------------------------------------------------------------------------------
/src/utils/item.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers";
2 | import { ItemType } from "../constants";
3 | import type { InputCriteria, Item, Order, OrderParameters } from "../types";
4 | import { getItemToCriteriaMap } from "./criteria";
5 | import { findGcd } from "./gcd";
6 |
7 | export const isCurrencyItem = ({ itemType }: Item) =>
8 | [ItemType.NATIVE, ItemType.ERC20].includes(itemType);
9 |
10 | export const isNativeCurrencyItem = ({ itemType }: Item) =>
11 | itemType === ItemType.NATIVE;
12 |
13 | export const isErc20Item = (itemType: Item["itemType"]) =>
14 | itemType === ItemType.ERC20;
15 |
16 | export const isErc721Item = (itemType: Item["itemType"]) =>
17 | [ItemType.ERC721, ItemType.ERC721_WITH_CRITERIA].includes(itemType);
18 |
19 | export const isErc1155Item = (itemType: Item["itemType"]) =>
20 | [ItemType.ERC1155, ItemType.ERC1155_WITH_CRITERIA].includes(itemType);
21 |
22 | export const isCriteriaItem = (itemType: Item["itemType"]) =>
23 | [ItemType.ERC721_WITH_CRITERIA, ItemType.ERC1155_WITH_CRITERIA].includes(
24 | itemType
25 | );
26 |
27 | export type TimeBasedItemParams = {
28 | isConsiderationItem?: boolean;
29 | currentBlockTimestamp: number;
30 | ascendingAmountTimestampBuffer: number;
31 | } & Pick;
32 |
33 | export const getPresentItemAmount = ({
34 | startAmount,
35 | endAmount,
36 | timeBasedItemParams,
37 | }: Pick
- & {
38 | timeBasedItemParams?: TimeBasedItemParams;
39 | }): BigNumber => {
40 | const startAmountBn = BigNumber.from(startAmount);
41 | const endAmountBn = BigNumber.from(endAmount);
42 |
43 | if (!timeBasedItemParams) {
44 | return startAmountBn.gt(endAmountBn) ? startAmountBn : endAmountBn;
45 | }
46 |
47 | const {
48 | isConsiderationItem,
49 | currentBlockTimestamp,
50 | ascendingAmountTimestampBuffer,
51 | startTime,
52 | endTime,
53 | } = timeBasedItemParams;
54 |
55 | const duration = BigNumber.from(endTime).sub(startTime);
56 | const isAscending = endAmountBn.gt(startAmount);
57 | const adjustedBlockTimestamp = BigNumber.from(
58 | isAscending
59 | ? currentBlockTimestamp + ascendingAmountTimestampBuffer
60 | : currentBlockTimestamp
61 | );
62 |
63 | if (adjustedBlockTimestamp.lt(startTime)) {
64 | return startAmountBn;
65 | }
66 |
67 | const elapsed = (
68 | adjustedBlockTimestamp.gt(endTime)
69 | ? BigNumber.from(endTime)
70 | : adjustedBlockTimestamp
71 | ).sub(startTime);
72 |
73 | const remaining = duration.sub(elapsed);
74 |
75 | // Adjust amounts based on current time
76 | // For offer items, we round down
77 | // For consideration items, we round up
78 | return startAmountBn
79 | .mul(remaining)
80 | .add(endAmountBn.mul(elapsed))
81 | .add(isConsiderationItem ? duration.sub(1) : 0)
82 | .div(duration);
83 | };
84 |
85 | export const getSummedTokenAndIdentifierAmounts = ({
86 | items,
87 | criterias,
88 | timeBasedItemParams,
89 | }: {
90 | items: Item[];
91 | criterias: InputCriteria[];
92 | timeBasedItemParams?: TimeBasedItemParams;
93 | }) => {
94 | const itemToCriteria = getItemToCriteriaMap(items, criterias);
95 |
96 | const tokenAndIdentifierToSummedAmount = items.reduce<
97 | Record>
98 | >((map, item) => {
99 | const identifierOrCriteria =
100 | itemToCriteria.get(item)?.identifier ?? item.identifierOrCriteria;
101 |
102 | return {
103 | ...map,
104 | [item.token]: {
105 | ...map[item.token],
106 | // Being explicit about the undefined type as it's possible for it to be undefined at first iteration
107 | [identifierOrCriteria]: (
108 | (map[item.token]?.[identifierOrCriteria] as BigNumber | undefined) ??
109 | BigNumber.from(0)
110 | ).add(
111 | getPresentItemAmount({
112 | startAmount: item.startAmount,
113 | endAmount: item.endAmount,
114 | timeBasedItemParams,
115 | })
116 | ),
117 | },
118 | };
119 | }, {});
120 |
121 | return tokenAndIdentifierToSummedAmount;
122 | };
123 |
124 | /**
125 | * Returns the maximum size of units possible for the order
126 | * If any of the items on a partially fillable order specify a different "startAmount" and "endAmount
127 | * (e.g. they are ascending-amount or descending-amount items), the fraction will be applied to both amounts
128 | * prior to determining the current price. This ensures that cleanly divisible amounts can be chosen when
129 | * constructing the order without a dependency on the time when the order is ultimately fulfilled.
130 | */
131 | export const getMaximumSizeForOrder = ({parameters: { offer, consideration }}: Order) => {
132 | const allItems = [...offer, ...consideration];
133 |
134 | const amounts = allItems.flatMap(({ startAmount, endAmount }) => [
135 | startAmount,
136 | endAmount,
137 | ]);
138 |
139 | return findGcd(amounts);
140 | };
141 |
--------------------------------------------------------------------------------
/src/utils/merkletree.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers";
2 | import { keccak256 } from "ethers/lib/utils";
3 | import MerkleTreeJS from "merkletreejs";
4 |
5 | const hashIdentifier = (identifier: string) =>
6 | keccak256(
7 | Buffer.from(
8 | BigNumber.from(identifier).toHexString().slice(2).padStart(64, "0"),
9 | "hex"
10 | )
11 | );
12 |
13 | /**
14 | * Simple wrapper over the MerkleTree in merkletreejs.
15 | * Handles hashing identifiers to be compatible with Seaport.
16 | */
17 | export class MerkleTree {
18 | tree: MerkleTreeJS;
19 |
20 | constructor(identifiers: string[]) {
21 | this.tree = new MerkleTreeJS(identifiers.map(hashIdentifier), keccak256, {
22 | sort: true,
23 | });
24 | }
25 |
26 | getProof(identifier: string): string[] {
27 | return this.tree.getHexProof(hashIdentifier(identifier));
28 | }
29 |
30 | getRoot() {
31 | return this.tree.getRoot().toString("hex") ? this.tree.getHexRoot() : "0";
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/order.ts:
--------------------------------------------------------------------------------
1 | import {BigNumber, BigNumberish, ethers} from "ethers";
2 | import {ItemType, ONE_HUNDRED_PERCENT_BP} from "../constants";
3 | import type {
4 | ConsiderationItem,
5 | CreateInputItem,
6 | Fee,
7 | Item,
8 | OfferItem,
9 | Order,
10 | OrderParameters,
11 | } from "../types";
12 | import {getMaximumSizeForOrder, isCurrencyItem} from "./item";
13 | import {MerkleTree} from "./merkletree";
14 | import {OrderStatus} from "../types";
15 |
16 | const multiplyBasisPoints = (amount: BigNumberish, basisPoints: BigNumberish) =>
17 | BigNumber.from(amount)
18 | .mul(BigNumber.from(basisPoints))
19 | .div(ONE_HUNDRED_PERCENT_BP);
20 |
21 | export const feeToConsiderationItem = ({
22 | fee,
23 | token,
24 | baseAmount,
25 | baseEndAmount = baseAmount,
26 | }: {
27 | fee: Fee;
28 | token: string;
29 | baseAmount: BigNumberish;
30 | baseEndAmount?: BigNumberish;
31 | }): ConsiderationItem => {
32 | return {
33 | itemType:
34 | token === ethers.constants.AddressZero ? ItemType.NATIVE : ItemType.ERC20,
35 | token,
36 | identifierOrCriteria: "0",
37 | startAmount: multiplyBasisPoints(baseAmount, fee.basisPoints).toString(),
38 | endAmount: multiplyBasisPoints(baseEndAmount, fee.basisPoints).toString(),
39 | recipient: fee.recipient,
40 | };
41 | };
42 |
43 | export const deductFees = (
44 | items: T[],
45 | fees?: readonly Fee[]
46 | ): T[] => {
47 | if (!fees) {
48 | return items;
49 | }
50 |
51 | const totalBasisPoints = fees.reduce(
52 | (accBasisPoints, fee) => accBasisPoints + fee.basisPoints,
53 | 0
54 | );
55 |
56 | return items.map((item) => ({
57 | ...item,
58 | startAmount: isCurrencyItem(item)
59 | ? BigNumber.from(item.startAmount)
60 | .sub(multiplyBasisPoints(item.startAmount, totalBasisPoints))
61 | .toString()
62 | : item.startAmount,
63 | endAmount: isCurrencyItem(item)
64 | ? BigNumber.from(item.endAmount)
65 | .sub(multiplyBasisPoints(item.endAmount, totalBasisPoints))
66 | .toString()
67 | : item.endAmount,
68 | }));
69 | };
70 |
71 | export const mapInputItemToOfferItem = (item: CreateInputItem): OfferItem => {
72 | // Item is an NFT
73 | if ("itemType" in item) {
74 | // Convert this to a criteria based item
75 | if ("identifiers" in item) {
76 | const tree = new MerkleTree(item.identifiers);
77 |
78 | return {
79 | itemType:
80 | item.itemType === ItemType.ERC721
81 | ? ItemType.ERC721_WITH_CRITERIA
82 | : ItemType.ERC1155_WITH_CRITERIA,
83 | token: item.token,
84 | identifierOrCriteria: tree.getRoot(),
85 | startAmount: item.amount ?? "1",
86 | endAmount: item.endAmount ?? item.amount ?? "1",
87 | };
88 | }
89 |
90 | if ("amount" in item || "endAmount" in item) {
91 | return {
92 | itemType: item.itemType,
93 | token: item.token,
94 | identifierOrCriteria: item.identifier,
95 | startAmount: item.amount,
96 | endAmount: item.endAmount ?? item.amount ?? "1",
97 | };
98 | }
99 |
100 | return {
101 | itemType: item.itemType,
102 | token: item.token,
103 | identifierOrCriteria: item.identifier,
104 | startAmount: "1",
105 | endAmount: "1",
106 | };
107 | }
108 |
109 | // Item is a currency
110 | return {
111 | itemType:
112 | item.token && item.token !== ethers.constants.AddressZero
113 | ? ItemType.ERC20
114 | : ItemType.NATIVE,
115 | token: item.token ?? ethers.constants.AddressZero,
116 | identifierOrCriteria: "0",
117 | startAmount: item.amount,
118 | endAmount: item.endAmount ?? item.amount,
119 | };
120 | };
121 |
122 | export const areAllCurrenciesSame = ({
123 | offer,
124 | consideration,
125 | }: Pick) => {
126 | const allItems = [...offer, ...consideration];
127 | const currencies = allItems.filter(isCurrencyItem);
128 |
129 | return currencies.every(
130 | ({itemType, token}) =>
131 | itemType === currencies[0].itemType &&
132 | token.toLowerCase() === currencies[0].token.toLowerCase()
133 | );
134 | };
135 |
136 | export const totalItemsAmount = (items: T[]) => {
137 | const initialValues = {
138 | startAmount: BigNumber.from(0),
139 | endAmount: BigNumber.from(0),
140 | };
141 |
142 | return items
143 | .map(({startAmount, endAmount}) => ({
144 | startAmount,
145 | endAmount,
146 | }))
147 | .reduce(
148 | (
149 | {startAmount: totalStartAmount, endAmount: totalEndAmount},
150 | {startAmount, endAmount}
151 | ) => ({
152 | startAmount: totalStartAmount.add(startAmount),
153 | endAmount: totalEndAmount.add(endAmount),
154 | }),
155 | {
156 | startAmount: BigNumber.from(0),
157 | endAmount: BigNumber.from(0),
158 | }
159 | );
160 | };
161 |
162 | /**
163 | * Maps order offer and consideration item amounts based on the order's filled status
164 | * After applying the fraction, we can view this order as the "canonical" order for which we
165 | * check approvals and balances
166 | */
167 | export const mapOrderAmountsFromFilledStatus = (
168 | order: Order,
169 | {totalFilled, totalSize}: { totalFilled: BigNumber; totalSize: BigNumber }
170 | ): Order => {
171 | if (totalFilled.eq(0) || totalSize.eq(0)) {
172 | return order;
173 | }
174 |
175 | // i.e if totalFilled is 3 and totalSize is 4, there are 1 / 4 order amounts left to fill.
176 | const basisPoints = totalSize
177 | .sub(totalFilled)
178 | .mul(ONE_HUNDRED_PERCENT_BP)
179 | .div(totalSize);
180 |
181 | return {
182 | parameters: {
183 | ...order.parameters,
184 | offer: order.parameters.offer.map((item) => ({
185 | ...item,
186 | startAmount: multiplyBasisPoints(
187 | item.startAmount,
188 | basisPoints
189 | ).toString(),
190 | endAmount: multiplyBasisPoints(item.endAmount, basisPoints).toString(),
191 | })),
192 | consideration: order.parameters.consideration.map((item) => ({
193 | ...item,
194 | startAmount: multiplyBasisPoints(
195 | item.startAmount,
196 | basisPoints
197 | ).toString(),
198 | endAmount: multiplyBasisPoints(item.endAmount, basisPoints).toString(),
199 | })),
200 | },
201 | signature: order.signature,
202 | };
203 | };
204 |
205 | /**
206 | * Maps order offer and consideration item amounts based on the units needed to fulfill
207 | * After applying the fraction, we can view this order as the "canonical" order for which we
208 | * check approvals and balances
209 | * Returns the numerator and denominator as well, converting this to an AdvancedOrder
210 | */
211 | export const mapOrderAmountsFromUnitsToFill = (
212 | order: Order,
213 | {
214 | unitsToFill,
215 | totalFilled,
216 | totalSize,
217 | }: { unitsToFill: BigNumberish; totalFilled: BigNumber; totalSize: BigNumber }
218 | ): Order => {
219 | const unitsToFillBn = BigNumber.from(unitsToFill);
220 |
221 | if (unitsToFillBn.lte(0)) {
222 | throw new Error("Units to fill must be greater than 1");
223 | }
224 |
225 | const maxUnits = getMaximumSizeForOrder(order);
226 |
227 | if (totalSize.eq(0)) {
228 | totalSize = maxUnits;
229 | }
230 |
231 | // This is the percentage of the order that is left to be fulfilled, and therefore we can't fill more than that.
232 | const remainingOrderPercentageToBeFilled = totalSize
233 | .sub(totalFilled)
234 | .mul(ONE_HUNDRED_PERCENT_BP)
235 | .div(totalSize);
236 |
237 | // i.e if totalSize is 8 and unitsToFill is 3, then we multiply every amount by 3 / 8
238 | const unitsToFillBasisPoints = unitsToFillBn
239 | .mul(ONE_HUNDRED_PERCENT_BP)
240 | .div(maxUnits);
241 |
242 | // We basically choose the lesser between the units requested to be filled and the actual remaining order amount left
243 | // This is so that if a user tries to fulfill an order that is 1/2 filled, and supplies a fraction such as 3/4, the maximum
244 | // amount to fulfill is 1/2 instead of 3/4
245 | const basisPoints = remainingOrderPercentageToBeFilled.gt(
246 | unitsToFillBasisPoints
247 | )
248 | ? unitsToFillBasisPoints
249 | : remainingOrderPercentageToBeFilled;
250 |
251 | return {
252 | parameters: {
253 | ...order.parameters,
254 | offer: order.parameters.offer.map((item) => ({
255 | ...item,
256 | startAmount: multiplyBasisPoints(
257 | item.startAmount,
258 | basisPoints
259 | ).toString(),
260 | endAmount: multiplyBasisPoints(item.endAmount, basisPoints).toString(),
261 | })),
262 | consideration: order.parameters.consideration.map((item) => ({
263 | ...item,
264 | startAmount: multiplyBasisPoints(
265 | item.startAmount,
266 | basisPoints
267 | ).toString(),
268 | endAmount: multiplyBasisPoints(item.endAmount, basisPoints).toString(),
269 | })),
270 | },
271 | signature: order.signature,
272 | };
273 | };
274 |
275 | export const generateRandomSalt = () => {
276 | return `0x${Buffer.from(ethers.utils.randomBytes(16)).toString("hex")}`;
277 | };
278 |
279 |
280 | export function validateAndSanitizeFromOrderStatus(
281 | order: Order,
282 | orderStatus: OrderStatus
283 | ): Order {
284 | const {isValidated, isCancelled, totalFilled, totalSize} = orderStatus;
285 |
286 | if (totalSize.gt(0) && totalFilled.div(totalSize).eq(1)) {
287 | throw new Error("The order you are trying to fulfill is already filled");
288 | }
289 |
290 | if (isCancelled) {
291 | throw new Error("The order you are trying to fulfill is cancelled");
292 | }
293 |
294 | if (isValidated) {
295 | // If the order is already validated, manually wipe the signature off of the order to save gas
296 | return {parameters: {...order.parameters}, signature: "0x"};
297 | }
298 |
299 | return order;
300 | }
301 |
302 | export const openseaAssetToAsset = (asset) => {
303 | return {
304 | "tokenId": asset.token_id,
305 | "tokenAddress": asset.address,
306 | "schemaName": asset.schema_name,
307 | "collection": {
308 | "royaltyFeePoints": asset.royaltyFeePoints,
309 | "royaltyFeeAddress": asset.royaltyFeeAddress
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/src/utils/usecase.ts:
--------------------------------------------------------------------------------
1 | import { CallOverrides, Contract, Overrides, PayableOverrides } from "ethers";
2 | import {
3 | CreateOrderAction,
4 | ExchangeAction,
5 | OrderUseCase,
6 | TransactionMethods,
7 | } from "../types";
8 |
9 | export const executeAllActions = async <
10 | T extends CreateOrderAction | ExchangeAction
11 | >(
12 | actions: OrderUseCase["actions"]
13 | ) => {
14 | for (let i = 0; i < actions.length - 1; i++) {
15 | const action = actions[i];
16 | if (action.type === "approval") {
17 | await action.transactionMethods.transact();
18 | }
19 | }
20 |
21 | const finalAction = actions[actions.length - 1] as T;
22 |
23 | return finalAction.type === "create"
24 | ? await finalAction.createOrder()
25 | : await finalAction.transactionMethods.transact();
26 | };
27 |
28 | const instanceOfOverrides = <
29 | T extends Overrides | PayableOverrides | CallOverrides
30 | >(
31 | obj: any
32 | ): obj is T => {
33 | const validKeys = [
34 | "gasLimit",
35 | "gasPrice",
36 | "maxFeePerGas",
37 | "maxPriorityFeePerGas",
38 | "nonce",
39 | "type",
40 | "accessList",
41 | "customData",
42 | "ccipReadEnabled",
43 | "value",
44 | "blockTag",
45 | "CallOverrides",
46 | ];
47 |
48 | return (
49 | obj === undefined ||
50 | Object.keys(obj).every((key) => validKeys.includes(key))
51 | );
52 | };
53 |
54 | export const getTransactionMethods = <
55 | T extends Contract,
56 | U extends keyof T["functions"]
57 | >(
58 | contract: T,
59 | method: U,
60 | args: Parameters
61 | ): TransactionMethods => {
62 | const lastArg = args[args.length - 1];
63 |
64 | let initialOverrides: Overrides;
65 |
66 | if (instanceOfOverrides(lastArg)) {
67 | initialOverrides = lastArg;
68 | args.pop();
69 | }
70 |
71 | return {
72 | callStatic: (overrides?: Overrides) => {
73 | const mergedOverrides = { ...initialOverrides, ...overrides };
74 |
75 | return contract.callStatic[method as string](
76 | ...[...args, mergedOverrides]
77 | );
78 | },
79 | estimateGas: (overrides?: Overrides) => {
80 | const mergedOverrides = { ...initialOverrides, ...overrides };
81 |
82 | return contract.estimateGas[method as string](
83 | ...[...args, mergedOverrides]
84 | );
85 | },
86 | transact: (overrides?: Overrides) => {
87 | const mergedOverrides = { ...initialOverrides, ...overrides };
88 |
89 | return contract[method as string](...args, mergedOverrides);
90 | },
91 | buildTransaction: (overrides?: Overrides) => {
92 | const mergedOverrides = { ...initialOverrides, ...overrides };
93 |
94 | return contract.populateTransaction[method as string](
95 | ...[...args, mergedOverrides]
96 | );
97 | },
98 | };
99 | };
100 |
--------------------------------------------------------------------------------
/test/abi/fulfillAdvancedOrder.ts:
--------------------------------------------------------------------------------
1 | import * as secrets from '../../../../secrets.json'
2 | import {Seaport} from "../../src/Seaport";
3 | import {SeaportSDK} from "../../src/index";
4 | import {Web3ABICoder} from "web3-abi-coder";
5 | import {SeaportABI} from "./Seaport";
6 |
7 |
8 |
9 | ;(async () => {
10 | try {
11 | const seaportCoder = new Web3ABICoder(SeaportABI)
12 | // const callData = "0xe7acab24000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e282f7d52da0e27d188732abf1fa02450d28cd4100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000ef708d45cc7a517faad9b36c2c1fb7e693da5e44000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000062a970530000000000000000000000000000000000000000000000000000000062aac1d300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000124a5206323ad920000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000054616c0815c306fc22417b96282ca4aa6f47d35700000000000000000000000000000000000000000000000000000000000020b700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002df69673c81800000000000000000000000000000000000000000000000000002df69673c818000000000000000000000000000ef708d45cc7a517faad9b36c2c1fb7e693da5e4400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000146d983375600000000000000000000000000000000000000000000000000000146d98337560000000000000000000000000005b3256965e7c3cf26e11fcaf296dfc8807c01073000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d48c89a602000000000000000000000000000000000000000000000000000003d48c89a6020000000000000000000000000003213579537bc370b5a1e22eeb3a60aa5bb954c930000000000000000000000000000000000000000000000000000000000000041a6e25f25d16061d70c209f2991a30f51f83bfca95f2b1acb314362476bfb70b37df79224e95315cbd89bdc4ef3cdaf469f2f81628ab69622e53f56cad69c24ad1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
13 | const callData = "0xe7acab240000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a56b3317ed60dc4e1027a63ffbe9df6fb10240100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000006f3258f5b99f9846c9f7b5031c3b02b943e3318d00000000000000000000000000000000e88fe2628ebc5da81d2b3cead633e89e0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000062ab21bb0000000000000000000000000000000000000000000000000000000062d2aebb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006034c9ec8a77970000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f01900e7506e537756525b2c10d119a7a50584410000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004fefa17b724000000000000000000000000000000000000000000000000000004fefa17b7240000000000000000000000000006f3258f5b99f9846c9f7b5031c3b02b943e3318d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000008de9c5a032463c561423387a9648c5c7bcc5bc900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a94d74f430000000000000000000000000000000000000000000000000000006a94d74f430000000000000000000000000006f3258f5b99f9846c9f7b5031c3b02b943e3318d0000000000000000000000000000000000000000000000000000000000000041c53eb20321f899950db9a9b03c6689f8d88d23b653f5d253123b3fadd810911d5a9108fa1e2176860b0e5a76905ee46f90a6312dee910ae329b2f08ca4c817641c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
14 | console.log(JSON.stringify(seaportCoder.decodeInput(callData), null, 2))
15 | } catch (e) {
16 | console.log(e)
17 | }
18 | }
19 | )()
20 |
21 |
22 | // fulfillAdvancedOrder 0xe7acab24
23 | // 1.advancedOrder:AdvancedOrder
24 | // parameters:
25 | // offerer:'0x6F3258f5B99f9846C9f7b5031c3b02B943e3318d',
26 | // zone:'0x00000000E88FE2628EbC5DA81d2b3CeaD633E89e',
27 | // offer[]:
28 | // {itemType:'2',token:'0xF01900e7506E537756525B2c10d119A7A5058441',identifierOrCriteria:'1',startAmount:'1',endAmount:'1'}
29 | // consideration[]:
30 | // {itemType:'0',token:'0x0000000000000000000000000000000000000000',identifierOrCriteria:'0',startAmount:'22500000000000000',endAmount:'22500000000000000',recipient:'0x6F3258f5B99f9846C9f7b5031c3b02B943e3318d'}
31 | // {itemType:'0',token:'0x0000000000000000000000000000000000000000',identifierOrCriteria:'0',startAmount:'625000000000000',endAmount:'625000000000000',recipient:'0x8De9C5A032463C561423387a9648c5C7BCC5BC90'}
32 | // {itemType:'0',token:'0x0000000000000000000000000000000000000000',identifierOrCriteria:'0',startAmount:'1875000000000000',endAmount:'1875000000000000',recipient:'0x6F3258f5B99f9846C9f7b5031c3b02B943e3318d'}
33 | // orderType:'2',
34 | // startTime:'1655382459',
35 | // endTime:'1657974459',
36 | // zoneHash:'0x0000000000000000000000000000000000000000000000000000000000000000',
37 | // salt:'27079639625791383',
38 | // conduitKey:'0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000',
39 | // totalOriginalConsiderationItems:'3',
40 | // numerator:'1',
41 | // denominator:'1',
42 | // signature:'0xc53eb20321f899950db9a9b03c6689f8d88d23b653f5d253123b3fadd810911d5a9108fa1e2176860b0e5a76905ee46f90a6312dee910ae329b2f08ca4c817641c',
43 | // extraData:'0x',
44 | // 2.criteriaResolvers:CriteriaResolver[]
45 | // 3.fulfillerConduitKey: 0x0000000000000000000000000000000000000000000000000000000000000000
46 | // 4.recipient: 0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401
47 |
48 |
--------------------------------------------------------------------------------
/test/abi/gem.fulfillAvailableAdvancedOrders.ts:
--------------------------------------------------------------------------------
1 | import * as secrets from '../../../../secrets.json'
2 | import {Seaport} from "../../src/Seaport";
3 | import {SeaportSDK} from "../../src/index";
4 | import {Web3ABICoder} from "web3-abi-coder";
5 | import {SeaportABI} from "./Seaport";
6 |
7 |
8 | const seller = '0x9F7A946d935c8Efc7A8329C0d894A69bA241345A'
9 | const buyer = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
10 |
11 | ///**
12 | // * @dev Basic orders can supply any number of additional recipients, with the
13 | // * implied assumption that they are supplied from the offered ETH (or other
14 | // * native token) or ERC20 token for the order.
15 | // */
16 | // struct AdditionalRecipient {
17 | // uint256 amount;
18 | // address payable recipient;
19 | // }
20 |
21 |
22 | // /**
23 | // * @dev The full set of order components, with the exception of the counter,
24 | // * must be supplied when fulfilling more sophisticated orders or groups of
25 | // * orders. The total number of original consideration items must also be
26 | // * supplied, as the caller may specify additional consideration items.
27 | // */
28 | // struct OrderParameters {
29 | // address offerer; // 0x00
30 | // address zone; // 0x20
31 | // OfferItem[] offer; // 0x40
32 | // ConsiderationItem[] consideration; // 0x60
33 | // OrderType orderType; // 0x80
34 | // uint256 startTime; // 0xa0
35 | // uint256 endTime; // 0xc0
36 | // bytes32 zoneHash; // 0xe0
37 | // uint256 salt; // 0x100
38 | // bytes32 conduitKey; // 0x120
39 | // uint256 totalOriginalConsiderationItems; // 0x140
40 | // // offer.length // 0x160
41 | // }
42 | //
43 | // struct BasicOrderParameters {
44 | // // calldata offset
45 | // address considerationToken; // 0x24
46 | // uint256 considerationIdentifier; // 0x44
47 | // uint256 considerationAmount; // 0x64
48 | // address payable offerer; // 0x84
49 | // address zone; // 0xa4
50 | // address offerToken; // 0xc4
51 | // uint256 offerIdentifier; // 0xe4
52 | // uint256 offerAmount; // 0x104
53 | // BasicOrderType basicOrderType; // 0x124
54 | // uint256 startTime; // 0x144
55 | // uint256 endTime; // 0x164
56 | // bytes32 zoneHash; // 0x184
57 | // uint256 salt; // 0x1a4
58 | // bytes32 offererConduitKey; // 0x1c4
59 | // bytes32 fulfillerConduitKey; // 0x1e4
60 | // uint256 totalOriginalAdditionalRecipients; // 0x204
61 | // AdditionalRecipient[] additionalRecipients; // 0x224
62 | // bytes signature; // 0x244
63 | // // Total length, excluding dynamic array data: 0x264 (580)
64 | // }
65 | //
66 | //(address considerationToken,
67 | // uint256 considerationIdentifier,
68 | // uint256 considerationAmount,
69 | // address offerer,
70 | // address zone,
71 | // address offerToken,
72 | // uint256 offerIdentifier,
73 | // uint256 offerAmount,
74 | // uint8 basicOrderType,
75 | // uint256 startTime,
76 | // uint256 endTime,
77 | // bytes32 zoneHash,
78 | // uint256 salt,
79 | // bytes32 offererConduitKey,
80 | // bytes32 fulfillerConduitKey,
81 | // uint256 totalOriginalAdditionalRecipients,
82 | // tuple(uint256 amount, address recipient)[] additionalRecipients,
83 | // bytes signature)
84 |
85 | ;(async () => {
86 | try {
87 | const seaportCoder = new Web3ABICoder(SeaportABI)
88 |
89 | const callData = "0x87201b4100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000006e00000000000000000000000000000000000000000000000000000000000000780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057c17fdc47720d3c56cfb0c3ded460267bcd642d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000005200000000000000000000000000000000000000000000000000000000000000580000000000000000000000000b38544ccf295d78b7ae7b2bae5dbebdb1f09910d000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000062aabcbd0000000000000000000000000000000000000000000000000000000062b3f73c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000004052425a34a35e53d916d94393cbc4eb1c572c3c0000000000000000000000000000000000000000000000000000000000001c3a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062967a5c8460000000000000000000000000000000000000000000000000000062967a5c846000000000000000000000000000b38544ccf295d78b7ae7b2bae5dbebdb1f09910d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000000000000000000000000000000002aa1efb94e0000000000000000000000000008de9c5a032463c561423387a9648c5c7bcc5bc900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005543df729c0000000000000000000000000000000000000000000000000000005543df729c000000000000000000000000000df8aaa3b5042b13fa2464ce7c6b29773e0e7ada10000000000000000000000000000000000000000000000000000000000000040f97da91b9288cd7b72e9e40d47d0e1d8f0e91123e48d51d9c154ff531ccde44a5fba5df37df570d134387a67fe158b10586a44dc3f32cefd3c0f3f21813a2e7600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"
90 | console.log(JSON.stringify(seaportCoder.decodeInput(callData), null, 2))
91 | } catch (e) {
92 | console.log(e)
93 | }
94 | }
95 | )()
96 |
97 |
98 | // cancel 0xfd9f1e10
99 | // fulfillAdvancedOrder 0xe7acab24
100 | // fulfillAvailableAdvancedOrders 0x87201b41
101 | // fulfillAvailableOrders 0xed98a574
102 |
103 | // fulfillBasicOrder 0xfb0f3ee1
104 |
105 | // fulfillOrder 0xb3a34c4c
106 | // getCounter 0xf07ec373
107 | // getOrderHash 0x79df72bd
108 | // getOrderStatus 0x46423aa7
109 | // incrementCounter 0x5b34b966
110 | // information 0xf47b7740
111 | // matchAdvancedOrders 0x55944a42
112 | // matchOrders 0xa8174404
113 | // name 0x06fdde03
114 | // validate 0x88147732
115 |
--------------------------------------------------------------------------------
/test/data/gcd.test.ts:
--------------------------------------------------------------------------------
1 | import {gcd} from "../../src/utils/gcd";
2 |
3 | console.log(gcd(8,102).toString())
4 |
--------------------------------------------------------------------------------
/test/seaport/api.test.ts:
--------------------------------------------------------------------------------
1 | import * as secrets from '../../../secrets.json'
2 | import {ETHToken, OrderSide, SellOrderParams, transactionToCallData} from "web3-accounts";
3 | import {SeaportSDK} from "../../src/index";
4 | import {generateRandomSalt} from "../../src/seaport";
5 | import {SeaportAPI} from "../../src/api/seaport";
6 | import {apiConfig} from "../data/orders";
7 |
8 | const buyer = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
9 | // const buyer = '0x32f4B63A46c1D12AD82cABC778D75aBF9889821a'
10 |
11 | // const apiBaseUrl: 'https://api.element.market/bridge/opensea'},
12 | // 44: {apiBaseUrl: 'https://api-test.element.market/bridge/opensea'},
13 | // proxyUrl: 'http://127.0.0.1:7890'
14 | const chainId = 4
15 | ;(async () => {
16 | // const solt = generateRandomSalt()
17 | // console.log(solt)
18 | const sdk = new SeaportAPI({...apiConfig[chainId], chainId})
19 | try {
20 | const owner = {
21 | owner: buyer,
22 | limit: 1,
23 | }
24 | const ownerAsset = await sdk.getAssets(owner)
25 | console.log(ownerAsset.length)
26 | // const ownerOrders = await sdk.getOrders(owner)
27 | // console.log(ownerOrders.count)
28 | const query = {
29 | asset_contract_address: '0x5fecbbbaf9f3126043a48a35eb2eb8667d469d53', //
30 | token_ids: ['8001'],
31 | side: OrderSide.All
32 | }
33 |
34 | const {orders} = await sdk.getOrders(query)
35 | console.log(orders)
36 |
37 | } catch (e) {
38 | console.log(e)
39 | }
40 | }
41 | )()
42 |
43 |
44 |
--------------------------------------------------------------------------------
/test/seaport/assert.test.ts:
--------------------------------------------------------------------------------
1 | import {seaportAssert} from "../../src/utils/assert";
2 | import {erc8001} from "../data/orders";
3 |
4 |
5 | if (!seaportAssert.validateOrderWithCounter(erc8001)) console.log(seaportAssert.validateOrderWithCounter.errors)
6 |
--------------------------------------------------------------------------------
/test/seaport/create.adjust.test.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2 | // @ts-ignore
3 | import * as secrets from '../../../secrets.json'
4 | import {ethers} from "web3-wallets";
5 | import {SeaportSDK} from "../../src/index";
6 | import {apiConfig, asset721} from "../data/orders";
7 | import {openseaAssetToAsset} from "../../src/utils/order";
8 | import {parseEther} from "@ethersproject/units/src.ts/index";
9 |
10 | // const buyer = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
11 | const buyer = '0x32f4B63A46c1D12AD82cABC778D75aBF9889821a'
12 |
13 | // proxyUrl: 'http://127.0.0.1:7890'
14 | const chainId = 1
15 | ;(async () => {
16 | const sdk = new SeaportSDK({
17 | chainId,
18 | address: buyer,
19 | privateKeys: secrets.privateKeys
20 | }, apiConfig[chainId])
21 | try {
22 |
23 | const openseaAsset = (await sdk.getOwnerAssets({limit: 1}))[0]
24 | console.log(openseaAsset)
25 |
26 | const oldOrder = {
27 | "parameters": {
28 | "offerer": "0x32f4B63A46c1D12AD82cABC778D75aBF9889821a",
29 | "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00",
30 | "orderType": 2,
31 | "startTime": "1658492751",
32 | "endTime": "1659097551",
33 | "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
34 | "salt": "38495518456876316",
35 | "offer": [
36 | {
37 | "itemType": 2,
38 | "token": "0x984ac9911c6839a6870a1040a5fb89dd66513bc5",
39 | "identifierOrCriteria": "6136",
40 | "startAmount": "1",
41 | "endAmount": "1"
42 | }
43 | ],
44 | "consideration": [
45 | {
46 | "itemType": 0,
47 | "token": "0x0000000000000000000000000000000000000000",
48 | "identifierOrCriteria": "0",
49 | "startAmount": "555000000000000000",
50 | "endAmount": "555000000000000000",
51 | "recipient": "0x32f4B63A46c1D12AD82cABC778D75aBF9889821a"
52 | },
53 | {
54 | "itemType": 0,
55 | "token": "0x0000000000000000000000000000000000000000",
56 | "identifierOrCriteria": "0",
57 | "startAmount": "15000000000000000",
58 | "endAmount": "15000000000000000",
59 | "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90"
60 | },
61 | {
62 | "itemType": 0,
63 | "token": "0x0000000000000000000000000000000000000000",
64 | "identifierOrCriteria": "0",
65 | "startAmount": "30000000000000000",
66 | "endAmount": "30000000000000000",
67 | "recipient": "0x545ed214984f3ec57fb6a614f2a6211f0481547f"
68 | }
69 | ],
70 | "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000",
71 | "counter": 0
72 | },
73 | "signature": "0x126747102bf87674b670413fc9b490130c04f1130116f780b272ffd6e66a7be15b6b8c7d30eb18cb2393c2e3feb1721484473bcae93b1b4342e115556e6e03851b"
74 | }
75 |
76 | // const approve = await sdk.getOrderApprove(sellParams, OrderSide.Sell)
77 | const order = await sdk.adjustOrder({
78 | orderStr: JSON.stringify(oldOrder),
79 | basePrice: ethers.utils.parseEther("0.91").toString(),
80 | "royaltyFeePoints": 500,
81 | "royaltyFeeAddress": "0x545ed214984f3ec57fb6a614f2a6211f0481547f"
82 | })
83 | // await sdk.contracts.checkOrderPost(JSON.stringify(order))
84 | const res = await sdk.postOrder(JSON.stringify(order))
85 | console.log("postOrder success", res)
86 |
87 | // const tx = await sdk.fulfillOrder(JSON.stringify(order))
88 | // await tx.wait()
89 | // console.log(tx.hash)
90 |
91 | } catch (e) {
92 | console.log(e)
93 | }
94 | }
95 | )()
96 |
97 |
98 |
--------------------------------------------------------------------------------
/test/seaport/create.listing.test.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2 | // @ts-ignore
3 | import * as secrets from '../../../secrets.json'
4 | import {ETHToken, OrderSide, SellOrderParams, transactionToCallData} from "web3-accounts";
5 | import {SeaportSDK} from "../../src/index";
6 | import {apiConfig, asset721} from "../data/orders";
7 | import {openseaAssetToAsset} from "../../src/utils/order";
8 |
9 | // const buyer = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
10 | const buyer = '0x32f4B63A46c1D12AD82cABC778D75aBF9889821a'
11 |
12 | // proxyUrl: 'http://127.0.0.1:7890'
13 | const chainId = 1
14 | ;(async () => {
15 | const sdk = new SeaportSDK({
16 | chainId,
17 | address: buyer,
18 | privateKeys: secrets.privateKeys
19 | }, apiConfig[chainId])
20 | try {
21 | const openseaAsset = (await sdk.getOwnerAssets({limit: 1}))[0]
22 | console.log(openseaAsset)
23 | // const sellParams = {
24 | // "asset": {
25 | // "tokenId": asset.token_id,
26 | // "tokenAddress": asset.address,
27 | // "schemaName": asset.schema_name,
28 | // "collection": {
29 | // "royaltyFeePoints": asset.royaltyFeePoints,
30 | // "royaltyFeeAddress": asset.royaltyFeeAddress
31 | // }
32 | // },
33 | // "startAmount": 0.5
34 | // } as SellOrderParams
35 | // const query = {
36 | // asset_contract_address: '0x984ac9911c6839a6870a1040a5fb89dd66513bc5', //
37 | // token_ids: ['6136'],
38 | // side:OrderSide.Sell
39 | //
40 | // }
41 | // const {orders} = await sdk.api.getOrders(query)
42 | // console.log(orders)
43 |
44 | // const asset = openseaAssetToAsset(openseaAsset)//asset721[chainId][0]
45 | const asset = asset721[chainId][0]
46 | const sellParams = {
47 | asset,
48 | "startAmount": 0.6
49 | } as SellOrderParams
50 | // const approve = await sdk.getOrderApprove(sellParams, OrderSide.Sell)
51 | const order = await sdk.createSellOrder(sellParams)
52 | // await sdk.contracts.checkOrderPost(JSON.stringify(order))
53 | const res = await sdk.postOrder(JSON.stringify(order))
54 | console.log("postOrder success", res)
55 |
56 | // const tx = await sdk.fulfillOrder(JSON.stringify(order))
57 | // await tx.wait()
58 | // console.log(tx.hash)
59 |
60 | } catch (e) {
61 | console.log(e)
62 | }
63 | }
64 | )()
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/seaport/create.offer.test.ts:
--------------------------------------------------------------------------------
1 |
2 | // @ts-ignore
3 | import * as secrets from '../../../secrets.json'
4 | import {BuyOrderParams, OrderSide} from "web3-accounts";
5 | import {SeaportSDK} from "../../src/index";
6 | import {apiConfig} from "../data/orders";
7 |
8 | const buyer = '0x32f4B63A46c1D12AD82cABC778D75aBF9889821a'
9 |
10 |
11 | const chainId = 4
12 | ;(async () => {
13 | const sdk = new SeaportSDK({
14 | chainId,
15 | address: buyer,
16 | privateKeys: secrets.privateKeys
17 | }, apiConfig[chainId])
18 | try {
19 | // const openseaAsset = (await sdk.getOwnerAssets({limit: 1}))[0]
20 | // const asset = openseaAssetToAsset(openseaAsset)
21 | const asset = {
22 | tokenId: '1',
23 | tokenAddress: '0x13e4ccba895870d99e4e196ac1d4b678aea196be',
24 | schemaName: 'ERC721'
25 | }
26 |
27 | const buyParams = {
28 | asset,
29 | "startAmount": 0.002
30 | } as BuyOrderParams
31 |
32 | // const apporve = await sdk.getOrderApprove(buyParams, OrderSide.Buy)
33 | const order = await sdk.createBuyOrder(buyParams)
34 | // console.log(order)
35 | const res = await sdk.postOrder(JSON.stringify(order))
36 | console.log(res)
37 |
38 | // const tx = await sdk.fulfillOrder(JSON.stringify(order))
39 | // await tx.wait()
40 | // console.log(tx.hash)
41 |
42 | } catch (e) {
43 | console.log(e)
44 | }
45 | }
46 | )()
47 |
--------------------------------------------------------------------------------
/test/seaport/fulfillOrders.test.ts:
--------------------------------------------------------------------------------
1 | import {apiConfig, erc8001, gemOrder, order721} from "../data/orders";
2 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3 | // @ts-ignore
4 | import * as secrets from '../../../secrets.json'
5 | import {OrderSide, SellOrderParams} from "web3-accounts";
6 | import {SeaportSDK} from "../../src/index";
7 |
8 | const address = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
9 | const chainId = 4
10 | ;(async () => {
11 | const sdk = new SeaportSDK(
12 | {chainId, address, privateKeys: secrets.privateKeys},
13 | apiConfig[chainId])
14 | try {
15 | const sellParams = {
16 | "asset": {
17 | "tokenId": "8001",
18 | "tokenAddress": "0x5FecBbBAf9f3126043A48A35eb2eb8667D469D53",
19 | "schemaName": "ERC721"
20 | },
21 | "startAmount": 0.02
22 | } as SellOrderParams
23 | const order = await sdk.createSellOrder(sellParams)
24 |
25 | const orderStr = JSON.stringify(order)
26 | // const res = await sdk.cancelOrders([orderStr])
27 |
28 | const res = await sdk.fulfillOrders({orderList: [{orderStr}]})
29 | await res.wait()
30 | console.log(res.hash)
31 | // const query = {
32 | // asset_contract_address: '0x5fecbbbaf9f3126043a48a35eb2eb8667d469d53', //
33 | // token_ids: ['8001'],
34 | // side: OrderSide.All
35 | // }
36 | // const {orders} = await sdk.api.getOrders(query)
37 | // console.log(orders)
38 | // const res = await sdk.fulfillOrders({orderList: [{orderStr: JSON.stringify(orders[0])}]})
39 |
40 | } catch (e) {
41 | console.log(e)
42 | }
43 | }
44 | )()
45 |
46 |
47 |
--------------------------------------------------------------------------------
/test/seaport/getOrderHash.test.ts:
--------------------------------------------------------------------------------
1 |
2 | import {SeaportSDK} from "../../src/index";
3 | // @ts-ignore
4 | import * as secrets from '../../../secrets.json'
5 | import {getEIP712StructHash} from "web3-wallets";
6 | import {EIP_712_ORDER_TYPE, EIP_712_PRIMARY_TYPE} from "../../src/constants";
7 |
8 | import {getOrderHash} from "../utisl";
9 | import {orderV2} from "../data/orders";
10 |
11 | const buyer = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
12 |
13 | const hash = getOrderHash(orderV2)
14 | const structhash = getEIP712StructHash(EIP_712_PRIMARY_TYPE, EIP_712_ORDER_TYPE, orderV2)
15 | console.assert(hash == "0xa1dc54ca93f82077855645df0a030fd8c242a13cd4e0c29682ab88a927524a1d")
16 | console.assert(hash == structhash)
17 | ;(async () => {
18 | const sdk = new SeaportSDK({
19 | chainId: 1,
20 | address: buyer,
21 | privateKeys: secrets.privateKeys
22 | })
23 | const orderHash = await sdk.contracts.seaport.getOrderHash(orderV2)
24 | console.assert(hash == orderHash)
25 | console.log(await sdk.getOwnerOrders({side:1}))
26 | })()
27 |
--------------------------------------------------------------------------------
/test/seaport/post.order.ts:
--------------------------------------------------------------------------------
1 | import {apiConfig, erc8001, gemOrder, order721} from "../data/orders";
2 | import {SeaportAPI} from "../../src/api/seaport";
3 | // @ts-ignore
4 | import * as secrets from '../../../secrets.json'
5 | import {OrderSide, SellOrderParams} from "web3-accounts";
6 | import {SeaportSDK} from "../../src/index";
7 |
8 | const address = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
9 | const chainId = 1
10 | ;(async () => {
11 | const sdk = new SeaportSDK(
12 | {chainId, address, privateKeys: secrets.privateKeys},
13 | apiConfig[chainId])
14 | try {
15 |
16 | const sellParams = {
17 | "asset": {
18 | "tokenId": "8001",
19 | "tokenAddress": "0x5FecBbBAf9f3126043A48A35eb2eb8667D469D53",
20 | "schemaName": "ERC721",
21 | "collection": {
22 | "royaltyFeePoints": 0,
23 | "royaltyFeeAddress": ""
24 | }
25 | },
26 | "startAmount": 0.02
27 | } as SellOrderParams
28 |
29 |
30 | // const order = await sdk.sea.createSellOrder(sellParams)
31 | // console.log(order)
32 | const res = await sdk.postOrder(JSON.stringify(order721))
33 |
34 |
35 | } catch (e) {
36 | console.log(e)
37 | }
38 | }
39 | )()
40 |
41 |
42 |
--------------------------------------------------------------------------------
/test/seaport/swap.test.ts:
--------------------------------------------------------------------------------
1 | import * as secrets from '../../../secrets.json'
2 | import {ETHToken, SellOrderParams, transactionToCallData} from "web3-accounts";
3 | import {SeaportSDK} from "../../src/index";
4 |
5 | const buyer = '0x0A56b3317eD60dC4E1027A63ffbE9df6fb102401'
6 |
7 | const chainId = 4
8 | const apiConfig = {
9 | 1: {
10 | proxyUrl: 'http://127.0.0.1:7890',
11 | apiTimeout: 10200,
12 | protocolFeePoints: 250
13 | },
14 | 4: {
15 | proxyUrl: 'http://127.0.0.1:7890',
16 | apiTimeout: 10200,
17 | protocolFeePoints: 250
18 | }
19 | }
20 |
21 | ;(async () => {
22 | const sdk = new SeaportSDK({
23 | chainId,
24 | address: buyer,
25 | privateKeys: secrets.privateKeys
26 | }, apiConfig[chainId])
27 | try {
28 |
29 | const sellParams = {
30 | "asset": {
31 | "tokenId": "26",
32 | "tokenAddress": "0x5f069e9e7311da572299533b7078859085f7d82c",
33 | "schemaName": "ERC721",
34 | "collection": {
35 | "royaltyFeePoints": 0,
36 | "royaltyFeeAddress": ""
37 | }
38 | },
39 | "startAmount": 0.012
40 | } as SellOrderParams
41 | const order = await sdk.createSellOrder(sellParams)
42 |
43 | // const callData = await sdk.contracts.fulfillBasicOrder({order}) //
44 | const recipient = "0x00000000006c3852cbef3e08e8df289169ede581"
45 | const callData = await sdk.contracts.fulfillAdvancedOrder({order, takerAmount: "1",recipient})
46 | const gas = await sdk.contracts.estimateGas(transactionToCallData(callData))
47 | const tx = await sdk.swap.batchBuyWithETHSimulate([{
48 | value: callData?.value?.toString() || "",
49 | tradeData: callData?.data || "",
50 | marketId: "1"
51 | }])
52 | console.log("OK", tx)
53 |
54 | } catch (e) {
55 | console.log(e)
56 | }
57 | }
58 | )()
59 |
--------------------------------------------------------------------------------
/test/utisl.ts:
--------------------------------------------------------------------------------
1 | import {OrderComponents} from "../src/types";
2 | import {ethers} from "ethers";
3 |
4 |
5 | /**
6 | * Calculates the order hash of order components so we can forgo executing a request to the contract
7 | * This saves us RPC calls and latency.
8 | */
9 | export function getOrderHash (orderComponents: OrderComponents): string {
10 | // ethers.utils.formatEther()
11 | const offerItemTypeString =
12 | "OfferItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount)";
13 | const considerationItemTypeString =
14 | "ConsiderationItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount,address recipient)";
15 | const orderComponentsPartialTypeString =
16 | "OrderComponents(address offerer,address zone,OfferItem[] offer,ConsiderationItem[] consideration,uint8 orderType,uint256 startTime,uint256 endTime,bytes32 zoneHash,uint256 salt,bytes32 conduitKey,uint256 counter)";
17 | const orderTypeString = `${orderComponentsPartialTypeString}${considerationItemTypeString}${offerItemTypeString}`;
18 |
19 | const offerItemTypeHash = ethers.utils.keccak256(
20 | ethers.utils.toUtf8Bytes(offerItemTypeString)
21 | );
22 | const considerationItemTypeHash = ethers.utils.keccak256(
23 | ethers.utils.toUtf8Bytes(considerationItemTypeString)
24 | );
25 | const orderTypeHash = ethers.utils.keccak256(
26 | ethers.utils.toUtf8Bytes(orderTypeString)
27 | );
28 |
29 | const offerHash = ethers.utils.keccak256(
30 | "0x" +
31 | orderComponents.offer
32 | .map((offerItem) => {
33 | return ethers.utils
34 | .keccak256(
35 | "0x" +
36 | [
37 | offerItemTypeHash.slice(2),
38 | offerItem.itemType.toString().padStart(64, "0"),
39 | offerItem.token.slice(2).padStart(64, "0"),
40 | ethers.BigNumber.from(offerItem.identifierOrCriteria)
41 | .toHexString()
42 | .slice(2)
43 | .padStart(64, "0"),
44 | ethers.BigNumber.from(offerItem.startAmount)
45 | .toHexString()
46 | .slice(2)
47 | .padStart(64, "0"),
48 | ethers.BigNumber.from(offerItem.endAmount)
49 | .toHexString()
50 | .slice(2)
51 | .padStart(64, "0"),
52 | ].join("")
53 | )
54 | .slice(2);
55 | })
56 | .join("")
57 | );
58 |
59 | const considerationHash = ethers.utils.keccak256(
60 | "0x" +
61 | orderComponents.consideration
62 | .map((considerationItem) => {
63 | return ethers.utils
64 | .keccak256(
65 | "0x" +
66 | [
67 | considerationItemTypeHash.slice(2),
68 | considerationItem.itemType.toString().padStart(64, "0"),
69 | considerationItem.token.slice(2).padStart(64, "0"),
70 | ethers.BigNumber.from(
71 | considerationItem.identifierOrCriteria
72 | )
73 | .toHexString()
74 | .slice(2)
75 | .padStart(64, "0"),
76 | ethers.BigNumber.from(considerationItem.startAmount)
77 | .toHexString()
78 | .slice(2)
79 | .padStart(64, "0"),
80 | ethers.BigNumber.from(considerationItem.endAmount)
81 | .toHexString()
82 | .slice(2)
83 | .padStart(64, "0"),
84 | considerationItem.recipient.slice(2).padStart(64, "0"),
85 | ].join("")
86 | )
87 | .slice(2);
88 | })
89 | .join("")
90 | );
91 |
92 | const derivedOrderHash = ethers.utils.keccak256(
93 | "0x" +
94 | [
95 | orderTypeHash.slice(2),
96 | orderComponents.offerer.slice(2).padStart(64, "0"),
97 | orderComponents.zone.slice(2).padStart(64, "0"),
98 | offerHash.slice(2),
99 | considerationHash.slice(2),
100 | orderComponents.orderType.toString().padStart(64, "0"),
101 | ethers.BigNumber.from(orderComponents.startTime)
102 | .toHexString()
103 | .slice(2)
104 | .padStart(64, "0"),
105 | ethers.BigNumber.from(orderComponents.endTime)
106 | .toHexString()
107 | .slice(2)
108 | .padStart(64, "0"),
109 | orderComponents.zoneHash.slice(2),
110 | orderComponents.salt.slice(2).padStart(64, "0"),
111 | orderComponents.conduitKey.slice(2).padStart(64, "0"),
112 | ethers.BigNumber.from(orderComponents.counter)
113 | .toHexString()
114 | .slice(2)
115 | .padStart(64, "0"),
116 | ].join("")
117 | );
118 |
119 | return derivedOrderHash;
120 | }
121 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "./src/**/*",
4 | "./index.ts"
5 | ],
6 | "exclude": [
7 | "./node_modules",
8 | "./test/**/*",
9 | "config/webpack.config.js",
10 | "config/webpack.node.config.js"
11 | ],
12 | "compilerOptions": {
13 | "baseUrl": "./src",
14 | "paths": {},
15 | // This must be specified if "paths" is.
16 | "outDir": "./lib",
17 | "declaration": true,
18 | "target": "ES2017",
19 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
20 | "module": "commonjs",
21 | /* 增加 type 类型的检查 */
22 | "isolatedModules": true,
23 | // 支持导入json模块
24 | "strict": true,
25 | /* Enable all strict type-checking options. */
26 | "esModuleInterop": true,
27 | /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
28 | "skipLibCheck": true,
29 | /* Skip type checking of declaration files. */
30 | "forceConsistentCasingInFileNames": true,
31 | /* Disallow inconsistently-cased references to the same file. */
32 | "typeRoots": [
33 | "node_modules/@0x/typescript-typings/types",
34 | "node_modules/@types"
35 | ],
36 | "downlevelIteration": true,
37 | "lib": [
38 | "es5",
39 | "dom",
40 | "es6",
41 | "es2019"
42 | ],
43 | "noImplicitAny": false,
44 | // 不允许隐式的 any 类型
45 | "removeComments": true,
46 | "resolveJsonModule": true
47 | }
48 | }
49 |
--------------------------------------------------------------------------------