use k256::ecdsa::SigningKey;
use crate::wallet;
use super::*;
pub async fn next_nonce(address_hex: &str) -> Result<u128, String> {
eth_get_transaction_count(address_hex).await
}
pub fn set_metadata_gas(byte_len: usize) -> u128 {
1_200_000 + byte_len as u128 * 8_500
}
pub async fn current_gas_price() -> Result<u128, String> {
eth_gas_price().await
}
pub async fn submit_and_wait_receipt(raw_hex: &str) -> Result<String, String> {
let tx_hash = eth_send_raw_transaction(raw_hex).await?;
wait_for_receipt(&tx_hash).await?;
Ok(tx_hash)
}
pub async fn token_balance_of(holder_hex: &str) -> Result<u128, String> {
erc20_balance_of(LOCALHARNESS_TOKEN_ADDRESS, holder_hex).await
}
pub async fn erc20_balance_of(token_hex: &str, holder_hex: &str) -> Result<u128, String> {
let holder_bytes = hex_to_bytes(holder_hex)?;
if holder_bytes.len() != 20 {
return Err(format!("holder must be 20 bytes, got {}", holder_bytes.len()));
}
let mut padded = [0u8; 32];
padded[12..].copy_from_slice(&holder_bytes);
let calldata_hex = encode_call_hex(selector("balanceOf(address)"), &[padded]);
let result = eth_call(token_hex, &calldata_hex).await?;
decode_u256_as_u128(&result)
}
pub(crate) fn decode_u256_as_u128(hex: &str) -> Result<u128, String> {
let trimmed = hex.trim_start_matches("0x");
if trimmed.is_empty() {
return Ok(0);
}
let tail = if trimmed.len() <= 32 {
trimmed
} else {
&trimmed[trimmed.len() - 32..]
};
u128::from_str_radix(tail, 16).map_err(|e| e.to_string())
}
pub const ALPHA_USD_ADDRESS: &str = "0x20c0000000000000000000000000000000000001";
pub async fn submit_tempo_self_paid(
sender: &SigningKey,
calls: Vec<crate::tempo_tx::TempoCall>,
fee_token: Option<&str>,
gas_limit: u128,
) -> Result<String, String> {
use crate::tempo_tx::{sign_self_paid, TempoTxBuilder};
let sender_addr = wallet::address(sender);
let sender_hex = address_to_hex(&sender_addr);
let nonce = eth_get_transaction_count(&sender_hex).await?;
let gas_price = eth_gas_price().await?;
let mut builder = TempoTxBuilder::new(CHAIN_ID)
.max_priority_fee_per_gas(gas_price)
.max_fee_per_gas(gas_price)
.gas_limit(gas_limit)
.nonce(nonce)
.calls(calls);
if let Some(token) = fee_token {
builder = builder.fee_token(parse_eth_address(token)?);
}
let tx = builder.build();
let raw = sign_self_paid(tx, sender);
let raw_hex = format!("0x{}", bytes_to_hex(&raw));
let tx_hash = eth_send_raw_transaction(&raw_hex).await?;
wait_for_receipt(&tx_hash).await?;
Ok(tx_hash)
}
pub async fn submit_tempo_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
calls: Vec<crate::tempo_tx::TempoCall>,
fee_token: &str,
gas_limit: u128,
) -> Result<String, String> {
use crate::tempo_tx::{sign_sponsored, TempoTxBuilder};
let sender_addr = wallet::address(sender);
let sender_hex = address_to_hex(&sender_addr);
let nonce = eth_get_transaction_count(&sender_hex).await?;
let gas_price = eth_gas_price().await?;
let tx = TempoTxBuilder::new(CHAIN_ID)
.max_priority_fee_per_gas(gas_price)
.max_fee_per_gas(gas_price)
.gas_limit(gas_limit)
.nonce(nonce)
.calls(calls)
.fee_token(parse_eth_address(fee_token)?)
.sponsored()
.build();
let raw = sign_sponsored(tx, sender, fee_payer);
let raw_hex = format!("0x{}", bytes_to_hex(&raw));
let tx_hash = eth_send_raw_transaction(&raw_hex).await?;
wait_for_receipt(&tx_hash).await?;
Ok(tx_hash)
}
pub(crate) fn parse_eth_address(hex_str: &str) -> Result<[u8; 20], String> {
let bytes = hex_to_bytes(hex_str)?;
if bytes.len() != 20 {
return Err(format!("address must be 20 bytes, got {}", bytes.len()));
}
let mut out = [0u8; 20];
out.copy_from_slice(&bytes);
Ok(out)
}
pub(crate) async fn sponsored_call_to(
sender: &SigningKey,
fee_payer: &SigningKey,
to_hex: &str,
input: Vec<u8>,
fee_token: &str,
gas_limit: u128,
) -> Result<String, String> {
let call = crate::tempo_tx::TempoCall {
to: parse_eth_address(to_hex)?,
value_wei: 0,
input,
};
submit_tempo_sponsored(sender, fee_payer, vec![call], fee_token, gas_limit).await
}
pub(crate) async fn sponsored_diamond_call(
sender: &SigningKey,
fee_payer: &SigningKey,
input: Vec<u8>,
fee_token: &str,
gas_limit: u128,
) -> Result<String, String> {
sponsored_call_to(sender, fee_payer, REGISTRY_ADDRESS, input, fee_token, gas_limit).await
}
pub(crate) async fn sponsored_escrow_diamond_call(
sender: &SigningKey,
fee_payer: &SigningKey,
amount_wei: u128,
input: Vec<u8>,
fee_token: &str,
gas_limit: u128,
) -> Result<String, String> {
let diamond_addr = parse_eth_address(REGISTRY_ADDRESS)?;
let token_addr = parse_eth_address(LOCALHARNESS_TOKEN_ADDRESS)?;
let approve_call = crate::tempo_tx::TempoCall {
to: token_addr,
value_wei: 0,
input: encode_approve(&diamond_addr, amount_wei),
};
let call = crate::tempo_tx::TempoCall {
to: diamond_addr,
value_wei: 0,
input,
};
submit_tempo_sponsored(sender, fee_payer, vec![approve_call, call], fee_token, gas_limit).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_u256_as_u128_truncation_and_empty() {
assert_eq!(decode_u256_as_u128("0x").unwrap(), 0);
assert_eq!(decode_u256_as_u128(&format!("0x{}", word_usize(42))).unwrap(), 42);
let max = format!("0x{}{}", "0".repeat(32), "f".repeat(32));
assert_eq!(decode_u256_as_u128(&max).unwrap(), u128::MAX);
}
}