use std::collections::HashSet;
use tari_ootle_common_types::{
SubstateRequirement,
engine_types::{limits, published_template::TemplateBlob},
};
use tari_ootle_transaction::{TransactionBuilder, UnsignedTransaction, args};
use tari_template_lib_types::{Amount, ResourceAddress, constants::TARI_TOKEN};
use crate::{
Address,
ToAccountAddress,
builtin_templates::traits::UnsignedTransactionBuilder,
provider::{Provider, ProviderError, WantInput},
};
pub type IAccount<'a, P> = AccountInvokeBuilder<'a, P>;
pub struct AccountInvokeBuilder<'a, P> {
builder: TransactionBuilder,
provider: &'a P,
want_list: HashSet<WantInput>,
}
impl<'a, P: Provider> UnsignedTransactionBuilder for AccountInvokeBuilder<'a, P> {
fn default_signer_address(&self) -> &Address {
self.provider.default_signer_address()
}
fn add_input<S: Into<SubstateRequirement>>(mut self, substate_id: S) -> Self {
self.builder = self.builder.add_input(substate_id);
self
}
async fn prepare(self) -> Result<UnsignedTransaction, ProviderError> {
let Self {
builder,
provider,
want_list,
..
} = self;
let unsigned_tx = builder.build_unsigned();
let unsigned_tx = provider.resolve_input_want_list(unsigned_tx, &want_list).await?;
Ok(unsigned_tx)
}
}
impl<'a, P: Provider> AccountInvokeBuilder<'a, P> {
pub fn new(provider: &'a P) -> Self {
let network = provider.network();
Self {
builder: TransactionBuilder::new(network).with_auto_fill_inputs(),
provider,
want_list: HashSet::new(),
}
}
pub fn pay_fee<A: Into<Amount>>(mut self, amount: A) -> Self {
let component_addr = self.default_signer_address().to_account_address();
self.want_list.insert(WantInput::VaultForResource {
component_address: component_addr,
resource_address: TARI_TOKEN,
required: true,
});
self.builder = self.builder.pay_fee_from_component(component_addr, amount);
self
}
fn next_bucket_name(&mut self) -> String {
format!("__AccountInvokeBuilder_{}", self.builder.next_workspace_id())
}
pub fn public_transfer<A: Into<Amount>>(
mut self,
to: &Address,
resource_address: ResourceAddress,
amount: A,
) -> Self {
let amount: Amount = amount.into();
if !amount.is_positive() {
panic!("Transfer amount must be positive");
}
let from_component_addr = self.default_signer_address().to_account_address();
let to_component_addr = to.to_account_address();
let bucket_name = self.next_bucket_name();
self.want_list.insert(WantInput::VaultForResource {
component_address: from_component_addr,
resource_address,
required: true,
});
self.want_list.insert(WantInput::SpecificSubstate {
substate_id: to_component_addr.into(),
required: false,
});
self.want_list.insert(WantInput::VaultForResource {
component_address: to_component_addr,
resource_address,
required: false,
});
self.builder = self
.builder
.call_method(from_component_addr, "withdraw", args![resource_address, amount])
.put_last_instruction_output_on_workspace(&bucket_name)
.create_account_with_bucket(*to.account_public_key(), bucket_name);
self
}
pub fn publish_template<T: TryInto<TemplateBlob>>(mut self, template: T) -> Self {
let Ok(template) = template.try_into() else {
panic!(
"Template blob exceeds maximum size of {} bytes",
limits::ENGINE_LIMITS.max_template_binary_size_bytes
);
};
self.builder = self.builder.publish_template(template);
self
}
}