1use crate::{
9 contract_client::*,
10 types::{transactions, RejectReason},
11 v2::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(crate::types::RejectReason),
41}
42
43impl From<RejectReason> for Cis4QueryError {
44 fn from(value: RejectReason) -> Self { Self::NodeRejected(value) }
45}
46
47impl Cis4QueryError {
48 pub fn is_contract_error(&self) -> Option<&crate::types::RejectReason> {
52 if let Self::NodeRejected(e) = self {
53 Some(e)
54 } else {
55 None
56 }
57 }
58}
59
60#[derive(thiserror::Error, Debug)]
61pub enum Cis4TransactionError {
63 #[error("Invalid receive name: {0}")]
65 InvalidReceiveName(#[from] contracts_common::NewReceiveNameError),
66
67 #[error("Parameter is too large: {0}")]
69 InvalidParams(#[from] ExceedsParameterSize),
70
71 #[error("RPC error: {0}")]
73 RPCError(#[from] super::v2::RPCError),
74
75 #[error("Rejected by the node: {0:?}.")]
77 NodeRejected(crate::types::RejectReason),
78}
79
80pub type Cis4TransactionMetadata = ContractTransactionMetadata;
82
83#[derive(Debug, Clone, Copy)]
84pub enum Cis4Type {}
87
88pub type Cis4Contract = ContractClient<Cis4Type>;
96
97impl Cis4Contract {
98 pub async fn credential_entry(
100 &mut self,
101 cred_id: CredentialHolderId,
102 bi: impl IntoBlockIdentifier,
103 ) -> Result<CredentialEntry, Cis4QueryError> {
104 let parameter =
105 OwnedParameter::from_serial(&cred_id).expect("Credential ID is a valid parameter.");
106
107 self.view_raw("credentialEntry", parameter, bi).await
108 }
109
110 pub async fn credential_status(
112 &mut self,
113 cred_id: CredentialHolderId,
114 bi: impl IntoBlockIdentifier,
115 ) -> Result<CredentialStatus, Cis4QueryError> {
116 let parameter =
117 OwnedParameter::from_serial(&cred_id).expect("Credential ID is a valid parameter.");
118
119 self.view_raw("credentialStatus", parameter, bi).await
120 }
121
122 pub async fn revocation_keys(
124 &mut self,
125 bi: impl IntoBlockIdentifier,
126 ) -> Result<Vec<RevocationKeyWithNonce>, Cis4QueryError> {
127 let parameter = OwnedParameter::empty();
128
129 self.view_raw("revocationKeys", parameter, bi).await
130 }
131
132 pub async fn registry_metadata(
134 &mut self,
135 bi: impl IntoBlockIdentifier,
136 ) -> Result<RegistryMetadata, Cis4QueryError> {
137 let parameter = OwnedParameter::empty();
138 self.view_raw("registryMetadata", parameter, bi).await
139 }
140
141 pub async fn issuer(
143 &mut self,
144 bi: impl IntoBlockIdentifier,
145 ) -> Result<IssuerKey, Cis4QueryError> {
146 let parameter = OwnedParameter::empty();
147
148 self.view_raw("issuer", parameter, bi).await
149 }
150
151 pub fn make_register_credential(
154 &self,
155 signer: &impl transactions::ExactSizeTransactionSigner,
156 metadata: &Cis4TransactionMetadata,
157 cred_info: &CredentialInfo,
158 additional_data: &[u8],
159 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
160 use contracts_common::Serial;
161 let mut payload = contracts_common::to_bytes(cred_info);
162 let actual = payload.len() + additional_data.len() + 2;
163 if payload.len() + additional_data.len() + 2 > MAX_PARAMETER_LEN {
164 return Err(Cis4TransactionError::InvalidParams(ExceedsParameterSize {
165 actual,
166 max: MAX_PARAMETER_LEN,
167 }));
168 }
169 (additional_data.len() as u16)
170 .serial(&mut payload)
171 .expect("We checked lengths above, so this must succeed.");
172 payload.extend_from_slice(additional_data);
173 let parameter = OwnedParameter::try_from(payload)?;
174 self.make_update_raw(signer, metadata, "registerCredential", parameter)
175 }
176
177 pub async fn register_credential(
179 &mut self,
180 signer: &impl transactions::ExactSizeTransactionSigner,
181 metadata: &Cis4TransactionMetadata,
182 cred_info: &CredentialInfo,
183 additional_data: &[u8],
184 ) -> Result<TransactionHash, Cis4TransactionError> {
185 let tx = self.make_register_credential(signer, metadata, cred_info, additional_data)?;
186 let hash = self.client.send_account_transaction(tx).await?;
187 Ok(hash)
188 }
189
190 pub fn make_revoke_credential_as_issuer(
192 &self,
193 signer: &impl transactions::ExactSizeTransactionSigner,
194 metadata: &Cis4TransactionMetadata,
195 cred_id: CredentialHolderId,
196 reason: Option<Reason>,
197 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
198 let parameter = OwnedParameter::from_serial(&(cred_id, reason))?;
199
200 self.make_update_raw(signer, metadata, "revokeCredentialIssuer", parameter)
201 }
202
203 pub async fn revoke_credential_as_issuer(
205 &mut self,
206 signer: &impl transactions::ExactSizeTransactionSigner,
207 metadata: &Cis4TransactionMetadata,
208 cred_id: CredentialHolderId,
209 reason: Option<Reason>,
210 ) -> Result<TransactionHash, Cis4TransactionError> {
211 let tx = self.make_revoke_credential_as_issuer(signer, metadata, cred_id, reason)?;
212 let hash = self.client.send_account_transaction(tx).await?;
213 Ok(hash)
214 }
215
216 pub async fn revoke_credential_as_holder(
222 &mut self,
223 signer: &impl transactions::ExactSizeTransactionSigner,
224 metadata: &Cis4TransactionMetadata,
225 web3signer: impl Web3IdSigner, nonce: u64,
227 reason: Option<Reason>,
228 ) -> Result<TransactionHash, Cis4TransactionError> {
229 let tx =
230 self.make_revoke_credential_as_holder(signer, metadata, web3signer, nonce, reason)?;
231 let hash = self.client.send_account_transaction(tx).await?;
232 Ok(hash)
233 }
234
235 pub fn make_revoke_credential_as_holder(
241 &self,
242 signer: &impl transactions::ExactSizeTransactionSigner,
243 metadata: &Cis4TransactionMetadata,
244 web3signer: impl Web3IdSigner, nonce: u64,
246 reason: Option<Reason>,
247 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
248 use contracts_common::Serial;
249 let mut to_sign = REVOKE_DOMAIN_STRING.to_vec();
250 let cred_id: CredentialHolderId = web3signer.id().into();
251 cred_id
252 .serial(&mut to_sign)
253 .expect("Serialization to vector does not fail.");
254 self.address
255 .serial(&mut to_sign)
256 .expect("Serialization to vector does not fail.");
257 nonce
258 .serial(&mut to_sign)
259 .expect("Serialization to vector does not fail.");
260 metadata
261 .expiry
262 .seconds
263 .checked_mul(1000)
264 .unwrap_or(u64::MAX)
265 .serial(&mut to_sign)
266 .expect("Serialization to vector does not fail.");
267 reason
268 .serial(&mut to_sign)
269 .expect("Serialization to vector does not fail.");
270 let sig = web3signer.sign(&to_sign);
271 let mut parameter_vec = sig.to_bytes().to_vec();
272 parameter_vec.extend_from_slice(&to_sign[REVOKE_DOMAIN_STRING.len()..]);
273 let parameter = OwnedParameter::try_from(parameter_vec)?;
274
275 self.make_update_raw(signer, metadata, "revokeCredentialHolder", parameter)
276 }
277
278 pub async fn revoke_credential_other(
285 &mut self,
286 signer: &impl transactions::ExactSizeTransactionSigner,
287 metadata: &Cis4TransactionMetadata,
288 revoker: impl Web3IdSigner, nonce: u64,
290 cred_id: CredentialHolderId,
291 reason: Option<&Reason>,
292 ) -> Result<TransactionHash, Cis4TransactionError> {
293 let tx =
294 self.make_revoke_credential_other(signer, metadata, revoker, nonce, cred_id, reason)?;
295 let hash = self.client.send_account_transaction(tx).await?;
296 Ok(hash)
297 }
298
299 pub fn make_revoke_credential_other(
307 &self,
308 signer: &impl transactions::ExactSizeTransactionSigner,
309 metadata: &Cis4TransactionMetadata,
310 revoker: impl Web3IdSigner, nonce: u64,
312 cred_id: CredentialHolderId,
313 reason: Option<&Reason>,
314 ) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
315 use contracts_common::Serial;
316 let mut to_sign = REVOKE_DOMAIN_STRING.to_vec();
317 cred_id
318 .serial(&mut to_sign)
319 .expect("Serialization to vector does not fail.");
320 self.address
321 .serial(&mut to_sign)
322 .expect("Serialization to vector does not fail.");
323 nonce
324 .serial(&mut to_sign)
325 .expect("Serialization to vector does not fail.");
326 metadata
327 .expiry
328 .seconds
329 .checked_mul(1000)
330 .unwrap_or(u64::MAX)
331 .serial(&mut to_sign)
332 .expect("Serialization to vector does not fail.");
333 RevocationKey::from(revoker.id())
334 .serial(&mut to_sign)
335 .expect("Serialization to vector does not fail.");
336 reason
337 .serial(&mut to_sign)
338 .expect("Serialization to vector does not fail.");
339 let sig = revoker.sign(&to_sign);
340 let mut parameter_vec = sig.to_bytes().to_vec();
341 parameter_vec.extend_from_slice(&to_sign[REVOKE_DOMAIN_STRING.len()..]);
342 let parameter = OwnedParameter::try_from(parameter_vec)?;
343
344 self.make_update_raw(signer, metadata, "revokeCredentialOther", parameter)
345 }
346}