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