hypercall_signer/
typed_data.rs1use 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}