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}