opensea-submarine
v0.1.1
Published
🏴☠️ 🧸 Enabling subversive transactions with OpenSea's GraphQL backend using Express middleware.
Downloads
8
Readme
opensea-submarine
OpenSea is the world's foremost NFT marketplace which takes great care to protect its API from abuse by ETH-hungry robots who are desperate to discover profit opportunities the fastest.
OpenSea makes this task difficult in a number of ways:
- Any request to the backend must be precisely-defined to satisfy strong CloudFlare protection.
- Robust client-side session management and adherence imposes additional complexity during request formation which dramatically complicates attempts to programmatically
fetch
the API. - The backend enforces that the structure of an individual request must resolve to a known checksum.
If that wasn't enough, the successfully returned contents of pages rendered by OpenSea's SPA are highly obfuscated to make the task of manual scraping slow, unreliable and limited in scalability.
By using a stealthy flavour of Puppeteer, this repository demonstrates that a user can hijack client-side GraphQL requests and repurpose them for custom queries. This enables the client to squat on the complex trusted setup and abstract away request complexity.
🚀 getting started
Using Yarn:
yarn add opensea-submarine
✏️ usage
This package exports an Express middleware which emulates a conventional GraphQL interface. GraphQL requests captured by the middleware are validated, sanitized and curried over into OpenSea's backend via request-squatting:
import cors from 'cors';
import express from 'express';
import axios from 'axios';
import {proxyMiddleware} from 'opensea-submarine';
const openSeaEnvironment = {
graphQLUri: 'https://opensea.io/__api/graphql/',
eventHistoryUri: 'https://opensea.io/collection/boredapeyachtclub?tab=activity',
privacyUri: 'https://opensea.io/privacy',
};
const proxyContext = await createProxyContext(openSeaEnvironment);
const server = await new Promise<Server>(
async resolve => {
const server = express()
.use(cors())
.use(await proxyMiddleware({
debug: true,
proxyContext,
}))
.listen(3000, () => resolve(server));
},
);
Then you're free to query the middleware using queries that are recognized by OpenSea:
import axios from 'axios';
const {data} = await axios({
url: 'http://localhost:3000/graphql',
method: 'post',
data: {"query":"query useIsEditableQuery(\n $collection: CollectionSlug!\n) {\n collection(collection: $collection) {\n isEditable\n id\n }\n}\n","variables":{"collection": "boredapeyachtclub"}},
});
// {"collection":{"isEditable":false,"id":"Q29sbGVjdGlvblR5cGU6NDg4NjIx"}}
You can check out the examples for additional insight.
Developers are reminded that the query content and x-signed-query
header must be identical to real graphql/
requests sourced from OpenSea, which can be found in your browser's Networking tab.
By contrast, request variables
are permitted to change freely.