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}