Skip to main content

defuse_core/payload/
ton_connect.rs

1use defuse_crypto::ed25519::{Ed25519PublicKey, Ed25519Signature};
2use defuse_ton_connect::TonConnect;
3pub use defuse_ton_connect::{TonConnectPayload, TonConnectPayloadSchema};
4use near_sdk::CryptoHash;
5use serde::{
6    Deserialize, Serialize,
7    de::{DeserializeOwned, Error},
8};
9
10use crate::payload::{Payload, SignedPayload};
11
12use super::{DefusePayload, ExtractDefusePayload};
13
14#[cfg_attr(feature = "abi", derive(::schemars::JsonSchema))]
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct SignedTonConnectPayload {
17    #[serde(flatten)]
18    pub payload: TonConnectPayload,
19
20    pub public_key: Ed25519PublicKey,
21    pub signature: Ed25519Signature,
22}
23
24impl Payload for SignedTonConnectPayload {
25    #[inline]
26    fn hash(&self) -> CryptoHash {
27        self.payload.try_prehash().expect("ton-connect hash")
28    }
29}
30
31impl SignedPayload for SignedTonConnectPayload {
32    type PublicKey = Ed25519PublicKey;
33
34    #[inline]
35    fn verify(&self) -> Option<Self::PublicKey> {
36        TonConnect::verify(
37            &self.public_key.try_into().ok()?,
38            &self.payload,
39            &self.signature.into(),
40        )
41        .then_some(&self.public_key)
42        .copied()
43    }
44}
45
46impl<T> ExtractDefusePayload<T> for SignedTonConnectPayload
47where
48    T: DeserializeOwned,
49{
50    type Error = serde_json::Error;
51
52    #[inline]
53    fn extract_defuse_payload(self) -> Result<DefusePayload<T>, Self::Error> {
54        self.payload.extract_defuse_payload()
55    }
56}
57
58impl<T> ExtractDefusePayload<T> for TonConnectPayload
59where
60    T: DeserializeOwned,
61{
62    type Error = serde_json::Error;
63
64    fn extract_defuse_payload(self) -> Result<DefusePayload<T>, Self::Error> {
65        let TonConnectPayloadSchema::Text { text } = self.payload else {
66            return Err(Error::custom("only text payload supported"));
67        };
68
69        let p: DefusePayload<T> = serde_json::from_str(&text)?;
70
71        // TON Connect [specification](https://docs.tonconsole.com/academy/sign-data#in-a-smart-contract-on-chain)
72        // requires to check that "timestamp is recent". We don't have fixed TTL
73        // for off-chain signatures but rather check if `deadline` is not expired.
74        //
75        // At first, we were asserting `(timestamp <= now())`, but that  was causing
76        // `simulate_intents()` to fail, since sometimes signed intent is simulated
77        // right after signing.
78        //
79        // So, we ended up to assert at least following:
80        if p.deadline < self.timestamp {
81            return Err(Error::custom("deadline < timestamp"));
82        }
83
84        Ok(p)
85    }
86}