Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Utils
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 9
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 loadConstants
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getHashedNameSync
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getNameAccountKey
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 reverseLookup
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 deserializeReverse
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getDomainKey
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
 _derive
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getReverseKey
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace Attestto\SolanaPhpSdk\Programs\SNS;
4
5use Attestto\SolanaPhpSdk\PublicKey;
6use Attestto\SolanaPhpSdk\Connection;
7use Attestto\SolanaPhpSdk\Exceptions\GenericException;
8use Attestto\SolanaPhpSdk\Accounts\NameRegistryStateAccount;
9use Attestto\SolanaPhpSdk\Exceptions\SNSError;
10
11
12class Utils
13{
14
15    // config.json file should be in the same directory as this file
16    public $config;
17
18    // Constructor
19    public function __construct($config = null)
20    {
21        if ($config) {
22            $this->config = $config;
23        } else {
24            $this->config = $this->loadConstants();
25        }
26        return $this;
27    }
28
29    private function loadConstants()
30    {
31        $jsonFilePath = dirname(__DIR__) . '/SNS/Constants/config.json';
32        return json_decode(file_get_contents($jsonFilePath), true);
33    }
34
35    public function getHashedNameSync(string $name): string
36    {
37        $input = $this->config['HASH_PREFIX'] . $name;
38        $hashed = hash('sha256', $input);
39        return $hashed;
40    }
41
42    /**
43     * @deprecated Use {@link getNameAccountKeySync} instead
44     * @param string $hashedName The hashed name buffer
45     * @param PublicKey|null $nameClass The name class public key
46     * @param PublicKey|null $nameParent The name parent public key
47     * @return PublicKey The public key of the name account
48     */
49    function getNameAccountKey(string $hashedName, ?PublicKey $nameClass = null, ?PublicKey $nameParent = null): PublicKey
50    {
51        $seeds = [$hashedName];
52        if ($nameClass) {
53            $seeds[] = $nameClass->toBuffer();
54        } else {
55            $seeds[] = str_repeat("\0", 32); // Buffer.alloc(32)
56        }
57        if ($nameParent) {
58            $seeds[] = $nameParent->toBuffer();
59        } else {
60            $seeds[] = str_repeat("\0", 32); // Buffer.alloc(32)
61        }
62        $result = PublicKey::findProgramAddress($seeds, NAME_PROGRAM_ID);
63        return $result[0];
64    }
65
66    //-- 
67    /**
68     * This function can be used to perform a reverse look up
69     * @param connection The Solana RPC connection
70     * @param nameAccount The public key of the domain to look up
71     * @returns The human readable domain name
72     */
73    public function reverseLookup(Connection $connection, PublicKey $nameAccount): string
74    {
75        $hashedReverseLookup = $this->getHashedNameSync($nameAccount->toBase58());
76        $reverseLookupAccount = $this->getNameAccountKeySync($hashedReverseLookup, $this->config->REVERSE_LOOKUP_CLASS);
77
78        $registry = NameRegistryStateAccount::retrieve($connection, $reverseLookupAccount);
79        if (!$registry['data']) {
80            throw new SNSError(SNSError::NoAccountData);
81        }
82
83        return $this->deserializeReverse($registry['data']);
84    }
85
86    public function deserializeReverse(
87        $data
88    ): ?string {
89        if (!$data) {
90            return null;
91        }
92        $nameLength = unpack('V', substr($data, 0, 4))[1];
93        return substr($data, 4, $nameLength);
94    }
95
96
97    /**
98    * This function can be used to compute the public key of a domain or subdomain
99    * @deprecated Use {@link getDomainKeySync} instead
100    * @param string $domain The domain to compute the public key for (e.g `bonfida.sol`, `dex.bonfida.sol`)
101    * @param bool $record Optional parameter: If the domain being resolved is a record
102    * @return array
103    * @throws SNSError
104    */
105    function getDomainKey(string $domain, bool $record = false): array
106    {
107        if (substr($domain, -4) === '.sol') {
108            $domain = substr($domain, 0, -4);
109        }
110        $splitted = explode('.', $domain);
111        if (count($splitted) === 2) {
112            $prefix = $record ? "\x01" : "\x00";
113            $sub = $prefix . $splitted[0];
114            $parentKey = $this->_derive($splitted[1])['pubkey'];
115            $result = $this->_derive($sub, $parentKey);
116            return array_merge($result, ['isSub' => true, 'parent' => $parentKey]);
117        } elseif (count($splitted) === 3 && $record) {
118            // Parent key
119            $parentKey = $this->_derive($splitted[2])['pubkey'];
120            // Sub domain
121            $subKey = $this->_derive("\x00" . $splitted[1], $parentKey)['pubkey'];
122            // Sub record
123            $recordPrefix = "\x01";
124            $result = $this->_derive($recordPrefix . $splitted[0], $subKey);
125            return array_merge($result, ['isSub' => true, 'parent' => $parentKey, 'isSubRecord' => true]);
126        } elseif (count($splitted) >= 3) {
127            throw new SNSError(ErrorType::InvalidInput);
128        }
129        $result = $this->_derive($domain, ROOT_DOMAIN_ACCOUNT);
130        return array_merge($result, ['isSub' => false, 'parent' => null]);
131    }
132
133    private function _derive(
134        string $name,
135        PublicKey $parent = ROOT_DOMAIN_ACCOUNT
136    ): array {
137        $hashed = $this->getHashedNameSync($name);
138        $pubkey = $this->getNameAccountKeySync($hashed, null, $parent);
139        return ['pubkey' => $pubkey, 'hashed' => $hashed];
140    }
141
142    /**
143    * This function can be used to get the key of the reverse account
144    * @deprecated Use {@link getReverseKeySync} instead
145    * @param string $domain The domain to compute the reverse for
146    * @param bool|null $isSub Whether the domain is a subdomain or not
147    * @return PublicKey The public key of the reverse account
148    */
149    function getReverseKey(string $domain, ?bool $isSub = false): PublicKey
150    {
151        $domainKey = $this->getDomainKey($domain);
152        $hashedReverseLookup = $this->getHashedName($domainKey['pubkey']->toBase58());
153        $reverseLookupAccount = $this->getNameAccountKey($hashedReverseLookup, REVERSE_LOOKUP_CLASS, $isSub ? $domainKey['parent'] : null);
154        return $reverseLookupAccount;
155    }
156}