/ bin / drk / src / transfer.rs
transfer.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  use 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  };
 26  use 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  };
 30  use darkfi_sdk::{
 31      crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, Keypair, PublicKey},
 32      pasta::pallas,
 33      tx::ContractCall,
 34  };
 35  use darkfi_serial::AsyncEncodable;
 36  
 37  use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
 38  
 39  impl Drk {
 40      /// Create a payment transaction. Returns the transaction object on success.
 41      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          // First get all unspent OwnCoins to see what our balance is
 51          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          // Fetch our default secret
 72          let secret = self.default_secret().await?;
 73          let keypair = Keypair::new(secret);
 74  
 75          // We'll also need our Merkle tree
 76          let tree = self.get_money_tree().await?;
 77  
 78          // Now we need to do a lookup for the zkas proof bincodes, and create
 79          // the circuit objects and proving keys so we can build the transaction.
 80          // We also do this through the RPC.
 81          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          // Creating Mint, Burn and Fee circuits proving keys
107          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          // Building transaction parameters
112          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          // Encode the call
129          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          // Create the TransactionBuilder containing the `Transfer` call
134          let mut tx_builder =
135              TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
136  
137          // We first have to execute the fee-less tx to gather its used gas, and then we feed
138          // it into the fee-creating function.
139          // We also tell it about any spent coins so we don't accidentally reuse them in the
140          // fee call.
141          // TODO: We have to build a proper coin selection algorithm so that we can utilize
142          // the Money::Transfer to merge any coins which would give us a coin with enough
143          // value for paying the transaction fee.
144          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          // Append the fee call to the transaction
152          tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
153  
154          // Now build the actual transaction and sign it with all necessary keys.
155          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  }