near_api/account/
create.rs

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