Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Welcome to near-kit.

If you have built on NEAR before, you know that the tooling can sometimes feel... heavy. near-kit is an attempt to fix that. It is a modern, lightweight, and opinionated TypeScript SDK designed to make interacting with the NEAR blockchain feel as simple as using fetch.

Why near-kit?

1. It Speaks Human

We hate calculating zeros. Instead of forcing you to convert units manually or worry about big integers, near-kit accepts strings like "10.5 NEAR" or "30 Tgas". The library handles the math and conversion for you.

2. Fluent API

Building transactions shouldn't require an instruction manual. near-kit uses a chainable builder pattern that makes your code read like a sentence:

await near
  .transaction("alice.near")
  .createAccount("bob.alice.near")
  .transfer("bob.alice.near", "10 NEAR")
  .send()

3. Type Safety

We leverage TypeScript to catch bugs before you run your code. From typed errors to fully typed contract interfaces, your IDE is your best friend.

4. Universal

Whether you are writing a backend script, a frontend React dApp, or an integration test, your code looks exactly the same. Learn the API once, use it everywhere.

What's Next?

Ready to write some code? Jump to the Quickstart.

Quickstart

Let's get you connected to the NEAR Testnet and sending your first transaction in under 2 minutes.

Prerequisites

  • Node.js or Bun installed.
  • A NEAR Testnet Account.
  • Your account's Private Key (starts with ed25519:...).

Installation

# npm
npm install near-kit

# bun
bun add near-kit

# yarn
yarn add near-kit

Your First Script

Create a file named index.ts.

import { Near } from "near-kit"

async function main() {
  // 1. Initialize the client
  // In a real app, ALWAYS use environment variables for keys!
  const near = new Near({
    network: "testnet",
    privateKey: "ed25519:...", // Replace with your actual private key
    defaultSignerId: "your-account.testnet", // Replace with your account ID
  })

  console.log("🔗 Connected to Testnet")

  // 2. Read some data (Free!)
  // Let's check the balance of the account we just connected
  const balance = await near.getBalance("your-account.testnet")
  console.log(`💰 Balance: ${balance}`)

  // 3. Write some data (Send a transaction)
  // We will send 1 NEAR to ourselves.
  // Note: "1 NEAR" is a string. No math required.
  console.log("💸 Sending 1 NEAR...")

  const result = await near
    .transaction("your-account.testnet")
    .transfer("your-account.testnet", "1 NEAR")
    .send()

  console.log(`✅ Transaction successful!`)
  console.log(`   Hash: ${result.transaction.hash}`)
}

main().catch(console.error)

Run it:

# using bun
bun run index.ts

# using tsx
npx tsx index.ts

The Mental Model

To use near-kit effectively, there are only three concepts you need to internalize. If you understand these, you can guess 90% of the API.

1. Everything hangs off Near

The Near class is your single entry point. You initialize it once with your configuration (network and credentials), and reuse that instance everywhere.

const near = new Near({ ...config });

// All operations start here:
near.view(...)
near.transaction(...)
near.getBalance(...)

2. We hate math (Units)

Blockchain development is plagued by unit conversion errors. near-kit solves this by using Human-Readable Strings.

  • Amounts: Never count 24 zeros. Use "1.5 NEAR" or "1 yocto".
  • Gas: Never calculate gas units. Use "30 Tgas".

The library parses these strings automatically. If you try to pass a raw number where a unit is expected, near-kit will throw an error to protect you from mistakes.

// ✅ Good
near.transfer("bob.near", "5 NEAR")

// ❌ Bad (Throws Error)
near.transfer("bob.near", 5) // Did you mean 5 NEAR or 5 yocto?

3. Transactions are Fluent Chains

NEAR transactions can contain multiple actions (e.g., "Create Account" AND "Transfer Money"). near-kit models this as a fluent chain.

  1. Start: .transaction(signer) identifies who is paying.
  2. Chain: Add as many actions as you want (.transfer, .functionCall).
  3. Send: Call .send() to sign and broadcast.
await near
  .transaction("alice.near") // 1. Start
  .createAccount("bob.alice.near") // 2. Chain
  .transfer("bob.alice.near", "1 NEAR") // 2. Chain
  .send() // 3. Send

Migrating from near-api-js

If you are coming from near-api-js, you will find near-kit to be more concise and less prone to "unit arithmetic" errors.

Core Philosophy Shifts

  1. No Account Object: In near-api-js, you create an Account object and call methods on it. In near-kit, you use the central Near instance and pass the signer's ID as an argument.

  2. Strings, not BigInts: You never need to import utils.format to calculate gas or storage costs. Just use type-safe strings.

  3. Fluent Builder: Instead of passing massive configuration objects, you chain readable methods.


Side-by-Side Comparisons

1. Connecting & Keys

near-api-js: You have to manually assemble the Account, JsonRpcProvider, and KeyPairSigner.

import { Account } from "@near-js/accounts"
import { JsonRpcProvider } from "@near-js/providers"
import { KeyPairSigner } from "@near-js/signers"

const provider = new JsonRpcProvider({
  url: "https://test.rpc.fastnear.com",
})
const signer = KeyPairSigner.fromSecretKey("ed25519:...")
const account = new Account("alice.testnet", provider, signer)

near-kit: Configuration is flattened. The KeyStore is optional for simple cases.

const near = new Near({
  network: "testnet",
  privateKey: "ed25519:...", // Automatically sets up an InMemoryKeyStore
  defaultSignerId: "alice.testnet",
})

2. Handling Units

near-api-js: Requires manual conversion, often leading to BN (BigNumber) headaches.

import { parseNearAmount } from "@near-js/utils"

const amount = parseNearAmount("10.5") // "1050000..."
const gas = "30000000000000" // Hope you counted the zeros right!

near-kit: Parses human-readable strings automatically.

const amount = "10.5 NEAR"
const gas = "30 Tgas"

3. Calling Contracts

near-api-js: Arguments are passed inside a configuration object.

const account = new Account("alice.testnet", provider, signer)

await account.callFunction({
  contractId: "market.near",
  methodName: "buy",
  args: { id: "1" },
  gas: "50000000000000",
  deposit: parseNearAmount("1")!,
})

near-kit: Uses a fluent chain.

await near
  .transaction("alice.testnet")
  .functionCall(
    "market.near",
    "buy",
    { id: "1" },
    { gas: "50 Tgas", attachedDeposit: "1 NEAR" }
  )
  .send()

4. Error Handling

near-api-js: Often throws raw RPC errors or generic "TypedErrors" that are hard to parse.

try {
  // ...
} catch (e) {
  // You have to inspect e.type or e.message string matching
  if (e.type === 'FunctionCallError') { ... }
}

near-kit: Throws distinct, standard JavaScript Error subclasses.

import { FunctionCallError } from "near-kit"

try {
  // ...
} catch (e) {
  if (e instanceof FunctionCallError) {
    console.log(e.panic) // Access the panic message directly
  }
}

5. Access Keys

near-api-js:

await account.addFunctionCallAccessKey({
  publicKey,
  contractId: "market.near",
  methodNames: ["buy"],
  allowance: parseNearAmount("0.25")!,
})

near-kit: Explicitly typed permissions object.

await near
  .transaction("alice.testnet")
  .addKey(publicKey, {
    type: "functionCall",
    receiverId: "market.near",
    methodNames: ["buy"],
    allowance: "0.25 NEAR",
  })
  .send()

Reading Data

Reading data from the blockchain is free, fast, and does not require a private key (unless you are connecting to a private node).

1. Calling View Methods

Use near.view() to query smart contracts. This connects to the RPC and fetches the state directly.

Basic Usage

Arguments are automatically JSON-serialized for you.

const messages = await near.view(
  "guestbook.near", // Contract ID
  "get_messages", // Method Name
  { limit: 10 } // Arguments object
)

Typed Return Values

By default, near.view returns any. You can pass a generic type to get a strongly-typed response.

// Define what the contract returns
type Message = {
  sender: string
  text: string
}

// Pass the type to .view<T>()
const messages = await near.view<Message[]>("guestbook.near", "get_messages", {
  limit: 10,
})

// Now 'messages' is typed as Message[]
console.log(messages[0].sender)

Pro Tip: For even better type safety (including arguments), check out Type-Safe Contracts.

2. Reading Historical Data (Time Travel)

By default, you read from the "Optimistic" head of the chain (the absolute latest state). Sometimes you need to read from a specific point in the past, or ensure you are reading fully finalized data.

Most read methods accept an options object as the last argument.

// Read from a specific Block Height
await near.view(
  "token.near",
  "ft_balance_of",
  { account_id: "alice.near" },
  {
    blockId: 120494923,
  }
)

// Read from a specific Block Hash
await near.getBalance("alice.near", {
  blockId: "GZ8vK...",
})

// Read only "Final" data (slower, but immutable)
await near.view(
  "game.near",
  "get_winner",
  {},
  {
    finality: "final",
  }
)

3. Checking Balances

Use near.getBalance() to get a formatted, human-readable string.

const balance = await near.getBalance("alice.near")
console.log(balance) // "10.50 NEAR"

Note: If you need the raw BigInt value for calculations (yoctoNEAR), use near.getAccountDetails("alice.near").

4. Batching Requests

If you need to make multiple read calls at once, use near.batch(). This runs them in parallel (like Promise.all) but preserves the types of the results in the returned tuple.

const [balance, messages, exists] = await near.batch(
  near.getBalance("alice.near"),
  near.view<Message[]>("guestbook.near", "get_messages", {}),
  near.accountExists("bob.near")
)

5. Network Status

Use near.getStatus() to check the health of the node and the latest block height.

const status = await near.getStatus()
console.log(`Latest Block: ${status.latestBlockHeight}`)
console.log(`Syncing: ${status.syncing}`)

Writing Data (Transactions)

To change state on the blockchain (send money, write to a contract), you must send a transaction. Transactions cost Gas and must be signed by an account with a private key.

1. The Transaction Builder

The primary way to write data is the fluent Transaction Builder. It allows you to chain multiple actions into a single, atomic package.

const result = await near
  .transaction("alice.near") // 1. Who is paying? (Signer)
  .functionCall(
    // 2. Action
    "guestbook.near",
    "add_message",
    { text: "Hello World" }
  )
  .send() // 3. Sign & Broadcast

2. Atomicity (Batching Actions)

You can chain multiple actions in one transaction. This is atomic: either every action succeeds, or the entire transaction is rolled back.

This is perfect for scenarios like "Create an account AND fund it AND deploy a contract."

await near
  .transaction("alice.near")
  .createAccount("bob.alice.near") // 1. Create
  .transfer("bob.alice.near", "1 NEAR") // 2. Fund
  .deployContract("bob.alice.near", code) // 3. Deploy Code
  .functionCall("bob.alice.near", "init", {}) // 4. Initialize
  .send()

If the init call fails, the account bob.alice.near will not be created, and the 1 NEAR will stay with Alice.

3. Attaching Gas & Deposits

When calling a function, you often need to attach Gas (computation limit) or a Deposit (real NEAR tokens).

These are passed as the 4th argument (the options object) to .functionCall.

await near
  .transaction("alice.near")
  .functionCall(
    "nft.near",
    "mint_token",
    { token_id: "1" },
    {
      gas: "100 Tgas", // Limit for complex operations
      attachedDeposit: "0.1 NEAR", // Payment (e.g. for storage)
    }
  )
  .send()
  • Gas: Defaults to 30 Tgas. Increase this for complex calculations.
  • Deposit: Defaults to 0. Required if the contract needs to pay for storage or if you are transferring value to a contract.

4. Working with Amounts (Dynamic Values)

For dynamic calculations, use the Amount helper instead of manually constructing strings:

import { Amount } from "near-kit"

// Dynamic NEAR amounts
const basePrice = 5
const quantity = 2
await near.send("bob.near", Amount.NEAR(basePrice * quantity)) // "10 NEAR"

// Fractional amounts
await near.send("bob.near", Amount.NEAR(10.5)) // "10.5 NEAR"

// YoctoNEAR (10^-24 NEAR) for precise calculations
await near.send("bob.near", Amount.yocto(1000000n)) // "1000000 yocto"

// Or pass bigint directly (treated as yoctoNEAR)
await near.send("bob.near", 1000000n)

Constants:

  • Amount.ZERO"0 yocto"
  • Amount.ONE_NEAR"1 NEAR"
  • Amount.ONE_YOCTO"1 yocto"

5. Shortcuts

For simple, single-action transactions, near-kit provides shortcuts. These are just syntax sugar around the builder.

// Shortcut for .transaction().transfer().send()
await near.send("bob.near", "5 NEAR")

// Shortcut for .transaction().functionCall().send()
await near.call("counter.near", "increment", {})

6. Inspecting the Result

The .send() method returns a FinalExecutionOutcome object. This contains everything that happened on-chain.

const result = await near.call(...);

// ✅ Check the Transaction Hash
console.log("Tx Hash:", result.transaction.hash);

// 📜 Check Logs
// Logs from ALL receipts in the transaction are collected here
const logs = result.receipts_outcome.flatMap(r => r.outcome.logs);
console.log("Contract Logs:", logs);

// 📦 Get the Return Value
// If the contract returned data (e.g. a JSON object), it's base64 encoded here.
if (result.status.SuccessValue) {
  const raw = Buffer.from(result.status.SuccessValue, 'base64').toString();
  const value = JSON.parse(raw);
  console.log("Returned:", value);
}

7. Execution Speed (WaitUntil)

By default, .send() waits until the transaction is "Optimistically Executed" (usually 1-2 seconds). You can change this behavior.

// Fast: Returns as soon as the network accepts it. No return value available.
.send({ waitUntil: "INCLUDED" })

// Slow: Waits for full BFT Finality (extra 2-3 seconds). 100% irreversible.
.send({ waitUntil: "FINAL" })

Type-Safe Contracts

Using near.view and near.call works well for quick scripts, but they are "stringly typed." If you misspell a method name or pass the wrong arguments, you won't know until your code crashes at runtime.

near-kit allows you to define a TypeScript interface for your contract. This gives you:

  1. Autocomplete for method names.
  2. Type Checking for arguments and return values.
  3. Inline Documentation (if you add JSDoc comments).

1. Define the Interface

You define a type that passes a generic object to Contract. This object must have view and call properties describing your methods.

import type { Contract } from "near-kit"

// Define your data shapes
type Message = { sender: string; text: string }

// Define the contract type using the Generic syntax
type Guestbook = Contract<{
  view: {
    // MethodName: (args: Arguments) => Promise<ReturnValue>
    get_messages: (args: { limit: number }) => Promise<Message[]>
    total_messages: () => Promise<number>
  }
  call: {
    // Call methods usually return void, but can return values too
    add_message: (args: { text: string }) => Promise<void>
  }
}>

2. Create the Proxy

Use the near.contract<T>() method to create a typed proxy for a specific contract ID.

// This object now has all the methods defined in your type
const guestbook = near.contract<Guestbook>("guestbook.near")

3. Use it!

You can now access methods directly under .view and .call.

Calling Views

Arguments are passed as the first parameter.

// ✅ Autocomplete works here!
const messages = await guestbook.view.get_messages({ limit: 5 })

// ❌ TypeScript Error: Argument 'limit' is missing
// const messages = await guestbook.view.get_messages({});

Calling Change Methods

Change methods take two arguments:

  1. Args: The arguments for the contract function.
  2. Options: (Optional) Gas and Attached Deposit.
await guestbook.call.add_message(
  { text: "Hello Types!" }, // 1. Contract Args
  { gas: "30 Tgas", attachedDeposit: "0.1 NEAR" } // 2. Transaction Options
)

Frontend Integration

near-kit is designed to work seamlessly in browser environments. The logic for building transactions is identical to the server-side; the only difference is that signing is delegated to the user's wallet via an Adapter.

🚀 Live Demo

See near-kit running in a real React application using both Wallet Selector and HOT Connector.


The Adapter Pattern

You initialize the Near instance with a wallet object instead of a private key.

const near = new Near({
  network: "testnet",
  wallet: fromWalletSelector(wallet), // <--- The Adapter
})

Once initialized, you use near exactly as you would in a backend script. When you call .send(), the library automatically triggers the wallet's popup for the user to approve the transaction.

1. NEAR Wallet Selector

@near-wallet-selector is a popular library for connecting to the NEAR ecosystem. It supports MyNearWallet, Meteor, Sender, Here, and many others.

Setup

import { setupWalletSelector } from "@near-wallet-selector/core"
import { setupModal } from "@near-wallet-selector/modal-ui"
import { Near, fromWalletSelector } from "near-kit"

// 1. Initialize Wallet Selector (Standard Setup)
const selector = await setupWalletSelector({
  network: "testnet",
  modules: [
    /* ... wallet modules ... */
  ],
})

const modal = setupModal(selector, { contractId: "guestbook.near" })

// 2. Connect
if (!selector.isSignedIn()) {
  modal.show()
}

// 3. Create the Near Instance
const wallet = await selector.wallet()

const near = new Near({
  network: "testnet",
  wallet: fromWalletSelector(wallet),
})

// 4. Transact
await near.send("bob.near", "1 NEAR")

2. HOT Connector

HOT Connector is the new standard, and intended to replace NEAR Wallet Selector.

Setup

import { NearConnector } from "@hot-labs/near-connect"
import { Near, fromHotConnect } from "near-kit"

// 1. Initialize Connector
const connector = new NearConnector({ network: "testnet" })

// 2. Listen for Sign In
connector.on("wallet:signIn", async (data) => {
  // 3. Create Near Instance
  const near = new Near({
    network: "testnet",
    wallet: fromHotConnect(connector),
  })

  console.log("Connected via HOT!")
})

// Trigger connection
connector.connect()

Best Practice: React Context

In a React application, you should create a WalletContext to store the Near instance so it can be accessed by any component.

Here is a simplified pattern from the Guestbook Demo:

// WalletProvider.tsx
import { createContext, useContext, useEffect, useState } from "react"
import { Near, fromWalletSelector } from "near-kit"

const WalletContext = createContext<{ near: Near | null }>(null)

export const WalletProvider = ({ children }) => {
  const [near, setNear] = useState<Near | null>(null)

  useEffect(() => {
    // ... setup selector ...
    const wallet = await selector.wallet()

    if (wallet) {
      setNear(
        new Near({
          network: "testnet",
          wallet: fromWalletSelector(wallet),
        })
      )
    }
  }, [])

  return (
    <WalletContext.Provider value={{ near }}>{children}</WalletContext.Provider>
  )
}

// Use it in any component!
export const useWallet = () => useContext(WalletContext)

The "Universal Code" Pattern

One of near-kit's superpowers is that it abstracts the "Signer."

Whether a transaction is signed by a generic private key, a secure server-side keystore, or a user's browser wallet, the code to build and send that transaction is identical.

This allows you to write "Universal Business Logic"—functions that define what to do, without caring where they are running.

1. The Business Logic

First, write your logic as a standalone function. It should accept a Near instance and a signerId.

// src/features/buy-item.ts
import { Near } from "near-kit"

/**
 * Buys an item from the market.
 * This function works on the Server, Client, and in Tests!
 */
export async function buyItem(near: Near, signerId: string, itemId: string) {
  console.log(`Attempting to buy ${itemId} as ${signerId}...`)

  return await near
    .transaction(signerId)
    .functionCall(
      "market.near",
      "buy",
      { item_id: itemId },
      { attachedDeposit: "1 NEAR", gas: "50 Tgas" }
    )
    .send()
}

2. Injecting the Near Instance

Now, let's see how to initialize the Near object for different environments.

Quick Scripts (The Convenience Way)

For simple scripts, passing a raw private key is the easiest method.

import { Near } from "near-kit"
import { buyItem } from "./features/buy-item"

const near = new Near({
  network: "testnet",
  privateKey: process.env.ADMIN_KEY, // "ed25519:..."
  defaultSignerId: "admin.testnet",
})

await buyItem(near, "admin.testnet", "sword-1")

Robust Backend (The KeyStore Way)

In production server environments, you rarely handle raw private keys strings manually. You use a KeyStore.

A KeyStore manages keys for multiple accounts. When you call .transaction("alice.near"), the Near instance automatically looks up Alice's key in the store.

import { Near } from "near-kit"
import { FileKeyStore } from "near-kit/keys" // Node.js only
import { buyItem } from "./features/buy-item"

// Load keys from the standard ~/.near-credentials directory
const keyStore = new FileKeyStore("~/.near-credentials", "testnet")

const near = new Near({
  network: "testnet",
  keyStore: keyStore, // Pass the store instead of a single key
})

// The Near instance will look inside ~/.near-credentials/testnet/worker.json
// to find the key for 'worker.testnet'
await buyItem(near, "worker.testnet", "sword-1")

Why use a KeyStore? It allows your app to manage multiple accounts (e.g., worker-1, worker-2, admin) seamlessly. You just switch the signerId argument, and near-kit finds the right key.

Frontend (The Wallet Way)

In the browser, you don't have private keys. You have a Wallet Adapter.

import { Near, fromWalletSelector } from "near-kit"
import { buyItem } from "./features/buy-item"

// ... setup wallet selector ...
const wallet = await selector.wallet()

const near = new Near({
  network: "testnet",
  wallet: fromWalletSelector(wallet), // Adapter handles signing
})

const userAccount = (await wallet.getAccounts())[0].accountId

// The wallet popup will appear for the user!
await buyItem(near, userAccount, "sword-1")

Testing (The Sandbox Way)

In tests, near-kit auto-configures everything when you pass the sandbox object.

import { Near } from "near-kit"
import { Sandbox } from "near-kit/sandbox"
import { buyItem } from "./features/buy-item"

const sandbox = await Sandbox.start()

const near = new Near({
  network: sandbox, // Auto-configures RPC and Root Key
})

await buyItem(near, sandbox.rootAccount.id, "sword-1")

Summary

EnvironmentConfig OptionWho Signs?
ScriptprivateKey: "..."The provided key string.
BackendkeyStore: storeThe key matching signerId found in the store.
Frontendwallet: adapterThe user (via wallet popup).
Sandboxnetwork: sandboxThe root account key (in-memory).

Your business logic (buyItem) never needs to know which one is happening.

Testing with Sandbox

Don't rely on Testnet for integration tests. It's slow, you need a faucet, and other people can mess up your state. near-kit includes a built-in Sandbox manager that runs a local NEAR node for you.

Setup

We recommend using a test runner like bun:test, jest, or vitest.

import { Near } from "near-kit"
import { Sandbox } from "near-kit/sandbox"
import { beforeAll, afterAll, test, expect } from "bun:test"

let sandbox: Sandbox
let near: Near

// 1. Start Sandbox
beforeAll(async () => {
  // This downloads a NEAR binary and starts a node locally
  sandbox = await Sandbox.start()

  // near-kit automatically configures the RPC and
  // loads the root account key for you.
  near = new Near({ network: sandbox })
})

// 2. Stop Sandbox
afterAll(async () => {
  if (sandbox) await sandbox.stop()
})

// 3. Write Tests
test("can create account", async () => {
  const newAccount = `test.${sandbox.rootAccount.id}`

  await near
    .transaction(sandbox.rootAccount.id)
    .createAccount(newAccount)
    .send()

  const exists = await near.accountExists(newAccount)
  expect(exists).toBe(true)
})

Pre-Funded Accounts

The sandbox comes with one "Root Account" (test.near) that has a massive balance. Use this account to create sub-accounts for your tests.

const root = sandbox.rootAccount
console.log(root.id) // "test.near"
console.log(root.secretKey) // "ed25519:..."

Error Handling

near-kit converts cryptic RPC JSON errors into typed JavaScript Error classes. You can catch these errors and handle them logically.

The Error Hierarchy

All errors extend NearError. You can check for specific types using instanceof.

import {
  FunctionCallError,
  AccountDoesNotExistError,
  NetworkError,
} from "near-kit"

try {
  await near.call("contract.near", "method", {})
} catch (e) {
  if (e instanceof FunctionCallError) {
    // The contract logic failed
    console.log("Panic:", e.panic)
    console.log("Logs:", e.logs)
  } else if (e instanceof AccountDoesNotExistError) {
    // The account isn't real
    console.log(`Account ${e.accountId} not found`)
  } else if (e instanceof NetworkError) {
    // RPC is down
    if (e.retryable) {
      // You might want to try again
    }
  }
}

Panic Messages

When a contract fails, the most important info is the Panic Message. near-kit extracts this from the deep RPC response and puts it right on error.panic.

Common panics include:

  • ERR_NOT_ENOUGH_FUNDS
  • ERR_INVALID_ARGUMENT
  • Smart contract panicked: ...

Use this string to debug or show user-friendly error messages in your UI.

Key Management

Managing private keys securely is the most critical part of any blockchain application. near-kit provides several KeyStore implementations to suit different environments, from temporary testing to secure production servers.

1. InMemoryKeyStore (Testing & Scripts)

  • Best for: Unit tests, CI/CD, and short-lived scripts.

This store keeps keys in a plain JavaScript object in RAM. If the process exits, the keys are gone.

import { InMemoryKeyStore, Near } from "near-kit"

const keyStore = new InMemoryKeyStore()

// You can pre-seed it with keys
await keyStore.add("alice.testnet", parseKey("ed25519:..."))

const near = new Near({
  network: "testnet",
  keyStore: keyStore,
})

2. FileKeyStore (Dev & Servers)

  • Best for: Local development and simple server deployments.
  • Requires: Node.js or Bun.

This store reads and writes keys to JSON files in the standard ~/.near-credentials directory. This makes it compatible with the NEAR CLI.

import { FileKeyStore } from "near-kit/keys";

// Uses ~/.near-credentials/testnet/
const keyStore = new FileKeyStore("~/.near-credentials", "testnet");

const near = new Near({
  network: "testnet",
  keyStore: keyStore
});

// Now you can sign for any account in that folder
await near.transaction("alice.testnet")...

3. NativeKeyStore (Maximum Security)

  • Best for: Production servers, desktop apps, and CLI tools.
  • Requires: Node.js/Bun and @napi-rs/keyring.

This store uses your operating system's native secure credential storage (Keychain on macOS, Credential Manager on Windows, libsecret on Linux). Keys are encrypted by the OS and protected by the user's password/biometrics.

# Install the native dependency first
npm install @napi-rs/keyring
import { NativeKeyStore } from "near-kit/keys/native"

// Keys are stored in the OS Keychain under "NEAR Credentials"
const keyStore = new NativeKeyStore()

const near = new Near({
  network: "mainnet",
  keyStore,
})

// The first time you add a key, the OS may prompt for permission
await keyStore.add("secure-admin.near", keyPair)

Note: OS Keyrings do not allow listing all keys for security reasons. You must know the accountId you want to retrieve.

4. RotatingKeyStore (High Throughput)

  • Best for: Trading bots, faucets, and high-traffic relayers.

The NEAR network processes transactions sequentially for each Access Key. If you try to send 50 transactions in parallel from one account, most will fail with InvalidNonce errors.

RotatingKeyStore solves this by managing multiple keys for a single account and rotating through them round-robin.

import { RotatingKeyStore } from "near-kit"

const keyStore = new RotatingKeyStore({
  "bot.near": ["ed25519:key1...", "ed25519:key2...", "ed25519:key3..."],
})

const near = new Near({ keyStore })

// Now you can fire off concurrent requests!
await Promise.all([
  near.send("a.near", "1 NEAR"),
  near.send("b.near", "1 NEAR"), // Uses Key 2
  near.send("c.near", "1 NEAR"), // Uses Key 3
])

Permissions

When you add a key to an account (using .addKey), you define what that key can do.

Full Access

Can do anything: transfer NEAR, delete the account, deploy code, add more keys.

  • Use case: Your main admin key.

Function Call Access

Can only call specific methods on a specific contract. It cannot transfer NEAR.

  • Use case: "Log in with NEAR", limited session keys, automated agents.
await near
  .transaction("alice.near")
  .addKey(newPublicKey, {
    type: "functionCall",
    receiverId: "game.near",
    methodNames: ["move", "attack"], // Only these methods
    allowance: "0.25 NEAR", // Max gas fees this key can spend
  })
  .send()

Advanced Transactions

The TransactionBuilder allows you to orchestrate complex account management and state changes. Understanding how these actions execute is critical for writing safe smart contracts and scripts.

1. Atomicity & Execution

All actions added to a single transaction are executed sequentially as one atomic unit on-chain:

  • If all actions succeed, the state changes produced by those actions are committed and any promises (cross-contract calls) they created are emitted.
  • If any action fails (e.g. a function call panics, a transfer hits insufficient balance, etc.), the entire transaction is reverted.

In other words: within one transaction / receipt, it’s all-or-nothing.

The atomicity boundary stops at the transaction itself

Cross-contract calls created by your transaction run later as separate receipts. Once those child receipts have been emitted and executed on other contracts, their effects are not automatically rolled back if something else fails later—you must implement any “compensating logic” yourself.

2. The "Factory" Pattern (Batching)

Because of the atomicity rules above, we can safely use the "Factory" pattern: creating a new sub-account, funding it, deploying a contract to it, and initializing that contract—all in one transaction.

Why do it this way?

If you deploy a contract but forget to initialize it, anyone could call init and take ownership. By bundling deploy and init in one atomic transaction, you guarantee that only you can initialize it, and if initialization fails, the account creation is rolled back entirely.

import { generateKey } from "near-kit"
import { readFileSync } from "fs"

const wasm = readFileSync("./token.wasm")
const newKey = generateKey() // Generate a fresh KeyPair

const newAccountId = "token.alice.near"

await near
  .transaction("alice.near")
  // 1. Create the account on-chain
  .createAccount(newAccountId)

  // 2. Fund it (needs storage for the contract)
  .transfer(newAccountId, "6 NEAR")

  // 3. Add a Full Access Key so we can control it later
  .addKey(newKey.publicKey.toString(), { type: "fullAccess" })

  // 4. Deploy the compiled Wasm
  .deployContract(newAccountId, wasm)

  // 5. Initialize the contract state
  .functionCall(newAccountId, "init", {
    owner_id: "alice.near",
    total_supply: "1000000",
  })

  .send()

console.log("🚀 Deployed!")

3. Managing Access Keys

NEAR has a unique permission system based on Access Keys. You can add multiple keys to a single account with different permission levels.

Adding a Restricted Key (FunctionCall)

This is how "Sign in with NEAR" works. You create a key that can only call specific methods on a specific contract. It cannot transfer NEAR.

const appKey = generateKey()

await near
  .transaction("alice.near")
  .addKey(appKey.publicKey.toString(), {
    type: "functionCall",

    // The ONLY contract this key can interact with
    receiverId: "game.near",

    // The ONLY methods this key can call
    // (Empty array = Any method on this contract)
    methodNames: ["move", "attack", "heal"],

    // The max amount of gas fees this key can spend
    allowance: "0.25 NEAR",
  })
  .send()

Understanding Allowance

The allowance is a specific amount of NEAR set aside strictly for gas fees. It cannot be transferred or withdrawn. If the key uses up this allowance, it will be deleted automatically.

Rotating Keys (Security)

To rotate keys (e.g., for security hygiene), you add a new key and delete the old one in the same transaction. This prevents you from locking yourself out.

const newMasterKey = generateKey()

await near
  .transaction("alice.near")
  .addKey(newMasterKey.publicKey.toString(), { type: "fullAccess" })
  .deleteKey("alice.near", "ed25519:OLD_KEY...")
  .send()

4. Staking

You can stake NEAR natively with a validator to earn rewards.

// Stake 100 NEAR with the 'figment' validator
await near
  .transaction("alice.near")
  .stake("figment.poolv1.near", "100 NEAR")
  .send()

Unstaking

To unstake, you typically need to call function methods (unstake, withdraw) on the staking pool contract rather than using a native action.

5. Deleting Accounts

You can delete an account to recover its storage rent (the NEAR locked to pay for its data). The account passed to .transaction() is the account being deleted, and the beneficiary receives the remaining NEAR balance.

// Delete 'old-account.alice.near' and send all funds to 'alice.near'
await near
  .transaction("old-account.alice.near")
  .deleteAccount({ beneficiary: "alice.near" })
  .send()

6. Transaction Lifecycle & Finality

When you call .send(), you can control exactly when near-kit returns using the waitUntil option.

The Lifecycle

  1. Validation: RPC checks structure.
  2. Inclusion: The transaction hits a validator node. Signature is checked, gas is pre-paid, nonce is updated.
  3. Execution: The receipt is processed. If it's a function call, the VM runs.
  4. Finalization: The block containing the transaction is finalized by consensus.

waitUntil Options

You can pass these options to .send({ waitUntil: "..." }).

EXECUTED_OPTIMISTIC (Default)

  • Returns: When the entire chain of receipts finishes execution.
  • Data: Full logs and return values are available.

Best for most cases

This is the default for a reason. It provides the return value you need for your UI, and happens relatively quickly (~2s).

INCLUDED

  • Returns: When the transaction is in a block.
  • Data: No return values or logs are available yet.

Missing Data

Use INCLUDED only for "fire-and-forget" UI feedback. You cannot check if the smart contract call actually succeeded or failed logic checks yet.

FINAL

  • Returns: When the block containing the last receipt is finalized.
  • State: 100% irreversible.

High Value Transfer

Use FINAL when moving large amounts of money to ensure 100% certainty.

await near.transaction("alice.near")
  .transfer("bob.near", "10000 NEAR")
  .send({ waitUntil: "FINAL" });

Meta-Transactions (Gasless)

Meta-transactions (NEP-366) allow a user to sign a transaction off-chain, which is then submitted and paid for by a separate "Relayer" account.

How it Works

  1. User (Client): Constructs a transaction and calls .delegate(). This returns a typed object (for UI/validation) and a serialized string (for transport).
  2. Transport: The user sends the serialized string (payload) to your backend.
  3. Relayer (Server): Your server decodes the payload back into an object, validates it, wraps it, and submits it.

1. Client Side: Signing the Action

On the frontend, use the .delegate() method. This works exactly like .send(), but implies no network activity and no gas cost for the signer.

It returns two properties:

  • signedDelegateAction: The raw JS object. Useful for debugging or showing details in your UI.
  • payload: A Base64-encoded string. This is what you send to the server.
// 1. Delegate (builds and signs the transaction off-chain)
const { signedDelegateAction, payload } = userNear
  .transaction("user.near")
  .functionCall("game.near", "move", { x: 1, y: 2 })
  .delegate()

// Inspect locally (Typed Object)
console.log("User signed for:", signedDelegateAction.delegateAction.receiverId)

// 3. Send the PAYLOAD to your API (Base64 String)
await fetch("https://api.mygame.com/relay", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ payload }),
})

2. Server Side: The Relayer

The server receives the Base64 string (payload). It must decode this string back into a typed object to inspect it before submitting.

Use near-kit's decodeSignedDelegateAction helper for this.

Example: Express.js Route

import { Near, decodeSignedDelegateAction } from "near-kit"

const relayer = new Near({
  network: "testnet",
  privateKey: process.env.RELAYER_KEY,
})

app.post("/relay", async (req, res) => {
  try {
    const { payload } = req.body

    // 1. Decode the Base64 string back into a Typed Object
    const userAction = decodeSignedDelegateAction(payload)

    // 2. SECURITY: Inspect the object before paying for it!
    // access the inner details via .delegateAction
    const innerAction = userAction.delegateAction

    // Example Check: Only allow calls to "game.near"
    if (innerAction.receiverId !== "game.near") {
      return res.status(400).send("Invalid target contract")
    }

    // 3. Submit to the Network
    // The relayer wraps the user's action in its own transaction
    const result = await relayer
      .transaction("relayer.near")
      .signedDelegateAction(userAction)
      .send()

    res.json({ hash: result.transaction.hash })
  } catch (e) {
    console.error(e)
    res.status(500).send("Relay failed")
  }
})

Security Checklist

Running a relayer makes you a target.

  1. Whitelist Receivers: Always check userAction.delegateAction.receiverId.
  2. Whitelist Methods: Inspect userAction.delegateAction.actions to ensure users are only calling allowed methods (e.g., "move" vs "withdraw").
  3. Rate Limiting: Rate limit your API endpoint to prevent draining your relayer's funds.

Message Signing (Authentication)

Transactions change the blockchain state and cost gas. Sometimes, you just want to prove who you are.

Message Signing (standardized in NEP-413) allows a user to sign a piece of data with their private key off-chain. This is free, instant, and is the standard way to implement "Log in with NEAR".

How it Works

  1. Client: Generates a random "nonce" and asks the user to sign a specific message.
  2. Wallet: Shows the message to the user. If approved, it returns a cryptographic signature.
  3. Backend: Verifies the signature against the user's public key and checks the nonce to prevent replay attacks.

1. The Client (Frontend)

Use near.signMessage to request a signature.

You must generate a random nonce. This ensures that a captured signature cannot be re-used by an attacker later.

import { Near, generateNonce } from "near-kit"

// 1. Generate a random 32-byte nonce with embedded timestamp
const nonce = generateNonce()

// 2. Request Signature
const signedMessage = await near.signMessage({
  message: "Log in to MyApp", // What the user sees
  recipient: "myapp.com", // Your app identifier (prevents phishing)
  nonce: nonce,
})

// 3. Send to Backend
await fetch("/api/login", {
  method: "POST",
  body: JSON.stringify({
    signedMessage,
    nonce: Array.from(nonce), // Convert Uint8Array to array for JSON
  }),
})

The signature field is base64 encoded per the NEP-413 specification. Send it unchanged to your backend.

2. The Server (Backend)

Automatic Expiration: Nonces include an embedded timestamp. Signatures older than 5 minutes are automatically rejected, limiting the replay attack window.

import { Near, verifyNep413Signature } from "near-kit"

const near = new Near({ network: "mainnet" })

app.post("/api/login", async (req, res) => {
  const { signedMessage, nonce } = req.body
  // Reconstruct nonce buffer from the JSON array
  const nonceBuffer = Buffer.from(nonce)

  // 1. Verify Signature
  const isValid = await verifyNep413Signature(
    signedMessage,
    {
      message: "Log in to MyApp", // Must match exactly what was signed
      recipient: "myapp.com", // Must match YOUR app
      nonce: nonceBuffer, // Must match the nonce sent
    },
    { near }
  )

  if (!isValid) {
    return res.status(401).send("Invalid or expired signature")
  }

  // 2. (Recommended) Check for Replays
  // if (db.seenNonces.has(nonceBuffer.toString('hex'))) ...

  // 3. Success!
  console.log(`User verified: ${signedMessage.accountId}`)
  res.send({ token: "session_token_123" })
})

This verifies the cryptographic signature and confirms the public key belongs to the claimed account as a full access key.

Customize expiration window if needed:

// Accept signatures up to 10 minutes old
await verifyNep413Signature(signedMessage, params, { near, maxAge: 10 * 60 * 1000 })

You can also check key existence directly:

// Returns true if the key exists and is a full access key
const hasKey = await near.fullAccessKeyExists("alice.near", "ed25519:...")

Security Critical: Replay Attacks

Cryptographic verification alone is not enough! If you do not check if the nonce has been used before, an attacker who intercepts the signed message can "replay" it to your server to log in as the user again.

Always store used nonces in your database (with an expiration time) and reject duplicates.

Type Definition

The SignedMessage object returned by the client and sent to the server looks like this:

type SignedMessage = {
  accountId: string // "alice.near"
  publicKey: string // "ed25519:..."
  signature: string // Base64-encoded signature per NEP-413 spec
}

near.signMessage returns a base64-encoded signature as specified in NEP-413. verifyNep413Signature also accepts legacy base58 signatures (with or without key type prefix) for backward compatibility.

Global Contracts

On NEAR, typically every smart contract account holds its own copy of the Wasm bytecode. This means if you deploy the same Token contract to 1,000 accounts, you are paying for that storage 1,000 times.

Global Contracts allow you to publish the Wasm bytecode once to a global registry. You can then deploy instances of that contract by simply referencing its hash.

Use Cases

  • Factories: If you are building a "DAO Factory" or "Token Factory" that deploys new contracts for users, this reduces the deployment cost from ~4 NEAR (storage) to <0.1 NEAR.
  • Standard Libraries: Publish a standard implementation (like a multisig) that everyone trusts and uses.

1. Publishing Code

Publishing requires a storage deposit proportional to the size of your Wasm file (approx 1 NEAR per 100KB).

Updatable Contracts (Default)

By default, published contracts are identified by your account ID. This allows you to update the code later by publishing again. All users who deployed referencing your account will automatically use the latest version you published.

import { readFileSync } from "fs"

const wasm = readFileSync("./my-token.wasm")

// Publish updatable contract (identified by your account)
await near.transaction("factory.near").publishContract(wasm).send()

// Later, you can update it:
const updatedWasm = readFileSync("./my-token-v2.wasm")
await near
  .transaction("factory.near")
  .publishContract(updatedWasm) // Overwrites previous version
  .send()

Users deploying with { accountId: "factory.near" } will always get your latest published version.

Immutable Contracts (Trustless)

For trustless protocols or when you want guaranteed immutability, publish the code identified by its SHA-256 hash. It can never be changed.

import { readFileSync } from "fs"
import { createHash } from "crypto" // Standard Node.js module

const wasm = readFileSync("./my-token.wasm")

// 1. Calculate the hash locally
const codeHash = createHash("sha256").update(wasm).digest()

// 2. Publish as immutable (identified by hash)
await near
  .transaction("deployer.near")
  .publishContract(wasm, { identifiedBy: "hash" })
  .send()

console.log("Immutable contract published!")
console.log("Hash:", codeHash.toString("base64"))

2. Deploying by Reference

To deploy, use deployFromPublished instead of deployContract. This action is extremely cheap because it uses almost no bandwidth or new storage.

Deploying from Account (Updatable)

This deploys whatever code factory.near has currently published. If they update the contract later, you'll automatically get the latest version.

await near
  .transaction("user.near")
  .createAccount("dao.user.near")
  .transfer("dao.user.near", "1 NEAR") // Storage for state, not code!
  .deployFromPublished({ accountId: "factory.near" })
  .functionCall("dao.user.near", "init", {})
  .send()

Deploying from Hash (Immutable)

For contracts published with identifiedBy: "hash", reference them by their SHA-256 hash:

const codeHash = "5FzD8..." // Base58-encoded hash from publishing

await near
  .transaction("user.near")
  .createAccount("token.user.near")
  .transfer("token.user.near", "1 NEAR")
  .deployFromPublished({ codeHash })
  .functionCall("token.user.near", "init", { supply: "1000" })
  .send()

3. Deterministic Account IDs (NEP-616)

NEP-616 enables deploying contracts to deterministic addresses (starting with 0s) derived from their initialization state. Perfect for sharded contract designs where you deploy many instances of the same contract.

Deploying with StateInit

import { deriveAccountId } from "near-kit"

// Deploy to deterministic address (auto-derived from state)
await near
  .transaction("alice.near")
  .stateInit({
    code: { accountId: "publisher.near" },
    data: new Map([["owner", "alice.near"]]),
    deposit: "1 NEAR",
  })
  .send()

// If already deployed, deposit is refunded automatically

The account ID is derived as: "0s" + hex(keccak256(borsh(state_init))[12..32])

Computing Addresses

// Derive the address without deploying
const accountId = deriveAccountId({
  code: { accountId: "publisher.near" },
  data: new Map([["owner", "alice.near"]]),
})
// => "0s1234567890abcdef1234567890abcdef12345678"

// Check if an account is deterministic
isDeterministicAccountId("0s123...") // true
isDeterministicAccountId("alice.near") // false

Why Use This?

  • Anyone can deploy: Since addresses are deterministic, anyone can pay to deploy if it doesn't exist
  • Sharded contracts: Deploy many instances with predictable addresses
  • Code verification: Verify that another contract is running specific code

Learn More

See NEP-616 for the full specification.

Action Reference

This page documents every method available on the TransactionBuilder.

To start a transaction:

near.transaction(signerId: string)

Token Operations

.transfer(receiverId, amount)

Sends NEAR tokens from the signer to the receiver.

  • receiverId: string - The account receiving the tokens.
  • amount: Amount - The amount to send (e.g. "10 NEAR", "0.5 NEAR", "1000 yocto").

.stake(publicKey, amount)

Stakes NEAR with a validator.

  • publicKey: string - The validator's public key.
  • amount: Amount - The amount to stake.

Contract Operations

.functionCall(contractId, methodName, args, options)

Calls a method on a smart contract.

  • contractId: string - The contract account ID.
  • methodName: string - The method to call.
  • args: object | Uint8Array - (Optional) Arguments. Objects are automatically JSON serialized.
  • options: object - (Optional)
    • gas: Gas - Computation limit (default: "30 Tgas").
    • attachedDeposit: Amount - NEAR to send to the contract (default: "0 yocto").

.deployContract(accountId, code)

Deploys Wasm code to an account. If the account already has a contract, it will be updated.

  • accountId: string - The account to deploy to.
  • code: Uint8Array - The raw bytes of the compiled Wasm file.

.stateInit(stateInit, options?)

Deploys a contract to a deterministic account ID (NEP-616). The address is auto-derived from the initialization state. Idempotent—refunds if already deployed.

  • stateInit: object
    • code: { accountId: string } | { codeHash: string } - Global contract reference
    • data: Map<string, string> - (Optional) Initial storage
    • deposit: Amount - Storage cost reserve
  • options: { refundTo?: string } - (Optional) Custom refund recipient
.stateInit({
  code: { accountId: "publisher.near" },
  data: new Map([["owner", "alice.near"]]),
  deposit: "1 NEAR",
})

See Global Contracts.

Account Management

.createAccount(accountId)

Creates a new account. This is often chained with .transfer (to fund it) and .addKey (to secure it).

  • accountId: string - The full ID of the new account (must be a sub-account of the signer).

.deleteAccount({ beneficiary })

Deletes the transaction receiver's account and sends all remaining funds to the beneficiary.

  • beneficiary: string - The account that receives the remaining NEAR balance.
// Delete 'old-account.alice.near' and send remaining funds to 'alice.near'
await near
  .transaction("old-account.alice.near")
  .deleteAccount({ beneficiary: "alice.near" })
  .send()

Access Keys

.addKey(publicKey, permission)

Adds a new access key to the account.

  • publicKey: string - The public key to add (ed25519 or secp256k1).
  • permission: AccessKeyPermission - One of:
    • { type: "fullAccess" } - Can do anything.
    • { type: "functionCall", receiverId: string, methodNames?: string[], allowance?: Amount } - Restricted to specific contracts/methods.
// Full Access
.addKey(pk, { type: "fullAccess" })

// Restricted Access (e.g. for a frontend app)
.addKey(pk, {
  type: "functionCall",
  receiverId: "app.near",
  methodNames: ["vote", "comment"],
  allowance: "0.25 NEAR" // Gas allowance
})

.deleteKey(accountId, publicKey)

Removes an access key.

  • accountId: string - The account to remove the key from.
  • publicKey: string - The public key to remove.

Global Contracts

.publishContract(code, options?)

Publishes contract code to the global registry so others can deploy it by reference.

  • code: Uint8Array - The Wasm bytes.
  • options: object - (Optional) Configuration for how the contract is identified:
    • identifiedBy: "hash" | "account" - How the contract is referenced:
      • "account" (default): Updatable by signer, identified by signer's account ID
      • "hash": Immutable, identified by code hash

Examples:

// Updatable contract (default) - identified by your account
.publishContract(wasm)

// Immutable contract - identified by hash
.publishContract(wasm, { identifiedBy: "hash" })

.deployFromPublished(reference)

Deploys contract code that was previously published to the registry. This saves gas by avoiding uploading the full Wasm bytes.

  • reference: One of:
    • { accountId: string } - Account ID of the publisher (for updatable contracts)
    • { codeHash: string } - Base58 hash of an immutable contract

Advanced / Meta-Transactions

.delegate(options?)

Instead of sending the transaction, this method signs it and returns a SignedDelegateAction payload. This is used for meta-transactions where a relayer pays the gas.

  • options: object - (Optional)
    • maxBlockHeight: bigint - Expiration block.
    • payloadFormat: "base64" | "bytes" - Output format.

Returns: { signedDelegateAction, payload, format }.

.signedDelegateAction(signedDelegate)

Adds a pre-signed delegate action to this transaction. Used by relayers to submit a user's action.

.signWith(key)

Overrides the signer for this specific transaction. Does not change the global Near configuration.

  • key: string | Signer - A private key string OR a custom signer function.
// Use a specific key just for this transaction
.signWith("ed25519:...")

// Use a custom signer (e.g. hardware wallet)
.signWith(async (hash) => {
  return ledger.sign(hash);
})

Configuration Options

These are the properties accepted by the Near class constructor.

const near = new Near({
  network: "testnet",
  privateKey: "...",
})

Network

Controls which NEAR network to connect to.

  • network: string | NetworkConfig

    • Presets: "mainnet", "testnet", "localnet".
    • Custom:
      {
        networkId: "my-private-net",
        rpcUrl: "http://127.0.0.1:3030"
      }
      
    • Sandbox: Passing a Sandbox instance automatically configures RPC and the root account key.
  • rpcUrl: string - (Optional) Override the RPC URL for a preset network (e.g. to use a specific provider like Infura or Lava).

  • headers: Record<string, string> - (Optional) Custom HTTP headers for RPC requests (e.g. { "Authorization": "Bearer ..." }).

Credentials & Signing

near-kit checks these in order: wallet > signer > privateKey > keyStore.

  • privateKey: string - A single private key (ed25519:...). Useful for scripts.
  • keyStore: KeyStore - A storage backend for multiple keys.
    • InMemoryKeyStore: Default.
    • FileKeyStore: Node.js only. Stores in ~/.near-credentials.
    • NativeKeyStore: Node.js only. Stores in macOS Keychain / Windows Credential Manager.
    • RotatingKeyStore: For high-concurrency apps.
  • wallet: WalletAdapter - A wrapper around a browser wallet (e.g. fromWalletSelector(wallet)).
  • signer: (hash: Uint8Array) => Promise<Signature> - A custom signing function. Use this for hardware wallets, KMS, or multi-sig logic.
  • defaultSignerId: string - The account ID to use when calling near.transaction() without arguments.

Execution Behavior

  • defaultWaitUntil: "INCLUDED" | "EXECUTED_OPTIMISTIC" | "FINAL"

    • Controls how long .send() waits before returning.
    • Default: "EXECUTED_OPTIMISTIC" (Transaction is executed and result is available).
    • "FINAL": Wait for BFT finality (safest, slowest).
    • "INCLUDED": Return as soon as the network accepts it (fastest, no result value).
  • retryConfig: object

    • maxRetries: number - Max retries for retryable network errors (429, 503, etc). Default: 4.
    • initialDelayMs: number - Backoff delay. Default: 1000ms.

Data Structures

This reference details the shape of objects returned by the Near client. Use this to understand exactly what fields are available when parsing results.

Transaction Result (FinalExecutionOutcome)

Returned by .send() and near.call().

type FinalExecutionOutcome = {
  // High-level status of the transaction execution
  final_execution_status: "NONE" | "INCLUDED" | "EXECUTED_OPTIMISTIC" | "FINAL";

  // Detailed status of the transaction
  status:
    | { SuccessValue: string } // Base64 encoded return value
    | { SuccessReceiptId: string } // ID of the final receipt
    | { Failure: object }; // Error object if failed

  // The transaction itself
  transaction: {
    signer_id: string;
    public_key: string;
    nonce: number;
    receiver_id: string;
    actions: Action[];
    signature: string;
    hash: string;
  };

  // Execution outcome of the transaction (initial receipt)
  transaction_outcome: ExecutionOutcomeWithId;

  // Execution outcomes of all subsequent receipts (cross-contract calls)
  receipts_outcome: ExecutionOutcomeWithId[];
};

Execution Outcome (ExecutionOutcomeWithId)

Contains gas usage and logs.

type ExecutionOutcomeWithId = {
  id: string; // Receipt ID
  block_hash: string;
  outcome: {
    logs: string[]; // Console logs from the contract
    receipt_ids: string[]; // IDs of receipts generated by this execution
    gas_burnt: number; // Gas used in Tgas units * 1e12
    tokens_burnt: string; // NEAR burnt (yoctoNEAR)
    executor_id: string; // The account that executed this receipt
    status: ExecutionStatus;
  };
};

Account Details (AccountView)

Returned by near.getAccountDetails(accountId).

type AccountView = {
  amount: string; // Balance in yoctoNEAR
  locked: string; // Staked balance in yoctoNEAR
  code_hash: string; // Hash of the contract code (111111... if none)
  storage_usage: number; // Storage used in bytes
  storage_paid_at: number;
  block_height: number;
  block_hash: string;
};

Access Key (AccessKeyView)

Returned by near.getAccessKey(accountId, publicKey).

type AccessKeyView = {
  nonce: number;
  permission:
    | "FullAccess"
    | {
        FunctionCall: {
          receiver_id: string;
          method_names: string[];
          allowance: string | null;
        };
      };
};

Errors

FunctionCallError

Thrown when a smart contract panics.

class FunctionCallError extends NearError {
  code: "FUNCTION_CALL_ERROR";
  contractId: string; // The contract that failed
  methodName?: string; // The method that failed
  panic?: string; // The panic message (e.g. "ERR_NO_DEPOSIT")
  logs: string[]; // Logs leading up to the failure
}

InvalidTransactionError

Thrown when the network rejects a transaction.

class InvalidTransactionError extends NearError {
  code: "INVALID_TRANSACTION";
  retryable: boolean; // True if it was a transient network issue
  shardCongested: boolean;
  shardStuck: boolean;
}

Utility Functions

Deterministic Account IDs (NEP-616)

deriveAccountId(stateInit)

Computes the deterministic account ID from initialization state.

import { deriveAccountId } from "near-kit";

deriveAccountId({
  code: { accountId: "publisher.near" },
  data: new Map([["owner", "alice.near"]]),
});
// => "0s1234567890abcdef1234567890abcdef12345678"

isDeterministicAccountId(accountId)

Checks if an account ID is deterministic (starts with 0s).

isDeterministicAccountId("0s123..."); // true
isDeterministicAccountId("alice.near"); // false

verifyDeterministicAccountId(accountId, stateInit)

Verifies an account ID matches the expected derivation.

verifyDeterministicAccountId("0s123...", {
  code: { accountId: "publisher.near" },
  data: new Map([["owner", "alice.near"]]),
});
// => true if matches

See Global Contracts.

For AI Agents

This documentation is available in a raw text format optimized for Large Language Models (LLMs).

If you are using AI coding assistants like Cursor, Windsurf, ChatGPT, or Claude, providing this context file significantly reduces hallucinations and ensures the AI uses the modern near-kit API instead of the legacy near-api-js.

Available Context Files

Contains the entire documentation book in a single text file. Use this for most coding tasks.

2. Index Only

A condensed table of contents. Use this if you are hitting context window limits and only need the agent to know the library structure.


How to Use

In Cursor / Windsurf

  1. Open the Chat pane.
  2. Type @ and choose Add Link (or just paste the URL).
  3. Paste: https://kit.near.tools/llms-full.txt
  4. The editor will ingest the documentation as a reference file.

In ChatGPT / Claude

  1. Paste the URL https://kit.near.tools/llms-full.txt into the chat.
  2. Add the prompt below.

When starting a new session, tell the AI explicitly to ignore the old library.

I am building an application using the `near-kit` library.

IMPORTANT: `near-kit` is NOT `near-api-js`.
- Do not use `near-api-js` patterns (like `utils.format` or `account.functionCall`).
- Use the provided `llms-full.txt` context file as the source of truth for API methods and types.