@keeperhub/wallet 0.1.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.
@@ -0,0 +1,328 @@
1
+ import * as viem from 'viem';
2
+ import { PublicClient } from 'viem';
3
+ export { runCli } from './cli.js';
4
+ export { runHookCli } from './hook-entrypoint.js';
5
+ export { base } from 'viem/chains';
6
+
7
+ type AgentTarget = {
8
+ agent: "claude-code" | "cursor" | "cline" | "windsurf" | "opencode";
9
+ skillsDir: string;
10
+ settingsFile: string;
11
+ hookSupport: "claude-code" | "notice";
12
+ };
13
+ declare function detectAgents(homeOverride?: string): AgentTarget[];
14
+
15
+ type WalletConfig = {
16
+ /** Turnkey sub-org ID returned by POST /api/agentic-wallet/provision */
17
+ subOrgId: string;
18
+ /** EVM-shared wallet address (same for Base chainId 8453 and Tempo chainId 4217) */
19
+ walletAddress: `0x${string}`;
20
+ /** 64-char lowercase hex HMAC secret, minted server-side at provision; never logged */
21
+ hmacSecret: string;
22
+ };
23
+ type HmacHeaders = {
24
+ "X-KH-Sub-Org": string;
25
+ "X-KH-Timestamp": string;
26
+ "X-KH-Signature": string;
27
+ };
28
+ type HookDecision = {
29
+ decision: "allow" | "deny" | "ask";
30
+ reason?: string;
31
+ };
32
+ declare class KeeperHubError extends Error {
33
+ readonly code: string;
34
+ constructor(code: string, message: string);
35
+ }
36
+ declare class WalletConfigMissingError extends Error {
37
+ constructor();
38
+ }
39
+
40
+ type ClientOptions = {
41
+ /** Defaults to process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com" */
42
+ baseUrl?: string;
43
+ /** Injected for tests; defaults to global fetch */
44
+ fetch?: typeof fetch;
45
+ };
46
+ /**
47
+ * 202 ask-tier envelope returned by /sign and /approval-request when the
48
+ * risk classifier routes a request to the ask queue. Callers poll
49
+ * `/api/agentic-wallet/approval-request/:id` until status !== "pending".
50
+ */
51
+ type AskTierResponse = {
52
+ _status: 202;
53
+ approvalRequestId: string;
54
+ };
55
+ /**
56
+ * HMAC-signed HTTP client for the KeeperHub agentic-wallet API surface.
57
+ * Every request to /api/agentic-wallet/* (except /provision, which uses
58
+ * the session cookie) flows through this class.
59
+ *
60
+ * @security No logging of headers, body, or response bodies. Any stdout
61
+ * emitter (the global console object or util.inspect) added to this
62
+ * file is a T-34-08 violation (grep-enforced in CI).
63
+ */
64
+ declare class KeeperHubClient {
65
+ private readonly baseUrl;
66
+ private readonly fetchImpl;
67
+ private readonly wallet;
68
+ constructor(wallet: WalletConfig, opts?: ClientOptions);
69
+ /**
70
+ * HMAC-signed POST/GET to any /api/agentic-wallet/* route except
71
+ * /provision. Path MUST start with a leading slash. Body is
72
+ * JSON.stringify'd (or the empty string for GET).
73
+ *
74
+ * Error mapping: non-2xx/non-202 surface as `KeeperHubError(code,
75
+ * message)` where `code` is the server-supplied field or the default
76
+ * taxonomy (`HMAC_INVALID`, `POLICY_BLOCKED`, `NOT_FOUND`,
77
+ * `TURNKEY_UPSTREAM`, `HTTP_<status>`). 202 ask-tier surfaces as an
78
+ * AskTierResponse envelope.
79
+ */
80
+ request<T>(method: "GET" | "POST", path: string, body?: unknown): Promise<T | AskTierResponse>;
81
+ }
82
+
83
+ type BalanceSnapshot = {
84
+ base: {
85
+ chain: "base";
86
+ token: "USDC";
87
+ amount: string;
88
+ address: `0x${string}`;
89
+ };
90
+ tempo: {
91
+ chain: "tempo";
92
+ token: "USDC.e";
93
+ amount: string;
94
+ address: `0x${string}`;
95
+ };
96
+ offChainCredit: {
97
+ amount: string;
98
+ currency: "USD";
99
+ };
100
+ };
101
+ type CheckBalanceOptions = {
102
+ /** Injectable viem client for Base (tests mock readContract). */
103
+ baseClient?: PublicClient;
104
+ /** Injectable viem client for Tempo (tests mock readContract). */
105
+ tempoClient?: PublicClient;
106
+ /** Injectable KeeperHubClient (tests inject a mocked fetch). */
107
+ khClient?: KeeperHubClient;
108
+ };
109
+ /**
110
+ * Read the wallet's balance across Base + Tempo + off-chain KeeperHub credit
111
+ * in parallel. All three legs must resolve; any single failure rejects the
112
+ * Promise.
113
+ *
114
+ * Amounts are formatted as decimal strings (6-decimal USDC precision) so the
115
+ * caller can render them without BigInt math.
116
+ */
117
+ declare function checkBalance(wallet: WalletConfig, opts?: CheckBalanceOptions): Promise<BalanceSnapshot>;
118
+
119
+ declare const tempo: {
120
+ blockExplorers: {
121
+ readonly default: {
122
+ readonly name: "Tempo Explorer";
123
+ readonly url: "https://explorer.tempo.xyz";
124
+ };
125
+ };
126
+ blockTime?: number | undefined | undefined;
127
+ contracts?: {
128
+ [x: string]: viem.ChainContract | {
129
+ [sourceId: number]: viem.ChainContract | undefined;
130
+ } | undefined;
131
+ ensRegistry?: viem.ChainContract | undefined;
132
+ ensUniversalResolver?: viem.ChainContract | undefined;
133
+ multicall3?: viem.ChainContract | undefined;
134
+ erc6492Verifier?: viem.ChainContract | undefined;
135
+ } | undefined;
136
+ ensTlds?: readonly string[] | undefined;
137
+ id: 4217;
138
+ name: "Tempo";
139
+ nativeCurrency: {
140
+ readonly decimals: 18;
141
+ readonly name: "Ether";
142
+ readonly symbol: "ETH";
143
+ };
144
+ experimental_preconfirmationTime?: number | undefined | undefined;
145
+ rpcUrls: {
146
+ readonly default: {
147
+ readonly http: readonly [string];
148
+ };
149
+ };
150
+ sourceId?: number | undefined | undefined;
151
+ testnet?: boolean | undefined | undefined;
152
+ custom?: Record<string, unknown> | undefined;
153
+ extendSchema?: Record<string, unknown> | undefined;
154
+ fees?: viem.ChainFees<undefined> | undefined;
155
+ formatters?: undefined;
156
+ prepareTransactionRequest?: ((args: viem.PrepareTransactionRequestParameters, options: {
157
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
158
+ }) => Promise<viem.PrepareTransactionRequestParameters>) | [fn: ((args: viem.PrepareTransactionRequestParameters, options: {
159
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
160
+ }) => Promise<viem.PrepareTransactionRequestParameters>) | undefined, options: {
161
+ runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
162
+ }] | undefined;
163
+ serializers?: viem.ChainSerializers<undefined, viem.TransactionSerializable> | undefined;
164
+ verifyHash?: ((client: viem.Client, parameters: viem.VerifyHashActionParameters) => Promise<viem.VerifyHashActionReturnType>) | undefined;
165
+ };
166
+ /** Circle-issued USDC on Base mainnet. */
167
+ declare const BASE_USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
168
+ /** Bridged USDC (USDC.e) on Tempo mainnet. NOT the same contract as BASE_USDC. */
169
+ declare const TEMPO_USDC_E: "0x20c000000000000000000000b9537d11c60e8b50";
170
+
171
+ type FundInstructions = {
172
+ /** Coinbase Onramp deeplink (legacy query-param form). */
173
+ coinbaseOnrampUrl: string;
174
+ /** Tempo deposit address — same as the input wallet (EVM address shared). */
175
+ tempoAddress: `0x${string}`;
176
+ /** Plain-ASCII guidance string; no emojis (CLAUDE.md rule). */
177
+ disclaimer: string;
178
+ };
179
+ /**
180
+ * Build Coinbase Onramp URL + Tempo deposit address for the given wallet.
181
+ *
182
+ * No HTTP calls are performed. The caller is expected to either print the
183
+ * resulting URL (CLI) or render it in a chat bubble (skill). The returned
184
+ * `disclaimer` explains the Onramp deprecation + the Tempo external-transfer
185
+ * fallback in plain ASCII so terminal clients with ASCII-only fonts render
186
+ * identically to emoji-capable clients.
187
+ *
188
+ * @throws if `walletAddress` does not match /^0x[0-9a-fA-F]{40}$/.
189
+ */
190
+ declare function fund(walletAddress: string): FundInstructions;
191
+
192
+ /**
193
+ * Mirror of lib/agentic-wallet/hmac.ts::computeSignature.
194
+ * Format (byte-for-byte identical to server):
195
+ * `${method}\n${path}\n${subOrgId}\n${sha256_hex(body)}\n${timestamp}`
196
+ * Post-HI-05: subOrgId is a signed field.
197
+ *
198
+ * @security Do NOT log the secret or the returned signature. Any stdout
199
+ * emitter (the global console object or util.inspect) added to this
200
+ * file is a T-34-08 violation (grep-enforced).
201
+ */
202
+ declare function computeSignature(secret: string, method: string, path: string, subOrgId: string, body: string, timestamp: string): string;
203
+ /**
204
+ * Build the three X-KH-* headers that authenticate every request to
205
+ * /api/agentic-wallet/* (except /provision, which uses the session cookie).
206
+ *
207
+ * Timestamp is unix seconds (Math.floor(Date.now() / 1000)); the server
208
+ * enforces a symmetric 300-second replay window.
209
+ */
210
+ declare function buildHmacHeaders(secret: string, method: string, path: string, subOrgId: string, body: string): HmacHeaders;
211
+
212
+ /**
213
+ * User-owned safety config at ~/.keeperhub/safety.json. File mode 0o644 so the
214
+ * user can freely edit thresholds and the allowlist; server-side Turnkey policy
215
+ * remains the authoritative hard cap (GUARD-06).
216
+ */
217
+ type SafetyConfig = {
218
+ auto_approve_max_usd: number;
219
+ ask_threshold_usd: number;
220
+ block_threshold_usd: number;
221
+ allowlisted_contracts: string[];
222
+ };
223
+ /**
224
+ * Defaults per 34-CONTEXT lines 61-68. Thresholds bracket the Turnkey policy
225
+ * hard cap (100 USDC). Allowlisted contracts mirror the server Turnkey policy
226
+ * allowlist (lib/agentic-wallet/policy.ts FACILITATOR_ALLOWLIST) -- lowercased
227
+ * for case-insensitive match against tool_input.to / paymentChallenge.payTo.
228
+ */
229
+ declare const DEFAULT_SAFETY_CONFIG: SafetyConfig;
230
+ declare function loadSafetyConfig(): Promise<SafetyConfig>;
231
+ declare function validateAndMerge(partial: Partial<SafetyConfig>): SafetyConfig;
232
+ declare function getSafetyConfigPath(): string;
233
+
234
+ type CreateHookOptions = {
235
+ /** Match against tool_name. Default: /keeperhub|wallet|sign/i */
236
+ toolNameMatcher?: (name: string) => boolean;
237
+ /** Injected for tests */
238
+ walletLoader?: () => Promise<WalletConfig>;
239
+ /** Injected for tests */
240
+ configLoader?: () => Promise<SafetyConfig>;
241
+ /** Injected for tests */
242
+ clientFactory?: (w: WalletConfig) => KeeperHubClient;
243
+ /**
244
+ * Called when the ask tier opens an approval URL. Default: write to stderr
245
+ * (stdout is reserved for the Claude Code hook JSON output).
246
+ */
247
+ onAskOpen?: (url: string) => void;
248
+ /** Polling config for the ask tier */
249
+ poll?: {
250
+ intervalMs: number;
251
+ maxAttempts: number;
252
+ };
253
+ };
254
+ /**
255
+ * Factory returning the PreToolUse hook function. The hook enforces the three
256
+ * client-side safety tiers (auto / ask / block) sourced EXCLUSIVELY from
257
+ * ~/.keeperhub/safety.json -- never from the tool payload (GUARD-05).
258
+ */
259
+ declare function createPreToolUseHook(options?: CreateHookOptions): Promise<(input: unknown) => Promise<HookDecision>>;
260
+
261
+ type MppChallenge = {
262
+ serialized: string;
263
+ };
264
+ declare function parseMppChallenge(response: Response): MppChallenge | null;
265
+
266
+ type PaySignerOptions = {
267
+ /** Override wallet loader (primarily for tests). */
268
+ walletLoader?: () => Promise<WalletConfig>;
269
+ /** Override KeeperHubClient factory (tests inject a mocked fetch). */
270
+ clientFactory?: (wallet: WalletConfig) => KeeperHubClient;
271
+ /** Replayed fetch (tests intercept the retry). */
272
+ fetchImpl?: typeof fetch;
273
+ /** Approval polling override: interval + max attempts. */
274
+ approval?: {
275
+ intervalMs: number;
276
+ maxAttempts: number;
277
+ };
278
+ };
279
+ type PaymentSigner = {
280
+ pay: (response: Response) => Promise<Response>;
281
+ };
282
+ declare function createPaymentSigner(opts?: PaySignerOptions): PaymentSigner;
283
+ declare const paymentSigner: PaymentSigner;
284
+
285
+ type InstallResult = {
286
+ skillWrites: Array<{
287
+ agent: string;
288
+ path: string;
289
+ status: "written" | "skipped";
290
+ }>;
291
+ hookRegistrations: Array<{
292
+ agent: string;
293
+ status: "registered" | "notice" | "skipped";
294
+ message?: string;
295
+ }>;
296
+ };
297
+ type InstallOptions = {
298
+ homeOverride?: string;
299
+ skillSourcePath?: string;
300
+ onNotice?: (msg: string) => void;
301
+ };
302
+ declare function registerClaudeCodeHook(settingsPath: string): Promise<void>;
303
+ declare function installSkill(options?: InstallOptions): Promise<InstallResult>;
304
+
305
+ declare function readWalletConfig(): Promise<WalletConfig>;
306
+ declare function writeWalletConfig(config: WalletConfig): Promise<void>;
307
+ declare function getWalletConfigPath(): string;
308
+
309
+ type X402Challenge = {
310
+ x402Version: 2;
311
+ accepts: Array<{
312
+ scheme: "exact";
313
+ network: string;
314
+ asset: string;
315
+ amount: string;
316
+ payTo: string;
317
+ maxTimeoutSeconds: number;
318
+ extra: Record<string, unknown>;
319
+ }>;
320
+ resource: {
321
+ url: string;
322
+ description: string;
323
+ mimeType: string;
324
+ };
325
+ };
326
+ declare function parseX402Challenge(response: Response): Promise<X402Challenge | null>;
327
+
328
+ export { type AgentTarget, type AskTierResponse, BASE_USDC, type BalanceSnapshot, type CheckBalanceOptions, type ClientOptions, type CreateHookOptions, DEFAULT_SAFETY_CONFIG, type FundInstructions, type HmacHeaders, type HookDecision, type InstallOptions, type InstallResult, KeeperHubClient, KeeperHubError, type MppChallenge, type PaymentSigner, type SafetyConfig, TEMPO_USDC_E, type WalletConfig, WalletConfigMissingError, type X402Challenge, buildHmacHeaders, checkBalance, computeSignature, createPaymentSigner, createPreToolUseHook, detectAgents, fund, getSafetyConfigPath, getWalletConfigPath, installSkill, loadSafetyConfig, parseMppChallenge, parseX402Challenge, paymentSigner, readWalletConfig, registerClaudeCodeHook, tempo, validateAndMerge, writeWalletConfig };