verze-app / solana-php-sdk

Simple PHP SDK for Solana JSON RPC endpoints
MIT License
88 stars 45 forks source link

Getting NFT metadata #10

Closed neverything closed 2 years ago

neverything commented 2 years ago

Hey @exzachlyvv and @mattstauffer thank you so much for putting in so much effort to make this available in PHP.

Using the @exzachlyvv fork created, I'm trying to rebuild something like this https://www.vicyyn.com/#/nft?DqGGFuiWRiJ6Mim1qafLWn6FZzBGGcSTyFFSJ8RpuqE8

The code used on the site above is: https://github.com/vicyyn/MetaplexMetadata-js

It somehow espaces me how to use the PublicKey::findProgramAddress compared to the one in the repo above. I can't seem to get the seed part working properly.

Any hint would be greatly appreciated

exzachlyvv commented 2 years ago

Hey @neverything

Here is a working example of PublicKey::findProgramAddress():

$programId = new PublicKey('BPFLoader1111111111111111111111111111111111');

list($programAddress, $nonce) = PublicKey::findProgramAddress(
    [Ed25519Keypair::bin2array('')],
    $programId
);

Parameter 1 needs to be an array of int arrays (array<array<int>>)

In the future, we can improve the DX so that you don't need to be concerned with binary information, but we are not there yet (thank you for being an early adopter! :D).

Now applying this to the code you are trying to replicate:

let [pda, bump] = await PublicKey.findProgramAddress([
  Buffer.from("metadata"),
  METADATA_PUBKEY.toBuffer(),
  new PublicKey(address).toBuffer(),
], METADATA_PUBKEY)

In PHP should roughly look like:

$METADATA_PUBKEY = new PublicKey(<Metaplex base58 ID... I think it is: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s">);

list($pda, $bump) = PublicKey::findProgramAddress([
    Ed25519Keypair::bin2array('metadata'),
    $METADATA_PUBKEY->toBytes(),
    (new PublicKey(address))->toBytes(),
], $METADATA_PUBKEY);
exzachlyvv commented 2 years ago

I think this package could benefit from a Buffer class that can easily convert anything into a binary array. I'll try and work on that soon.

neverything commented 2 years ago

Wow @exzachlyvv thank you so much, with your help I got it working and can now get the proper programAddress. Please let me know if I can assist with testing or anything.

Ha yes a proper Buffer class would help a lot.

The next problem is going to be finding out how to deserialze the borsh data containing the metadata when calling:

$client->call('getAccountInfo', ['DSQiKA3zxbvXwJMT8kjUCVPRn9WbEnps18BoQPyRhxQp', ["encoding" => "base64"]]);

exzachlyvv commented 2 years ago

Hey @neverything

The addition of the Buffer class is complete and merged into my fork. There will be some breaking changes in there for you.

This should now work for you:

$METADATA_PUBKEY = new PublicKey(<Metaplex base58 ID... I think it is: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s">);

list($pda, $bump) = PublicKey::findProgramAddress([
    'metadata',
    $METADATA_PUBKEY,
    (new PublicKey(address)),
], $METADATA_PUBKEY);
neverything commented 2 years ago

Wow that's amazing @exzachlyvv will test it tonight. Thank you 🙏

neverything commented 2 years ago

Works like a charm @exzachlyvv! Thank you so much

mattstauffer commented 2 years ago

@neverything What's your progress looking like on deserializing the Borsh data? I tried a few tools that all failed and ended up just doing some nasty string manipulation on a base64 decode, if I remember correctly.

@exzachlyvv Fantastic work on this man!!

neverything commented 2 years ago

@mattstauffer doing the same 😅. Running base64 decode and using regex to get the url for the json file. So far works with arweave, staratlas, ipfs.

As long as there is no borsh implementation for php (lack of skills on my side to build one myself😅), I might end up using Symfony Process to run a node/python/go implementation to get the borsh result and turn it into a dto or something 🤷‍♀️.

For now I'm happy to get the url to the json file for the nft.

exzachlyvv commented 2 years ago

I can work on a PHP port of the borsh library next :D

neverything commented 2 years ago

@mattstauffer my very nasty string manipulation in case you were wondering 🤣

protected function hasMetaDataUrl(string $metaDataRaw)
    {
        $raw_url = iconv(mb_detect_encoding($metaDataRaw, mb_detect_order(), true), "UTF-8//IGNORE", $metaDataRaw);
        $raw_url = preg_replace('/[[:cntrl:]]/', '', $raw_url);
        $raw_url = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $raw_url);

        $reg_exUrl = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/";

        if (preg_match($reg_exUrl, $raw_url, $url)) {

            $url = Str::of($url[0])->before('\0');

            if (Str::contains($url, 'https://arweave.net/')) {
                $path = Str::after($url, 'https://arweave.net/');
                $path = Str::substr($path, 0, 43);
                $url = 'https://arweave.net/' . $path;
            }

            $this->metaDataUrl = $url;

            return true;
        } else {
            return false;
        }
    }