diff --git a/app/Http/Client/Mnemonic/MnemonicPendingRequest.php b/app/Http/Client/Mnemonic/MnemonicPendingRequest.php index a8dcfc52a..d72d5b6ba 100644 --- a/app/Http/Client/Mnemonic/MnemonicPendingRequest.php +++ b/app/Http/Client/Mnemonic/MnemonicPendingRequest.php @@ -180,56 +180,39 @@ public function getCollectionFloorPrice(Chain $chain, string $contractAddress): } } - // https://docs.mnemonichq.com/reference/collectionsservice_getmetadata + /** + * Fetch the banner image for the collection. + * + * @see https://docs.mnemonichq.com/reference/foundationalservice_getnftcontracts + */ public function getCollectionBanner(Chain $chain, string $contractAddress): ?string { $this->chain = MnemonicChain::fromChain($chain); - /** @var array $data */ - $data = self::get(sprintf('/collections/v1beta2/%s/metadata', $contractAddress), [])->json('metadata'); - - $metadata = collect($data)->mapWithKeys(fn ($item) => [$item['type'] => $item['value']]); - - // *Note:* The API also returns an image for `TYPE_BANNER_IMAGE_URL`, - // however, I noticed that, (at least with the collections we have for - // testing), all images that return a value for - // `TYPE_ORIGINAL_BANNER_IMAGE_URL` also return a value for - // `TYPE_BANNER_IMAGE_URL`. On the other hand, images that don't return - // anything for `TYPE_ORIGINAL_BANNER_IMAGE_URL` do return a - // `TYPE_BANNER_IMAGE_URL` URL, but that URL shows an - // "Image metadata not found" message. Additionally, the image that - // comes from TYPE_BANNER_IMAGE_URL is too small to display in the header. - // In addition to that the image that comes from - // TYPE_ORIGINAL_BANNER_IMAGE_URL are urls from the OpenSea CDN,which - // seems to use the `imgix.com` API (or something similar). - // This means we can use GET attributes to resize the image to a more - // appropriate size for our banners. - $image = $metadata->get('TYPE_ORIGINAL_BANNER_IMAGE_URL'); - - if ($image === null) { + $url = $this->get('/foundational/v1beta2/nft_contracts', [ + 'contractAddresses' => $contractAddress, + ])->json('nftContracts.0.bannerImageUrl'); + + if ($url === null || $url === '') { return null; } - // The image from `TYPE_ORIGINAL_BANNER_IMAGE_URL` includes a w= parameter - // that we can use to store a bigger image in our db. - return NftImageUrl::get($image, ImageSize::Banner); + // The URL includes a w= parameter. We want to normalize it, so that we can store a higher resolution image... + return NftImageUrl::get($url, ImageSize::Banner); } - // https://docs.mnemonichq.com/reference/collectionsservice_getownerscount - public function getCollectionOwners(Chain $chain, string $contractAddress): ?int + /** + * Retrieve the number of owners of the collection. + * + * @see https://docs.mnemonichq.com/reference/collectionsservice_gettotals + */ + public function getCollectionOwners(Chain $chain, string $contractAddress): int { $this->chain = MnemonicChain::fromChain($chain); - /** @var array $data */ - $data = self::get(sprintf('/collections/v1beta2/%s/metadata', $contractAddress), [ - 'includeStats' => true, - ])->json(); - - if (! is_numeric($data['ownersCount'])) { - return null; - } - - return intval($data['ownersCount']); + return (int) $this->get( + '/collections/v1beta2/'.$contractAddress.'/totals' + )->json('ownersCount'); } /** diff --git a/app/Jobs/FetchCollectionOwners.php b/app/Jobs/FetchCollectionOwners.php index 75f10f6bf..f116789c8 100644 --- a/app/Jobs/FetchCollectionOwners.php +++ b/app/Jobs/FetchCollectionOwners.php @@ -15,7 +15,6 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Log; class FetchCollectionOwners implements ShouldQueue { @@ -35,10 +34,6 @@ public function __construct( */ public function handle(): void { - Log::info('FetchCollectionOwners Job: Processing', [ - 'collection' => $this->collection->address, - ]); - $owners = Mnemonic::getCollectionOwners( chain: $this->collection->network->chain(), contractAddress: $this->collection->address @@ -47,11 +42,6 @@ public function handle(): void $this->collection->update([ 'owners' => $owners, ]); - - Log::info('FetchCollectionOwners Job: Handled', [ - 'collection' => $this->collection->address, - 'owners' => $owners, - ]); } public function uniqueId(): string diff --git a/app/Support/Facades/Mnemonic.php b/app/Support/Facades/Mnemonic.php index a765d5c77..b0372144b 100644 --- a/app/Support/Facades/Mnemonic.php +++ b/app/Support/Facades/Mnemonic.php @@ -20,7 +20,7 @@ * @method static string getNativeBalance(Wallet $wallet, Network $network) * @method static Web3CollectionFloorPrice | null getCollectionFloorPrice(Chain $chain, string $contractAddress) * @method static string | null getCollectionBanner(Chain $chain, string $contractAddress) - * @method static int | null getCollectionOwners(Chain $chain, string $contractAddress) + * @method static int getCollectionOwners(Chain $chain, string $contractAddress) * @method static Web3Volume getLatestCollectionVolume(Chain $chain, string $contractAddress) * @method static Collection getCollectionVolumeHistory(Chain $chain, string $address) * @method static Collection getCollectionTraits(Chain $chain, string $contractAddress) diff --git a/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php b/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php index 337b491cc..3a8461b46 100644 --- a/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php +++ b/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php @@ -88,8 +88,7 @@ it('should get owners', function () { Mnemonic::fake([ - 'https://polygon-rest.api.mnemonichq.com/collections/v1beta2/*/metadata?includeStats=1' => Http::sequence() - ->push(['ownersCount' => '789'], 200), + 'https://polygon-rest.api.mnemonichq.com/collections/v1beta2/*/totals' => Http::response(['ownersCount' => '789']), ]); $network = Network::polygon(); @@ -103,6 +102,54 @@ expect($data)->toBe(789); }); +it('should get banner URL for the collection', function () { + Mnemonic::fake([ + 'https://polygon-rest.api.mnemonichq.com/foundational/v1beta2/nft_contracts?contractAddresses=*' => Http::response([ + 'nftContracts' => [[ + 'bannerImageUrl' => 'https://example.com', + ]], + ]), + ]); + + $network = Network::polygon(); + + $collection = Collection::factory()->create([ + 'network_id' => $network->id, + ]); + + $banner = Mnemonic::getCollectionBanner(Chain::Polygon, $collection->address); + + expect($banner)->toBe('https://example.com'); +}); + +it('should normalize the banner URL for the collection', function () { + Mnemonic::fake([ + 'https://polygon-rest.api.mnemonichq.com/foundational/v1beta2/nft_contracts?contractAddresses=*' => Http::response([ + 'nftContracts' => [[ + 'bannerImageUrl' => 'https://i.seadn.io/gae/test?w=150&auto=format', + ]], + ]), + ]); + + $banner = Mnemonic::getCollectionBanner(Chain::Polygon, '0x123'); + + expect($banner)->toBe('https://i.seadn.io/gae/test?w=1378&auto=format'); +}); + +it('can handle empty banner URLs', function () { + Mnemonic::fake([ + 'https://polygon-rest.api.mnemonichq.com/foundational/v1beta2/nft_contracts?contractAddresses=*' => Http::response([ + 'nftContracts' => [[ + 'bannerImageUrl' => '', + ]], + ]), + ]); + + $banner = Mnemonic::getCollectionBanner(Chain::Polygon, '0x123'); + + expect($banner)->toBeNull(); +}); + it('should get volume', function () { Mnemonic::fake([ 'https://polygon-rest.api.mnemonichq.com/collections/v1beta2/*/sales_volume/DURATION_1_DAY/GROUP_BY_PERIOD_1_DAY' => Http::sequence() diff --git a/tests/App/Jobs/FetchCollectionBannerTest.php b/tests/App/Jobs/FetchCollectionBannerTest.php index 6cb0a15e8..ee2fad55b 100644 --- a/tests/App/Jobs/FetchCollectionBannerTest.php +++ b/tests/App/Jobs/FetchCollectionBannerTest.php @@ -6,12 +6,9 @@ use App\Models\Collection; use App\Models\Network; use App\Support\Facades\Mnemonic; -use Illuminate\Support\Facades\Http; it('should fetch nft collection banner', function () { - Mnemonic::fake([ - 'https://polygon-rest.api.mnemonichq.com/collections/v1beta2/*/metadata' => Http::response(fixtureData('mnemonic.nft_metadata'), 200), - ]); + Mnemonic::shouldReceive('getCollectionBanner')->andReturn('https://i.seadn.io/gcs/files/f0d3006fb5a1f09d1619a024762f5aee.png?w=1378&auto=format'); $network = Network::polygon(); @@ -31,17 +28,12 @@ $collection->refresh(); - expect($collection->banner())->toBe('https://i.seadn.io/gcs/files/f0d3006fb5a1f09d1619a024762f5aee.png?w=1378&auto=format') - ->and($collection->image())->toBe('image-url'); + expect($collection->banner())->toBe('https://i.seadn.io/gcs/files/f0d3006fb5a1f09d1619a024762f5aee.png?w=1378&auto=format'); + expect($collection->image())->toBe('image-url'); }); it('should fetch nft collection banner in case no image', function () { - $response = fixtureData('mnemonic.nft_metadata'); - $response['metadata'] = []; - - Mnemonic::fake([ - 'https://polygon-rest.api.mnemonichq.com/collections/v1beta2/*/metadata' => Http::response($response, 200), - ]); + Mnemonic::shouldReceive('getCollectionBanner')->andReturn(null); $network = Network::polygon(); diff --git a/tests/App/Jobs/FetchCollectionOwnersTest.php b/tests/App/Jobs/FetchCollectionOwnersTest.php index 8be05f065..418cdd99e 100644 --- a/tests/App/Jobs/FetchCollectionOwnersTest.php +++ b/tests/App/Jobs/FetchCollectionOwnersTest.php @@ -6,14 +6,9 @@ use App\Models\Collection; use App\Models\Network; use App\Support\Facades\Mnemonic; -use Illuminate\Support\Facades\Http; it('should fetch nft collection owners', function () { - Mnemonic::fake([ - 'https://polygon-rest.api.mnemonichq.com/collections/v1beta2/*/metadata?includeStats=1' => Http::response([ - 'ownersCount' => '789', - ], 200), - ]); + Mnemonic::shouldReceive('getCollectionOwners')->andReturn(789); $network = Network::polygon(); @@ -34,11 +29,7 @@ }); it('should handle null owner count', function () { - Mnemonic::fake([ - 'https://polygon-rest.api.mnemonichq.com/collections/v1beta2/*/metadata?includeStats=1' => Http::response([ - 'ownersCount' => null, - ], 200), - ]); + Mnemonic::shouldReceive('getCollectionOwners')->andReturn(0); $network = Network::polygon(); @@ -55,7 +46,7 @@ $collection->refresh(); - expect($collection->owners)->toBeNull(); + expect($collection->owners)->toBe(0); }); it('has a unique ID', function () {