| 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 |
3 | namespace Attestto\SolanaPhpSdk\Programs\SNS;
4 |
5 | use Attestto\SolanaPhpSdk\PublicKey;
6 | use Attestto\SolanaPhpSdk\Connection;
7 | use Attestto\SolanaPhpSdk\Exceptions\GenericException;
8 | use Attestto\SolanaPhpSdk\Accounts\NameRegistryStateAccount;
9 | use Attestto\SolanaPhpSdk\Exceptions\SNSError;
10 |
11 |
12 | class 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 | }