derive-rs 0.1.16

Rust SDK for Derive.xyz
use crate::types::Environment;
use alloy::{
    hex::encode,
    primitives::{Address, B256, U256, keccak256},
};
use alloy_sol_types::{SolValue, sol};
use anyhow::Result;
use chrono::{Duration, Utc};

pub trait ModuleData {
    fn get_action_data(&self) -> Vec<u8>;
}

pub enum ModuleType {
    Trade,
    SpotTransfer,
    Withdraw,
    RfqPositionTransfer,
    SetSessionKey,
    // ExternalTransfer,
    // WhitelistedRecipient,
    Vault,
}

fn get_trade_module(module: ModuleType) -> &'static str {
    match module {
        ModuleType::Trade => "0xB8D20c2B7a1Ad2EE33Bc50eF10876eD3035b5e7b",
        ModuleType::SetSessionKey => "0xe330CF64ff6EbF41699aad344Cb21d78db1D2bb6",
        ModuleType::Withdraw => "0x9d0E8f5b25384C7310CB8C6aE32C8fbeb645d083",
        ModuleType::SpotTransfer => "0x01259207A40925b794C8ac320456F7F6c8FE2636",
        ModuleType::RfqPositionTransfer => "0x9371352CCef6f5b36EfDFE90942fFE622Ab77F1D",
        ModuleType::Vault => "0x2885c174ebf5524aED9c721d60c12b1537685186",
    }
}

fn get_action_typehash(env: &Environment) -> &'static str {
    match env {
        Environment::Mainnet => {
            "0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17"
        }
        Environment::Testnet => {
            "0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17"
        }
    }
}

pub fn get_domain_separator(env: &Environment) -> &'static str {
    match env {
        Environment::Mainnet => {
            "0xd96e5f90797da7ec8dc4e276260c7f3f87fedf68775fbe1ef116e996fc60441b"
        }
        Environment::Testnet => {
            "0x24d674cd5f2b9d564691c51e9d88f649b99246a2244dd74ce27b96578d773e85"
        }
    }
}

use anyhow::Context;
use tracing::debug;
sol! {
    #![sol(all_derives)]

    struct ActionData {
        bytes32 action_typehash;
        uint256 subaccount_id;
        uint256 nonce;
        address module;
        bytes32 data;
        uint256 expiry;
        address owner;
        address signer;
    }
}

impl ActionData {
    fn get_nonce_and_expiry() -> Result<(U256, U256)> {
        let now = Utc::now();

        let nonce = now
            .timestamp_nanos_opt()
            .context("current timestamp cannot be represented in nanoseconds")?;
        let signature_expiry_sec = (now + Duration::seconds(3_500)).timestamp();
        Ok((
            U256::from(u64::try_from(nonce)?),
            U256::from(u64::try_from(signature_expiry_sec)?),
        ))
    }

    pub fn new<T>(
        module_data: T,
        subaccount_id: u64,
        signer_address: Address,
        derive_smart_contract_address: &Address,
        env: &Environment,
        module_type: ModuleType,
    ) -> Result<Self>
    where
        T: SolValue + ModuleData,
    {
        let encoded_data = module_data.get_action_data();
        let hex = format!("0x{}", encode(&encoded_data));
        debug!("Encoded data: {}", hex);
        let (nonce, expiry) = Self::get_nonce_and_expiry()?;
        let module = get_trade_module(module_type).parse::<Address>()?;
        let data = keccak256(&encoded_data);
        let action_typehash = get_action_typehash(env).parse::<B256>()?;
        Ok(Self {
            action_typehash,
            subaccount_id: U256::from(subaccount_id),
            nonce,
            module,
            data,
            expiry,
            owner: *derive_smart_contract_address,
            signer: signer_address,
        })
    }

    pub fn action_hash(&self) -> B256 {
        keccak256(self.abi_encode())
    }

    pub fn hash(&self, env: &Environment) -> B256 {
        let domain_separator = get_domain_separator(env)
            .parse::<B256>()
            .expect("invalid DOMAIN_SEPARATOR");
        let action_hash = self.action_hash();
        let mut encoded = [0u8; 66];
        encoded[0] = 0x19;
        encoded[1] = 0x01;
        encoded[2..34].copy_from_slice(domain_separator.as_slice());
        encoded[34..66].copy_from_slice(action_hash.as_slice());

        keccak256(encoded)
    }
}