# near-kit > A TypeScript library for interacting with NEAR - simple, type-safe, and intuitive # 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: ```typescript 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](/start-here/quickstart.md). # 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](https://testnet.mynearwallet.com/). - Your account's **Private Key** (starts with `ed25519:...`). ## Installation ```bash # npm npm install near-kit # bun bun add near-kit # yarn yarn add near-kit ``` ## Your First Script Create a file named `index.ts`. ```typescript 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: ```bash # 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. ```typescript 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. ```typescript // ✅ 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. ```typescript 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. ```typescript 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. ```typescript 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. ```typescript 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. ```typescript const amount = "10.5 NEAR" const gas = "30 Tgas" ``` ### 3. Calling Contracts **near-api-js:** Arguments are passed inside a configuration object. ```typescript 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. ```typescript 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. ```typescript 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. ```typescript 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:** ```typescript await account.addFunctionCallAccessKey({ publicKey, contractId: "market.near", methodNames: ["buy"], allowance: parseNearAmount("0.25")!, }) ``` **near-kit:** Explicitly typed permissions object. ```typescript 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. ```typescript 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. ```typescript // Define what the contract returns type Message = { sender: string text: string } // Pass the type to .view() const messages = await near.view("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](./type-safe-contracts.md). ## 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. ```typescript // 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. ```typescript 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. ```typescript const [balance, messages, exists] = await near.batch( near.getBalance("alice.near"), near.view("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. ```typescript 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. ```typescript 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." ```typescript 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`. ```typescript 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: ```typescript 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. ```typescript // 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. ```typescript 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. ```typescript // 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. ```typescript 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 get_messages: (args: { limit: number }) => Promise total_messages: () => Promise } call: { // Call methods usually return void, but can return values too add_message: (args: { text: string }) => Promise } }> ``` ## 2. Create the Proxy Use the `near.contract()` method to create a typed proxy for a specific contract ID. ```typescript // This object now has all the methods defined in your type const guestbook = near.contract("guestbook.near") ``` ## 3. Use it! You can now access methods directly under `.view` and `.call`. ### Calling Views Arguments are passed as the first parameter. ```typescript // ✅ 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. ```typescript 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. - **Live App:** [guestbook.near.tools](https://guestbook.near.tools/) - **Source Code:** [github.com/r-near/near-kit-guestbook-demo](https://github.com/r-near/near-kit-guestbook-demo) --- ## The Adapter Pattern You initialize the `Near` instance with a `wallet` object instead of a private key. ```typescript 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`](https://github.com/near/wallet-selector) is a popular library for connecting to the NEAR ecosystem. It supports MyNearWallet, Meteor, Sender, Here, and many others. ### Setup ```typescript 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](https://github.com/azbang/hot-connector) is the new standard, and intended to replace NEAR Wallet Selector. ### Setup ```typescript 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](https://github.com/r-near/near-kit-guestbook-demo/blob/main/src/WalletProvider.tsx): ```jsx // 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(null) useEffect(() => { // ... setup selector ... const wallet = await selector.wallet() if (wallet) { setNear( new Near({ network: "testnet", wallet: fromWalletSelector(wallet), }) ) } }, []) return ( {children} ) } // 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`. ```typescript // 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. ```typescript 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. ```typescript 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. ```typescript 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. ```typescript 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 | Environment | Config Option | Who Signs? | | :----------- | :------------------ | :---------------------------------------------- | | **Script** | `privateKey: "..."` | The provided key string. | | **Backend** | `keyStore: store` | The key matching `signerId` found in the store. | | **Frontend** | `wallet: adapter` | The user (via wallet popup). | | **Sandbox** | `network: sandbox` | The 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`. ```typescript 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. ```typescript 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`. ```typescript 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. ```typescript 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. ```typescript 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. ```bash # Install the native dependency first npm install @napi-rs/keyring ``` ```typescript 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. ```typescript 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. ```typescript 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.** ```admonish warning title="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. ```admonish tip title="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. ``` ```typescript 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. ```typescript 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() ``` ```admonish info title="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. ```typescript 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. ```typescript // Stake 100 NEAR with the 'figment' validator await near .transaction("alice.near") .stake("figment.poolv1.near", "100 NEAR") .send() ``` ```admonish note title="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. ```typescript // 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. ```admonish tip title="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.** ```admonish warning title="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. ````admonish example title="High Value Transfer" Use `FINAL` when moving large amounts of money to ensure 100% certainty. ```typescript 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. ```typescript // 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 ```typescript 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](https://github.com/near/NEPs/blob/master/neps/nep-0413.md)) 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. ```typescript 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. ```typescript 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: ```typescript // Accept signatures up to 10 minutes old await verifyNep413Signature(signedMessage, params, { near, maxAge: 10 * 60 * 1000 }) ``` You can also check key existence directly: ```typescript // Returns true if the key exists and is a full access key const hasKey = await near.fullAccessKeyExists("alice.near", "ed25519:...") ``` ```admonish warning title="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: ```typescript 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. ```typescript 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. ```typescript 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. ```typescript 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: ```typescript 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 ```typescript 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 ```typescript // 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 ```admonish info title="Learn More" See [NEP-616](https://github.com/near/NEPs/blob/master/neps/nep-0616.md) for the full specification. ``` # Action Reference This page documents every method available on the `TransactionBuilder`. To start a transaction: ```typescript 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` - (Optional) Initial storage - `deposit`: `Amount` - Storage cost reserve - **options**: `{ refundTo?: string }` - (Optional) Custom refund recipient ```typescript .stateInit({ code: { accountId: "publisher.near" }, data: new Map([["owner", "alice.near"]]), deposit: "1 NEAR", }) ``` See [Global Contracts](/in-depth/global-contracts.md#3-deterministic-account-ids-nep-616). ## 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. ```typescript // 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. ```typescript // 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:** ```typescript // 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. ```typescript // 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. ```typescript const near = new Near({ network: "testnet", privateKey: "...", }) ``` ## Network Controls which NEAR network to connect to. - **`network`**: `string | NetworkConfig` - **Presets**: `"mainnet"`, `"testnet"`, `"localnet"`. - **Custom**: ```typescript { 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` - (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` - 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: `1000`ms. # 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()`. ```typescript 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. ```typescript 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)`. ```typescript 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)`. ```typescript type AccessKeyView = { nonce: number; permission: | "FullAccess" | { FunctionCall: { receiver_id: string; method_names: string[]; allowance: string | null; }; }; }; ``` ## Errors ### `FunctionCallError` Thrown when a smart contract panics. ```typescript 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. ```typescript 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. ```typescript 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`). ```typescript isDeterministicAccountId("0s123..."); // true isDeterministicAccountId("alice.near"); // false ``` #### `verifyDeterministicAccountId(accountId, stateInit)` Verifies an account ID matches the expected derivation. ```typescript verifyDeterministicAccountId("0s123...", { code: { accountId: "publisher.near" }, data: new Map([["owner", "alice.near"]]), }); // => true if matches ``` See [Global Contracts](/in-depth/global-contracts.md#3-deterministic-account-ids-nep-616). # 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 ### 1. Full Context (Recommended) Contains the entire documentation book in a single text file. Use this for most coding tasks. - **URL:** [https://kit.near.tools/llms-full.txt](https://kit.near.tools/llms-full.txt) - **Filename:** `llms-full.txt` ### 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. - **URL:** [https://kit.near.tools/llms.txt](https://kit.near.tools/llms.txt) - **Filename:** `llms.txt` --- ## 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](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](https://kit.near.tools/llms-full.txt) into the chat. 2. Add the prompt below. ## Recommended Prompt When starting a new session, tell the AI explicitly to ignore the old library. ```text 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. ```