@xkova/sdk 0.1.0-alpha.1

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 (76) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +453 -0
  3. package/dist/abi.d.ts +10288 -0
  4. package/dist/abi.js +6645 -0
  5. package/dist/client.d.ts +80 -0
  6. package/dist/client.js +251 -0
  7. package/dist/cursor.d.ts +21 -0
  8. package/dist/cursor.js +28 -0
  9. package/dist/errors.d.ts +31 -0
  10. package/dist/errors.js +63 -0
  11. package/dist/generated/types.d.ts +13371 -0
  12. package/dist/generated/types.js +3 -0
  13. package/dist/idempotency.d.ts +25 -0
  14. package/dist/idempotency.js +46 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.js +8 -0
  17. package/dist/member-action-intents.d.ts +72 -0
  18. package/dist/member-action-intents.js +181 -0
  19. package/dist/member-bff.d.ts +535 -0
  20. package/dist/member-bff.js +1594 -0
  21. package/dist/member-resources.d.ts +203 -0
  22. package/dist/member-resources.js +56 -0
  23. package/dist/request-signing.d.ts +23 -0
  24. package/dist/request-signing.js +112 -0
  25. package/dist/resources/account-holders.d.ts +295 -0
  26. package/dist/resources/account-holders.js +166 -0
  27. package/dist/resources/base.d.ts +47 -0
  28. package/dist/resources/base.js +25 -0
  29. package/dist/resources/branding.d.ts +154 -0
  30. package/dist/resources/branding.js +34 -0
  31. package/dist/resources/chains.d.ts +6 -0
  32. package/dist/resources/chains.js +6 -0
  33. package/dist/resources/contacts.d.ts +55 -0
  34. package/dist/resources/contacts.js +22 -0
  35. package/dist/resources/escrows.d.ts +45 -0
  36. package/dist/resources/escrows.js +18 -0
  37. package/dist/resources/fee-schedules.d.ts +40 -0
  38. package/dist/resources/fee-schedules.js +15 -0
  39. package/dist/resources/index.d.ts +21 -0
  40. package/dist/resources/index.js +21 -0
  41. package/dist/resources/integration-gateways.d.ts +114 -0
  42. package/dist/resources/integration-gateways.js +27 -0
  43. package/dist/resources/onboarding.d.ts +18 -0
  44. package/dist/resources/onboarding.js +9 -0
  45. package/dist/resources/payment-requests.d.ts +115 -0
  46. package/dist/resources/payment-requests.js +27 -0
  47. package/dist/resources/payments.d.ts +182 -0
  48. package/dist/resources/payments.js +33 -0
  49. package/dist/resources/ramps.d.ts +163 -0
  50. package/dist/resources/ramps.js +44 -0
  51. package/dist/resources/rwa.d.ts +397 -0
  52. package/dist/resources/rwa.js +150 -0
  53. package/dist/resources/tenant.d.ts +136 -0
  54. package/dist/resources/tenant.js +42 -0
  55. package/dist/resources/tokenization.d.ts +394 -0
  56. package/dist/resources/tokenization.js +126 -0
  57. package/dist/resources/transactions.d.ts +8 -0
  58. package/dist/resources/transactions.js +6 -0
  59. package/dist/resources/wallets.d.ts +56 -0
  60. package/dist/resources/wallets.js +21 -0
  61. package/dist/resources/webhooks.d.ts +115 -0
  62. package/dist/resources/webhooks.js +43 -0
  63. package/dist/resources/workflows.d.ts +44 -0
  64. package/dist/resources/workflows.js +70 -0
  65. package/dist/resources/workspace-tokens.d.ts +6 -0
  66. package/dist/resources/workspace-tokens.js +6 -0
  67. package/dist/resources/workspaces.d.ts +25 -0
  68. package/dist/resources/workspaces.js +12 -0
  69. package/dist/retry.d.ts +21 -0
  70. package/dist/retry.js +44 -0
  71. package/dist/rwa-verify.d.ts +50 -0
  72. package/dist/rwa-verify.js +159 -0
  73. package/dist/webhook.d.ts +24 -0
  74. package/dist/webhook.js +56 -0
  75. package/llms.txt +84 -0
  76. package/package.json +62 -0
@@ -0,0 +1,3 @@
1
+ /** Generated from the XKOVA customer API schema. Do not edit directly. */
2
+ /* eslint-disable */
3
+ export {};
@@ -0,0 +1,25 @@
1
+ /** Reuse one key for every attempt of the same logical write. */
2
+ /**
3
+ * Save the returned options before the first attempt. With the same key and
4
+ * body, a retry returns the original result instead of creating another resource.
5
+ *
6
+ * ```ts
7
+ * const opts = withIdempotency();
8
+ * const created = await client.accountHolders.create(body, opts);
9
+ * ```
10
+ *
11
+ * Without these options, the client generates a new key for each call.
12
+ */
13
+ export declare function withIdempotency(): {
14
+ key: string;
15
+ /** Alias for `key` accepted by client mutation methods. */
16
+ idempotencyKey: string;
17
+ headers: {
18
+ 'Idempotency-Key': string;
19
+ };
20
+ };
21
+ /**
22
+ * Supported Node runtimes use `crypto.randomUUID()`. Without it, this helper
23
+ * uses a non-cryptographic generator. Do not use that path for production keys.
24
+ */
25
+ export declare function generateIdempotencyKey(): string;
@@ -0,0 +1,46 @@
1
+ /** Reuse one key for every attempt of the same logical write. */
2
+ /**
3
+ * Save the returned options before the first attempt. With the same key and
4
+ * body, a retry returns the original result instead of creating another resource.
5
+ *
6
+ * ```ts
7
+ * const opts = withIdempotency();
8
+ * const created = await client.accountHolders.create(body, opts);
9
+ * ```
10
+ *
11
+ * Without these options, the client generates a new key for each call.
12
+ */
13
+ export function withIdempotency() {
14
+ const key = generateIdempotencyKey();
15
+ return { key, idempotencyKey: key, headers: { 'Idempotency-Key': key } };
16
+ }
17
+ /**
18
+ * Supported Node runtimes use `crypto.randomUUID()`. Without it, this helper
19
+ * uses a non-cryptographic generator. Do not use that path for production keys.
20
+ */
21
+ export function generateIdempotencyKey() {
22
+ const c = globalThis.crypto;
23
+ if (c?.randomUUID)
24
+ return c.randomUUID();
25
+ return v4Fallback();
26
+ }
27
+ function v4Fallback() {
28
+ const hex = '0123456789abcdef';
29
+ const out = [];
30
+ for (let i = 0; i < 36; i += 1) {
31
+ if (i === 8 || i === 13 || i === 18 || i === 23) {
32
+ out.push('-');
33
+ continue;
34
+ }
35
+ if (i === 14) {
36
+ out.push('4');
37
+ continue;
38
+ }
39
+ if (i === 19) {
40
+ out.push(hex[8 + Math.floor(Math.random() * 4)]);
41
+ continue;
42
+ }
43
+ out.push(hex[Math.floor(Math.random() * 16)]);
44
+ }
45
+ return out.join('');
46
+ }
@@ -0,0 +1,11 @@
1
+ /** Server-side TypeScript client for XKOVA customer API operations. */
2
+ export { XKOVAClient, type XKOVAClientOptions, type XKOVAMutationOptions, type XKOVARequestControl, } from './client.js';
3
+ export type { XKOVARequestSigner, XKOVARequestSignInput, XKOVARequestSigningAlgorithm, } from './request-signing.js';
4
+ export { generateIdempotencyKey, withIdempotency } from './idempotency.js';
5
+ export { withRetry, type RetryPolicy } from './retry.js';
6
+ export { collectCursor, paginateCursor, type CursorPage, type PaginateCursorOptions, } from './cursor.js';
7
+ export { XKOVAApiError, XKOVAUploadError } from './errors.js';
8
+ export { type PollOptions } from './resources/workflows.js';
9
+ export { type XKOVAMemberSessionContext } from './resources/account-holders.js';
10
+ export { verifyWebhookSignature, type VerifyWebhookSignatureResult } from './webhook.js';
11
+ export { verifyInclusion, verifyConsistency, verifySth, type VerifyInclusionInput, type VerifyConsistencyInput, type VerifyTreeHeadInput, } from './rwa-verify.js';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /** Server-side TypeScript client for XKOVA customer API operations. */
2
+ export { XKOVAClient, } from './client.js';
3
+ export { generateIdempotencyKey, withIdempotency } from './idempotency.js';
4
+ export { withRetry } from './retry.js';
5
+ export { collectCursor, paginateCursor, } from './cursor.js';
6
+ export { XKOVAApiError, XKOVAUploadError } from './errors.js';
7
+ export { verifyWebhookSignature } from './webhook.js';
8
+ export { verifyInclusion, verifyConsistency, verifySth, } from './rwa-verify.js';
@@ -0,0 +1,72 @@
1
+ import type { XKOVAMemberResources } from './member-resources.js';
2
+ import type { ReqBody } from './resources/base.js';
3
+ export type XKOVAMemberIntentRequest = {
4
+ readonly operation: 'payment';
5
+ readonly body: ReqBody<'createPayment'>;
6
+ } | {
7
+ readonly operation: 'escrow';
8
+ readonly body: ReqBody<'createEscrow'>;
9
+ } | {
10
+ readonly operation: 'payment_request_approve';
11
+ readonly id: string;
12
+ readonly senderWalletId: string;
13
+ readonly accountHolderId: string;
14
+ };
15
+ export interface XKOVAMemberPreparedAction {
16
+ readonly resourceType: 'payment' | 'escrow';
17
+ readonly resourceId: string;
18
+ readonly action: 'sign' | 'create';
19
+ readonly claimUrl?: string;
20
+ }
21
+ export interface StoredXKOVAMemberActionIntent {
22
+ readonly clientFingerprint: string;
23
+ readonly sessionReference: string;
24
+ readonly accountHolderId: string;
25
+ readonly inputDigest: string;
26
+ readonly creationIdempotencyKey: string;
27
+ readonly request: XKOVAMemberIntentRequest;
28
+ /** Safe application-owned form values, not credentials or authority. */
29
+ readonly review: Readonly<Record<string, string>>;
30
+ readonly reviewUri: string;
31
+ readonly expiresAt: number;
32
+ /** Retain through the application session so an expired reference cannot create again. */
33
+ readonly retainUntil: number;
34
+ readonly prepared?: XKOVAMemberPreparedAction;
35
+ }
36
+ /** Encrypt at rest, serialize per reference across replicas, and retain until retainUntil. */
37
+ export interface XKOVAMemberActionIntentStore {
38
+ put(reference: string, value: StoredXKOVAMemberActionIntent): Promise<void>;
39
+ get(reference: string): Promise<StoredXKOVAMemberActionIntent | null>;
40
+ replace(reference: string, value: StoredXKOVAMemberActionIntent): Promise<void>;
41
+ runExclusive<T>(reference: string, work: () => Promise<T>): Promise<T>;
42
+ }
43
+ export declare class XKOVAMemberIntentError extends Error {
44
+ readonly code: 'action_intent_unavailable' | 'action_intent_conflict' | 'action_intent_expired' | 'action_intent_invalid';
45
+ readonly status: 404 | 409 | 410 | 422;
46
+ constructor(code: 'action_intent_unavailable' | 'action_intent_conflict' | 'action_intent_expired' | 'action_intent_invalid', status: 404 | 409 | 410 | 422);
47
+ }
48
+ /** Obtain from `resolveActionIntents` so reviews use the current session. */
49
+ export declare class XKOVAMemberActionIntents {
50
+ private readonly binding;
51
+ private readonly store;
52
+ private readonly resources;
53
+ constructor(binding: {
54
+ clientFingerprint: string;
55
+ sessionReference: string;
56
+ accountHolderId: string;
57
+ applicationExpiresAt: number;
58
+ }, store: XKOVAMemberActionIntentStore, resources: XKOVAMemberResources);
59
+ create(input: {
60
+ reference: string;
61
+ browserOrigin: string;
62
+ reviewUri: string;
63
+ request: XKOVAMemberIntentRequest;
64
+ review: Readonly<Record<string, string>>;
65
+ }): Promise<StoredXKOVAMemberActionIntent>;
66
+ read(reference: string): Promise<StoredXKOVAMemberActionIntent>;
67
+ /** Explicit user initiation only. No signing or execution, and no retry with a different key. */
68
+ prepare(reference: string): Promise<StoredXKOVAMemberActionIntent & {
69
+ prepared: XKOVAMemberPreparedAction;
70
+ }>;
71
+ private assertBound;
72
+ }
@@ -0,0 +1,181 @@
1
+ import { createHash } from 'node:crypto';
2
+ export class XKOVAMemberIntentError extends Error {
3
+ code;
4
+ status;
5
+ constructor(code, status) {
6
+ super(code === 'action_intent_conflict'
7
+ ? 'This action has different reviewed input. Start a new review.'
8
+ : code === 'action_intent_expired'
9
+ ? 'This review expired. Check activity before starting another action.'
10
+ : code === 'action_intent_invalid'
11
+ ? 'The action reference is invalid. Check your device clock and start a new review.'
12
+ : 'This action review is unavailable.');
13
+ this.code = code;
14
+ this.status = status;
15
+ this.name = 'XKOVAMemberIntentError';
16
+ }
17
+ }
18
+ /** Obtain from `resolveActionIntents` so reviews use the current session. */
19
+ export class XKOVAMemberActionIntents {
20
+ binding;
21
+ store;
22
+ resources;
23
+ constructor(binding, store, resources) {
24
+ this.binding = binding;
25
+ this.store = store;
26
+ this.resources = resources;
27
+ }
28
+ async create(input) {
29
+ const key = intentKey(input.reference);
30
+ const expiresAt = referenceExpiresAt(input.reference);
31
+ const location = new URL(input.reviewUri);
32
+ if (location.origin !== input.browserOrigin ||
33
+ location.username ||
34
+ location.password ||
35
+ location.hash ||
36
+ (location.protocol !== 'https:' &&
37
+ !(location.protocol === 'http:' && location.hostname === 'localhost')))
38
+ throw new TypeError('Action review requires the exact application origin');
39
+ const snapshot = JSON.parse(canonicalJson({
40
+ request: input.request,
41
+ review: input.review,
42
+ reviewUri: location.toString(),
43
+ }));
44
+ if (Object.keys(snapshot.review).length > 20 ||
45
+ Object.entries(snapshot.review).some(([name, value]) => !/^[a-z][a-z0-9_]{0,63}$/.test(name) || typeof value !== 'string' || value.length > 1024))
46
+ throw new TypeError('Action review must contain bounded text fields');
47
+ const inputDigest = digest(canonicalJson(snapshot));
48
+ return this.store.runExclusive(key, async () => {
49
+ const existing = await this.store.get(key);
50
+ if (existing) {
51
+ this.assertBound(existing);
52
+ if (existing.inputDigest !== inputDigest)
53
+ throw new XKOVAMemberIntentError('action_intent_conflict', 409);
54
+ return existing;
55
+ }
56
+ const intent = {
57
+ ...snapshot,
58
+ ...this.binding,
59
+ inputDigest,
60
+ creationIdempotencyKey: `member-intent-${digest(`${this.binding.clientFingerprint}:${this.binding.sessionReference}:${input.reference}`)}`,
61
+ expiresAt: Math.min(expiresAt, this.binding.applicationExpiresAt),
62
+ retainUntil: this.binding.applicationExpiresAt,
63
+ };
64
+ await this.store.put(key, intent);
65
+ return intent;
66
+ });
67
+ }
68
+ async read(reference) {
69
+ const value = await this.store.get(intentKey(reference));
70
+ if (!value)
71
+ throw new XKOVAMemberIntentError('action_intent_unavailable', 404);
72
+ this.assertBound(value);
73
+ return value;
74
+ }
75
+ /** Explicit user initiation only. No signing or execution, and no retry with a different key. */
76
+ async prepare(reference) {
77
+ return this.store.runExclusive(intentKey(reference), async () => {
78
+ const value = await this.read(reference);
79
+ if (value.prepared)
80
+ return { ...value, prepared: value.prepared };
81
+ const options = { idempotencyKey: value.creationIdempotencyKey };
82
+ let prepared;
83
+ switch (value.request.operation) {
84
+ case 'payment': {
85
+ const resource = await this.resources.payments.create(value.request.body, options);
86
+ prepared = { resourceType: 'payment', resourceId: resource.id, action: 'sign' };
87
+ break;
88
+ }
89
+ case 'escrow': {
90
+ const resource = await this.resources.escrows.create(value.request.body, options);
91
+ prepared = {
92
+ resourceType: 'escrow',
93
+ resourceId: resource.escrow.id,
94
+ action: 'create',
95
+ claimUrl: resource.claim_url_short ?? resource.claim_url,
96
+ };
97
+ break;
98
+ }
99
+ case 'payment_request_approve': {
100
+ const resource = await this.resources.paymentRequests.approve(value.request.id, {
101
+ accountHolderId: value.request.accountHolderId,
102
+ senderWalletId: value.request.senderWalletId,
103
+ }, options);
104
+ prepared = { resourceType: 'payment', resourceId: resource.id, action: 'sign' };
105
+ break;
106
+ }
107
+ }
108
+ const completed = { ...value, prepared };
109
+ await this.store.replace(intentKey(reference), completed);
110
+ return completed;
111
+ });
112
+ }
113
+ assertBound(value) {
114
+ if (value.clientFingerprint !== this.binding.clientFingerprint ||
115
+ value.sessionReference !== this.binding.sessionReference ||
116
+ value.accountHolderId !== this.binding.accountHolderId)
117
+ throw new XKOVAMemberIntentError('action_intent_unavailable', 404);
118
+ if (value.expiresAt <= Date.now())
119
+ throw new XKOVAMemberIntentError('action_intent_expired', 410);
120
+ }
121
+ }
122
+ function intentKey(reference) {
123
+ if (!/^[A-Za-z0-9_-]{43}$/.test(reference))
124
+ throw new XKOVAMemberIntentError('action_intent_invalid', 422);
125
+ return digest(reference);
126
+ }
127
+ /**
128
+ * A public review reference contains a six-byte creation time and 26 random bytes.
129
+ * The time grants no authority. It caps replay to one hour, even after store loss,
130
+ * strictly inside the API's 24-hour idempotency retention. Changing the time changes
131
+ * the reference and is a new intent, never a retry of the old one.
132
+ */
133
+ function referenceExpiresAt(reference) {
134
+ const bytes = Buffer.from(reference, 'base64url');
135
+ if (bytes.length !== 32 || bytes.toString('base64url') !== reference)
136
+ throw new XKOVAMemberIntentError('action_intent_invalid', 422);
137
+ const createdAt = bytes.readUIntBE(0, 6);
138
+ if (createdAt > Date.now() + 5 * 60_000)
139
+ throw new XKOVAMemberIntentError('action_intent_invalid', 422);
140
+ const expiresAt = createdAt + 60 * 60_000;
141
+ if (expiresAt <= Date.now())
142
+ throw new XKOVAMemberIntentError('action_intent_expired', 410);
143
+ return expiresAt;
144
+ }
145
+ function digest(value) {
146
+ return createHash('sha256').update(value).digest('hex');
147
+ }
148
+ function canonicalJson(input) {
149
+ const ancestors = new Set();
150
+ let nodes = 0;
151
+ const visit = (value, depth = 0) => {
152
+ if (++nodes > 4096 || depth > 32)
153
+ throw new TypeError('Action intent input is too large or deeply nested');
154
+ if (value === null || typeof value === 'boolean' || typeof value === 'string')
155
+ return value;
156
+ if (typeof value === 'number' && Number.isFinite(value))
157
+ return value;
158
+ if (typeof value === 'object') {
159
+ if (ancestors.has(value))
160
+ throw new TypeError('Action intent input must not contain cycles');
161
+ ancestors.add(value);
162
+ try {
163
+ if (Array.isArray(value))
164
+ return value.map((child) => visit(child, depth + 1));
165
+ if (Object.getPrototypeOf(value) === null ||
166
+ Object.getPrototypeOf(Object.getPrototypeOf(value)) === null)
167
+ return Object.fromEntries(Object.entries(value)
168
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
169
+ .map(([key, child]) => [key, visit(child, depth + 1)]));
170
+ }
171
+ finally {
172
+ ancestors.delete(value);
173
+ }
174
+ }
175
+ throw new TypeError('Action intent input must contain only JSON values');
176
+ };
177
+ const serialized = JSON.stringify(visit(input));
178
+ if (serialized.length > 16_384)
179
+ throw new TypeError('Action intent input is too large');
180
+ return serialized;
181
+ }