# JavaScript SDK

> NeuroChain JavaScript SDKs — the dApp SDK for reads, signed transfers, contract calls, deploys and staking, and the Signer SDK that lets an external dApp ask the user's wallet to sign.

Source: https://docs.nro.world/build/sdk

There are two client libraries, both in the repository:

| Library | Where | Use it when |
|---------|-------|-------------|
| **dApp SDK** (`NeuroChain`) | `neurochain-web/src/sdk/neurochain.js` | Your page holds the user's key (or you are scripting against your own wallet) |
| **Signer SDK** (`NeuroSigner`) | `packages/neurochain-signer-sdk` | Your dApp runs on another site and the user signs in their NeuroChain wallet, in a pop-up |

Neither is published to npm yet; copy the module or build the package from the repository.

## dApp SDK

A single ES module with no dependencies. Reads are plain `fetch` (browser or Node 18+); writes sign with ML-DSA-65 using the same crypto WASM as the wallet, so the node accepts the signatures.

```js
import { NeuroChain, OP_TARGETS, toRaw } from './neurochain.js'

const nc = new NeuroChain({ gateway: 'http://localhost:3000' })
```

| Option | Default | Meaning |
|--------|---------|---------|
| `gateway` | `http://localhost:3000` | Gateway base URL |
| `crypto` | — | An already-initialised crypto WASM module |
| `cryptoLoader` | loads `../wasm/neuro_wasm_crypto.js` | Async function returning the initialised module |

### Amounts

Every `value` and `amount` you pass is **human NRO** (`'1.5'`, `2`, `'0.000021'`). The SDK converts to integer uNRO with `toRaw()`, so the amount you sign and the amount you send are always the same. For display, use the `*_human` fields the API returns.

### Reads

| Method | Calls | Returns |
|--------|-------|---------|
| `networkStatus()` | `GET /api/v1/network/status` | Height, shards, validators |
| `shards()` | `GET /api/v1/shards` | Per-shard heights |
| `account(addr)` | `GET /api/v1/accounts/:addr` | `balance`, `balance_human`, `nonce`, … |
| `balance(addr)` | via `account` | `{ raw, human, nonce }` |
| `nextNonce(addr)` | via `account` | Confirmed nonce + 1 |
| `validators()` | `GET /api/v1/validators` | Validator list with stake and metrics |
| `blocks({ limit, offset })` | `GET /api/v1/blocks` | Latest blocks |
| `block(height)` | `GET /api/v1/blocks/:height` | One block |
| `tx(hash)` | `GET /api/v1/transactions/:hash` | One transaction |
| `contract(addr)` / `contracts({ limit, offset })` | `GET /api/v1/contracts…` | Contract metadata |
| `gasEstimate()` | `GET /api/v1/gas/estimate` | `base_fee` and estimates |
| `contractQuery(addr, fn, args)` | `POST /api/v1/contracts/:addr/call` with `dry_run: true` | Parsed return value; no transaction, no gas |

### Wallet

```js
const wallet = await nc.walletFromMnemonic('<24 words>')
wallet.address    // neuro:<15 hex>.human
wallet.publicKey  // ML-DSA-65 public key, hex
```

The private seed stays inside the wallet object; it is never exposed as a property.

### Writes

```js
// Transfer
await nc.transfer({ wallet, to: 'neuro:<address>.human', value: '1.5', gasPrice })

// Any transaction, optionally with call data
await nc.sendTransaction({ wallet, to, value, data, gasPrice })

// Contract call — JSON call data { function, args }
await nc.contractCall({ wallet, address: 'neuro:<10 hex>.app', fn: 'transfer', args: { … }, value, gasPrice })

// Deploy — a transaction to neuro:system.deploy
await nc.deployContract({ wallet, bytecodeHex, name, description, template, initArgs, gasPrice })
```

Each write reads the next nonce, signs `nc-tx-v2|from|to|value|nonce`, and posts to `POST /api/v1/transactions`.

::: warning Set the gas price
The SDK defaults to `gasPrice: 1` (deploys: 5). The node refuses a transaction priced below the current base fee, which rises under load. Read it first and pass `gasPrice` explicitly:

```js
const { base_fee } = await nc.gasEstimate()
const gasPrice = Math.ceil(base_fee * 1.5)
```
:::

### Signed operations

Staking, name registration and other system operations sign against a system target with a millisecond-timestamp nonce (see [Wallet → Signed operations](/learn/wallet#signed-operations)):

```js
const op = nc.signOp(wallet, OP_TARGETS.name, 0)   // { signature, public_key, nonce }
// merge op into the endpoint's request body
```

`OP_TARGETS` has `stake` (`neuro:system.stake`) and `name` (`neuro:system.name`).

### Known issues

::: danger Known issues in the current SDK
- **Transfers with a non-zero value fail.** `sendTransaction` / `transfer` send `value` as a JSON string; the node reads only a number and refuses the request with `Missing from/to/value`. Calls and deploys with value 0 are not affected. Workaround — sign and post with a numeric value:
  ```js
  const nonce = await nc.nextNonce(wallet.address)
  const value = Number(toRaw('1.5'))               // uNRO as a number
  const sig = wallet.sign(to, value, nonce)
  await fetch(GATEWAY + '/api/v1/transactions', { method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ from: wallet.address, to, value, nonce, gas_price, ...sig }) })
  ```
- `contractQuery` returns the raw string when the contract's reply is not clean JSON — NRC-20 replies end with a NUL byte, so you get `'{"ok":true,"value":…}'` as a string; strip `\u0000` and parse.
- `stake()` signs the amount converted to uNRO twice, so the node rejects the signature. Until it is fixed, build the request yourself: `{ address, amount: toRaw(amount), ...nc.signOp(wallet, OP_TARGETS.stake, amount) }` posted to `/api/v1/staking/stake`.
- `staking(addr)` requests `/api/v1/staking/:addr`, which the gateway does not serve; use `GET /api/v1/staking/info/:address`.
- `contractCall` adds a `caller` field to the call JSON. Contracts ignore it — identity comes from the host — so it is harmless.
:::

### Crypto module

Signing needs `neuro_wasm_crypto` — the wallet's WASM build (`neurochain-web/src/wasm/neuro_wasm_crypto.js` and `neuro_wasm_crypto_bg.wasm`). It must export `nc_keypair_from_words`, `nc_tx_message` and `nc_sign`. If your page serves it elsewhere, pass a loader:

```js
const nc = new NeuroChain({
  gateway: 'http://localhost:3000',
  cryptoLoader: async () => {
    const mod = await import('/wasm/neuro_wasm_crypto.js')
    await mod.default()
    return mod
  },
})
```

Without the module, reads work and every write throws.

## Signer SDK

For a dApp on another origin (an exchange, a game) that must not see the user's phrase. The user's NeuroChain web app opens in a pop-up, the user approves, and the signature comes back over `postMessage`.

```ts
import { NeuroSigner } from 'neurochain-signer-sdk'

const signer = new NeuroSigner({
  signerUrl: 'http://localhost:8080/signer',      // the wallet app's /signer page
  allowedOrigins: ['http://localhost:8080'],
})

// Must run directly inside a click handler, or the browser blocks the pop-up
button.onclick = async () => {
  const { address, publicKey } = await signer.connect()
  const { signature, nonce } = await signer.signOperation({
    to: 'neuro:system.dex',
    value: 1000000,          // uNRO
  })
}
```

| Method | Returns |
|--------|---------|
| `connect()` | `{ address, publicKey }` |
| `signOperation({ to, value, actor? })` | `{ signature, publicKey, nonce }` — a signed operation for `actor` (default: the connected address) |
| `isConnected()`, `getAddress()`, `getPublicKey()` | Connection state |
| `close()` | Closes the pop-up and disconnects |

Errors carry a `code`: `POPUP_BLOCKED`, `CONNECTION_TIMEOUT`, `SIGNATURE_REJECTED`, `INVALID_ORIGIN`, `NOT_CONNECTED`. On `POPUP_BLOCKED`, show the user a direct link to the signer page.

Messages are accepted only from `allowedOrigins`; set it to your wallet app's origin.
