hiero_sdk/token/
token_associate_transaction.rs1use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::token_service_client::TokenServiceClient;
5use tonic::transport::Channel;
6
7use crate::ledger_id::RefLedgerId;
8use crate::protobuf::FromProtobuf;
9use crate::transaction::{
10 AnyTransactionData,
11 ChunkInfo,
12 ToSchedulableTransactionDataProtobuf,
13 ToTransactionDataProtobuf,
14 TransactionData,
15 TransactionExecute,
16};
17use crate::{
18 AccountId,
19 BoxGrpcFuture,
20 Error,
21 ToProtobuf,
22 TokenId,
23 Transaction,
24 ValidateChecksums,
25};
26
27pub type TokenAssociateTransaction = Transaction<TokenAssociateTransactionData>;
41
42#[derive(Debug, Clone, Default)]
43pub struct TokenAssociateTransactionData {
44 account_id: Option<AccountId>,
46
47 token_ids: Vec<TokenId>,
49}
50
51impl TokenAssociateTransaction {
52 #[must_use]
54 pub fn get_account_id(&self) -> Option<AccountId> {
55 self.data().account_id
56 }
57
58 pub fn account_id(&mut self, account_id: AccountId) -> &mut Self {
60 self.data_mut().account_id = Some(account_id);
61 self
62 }
63
64 #[must_use]
66 pub fn get_token_ids(&self) -> &[TokenId] {
67 &self.data().token_ids
68 }
69
70 pub fn token_ids(&mut self, token_ids: impl IntoIterator<Item = TokenId>) -> &mut Self {
72 self.data_mut().token_ids = token_ids.into_iter().collect();
73 self
74 }
75}
76
77impl TransactionData for TokenAssociateTransactionData {}
78
79impl TransactionExecute for TokenAssociateTransactionData {
80 fn execute(
81 &self,
82 channel: Channel,
83 request: services::Transaction,
84 ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
85 Box::pin(async { TokenServiceClient::new(channel).associate_tokens(request).await })
86 }
87}
88
89impl ValidateChecksums for TokenAssociateTransactionData {
90 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
91 self.account_id.validate_checksums(ledger_id)?;
92 for token_id in &self.token_ids {
93 token_id.validate_checksums(ledger_id)?;
94 }
95 Ok(())
96 }
97}
98
99impl ToTransactionDataProtobuf for TokenAssociateTransactionData {
100 fn to_transaction_data_protobuf(
101 &self,
102 chunk_info: &ChunkInfo,
103 ) -> services::transaction_body::Data {
104 let _ = chunk_info.assert_single_transaction();
105
106 services::transaction_body::Data::TokenAssociate(self.to_protobuf())
107 }
108}
109
110impl ToSchedulableTransactionDataProtobuf for TokenAssociateTransactionData {
111 fn to_schedulable_transaction_data_protobuf(
112 &self,
113 ) -> services::schedulable_transaction_body::Data {
114 services::schedulable_transaction_body::Data::TokenAssociate(self.to_protobuf())
115 }
116}
117
118impl From<TokenAssociateTransactionData> for AnyTransactionData {
119 fn from(transaction: TokenAssociateTransactionData) -> Self {
120 Self::TokenAssociate(transaction)
121 }
122}
123
124impl FromProtobuf<services::TokenAssociateTransactionBody> for TokenAssociateTransactionData {
125 fn from_protobuf(pb: services::TokenAssociateTransactionBody) -> crate::Result<Self> {
126 Ok(Self {
127 account_id: Option::from_protobuf(pb.account)?,
128 token_ids: Vec::from_protobuf(pb.tokens)?,
129 })
130 }
131}
132
133impl ToProtobuf for TokenAssociateTransactionData {
134 type Protobuf = services::TokenAssociateTransactionBody;
135
136 fn to_protobuf(&self) -> Self::Protobuf {
137 let account = self.account_id.to_protobuf();
138 let tokens = self.token_ids.to_protobuf();
139
140 services::TokenAssociateTransactionBody { account, tokens }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use expect_test::expect_file;
147 use hiero_sdk_proto::services;
148
149 use crate::protobuf::{
150 FromProtobuf,
151 ToProtobuf,
152 };
153 use crate::token::TokenAssociateTransactionData;
154 use crate::transaction::test_helpers::{
155 check_body,
156 transaction_body,
157 TEST_ACCOUNT_ID,
158 TEST_TOKEN_ID,
159 };
160 use crate::{
161 AnyTransaction,
162 TokenAssociateTransaction,
163 };
164
165 fn make_transaction() -> TokenAssociateTransaction {
166 let mut tx = TokenAssociateTransaction::new_for_tests();
167
168 tx.account_id(TEST_ACCOUNT_ID).token_ids([TEST_TOKEN_ID]).freeze().unwrap();
169
170 tx
171 }
172
173 #[test]
174 fn serialize() {
175 let tx = make_transaction();
176
177 let tx = transaction_body(tx);
178
179 let tx = check_body(tx);
180
181 expect_file!["./snapshots/token_associate_transaction/serialize.txt"].assert_debug_eq(&tx);
182 }
183
184 #[test]
185 fn to_from_bytes() {
186 let tx = make_transaction();
187
188 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
189
190 let tx = transaction_body(tx);
191 let tx2 = transaction_body(tx2);
192
193 assert_eq!(tx, tx2)
194 }
195
196 #[test]
197 fn from_proto_body() {
198 let tx = services::TokenAssociateTransactionBody {
199 account: Some(TEST_ACCOUNT_ID.to_protobuf()),
200 tokens: Vec::from([TEST_TOKEN_ID.to_protobuf()]),
201 };
202
203 let data = TokenAssociateTransactionData::from_protobuf(tx).unwrap();
204
205 assert_eq!(data.account_id, Some(TEST_ACCOUNT_ID));
206 assert_eq!(data.token_ids, &[TEST_TOKEN_ID]);
207 }
208
209 #[test]
210 fn get_set_token_ids() {
211 let token_ids = [TEST_TOKEN_ID];
212 let mut tx = TokenAssociateTransaction::new();
213 tx.token_ids(token_ids.to_owned());
214
215 assert_eq!(tx.get_token_ids(), &token_ids[..]);
216 }
217
218 #[test]
219 #[should_panic]
220 fn get_set_token_ids_frozen_panic() {
221 make_transaction().token_ids([TEST_TOKEN_ID]);
222 }
223
224 #[test]
225 fn get_set_account_id() {
226 let mut tx = TokenAssociateTransaction::new();
227 tx.account_id(TEST_ACCOUNT_ID);
228
229 assert_eq!(tx.get_account_id(), Some(TEST_ACCOUNT_ID));
230 }
231
232 #[test]
233 #[should_panic]
234 fn get_set_account_id_frozen_panic() {
235 make_transaction().account_id(TEST_ACCOUNT_ID);
236 }
237}