1use crate::{
9 contract_client::*,
10 types::{transactions, RejectReason},
11 v2::{self, IntoBlockIdentifier},
12};
13pub use concordium_base::{cis2_types::MetadataUrl, cis4_types::*};
14use concordium_base::{
15 constants::MAX_PARAMETER_LEN,
16 contracts_common,
17 hashes::TransactionHash,
18 smart_contracts::{ExceedsParameterSize, OwnedParameter},
19 transactions::{AccountTransaction, EncodedPayload},
20 web3id::{CredentialHolderId, Web3IdSigner, REVOKE_DOMAIN_STRING},
21};
22
23#[derive(thiserror::Error, Debug)]
24pub enum Cis4QueryError {
26 #[error("Invalid receive name: {0}")]
28 InvalidReceiveName(#[from] contracts_common::NewReceiveNameError),
29
30 #[error("RPC error: {0}")]
32 RPCError(#[from] super::v2::QueryError),
33
34 #[error("Failed parsing the response.")]
36 ResponseParseError(#[from] contracts_common::ParseError),
37
38 #[error("Rejected by the node: {0:?}.")]
40 NodeRejected(v2::Upward<crate::types::RejectReason>),
41}
42
43impl From<v2::Upward<RejectReason>> for Cis4QueryError {
44 fn from(value: v2::Upward<RejectReason>) -> Self {
45 Self::NodeRejected(value)
46 }
47}
48impl From<RejectReason> for Cis4QueryError {
49 fn from(value: RejectReason) -> Self {
50 Self::NodeRejected(v2::Upward::Known(value))
51 }
52}
53
54impl Cis4QueryError {
55 pub fn is_contract_error(&self) -> Option<v2::Upward<&crate::types::RejectReason>> {
59 if let Self::NodeRejected(e) = self {
60 Some(e.as_ref())
61 } else {
62 None
63 }
64 }
65}
66
67#[derive(thiserror::Error, Debug)]
68pub enum Cis4TransactionError {
70 #[error("Invalid receive name: {0}")]
72 InvalidReceiveName(#[from] contracts_common::NewReceiveNameError),
73
74 #[error("Parameter is too large: {0}")]
76 InvalidParams(#[from] ExceedsParameterSize),
77
78 #[error("RPC error: {0}")]
80 RPCError(#[from] super::v2::RPCError),
81
82 #[error("Rejected by the node: {0:?}.")]
84 NodeRejected(v2::Upward<crate::types::RejectReason>),
85}
86
87pub type Cis4TransactionMetadata = ContractTransactionMetadata;
89
90#[derive(Debug, Clone, Copy)]
91pub enum Cis4Type {}
94
95pub type Cis4Contract = ContractClient<Cis4Type>;
103
104impl Cis4Contract {
105 pub async fn credential_entry(
107 &mut self,
108 cred_id: CredentialHolderId,
109 bi: impl IntoBlockIdentifier,
110 ) -> Result<CredentialEntry, Cis4QueryError> {
111 let parameter =
112 OwnedParameter::from_serial(&cred_id).expect("Credential ID is a valid parameter.");
113
114 self.view_raw("credentialEntry", parameter, bi).await
115 }
116
117 pub async fn credential_status(
119 &mut self,
120 cred_id: CredentialHolderId,
121 bi: impl IntoBlockIdentifier,
122 ) -> Result<CredentialStatus, Cis4QueryError> {
123 let parameter =
124 OwnedParameter::from_serial(&cred_id).expect("Credential ID is a valid parameter.");
125
126 self.view_raw("credentialStatus", parameter, bi).await
127 }
128
129 pub async fn revocation_keys(
131 &mut self,
132 bi: impl IntoBlockIdentifier,
133 ) -> Result<Vec<RevocationKeyWithNonce>, Cis4QueryError> {
134 let parameter = OwnedParameter::empty();
135
136 self.view_raw("revocationKeys", parameter, bi).await
137 }
138
139 pub async fn registry_metadata(
141 &mut self,
142 bi: impl IntoBlockIdentifier,
143 ) -> Result<RegistryMetadata, Cis4QueryError> {
144 let parameter = OwnedParameter::empty();
145 self.view_raw("registryMetadata", parameter, bi).await
146 }
147
148 pub async fn issuer(
150 &mut self,
151 bi: impl IntoBlockIdentifier,
152 ) -> Result<IssuerKey, Cis4QueryError> {
153 let parameter = OwnedParameter::empty();
154
155 self.view_raw("issuer", parameter, bi).await
156 }
157
158 pub fn make_register_credential(
161 &self,
162 signer: &impl transactions::ExactSizeTransactionSigner,
163 metadata: &Cis4TransactionMetadata,
164 cred_info: &CredentialInfo,
165 additional_data: &[u8],
166 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
167 use contracts_common::Serial;
168 let mut payload = contracts_common::to_bytes(cred_info);
169 let actual = payload.len() + additional_data.len() + 2;
170 if payload.len() + additional_data.len() + 2 > MAX_PARAMETER_LEN {
171 return Err(Cis4TransactionError::InvalidParams(ExceedsParameterSize {
172 actual,
173 max: MAX_PARAMETER_LEN,
174 }));
175 }
176 (additional_data.len() as u16)
177 .serial(&mut payload)
178 .expect("We checked lengths above, so this must succeed.");
179 payload.extend_from_slice(additional_data);
180 let parameter = OwnedParameter::try_from(payload)?;
181 self.make_update_raw(signer, metadata, "registerCredential", parameter)
182 }
183
184 pub async fn register_credential(
186 &mut self,
187 signer: &impl transactions::ExactSizeTransactionSigner,
188 metadata: &Cis4TransactionMetadata,
189 cred_info: &CredentialInfo,
190 additional_data: &[u8],
191 ) -> Result<TransactionHash, Cis4TransactionError> {
192 let tx = self.make_register_credential(signer, metadata, cred_info, additional_data)?;
193 let hash = self.client.send_account_transaction(tx).await?;
194 Ok(hash)
195 }
196
197 pub fn make_revoke_credential_as_issuer(
199 &self,
200 signer: &impl transactions::ExactSizeTransactionSigner,
201 metadata: &Cis4TransactionMetadata,
202 cred_id: CredentialHolderId,
203 reason: Option<Reason>,
204 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
205 let parameter = OwnedParameter::from_serial(&(cred_id, reason))?;
206
207 self.make_update_raw(signer, metadata, "revokeCredentialIssuer", parameter)
208 }
209
210 pub async fn revoke_credential_as_issuer(
212 &mut self,
213 signer: &impl transactions::ExactSizeTransactionSigner,
214 metadata: &Cis4TransactionMetadata,
215 cred_id: CredentialHolderId,
216 reason: Option<Reason>,
217 ) -> Result<TransactionHash, Cis4TransactionError> {
218 let tx = self.make_revoke_credential_as_issuer(signer, metadata, cred_id, reason)?;
219 let hash = self.client.send_account_transaction(tx).await?;
220 Ok(hash)
221 }
222
223 pub async fn revoke_credential_as_holder(
229 &mut self,
230 signer: &impl transactions::ExactSizeTransactionSigner,
231 metadata: &Cis4TransactionMetadata,
232 web3signer: impl Web3IdSigner, nonce: u64,
234 reason: Option<Reason>,
235 ) -> Result<TransactionHash, Cis4TransactionError> {
236 let tx =
237 self.make_revoke_credential_as_holder(signer, metadata, web3signer, nonce, reason)?;
238 let hash = self.client.send_account_transaction(tx).await?;
239 Ok(hash)
240 }
241
242 pub fn make_revoke_credential_as_holder(
248 &self,
249 signer: &impl transactions::ExactSizeTransactionSigner,
250 metadata: &Cis4TransactionMetadata,
251 web3signer: impl Web3IdSigner, nonce: u64,
253 reason: Option<Reason>,
254 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
255 use contracts_common::Serial;
256 let mut to_sign = REVOKE_DOMAIN_STRING.to_vec();
257 let cred_id: CredentialHolderId = web3signer.id().into();
258 cred_id
259 .serial(&mut to_sign)
260 .expect("Serialization to vector does not fail.");
261 self.address
262 .serial(&mut to_sign)
263 .expect("Serialization to vector does not fail.");
264 nonce
265 .serial(&mut to_sign)
266 .expect("Serialization to vector does not fail.");
267 metadata
268 .expiry
269 .seconds
270 .checked_mul(1000)
271 .unwrap_or(u64::MAX)
272 .serial(&mut to_sign)
273 .expect("Serialization to vector does not fail.");
274 reason
275 .serial(&mut to_sign)
276 .expect("Serialization to vector does not fail.");
277 let sig = web3signer.sign(&to_sign);
278 let mut parameter_vec = sig.to_bytes().to_vec();
279 parameter_vec.extend_from_slice(&to_sign[REVOKE_DOMAIN_STRING.len()..]);
280 let parameter = OwnedParameter::try_from(parameter_vec)?;
281
282 self.make_update_raw(signer, metadata, "revokeCredentialHolder", parameter)
283 }
284
285 pub async fn revoke_credential_other(
292 &mut self,
293 signer: &impl transactions::ExactSizeTransactionSigner,
294 metadata: &Cis4TransactionMetadata,
295 revoker: impl Web3IdSigner, nonce: u64,
297 cred_id: CredentialHolderId,
298 reason: Option<&Reason>,
299 ) -> Result<TransactionHash, Cis4TransactionError> {
300 let tx =
301 self.make_revoke_credential_other(signer, metadata, revoker, nonce, cred_id, reason)?;
302 let hash = self.client.send_account_transaction(tx).await?;
303 Ok(hash)
304 }
305
306 pub fn make_revoke_credential_other(
314 &self,
315 signer: &impl transactions::ExactSizeTransactionSigner,
316 metadata: &Cis4TransactionMetadata,
317 revoker: impl Web3IdSigner, nonce: u64,
319 cred_id: CredentialHolderId,
320 reason: Option<&Reason>,
321 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
322 use contracts_common::Serial;
323 let mut to_sign = REVOKE_DOMAIN_STRING.to_vec();
324 cred_id
325 .serial(&mut to_sign)
326 .expect("Serialization to vector does not fail.");
327 self.address
328 .serial(&mut to_sign)
329 .expect("Serialization to vector does not fail.");
330 nonce
331 .serial(&mut to_sign)
332 .expect("Serialization to vector does not fail.");
333 metadata
334 .expiry
335 .seconds
336 .checked_mul(1000)
337 .unwrap_or(u64::MAX)
338 .serial(&mut to_sign)
339 .expect("Serialization to vector does not fail.");
340 RevocationKey::from(revoker.id())
341 .serial(&mut to_sign)
342 .expect("Serialization to vector does not fail.");
343 reason
344 .serial(&mut to_sign)
345 .expect("Serialization to vector does not fail.");
346 let sig = revoker.sign(&to_sign);
347 let mut parameter_vec = sig.to_bytes().to_vec();
348 parameter_vec.extend_from_slice(&to_sign[REVOKE_DOMAIN_STRING.len()..]);
349 let parameter = OwnedParameter::try_from(parameter_vec)?;
350
351 self.make_update_raw(signer, metadata, "revokeCredentialOther", parameter)
352 }
353}