1use darkfi::{
20 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
21 util::parse::{decode_base10, encode_base10},
22 zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
23 zkas::ZkBinary,
24 Error, Result,
25};
26use darkfi_money_contract::{
27 client::transfer_v1::make_transfer_call, model::TokenId, MoneyFunction,
28 MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
29};
30use darkfi_sdk::{
31 crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, Keypair, PublicKey},
32 pasta::pallas,
33 tx::ContractCall,
34};
35use darkfi_serial::AsyncEncodable;
36
37use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
38
39impl Drk {
40 pub async fn transfer(
42 &self,
43 amount: &str,
44 token_id: TokenId,
45 recipient: PublicKey,
46 spend_hook: Option<FuncId>,
47 user_data: Option<pallas::Base>,
48 half_split: bool,
49 ) -> Result<Transaction> {
50 let owncoins = self.get_token_coins(&token_id).await?;
52 if owncoins.is_empty() {
53 return Err(Error::Custom(format!(
54 "Did not find any unspent coins with token ID: {token_id}"
55 )))
56 }
57
58 let amount = decode_base10(amount, BALANCE_BASE10_DECIMALS, false)?;
59 let mut balance = 0;
60 for coin in owncoins.iter() {
61 balance += coin.note.value;
62 }
63
64 if balance < amount {
65 return Err(Error::Custom(format!(
66 "Not enough balance for token ID: {token_id}, found: {}",
67 encode_base10(balance, BALANCE_BASE10_DECIMALS)
68 )))
69 }
70
71 let secret = self.default_secret().await?;
73 let keypair = Keypair::new(secret);
74
75 let tree = self.get_money_tree().await?;
77
78 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
82
83 let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
84 else {
85 return Err(Error::Custom("Mint circuit not found".to_string()))
86 };
87
88 let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
89 else {
90 return Err(Error::Custom("Burn circuit not found".to_string()))
91 };
92
93 let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
94 else {
95 return Err(Error::Custom("Fee circuit not found".to_string()))
96 };
97
98 let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
99 let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
100 let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
101
102 let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
103 let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
104 let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
105
106 let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
108 let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
109 let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
110
111 let (params, secrets, spent_coins) = make_transfer_call(
113 keypair,
114 recipient,
115 amount,
116 token_id,
117 owncoins,
118 tree.clone(),
119 spend_hook,
120 user_data,
121 mint_zkbin,
122 mint_pk,
123 burn_zkbin,
124 burn_pk,
125 half_split,
126 )?;
127
128 let mut data = vec![MoneyFunction::TransferV1 as u8];
130 params.encode_async(&mut data).await?;
131 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
132
133 let mut tx_builder =
135 TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
136
137 let mut tx = tx_builder.build()?;
145 let sigs = tx.create_sigs(&secrets.signature_secrets)?;
146 tx.signatures.push(sigs);
147
148 let (fee_call, fee_proofs, fee_secrets) =
149 self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)).await?;
150
151 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
153
154 let mut tx = tx_builder.build()?;
156 let sigs = tx.create_sigs(&secrets.signature_secrets)?;
157 tx.signatures.push(sigs);
158 let sigs = tx.create_sigs(&fee_secrets)?;
159 tx.signatures.push(sigs);
160
161 Ok(tx)
162 }
163}