clutch-hub-sdk-js 2.0.1 → 3.0.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## [3.0.0](https://github.com/clutchprotocol/clutch-hub-sdk-js/compare/v2.0.1...v3.0.0) (2026-07-30)
2
+
3
+
4
+ ### ⚠ BREAKING CHANGES
5
+
6
+ * verifying an unsigned transaction now requires a chainId
7
+ pinned via the ClutchHubSdk constructor or expected.chainId.
8
+
9
+ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
10
+ * signTransaction's hash preimage and signed payload
11
+ both gained chain_id (inserted after nonce; everything after it shifts
12
+ by one index). fare/amount/balance public types moved from number to
13
+ bigint; the corresponding GraphQL mutation variables changed from
14
+ Int to String. buildAuthChallengeMessage/authChallengeHashHex/
15
+ signAuthChallenge gained a required leading chainId parameter and the
16
+ auth challenge string format changed — no fallback to the old
17
+ two-field format. Requires clutch-node treasury-break and a hub-api
18
+ build with chainInfo/createUnsignedBurn. The orchestrator REST client
19
+ described in the task brief was deliberately not built: it targets a
20
+ payment-orchestrator service that does not exist yet.
21
+
22
+ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
23
+
24
+ ### Features
25
+
26
+ * v3 wire format — chain_id in signing, bigint amounts, tx verification, Burn ([b677894](https://github.com/clutchprotocol/clutch-hub-sdk-js/commit/b677894d92cd39a9444bec93b09226d775145dfd))
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * fail closed when verifying an unsigned tx with no pinned chainId ([353a5f2](https://github.com/clutchprotocol/clutch-hub-sdk-js/commit/353a5f278bb2192cdfa4dbed9c4d850cd1a06042))
32
+
1
33
  ## [2.0.1](https://github.com/clutchprotocol/clutch-hub-sdk-js/compare/v2.0.0...v2.0.1) (2026-07-24)
2
34
 
3
35
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './types';
2
- export * from './sdk';
3
- export { hubGraphqlWsUrl, RIDE_REQUEST_GQL_FIELDS, RIDE_OFFER_GQL_FIELDS, ACTIVE_TRIP_GQL_FIELDS, RECENT_TRIP_GQL_FIELDS, createHubSubscriptionClient, } from './subscriptions';
4
- export type { SubscriptionHandlers } from './subscriptions';
1
+ export * from './types.js';
2
+ export * from './sdk.js';
3
+ export { hubGraphqlWsUrl, RIDE_REQUEST_GQL_FIELDS, RIDE_OFFER_GQL_FIELDS, ACTIVE_TRIP_GQL_FIELDS, RECENT_TRIP_GQL_FIELDS, createHubSubscriptionClient, } from './subscriptions.js';
4
+ export type { SubscriptionHandlers } from './subscriptions.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export * from './types';
2
- export * from './sdk';
3
- export { hubGraphqlWsUrl, RIDE_REQUEST_GQL_FIELDS, RIDE_OFFER_GQL_FIELDS, ACTIVE_TRIP_GQL_FIELDS, RECENT_TRIP_GQL_FIELDS, createHubSubscriptionClient, } from './subscriptions';
1
+ export * from './types.js';
2
+ export * from './sdk.js';
3
+ export { hubGraphqlWsUrl, RIDE_REQUEST_GQL_FIELDS, RIDE_OFFER_GQL_FIELDS, ACTIVE_TRIP_GQL_FIELDS, RECENT_TRIP_GQL_FIELDS, createHubSubscriptionClient, } from './subscriptions.js';
package/dist/sdk.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'buffer';
2
- import { type SubscriptionHandlers } from './subscriptions';
3
- import { AvailableRideRequest, AvailableRideOffer, AvailableActiveTrip, AvailableCompletedTrip, AvailableRecentTrip, FaucetResponse, MapBounds, RideRequestArgs, RideOfferArgs, RideAcceptanceArgs, RidePayArgs, RideCancelArgs, RideRequestCancelArgs, Signature } from './types';
2
+ import { type SubscriptionHandlers } from './subscriptions.js';
3
+ import { AvailableRideRequest, AvailableRideOffer, AvailableActiveTrip, AvailableCompletedTrip, AvailableRecentTrip, BurnArgs, FaucetResponse, MapBounds, RideRequestArgs, RideOfferArgs, RideAcceptanceArgs, RidePayArgs, RideCancelArgs, RideRequestCancelArgs, Signature } from './types.js';
4
4
  /** Strip 0x/0X prefix - hex parsers (e.g. @noble/secp256k1) do not accept it. Exported for consumers. */
5
5
  export declare function stripHexPrefix(hex: string): string;
6
6
  /**
@@ -17,21 +17,23 @@ declare global {
17
17
  export declare const AUTH_CHALLENGE_PREFIX = "clutch-auth";
18
18
  /**
19
19
  * Canonical auth challenge message for `generateToken`. Must match clutch-hub-api
20
- * (`hub::auth::build_auth_challenge_message`) byte-for-byte: the exact `publicKey` string
21
- * sent as the mutation argument and the timestamp in decimal unix seconds.
20
+ * (`hub::auth::build_auth_challenge_message`) byte-for-byte: `clutch-auth:{chainId}:{publicKey}:{timestamp}`.
21
+ * `chainId` binds the signed challenge to this hub's chain — without it, a challenge captured
22
+ * on one chain would authenticate the same key on any other Clutch hub within the clock-skew
23
+ * window. Breaking change from the pre-treasury (chainId-less) format; no fallback.
22
24
  */
23
- export declare function buildAuthChallengeMessage(publicKey: string, timestamp: number): string;
25
+ export declare function buildAuthChallengeMessage(chainId: number, publicKey: string, timestamp: number): string;
24
26
  /**
25
27
  * Keccak-256 of the canonical auth message as 64-char lowercase hex (no 0x).
26
28
  * The signature is then computed over the UTF-8 bytes of this hex string (see `signHashHex`),
27
29
  * the same convention used for transaction hashes.
28
30
  */
29
- export declare function authChallengeHashHex(publicKey: string, timestamp: number): string;
31
+ export declare function authChallengeHashHex(chainId: number, publicKey: string, timestamp: number): string;
30
32
  /**
31
33
  * Sign the `generateToken` proof-of-key-ownership challenge.
32
34
  * @param timestamp Unix seconds; the Hub API rejects timestamps more than ±120s from server time.
33
35
  */
34
- export declare function signAuthChallenge(publicKey: string, timestamp: number, privateKey: string): Promise<Signature>;
36
+ export declare function signAuthChallenge(chainId: number, publicKey: string, timestamp: number, privateKey: string): Promise<Signature>;
35
37
  /**
36
38
  * Represents an unsigned transaction returned by the GraphQL API.
37
39
  */
@@ -39,7 +41,58 @@ export interface UnsignedTransaction {
39
41
  data: any;
40
42
  from: string;
41
43
  nonce: number;
44
+ /** u64 on the wire; kept as `number` here since real chain ids fit well under 2^53. */
45
+ chain_id: number;
42
46
  }
47
+ /**
48
+ * Expectations `signTransaction` verifies an unsigned blob against before signing it — see
49
+ * `verifyUnsignedTransaction`. The hub is untrusted in this design (that's the entire point of
50
+ * client-side signing), so a caller who knows what it asked for should say so and have the SDK
51
+ * check the hub's answer instead of signing it blind.
52
+ */
53
+ export interface ExpectedTx {
54
+ type: 'RideRequest' | 'RideOffer' | 'RidePay' | 'RideAcceptance' | 'RideCancel' | 'RideRequestCancel' | 'Burn';
55
+ /** The wallet's own address/pk form; `signTransaction` fills this in automatically. */
56
+ from?: string;
57
+ /** Pinned CLIENT-side (app config, e.g. 2077) — never sourced from the hub's own `chainInfo`. */
58
+ chainId?: number;
59
+ /** RideRequest/RideOffer/RidePay. */
60
+ fare?: bigint;
61
+ /** Burn. */
62
+ amount?: bigint;
63
+ /** The acceptance/offer/request hash the caller itself passed in. */
64
+ refTxHash?: string;
65
+ /** Burn. */
66
+ redemptionRef?: string;
67
+ }
68
+ /**
69
+ * Result of a passing `verifyUnsignedTransaction` check.
70
+ */
71
+ export interface VerifiedTx {
72
+ /**
73
+ * The referrer the hub injected into this transaction, if any. The hub picks the referrer
74
+ * server-side and there is currently no signed-quote flow to pin it client-side, so this
75
+ * value CANNOT be verified — it is surfaced only so a caller can display it to the user
76
+ * before they sign. Displaying it is the interim mitigation, not a fix; full referrer
77
+ * pinning needs the signed-quote flow (a later plan).
78
+ */
79
+ referrer: string | null;
80
+ }
81
+ /**
82
+ * Verify an unsigned-transaction blob from the hub against what the caller actually asked for,
83
+ * before it gets signed. Pure and side-effect-free.
84
+ *
85
+ * WHY THIS EXISTS: the hub is the untrusted party in this design — the private key never
86
+ * leaves the client precisely because the hub is not trusted — yet without this check a
87
+ * compromised hub can alter the fare, swap the referrer, or hand back a different chain's id,
88
+ * and the SDK would sign whatever it was given. This closes the blind-signing hole for every
89
+ * field it's possible to pin client-side. It does NOT close the referrer hole (see
90
+ * `VerifiedTx.referrer`).
91
+ *
92
+ * Any mismatch throws `Error('unsigned tx does not match request: <field>')` naming the
93
+ * offending field.
94
+ */
95
+ export declare function verifyUnsignedTransaction(unsignedTx: UnsignedTransaction, expected: ExpectedTx): VerifiedTx;
43
96
  /**
44
97
  * SDK for interacting with the Clutch Hub API and signing transactions.
45
98
  * Provides client-side transaction signing and blockchain interaction capabilities.
@@ -49,14 +102,24 @@ export declare class ClutchHubSdk {
49
102
  private publicKey;
50
103
  private token;
51
104
  private tokenExpireTime;
105
+ private chainId;
106
+ /** Whether the caller actually passed a `chainId` (vs. the 0 default) — see `signTransaction`. */
107
+ private chainIdConfigured;
52
108
  /**
53
109
  * @param apiUrl Hub API base URL.
54
110
  * @param publicKey Wallet address (0x + 40 hex) or uncompressed public key (130 hex).
55
111
  * @param privateKey Optional wallet private key, required to obtain JWTs: `generateToken`
56
112
  * demands a signed proof-of-key-ownership challenge. May also be provided later via
57
113
  * {@link setPrivateKey}. Never sent to the API — only used for local signing.
114
+ * @param chainId This chain's id (e.g. 2077 for the app's own config), used for the
115
+ * chain-bound auth challenge and as the default `expected.chainId` pin in
116
+ * {@link signTransaction}'s `verifyUnsignedTransaction` check. Get this from app config,
117
+ * never from the hub's own `chainInfo` response — asking the untrusted party what chain
118
+ * it is defeats the check chain_id exists to provide. If omitted, `signTransaction` still
119
+ * verifies every other `expected` field but skips the chain_id pin (nothing was pinned to
120
+ * check against) rather than failing every real transaction against a phantom "chain 0".
58
121
  */
59
- constructor(apiUrl: string, publicKey: string, privateKey?: string);
122
+ constructor(apiUrl: string, publicKey: string, privateKey?: string, chainId?: number);
60
123
  /**
61
124
  * Get the current public key associated with this SDK instance.
62
125
  * @returns The public key string
@@ -90,6 +153,12 @@ export declare class ClutchHubSdk {
90
153
  private subscribeGraphqlListField;
91
154
  private executeGraphQL;
92
155
  private ensureAuth;
156
+ /**
157
+ * Public wrapper around `ensureAuth`: resolves (fetching if needed) a valid JWT for this
158
+ * wallet and returns it as an `Authorization: Bearer <token>` header ready to attach to a
159
+ * hand-rolled request (e.g. an orchestrator REST client that reuses this SDK's auth).
160
+ */
161
+ getAuthHeaders(): Promise<Record<string, string>>;
93
162
  /**
94
163
  * Fetches an unsigned ride request transaction from the GraphQL API.
95
164
  */
@@ -118,10 +187,15 @@ export declare class ClutchHubSdk {
118
187
  * Only the passenger who created the request can cancel.
119
188
  */
120
189
  createUnsignedRideRequestCancel(args: RideRequestCancelArgs): Promise<UnsignedTransaction>;
190
+ /**
191
+ * Fetches an unsigned Burn transaction. Burns `amount` CLT from the caller's balance,
192
+ * optionally tagged with a treasury `redemptionRef` (hex(keccak256(intent_id))).
193
+ */
194
+ createUnsignedBurn(args: BurnArgs): Promise<UnsignedTransaction>;
121
195
  /**
122
196
  * Signs a transaction and returns the signature and raw RLP-encoded payload.
123
197
  */
124
- signTransaction(unsignedTx: UnsignedTransaction, privateKey: string): Promise<Signature & {
198
+ signTransaction(unsignedTx: UnsignedTransaction, privateKey: string, expected?: ExpectedTx): Promise<Signature & {
125
199
  rawTransaction: string;
126
200
  txHash: string;
127
201
  }>;
@@ -193,14 +267,14 @@ export declare class ClutchHubSdk {
193
267
  /**
194
268
  * Fetches the current account balance for a public key.
195
269
  */
196
- getAccountBalance(publicKey?: string): Promise<number>;
270
+ getAccountBalance(publicKey?: string): Promise<bigint>;
197
271
  /**
198
272
  * Subscribes to periodic `accountBalance` updates over WebSocket.
199
273
  * Returns a dispose function to stop the subscription.
200
274
  */
201
275
  subscribeAccountBalance(options: {
202
276
  publicKey?: string;
203
- } | undefined, handlers: SubscriptionHandlers<number>): () => void;
277
+ } | undefined, handlers: SubscriptionHandlers<bigint>): () => void;
204
278
  /**
205
279
  * Request test CLT from the Hub API faucet (POST /faucet). Requires `faucet_enabled` and a funded
206
280
  * `faucet_private_key` on the server. No GraphQL auth token required for this HTTP endpoint.
@@ -218,3 +292,10 @@ export declare class ClutchHubSdk {
218
292
  private static readonly floatView;
219
293
  private float64ToUint64;
220
294
  }
295
+ /**
296
+ * Formats CLT base units (micro-USD, at the 1 USD = 1,000,000 CLT peg) as a `$`-prefixed
297
+ * decimal string for display — integer math only, never floats, since a float division would
298
+ * reintroduce the precision loss bigint amounts exist to avoid. Cents are floored (truncated),
299
+ * matching how the treasury peg treats CLT as an integer.
300
+ */
301
+ export declare function formatUsd(microUsd: bigint): string;