1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/// not ready for thought
use crate::{
eth::{
self,
Bytes,
Provider,
Address as EthAddress
},
hypermap::{
self,
Hypermap
},
println as kiprintln,
};
use alloy_primitives::{
B256,
FixedBytes
};
use alloy::rpc_types::request::{
TransactionInput,
TransactionRequest
};
use alloy_sol_types::SolValue;
use std::str::FromStr;
use hex;
use serde::{
Serialize,
Deserialize
};
/// ERC-6551 Account Interface for interacting with Token-Bound Accounts
pub struct Erc6551Account {
address: EthAddress,
provider: Provider,
}
impl Erc6551Account {
pub fn new(address: EthAddress, provider: Provider) -> Self {
Self { address, provider }
}
/// Get token information from the TBA
pub fn token(&self) -> Result<(u64, EthAddress, u64)> {
// Function selector for token()
let selector = [0x45, 0xc3, 0x11, 0x87]; // token()
let tx_req = TransactionRequest::default()
.input(TransactionInput::new(Bytes::from(selector.to_vec())))
.to(self.address);
let res_bytes = self.provider.call(tx_req, None)?;
// Parse the response (chainId, tokenContract, tokenId)
if res_bytes.len() < 96 {
return Err(anyhow!("Invalid response length for token() call"));
}
// Extract chainId (first 32 bytes)
let chain_id = u64::from_be_bytes([
res_bytes[24], res_bytes[25], res_bytes[26], res_bytes[27],
res_bytes[28], res_bytes[29], res_bytes[30], res_bytes[31],
]);
// Extract tokenContract (next 32 bytes, but we only need 20 bytes)
let token_contract = EthAddress::from_slice(&res_bytes[44..64]);
// Extract tokenId (last 32 bytes)
let token_id = u64::from_be_bytes([
res_bytes[88], res_bytes[89], res_bytes[90], res_bytes[91],
res_bytes[92], res_bytes[93], res_bytes[94], res_bytes[95],
]);
Ok((chain_id, token_contract, token_id))
}
/// Check if a signer is valid for this account
pub fn is_valid_signer(&self, signer: &EthAddress) -> Result<bool> {
// Function selector for isValidSigner(address,bytes)
let selector = [0x52, 0x65, 0x78, 0x4c, 0x3c];
// Encode the signer address (padded to 32 bytes)
let mut call_data = Vec::with_capacity(68);
call_data.extend_from_slice(&selector);
// Add the address parameter (padded to 32 bytes)
call_data.extend_from_slice(&[0; 12]); // 12 bytes padding
call_data.extend_from_slice(signer.as_slice());
// Add offset to empty bytes array (32) and length (0)
call_data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32]);
call_data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let tx_req = TransactionRequest::default()
.input(TransactionInput::new(Bytes::from(call_data)))
.to(self.address);
match self.provider.call(tx_req, None) {
Ok(res) => {
// Expect a boolean response (32 bytes with last byte being 0 or 1)
if res.len() >= 32 && (res[31] == 1) {
Ok(true)
} else {
Ok(false)
}
},
Err(e) => {
kiprintln!("isValidSigner call failed: {:?}", e);
Ok(false)
}
}
}
/// Get the implementation address of this TBA
pub fn get_implementation(&self) -> Result<EthAddress> {
// EIP-1967 implementation slot
let impl_slot = B256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")?;
match self.provider.get_storage_at(self.address, impl_slot, None) {
Ok(value) => {
// Extract address from storage value (last 20 bytes)
let bytes = value.as_ref();
if bytes.len() >= 20 {
let impl_addr = EthAddress::from_slice(&bytes[bytes.len()-20..]);
Ok(impl_addr)
} else {
Err(anyhow!("Invalid implementation address format"))
}
},
Err(e) => Err(anyhow!("Failed to get implementation: {:?}", e)),
}
}
/// Check if an auth-key is set on the TBA
pub fn has_auth_key(&self) -> Result<bool> {
// Storage slot for auth keys (depends on implementation)
// This would need to match the exact implementation's storage layout
let auth_key_slot = B256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001")?;
match self.provider.get_storage_at(self.address, auth_key_slot, None) {
Ok(value) => {
// Check if the slot has a non-zero value
let bytes = value.as_ref();
Ok(!bytes.iter().all(|&b| b == 0))
},
Err(e) => {
kiprintln!("Failed to check auth key: {:?}", e);
Ok(false)
}
}
}
}
/// Complete TBA information structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TbaInfo {
pub node_name: String,
pub tba_address: EthAddress,
pub owner_address: EthAddress,
pub implementation: EthAddress,
pub chain_id: u64,
pub token_contract: EthAddress,
pub token_id: u64,
pub auth_signers: Vec<EthAddress>,
}
/// Extended Hypermap functionality specifically for TBA exploration
pub struct TbaExplorer {
hypermap: Hypermap,
}
impl TbaExplorer {
pub fn new(timeout: u64) -> Self {
Self {
hypermap: Hypermap::default(timeout),
}
}
/// Format a node name to ensure it has proper extension
pub fn format_node_name(&self, node_name: &str) -> String {
if !node_name.contains('.') {
format!("{}.hypr", node_name)
} else {
node_name.to_string()
}
}
/// Get information about a TBA by node name
pub fn get_tba_info(&self, node_name: &str) -> Result<TbaInfo> {
// Format node name properly
let name = self.format_node_name(node_name);
// Get TBA and owner from Hypermap
let (tba_address, owner_address, _) = self.hypermap.get(&name)?;
// Create ERC-6551 account wrapper
let account = Erc6551Account::new(tba_address, self.hypermap.provider.clone());
// Get token info
let (chain_id, token_contract, token_id) = match account.token() {
Ok(info) => info,
Err(e) => {
kiprintln!("Failed to get token info: {:?}", e);
// Provide defaults
(0, EthAddress::default(), 0)
}
};
// Get implementation
let implementation = match account.get_implementation() {
Ok(impl_addr) => impl_addr,
Err(e) => {
kiprintln!("Failed to get implementation: {:?}", e);
EthAddress::default()
}
};
// Get custom auth signers from ~auth-signers note if it exists
let auth_signers = self.get_auth_signers(&name)?;
Ok(TbaInfo {
node_name: name,
tba_address,
owner_address,
implementation,
chain_id,
token_contract,
token_id,
auth_signers,
})
}
/// Get authorized signers from a node's notes
fn get_auth_signers(&self, node_name: &str) -> Result<Vec<EthAddress>> {
let mut auth_signers = Vec::new();
// Get the namehash
let namehash = hypermap::namehash(node_name);
// Create filter for ~auth-signers note
let note_filter = self.hypermap.notes_filter(&["~auth-signers"])
.topic1(vec![FixedBytes::<32>::from_str(&namehash)?]);
// Get logs
let logs = self.hypermap.provider.get_logs(¬e_filter)?;
if logs.is_empty() {
return Ok(Vec::new());
}
// Process the latest version of the note
if let Some(latest_log) = logs.last() {
if let Ok(note) = hypermap::decode_note_log(latest_log) {
// Try to parse as JSON list of addresses
if let Ok(json) = serde_json::from_slice::<serde_json::Value>(¬e.data) {
if let Some(addresses) = json.as_array() {
for addr in addresses {
if let Some(addr_str) = addr.as_str() {
if let Ok(address) = EthAddress::from_str(addr_str) {
auth_signers.push(address);
}
}
}
}
} else {
// If not JSON, try to parse as comma-separated list
let data_str = String::from_utf8_lossy(¬e.data);
for addr_str in data_str.split(',') {
let trimmed = addr_str.trim();
if let Ok(address) = EthAddress::from_str(trimmed) {
auth_signers.push(address);
}
}
}
}
}
Ok(auth_signers)
}
/// Check if an address is authorized to sign for a node's TBA
pub fn is_authorized_signer(&self, node_name: &str, signer: &EthAddress) -> Result<bool> {
// Get TBA info
let tba_info = self.get_tba_info(node_name)?;
// Check if signer is owner (always authorized)
if &tba_info.owner_address == signer {
return Ok(true);
}
// Check if signer is in auth_signers list from note
if tba_info.auth_signers.contains(signer) {
return Ok(true);
}
// Check using isValidSigner directly
let account = Erc6551Account::new(tba_info.tba_address, self.hypermap.provider.clone());
account.is_valid_signer(signer)
}
/// Get all ~net-key and any custom signing keys from a node
pub fn get_signing_keys(&self, node_name: &str) -> Result<Vec<Vec<u8>>> {
let mut keys = Vec::new();
// Format node name
let name = self.format_node_name(node_name);
// Get the namehash
let namehash = hypermap::namehash(&name);
// Create filter for ~net-key and ~signing-key notes
let keys_filter = self.hypermap.notes_filter(&["~net-key", "~signing-key"])
.topic1(vec![FixedBytes::<32>::from_str(&namehash)?]);
// Get logs
let logs = self.hypermap.provider.get_logs(&keys_filter)?;
for log in logs {
if let Ok(note) = hypermap::decode_note_log(&log) {
// Add the key data
keys.push(note.data.to_vec());
}
}
Ok(keys)
}
/// Check if a TBA supports the custom auth signer mechanism
pub fn supports_auth_signers(&self, node_name: &str) -> Result<bool> {
// Get TBA info
let tba_info = self.get_tba_info(node_name)?;
// Check if the implementation is the customized TBA
// This would require knowing the specific implementation address
// For now, we'll just check if the implementation isn't the default
if tba_info.implementation == EthAddress::default() {
return Ok(false);
}
// We could also check if there's already an auth_signers note
if !tba_info.auth_signers.is_empty() {
return Ok(true);
}
// Try calling isValidSigner with a test address to see if it works
let account = Erc6551Account::new(tba_info.tba_address, self.hypermap.provider.clone());
// Check if auth key slot is used
account.has_auth_key()
}
/// Format key data for storage in Hypermap
pub fn format_auth_signers(&self, signers: &[EthAddress]) -> Result<Bytes> {
// Format as JSON array of address strings
let signer_strings: Vec<String> = signers.iter()
.map(|addr| addr.to_string())
.collect();
let json = serde_json::to_string(&signer_strings)?;
Ok(Bytes::copy_from_slice(json.as_bytes()))
}
}
/// Helper function to convert a hex string to an Address
pub fn hex_to_address(hex_str: &str) -> Result<EthAddress> {
let cleaned = hex_str.trim_start_matches("0x");
if cleaned.len() != 40 {
return Err(anyhow!("Invalid address length"));
}
let bytes = hex::decode(cleaned)?;
Ok(EthAddress::from_slice(&bytes))
}
/// Helper function to convert bytes to a human-readable format
pub fn format_bytes(bytes: &[u8]) -> String {
if bytes.len() <= 64 {
// For small data, show full hex
format!("0x{}", hex::encode(bytes))
} else {
// For larger data, truncate
format!("0x{}...{} ({} bytes)",
hex::encode(&bytes[..8]),
hex::encode(&bytes[bytes.len() - 8..]),
bytes.len())
}
}