@riseworks/riseworks-sdk 1.0.4

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.
Files changed (41) hide show
  1. package/README.md +240 -0
  2. package/ai-skills/README.md +28 -0
  3. package/ai-skills/STANDARDS.md +66 -0
  4. package/ai-skills/references/API_WORKFLOWS.md +200 -0
  5. package/ai-skills/references/TYPES.md +190 -0
  6. package/ai-skills/rise-auth-and-setup/README.md +14 -0
  7. package/ai-skills/rise-auth-and-setup/SKILL.md +57 -0
  8. package/ai-skills/rise-auth-and-setup/metadata.json +12 -0
  9. package/ai-skills/rise-auth-and-setup/references/SETUP_PATTERNS.md +26 -0
  10. package/ai-skills/rise-debugging-and-errors/SKILL.md +51 -0
  11. package/ai-skills/rise-payments-workflows/README.md +19 -0
  12. package/ai-skills/rise-payments-workflows/SKILL.md +109 -0
  13. package/ai-skills/rise-payments-workflows/metadata.json +12 -0
  14. package/ai-skills/rise-payments-workflows/references/PAYMENT_PATTERNS.md +36 -0
  15. package/ai-skills/rise-sdk-integration/README.md +25 -0
  16. package/ai-skills/rise-sdk-integration/SKILL.md +130 -0
  17. package/ai-skills/rise-sdk-integration/metadata.json +12 -0
  18. package/ai-skills/rise-sdk-integration/references/PATTERNS.md +28 -0
  19. package/ai-skills/rise-security-and-approvals/SKILL.md +33 -0
  20. package/ai-skills/rise-teams-and-invites/README.md +13 -0
  21. package/ai-skills/rise-teams-and-invites/SKILL.md +66 -0
  22. package/ai-skills/rise-teams-and-invites/metadata.json +12 -0
  23. package/ai-skills/rise-teams-and-invites/references/TEAM_PATTERNS.md +25 -0
  24. package/ai-skills/rise-v1-migration/README.md +22 -0
  25. package/ai-skills/rise-v1-migration/SKILL.md +184 -0
  26. package/ai-skills/rise-v1-migration/metadata.json +13 -0
  27. package/ai-skills/rise-v1-migration/references/ENDPOINT_MAPPINGS.md +72 -0
  28. package/ai-skills/rise-v1-migration/references/MIGRATION_PATTERNS.md +66 -0
  29. package/ai-skills/rise-webhooks/README.md +41 -0
  30. package/ai-skills/rise-webhooks/SKILL.md +143 -0
  31. package/ai-skills/rise-webhooks/metadata.json +13 -0
  32. package/ai-skills/rise-webhooks/references/HANDLER.md +60 -0
  33. package/ai-skills/rise-webhooks/references/REGISTRATION.md +113 -0
  34. package/dist/index.cjs +1406 -0
  35. package/dist/index.d.cts +6865 -0
  36. package/dist/index.d.cts.map +1 -0
  37. package/dist/index.d.mts +6865 -0
  38. package/dist/index.d.mts.map +1 -0
  39. package/dist/index.mjs +1383 -0
  40. package/package.json +87 -0
  41. package/scripts/add-skills.mjs +158 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1406 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('node:crypto');
4
+ var ethers = require('ethers');
5
+
6
+ function _interopNamespaceDefault(e) {
7
+ var n = Object.create(null);
8
+ if (e) {
9
+ Object.keys(e).forEach(function (k) {
10
+ if (k !== 'default') {
11
+ var d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: function () { return e[k]; }
15
+ });
16
+ }
17
+ });
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
+ }
22
+
23
+ var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
24
+
25
+ const PRIVATE_KEY_LENGTH = 66;
26
+ const RISE_ID_LENGTH = 42;
27
+ const RISE_API_URLS = {
28
+ dev: "https://b2b-api.dev-riseworks.io",
29
+ stg: "https://integrations-api.staging-riseworks.io",
30
+ prod: "https://integrations-api.riseworks.io"
31
+ };
32
+ const EXPECTED_CHAIN_IDS = {
33
+ dev: [421614],
34
+ stg: [421614],
35
+ prod: [42161]
36
+ };
37
+ const CENTS_TO_TOKEN_UNITS = 10000n;
38
+ const generateIdempotencyKey = () => crypto.randomUUID();
39
+ const apiPath = (strings, ...values) => strings.reduce((path, part, i) => {
40
+ if (i >= values.length) return `${path}${part}`;
41
+ const value = String(values[i]);
42
+ if (value === "" || value === "." || value === "..") {
43
+ throw new Error(
44
+ `Invalid path parameter ${JSON.stringify(value)}: expected a non-empty id`
45
+ );
46
+ }
47
+ return `${path}${part}${encodeURIComponent(value)}`;
48
+ }, "");
49
+ class ApiGroup {
50
+ client;
51
+ constructor(client) {
52
+ this.client = client;
53
+ }
54
+ request(method, path, data, query, extraHeaders) {
55
+ return this.client.request(method, path, data, query, extraHeaders);
56
+ }
57
+ }
58
+ class WebhooksApi extends ApiGroup {
59
+ /**
60
+ * Register a new webhook endpoint to receive real-time notifications
61
+ *
62
+ * @param data - Webhook registration data including URL, events, and secret
63
+ * @returns Promise resolving to the registered webhook response
64
+ * @throws Error if registration fails or validation errors occur
65
+ */
66
+ async register(data) {
67
+ return this.request(
68
+ "POST",
69
+ "/v2/webhooks/register",
70
+ data
71
+ );
72
+ }
73
+ /**
74
+ * Update an existing webhook endpoint configuration
75
+ *
76
+ * @param webhookNanoid - Unique identifier of the webhook to update
77
+ * @param data - Updated webhook configuration data
78
+ * @returns Promise resolving to the updated webhook response
79
+ * @throws Error if update fails or webhook not found
80
+ */
81
+ async update(webhookNanoid, data) {
82
+ return this.request(
83
+ "PUT",
84
+ apiPath`/v2/webhooks/${webhookNanoid}`,
85
+ data
86
+ );
87
+ }
88
+ /**
89
+ * Retrieve details of a specific webhook endpoint
90
+ *
91
+ * @param webhookNanoid - Unique identifier of the webhook to retrieve
92
+ * @returns Promise resolving to the webhook details
93
+ * @throws Error if webhook not found or access denied
94
+ */
95
+ async get(webhookNanoid) {
96
+ return this.request(
97
+ "GET",
98
+ apiPath`/v2/webhooks/${webhookNanoid}`
99
+ );
100
+ }
101
+ /**
102
+ * List all webhook endpoints for the authenticated user or organization
103
+ *
104
+ * @param params - Query parameters for filtering and pagination
105
+ * @returns Promise resolving to the list of webhook endpoints
106
+ * @throws Error if listing fails or access denied
107
+ */
108
+ async list(params) {
109
+ return this.request(
110
+ "GET",
111
+ "/v2/webhooks",
112
+ void 0,
113
+ params
114
+ );
115
+ }
116
+ /**
117
+ * Send a test event to a webhook endpoint to verify connectivity
118
+ *
119
+ * @param webhookNanoid - Unique identifier of the webhook to test
120
+ * @param data - Test event data to send
121
+ * @returns Promise resolving to the test delivery response
122
+ * @throws Error if test fails or webhook not found
123
+ */
124
+ async test(webhookNanoid, data) {
125
+ return this.request(
126
+ "POST",
127
+ apiPath`/v2/webhooks/test/${webhookNanoid}`,
128
+ data
129
+ );
130
+ }
131
+ /**
132
+ * Retry a failed webhook delivery attempt
133
+ *
134
+ * @param deliveryNanoid - Unique identifier of the failed delivery to retry
135
+ * @returns Promise resolving to the retry delivery response
136
+ * @throws Error if retry fails or delivery not found
137
+ */
138
+ async retryDelivery(deliveryNanoid) {
139
+ return this.request(
140
+ "POST",
141
+ apiPath`/v2/webhooks/retry/${deliveryNanoid}`
142
+ );
143
+ }
144
+ /**
145
+ * Retrieve delivery history for a specific webhook endpoint
146
+ *
147
+ * @param webhookNanoid - Unique identifier of the webhook
148
+ * @param params - Query parameters for filtering and pagination
149
+ * @returns Promise resolving to the delivery history
150
+ * @throws Error if history retrieval fails or webhook not found
151
+ */
152
+ async getDeliveryHistory(webhookNanoid, params) {
153
+ return this.request(
154
+ "GET",
155
+ apiPath`/v2/webhooks/${webhookNanoid}/deliveries`,
156
+ void 0,
157
+ params
158
+ );
159
+ }
160
+ /**
161
+ * Retrieve a single webhook event (the original event payload Rise emitted)
162
+ * for a specific endpoint. Pair with {@link getDeliveryHistory} to inspect the
163
+ * delivery attempts for the same event.
164
+ *
165
+ * @param params - Webhook endpoint nanoid and the event nanoid to fetch
166
+ * @returns Promise resolving to the webhook event detail
167
+ * @throws Error if the event or endpoint is not found or access is denied
168
+ */
169
+ async getEvent(params) {
170
+ return this.request(
171
+ "GET",
172
+ apiPath`/v2/webhooks/${params.webhook_nanoid}/events/${params.event_nanoid}`
173
+ );
174
+ }
175
+ }
176
+ class AuthApi extends ApiGroup {
177
+ /**
178
+ * Get a Sign-In with Ethereum (SIWE) message for wallet authentication
179
+ *
180
+ * @param params - Authentication parameters including wallet address and Rise ID
181
+ * @returns Promise resolving to the SIWE message for signing
182
+ * @throws Error if message generation fails or invalid parameters
183
+ */
184
+ async getSiwe(params) {
185
+ return this.request(
186
+ "GET",
187
+ "/v2/auth/siwe",
188
+ void 0,
189
+ params
190
+ );
191
+ }
192
+ /**
193
+ * Verify a signed SIWE message to authenticate the user
194
+ *
195
+ * @param data - Verification data including the signed message and signature
196
+ * @returns Promise resolving to the authentication response with JWT token
197
+ * @throws Error if verification fails or signature is invalid
198
+ */
199
+ async verifySiwe(data) {
200
+ return this.request("POST", "/v2/auth/verify", data);
201
+ }
202
+ }
203
+ class CompanyApi extends ApiGroup {
204
+ /**
205
+ * Retrieve all users associated with a specific company
206
+ *
207
+ * @param params - Company parameters including company nanoid
208
+ * @returns Promise resolving to the list of company users
209
+ * @throws Error if retrieval fails or company not found
210
+ */
211
+ async getUsers(params) {
212
+ return this.request(
213
+ "GET",
214
+ apiPath`/v2/company/${params.nanoid}/users`
215
+ );
216
+ }
217
+ /**
218
+ * Retrieve detailed information about a company/organization
219
+ *
220
+ * @param params - Company parameters including company nanoid
221
+ * @returns Promise resolving to the organization details
222
+ * @throws Error if retrieval fails or company not found
223
+ */
224
+ async getDetails(params) {
225
+ return this.request(
226
+ "GET",
227
+ apiPath`/v2/company/${params.nanoid}/details`
228
+ );
229
+ }
230
+ /**
231
+ * Retrieve the current settings and configuration for a company
232
+ *
233
+ * @param params - Company parameters including company nanoid
234
+ * @returns Promise resolving to the company settings
235
+ * @throws Error if retrieval fails or company not found
236
+ */
237
+ async getSettings(params) {
238
+ return this.request(
239
+ "GET",
240
+ apiPath`/v2/company/${params.nanoid}/settings`
241
+ );
242
+ }
243
+ /**
244
+ * Retrieve role-specific settings for a user within a company
245
+ *
246
+ * @param params - Company and user parameters
247
+ * @returns Promise resolving to the role settings
248
+ * @throws Error if retrieval fails or user/company not found
249
+ */
250
+ async getRoleSettings(params) {
251
+ return this.request(
252
+ "GET",
253
+ apiPath`/v2/company/${params.nanoid}/members/${params.user_nanoid}/settings`
254
+ );
255
+ }
256
+ /**
257
+ * Retrieve contact information for a company/organization
258
+ *
259
+ * @param params - Company parameters including company nanoid
260
+ * @returns Promise resolving to the organization contacts
261
+ * @throws Error if retrieval fails or company not found
262
+ */
263
+ async getContacts(params) {
264
+ return this.request(
265
+ "GET",
266
+ apiPath`/v2/company/${params.nanoid}/contacts`
267
+ );
268
+ }
269
+ /**
270
+ * Retrieve ownership information for a company/organization
271
+ *
272
+ * @param params - Company parameters including company nanoid
273
+ * @returns Promise resolving to the organization ownership details
274
+ * @throws Error if retrieval fails or company not found
275
+ */
276
+ async getOwnership(params) {
277
+ return this.request(
278
+ "GET",
279
+ apiPath`/v2/company/${params.nanoid}/ownership`
280
+ );
281
+ }
282
+ }
283
+ class EntityBalanceApi extends ApiGroup {
284
+ /**
285
+ * Retrieve the current balance for an entity (company, team, or user)
286
+ *
287
+ * @param params - Balance request parameters including entity identifier
288
+ * @returns Promise resolving to the entity balance information
289
+ * @throws Error if retrieval fails or entity not found
290
+ */
291
+ async get(params) {
292
+ return this.request(
293
+ "GET",
294
+ "/v2/balance",
295
+ void 0,
296
+ params
297
+ );
298
+ }
299
+ }
300
+ class InvitesApi extends ApiGroup {
301
+ /**
302
+ * Send invitations to team members via email
303
+ *
304
+ * @param data - Invitation data including emails and team information
305
+ * @returns Promise resolving to the invitation response
306
+ * @throws Error if invitation fails or validation errors occur
307
+ */
308
+ async send(data) {
309
+ return this.request("POST", "/v2/invites", data);
310
+ }
311
+ /**
312
+ * Get typed data for manager invite signing (manual flow)
313
+ *
314
+ * @param data - Manager invite request data
315
+ * @returns Promise resolving to the typed data for manual signing
316
+ * @throws Error if typed data generation fails or validation errors occur
317
+ */
318
+ async getManagerInviteTypedData(data) {
319
+ return this.request(
320
+ "POST",
321
+ "/v2/invites/manager",
322
+ data
323
+ );
324
+ }
325
+ /**
326
+ * Send manager invite and sign
327
+ */
328
+ async sendManagerInvite(data) {
329
+ const { privateKey, ...inviteData } = data;
330
+ const createResponse = await this.getManagerInviteTypedData(inviteData);
331
+ const { signer, signature } = await this.client.signTypedData(
332
+ createResponse.data.typed_data.domain,
333
+ createResponse.data.typed_data.types,
334
+ createResponse.data.typed_data.typed_data,
335
+ privateKey
336
+ );
337
+ const executeData = {
338
+ invites: createResponse.data.invites,
339
+ signer,
340
+ typed_data: createResponse.data.typed_data.typed_data,
341
+ signature
342
+ };
343
+ return this.executeManagerInviteWithSignedData(executeData);
344
+ }
345
+ /**
346
+ * Execute manager invite with pre-signed data (manual flow)
347
+ *
348
+ * @param data - Signed manager invite data including signature
349
+ * @returns Promise resolving to the executed manager invite response
350
+ * @throws Error if execution fails or signature is invalid
351
+ */
352
+ async executeManagerInviteWithSignedData(data) {
353
+ return this.request(
354
+ "PUT",
355
+ "/v2/invites/manager",
356
+ data
357
+ );
358
+ }
359
+ /**
360
+ * Retrieve all invitations for a company or team
361
+ *
362
+ * @param params - Query parameters for filtering and pagination
363
+ * @returns Promise resolving to the list of invitations
364
+ * @throws Error if retrieval fails or access denied
365
+ */
366
+ async get(params) {
367
+ return this.request(
368
+ "GET",
369
+ "/v2/invites",
370
+ void 0,
371
+ params
372
+ );
373
+ }
374
+ }
375
+ class MeApi extends ApiGroup {
376
+ /**
377
+ * Retrieve information about the currently authenticated user
378
+ *
379
+ * @returns Promise resolving to the current user's information
380
+ * @throws Error if retrieval fails or authentication invalid
381
+ */
382
+ async get() {
383
+ return this.request("GET", "/v2/me");
384
+ }
385
+ }
386
+ const assertPaymentTypedDataMatchesRequest = (toSign, requested, expectedChainIds) => {
387
+ const chainId = toSign.domain?.chainId;
388
+ if (expectedChainIds && typeof chainId === "number" && !expectedChainIds.includes(chainId)) {
389
+ throw new Error(
390
+ `Refusing to sign: typed data chainId ${chainId} is not one of the expected chain ids [${expectedChainIds.join(", ")}] for this environment`
391
+ );
392
+ }
393
+ const signedFields = toSign.typed_data.data;
394
+ if (!signedFields) return;
395
+ if (typeof signedFields.paymentsCount === "string") {
396
+ const count = Number(signedFields.paymentsCount);
397
+ if (!Number.isInteger(count) || count > requested.to.length) {
398
+ throw new Error(
399
+ `Refusing to sign: typed data authorizes ${signedFields.paymentsCount} payments but only ${requested.to.length} were requested`
400
+ );
401
+ }
402
+ }
403
+ if (typeof signedFields.maxAmountPerPayment === "string") {
404
+ const maxRequestedCents = requested.to.reduce(
405
+ (max, recipient) => recipient.amount_cents > max ? recipient.amount_cents : max,
406
+ 0
407
+ );
408
+ if (BigInt(signedFields.maxAmountPerPayment) > BigInt(maxRequestedCents) * CENTS_TO_TOKEN_UNITS) {
409
+ throw new Error(
410
+ `Refusing to sign: typed data authorizes up to ${signedFields.maxAmountPerPayment} token units per payment, above the requested maximum of ${maxRequestedCents} cents`
411
+ );
412
+ }
413
+ }
414
+ };
415
+ class PaymentsApi extends ApiGroup {
416
+ /**
417
+ * Retrieve a list of payments with optional filtering and pagination
418
+ *
419
+ * @param params - Query parameters for filtering and pagination
420
+ * @returns Promise resolving to the list of payments
421
+ * @throws Error if retrieval fails or access denied
422
+ */
423
+ async get(params) {
424
+ return this.request(
425
+ "GET",
426
+ "/v2/payments",
427
+ void 0,
428
+ params
429
+ );
430
+ }
431
+ /**
432
+ * Get typed data for payment signing (manual flow)
433
+ *
434
+ * @param data - Payment request data
435
+ * @param idempotencyKey - Optional idempotency key sent as `x-idempotency-key`.
436
+ * When omitted, the transport generates a fresh one per call. Use a
437
+ * DIFFERENT key for the matching `executePaymentWithSignedData` call — the
438
+ * server takes the payment group id from the signed data, not the header, so
439
+ * the two calls must not share a key (`sendPayment` handles this for you).
440
+ * @returns Promise resolving to the typed data for manual signing
441
+ * @throws Error if typed data generation fails or validation errors occur
442
+ */
443
+ async getPaymentTypedData(data, idempotencyKey) {
444
+ return this.request(
445
+ "POST",
446
+ "/v2/payments",
447
+ data,
448
+ void 0,
449
+ idempotencyKey ? { "x-idempotency-key": idempotencyKey } : void 0
450
+ );
451
+ }
452
+ /**
453
+ * Create and send payment with signature.
454
+ *
455
+ * Before signing, the server-provided typed data is checked against what the
456
+ * caller asked for (chain id, payment count, max per-payment amount) — the
457
+ * SDK refuses to sign an authorization broader than the request. The result
458
+ * carries the prepare step's `failed_payments` and `duplicates` so silently
459
+ * skipped or repeated recipients are visible to the caller.
460
+ */
461
+ async sendPayment(data) {
462
+ const { privateKey, idempotencyKey, ...paymentData } = data;
463
+ const createResponse = await this.getPaymentTypedData(
464
+ paymentData,
465
+ idempotencyKey
466
+ );
467
+ assertPaymentTypedDataMatchesRequest(
468
+ createResponse.data.to_sign,
469
+ paymentData,
470
+ this.client.getExpectedChainIds()
471
+ );
472
+ const { signer, signature } = await this.client.signTypedData(
473
+ createResponse.data.to_sign.domain,
474
+ createResponse.data.to_sign.types,
475
+ createResponse.data.to_sign.typed_data,
476
+ privateKey
477
+ );
478
+ const executeData = {
479
+ ...paymentData,
480
+ signer,
481
+ typed_data: createResponse.data.to_sign.typed_data,
482
+ signature,
483
+ payment_data: createResponse.data.payment_data
484
+ };
485
+ const executeResponse = await this.executePaymentWithSignedData(executeData);
486
+ return {
487
+ ...executeResponse,
488
+ failed_payments: createResponse.data.failed_payments,
489
+ duplicates: createResponse.data.duplicates
490
+ };
491
+ }
492
+ /**
493
+ * Execute payment with pre-signed data (manual flow)
494
+ *
495
+ * @param data - Signed payment data including signature
496
+ * @param idempotencyKey - Optional idempotency key sent as `x-idempotency-key`.
497
+ * When omitted, the transport generates a fresh one per call. Use a fresh
498
+ * key here (not the one from `getPaymentTypedData`) — the server takes the
499
+ * payment group id from the signed data, so reusing the prepare key only
500
+ * collides with the request-dedup window.
501
+ * @returns Promise resolving to the executed payment response
502
+ * @throws Error if execution fails or signature is invalid
503
+ */
504
+ async executePaymentWithSignedData(data, idempotencyKey) {
505
+ return this.request(
506
+ "PUT",
507
+ "/v2/payments",
508
+ data,
509
+ void 0,
510
+ idempotencyKey ? { "x-idempotency-key": idempotencyKey } : void 0
511
+ );
512
+ }
513
+ }
514
+ class PayrollApi extends ApiGroup {
515
+ /**
516
+ * Enable payroll functionality for a specific team
517
+ *
518
+ * @param data - Payroll enablement configuration data
519
+ * @returns Promise resolving to the payroll enablement response
520
+ * @throws Error if enablement fails or team not found
521
+ */
522
+ async enable(params, data) {
523
+ return this.request(
524
+ "POST",
525
+ apiPath`/v2/payroll/team/${params.team_nanoid}`,
526
+ data
527
+ );
528
+ }
529
+ /**
530
+ * Retrieve payroll information for a specific team
531
+ *
532
+ * @param params - Team payroll parameters including team nanoid
533
+ * @returns Promise resolving to the team payroll information
534
+ * @throws Error if retrieval fails or team not found
535
+ */
536
+ async getTeamPayroll(params) {
537
+ return this.request(
538
+ "GET",
539
+ apiPath`/v2/payroll/team/${params.team_nanoid}`
540
+ );
541
+ }
542
+ }
543
+ class TeamApi extends ApiGroup {
544
+ }
545
+ class UserApi extends ApiGroup {
546
+ /**
547
+ * Update the address information for the current user
548
+ *
549
+ * @param data - Updated user address information
550
+ * @returns Promise that resolves when update is complete
551
+ * @throws Error if update fails or validation errors occur
552
+ */
553
+ async updateAddress(data) {
554
+ return this.request("PUT", "/v2/user/address", data);
555
+ }
556
+ /**
557
+ * Update the avatar/profile picture for the current user
558
+ *
559
+ * @param data - Avatar update data including image file
560
+ * @returns Promise resolving to the avatar update response
561
+ * @throws Error if update fails or file validation errors occur
562
+ */
563
+ async updateAvatar(data) {
564
+ return this.request(
565
+ "POST",
566
+ "/v2/user/avatar/update",
567
+ data
568
+ );
569
+ }
570
+ /**
571
+ * Retrieve all companies associated with the current user
572
+ *
573
+ * @returns Promise resolving to the list of user's companies
574
+ * @throws Error if retrieval fails or authentication invalid
575
+ */
576
+ async getCompanies() {
577
+ return this.request("GET", "/v2/user/organizations");
578
+ }
579
+ /**
580
+ * Retrieve all teams associated with the current user
581
+ *
582
+ * @returns Promise resolving to the list of user's teams
583
+ * @throws Error if retrieval fails or authentication invalid
584
+ */
585
+ async getTeams() {
586
+ return this.request("GET", "/v2/user/teams");
587
+ }
588
+ }
589
+ let RiseApiClient$1 = class RiseApiClient {
590
+ baseUrl;
591
+ jwtToken;
592
+ riseId;
593
+ privateKey;
594
+ timeout;
595
+ headers;
596
+ isGeneratingJwt = false;
597
+ // API Groups
598
+ webhooks;
599
+ auth;
600
+ company;
601
+ entityBalance;
602
+ invites;
603
+ me;
604
+ payments;
605
+ payroll;
606
+ team;
607
+ user;
608
+ constructor(options) {
609
+ if (!(options.jwtToken || options.riseIdAuth)) {
610
+ throw new Error(
611
+ "Either JWT token or riseIdAuth configuration is required for authentication"
612
+ );
613
+ }
614
+ if (options.riseIdAuth) {
615
+ if (!(options.riseIdAuth.riseId && options.riseIdAuth.privateKey)) {
616
+ throw new Error(
617
+ "riseIdAuth configuration must include both riseId and privateKey"
618
+ );
619
+ }
620
+ if (!options.riseIdAuth.riseId.startsWith("0x") || options.riseIdAuth.riseId.length !== RISE_ID_LENGTH) {
621
+ throw new Error(
622
+ "Rise ID should be a valid hex address starting with 0x and exactly 42 characters long"
623
+ );
624
+ }
625
+ if (!options.riseIdAuth.privateKey.startsWith("0x") || options.riseIdAuth.privateKey.length !== PRIVATE_KEY_LENGTH) {
626
+ throw new Error(
627
+ "Private key should be a valid hex string starting with 0x and 64 characters long"
628
+ );
629
+ }
630
+ }
631
+ if (options.baseUrl) {
632
+ const isHttps = options.baseUrl.startsWith("https://");
633
+ const isLocalhost = /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/.test(options.baseUrl);
634
+ if (!(isHttps || isLocalhost)) {
635
+ throw new Error(
636
+ `baseUrl must use https:// (got "${options.baseUrl}"). Plaintext http:// is only allowed for localhost.`
637
+ );
638
+ }
639
+ this.baseUrl = options.baseUrl;
640
+ } else {
641
+ const environment = options.environment ?? "prod";
642
+ this.baseUrl = RISE_API_URLS[environment];
643
+ }
644
+ this.jwtToken = options.jwtToken ?? "";
645
+ this.riseId = options.riseIdAuth?.riseId ?? "";
646
+ this.privateKey = options.riseIdAuth?.privateKey ?? "";
647
+ this.timeout = options.timeout ?? 3e4;
648
+ const authHeader = this.jwtToken ? `Bearer ${this.jwtToken}` : "";
649
+ this.headers = {
650
+ "Content-Type": "application/json",
651
+ Authorization: authHeader,
652
+ ...options.headers
653
+ };
654
+ this.webhooks = new WebhooksApi(this);
655
+ this.auth = new AuthApi(this);
656
+ this.company = new CompanyApi(this);
657
+ this.entityBalance = new EntityBalanceApi(this);
658
+ this.invites = new InvitesApi(this);
659
+ this.me = new MeApi(this);
660
+ this.payments = new PaymentsApi(this);
661
+ this.payroll = new PayrollApi(this);
662
+ this.team = new TeamApi(this);
663
+ this.user = new UserApi(this);
664
+ }
665
+ /**
666
+ * Update the JWT token for authentication
667
+ *
668
+ * @param newToken - New JWT token to use for authentication
669
+ * @throws Error if the token is empty or invalid
670
+ */
671
+ updateToken(newToken) {
672
+ if (!newToken) {
673
+ throw new Error("JWT token cannot be empty");
674
+ }
675
+ this.jwtToken = newToken;
676
+ this.headers.Authorization = `Bearer ${this.jwtToken}`;
677
+ }
678
+ /**
679
+ * Get the current JWT token
680
+ *
681
+ * @returns The current JWT token string
682
+ */
683
+ getToken() {
684
+ return this.jwtToken;
685
+ }
686
+ /**
687
+ * Check if the client is using JWT token authentication
688
+ *
689
+ * @returns True if using JWT token, false otherwise
690
+ */
691
+ isUsingJwtToken() {
692
+ return !!this.jwtToken;
693
+ }
694
+ /**
695
+ * Check if the client is using Rise ID and private key authentication
696
+ *
697
+ * @returns True if using Rise ID auth, false otherwise
698
+ */
699
+ isUsingRiseIdAuth() {
700
+ return !!(this.riseId && this.privateKey);
701
+ }
702
+ /**
703
+ * Get the Rise ID used for authentication
704
+ *
705
+ * @returns The Rise ID string
706
+ */
707
+ getRiseId() {
708
+ return this.riseId;
709
+ }
710
+ /**
711
+ * Resolve the signing key (per-call override or the configured key) or throw.
712
+ * Private on purpose — the raw key is never handed out; callers sign via
713
+ * {@link signTypedData} / {@link signMessage} and receive only the signature.
714
+ */
715
+ resolveSigningKey(overrideKey) {
716
+ const key = overrideKey ?? this.privateKey;
717
+ if (!key) {
718
+ throw new Error(
719
+ "A private key is required to sign. Provide privateKey in the request or configure riseIdAuth when creating the client."
720
+ );
721
+ }
722
+ return key;
723
+ }
724
+ /**
725
+ * Sign EIP-712 typed data with the configured key (or a per-call override).
726
+ * The key is used in-process and never returned, so it cannot be exfiltrated.
727
+ *
728
+ * @returns The signer address and the signature
729
+ */
730
+ async signTypedData(domain, types, value, overrideKey) {
731
+ const wallet = new ethers.ethers.Wallet(this.resolveSigningKey(overrideKey));
732
+ const signature = await wallet.signTypedData(domain, types, value);
733
+ return { signer: wallet.address, signature };
734
+ }
735
+ /**
736
+ * Sign a plain message with the configured key (or a per-call override).
737
+ *
738
+ * @returns The signer address and the signature
739
+ */
740
+ async signMessage(message, overrideKey) {
741
+ const wallet = new ethers.ethers.Wallet(this.resolveSigningKey(overrideKey));
742
+ const signature = await wallet.signMessage(message);
743
+ return { signer: wallet.address, signature };
744
+ }
745
+ /**
746
+ * Build an error for a failed API response. The status line goes in the
747
+ * message; the raw response body is attached as a NON-enumerable `responseBody`
748
+ * so it can be inspected deliberately but does not leak into
749
+ * JSON.stringify(error) or structured loggers that serialize error fields.
750
+ */
751
+ buildApiError(status, statusText, body) {
752
+ const error = new Error(`API request failed: ${status} ${statusText}`);
753
+ Object.defineProperty(error, "status", {
754
+ value: status,
755
+ enumerable: false
756
+ });
757
+ Object.defineProperty(error, "responseBody", {
758
+ value: body,
759
+ enumerable: false
760
+ });
761
+ return error;
762
+ }
763
+ /**
764
+ * Manually refresh the JWT token using Rise ID and private key
765
+ *
766
+ * @returns Promise resolving to the new JWT token
767
+ * @throws Error if Rise ID or private key not provided
768
+ */
769
+ async refreshJwtToken() {
770
+ if (!(this.privateKey && this.riseId)) {
771
+ throw new Error("Rise ID and private key are required for JWT refresh");
772
+ }
773
+ return await this.generateJwtToken();
774
+ }
775
+ /**
776
+ * Hostname of the configured base URL, or '' when it does not parse.
777
+ * Environment/chain checks compare hostnames, never URL substrings — a
778
+ * substring like 'riseworks.io' also matches attacker hosts such as
779
+ * evil.example/riseworks.io.
780
+ */
781
+ getBaseHostname() {
782
+ try {
783
+ return new URL(this.baseUrl).hostname;
784
+ } catch {
785
+ return "";
786
+ }
787
+ }
788
+ /**
789
+ * Get the current API environment
790
+ *
791
+ * @returns The current environment ('dev', 'stg', or 'prod')
792
+ */
793
+ getEnvironment() {
794
+ if (this.baseUrl === RISE_API_URLS.dev) return "dev";
795
+ if (this.baseUrl === RISE_API_URLS.stg) return "stg";
796
+ if (this.baseUrl === RISE_API_URLS.prod) return "prod";
797
+ const hostname = this.getBaseHostname();
798
+ if (hostname === "dev-riseworks.io" || hostname.endsWith(".dev-riseworks.io")) {
799
+ return "dev";
800
+ }
801
+ if (hostname === "staging-riseworks.io" || hostname.endsWith(".staging-riseworks.io")) {
802
+ return "stg";
803
+ }
804
+ return "prod";
805
+ }
806
+ /**
807
+ * Update the API environment and base URL
808
+ *
809
+ * @param environment - New environment to switch to
810
+ */
811
+ updateEnvironment(environment) {
812
+ this.baseUrl = RISE_API_URLS[environment];
813
+ }
814
+ /**
815
+ * Chain ids `sendPayment` will sign typed data for, resolved from the base
816
+ * URL. Null for custom base URLs (local nodes, proxies) — the pre-signing
817
+ * chain check is skipped there, since the expected chain is unknowable.
818
+ */
819
+ getExpectedChainIds() {
820
+ const hostname = this.getBaseHostname();
821
+ const riseDomains = [
822
+ "riseworks.io",
823
+ "dev-riseworks.io",
824
+ "staging-riseworks.io"
825
+ ];
826
+ const isKnownRiseUrl = Object.values(RISE_API_URLS).some((url) => url === this.baseUrl) || riseDomains.some(
827
+ (domain) => hostname === domain || hostname.endsWith(`.${domain}`)
828
+ );
829
+ return isKnownRiseUrl ? EXPECTED_CHAIN_IDS[this.getEnvironment()] : null;
830
+ }
831
+ /**
832
+ * Generate a JWT token using Sign-In with Ethereum (SIWE) authentication
833
+ *
834
+ * @returns Promise resolving to the generated JWT token
835
+ * @throws Error if Rise ID or private key not provided, or if generation fails
836
+ */
837
+ async generateJwtToken() {
838
+ if (!(this.riseId && this.privateKey)) {
839
+ throw new Error("Rise ID and private key are required for JWT generation");
840
+ }
841
+ if (this.isGeneratingJwt) {
842
+ throw new Error("JWT generation already in progress");
843
+ }
844
+ try {
845
+ await ethers.ethers.resolveAddress(this.riseId);
846
+ } catch (error) {
847
+ throw new Error(
848
+ `Invalid Rise ID address: ${error instanceof Error ? error.message : "Address validation failed"}`
849
+ );
850
+ }
851
+ this.isGeneratingJwt = true;
852
+ try {
853
+ const wallet = new ethers.ethers.Wallet(this.privateKey);
854
+ const walletAddress = wallet.address;
855
+ const siweUrl = `${this.baseUrl}/v2/auth/siwe?wallet=${walletAddress}&riseid=${this.riseId}`;
856
+ const siweResponse = await fetch(siweUrl);
857
+ if (!siweResponse.ok) {
858
+ const errorData = await siweResponse.json();
859
+ throw new Error(
860
+ `HTTP ${siweResponse.status}: ${errorData.data || "Unknown error"}`
861
+ );
862
+ }
863
+ const siweData = await siweResponse.json();
864
+ if (!siweData.success) {
865
+ throw new Error(siweData.data || "Failed to get SIWE message");
866
+ }
867
+ const siweMessage = siweData.data.siwe;
868
+ const signature = await wallet.signMessage(siweMessage);
869
+ const nonceMatch = siweMessage.match(/Nonce: (.+)/);
870
+ const nonce = nonceMatch?.[1]?.trim() ?? "";
871
+ if (!nonce) {
872
+ throw new Error("Could not extract nonce from SIWE message");
873
+ }
874
+ const verifyResponse = await fetch(`${this.baseUrl}/v2/auth/verify`, {
875
+ method: "POST",
876
+ headers: {
877
+ "Content-Type": "application/json"
878
+ },
879
+ body: JSON.stringify({
880
+ message: siweMessage,
881
+ sig: signature,
882
+ nonce
883
+ })
884
+ });
885
+ if (!verifyResponse.ok) {
886
+ const errorData = await verifyResponse.json();
887
+ throw new Error(
888
+ `HTTP ${verifyResponse.status}: ${errorData.data || "Verification failed"}`
889
+ );
890
+ }
891
+ const verifyData = await verifyResponse.json();
892
+ if (!verifyData.success) {
893
+ throw new Error(verifyData.data || "Signature verification failed");
894
+ }
895
+ const jwtToken = verifyData.data.jwt;
896
+ this.jwtToken = jwtToken;
897
+ this.headers.Authorization = `Bearer ${this.jwtToken}`;
898
+ return jwtToken;
899
+ } finally {
900
+ this.isGeneratingJwt = false;
901
+ }
902
+ }
903
+ /**
904
+ * Check if JWT token is valid by making a test request
905
+ */
906
+ async isJwtValid() {
907
+ if (!this.jwtToken) {
908
+ return false;
909
+ }
910
+ try {
911
+ const response = await fetch(`${this.baseUrl}/v2/me`, {
912
+ method: "GET",
913
+ headers: this.headers,
914
+ signal: AbortSignal.timeout(this.timeout)
915
+ });
916
+ return response.ok;
917
+ } catch {
918
+ return false;
919
+ }
920
+ }
921
+ /**
922
+ * Helper function to parse response body from different formats
923
+ */
924
+ async parseResponseBody(response) {
925
+ try {
926
+ return await response.json();
927
+ } catch {
928
+ try {
929
+ const text = await response.text();
930
+ return JSON.parse(text);
931
+ } catch {
932
+ return {};
933
+ }
934
+ }
935
+ }
936
+ /**
937
+ * Makes an HTTP request with proper error handling and type safety
938
+ */
939
+ async request(method, path, data, query, extraHeaders) {
940
+ if (this.riseId && this.privateKey && !(this.jwtToken && await this.isJwtValid())) {
941
+ try {
942
+ await this.generateJwtToken();
943
+ } catch (error) {
944
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
945
+ throw new Error(`Failed to generate JWT token: ${errorMessage}`);
946
+ }
947
+ }
948
+ const methodUpper = method.toUpperCase();
949
+ const autoIdempotencyKey = (methodUpper === "POST" || methodUpper === "PUT") && !extraHeaders?.["x-idempotency-key"] ? { "x-idempotency-key": generateIdempotencyKey() } : void 0;
950
+ const headers = extraHeaders || autoIdempotencyKey ? { ...this.headers, ...extraHeaders, ...autoIdempotencyKey } : this.headers;
951
+ const url = new URL(path, this.baseUrl);
952
+ if (query) {
953
+ for (const [key, value] of Object.entries(query)) {
954
+ if (value === void 0 || value === null) continue;
955
+ if (Array.isArray(value)) {
956
+ for (const item of value) {
957
+ if (item !== void 0 && item !== null) {
958
+ url.searchParams.append(key, String(item));
959
+ }
960
+ }
961
+ } else {
962
+ url.searchParams.append(key, String(value));
963
+ }
964
+ }
965
+ }
966
+ const controller = new AbortController();
967
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
968
+ try {
969
+ const response = await fetch(url.toString(), {
970
+ method,
971
+ headers,
972
+ body: data ? JSON.stringify(data) : void 0,
973
+ signal: controller.signal
974
+ });
975
+ clearTimeout(timeoutId);
976
+ if (!response.ok) {
977
+ const isIdempotent = ["GET", "HEAD", "OPTIONS"].includes(
978
+ method.toUpperCase()
979
+ );
980
+ if (response.status === 401 && this.riseId && this.privateKey && isIdempotent) {
981
+ clearTimeout(timeoutId);
982
+ try {
983
+ await this.generateJwtToken();
984
+ } catch (jwtError) {
985
+ const errorMessage = jwtError instanceof Error ? jwtError.message : "Unknown error";
986
+ throw new Error(`Failed to refresh JWT token: ${errorMessage}`);
987
+ }
988
+ const retryHeaders = extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers;
989
+ const retryController = new AbortController();
990
+ const retryTimeoutId = setTimeout(
991
+ () => retryController.abort(),
992
+ this.timeout
993
+ );
994
+ try {
995
+ const retryResponse = await fetch(url.toString(), {
996
+ method,
997
+ headers: retryHeaders,
998
+ body: data ? JSON.stringify(data) : void 0,
999
+ signal: retryController.signal
1000
+ });
1001
+ if (!retryResponse.ok) {
1002
+ const errorData2 = await this.parseResponseBody(retryResponse);
1003
+ throw this.buildApiError(
1004
+ retryResponse.status,
1005
+ retryResponse.statusText,
1006
+ errorData2
1007
+ );
1008
+ }
1009
+ return await this.parseResponseBody(retryResponse);
1010
+ } finally {
1011
+ clearTimeout(retryTimeoutId);
1012
+ }
1013
+ }
1014
+ const errorData = await this.parseResponseBody(response);
1015
+ throw this.buildApiError(
1016
+ response.status,
1017
+ response.statusText,
1018
+ errorData
1019
+ );
1020
+ }
1021
+ return await this.parseResponseBody(response);
1022
+ } catch (error) {
1023
+ clearTimeout(timeoutId);
1024
+ if (error instanceof Error) {
1025
+ throw error;
1026
+ }
1027
+ throw new Error("Request failed");
1028
+ }
1029
+ }
1030
+ };
1031
+
1032
+ class BillPayApi {
1033
+ constructor(client) {
1034
+ this.client = client;
1035
+ }
1036
+ async listRecipients(params) {
1037
+ return this.client.rawRequest(
1038
+ "GET",
1039
+ apiPath`/v2/payments/teams/${params.team_nanoid}/external_entity`
1040
+ );
1041
+ }
1042
+ async createRecipient(params, data) {
1043
+ return this.client.rawRequest(
1044
+ "POST",
1045
+ apiPath`/v2/payments/teams/${params.team_nanoid}/external_entity/`,
1046
+ data
1047
+ );
1048
+ }
1049
+ async prepareInstantPayment(data) {
1050
+ return this.client.rawRequest(
1051
+ "POST",
1052
+ "/v2/payments/external_entity/instant",
1053
+ data
1054
+ );
1055
+ }
1056
+ async create(data) {
1057
+ return this.prepareInstantPayment(data);
1058
+ }
1059
+ async executeInstantPayment(data) {
1060
+ return this.client.rawRequest(
1061
+ "PUT",
1062
+ "/v2/payments/external_entity/instant",
1063
+ data
1064
+ );
1065
+ }
1066
+ async execute(data) {
1067
+ return this.executeInstantPayment(data);
1068
+ }
1069
+ async sendInstantPayment(data) {
1070
+ const { privateKey, ...paymentData } = data;
1071
+ const createResponse = await this.prepareInstantPayment(paymentData);
1072
+ const { signer, signature } = await this.client.signTypedData(
1073
+ createResponse.data.domain,
1074
+ createResponse.data.types,
1075
+ createResponse.data.typed_data,
1076
+ privateKey
1077
+ );
1078
+ return this.executeInstantPayment({
1079
+ ...paymentData,
1080
+ signer,
1081
+ typed_data: createResponse.data.typed_data,
1082
+ signature
1083
+ });
1084
+ }
1085
+ async send(data) {
1086
+ return this.sendInstantPayment(data);
1087
+ }
1088
+ }
1089
+ class RiseApiClient extends RiseApiClient$1 {
1090
+ organizations;
1091
+ billPay;
1092
+ teams;
1093
+ constructor(options) {
1094
+ super(options);
1095
+ const client = this;
1096
+ Object.assign(this.webhooks, {
1097
+ delete: async (webhookNanoid) => client.rawRequest(
1098
+ "DELETE",
1099
+ apiPath`/v2/webhooks/${webhookNanoid}`
1100
+ ),
1101
+ listDeliveries: async (webhookNanoid, params) => this.webhooks.getDeliveryHistory(webhookNanoid, params)
1102
+ });
1103
+ Object.assign(this.payments, {
1104
+ prepare: async (data, idempotencyKey) => this.payments.getPaymentTypedData(data, idempotencyKey),
1105
+ create: async (data, idempotencyKey) => this.payments.getPaymentTypedData(data, idempotencyKey),
1106
+ execute: async (data, idempotencyKey) => this.payments.executePaymentWithSignedData(data, idempotencyKey)
1107
+ });
1108
+ Object.assign(this.invites, {
1109
+ prepareManagerInvite: async (data) => this.invites.getManagerInviteTypedData(data),
1110
+ executeManagerInvite: async (data) => this.invites.executeManagerInviteWithSignedData(data)
1111
+ });
1112
+ Object.assign(this.team, {
1113
+ create: async (data) => client.rawRequest("POST", "/v2/teams", data),
1114
+ get: async (params) => client.rawRequest(
1115
+ "GET",
1116
+ apiPath`/v2/teams/${params.team_nanoid}`
1117
+ ),
1118
+ update: async (paramsOrData, data) => {
1119
+ const params = data ? paramsOrData : { team_nanoid: paramsOrData.team_nanoid };
1120
+ const updateData = data ?? paramsOrData;
1121
+ return client.rawRequest(
1122
+ "PUT",
1123
+ apiPath`/v2/teams/${params.team_nanoid}`,
1124
+ updateData
1125
+ );
1126
+ },
1127
+ delete: async (params) => client.rawRequest(
1128
+ "DELETE",
1129
+ apiPath`/v2/teams/${params.team_nanoid}`
1130
+ ),
1131
+ getUsers: async (params) => client.rawRequest(
1132
+ "GET",
1133
+ apiPath`/v2/teams/${params.team_nanoid}/users`
1134
+ ),
1135
+ getSettings: async (params) => client.rawRequest(
1136
+ "GET",
1137
+ apiPath`/v2/teams/${params.team_nanoid}/settings`
1138
+ ),
1139
+ updateSettings: async (params, data) => client.rawRequest(
1140
+ "PUT",
1141
+ apiPath`/v2/teams/${params.team_nanoid}/settings`,
1142
+ data
1143
+ ),
1144
+ getMemberSettings: async (params) => client.rawRequest(
1145
+ "GET",
1146
+ apiPath`/v2/teams/${params.team_nanoid}/member/${params.user_nanoid}/settings`
1147
+ ),
1148
+ updateMemberSettings: async (params, data) => client.rawRequest(
1149
+ "PUT",
1150
+ apiPath`/v2/teams/${params.team_nanoid}/member/${params.user_nanoid}/settings`,
1151
+ data
1152
+ ),
1153
+ getMemberSummary: async (params) => client.rawRequest(
1154
+ "GET",
1155
+ apiPath`/v2/teams/${params.team_nanoid}/member/${params.user_nanoid}/summary`
1156
+ ),
1157
+ removeMember: async (params) => client.rawRequest(
1158
+ "DELETE",
1159
+ apiPath`/v2/teams/${params.team_nanoid}/member/${params.user_nanoid}`
1160
+ )
1161
+ });
1162
+ Object.assign(this.user, {
1163
+ // Alias for the generated getCompanies (same /v2/user/organizations route).
1164
+ getOrganizations: async () => client.rawRequest(
1165
+ "GET",
1166
+ "/v2/user/organizations"
1167
+ )
1168
+ });
1169
+ this.organizations = this.company;
1170
+ this.billPay = new BillPayApi(client);
1171
+ this.teams = this.team;
1172
+ }
1173
+ rawRequest(method, path, data, query) {
1174
+ return this.request(method, path, data, query);
1175
+ }
1176
+ }
1177
+
1178
+ const getUnixTimestamp = () => Math.floor(Date.now() / 1e3);
1179
+ class WebhookValidationError extends Error {
1180
+ constructor(message, code) {
1181
+ super(message);
1182
+ this.code = code;
1183
+ this.name = "WebhookValidationError";
1184
+ }
1185
+ }
1186
+ class WebhookValidator {
1187
+ secret;
1188
+ tolerance;
1189
+ constructor(secret, options) {
1190
+ if (!secret) {
1191
+ throw new Error("Webhook secret is required");
1192
+ }
1193
+ this.secret = secret;
1194
+ this.tolerance = options?.tolerance ?? 600;
1195
+ }
1196
+ /**
1197
+ * Validates a webhook event and returns the typed event object
1198
+ *
1199
+ * Throws an error if validation fails. Use validateEventSafe() for non-throwing validation.
1200
+ *
1201
+ * @param requestBody - The raw webhook request body (string or Buffer). Pass the raw bytes, NOT a parsed object — re-serializing changes the bytes and breaks signature verification.
1202
+ * @param signature - The X-Rise-Signature header value from the webhook request
1203
+ * @param options - Optional validation configuration
1204
+ * @returns The parsed and typed webhook event
1205
+ * @throws WebhookValidationError if validation fails
1206
+ *
1207
+ */
1208
+ validateEvent(requestBody, signature, options) {
1209
+ const result = this.validateEventSafe(requestBody, signature, options);
1210
+ if (!(result.event && result.isValid)) {
1211
+ throw new WebhookValidationError(
1212
+ result.error ?? "Webhook validation failed",
1213
+ result.code ?? (result.timestampValid === false ? "TIMESTAMP_TOO_OLD" : "INVALID_SIGNATURE")
1214
+ );
1215
+ }
1216
+ return result.event;
1217
+ }
1218
+ /**
1219
+ * Safely validates a webhook event without throwing errors
1220
+ *
1221
+ * Returns a result object instead of throwing. Use validateEvent() for throwing validation.
1222
+ *
1223
+ * @param requestBody - The raw webhook request body (string or Buffer). Pass the raw bytes, NOT a parsed object — re-serializing changes the bytes and breaks signature verification.
1224
+ * @param signature - The X-Rise-Signature header value from the webhook request
1225
+ * @param options - Optional validation configuration
1226
+ * @returns WebhookValidationResult containing validation status and event data
1227
+ *
1228
+ */
1229
+ validateEventSafe(requestBody, signature, options = {}) {
1230
+ const { tolerance = this.tolerance } = options;
1231
+ try {
1232
+ const body = requestBody;
1233
+ if (body === null || body === void 0) {
1234
+ throw new WebhookValidationError(
1235
+ "Request body cannot be null or undefined.",
1236
+ "MALFORMED_PAYLOAD"
1237
+ );
1238
+ }
1239
+ if (typeof body !== "string" && !Buffer.isBuffer(body)) {
1240
+ throw new WebhookValidationError(
1241
+ `Invalid request body type: ${typeof body}. Expected the raw string or Buffer body (a parsed object would break signature verification).`,
1242
+ "MALFORMED_PAYLOAD"
1243
+ );
1244
+ }
1245
+ const payloadString = typeof body === "string" ? body : body.toString("utf8");
1246
+ if (!signature) {
1247
+ return {
1248
+ isValid: false,
1249
+ code: "INVALID_SIGNATURE",
1250
+ error: "Missing or invalid signature header"
1251
+ };
1252
+ }
1253
+ const signatureParts = this.parseSignatureHeader(signature);
1254
+ if (!signatureParts) {
1255
+ return {
1256
+ isValid: false,
1257
+ code: "INVALID_SIGNATURE",
1258
+ error: "Invalid signature format - expected format: t=timestamp,v1=hash"
1259
+ };
1260
+ }
1261
+ const { timestamp, hash } = signatureParts;
1262
+ const currentTime = getUnixTimestamp();
1263
+ const timeDiff = currentTime - timestamp;
1264
+ if (timeDiff > tolerance) {
1265
+ return {
1266
+ isValid: false,
1267
+ code: "TIMESTAMP_TOO_OLD",
1268
+ error: `Timestamp too old (${timeDiff}s ago, tolerance: ${tolerance}s)`,
1269
+ timestampValid: false
1270
+ };
1271
+ }
1272
+ if (timestamp > currentTime + 60) {
1273
+ return {
1274
+ isValid: false,
1275
+ code: "TIMESTAMP_TOO_OLD",
1276
+ error: `Timestamp is in the future (${timestamp - currentTime}s ahead)`,
1277
+ timestampValid: false
1278
+ };
1279
+ }
1280
+ const expectedHash = this.generateSignature(timestamp, payloadString);
1281
+ const signatureValid = this.compareSignatures(hash, expectedHash);
1282
+ if (!signatureValid) {
1283
+ return {
1284
+ isValid: false,
1285
+ code: "INVALID_SIGNATURE",
1286
+ error: "Invalid signature",
1287
+ timestampValid: true
1288
+ };
1289
+ }
1290
+ const payloadObject = JSON.parse(payloadString);
1291
+ if (payloadObject.event_version?.includes("2.")) {
1292
+ switch (payloadObject.event_type) {
1293
+ case "payment.sent":
1294
+ return {
1295
+ isValid: true,
1296
+ event: payloadObject,
1297
+ timestampValid: true
1298
+ };
1299
+ case "withdraw_account.duplicated_detected":
1300
+ return {
1301
+ isValid: true,
1302
+ event: payloadObject,
1303
+ timestampValid: true
1304
+ };
1305
+ case "invite.accepted":
1306
+ return {
1307
+ isValid: true,
1308
+ event: payloadObject,
1309
+ timestampValid: true
1310
+ };
1311
+ case "deposit.received":
1312
+ return {
1313
+ isValid: true,
1314
+ event: payloadObject,
1315
+ timestampValid: true
1316
+ };
1317
+ case "payment.group.created":
1318
+ return {
1319
+ isValid: true,
1320
+ event: payloadObject,
1321
+ timestampValid: true
1322
+ };
1323
+ default:
1324
+ return {
1325
+ isValid: false,
1326
+ code: "UNKNOWN_EVENT_TYPE",
1327
+ error: `Unknown event type: ${payloadObject.event_type}`,
1328
+ timestampValid: true
1329
+ };
1330
+ }
1331
+ }
1332
+ return {
1333
+ isValid: false,
1334
+ code: "UNKNOWN_EVENT_TYPE",
1335
+ error: `Unknown event version: ${payloadObject.event_version}`,
1336
+ timestampValid: true
1337
+ };
1338
+ } catch (error) {
1339
+ return {
1340
+ isValid: false,
1341
+ code: error instanceof WebhookValidationError ? error.code : "MALFORMED_PAYLOAD",
1342
+ error: error instanceof Error ? error.message : "Unknown validation error"
1343
+ };
1344
+ }
1345
+ }
1346
+ /**
1347
+ * Generates a signature for a given timestamp and payload
1348
+ *
1349
+ * Useful for testing or custom validation logic.
1350
+ *
1351
+ * @param timestamp - Unix timestamp in seconds
1352
+ * @param payload - The webhook payload as a string
1353
+ * @returns The generated signature as a hexadecimal string
1354
+ */
1355
+ generateSignature(timestamp, payload) {
1356
+ const signedPayload = `${timestamp}.${payload}`;
1357
+ return crypto__namespace.createHmac("sha256", this.secret).update(signedPayload).digest("hex");
1358
+ }
1359
+ /**
1360
+ * Parses the X-Rise-Signature header (format: t=timestamp,v1=hash)
1361
+ *
1362
+ * @param signature - The X-Rise-Signature header value
1363
+ * @returns Object containing timestamp and hash, or null if parsing fails
1364
+ */
1365
+ parseSignatureHeader(signature) {
1366
+ try {
1367
+ const parts = signature.split(",");
1368
+ if (parts.length !== 2) return null;
1369
+ const timestampPart = parts[0]?.split("=");
1370
+ const hashPart = parts[1]?.split("=");
1371
+ if (timestampPart?.length !== 2 || hashPart?.length !== 2) return null;
1372
+ if (timestampPart[0] !== "t" || hashPart[0] !== "v1") return null;
1373
+ if (!timestampPart[1]) return null;
1374
+ const timestamp = Number.parseInt(timestampPart[1], 10);
1375
+ const hash = hashPart[1];
1376
+ if (Number.isNaN(timestamp) || !hash) return null;
1377
+ return { timestamp, hash };
1378
+ } catch {
1379
+ return null;
1380
+ }
1381
+ }
1382
+ /**
1383
+ * Compares two signatures using timing-safe comparison
1384
+ *
1385
+ * @param received - The signature received in the webhook request
1386
+ * @param expected - The signature calculated from the payload and secret
1387
+ * @returns True if signatures match, false otherwise
1388
+ */
1389
+ compareSignatures(received, expected) {
1390
+ try {
1391
+ const receivedBuffer = Buffer.from(received, "hex");
1392
+ const expectedBuffer = Buffer.from(expected, "hex");
1393
+ if (receivedBuffer.length !== expectedBuffer.length) {
1394
+ return false;
1395
+ }
1396
+ return crypto__namespace.timingSafeEqual(receivedBuffer, expectedBuffer);
1397
+ } catch {
1398
+ return false;
1399
+ }
1400
+ }
1401
+ }
1402
+
1403
+ exports.GeneratedRiseApiClient = RiseApiClient$1;
1404
+ exports.RiseApiClient = RiseApiClient;
1405
+ exports.WebhookValidationError = WebhookValidationError;
1406
+ exports.WebhookValidator = WebhookValidator;