near-kit
A simple, intuitive TypeScript library for interacting with NEAR Protocol. Designed to feel like a modern fetch library - easy for beginners, powerful for advanced users.
Features
- Simple things should be simple - One-line commands for common operations
- Type safety everywhere - Full TypeScript support with IDE autocomplete
- Progressive complexity - Basic API for simple needs, advanced features when required
- Powerful transaction builder - Fluent, human-readable API for transactions
- Wallet-ready - Full support for NEAR Connect, drop-in integration
Installation
npm install near-kit
# or
bun install near-kit
Quick Start
import { Near } from "near-kit"
// Initialize for backend/scripts
const near = new Near({
network: "testnet",
privateKey: "ed25519:...",
defaultSignerId: "alice.testnet",
})
// View methods (read-only, no gas)
const balance = await near.view("example.testnet", "get_balance", {
account_id: "alice.testnet",
})
// Call methods (requires signature, costs gas)
await near.call(
"example.testnet",
"increment",
{},
{ attachedDeposit: "0.1 NEAR" }
)
// Send NEAR tokens
await near.send("bob.testnet", "5 NEAR")
Getting Started
near-kit provides a unified API that works across different environments. Configuration varies by environment, but the API for calling contracts, sending transactions, and building transactions remains identical.
Backend & Scripts
For local testing, use the sandbox (no network or key configuration needed):
import { Sandbox } from "near-kit"
const sandbox = await Sandbox.start()
const near = new Near({ network: sandbox })
// Test with automatically provisioned accounts
await near.call("contract.test.near", "method", {})
await sandbox.stop()
For testnet/mainnet development, pass a private key directly:
const near = new Near({
network: "testnet",
privateKey: "ed25519:...",
defaultSignerId: "alice.testnet",
})
For production applications, use a keyStore:
import { FileKeyStore } from "near-kit"
const near = new Near({
network: "testnet",
keyStore: new FileKeyStore("~/.near-credentials"),
})
Frontend & Wallets
In the browser, connect to user wallets. The same near.call(), near.send(), and near.transaction() methods work seamlessly:
import { Near, fromHotConnect } from "near-kit"
import { NearConnector } from "@hot-labs/near-connect"
const connector = new NearConnector({ network: "testnet" })
const near = new Near({
network: "testnet",
wallet: fromHotConnect(connector),
})
// Same API as backend
await near.call("contract.near", "method", { arg: "value" })
This works through a signer abstraction - whether you pass privateKey, keyStore, wallet, or sandbox, they all implement the same signing interface internally.
Core API
Initialization
// Simple - defaults to mainnet
const near = new Near()
// With network selection
const near = new Near({ network: "testnet" })
// With custom configuration
const near = new Near({
network: "testnet",
privateKey: "ed25519:...",
})
Basic Operations
// View methods (free, no signature required)
const result = await near.view("contract.near", "get_data", { key: "value" })
// Check account balance
const balance = await near.getBalance("alice.near")
// Check if account exists
const exists = await near.accountExists("alice.near")
// Get network status
const status = await near.getStatus()
Type-Safe Contracts
import type { Contract } from "near-kit"
// Define contract interface using Contract<> helper
type MyContract = Contract<{
view: {
get_balance: (args: { account_id: string }) => Promise<string>
get_info: () => Promise<{ name: string; version: string }>
}
call: {
// Just define args - options parameter automatically added!
transfer: (args: { to: string; amount: string }) => Promise<void>
}
}>
// Create type-safe contract
const contract = near.contract<MyContract>("example.near")
// Fully typed method calls
const balance = await contract.view.get_balance({ account_id: "alice.near" })
const info = await contract.view.get_info()
// Call methods automatically get options parameter
await contract.call.transfer(
{ to: "bob.near", amount: "10" },
{ attachedDeposit: "1 NEAR" }
)
Transaction Builder
// Alice builds a transaction with multiple actions
// 'alice.near' is the signer - the account that signs and pays for this transaction
const receipt = await near
.transaction("alice.near") // Alice signs
.transfer("bob.near", "10 NEAR") // Alice sends Bob 10 NEAR
.functionCall(
"market.near",
"buy",
{ id: "123" },
{ attachedDeposit: "5 NEAR" } // Alice attaches 5 NEAR to the call
)
.send()
Batch Operations
// Run multiple operations in parallel
const [balance, status, exists] = await near.batch(
near.getBalance("alice.near"),
near.getStatus(),
near.accountExists("bob.near")
)
Local Testing with Sandbox
import { Sandbox } from "near-kit"
const sandbox = await Sandbox.start()
const near = new Near({ network: sandbox })
// ... run tests
await sandbox.stop()
With test framework:
let sandbox: Sandbox
beforeAll(async () => {
sandbox = await Sandbox.start()
})
afterAll(async () => {
await sandbox.stop()
})
State manipulation — patch state directly, fast-forward blocks, and take snapshots:
import { EMPTY_CODE_HASH } from "near-kit/sandbox"
// Patch blockchain state without transactions
await sandbox.patchState([{
Account: {
account_id: "alice.test.near",
account: { amount: "5000000000000000000000000", locked: "0", code_hash: EMPTY_CODE_HASH, storage_usage: 100 }
}
}])
// Fast-forward blocks for time-dependent logic
await sandbox.fastForward(100)
// Snapshot & restore state between tests
const snapshot = await sandbox.dumpState()
// ... run test that modifies state ...
await sandbox.restoreState(snapshot)
// Restart with clean genesis (optionally baking in a snapshot)
await sandbox.restart(snapshot)
Key Management
import { InMemoryKeyStore, FileKeyStore, RotatingKeyStore } from "near-kit"
// In-memory (runtime only)
const near = new Near({
keyStore: new InMemoryKeyStore({
"alice.near": "ed25519:...",
}),
})
// File-based (persistent)
const near = new Near({
keyStore: new FileKeyStore("~/.near-credentials"),
})
// Rotating keys for high-throughput concurrent transactions
const near = new Near({
keyStore: new RotatingKeyStore({
"alice.near": ["ed25519:key1...", "ed25519:key2...", "ed25519:key3..."],
}),
})
Wallet Integration
near-kit integrates with NEAR Connect through a signer abstraction. The wallet adapter is converted to a signer via fromHotConnect(), allowing the same API to work across backend and frontend without separate client implementations.
import { Near, fromHotConnect } from "near-kit"
import { NearConnector } from "@hot-labs/near-connect"
// Create connector
const connector = new NearConnector({ network: "testnet" })
// Wait for user to connect
connector.on("wallet:signIn", async () => {
const near = new Near({
network: "testnet",
wallet: fromHotConnect(connector),
})
// All operations now use the wallet for signing
await near.call("contract.near", "method", { arg: "value" })
await near.send("bob.near", "10 NEAR")
})
// Trigger wallet connection
await connector.signIn()
Error Handling
Errors are organized by category and include detailed context for debugging. Use instanceof checks to handle specific error types.
Network Errors
import { NetworkError, TimeoutError } from "near-kit"
try {
await near.call("contract.near", "method", {})
} catch (error) {
if (error instanceof TimeoutError) {
console.log("Request timed out - already retried automatically")
} else if (error instanceof NetworkError) {
// Handle other network issues
}
}
Transaction Errors
import { InsufficientBalanceError, InvalidNonceError } from "near-kit"
try {
await near.send("bob.near", "1000000 NEAR")
} catch (error) {
if (error instanceof InsufficientBalanceError) {
console.log(`Need ${error.required}, have ${error.available}`)
} else if (error instanceof InvalidNonceError) {
// Already retried automatically - only thrown if retries exhausted
}
}
Contract Errors
import { FunctionCallError } from "near-kit"
try {
await near.call("contract.near", "method", {})
} catch (error) {
if (error instanceof FunctionCallError) {
console.log(`Contract panicked: ${error.panic}`)
console.log(`Logs:`, error.logs)
}
}
Advanced Features
Batch Actions (Multi-Action Transactions)
Deploy and initialize a contract in a single transaction:
const contractWasm = await fs.readFile("./contract.wasm")
await near
.transaction("alice.near")
.createAccount("contract.alice.near")
.transfer("contract.alice.near", "10 NEAR")
.deployContract("contract.alice.near", contractWasm)
.functionCall("contract.alice.near", "init", { owner: "alice.near" })
.send()
NEP-413 Message Signing
Authenticate users without gas fees:
import { hex } from "@scure/base"
import { generateNonce, Near } from "near-kit"
const nonce = generateNonce()
const signedMessage = await near.signMessage({
message: "Login to MyApp",
recipient: "myapp.near",
nonce,
})
// Send to backend for verification
await fetch("/api/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
signedMessage,
message: "Login to MyApp",
recipient: "myapp.near",
nonce: hex.encode(nonce), // hex-encode for JSON transport
}),
})
Delegate Actions (NEP-366)
Enable meta-transactions and sponsored transactions where a relayer pays the gas:
import { decodeSignedDelegateAction, Near } from "near-kit"
// User creates and signs a delegate action (no gas cost to user)
const userNear = new Near({
network: "testnet",
privateKey: "ed25519:...", // User's key
})
const { signedDelegateAction, payload } = await userNear
.transaction("user.near")
.transfer("recipient.near", "1 NEAR")
.delegate({ blockHeightOffset: 100 })
// Relayer submits the transaction (pays the gas)
const relayerNear = new Near({
network: "testnet",
privateKey: "ed25519:...", // Relayer's key
})
await relayerNear
.transaction("relayer.near")
.signedDelegateAction(decodeSignedDelegateAction(payload))
.send()
Automatic Nonce Management
No more nonce conflicts - the library handles nonce tracking and retries automatically:
// Safe to run multiple transactions concurrently
await Promise.all([
near.send("bob.near", "1 NEAR"),
near.send("charlie.near", "1 NEAR"),
near.send("dave.near", "1 NEAR"),
])
// Nonces are automatically managed and conflicts are retried
For high-throughput scenarios (trading bots, faucets, relayers), use RotatingKeyStore with multiple access keys per account:
import { RotatingKeyStore } from "near-kit"
const keyStore = new RotatingKeyStore({
"bot.near": ["ed25519:key1...", "ed25519:key2...", "ed25519:key3..."],
})
const near = new Near({ keyStore })
// Send 20 concurrent transactions - each key handles its own nonce
await Promise.all(
Array(20)
.fill(0)
.map(() => near.send("recipient.near", "0.1 NEAR"))
)
Smart Retry Logic
Automatic retries for network errors with exponential backoff:
try {
await near.call("contract.near", "method", {})
} catch (error) {
if (error instanceof TimeoutError && error.retryable) {
// Already retried automatically
}
}
Development
# Install dependencies
bun install
# Run tests
bun test
# Build
bun run build
# Run examples
bun run examples/quickstart.ts
License
MIT