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.
- 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 HOT Connector and NEAR Wallet Selector, drop-in integration
npm install near-kit
# or
bun install near-kitimport { 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")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.
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"),
})In the browser, connect to user wallets. The same near.call(), near.send(), and near.transaction() methods work seamlessly:
import { fromWalletSelector } from "near-kit"
const near = new Near({
network: "testnet",
wallet: fromWalletSelector(walletInstance),
})
// 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.
// 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:...",
})// 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()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" }
)// 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()// Run multiple operations in parallel
const [balance, status, exists] = await near.batch(
near.getBalance("alice.near"),
near.getStatus(),
near.accountExists("bob.near")
)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()
})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..."],
}),
})near-kit integrates with Wallet Selector and HOT Connector through a signer abstraction. Wallet adapters are converted to signers via fromWalletSelector() and fromHotConnect() shims, allowing the same API to work across backend and frontend without separate client implementations.
import { Near, fromWalletSelector } from "near-kit"
import { setupWalletSelector } from "@near-wallet-selector/core"
import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"
import { setupHereWallet } from "@near-wallet-selector/here-wallet"
// Setup wallet selector
const selector = await setupWalletSelector({
network: "testnet",
modules: [setupMyNearWallet(), setupHereWallet()],
})
// Get wallet instance (after user connects)
const wallet = await selector.wallet()
// Use with near-kit
const near = new Near({
network: "testnet",
wallet: fromWalletSelector(wallet),
})
// All operations now use the wallet for signing
await near.call("contract.near", "method", { arg: "value" })
await near.send("bob.near", "10 NEAR")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),
})
// Use near-kit with the connected wallet
await near.call("contract.near", "method", { arg: "value" })
})
// Trigger wallet connection
await connector.signIn()Errors are organized by category and include detailed context for debugging. Use instanceof checks to handle specific error types.
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
}
}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
}
}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)
}
}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()Authenticate users without gas fees:
const signedMessage = await near.signMessage({
message: "Login to MyApp",
recipient: "myapp.near",
nonce: crypto.getRandomValues(new Uint8Array(32)),
})
// Send to backend for verification
await fetch("/api/auth", {
method: "POST",
body: JSON.stringify(signedMessage),
})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()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 retriedFor 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"))
)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
}
}# Install dependencies
bun install
# Run tests
bun test
# Build
bun run build
# Run examples
bun run examples/quickstart.tsMIT