# JavaScript SDK

> NeuroChain JavaScript SDK — 조회, 서명된 송금, 컨트랙트 호출, 배포, 스테이킹을 위한 dApp SDK와 외부 dApp이 사용자의 지갑에 서명을 요청할 수 있게 하는 Signer SDK.

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

저장소에는 두 개의 클라이언트 라이브러리가 있습니다.

| 라이브러리 | 위치 | 사용 시점 |
|------------|------|-----------|
| **dApp SDK** (`NeuroChain`) | `neurochain-web/src/sdk/neurochain.js` | 여러분의 페이지가 사용자의 키를 보유할 때(또는 자신의 지갑을 대상으로 스크립트를 작성할 때) |
| **Signer SDK** (`NeuroSigner`) | `packages/neurochain-signer-sdk` | dApp이 다른 사이트에서 실행되고, 사용자가 팝업으로 열린 자신의 NeuroChain 지갑에서 서명할 때 |

둘 다 아직 npm에 게시되지 않았습니다. 모듈을 복사하거나 저장소에서 패키지를 빌드하세요.

## dApp SDK

의존성이 없는 단일 ES 모듈입니다. 조회는 평범한 `fetch`(브라우저 또는 Node 18+)로 이루어지고, 쓰기는 지갑과 같은 암호화 WASM을 사용해 ML-DSA-65로 서명하므로 노드가 서명을 받아들입니다.

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

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

| 옵션 | 기본값 | 의미 |
|------|--------|------|
| `gateway` | `http://localhost:3000` | gateway 기본 URL |
| `crypto` | — | 이미 초기화된 암호화 WASM 모듈 |
| `cryptoLoader` | `../wasm/neuro_wasm_crypto.js`를 로드 | 초기화된 모듈을 반환하는 비동기 함수 |

### 금액

여러분이 전달하는 모든 `value`와 `amount`는 **사람이 읽는 NRO**(`'1.5'`, `2`, `'0.000021'`)입니다. SDK는 `toRaw()`로 정수 uNRO로 변환하므로, 서명하는 금액과 보내는 금액이 항상 같습니다. 화면 표시에는 API가 반환하는 `*_human` 필드를 사용하세요.

### 조회

| 메서드 | 호출 | 반환 |
|--------|------|------|
| `networkStatus()` | `GET /api/v1/network/status` | 높이, 샤드, 검증자 |
| `shards()` | `GET /api/v1/shards` | 샤드별 높이 |
| `account(addr)` | `GET /api/v1/accounts/:addr` | `balance`, `balance_human`, `nonce`, … |
| `balance(addr)` | `account` 경유 | `{ raw, human, nonce }` |
| `nextNonce(addr)` | `account` 경유 | 확정된 nonce + 1 |
| `validators()` | `GET /api/v1/validators` | 스테이크와 메트릭이 포함된 검증자 목록 |
| `blocks({ limit, offset })` | `GET /api/v1/blocks` | 최근 블록 |
| `block(height)` | `GET /api/v1/blocks/:height` | 블록 하나 |
| `tx(hash)` | `GET /api/v1/transactions/:hash` | 트랜잭션 하나 |
| `contract(addr)` / `contracts({ limit, offset })` | `GET /api/v1/contracts…` | 컨트랙트 메타데이터 |
| `gasEstimate()` | `GET /api/v1/gas/estimate` | `base_fee`와 추정치 |
| `contractQuery(addr, fn, args)` | `dry_run: true`로 `POST /api/v1/contracts/:addr/call` | 파싱된 반환 값; 트랜잭션 없음, 가스 없음 |

### 지갑

```js
const wallet = await nc.walletFromMnemonic('<24개 단어>')
wallet.address    // neuro:<15 hex>.human
wallet.publicKey  // ML-DSA-65 공개 키, hex
```

개인 시드는 지갑 객체 안에 머무르며, 속성으로 노출되지 않습니다.

### 쓰기

```js
// 송금
await nc.transfer({ wallet, to: 'neuro:<주소>.human', value: '1.5', gasPrice })

// 임의의 트랜잭션, 필요하면 호출 데이터 포함
await nc.sendTransaction({ wallet, to, value, data, gasPrice })

// 컨트랙트 호출 — JSON 호출 데이터 { function, args }
await nc.contractCall({ wallet, address: 'neuro:<10 hex>.app', fn: 'transfer', args: { … }, value, gasPrice })

// 배포 — neuro:system.deploy로 보내는 트랜잭션
await nc.deployContract({ wallet, bytecodeHex, name, description, template, initArgs, gasPrice })
```

각 쓰기 작업은 다음 nonce를 읽고, `nc-tx-v2|from|to|value|nonce`에 서명한 뒤 `POST /api/v1/transactions`로 전송합니다.

::: warning 가스 가격을 지정하세요
SDK의 기본값은 `gasPrice: 1`(배포: 5)입니다. 노드는 현재 기본 수수료보다 낮은 가격의 트랜잭션을 거부하며, 기본 수수료는 부하가 걸리면 올라갑니다. 먼저 이를 읽고 `gasPrice`를 명시적으로 전달하세요.

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

### 서명된 작업

스테이킹, 이름 등록 등 시스템 작업은 밀리초 타임스탬프 nonce와 함께 시스템 대상에 대해 서명합니다([지갑 → 서명된 작업](/ko/learn/wallet#signed-operations) 참고).

```js
const op = nc.signOp(wallet, OP_TARGETS.name, 0)   // { signature, public_key, nonce }
// op를 엔드포인트의 요청 본문에 병합
```

`OP_TARGETS`에는 `stake`(`neuro:system.stake`)와 `name`(`neuro:system.name`)이 있습니다.

### 알려진 문제 {#known-issues}

::: danger 현재 SDK의 알려진 문제
- **0이 아닌 금액의 송금이 실패합니다.** `sendTransaction` / `transfer`는 `value`를 JSON 문자열로 보내는데, 노드는 숫자만 읽으므로 `Missing from/to/value`로 요청을 거부합니다. 금액이 0인 호출과 배포는 영향을 받지 않습니다. 해결 방법 — 숫자 값으로 서명하고 전송하세요.
  ```js
  const nonce = await nc.nextNonce(wallet.address)
  const value = Number(toRaw('1.5'))               // 숫자로 된 uNRO
  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 }) })
  ```
- 컨트랙트 응답이 깨끗한 JSON이 아니면 `contractQuery`는 원시 문자열을 반환합니다. NRC-20 응답은 NUL 바이트로 끝나므로 `'{"ok":true,"value":…}'`를 문자열로 받게 됩니다. `\u0000`을 제거한 뒤 파싱하세요.
- `stake()`는 uNRO로 변환된 금액을 두 번 변환해 서명하므로 노드가 서명을 거부합니다. 수정될 때까지는 요청을 직접 만드세요: `{ address, amount: toRaw(amount), ...nc.signOp(wallet, OP_TARGETS.stake, amount) }`를 `/api/v1/staking/stake`로 전송합니다.
- `staking(addr)`는 gateway가 제공하지 않는 `/api/v1/staking/:addr`를 요청합니다. `GET /api/v1/staking/info/:address`를 사용하세요.
- `contractCall`은 호출 JSON에 `caller` 필드를 추가합니다. 컨트랙트는 이를 무시하므로 — 신원은 호스트에서 옵니다 — 해가 없습니다.
:::

### 암호화 모듈 {#crypto-module}

서명에는 `neuro_wasm_crypto` — 지갑의 WASM 빌드(`neurochain-web/src/wasm/neuro_wasm_crypto.js`와 `neuro_wasm_crypto_bg.wasm`) — 가 필요합니다. 이 모듈은 `nc_keypair_from_words`, `nc_tx_message`, `nc_sign`을 export해야 합니다. 페이지에서 다른 위치로 제공한다면 로더를 전달하세요.

```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
  },
})
```

모듈이 없으면 조회는 동작하지만 모든 쓰기 작업은 예외를 던집니다.

## Signer SDK {#signer-sdk}

사용자의 시드 문구를 보아서는 안 되는 다른 origin의 dApp(거래소, 게임)을 위한 것입니다. 사용자의 NeuroChain 웹 앱이 팝업으로 열리고, 사용자가 승인하면 서명이 `postMessage`로 돌아옵니다.

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

const signer = new NeuroSigner({
  signerUrl: 'http://localhost:8080/signer',      // 지갑 앱의 /signer 페이지
  allowedOrigins: ['http://localhost:8080'],
})

// 클릭 핸들러 안에서 직접 실행해야 합니다. 그렇지 않으면 브라우저가 팝업을 차단합니다
button.onclick = async () => {
  const { address, publicKey } = await signer.connect()
  const { signature, nonce } = await signer.signOperation({
    to: 'neuro:system.dex',
    value: 1000000,          // uNRO
  })
}
```

| 메서드 | 반환 |
|--------|------|
| `connect()` | `{ address, publicKey }` |
| `signOperation({ to, value, actor? })` | `{ signature, publicKey, nonce }` — `actor`(기본값: 연결된 주소)에 대한 서명된 작업 |
| `isConnected()`, `getAddress()`, `getPublicKey()` | 연결 상태 |
| `close()` | 팝업을 닫고 연결을 해제 |

오류에는 `code`가 있습니다: `POPUP_BLOCKED`, `CONNECTION_TIMEOUT`, `SIGNATURE_REJECTED`, `INVALID_ORIGIN`, `NOT_CONNECTED`. `POPUP_BLOCKED`인 경우 사용자에게 signer 페이지로 가는 직접 링크를 보여 주세요.

메시지는 `allowedOrigins`에서 온 것만 허용됩니다. 여러분의 지갑 앱 origin으로 설정하세요.
