Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Opensea Laravel Package

owenvoke/opensea

A Laravel and PHP client for the OpenSea API, offering an expressive SDK for NFT marketplace data such as collections, assets, events, and wallets. It integrates easily into Laravel apps and standalone PHP projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and authenticate first.

composer require owenvoke/opensea

OpenSea's v2 API requires a key from https://opensea.io/settings/developer. Free tier is roughly 4 requests/second — that number should shape everything you build on top of this package.

If you're on Laravel, publish the config and put the key in .env rather than hardcoding it:

php artisan vendor:publish --tag="opensea-config"
OPENSEA_API_KEY=your-key-here

Where to look first. Don't start in the README — start in src/. Read in this order:

  1. src/OpenSea.php (or equivalent connector class) — this is the Saloon connector; it defines the base URL, the X-API-KEY header, and which resource methods are exposed.
  2. src/Resources/ — one class per API area (Collection, Nft, Event, Order, …). Each public method maps 1:1 to an endpoint, which is the fastest way to discover what's actually supported versus what OpenSea documents.
  3. src/Dto/ / src/Responses/ — the typed response objects. Their nullable properties tell you exactly what the API can omit in practice.

First use case: collection stats for a dashboard.

use OwenVoke\OpenSea\OpenSea;

$openSea = app(OpenSea::class); // or OpenSea::make()

$stats = $openSea->collection()->stats('boredapeyachtclub');

$stats->floorPrice;   // wei, as a string
$stats->volume24h;
$stats->numOwners;

That single call is the 80% case: a floor-price ticker, a "is this collection moving" alert, or a portfolio valuation. Get that working end-to-end (key → request → DTO → rendered number) before touching anything else, because it exercises the auth header, the connector, the DTO hydration, and the wei conversion all at once.

Implementation Patterns

Wrap the connector, never call it from controllers. The package hands you a stateless connector; your app wants a domain service with caching, retries, and a stable return type.

final class CollectionStatsService
{
    public function __construct(private OpenSea $openSea) {}

    public function floorPriceEth(string $slug): ?string
    {
        return Cache::remember("os:stats:{$slug}", now()->addMinutes(5), function () use ($slug) {
            $stats = $this->openSea->collection()->stats($slug);

            return $stats->floorPrice
                ? bcdiv((string) $stats->floorPrice, '1e18', 6)
                : null;
        });
    }
}

Cache aggressively, and know your staleness budget. Floor prices are the most-requested and least time-sensitive thing you'll fetch. Five minutes for a dashboard, sixty seconds for a trading tool, thirty seconds only if you're paying for a higher tier. Caching is also your primary rate-limit defence.

Batch wallet lookups with a pool, not a foreach. Portfolio valuation means N collections or N wallets. Saloon's pool support (or Laravel's Http::pool if you bypass the connector) lets you fan out; a naive loop against a 4 req/s limit will make an endpoint time out at ~20 wallets.

$responses = $pool->send(); // keep concurrency at 3-4 on the free tier

Push anything larger into a queued job with ->onQueue('opensea') and a dedicated worker, so a slow API never blocks a web request.

Poll events with cursors, not timestamps. The events endpoints return a next cursor; persist it (a sync_state table or cache key) and pass it back on the next run. Timestamp-based polling silently drops events that occur during pagination gaps.

$cursor = Cache::get("os:cursor:{$slug}");
$events = $openSea->event()->byCollection($slug, cursor: $cursor);
Cache::forever("os:cursor:{$slug}", $events->next);

Prefer the DTOs but keep raw JSON reachable. Use typed accessors for the fields you rely on, and fall back to the underlying array for anything OpenSea adds mid-version. If the package's DTOs are strict, a new/renamed API field can throw during hydration — a wrapper that catches and logs the raw payload will save you an incident.

Mock in tests, always. Saloon ships a mock client; use it so CI never touches the live API (and never burns your key's quota).

$openSea = OpenSea::fake([
    '*/collections/*/stats' => Http::response(['total' => ['floor_price' => '1000000000000000000']], 200),
]);

Integration surface. If you also produce NFTs or place orders, note that order creation on OpenSea requires EIP-712 signing with the seller's wallet — that's a signing-library job (web3p/web3.php, ethers) that this package isn't going to do for you. Treat the package as a read layer and handle writes separately.

Gotchas and Tips

Every wei value is a string, and that's on purpose. floor_price regularly exceeds PHP_INT_MAX on 32-bit builds and floats will silently lose precision on large values. Use bcdiv/brick/math, never (int) or (float), and never do arithmetic in wei then cast at the end.

floor_price is nullable and can be zero. A collection with no listings returns no floor, not a floor of zero. Distinguish null (no data) from "0" (genuinely worthless) before you render it — dashboards that show "Ξ0.00 floor" for a delisted collection erode trust fast.

429s are normal, not exceptional. Add Saloon's retry middleware with exponential backoff and respect Retry-After if present. Without it, a single burst of 20 wallet lookups will start failing and your error logs will blame the wrong thing.

Slugs vs. contract addresses are different keys. Collections are addressed by slug (boredapeyachtclub); individual NFTs by (chain, contract, tokenId). Passing a contract address where a slug is expected returns a 400 that reads like a 404. Also: chain is a required string (ethereum, matic, base, arbitrum, …) on most asset endpoints — omit it and the error is unhelpful.

Pagination is cursor-based. next is an opaque token, not an offset. Loop until it's empty and persist it between runs; don't try to compute pages.

Pin the version. Low star count and low dependent count mean the package tracks OpenSea's API at one person's pace. "owenvoke/opensea": "2.*" in composer.json, and check the changelog before minor bumps — endpoint deprecations land here as breaking changes.

Watch the API version boundary. OpenSea retired large parts of v1; if you find a resource method documented but returning 404/410, you're probably looking at a leftover v1 endpoint. Cross-check the path against OpenSea's current v2 reference rather than trusting the wrapper's method name.

Debugging tip: drop a Saloon middleware that logs the full request URL and response body to a dedicated channel. When a DTO fails to hydrate, the exception message rarely tells you which field was unexpected — the raw JSON does, immediately.

Extension points worth knowing: the connector is subclassable, so you can override the base URL to point at a local fixture server or a caching proxy; and Saloon middleware is the right place for cross-cutting concerns (rate limiting, retries, request logging, key rotation) rather than sprinkling them through your service classes.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package