Closed faridmovsumov closed 2 years ago
Finally figured out how to get it. Code should look something like this.
$mint = "4EbYVzfS5ad5wYcAdoaxJM7HUeiQq9RnCeSm289CUcC6";
$METADATA_PUBKEY = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
/** @var PublicKey $pda */
list($pda, $bump) = PublicKey::findProgramAddress([
'metadata',
$METADATA_PUBKEY,
(new PublicKey($mint)),
], $METADATA_PUBKEY);
$metadataAddress = $pda->getPublicKey()->toBase58();
$sdk = new Connection(new SolanaRpcClient(SolanaRpcClient::MAINNET_ENDPOINT));
$accountInfo = $sdk->getAccountInfo($metadataAddress);
$base64Data = $accountInfo['data'][0];
$buffer = Buffer::from(base64_decode($base64Data));
$metadata = Metadata::fromBuffer($buffer->toArray());
dd($metadata);
And output
I refactored the code a bit, now it is possible to fetch all NFT's by the given wallet address. (Laravel)
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Tighten\SolanaPhpSdk\Accounts\Metadata;
use Tighten\SolanaPhpSdk\Connection;
use Tighten\SolanaPhpSdk\Programs\MetaplexProgram;
use Tighten\SolanaPhpSdk\Programs\SplTokenProgram;
use Tighten\SolanaPhpSdk\PublicKey;
use Tighten\SolanaPhpSdk\SolanaRpcClient;
use Tighten\SolanaPhpSdk\Util\Buffer;
class NftController extends Controller
{
/*
* @see https://docs.metaplex.com/architecture/deep_dive/overview
*/
public function getNfts(Request $request)
{
$walletAddress = $request->get('walletAddress');
$solanaRpcClient = new SolanaRpcClient(SolanaRpcClient::MAINNET_ENDPOINT);
$splTokenProgram = new SplTokenProgram($solanaRpcClient);
$tokenAccounts = $splTokenProgram->getTokenAccountsByOwner($walletAddress);
if (empty($tokenAccounts['value'])) {
new Response("Nft not found", 404);
}
$tokens = $tokenAccounts['value'];
$sdk = new Connection($solanaRpcClient);
$nftMetaDatas = [];
foreach ($tokens as $token) {
try {
if (empty($token['account']['data']['parsed']['info']['mint'])) {
continue;
}
$mint = $token['account']['data']['parsed']['info']['mint'];
$metaplexProgramPublicKey = new PublicKey(MetaplexProgram::METAPLEX_PROGRAM_ID);
/** @var PublicKey $pda */
list($pda, $bump) = PublicKey::findProgramAddress([
'metadata',
$metaplexProgramPublicKey,
(new PublicKey($mint)),
], $metaplexProgramPublicKey);
$metadataAddress = $pda->getPublicKey()->toBase58();
$accountInfo = $sdk->getAccountInfo($metadataAddress);
if (empty($accountInfo['owner']) || $accountInfo['owner'] !== MetaplexProgram::METAPLEX_PROGRAM_ID) {
//This is not nft
continue;
}
if (empty($accountInfo['data'][0]) || empty($accountInfo['data'][1]) || $accountInfo['data'][1] !== 'base64') {
//Not the format we are trying to convert
continue;
}
$base64Data = $accountInfo['data'][0];
$buffer = Buffer::from(base64_decode($base64Data));
$metadata = Metadata::fromBuffer($buffer->toArray());
$nftMetaDatas[] = $metadata;
} catch (\Throwable $throwable) {
Log::error("Error while fetching NFT for wallet address $walletAddress Reason: " . $throwable->getMessage());
continue;
}
}
if (empty($nftMetaDatas)) {
new Response("Nft not found", 404);
}
return new Response($nftMetaDatas);
}
}
Cool and thanks for the example! I'm doing it in a similar fashion but with multiple jobs running the background.
One thing that I might add, is an additional check for NFTs as your code currently will return USDT & USDC token accounts as NFTs with metadata.
While it's not perfect, I'm running additional checks mentioned here https://gist.github.com/creativedrewy/9bce794ff278aae23b64e6dc8f10e906#step-2-locate-nft-accounts
@neverything thanks for sharing, seems like the biggest challenge is to not hit rate limits. Do you have any example of how do you handle it?
Using rate limiter middleware on the jobs and some custom delays.
Update: But I must admit, I haven't fully figured out how to make it run perfectly and smooth. Currently running roughly 4.5k jobs per hour in the background on Redis/Horizon.
Now that the Borsh implementation with the Metadata stuff is working in this package, I'm trying to implement a more relaxed approach, especially when it comes to updating token accounts and NFT metadata. But I'm still learning (not a pro at any of this yet), hence my thread on twitter https://twitter.com/neverything/status/1478751444329238533
Thanks for sharing your code! Hopefully it will help others :)
Found important detail want to also share here. getTokenAccountsByOwner method retrieves old NFTs which has been sold. You should check if the amount is not empty (0 is considered empty in PHP)
if(empty($token['account']['data']['parsed']['info']['tokenAmount']['amount'])){
Log::info('This NFT does not belongs to account anymore, skip');
continue;
}
@faridmovsumov thanks for sharing. I was wondering if there was an easy way to check this without going through all the transactions from a wallet or a NFT token account.
Unfortunately couldn't find anything easier, there is no option to filter out this in API as far as I understood.
Hello,
I am super happy to discover this repository and appreciate what you do here. I checked previous issues and found past discussions regarding metadata, but unfortunately, I couldn't make it work. Mint is 4EbYVzfS5ad5wYcAdoaxJM7HUeiQq9RnCeSm289CUcC6 (I also tried with 3k7M6KoYQ8kkpcWqceAZokLJHfiVfHGNtU7an5BGPh3C)
I am trying to get metadata with the following code
But it is throwing the following error
Tighten\SolanaPhpSdk\Borsh\BorshException: Expected buffer length 32 isn't within bounds in file /Users/farid/projects/web3/vendor/tightenco/solana-php-sdk/src/Borsh/BinaryReader.php on line 177
I also checked this token on Solscan and it looks correct. Not sure if I am using the correct id. https://solscan.io/token/4EbYVzfS5ad5wYcAdoaxJM7HUeiQq9RnCeSm289CUcC6#metadata
Could you please help me to understand what is wrong here?