near_api/contract.rs
1use std::sync::Arc;
2
3use borsh::BorshDeserialize;
4use near_api_types::{
5 AccountId, Action, CryptoHash, Data, FunctionArgs, NearGas, NearToken, Reference, StoreKey,
6 contract::ContractSourceMetadata,
7 transaction::actions::{
8 DeployContractAction, DeployGlobalContractAction, FunctionCallAction,
9 GlobalContractDeployMode, GlobalContractIdentifier, UseGlobalContractAction,
10 },
11};
12use serde::{Deserialize, Serialize, de::DeserializeOwned};
13
14use crate::{
15 advanced::{query_request::QueryRequest, query_rpc::SimpleQueryRpc},
16 common::{
17 query::{
18 CallResultBorshHandler, CallResultHandler, PostprocessHandler, RequestBuilder,
19 ViewCodeHandler, ViewStateHandler,
20 },
21 send::ExecuteSignedTransaction,
22 utils::to_base64,
23 },
24 errors::BuilderError,
25 signer::Signer,
26 transactions::{ConstructTransaction, SelfActionBuilder, Transaction},
27};
28
29/// Contract-related interactions with the NEAR Protocol
30///
31/// The [`Contract`] struct provides methods to interact with NEAR contracts, including calling functions, querying storage, and deploying contracts.
32///
33/// # Examples
34///
35/// ```rust,no_run
36/// use near_api::*;
37///
38/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
39/// let abi = Contract("some_contract.testnet".parse()?).abi().fetch_from_testnet().await?;
40/// println!("ABI: {:?}", abi);
41/// # Ok(())
42/// # }
43/// ```
44#[derive(Clone, Debug)]
45pub struct Contract(pub AccountId);
46
47impl Contract {
48 /// Prepares a call to a contract function.
49 ///
50 /// This will return a builder that can be used to prepare a query or a transaction.
51 ///
52 /// ## Calling view function `get_number`
53 /// ```rust,no_run
54 /// use near_api::*;
55 ///
56 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
57 /// let number: Data<u64> = Contract("some_contract.testnet".parse()?)
58 /// .call_function("get_number", ())?
59 /// .read_only()
60 /// .fetch_from_testnet()
61 /// .await?;
62 /// println!("Number: {:?}", number);
63 /// # Ok(())
64 /// # }
65 /// ```
66 ///
67 /// ## Calling a state changing function `set_number`
68 /// ```rust,no_run
69 /// use near_api::*;
70 /// use serde_json::json;
71 ///
72 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
73 /// let signer = Signer::new(Signer::from_ledger())?;
74 /// let result = Contract("some_contract.testnet".parse()?)
75 /// .call_function("set_number", json!({ "number": 100 }))?
76 /// .transaction()
77 /// // Optional
78 /// .gas(NearGas::from_tgas(200))
79 /// .with_signer("alice.testnet".parse()?, signer)
80 /// .send_to_testnet()
81 /// .await?;
82 /// # Ok(())
83 /// # }
84 /// ```
85 pub fn call_function<Args>(
86 &self,
87 method_name: &str,
88 args: Args,
89 ) -> Result<CallFunctionBuilder, BuilderError>
90 where
91 Args: serde::Serialize,
92 {
93 let args = serde_json::to_vec(&args)?;
94
95 Ok(CallFunctionBuilder {
96 contract: self.0.clone(),
97 method_name: method_name.to_string(),
98 args,
99 })
100 }
101
102 /// Prepares a transaction to deploy a contract to the provided account.
103 ///
104 /// The code is the wasm bytecode of the contract. For more information on how to compile your contract,
105 /// please refer to the [NEAR documentation](https://docs.near.org/build/smart-contracts/quickstart).
106 ///
107 /// ## Deploying the contract
108 /// ```rust,no_run
109 /// use near_api::*;
110 ///
111 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
112 /// let code = std::fs::read("path/to/your/contract.wasm")?;
113 /// let signer = Signer::new(Signer::from_ledger())?;
114 /// let result = Contract::deploy("contract.testnet".parse()?)
115 /// .use_code(code)
116 /// .without_init_call()
117 /// .with_signer(signer)
118 /// .send_to_testnet()
119 /// .await?;
120 /// # Ok(())
121 /// # }
122 /// ```
123 ///
124 /// ## Deploying the contract with an init call
125 /// ```rust,no_run
126 /// use near_api::*;
127 /// use serde_json::json;
128 ///
129 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
130 /// let code = std::fs::read("path/to/your/contract.wasm")?;
131 /// let signer = Signer::new(Signer::from_ledger())?;
132 /// let result = Contract::deploy("contract.testnet".parse()?)
133 /// .use_code(code)
134 /// .with_init_call("init", json!({ "number": 100 }))?
135 /// // Optional
136 /// .gas(NearGas::from_tgas(200))
137 /// .with_signer(signer)
138 /// .send_to_testnet()
139 /// .await?;
140 /// # Ok(())
141 /// # }
142 /// ```
143 pub const fn deploy(contract: AccountId) -> DeployBuilder {
144 DeployBuilder::new(contract)
145 }
146
147 /// Prepares a transaction to deploy a code to the global contract code storage.
148 ///
149 /// This will allow other users to reference given code as hash or account-id and reduce
150 /// the gas cost for deployment.
151 ///
152 /// Please be aware that the deploy costs 10x more compared to the regular costs and the tokens are burnt
153 /// with no way to get it back.
154 ///
155 /// ## Example deploying a contract to the global contract code storage as hash
156 /// ```rust,no_run
157 /// use near_api::*;
158 ///
159 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
160 /// let code = std::fs::read("path/to/your/contract.wasm")?;
161 /// let signer = Signer::new(Signer::from_ledger())?;
162 /// let result = Contract::deploy_global_contract_code(code)
163 /// .as_hash()
164 /// .with_signer("some-account.testnet".parse()?, signer)
165 /// .send_to_testnet()
166 /// .await?;
167 /// # Ok(())
168 /// # }
169 /// ```
170 ///
171 /// ## Example deploying a contract to the global contract code storage as account-id
172 ///
173 /// The difference between the hash and account-id version is that the account-id version
174 /// upgradable and can be changed.
175 ///
176 /// ```rust,no_run
177 /// use near_api::*;
178 ///
179 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
180 /// let code = std::fs::read("path/to/your/contract.wasm")?;
181 /// let signer = Signer::new(Signer::from_ledger())?;
182 /// let result = Contract::deploy_global_contract_code(code)
183 /// .as_account_id("nft-contract.testnet".parse()?)
184 /// .with_signer(signer)
185 /// .send_to_testnet()
186 /// .await?;
187 /// # Ok(())
188 /// # }
189 /// ```
190 pub const fn deploy_global_contract_code(code: Vec<u8>) -> GlobalDeployBuilder {
191 GlobalDeployBuilder::new(code)
192 }
193
194 /// Prepares a query to fetch the [ABI](near_api_types::abi::AbiRoot) of the contract using the following [standard](https://github.com/near/near-abi-rs).
195 ///
196 /// Please be aware that not all the contracts provide the ABI.
197 ///
198 /// # Example
199 /// ```rust,no_run
200 /// use near_api::*;
201 ///
202 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
203 /// let abi = Contract("some_contract.testnet".parse()?).abi().fetch_from_testnet().await?;
204 /// println!("ABI: {:?}", abi);
205 /// # Ok(())
206 /// # }
207 /// ```
208 pub fn abi(
209 &self,
210 ) -> RequestBuilder<
211 PostprocessHandler<Option<near_api_types::abi::AbiRoot>, CallResultHandler<Vec<u8>>>,
212 > {
213 self.call_function("__contract_abi", ())
214 .expect("arguments are always serializable")
215 .read_only()
216 .map(|data: Data<Vec<u8>>| {
217 serde_json::from_slice(zstd::decode_all(data.data.as_slice()).ok()?.as_slice()).ok()
218 })
219 }
220
221 /// Prepares a query to fetch the wasm code ([Data]<[ContractCodeView](near_api_types::ContractCodeView)>) of the contract.
222 ///
223 /// # Example
224 /// ```rust,no_run
225 /// use near_api::*;
226 ///
227 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
228 /// let wasm = Contract("some_contract.testnet".parse()?).wasm().fetch_from_testnet().await?;
229 /// println!("WASM: {}", wasm.data.code_base64);
230 /// # Ok(())
231 /// # }
232 /// ```
233 pub fn wasm(&self) -> RequestBuilder<ViewCodeHandler> {
234 let request = QueryRequest::ViewCode {
235 account_id: self.0.clone(),
236 };
237
238 RequestBuilder::new(
239 SimpleQueryRpc { request },
240 Reference::Optimistic,
241 ViewCodeHandler,
242 )
243 }
244
245 /// Prepares a query to fetch the storage of the contract ([Data]<[ViewStateResult](near_api_types::ViewStateResult)>) using the given prefix as a filter.
246 ///
247 /// It helpful if you are aware of the storage that you are looking for.
248 ///
249 /// # Example
250 /// ```rust,no_run
251 /// use near_api::*;
252 ///
253 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
254 /// let storage = Contract("some_contract.testnet".parse()?)
255 /// .view_storage_with_prefix(b"se")
256 /// .fetch_from_testnet()
257 /// .await?;
258 /// println!("Storage: {:?}", storage);
259 /// # Ok(())
260 /// # }
261 /// ```
262 pub fn view_storage_with_prefix(&self, prefix: &[u8]) -> RequestBuilder<ViewStateHandler> {
263 let request = QueryRequest::ViewState {
264 account_id: self.0.clone(),
265 prefix_base64: StoreKey(to_base64(prefix)),
266 include_proof: Some(false),
267 };
268
269 RequestBuilder::new(
270 SimpleQueryRpc { request },
271 Reference::Optimistic,
272 ViewStateHandler,
273 )
274 }
275
276 /// Prepares a query to fetch the storage of the contract ([Data]<[ViewStateResult](near_api_types::ViewStateResult)>).
277 ///
278 /// Please be aware that large storage queries might fail.
279 ///
280 /// # Example
281 /// ```rust,no_run
282 /// use near_api::*;
283 ///
284 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
285 /// let storage = Contract("some_contract.testnet".parse()?)
286 /// .view_storage()
287 /// .fetch_from_testnet()
288 /// .await?;
289 /// println!("Storage: {:?}", storage);
290 /// # Ok(())
291 /// # }
292 /// ```
293 pub fn view_storage(&self) -> RequestBuilder<ViewStateHandler> {
294 self.view_storage_with_prefix(&[])
295 }
296
297 /// Prepares a query to fetch the contract source metadata([Data]<[ContractSourceMetadata]>) using [NEP-330](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) standard.
298 ///
299 /// The contract source metadata is a standard interface that allows auditing and viewing source code for a deployed smart contract.
300 /// Implementation of this standard is purely optional but is recommended for developers whose contracts are open source.
301 ///
302 /// # Examples
303 ///
304 /// ```rust,no_run
305 /// use near_api::*;
306 ///
307 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
308 /// let source_metadata = Contract("some_contract.testnet".parse()?)
309 /// .contract_source_metadata()
310 /// .fetch_from_testnet()
311 /// .await?;
312 /// println!("Source metadata: {:?}", source_metadata);
313 /// # Ok(())
314 /// # }
315 /// ```
316 /// A more verbose runnable example is present in `examples/contract_source_metadata.rs`:
317 /// ```rust,no_run
318 #[doc = include_str!("../examples/contract_source_metadata.rs")]
319 /// ```
320 pub fn contract_source_metadata(
321 &self,
322 ) -> RequestBuilder<CallResultHandler<ContractSourceMetadata>> {
323 self.call_function("contract_source_metadata", ())
324 .expect("arguments are always serializable")
325 .read_only()
326 }
327}
328
329#[derive(Clone, Debug)]
330pub struct DeployBuilder {
331 pub contract: AccountId,
332}
333
334impl DeployBuilder {
335 pub const fn new(contract: AccountId) -> Self {
336 Self { contract }
337 }
338
339 /// Prepares a transaction to deploy a contract to the provided account
340 ///
341 /// The code is the wasm bytecode of the contract. For more information on how to compile your contract,
342 /// please refer to the [NEAR documentation](https://docs.near.org/build/smart-contracts/quickstart).
343 ///
344 /// # Example
345 /// ```rust,no_run
346 /// use near_api::*;
347 ///
348 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
349 /// let code = std::fs::read("path/to/your/contract.wasm")?;
350 /// let signer = Signer::new(Signer::from_ledger())?;
351 /// let result = Contract::deploy("contract.testnet".parse()?)
352 /// .use_code(code)
353 /// .without_init_call()
354 /// .with_signer(signer)
355 /// .send_to_testnet()
356 /// .await?;
357 /// # Ok(())
358 /// # }
359 pub fn use_code(self, code: Vec<u8>) -> SetDeployActionBuilder {
360 SetDeployActionBuilder::new(
361 self.contract,
362 Action::DeployContract(DeployContractAction { code }),
363 )
364 }
365
366 // /// Prepares a transaction to deploy a contract to the provided account using a immutable hash reference to the code from the global contract code storage.
367 // ///
368 // /// # Example
369 // /// ```rust,no_run
370 // /// use near_api::*;
371 // ///
372 // /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
373 // /// let signer = Signer::new(Signer::from_ledger())?;
374 // /// let result = Contract::deploy("contract.testnet".parse()?)
375 // /// .use_global_hash("DxfRbrjT3QPmoANMDYTR6iXPGJr7xRUyDnQhcAWjcoFF".parse()?)
376 // /// .without_init_call()
377 // /// .with_signer(signer)
378 // /// .send_to_testnet()
379 // /// .await?;
380 // /// # Ok(())
381 // /// # }
382 pub fn use_global_hash(self, global_hash: CryptoHash) -> SetDeployActionBuilder {
383 SetDeployActionBuilder::new(
384 self.contract,
385 Action::UseGlobalContract(Box::new(UseGlobalContractAction {
386 contract_identifier: GlobalContractIdentifier::CodeHash(global_hash),
387 })),
388 )
389 }
390
391 // /// Prepares a transaction to deploy a contract to the provided account using a mutable account-id reference to the code from the global contract code storage.
392 // ///
393 // /// Please note that you have to trust the account-id that you are providing. As the code is mutable, the owner of the referenced account can
394 // /// change the code at any time which might lead to unexpected behavior or malicious activity.
395 // ///
396 // /// # Example
397 // /// ```rust,no_run
398 // /// use near_api::*;
399 // ///
400 // /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
401 // /// let signer = Signer::new(Signer::from_ledger())?;
402 // /// let result = Contract::deploy("contract.testnet".parse()?)
403 // /// .use_global_account_id("nft-contract.testnet".parse()?)
404 // /// .without_init_call()
405 // /// .with_signer(signer)
406 // /// .send_to_testnet()
407 // /// .await?;
408 // /// # Ok(())
409 // /// # }
410 pub fn use_global_account_id(self, global_account_id: AccountId) -> SetDeployActionBuilder {
411 SetDeployActionBuilder::new(
412 self.contract,
413 Action::UseGlobalContract(Box::new(UseGlobalContractAction {
414 contract_identifier: GlobalContractIdentifier::AccountId(global_account_id),
415 })),
416 )
417 }
418}
419
420#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
421pub struct SetDeployActionBuilder {
422 contract: AccountId,
423 deploy_action: Action,
424}
425
426impl SetDeployActionBuilder {
427 pub const fn new(contract: AccountId, deploy_action: Action) -> Self {
428 Self {
429 contract,
430 deploy_action,
431 }
432 }
433
434 /// Prepares a transaction to deploy a contract to the provided account without an init call.
435 ///
436 /// This will deploy the contract without calling any function.
437 pub fn without_init_call(self) -> ConstructTransaction {
438 Transaction::construct(self.contract.clone(), self.contract).add_action(self.deploy_action)
439 }
440
441 /// Prepares a transaction to deploy a contract to the provided account with an init call.
442 ///
443 /// This will deploy the contract and call the init function with the provided arguments as a single transaction.
444 pub fn with_init_call<Args: Serialize>(
445 self,
446 method_name: &str,
447 args: Args,
448 ) -> Result<SetDeployActionWithInitCallBuilder, BuilderError> {
449 let args = serde_json::to_vec(&args)?;
450
451 Ok(SetDeployActionWithInitCallBuilder::new(
452 self.contract.clone(),
453 method_name.to_string(),
454 args,
455 self.deploy_action,
456 ))
457 }
458}
459
460#[derive(Clone, Debug)]
461pub struct SetDeployActionWithInitCallBuilder {
462 contract: AccountId,
463 method_name: String,
464 args: Vec<u8>,
465 deploy_action: Action,
466 gas: Option<NearGas>,
467 deposit: Option<NearToken>,
468}
469
470impl SetDeployActionWithInitCallBuilder {
471 const fn new(
472 contract: AccountId,
473 method_name: String,
474 args: Vec<u8>,
475 deploy_action: Action,
476 ) -> Self {
477 Self {
478 contract,
479 method_name,
480 args,
481 deploy_action,
482 gas: None,
483 deposit: None,
484 }
485 }
486
487 /// Specify the gas limit for the transaction. By default it is set to 100 TGas.
488 pub const fn gas(mut self, gas: NearGas) -> Self {
489 self.gas = Some(gas);
490 self
491 }
492
493 /// Specify the near deposit for the transaction. By default it is set to 0.
494 ///
495 /// Please note that the method should be [`payable`](https://docs.near.org/build/smart-contracts/anatomy/functions#payable-functions) in the contract to accept the deposit.
496 /// Otherwise the transaction will fail.
497 pub const fn deposit(mut self, deposit: NearToken) -> Self {
498 self.deposit = Some(deposit);
499 self
500 }
501
502 /// Specify the signer for the transaction. This will wrap-up the process of the preparing transaction.
503 ///
504 /// This will return the [`ExecuteSignedTransaction`] that can be used to sign and send the transaction to the network.
505 pub fn with_signer(self, signer: Arc<Signer>) -> ExecuteSignedTransaction {
506 let gas = self.gas.unwrap_or_else(|| NearGas::from_tgas(100));
507 let deposit = self.deposit.unwrap_or_else(|| NearToken::from_yoctonear(0));
508
509 Transaction::construct(self.contract.clone(), self.contract)
510 .add_action(self.deploy_action)
511 .add_action(Action::FunctionCall(Box::new(FunctionCallAction {
512 method_name: self.method_name.to_owned(),
513 args: self.args,
514 gas,
515 deposit,
516 })))
517 .with_signer(signer)
518 }
519}
520
521#[derive(Clone, Debug)]
522pub struct GlobalDeployBuilder {
523 code: Vec<u8>,
524}
525
526impl GlobalDeployBuilder {
527 pub const fn new(code: Vec<u8>) -> Self {
528 Self { code }
529 }
530
531 /// Prepares a transaction to deploy a code to the global contract code storage and reference it by hash.
532 ///
533 /// The code is immutable and cannot be changed once deployed.
534 ///
535 /// # Example
536 /// ```rust,no_run
537 /// use near_api::*;
538 ///
539 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
540 /// let code = std::fs::read("path/to/your/contract.wasm")?;
541 /// let signer = Signer::new(Signer::from_ledger())?;
542 /// let result = Contract::deploy_global_contract_code(code)
543 /// .as_hash()
544 /// .with_signer("some-account.testnet".parse()?, signer)
545 /// .send_to_testnet()
546 /// .await?;
547 /// # Ok(())
548 /// # }
549 #[allow(clippy::wrong_self_convention)]
550 pub fn as_hash(self) -> SelfActionBuilder {
551 SelfActionBuilder::new().add_action(Action::DeployGlobalContract(
552 DeployGlobalContractAction {
553 code: self.code,
554 deploy_mode: GlobalContractDeployMode::CodeHash,
555 },
556 ))
557 }
558
559 /// Prepares a transaction to deploy a code to the global contract code storage and reference it by account-id.
560 ///
561 /// You would be able to change the code later on.
562 /// Please note that every subsequent upgrade will charge full deployment cost.
563 ///
564 /// # Example
565 /// ```rust,no_run
566 /// use near_api::*;
567 ///
568 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
569 /// let code = std::fs::read("path/to/your/contract.wasm")?;
570 /// let signer = Signer::new(Signer::from_ledger())?;
571 /// let result = Contract::deploy_global_contract_code(code)
572 /// .as_account_id("some-account.testnet".parse()?)
573 /// .with_signer(signer)
574 /// .send_to_testnet()
575 /// .await?;
576 /// # Ok(())
577 /// # }
578 #[allow(clippy::wrong_self_convention)]
579 pub fn as_account_id(self, signer_id: AccountId) -> ConstructTransaction {
580 Transaction::construct(signer_id.clone(), signer_id).add_action(
581 Action::DeployGlobalContract(DeployGlobalContractAction {
582 code: self.code,
583 deploy_mode: GlobalContractDeployMode::AccountId,
584 }),
585 )
586 }
587}
588
589#[derive(Clone, Debug)]
590pub struct CallFunctionBuilder {
591 contract: AccountId,
592 method_name: String,
593 args: Vec<u8>,
594}
595
596impl CallFunctionBuilder {
597 /// Prepares a read-only query that doesn't require a signing transaction.
598 ///
599 /// ## Example
600 /// ```rust,no_run
601 /// use near_api::*;
602 ///
603 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
604 /// let balance: Data<u64> = Contract("some_contract.testnet".parse()?).call_function("get_balance", ())?.read_only().fetch_from_testnet().await?;
605 /// println!("Balance: {:?}", balance);
606 ///
607 /// let balance_at_block: Data<u64> = Contract("some_contract.testnet".parse()?).call_function("get_balance", ())?.read_only().at(Reference::AtBlock(1000000)).fetch_from_testnet().await?;
608 /// println!("Balance at block 1000000: {:?}", balance_at_block);
609 /// # Ok(())
610 /// # }
611 /// ```
612 pub fn read_only<Response: Send + Sync + DeserializeOwned>(
613 self,
614 ) -> RequestBuilder<CallResultHandler<Response>> {
615 let request = QueryRequest::CallFunction {
616 account_id: self.contract,
617 method_name: self.method_name,
618 args_base64: FunctionArgs(to_base64(&self.args)),
619 };
620
621 RequestBuilder::new(
622 SimpleQueryRpc { request },
623 Reference::Optimistic,
624 CallResultHandler::<Response>::new(),
625 )
626 }
627
628 /// Prepares a read-only query that deserializes the response using Borsh instead of JSON.
629 ///
630 /// This method is useful when the contract returns Borsh-encoded data instead of JSON.
631 ///
632 /// ## Example
633 /// ```rust,no_run
634 /// use near_api::*;
635 ///
636 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
637 /// let value: Data<u64> = Contract("some_contract.testnet".parse()?)
638 /// .call_function("get_number", ())?
639 /// .read_only_borsh()
640 /// .fetch_from_testnet()
641 /// .await?;
642 /// println!("Value: {:?}", value);
643 /// # Ok(())
644 /// # }
645 /// ```
646 pub fn read_only_borsh<Response: Send + Sync + BorshDeserialize>(
647 self,
648 ) -> RequestBuilder<CallResultBorshHandler<Response>> {
649 let request = QueryRequest::CallFunction {
650 account_id: self.contract,
651 method_name: self.method_name,
652 args_base64: FunctionArgs(to_base64(&self.args)),
653 };
654
655 RequestBuilder::new(
656 SimpleQueryRpc { request },
657 Reference::Optimistic,
658 CallResultBorshHandler::<Response>::new(),
659 )
660 }
661
662 /// Prepares a transaction that will call a contract function leading to a state change.
663 ///
664 /// This will require a signer to be provided and gas to be paid.
665 pub fn transaction(self) -> ContractTransactBuilder {
666 ContractTransactBuilder::new(self.contract, self.method_name, self.args)
667 }
668}
669
670#[derive(Clone, Debug)]
671pub struct ContractTransactBuilder {
672 contract: AccountId,
673 method_name: String,
674 args: Vec<u8>,
675 gas: Option<NearGas>,
676 deposit: Option<NearToken>,
677}
678
679impl ContractTransactBuilder {
680 const fn new(contract: AccountId, method_name: String, args: Vec<u8>) -> Self {
681 Self {
682 contract,
683 method_name,
684 args,
685 gas: None,
686 deposit: None,
687 }
688 }
689
690 /// Specify the gas limit for the transaction. By default it is set to 100 TGas.
691 pub const fn gas(mut self, gas: NearGas) -> Self {
692 self.gas = Some(gas);
693 self
694 }
695
696 /// Specify the near deposit for the transaction. By default it is set to 0.
697 ///
698 /// Please note that the method should be [`payable`](https://docs.near.org/build/smart-contracts/anatomy/functions#payable-functions) in the contract to accept the deposit.
699 /// Otherwise the transaction will fail.
700 pub const fn deposit(mut self, deposit: NearToken) -> Self {
701 self.deposit = Some(deposit);
702 self
703 }
704
705 /// Specify the signer for the transaction. This will wrap-up the process of the preparing transaction.
706 ///
707 /// This will return the [`ExecuteSignedTransaction`] that can be used to sign and send the transaction to the network.
708 pub fn with_signer(
709 self,
710 signer_id: AccountId,
711 signer: Arc<Signer>,
712 ) -> ExecuteSignedTransaction {
713 self.with_signer_account(signer_id).with_signer(signer)
714 }
715
716 // Re-used by stake.rs and tokens.rs as we do have extra signer_id context, but we don't need there a signer
717 pub(crate) fn with_signer_account(self, signer_id: AccountId) -> ConstructTransaction {
718 let gas = self.gas.unwrap_or_else(|| NearGas::from_tgas(100));
719 let deposit = self.deposit.unwrap_or_else(|| NearToken::from_yoctonear(0));
720
721 Transaction::construct(signer_id, self.contract).add_action(Action::FunctionCall(Box::new(
722 FunctionCallAction {
723 method_name: self.method_name.to_owned(),
724 args: self.args,
725 gas,
726 deposit,
727 },
728 )))
729 }
730}