Skip to main content

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