defuse_ton_connect/schema/
mod.rs

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