API Libraries
Learn to use APIs in JavaScript, Rust, and Python to interact with the blockchain.
We offer a collection of language-specific libraries that allow developers to interact with the NEAR blockchain from both frontend and backend applications. The different APIs allow you to perform a variety of actions on the NEAR blockchain, including but not limited to:
- Create and manage NEAR accounts
- Call functions on smart contracts
- Transfer tokens, including native NEAR, Fungible Tokens, Non-Fungible Tokens
- Sign transactions/meta-transactions/messages and broadcasting them to the network
- Deploy smart contracts
Available APIs
We have APIs available for Javascript, Rust, and Python. Add them to your project using the following commands:
[tab] 🌐 near-api-js
npm i near-api-js
[tab] 🌐 near-kit
npm i near-kit
[tab] 🦀 near-api-rs
cargo add near-api
[tab] 🦀 near-kit-rs
cargo add near-kit --features mnemonic,file-signer,keyring
[tab] 🐍 near-kit-py
pip install near-kit python-dotenvThe Python package is named
near-kitand is imported asnear.
[tab] 🐍 py-near
pip install py-near
[!TIP]
If you are building a web app and need to add Wallet Login on it you will instead need a Wallet Connector.
Use Node.js 22.22.2 or newer for the JavaScript examples. The complete examples include dependency lockfiles and typechecks for each JavaScript library. The repository also includes near-api-rs, near-kit-rs, and near-kit-py examples with dependency lockfiles. The Rust examples use Rust 1.96 and the Python examples require Python 3.11 or newer.
Signing examples load their account ID and key from a shared .env file, as described in the repository's setup instructions. Run each example from its package directory. Excerpts below focus on individual operations; the linked source files include the surrounding client and signer setup.
[accordion] The JavaScript near-kit private-key helper
The JavaScript near-kit examples import
privateKeyFromStringfrom a shared helper. Include this file when copying an example: it parses serialized key material before passing it to the client and rejects missing or malformed keys.import { parseKey } from "near-kit"; /** * Parse and validate serialized key material before passing it to the client. * @param {string | undefined} value * @returns {import("near-kit").PrivateKey} */ export function privateKeyFromString(value) { if (!value) throw new Error("A private key is required for this example"); // near-kit returns a plain string here, even though parseKey validated it. return /** @type {import("near-kit").PrivateKey} */ (parseKey(value).secretKey); }
Account
Get Balance
Queries an account balance. The examples below show the fields and amount types returned by each library.
[tab] 🌐 near-api-js
import { Account, JsonRpcProvider } from "near-api-js"; import { NEAR, FungibleToken } from "near-api-js/tokens"; import { USDT } from "near-api-js/tokens/testnet"; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const accountId: string = 'influencer.testnet'; const account = new Account(accountId, provider); // ------- Fetch NEAR tokens balance ------- const nearTokensBalanceInt: bigint = await account.getBalance(NEAR); console.log("NEAR: ", NEAR.toDecimal(nearTokensBalanceInt, 2)); // ------- Fetch USDT tokens balance ------- const usdtTokensBalanceInt: bigint = await account.getBalance(USDT); console.log("USDT: ", USDT.toDecimal(usdtTokensBalanceInt, 2)); // ------- Fetch REF tokens balance in the smallest units as BigInt ------- const REF = new FungibleToken("ref.fakes.testnet", { decimals: 18, symbol: "REF", name: "REF Token", }); const refTokensBalanceInt: bigint = await account.getBalance(REF); console.log("REF: ", REF.toDecimal(refTokensBalanceInt, 2));
[tab] 🌐 near-kit
import { Near } from "near-kit"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: "../.env" }); // Create a connection to testnet (no signing needed for view operations) const near = new Near({ network: "testnet" }); // Get the account ID from environment or use a default const accountId = process.env.ACCOUNT_ID || "example-account.testnet"; // Get NEAR balance for the account const balance = await near.getBalance(accountId); console.log(`Account: ${accountId}`); console.log(`NEAR Balance: ${balance} NEAR`);
[tab] 🦀 near-api-rs
use near_api::{Account, AccountId, NetworkConfig, Tokens}; #[tokio::main] async fn main() { let my_account_id: AccountId = "example-account.testnet".parse().unwrap(); // Create an account object let my_account = Account(my_account_id.clone()); // Create a connection to the NEAR testnet let network = NetworkConfig::testnet(); // Gets the available, and staked balance in yoctoNEAR let near_balance = Tokens::account(my_account_id.clone()) .near_balance() .fetch_from(&network) .await .unwrap(); println!("{:?}", near_balance); // Account's state, including its code hash and storage usage let account_info = my_account.view().fetch_from(&network).await.unwrap(); println!("{:?}", account_info); }
[tab] 🦀 near-kit-rs
// Create a connection to testnet (no signing needed for view operations) let near = Near::testnet().build(); let account_id = "example-account.testnet"; // Get the account balance let balance = near.balance(account_id).await?; println!("Account: {account_id}"); println!( "Available: {}, staked: {}", balance.available, balance.locked );
[tab] 🐍 near-kit-py
The one-shot Python helpers default to mainnet. Select testnet explicitly:
import near account_id = "example-account.testnet" balance = near.balance(account_id, network="testnet") print(f"Balance: {balance}")
[tab] 🐍 py-near
from py_near.account import Account account = Account(account_id="example-account.testnet", rpc_addr="https://rpc.testnet.pagoda.co") await account.startup() account_balance = account.get_balance()
Get State
Get basic account information, such as its code hash and storage usage.
[!TIP]
If you are using this call to validate a recipient before sending tokens from a frontend, see Validating Recipient Accounts for the full strategy.
[tab] 🌐 near-api-js
import { Account, JsonRpcProvider } from "near-api-js"; // Option 1: Gather details through the RPC Provider const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); const rpcState = await provider.viewAccount({accountId: "example-account.testnet"}); console.log(rpcState); // Option 2: Use an Account object const account = new Account("example-account.testnet", provider); const accountState = await account.getState(); console.log(accountState);
[tab] 🌐 near-kit
import { Near } from "near-kit"; // Create a connection to testnet const near = new Near({ network: "testnet" }); // Get account state using the Near instance const accountId = "example-account.testnet"; const account = await near.getAccount(accountId); console.log(account);
[tab] 🦀 near-api-rs
let account_info = my_account.view().fetch_from(&network).await.unwrap();
[tab] 🦀 near-kit-rs
// Get the account state: code hash and storage usage let account = near.account(account_id).await?; println!("Code hash: {}", account.code_hash); println!("Storage usage: {} bytes", account.storage_usage);
[tab] 🐍 near-kit-py
The one-shot Python helpers default to mainnet. Select testnet explicitly:
import near account_id = "example-account.testnet" account = near.account(account_id, network="testnet") print(f"Code hash: {account.code_hash}") print(f"Storage usage: {account.storage_usage} bytes")
[tab] 🐍 py-near
from py_near.account import Account account = Account(account_id="example-account.testnet", rpc_addr="https://rpc.testnet.pagoda.co") await account.startup() account_state = account.fetch_state()
Create Named Account
To create a named account like user.testnet, you need to call the create_account function on near (or testnet), passing as parameters the new account ID, and a public key to add as FullAccess key.
[tab] 🌐 near-api-js
import dotenv from "dotenv"; import { Account, JsonRpcProvider, KeyPair, KeyPairString, nearToYocto } from "near-api-js"; dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const account = new Account(accountId, provider, privateKey); // example-account.testnet // Generate a new accountId and key const newAccountId: string = Date.now() + ".testnet"; const keyPair = KeyPair.fromRandom("ed25519"); const publicKey: string = keyPair.getPublicKey().toString(); await account.createAccount({ newAccountId, publicKey, nearToTransfer: nearToYocto("0") }); console.log(`Created ${newAccountId} with private key ${keyPair.toString()}`); // Option 2: Call `testnet` directly const newAccountId2: string = Date.now() + ".testnet"; await account.callFunction({ contractId: "testnet", methodName: "create_account", args: { "new_account_id": newAccountId2, "new_public_key": publicKey } }); console.log(`Created account ${newAccountId2} with privateKey: ${keyPair.toString()}`);
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near, generateKey } from "near-kit"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Generate a new key pair for the top-level account const keyPair = generateKey(); const publicKey = keyPair.publicKey.toString(); // Create a unique top-level account name using timestamp const newAccountId = `${Date.now()}.testnet`; // Create the top-level account by calling the testnet contract await near.call("testnet", "create_account", { new_account_id: newAccountId, new_public_key: publicKey, }); console.log(`Created top-level account: ${newAccountId}`); console.log(`Private key: ${keyPair.secretKey}`);
[tab] 🦀 near-api-rs
let private_key = signer::generate_secret_key().unwrap(); let create_account_result = Account::create_account(new_account_id.clone()) // example-account.testnet .fund_myself( account_id.clone(), NearToken::from_millinear(100), // Initial balance for new account in yoctoNEAR ) .with_public_key(private_key.public_key()) .with_signer(signer.clone()) // Signer is the account that is creating the new account .send_to(&network) .await .unwrap(); println!("Private key: {:?}", private_key.to_string()); println!("Public key: {:?}", private_key.public_key().to_string()); println!("{:?}", create_account_result);[accordion] Creating an account from a seed phrase
You can also create an account via a randomly generated seed phrase.
let (seed_phrase, seed_pk) = signer::generate_seed_phrase().unwrap(); let create_account_result = Account::create_account(new_account_id.clone()) // example-account.testnet .fund_myself( account_id.clone(), NearToken::from_millinear(100), // Initial balance for new account in yoctoNEAR ) .with_public_key(seed_pk) .with_signer(signer.clone()) // Signer is the account that is creating the new account .send_to(&network) .await .unwrap(); println!("Seed phrase: {:?}", seed_phrase); println!("{:?}", create_account_result); }
[tab] 🦀 near-kit-rs
[tab] random key
use near_kit::signer::SecretKey; use near_kit::{Error, Gas, Near, NearToken}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Generate a new key pair for the top-level account let key = SecretKey::generate_ed25519(); // Create a unique top-level account name using the timestamp let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis(); let new_account_id = format!("{timestamp}.testnet"); // Create the top-level account by calling the testnet contract near.call("testnet", "create_account") .args(serde_json::json!({ "new_account_id": new_account_id, "new_public_key": key.public_key().to_string(), })) .deposit(NearToken::from_millinear(100)) .gas(Gas::from_tgas(30)) .await?; println!("Created top-level account: {new_account_id}"); println!("Private key: {key}"); Ok(()) }[tab] seed phrase
use near_kit::signer::SecretKey; use near_kit::{Error, Gas, Near, NearToken}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Generate a new seed phrase and derive its key (path m/44'/397'/0') let (seed_phrase, key) = SecretKey::generate_with_seed_phrase()?; // Create a unique top-level account name using the timestamp let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis(); let new_account_id = format!("{timestamp}.testnet"); // Create the top-level account by calling the testnet contract near.call("testnet", "create_account") .args(serde_json::json!({ "new_account_id": new_account_id, "new_public_key": key.public_key().to_string(), })) .deposit(NearToken::from_millinear(100)) .gas(Gas::from_tgas(30)) .await?; println!("Created account: {new_account_id}"); println!("Seed phrase: {seed_phrase}"); Ok(()) }
[tab] 🐍 near-kit-py
[tab] random key
import os import time from dotenv import load_dotenv from near import Near, generate_key # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] # Create a connection to testnet with signing capabilities client = Near(network="testnet", account_id=account_id, private_key=private_key) # Generate a new key pair for the top-level account key = generate_key() # Create a unique top-level account name using the timestamp new_account_id = f"{int(time.time() * 1000)}.testnet" # Create the top-level account by calling the testnet contract client.call( "testnet", "create_account", {"new_account_id": new_account_id, "new_public_key": str(key.public_key)}, deposit="0.1 NEAR", gas="30 Tgas", ) print(f"Created top-level account: {new_account_id}") print(f"Private key: {key.secret_key}")[tab] seed phrase
import os import time from dotenv import load_dotenv from near import Near, generate_seed_phrase, key_from_seed_phrase # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] # Create a connection to testnet with signing capabilities client = Near(network="testnet", account_id=account_id, private_key=private_key) # Generate a new seed phrase and derive its key (path m/44'/397'/0') seed_phrase = generate_seed_phrase() key = key_from_seed_phrase(seed_phrase) # Create a unique top-level account name using the timestamp new_account_id = f"{int(time.time() * 1000)}.testnet" # Create the top-level account by calling the testnet contract client.call( "testnet", "create_account", {"new_account_id": new_account_id, "new_public_key": str(key.public_key)}, deposit="0.1 NEAR", gas="30 Tgas", ) print(f"Created account: {new_account_id}") print(f"Seed phrase: {seed_phrase}")
[tab] 🐍 py-near
await account.function_call("testnet", "create_account", {"new_account_id": "example-account.testnet", "new_public_key": "ed25519:..."}, "30000000000000", 1 * NEAR)
Create Sub-Account
Accounts on NEAR can create sub-accounts under their own namespace, which is useful for organizing accounts by purpose — for example, project.user.testnet.
[tab] 🌐 near-api-js
import dotenv from "dotenv"; import { Account, JsonRpcProvider, KeyPair, KeyPairString, nearToYocto } from "near-api-js"; dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const account = new Account(accountId, provider, privateKey); // example-account.testnet // Generate a new account prefix and key pair const prefix: string = Date.now().toString(); const keyPair = KeyPair.fromRandom("ed25519"); const publicKey: string = keyPair.getPublicKey().toString(); await account.createSubAccount({ accountOrPrefix: prefix, // prefix for the sub account (e.g. sub.near.testnet) publicKey, // ed25519:2ASWc... nearToTransfer: nearToYocto("0") // Initial balance for new account in yoctoNEAR }); console.log(`Created ${prefix}.${accountId} with private key ${keyPair.toString()}`);
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near, generateKey } from "near-kit"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Generate a new key pair for the subaccount const keyPair = generateKey(); const publicKey = keyPair.publicKey.toString(); // Create a unique subaccount name using timestamp const prefix = Date.now().toString(); const newAccountId = `${prefix}.${accountId}`; // Create the subaccount using transaction builder with chained actions await near .transaction(accountId) .createAccount(newAccountId) .transfer(newAccountId, "0.1 NEAR") .addKey(publicKey, { type: "fullAccess" }) .send(); console.log(`Created subaccount: ${newAccountId}`); console.log(`Private key: ${keyPair.secretKey}`);
[tab] 🦀 near-api-rs
let private_key = signer::generate_secret_key().unwrap(); let create_sub_account_result = Account::create_account(sub_account_id.clone()) // sub.example-account.testnet .fund_myself( account_id.clone(), NearToken::from_millinear(100), // Initial balance for sub account in yoctoNEAR ) .with_public_key(private_key.public_key()) .with_signer(signer.clone()) // Signer is the account that is creating the sub account .send_to(&network) .await .unwrap(); println!("Private key: {:?}", private_key.to_string()); println!("Public key: {:?}", private_key.public_key().to_string()); println!("{:?}", create_sub_account_result); }
[tab] 🦀 near-kit-rs
use near_kit::signer::SecretKey; use near_kit::{Error, Near, NearToken}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Generate a new key pair for the subaccount let key = SecretKey::generate_ed25519(); // Create a unique subaccount name using the timestamp let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis(); let new_account_id = format!("{timestamp}.{account_id}"); // Create the subaccount, fund it and add a key in one atomic transaction near.transaction(&new_account_id) .create_account() .transfer(NearToken::from_millinear(100)) .add_full_access_key(key.public_key()) .send() .await?; println!("Created subaccount: {new_account_id}"); println!("Private key: {key}"); Ok(()) }
[tab] 🐍 near-kit-py
import os import time from dotenv import load_dotenv from near import Near, add_full_access_key, create_account, generate_key, transfer # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] # Create a connection to testnet with signing capabilities client = Near(network="testnet", account_id=account_id, private_key=private_key) # Generate a new key pair for the subaccount key = generate_key() # Create a unique subaccount name using the timestamp new_account_id = f"{int(time.time() * 1000)}.{account_id}" # Create the subaccount, fund it and add a key in one atomic transaction client.send_transaction( new_account_id, actions=[create_account(), transfer("0.1 NEAR"), add_full_access_key(key.public_key)], ) print(f"Created subaccount: {new_account_id}") print(f"Private key: {key.secret_key}")
[tab] 🐍 py-near
Create a sub-account and fund it with your main account:
from py_near.account import Account from py_near.dapps.core import NEAR account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") await account.startup() res = account.create_account(account_id="sub.example-account.testnet", public_key="...", initial_balance=1 * NEAR)
[!NOTE]
Parent accounts have no control over their sub-accounts, they are completely independent.
Create Deterministic Account
Use near-kit to create a deterministic account from a global contract and initial storage. The library derives the 0s address from the state init.
import { Near } from "near-kit";
const near = new Near({
network: "testnet",
privateKey: "ed25519:<alice-private-key>",
defaultSignerId: "alice.testnet",
});
await near
.transaction("alice.testnet")
.stateInit({
code: { accountId: "publisher.testnet" },
data: new Map(),
deposit: "0.1 NEAR",
})
.send();
Replace publisher.testnet with an account that has already deployed a global contract. data maps storage keys to values as Uint8Array bytes; an empty map starts with empty storage. The deposit covers storage costs, and any excess is refunded.
See the near-kit deterministic account guide for initial storage and address derivation examples.
Delete Account
Accounts on NEAR can delete themselves, transferring any remaining balance to a specified beneficiary account.
[tab] 🌐 near-api-js
const accountToDelete = new Account(deleteMe, provider, privateKey); // Delete the account const beneficiary: string = process.env.ACCOUNT_ID!; await accountToDelete.deleteAccount(beneficiary);
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near, generateKey } from "near-kit"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Generate a new key pair for the account to be deleted const keyPair = generateKey(); const publicKey = keyPair.publicKey.toString(); // Create a unique top-level account name using timestamp const accountToDelete = `${Date.now()}.testnet`; // First, create the account by calling the testnet contract await near.call("testnet", "create_account", { new_account_id: accountToDelete, new_public_key: publicKey, }); console.log(`Created account: ${accountToDelete}`); // Fund the new account so it can pay for the delete transaction await near.send(accountToDelete, "0.01 NEAR"); console.log(`Funded account with 0.01 NEAR`); // Create a new Near instance with the new account's key to delete it const nearForNewAccount = new Near({ network: "testnet", privateKey: privateKeyFromString(keyPair.secretKey), defaultSignerId: accountToDelete, }); // Delete the account, sending remaining balance to beneficiary await nearForNewAccount .transaction(accountToDelete) .deleteAccount({ beneficiary: accountId }) .send(); console.log(`Deleted account: ${accountToDelete}`); console.log(`Beneficiary: ${accountId}`);
[tab] 🦀 near-api-rs
// Delete the account with account Id of the account object let delete_account_result = account_to_delete .delete_account_with_beneficiary(beneficiary_account_id.clone()) // example-beneficiary.testnet .with_signer(signer.clone()) // Signer is the account that is being deleted .send_to(&network) .await .unwrap(); println!("{:?}", delete_account_result); }
[tab] 🦀 near-kit-rs
use near_kit::signer::{InMemorySigner, SecretKey}; use near_kit::transaction::Final; use near_kit::{Error, Near, NearToken}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Create a subaccount that we will delete afterwards let key = SecretKey::generate_ed25519(); let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis(); let account_to_delete = format!("{timestamp}.{account_id}"); near.transaction(&account_to_delete) .create_account() .transfer(NearToken::from_millinear(10)) .add_full_access_key(key.public_key()) .send() .wait_until::<Final>() .await?; println!("Created account: {account_to_delete}"); // Sign as the new account, reusing the same RPC connection let new_account = near.with_signer(InMemorySigner::from_secret_key( account_to_delete.as_str(), key, )?); // Delete the account, sending the remaining balance to the beneficiary new_account .transaction(&account_to_delete) .delete_account(&account_id) .send() .await?; println!("Deleted account: {account_to_delete}"); println!("Beneficiary: {account_id}"); Ok(()) }
[tab] 🐍 near-kit-py
import os import time from dotenv import load_dotenv from near import ( KeyPairSigner, Near, add_full_access_key, create_account, delete_account, generate_key, transfer, ) # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] # Create a connection to testnet with signing capabilities client = Near(network="testnet", account_id=account_id, private_key=private_key) # Create a subaccount that we will delete afterwards key = generate_key() account_to_delete = f"{int(time.time() * 1000)}.{account_id}" client.send_transaction( account_to_delete, actions=[create_account(), transfer("0.01 NEAR"), add_full_access_key(key.public_key)], wait_until="FINAL", ) print(f"Created account: {account_to_delete}") # Sign as the new account, reusing the same connection new_account = client.with_signer(KeyPairSigner(account_to_delete, key)) # Delete the account, sending the remaining balance to the beneficiary new_account.send_transaction(account_to_delete, actions=[delete_account(account_id)]) print(f"Deleted account: {account_to_delete}") print(f"Beneficiary: {account_id}")
[!NOTE]
Deleting an account DOES NOT affect its sub-accounts - they will remain active.
[!CAUTION]
The Beneficiary Only Receives NEAR Tokens
Fungible (FTs) or Non-Fungible tokens (NFTs) held by the account ARE NOT automatically transferred. These tokens are still associated with the account, even after the account is deleted. Make sure to transfer those assets manually before deletion, or you're risking losing them permanently. Once the account is gone, those assets are effectively stuck unless the same account is recreated by anyone (not necessarily you).
[!CAUTION]
Make Sure the Beneficiary Account Exists
If the beneficiary account doesn't exist, all NEAR tokens sent to it will be burned. Double-check the account ID before proceeding.
Transactions
Send Tokens
Accounts can transfer different types of tokens to other accounts, including the native NEAR token and NEP-141 fungible tokens.
[tab] 🌐 near-api-js
import dotenv from "dotenv"; import { Account, JsonRpcProvider, KeyPairString } from "near-api-js"; import { NEAR, FungibleToken } from "near-api-js/tokens"; import { USDT } from "near-api-js/tokens/testnet"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const account = new Account(accountId, provider, privateKey); // example-account.testnet // ------- Send NEAR tokens to another account ------- const sendNearTokensResult = await account.transfer({ token: NEAR, amount: NEAR.toUnits("0.1"), receiverId: "influencer.testnet" }); console.log(sendNearTokensResult); // ------- Send USDT tokens to another account ------- // NOTE: Use https://testnet.rhea.finance/#near|usdtt.fakes.testnet to get USDT token const sendUsdtTokensResult = await account.transfer({ token: USDT, amount: USDT.toUnits("1"), // Amount of USDT being sent receiverId: "influencer.testnet" }); console.log(sendUsdtTokensResult); // ------- Send REF tokens to another account ------- // Use https://testnet.rhea.finance/#near|ref.fakes.testnet to get REF tokens const REF = new FungibleToken("ref.fakes.testnet", { decimals: 18, symbol: "REF", name: "REF Token", }); const sendREFsResult = await account.transfer({ token: REF, amount: REF.toUnits("1"), // Amount of REF tokens being sent receiverId: "influencer.testnet" }).catch(() => {}); // ignore errors if already registered console.log(sendREFsResult); // Lower level Primitives console.log( "Is influencer.testnet registered for USDT?", await USDT.isAccountRegistered({ accountId: "influencer.testnet", provider, }) ) console.log( "Registering influencer.testnet for USDT", await USDT.registerAccount({ accountIdToRegister: "influencer.testnet", fundingAccount: account, }) ) // ------- Sending tokens to an unregistered account will automatically register it ------- const randomAccountId = `${Math.random().toString(36).substring(2, 15)}.testnet`; const isRegistered = await USDT.isAccountRegistered({ accountId: randomAccountId, provider, }) if (isRegistered) throw new Error(`${randomAccountId} is already registered`); await account.transfer({ token: USDT, amount: USDT.toUnits("1"), receiverId: randomAccountId, }); const randomAccount = new Account(randomAccountId, provider); const balance = await USDT.getBalance(randomAccount); console.log(`Balance of ${randomAccountId}:`, USDT.toDecimal(balance));
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near } from "near-kit"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Send NEAR tokens to another account const result = await near.send("receiver-account.testnet", "0.1 NEAR"); console.log("Transfer result:", result);
[tab] 🦀 near-api-rs
let send_tokens_result = Tokens::account(account_id.clone()) // example-account.testnet .send_to("receiver-account.testnet".parse().unwrap()) .near(NearToken::from_near(1)) .with_signer(signer.clone()) .send_to(&network) .await .unwrap();
[tab] 🦀 near-kit-rs
use near_kit::{Error, Near, NearToken}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Send NEAR tokens to another account let outcome = near .transfer("receiver-account.testnet", NearToken::from_millinear(100)) .await?; println!("Transfer result: {:?}", outcome.transaction_hash()); Ok(()) }
[tab] 🐍 near-kit-py
import os from dotenv import load_dotenv from near import Near # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] # Create a connection to testnet with signing capabilities client = Near(network="testnet", account_id=account_id, private_key=private_key) # Send NEAR tokens to another account result = client.send("receiver-account.testnet", "0.1 NEAR") print(f"Transfer result: {result.transaction_hash}")
[tab] 🐍 py-near
from py_near.account import Account from py_near.dapps.core import NEAR account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") await account.startup() await account.send_money("receiver-account.testnet", 1 * NEAR)
Call Function
A smart contract exposes its methods, and making a function call that modifies state requires a Signer/KeyPair. You can optionally attach a NEAR deposit to the call.
[tab] 🌐 near-api-js
[tab] function call
import dotenv from "dotenv"; import { Account, JsonRpcProvider, teraToGas, KeyPairString, nearToYocto } from "near-api-js"; // Create an account object dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a testnet provider const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // For read only calls, you can use the provider directly const messages = await provider.callFunction({ contractId: 'guestbook.near-examples.testnet', method: "get_messages", args: {}, }); console.log(messages); // To modify state, you need an account to sign the transaction const account = new Account(accountId, provider, privateKey); // Call the contract await account.callFunction({ contractId: 'guestbook.near-examples.testnet', methodName: "add_message", args: { text: "Hello!" }, gas: teraToGas('30'), deposit: nearToYocto('0.1'), });[!TIP]
When using Typescript, you can type the return of
callFunction<T>.[tab] typed contract
import { nearToYocto, Account, Contract, JsonRpcProvider, KeyPairString } from "near-api-js"; import abi from "./guestbook.abi.js"; import dotenv from "dotenv"; dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const account = new Account(accountId, provider, privateKey); // example-account.testnet // Make a function call that modifies state const contract = new Contract({ contractId: "guestbook.near-examples.testnet", provider: provider, abi: abi, }); contract.call.add_message({ deposit: nearToYocto("0.1"), args: { text: "Hello, NEAR!" }, account: account, }); // TypeScript infers the type of this method // Make a read-only function call const totalMessages = await contract.view.total_messages(); console.log({ totalMessages }); // Send a tx to add message const result = await contract.call.add_message({ account, args: { text: "Hello, world!" }, }); console.log({ result });
[tab] 🌐 near-kit
[tab] function call
import { privateKeyFromString } from "./helpers/private-key.js"; import dotenv from "dotenv"; import { Near } from "near-kit"; // Create a connection to testnet with credentials dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; const near = new Near({ network: "testnet", privateKey, defaultSignerId: accountId }); // For read only calls, you can use the provider directly const messages = await near.view( 'guestbook.near-examples.testnet', 'get_messages', {} ); console.log(messages); // To modify state, call the contract method await near.call( 'guestbook.near-examples.testnet', 'add_message', { text: "Hello!" }, { gas: "30 Tgas" } );[!TIP]
When using Typescript, you can type the return of
Near.view<T>andNear.call<T>.[tab] typed contract
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near } from "near-kit"; import dotenv from "dotenv"; dotenv.config({ path: "../.env" }); // Create a connection to testnet with credentials from environment const near = new Near({ network: "testnet", privateKey: privateKeyFromString(process.env.PRIVATE_KEY), defaultSignerId: process.env.ACCOUNT_ID }); // Define the contract interface const guestbook = near.contract("guestbook.near-examples.testnet"); // View: get total messages const totalMessages = await guestbook.view.total_messages(); console.log({ totalMessages }); // Call: add a message const result = await guestbook.call.add_message({ text: "Hello from near-kit!" }, { gas: "30 Tgas" }); console.log({ result });
[tab] 🦀 near-api-rs
let contract_id: AccountId = "guestbook.near-examples.testnet".parse().unwrap(); let contract = Contract(contract_id.clone());let args = json!({ "text": "Hello, world!" }); let function_call_result = contract .call_function("add_message", args) .transaction() .deposit(NearToken::from_near(1)) .with_signer(account_id.clone(), signer.clone()) // Signer is the account that is calling the function .send_to(&network) .await .unwrap();
[tab] 🦀 near-kit-rs
// To modify state, call the contract method near.call("guestbook.near-examples.testnet", "add_message") .args(serde_json::json!({ "text": "Hello!" })) .gas(Gas::from_tgas(30)) .await?;
[tab] 🐍 near-kit-py
# To modify state, call the contract method client.call("guestbook.near-examples.testnet", "add_message", {"text": "Hello!"}, gas="30 Tgas")
[tab] 🐍 py-near
await account.function_call("usn.near", "ft_transfer", {"receiver_id": "bob.near", "amount": "1000000000000000000000000"})
Batch Actions
You can send multiple actions in a batch to a single receiver. If one action fails then the entire batch of actions will be reverted.
[tab] 🌐 near-api-js
import dotenv from "dotenv"; import { Account, JsonRpcProvider, actions, teraToGas, nearToYocto, KeyPairString } from "near-api-js"; dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const account = new Account(accountId, provider, privateKey); // example-account.testnet // Send the batch of actions const batchActionsResult = await account.signAndSendTransaction({ receiverId: "counter.near-examples.testnet", actions: [ actions.functionCall("increment", {}, teraToGas("30"), 0n), actions.transfer(nearToYocto("0.001")) ], }); console.log(batchActionsResult);
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near } from "near-kit"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Batch multiple actions in one transaction const result = await near.transaction(accountId) .functionCall("counter.near-examples.testnet", "increment", {}, { gas: "30 Tgas" }) .transfer("counter.near-examples.testnet", "0.001 NEAR") .send(); console.log("Batch actions result:", result);
[tab] 🦀 near-api-rs
// Prepare the actions let call_action = Action::FunctionCall(Box::new(FunctionCallAction { method_name: "increment".to_string(), args: serde_json::json!({}).to_string().into_bytes(), gas: near_api::NearGas::from_tgas(30), deposit: near_api::NearToken::from_yoctonear(0), })); let transfer_action = Action::Transfer(TransferAction { deposit: near_api::NearToken::from_near(1), }); // Transfer 1 NEAR // Send the batch of actions let batch_actions_result = Transaction::construct( account_id.clone(), "counter.near-examples.testnet".parse().unwrap(), ) .add_actions(vec![call_action, transfer_action]) .with_signer(signer) .send_to(&network) .await .unwrap();
[tab] 🦀 near-kit-rs
use near_kit::{Error, Gas, Near, NearToken}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Batch multiple actions in one atomic transaction to the same receiver let outcome = near .transaction("counter.near-examples.testnet") .call("increment") .gas(Gas::from_tgas(30)) .finish() .transfer(NearToken::from_millinear(1)) .send() .await?; println!("Batch actions result: {:?}", outcome.transaction_hash()); Ok(()) }
[tab] 🐍 near-kit-py
import os from dotenv import load_dotenv from near import Near, function_call, transfer # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] # Create a connection to testnet with signing capabilities client = Near(network="testnet", account_id=account_id, private_key=private_key) # Batch multiple actions in one atomic transaction to the same receiver result = client.send_transaction( "counter.near-examples.testnet", actions=[function_call("increment", gas="30 Tgas"), transfer("0.001 NEAR")], ) print(f"Batch actions result: {result.transaction_hash}")
Simultaneous Transactions
The only way to have true simultaneous transactions is to use multiple access keys on a same account. Each access key maintains its own nonce, allowing transactions signed with different keys to be processed in parallel:
[tab] 🌐 near-api-js
import dotenv from "dotenv"; import { NEAR } from "near-api-js/tokens"; import { Account, actions, JsonRpcProvider, KeyPair, MultiKeySigner, KeyPairString } from "near-api-js"; dotenv.config({ path: "../.env" }); // Loads .env const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create 10 keys and add them to the account const account = new Account(accountId, provider, privateKey); const keys: KeyPair[] = []; const txActions: ReturnType<typeof actions.addFullAccessKey>[] = []; for (let j = 0; j < 10; j++) { const newKeyPair = KeyPair.fromRandom('ed25519'); keys.push(newKeyPair); txActions.push(actions.addFullAccessKey(newKeyPair.getPublicKey())); } await account.signAndSendTransaction({ receiverId: accountId, actions: txActions }); // Send NEAR tokens using multiple keys const multiKeySigner = new MultiKeySigner(keys); const multiAccount = new Account(accountId, provider, multiKeySigner); const transfers = [...Array(100)].map(() => multiAccount.transfer({ token: NEAR, amount: NEAR.toUnits("0.001"), receiverId: "influencer.testnet" }) ); const sendNearTokensResults = await Promise.all(transfers); sendNearTokensResults.forEach(result => console.log(result));
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near, generateKey, RotatingKeyStore } from "near-kit"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a RotatingKeyStore for concurrent transactions const keyStore = new RotatingKeyStore(); // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Add multiple keys to the account for concurrent transactions const keys = []; for (let i = 0; i < 5; i++) { const newKey = generateKey(); keys.push(newKey); // Add key to the account on-chain await near .transaction(accountId) .addKey(newKey.publicKey.toString(), { type: "fullAccess" }) .send(); // Add key to the rotating store await keyStore.add(accountId, newKey); } console.log(`Added ${keys.length} keys to account`); // Create a new Near client with the rotating keystore const nearWithRotating = new Near({ network: "testnet", keyStore, defaultSignerId: accountId, }); // Send multiple concurrent transactions // The RotatingKeyStore will distribute keys to avoid nonce conflicts const transfers = Array.from({ length: 10 }, (_, i) => nearWithRotating.send("influencer.testnet", "0.001 NEAR") ); const results = await Promise.all(transfers); console.log(`Completed ${results.length} concurrent transfers`); // Clean up: delete the keys we added for (const key of keys) { await near .transaction(accountId) .deleteKey(accountId, key.publicKey.toString()) .send(); } console.log("Cleaned up keys");
[tab] 🦀 near-api-rs
// Prepare the transactions let args = serde_json::to_vec(&json!({ "text": "Hello, world!" })) .unwrap(); let action1 = Action::FunctionCall(Box::new(FunctionCallAction { method_name: "add_message".to_string(), args, gas: near_api::NearGas::from_tgas(100), deposit: near_api::NearToken::from_yoctonear(0), })); let tx1 = Transaction::construct( account_id.clone(), "guestbook.near-examples.testnet".parse().unwrap(), ) .add_action(action1) .with_signer(signer.clone()); let action2 = Action::FunctionCall(Box::new(FunctionCallAction { method_name: "increment".to_string(), args: b"{}".to_vec(), gas: near_api::NearGas::from_tgas(100), deposit: near_api::NearToken::from_yoctonear(0), })); let tx2 = Transaction::construct( account_id.clone(), "counter.near-examples.testnet".parse().unwrap(), ) .add_action(action2) .with_signer(signer.clone()); // Send the transactions simultaneously let (tx1_result, tx2_result) = tokio::join!(tx1.send_to(&network), tx2.send_to(&network));
[tab] 🦀 near-kit-rs
use near_kit::{Error, Gas, Near}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Prepare the transactions let tx1 = near .call("guestbook.near-examples.testnet", "add_message") .args(serde_json::json!({ "text": "Hello, world!" })) .gas(Gas::from_tgas(30)); let tx2 = near .call("counter.near-examples.testnet", "increment") .gas(Gas::from_tgas(30)); // Send them at the same time: the client reserves a distinct nonce for each let (r1, r2) = tokio::try_join!(tx1, tx2)?; println!("Guestbook tx: {:?}", r1.transaction_hash()); println!("Counter tx: {:?}", r2.transaction_hash()); Ok(()) }
[tab] 🐍 near-kit-py
import asyncio import os from dotenv import load_dotenv from near import AsyncNear # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] async def main(): # Create an async connection to testnet with signing capabilities async with AsyncNear(network="testnet", account_id=account_id, private_key=private_key) as client: # Prepare the transactions tx1 = client.call("guestbook.near-examples.testnet", "add_message", {"text": "Hello, world!"}) tx2 = client.call("counter.near-examples.testnet", "increment") # Send them at the same time: the client reserves a distinct nonce for each results = await asyncio.gather(tx1, tx2) for result in results: print(f"Transaction: {result.transaction_hash}") asyncio.run(main())
[tab] 🐍 py-near
import asyncio from py_near.account import Account account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") await account.startup() # Prepare the transactions tx1 = account.function_call("guestbook.near-examples.testnet", "add_message", { "text": "Hello, world!" }) tx2 = account.function_call("counter.near-examples.testnet", "increment", {}) # Send the transactions simultaneously transactionsResults = await asyncio.gather(tx1, tx2)
[!WARNING]
Simultaneous execution means there's no guarantee of order or success. Any transaction may fail independently. If your use case requires strict ordering, then you should stick to sending transactions sequentially from a single key.
Deploy a Contract
On NEAR, a smart contract is deployed as a WASM file. Every account has the potential to become a contract — you simply need to deploy code to it.
[tab] 🌐 near-api-js
import { Account, JsonRpcProvider, KeyPairString } from "near-api-js"; import fs from "fs"; import dotenv from "dotenv"; dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const account = new Account(accountId, provider, privateKey); // example-account.testnet // Deploy a contract to the account const result = await account.deployContract( fs.readFileSync("../contracts/contract.wasm") // Path of contract WASM relative to the working directory ); console.log(result);
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near } from "near-kit"; import dotenv from "dotenv"; import fs from "fs"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Deploy a contract to the account const wasmCode = fs.readFileSync("../contracts/contract.wasm"); const result = await near.transaction(accountId) .deployContract(accountId, wasmCode) .send(); console.log("Deploy contract result:", result);
[tab] 🦀 near-api-rs
Note that the
signerhere needs to be a signer for the sameaccount_idas the one used to construct theContractobject.let deploy_result = Contract::deploy(account_id.clone()) .use_code(include_bytes!("../../contracts/contract.wasm").to_vec()) // Path of contract WASM relative to this file .without_init_call() .with_signer(signer) // Signer is the account that is deploying the contract .send_to(&network) .await .unwrap(); println!("{:?}", deploy_result);
[tab] 🦀 near-kit-rs
use near_kit::{Error, Near}; #[tokio::main] async fn main() -> Result<(), Error> { // Load environment variables dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); // Create a connection to testnet with signing capabilities let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Deploy a contract to the signer account let wasm_code = std::fs::read("../contracts/contract.wasm").expect("contract.wasm"); let outcome = near.deploy(wasm_code).send().await?; println!("Deploy contract result: {:?}", outcome.transaction_hash()); Ok(()) }
[tab] 🐍 near-kit-py
import os from dotenv import load_dotenv from near import Near, deploy_contract # Load environment variables load_dotenv() private_key = os.environ["PRIVATE_KEY"] account_id = os.environ["ACCOUNT_ID"] # Create a connection to testnet with signing capabilities client = Near(network="testnet", account_id=account_id, private_key=private_key) # Deploy a contract to the signer account with open("../contracts/contract.wasm", "rb") as f: wasm_code = f.read() result = client.send_transaction(account_id, actions=[deploy_contract(wasm_code)]) print(f"Deploy contract result: {result.transaction_hash}")
[tab] 🐍 py-near
import asyncio from py_near.account import Account account = Account(account_id="example-account.testnet", private_key="ed25519:...", rpc_addr="https://rpc.testnet.pagoda.co") await account.startup() with open("contract.wasm", "rb") as f: contract_code = f.read() await account.deploy_contract(contract_code)
Deploy a Global Contract
Global contracts allow smart contracts to be deployed once and reused by any account without incurring high storage costs.
There are two ways to reference a global contract:
- By account: The contract code is tied to another account.
- By hash: You reference the contract by its immutable code hash.
[tab] 🌐 near-api-js
[tab] by account
import { Account, JsonRpcProvider, KeyPair, KeyPairString } from "near-api-js"; import { NEAR } from "near-api-js/tokens"; import { readFileSync } from "fs"; import dotenv from "dotenv"; dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const deployer = new Account(accountId, provider, privateKey); // example-account.testnet // Path of contract WASM relative to the working directory const wasm: Uint8Array = new Uint8Array(readFileSync("../contracts/contract.wasm")); const deployResult = await deployer.deployGlobalContract(wasm, "accountId"); console.log(deployResult);[tab] by hash
import { Account, JsonRpcProvider, KeyPair, KeyPairString, baseEncode } from "near-api-js"; import { NEAR } from "near-api-js/tokens"; import { readFileSync } from "fs"; import { createHash } from "crypto"; import dotenv from "dotenv"; dotenv.config({ path: "../.env" }); const privateKey = process.env.PRIVATE_KEY! as KeyPairString; const accountId: string = process.env.ACCOUNT_ID!; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Create an account object const deployer = new Account(accountId, provider, privateKey); // example-account.testnet // Path of contract WASM relative to the working directory const wasm: Uint8Array = new Uint8Array(readFileSync("../contracts/contract.wasm")); const deployResult = await deployer.deployGlobalContract(wasm, "codeHash"); console.log(deployResult);
[tab] 🌐 near-kit
[tab] by account
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near, generateKey } from "near-kit"; import dotenv from "dotenv"; import fs from "fs"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Read the contract WASM const wasmCode = fs.readFileSync("../contracts/contract.wasm"); // Publish as a global contract (identified by account ID) // Other accounts can use this contract without deploying their own copy const publishResult = await near .transaction(accountId) .publishContract(wasmCode, { identifiedBy: "account" }) .send(); console.log("Published global contract:", publishResult.transaction.hash);[tab] by hash
import { privateKeyFromString } from "./helpers/private-key.js"; import { Near, generateKey } from "near-kit"; import dotenv from "dotenv"; import fs from "fs"; import { createHash } from "crypto"; // Load environment variables dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; // Create a connection to testnet with signing capabilities const near = new Near({ network: "testnet", privateKey: privateKey, // ed25519:5Fg2... defaultSignerId: accountId, // example-account.testnet }); // Read the contract WASM const wasmCode = fs.readFileSync("../contracts/contract.wasm"); // Calculate the code hash (SHA-256) const codeHash = createHash("sha256").update(wasmCode).digest(); // Publish as a global contract (identified by code hash) // This creates an immutable contract reference const publishResult = await near .transaction(accountId) .publishContract(wasmCode, { identifiedBy: "hash" }) .send(); console.log("Published global contract by hash:", publishResult.transaction.hash);
[tab] 🦀 near-api-rs
Once you've created an Account instance, you can deploy your regular contract as a global contract.
[tab] by account
To deploy a global contract by account, use the
deploy_global_contract_codefunction with theas_account_idmethod.let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); let code = std::fs::read("path/to/your/contract.wasm").unwrap(); let signer = Signer::from_secret_key(private_key).unwrap(); let result = Contract::deploy_global_contract_code(code) .as_account_id(global_account_id) .with_signer(signer) .send_to_testnet() .await.unwrap();[tab] by hash
To deploy a global contract by hash, use the
deploy_global_contract_codefunction with theas_hashmethod.let account_id: AccountId = "my-account.testnet".parse().unwrap(); let code = std::fs::read("path/to/your/contract.wasm").unwrap(); let signer = Signer::from_secret_key(private_key).unwrap(); let result = Contract::deploy_global_contract_code(code) .as_hash() .with_signer(account_id, signer) .send_to_testnet() .await.unwrap();
[tab] 🦀 near-kit-rs
[tab] by account
// Read the contract WASM let wasm_code = std::fs::read("../contracts/contract.wasm").expect("contract.wasm"); // Publish as a global contract identified by the account ID. // Other accounts can use this contract without deploying their own copy, // and re-publishing from this account updates all of them. let outcome = near .publish(wasm_code, PublishMode::Updatable) .send() .wait_until::<Final>() .await?; println!( "Published global contract: {:?}", outcome.transaction_hash() );[tab] by hash
// Read the contract WASM and compute its code hash let wasm_code = std::fs::read("../contracts/contract.wasm").expect("contract.wasm"); let code_hash = CryptoHash::hash(&wasm_code); // Publish as a global contract identified by its code hash (immutable) let outcome = near .publish(wasm_code, PublishMode::Immutable) .send() .wait_until::<Final>() .await?; println!( "Published global contract by hash: {:?}", outcome.transaction_hash() );
[tab] 🐍 near-kit-py
[tab] by account
# Read the contract WASM with open("../contracts/contract.wasm", "rb") as f: wasm_code = f.read() # Publish as a global contract identified by the account ID. # Other accounts can use this contract without deploying their own copy, # and re-publishing from this account updates all of them. result = client.send_transaction(account_id, actions=[publish_contract(wasm_code)]) print(f"Published global contract: {result.transaction_hash}")[tab] by hash
# Read the contract WASM and compute its code hash (SHA-256) with open("../contracts/contract.wasm", "rb") as f: wasm_code = f.read() code_hash = hashlib.sha256(wasm_code).digest() # Publish as a global contract identified by its code hash (immutable) result = client.send_transaction( account_id, actions=[publish_contract(wasm_code, identified_by="hash")], wait_until="FINAL" ) print(f"Published global contract by hash: {result.transaction_hash}")
Use a Global Contract
Once a global contract has been deployed, here is how you can reference and use it from another account.
[tab] 🌐 near-api-js
[tab] by account
const consumer = new Account( consumerAccountId, provider, key.toString() as KeyPairString ); await consumer.useGlobalContract({ accountId: deployer.accountId }); const contract = await consumer.getContractCode(); console.log("Size", contract.code.length, "Hash", contract.hash);[tab] by hash
const consumer = new Account( consumerAccountId, provider, key.toString() as KeyPairString ); const useResult = await consumer.useGlobalContract({ codeHash: Uint8Array.from(Buffer.from(hash, 'base64')), }); console.log(useResult); await provider.viewTransactionStatus({ txHash: useResult.transaction.hash, accountId: consumer.accountId }); const contract = await consumer.getContractCode(); console.log("Size", contract.code.length, "Hash", contract.hash);
[tab] 🌐 near-kit
[tab] by account
// Create a consumer account that will use the global contract const consumerKey = generateKey(); const consumerAccountId = `${Date.now()}.${accountId}`; await near .transaction(accountId) .createAccount(consumerAccountId) .transfer(consumerAccountId, "0.1 NEAR") .addKey(consumerKey.publicKey.toString(), { type: "fullAccess" }) .send(); console.log("Created consumer account:", consumerAccountId); // Consumer uses the global contract by referencing the publisher's account ID const consumerNear = new Near({ network: "testnet", privateKey: privateKeyFromString(consumerKey.secretKey), defaultSignerId: consumerAccountId, }); await consumerNear .transaction(consumerAccountId) .deployFromPublished({ accountId: accountId }) .send(); console.log("Consumer is now using global contract from:", accountId); // Clean up: delete consumer account await consumerNear .transaction(consumerAccountId) .deleteAccount({ beneficiary: accountId }) .send(); console.log("Cleaned up consumer account");[tab] by hash
// Create a consumer account const consumerKey = generateKey(); const consumerAccountId = `${Date.now()}.${accountId}`; const createResult = await near .transaction(accountId) .createAccount(consumerAccountId) .transfer(consumerAccountId, "0.1 NEAR") .addKey(consumerKey.publicKey.toString(), { type: "fullAccess" }) .send(); await near.getTransactionStatus(createResult.transaction.hash, accountId, "FINAL"); console.log("Created consumer account:", consumerAccountId); // Consumer uses the global contract by referencing the code hash const consumerNear = new Near({ network: "testnet", privateKey: privateKeyFromString(consumerKey.secretKey), defaultSignerId: consumerAccountId, }); await consumerNear .transaction(consumerAccountId) .deployFromPublished({ codeHash: codeHash }) .send(); console.log("Consumer is now using global contract by hash"); // Clean up: delete consumer account await consumerNear .transaction(consumerAccountId) .deleteAccount({ beneficiary: accountId }) .send(); console.log("Cleaned up consumer account");
[tab] 🦀 near-api-rs
[tab] by account
To reference a global contract by account, call
use_global_account_idand pass the sourceaccountIdwhere the contract was originally deployed.let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); let my_account_id: AccountId = "my-contract.testnet".parse().unwrap(); let my_signer = Signer::from_secret_key(private_key).unwrap(); let result = Contract::deploy(my_account_id) .use_global_account_id(global_account_id) .without_init_call() .with_signer(my_signer) .send_to_testnet() .await.unwrap();[tab] by hash
To reference a global contract by hash, call
use_global_hashand pass the sourcehashof the original contract.let global_hash: types::CryptoHash = "DxfRbrjT3QPmoANMDYTR6iXPGJr7xRUyDnQhcAWjcoFF".parse().unwrap(); let account_id: AccountId = "my-contract.testnet".parse().unwrap(); let signer = Signer::from_secret_key(private_key).unwrap(); let result = Contract::deploy(account_id) .use_global_hash(global_hash) .without_init_call() .with_signer(signer) .send_to_testnet() .await.unwrap();
[tab] 🦀 near-kit-rs
[tab] by account
// The consumer uses the global contract by referencing the publisher's account ID let consumer = near.with_signer(InMemorySigner::from_secret_key( consumer_account_id.as_str(), consumer_key, )?); consumer.deploy_from(&account_id).send().await?; println!("Consumer is now using the global contract from: {account_id}");[tab] by hash
// The consumer uses the global contract by referencing the code hash let consumer = near.with_signer(InMemorySigner::from_secret_key( consumer_account_id.as_str(), consumer_key, )?); consumer.deploy_from(code_hash).send().await?; println!("Consumer is now using the global contract {code_hash}");
[tab] 🐍 near-kit-py
[tab] by account
# The consumer uses the global contract by referencing the publisher's account ID consumer = client.with_signer(KeyPairSigner(consumer_account_id, consumer_key)) consumer.send_transaction(consumer_account_id, actions=[use_global_contract(account_id=account_id)]) print(f"Consumer is now using the global contract from: {account_id}")[tab] by hash
# The consumer uses the global contract by referencing the code hash consumer = client.with_signer(KeyPairSigner(consumer_account_id, consumer_key)) consumer.send_transaction( consumer_account_id, actions=[use_global_contract(code_hash=code_hash)], wait_until="FINAL" ) print("Consumer is now using the global contract by hash")
View Function
View functions are read-only methods on a smart contract that do not modify state. You can call them without using an account or signing a transaction.
[tab] 🌐 near-api-js
// Create a testnet provider const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // For read only calls, you can use the provider directly const messages = await provider.callFunction({ contractId: 'guestbook.near-examples.testnet', method: "get_messages", args: {}, }); console.log(messages);[!TIP]
When using Typescript, you can type the return of
callFunction<T>.
[tab] 🌐 near-kit
import { privateKeyFromString } from "./helpers/private-key.js"; import dotenv from "dotenv"; import { Near } from "near-kit"; // Create a connection to testnet with credentials dotenv.config({ path: "../.env" }); const privateKey = privateKeyFromString(process.env.PRIVATE_KEY); const accountId = process.env.ACCOUNT_ID; const near = new Near({ network: "testnet", privateKey, defaultSignerId: accountId }); // For read only calls, you can use the provider directly const messages = await near.view( 'guestbook.near-examples.testnet', 'get_messages', {} ); console.log(messages);[!TIP]
When using Typescript, you can type the return of
Near.view<T>.
[tab] 🦀 near-api-rs
// Create contract object let contract_id: AccountId = "guestbook.near-examples.testnet".parse().unwrap(); let contract = Contract(contract_id.clone()); // Make a view call to a contract let view_call_result: Data<u32> = contract .call_function("total_messages", ()) .read_only() .fetch_from(&network) .await .unwrap();
[tab] 🦀 near-kit-rs
// Read-only calls do not need a signer let total: u32 = near .view("guestbook.near-examples.testnet", "total_messages") .await?; println!("Total messages: {total}"); let messages: Vec<serde_json::Value> = near .view("guestbook.near-examples.testnet", "get_messages") .args(serde_json::json!({ "from_index": "0", "limit": "10" })) .await?; println!("Messages: {messages:#?}");
[tab] 🐍 near-kit-py
# Read-only calls do not need a signer total = client.view("guestbook.near-examples.testnet", "total_messages") print(f"Total messages: {total}") messages = client.view( "guestbook.near-examples.testnet", "get_messages", {"from_index": "0", "limit": "10"} ) print(messages)
[tab] 🐍 py-near
view_call_result = await account.view_function("guestbook.near-examples.testnet", "total_messages", {}) # If args are required, they can be passed in like this in the 3rd argument: # { # "from_index": "0", # "limit": "10" # } print(view_call_result)
Keys
Get All Access Keys
[tab] 🌐 near-api-js
// Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // Query keys with the provider const keysProvider = await provider.viewAccessKeyList({ accountId }); console.log(keysProvider);
[tab] 🦀 near-api-rs
let keys = account.list_keys().fetch_from(&network).await.unwrap();
[tab] 🦀 near-kit-rs
// List all access keys of the account let keys = near.access_keys(&account_id).await?; println!("Account has {} keys", keys.keys.len());
[tab] 🐍 near-kit-py
# List all access keys of the account keys = client.access_keys(account_id) print(f"Account has {len(keys)} keys")
[tab] 🐍 py-near
keys = await account.get_access_key_list()
Add Full Access Key
A Full Access key grants complete control over the account.
Anyone with this key can transfer funds, sign transactions, interact with contracts, or even delete the account entirely.
[tab] 🌐 near-api-js
// New keys const fullKeyPair = KeyPair.fromRandom("ed25519"); const fnKeyPair = KeyPair.fromRandom("ed25519"); // Add a Full Access Key await account.addFullAccessKey( fullKeyPair.getPublicKey().toString() // ed25519:2ASWc... );
[tab] 🌐 near-kit
// Generate new key pairs const fullAccessKey = generateKey(); const functionCallKey = generateKey(); console.log("Generated Full Access Key:", fullAccessKey.publicKey.toString()); console.log("Generated Function Call Key:", functionCallKey.publicKey.toString()); // Add a Full Access Key using transaction builder await near .transaction(accountId) .addKey(fullAccessKey.publicKey.toString(), { type: "fullAccess" }) .send();
[tab] 🦀 near-api-rs
// Add full access key let new_full_private_key = signer::generate_secret_key().unwrap(); // Get the private key from the keypair let add_full_key_result = account .add_key( AccessKeyPermission::FullAccess, new_full_private_key.public_key(), ) .with_signer(signer.clone()) .send_to(&network) .await .unwrap(); let new_full_public_key = new_full_private_key.public_key().to_string(); println!("{:?}", new_full_public_key); println!("{:?}", add_full_key_result);
[tab] 🦀 near-kit-rs
// Generate new key pairs let full_access_key = SecretKey::generate_ed25519(); let function_call_key = SecretKey::generate_ed25519(); // Add a Full Access Key near.add_full_access_key(full_access_key.public_key()) .send() .await?; println!("Added Full Access Key: {}", full_access_key.public_key());
[tab] 🐍 near-kit-py
# Generate new key pairs full_access_key = generate_key() function_call_key = generate_key() # Add a Full Access Key client.send_transaction(account_id, actions=[add_full_access_key(full_access_key.public_key)]) print(f"Added Full Access Key: {full_access_key.public_key}")
[tab] 🐍 py-near
keys = await account.add_full_access_public_key("5X9WvUbRV3aSd9Py1LK7HAndqoktZtcgYdRjMt86SxMj")
Add Function Call Key
A Function Call access key is designed specifically to sign transactions that include only functionCall actions to a specific contract.
You can further restrict this key by:
- Limiting which method names can be called
- Capping the amount of
NEARthe key can spend on transaction fees
[tab] 🌐 near-api-js
// Add Function Call Access Key await account.addFunctionCallAccessKey({ publicKey: fnKeyPair.getPublicKey(), // The new public key ed25519:2ASWc... contractId: "example-contract.testnet", // Contract this key is allowed to call (optional) methodNames: ["example_method"], // Methods this key is allowed to call (optional) allowance: NEAR.toUnits("0.25") // Gas allowance key can use to call methods (optional) });
[tab] 🌐 near-kit
// Add a Function Call Access Key using transaction builder await near .transaction(accountId) .addKey(functionCallKey.publicKey.toString(), { type: "functionCall", receiverId: "example-contract.testnet", methodNames: ["example_method"], allowance: "0.25 NEAR", }) .send(); console.log(`Added Function Call Key: ${functionCallKey.publicKey.toString()}`);
[tab] 🦀 near-api-rs
// Add function call access key let new_function_call_key = AccessKeyPermission::FunctionCall(FunctionCallPermission { allowance: Some(near_api::NearToken::from_millinear(250)), // Gas allowance key can use to call methods (optional) receiver_id: "example-contract.testnet".to_string(), // Contract this key is allowed to call method_names: vec!["example_method".to_string()], // Methods this key is allowed to call }); let new_function_private_key = signer::generate_secret_key().unwrap(); let add_function_key_result = account .add_key(new_function_call_key, new_function_private_key.public_key()) .with_signer(signer.clone()) .send_to(&network) .await .unwrap(); let new_function_public_key = new_function_private_key.public_key().to_string(); println!("{:?}", new_function_public_key); println!("{:?}", add_function_key_result);
[tab] 🦀 near-kit-rs
// Add a Function Call Access Key near.transaction(&account_id) .add_function_call_key( function_call_key.public_key(), "example-contract.testnet", // contract this key is allowed to call vec!["example_method".to_string()], // methods it can call (empty = all) Some(NearToken::from_millinear(250)), // gas allowance (None = unlimited) ) .send() .await?; println!( "Added Function Call Key: {}", function_call_key.public_key() );
[tab] 🐍 near-kit-py
# Add a Function Call Access Key client.send_transaction( account_id, actions=[ add_function_call_key( function_call_key.public_key, "example-contract.testnet", # contract this key is allowed to call ["example_method"], # methods it can call (optional, empty = all) allowance="0.25 NEAR", # gas allowance (optional) ) ], ) print(f"Added Function Call Key: {function_call_key.public_key}")
[tab] 🐍 py-near
await account.add_public_key( "5X9WvUbRV3aSd9Py1LK7HAndqoktZtcgYdRjMt86SxMj", "example-contract.testnet", # Contract this key is allowed to call ["example_method"], # Methods this key is allowed to call (optional) 0.25 * NEAR # Gas allowance key can use to call methods (optional) )
[!TIP]
For security reasons, Function Call access keys can only be used with function calls that attach zero
NEARtokens. Any attempt to include a deposit will result in a failed transaction.
Delete Access Key
Accounts on NEAR can delete their own keys.
[tab] 🌐 near-api-js
await account.deleteKey(fnKeyPair.getPublicKey().toString());
[tab] 🌐 near-kit
const nearWithNewKey = new Near({ network: "testnet", privateKey: privateKeyFromString(fullAccessKey.secretKey), defaultSignerId: accountId, }); await nearWithNewKey .transaction(accountId) .deleteKey(accountId, functionCallKey.publicKey.toString()) .send();
[tab] 🦀 near-api-rs
// Delete full access key let public_key_to_delete = new_full_public_key; let delete_full_key_result = account .delete_key(public_key_to_delete.parse::<near_api::PublicKey>().unwrap()) .with_signer(signer.clone()) .send_to(&network) .await .unwrap(); println!("{:?}", delete_full_key_result); }
[tab] 🦀 near-kit-rs
// Use the new Full Access Key to delete the Function Call Key let near_with_new_key = near.with_signer(InMemorySigner::from_secret_key( account_id.as_str(), full_access_key.clone(), )?); near_with_new_key .delete_key(function_call_key.public_key()) .send() .await?; println!( "Deleted Function Call Key: {}", function_call_key.public_key() );
[tab] 🐍 near-kit-py
# Use the new Full Access Key to delete the Function Call Key client_with_new_key = client.with_signer(KeyPairSigner(account_id, full_access_key)) client_with_new_key.send_transaction(account_id, actions=[delete_key(function_call_key.public_key)]) print(f"Deleted Function Call Key: {function_call_key.public_key}")
[tab] 🐍 py-near
await account.delete_public_key("5X9WvUbRV3aSd9Py1LK7HAndqoktZtcgYdRjMt86SxMj")
[!CAUTION]
Be very careful when deleting keys. Removing all keys from an account will cause you to lose access to the account permanently.
Post-Quantum Keys (ML-DSA-65)
NEAR supports the post-quantum ml-dsa-65 signature scheme (FIPS 204). You add and use an ml-dsa-65 key exactly like an ed25519 one — it can be a full-access or function-call key and signs transactions the same way.
Run these examples from their respective package directories using the shared .env configuration in the example repository. Each registers a temporary post-quantum key, signs a testnet self-transfer, and removes the key afterward. Running them submits transactions and pays testnet gas fees.
[tab] 🌐 near-kit
import { MlDsa65KeyPair, Near } from "near-kit"; import dotenv from "dotenv"; import { privateKeyFromString } from "./helpers/private-key.js"; // Use an existing testnet account and its full-access key. dotenv.config({ path: "../.env" }); const accountId = process.env.ACCOUNT_ID; if (!accountId) throw new Error("ACCOUNT_ID is required"); const near = new Near({ network: "testnet", privateKey: privateKeyFromString(process.env.PRIVATE_KEY), defaultSignerId: accountId, }); // Generate an ML-DSA-65 key and register it using the existing signer. const pqKey = MlDsa65KeyPair.fromRandom(); const publicKey = pqKey.publicKey.toString(); await near .transaction(accountId) .addKey(publicKey, { type: "fullAccess" }) .send({ waitUntil: "FINAL" }); console.log("Added post-quantum key:", publicKey); try { const pqNear = new Near({ network: "testnet", privateKey: privateKeyFromString(pqKey.secretKey), defaultSignerId: accountId, }); // Sign a self-transfer with the post-quantum key. This transaction pays gas. await pqNear.transaction(accountId).transfer(accountId, "0.001 NEAR").send(); console.log("Post-quantum self-transfer completed"); } finally { // Remove the temporary key using the original signer. await near.transaction(accountId).deleteKey(accountId, publicKey).send(); console.log("Removed post-quantum key:", publicKey); }
[tab] 🦀 near-kit-rs
use near_kit::signer::{InMemorySigner, SecretKey}; use near_kit::transaction::Final; use near_kit::{Error, Near, NearToken}; #[tokio::main] async fn main() -> Result<(), Error> { // Use an existing testnet account and its full-access key. dotenvy::from_filename("../.env").ok(); let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY"); let account_id = std::env::var("ACCOUNT_ID").expect("ACCOUNT_ID"); let near = Near::testnet() .credentials(&private_key, &account_id)? .build(); // Generate an ML-DSA-65 key and register it using the existing signer. let pq_key = SecretKey::generate_ml_dsa65(); let public_key = pq_key.public_key(); near.add_full_access_key(public_key.clone()) .send() .wait_until::<Final>() .await?; println!("Added post-quantum key: {public_key}"); let pq_near = near.with_signer(InMemorySigner::from_secret_key( account_id.as_str(), pq_key, )?); // Sign a self-transfer with the post-quantum key. This transaction pays gas. let transfer_result = pq_near .transfer(account_id.as_str(), NearToken::from_millinear(1)) .await; // Remove the temporary key using the original signer, even if the transfer failed. near.delete_key(public_key.clone()) .send() .wait_until::<Final>() .await?; println!("Removed post-quantum key: {public_key}"); transfer_result?; println!("Post-quantum self-transfer completed"); Ok(()) }
[tab] 🐍 near-kit-py
import os from dotenv import load_dotenv from near import KeyPairSigner, MlDsa65KeyPair, Near, add_full_access_key, delete_key def main() -> None: # Use an existing testnet account and its full-access key. load_dotenv("../.env") account_id = os.environ["ACCOUNT_ID"] client = Near(network="testnet", account_id=account_id, private_key=os.environ["PRIVATE_KEY"]) # Generate an ML-DSA-65 key and register it using the existing signer. pq_key = MlDsa65KeyPair.generate() client.send_transaction( account_id, actions=[add_full_access_key(pq_key.public_key)], wait_until="FINAL" ) print(f"Added post-quantum key: {pq_key.public_key}") try: pq_client = Near(network="testnet", signer=KeyPairSigner(account_id, pq_key)) # Sign a self-transfer with the post-quantum key. This transaction pays gas. pq_client.send(account_id, "0.001 NEAR") print("Post-quantum self-transfer completed") finally: # Remove the temporary key using the original signer. client.send_transaction(account_id, actions=[delete_key(pq_key.public_key)]) print(f"Removed post-quantum key: {pq_key.public_key}") if __name__ == "__main__": main()
[!NOTE]
On-chain, an
ml-dsa-65access key is stored as a 32-byte hash, so key listings such asview_access_key_listreturn anml-dsa-65-hash:handle instead of the full key — parse it withparseMlDsa65Handle()(near-kit) for display and comparison; the handle cannot sign or be re-added. Post-quantum keys are currently supported bynear-kit(TypeScript),near-kit-rs(Rust) andnear-kit-py(Python); support innear-api-jsis pending.
Validate Message Signatures
Connected wallets can sign NEP-413 messages with signMessage. Use NEAR Connect to connect the wallet, then verify its signature with the following code:
[tab] 🌐 near-api-js
import { verifyMessage } from "near-api-js/nep413"; import { JsonRpcProvider } from "near-api-js"; // Create a connection to testnet RPC const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com", }); // This is the challenge given to the user to sign const MESSAGE: string = "log me in"; const APP: string = "http://localhost:3000"; const CHALLENGE: Buffer = Buffer.from(Array.from(Array(32).keys())); // This is the object returned by `wallet.signMessage` in wallet selector const walletReturn = { "signature": "IfModLa3g3czlyPhkg/LSkTFSy7XCGreStZJTDIO1m3viEnYFLdXfpz1gYUVKYv3W2vwcV77TmGEzc9y0Nz+AA==", "accountId": "maguila.testnet", "publicKey": "ed25519:AtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm" }; await verifyMessage({ signerAccountId: walletReturn.accountId, signerPublicKey: walletReturn.publicKey, signature: new Uint8Array(Buffer.from(walletReturn.signature, 'base64')), payload: { message: MESSAGE, recipient: APP, nonce: CHALLENGE }, provider });
[tab] 🌐 near-kit
import { Near, verifyNep413Signature } from "near-kit"; // Create Near client for testnet (no signing needed for verification) const near = new Near({ network: "testnet" }); // The challenge given to the user to sign const MESSAGE = "log me in"; const APP = "http://localhost:3000"; const CHALLENGE = Buffer.from(Array.from(Array(32).keys())); // The object returned by wallet.signMessage in wallet selector const signedMessage = { signature: "IfModLa3g3czlyPhkg/LSkTFSy7XCGreStZJTDIO1m3viEnYFLdXfpz1gYUVKYv3W2vwcV77TmGEzc9y0Nz+AA==", accountId: "maguila.testnet", publicKey: "ed25519:AtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm", }; // Verify the signature const isValid = await verifyNep413Signature( signedMessage, { message: MESSAGE, recipient: APP, nonce: CHALLENGE, }, { near, maxAge: Infinity }, // Disable timestamp check for this demo ); console.log("Signature valid:", isValid);
[tab] 🦀 near-kit-rs
Use the original challenge stored by your application. Generate its nonce with
near_kit::standards::nep413::generate_nonce()so the default timestamp-expiry check applies, and reject reused challenges.use near_kit::{ standards::nep413::{self, SignMessageParams, SignedMessage}, Error, Near, }; // Verify the original challenge stored by the application, not client-supplied parameters. async fn verify_wallet_message( signed: &SignedMessage, challenge: &SignMessageParams, ) -> Result<bool, Error> { let near = Near::testnet().build(); // Checks the signature, timestamp-nonce expiry, account ownership, and FullAccess permission. nep413::verify(signed, challenge, &near, Default::default()).await }
nep413::verifychecks the signature, nonce expiry, and that the signing key is a full-access key on the claimed account.
[tab] 🐍 near-kit-py
Use the original message, recipient, and nonce stored by your application. Generate the nonce with
near.generate_nonce()so the default timestamp-expiry check applies, and reject reused challenges.from near import AccessKeyNotFoundError, Near, SignedMessage, generate_nonce, verify_message # Verify the original challenge stored by the application, not client-supplied parameters. def verify_wallet_message( signed: SignedMessage, message: str, recipient: str, nonce: bytes ) -> bool: if not verify_message(signed, message, recipient, nonce): return False # Signature verification alone does not prove account ownership. client = Near(network="testnet") try: key = client.access_key(signed.account_id, signed.public_key) except AccessKeyNotFoundError: return False return key.permission == "FullAccess"
verify_messagechecks the signature and nonce expiry. The additional RPC query verifies that the signing key is a full-access key on the claimed account.
Additional resources
[tab] 🌐 near-api-js
[tab] 🌐 near-kit
[tab] 🦀 near-api-rs
[tab] 🦀 near-kit-rs
[tab] 🐍 near-kit-py
[tab] 🐍 py-near