use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::primitives::{Address, Hash, Timestamp};
pub const TENZRO_MAINNET_CHAIN_ID: u32 = 0x10ED20;
pub const TENZRO_TESTNET_CHAIN_ID: u32 = 0x10ED21;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProofRoute {
LayerZero,
Wormhole,
DeBridge,
Hyperlane,
}
impl ProofRoute {
pub fn as_str(&self) -> &'static str {
match self {
ProofRoute::LayerZero => "layerzero",
ProofRoute::Wormhole => "wormhole",
ProofRoute::DeBridge => "debridge",
ProofRoute::Hyperlane => "hyperlane",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenAmount {
pub token: Vec<u8>,
pub amount: [u8; 32],
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Output {
pub token: Vec<u8>,
pub amount: [u8; 32],
pub recipient: [u8; 32],
pub chain_id: u32,
}
pub type TargetOutput = Output;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FillInstruction {
pub destination_chain_id: u32,
pub destination_settler: [u8; 32],
pub origin_data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TenzroOrderData {
pub inputs: Vec<TokenAmount>,
pub outputs: Vec<TargetOutput>,
pub dest_chain_id: u32,
pub dest_recipient: [u8; 32],
pub fill_deadline: u32,
pub proof_route: ProofRoute,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bridge_fee_hint: Option<BridgeFeeHint>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BridgeFeeHint {
pub quote_id_hex: String,
pub tnzo_amount_wei: String,
pub valid_until_ms: u64,
pub preferred_adapter: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CrossChainOrder {
pub settlement_contract: Address,
pub swapper: Address,
pub nonce: u64,
pub origin_chain_id: u32,
pub fill_deadline: u32,
pub order_data_type: Hash,
pub order_data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GaslessCrossChainOrder {
pub settlement_contract: Address,
pub swapper: Address,
pub nonce: u64,
pub origin_chain_id: u32,
pub fill_deadline: u32,
pub order_data_type: Hash,
pub order_data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResolvedCrossChainOrder {
pub settlement_contract: Address,
pub swapper: Address,
pub nonce: u64,
pub origin_chain_id: u32,
pub fill_deadline: u32,
pub max_spent: Vec<Output>,
pub min_received: Vec<Output>,
pub fill_instructions: Vec<FillInstruction>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrderState {
Open,
AwaitingProof,
Settled,
Refunded,
ForceRefundEligible,
}
impl OrderState {
pub fn as_str(&self) -> &'static str {
match self {
OrderState::Open => "open",
OrderState::AwaitingProof => "awaiting_proof",
OrderState::Settled => "settled",
OrderState::Refunded => "refunded",
OrderState::ForceRefundEligible => "force_refund_eligible",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tenzro7683Order {
pub order_id: Hash,
pub state: OrderState,
pub resolved: ResolvedCrossChainOrder,
pub escrow_id: Hash,
pub opened_at: Timestamp,
pub fill_deadline: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filler: Option<Address>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fill_proof_hash: Option<Hash>,
pub proof_route: ProofRoute,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FillRecord {
pub order_id: Hash,
pub origin_chain_id: u32,
pub origin_settler: Address,
pub filler: Address,
pub recipient: [u8; 32],
pub outputs: Vec<Output>,
pub fill_tx_hash: Hash,
pub filled_at: Timestamp,
pub proof_route: ProofRoute,
}
pub const ORDER_KEY_PREFIX: &[u8] = b"7683_origin:";
pub const FILL_KEY_PREFIX: &[u8] = b"7683_dest:";
pub fn fill_storage_key(order_id: &Hash) -> Vec<u8> {
let mut key = Vec::with_capacity(FILL_KEY_PREFIX.len() + 32);
key.extend_from_slice(FILL_KEY_PREFIX);
key.extend_from_slice(order_id.as_bytes());
key
}
pub fn order_storage_key(order_id: &Hash) -> Vec<u8> {
let mut key = Vec::with_capacity(ORDER_KEY_PREFIX.len() + 32);
key.extend_from_slice(ORDER_KEY_PREFIX);
key.extend_from_slice(order_id.as_bytes());
key
}
pub fn compute_order_id(order: &CrossChainOrder) -> Hash {
let mut hasher = Sha256::new();
hasher.update(b"tenzro/7683/order");
hasher.update(order.swapper.as_bytes());
hasher.update(order.nonce.to_le_bytes());
hasher.update(order.origin_chain_id.to_le_bytes());
hasher.update(order.fill_deadline.to_le_bytes());
hasher.update(order.order_data_type.as_bytes());
hasher.update(&order.order_data);
let result = hasher.finalize();
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&result);
Hash::new(bytes)
}
pub fn u128_to_uint256_be(value: u128) -> [u8; 32] {
let mut bytes = [0u8; 32];
bytes[16..].copy_from_slice(&value.to_be_bytes());
bytes
}
pub fn uint256_be_to_u128(bytes: &[u8; 32]) -> Option<u128> {
if bytes[..16].iter().any(|&b| b != 0) {
return None;
}
let mut low = [0u8; 16];
low.copy_from_slice(&bytes[16..]);
Some(u128::from_be_bytes(low))
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_order() -> CrossChainOrder {
CrossChainOrder {
settlement_contract: Address::new([1u8; 32]),
swapper: Address::new([2u8; 32]),
nonce: 42,
origin_chain_id: TENZRO_TESTNET_CHAIN_ID,
fill_deadline: 1_700_001_000,
order_data_type: Hash::new([3u8; 32]),
order_data: b"opaque-payload".to_vec(),
}
}
#[test]
fn chain_ids_match_spec() {
assert_eq!(TENZRO_MAINNET_CHAIN_ID, 0x10ED20);
assert_eq!(TENZRO_TESTNET_CHAIN_ID, 0x10ED21);
}
#[test]
fn compute_order_id_is_deterministic() {
let order = sample_order();
assert_eq!(compute_order_id(&order), compute_order_id(&order));
}
#[test]
fn compute_order_id_changes_on_field_change() {
let order = sample_order();
let baseline = compute_order_id(&order);
let mut nonce_changed = order.clone();
nonce_changed.nonce = 43;
assert_ne!(compute_order_id(&nonce_changed), baseline);
let mut deadline_changed = order.clone();
deadline_changed.fill_deadline = 1_700_001_001;
assert_ne!(compute_order_id(&deadline_changed), baseline);
let mut data_changed = order.clone();
data_changed.order_data = b"different-payload".to_vec();
assert_ne!(compute_order_id(&data_changed), baseline);
}
#[test]
fn proof_route_str() {
assert_eq!(ProofRoute::LayerZero.as_str(), "layerzero");
assert_eq!(ProofRoute::Wormhole.as_str(), "wormhole");
assert_eq!(ProofRoute::DeBridge.as_str(), "debridge");
assert_eq!(ProofRoute::Hyperlane.as_str(), "hyperlane");
}
#[test]
fn order_state_str() {
assert_eq!(OrderState::Open.as_str(), "open");
assert_eq!(OrderState::AwaitingProof.as_str(), "awaiting_proof");
assert_eq!(OrderState::Settled.as_str(), "settled");
assert_eq!(OrderState::Refunded.as_str(), "refunded");
assert_eq!(
OrderState::ForceRefundEligible.as_str(),
"force_refund_eligible"
);
}
#[test]
fn uint256_round_trip() {
for v in [0u128, 1, 12345, u128::MAX] {
let bytes = u128_to_uint256_be(v);
assert_eq!(uint256_be_to_u128(&bytes), Some(v));
}
}
#[test]
fn uint256_high_bits_rejected() {
let mut bytes = [0u8; 32];
bytes[0] = 1;
assert_eq!(uint256_be_to_u128(&bytes), None);
}
#[test]
fn key_prefixes_are_distinct() {
assert_ne!(ORDER_KEY_PREFIX, FILL_KEY_PREFIX);
}
#[test]
fn order_envelope_round_trips_through_serde() {
let resolved = ResolvedCrossChainOrder {
settlement_contract: Address::new([1u8; 32]),
swapper: Address::new([2u8; 32]),
nonce: 7,
origin_chain_id: TENZRO_MAINNET_CHAIN_ID,
fill_deadline: 1_700_002_000,
max_spent: vec![Output {
token: vec![0xaa; 20],
amount: u128_to_uint256_be(1_000_000),
recipient: [0xbb; 32],
chain_id: 8453, }],
min_received: vec![],
fill_instructions: vec![FillInstruction {
destination_chain_id: 8453,
destination_settler: [0xcc; 32],
origin_data: vec![0xdd, 0xee],
}],
};
let envelope = Tenzro7683Order {
order_id: Hash::new([9u8; 32]),
state: OrderState::Open,
resolved,
escrow_id: Hash::new([0xee; 32]),
opened_at: Timestamp::new(1_700_000_000),
fill_deadline: 1_700_002_000,
filler: None,
fill_proof_hash: None,
proof_route: ProofRoute::LayerZero,
};
let bytes = serde_json::to_vec(&envelope).unwrap();
let decoded: Tenzro7683Order = serde_json::from_slice(&bytes).unwrap();
assert_eq!(decoded, envelope);
}
#[test]
fn order_data_bridge_fee_hint_round_trips() {
let order = TenzroOrderData {
inputs: vec![],
outputs: vec![],
dest_chain_id: 8453,
dest_recipient: [0u8; 32],
fill_deadline: 1_700_002_000,
proof_route: ProofRoute::LayerZero,
bridge_fee_hint: Some(BridgeFeeHint {
quote_id_hex: "0xabcd".to_string(),
tnzo_amount_wei: "5100000".to_string(),
valid_until_ms: 1_700_001_000,
preferred_adapter: "layerzero".to_string(),
}),
};
let bytes = serde_json::to_vec(&order).unwrap();
let decoded: TenzroOrderData = serde_json::from_slice(&bytes).unwrap();
assert_eq!(decoded, order);
assert!(decoded.bridge_fee_hint.is_some());
}
#[test]
fn order_data_without_fee_hint_is_backward_compat() {
let bytes = br#"{
"inputs": [],
"outputs": [],
"dest_chain_id": 8453,
"dest_recipient": [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],
"fill_deadline": 1700002000,
"proof_route": "LayerZero"
}"#;
let decoded: TenzroOrderData = serde_json::from_slice(bytes).unwrap();
assert!(decoded.bridge_fee_hint.is_none());
}
}