From c01e8ef7e5fab8b06002b3718d4a72bf28ebada8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20Crnkovi=C4=87?= <6536260+crnkovic@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:12:26 +0100 Subject: [PATCH 1/5] wip --- .../Mnemonic/MnemonicPendingRequest.php | 59 +++++++------------ app/Support/Facades/Mnemonic.php | 2 +- 2 files changed, 22 insertions(+), 39 deletions(-) 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/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) From e50717c976dd1c184204520099c288120e8e77dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20Crnkovi=C4=87?= <6536260+crnkovic@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:14:02 +0100 Subject: [PATCH 2/5] Update FetchCollectionOwners.php --- app/Jobs/FetchCollectionOwners.php | 10 ---------- 1 file changed, 10 deletions(-) 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 From cb623997e577b0adf759ec71889f262e4c4e7fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20Crnkovi=C4=87?= <6536260+crnkovic@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:28:04 +0100 Subject: [PATCH 3/5] fix tests --- .../Mnemonic/MnemonicPendingRequestTest.php | 3 +-- tests/App/Jobs/FetchCollectionBannerTest.php | 16 ++++------------ tests/App/Jobs/FetchCollectionOwnersTest.php | 14 +++----------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php b/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php index 337b491cc..879522844 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(); 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..411c1543d 100644 --- a/tests/App/Jobs/FetchCollectionOwnersTest.php +++ b/tests/App/Jobs/FetchCollectionOwnersTest.php @@ -9,11 +9,7 @@ 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 +30,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 +47,7 @@ $collection->refresh(); - expect($collection->owners)->toBeNull(); + expect($collection->owners)->toBe(0); }); it('has a unique ID', function () { From fbdb583c6a98ab5fc85bedfa8fbb85cf67d1ce3a Mon Sep 17 00:00:00 2001 From: crnkovic Date: Thu, 25 Jan 2024 15:29:45 +0000 Subject: [PATCH 4/5] style: resolve style guide violations --- tests/App/Jobs/FetchCollectionOwnersTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/App/Jobs/FetchCollectionOwnersTest.php b/tests/App/Jobs/FetchCollectionOwnersTest.php index 411c1543d..418cdd99e 100644 --- a/tests/App/Jobs/FetchCollectionOwnersTest.php +++ b/tests/App/Jobs/FetchCollectionOwnersTest.php @@ -6,7 +6,6 @@ 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::shouldReceive('getCollectionOwners')->andReturn(789); From bfe554681072310e881c19227e36211c076dd226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josip=20Crnkovi=C4=87?= <6536260+crnkovic@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:41:44 +0100 Subject: [PATCH 5/5] Update MnemonicPendingRequestTest.php --- .../Mnemonic/MnemonicPendingRequestTest.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php b/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php index 879522844..3a8461b46 100644 --- a/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php +++ b/tests/App/Http/Client/Mnemonic/MnemonicPendingRequestTest.php @@ -102,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()