drk/
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
19use 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    /// 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}