@neus/sdk 1.0.9 → 1.0.12

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/README.md CHANGED
@@ -1,104 +1,208 @@
1
1
  # @neus/sdk
2
2
 
3
- [![npm](https://img.shields.io/npm/v/%40neus%2Fsdk?logo=npm&label=%40neus%2Fsdk&color=98C0EF)](https://www.npmjs.com/package/@neus/sdk)
3
+ Create, check, and reuse NEUS trust receipts from apps and backends. The same package ships the **`neus`** CLI for wiring **hosted NEUS MCP** into editors and agents.
4
4
 
5
- **Portable trust, shipped as APIs and widgets.** For **Web2-style apps**, prefer **Hosted Verify** (`getHostedCheckoutUrl` or `VerifyGate`) so users never touch `window.ethereum` in your UI. Use **`npk_*` + `gateCheck`** on your server for allow/deny. Store **`qHash`**, then reuse it.
5
+ NEUS makes trust portable across the internet so people, apps, and AI agents can prove what is real before access, payout, or execution.
6
6
 
7
- ## Install
7
+ ## Install (library)
8
8
 
9
9
  ```bash
10
10
  npm install @neus/sdk
11
11
  ```
12
12
 
13
- ## Minimal working example (hosted, no wallet in your page)
13
+ ## Connect editors and assistants (MCP)
14
14
 
15
- ```javascript
16
- import { getHostedCheckoutUrl } from '@neus/sdk';
15
+ Use one command to merge NEUS into Cursor, VS Code, and Claude Code when those tools are detected. No separate NEUS editor extension is required.
17
16
 
18
- window.location.href = getHostedCheckoutUrl({
19
- verifiers: ['ownership-basic'],
20
- returnUrl: 'https://myapp.com/neus/callback',
21
- });
17
+ ```bash
18
+ npx -y -p @neus/sdk neus setup
22
19
  ```
23
20
 
24
- After Hosted Verify, NEUS redirects to `returnUrl` with **`qHash`** in the query string persist it on your server next to your user id.
21
+ Add your NEUS Profile access key in the same step (needed for account-aware tools such as `neus_me`):
25
22
 
26
- ## Server check (use your access key, not in the browser)
23
+ ```bash
24
+ npx -y -p @neus/sdk neus setup --access-key <npk_...>
25
+ ```
27
26
 
28
- ```javascript
29
- import { NeusClient } from '@neus/sdk';
27
+ Check configuration and connectivity:
30
28
 
31
- const client = new NeusClient({ appId: 'your-app-id' });
29
+ ```bash
30
+ npx -y -p @neus/sdk neus doctor
31
+ ```
32
32
 
33
- const check = await client.gateCheck({
34
- address: '0x...',
35
- verifierIds: ['ownership-basic'],
33
+ Create keys under **Profile → Account → Access keys** on [neus.network](https://neus.network/profile?tab=account). Never put access keys in browser bundles or public repos.
34
+
35
+ | Topic | Link |
36
+ | --- | --- |
37
+ | Setup, JSON snippets, and headers | [MCP setup](https://docs.neus.network/mcp/setup) |
38
+ | Tools and session order | [MCP overview](https://docs.neus.network/mcp/overview) |
39
+ | Discovery URLs | [Discovery and endpoints](https://docs.neus.network/mcp/endpoints) |
40
+ | Optional Claude Code plugin + skill | [NEUS for Claude Code](https://docs.neus.network/mcp/claude-code-marketplace) |
41
+
42
+ Prefer `neus setup` over hand-editing config files so every host stays on **`https://mcp.neus.network/mcp`**.
43
+
44
+ ## What you can build
45
+
46
+ - Issue a proof that a user, wallet, org, app, file, release, profile, or result belongs to someone
47
+ - Store the returned `qHash` as a durable trust receipt ID
48
+ - Check receipts later for access, eligibility, provenance, or display
49
+ - Add proof-gated UX with React widgets
50
+ - Connect IDEs and agents through optional MCP
51
+
52
+ ## Fastest path: Hosted Verify
53
+
54
+ Use Hosted Verify when you want NEUS to handle the signing/verification flow outside your app UI.
55
+
56
+ ```js
57
+ import { getHostedCheckoutUrl } from '@neus/sdk';
58
+
59
+ const url = getHostedCheckoutUrl({
60
+ verifiers: ['ownership-basic'],
61
+ returnUrl: 'https://yourapp.com/auth/callback'
36
62
  });
63
+
64
+ window.location.assign(url);
37
65
  ```
38
66
 
39
- ## Advanced: sign inside your app
67
+ After completion, NEUS redirects back with a `qHash`.
68
+
69
+ Store that `qHash` with your user record.
40
70
 
41
- Only when you intentionally need a browser wallet:
71
+ ## Create a signed receipt directly
42
72
 
43
- ```javascript
73
+ When your app handles signing. This example is EVM. For non-EVM wallets, pass the provider explicitly and include `chain` as a CAIP-2 value; see the CAIP-380 standards page in the docs.
74
+
75
+ ```js
44
76
  import { NeusClient } from '@neus/sdk';
45
77
 
46
- const client = new NeusClient({ appId: 'your-app-id' });
78
+ const client = new NeusClient({
79
+ appId: 'your-app-id'
80
+ });
47
81
 
48
82
  const proof = await client.verify({
49
83
  verifier: 'ownership-basic',
50
- content: 'Hello NEUS',
51
- wallet: window.ethereum,
84
+ data: {
85
+ owner: '0x...',
86
+ contentType: 'application/json',
87
+ content: JSON.stringify({
88
+ title: 'Verified claim',
89
+ type: 'project-update',
90
+ summary: 'Public summary of what is being proven.'
91
+ }),
92
+ reference: {
93
+ type: 'url',
94
+ id: 'https://example.com/source',
95
+ title: 'Source record'
96
+ }
97
+ },
98
+ wallet: window.ethereum // EVM provider
52
99
  });
53
- const qHash = proof.qHash;
54
- ```
55
-
56
- ## Core methods
57
100
 
58
- | Method | Purpose |
59
- | --- | --- |
60
- | `client.verify()` | Create proof |
61
- | `client.getProof()` | Fetch by `qHash` |
62
- | `client.pollProofStatus()` | Wait for async completion |
63
- | `client.gateCheck()` | **Server eligibility** (use for real gates) |
64
- | `client.checkGate()` | Local preview only |
65
- | `getHostedCheckoutUrl()` | Hosted verify URL |
66
- | `client.createWalletLinkData()` | Build advanced direct wallet-link payload |
101
+ console.log(proof.qHash);
102
+ console.log(proof.proofUrl);
103
+ ```
67
104
 
68
- ## VerifyGate (React)
105
+ ## React widget
69
106
 
70
- Defaults: private create (`privacyLevel: 'private'`, `publicDisplay: false`). Set `proofOptions` only when you intentionally need public visibility.
107
+ Use `VerifyGate` when you want a drop-in verification flow in React.
71
108
 
72
109
  ```jsx
73
110
  import { VerifyGate } from '@neus/sdk/widgets';
74
111
 
75
- <VerifyGate appId="your-app-id" requiredVerifiers={['ownership-basic']}>
76
- <ProtectedContent />
77
- </VerifyGate>
112
+ export function Page() {
113
+ return (
114
+ <VerifyGate
115
+ appId="your-app-id"
116
+ requiredVerifiers={['ownership-basic']}
117
+ verifierData={{
118
+ 'ownership-basic': {
119
+ owner: '0x...',
120
+ contentType: 'application/json',
121
+ content: JSON.stringify({
122
+ title: 'Verified claim',
123
+ summary: 'Public summary of what is being proven.'
124
+ }),
125
+ reference: {
126
+ type: 'url',
127
+ id: 'https://example.com/source'
128
+ }
129
+ }
130
+ }}
131
+ onVerified={(result) => {
132
+ console.log(result.qHash || result.qHashes);
133
+ }}
134
+ />
135
+ );
136
+ }
137
+ ```
138
+
139
+ ## Check receipts
140
+
141
+ Use `gateCheck` from trusted server code when you need allow/deny or eligibility checks.
142
+
143
+ ```js
144
+ import { NeusClient } from '@neus/sdk';
145
+
146
+ const client = new NeusClient({
147
+ appId: 'your-app-id'
148
+ });
149
+
150
+ const result = await client.gateCheck({
151
+ address: '0x...',
152
+ verifierIds: ['ownership-basic']
153
+ });
154
+
155
+ if (!result.data?.eligible) {
156
+ throw new Error('Access denied');
157
+ }
78
158
  ```
79
159
 
80
- [Widgets README](./widgets/README.md)
160
+ Never ship access keys in browser code.
161
+
162
+ ## Core methods
163
+
164
+ | Method | Use it for |
165
+ | ------------------------------- | ------------------------------------------- |
166
+ | `getHostedCheckoutUrl()` | Send a user to Hosted Verify |
167
+ | `client.verify()` | Create a proof |
168
+ | `client.getProof()` | Fetch a proof by `qHash` |
169
+ | `client.pollProofStatus()` | Wait for async completion |
170
+ | `client.gateCheck()` | Server-side eligibility checks |
171
+ | `client.checkGate()` | Local preview against already-loaded proofs |
172
+ | `client.createWalletLinkData()` | Advanced wallet-link payloads |
81
173
 
82
- ## Config
174
+ ## Configuration
83
175
 
84
- ```javascript
176
+ ```js
85
177
  const client = new NeusClient({
86
178
  apiUrl: 'https://api.neus.network',
87
- appId: 'my-app',
88
- timeout: 30000,
179
+ appId: 'your-app-id',
180
+ timeout: 30000
89
181
  });
90
182
  ```
91
183
 
92
- ## Docs
184
+ `appId` is public attribution for your app.
185
+ `apiKey` / `npk_*` is optional and server-side only.
186
+
187
+ ## MCP: step-by-step (alternative to `setup`)
188
+
189
+ ```bash
190
+ npx -y -p @neus/sdk neus init
191
+ ```
93
192
 
94
- - [Quickstart](https://docs.neus.network/quickstart)
95
- - [Examples](https://docs.neus.network/examples) — start with the [trust receipts showcase](https://github.com/neus/network/tree/main/examples/trust-receipts-showcase) locally; [live demo](https://neus.network/demo) on the product site
96
- - [SDK](https://docs.neus.network/sdks/javascript)
97
- - [MCP](https://docs.neus.network/mcp/overview) for IDEs and assistants
98
- - [Widgets](https://docs.neus.network/widgets/overview)
99
- - [API](https://docs.neus.network/api/overview)
100
- - [Hosted Verify](https://docs.neus.network/cookbook/auth-hosted-verify)
193
+ Add or rotate a Profile access key on an existing install:
101
194
 
102
- ## Contributing
195
+ ```bash
196
+ npx -y -p @neus/sdk neus auth --access-key <npk_...>
197
+ ```
198
+
199
+ Claude Code (optional): add marketplace `https://github.com/neus/network`, install **`neus-mcp@neus`**, then run **`neus setup`** so your key matches every editor. See [NEUS for Claude Code](https://docs.neus.network/mcp/claude-code-marketplace).
200
+
201
+ ## Docs
103
202
 
104
- See [CONTRIBUTING.md](../CONTRIBUTING.md) for how to propose documentation, SDK, and example changes.
203
+ - Quickstart: https://docs.neus.network/quickstart
204
+ - JavaScript SDK: https://docs.neus.network/sdks/javascript
205
+ - Ownership Basic: https://docs.neus.network/verification/ownership-basic
206
+ - Widgets: https://docs.neus.network/widgets/overview
207
+ - MCP: https://docs.neus.network/mcp/overview
208
+ - API: https://docs.neus.network/api/overview
package/SECURITY.md CHANGED
@@ -20,7 +20,7 @@ Treat **wallet signatures** and **API keys** as secrets. Do not log them, expose
20
20
 
21
21
  **`VerifyGate`** create mode also defaults to **private**.
22
22
 
23
- Use public visibility only when you intentionally need proof reuse without owner-authenticated access:
23
+ Use public visibility only when you need proof reuse without owner-authenticated access:
24
24
 
25
25
  - unlisted public: `privacyLevel: 'public'`, `publicDisplay: false`
26
26
  - listed public: `privacyLevel: 'public'`, `publicDisplay: true`
package/cjs/client.cjs CHANGED
@@ -421,6 +421,11 @@ function normalizeBrowserSignerString(raw) {
421
421
  }
422
422
  return null;
423
423
  }
424
+ function isPlaceholderNeusSignature(signature) {
425
+ const s = typeof signature === "string" ? signature.trim() : "";
426
+ if (!s) return true;
427
+ return /^0x0+$/i.test(s);
428
+ }
424
429
  var validateVerifierData = (verifierId, data) => {
425
430
  if (!data || typeof data !== "object") {
426
431
  return { valid: false, error: "Data object is required" };
@@ -797,7 +802,7 @@ var NeusClient = class {
797
802
  }
798
803
  _getDefaultBrowserWallet() {
799
804
  if (typeof window === "undefined") return null;
800
- return window.ethereum || window.solana || window.phantom && window.phantom.solana || null;
805
+ return window.ethereum || null;
801
806
  }
802
807
  async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
803
808
  const providerWallet = wallet || this._getDefaultBrowserWallet();
@@ -922,8 +927,57 @@ var NeusClient = class {
922
927
  ...label ? { label } : {}
923
928
  };
924
929
  }
930
+ async verifyFromApp(params) {
931
+ if (!params || typeof params !== "object") {
932
+ throw new ValidationError("verifyFromApp requires a params object");
933
+ }
934
+ const {
935
+ user,
936
+ verifier = "ownership-basic",
937
+ content,
938
+ data: explicitData = null,
939
+ options = {}
940
+ } = params;
941
+ if (!user || typeof user !== "object") {
942
+ throw new ValidationError("verifyFromApp requires user object");
943
+ }
944
+ const walletAddress = user.walletAddress || user.address || user.identity;
945
+ if (!walletAddress || typeof walletAddress !== "string") {
946
+ throw new ValidationError("verifyFromApp requires user.walletAddress");
947
+ }
948
+ const delegationQHash = this.config.appLinkQHash || typeof process !== "undefined" && process.env && process.env.NEUS_APP_LINK_QHASH || null;
949
+ let data;
950
+ if (explicitData && typeof explicitData === "object") {
951
+ data = { owner: walletAddress, ...explicitData };
952
+ } else if (content && typeof content === "object") {
953
+ data = {
954
+ owner: walletAddress,
955
+ content: JSON.stringify(content),
956
+ contentType: typeof content.contentType === "string" ? content.contentType : "application/json",
957
+ reference: content.reference || { type: "other" }
958
+ };
959
+ } else if (typeof content === "string") {
960
+ data = {
961
+ owner: walletAddress,
962
+ content,
963
+ reference: { type: "other" }
964
+ };
965
+ } else {
966
+ data = { owner: walletAddress, reference: { type: "other" } };
967
+ }
968
+ const signedTimestamp = Date.now();
969
+ const verifierIds = [verifier];
970
+ return this.verify({
971
+ verifierIds,
972
+ data,
973
+ walletAddress,
974
+ signedTimestamp,
975
+ ...delegationQHash ? { delegationQHash } : {},
976
+ options
977
+ });
978
+ }
925
979
  async verify(params) {
926
- if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
980
+ if ((isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
927
981
  const { content, verifier = "ownership-basic", data: data2 = null, wallet = null, options: options2 = {} } = params;
928
982
  if (verifier === "ownership-basic" && !data2 && (!content || typeof content !== "string")) {
929
983
  throw new ValidationError("content is required and must be a string (or use data param with owner + reference)");
@@ -976,7 +1030,7 @@ var NeusClient = class {
976
1030
  }
977
1031
  } else {
978
1032
  if (typeof window === "undefined" || !window.ethereum) {
979
- throw new ConfigurationError("No Web3 wallet detected. Please install MetaMask or provide wallet parameter.");
1033
+ throw new ConfigurationError("No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.");
980
1034
  }
981
1035
  await window.ethereum.request({ method: "eth_requestAccounts" });
982
1036
  provider = window.ethereum;
@@ -1264,13 +1318,15 @@ ${bytes.length}`;
1264
1318
  verifierIds,
1265
1319
  data,
1266
1320
  walletAddress,
1267
- signature,
1321
+ signature: rawSignature,
1268
1322
  signedTimestamp,
1269
1323
  chainId,
1270
1324
  chain,
1271
1325
  signatureMethod,
1326
+ delegationQHash,
1272
1327
  options = {}
1273
1328
  } = params;
1329
+ const signature = isPlaceholderNeusSignature(rawSignature) ? void 0 : rawSignature;
1274
1330
  const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
1275
1331
  const normalizeVerifierId = (id) => {
1276
1332
  if (typeof id !== "string") return id;
@@ -1287,8 +1343,11 @@ ${bytes.length}`;
1287
1343
  if (!walletAddress || typeof walletAddress !== "string") {
1288
1344
  throw new ValidationError("walletAddress is required");
1289
1345
  }
1290
- if (!signature) {
1291
- throw new ValidationError("signature is required");
1346
+ const hasAppAttribution = typeof this.config.appId === "string" && this.config.appId.trim().length > 0;
1347
+ if (!signature && !delegationQHash && !hasAppAttribution) {
1348
+ throw new ValidationError(
1349
+ "signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required"
1350
+ );
1292
1351
  }
1293
1352
  if (!signedTimestamp || typeof signedTimestamp !== "number") {
1294
1353
  throw new ValidationError("signedTimestamp is required");
@@ -1314,11 +1373,12 @@ ${bytes.length}`;
1314
1373
  verifierIds: normalizedVerifierIds,
1315
1374
  data,
1316
1375
  walletAddress,
1317
- signature,
1376
+ ...signature ? { signature } : {},
1318
1377
  signedTimestamp,
1319
1378
  ...resolvedChainId !== null && { chainId: resolvedChainId },
1320
1379
  ...chain && { chain },
1321
1380
  ...signatureMethod && { signatureMethod },
1381
+ ...delegationQHash && { delegationQHash },
1322
1382
  options: optionsPayload
1323
1383
  };
1324
1384
  const response = await this._makeRequest("POST", "/api/v1/verification", requestData);
package/cjs/index.cjs CHANGED
@@ -1081,6 +1081,11 @@ function normalizeBrowserSignerString(raw) {
1081
1081
  }
1082
1082
  return null;
1083
1083
  }
1084
+ function isPlaceholderNeusSignature(signature) {
1085
+ const s = typeof signature === "string" ? signature.trim() : "";
1086
+ if (!s) return true;
1087
+ return /^0x0+$/i.test(s);
1088
+ }
1084
1089
  var FALLBACK_PUBLIC_VERIFIER_CATALOG, EVM_ADDRESS_RE, WALLET_LINK_RELATIONSHIP_TYPES, validateVerifierData, NeusClient;
1085
1090
  var init_client = __esm({
1086
1091
  "client.js"() {
@@ -1481,7 +1486,7 @@ var init_client = __esm({
1481
1486
  }
1482
1487
  _getDefaultBrowserWallet() {
1483
1488
  if (typeof window === "undefined") return null;
1484
- return window.ethereum || window.solana || window.phantom && window.phantom.solana || null;
1489
+ return window.ethereum || null;
1485
1490
  }
1486
1491
  async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
1487
1492
  const providerWallet = wallet || this._getDefaultBrowserWallet();
@@ -1606,8 +1611,57 @@ var init_client = __esm({
1606
1611
  ...label ? { label } : {}
1607
1612
  };
1608
1613
  }
1614
+ async verifyFromApp(params) {
1615
+ if (!params || typeof params !== "object") {
1616
+ throw new ValidationError("verifyFromApp requires a params object");
1617
+ }
1618
+ const {
1619
+ user,
1620
+ verifier = "ownership-basic",
1621
+ content,
1622
+ data: explicitData = null,
1623
+ options = {}
1624
+ } = params;
1625
+ if (!user || typeof user !== "object") {
1626
+ throw new ValidationError("verifyFromApp requires user object");
1627
+ }
1628
+ const walletAddress = user.walletAddress || user.address || user.identity;
1629
+ if (!walletAddress || typeof walletAddress !== "string") {
1630
+ throw new ValidationError("verifyFromApp requires user.walletAddress");
1631
+ }
1632
+ const delegationQHash = this.config.appLinkQHash || typeof process !== "undefined" && process.env && process.env.NEUS_APP_LINK_QHASH || null;
1633
+ let data;
1634
+ if (explicitData && typeof explicitData === "object") {
1635
+ data = { owner: walletAddress, ...explicitData };
1636
+ } else if (content && typeof content === "object") {
1637
+ data = {
1638
+ owner: walletAddress,
1639
+ content: JSON.stringify(content),
1640
+ contentType: typeof content.contentType === "string" ? content.contentType : "application/json",
1641
+ reference: content.reference || { type: "other" }
1642
+ };
1643
+ } else if (typeof content === "string") {
1644
+ data = {
1645
+ owner: walletAddress,
1646
+ content,
1647
+ reference: { type: "other" }
1648
+ };
1649
+ } else {
1650
+ data = { owner: walletAddress, reference: { type: "other" } };
1651
+ }
1652
+ const signedTimestamp = Date.now();
1653
+ const verifierIds = [verifier];
1654
+ return this.verify({
1655
+ verifierIds,
1656
+ data,
1657
+ walletAddress,
1658
+ signedTimestamp,
1659
+ ...delegationQHash ? { delegationQHash } : {},
1660
+ options
1661
+ });
1662
+ }
1609
1663
  async verify(params) {
1610
- if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
1664
+ if ((isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
1611
1665
  const { content, verifier = "ownership-basic", data: data2 = null, wallet = null, options: options2 = {} } = params;
1612
1666
  if (verifier === "ownership-basic" && !data2 && (!content || typeof content !== "string")) {
1613
1667
  throw new ValidationError("content is required and must be a string (or use data param with owner + reference)");
@@ -1660,7 +1714,7 @@ var init_client = __esm({
1660
1714
  }
1661
1715
  } else {
1662
1716
  if (typeof window === "undefined" || !window.ethereum) {
1663
- throw new ConfigurationError("No Web3 wallet detected. Please install MetaMask or provide wallet parameter.");
1717
+ throw new ConfigurationError("No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.");
1664
1718
  }
1665
1719
  await window.ethereum.request({ method: "eth_requestAccounts" });
1666
1720
  provider = window.ethereum;
@@ -1948,13 +2002,15 @@ ${bytes.length}`;
1948
2002
  verifierIds,
1949
2003
  data,
1950
2004
  walletAddress,
1951
- signature,
2005
+ signature: rawSignature,
1952
2006
  signedTimestamp,
1953
2007
  chainId,
1954
2008
  chain,
1955
2009
  signatureMethod,
2010
+ delegationQHash,
1956
2011
  options = {}
1957
2012
  } = params;
2013
+ const signature = isPlaceholderNeusSignature(rawSignature) ? void 0 : rawSignature;
1958
2014
  const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
1959
2015
  const normalizeVerifierId = (id) => {
1960
2016
  if (typeof id !== "string") return id;
@@ -1971,8 +2027,11 @@ ${bytes.length}`;
1971
2027
  if (!walletAddress || typeof walletAddress !== "string") {
1972
2028
  throw new ValidationError("walletAddress is required");
1973
2029
  }
1974
- if (!signature) {
1975
- throw new ValidationError("signature is required");
2030
+ const hasAppAttribution = typeof this.config.appId === "string" && this.config.appId.trim().length > 0;
2031
+ if (!signature && !delegationQHash && !hasAppAttribution) {
2032
+ throw new ValidationError(
2033
+ "signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required"
2034
+ );
1976
2035
  }
1977
2036
  if (!signedTimestamp || typeof signedTimestamp !== "number") {
1978
2037
  throw new ValidationError("signedTimestamp is required");
@@ -1998,11 +2057,12 @@ ${bytes.length}`;
1998
2057
  verifierIds: normalizedVerifierIds,
1999
2058
  data,
2000
2059
  walletAddress,
2001
- signature,
2060
+ ...signature ? { signature } : {},
2002
2061
  signedTimestamp,
2003
2062
  ...resolvedChainId !== null && { chainId: resolvedChainId },
2004
2063
  ...chain && { chain },
2005
2064
  ...signatureMethod && { signatureMethod },
2065
+ ...delegationQHash && { delegationQHash },
2006
2066
  options: optionsPayload
2007
2067
  };
2008
2068
  const response = await this._makeRequest("POST", "/api/v1/verification", requestData);
package/cli/neus.mjs CHANGED
@@ -203,16 +203,18 @@ function printUsage(exitCode = 0) {
203
203
  'Usage: neus <command> [options]',
204
204
  '',
205
205
  'Commands:',
206
+ ' setup One-command: run init, then auth if --access-key is provided',
206
207
  ' init Configure supported MCP clients automatically',
207
208
  ' auth Add or update a personal access key for NEUS MCP',
208
209
  ' status Show current NEUS MCP setup',
210
+ ' doctor Deep check: config status, profile connection, agent verification',
209
211
  ' help Show this message',
210
212
  '',
211
213
  'Options:',
212
214
  ' --client <name[,name]> Limit setup to claude, cursor, or vscode',
213
215
  ' --project Write shared project config instead of user config',
214
216
  ' --access-key <npk_...> Configure Bearer auth for personal account tools',
215
- ' --json Emit machine-readable output',
217
+ ' --json Print JSON output',
216
218
  ' --dry-run Preview changes without writing files',
217
219
  ];
218
220
  const stream = exitCode === 0 ? process.stdout : process.stderr;
@@ -246,8 +248,8 @@ function ensureClientSelection(scope, clients) {
246
248
  }
247
249
 
248
250
  function ensureSafeAuth(command, scope, accessKey) {
249
- if (command === 'auth' && scope !== 'user') {
250
- throw new Error('`neus auth` only supports user scope so access keys never land in shared project config.');
251
+ if ((command === 'auth' || command === 'setup') && scope !== 'user') {
252
+ throw new Error('`neus ${command}` only supports user scope so access keys never land in shared project config.');
251
253
  }
252
254
  if (scope === 'project' && accessKey) {
253
255
  throw new Error('Access keys are only supported in user scope. Remove --project or omit --access-key.');
@@ -256,7 +258,7 @@ function ensureSafeAuth(command, scope, accessKey) {
256
258
 
257
259
  function buildCursorServer(accessKey) {
258
260
  return {
259
- type: 'streamableHttp',
261
+ type: 'http',
260
262
  url: NEUS_MCP_URL,
261
263
  ...(accessKey ? { headers: { Authorization: `Bearer ${accessKey}` } } : {}),
262
264
  };
@@ -548,6 +550,14 @@ function printResultSummary(command, scope, results, accessKey) {
548
550
  if (command === 'init' && !accessKey) {
549
551
  lines.push(`Account tools stay optional. Add personal auth later with: neus auth --access-key <npk_...>`);
550
552
  }
553
+ if (command === 'init' || command === 'setup') {
554
+ lines.push(
555
+ 'Claude Code (optional): plugin neus-mcp@neus + docs — https://docs.neus.network/mcp/claude-code-marketplace',
556
+ );
557
+ lines.push(
558
+ 'Cursor / VS Code: same command when those apps are detected (local MCP config) — https://docs.neus.network/mcp/setup',
559
+ );
560
+ }
551
561
  if ((command === 'init' || command === 'auth') && accessKey) {
552
562
  lines.push('Personal account tools are enabled where the client supports user-scope auth setup.');
553
563
  }
@@ -659,6 +669,97 @@ function runStatus(options) {
659
669
  printResultSummary('status', scope, inspected, '');
660
670
  }
661
671
 
672
+ function runSetup(options) {
673
+ const scope = resolveScope(options);
674
+ ensureSafeAuth('setup', scope, options.accessKey);
675
+ const cwd = process.cwd();
676
+ if (options.project && options.accessKey) {
677
+ throw new Error('Access keys are only supported in user scope. Remove --project or omit --access-key.');
678
+ }
679
+
680
+ const clients = resolveClients(scope, options.clients);
681
+ ensureClientSelection(scope, clients);
682
+
683
+ const initResults = runClientOperations(
684
+ clients,
685
+ scope,
686
+ cwd,
687
+ options.dryRun,
688
+ (client) => installClient(client, scope, options.accessKey, options.dryRun, cwd),
689
+ );
690
+
691
+ const payload = {
692
+ command: 'setup',
693
+ scope,
694
+ detectedClients: defaultUserClients(),
695
+ clients,
696
+ accessKeyConfigured: Boolean(options.accessKey),
697
+ results: initResults,
698
+ hasErrors: initResults.some((result) => result.error),
699
+ };
700
+
701
+ if (options.json) {
702
+ printJson(payload);
703
+ } else {
704
+ printResultSummary('setup', scope, initResults, options.accessKey);
705
+ }
706
+
707
+ if (payload.hasErrors) {
708
+ process.exitCode = 1;
709
+ }
710
+ }
711
+
712
+ function runDoctor(options) {
713
+ const scope = resolveScope(options);
714
+ const cwd = process.cwd();
715
+ const clients = resolveClients(scope, options.clients);
716
+ ensureClientSelection(scope, clients);
717
+
718
+ const inspected = runClientOperations(clients, scope, cwd, options.dryRun, (client) =>
719
+ inspectClient(client, scope, cwd),
720
+ );
721
+ const configuredClients = inspected.filter((r) => r.configured);
722
+ const payload = {
723
+ command: 'doctor',
724
+ scope,
725
+ clients: inspected,
726
+ configuredCount: configuredClients.length,
727
+ accessKeyPresent: Boolean(options.accessKey),
728
+ profileConnectable: false,
729
+ agentVerified: false,
730
+ summary: '',
731
+ hasErrors: inspected.some((result) => result.error),
732
+ };
733
+
734
+ if (options.json) {
735
+ printJson(payload);
736
+ return;
737
+ }
738
+
739
+ printResultSummary('doctor', scope, inspected, '');
740
+
741
+ const lines = [];
742
+ if (configuredClients.length > 0) {
743
+ lines.push(`MCP reachable: ${configuredClients.map((r) => r.client).join(', ')} ready at ${NEUS_MCP_URL}.`);
744
+ } else {
745
+ lines.push('MCP reachable: No clients configured. Run `neus setup` or `neus init` first.');
746
+ process.stdout.write(`\n${lines.join('\n')}\n`);
747
+ process.exit(1);
748
+ }
749
+
750
+ if (options.accessKey) {
751
+ lines.push('Profile connection: auth header present. Connect to the MCP endpoint and run `neus_me` to confirm.');
752
+ } else {
753
+ lines.push(`Profile connection: No access key found. Run \`neus auth --access-key <npk_...>\` (create one at ${NEUS_ACCESS_KEYS_URL}) and reconnect.`);
754
+ }
755
+
756
+ lines.push('Agent verification: Run `neus_agent_link` and `neus_proofs_check` inside the MCP-connected client to verify agent identity and delegation proofs.');
757
+ lines.push('');
758
+ lines.push('Next: Open your editor/IDE, connect to the NEUS MCP endpoint, and run `neus_context`.');
759
+
760
+ process.stdout.write(`\n${lines.join('\n')}\n`);
761
+ }
762
+
662
763
  function main() {
663
764
  try {
664
765
  const { command, options } = parseArgs(process.argv.slice(2));
@@ -679,6 +780,14 @@ function main() {
679
780
  runStatus(options);
680
781
  return;
681
782
  }
783
+ if (command === 'setup') {
784
+ runSetup(options);
785
+ return;
786
+ }
787
+ if (command === 'doctor') {
788
+ runDoctor(options);
789
+ return;
790
+ }
682
791
 
683
792
  process.stderr.write(`Unknown subcommand: ${command}\n`);
684
793
  printUsage(1);
package/client.js CHANGED
@@ -48,6 +48,13 @@ function normalizeBrowserSignerString(raw) {
48
48
  return null;
49
49
  }
50
50
 
51
+ /** Treats SDK placeholder signatures as absent so the protocol can use session or app-link delegation. */
52
+ function isPlaceholderNeusSignature(signature) {
53
+ const s = typeof signature === 'string' ? signature.trim() : '';
54
+ if (!s) return true;
55
+ return /^0x0+$/i.test(s);
56
+ }
57
+
51
58
  const validateVerifierData = (verifierId, data) => {
52
59
  if (!data || typeof data !== 'object') {
53
60
  return { valid: false, error: 'Data object is required' };
@@ -450,10 +457,12 @@ export class NeusClient {
450
457
  throw new ConfigurationError('Invalid wallet provider');
451
458
  }
452
459
 
453
- _getDefaultBrowserWallet() {
454
- if (typeof window === 'undefined') return null;
455
- return window.ethereum || window.solana || (window.phantom && window.phantom.solana) || null;
456
- }
460
+ _getDefaultBrowserWallet() {
461
+ if (typeof window === 'undefined') return null;
462
+ // Legacy convenience fallback only. Non-EVM wallets must be passed explicitly
463
+ // with CAIP-2 chain context so the SDK does not route them through EVM RPC.
464
+ return window.ethereum || null;
465
+ }
457
466
 
458
467
  async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
459
468
  const providerWallet = wallet || this._getDefaultBrowserWallet();
@@ -595,8 +604,74 @@ export class NeusClient {
595
604
  };
596
605
  }
597
606
 
607
+ async verifyFromApp(params) {
608
+ if (!params || typeof params !== 'object') {
609
+ throw new ValidationError('verifyFromApp requires a params object');
610
+ }
611
+
612
+ const {
613
+ user,
614
+ verifier = 'ownership-basic',
615
+ content,
616
+ data: explicitData = null,
617
+ options = {}
618
+ } = params;
619
+
620
+ if (!user || typeof user !== 'object') {
621
+ throw new ValidationError('verifyFromApp requires user object');
622
+ }
623
+
624
+ const walletAddress =
625
+ user.walletAddress ||
626
+ user.address ||
627
+ user.identity;
628
+ if (!walletAddress || typeof walletAddress !== 'string') {
629
+ throw new ValidationError('verifyFromApp requires user.walletAddress');
630
+ }
631
+
632
+ const delegationQHash =
633
+ this.config.appLinkQHash ||
634
+ (typeof process !== 'undefined' && process.env && process.env.NEUS_APP_LINK_QHASH) ||
635
+ null;
636
+
637
+ let data;
638
+ if (explicitData && typeof explicitData === 'object') {
639
+ data = { owner: walletAddress, ...explicitData };
640
+ } else if (content && typeof content === 'object') {
641
+ data = {
642
+ owner: walletAddress,
643
+ content: JSON.stringify(content),
644
+ contentType: typeof content.contentType === 'string' ? content.contentType : 'application/json',
645
+ reference: content.reference || { type: 'other' }
646
+ };
647
+ } else if (typeof content === 'string') {
648
+ data = {
649
+ owner: walletAddress,
650
+ content,
651
+ reference: { type: 'other' }
652
+ };
653
+ } else {
654
+ data = { owner: walletAddress, reference: { type: 'other' } };
655
+ }
656
+
657
+ const signedTimestamp = Date.now();
658
+ const verifierIds = [verifier];
659
+
660
+ return this.verify({
661
+ verifierIds,
662
+ data,
663
+ walletAddress,
664
+ signedTimestamp,
665
+ ...(delegationQHash ? { delegationQHash } : {}),
666
+ options
667
+ });
668
+ }
669
+
598
670
  async verify(params) {
599
- if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
671
+ if (
672
+ (isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) &&
673
+ (params?.verifier || params?.content || params?.data)
674
+ ) {
600
675
  const { content, verifier = 'ownership-basic', data = null, wallet = null, options = {} } = params;
601
676
 
602
677
  if (verifier === 'ownership-basic' && !data && (!content || typeof content !== 'string')) {
@@ -657,10 +732,10 @@ export class NeusClient {
657
732
  walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
658
733
  }
659
734
  }
660
- } else {
661
- if (typeof window === 'undefined' || !window.ethereum) {
662
- throw new ConfigurationError('No Web3 wallet detected. Please install MetaMask or provide wallet parameter.');
663
- }
735
+ } else {
736
+ if (typeof window === 'undefined' || !window.ethereum) {
737
+ throw new ConfigurationError('No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.');
738
+ }
664
739
  await window.ethereum.request({ method: 'eth_requestAccounts' });
665
740
  provider = window.ethereum;
666
741
  const accounts = await provider.request({ method: 'eth_accounts' });
@@ -962,14 +1037,17 @@ export class NeusClient {
962
1037
  verifierIds,
963
1038
  data,
964
1039
  walletAddress,
965
- signature,
1040
+ signature: rawSignature,
966
1041
  signedTimestamp,
967
1042
  chainId,
968
1043
  chain,
969
1044
  signatureMethod,
1045
+ delegationQHash,
970
1046
  options = {}
971
1047
  } = params;
972
1048
 
1049
+ const signature = isPlaceholderNeusSignature(rawSignature) ? undefined : rawSignature;
1050
+
973
1051
  const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
974
1052
 
975
1053
  const normalizeVerifierId = (id) => {
@@ -988,8 +1066,12 @@ export class NeusClient {
988
1066
  if (!walletAddress || typeof walletAddress !== 'string') {
989
1067
  throw new ValidationError('walletAddress is required');
990
1068
  }
991
- if (!signature) {
992
- throw new ValidationError('signature is required');
1069
+ const hasAppAttribution =
1070
+ typeof this.config.appId === 'string' && this.config.appId.trim().length > 0;
1071
+ if (!signature && !delegationQHash && !hasAppAttribution) {
1072
+ throw new ValidationError(
1073
+ 'signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required'
1074
+ );
993
1075
  }
994
1076
  if (!signedTimestamp || typeof signedTimestamp !== 'number') {
995
1077
  throw new ValidationError('signedTimestamp is required');
@@ -1019,11 +1101,12 @@ export class NeusClient {
1019
1101
  verifierIds: normalizedVerifierIds,
1020
1102
  data,
1021
1103
  walletAddress,
1022
- signature,
1104
+ ...(signature ? { signature } : {}),
1023
1105
  signedTimestamp,
1024
1106
  ...(resolvedChainId !== null && { chainId: resolvedChainId }),
1025
1107
  ...(chain && { chain }),
1026
1108
  ...(signatureMethod && { signatureMethod }),
1109
+ ...(delegationQHash && { delegationQHash }),
1027
1110
  options: optionsPayload
1028
1111
  };
1029
1112
 
package/errors.js CHANGED
@@ -79,7 +79,7 @@ export class NetworkError extends SDKError {
79
79
  super(message, code);
80
80
  this.name = 'NetworkError';
81
81
  this.originalError = originalError;
82
- this.isRetryable = true; // Network errors are typically retryable
82
+ this.isRetryable = true; // Network errors are retryable
83
83
  }
84
84
 
85
85
  static isNetworkError(error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@neus/sdk",
3
- "version": "1.0.9",
4
- "description": "Issue and check NEUS trust receipts across apps, agents, gates, payments, and workflows.",
3
+ "version": "1.0.12",
4
+ "description": "NEUS makes trust portable across the internet — so people, apps, and AI agents can prove what is real before access, payout, or execution.",
5
5
  "bin": {
6
6
  "neus": "cli/neus.mjs"
7
7
  },
package/types.d.ts CHANGED
@@ -7,7 +7,9 @@ declare module '@neus/sdk' {
7
7
 
8
8
  export class NeusClient {
9
9
  constructor(config?: NeusClientConfig);
10
-
10
+
11
+ verifyFromApp(params: VerifyFromAppParams): Promise<VerificationResult>;
12
+
11
13
  verify(params: VerifyParams): Promise<VerificationResult>;
12
14
 
13
15
  getProof(qHash: string): Promise<StatusResult>;
@@ -57,6 +59,8 @@ declare module '@neus/sdk' {
57
59
  apiUrl?: string;
58
60
  apiKey?: string;
59
61
  appId?: string;
62
+ /** Optional: only when using legacy `delegationQHash` on verify requests. App-linked servers rely on `appId` + Origin + stored delegation. */
63
+ appLinkQHash?: string;
60
64
  paymentSignature?: string;
61
65
  extraHeaders?: Record<string, string>;
62
66
  timeout?: number;
@@ -92,6 +96,18 @@ declare module '@neus/sdk' {
92
96
  meta?: Record<string, any>;
93
97
  }
94
98
 
99
+ export interface VerifyFromAppParams {
100
+ user: {
101
+ walletAddress?: string;
102
+ address?: string;
103
+ identity?: string;
104
+ };
105
+ verifier?: VerifierId;
106
+ content?: string | Record<string, any>;
107
+ data?: VerificationData;
108
+ options?: VerifyOptions;
109
+ }
110
+
95
111
  export interface VerifyParams {
96
112
  verifier?: VerifierId;
97
113
  content?: string;
@@ -105,6 +121,7 @@ declare module '@neus/sdk' {
105
121
  chainId?: number;
106
122
  chain?: string;
107
123
  signatureMethod?: string;
124
+ delegationQHash?: string;
108
125
  wallet?: WalletLike;
109
126
  }
110
127
 
package/widgets/README.md CHANGED
@@ -10,7 +10,7 @@ npm install @neus/sdk react react-dom
10
10
 
11
11
  ## VerifyGate
12
12
 
13
- Create mode defaults to **private**. Override `proofOptions` only when you intentionally need public visibility for link-based or public checks.
13
+ Create mode defaults to **private**. Override `proofOptions` only when you need public visibility for link-based or public checks.
14
14
 
15
15
  ```jsx
16
16
  import { VerifyGate } from '@neus/sdk/widgets';
@@ -307,7 +307,7 @@ function VerifyGate({
307
307
  return provider.address;
308
308
  }
309
309
  if (typeof provider.request !== "function") {
310
- throw new Error("No wallet provider available");
310
+ throw new Error("Connect a wallet and try again.");
311
311
  }
312
312
  let accounts = await provider.request({ method: "eth_accounts" });
313
313
  if (!accounts || accounts.length === 0) {
@@ -315,7 +315,7 @@ function VerifyGate({
315
315
  accounts = await provider.request({ method: "eth_accounts" });
316
316
  }
317
317
  if (!accounts || accounts.length === 0) {
318
- throw new Error("No wallet accounts available");
318
+ throw new Error("Connect a wallet and try again.");
319
319
  }
320
320
  return accounts[0];
321
321
  }, [wallet]);
@@ -388,7 +388,7 @@ function VerifyGate({
388
388
  }, [client, buildGateRequirements, wallet, inferChainFromAddress, signatureMethod]);
389
389
  const launchHostedCheckout = useCallback(async () => {
390
390
  if (typeof window === "undefined") {
391
- throw new Error("Hosted checkout is only available in browser environments");
391
+ throw new Error("Open this in a browser to verify.");
392
392
  }
393
393
  const origin = window.location.origin;
394
394
  const returnUrl = window.location.href;
@@ -446,7 +446,7 @@ function VerifyGate({
446
446
  completed = true;
447
447
  cleanup();
448
448
  if (payload?.eligible === false) {
449
- reject(new Error("Hosted checkout completed but eligibility was not satisfied."));
449
+ reject(new Error("Verification could not be completed."));
450
450
  return;
451
451
  }
452
452
  resolve(payload);
@@ -573,7 +573,7 @@ function VerifyGate({
573
573
  const buildDataForVerifier = (verifierId) => {
574
574
  if (!CREATABLE_VERIFIERS.has(verifierId)) {
575
575
  throw new Error(
576
- `${verifierId} cannot be created via the wallet flow. It requires hosted checkout or a server integration.`
576
+ "This check requires the hosted verifier."
577
577
  );
578
578
  }
579
579
  const explicit = verifierData && verifierData[verifierId];
@@ -587,12 +587,12 @@ function VerifyGate({
587
587
  if (verifierId === "wallet-link") {
588
588
  if (!explicit?.secondaryWalletAddress || !explicit?.signature || !explicit?.chain || !explicit?.signatureMethod) {
589
589
  throw new Error(
590
- "wallet-link direct mode requires verifierData: { secondaryWalletAddress, signature, chain, signatureMethod }. For user-facing flows, prefer hosted checkout."
590
+ "Missing required wallet details."
591
591
  );
592
592
  }
593
593
  return explicit;
594
594
  }
595
- throw new Error(`${verifierId} requires explicit verifierData`);
595
+ throw new Error("Missing required verification details.");
596
596
  };
597
597
  const verifyOne = async (verifierId) => {
598
598
  const dataForVerifier = buildDataForVerifier(verifierId);
@@ -616,7 +616,7 @@ function VerifyGate({
616
616
  const verifiedVerifiers = final?.data?.verifiedVerifiers || [];
617
617
  const verifierResult = verifiedVerifiers.find((v) => v.verifierId === verifierId);
618
618
  if (!verifierResult || verifierResult.verified !== true) {
619
- throw new Error(`Verification failed for ${verifierId}`);
619
+ throw new Error("Verification could not be completed.");
620
620
  }
621
621
  const hubTx = final?.data?.hubTransaction || {};
622
622
  const crosschain = final?.data?.crosschain || {};
@@ -849,7 +849,7 @@ function VerifyGate({
849
849
  textUnderlineOffset: "2px",
850
850
  opacity: disabled || isProcessing ? 0.6 : 0.9
851
851
  },
852
- children: "Already verified? Sign to reuse existing proofs."
852
+ children: "Already verified? Reuse your proof."
853
853
  }
854
854
  ),
855
855
  error && /* @__PURE__ */ jsx("div", { style: {