├── .nvmrc ├── .prettierignore ├── .eslintignore ├── .github ├── CODEOWNERS ├── workflows │ ├── npm-publish.yml │ ├── stale.yml │ └── ci.yml ├── pull_request_template.md └── contributing.md ├── src ├── index.ts ├── helpers.ts ├── constants.ts ├── client.ts └── types.ts ├── img └── banner.png ├── .husky ├── pre-commit └── pre-push ├── .prettierrc ├── renovate.json ├── .gitignore ├── tsconfig.json ├── .vscode └── settings.json ├── .eslintrc.cjs ├── jest.config.ts ├── LICENSE ├── tests ├── helpers.ts └── client.spec.ts ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 24.12.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ProjectOpenSea/developer-experience 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client.js'; 2 | export * from './types.js'; 3 | -------------------------------------------------------------------------------- /img/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectOpenSea/stream-js/HEAD/img/banner.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run check-types 5 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | export const collectionTopic = (slug: string) => { 2 | return `collection:${slug}`; 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { Network } from './types.js'; 2 | 3 | export const ENDPOINTS = { 4 | [Network.MAINNET]: 'wss://stream.openseabeta.com/socket' 5 | }; 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "packageRules": [ 4 | { 5 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 6 | "automerge": true 7 | } 8 | ], 9 | "schedule": ["before 9am on monday"] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Optional npm cache directory 13 | .npm 14 | 15 | # Optional eslint cache 16 | .eslintcache 17 | 18 | # Yarn Integrity file 19 | .yarn-integrity 20 | 21 | # Examples 22 | examples/ 23 | 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*.ts"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "./dist", 8 | "strict": true, 9 | "declaration": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true, 12 | "baseUrl": "." 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "files.insertFinalNewline": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | }, 7 | "[javascript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "[typescript]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true 5 | }, 6 | root: true, 7 | reportUnusedDisableDirectives: true, 8 | parser: '@typescript-eslint/parser', 9 | plugins: ['@typescript-eslint', 'prettier'], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/recommended', 13 | 'plugin:prettier/recommended' 14 | ], 15 | rules: { 16 | 'prettier/prettier': 2 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import type { Config } from '@jest/types'; 3 | 4 | const pack = require('./package'); 5 | 6 | const config: Config.InitialOptions = { 7 | displayName: pack.name, 8 | id: pack.name, 9 | modulePaths: ['/src'], 10 | preset: 'ts-jest/presets/default-esm', 11 | extensionsToTreatAsEsm: ['.ts'], 12 | testEnvironment: 'jsdom', 13 | moduleNameMapper: { 14 | '^(\\.{1,2}/.*)\\.js$': '$1' 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | id-token: write 16 | steps: 17 | - uses: actions/checkout@v5 18 | - uses: actions/setup-node@v6 19 | with: 20 | node-version-file: .nvmrc 21 | registry-url: https://registry.npmjs.org/ 22 | - run: npm ci 23 | - run: npm run build 24 | - run: npm test 25 | - run: npm publish --provenance --access public 26 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues' 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions. If you believe this was a mistake, please comment.' 14 | stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions. If you believe this was a mistake, please comment.' 15 | days-before-stale: 60 16 | days-before-close: 14 17 | operations-per-run: 100 18 | exempt-pr-labels: 'work-in-progress' 19 | exempt-issue-labels: 'work-in-progress' 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OpenSea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/helpers.ts: -------------------------------------------------------------------------------- 1 | import type { Channel, Socket } from 'phoenix'; 2 | import { BaseStreamMessage, EventType, OpenSeaStreamClient } from '../src'; 3 | 4 | export const getSocket = (client: OpenSeaStreamClient): Socket => { 5 | // @ts-expect-error private access 6 | return client.socket; 7 | }; 8 | 9 | export const getChannels = ( 10 | client: OpenSeaStreamClient 11 | ): Map => { 12 | // @ts-expect-error private access 13 | return client.channels; 14 | }; 15 | 16 | type ChannelParams = { 17 | join_ref?: string; 18 | ref?: string; 19 | topic: string; 20 | event: EventType; 21 | payload: BaseStreamMessage; 22 | }; 23 | 24 | export const encode = ({ 25 | join_ref, 26 | ref, 27 | topic, 28 | event, 29 | payload 30 | }: ChannelParams) => { 31 | return JSON.stringify([join_ref, ref, topic, event, payload]); 32 | }; 33 | 34 | export const mockEvent = ( 35 | eventType: EventType, 36 | payload: Payload 37 | ): BaseStreamMessage => { 38 | return { 39 | event_type: eventType, 40 | payload, 41 | sent_at: Date.now().toString() 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Change Overview 2 | 3 | 4 | 5 | ## Impact of Change 6 | 7 | 8 | 9 | - [ ] Bug fix 10 | - [ ] External Facing (resolves an issue customers are currently experiencing) 11 | - [ ] Security Impact (fixes a potential vulnerability) 12 | - [ ] Feature 13 | - [ ] Visible Change (changes semver of API surface or other change that would impact user/dev experience) 14 | - [ ] High Usage (impacts a major part of the core workflow for users) 15 | - [ ] Performance Improvement 16 | - [ ] Refactoring 17 | - [ ] Other: _Describe here_ 18 | 19 | ## Detailed Technical Description of Change 20 | 21 | 22 | 23 | ## Testing Approach and Results 24 | 25 | 26 | 27 | ## Collateral Work or Changes 28 | 29 | 30 | 31 | ## Operational Impact 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: code-quality 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | CI: true 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v5 17 | - uses: actions/setup-node@v6 18 | with: 19 | node-version-file: .nvmrc 20 | 21 | - name: Install dependencies 22 | run: npm ci 23 | 24 | - name: Run linters 25 | run: npm run lint:check 26 | 27 | test: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v5 31 | - uses: actions/setup-node@v6 32 | with: 33 | node-version-file: .nvmrc 34 | 35 | - name: Install dependencies 36 | run: npm ci 37 | 38 | - name: Run tests 39 | run: npm run coverage -- --ci --runInBand 40 | 41 | - name: Upload code coverage 42 | uses: coverallsapp/github-action@master 43 | with: 44 | github-token: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | test-earliest-node-engine-support: 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v5 50 | - uses: actions/setup-node@v6 51 | 52 | - name: Install dependencies 53 | run: npm ci 54 | 55 | - name: Run tests 56 | run: npm run test -- --ci --runInBand 57 | 58 | build: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v5 62 | - uses: actions/setup-node@v6 63 | with: 64 | node-version-file: .nvmrc 65 | 66 | - name: Install dependencies 67 | run: npm ci 68 | 69 | - name: Build 70 | run: npm run build 71 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to OpenSea Stream API JS SDK 2 | 3 | ## Developing 4 | 5 | We PR off of feature branches that get submitted to `main`. This is the branch that all pull 6 | requests should be made against. Version releases are bumped using `npm version patch`. Make sure to include the `Developer-Experience` team on any PR you submit. 7 | 8 | To develop locally: 9 | 10 | 1. [clone](https://help.github.com/articles/cloning-a-repository/) this repo to your local device. 11 | 12 | ```sh 13 | git clone https://github.com/ProjectOpenSea/stream-js 14 | ``` 15 | 16 | 2. Create a new branch: 17 | ``` 18 | git checkout -b MY_BRANCH_NAME 19 | ``` 20 | 3. If you have Node Version Manager, run `nvm use` to ensure a compatible version is being used. 21 | 22 | 4. Install the dependencies with: 23 | ``` 24 | npm install 25 | ``` 26 | 5. Start developing and watch for code changes: 27 | ``` 28 | npm run dev 29 | ``` 30 | 6. Look into `package.json` to see what other run configurations (such as `test`, `check-types`, and `lint:check`) you can use. 31 | 32 | ## Building 33 | 34 | You can build the project, including all type definitions, with: 35 | 36 | ```bash 37 | npm run build 38 | ``` 39 | 40 | ### Running tests 41 | 42 | ```sh 43 | yarn test 44 | ``` 45 | 46 | ### Linting 47 | 48 | To check the formatting of your code: 49 | 50 | ```sh 51 | npm run prettier 52 | ``` 53 | 54 | If you get errors, you can fix them with: 55 | 56 | ```sh 57 | npm run prettier:fix 58 | ``` 59 | 60 | ### Set as a local dependency in package.json 61 | 62 | While developing and debugging changes to this SDK, you can 'test-run' them locally inside another package using `npm link`. 63 | 64 | 1. Inside this repo, run 65 | 66 | ```sh 67 | npm link 68 | ``` 69 | 70 | 2. In your other package's root directory, make sure to remove `@opensea/stream-js` from `node_modules` with: 71 | 72 | ```sh 73 | rm -rf ./node_modules/@opensea/stream-js 74 | ``` 75 | 76 | 3. In your other package's root directory, run: 77 | 78 | ```sh 79 | npm link @opensea/stream-js 80 | ``` 81 | 82 | to re-install all of the dependencies. 83 | 84 | Note that this SDK will be copied from the locally compiled version as opposed to from being downloaded from the NPM registry. 85 | 86 | 4. Run your application as you normally would. 87 | 88 | ## Publishing 89 | 90 | Repository admins can use `npm version patch` to create a new patch version. 91 | 92 | - For minor version updates, use `npm version minor`. 93 | - For major version updates, use `npm version major`. 94 | 95 | When creating a new version, submit a PR listing all the changes that you'd like to roll out with this change (since the last major version). 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opensea/stream-js", 3 | "version": "0.2.3", 4 | "description": "A TypeScript SDK to receive pushed updates from OpenSea over websocket", 5 | "license": "MIT", 6 | "author": "OpenSea Developers", 7 | "homepage": "https://docs.opensea.io/reference/stream-api-overview", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/ProjectOpenSea/stream-js.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/ProjectOpenSea/stream-js/issues" 14 | }, 15 | "type": "module", 16 | "exports": { 17 | "require": "./dist/index.cjs", 18 | "default": "./dist/index.modern.js", 19 | "types": "./dist/index.d.ts" 20 | }, 21 | "main": "./dist/index.cjs", 22 | "module": "./dist/index.module.js", 23 | "files": [ 24 | "dist", 25 | "src" 26 | ], 27 | "scripts": { 28 | "build": "microbundle", 29 | "check-types": "tsc --noEmit", 30 | "coverage": "npm run test -- --coverage --collectCoverageFrom='./src/**'", 31 | "dev": "microbundle watch", 32 | "eslint": "eslint . --max-warnings 0 --ext .js,.ts --fix", 33 | "lint:check": "concurrently \"npm run check-types\" \"npm run eslint\" \"npm run prettier:check\" \"npm run prettier:check:package.json\"", 34 | "prepare": "husky install", 35 | "prettier:check": "prettier --check .", 36 | "prettier:check:package.json": "prettier-package-json --list-different", 37 | "prettier:fix": "prettier --write . && prettier-package-json --write package.json", 38 | "prepublish": "npm run build", 39 | "test": "jest" 40 | }, 41 | "sideEffects": false, 42 | "types": "./dist/index.d.ts", 43 | "dependencies": { 44 | "phoenix": "^1.6.15" 45 | }, 46 | "devDependencies": { 47 | "@types/jest": "^29.5.0", 48 | "@types/phoenix": "^1.5.6", 49 | "@types/ws": "^8.5.4", 50 | "@typescript-eslint/eslint-plugin": "^5.59.6", 51 | "@typescript-eslint/parser": "^5.59.6", 52 | "concurrently": "^9.0.0", 53 | "eslint": "^8.41.0", 54 | "eslint-config-prettier": "^10.0.0", 55 | "eslint-plugin-prettier": "^5.0.1", 56 | "husky": "^8.0.3", 57 | "jest": "^29.5.0", 58 | "jest-environment-jsdom": "^29.5.0", 59 | "jest-websocket-mock": "^2.4.0", 60 | "lint-staged": "^16.0.0", 61 | "microbundle": "^0.15.1", 62 | "prettier": "^3.1.1", 63 | "prettier-package-json": "^2.6.4", 64 | "ts-jest": "^29.1.0", 65 | "ts-node": "^10.9.1", 66 | "typescript": "^5.0.4" 67 | }, 68 | "keywords": [ 69 | "collectibles", 70 | "crypto", 71 | "ethereum", 72 | "javascript", 73 | "marketplace", 74 | "nft", 75 | "node", 76 | "non-fungible-tokens", 77 | "opensea", 78 | "project-opensea", 79 | "sdk", 80 | "smart-contracts", 81 | "typescript", 82 | "websocket" 83 | ], 84 | "engines": { 85 | "node": ">=16.11.0" 86 | }, 87 | "lint-staged": { 88 | "package.json": [ 89 | "prettier-package-json --write" 90 | ], 91 | "**/*.{ts,tsx,js,jsx,html,md,mdx,yml,json}": [ 92 | "prettier --write" 93 | ], 94 | "**/*.{ts,tsx,js,jsx}": [ 95 | "eslint --cache --fix" 96 | ] 97 | }, 98 | "source": "src/index.ts", 99 | "unpkg": "./dist/index.umd.js" 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![Version][version-badge]][version-link] 6 | [![npm][npm-badge]][npm-link] 7 | [![Test CI][ci-badge]][ci-link] 8 | [![Coverage Status][coverage-badge]][coverage-link] 9 | [![License][license-badge]][license-link] 10 | [![Docs][docs-badge]][docs-link] 11 | 12 | # OpenSea Stream API - TypeScript SDK 13 | 14 | A TypeScript SDK for receiving updates from the OpenSea Stream API - pushed over websockets. We currently support the following event types on a per-collection basis: 15 | 16 | - item listed 17 | - item sold 18 | - item transferred 19 | - item metadata updates 20 | - item cancelled 21 | - item received offer 22 | - item received bid 23 | 24 | This is a best effort delivery messaging system. Messages that are not received due to connection errors will not be re-sent. Messages may be delivered out of order. This SDK is offered as a beta experience as we work with developers in the ecosystem to make this a more robust and reliable system. 25 | 26 | Documentation: https://docs.opensea.io/reference/stream-api-overview 27 | 28 | # Installation 29 | 30 | Please use Node.js version 16 or greater to make sure common crypto dependencies work. 31 | 32 | - Install this package: `npm install @opensea/stream-js` 33 | - Install types for phoenix: `npm install --save-dev @types/phoenix` 34 | - **NodeJS only:** Install required libraries: `npm install ws node-localstorage` 35 | 36 | # Getting Started 37 | 38 | ## Authentication 39 | 40 | In order to make onboarding easy, we've integrated the OpenSea Stream API with our existing API key system. The API keys you have been using for the REST API should work here as well. If you don't already have one, you can create an API key in your OpenSea account settings. 41 | 42 | ## Create a client 43 | 44 | ### Browser 45 | 46 | ```typescript 47 | import { OpenSeaStreamClient } from '@opensea/stream-js'; 48 | 49 | const client = new OpenSeaStreamClient({ 50 | token: 'YOUR_OPENSEA_API_KEY' 51 | }); 52 | ``` 53 | 54 | ### Node.js 55 | 56 | ```typescript 57 | import { OpenSeaStreamClient } from '@opensea/stream-js'; 58 | import { WebSocket } from 'ws'; 59 | import { LocalStorage } from 'node-localstorage'; 60 | 61 | const client = new OpenSeaStreamClient({ 62 | token: 'YOUR_OPENSEA_API_KEY', 63 | connectOptions: { 64 | transport: WebSocket, 65 | sessionStorage: LocalStorage 66 | } 67 | }); 68 | ``` 69 | 70 | You can also optionally pass in: 71 | 72 | - a `network` parameter (defaults to `Network.MAINNET`) 73 | - `apiUrl` if you would like to access another OpenSea Stream API endpoint. Not needed if you provide a network or use the default values. 74 | - an `onError` callback to handle errors. The default behavior is to `console.error` the error. 75 | - a `logLevel` to set the log level. The default is `LogLevel.INFO`. 76 | 77 | ## Available Networks 78 | 79 | The OpenSea Stream API is available on mainnet: 80 | 81 | ### Mainnet 82 | 83 | `wss://stream.openseabeta.com/socket` 84 | 85 | ## Manually connecting to the socket (optional) 86 | 87 | The client will automatically connect to the socket as soon as you subscribe to the first channel. 88 | If you would like to connect to the socket manually (before that), you can do so: 89 | 90 | ```typescript 91 | client.connect(); 92 | ``` 93 | 94 | After successfully connecting to our websocket it is time to listen to specific events you're interested in! 95 | 96 | ## Streaming metadata updates 97 | 98 | We will only send out metadata updates when we detect that the metadata provided in `tokenURI` has changed from what OpenSea has previously cached. 99 | 100 | ```typescript 101 | client.onItemMetadataUpdated('collection-slug', (event) => { 102 | // handle event 103 | }); 104 | ``` 105 | 106 | ## Streaming item listed events 107 | 108 | ```typescript 109 | client.onItemListed('collection-slug', (event) => { 110 | // handle event 111 | }); 112 | ``` 113 | 114 | ## Streaming item sold events 115 | 116 | ```typescript 117 | client.onItemSold('collection-slug', (event) => { 118 | // handle event 119 | }); 120 | ``` 121 | 122 | ## Streaming item transferred events 123 | 124 | ```typescript 125 | client.onItemTransferred('collection-slug', (event) => { 126 | // handle event 127 | }); 128 | ``` 129 | 130 | ## Streaming bids and offers 131 | 132 | ```typescript 133 | client.onItemReceivedBid('collection-slug', (event) => { 134 | // handle event 135 | }); 136 | 137 | client.onItemReceivedOffer('collection-slug', (event) => { 138 | // handle event 139 | }); 140 | ``` 141 | 142 | ## Streaming multiple event types 143 | 144 | ```typescript 145 | client.onEvents( 146 | 'collection-slug', 147 | [EventType.ITEM_RECEIVED_OFFER, EventType.ITEM_TRANSFERRED], 148 | (event) => { 149 | // handle event 150 | } 151 | ); 152 | ``` 153 | 154 | ## Streaming order cancellations events 155 | 156 | ```typescript 157 | client.onItemCancelled('collection-slug', (event) => { 158 | // handle event 159 | }); 160 | ``` 161 | 162 | # Subscribing to events from all collections 163 | 164 | If you'd like to listen to an event from all collections use wildcard `*` for the `collectionSlug` parameter. 165 | 166 | # Types 167 | 168 | Types are included to make working with our event payload objects easier. 169 | 170 | # Disconnecting 171 | 172 | ## From a specific stream 173 | 174 | All subscription methods return a callback function that will unsubscribe from a stream when invoked. 175 | 176 | ```typescript 177 | const unsubscribe = client.onItemMetadataUpdated('collection-slug', noop); 178 | 179 | unsubscribe(); 180 | ``` 181 | 182 | ## From the socket 183 | 184 | ```typescript 185 | client.disconnect(); 186 | ``` 187 | 188 | ## Contributing 189 | 190 | See [the contributing guide](./.github/contributing.md) for detailed instructions on how to get started with this project. 191 | 192 | ## License 193 | 194 | [MIT](LICENSE) Copyright 2022 Ozone Networks, Inc. 195 | 196 | [version-badge]: https://img.shields.io/github/package-json/v/ProjectOpenSea/stream-js 197 | [version-link]: https://github.com/ProjectOpenSea/stream-js/releases 198 | [npm-badge]: https://img.shields.io/npm/v/@opensea/stream-js?color=red 199 | [npm-link]: https://www.npmjs.com/package/@opensea/stream-js 200 | [ci-badge]: https://github.com/ProjectOpenSea/stream-js/actions/workflows/ci.yml/badge.svg 201 | [ci-link]: https://github.com/ProjectOpenSea/stream-js/actions/workflows/ci.yml 202 | [coverage-badge]: https://coveralls.io/repos/github/ProjectOpenSea/stream-js/badge.svg?branch=main 203 | [coverage-link]: https://coveralls.io/github/ProjectOpenSea/stream-js?branch=main 204 | [license-badge]: https://img.shields.io/github/license/ProjectOpenSea/stream-js 205 | [license-link]: https://github.com/ProjectOpenSea/stream-js/blob/main/LICENSE 206 | [docs-badge]: https://img.shields.io/badge/Stream.js-documentation-informational 207 | [docs-link]: https://github.com/ProjectOpenSea/stream-js#getting-started 208 | -------------------------------------------------------------------------------- /tests/client.spec.ts: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals'; 2 | import { WS } from 'jest-websocket-mock'; 3 | import { getSocket, getChannels, encode, mockEvent } from './helpers.js'; 4 | import { 5 | EventType, 6 | LogLevel, 7 | OnClientEvent, 8 | OpenSeaStreamClient 9 | } from '../src/index.js'; 10 | import { collectionTopic } from '../src/helpers.js'; 11 | 12 | let server: WS; 13 | let streamClient: OpenSeaStreamClient; 14 | 15 | const clientOpts = { 16 | token: 'test', 17 | apiUrl: 'ws://localhost:1234', 18 | logLevel: LogLevel.WARN 19 | }; 20 | 21 | beforeEach(() => { 22 | server = new WS('ws://localhost:1234'); 23 | }); 24 | 25 | afterEach(() => { 26 | server.close(); 27 | }); 28 | 29 | afterEach(() => { 30 | streamClient.disconnect(); 31 | }); 32 | 33 | test('constructor', async () => { 34 | streamClient = new OpenSeaStreamClient(clientOpts); 35 | 36 | const socket = getSocket(streamClient); 37 | expect(socket.protocol()).toBe('ws'); 38 | expect(socket.endPointURL()).toBe( 39 | 'ws://localhost:1234/websocket?token=test&vsn=2.0.0' 40 | ); 41 | expect(socket.isConnected()).toBe(false); 42 | }); 43 | 44 | describe('unsubscribe', () => { 45 | test('channel', () => { 46 | streamClient = new OpenSeaStreamClient(clientOpts); 47 | 48 | const unsubscribec1 = streamClient.onItemListed('c1', jest.fn()); 49 | const unsubscribec2 = streamClient.onItemListed('c2', jest.fn()); 50 | 51 | expect(Array.from(getChannels(streamClient).keys())).toEqual([ 52 | 'collection:c1', 53 | 'collection:c2' 54 | ]); 55 | 56 | unsubscribec1(); 57 | expect(Array.from(getChannels(streamClient).keys())).toEqual([ 58 | 'collection:c2' 59 | ]); 60 | 61 | unsubscribec2(); 62 | expect(Array.from(getChannels(streamClient).keys())).toEqual([]); 63 | }); 64 | 65 | test('socket', () => { 66 | streamClient = new OpenSeaStreamClient(clientOpts); 67 | 68 | streamClient.onItemListed('c1', jest.fn()); 69 | streamClient.onItemListed('c2', jest.fn()); 70 | 71 | streamClient.disconnect(); 72 | expect(Array.from(getChannels(streamClient).keys())).toEqual([]); 73 | }); 74 | }); 75 | 76 | describe('event streams', () => { 77 | Object.values(EventType).forEach((eventType) => { 78 | test(`${eventType}`, async () => { 79 | const collectionSlug = 'c1'; 80 | 81 | streamClient = new OpenSeaStreamClient({ 82 | ...clientOpts, 83 | connectOptions: { transport: WebSocket } 84 | }); 85 | 86 | // connection will fail as phoenix socket modified the endpoint url 87 | const socket = getSocket(streamClient); 88 | jest 89 | .spyOn(socket, 'endPointURL') 90 | .mockImplementation(() => 'ws://localhost:1234'); 91 | 92 | const onItemListed = jest.fn(); 93 | const unsubscribe = streamClient.onEvents( 94 | collectionSlug, 95 | [eventType], 96 | (event) => onItemListed(event) 97 | ); 98 | 99 | const payload = mockEvent(eventType, {}); 100 | 101 | server.send( 102 | encode({ 103 | topic: collectionTopic(collectionSlug), 104 | event: eventType, 105 | payload 106 | }) 107 | ); 108 | 109 | expect(onItemListed).toBeCalledWith(payload); 110 | 111 | server.send( 112 | encode({ 113 | topic: collectionTopic(collectionSlug), 114 | event: eventType, 115 | payload 116 | }) 117 | ); 118 | 119 | expect(onItemListed).toBeCalledTimes(2); 120 | 121 | unsubscribe(); 122 | 123 | server.send( 124 | encode({ 125 | topic: collectionTopic(collectionSlug), 126 | event: eventType, 127 | payload 128 | }) 129 | ); 130 | 131 | expect(onItemListed).toBeCalledTimes(2); 132 | }); 133 | }); 134 | }); 135 | 136 | describe('middleware', () => { 137 | test('single', () => { 138 | const collectionSlug = 'c1'; 139 | 140 | const onClientEvent = jest 141 | .fn() 142 | .mockImplementation(() => true) as OnClientEvent; 143 | 144 | streamClient = new OpenSeaStreamClient({ 145 | ...clientOpts, 146 | connectOptions: { transport: WebSocket }, 147 | onEvent: onClientEvent 148 | }); 149 | 150 | const socket = getSocket(streamClient); 151 | jest 152 | .spyOn(socket, 'endPointURL') 153 | .mockImplementation(() => 'ws://localhost:1234'); 154 | 155 | const onEvent = jest.fn(); 156 | 157 | const listingEvent = mockEvent(EventType.ITEM_LISTED, {}); 158 | const saleEvent = mockEvent(EventType.ITEM_SOLD, {}); 159 | 160 | streamClient.onEvents( 161 | collectionSlug, 162 | [EventType.ITEM_LISTED, EventType.ITEM_SOLD], 163 | (event) => onEvent(event) 164 | ); 165 | 166 | server.send( 167 | encode({ 168 | topic: collectionTopic(collectionSlug), 169 | event: EventType.ITEM_LISTED, 170 | payload: listingEvent 171 | }) 172 | ); 173 | 174 | server.send( 175 | encode({ 176 | topic: collectionTopic(collectionSlug), 177 | event: EventType.ITEM_SOLD, 178 | payload: saleEvent 179 | }) 180 | ); 181 | 182 | expect(onClientEvent).nthCalledWith( 183 | 1, 184 | collectionSlug, 185 | EventType.ITEM_LISTED, 186 | listingEvent 187 | ); 188 | 189 | expect(onClientEvent).nthCalledWith( 190 | 2, 191 | collectionSlug, 192 | EventType.ITEM_SOLD, 193 | saleEvent 194 | ); 195 | 196 | expect(onEvent).nthCalledWith(1, listingEvent); 197 | expect(onEvent).nthCalledWith(2, saleEvent); 198 | 199 | streamClient.disconnect(); 200 | }); 201 | 202 | test('filter out events', () => { 203 | const collectionSlug = 'c1'; 204 | 205 | const onClientEvent = jest.fn().mockImplementation( 206 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 207 | (_c, _e, event: any) => event.payload.chain.name === 'ethereum' 208 | ) as OnClientEvent; 209 | 210 | streamClient = new OpenSeaStreamClient({ 211 | ...clientOpts, 212 | connectOptions: { transport: WebSocket }, 213 | onEvent: onClientEvent 214 | }); 215 | 216 | const socket = getSocket(streamClient); 217 | jest 218 | .spyOn(socket, 'endPointURL') 219 | .mockImplementation(() => 'ws://localhost:1234'); 220 | 221 | const onEvent = jest.fn(); 222 | 223 | const ethereumListing = mockEvent(EventType.ITEM_LISTED, { 224 | chain: { name: 'ethereum' } 225 | }); 226 | const polygonListing = mockEvent(EventType.ITEM_LISTED, { 227 | chain: { name: 'polygon' } 228 | }); 229 | 230 | streamClient.onEvents( 231 | collectionSlug, 232 | [EventType.ITEM_LISTED, EventType.ITEM_SOLD], 233 | (event) => onEvent(event) 234 | ); 235 | 236 | server.send( 237 | encode({ 238 | topic: collectionTopic(collectionSlug), 239 | event: EventType.ITEM_LISTED, 240 | payload: ethereumListing 241 | }) 242 | ); 243 | 244 | server.send( 245 | encode({ 246 | topic: collectionTopic(collectionSlug), 247 | event: EventType.ITEM_SOLD, 248 | payload: polygonListing 249 | }) 250 | ); 251 | 252 | expect(onClientEvent).nthCalledWith( 253 | 1, 254 | collectionSlug, 255 | EventType.ITEM_LISTED, 256 | ethereumListing 257 | ); 258 | 259 | expect(onClientEvent).nthCalledWith( 260 | 2, 261 | collectionSlug, 262 | EventType.ITEM_SOLD, 263 | polygonListing 264 | ); 265 | 266 | expect(onEvent).toHaveBeenCalledTimes(1); 267 | expect(onEvent).toHaveBeenCalledWith(ethereumListing); 268 | 269 | streamClient.disconnect(); 270 | }); 271 | }); 272 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { Socket, Channel } from 'phoenix'; 2 | import { collectionTopic } from './helpers.js'; 3 | import { 4 | ClientConfig, 5 | BaseStreamMessage, 6 | EventType, 7 | ItemMetadataUpdate, 8 | ItemListedEvent, 9 | ItemSoldEvent, 10 | ItemTransferredEvent, 11 | ItemReceivedBidEvent, 12 | ItemReceivedOfferEvent, 13 | ItemCancelledEvent, 14 | CollectionOfferEvent, 15 | TraitOfferEvent, 16 | Callback, 17 | LogLevel, 18 | Network, 19 | OnClientEvent, 20 | OrderValidationEvent 21 | } from './types.js'; 22 | import { ENDPOINTS } from './constants.js'; 23 | 24 | export class OpenSeaStreamClient { 25 | private socket: Socket; 26 | private channels: Map; 27 | private logLevel: LogLevel; 28 | private onEvent: OnClientEvent; 29 | 30 | constructor({ 31 | network = Network.MAINNET, 32 | token, 33 | apiUrl, 34 | connectOptions, 35 | logLevel = LogLevel.INFO, 36 | onError = (error) => this.error(error), 37 | onEvent = () => true 38 | }: ClientConfig) { 39 | const endpoint = apiUrl || ENDPOINTS[network]; 40 | const webTransportDefault = 41 | typeof window !== 'undefined' ? window.WebSocket : undefined; 42 | this.socket = new Socket(endpoint, { 43 | params: { token }, 44 | transport: webTransportDefault, 45 | ...connectOptions 46 | }); 47 | 48 | this.socket.onError(onError); 49 | this.channels = new Map(); 50 | this.logLevel = logLevel; 51 | this.onEvent = onEvent; 52 | } 53 | 54 | private debug(message: unknown) { 55 | if (this.logLevel <= LogLevel.DEBUG) { 56 | console.debug('[DEBUG]:', message); 57 | } 58 | } 59 | 60 | private info(message: unknown) { 61 | if (this.logLevel <= LogLevel.INFO) { 62 | console.info('[INFO]:', message); 63 | } 64 | } 65 | 66 | private warn(message: unknown) { 67 | if (this.logLevel <= LogLevel.WARN) { 68 | console.warn('[WARN]:', message); 69 | } 70 | } 71 | 72 | private error(message: unknown) { 73 | if (this.logLevel <= LogLevel.ERROR) { 74 | console.error('[ERROR]:', message); 75 | } 76 | } 77 | 78 | public connect = () => { 79 | this.debug('Connecting to socket'); 80 | this.socket.connect(); 81 | }; 82 | 83 | public disconnect = ( 84 | callback = () => this.info(`Successfully disconnected from socket`) 85 | ) => { 86 | this.channels.clear(); 87 | return this.socket.disconnect(callback); 88 | }; 89 | 90 | private createChannel = (topic: string): Channel => { 91 | const channel = this.socket.channel(topic); 92 | channel 93 | .join() 94 | .receive('ok', () => this.info(`Successfully joined channel "${topic}"`)) 95 | .receive('error', () => this.error(`Failed to join channel "${topic}"`)); 96 | 97 | this.channels.set(topic, channel); 98 | return channel; 99 | }; 100 | 101 | private getChannel = (topic: string): Channel => { 102 | let channel = this.channels.get(topic); 103 | if (!channel) { 104 | this.debug(`Creating channel for topic: "${topic}"`); 105 | channel = this.createChannel(topic); 106 | } 107 | return channel; 108 | }; 109 | 110 | private on = >( 111 | eventType: EventType, 112 | collectionSlug: string, 113 | callback: Callback 114 | ) => { 115 | this.socket.connect(); 116 | 117 | const topic = collectionTopic(collectionSlug); 118 | this.debug(`Fetching channel ${topic}`); 119 | const channel = this.getChannel(topic); 120 | this.debug(`Subscribing to ${eventType} events on ${topic}`); 121 | 122 | const onClientEvent = this.onEvent; 123 | channel.on(eventType, (event) => { 124 | if (onClientEvent(collectionSlug, eventType, event)) { 125 | callback(event); 126 | } 127 | }); 128 | 129 | return () => { 130 | this.debug(`Unsubscribing from ${eventType} events on ${topic}`); 131 | channel.leave().receive('ok', () => { 132 | this.channels.delete(topic); 133 | this.info( 134 | `Successfully left channel "${topic}" listening for ${eventType}` 135 | ); 136 | }); 137 | }; 138 | }; 139 | 140 | public onItemMetadataUpdated = ( 141 | collectionSlug: string, 142 | callback: Callback 143 | ) => { 144 | this.debug(`Listening for item metadata updates on "${collectionSlug}"`); 145 | return this.on(EventType.ITEM_METADATA_UPDATED, collectionSlug, callback); 146 | }; 147 | 148 | public onItemCancelled = ( 149 | collectionSlug: string, 150 | callback: Callback 151 | ) => { 152 | this.debug(`Listening for item cancellations on "${collectionSlug}"`); 153 | return this.on(EventType.ITEM_CANCELLED, collectionSlug, callback); 154 | }; 155 | 156 | public onItemListed = ( 157 | collectionSlug: string, 158 | callback: Callback 159 | ) => { 160 | this.debug(`Listening for item listings on "${collectionSlug}"`); 161 | return this.on(EventType.ITEM_LISTED, collectionSlug, callback); 162 | }; 163 | 164 | public onItemSold = ( 165 | collectionSlug: string, 166 | callback: Callback 167 | ) => { 168 | this.debug(`Listening for item sales on "${collectionSlug}"`); 169 | return this.on(EventType.ITEM_SOLD, collectionSlug, callback); 170 | }; 171 | 172 | public onItemTransferred = ( 173 | collectionSlug: string, 174 | callback: Callback 175 | ) => { 176 | this.debug(`Listening for item transfers on "${collectionSlug}"`); 177 | return this.on(EventType.ITEM_TRANSFERRED, collectionSlug, callback); 178 | }; 179 | 180 | public onItemReceivedOffer = ( 181 | collectionSlug: string, 182 | callback: Callback 183 | ) => { 184 | this.debug(`Listening for item offers on "${collectionSlug}"`); 185 | return this.on(EventType.ITEM_RECEIVED_OFFER, collectionSlug, callback); 186 | }; 187 | 188 | public onItemReceivedBid = ( 189 | collectionSlug: string, 190 | callback: Callback 191 | ) => { 192 | this.debug(`Listening for item bids on "${collectionSlug}"`); 193 | return this.on(EventType.ITEM_RECEIVED_BID, collectionSlug, callback); 194 | }; 195 | 196 | public onCollectionOffer = ( 197 | collectionSlug: string, 198 | callback: Callback 199 | ) => { 200 | this.debug(`Listening for collection offers on "${collectionSlug}"`); 201 | return this.on(EventType.COLLECTION_OFFER, collectionSlug, callback); 202 | }; 203 | 204 | public onTraitOffer = ( 205 | collectionSlug: string, 206 | callback: Callback 207 | ) => { 208 | this.debug(`Listening for trait offers on "${collectionSlug}"`); 209 | return this.on(EventType.TRAIT_OFFER, collectionSlug, callback); 210 | }; 211 | 212 | public onOrderInvalidate = ( 213 | collectionSlug: string, 214 | callback: Callback 215 | ) => { 216 | this.debug( 217 | `Listening for order invalidation events on "${collectionSlug}"` 218 | ); 219 | return this.on(EventType.ORDER_INVALIDATE, collectionSlug, callback); 220 | }; 221 | 222 | public onOrderRevalidate = ( 223 | collectionSlug: string, 224 | callback: Callback 225 | ) => { 226 | this.debug( 227 | `Listening for order revalidation events on "${collectionSlug}"` 228 | ); 229 | return this.on(EventType.ORDER_REVALIDATE, collectionSlug, callback); 230 | }; 231 | 232 | public onEvents = ( 233 | collectionSlug: string, 234 | eventTypes: EventType[], 235 | callback: Callback> 236 | ) => { 237 | const subscriptions = eventTypes.map((eventType) => 238 | this.on(eventType, collectionSlug, callback) 239 | ); 240 | 241 | return () => { 242 | for (const unsubscribe of subscriptions) { 243 | unsubscribe(); 244 | } 245 | }; 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { SocketConnectOption } from 'phoenix'; 2 | 3 | export type OnClientEvent = ( 4 | collection: string, 5 | eventType: EventType, 6 | event: BaseStreamMessage 7 | ) => boolean; 8 | 9 | /** 10 | * OpenSea Stream API configuration object 11 | * @param token API key to use for API 12 | * @param network `Network` type to use. Defaults to `Network.MAINNET` 13 | * @param apiUrl Optional base URL to use for the API. 14 | * @param connectOptions `SocketConnectOption` type to use to connect to the Stream API socket. 15 | * @param onError a callback function to use whenever errors occur in the SDK. 16 | * @param logLevel `LogLevel` type to define the amount of logging the SDK should provide. 17 | * @param onEvent a callback function to use whenever an event is emitted in the SDK. Can be used to globally apply some logic, e.g emitting metric/logging etc. If the onEvent handler returns false, event will be filtered and the subscription callback won't be invoked. 18 | */ 19 | export type ClientConfig = { 20 | network?: Network; 21 | apiUrl?: string; 22 | token: string; 23 | connectOptions?: Partial; 24 | onError?: (error: unknown) => void; 25 | logLevel?: LogLevel; 26 | onEvent?: OnClientEvent; 27 | }; 28 | 29 | export enum Network { 30 | MAINNET = 'mainnet' 31 | } 32 | 33 | export enum EventType { 34 | ITEM_METADATA_UPDATED = 'item_metadata_updated', 35 | ITEM_LISTED = 'item_listed', 36 | ITEM_SOLD = 'item_sold', 37 | ITEM_TRANSFERRED = 'item_transferred', 38 | ITEM_RECEIVED_OFFER = 'item_received_offer', 39 | ITEM_RECEIVED_BID = 'item_received_bid', 40 | ITEM_CANCELLED = 'item_cancelled', 41 | COLLECTION_OFFER = 'collection_offer', 42 | TRAIT_OFFER = 'trait_offer', 43 | ORDER_INVALIDATE = 'order_invalidate', 44 | ORDER_REVALIDATE = 'order_revalidate' 45 | } 46 | 47 | interface BaseItemMetadataType { 48 | name: string | null; 49 | image_url: string | null; 50 | animation_url: string | null; 51 | metadata_url: string | null; 52 | } 53 | 54 | export type BaseItemType = { 55 | nft_id: string; 56 | permalink: string; 57 | metadata: Metadata; 58 | chain: { 59 | name: string; 60 | }; 61 | }; 62 | 63 | export type Payload = { 64 | item: BaseItemType; 65 | collection: { 66 | slug: string; 67 | }; 68 | chain: string; 69 | }; 70 | 71 | export type BaseStreamMessage = { 72 | event_type: string; 73 | sent_at: string; 74 | payload: Payload; 75 | }; 76 | 77 | export type Trait = { 78 | trait_type: string; 79 | value: string | null; 80 | display_type: string | null; 81 | max_value: number | null; 82 | trait_count: number | null; 83 | order: number | null; 84 | }; 85 | 86 | interface ExtendedItemMetadataType extends BaseItemMetadataType { 87 | description: string | null; 88 | background_color: string | null; 89 | traits: Trait[]; 90 | } 91 | 92 | export type ItemMetadataUpdatePayload = { 93 | collection: { slug: string }; 94 | item: BaseItemType; 95 | }; 96 | 97 | export type ItemMetadataUpdate = BaseStreamMessage; 98 | 99 | export type Account = { 100 | address: string; 101 | }; 102 | 103 | export type PaymentToken = { 104 | address: string; 105 | decimals: number; 106 | eth_price: string; 107 | name: string; 108 | symbol: string; 109 | usd_price: string; 110 | }; 111 | 112 | export type AssetContractCriteria = { 113 | address: string; 114 | }; 115 | 116 | export type CollectionIdentifier = { 117 | slug: string; 118 | }; 119 | 120 | export interface ItemListedEventPayload extends Payload { 121 | quantity: number; 122 | listing_type: string; 123 | listing_date: string; 124 | expiration_date: string; 125 | maker: Account; 126 | taker: Account; 127 | base_price: string; 128 | payment_token: PaymentToken; 129 | is_private: boolean; 130 | order_hash: string; 131 | event_timestamp: string; 132 | } 133 | 134 | export type ItemListedEvent = BaseStreamMessage; 135 | 136 | export type Transaction = { 137 | hash: string; 138 | timestamp: string; 139 | }; 140 | 141 | export interface ItemSoldEventPayload extends Payload { 142 | quantity: number; 143 | listing_type: string; 144 | closing_date: string; 145 | transaction: Transaction; 146 | maker: Account; 147 | taker: Account; 148 | order_hash: string; 149 | sale_price: string; 150 | payment_token: PaymentToken; 151 | is_private: boolean; 152 | event_timestamp: string; 153 | } 154 | 155 | export type ItemSoldEvent = BaseStreamMessage; 156 | 157 | export interface ItemTransferredEventPayload extends Payload { 158 | from_account: Account; 159 | quantity: number; 160 | to_account: Account; 161 | transaction: Transaction; 162 | event_timestamp: string; 163 | } 164 | 165 | export type ItemTransferredEvent = 166 | BaseStreamMessage; 167 | 168 | export interface ItemReceivedBidEventPayload extends Payload { 169 | quantity: number; 170 | created_date: string; 171 | expiration_date: string; 172 | maker: Account; 173 | taker: Account; 174 | order_hash: string; 175 | base_price: string; 176 | payment_token: PaymentToken; 177 | event_timestamp: string; 178 | } 179 | 180 | export type ItemReceivedBidEvent = 181 | BaseStreamMessage; 182 | 183 | export interface ItemReceivedOfferEventPayload extends Payload { 184 | quantity: number; 185 | created_date: string; 186 | expiration_date: string; 187 | maker: Account; 188 | taker: Account; 189 | order_hash: string; 190 | base_price: string; 191 | payment_token: PaymentToken; 192 | event_timestamp: string; 193 | } 194 | 195 | export type ItemReceivedOfferEvent = 196 | BaseStreamMessage; 197 | 198 | export interface ItemCancelledEventPayload extends Payload { 199 | quantity: number; 200 | base_price: string; 201 | expiration_date: string; 202 | maker: Account; 203 | taker: Account; 204 | listing_type: string; 205 | listing_date: string; 206 | transaction: Transaction; 207 | payment_token: PaymentToken; 208 | order_hash: string; 209 | is_private: boolean; 210 | event_timestamp: string; 211 | } 212 | 213 | export type ItemCancelledEvent = BaseStreamMessage; 214 | 215 | export interface CollectionOfferEventPayload extends Payload { 216 | quantity: number; 217 | created_date: string; 218 | expiration_date: string; 219 | maker: Account; 220 | taker: Account; 221 | base_price: string; 222 | order_hash: string; 223 | payment_token: PaymentToken; 224 | collection_criteria: CollectionIdentifier; 225 | asset_contract_criteria: AssetContractCriteria; 226 | event_timestamp: string; 227 | } 228 | 229 | export type CollectionOfferEvent = 230 | BaseStreamMessage; 231 | 232 | export type TraitCriteria = { 233 | trait_type: string; 234 | trait_name: string; 235 | }; 236 | 237 | export interface TraitOfferEventPayload extends Payload { 238 | quantity: number; 239 | created_date: string; 240 | expiration_date: string; 241 | maker: Account; 242 | taker: Account; 243 | base_price: string; 244 | order_hash: string; 245 | payment_token: PaymentToken; 246 | collection_criteria: CollectionIdentifier; 247 | asset_contract_criteria: AssetContractCriteria; 248 | trait_criteria?: TraitCriteria; 249 | trait_criteria_list?: TraitCriteria[]; 250 | event_timestamp: string; 251 | } 252 | 253 | export type TraitOfferEvent = BaseStreamMessage; 254 | 255 | export interface OrderValidationEventPayload { 256 | event_timestamp: string; 257 | order_hash: string; 258 | protocol_address: string; 259 | chain: { 260 | name: string; 261 | }; 262 | collection: { 263 | slug: string; 264 | }; 265 | item: BaseItemType; 266 | } 267 | 268 | export type OrderValidationEvent = 269 | BaseStreamMessage; 270 | 271 | export type Callback = (event: Event) => unknown; 272 | 273 | export enum LogLevel { 274 | DEBUG = 20, 275 | INFO = 30, 276 | WARN = 40, 277 | ERROR = 50 278 | } 279 | --------------------------------------------------------------------------------