use super::*;
use k256::ecdsa::SigningKey;
use sha3::{Digest, Keccak256};
pub(crate) fn encode_create_room() -> Vec<u8> {
selector("createRoom()").to_vec()
}
pub(crate) fn encode_room_add_member(room_id: u64, member: &[u8; 20]) -> Vec<u8> {
let mut d = selector("roomAddMember(uint256,address)").to_vec();
d.extend_from_slice(&u256_be(room_id as u128));
d.extend_from_slice(&address_word(member));
d
}
pub(crate) fn encode_append_op(room_id: u64, blob: &[u8]) -> Vec<u8> {
let mut d = selector("appendOp(uint256,bytes)").to_vec();
d.extend_from_slice(&u256_be(room_id as u128));
d.extend_from_slice(&u256_be(0x40)); push_abi_bytes(&mut d, blob);
d
}
pub(crate) fn encode_clear_room(room_id: u64) -> Vec<u8> {
let mut d = selector("clearRoom(uint256)").to_vec();
d.extend_from_slice(&u256_be(room_id as u128));
d
}
pub async fn create_room_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(sender, fee_payer, encode_create_room(), fee_token, 2_000_000).await
}
pub async fn room_add_member_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
room_id: u64,
member: &[u8; 20],
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(
sender,
fee_payer,
encode_room_add_member(room_id, member),
fee_token,
1_500_000,
)
.await
}
pub async fn append_op_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
room_id: u64,
blob: &[u8],
fee_token: &str,
) -> Result<String, String> {
let gas = 2_000_000u128 + (blob.len() as u128) * 9_000;
sponsored_diamond_call(sender, fee_payer, encode_append_op(room_id, blob), fee_token, gas).await
}
pub async fn clear_room_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
room_id: u64,
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(sender, fee_payer, encode_clear_room(room_id), fee_token, 1_500_000).await
}
pub async fn ops_of(room_id: u64, from_index: u64) -> Result<Vec<AddrTsBytes>, String> {
let res = read_view(
selector("opsOf(uint256,uint256)"),
&[u256_be(room_id as u128), u256_be(from_index as u128)],
)
.await?;
Ok(decode_addr_ts_bytes_array(&res))
}
pub async fn op_count(room_id: u64) -> Result<u64, String> {
let res = read_view(selector("opCount(uint256)"), &[u256_be(room_id as u128)]).await?;
Ok(read_word_u64(&res))
}
pub async fn room_epoch(room_id: u64) -> Result<u64, String> {
let res = read_view(selector("roomEpoch(uint256)"), &[u256_be(room_id as u128)]).await?;
Ok(read_word_u64(&res))
}
pub async fn room_creator(room_id: u64) -> Result<String, String> {
let res = read_view(selector("roomCreator(uint256)"), &[u256_be(room_id as u128)]).await?;
Ok(read_word_address(&res))
}
pub async fn room_is_member(room_id: u64, who: &[u8; 20]) -> Result<bool, String> {
let res = read_view(
selector("roomIsMember(uint256,address)"),
&[u256_be(room_id as u128), address_word(who)],
)
.await?;
Ok(read_word_u64(&res) != 0)
}
pub async fn room_members_of(room_id: u64) -> Result<Vec<String>, String> {
let res = read_view(selector("roomMembersOf(uint256)"), &[u256_be(room_id as u128)]).await?;
Ok(decode_address_array(&res))
}
pub async fn room_id_created_by(creator_hex: &str) -> Result<Option<u64>, String> {
let topic0 = format!(
"0x{}",
bytes_to_hex(&Keccak256::digest(b"RoomCreated(uint256,address)"))
);
let creator = creator_hex.trim_start_matches("0x").to_lowercase();
let topic2 = format!("0x{:0>64}", creator);
let latest_hex = rpc("eth_blockNumber", serde_json::json!([])).await?;
let latest = parse_hex_quantity(&latest_hex)? as u64;
let from = latest.saturating_sub(99_000);
let from_hex = format!("0x{from:x}");
let topics = vec![
serde_json::json!(topic0),
serde_json::Value::Null,
serde_json::json!(topic2),
];
let logs = eth_get_logs(REGISTRY_ADDRESS(), topics, &from_hex).await?;
let mut best: Option<u64> = None;
for log in &logs {
if let Some(id) = log
.get("topics")
.and_then(|t| t.as_array())
.and_then(|t| t.get(1))
.and_then(|t| t.as_str())
.and_then(|t| u64::from_str_radix(t.trim_start_matches("0x").trim_start_matches('0'), 16).ok())
{
best = Some(best.map_or(id, |b| b.min(id)));
}
}
Ok(best)
}
fn read_word_u64(hex: &str) -> u64 {
match hex_to_bytes(hex) {
Ok(b) if b.len() >= 32 => u64::from_be_bytes(b[24..32].try_into().unwrap_or_default()),
_ => 0,
}
}
fn read_word_address(hex: &str) -> String {
match hex_to_bytes(hex) {
Ok(b) if b.len() >= 32 => format!("0x{}", bytes_to_hex(&b[12..32])),
_ => "0x0000000000000000000000000000000000000000".to_string(),
}
}
fn decode_address_array(hex: &str) -> Vec<String> {
let raw = match hex_to_bytes(hex) {
Ok(b) => b,
Err(_) => return Vec::new(),
};
let read_usize = |off: usize| -> Option<usize> {
let end = off.checked_add(32)?;
let w = raw.get(off..end)?;
Some(u64::from_be_bytes(w[24..32].try_into().ok()?) as usize)
};
let mut out = Vec::new();
let Some(arr_off) = read_usize(0) else {
return out;
};
let Some(len) = read_usize(arr_off) else {
return out;
};
let Some(base) = arr_off.checked_add(32) else {
return out;
};
for i in 0..len {
let Some(slot) = i.checked_mul(32).and_then(|o| base.checked_add(o)) else {
break;
};
match slot.checked_add(12).zip(slot.checked_add(32)).and_then(|(a, b)| raw.get(a..b)) {
Some(a) => out.push(format!("0x{}", bytes_to_hex(a))),
None => break,
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_append_op_layout() {
let d = encode_append_op(7, b"hi");
assert_eq!(d.len(), 4 + 32 + 32 + 32 + 32);
assert_eq!(&d[..4], &selector("appendOp(uint256,bytes)"));
assert_eq!(d[4 + 32 + 31], 0x40);
assert_eq!(d[4 + 64 + 31], 2);
}
#[test]
fn read_word_helpers() {
let hex = format!("0x{:0>64}", "2a"); assert_eq!(read_word_u64(&hex), 42);
let addr_word = format!("0x{:0>24}{}", "", "a".repeat(40));
assert_eq!(read_word_address(&addr_word), format!("0x{}", "a".repeat(40)));
}
#[test]
fn decode_address_array_round_trip() {
let a = "a".repeat(40);
let b = "b".repeat(40);
let off = format!("{:0>64x}", 0x20);
let len = format!("{:0>64x}", 2);
let a_word = format!("{:0>24}{a}", "");
let b_word = format!("{:0>24}{b}", "");
let hex = format!("0x{off}{len}{a_word}{b_word}");
let got = decode_address_array(&hex);
assert_eq!(got, vec![format!("0x{a}"), format!("0x{b}")]);
}
}