diff --git a/components/opensea/opensea.app.mjs b/components/opensea/opensea.app.mjs index 5c0b65d7b062c..2be62f0ec6313 100644 --- a/components/opensea/opensea.app.mjs +++ b/components/opensea/opensea.app.mjs @@ -1,26 +1,38 @@ import { axios } from "@pipedream/platform"; +import Bottleneck from "bottleneck"; +const limiter = new Bottleneck({ + minTime: 200, // 5 requests per second + maxConcurrent: 1, +}); +const axiosRateLimiter = limiter.wrap(axios); export default { type: "app", app: "opensea", propDefinitions: {}, methods: { - async retrieveEvents({ - $, contract, eventType, occurredAfter, cursor, + _baseUrl() { + return "https://api.opensea.io/api/v2"; + }, + _makeRequest({ + $ = this, + path, + ...otherOpts }) { - const apiKey = this.$auth.api_key; - return axios($ ?? this, { - url: "https://api.opensea.io/api/v1/events", - params: { - only_opensea: false, - asset_contract_address: contract, - event_type: eventType, - occurred_after: occurredAfter, - cursor, - }, + return axiosRateLimiter($, { + url: `${this._baseUrl()}${path}`, headers: { - "X-API-KEY": apiKey, + "X-API-KEY": this.$auth.api_key, }, + ...otherOpts, + }); + }, + retrieveEvents({ + collectionSlug, ...opts + }) { + return this._makeRequest({ + path: `/events/collection/${collectionSlug}`, + ...opts, }); }, }, diff --git a/components/opensea/package.json b/components/opensea/package.json index e4385afa2338d..f289b6d70aea7 100644 --- a/components/opensea/package.json +++ b/components/opensea/package.json @@ -1,21 +1,20 @@ { "name": "@pipedream/opensea", - "version": "0.0.3", + "version": "0.0.4", "description": "Pipedream Opensea Components", "main": "opensea.app.mjs", "keywords": [ "pipedream", "opensea" ], - "files": [ - "dist" - ], "homepage": "https://pipedream.com/apps/opensea", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" }, "dependencies": { - "@pipedream/platform": "^1.2.0" + "@pipedream/platform": "^3.0.3", + "bottleneck": "^2.19.5", + "md5": "^2.3.0" } } diff --git a/components/opensea/sources/new-collection-events/new-collection-events.mjs b/components/opensea/sources/new-collection-events/new-collection-events.mjs index dde3a4bb3bdab..519a22619bde1 100644 --- a/components/opensea/sources/new-collection-events/new-collection-events.mjs +++ b/components/opensea/sources/new-collection-events/new-collection-events.mjs @@ -1,13 +1,13 @@ import opensea from "../../opensea.app.mjs"; import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import md5 from "md5"; export default { - name: "New Collection Events", - version: "0.0.3", key: "opensea-new-collection-events", - description: - "Emit new filtered events. [See docs](https://docs.opensea.io/reference/retrieving-asset-events)", - dedupe: "greatest", + name: "New Collection Events", + description: "Emit new filtered events for a collection. [See the documentation](https://docs.opensea.io/reference/list_events_by_collection)", + version: "0.0.4", + dedupe: "unique", type: "source", props: { opensea, @@ -18,54 +18,81 @@ export default { intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, }, }, - contractAddress: { + collectionSlug: { type: "string", - label: "Contract Address", - description: "Collection contract address", + label: "Collection Slug", + description: "Unique string to identify a collection on OpenSea. This can be found by visiting the collection on the OpenSea website and noting the last path parameter.", }, eventType: { type: "string", options: [ - "sales", - "listings", + "all", + "cancel", + "listing", + "offer", + "order", + "redemption", + "sale", + "transfer", ], label: "Event Type", - description: "OpenSea event type", + description: "The type of event to filter by", + default: "all", + optional: true, }, }, methods: { - getLastTimestamp() { + _getLastTimestamp() { return this.db.get("lastTimestamp"); }, - setLastTimestamp(ts) { + _setLastTimestamp(ts) { this.db.set("lastTimestamp", ts); }, - }, - async run() { - const eventType = this.eventType === "sales" - ? "successful" - : "created"; - const lastTimestamp = this.getLastTimestamp(); - let cursor = null; - do { - const resp = await this.opensea.retrieveEvents({ - contract: this.contractAddress, - eventType, - occurredAfter: lastTimestamp, - cursor, - }); - resp.asset_events.forEach((event) => { - this.$emit(event, { - id: event.id, - summary: `${event.asset.name} ${this.eventType} event`, - ts: +new Date(event.created_date), + generateMeta(event) { + return { + id: md5(JSON.stringify(event)), + summary: `New ${event.event_type} event`, + ts: event.event_timestamp, + }; + }, + async processEvent(max) { + const lastTimestamp = this._getLastTimestamp(); + let next = null; + let events = []; + do { + const resp = await this.opensea.retrieveEvents({ + collectionSlug: this.collectionSlug, + params: { + event_type: this.eventType, + after: lastTimestamp, + next, + }, }); - }); - if (!cursor && resp.asset_events.length > 0) { - const ts = Math.floor(new Date(resp.asset_events[0].created_date).getTime() / 1000); - this.setLastTimestamp(ts); + if (!resp?.asset_events) { + break; + } + events.push(...resp.asset_events); + next = resp.next; + } while (lastTimestamp && next); + + if (!events.length) { + return; } - cursor = resp.next; - } while (lastTimestamp && cursor); + this._setLastTimestamp(events[0].event_timestamp); + if (max) { + events = events.slice(0, max); + } + events.reverse().forEach((event) => { + this.$emit(event, this.generateMeta(event)); + }); + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + async run() { + await this.processEvent(); }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c607001294094..4f0b997dc7a13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,8 +94,8 @@ importers: specifier: ^12.3.4 version: 12.5.0(enquirer@2.4.1) pnpm: - specifier: 9.14.3 - version: 9.14.3 + specifier: 9.14.2 + version: 9.14.2 putout: specifier: '>=36' version: 36.13.1(eslint@8.57.1)(typescript@5.6.3) @@ -7196,8 +7196,14 @@ importers: components/opensea: dependencies: '@pipedream/platform': - specifier: ^1.2.0 - version: 1.6.6 + specifier: ^3.0.3 + version: 3.0.3 + bottleneck: + specifier: ^2.19.5 + version: 2.19.5 + md5: + specifier: ^2.3.0 + version: 2.3.0 components/openweather_api: dependencies: @@ -23118,8 +23124,8 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - pnpm@9.14.3: - resolution: {integrity: sha512-wPU+6ZR37ZabgrKJrQEaXRa/FiPJV+fynqvo0MALV0wpuMf1T2xn7nEMc/KFyBVNB85EtG/iwO60dqkEQbrDcQ==} + pnpm@9.14.2: + resolution: {integrity: sha512-biuvd9Brk2IpQVLIUcTyeO3jerHro6Vf2jF6SheyCfTbuXP7JQp3q8Rjo0H8sfF/F8+iQJHE6zGc2g2bhCeDhw==} engines: {node: '>=18.12'} hasBin: true @@ -24579,22 +24585,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please downgrade to v7.1.5 if you need IE/ActiveXObject support OR upgrade to v8.0.0 as we no longer support IE and published an incorrect patch version (see https://github.com/visionmedia/superagent/issues/1731) supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} @@ -40518,7 +40524,7 @@ snapshots: pluralize@8.0.0: {} - pnpm@9.14.3: {} + pnpm@9.14.2: {} points-on-curve@0.2.0: {}