@kushbitx/sdk 0.1.3 → 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 +3 -3
- package/kushbitx.d.ts +3 -3
- package/kushbitx.mjs +27 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
161
|
+
try {
|
|
162
|
+
return report.transactionDigest.toLowerCase() === await preflightDigest(transaction);
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
144
166
|
}
|