@kushbitx/sdk 0.1.3 → 0.1.5

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
@@ -12,7 +12,7 @@ Zero-dependency ESM client and runnable Node.js example for the complete KushBit
12
12
  | Token-risk report | 0.001 USDC | Receive structured token-risk evidence with missing checks identified. |
13
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
+ | 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; Protected SpendGuard can cap what an agent pays if your signer enforces it, but does not detect prompt injection either. Service name `kushbitx-preflight`. |
16
16
 
17
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.
18
18
 
@@ -48,17 +48,17 @@ const { challenge } = await kushbitx.getPaymentChallenge('kushbitx-preflight', i
48
48
 
49
49
  ### Preflight 1.1: sign only the transaction that was checked
50
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`.
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
52
 
53
53
  ```js
54
54
  import { canSign } from '@kushbitx/sdk';
55
55
 
56
- const tx = { from: wallet, to, value: '0', data }; // the exact unsigned transaction
56
+ const tx = { chainId: 8453, from: wallet, to, value: '0', data }; // the exact unsigned transaction
57
57
  // ... pay for POST /api/preflight with your x402 client, get `report` ...
58
58
  if (await canSign(report, tx)) {
59
59
  // ALLOW, same digest, not expired: sign now
60
60
  } else {
61
- // REVIEW, BLOCK, changed transaction or expired: do not sign
61
+ // REVIEW, BLOCK, another network, changed transaction or expired: do not sign
62
62
  }
63
63
  ```
64
64
 
package/kushbitx.d.ts CHANGED
@@ -123,9 +123,9 @@ export interface PaymentChallenge {
123
123
  export interface RecoveryCredentials { id: string; key: string; [k: string]: unknown }
124
124
 
125
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>;
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
129
 
130
130
  export declare class KushBitxClient {
131
131
  constructor(options?: ClientOptions);
package/kushbitx.mjs CHANGED
@@ -129,16 +129,38 @@ export const paidServices = PAID_SERVICES;
129
129
  // Preflight 1.1: the digest the service returns in transactionDigest. Compute it from the transaction you are about
130
130
  // to sign; sign only when it matches an ALLOW verdict that has not expired.
131
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() });
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() });
134
149
  const subtle = globalThis.crypto?.subtle ?? (await import('node:crypto')).webcrypto.subtle;
135
150
  const hash = await subtle.digest('SHA-256', new TextEncoder().encode(canonical));
136
151
  return '0x' + Array.from(new Uint8Array(hash), b => b.toString(16).padStart(2, '0')).join('');
137
152
  }
138
153
 
139
- /** True only for an ALLOW verdict on this exact transaction, before expiresAt. Anything else: do not sign. */
154
+ /** True only for an ALLOW verdict on this exact Base transaction (chainId 8453, stated), before expiresAt. Anything else: do not sign. */
140
155
  export async function canSign(report, transaction, now = Date.now()) {
141
- if (!report || report.verdict !== 'ALLOW' || typeof report.transactionDigest !== 'string') return false;
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;
142
160
  if (!report.expiresAt || !(Date.parse(report.expiresAt) > now)) return false;
143
- return report.transactionDigest === await preflightDigest(transaction);
161
+ try {
162
+ return report.transactionDigest.toLowerCase() === await preflightDigest(transaction);
163
+ } catch {
164
+ return false;
165
+ }
144
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kushbitx/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Zero-dependency ESM client for KushBitx AgentProof services on Base",
5
5
  "type": "module",
6
6
  "exports": {