1pub mod external_router;
4pub mod state;
5pub mod types;
6pub mod utils;
7
8use crate::{
9 crypto::{
10 self,
11 config::{Config, Environment},
12 ethereum::{recover_address_from_eth_signature, sign_message},
13 vec_u8_to_string,
14 },
15 owner, random,
16};
17use candid::Principal;
18use ic_cdk::caller;
19use state::*;
20use types::Subscriber;
21
22const REMITTANCE_EVENT: &str = "REMITTANCE";
23
24pub fn init(env_opt: Option<Environment>) {
26 owner::init_owner();
27 random::init_ic_rand();
28
29 if let Some(env) = env_opt {
31 CONFIG.with(|s| {
32 let mut state = s.borrow_mut();
33 *state = Config::from(env);
34 })
35 }
36}
37
38pub fn owner() -> String {
40 owner::get_owner()
41}
42
43pub fn name() -> String {
45 format!("remittance canister")
46}
47
48pub async fn subscribe_to_dc(canister_id: Principal) {
50 let subscriber = Subscriber {
51 topic: REMITTANCE_EVENT.to_string(),
52 };
53 let _call_result: Result<(), _> = ic_cdk::call(canister_id, "subscribe", (subscriber,)).await;
54 DC_CANISTERS.with(|dc_canister| {
56 let mut borrowed_canister = dc_canister.borrow_mut();
57 if !borrowed_canister.contains(&canister_id) {
58 borrowed_canister.push(canister_id)
59 }
60 });
61}
62
63pub async fn subscribe_to_pdc(pdc_canister_id: Principal) {
65 subscribe_to_dc(pdc_canister_id).await;
66 IS_PDC_CANISTER.with(|is_pdc_canister| {
67 is_pdc_canister.borrow_mut().insert(pdc_canister_id, true);
68 });
69}
70
71pub fn update_remittance(
73 new_remittances: Vec<types::DataModel>,
74 dc_canister: Principal,
75) -> Result<(), String> {
76 let is_pdc =
77 IS_PDC_CANISTER.with(|is_pdc_canister| is_pdc_canister.borrow().contains_key(&caller()));
78
79 if let Err(text) = utils::validate_remittance_data(is_pdc, &new_remittances, dc_canister) {
81 return Err(text);
82 }
83
84 for new_remittance in new_remittances {
86 let _: Result<(), String> = match new_remittance.action.clone() {
87 types::Action::Adjust => {
88 utils::update_balance(&new_remittance, dc_canister);
89 Ok(())
90 }
91 types::Action::Deposit => {
92 utils::update_balance(&new_remittance, dc_canister);
93 utils::update_canister_balance(
95 new_remittance.token,
96 new_remittance.chain,
97 dc_canister,
98 new_remittance.amount,
99 );
100 Ok(())
101 }
102 types::Action::Withdraw => {
103 utils::confirm_withdrawal(
104 new_remittance.token.to_string(),
105 new_remittance.chain.to_string(),
106 new_remittance.account.to_string(),
107 new_remittance.amount.abs() as u64,
108 dc_canister,
109 );
110 utils::update_canister_balance(
112 new_remittance.token,
113 new_remittance.chain,
114 dc_canister,
115 -new_remittance.amount,
116 );
117 Ok(())
118 }
119 types::Action::CancelWithdraw => {
120 utils::cancel_withdrawal(
121 new_remittance.token.to_string(),
122 new_remittance.chain.to_string(),
123 new_remittance.account.to_string(),
124 new_remittance.amount.abs() as u64,
125 dc_canister,
126 );
127 Ok(())
128 }
129 };
130 }
131
132 Ok(())
133}
134
135pub async fn remit(
137 token: String,
138 chain: String,
139 account: String,
140 dc_canister: Principal,
141 amount: u64,
142 proof: String,
143) -> Result<types::RemittanceReply, Box<dyn std::error::Error>> {
144 let _derived_address = recover_address_from_eth_signature(proof, format!("{amount}"))?;
146
147 assert!(
149 _derived_address == account.to_lowercase(),
150 "INVALID_SIGNATURE"
151 );
152 assert!(amount > 0, "AMOUNT < 0");
154
155 let chain: types::Chain = chain.try_into()?;
157 let token: types::Wallet = token.try_into()?;
158 let account: types::Wallet = account.try_into()?;
159
160 let hash_key = (
161 token.clone(),
162 chain.clone(),
163 account.clone(),
164 dc_canister.clone(),
165 );
166
167 let withheld_balance = utils::get_remitted_balance(
169 token.clone(),
170 chain.clone(),
171 account.clone(),
172 dc_canister.clone(),
173 amount,
174 );
175
176 let response: types::RemittanceReply;
177 if withheld_balance.balance == amount {
179 let message_hash = utils::hash_remittance_parameters(
180 withheld_balance.nonce,
181 amount,
182 &account.to_string(),
183 &chain.to_string(),
184 &dc_canister.to_string(),
185 &token.to_string(),
186 );
187
188 response = types::RemittanceReply {
189 hash: vec_u8_to_string(&message_hash),
190 signature: withheld_balance.signature.clone(),
191 nonce: withheld_balance.nonce,
192 amount,
193 };
194 } else {
195 let nonce = random::get_random_number();
196 let message_hash = utils::hash_remittance_parameters(
197 nonce,
198 amount,
199 &account.to_string(),
200 &chain.to_string(),
201 &dc_canister.to_string(),
202 &token.to_string(),
203 );
204 let balance = get_available_balance(
205 token.to_string(),
206 chain.to_string(),
207 account.to_string(),
208 dc_canister.clone(),
209 )?
210 .balance;
211
212 if amount > balance {
214 panic!("REMIT_AMOUNT:{amount} > AVAILABLE_BALANCE:{balance}");
215 }
216
217 let config_store = CONFIG.with(|store| store.borrow().clone());
219 let signature_reply = sign_message(&message_hash, &config_store).await?;
220 let signature_string = format!("0x{}", signature_reply.signature_hex);
221
222 REMITTANCE.with(|remittance| {
224 if let Some(existing_data) = remittance.borrow_mut().get_mut(&hash_key) {
225 existing_data.balance = existing_data.balance - amount;
226 }
227 });
228 WITHHELD_AMOUNTS.with(|withheld_amount| {
230 withheld_amount
231 .borrow_mut()
232 .entry(hash_key.clone())
233 .or_insert(Vec::new())
234 .push(amount);
235 });
236 WITHHELD_REMITTANCE.with(|withheld| {
238 let mut withheld_remittance_store = withheld.borrow_mut();
239 withheld_remittance_store.insert(
240 (
241 token.clone(),
242 chain.clone(),
243 account.clone(),
244 dc_canister.clone(),
245 amount,
246 ),
247 types::WithheldAccount {
248 balance: amount,
249 signature: signature_string.clone(),
250 nonce,
251 },
252 );
253 });
254 response = types::RemittanceReply {
256 hash: format!("0x{}", vec_u8_to_string(&message_hash)),
257 signature: signature_string.clone(),
258 nonce,
259 amount,
260 };
261 }
262
263 Ok(response)
264}
265
266pub fn get_available_balance(
268 token: String,
269 chain: String,
270 account: String,
271 dc_canister: Principal,
272) -> Result<types::Account, Box<dyn std::error::Error>> {
273 let chain: types::Chain = chain.try_into()?;
274 let token: types::Wallet = token.try_into()?;
275 let account: types::Wallet = account.try_into()?;
276
277 let amount = utils::get_available_balance(token, chain, account, dc_canister);
279
280 Ok(amount)
281}
282
283pub fn get_canister_balance(
285 token: String,
286 chain: String,
287 dc_canister: Principal,
288) -> Result<types::Account, Box<dyn std::error::Error>> {
289 let chain: types::Chain = chain.try_into().unwrap();
290 let token: types::Wallet = token.try_into().unwrap();
291
292 let amount = utils::get_canister_balance(token, chain, dc_canister);
294
295 Ok(amount)
296}
297
298pub fn get_withheld_balance(
300 token: String,
301 chain: String,
302 account: String,
303 dc_canister: Principal,
304) -> Result<types::Account, Box<dyn std::error::Error>> {
305 let chain: types::Chain = chain.try_into()?;
306 let token: types::Wallet = token.try_into()?;
307 let account: types::Wallet = account.try_into()?;
308
309 let existing_key = (token.clone(), chain.clone(), account.clone(), dc_canister);
310
311 let sum = WITHHELD_AMOUNTS.with(|withheld_amount| {
313 let withheld_amount = withheld_amount.borrow();
314 let values = withheld_amount.get(&existing_key);
315
316 match values {
317 Some(vec) => vec.iter().sum::<u64>(),
318 None => 0,
319 }
320 });
321
322 Ok(types::Account { balance: sum })
323}
324
325pub fn get_reciept(
327 dc_canister: Principal,
328 nonce: u64,
329) -> Result<types::RemittanceReciept, Box<dyn std::error::Error>> {
330 let key = (dc_canister.clone(), nonce.clone());
331 Ok(REMITTANCE_RECIEPTS.with(|remittance_reciepts| {
332 remittance_reciepts
333 .borrow()
334 .get(&key)
335 .expect("RECIEPT_NOT_FOUND")
336 .clone()
337 }))
338}
339
340pub async fn public_key() -> Result<crypto::ecdsa::PublicKeyReply, Box<dyn std::error::Error>> {
342 let config = CONFIG.with(|c| c.borrow().clone());
343
344 let request = crypto::ecdsa::ECDSAPublicKey {
345 canister_id: None,
346 derivation_path: vec![],
347 key_id: config.key.to_key_id(),
348 };
349
350 let (res,): (crypto::ecdsa::ECDSAPublicKeyReply,) = ic_cdk::call(
351 Principal::management_canister(),
352 "ecdsa_public_key",
353 (request,),
354 )
355 .await
356 .map_err(|e| format!("ECDSA_PUBLIC_KEY_FAILED: {}\t,Error_code:{:?}", e.1, e.0))?;
357
358 let address = crypto::ethereum::get_address_from_public_key(res.public_key.clone())?;
359
360 Ok(crypto::ecdsa::PublicKeyReply {
361 sec1_pk: hex::encode(res.public_key),
362 etherum_pk: address,
363 })
364}