@kushbitx/sdk 0.1.2 → 0.1.4

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.00098 USDC | Receive structured token-risk evidence with missing checks identified. |
13
- | Transaction preflight | 0.00098 USDC | Inspect an unsigned Base transaction before signing. |
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
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. `canSign` needs the transaction to state `chainId: 8453`; a missing or other chainId is never accepted. 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 = { chainId: 8453, 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, another network, 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:
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: { chainId?: number | string | bigint; from?: Address | null; to: Address; value?: string; data?: string }): Promise<`0x${string}`>;
127
+ /** True only for ALLOW on this exact Base transaction before expiresAt. chainId must be stated and be 8453; a missing or other chainId, or any changed field, is false. */
128
+ export declare function canSign(report: PreflightReport | Record<string, unknown>, transaction: { chainId: number | string | bigint; 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) {
@@ -123,3 +125,42 @@ export class KushBitxClient {
123
125
  }
124
126
 
125
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
+ // The transaction's own chainId goes into the digest (default 8453 when absent), so a report for Base never
133
+ // matches the same fields on another network.
134
+ const BASE_CHAIN_ID = 8453;
135
+ function chainIdOf(transaction) {
136
+ const raw = transaction?.chainId;
137
+ if (raw === undefined || raw === null) return BASE_CHAIN_ID;
138
+ if (typeof raw === 'bigint') return Number(raw);
139
+ if (typeof raw === 'string' && /^0x[0-9a-f]+$/i.test(raw)) return Number(BigInt(raw));
140
+ if (typeof raw === 'string' && /^[0-9]+$/.test(raw)) return Number(raw);
141
+ if (typeof raw === 'number' && Number.isInteger(raw)) return raw;
142
+ return NaN;
143
+ }
144
+ export async function preflightDigest(transaction) {
145
+ const { from = null, to, value = '0', data = '0x' } = transaction || {};
146
+ const chainId = chainIdOf(transaction);
147
+ if (!Number.isInteger(chainId)) throw new Error('chainId must be an integer');
148
+ const canonical = JSON.stringify({ chainId, from: from ? String(from).toLowerCase() : null, to: required(to, 'to').toLowerCase(), value: BigInt(value || '0').toString(), data: String(data || '0x').toLowerCase() });
149
+ const subtle = globalThis.crypto?.subtle ?? (await import('node:crypto')).webcrypto.subtle;
150
+ const hash = await subtle.digest('SHA-256', new TextEncoder().encode(canonical));
151
+ return '0x' + Array.from(new Uint8Array(hash), b => b.toString(16).padStart(2, '0')).join('');
152
+ }
153
+
154
+ /** True only for an ALLOW verdict on this exact Base transaction (chainId 8453, stated), before expiresAt. Anything else: do not sign. */
155
+ export async function canSign(report, transaction, now = Date.now()) {
156
+ if (!report || typeof report !== 'object' || report.verdict !== 'ALLOW' || typeof report.transactionDigest !== 'string') return false;
157
+ // The transaction must state chainId 8453; a missing chainId is never assumed to be Base.
158
+ if (!transaction || typeof transaction !== 'object' || transaction.chainId === undefined || transaction.chainId === null || chainIdOf(transaction) !== BASE_CHAIN_ID) return false;
159
+ if (report.chainId !== undefined && Number(report.chainId) !== BASE_CHAIN_ID) return false;
160
+ if (!report.expiresAt || !(Date.parse(report.expiresAt) > now)) return false;
161
+ try {
162
+ return report.transactionDigest.toLowerCase() === await preflightDigest(transaction);
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@kushbitx/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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,7 +24,9 @@
18
24
  "base",
19
25
  "usdc",
20
26
  "payment-security",
21
- "spendguard"
27
+ "spendguard",
28
+ "preflight",
29
+ "typescript"
22
30
  ],
23
31
  "homepage": "https://github.com/BHamim/kushbitx-sdk#readme",
24
32
  "repository": {
@@ -29,5 +37,6 @@
29
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
  }