@keeperhub/wallet 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/dist/index.js CHANGED
@@ -146,34 +146,6 @@ function fund(walletAddress) {
146
146
  };
147
147
  }
148
148
 
149
- // src/hmac.ts
150
- import { createHash, createHmac } from "crypto";
151
- function computeSignature(secret, method, path, subOrgId, body, timestamp) {
152
- const bodyDigest = createHash("sha256").update(body).digest("hex");
153
- const signingString = `${method}
154
- ${path}
155
- ${subOrgId}
156
- ${bodyDigest}
157
- ${timestamp}`;
158
- return createHmac("sha256", secret).update(signingString).digest("hex");
159
- }
160
- function buildHmacHeaders(secret, method, path, subOrgId, body) {
161
- const timestamp = String(Math.floor(Date.now() / 1e3));
162
- const signature = computeSignature(
163
- secret,
164
- method,
165
- path,
166
- subOrgId,
167
- body,
168
- timestamp
169
- );
170
- return {
171
- "X-KH-Sub-Org": subOrgId,
172
- "X-KH-Timestamp": timestamp,
173
- "X-KH-Signature": signature
174
- };
175
- }
176
-
177
149
  // src/skill-install.ts
178
150
  import { chmod, copyFile, mkdir, readFile, writeFile } from "fs/promises";
179
151
  import { dirname as dirname2, join as join2 } from "path";
@@ -385,47 +357,6 @@ async function cmdAdd(opts = {}) {
385
357
  process.stdout.write(`config written to ${getWalletConfigPath()}
386
358
  `);
387
359
  }
388
- async function cmdLink(opts = {}) {
389
- const wallet = await readWalletConfig();
390
- const baseUrl = resolveBaseUrl(opts.baseUrl);
391
- const sessionCookie = process.env.KH_SESSION_COOKIE;
392
- if (!sessionCookie) {
393
- process.stderr.write(
394
- "[keeperhub-wallet] link requires KH_SESSION_COOKIE env var.\nSign in at app.keeperhub.com, copy the session cookie, and re-run with:\n KH_SESSION_COOKIE='<cookie>' npx @keeperhub/wallet link\n"
395
- );
396
- process.exit(1);
397
- }
398
- const body = JSON.stringify({ subOrgId: wallet.subOrgId });
399
- const headers = buildHmacHeaders(
400
- wallet.hmacSecret,
401
- "POST",
402
- "/api/agentic-wallet/link",
403
- wallet.subOrgId,
404
- body
405
- );
406
- const response = await fetch(`${baseUrl}/api/agentic-wallet/link`, {
407
- method: "POST",
408
- headers: {
409
- ...headers,
410
- "content-type": "application/json",
411
- cookie: sessionCookie
412
- },
413
- body
414
- });
415
- const json = await response.json().catch(() => ({}));
416
- if (!response.ok) {
417
- process.stderr.write(
418
- `[keeperhub-wallet] link failed: ${json.code ?? response.status}: ${json.error ?? ""}
419
- `
420
- );
421
- process.exit(1);
422
- }
423
- if (json.already) {
424
- process.stdout.write("already linked\n");
425
- return;
426
- }
427
- process.stdout.write("linked\n");
428
- }
429
360
  async function cmdFund() {
430
361
  const wallet = await readWalletConfig();
431
362
  const out = fund(wallet.walletAddress);
@@ -459,11 +390,6 @@ async function runCli(argv = process.argv) {
459
390
  program.command("add").description("Provision a new agentic wallet (no account required)").option("--base-url <url>", "KeeperHub API base URL").action(async (opts) => {
460
391
  await cmdAdd(opts);
461
392
  });
462
- program.command("link").description(
463
- "Link the current wallet to your KeeperHub account (requires KH_SESSION_COOKIE env)"
464
- ).option("--base-url <url>", "KeeperHub API base URL").action(async (opts) => {
465
- await cmdLink(opts);
466
- });
467
393
  program.command("fund").description(
468
394
  "Print Coinbase Onramp URL (Base USDC) and Tempo deposit address"
469
395
  ).action(async () => {
@@ -524,6 +450,34 @@ async function runCli(argv = process.argv) {
524
450
  }
525
451
  }
526
452
 
453
+ // src/hmac.ts
454
+ import { createHash, createHmac } from "crypto";
455
+ function computeSignature(secret, method, path, subOrgId, body, timestamp) {
456
+ const bodyDigest = createHash("sha256").update(body).digest("hex");
457
+ const signingString = `${method}
458
+ ${path}
459
+ ${subOrgId}
460
+ ${bodyDigest}
461
+ ${timestamp}`;
462
+ return createHmac("sha256", secret).update(signingString).digest("hex");
463
+ }
464
+ function buildHmacHeaders(secret, method, path, subOrgId, body) {
465
+ const timestamp = String(Math.floor(Date.now() / 1e3));
466
+ const signature = computeSignature(
467
+ secret,
468
+ method,
469
+ path,
470
+ subOrgId,
471
+ body,
472
+ timestamp
473
+ );
474
+ return {
475
+ "X-KH-Sub-Org": subOrgId,
476
+ "X-KH-Timestamp": timestamp,
477
+ "X-KH-Signature": signature
478
+ };
479
+ }
480
+
527
481
  // src/client.ts
528
482
  var TRAILING_SLASH2 = /\/$/;
529
483
  function defaultCodeForStatus(status) {
@@ -676,8 +630,6 @@ function getSafetyConfigPath() {
676
630
  }
677
631
 
678
632
  // src/hook.ts
679
- var DEFAULT_POLL = { intervalMs: 2e3, maxAttempts: 150 };
680
- var APPROVAL_URL_BASE = "https://app.keeperhub.com/approve/";
681
633
  var USDC_DECIMALS2 = 1e6;
682
634
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
683
635
  var MICRO_USDC_RE = /^\d+$/;
@@ -728,16 +680,6 @@ function usdToMicro(usd) {
728
680
  async function createPreToolUseHook(options = {}) {
729
681
  const toolMatcher = options.toolNameMatcher ?? defaultToolMatcher;
730
682
  const configLoader = options.configLoader ?? loadSafetyConfig;
731
- const walletLoader = options.walletLoader ?? readWalletConfig;
732
- const clientFactory = options.clientFactory ?? ((w) => new KeeperHubClient(w));
733
- const onAskOpen = options.onAskOpen ?? ((url) => {
734
- process.stderr.write(
735
- `
736
- [keeperhub-wallet] Approval required. Visit: ${url}
737
- `
738
- );
739
- });
740
- const poll = options.poll ?? DEFAULT_POLL;
741
683
  const safety = await configLoader();
742
684
  return async (raw) => {
743
685
  const hookInput = raw ?? {};
@@ -753,43 +695,10 @@ async function createPreToolUseHook(options = {}) {
753
695
  return { decision: "deny", reason: "AMOUNT_UNDETERMINED" };
754
696
  }
755
697
  const blockMicro = usdToMicro(safety.block_threshold_usd);
756
- const askMicro = usdToMicro(safety.ask_threshold_usd);
757
698
  const autoMicro = usdToMicro(safety.auto_approve_max_usd);
758
699
  if (amountMicro > blockMicro) {
759
700
  return { decision: "deny", reason: "BLOCKED_BY_SAFETY_RULE" };
760
701
  }
761
- if (amountMicro >= askMicro) {
762
- const wallet = await walletLoader();
763
- const client = clientFactory(wallet);
764
- const created = await client.request("POST", "/api/agentic-wallet/approval-request", {
765
- // Server contract (Phase 33): riskLevel MUST be 'ask' or 'block';
766
- // operationPayload MUST be a non-array object. The hook only creates
767
- // approval-requests at the ask tier (block tier short-circuits above).
768
- riskLevel: "ask",
769
- operationPayload: {
770
- amountMicroUsdc: amountMicro.toString(),
771
- contractAddress: contractAddr ?? "",
772
- toolName: hookInput.tool_name ?? "",
773
- reason: `Agent tool ${hookInput.tool_name}`
774
- }
775
- });
776
- const approvalId = "_status" in created ? created.approvalRequestId : created.id;
777
- onAskOpen(`${APPROVAL_URL_BASE}${approvalId}`);
778
- for (let attempt = 0; attempt < poll.maxAttempts; attempt++) {
779
- await new Promise((r) => setTimeout(r, poll.intervalMs));
780
- const status = await client.request("GET", `/api/agentic-wallet/approval-request/${approvalId}`);
781
- if (!("status" in status)) {
782
- continue;
783
- }
784
- if (status.status === "approved") {
785
- return { decision: "allow" };
786
- }
787
- if (status.status === "rejected") {
788
- return { decision: "deny", reason: "USER_REJECTED" };
789
- }
790
- }
791
- return { decision: "deny", reason: "APPROVAL_TIMEOUT" };
792
- }
793
702
  if (amountMicro <= autoMicro) {
794
703
  return { decision: "allow" };
795
704
  }
@@ -846,6 +755,19 @@ function parseMppChallenge(response) {
846
755
  // src/payment-signer.ts
847
756
  import { randomBytes } from "crypto";
848
757
 
758
+ // src/workflow-slug.ts
759
+ var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
760
+ function extractKeeperHubWorkflowSlug(url) {
761
+ if (!url || url.length === 0) {
762
+ return { ok: false, reason: "EMPTY_URL" };
763
+ }
764
+ const match = KEEPERHUB_WORKFLOW_RE.exec(url);
765
+ if (!match || !match[1]) {
766
+ return { ok: false, reason: "URL_PATTERN_MISMATCH" };
767
+ }
768
+ return { ok: true, slug: match[1] };
769
+ }
770
+
849
771
  // src/x402-detect.ts
850
772
  function isX402Shape(value) {
851
773
  if (typeof value !== "object" || value === null) {
@@ -944,9 +866,17 @@ function createPaymentSigner(opts = {}) {
944
866
  return result.signature;
945
867
  }
946
868
  async function payViaMpp(response, mpp, wallet) {
869
+ const slug = extractKeeperHubWorkflowSlug(response.url);
870
+ if (!slug.ok) {
871
+ throw new KeeperHubError(
872
+ "UNSUPPORTED_RECIPIENT",
873
+ `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`
874
+ );
875
+ }
947
876
  const client = clientFactory(wallet);
948
877
  const signature = await signOrPoll(client, {
949
878
  chain: "tempo",
879
+ workflowSlug: slug.slug,
950
880
  paymentChallenge: {
951
881
  kind: "mpp",
952
882
  serialized: mpp.serialized,
@@ -966,6 +896,13 @@ function createPaymentSigner(opts = {}) {
966
896
  "x402 challenge has no accepts entries"
967
897
  );
968
898
  }
899
+ const slug = extractKeeperHubWorkflowSlug(x402.resource.url || response.url);
900
+ if (!slug.ok) {
901
+ throw new KeeperHubError(
902
+ "UNSUPPORTED_RECIPIENT",
903
+ `This wallet only signs payments for KeeperHub workflows. The 402 came from a URL that does not match /api/mcp/workflows/<slug>/call (reason: ${slug.reason}). See KEEP-311 for generic x402 support.`
904
+ );
905
+ }
969
906
  const now = Math.floor(Date.now() / 1e3);
970
907
  const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
971
908
  const validBefore = now + accept.maxTimeoutSeconds;
@@ -973,6 +910,7 @@ function createPaymentSigner(opts = {}) {
973
910
  const client = clientFactory(wallet);
974
911
  const signature = await signOrPoll(client, {
975
912
  chain: "base",
913
+ workflowSlug: slug.slug,
976
914
  paymentChallenge: {
977
915
  kind: "x402",
978
916
  payTo: accept.payTo,