ton_contracts/wallet/
mod.rs

1#![cfg(feature = "wallet")]
2//! TON [Wallet](https://docs.ton.org/participate/wallets/contracts)
3
4pub mod mnemonic;
5mod signer;
6pub mod v4r2;
7pub mod v5r1;
8mod version;
9
10pub use self::{signer::*, version::*};
11
12use core::marker::PhantomData;
13use std::sync::Arc;
14
15use chrono::{DateTime, Utc};
16use num_bigint::BigUint;
17use tlb_ton::{
18    Cell, MsgAddress,
19    action::SendMsgAction,
20    message::{CommonMsgInfo, ExternalInMsgInfo, Message},
21    ser::{CellBuilderError, CellSerializeExt},
22    state_init::StateInit,
23};
24
25/// Generic wallet for signing messages
26///
27/// ```rust
28/// # use ton_contracts::wallet::{
29/// #   mnemonic::Mnemonic,
30/// #   KeyPair,
31/// #   Wallet,
32/// #   v4r2::V4R2,
33/// # };
34/// let mnemonic: Mnemonic = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell"
35///     .parse()
36///     .unwrap();
37/// let keypair = mnemonic.generate_keypair(None).unwrap();
38/// let wallet = Wallet::<V4R2>::derive_default(keypair).unwrap();
39///
40/// assert_eq!(
41///     wallet.address(),
42///     "UQA7RMTgzvcyxNNLmK2HdklOvFE8_KNMa-btKZ0dPU1UsqfC".parse().unwrap(),
43/// )
44/// ```
45pub struct Wallet<V> {
46    address: MsgAddress,
47    wallet_id: u32,
48    keypair: KeyPair,
49    _phantom: PhantomData<V>,
50}
51
52impl<V> Wallet<V>
53where
54    V: WalletVersion,
55{
56    #[inline]
57    pub const fn new(address: MsgAddress, keypair: KeyPair, wallet_id: u32) -> Self {
58        Self {
59            address,
60            wallet_id,
61            keypair,
62            _phantom: PhantomData,
63        }
64    }
65
66    /// Derive wallet from its workchain, keypair and id
67    #[inline]
68    pub fn derive(
69        workchain_id: i32,
70        keypair: KeyPair,
71        wallet_id: u32,
72    ) -> Result<Self, CellBuilderError> {
73        Ok(Self::new(
74            MsgAddress::derive(workchain_id, V::state_init(wallet_id, keypair.public_key))?,
75            keypair,
76            wallet_id,
77        ))
78    }
79
80    /// Shortcut for [`Wallet::derive()`] with default workchain and wallet id
81    #[inline]
82    pub fn derive_default(keypair: KeyPair) -> Result<Self, CellBuilderError> {
83        Self::derive(0, keypair, V::DEFAULT_WALLET_ID)
84    }
85
86    /// Address of the wallet
87    #[inline]
88    pub const fn address(&self) -> MsgAddress {
89        self.address
90    }
91
92    /// ID of the wallet
93    #[inline]
94    pub const fn wallet_id(&self) -> u32 {
95        self.wallet_id
96    }
97
98    #[inline]
99    pub const fn public_key(&self) -> &[u8; PUBLIC_KEY_LENGTH] {
100        &self.keypair.public_key
101    }
102
103    /// Create external body for this wallet.
104    #[inline]
105    pub fn create_sign_body(
106        &self,
107        expire_at: DateTime<Utc>,
108        seqno: u32,
109        msgs: impl IntoIterator<Item = SendMsgAction>,
110    ) -> V::SignBody {
111        V::create_sign_body(self.wallet_id, expire_at, seqno, msgs)
112    }
113
114    #[inline]
115    pub fn sign(&self, msg: impl AsRef<[u8]>) -> anyhow::Result<[u8; 64]> {
116        self.keypair.sign(msg)
117    }
118
119    /// Shortcut to [create](Wallet::create_sign_body),
120    /// [sign](Wallet::sign_body) and [wrap](Wallet::wrap_external_msg) external
121    /// message ready for sending to TON blockchain.
122    ///
123    /// ```rust
124    /// # use hex_literal::hex;
125    /// # use tlb_ton::{
126    /// #   Cell,
127    /// #   message::Message,
128    /// #   currency::ONE_TON,
129    /// #   action::SendMsgAction,
130    /// # };
131    /// # use ton_contracts::wallet::{
132    /// #   mnemonic::Mnemonic,
133    /// #   v5r1::V5R1,
134    /// #   KeyPair,
135    /// #   Wallet,
136    /// # };
137    /// #
138    /// # let mnemonic: Mnemonic = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell"
139    /// #     .parse()
140    /// #     .unwrap();
141    /// # let keypair = mnemonic.generate_keypair(None).unwrap();
142    /// # let wallet = Wallet::<V5R1>::derive_default(keypair).unwrap();
143    /// let msg = wallet.create_external_message(
144    ///     Default::default(), // DateTime::UNIX_EPOCH means no deadline
145    ///     0, // seqno
146    ///     [SendMsgAction {
147    ///         mode: 3,
148    ///         message: Message::<()>::transfer(
149    ///             "EQAWezezpqKTbO6xjCussXDdIeJ7XxTcErjA6uD3T3r7AwTk"
150    ///                 .parse()
151    ///                 .unwrap(),
152    ///             ONE_TON.clone(),
153    ///             false,
154    ///         )
155    ///             .normalize()
156    ///             .unwrap(),
157    ///     }],
158    ///     false, // do not deploy wallet
159    ///     ).unwrap();
160    /// # let mut b = Cell::builder();
161    /// # b.store(msg).unwrap();
162    /// ```
163    #[inline]
164    pub fn create_external_message(
165        &self,
166        expire_at: DateTime<Utc>,
167        seqno: u32,
168        msgs: impl IntoIterator<Item = SendMsgAction>,
169        state_init: bool,
170    ) -> anyhow::Result<Message<V::ExternalMsgBody, Arc<Cell>, V::Data>> {
171        let sign_body = self.create_sign_body(expire_at, seqno, msgs);
172        let signature = self.sign_body(&sign_body)?;
173        let body = V::wrap_signed_external(sign_body, signature);
174        let wrapped = self.wrap_external_msg(body, state_init);
175        Ok(wrapped)
176    }
177
178    /// Sign body from [`.create_sign_body()`](Wallet::create_sign_body)
179    /// using this wallet's private key
180    #[inline]
181    pub fn sign_body(&self, msg: &V::SignBody) -> anyhow::Result<[u8; 64]> {
182        self.sign(msg.to_cell()?.hash())
183    }
184
185    /// Wrap signed body from [`.sign_body()`](Wallet::sign_body) in a message
186    /// ready for sending to TON blockchain.
187    #[inline]
188    pub fn wrap_external_msg(
189        &self,
190        body: V::ExternalMsgBody,
191        state_init: bool,
192    ) -> Message<V::ExternalMsgBody, Arc<Cell>, V::Data> {
193        Message {
194            info: CommonMsgInfo::ExternalIn(ExternalInMsgInfo {
195                src: MsgAddress::NULL,
196                dst: self.address(),
197                import_fee: BigUint::ZERO,
198            }),
199            init: state_init.then(|| self.state_init()),
200            body,
201        }
202    }
203
204    #[inline]
205    pub fn state_init(&self) -> StateInit<Arc<Cell>, V::Data> {
206        V::state_init(self.wallet_id(), *self.public_key())
207    }
208}