Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
73.77% |
45 / 61 |
|
66.67% |
6 / 9 |
CRAP | |
0.00% |
0 / 1 |
| Utils | |
73.77% |
45 / 61 |
|
66.67% |
6 / 9 |
32.55 | |
0.00% |
0 / 1 |
| loadConstants | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| getHashedNameSync | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| getNameAccountKeySync | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 | |||
| reverseLookup | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| deserializeReverse | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| getDomainKeySync | |
70.00% |
14 / 20 |
|
0.00% |
0 / 1 |
11.19 | |||
| _deriveSync | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getReverseKeySync | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
| getNameOwner | |
100.00% |
1 / 1 |
|
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 | } |