├── .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 | 2 | 3 | 4 | 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 | 3 | 4 | WalletConnect 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 |
132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 142 | 148 | 149 | 150 | 151 | 152 | 155 | 156 | 157 | 158 |
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 | setPage(val.key)} 33 | items={items} 34 | /> 35 | 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 | --------------------------------------------------------------------------------