Skip to main content

hypercall_signer/
typed_data.rs

1use alloy::{dyn_abi::TypedData, primitives::Address};
2use hypercall_types::{
3    directives::{self, ActionKey, DomainKind, TypeFieldSpec, HYPERCALL_DOMAIN_VERSION},
4    WalletAddress,
5};
6use serde_json::{json, Map, Value};
7
8use crate::RsmSignerError;
9
10pub fn build_typed_data(
11    action_key: ActionKey,
12    account: &WalletAddress,
13    nonce: u64,
14    action_json: Value,
15    chain_id: u64,
16) -> Result<TypedData, RsmSignerError> {
17    let spec = action_key.spec();
18    if spec.domain != DomainKind::Rsm {
19        return Err(RsmSignerError::UnsupportedAction(format!(
20            "action key {} is not an RSM signing action",
21            action_key.as_str()
22        )));
23    }
24    let typed_data = spec.typed_data.ok_or_else(|| {
25        RsmSignerError::UnsupportedAction(format!(
26            "typed-data metadata missing for action key {}",
27            action_key.as_str()
28        ))
29    })?;
30
31    let mut types = Map::new();
32    types.insert(
33        "EIP712Domain".to_string(),
34        json!([
35            {"name": "name", "type": "string"},
36            {"name": "version", "type": "string"},
37            {"name": "chainId", "type": "uint256"},
38            {"name": "verifyingContract", "type": "address"}
39        ]),
40    );
41    types.insert(
42        typed_data.inner_type.to_string(),
43        type_fields_json(typed_data.inner_fields),
44    );
45    types.insert(
46        spec.primary_type.to_string(),
47        json!([
48            {"name": "account", "type": "address"},
49            {"name": "nonce", "type": "uint64"},
50            {"name": "action", "type": typed_data.inner_type}
51        ]),
52    );
53
54    serde_json::from_value(json!({
55        "types": types,
56        "primaryType": spec.primary_type,
57        "domain": {
58            "name": directives::HYPERCALL_RSM_DOMAIN_NAME,
59            "version": HYPERCALL_DOMAIN_VERSION,
60            "chainId": chain_id,
61            "verifyingContract": Address::ZERO.to_string(),
62        },
63        "message": {
64            "account": account.to_string(),
65            "nonce": nonce.to_string(),
66            "action": action_json,
67        }
68    }))
69    .map_err(|error| {
70        RsmSignerError::InvalidAction(format!(
71            "failed to build typed-data payload for {}: {}",
72            action_key.as_str(),
73            error
74        ))
75    })
76}
77
78fn type_fields_json(fields: &'static [TypeFieldSpec]) -> Value {
79    Value::Array(
80        fields
81            .iter()
82            .map(|field| json!({ "name": field.name, "type": field.kind }))
83            .collect(),
84    )
85}