nansen-cli 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +118 -164
- package/CLAUDE.md +16 -19
- package/README.md +163 -112
- package/package.json +3 -1
- package/scripts/check-changeset.js +28 -0
- package/src/api.js +41 -59
- package/src/cli.js +387 -353
- package/src/ens.js +163 -0
- package/src/update-check.js +35 -0
- package/vitest.e2e.config.js +10 -0
package/AGENTS.md
CHANGED
|
@@ -1,222 +1,176 @@
|
|
|
1
|
-
# AGENTS.md —
|
|
1
|
+
# AGENTS.md — Contributor Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guidance for AI coding agents (Claude Code, Codex, Copilot, etc.) working on this repository. If you're an agent **using** the CLI, see [README.md](README.md).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Architecture
|
|
6
6
|
|
|
7
|
-
**Prefer the [REST API](https://docs.nansen.ai) for most agent use cases** — no install step, no dependency issues.
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# Direct API call — no CLI needed
|
|
11
|
-
curl -s -X POST https://api.nansen.ai/api/v1/token-screener \
|
|
12
|
-
-H "apiKey: YOUR_KEY" \
|
|
13
|
-
-H "Content-Type: application/json" \
|
|
14
|
-
-d '{"chain":"solana","pagination":{"page":1,"per_page":5}}' | jq .
|
|
15
7
|
```
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# Method 2: Environment variable (no interaction needed)
|
|
30
|
-
export NANSEN_API_KEY=<key>
|
|
31
|
-
|
|
32
|
-
# Method 3: Config file (write directly — no validation call burned)
|
|
33
|
-
mkdir -p ~/.nansen && echo '{"apiKey":"<key>","baseUrl":"https://api.nansen.ai"}' > ~/.nansen/config.json && chmod 600 ~/.nansen/config.json
|
|
8
|
+
src/
|
|
9
|
+
├── index.js # Entry point (shebang, calls runCLI)
|
|
10
|
+
├── cli.js # Command router, arg parsing, schema, help text
|
|
11
|
+
├── api.js # NansenAPI client (REST, retry, cache, x402 auto-pay)
|
|
12
|
+
├── wallet.js # Wallet CRUD (create/list/show/export/delete/send)
|
|
13
|
+
├── trading.js # Quote + execute swaps (OKX router via API)
|
|
14
|
+
├── transfer.js # Token/native transfers (EVM + Solana)
|
|
15
|
+
├── x402.js # x402 payment orchestration (picks network, signs)
|
|
16
|
+
├── x402-evm.js # EVM payment signing (EIP-3009 transferWithAuthorization)
|
|
17
|
+
├── x402-svm.js # Solana payment signing (SPL transfer)
|
|
18
|
+
├── crypto.js # Key encryption/decryption (AES-256-GCM or plaintext)
|
|
19
|
+
└── update-check.js # Version upgrade notice
|
|
34
20
|
```
|
|
35
21
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### Auth Priority
|
|
22
|
+
### Command routing
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
2. `~/.nansen/config.json` file
|
|
42
|
-
3. Prompt (interactive only)
|
|
24
|
+
`src/index.js` → `runCLI()` in `src/cli.js`
|
|
43
25
|
|
|
44
|
-
|
|
26
|
+
Commands are built by three functions, merged in `runCLI()`:
|
|
27
|
+
- `buildCommands()` in cli.js — analytics commands (smart-money, profiler, token, etc.)
|
|
28
|
+
- `buildWalletCommands()` in wallet.js — wallet subcommands
|
|
29
|
+
- `buildTradingCommands()` in trading.js — quote/execute
|
|
45
30
|
|
|
46
|
-
|
|
31
|
+
Commands listed in `NO_AUTH_COMMANDS` skip API initialization. Everything else instantiates `NansenAPI` with retry, cache, and x402 config.
|
|
47
32
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
# This is cheap and fast:
|
|
52
|
-
nansen schema | head -1
|
|
53
|
-
# Expected: {"version":"1.x.x","commands":{...}}
|
|
33
|
+
### Data flow: trade
|
|
54
34
|
|
|
55
|
-
# This burns a credit but proves API access:
|
|
56
|
-
nansen token screener --chain solana --limit 1
|
|
57
35
|
```
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### 1. Always Use `--fields` to Reduce Token Burn
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# ❌ Returns everything (huge JSON, wastes agent context)
|
|
65
|
-
nansen smart-money netflow --chain solana
|
|
66
|
-
|
|
67
|
-
# ✅ Only what you need
|
|
68
|
-
nansen smart-money netflow --chain solana --fields token_symbol,net_flow_usd,chain --limit 10
|
|
36
|
+
CLI args → api.js GET /defi/quote → quote response
|
|
37
|
+
→ wallet.js decrypt key → trading.js sign tx → api.js POST /defi/execute → broadcast
|
|
69
38
|
```
|
|
70
39
|
|
|
71
|
-
###
|
|
40
|
+
### Data flow: x402 auto-pay
|
|
72
41
|
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
42
|
+
```
|
|
43
|
+
api.js (any call) → 402 response with payment requirements
|
|
44
|
+
→ x402.js rankRequirements() → picks cheapest network (EVM first)
|
|
45
|
+
→ x402-evm.js or x402-svm.js → sign USDC payment
|
|
46
|
+
→ api.js retries original request with Payment-Signature header
|
|
77
47
|
```
|
|
78
48
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Every response has `success: true/false`. Errors include `code` for routing:
|
|
82
|
-
|
|
83
|
-
| Code | What To Do |
|
|
84
|
-
|------|------------|
|
|
85
|
-
| `CREDITS_EXHAUSTED` | **Stop all calls.** Tell the human. |
|
|
86
|
-
| `RATE_LIMITED` | Wait. Auto-retry handles this. |
|
|
87
|
-
| `UNSUPPORTED_FILTER` | Remove the filter, retry without it. |
|
|
88
|
-
| `UNAUTHORIZED` | Key is wrong. Re-auth. |
|
|
89
|
-
| `INVALID_ADDRESS` | Check address format for the chain. |
|
|
49
|
+
If EVM payment fails (insufficient funds), the async generator yields a Solana signature as fallback.
|
|
90
50
|
|
|
91
|
-
###
|
|
51
|
+
### Output convention
|
|
92
52
|
|
|
93
|
-
|
|
94
|
-
- `profiler labels` + `profiler balance` are expensive — batch 3-4 at a time
|
|
95
|
-
- `schema`, `help`, `cache` are free (no API key needed)
|
|
96
|
-
- If you get `CREDITS_EXHAUSTED`, **stop immediately** — don't retry
|
|
53
|
+
Core functions return data objects. The CLI layer formats via `formatOutput()`. Never `console.log` in core functions — use the `log` dependency injection for CLI output.
|
|
97
54
|
|
|
98
|
-
|
|
55
|
+
## Development
|
|
99
56
|
|
|
100
57
|
```bash
|
|
101
|
-
#
|
|
102
|
-
|
|
58
|
+
npm install # Install dependencies
|
|
59
|
+
npm test # Run tests (vitest)
|
|
60
|
+
npm run test:watch # Watch mode
|
|
61
|
+
npm run test:coverage # With coverage
|
|
103
62
|
```
|
|
104
63
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
The CLI exposes `--limit N` which maps to `{page: 1, per_page: N}` in the API request. **There is no `--page` flag** — the CLI always fetches page 1. To access later pages, use the REST API directly.
|
|
64
|
+
### Running locally
|
|
108
65
|
|
|
109
66
|
```bash
|
|
110
|
-
|
|
111
|
-
nansen smart-money netflow --chain solana --limit 50
|
|
112
|
-
|
|
113
|
-
# REST API: full pagination control
|
|
114
|
-
curl -s -X POST https://api.nansen.ai/api/v1/smart-money/netflow \
|
|
115
|
-
-H "apiKey: $NANSEN_API_KEY" -H "Content-Type: application/json" \
|
|
116
|
-
-d '{"chains":["solana"],"pagination":{"page":2,"per_page":50}}'
|
|
117
|
-
```
|
|
67
|
+
node src/index.js <command> [options]
|
|
118
68
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
# Request 50, get 23 back → last page
|
|
125
|
-
nansen token holders --token <addr> --chain solana --limit 50
|
|
126
|
-
# Check: .data.data.length (or .data.results.length) < 50 → done
|
|
69
|
+
# Examples
|
|
70
|
+
node src/index.js wallet create my-wallet
|
|
71
|
+
node src/index.js smart-money --chain solana --limit 5
|
|
127
72
|
```
|
|
128
73
|
|
|
129
|
-
|
|
74
|
+
## Testing
|
|
130
75
|
|
|
131
|
-
|
|
76
|
+
- **Framework:** Vitest
|
|
77
|
+
- **Test files:** `src/__tests__/*.test.js`
|
|
78
|
+
- **Current:** 577 tests across 13 test files
|
|
79
|
+
- **All new code must have tests**
|
|
80
|
+
- **Mock all RPC/API calls** — never hit real networks in tests
|
|
132
81
|
|
|
133
|
-
###
|
|
82
|
+
### Test structure
|
|
134
83
|
|
|
135
|
-
|
|
84
|
+
```js
|
|
85
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
136
86
|
|
|
137
|
-
|
|
138
|
-
{ "success": true, "data": <raw_api_response> }
|
|
139
|
-
```
|
|
87
|
+
global.fetch = vi.fn();
|
|
140
88
|
|
|
141
|
-
|
|
89
|
+
describe('featureName', () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
fetch.mockReset();
|
|
92
|
+
});
|
|
142
93
|
|
|
143
|
-
|
|
144
|
-
{
|
|
94
|
+
it('should do the thing', async () => {
|
|
95
|
+
fetch.mockResolvedValueOnce({
|
|
96
|
+
ok: true,
|
|
97
|
+
json: async () => ({ jsonrpc: '2.0', result: '0x...', id: 1 })
|
|
98
|
+
});
|
|
99
|
+
// test logic
|
|
100
|
+
});
|
|
101
|
+
});
|
|
145
102
|
```
|
|
146
103
|
|
|
147
|
-
###
|
|
104
|
+
### Required RPC mocks by code path
|
|
148
105
|
|
|
149
|
-
|
|
106
|
+
**EVM transfers:** `eth_getBalance`, `eth_gasPrice`, `eth_maxPriorityFeePerGas`, `eth_getTransactionCount`, `eth_estimateGas`, `eth_getCode`, `eth_sendRawTransaction`, `eth_getTransactionReceipt`
|
|
150
107
|
|
|
151
|
-
|
|
152
|
-
|-------|------------------|
|
|
153
|
-
| `data.data` (array) | token screener |
|
|
154
|
-
| `data.results` (array) | entity search |
|
|
155
|
-
| `data.data.results` (array) | most profiler endpoints |
|
|
156
|
-
| `data.netflows` | smart-money netflow |
|
|
157
|
-
| `data.trades` | smart-money dex-trades |
|
|
158
|
-
| `data.holdings` | smart-money holdings |
|
|
159
|
-
| `data.holders` | token holders |
|
|
160
|
-
|
|
161
|
-
When parsing, check for the array at each level. `--table` and `--stream` handle this automatically, but if you're parsing raw JSON with `jq`, probe the shape first:
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
nansen smart-money netflow --chain solana | jq 'keys, .data | keys'
|
|
165
|
-
```
|
|
108
|
+
**Solana transfers:** `getBalance`, `getLatestBlockhash`, `sendTransaction`, `getSignatureStatuses`
|
|
166
109
|
|
|
167
|
-
|
|
110
|
+
**SPL token transfers** (additionally): `getTokenAccountsByOwner`, `getAccountInfo`
|
|
168
111
|
|
|
169
|
-
|
|
112
|
+
**Wallet operations:** No RPC mocks needed (file I/O only). Mock `fs` if testing file paths.
|
|
170
113
|
|
|
171
|
-
|
|
114
|
+
**API calls:** Mock `fetch` to return `{ ok: true, json: () => ({...}) }` or `{ ok: false, status: 402, headers: new Headers({...}) }` for x402 paths.
|
|
172
115
|
|
|
173
|
-
|
|
116
|
+
## Style Guide
|
|
174
117
|
|
|
175
|
-
|
|
118
|
+
- **ESM only** (`import`/`export`). No TypeScript, no transpilation.
|
|
119
|
+
- **No interactive prompts in core functions.** Use env vars: `NANSEN_WALLET_PASSWORD`, `NANSEN_API_KEY`.
|
|
120
|
+
- **Error handling:** `throw new Error('descriptive message')` in core. CLI catches and formats.
|
|
121
|
+
- **Actionable error messages** — tell the user what to do:
|
|
122
|
+
- ❌ `"Authentication failed"`
|
|
123
|
+
- ✅ `"Not logged in. Run: nansen login"`
|
|
124
|
+
- **BigInt for token amounts.** Never use floating point. Parse to BigInt with decimals.
|
|
125
|
+
- **Chain branching:** Use `chain === 'solana'` checks, not inheritance/polymorphism.
|
|
126
|
+
- **Minimal dependencies.** Prefer Node.js built-in APIs (crypto, fs, path, http).
|
|
176
127
|
|
|
177
|
-
|
|
178
|
-
- `smart_money_holders` — absent for tokens without smart money tracking
|
|
179
|
-
- Flow intelligence fields — may all be `0` for illiquid tokens (not an error)
|
|
180
|
-
- Perp endpoints (`--symbol`) only work for Hyperliquid; `--token` is for on-chain tokens
|
|
128
|
+
## PR Checklist
|
|
181
129
|
|
|
182
|
-
|
|
130
|
+
- [ ] `npm test` passes (all tests)
|
|
131
|
+
- [ ] New code paths have test coverage
|
|
132
|
+
- [ ] No hardcoded secrets, API keys, or private keys
|
|
133
|
+
- [ ] No `console.log` in core functions (use `log` dep injection)
|
|
134
|
+
- [ ] Error messages are actionable (tell user what to do)
|
|
135
|
+
- [ ] CLI help text updated if adding/changing commands
|
|
136
|
+
- [ ] RPC mocks cover all methods in the code path
|
|
137
|
+
- [ ] Wallet flows work both with and without `NANSEN_WALLET_PASSWORD`
|
|
138
|
+
- [ ] Changeset added if changing user-facing behavior (add a `.changeset/<name>.md` file — `npm test` will warn if missing)
|
|
183
139
|
|
|
184
|
-
|
|
140
|
+
## Chains & Networks
|
|
185
141
|
|
|
186
|
-
|
|
142
|
+
**EVM:** Ethereum (chain ID 1), Base (8453). `CHAIN_IDS` in transfer.js only maps these two — other EVM chains will fail for transfers.
|
|
187
143
|
|
|
188
|
-
|
|
144
|
+
**Solana:** mainnet-beta. Supports native SOL, standard SPL tokens, and Token-2022 (Token Extensions).
|
|
189
145
|
|
|
190
|
-
|
|
146
|
+
**RPC endpoints:** Hardcoded in `CHAIN_RPCS` (transfer.js). Nansen API handles RPC for trading.
|
|
191
147
|
|
|
192
|
-
|
|
193
|
-
|---------|-----|
|
|
194
|
-
| `command not found: nansen` | `npm install -g nansen-cli` or `npx nansen-cli` |
|
|
195
|
-
| `UNAUTHORIZED` after login | Check `cat ~/.nansen/config.json` — key may not have saved. Write it directly. |
|
|
196
|
-
| Login hangs or fails | Skip `nansen login`, write config directly (see Install & Auth above) |
|
|
197
|
-
| Huge JSON response | Use `--fields` to select only needed columns |
|
|
198
|
-
| Perp endpoints empty or erroring | Use `--symbol BTC` not `--token`. Perp endpoints are Hyperliquid-only. |
|
|
199
|
-
| JUP DCA returns error | Solana-only endpoint |
|
|
200
|
-
| `UNSUPPORTED_FILTER` on token holders | Not all tokens have smart money data. Remove `--smart-money` and retry. |
|
|
201
|
-
| `CREDITS_EXHAUSTED` | Stop all calls. Check [app.nansen.ai](https://app.nansen.ai). No retry will help. |
|
|
148
|
+
## Key Constants
|
|
202
149
|
|
|
203
|
-
|
|
150
|
+
| Constant | Value |
|
|
151
|
+
|----------|-------|
|
|
152
|
+
| USDC (Base) | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
|
|
153
|
+
| USDC (Solana) | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
|
|
154
|
+
| x402 payment | $0.05 USDC per API call |
|
|
155
|
+
| Gas buffer | API provides `quote.gas` with 1.5x multiplier — use directly |
|
|
204
156
|
|
|
205
|
-
|
|
206
|
-
|------|------------|
|
|
207
|
-
| `CREDITS_EXHAUSTED` | **Stop all calls.** Tell the human. Check dashboard. |
|
|
208
|
-
| `RATE_LIMITED` | Wait — auto-retry handles this by default. |
|
|
209
|
-
| `UNSUPPORTED_FILTER` | Remove the filter, retry without it. |
|
|
210
|
-
| `UNAUTHORIZED` | Key is wrong or missing. Re-auth. |
|
|
211
|
-
| `INVALID_ADDRESS` | Check address format matches the chain (EVM: `0x...`, Solana: Base58). |
|
|
157
|
+
## Endpoint Quirks
|
|
212
158
|
|
|
213
|
-
|
|
159
|
+
These are internal details agents should know when writing or debugging tests:
|
|
214
160
|
|
|
215
|
-
- **`token holders --smart-money`** —
|
|
216
|
-
- **`token flow-intelligence`** — May return all-zero flows for tokens
|
|
217
|
-
- **`
|
|
161
|
+
- **`token holders --smart-money`** — Returns `UNSUPPORTED_FILTER` for tokens without smart money tracking. Not all tokens have this data.
|
|
162
|
+
- **`token flow-intelligence`** — May return all-zero flows for illiquid tokens. Normal, not an error.
|
|
163
|
+
- **`token screener --search`** — Client-side filtering. The CLI fetches up to 500 results, then filters locally.
|
|
164
|
+
- **`--fields`** — Applies to the entire response tree, including the `success`/`data` wrapper.
|
|
165
|
+
- **Profiler beta endpoints** use `recordsPerPage` instead of `per_page`. The CLI handles this automatically.
|
|
218
166
|
- **`profiler perp-positions`** — No pagination support; the API ignores the pagination parameter.
|
|
219
167
|
|
|
220
|
-
##
|
|
168
|
+
## Known Gotchas
|
|
221
169
|
|
|
222
|
-
|
|
170
|
+
1. **EIP-7702 delegated accounts** on Base have contract code. Always use `eth_estimateGas`, never hardcode 21000 gas.
|
|
171
|
+
2. **Solana SPL account ordering:** Writable accounts (destATA) must precede readonly (mint) in the transaction message.
|
|
172
|
+
3. **`getSignatureStatuses`** over `confirmTransaction` — the latter is deprecated and unreliable on public RPCs.
|
|
173
|
+
4. **`--max` native SOL:** Reserve 5000 lamports for fee. On EVM L2s, reserve 3x estimated gas for L1 data posting fees.
|
|
174
|
+
5. **Token-2022:** Use `TOKEN_2022_PROGRAM_ID` and `TransferCheckedInstruction` (not plain `Transfer`).
|
|
175
|
+
6. **CreateATA path:** When recipient doesn't have a token account, the sender creates it. This path in transfer.js has limited test coverage — add tests if modifying.
|
|
176
|
+
7. **`CHAIN_IDS` is incomplete:** Only ethereum and base are mapped. Adding new EVM chain support requires updating this map.
|
package/CLAUDE.md
CHANGED
|
@@ -125,7 +125,7 @@ Returns JSON with all commands, subcommands, option types/defaults, return field
|
|
|
125
125
|
|
|
126
126
|
### Field Filtering
|
|
127
127
|
```bash
|
|
128
|
-
nansen smart-money netflow --fields token_symbol,net_flow_usd,chain
|
|
128
|
+
nansen research smart-money netflow --fields token_symbol,net_flow_usd,chain
|
|
129
129
|
```
|
|
130
130
|
Reduces response size by including only specified fields. Works with nested data structures.
|
|
131
131
|
|
|
@@ -180,41 +180,38 @@ Structured error codes for programmatic handling:
|
|
|
180
180
|
|
|
181
181
|
## Publishing (npm)
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
**DO NOT manually run `npm version` or `npm publish`. CI handles everything.**
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
1. **Push to main** — CI runs tests
|
|
186
|
+
2. **CI creates a "Version Packages" PR** — bumps version + updates CHANGELOG
|
|
187
|
+
3. **Merge the Version PR** — CI auto-publishes to npm
|
|
186
188
|
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
npx changeset
|
|
190
|
-
# Or manually create .changeset/<name>.md
|
|
191
|
-
```
|
|
189
|
+
If you mess up: accidentally bumped version manually? `git revert` and add a changeset instead. CI publish failed? Check GitHub Actions logs, likely needs `NPM_TOKEN` secret refresh.
|
|
192
190
|
|
|
193
|
-
|
|
191
|
+
## Changesets
|
|
194
192
|
|
|
195
|
-
|
|
193
|
+
Every PR that changes user-facing behavior **must** include a changeset file. `npm test` will warn if one is missing. The changeset description ends up in CHANGELOG.md (auto-generated by CI), so write it as a user-facing changelog entry.
|
|
196
194
|
|
|
197
|
-
|
|
195
|
+
**Needs a changeset:** new features, bug fixes, breaking changes, changed CLI output, new/modified commands.
|
|
196
|
+
|
|
197
|
+
**Does NOT need a changeset:** docs-only, test-only, refactors with no behavior change, CI/tooling.
|
|
198
|
+
|
|
199
|
+
Add a file to `.changeset/` with a descriptive kebab-case name:
|
|
198
200
|
|
|
199
|
-
### Changeset format:
|
|
200
201
|
```markdown
|
|
201
202
|
---
|
|
202
203
|
"nansen-cli": minor
|
|
203
204
|
---
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
Short description of the change (appears in CHANGELOG)
|
|
206
207
|
```
|
|
207
208
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
### If you mess up:
|
|
211
|
-
- Accidentally bumped version manually? `git revert` and add a changeset instead
|
|
212
|
-
- CI publish failed? Check GitHub Actions logs, likely needs `NPM_TOKEN` secret refresh
|
|
209
|
+
Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes.
|
|
213
210
|
|
|
214
211
|
## PR Checklist
|
|
215
212
|
|
|
216
213
|
- [ ] Tests pass (`npm test`)
|
|
217
214
|
- [ ] New endpoints have tests in all 3 test files
|
|
218
215
|
- [ ] README.md updated if adding user-facing features
|
|
219
|
-
- [ ]
|
|
216
|
+
- [ ] Changeset added for user-facing changes (see above)
|
|
220
217
|
- [ ] No new dependencies (keep it lightweight)
|