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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
use near_account_id::AccountId;
use near_crypto::{PublicKey, Signer};
use near_gas::NearGas;
use near_primitives::account::AccessKey;
use near_primitives::borsh;
use near_primitives::hash::CryptoHash;
use near_primitives::transaction::{
Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
};
use near_primitives::views::FinalExecutionOutcomeView;
use near_token::NearToken;
use crate::signer::ExposeAccountId;
use crate::{Client, Error, Result};
pub const MAX_GAS: NearGas = NearGas::from_tgas(300);
pub const DEFAULT_CALL_FN_GAS: NearGas = NearGas::from_tgas(10);
pub const DEFAULT_CALL_DEPOSIT: NearToken = NearToken::from_near(0);
/// A set of arguments we can provide to a transaction, containing
/// the function name, arguments, the amount of gas to use and deposit.
#[derive(Debug)]
pub struct Function {
pub(crate) name: String,
pub(crate) args: Result<Vec<u8>>,
pub(crate) deposit: NearToken,
pub(crate) gas: NearGas,
}
impl Function {
/// Initialize a new instance of [`Function`], tied to a specific function on a
/// contract that lives directly on a contract we've specified in [`Transaction`].
pub fn new(name: &str) -> Self {
Self {
name: name.into(),
args: Ok(vec![]),
deposit: DEFAULT_CALL_DEPOSIT,
gas: DEFAULT_CALL_FN_GAS,
}
}
/// Provide the arguments for the call. These args are serialized bytes from either
/// a JSON or Borsh serializable set of arguments. To use the more specific versions
/// with better quality of life, use `args_json` or `args_borsh`.
pub fn args(mut self, args: Vec<u8>) -> Self {
if self.args.is_err() {
return self;
}
self.args = Ok(args);
self
}
/// Similar to `args`, specify an argument that is JSON serializable and can be
/// accepted by the equivalent contract. Recommend to use something like
/// `serde_json::json!` macro to easily serialize the arguments.
pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
match serde_json::to_vec(&args) {
Ok(args) => self.args = Ok(args),
Err(e) => self.args = Err(Error::Serialization(e)),
}
self
}
/// Similar to `args`, specify an argument that is borsh serializable and can be
/// accepted by the equivalent contract.
pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
match args.try_to_vec() {
Ok(args) => self.args = Ok(args),
Err(e) => self.args = Err(Error::Io(e)),
}
self
}
/// Specify the amount of tokens to be deposited where `deposit` is the amount of
/// tokens in yocto near.
pub fn deposit(mut self, deposit: NearToken) -> Self {
self.deposit = deposit;
self
}
/// Specify the amount of gas to be used.
pub fn gas(mut self, gas: NearGas) -> Self {
self.gas = gas;
self
}
/// Use the maximum amount of gas possible to perform this function call into the contract.
pub fn max_gas(self) -> Self {
self.gas(MAX_GAS)
}
pub(crate) fn into_action(self) -> Result<FunctionCallAction> {
Ok(FunctionCallAction {
args: self.args?,
method_name: self.name,
gas: self.gas.as_gas(),
deposit: self.deposit.as_yoctonear(),
})
}
}
pub struct FunctionCallTransaction<'a, S> {
pub(crate) client: &'a Client,
pub(crate) signer: S,
pub(crate) receiver_id: AccountId,
pub(crate) function: Function,
}
impl<S> FunctionCallTransaction<'_, S> {
/// Provide the arguments for the call. These args are serialized bytes from either
/// a JSON or Borsh serializable set of arguments. To use the more specific versions
/// with better quality of life, use `args_json` or `args_borsh`.
pub fn args(mut self, args: Vec<u8>) -> Self {
self.function = self.function.args(args);
self
}
/// Similar to `args`, specify an argument that is JSON serializable and can be
/// accepted by the equivalent contract. Recommend to use something like
/// `serde_json::json!` macro to easily serialize the arguments.
pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
self.function = self.function.args_json(args);
self
}
/// Similar to `args`, specify an argument that is borsh serializable and can be
/// accepted by the equivalent contract.
pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
self.function = self.function.args_borsh(args);
self
}
/// Specify the amount of tokens to be deposited where `deposit` is the amount of
/// tokens in yocto near.
pub fn deposit(mut self, deposit: NearToken) -> Self {
self.function = self.function.deposit(deposit);
self
}
/// Specify the amount of gas to be used.
pub fn gas(mut self, gas: NearGas) -> Self {
self.function = self.function.gas(gas);
self
}
/// Use the maximum amount of gas possible to perform this function call into the contract.
pub fn max_gas(self) -> Self {
self.gas(MAX_GAS)
}
}
impl<'a, S> FunctionCallTransaction<'a, S>
where
S: Signer + ExposeAccountId + 'static,
{
/// Process the transaction, and return the result of the execution.
pub async fn transact(self) -> Result<FinalExecutionOutcomeView> {
self.client
.send_tx(
&self.signer,
&self.receiver_id,
vec![self.function.into_action()?.into()],
)
.await
}
/// Send the transaction to the network to be processed. This will be done asynchronously
/// without waiting for the transaction to complete. This returns us a [`TransactionStatus`]
/// for which we can call into [`status`] and/or `.await` to retrieve info about whether
/// the transaction has been completed or not. Note that `.await` will wait till completion
/// of the transaction.
pub async fn transact_async(self) -> Result<CryptoHash> {
self.client
.send_tx_async(
&self.signer,
&self.receiver_id,
vec![self.function.into_action()?.into()],
)
.await
}
}
/// A builder-like object that will allow specifying various actions to be performed
/// in a single transaction. For details on each of the actions, find them in
/// [NEAR transactions](https://docs.near.org/docs/concepts/transaction).
///
/// All actions are performed on the account specified by `receiver_id`.
pub struct Transaction<'a, S> {
client: &'a Client,
signer: S,
receiver_id: AccountId,
// Result used to defer errors in argument parsing to later when calling into transact
actions: Result<Vec<Action>>,
}
impl<'a, S> Transaction<'a, S>
where
S: Signer + ExposeAccountId + 'static,
{
pub(crate) fn new(client: &'a Client, signer: S, receiver_id: AccountId) -> Self {
Self {
client,
signer,
receiver_id,
actions: Ok(Vec::new()),
}
}
/// Process the transaction, and return the result of the execution.
pub async fn transact(self) -> Result<FinalExecutionOutcomeView> {
self.client
.send_tx(&self.signer, &self.receiver_id, self.actions?)
.await
}
/// Send the transaction to the network to be processed. This will be done asynchronously
/// without waiting for the transaction to complete. This returns us a [`TransactionStatus`]
/// for which we can call into [`status`] and/or `.await` to retrieve info about whether
/// the transaction has been completed or not. Note that `.await` will wait till completion
/// of the transaction.
///
/// [`status`]: TransactionStatus::status
pub async fn transact_async(self) -> Result<CryptoHash> {
self.client
.send_tx_async(&self.signer, &self.receiver_id, self.actions?)
.await
}
}
impl<S> Transaction<'_, S> {
/// Adds a key to the `receiver_id`'s account, where the public key can be used
/// later to delete the same key.
pub fn add_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
AddKeyAction {
public_key: pk.into(),
access_key: ak.into(),
}
.into(),
);
}
self
}
/// Call into the `receiver_id`'s contract with the specific function arguments.
pub fn call(mut self, function: Function) -> Self {
let args = match function.args {
Ok(args) => args,
Err(err) => {
self.actions = Err(err);
return self;
}
};
if let Ok(actions) = &mut self.actions {
actions.push(Action::FunctionCall(FunctionCallAction {
method_name: function.name.to_string(),
args,
deposit: function.deposit.as_yoctonear(),
gas: function.gas.as_gas(),
}));
}
self
}
/// Create a new account with the account id being `receiver_id`.
pub fn create_account(mut self) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(CreateAccountAction {}.into());
}
self
}
/// Deletes the `receiver_id`'s account. The beneficiary specified by
/// `beneficiary_id` will receive the funds of the account deleted.
pub fn delete_account(mut self, beneficiary_id: &AccountId) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
DeleteAccountAction {
beneficiary_id: beneficiary_id.clone(),
}
.into(),
);
}
self
}
/// Deletes a key from the `receiver_id`'s account, where the public key is
/// associated with the access key to be deleted.
pub fn delete_key(mut self, pk: PublicKey) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(DeleteKeyAction { public_key: pk }.into());
}
self
}
/// Deploy contract code or WASM bytes to the `receiver_id`'s account.
pub fn deploy(mut self, code: &[u8]) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(DeployContractAction { code: code.into() }.into());
}
self
}
/// An action which stakes the signer's tokens and setups a validator public key.
pub fn stake(mut self, stake: NearToken, pk: PublicKey) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
StakeAction {
stake: stake.as_yoctonear(),
public_key: pk,
}
.into(),
);
}
self
}
/// Transfer `deposit` amount from `signer`'s account into `receiver_id`'s account.
pub fn transfer(mut self, deposit: NearToken) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
TransferAction {
deposit: deposit.as_yoctonear(),
}
.into(),
);
}
self
}
}
impl Client {
/// Start calling into a contract on a specific function. Returns a [`FunctionCallTransaction`]
/// object where we can use to add more parameters such as the arguments, deposit, and gas.
pub fn call<S: Signer + ExposeAccountId>(
&self,
signer: S,
contract_id: &AccountId,
function: &str,
) -> FunctionCallTransaction<'_, S> {
FunctionCallTransaction {
client: self,
signer,
receiver_id: contract_id.clone(),
function: Function::new(function),
}
}
/// Start a batch transaction. Returns a [`Transaction`] object that we can
/// use to add Actions to the batched transaction. Call `transact` to send
/// the batched transaction to the network.
pub fn batch<S: Signer + ExposeAccountId + 'static>(
&self,
signer: S,
contract_id: &AccountId,
function: &str,
) -> Transaction<'_, S> {
Transaction::new(self, signer, contract_id.clone()).call(Function::new(function))
}
}