| 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 |
3 | namespace Attestto\SolanaPhpSdk\Programs\SNS;
4 |
5 | use Attestto\SolanaPhpSdk\Exceptions\AccountNotFoundException;
6 | use Attestto\SolanaPhpSdk\Programs\SNS\State\NameRegistryStateAccount;
7 | use Attestto\SolanaPhpSdk\Connection;
8 | use Attestto\SolanaPhpSdk\Exceptions\InputValidationException;
9 | use Attestto\SolanaPhpSdk\Exceptions\SNSError;
10 |
11 | use Attestto\SolanaPhpSdk\PublicKey;
12 | use Attestto\SolanaPhpSdk\Util\Buffer;
13 |
14 | trait 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 | }