@dubsdotapp/node 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,70 @@
1
+ declare const DEFAULT_BASE_URL = "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1";
2
+ type DubsNetwork = 'devnet' | 'mainnet-beta';
3
+ declare const NETWORK_CONFIG: Record<DubsNetwork, {
4
+ baseUrl: string;
5
+ }>;
6
+
7
+ /** Configuration for the Dubs client */
8
+ interface DubsConfig {
9
+ /** Your Dubs API key (from the developer portal) */
10
+ apiKey: string;
11
+ /** Resolution secret for signing resolveGame requests (from the developer portal) */
12
+ resolutionSecret?: string;
13
+ /** Explicit base URL — overrides `network` if both provided */
14
+ baseUrl?: string;
15
+ /** Network shorthand — sets the base URL automatically */
16
+ network?: DubsNetwork;
17
+ }
18
+ /** Parameters for resolving a game */
19
+ interface ResolveGameParams {
20
+ /** Which side won: 'home', 'away', 'draw', or null for refund */
21
+ winner: 'home' | 'away' | 'draw' | null;
22
+ /** Optional metadata to attach to the resolution */
23
+ metadata?: Record<string, unknown>;
24
+ }
25
+ /** Result from resolving a game */
26
+ interface ResolveGameResult {
27
+ success: boolean;
28
+ gameId: string;
29
+ winner: string | null;
30
+ signature: string;
31
+ explorerUrl: string;
32
+ }
33
+ /** A parsed webhook event from Dubs */
34
+ interface WebhookEvent {
35
+ event: string;
36
+ timestamp: string;
37
+ data: Record<string, unknown>;
38
+ }
39
+
40
+ declare class Dubs {
41
+ private readonly apiKey;
42
+ private readonly resolutionSecret?;
43
+ private readonly baseUrl;
44
+ constructor(config: DubsConfig);
45
+ /**
46
+ * Resolve a custom game (game_mode=6).
47
+ *
48
+ * Automatically computes the HMAC-SHA256 signature using your resolution secret.
49
+ */
50
+ resolveGame(gameId: string, params: ResolveGameParams): Promise<ResolveGameResult>;
51
+ /**
52
+ * Verify an incoming Dubs webhook request.
53
+ *
54
+ * @param rawBody - The raw request body (string or Buffer)
55
+ * @param signature - The X-Dubs-Signature header value
56
+ * @param secret - Your webhook secret (from the developer portal)
57
+ * @returns The parsed webhook event
58
+ * @throws DubsApiError if the signature is invalid
59
+ */
60
+ static verifyWebhook(rawBody: string | Buffer, signature: string, secret: string): WebhookEvent;
61
+ private request;
62
+ }
63
+
64
+ declare class DubsApiError extends Error {
65
+ readonly code: string;
66
+ readonly httpStatus: number;
67
+ constructor(code: string, message: string, httpStatus: number);
68
+ }
69
+
70
+ export { DEFAULT_BASE_URL, Dubs, DubsApiError, type DubsConfig, type DubsNetwork, NETWORK_CONFIG, type ResolveGameParams, type ResolveGameResult, type WebhookEvent };
@@ -0,0 +1,70 @@
1
+ declare const DEFAULT_BASE_URL = "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1";
2
+ type DubsNetwork = 'devnet' | 'mainnet-beta';
3
+ declare const NETWORK_CONFIG: Record<DubsNetwork, {
4
+ baseUrl: string;
5
+ }>;
6
+
7
+ /** Configuration for the Dubs client */
8
+ interface DubsConfig {
9
+ /** Your Dubs API key (from the developer portal) */
10
+ apiKey: string;
11
+ /** Resolution secret for signing resolveGame requests (from the developer portal) */
12
+ resolutionSecret?: string;
13
+ /** Explicit base URL — overrides `network` if both provided */
14
+ baseUrl?: string;
15
+ /** Network shorthand — sets the base URL automatically */
16
+ network?: DubsNetwork;
17
+ }
18
+ /** Parameters for resolving a game */
19
+ interface ResolveGameParams {
20
+ /** Which side won: 'home', 'away', 'draw', or null for refund */
21
+ winner: 'home' | 'away' | 'draw' | null;
22
+ /** Optional metadata to attach to the resolution */
23
+ metadata?: Record<string, unknown>;
24
+ }
25
+ /** Result from resolving a game */
26
+ interface ResolveGameResult {
27
+ success: boolean;
28
+ gameId: string;
29
+ winner: string | null;
30
+ signature: string;
31
+ explorerUrl: string;
32
+ }
33
+ /** A parsed webhook event from Dubs */
34
+ interface WebhookEvent {
35
+ event: string;
36
+ timestamp: string;
37
+ data: Record<string, unknown>;
38
+ }
39
+
40
+ declare class Dubs {
41
+ private readonly apiKey;
42
+ private readonly resolutionSecret?;
43
+ private readonly baseUrl;
44
+ constructor(config: DubsConfig);
45
+ /**
46
+ * Resolve a custom game (game_mode=6).
47
+ *
48
+ * Automatically computes the HMAC-SHA256 signature using your resolution secret.
49
+ */
50
+ resolveGame(gameId: string, params: ResolveGameParams): Promise<ResolveGameResult>;
51
+ /**
52
+ * Verify an incoming Dubs webhook request.
53
+ *
54
+ * @param rawBody - The raw request body (string or Buffer)
55
+ * @param signature - The X-Dubs-Signature header value
56
+ * @param secret - Your webhook secret (from the developer portal)
57
+ * @returns The parsed webhook event
58
+ * @throws DubsApiError if the signature is invalid
59
+ */
60
+ static verifyWebhook(rawBody: string | Buffer, signature: string, secret: string): WebhookEvent;
61
+ private request;
62
+ }
63
+
64
+ declare class DubsApiError extends Error {
65
+ readonly code: string;
66
+ readonly httpStatus: number;
67
+ constructor(code: string, message: string, httpStatus: number);
68
+ }
69
+
70
+ export { DEFAULT_BASE_URL, Dubs, DubsApiError, type DubsConfig, type DubsNetwork, NETWORK_CONFIG, type ResolveGameParams, type ResolveGameResult, type WebhookEvent };
package/dist/index.js ADDED
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
34
+ Dubs: () => Dubs,
35
+ DubsApiError: () => DubsApiError,
36
+ NETWORK_CONFIG: () => NETWORK_CONFIG
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/client.ts
41
+ var import_crypto = __toESM(require("crypto"));
42
+
43
+ // src/constants.ts
44
+ var DEFAULT_BASE_URL = "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1";
45
+ var NETWORK_CONFIG = {
46
+ "mainnet-beta": {
47
+ baseUrl: "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1"
48
+ },
49
+ devnet: {
50
+ baseUrl: "https://dubs-server-dev-55d1fba09a97.herokuapp.com/api/developer/v1"
51
+ }
52
+ };
53
+
54
+ // src/errors.ts
55
+ var DubsApiError = class extends Error {
56
+ code;
57
+ httpStatus;
58
+ constructor(code, message, httpStatus) {
59
+ super(message);
60
+ this.name = "DubsApiError";
61
+ this.code = code;
62
+ this.httpStatus = httpStatus;
63
+ }
64
+ };
65
+
66
+ // src/client.ts
67
+ var Dubs = class {
68
+ apiKey;
69
+ resolutionSecret;
70
+ baseUrl;
71
+ constructor(config) {
72
+ this.apiKey = config.apiKey;
73
+ this.resolutionSecret = config.resolutionSecret;
74
+ if (config.baseUrl) {
75
+ this.baseUrl = config.baseUrl;
76
+ } else if (config.network) {
77
+ this.baseUrl = NETWORK_CONFIG[config.network].baseUrl;
78
+ } else {
79
+ this.baseUrl = DEFAULT_BASE_URL;
80
+ }
81
+ }
82
+ /**
83
+ * Resolve a custom game (game_mode=6).
84
+ *
85
+ * Automatically computes the HMAC-SHA256 signature using your resolution secret.
86
+ */
87
+ async resolveGame(gameId, params) {
88
+ if (!this.resolutionSecret) {
89
+ throw new DubsApiError(
90
+ "missing_resolution_secret",
91
+ "resolutionSecret is required for resolveGame(). Pass it in the Dubs constructor.",
92
+ 0
93
+ );
94
+ }
95
+ const body = JSON.stringify({
96
+ winner: params.winner,
97
+ ...params.metadata && { metadata: params.metadata }
98
+ });
99
+ const hmac = import_crypto.default.createHmac("sha256", this.resolutionSecret).update(body).digest("hex");
100
+ return this.request("POST", `/games/${gameId}/resolve`, body, {
101
+ "x-dubs-signature": `sha256=${hmac}`
102
+ });
103
+ }
104
+ /**
105
+ * Verify an incoming Dubs webhook request.
106
+ *
107
+ * @param rawBody - The raw request body (string or Buffer)
108
+ * @param signature - The X-Dubs-Signature header value
109
+ * @param secret - Your webhook secret (from the developer portal)
110
+ * @returns The parsed webhook event
111
+ * @throws DubsApiError if the signature is invalid
112
+ */
113
+ static verifyWebhook(rawBody, signature, secret) {
114
+ const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf-8");
115
+ const expected = import_crypto.default.createHmac("sha256", secret).update(body).digest("hex");
116
+ if (signature.length !== expected.length || !import_crypto.default.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
117
+ throw new DubsApiError("invalid_signature", "Webhook signature verification failed", 401);
118
+ }
119
+ return JSON.parse(body);
120
+ }
121
+ // ── Private ──
122
+ async request(method, path, body, extraHeaders) {
123
+ const url = `${this.baseUrl}${path}`;
124
+ const headers = {
125
+ "x-api-key": this.apiKey,
126
+ "Content-Type": "application/json",
127
+ ...extraHeaders
128
+ };
129
+ const res = await fetch(url, {
130
+ method,
131
+ headers,
132
+ ...body && { body }
133
+ });
134
+ const json = await res.json();
135
+ if (!res.ok) {
136
+ throw new DubsApiError(
137
+ json.code || "api_error",
138
+ json.message || json.error || `Request failed with status ${res.status}`,
139
+ res.status
140
+ );
141
+ }
142
+ return json;
143
+ }
144
+ };
145
+ // Annotate the CommonJS export names for ESM import in node:
146
+ 0 && (module.exports = {
147
+ DEFAULT_BASE_URL,
148
+ Dubs,
149
+ DubsApiError,
150
+ NETWORK_CONFIG
151
+ });
152
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/constants.ts","../src/errors.ts"],"sourcesContent":["export { Dubs } from './client';\nexport { DubsApiError } from './errors';\nexport type { DubsConfig, ResolveGameParams, ResolveGameResult, WebhookEvent } from './types';\nexport { DEFAULT_BASE_URL, NETWORK_CONFIG } from './constants';\nexport type { DubsNetwork } from './constants';\n","import crypto from 'crypto';\nimport { DEFAULT_BASE_URL, NETWORK_CONFIG } from './constants';\nimport { DubsApiError } from './errors';\nimport type { DubsConfig, ResolveGameParams, ResolveGameResult, WebhookEvent } from './types';\n\nexport class Dubs {\n private readonly apiKey: string;\n private readonly resolutionSecret?: string;\n private readonly baseUrl: string;\n\n constructor(config: DubsConfig) {\n this.apiKey = config.apiKey;\n this.resolutionSecret = config.resolutionSecret;\n\n if (config.baseUrl) {\n this.baseUrl = config.baseUrl;\n } else if (config.network) {\n this.baseUrl = NETWORK_CONFIG[config.network].baseUrl;\n } else {\n this.baseUrl = DEFAULT_BASE_URL;\n }\n }\n\n /**\n * Resolve a custom game (game_mode=6).\n *\n * Automatically computes the HMAC-SHA256 signature using your resolution secret.\n */\n async resolveGame(gameId: string, params: ResolveGameParams): Promise<ResolveGameResult> {\n if (!this.resolutionSecret) {\n throw new DubsApiError(\n 'missing_resolution_secret',\n 'resolutionSecret is required for resolveGame(). Pass it in the Dubs constructor.',\n 0,\n );\n }\n\n const body = JSON.stringify({\n winner: params.winner,\n ...(params.metadata && { metadata: params.metadata }),\n });\n\n const hmac = crypto.createHmac('sha256', this.resolutionSecret).update(body).digest('hex');\n\n return this.request<ResolveGameResult>('POST', `/games/${gameId}/resolve`, body, {\n 'x-dubs-signature': `sha256=${hmac}`,\n });\n }\n\n /**\n * Verify an incoming Dubs webhook request.\n *\n * @param rawBody - The raw request body (string or Buffer)\n * @param signature - The X-Dubs-Signature header value\n * @param secret - Your webhook secret (from the developer portal)\n * @returns The parsed webhook event\n * @throws DubsApiError if the signature is invalid\n */\n static verifyWebhook(rawBody: string | Buffer, signature: string, secret: string): WebhookEvent {\n const body = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf-8');\n const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');\n\n if (\n signature.length !== expected.length ||\n !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))\n ) {\n throw new DubsApiError('invalid_signature', 'Webhook signature verification failed', 401);\n }\n\n return JSON.parse(body) as WebhookEvent;\n }\n\n // ── Private ──\n\n private async request<T>(\n method: string,\n path: string,\n body?: string,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = {\n 'x-api-key': this.apiKey,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n };\n\n const res = await fetch(url, {\n method,\n headers,\n ...(body && { body }),\n });\n\n const json = (await res.json()) as Record<string, unknown>;\n\n if (!res.ok) {\n throw new DubsApiError(\n (json.code as string) || 'api_error',\n (json.message as string) || (json.error as string) || `Request failed with status ${res.status}`,\n res.status,\n );\n }\n\n return json as T;\n }\n}\n","export const DEFAULT_BASE_URL = 'https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1';\n\nexport type DubsNetwork = 'devnet' | 'mainnet-beta';\n\nexport const NETWORK_CONFIG: Record<DubsNetwork, { baseUrl: string }> = {\n 'mainnet-beta': {\n baseUrl: 'https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1',\n },\n devnet: {\n baseUrl: 'https://dubs-server-dev-55d1fba09a97.herokuapp.com/api/developer/v1',\n },\n};\n","export class DubsApiError extends Error {\n public readonly code: string;\n public readonly httpStatus: number;\n\n constructor(code: string, message: string, httpStatus: number) {\n super(message);\n this.name = 'DubsApiError';\n this.code = code;\n this.httpStatus = httpStatus;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAAmB;;;ACAZ,IAAM,mBAAmB;AAIzB,IAAM,iBAA2D;AAAA,EACtE,gBAAgB;AAAA,IACd,SAAS;AAAA,EACX;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,EACX;AACF;;;ACXO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtB;AAAA,EACA;AAAA,EAEhB,YAAY,MAAc,SAAiB,YAAoB;AAC7D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;;;AFLO,IAAM,OAAN,MAAW;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAAoB;AAC9B,SAAK,SAAS,OAAO;AACrB,SAAK,mBAAmB,OAAO;AAE/B,QAAI,OAAO,SAAS;AAClB,WAAK,UAAU,OAAO;AAAA,IACxB,WAAW,OAAO,SAAS;AACzB,WAAK,UAAU,eAAe,OAAO,OAAO,EAAE;AAAA,IAChD,OAAO;AACL,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,QAAgB,QAAuD;AACvF,QAAI,CAAC,KAAK,kBAAkB;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,GAAI,OAAO,YAAY,EAAE,UAAU,OAAO,SAAS;AAAA,IACrD,CAAC;AAED,UAAM,OAAO,cAAAA,QAAO,WAAW,UAAU,KAAK,gBAAgB,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAEzF,WAAO,KAAK,QAA2B,QAAQ,UAAU,MAAM,YAAY,MAAM;AAAA,MAC/E,oBAAoB,UAAU,IAAI;AAAA,IACpC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,cAAc,SAA0B,WAAmB,QAA8B;AAC9F,UAAM,OAAO,OAAO,YAAY,WAAW,UAAU,QAAQ,SAAS,OAAO;AAC7E,UAAM,WAAW,cAAAA,QAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAE9E,QACE,UAAU,WAAW,SAAS,UAC9B,CAAC,cAAAA,QAAO,gBAAgB,OAAO,KAAK,SAAS,GAAG,OAAO,KAAK,QAAQ,CAAC,GACrE;AACA,YAAM,IAAI,aAAa,qBAAqB,yCAAyC,GAAG;AAAA,IAC1F;AAEA,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB;AAAA;AAAA,EAIA,MAAc,QACZ,QACA,MACA,MACA,cACY;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAElC,UAAM,UAAkC;AAAA,MACtC,aAAa,KAAK;AAAA,MAClB,gBAAgB;AAAA,MAChB,GAAG;AAAA,IACL;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,EAAE,KAAK;AAAA,IACrB,CAAC;AAED,UAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACP,KAAK,QAAmB;AAAA,QACxB,KAAK,WAAuB,KAAK,SAAoB,8BAA8B,IAAI,MAAM;AAAA,QAC9F,IAAI;AAAA,MACN;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","names":["crypto"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,112 @@
1
+ // src/client.ts
2
+ import crypto from "crypto";
3
+
4
+ // src/constants.ts
5
+ var DEFAULT_BASE_URL = "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1";
6
+ var NETWORK_CONFIG = {
7
+ "mainnet-beta": {
8
+ baseUrl: "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1"
9
+ },
10
+ devnet: {
11
+ baseUrl: "https://dubs-server-dev-55d1fba09a97.herokuapp.com/api/developer/v1"
12
+ }
13
+ };
14
+
15
+ // src/errors.ts
16
+ var DubsApiError = class extends Error {
17
+ code;
18
+ httpStatus;
19
+ constructor(code, message, httpStatus) {
20
+ super(message);
21
+ this.name = "DubsApiError";
22
+ this.code = code;
23
+ this.httpStatus = httpStatus;
24
+ }
25
+ };
26
+
27
+ // src/client.ts
28
+ var Dubs = class {
29
+ apiKey;
30
+ resolutionSecret;
31
+ baseUrl;
32
+ constructor(config) {
33
+ this.apiKey = config.apiKey;
34
+ this.resolutionSecret = config.resolutionSecret;
35
+ if (config.baseUrl) {
36
+ this.baseUrl = config.baseUrl;
37
+ } else if (config.network) {
38
+ this.baseUrl = NETWORK_CONFIG[config.network].baseUrl;
39
+ } else {
40
+ this.baseUrl = DEFAULT_BASE_URL;
41
+ }
42
+ }
43
+ /**
44
+ * Resolve a custom game (game_mode=6).
45
+ *
46
+ * Automatically computes the HMAC-SHA256 signature using your resolution secret.
47
+ */
48
+ async resolveGame(gameId, params) {
49
+ if (!this.resolutionSecret) {
50
+ throw new DubsApiError(
51
+ "missing_resolution_secret",
52
+ "resolutionSecret is required for resolveGame(). Pass it in the Dubs constructor.",
53
+ 0
54
+ );
55
+ }
56
+ const body = JSON.stringify({
57
+ winner: params.winner,
58
+ ...params.metadata && { metadata: params.metadata }
59
+ });
60
+ const hmac = crypto.createHmac("sha256", this.resolutionSecret).update(body).digest("hex");
61
+ return this.request("POST", `/games/${gameId}/resolve`, body, {
62
+ "x-dubs-signature": `sha256=${hmac}`
63
+ });
64
+ }
65
+ /**
66
+ * Verify an incoming Dubs webhook request.
67
+ *
68
+ * @param rawBody - The raw request body (string or Buffer)
69
+ * @param signature - The X-Dubs-Signature header value
70
+ * @param secret - Your webhook secret (from the developer portal)
71
+ * @returns The parsed webhook event
72
+ * @throws DubsApiError if the signature is invalid
73
+ */
74
+ static verifyWebhook(rawBody, signature, secret) {
75
+ const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf-8");
76
+ const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");
77
+ if (signature.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
78
+ throw new DubsApiError("invalid_signature", "Webhook signature verification failed", 401);
79
+ }
80
+ return JSON.parse(body);
81
+ }
82
+ // ── Private ──
83
+ async request(method, path, body, extraHeaders) {
84
+ const url = `${this.baseUrl}${path}`;
85
+ const headers = {
86
+ "x-api-key": this.apiKey,
87
+ "Content-Type": "application/json",
88
+ ...extraHeaders
89
+ };
90
+ const res = await fetch(url, {
91
+ method,
92
+ headers,
93
+ ...body && { body }
94
+ });
95
+ const json = await res.json();
96
+ if (!res.ok) {
97
+ throw new DubsApiError(
98
+ json.code || "api_error",
99
+ json.message || json.error || `Request failed with status ${res.status}`,
100
+ res.status
101
+ );
102
+ }
103
+ return json;
104
+ }
105
+ };
106
+ export {
107
+ DEFAULT_BASE_URL,
108
+ Dubs,
109
+ DubsApiError,
110
+ NETWORK_CONFIG
111
+ };
112
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/constants.ts","../src/errors.ts"],"sourcesContent":["import crypto from 'crypto';\nimport { DEFAULT_BASE_URL, NETWORK_CONFIG } from './constants';\nimport { DubsApiError } from './errors';\nimport type { DubsConfig, ResolveGameParams, ResolveGameResult, WebhookEvent } from './types';\n\nexport class Dubs {\n private readonly apiKey: string;\n private readonly resolutionSecret?: string;\n private readonly baseUrl: string;\n\n constructor(config: DubsConfig) {\n this.apiKey = config.apiKey;\n this.resolutionSecret = config.resolutionSecret;\n\n if (config.baseUrl) {\n this.baseUrl = config.baseUrl;\n } else if (config.network) {\n this.baseUrl = NETWORK_CONFIG[config.network].baseUrl;\n } else {\n this.baseUrl = DEFAULT_BASE_URL;\n }\n }\n\n /**\n * Resolve a custom game (game_mode=6).\n *\n * Automatically computes the HMAC-SHA256 signature using your resolution secret.\n */\n async resolveGame(gameId: string, params: ResolveGameParams): Promise<ResolveGameResult> {\n if (!this.resolutionSecret) {\n throw new DubsApiError(\n 'missing_resolution_secret',\n 'resolutionSecret is required for resolveGame(). Pass it in the Dubs constructor.',\n 0,\n );\n }\n\n const body = JSON.stringify({\n winner: params.winner,\n ...(params.metadata && { metadata: params.metadata }),\n });\n\n const hmac = crypto.createHmac('sha256', this.resolutionSecret).update(body).digest('hex');\n\n return this.request<ResolveGameResult>('POST', `/games/${gameId}/resolve`, body, {\n 'x-dubs-signature': `sha256=${hmac}`,\n });\n }\n\n /**\n * Verify an incoming Dubs webhook request.\n *\n * @param rawBody - The raw request body (string or Buffer)\n * @param signature - The X-Dubs-Signature header value\n * @param secret - Your webhook secret (from the developer portal)\n * @returns The parsed webhook event\n * @throws DubsApiError if the signature is invalid\n */\n static verifyWebhook(rawBody: string | Buffer, signature: string, secret: string): WebhookEvent {\n const body = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf-8');\n const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');\n\n if (\n signature.length !== expected.length ||\n !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))\n ) {\n throw new DubsApiError('invalid_signature', 'Webhook signature verification failed', 401);\n }\n\n return JSON.parse(body) as WebhookEvent;\n }\n\n // ── Private ──\n\n private async request<T>(\n method: string,\n path: string,\n body?: string,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = {\n 'x-api-key': this.apiKey,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n };\n\n const res = await fetch(url, {\n method,\n headers,\n ...(body && { body }),\n });\n\n const json = (await res.json()) as Record<string, unknown>;\n\n if (!res.ok) {\n throw new DubsApiError(\n (json.code as string) || 'api_error',\n (json.message as string) || (json.error as string) || `Request failed with status ${res.status}`,\n res.status,\n );\n }\n\n return json as T;\n }\n}\n","export const DEFAULT_BASE_URL = 'https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1';\n\nexport type DubsNetwork = 'devnet' | 'mainnet-beta';\n\nexport const NETWORK_CONFIG: Record<DubsNetwork, { baseUrl: string }> = {\n 'mainnet-beta': {\n baseUrl: 'https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1',\n },\n devnet: {\n baseUrl: 'https://dubs-server-dev-55d1fba09a97.herokuapp.com/api/developer/v1',\n },\n};\n","export class DubsApiError extends Error {\n public readonly code: string;\n public readonly httpStatus: number;\n\n constructor(code: string, message: string, httpStatus: number) {\n super(message);\n this.name = 'DubsApiError';\n this.code = code;\n this.httpStatus = httpStatus;\n }\n}\n"],"mappings":";AAAA,OAAO,YAAY;;;ACAZ,IAAM,mBAAmB;AAIzB,IAAM,iBAA2D;AAAA,EACtE,gBAAgB;AAAA,IACd,SAAS;AAAA,EACX;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,EACX;AACF;;;ACXO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtB;AAAA,EACA;AAAA,EAEhB,YAAY,MAAc,SAAiB,YAAoB;AAC7D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;;;AFLO,IAAM,OAAN,MAAW;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAAoB;AAC9B,SAAK,SAAS,OAAO;AACrB,SAAK,mBAAmB,OAAO;AAE/B,QAAI,OAAO,SAAS;AAClB,WAAK,UAAU,OAAO;AAAA,IACxB,WAAW,OAAO,SAAS;AACzB,WAAK,UAAU,eAAe,OAAO,OAAO,EAAE;AAAA,IAChD,OAAO;AACL,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,QAAgB,QAAuD;AACvF,QAAI,CAAC,KAAK,kBAAkB;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,GAAI,OAAO,YAAY,EAAE,UAAU,OAAO,SAAS;AAAA,IACrD,CAAC;AAED,UAAM,OAAO,OAAO,WAAW,UAAU,KAAK,gBAAgB,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAEzF,WAAO,KAAK,QAA2B,QAAQ,UAAU,MAAM,YAAY,MAAM;AAAA,MAC/E,oBAAoB,UAAU,IAAI;AAAA,IACpC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,cAAc,SAA0B,WAAmB,QAA8B;AAC9F,UAAM,OAAO,OAAO,YAAY,WAAW,UAAU,QAAQ,SAAS,OAAO;AAC7E,UAAM,WAAW,OAAO,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAE9E,QACE,UAAU,WAAW,SAAS,UAC9B,CAAC,OAAO,gBAAgB,OAAO,KAAK,SAAS,GAAG,OAAO,KAAK,QAAQ,CAAC,GACrE;AACA,YAAM,IAAI,aAAa,qBAAqB,yCAAyC,GAAG;AAAA,IAC1F;AAEA,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB;AAAA;AAAA,EAIA,MAAc,QACZ,QACA,MACA,MACA,cACY;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAElC,UAAM,UAAkC;AAAA,MACtC,aAAa,KAAK;AAAA,MAClB,gBAAgB;AAAA,MAChB,GAAG;AAAA,IACL;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,EAAE,KAAK;AAAA,IACrB,CAAC;AAED,UAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACP,KAAK,QAAmB;AAAA,QACxB,KAAK,WAAuB,KAAK,SAAoB,8BAA8B,IAAI,MAAM;AAAA,QAC9F,IAAI;AAAA,MACN;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@dubsdotapp/node",
3
+ "version": "0.1.0",
4
+ "description": "Server-side Node.js SDK for the Dubs betting platform",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "typecheck": "tsc --noEmit",
22
+ "dev": "tsup --watch",
23
+ "clean": "rm -rf dist"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.3.0",
27
+ "tsup": "^8.0.0",
28
+ "typescript": "^5.3.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "keywords": [
34
+ "solana",
35
+ "betting",
36
+ "dubs",
37
+ "web3",
38
+ "node",
39
+ "server"
40
+ ],
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/iheartsolana/dubs-node"
45
+ }
46
+ }
package/src/client.ts ADDED
@@ -0,0 +1,107 @@
1
+ import crypto from 'crypto';
2
+ import { DEFAULT_BASE_URL, NETWORK_CONFIG } from './constants';
3
+ import { DubsApiError } from './errors';
4
+ import type { DubsConfig, ResolveGameParams, ResolveGameResult, WebhookEvent } from './types';
5
+
6
+ export class Dubs {
7
+ private readonly apiKey: string;
8
+ private readonly resolutionSecret?: string;
9
+ private readonly baseUrl: string;
10
+
11
+ constructor(config: DubsConfig) {
12
+ this.apiKey = config.apiKey;
13
+ this.resolutionSecret = config.resolutionSecret;
14
+
15
+ if (config.baseUrl) {
16
+ this.baseUrl = config.baseUrl;
17
+ } else if (config.network) {
18
+ this.baseUrl = NETWORK_CONFIG[config.network].baseUrl;
19
+ } else {
20
+ this.baseUrl = DEFAULT_BASE_URL;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Resolve a custom game (game_mode=6).
26
+ *
27
+ * Automatically computes the HMAC-SHA256 signature using your resolution secret.
28
+ */
29
+ async resolveGame(gameId: string, params: ResolveGameParams): Promise<ResolveGameResult> {
30
+ if (!this.resolutionSecret) {
31
+ throw new DubsApiError(
32
+ 'missing_resolution_secret',
33
+ 'resolutionSecret is required for resolveGame(). Pass it in the Dubs constructor.',
34
+ 0,
35
+ );
36
+ }
37
+
38
+ const body = JSON.stringify({
39
+ winner: params.winner,
40
+ ...(params.metadata && { metadata: params.metadata }),
41
+ });
42
+
43
+ const hmac = crypto.createHmac('sha256', this.resolutionSecret).update(body).digest('hex');
44
+
45
+ return this.request<ResolveGameResult>('POST', `/games/${gameId}/resolve`, body, {
46
+ 'x-dubs-signature': `sha256=${hmac}`,
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Verify an incoming Dubs webhook request.
52
+ *
53
+ * @param rawBody - The raw request body (string or Buffer)
54
+ * @param signature - The X-Dubs-Signature header value
55
+ * @param secret - Your webhook secret (from the developer portal)
56
+ * @returns The parsed webhook event
57
+ * @throws DubsApiError if the signature is invalid
58
+ */
59
+ static verifyWebhook(rawBody: string | Buffer, signature: string, secret: string): WebhookEvent {
60
+ const body = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf-8');
61
+ const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');
62
+
63
+ if (
64
+ signature.length !== expected.length ||
65
+ !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
66
+ ) {
67
+ throw new DubsApiError('invalid_signature', 'Webhook signature verification failed', 401);
68
+ }
69
+
70
+ return JSON.parse(body) as WebhookEvent;
71
+ }
72
+
73
+ // ── Private ──
74
+
75
+ private async request<T>(
76
+ method: string,
77
+ path: string,
78
+ body?: string,
79
+ extraHeaders?: Record<string, string>,
80
+ ): Promise<T> {
81
+ const url = `${this.baseUrl}${path}`;
82
+
83
+ const headers: Record<string, string> = {
84
+ 'x-api-key': this.apiKey,
85
+ 'Content-Type': 'application/json',
86
+ ...extraHeaders,
87
+ };
88
+
89
+ const res = await fetch(url, {
90
+ method,
91
+ headers,
92
+ ...(body && { body }),
93
+ });
94
+
95
+ const json = (await res.json()) as Record<string, unknown>;
96
+
97
+ if (!res.ok) {
98
+ throw new DubsApiError(
99
+ (json.code as string) || 'api_error',
100
+ (json.message as string) || (json.error as string) || `Request failed with status ${res.status}`,
101
+ res.status,
102
+ );
103
+ }
104
+
105
+ return json as T;
106
+ }
107
+ }
@@ -0,0 +1,12 @@
1
+ export const DEFAULT_BASE_URL = 'https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1';
2
+
3
+ export type DubsNetwork = 'devnet' | 'mainnet-beta';
4
+
5
+ export const NETWORK_CONFIG: Record<DubsNetwork, { baseUrl: string }> = {
6
+ 'mainnet-beta': {
7
+ baseUrl: 'https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1',
8
+ },
9
+ devnet: {
10
+ baseUrl: 'https://dubs-server-dev-55d1fba09a97.herokuapp.com/api/developer/v1',
11
+ },
12
+ };
package/src/errors.ts ADDED
@@ -0,0 +1,11 @@
1
+ export class DubsApiError extends Error {
2
+ public readonly code: string;
3
+ public readonly httpStatus: number;
4
+
5
+ constructor(code: string, message: string, httpStatus: number) {
6
+ super(message);
7
+ this.name = 'DubsApiError';
8
+ this.code = code;
9
+ this.httpStatus = httpStatus;
10
+ }
11
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { Dubs } from './client';
2
+ export { DubsApiError } from './errors';
3
+ export type { DubsConfig, ResolveGameParams, ResolveGameResult, WebhookEvent } from './types';
4
+ export { DEFAULT_BASE_URL, NETWORK_CONFIG } from './constants';
5
+ export type { DubsNetwork } from './constants';
package/src/types.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { DubsNetwork } from './constants';
2
+
3
+ /** Configuration for the Dubs client */
4
+ export interface DubsConfig {
5
+ /** Your Dubs API key (from the developer portal) */
6
+ apiKey: string;
7
+ /** Resolution secret for signing resolveGame requests (from the developer portal) */
8
+ resolutionSecret?: string;
9
+ /** Explicit base URL — overrides `network` if both provided */
10
+ baseUrl?: string;
11
+ /** Network shorthand — sets the base URL automatically */
12
+ network?: DubsNetwork;
13
+ }
14
+
15
+ /** Parameters for resolving a game */
16
+ export interface ResolveGameParams {
17
+ /** Which side won: 'home', 'away', 'draw', or null for refund */
18
+ winner: 'home' | 'away' | 'draw' | null;
19
+ /** Optional metadata to attach to the resolution */
20
+ metadata?: Record<string, unknown>;
21
+ }
22
+
23
+ /** Result from resolving a game */
24
+ export interface ResolveGameResult {
25
+ success: boolean;
26
+ gameId: string;
27
+ winner: string | null;
28
+ signature: string;
29
+ explorerUrl: string;
30
+ }
31
+
32
+ /** A parsed webhook event from Dubs */
33
+ export interface WebhookEvent {
34
+ event: string;
35
+ timestamp: string;
36
+ data: Record<string, unknown>;
37
+ }