near_api/account/
create.rs

1use std::convert::Infallible;
2
3use near_api_types::{
4    AccessKey, AccessKeyPermission, AccountId, Action, NearGas, NearToken, PublicKey,
5    transaction::{
6        PrepopulateTransaction,
7        actions::{AddKeyAction, CreateAccountAction, TransferAction},
8    },
9};
10use reqwest::Response;
11use serde_json::json;
12use url::Url;
13
14use crate::{
15    Contract, NetworkConfig,
16    common::send::Transactionable,
17    errors::{AccountCreationError, FaucetError, ValidationError},
18    transactions::{ConstructTransaction, TransactionWithSign},
19};
20
21#[derive(Clone, Debug)]
22pub struct CreateAccountBuilder {
23    pub account_id: AccountId,
24}
25
26impl CreateAccountBuilder {
27    /// Create an NEAR account and fund it by your own
28    ///
29    /// You can only create an sub-account of your own account or sub-account of the linkdrop account ([near](https://nearblocks.io/address/near) on mainnet , [testnet](https://testnet.nearblocks.io/address/testnet) on testnet)
30    pub fn fund_myself(
31        self,
32        signer_account_id: AccountId,
33        initial_balance: NearToken,
34    ) -> PublicKeyProvider<TransactionWithSign<CreateAccountFundMyselfTx>, AccountCreationError>
35    {
36        PublicKeyProvider::new(Box::new(move |public_key| {
37            let (actions, receiver_id) = if self.account_id.is_sub_account_of(&signer_account_id) {
38                (
39                    vec![
40                        Action::CreateAccount(CreateAccountAction {}),
41                        Action::Transfer(TransferAction {
42                            deposit: initial_balance,
43                        }),
44                        Action::AddKey(Box::new(AddKeyAction {
45                            public_key,
46                            access_key: AccessKey {
47                                nonce: 0.into(),
48                                permission: AccessKeyPermission::FullAccess,
49                            },
50                        })),
51                    ],
52                    self.account_id.clone(),
53                )
54            } else if let Some(linkdrop_account_id) = self.account_id.get_parent_account_id() {
55                (
56                    Contract(linkdrop_account_id.to_owned())
57                        .call_function(
58                            "create_account",
59                            json!({
60                                "new_account_id": self.account_id.to_string(),
61                                "new_public_key": public_key,
62                            }),
63                        )?
64                        .transaction()
65                        .gas(NearGas::from_tgas(30))
66                        .deposit(initial_balance)
67                        .with_signer_account(signer_account_id.clone())
68                        .prepopulated()
69                        .actions,
70                    linkdrop_account_id.to_owned(),
71                )
72            } else {
73                return Err(AccountCreationError::TopLevelAccountIsNotAllowed);
74            };
75
76            let prepopulated = ConstructTransaction::new(signer_account_id, receiver_id)
77                .add_actions(actions)
78                .prepopulated();
79
80            Ok(TransactionWithSign {
81                tx: CreateAccountFundMyselfTx { prepopulated },
82            })
83        }))
84    }
85
86    /// Create an account sponsored by faucet service
87    ///
88    /// This is a way to create an account without having to fund it. It works only on testnet.
89    /// You can only create an sub-account of the [testnet](https://testnet.nearblocks.io/address/testnet) account
90    pub fn sponsor_by_faucet_service(self) -> PublicKeyProvider<CreateAccountByFaucet, Infallible> {
91        PublicKeyProvider::new(Box::new(move |public_key| {
92            Ok(CreateAccountByFaucet {
93                new_account_id: self.account_id,
94                public_key,
95            })
96        }))
97    }
98}
99
100#[derive(Clone, Debug)]
101pub struct CreateAccountByFaucet {
102    pub new_account_id: AccountId,
103    pub public_key: PublicKey,
104}
105
106impl CreateAccountByFaucet {
107    /// Sends the account creation request to the default testnet faucet service.
108    ///
109    /// The account will be created as a sub-account of the [testnet](https://testnet.nearblocks.io/address/testnet) account
110    pub async fn send_to_testnet_faucet(self) -> Result<Response, FaucetError> {
111        let testnet = NetworkConfig::testnet();
112        self.send_to_config_faucet(&testnet).await
113    }
114
115    /// Sends the account creation request to the faucet service specified in the network config.
116    /// This way you can specify your own faucet service.
117    ///
118    /// The function sends the request in the following format:
119    /// ```json
120    /// {
121    ///     "newAccountId": "new_account_id",
122    ///     "newAccountPublicKey": "new_account_public_key"
123    /// }
124    /// ```
125    pub async fn send_to_config_faucet(
126        self,
127        config: &NetworkConfig,
128    ) -> Result<Response, FaucetError> {
129        let faucet_service_url = match &config.faucet_url {
130            Some(url) => url,
131            None => return Err(FaucetError::FaucetIsNotDefined(config.network_name.clone())),
132        };
133
134        self.send_to_faucet(faucet_service_url).await
135    }
136
137    /// Sends the account creation request to the faucet service specified by the URL.
138    ///
139    /// The function sends the request in the following format:
140    /// ```json
141    /// {
142    ///     "newAccountId": "new_account_id",
143    ///     "newAccountPublicKey": "new_account_public_key"
144    /// }
145    /// ```
146    pub async fn send_to_faucet(self, url: &Url) -> Result<Response, FaucetError> {
147        let mut data = std::collections::HashMap::new();
148        data.insert("newAccountId", self.new_account_id.to_string());
149        data.insert("newAccountPublicKey", self.public_key.to_string());
150
151        let client = reqwest::Client::new();
152
153        Ok(client.post(url.clone()).json(&data).send().await?)
154    }
155}
156
157#[derive(Clone, Debug)]
158pub struct CreateAccountFundMyselfTx {
159    prepopulated: PrepopulateTransaction,
160}
161
162#[async_trait::async_trait]
163impl Transactionable for CreateAccountFundMyselfTx {
164    fn prepopulated(&self) -> PrepopulateTransaction {
165        self.prepopulated.clone()
166    }
167
168    async fn validate_with_network(&self, network: &NetworkConfig) -> Result<(), ValidationError> {
169        if self
170            .prepopulated
171            .receiver_id
172            .is_sub_account_of(&self.prepopulated.signer_id)
173        {
174            return Ok(());
175        }
176
177        match &network.linkdrop_account_id {
178            Some(linkdrop) => {
179                if &self.prepopulated.receiver_id != linkdrop {
180                    Err(AccountCreationError::AccountShouldBeSubAccountOfSignerOrLinkdrop)?;
181                }
182            }
183            None => Err(AccountCreationError::LinkdropIsNotDefined)?,
184        }
185
186        Ok(())
187    }
188}
189
190pub type PublicKeyCallback<T, E> = dyn FnOnce(PublicKey) -> Result<T, E>;
191
192pub struct PublicKeyProvider<T, E> {
193    next_step: Box<PublicKeyCallback<T, E>>,
194}
195
196impl<T, E> PublicKeyProvider<T, E> {
197    pub const fn new(next_step: Box<PublicKeyCallback<T, E>>) -> Self {
198        Self { next_step }
199    }
200
201    pub fn public_key(self, pk: impl Into<PublicKey>) -> Result<T, E> {
202        (self.next_step)(pk.into())
203    }
204}