Transfers & Actions
This page describes the different types of actions that a smart contract can perform on NEAR like transferring NEAR, calling other contracts, creating sub-accounts, and deploying contracts. It also explains how to add access keys to accounts.
Smart contracts can perform specific Actions such as transferring NEAR, or calling other contracts.
An important property of Actions is that they can be batched together when acting on the same contract. Batched actions act as a unit: they execute in the same receipt, and if any fails, then they all get reverted.
Actions can be batched only when they act on the same contract. You can batch calling two methods on a contract,
but cannot call two methods on different contracts.
Transfer NEAR Ⓝ
You can send $NEAR from your contract to any other account on the network. The Gas cost for transferring $NEAR is fixed and is based on the protocol's genesis config. Currently, it costs ~0.45 TGas.
- 🦀 Rust
- 🌐 JavaScript
- 🐍 Python
- 🐹 GO
use near_sdk::{near, AccountId, Promise, NearToken};
#[near(contract_state)]
#[derive(Default)]
pub struct Contract { }
#[near]
impl Contract {
pub fn transfer(&self, to: AccountId, amount: NearToken){
Promise::new(to).transfer(amount);
}
}
import { NearBindgen, NearPromise, call } from 'near-sdk-js'
import { AccountId } from 'near-sdk-js/lib/types'
@NearBindgen({})
class Contract{
@call({})
transfer({ to, amount }: { to: AccountId, amount: bigint }) {
return NearPromise.new(to).transfer(amount);
}
}
from near_sdk_py import call, Contract
from near_sdk_py.promises import Promise
class ExampleContract(Contract):
@call
def transfer(self, to, amount):
"""Transfers NEAR to another account"""
# Create a promise to transfer NEAR
return Promise.create_batch(to).transfer(amount)
package main
import (
"github.com/vlmoon99/near-sdk-go/promise"
"github.com/vlmoon99/near-sdk-go/types"
)
// @contract:state
type Contract struct{}
type TransferTokenInput struct {
To string `json:"to"`
Amount string `json:"amount"`
}
// @contract:payable min_deposit=1NEAR
func (c *Contract) ExampleTransferToken(input TransferTokenInput) error {
amount, err := types.U128FromString(input.Amount)
if err != nil {
return err
}
promise.CreateBatch(input.To).
Transfer(amount)
return nil
}
The only case where a transfer will fail is if the receiver account does not exist.
Remember that your balance is used to cover for the contract's storage. When sending money, make sure you always leave enough to cover for future storage needs.
Function Call
Your smart contract can call methods in another contract. In the snippet below we call a method in a deployed Hello NEAR contract, and check if everything went right in the callback.
- 🦀 Rust
- 🌐 JavaScript
- 🐍 Python
- 🐹 GO
use near_sdk::{near, env, log, Promise, Gas, PromiseError};
use serde_json::json;
#[near(contract_state)]
#[derive(Default)]
pub struct Contract { }
const HELLO_NEAR: &str = "hello-nearverse.testnet";
const NO_DEPOSIT: u128 = 0;
const CALL_GAS: Gas = Gas(5_000_000_000_000);
#[near]
impl Contract {
pub fn call_method(&self){
let args = json!({ "message": "howdy".to_string() })
.to_string().into_bytes().to_vec();
Promise::new(HELLO_NEAR.parse().unwrap())
.function_call("set_greeting".to_string(), args, NO_DEPOSIT, CALL_GAS)
.then(
Promise::new(env::current_account_id())
.function_call("callback".to_string(), Vec::new(), NO_DEPOSIT, CALL_GAS)
);
}
pub fn callback(&self, #[callback_result] result: Result<(), PromiseError>){
if result.is_err(){
log!("Something went wrong")
}else{
log!("Message changed")
}
}
}
import { NearBindgen, near, call, bytes, NearPromise } from 'near-sdk-js'
import { AccountId } from 'near-sdk-js/lib/types'
const HELLO_NEAR: AccountId = "hello-nearverse.testnet";
const NO_DEPOSIT: bigint = BigInt(0);
const CALL_GAS: bigint = BigInt("10000000000000");
@NearBindgen({})
class Contract {
@call({})
call_method({}): NearPromise {
const args = bytes(JSON.stringify({ message: "howdy" }))
return NearPromise.new(HELLO_NEAR)
.functionCall("set_greeting", args, NO_DEPOSIT, CALL_GAS)
.then(
NearPromise.new(near.currentAccountId())
.functionCall("callback", bytes(JSON.stringify({})), NO_DEPOSIT, CALL_GAS)
)
.asReturn()
}
@call({privateFunction: true})
callback({}): boolean {
let result, success;
try{ result = near.promiseResult(0); success = true }
catch{ result = undefined; success = false }
if (success) {
near.log(`Success!`)
return true
} else {
near.log("Promise failed...")
return false
}
}
}
from near_sdk_py import call, callback, Contract
from near_sdk_py.promises import CrossContract, Promise, PromiseResult
from near_sdk_py.constants import ONE_TGAS
# Constants
HELLO_NEAR = "hello-nearverse.testnet"
NO_DEPOSIT = 0
CALL_GAS = 10 * ONE_TGAS
class ExampleContract(Contract):
@call
def call_method(self):
"""Call a method on an external contract with a callback"""
# Create a contract reference
hello = CrossContract(HELLO_NEAR)
# Call the external contract and use our callback to process the result
return hello.call("set_greeting", {"message": "howdy"}).then("callback")
@callback
def callback(self, result: PromiseResult):
"""Handle the result of the external contract call"""
# The @callback decorator automatically handles success/failure checking
if not result.success:
# The remote call failed
self.log_error("Promise failed...")
return False
# The remote call succeeded
self.log_info("Success!")
return True
package main
import (
"strconv"
"github.com/vlmoon99/near-sdk-go/env"
"github.com/vlmoon99/near-sdk-go/promise"
"github.com/vlmoon99/near-sdk-go/types"
)
// @contract:state
type Contract struct{}
type MessageInput struct {
Message string `json:"message"`
}
// @contract:payable min_deposit=0.00001NEAR
func (c *Contract) ExampleFunctionCall() {
gas := uint64(types.ONE_TERA_GAS * 10)
accountId := "hello-nearverse.testnet"
args := map[string]string{
"message": "howdy",
}
promise.NewCrossContract(accountId).
Gas(gas).
Call("set_greeting", args).
Then("example_function_call_callback", args)
}
// @contract:view
// @contract:promise_callback
func (c *Contract) ExampleFunctionCallCallback(input MessageInput, result promise.PromiseResult) MessageInput {
env.LogString("Executing callback")
env.LogString("Input Message : " + input.Message)
if result.Success {
env.LogString("Cross-contract call executed successfully")
env.LogString("Promise Result Status --> " + strconv.FormatInt(int64(result.StatusCode), 10))
if len(result.Data) > 0 {
env.LogString("Batch call data: " + string(result.Data))
}
} else {
env.LogString("Cross-contract call failed")
}
return input
}
The snippet showed above is a low level way of calling other methods. We recommend make calls to other contracts as explained in the Cross-contract Calls section.
Create a Sub Account
Your contract can create direct sub accounts of itself, for example, user.near can create sub.user.near.
Accounts do NOT have control over their sub-accounts, since they have their own keys.
Sub-accounts are simply useful for organizing your accounts (e.g. dao.project.near, token.project.near).
- 🦀 Rust
- 🌐 JavaScript
- 🐍 Python
- 🐹 GO
use near_sdk::{near, env, Promise, NearToken};
#[near(contract_state)]
#[derive(Default)]
pub struct Contract { }
const MIN_STORAGE: NearToken = NearToken::from_millinear(1); //0.001Ⓝ
#[near]
impl Contract {
pub fn create(&mut self, prefix: String) {
let account_id = prefix + "." + &env::current_account_id().to_string();
Promise::new(account_id.parse().unwrap())
.create_account()
.transfer(MIN_STORAGE);
}
}
import { NearBindgen, near, call, NearPromise } from 'near-sdk-js'
const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ
@NearBindgen({})
class Contract {
@call({payableFunction:true})
create({prefix}:{prefix: String}) {
const account_id = `${prefix}.${near.currentAccountId()}`
NearPromise.new(account_id)
.createAccount()
.transfer(MIN_STORAGE)
}
}
from near_sdk_py import call, Contract
from near_sdk_py.promises import Promise
from near_sdk_py.constants import ONE_NEAR
# Minimum amount needed for storage
MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ
class ExampleContract(Contract):
@call
def create(self, prefix):
"""Create a subaccount"""
# Generate the new account ID
account_id = f"{prefix}.{self.current_account_id}"
# Create the new account and transfer some NEAR for storage
return Promise.create_batch(account_id)\
.create_account()\
.transfer(MIN_STORAGE)
package main
import (
"github.com/vlmoon99/near-sdk-go/env"
"github.com/vlmoon99/near-sdk-go/promise"
"github.com/vlmoon99/near-sdk-go/types"
)
// @contract:state
type Contract struct{}
// @contract:payable min_deposit=0.001NEAR
func (c *Contract) ExampleCreateSubaccount(prefix string) {
currentAccountId, err := env.GetCurrentAccountId()
if err != nil {
env.PanicStr("Failed to get current account")
}
subaccountId := prefix + "." + currentAccountId
amount, err := types.U128FromString("1000000000000000000000") //0.001Ⓝ
if err != nil {
env.PanicStr("Bad amount format")
}
promise.CreateBatch(subaccountId).
CreateAccount().
Transfer(amount)
}
Notice that in the snippet we are transferring some money to the new account for storage
When you create an account from within a contract, it has no keys by default. If you don't explicitly add keys to it or deploy a contract on creation then it will be locked.
Creating .testnet / .near Accounts
Accounts can only create immediate sub-accounts of themselves.
If your contract wants to create a .mainnet or .testnet account, then it needs to call
the create_account method of near or testnet root contracts.
- 🦀 Rust
- 🌐 JavaScript
- 🐍 Python
- 🐹 GO
use near_sdk::{near, Promise, Gas, NearToken };
use serde_json::json;
#[near(contract_state)]
#[derive(Default)]
pub struct Contract { }
const CALL_GAS: Gas = Gas::from_gas(28_000_000_000_000);
const MIN_STORAGE: NearToken = NearToken::from_yoctonear(1_820_000_000_000_000_000_000); //0.00182Ⓝ
#[near]
impl Contract {
pub fn create_account(&mut self, account_id: String, public_key: String){
let args = json!({
"new_account_id": account_id,
"new_public_key": public_key,
}).to_string().into_bytes().to_vec();
// Use "near" to create mainnet accounts
Promise::new("testnet".parse().unwrap())
.function_call("create_account".to_string(), args, MIN_STORAGE, CALL_GAS);
}
}
import { NearBindgen, near, call, bytes, NearPromise } from 'near-sdk-js'
const MIN_STORAGE: bigint = BigInt("1820000000000000000000"); //0.00182Ⓝ
const CALL_GAS: bigint = BigInt("28000000000000");
@NearBindgen({})
class Contract {
@call({})
create_account({account_id, public_key}:{account_id: String, public_key: String}) {
const args = bytes(JSON.stringify({
"new_account_id": account_id,
"new_public_key": public_key
}))
NearPromise.new("testnet")
.functionCall("create_account", args, MIN_STORAGE, CALL_GAS);
}
}
from near_sdk_py import call, Contract
from near_sdk_py.promises import Promise
from near_sdk_py.constants import ONE_NEAR, ONE_TGAS
# Constants
MIN_STORAGE = int(1.82 * ONE_NEAR / 1000) # 0.00182Ⓝ
CALL_GAS = 28 * ONE_TGAS
class ExampleContract(Contract):
@call
def create_account(self, account_id, public_key):
"""Create a testnet account by calling the testnet contract"""
# Create the arguments for the create_account method
args = {
"new_account_id": account_id,
"new_public_key": public_key
}
# Call the testnet contract to create the account
return Promise.create_batch("testnet")\
.function_call(
"create_account",
args,
MIN_STORAGE,
CALL_GAS
)
package main
import (
"github.com/vlmoon99/near-sdk-go/promise"
"github.com/vlmoon99/near-sdk-go/types"
)
// @contract:state
type Contract struct{}
type CreateAccountInput struct {
AccountId string `json:"account_id"`
PublicKey string `json:"public_key"`
}
// @contract:payable min_deposit=0.002NEAR
func (c *Contract) ExampleCreateAccount(args CreateAccountInput) {
amount, _ := types.U128FromString("2000000000000000000000") // 0.002 NEAR
gas := uint64(200 * types.ONE_TERA_GAS)
//publicKey (base58) - 4omJwNS1WbniWtbPkLYBrFwN3YLeffXCkpvriYgeLhst (generate your own for testing)
//accountId - nearsdkdocs1.testnet (write your own for testing)
createArgs := map[string]string{
"new_account_id": args.AccountId,
"new_public_key": args.PublicKey,
}
promise.CreateBatch("testnet").
FunctionCall("create_account", createArgs, amount, gas)
}
Deploy a Contract
When creating an account you can also batch the action of deploying a contract to it. Note that for this, you will need to pre-load the byte-code you want to deploy in your contract.
- 🦀 Rust
- 🐍 Python
- 🐹 GO
use near_sdk::{near, env, Promise, NearToken};
#[near(contract_state)]
#[derive(Default)]
pub struct Contract { }
const MIN_STORAGE: NearToken = NearToken::from_millinear(1100); //1.1Ⓝ
const HELLO_CODE: &[u8] = include_bytes!("./hello.wasm");
#[near]
impl Contract {
pub fn create_hello(&self, prefix: String){
let account_id = prefix + "." + &env::current_account_id().to_string();
Promise::new(account_id.parse().unwrap())
.create_account()
.transfer(MIN_STORAGE)
.deploy_contract(HELLO_CODE.to_vec());
}
}
from near_sdk_py import call, Context, Contract
from near_sdk_py.promises import Promise
from near_sdk_py.constants import ONE_NEAR
class ExampleContract(Contract):
@call
def deploy_contract(self, prefix):
"""Create an account and deploy a contract to it"""
# This would require loading the contract bytes in Python
# Load contract bytes - for example purposes only
# In a real implementation, you'd need to read this from storage or include it
contract_bytes = b'...' # This should be actual WASM bytes
MIN_STORAGE = 1.1 * ONE_NEAR # 1.1Ⓝ
# Generate the new account ID
account_id = f"{prefix}.{self.current_account_id}"
# Create batch of actions
return Promise.create_batch(account_id)\
.create_account()\
.transfer(MIN_STORAGE)\
.deploy_contract(contract_bytes)
package main
import (
_ "embed"
"github.com/vlmoon99/near-sdk-go/env"
"github.com/vlmoon99/near-sdk-go/promise"
"github.com/vlmoon99/near-sdk-go/types"
)
//go:embed status_message_go.wasm
var contractWasm []byte
// @contract:state
type Contract struct{}
// @contract:payable min_deposit=1.1NEAR
func (c *Contract) ExampleDeployContract(prefix string) {
currentAccountId, _ := env.GetCurrentAccountId()
subaccountId := prefix + "." + currentAccountId
amount, _ := types.U128FromString("1100000000000000000000000") // 1.1Ⓝ
promise.CreateBatch(subaccountId).
CreateAccount().
Transfer(amount).
DeployContract(contractWasm)
}
If an account with a contract deployed does not have any access keys, this is known as a locked contract. When the account is locked, it cannot sign transactions therefore, actions can only be performed from within the contract code.
Add Keys
When you use actions to create a new account, the created account does not have any access keys, meaning that it cannot sign transactions (e.g. to update its contract, delete itself, transfer money).
There are two options for adding keys to the account:
add_access_key: adds a key that can only call specific methods on a specified contract.add_full_access_key: adds a key that has full access to the account.
- 🦀 Rust
- 🌐 JavaScript
- 🐍 Python
- 🐹 GO
use near_sdk::{near, env, Promise, NearToken, PublicKey};
#[near(serializers = [json, borsh])]
#[derive(Default)]
pub struct Contract { }
const MIN_STORAGE: NearToken = NearToken::from_millinear(1100); //1.1Ⓝ
const HELLO_CODE: &[u8] = include_bytes!("./hello.wasm");
#[near]
impl Contract {
pub fn create_hello(&self, prefix: String, public_key: PublicKey){
let account_id = prefix + "." + &env::current_account_id().to_string();
Promise::new(account_id.parse().unwrap())
.create_account()
.transfer(MIN_STORAGE)
.deploy_contract(HELLO_CODE.to_vec())
.add_full_access_key(public_key);
}
}
import { NearBindgen, near, call, NearPromise } from 'near-sdk-js'
import { PublicKey } from 'near-sdk-js/lib/types'
const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ
@NearBindgen({})
class Contract {
@call({})
create_hello({prefix, public_key}:{prefix: String, public_key: PublicKey}) {
const account_id = `${prefix}.${near.currentAccountId()}`
NearPromise.new(account_id)
.createAccount()
.transfer(MIN_STORAGE)
.addFullAccessKey(public_key)
}
}
from near_sdk_py import call, Contract
from near_sdk_py.promises import Promise
from near_sdk_py.constants import ONE_NEAR
# Minimum amount needed for storage
MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ
class ExampleContract(Contract):
@call
def create_with_key(self, prefix, public_key):
"""Create a subaccount with a full access key"""
# Generate the new account ID
account_id = f"{prefix}.{self.current_account_id}"
# Create the new account, transfer some NEAR, and add a key
return Promise.create_batch(account_id)\
.create_account()\
.transfer(MIN_STORAGE)\
.add_full_access_key(public_key)
package main
import (
"github.com/vlmoon99/near-sdk-go/env"
"github.com/vlmoon99/near-sdk-go/promise"
"github.com/vlmoon99/near-sdk-go/types"
)
// @contract:state
type Contract struct{}
type AddKeysInput struct {
Prefix string `json:"prefix"`
PublicKey string `json:"public_key"`
}
// @contract:payable min_deposit=0.001NEAR
func (c *Contract) ExampleAddKeys(input AddKeysInput) {
currentAccountId, _ := env.GetCurrentAccountId()
subaccountId := input.Prefix + "." + currentAccountId
amount, _ := types.U128FromString("1000000000000000000000") // 0.001Ⓝ
promise.CreateBatch(subaccountId).
CreateAccount().
Transfer(amount).
AddFullAccessKey([]byte(input.PublicKey), 0)
}
Notice that what you actually add is a "public key". Whoever holds its private counterpart, i.e. the private-key, will be able to use the newly access key.
If an account with a contract deployed does not have any access keys, this is known as a locked contract. When the account is locked, it cannot sign transactions therefore, actions can only be performed from within the contract code.
Delete Account
There are two scenarios in which you can use the delete_account action:
- As the last action in a chain of batched actions.
- To make your smart contract delete its own account.
- 🦀 Rust
- 🌐 JavaScript
- 🐍 Python
- 🐹 GO
use near_sdk::{near, env, Promise, NearToken, AccountId};
#[near(contract_state)]
#[derive(Default)]
pub struct Contract { }
const MIN_STORAGE: NearToken = NearToken::from_millinear(1); //0.001Ⓝ
#[near]
impl Contract {
pub fn create_delete(&self, prefix: String, beneficiary: AccountId){
let account_id = prefix + "." + &env::current_account_id().to_string();
Promise::new(account_id.parse().unwrap())
.create_account()
.transfer(MIN_STORAGE)
.delete_account(beneficiary);
}
pub fn self_delete(beneficiary: AccountId){
Promise::new(env::current_account_id())
.delete_account(beneficiary);
}
}
import { NearBindgen, near, call, NearPromise } from 'near-sdk-js'
import { AccountId } from 'near-sdk-js/lib/types'
const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ
@NearBindgen({})
class Contract {
@call({})
create_delete({prefix, beneficiary}:{prefix: String, beneficiary: AccountId}) {
const account_id = `${prefix}.${near.currentAccountId()}`
NearPromise.new(account_id)
.createAccount()
.transfer(MIN_STORAGE)
.deleteAccount(beneficiary)
}
@call({})
self_delete({beneficiary}:{beneficiary: AccountId}) {
NearPromise.new(near.currentAccountId())
.deleteAccount(beneficiary)
}
}
from near_sdk_py import call, Contract
from near_sdk_py.promises import Promise
from near_sdk_py.constants import ONE_NEAR
# Minimum amount needed for storage
MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ
class ExampleContract(Contract):
@call
def create_delete(self, prefix, beneficiary):
"""Create an account and immediately delete it, sending funds to a beneficiary"""
# Generate the new account ID
account_id = f"{prefix}.{self.current_account_id}"
# Create the account, transfer funds, then delete it
return Promise.create_batch(account_id)\
.create_account()\
.transfer(MIN_STORAGE)\
.delete_account(beneficiary)
@call
def self_delete(self, beneficiary):
"""Delete this contract's account"""
# Delete the account and send remaining funds to beneficiary
return Promise.create_batch(self.current_account_id)\
.delete_account(beneficiary)
package main
import (
"github.com/vlmoon99/near-sdk-go/env"
"github.com/vlmoon99/near-sdk-go/promise"
"github.com/vlmoon99/near-sdk-go/types"
)
// @contract:state
type Contract struct{}
type DeleteAccountInput struct {
Prefix string `json:"prefix"`
Beneficiary string `json:"beneficiary"`
}
type SelfDeleteInput struct {
Beneficiary string `json:"beneficiary"`
}
// @contract:payable min_deposit=0.001NEAR
func (c *Contract) ExampleCreateDeleteAccount(input DeleteAccountInput) {
currentAccountId, _ := env.GetCurrentAccountId()
subaccountId := input.Prefix + "." + currentAccountId
amount, _ := types.U128FromString("1000000000000000000000") // 0.001Ⓝ
promise.CreateBatch(subaccountId).
CreateAccount().
Transfer(amount).
DeleteAccount(input.Beneficiary)
}
// @contract:mutating
func (c *Contract) ExampleSelfDeleteAccount(input SelfDeleteInput) {
currentAccountId, _ := env.GetCurrentAccountId()
promise.CreateBatch(currentAccountId).
DeleteAccount(input.Beneficiary)
}
If the beneficiary account does not exist the funds will be dispersed among validators.
Do not use delete to try fund a new account. Since the account doesn't exist the tokens will be lost.