1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use dapi_grpc::platform::VersionedGrpcResponse;
use dpp::dashcore::Address;
use dpp::identity::accessors::IdentityGettersV0;

use dpp::identity::core_script::CoreScript;
use dpp::identity::signer::Signer;
use dpp::identity::Identity;
use dpp::prelude::UserFeeIncrease;

use dpp::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition;
use drive_proof_verifier::error::ContextProviderError;
use drive_proof_verifier::DataContractProvider;

use crate::platform::block_info_from_metadata::block_info_from_metadata;
use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition;
use crate::platform::transition::put_settings::PutSettings;
use crate::{Error, Sdk};
use dpp::state_transition::identity_credit_withdrawal_transition::methods::IdentityCreditWithdrawalTransitionMethodsV0;
use dpp::state_transition::proof_result::StateTransitionProofResult;
use dpp::withdrawal::Pooling;
use drive::drive::Drive;
use rs_dapi_client::{DapiRequest, RequestSettings};

#[async_trait::async_trait]
pub trait WithdrawFromIdentity {
    /// Function to withdraw credits from an identity. Returns the final identity balance.
    async fn withdraw<S: Signer + Send>(
        &self,
        sdk: &Sdk,
        address: Address,
        amount: u64,
        core_fee_per_byte: Option<u32>,
        user_fee_increase: Option<UserFeeIncrease>,
        signer: S,
        settings: Option<PutSettings>,
    ) -> Result<u64, Error>;
}

#[async_trait::async_trait]
impl WithdrawFromIdentity for Identity {
    async fn withdraw<S: Signer + Send>(
        &self,
        sdk: &Sdk,
        address: Address,
        amount: u64,
        core_fee_per_byte: Option<u32>,
        user_fee_increase: Option<UserFeeIncrease>,
        signer: S,
        settings: Option<PutSettings>,
    ) -> Result<u64, Error> {
        let new_identity_nonce = sdk.get_identity_nonce(self.id(), true, settings).await?;
        let state_transition = IdentityCreditWithdrawalTransition::try_from_identity(
            self,
            None,
            CoreScript::new(address.script_pubkey()),
            amount,
            Pooling::Never,
            core_fee_per_byte.unwrap_or(1),
            user_fee_increase.unwrap_or_default(),
            signer,
            new_identity_nonce,
            sdk.version(),
            None,
        )?;

        let request = state_transition.broadcast_request_for_state_transition()?;

        request
            .clone()
            .execute(sdk, settings.unwrap_or_default().request_settings)
            .await?;

        let request = state_transition.wait_for_state_transition_result_request()?;

        let response = request.execute(sdk, RequestSettings::default()).await?;

        let block_info = block_info_from_metadata(response.metadata()?)?;

        let proof = response.proof_owned()?;
        let context_provider =
            sdk.context_provider()
                .ok_or(Error::from(ContextProviderError::Config(
                    "Context provider not initialized".to_string(),
                )))?;

        let (_, result) = Drive::verify_state_transition_was_executed_with_proof(
            &state_transition,
            &block_info,
            proof.grovedb_proof.as_slice(),
            &context_provider.as_contract_lookup_fn(),
            sdk.version(),
        )?;

        match result {
            StateTransitionProofResult::VerifiedPartialIdentity(identity) => {
                identity.balance.ok_or(Error::DapiClientError(
                    "expected an identity balance".to_string(),
                ))
            }
            _ => Err(Error::DapiClientError("proved a non identity".to_string())),
        }
    }
}