@kushbitx/sdk 0.1.1 → 0.1.3

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
@@ -9,9 +9,10 @@ Zero-dependency ESM client and runnable Node.js example for the complete KushBit
9
9
  | Token market preview | Free | Inspect basic Base token market data without connecting a wallet. |
10
10
  | Data coverage check | Free | See which risk checks have enough evidence before buying a report. |
11
11
  | SpendGuard | Free pilot | Evaluate payment policy before money moves, with optional protected policies and human approval. |
12
- | Token-risk report | 0.25 USDC | Receive structured token-risk evidence with missing checks identified. |
13
- | Transaction preflight | 0.05 USDC | Inspect an unsigned Base transaction before signing. |
14
- | Payment verification | 0.01 USDC | Verify an existing Base USDC payment against the expected recipient and amount. |
12
+ | Token-risk report | 0.001 USDC | Receive structured token-risk evidence with missing checks identified. |
13
+ | Transaction preflight | 0.001 USDC | Inspect an unsigned Base transaction before signing. |
14
+ | Payment verification | 0.00288 USDC | Verify an existing Base USDC payment against the expected recipient and amount. |
15
+ | KushBitx Preflight | 0.005 USDC | One verdict (ALLOW, REVIEW, BLOCK) before money moves, fail-closed: transaction simulation, approval and transfer decoding, token and recipient screening, optional protected SpendGuard policy. Does not detect prompt injection (use Protected SpendGuard). Service name `kushbitx-preflight`. |
15
16
 
16
17
  Paid endpoints use x402 and require explicit payment. There is no subscription. A delivered report can be `INCOMPLETE` when evidence is unavailable and may still incur the displayed fee.
17
18
 
@@ -33,6 +34,38 @@ console.log(preview);
33
34
 
34
35
  The SDK never requests, stores, or signs with a private key. It can discover an x402 challenge, prepare recovery, submit an externally signed payment authorization, and recover a report.
35
36
 
37
+ ## TypeScript
38
+
39
+ Types ship with the package (`kushbitx.d.ts`), including `PreflightInput` and `PreflightReport`:
40
+
41
+ ```ts
42
+ import { KushBitxClient, type PreflightInput } from '@kushbitx/sdk';
43
+
44
+ const kushbitx = new KushBitxClient();
45
+ const input: PreflightInput = { chainId: 8453, to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', data: '0x', intent: 'contract_call' };
46
+ const { challenge } = await kushbitx.getPaymentChallenge('kushbitx-preflight', input); // HTTP 402, nothing charged
47
+ ```
48
+
49
+ ### Preflight 1.1: sign only the transaction that was checked
50
+
51
+ Send `from`; without it the verdict is REVIEW. Each response has `transactionDigest` and `expiresAt` (60 seconds). For swaps, add `expectedTokenOut`, and optionally `minAmountOut` and `expectedRecipient`.
52
+
53
+ ```js
54
+ import { canSign } from '@kushbitx/sdk';
55
+
56
+ const tx = { from: wallet, to, value: '0', data }; // the exact unsigned transaction
57
+ // ... pay for POST /api/preflight with your x402 client, get `report` ...
58
+ if (await canSign(report, tx)) {
59
+ // ALLOW, same digest, not expired: sign now
60
+ } else {
61
+ // REVIEW, BLOCK, changed transaction or expired: do not sign
62
+ }
63
+ ```
64
+
65
+ Required checks per transaction type: `requiredChecks` in https://kushbitx.com/api/services.
66
+
67
+ Current prices: https://kushbitx.com/api/services
68
+
36
69
  ## Runnable example
37
70
 
38
71
  Download `agent.mjs`, then run a free flow without installing packages:
@@ -62,11 +95,16 @@ import { KushBitxClient } from 'https://kushbitx.com/sdk/kushbitx.mjs';
62
95
  ## Links
63
96
 
64
97
  - Website: https://kushbitx.com
98
+ - Source: https://github.com/BHamim/kushbitx-sdk
65
99
  - Tools: https://kushbitx.com/#tools
66
100
  - SpendGuard: https://kushbitx.com/spendguard
67
101
  - Integration example: https://kushbitx.com/examples/agent.mjs
68
102
  - Contact: Admin@kushbitx.com
69
103
 
104
+ ## Community contributions
105
+
106
+ - [@mengxin10086](https://github.com/mengxin10086) built an independent MCP bridge for the free KushBitx endpoints. KushBitx reviewed and accepted commit `0e4d3226` ([source at that commit](https://github.com/mengxin10086/kushbitx-mcp-agent/tree/0e4d32267d4692d32cfb7c451d391cd59093a883)). It is third-party code, maintained by its author.
107
+
70
108
  ## Security
71
109
 
72
110
  Do not disclose suspected vulnerabilities in public issues. Report them privately to Admin@kushbitx.com.
package/kushbitx.d.ts ADDED
@@ -0,0 +1,158 @@
1
+ // Type declarations for @kushbitx/sdk 0.1.3. The SDK never requests, stores or signs with a private key.
2
+
3
+ /** Paid service names accepted by the payment helpers, and the route each one calls. */
4
+ export type PaidService =
5
+ | 'token-risk'
6
+ | 'preflight' | 'transaction-preflight'
7
+ | 'payment' | 'payment-proof' | 'verify-payment'
8
+ | 'kushbitx-preflight';
9
+
10
+ export declare const paidServices: Readonly<Record<PaidService, string>>;
11
+
12
+ export interface ClientOptions {
13
+ /** API origin. Default https://kushbitx.com (use https://staging.kushbitx.com for the Testnet sandbox). */
14
+ baseUrl?: string;
15
+ /** Fetch implementation. Default globalThis.fetch. */
16
+ fetchFn?: typeof fetch;
17
+ }
18
+
19
+ export type Address = `0x${string}`;
20
+ export type Verdict = 'ALLOW' | 'WARN' | 'BLOCK' | 'INCOMPLETE';
21
+
22
+ export interface TokenInput { chain: 'base'; address: Address }
23
+ export interface TransactionInput { chain: 'base'; to: Address; from?: Address; valueWei?: string; data?: `0x${string}` }
24
+ export interface PaymentProofInput { chain: 'base'; txHash: `0x${string}`; expectedTo: Address; expectedAmount: string; expectedFrom?: Address }
25
+
26
+ /** Input of KushBitx Preflight (POST /api/preflight, 0.005 USDC). */
27
+ export interface PreflightInput {
28
+ chainId: 8453;
29
+ to: Address;
30
+ /** Signing wallet. Required in practice: without it the verdict is REVIEW (sender_unknown). */
31
+ from?: Address;
32
+ /** Native ETH value in wei. */
33
+ value?: string;
34
+ data?: `0x${string}`;
35
+ intent?: 'swap' | 'transfer' | 'approve' | 'buy_token' | 'contract_call';
36
+ tokenAddress?: Address;
37
+ amount?: string;
38
+ /** Protected SpendGuard policy; send the agent key as Authorization: Bearer. Base USDC transfers only. */
39
+ policy?: { policyId: string; agentId: string; requestId: string };
40
+ /** Swap: who must receive the output. Default: from. */
41
+ expectedRecipient?: Address;
42
+ /** Swap: smallest acceptable output, in token units (for example "25.5"). */
43
+ minAmountOut?: string;
44
+ /** Swap: token the swap must deliver; 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native ETH. */
45
+ expectedTokenOut?: Address;
46
+ }
47
+
48
+ export type PreflightTransactionType = 'native_transfer' | 'erc20_transfer' | 'approval' | 'swap' | 'contract_call';
49
+
50
+ export interface PreflightFinding {
51
+ code: string;
52
+ severity: 'info' | 'low' | 'medium' | 'high' | 'critical';
53
+ kind: 'fact' | 'inference';
54
+ message: string;
55
+ source: string;
56
+ sourceTimestamp: string;
57
+ onchainBlock: number | null;
58
+ evidence?: Record<string, unknown>;
59
+ }
60
+
61
+ export interface PreflightReport {
62
+ /** "1.1" since 2026-10-31. */
63
+ reportVersion: string;
64
+ service: 'preflight';
65
+ generatedAt: string;
66
+ checkedAt: string;
67
+ /** checkedAt + 60 seconds. Do not sign after this time. */
68
+ expiresAt: string;
69
+ /** Compare with preflightDigest(transaction) before signing. */
70
+ transactionDigest: `0x${string}`;
71
+ transactionDigestMethod: string;
72
+ validity: string;
73
+ transactionType: PreflightTransactionType;
74
+ /** Checks this transaction type needs; a missing one gives REVIEW. */
75
+ requiredChecks: string[];
76
+ /** Simulated transfers and approvals (eth_simulateV1), and the swap output check. */
77
+ effects: { simulated: boolean; transfers?: Array<Record<string, unknown>>; approvals?: Array<Record<string, unknown>>; swap?: { expectedRecipient: Address; expectedTokenOut: Address; receivedAtomic: string; minAmountOutAtomic: string | null }; error?: string } | null;
78
+ input: Record<string, unknown>;
79
+ chainId: 8453;
80
+ onchainBlock: number | null;
81
+ verdict: 'ALLOW' | 'REVIEW' | 'BLOCK';
82
+ /** What Preflight does not check, such as prompt injection. */
83
+ notChecked: string[];
84
+ riskLevel: 'low' | 'medium' | 'high' | 'critical' | 'unknown';
85
+ reasonCodes: string[];
86
+ findings: PreflightFinding[];
87
+ checksPerformed: string[];
88
+ checksUnavailable: string[];
89
+ limitations: string[];
90
+ reports: Record<string, unknown>;
91
+ attribution?: { text: string; url: string; logo: string; scope: string };
92
+ }
93
+
94
+ export interface SpendPolicy {
95
+ maxPerTransaction: string;
96
+ remainingDailyBudget: string;
97
+ requireHumanAbove: string;
98
+ maxRepeats: number;
99
+ allowedRecipients: Address[];
100
+ blockUnknownRecipients: boolean;
101
+ }
102
+
103
+ export interface SpendInput {
104
+ agentId: string;
105
+ requestId: string;
106
+ chain: 'base';
107
+ asset: 'USDC';
108
+ recipient: Address;
109
+ amount: string;
110
+ policy: SpendPolicy;
111
+ context?: { repeatCount?: number; agentProofDecision?: 'ALLOW' | 'WARN' | 'BLOCK' | 'INCOMPLETE' | 'UNKNOWN' };
112
+ }
113
+
114
+ export interface PaymentChallenge {
115
+ service: PaidService;
116
+ path: string;
117
+ /** The 402 JSON body (x402Version, accepts, resource). */
118
+ challenge: Record<string, unknown>;
119
+ /** The base64 PAYMENT-REQUIRED header, for an x402 client. */
120
+ paymentRequired: string | null;
121
+ }
122
+
123
+ export interface RecoveryCredentials { id: string; key: string; [k: string]: unknown }
124
+
125
+ /** The digest KushBitx Preflight returns in transactionDigest, computed locally from the transaction you will sign. */
126
+ export declare function preflightDigest(transaction: { from?: Address | null; to: Address; value?: string; data?: string }): Promise<`0x${string}`>;
127
+ /** True only for ALLOW on this exact transaction before expiresAt. */
128
+ export declare function canSign(report: PreflightReport | Record<string, unknown>, transaction: { from?: Address | null; to: Address; value?: string; data?: string }, now?: number): Promise<boolean>;
129
+
130
+ export declare class KushBitxClient {
131
+ constructor(options?: ClientOptions);
132
+ baseUrl: string;
133
+ request(path: string, options?: { method?: string; body?: unknown; bearer?: string; headers?: Record<string, string> }): Promise<{ response: Response; data: unknown; parseError: unknown }>;
134
+ expect<T = Record<string, unknown>>(path: string, options?: { method?: string; body?: unknown; bearer?: string; headers?: Record<string, string> }, statuses?: number[]): Promise<T>;
135
+
136
+ /** Free market preview; no risk verdict. */
137
+ previewToken(address: Address): Promise<Record<string, unknown>>;
138
+ /** Free SpendGuard check. Advisory only: the caller supplies the policy. */
139
+ evaluateSpend(input: SpendInput): Promise<Record<string, unknown>>;
140
+ createSpendPolicy(policy: Record<string, unknown>): Promise<Record<string, unknown>>;
141
+ getSpendPolicy(policyId: string, adminKey: string): Promise<Record<string, unknown>>;
142
+ updateSpendPolicy(policyId: string, adminKey: string, policy: Record<string, unknown>): Promise<Record<string, unknown>>;
143
+ requestSpendDecision(input: Record<string, unknown>, agentKey: string): Promise<Record<string, unknown>>;
144
+ getSpendDecision(decisionId: string, key: string): Promise<Record<string, unknown>>;
145
+ approveSpendDecision(decisionId: string, adminKey: string): Promise<Record<string, unknown>>;
146
+
147
+ /** Ask for the x402 challenge (HTTP 402). Nothing is charged. */
148
+ getPaymentChallenge(service: 'kushbitx-preflight', input: PreflightInput): Promise<PaymentChallenge>;
149
+ getPaymentChallenge(service: 'token-risk', input: TokenInput): Promise<PaymentChallenge>;
150
+ getPaymentChallenge(service: 'preflight' | 'transaction-preflight', input: TransactionInput): Promise<PaymentChallenge>;
151
+ getPaymentChallenge(service: 'payment' | 'payment-proof' | 'verify-payment', input: PaymentProofInput): Promise<PaymentChallenge>;
152
+ /** Store recovery credentials before signing, so an interrupted call can be restored without paying again. */
153
+ prepareRecovery(service: PaidService, input: object): Promise<RecoveryCredentials>;
154
+ /** Send an externally signed payment (PAYMENT-SIGNATURE). Returns the report, or a pending status (HTTP 202). */
155
+ submitPaidCheck(service: 'kushbitx-preflight', input: PreflightInput, auth: { paymentSignature: string; reportId: string; recoveryKey: string }): Promise<PreflightReport | Record<string, unknown>>;
156
+ submitPaidCheck(service: PaidService, input: object, auth: { paymentSignature: string; reportId: string; recoveryKey: string }): Promise<Record<string, unknown>>;
157
+ restoreReport(args: { id: string; key: string; txHash?: string }): Promise<Record<string, unknown>>;
158
+ }
package/kushbitx.mjs CHANGED
@@ -4,7 +4,9 @@ const PAID_SERVICES = Object.freeze({
4
4
  'transaction-preflight': '/api/transaction-preflight',
5
5
  payment: '/api/verify-payment',
6
6
  'payment-proof': '/api/verify-payment',
7
- 'verify-payment': '/api/verify-payment'
7
+ 'verify-payment': '/api/verify-payment',
8
+ // KushBitx Preflight (0.005 USDC): one verdict before money moves. 'preflight' stays Transaction Preflight.
9
+ 'kushbitx-preflight': '/api/preflight'
8
10
  });
9
11
 
10
12
  function required(value, name) {
@@ -12,6 +14,17 @@ function required(value, name) {
12
14
  return value;
13
15
  }
14
16
 
17
+ function errorMessage(data) {
18
+ return data && typeof data === 'object' && typeof data.error === 'string' ? data.error : '';
19
+ }
20
+
21
+ // A success needs a JSON object body. null, arrays, scalars and invalid JSON are errors.
22
+ function jsonObject({ data, parseError }, status) {
23
+ if (parseError) throw new Error('KushBitx returned invalid JSON with HTTP ' + status, { cause: parseError });
24
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) throw new Error('KushBitx returned an empty or non-object JSON body with HTTP ' + status);
25
+ return data;
26
+ }
27
+
15
28
  export class KushBitxClient {
16
29
  constructor({ baseUrl = 'https://kushbitx.com', fetchFn = globalThis.fetch } = {}) {
17
30
  if (typeof fetchFn !== 'function') throw new Error('A fetch implementation is required');
@@ -29,14 +42,22 @@ export class KushBitxClient {
29
42
  },
30
43
  ...(body === undefined ? {} : { body: JSON.stringify(body) })
31
44
  });
32
- const data = await response.json().catch(() => ({}));
33
- return { response, data };
45
+ // An unreadable body stays visible as data=null; it is never replaced by an empty object.
46
+ let data = null;
47
+ let parseError = null;
48
+ try {
49
+ data = await response.json();
50
+ } catch (error) {
51
+ parseError = error;
52
+ }
53
+ return { response, data, parseError };
34
54
  }
35
55
 
36
56
  async expect(path, options, statuses = [200]) {
37
57
  const result = await this.request(path, options);
38
- if (!statuses.includes(result.response.status)) throw new Error(result.data.error || 'KushBitx request failed with HTTP ' + result.response.status);
39
- return result.data;
58
+ const { status } = result.response;
59
+ if (!statuses.includes(status)) throw new Error(errorMessage(result.data) || 'KushBitx request failed with HTTP ' + status);
60
+ return jsonObject(result, status);
40
61
  }
41
62
 
42
63
  previewToken(address) {
@@ -75,8 +96,8 @@ export class KushBitxClient {
75
96
  const path = PAID_SERVICES[service];
76
97
  if (!path) throw new Error('Unknown paid service');
77
98
  const result = await this.request(path, { body: input });
78
- if (result.response.status !== 402) throw new Error(result.data.error || 'Expected an HTTP 402 payment challenge');
79
- return { service, path, challenge: result.data, paymentRequired: result.response.headers.get('PAYMENT-REQUIRED') };
99
+ if (result.response.status !== 402) throw new Error(errorMessage(result.data) || 'Expected an HTTP 402 payment challenge');
100
+ return { service, path, challenge: jsonObject(result, 402), paymentRequired: result.response.headers.get('PAYMENT-REQUIRED') };
80
101
  }
81
102
 
82
103
  prepareRecovery(service, input) {
@@ -104,3 +125,20 @@ export class KushBitxClient {
104
125
  }
105
126
 
106
127
  export const paidServices = PAID_SERVICES;
128
+
129
+ // Preflight 1.1: the digest the service returns in transactionDigest. Compute it from the transaction you are about
130
+ // to sign; sign only when it matches an ALLOW verdict that has not expired.
131
+ // sha256 of JSON {chainId,from,to,value,data} in that key order; from, to and data lowercase; value a decimal wei string.
132
+ export async function preflightDigest({ from = null, to, value = '0', data = '0x' }) {
133
+ const canonical = JSON.stringify({ chainId: 8453, from: from ? String(from).toLowerCase() : null, to: required(to, 'to').toLowerCase(), value: BigInt(value || '0').toString(), data: String(data || '0x').toLowerCase() });
134
+ const subtle = globalThis.crypto?.subtle ?? (await import('node:crypto')).webcrypto.subtle;
135
+ const hash = await subtle.digest('SHA-256', new TextEncoder().encode(canonical));
136
+ return '0x' + Array.from(new Uint8Array(hash), b => b.toString(16).padStart(2, '0')).join('');
137
+ }
138
+
139
+ /** True only for an ALLOW verdict on this exact transaction, before expiresAt. Anything else: do not sign. */
140
+ export async function canSign(report, transaction, now = Date.now()) {
141
+ if (!report || report.verdict !== 'ALLOW' || typeof report.transactionDigest !== 'string') return false;
142
+ if (!report.expiresAt || !(Date.parse(report.expiresAt) > now)) return false;
143
+ return report.transactionDigest === await preflightDigest(transaction);
144
+ }
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@kushbitx/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Zero-dependency ESM client for KushBitx AgentProof services on Base",
5
5
  "type": "module",
6
- "exports": "./kushbitx.mjs",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./kushbitx.d.ts",
9
+ "import": "./kushbitx.mjs"
10
+ }
11
+ },
7
12
  "files": [
8
13
  "kushbitx.mjs",
14
+ "kushbitx.d.ts",
9
15
  "README.md",
10
16
  "LICENSE"
11
17
  ],
@@ -18,16 +24,19 @@
18
24
  "base",
19
25
  "usdc",
20
26
  "payment-security",
21
- "spendguard"
27
+ "spendguard",
28
+ "preflight",
29
+ "typescript"
22
30
  ],
23
- "homepage": "https://kushbitx.com/developers",
31
+ "homepage": "https://github.com/BHamim/kushbitx-sdk#readme",
24
32
  "repository": {
25
- "type": "url",
26
- "url": "https://kushbitx.com/developers"
33
+ "type": "git",
34
+ "url": "git+https://github.com/BHamim/kushbitx-sdk.git"
27
35
  },
28
36
  "bugs": {
29
- "url": "https://kushbitx.com/developers",
37
+ "url": "https://github.com/BHamim/kushbitx-sdk/issues",
30
38
  "email": "admin@kushbitx.com"
31
39
  },
32
- "license": "Apache-2.0"
40
+ "license": "Apache-2.0",
41
+ "types": "./kushbitx.d.ts"
33
42
  }