@ritkey/sdk 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,58 @@
1
+ /**
2
+ * Webhook verification helper for receivers.
3
+ *
4
+ * Use this on YOUR endpoint that receives webhook deliveries from Ritkey:
5
+ *
6
+ * import { verifyWebhook } from '@ritkey/sdk';
7
+ *
8
+ * app.post('/ritkey-hook', async (req, res) => {
9
+ * const rawBody = await readRawBody(req);
10
+ * const result = verifyWebhook(rawBody, req.headers['ritkey-signature'], MY_SECRET);
11
+ * if (!result.ok) return res.status(401).end(result.reason);
12
+ * // result.event is now a typed RitkeyEvent
13
+ * handleEvent(result.event);
14
+ * res.status(200).end();
15
+ * });
16
+ *
17
+ * IMPORTANT: rawBody must be the EXACT bytes Ritkey sent. If your framework
18
+ * parses JSON before you see the body, your HMAC will not match. Use a raw
19
+ * body parser for the webhook route.
20
+ */
21
+ import type { RitkeyEvent, EventType } from '../types.js';
22
+ export interface VerifyOptions {
23
+ /**
24
+ * Reject signatures older than this many seconds. Defaults to 300 (5 min).
25
+ * Set to 0 to disable timestamp check (NOT recommended).
26
+ */
27
+ toleranceSeconds?: number;
28
+ /** Override "now" for tests. */
29
+ nowSeconds?: number;
30
+ }
31
+ export type VerifyResult<T = unknown> = {
32
+ ok: true;
33
+ event: RitkeyEvent<T>;
34
+ timestamp: number;
35
+ } | {
36
+ ok: false;
37
+ reason: string;
38
+ };
39
+ /**
40
+ * Verify a Ritkey webhook delivery.
41
+ *
42
+ * @param rawBody The exact request body bytes (string or Buffer).
43
+ * @param sigHeader The value of the `Ritkey-Signature` header. May be undefined / string[] / string.
44
+ * @param secret Your subscription's `whsec_...` secret.
45
+ * @param opts Optional tolerance / clock override.
46
+ */
47
+ export declare function verifyWebhook<T = unknown>(rawBody: string | Uint8Array, sigHeader: string | string[] | undefined, secret: string, opts?: VerifyOptions): VerifyResult<T>;
48
+ /**
49
+ * Narrow a verified event to a specific type tag.
50
+ *
51
+ * if (isEvent(result.event, 'tx.sent')) {
52
+ * // result.event.data is now typed
53
+ * }
54
+ */
55
+ export declare function isEvent<E extends EventType>(event: RitkeyEvent, type: E): event is RitkeyEvent & {
56
+ type: E;
57
+ };
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/verify/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAIlC;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,EACvC,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EACxC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,aAAkB,GACvB,YAAY,CAAC,CAAC,CAAC,CA8DjB;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,SAAS,EACzC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,CAAC,GACN,KAAK,IAAI,WAAW,GAAG;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAEpC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Webhook verification helper for receivers.
3
+ *
4
+ * Use this on YOUR endpoint that receives webhook deliveries from Ritkey:
5
+ *
6
+ * import { verifyWebhook } from '@ritkey/sdk';
7
+ *
8
+ * app.post('/ritkey-hook', async (req, res) => {
9
+ * const rawBody = await readRawBody(req);
10
+ * const result = verifyWebhook(rawBody, req.headers['ritkey-signature'], MY_SECRET);
11
+ * if (!result.ok) return res.status(401).end(result.reason);
12
+ * // result.event is now a typed RitkeyEvent
13
+ * handleEvent(result.event);
14
+ * res.status(200).end();
15
+ * });
16
+ *
17
+ * IMPORTANT: rawBody must be the EXACT bytes Ritkey sent. If your framework
18
+ * parses JSON before you see the body, your HMAC will not match. Use a raw
19
+ * body parser for the webhook route.
20
+ */
21
+ import { createHmac, timingSafeEqual } from 'node:crypto';
22
+ const DEFAULT_TOLERANCE_SECONDS = 300;
23
+ /**
24
+ * Verify a Ritkey webhook delivery.
25
+ *
26
+ * @param rawBody The exact request body bytes (string or Buffer).
27
+ * @param sigHeader The value of the `Ritkey-Signature` header. May be undefined / string[] / string.
28
+ * @param secret Your subscription's `whsec_...` secret.
29
+ * @param opts Optional tolerance / clock override.
30
+ */
31
+ export function verifyWebhook(rawBody, sigHeader, secret, opts = {}) {
32
+ if (!secret)
33
+ return { ok: false, reason: 'missing secret' };
34
+ const header = Array.isArray(sigHeader) ? sigHeader[0] : sigHeader;
35
+ if (!header)
36
+ return { ok: false, reason: 'missing Ritkey-Signature header' };
37
+ // Parse `t=<ts>,v1=<hex>` — order-independent.
38
+ let tStr;
39
+ let v1Str;
40
+ for (const part of header.split(',')) {
41
+ const eq = part.indexOf('=');
42
+ if (eq < 0)
43
+ continue;
44
+ const k = part.slice(0, eq).trim();
45
+ const v = part.slice(eq + 1).trim();
46
+ if (k === 't')
47
+ tStr = v;
48
+ else if (k === 'v1')
49
+ v1Str = v;
50
+ }
51
+ if (!tStr || !v1Str)
52
+ return { ok: false, reason: 'malformed Ritkey-Signature header' };
53
+ const timestamp = parseInt(tStr, 10);
54
+ if (!Number.isFinite(timestamp)) {
55
+ return { ok: false, reason: 'invalid timestamp in signature' };
56
+ }
57
+ // Timestamp tolerance check (replay protection).
58
+ const tolerance = opts.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
59
+ if (tolerance > 0) {
60
+ const now = opts.nowSeconds ?? Math.floor(Date.now() / 1000);
61
+ if (Math.abs(now - timestamp) > tolerance) {
62
+ return { ok: false, reason: 'signature timestamp outside tolerance window' };
63
+ }
64
+ }
65
+ // Compute expected HMAC over `<t>.<rawBody>`.
66
+ const bodyStr = typeof rawBody === 'string' ? rawBody : Buffer.from(rawBody).toString('utf8');
67
+ const signedPayload = `${tStr}.${bodyStr}`;
68
+ const expected = createHmac('sha256', secret).update(signedPayload).digest('hex');
69
+ // Timing-safe compare. Lengths must match before timingSafeEqual.
70
+ const expBuf = Buffer.from(expected, 'hex');
71
+ let provBuf;
72
+ try {
73
+ provBuf = Buffer.from(v1Str, 'hex');
74
+ }
75
+ catch {
76
+ return { ok: false, reason: 'invalid signature encoding' };
77
+ }
78
+ if (expBuf.length !== provBuf.length) {
79
+ return { ok: false, reason: 'signature mismatch' };
80
+ }
81
+ if (!timingSafeEqual(expBuf, provBuf)) {
82
+ return { ok: false, reason: 'signature mismatch' };
83
+ }
84
+ // Parse the event payload.
85
+ let event;
86
+ try {
87
+ event = JSON.parse(bodyStr);
88
+ }
89
+ catch {
90
+ return { ok: false, reason: 'body is not valid JSON' };
91
+ }
92
+ return { ok: true, event, timestamp };
93
+ }
94
+ /**
95
+ * Narrow a verified event to a specific type tag.
96
+ *
97
+ * if (isEvent(result.event, 'tx.sent')) {
98
+ * // result.event.data is now typed
99
+ * }
100
+ */
101
+ export function isEvent(event, type) {
102
+ return event.type === type;
103
+ }
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/verify/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@ritkey/sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for Ritkey — wallet operations, webhooks, and event subscriptions",
5
+ "license": "MIT",
6
+ "author": "Chiefmmorgs <chiefmmorgs@gmail.com>",
7
+ "homepage": "https://github.com/mmorgsmorgan/ritual-agentic-wallet-#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/mmorgsmorgan/ritual-agentic-wallet-.git",
11
+ "directory": "packages/sdk"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/mmorgsmorgan/ritual-agentic-wallet-/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "dev": "tsc --watch",
33
+ "test": "node --test test/*.test.mjs",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "keywords": [
43
+ "ritkey",
44
+ "ritual",
45
+ "wallet",
46
+ "sdk",
47
+ "mpc",
48
+ "webhooks",
49
+ "ai-agents",
50
+ "ethereum"
51
+ ],
52
+ "devDependencies": {
53
+ "@types/node": "^22.15.21",
54
+ "typescript": "^5.8.3"
55
+ }
56
+ }