defuse_ton_connect/schema/
mod.rs

1use core::str;
2use std::borrow::Cow;
3use std::fmt::Debug;
4
5use near_sdk::near;
6use tlb_ton::{MsgAddress, StringError};
7
8#[cfg(feature = "binary")]
9mod binary;
10#[cfg(feature = "cell")]
11mod cell;
12#[cfg(feature = "text")]
13mod text;
14
15pub struct TonConnectPayloadContext<'a> {
16    pub address: MsgAddress,
17    pub domain: Cow<'a, str>,
18    pub timestamp: u64,
19}
20
21impl TonConnectPayloadContext<'_> {
22    // See https://docs.tonconsole.com/academy/sign-data#how-the-signature-is-built
23    #[cfg(any(feature = "binary", feature = "text"))]
24    pub fn create_payload_hash(
25        &self,
26        payload_prefix: &[u8],
27        payload: &[u8],
28    ) -> Result<near_sdk::CryptoHash, StringError> {
29        let domain_len = u32::try_from(self.domain.len())
30            .map_err(|_| tlb_ton::Error::custom("domain: overflow"))?;
31        let payload_len = u32::try_from(payload.len())
32            .map_err(|_| tlb_ton::Error::custom("payload: overflow"))?;
33
34        let bytes = [
35            [0xff, 0xff].as_slice(),
36            b"ton-connect/sign-data/",
37            &self.address.workchain_id.to_be_bytes(),
38            self.address.address.as_ref(),
39            &domain_len.to_be_bytes(),
40            self.domain.as_bytes(),
41            &self.timestamp.to_be_bytes(),
42            payload_prefix,
43            &payload_len.to_be_bytes(),
44            payload,
45        ]
46        .concat();
47
48        Ok(near_sdk::env::sha256_array(&bytes))
49    }
50}
51
52pub trait PayloadSchema {
53    fn hash_with_context(
54        &self,
55        context: TonConnectPayloadContext,
56    ) -> Result<near_sdk::CryptoHash, StringError>;
57}
58
59/// See <https://docs.tonconsole.com/academy/sign-data#choosing-the-right-format>
60#[cfg_attr(test, derive(arbitrary::Arbitrary))]
61#[near(serializers = [json])]
62#[serde(tag = "type", rename_all = "snake_case")]
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum TonConnectPayloadSchema {
65    #[cfg(feature = "text")]
66    Text(text::TextPayload),
67    #[cfg(feature = "binary")]
68    Binary(binary::BinaryPayload),
69    #[cfg(feature = "cell")]
70    Cell(cell::CellPayload),
71}
72
73impl TonConnectPayloadSchema {
74    #[cfg(feature = "text")]
75    pub fn text(txt: impl Into<String>) -> Self {
76        Self::Text(text::TextPayload { text: txt.into() })
77    }
78
79    #[cfg(feature = "binary")]
80    pub fn binary(bytes: impl Into<Vec<u8>>) -> Self {
81        Self::Binary(binary::BinaryPayload {
82            bytes: bytes.into(),
83        })
84    }
85
86    #[cfg(feature = "cell")]
87    pub const fn cell(schema_crc: u32, cell: tlb_ton::Cell) -> Self {
88        Self::Cell(cell::CellPayload { schema_crc, cell })
89    }
90}
91
92impl PayloadSchema for TonConnectPayloadSchema {
93    fn hash_with_context(
94        &self,
95        context: TonConnectPayloadContext,
96    ) -> Result<near_sdk::CryptoHash, StringError> {
97        match self {
98            #[cfg(feature = "text")]
99            Self::Text(payload) => payload.hash_with_context(context),
100            #[cfg(feature = "binary")]
101            Self::Binary(payload) => payload.hash_with_context(context),
102            #[cfg(feature = "cell")]
103            Self::Cell(payload) => payload.hash_with_context(context),
104        }
105    }
106}