use std::convert::Infallible;
use near_api_types::{
AccessKey, AccessKeyPermission, AccountId, Action, NearGas, NearToken, PublicKey,
transaction::{
PrepopulateTransaction,
actions::{AddKeyAction, CreateAccountAction, TransferAction},
},
};
use reqwest::Response;
use serde_json::json;
use url::Url;
use crate::{
Contract, NetworkConfig,
common::send::Transactionable,
errors::{AccountCreationError, ArgumentValidationError, FaucetError, ValidationError},
transactions::{ConstructTransaction, TransactionWithSign},
};
#[derive(Clone, Debug)]
pub struct CreateAccountBuilder {
pub account_id: AccountId,
}
impl CreateAccountBuilder {
pub fn fund_myself(
self,
signer_account_id: AccountId,
initial_balance: NearToken,
) -> FundMyselfBuilder {
FundMyselfBuilder {
new_account_id: self.account_id,
signer_account_id,
initial_balance,
}
}
pub fn sponsor_by_faucet_service(self) -> SponsorByFaucetServiceBuilder {
SponsorByFaucetServiceBuilder {
new_account_id: self.account_id,
}
}
}
#[derive(Clone, Debug)]
pub struct FundMyselfBuilder {
new_account_id: AccountId,
signer_account_id: AccountId,
initial_balance: NearToken,
}
impl FundMyselfBuilder {
pub fn with_public_key(
self,
pk: impl Into<PublicKey>,
) -> TransactionWithSign<CreateAccountFundMyselfTx> {
let public_key = pk.into();
let transaction = if self
.new_account_id
.is_sub_account_of(&self.signer_account_id)
{
ConstructTransaction::new(self.signer_account_id.clone(), self.new_account_id.clone())
.add_actions(vec![
Action::CreateAccount(CreateAccountAction {}),
Action::Transfer(TransferAction {
deposit: self.initial_balance,
}),
Action::AddKey(Box::new(AddKeyAction {
public_key,
access_key: AccessKey {
nonce: 0.into(),
permission: AccessKeyPermission::FullAccess,
},
})),
])
.transaction
} else if let Some(linkdrop_account_id) = self.new_account_id.get_parent_account_id() {
Contract(linkdrop_account_id.to_owned())
.call_function(
"create_account",
json!({
"new_account_id": self.new_account_id.to_string(),
"new_public_key": public_key,
}),
)
.transaction()
.gas(NearGas::from_tgas(30))
.deposit(self.initial_balance)
.with_signer_account(self.signer_account_id.clone())
.transaction
} else {
Err(AccountCreationError::TopLevelAccountIsNotAllowed.into())
};
TransactionWithSign {
tx: CreateAccountFundMyselfTx {
prepopulated: transaction,
},
}
}
}
#[derive(Clone, Debug)]
pub struct SponsorByFaucetServiceBuilder {
new_account_id: AccountId,
}
impl SponsorByFaucetServiceBuilder {
pub fn with_public_key(
self,
pk: impl Into<PublicKey>,
) -> Result<CreateAccountByFaucet, Infallible> {
Ok(CreateAccountByFaucet {
new_account_id: self.new_account_id,
public_key: pk.into(),
})
}
}
#[derive(Clone, Debug)]
pub struct CreateAccountByFaucet {
pub new_account_id: AccountId,
pub public_key: PublicKey,
}
impl CreateAccountByFaucet {
pub async fn send_to_testnet_faucet(self) -> Result<Response, FaucetError> {
let testnet = NetworkConfig::testnet();
self.send_to_config_faucet(&testnet).await
}
pub async fn send_to_config_faucet(
self,
config: &NetworkConfig,
) -> Result<Response, FaucetError> {
let faucet_service_url = match &config.faucet_url {
Some(url) => url,
None => return Err(FaucetError::FaucetIsNotDefined(config.network_name.clone())),
};
self.send_to_faucet(faucet_service_url).await
}
pub async fn send_to_faucet(self, url: &Url) -> Result<Response, FaucetError> {
let mut data = std::collections::HashMap::new();
data.insert("newAccountId", self.new_account_id.to_string());
data.insert("newAccountPublicKey", self.public_key.to_string());
let client = reqwest::Client::new();
Ok(client.post(url.clone()).json(&data).send().await?)
}
}
#[derive(Clone, Debug)]
pub struct CreateAccountFundMyselfTx {
prepopulated: Result<PrepopulateTransaction, ArgumentValidationError>,
}
#[async_trait::async_trait]
impl Transactionable for CreateAccountFundMyselfTx {
fn prepopulated(&self) -> Result<PrepopulateTransaction, ArgumentValidationError> {
self.prepopulated.clone()
}
async fn validate_with_network(&self, network: &NetworkConfig) -> Result<(), ValidationError> {
let prepopulated = self.prepopulated()?;
if prepopulated
.receiver_id
.is_sub_account_of(&prepopulated.signer_id)
{
return Ok(());
}
match &network.linkdrop_account_id {
Some(linkdrop) => {
if &prepopulated.receiver_id != linkdrop {
Err(AccountCreationError::AccountShouldBeSubAccountOfSignerOrLinkdrop)?;
}
}
None => Err(AccountCreationError::LinkdropIsNotDefined)?,
}
Ok(())
}
}