@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/cli.cjs +0 -74
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +0 -74
- package/dist/cli.js.map +1 -1
- package/dist/hook-entrypoint.cjs +0 -185
- package/dist/hook-entrypoint.cjs.map +1 -1
- package/dist/hook-entrypoint.js +0 -185
- package/dist/hook-entrypoint.js.map +1 -1
- package/dist/index.cjs +57 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -15
- package/dist/index.d.ts +18 -15
- package/dist/index.js +57 -119
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skill/keeperhub-wallet.skill.md +25 -7
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,
|