@xkova/sdk 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
@@ -34,6 +34,14 @@ export interface XKOVAMemberSessionMaterial extends XKOVAMemberContext {
34
34
  /** Confidential refresh credential, BFF storage only. */
35
35
  readonly refreshToken: string;
36
36
  }
37
+ export interface XKOVAExternalMemberEnrollment {
38
+ readonly accountHolderId: string;
39
+ readonly applicationId: string;
40
+ readonly publishableKey: string;
41
+ readonly applicationConfigVersion: number;
42
+ readonly providerNamespace: string;
43
+ readonly created: boolean;
44
+ }
37
45
  export interface XKOVABrowserMemberProjection {
38
46
  readonly signedIn: true;
39
47
  readonly accountHolderId: string;
@@ -49,8 +57,12 @@ export interface StoredXKOVAMemberSession<TApplicationData = unknown> extends XK
49
57
  }
50
58
  /**
51
59
  * `runExclusive` must serialize refresh and logout for the same reference
52
- * across all BFF instances. Use a database transaction or distributed lock;
53
- * a process-local mutex cannot coordinate multiple instances.
60
+ * across all BFF instances. Use a distributed or session-level database lock;
61
+ * a process-local mutex cannot coordinate multiple instances. This lock is not
62
+ * a surrounding rollback transaction: every awaited put/replace/delete must be
63
+ * durable before resolving and must survive a later error from work(). In
64
+ * particular, a pending refresh-attempt checkpoint cannot roll back after the
65
+ * provider/API has acted. replace is exact-existing-only, never an upsert.
54
66
  */
55
67
  export interface XKOVAMemberSessionStore<TApplicationData = unknown> {
56
68
  put(reference: string, session: StoredXKOVAMemberSession<TApplicationData>): Promise<void>;
@@ -116,7 +128,7 @@ export type XKOVAMemberAuthorizationTransaction<TApplicationData = unknown> = {
116
128
  readonly sessionReference: string;
117
129
  readonly intent: XKOVAAccountCenterIntent;
118
130
  readonly actionAuthorization?: {
119
- readonly method: 'hosted_passkey';
131
+ readonly method: 'hosted_passkey' | 'xkova_webauthn';
120
132
  } | {
121
133
  readonly method: 'external_oidc';
122
134
  readonly actionToken: string;
@@ -169,6 +181,11 @@ export type XKOVAMemberAuthorizationTransaction<TApplicationData = unknown> = {
169
181
  * every BFF instance. `replace` and `delete` must target that exact state. A
170
182
  * production store must encrypt the transaction because it can briefly hold a
171
183
  * confidential provider proof while XKOVA session exchange is retried.
184
+ * Every awaited write must commit independently of a later work() exception.
185
+ * Never wrap the entire callback in a rollback transaction: that can erase the
186
+ * staged proof after its one-time provider code has already been consumed.
187
+ * On uncertain write acknowledgement, reconcile the same exact record; never
188
+ * re-exchange the provider code or invent a replacement operation identity.
172
189
  */
173
190
  export interface XKOVAMemberAuthorizationTransactionStore<TApplicationData = unknown> {
174
191
  put(stateHash: string, transaction: XKOVAMemberAuthorizationTransaction<TApplicationData>): Promise<void>;
@@ -198,6 +215,10 @@ export type XKOVAMemberProofExchangeTransaction<TApplicationData = unknown> = {
198
215
  * Durable transaction store for a confidential proof exchange that does not
199
216
  * use the browser callback helper. Pending records contain a provider proof
200
217
  * and customer application data, so production adapters must encrypt them.
218
+ * Serialize the exact reference across replicas without a surrounding rollback
219
+ * transaction. Awaited put/replace/delete checkpoints must remain committed if
220
+ * the later exchange fails. Reconcile an uncertain acknowledgement against the
221
+ * same reference and attempt identity before retrying.
201
222
  */
202
223
  export interface XKOVAMemberProofExchangeTransactionStore<TApplicationData = unknown> {
203
224
  put(exchangeHash: string, transaction: XKOVAMemberProofExchangeTransaction<TApplicationData>): Promise<void>;
@@ -239,7 +260,7 @@ export interface XKOVAAccountCenterHandoffCompletion {
239
260
  readonly applicationReturnUri?: string;
240
261
  }
241
262
  export type XKOVAMemberActionAuthorizationStart = {
242
- readonly authorizationMethod: 'hosted_passkey';
263
+ readonly authorizationMethod: 'hosted_passkey' | 'xkova_webauthn';
243
264
  readonly authorizationUrl: string;
244
265
  readonly state: string;
245
266
  readonly expiresAt: string;
@@ -321,6 +342,18 @@ export declare class XKOVAMemberBffClient {
321
342
  exchangeProof(proofToken: string, options: {
322
343
  readonly attemptId: string;
323
344
  }): Promise<XKOVAMemberSessionMaterial>;
345
+ /**
346
+ * BFF-only membership provisioning after validating the provider identity or
347
+ * resolving it from a trusted directory. This creates no login session or
348
+ * wallet and does not substitute for genuine provider-proof verification.
349
+ * Save one idempotencyKey before the first attempt and retain the same subject,
350
+ * selector and observed configuration version during response-loss recovery.
351
+ */
352
+ enrollExternalMember(input: {
353
+ readonly externalSub: string;
354
+ readonly applicationConfigVersion: number;
355
+ readonly idempotencyKey: string;
356
+ }): Promise<XKOVAExternalMemberEnrollment>;
324
357
  refresh(refreshToken: string, options: {
325
358
  readonly attemptId: string;
326
359
  }): Promise<XKOVAMemberSessionMaterial>;
@@ -426,6 +459,17 @@ export declare class XKOVAMemberBffClient {
426
459
  readonly transactionStore: XKOVAMemberAuthorizationTransactionStore<TApplicationData>;
427
460
  readonly sessionStore: XKOVAMemberSessionStore<TApplicationData>;
428
461
  readonly exchangeProviderCode: (input: XKOVAMemberProviderCodeExchangeInput) => XKOVAMemberProviderCodeExchangeResult | Promise<XKOVAMemberProviderCodeExchangeResult>;
462
+ /**
463
+ * Optional OIDC enrollment, invoked only after the validated provider proof
464
+ * is durably staged. Validate/extract its exact subject on your backend and
465
+ * call enrollExternalMember with this stable idempotency key. Do not perform
466
+ * enrollment RPC inside exchangeProviderCode: failure there can otherwise
467
+ * require reusing an already-consumed provider authorization code.
468
+ */
469
+ readonly ensureMemberEnrollment?: (input: {
470
+ readonly proofToken: string;
471
+ readonly idempotencyKey: string;
472
+ }) => Promise<XKOVAExternalMemberEnrollment>;
429
473
  readonly applicationData: TApplicationData;
430
474
  readonly applicationSessionSeconds?: number;
431
475
  }): Promise<EstablishedXKOVAMemberSession<TApplicationData>>;
@@ -455,6 +499,11 @@ export declare class XKOVAMemberBffClient {
455
499
  resolveResources<TApplicationData>(input: {
456
500
  readonly reference: string;
457
501
  readonly sessionStore: XKOVAMemberSessionStore<TApplicationData>;
502
+ /**
503
+ * Defaults true. Set false only when the bound resource request executes
504
+ * immediately and therefore performs the same exact-session validation.
505
+ */
506
+ readonly validateWithXkova?: boolean;
458
507
  }): Promise<{
459
508
  readonly session: StoredXKOVAMemberSession<TApplicationData>;
460
509
  readonly resources: XKOVAMemberResources;
@@ -79,6 +79,48 @@ export class XKOVAMemberBffClient {
79
79
  proof_token: proofToken,
80
80
  }), 'proof exchange');
81
81
  }
82
+ /**
83
+ * BFF-only membership provisioning after validating the provider identity or
84
+ * resolving it from a trusted directory. This creates no login session or
85
+ * wallet and does not substitute for genuine provider-proof verification.
86
+ * Save one idempotencyKey before the first attempt and retain the same subject,
87
+ * selector and observed configuration version during response-loss recovery.
88
+ */
89
+ async enrollExternalMember(input) {
90
+ if (typeof input.externalSub !== 'string' ||
91
+ input.externalSub.length === 0 ||
92
+ input.externalSub.length > 512 ||
93
+ /\u0000/u.test(input.externalSub) ||
94
+ !/\S/u.test(input.externalSub))
95
+ throw new TypeError('externalSub must be an exact nonempty provider subject of at most 512 characters');
96
+ assertPositiveInteger(input.applicationConfigVersion, 'applicationConfigVersion');
97
+ if (!/^[A-Za-z0-9._:-]{1,255}$/u.test(input.idempotencyKey))
98
+ throw new TypeError('idempotencyKey must be a retained opaque operation key of at most 255 characters');
99
+ const value = await this.recoverablePostJson('/member-auth/enrollments', {
100
+ publishable_key: this.options.publishableKey,
101
+ application_config_version: input.applicationConfigVersion,
102
+ external_sub: input.externalSub,
103
+ }, undefined, input.idempotencyKey);
104
+ const result = requireObject(value, 'external member enrollment');
105
+ const applicationId = requireString(result, 'application_id', 'external member enrollment');
106
+ assertUuid(applicationId, 'enrollment application id');
107
+ const publishableKey = requireString(result, 'publishable_key', 'external member enrollment');
108
+ const applicationConfigVersion = requirePositiveInteger(result, 'application_config_version', 'external member enrollment');
109
+ const providerNamespace = requireString(result, 'provider_namespace', 'external member enrollment');
110
+ if (publishableKey !== this.options.publishableKey ||
111
+ applicationConfigVersion !== input.applicationConfigVersion ||
112
+ !providerNamespace.startsWith('oidc:') ||
113
+ typeof result.created !== 'boolean')
114
+ throw new Error('XKOVA external member enrollment returned a different application or configuration');
115
+ return {
116
+ accountHolderId: requireString(result, 'account_holder_id', 'external member enrollment'),
117
+ applicationId,
118
+ publishableKey,
119
+ applicationConfigVersion,
120
+ providerNamespace,
121
+ created: result.created,
122
+ };
123
+ }
82
124
  async refresh(refreshToken, options) {
83
125
  if (refreshToken.length < 20) {
84
126
  throw new TypeError('member refresh token is malformed');
@@ -536,15 +578,15 @@ export class XKOVAMemberBffClient {
536
578
  const object = requireObject(value, 'member action authorization start');
537
579
  const method = requireString(object, 'authorization_method', 'member action authorization start');
538
580
  const { expiresAt, expiresAtMillis } = boundedAuthorizationExpiry(object, provisionalExpiresAt, 'member action authorization start');
539
- if (method === 'hosted_passkey') {
581
+ if (method === 'hosted_passkey' || method === 'xkova_webauthn') {
540
582
  const authorizationUrl = normalizeHostedAuthorizationUrl(requireString(object, 'authorization_url', 'member action authorization start'));
541
583
  await input.transactionStore.replace(stateHash, {
542
584
  ...transaction,
543
- actionAuthorization: { method: 'hosted_passkey' },
585
+ actionAuthorization: { method },
544
586
  expiresAt: expiresAtMillis,
545
587
  });
546
588
  return {
547
- authorizationMethod: 'hosted_passkey',
589
+ authorizationMethod: method,
548
590
  authorizationUrl,
549
591
  state,
550
592
  expiresAt,
@@ -606,7 +648,7 @@ export class XKOVAMemberBffClient {
606
648
  if (initial?.completionMode === 'account_center_result_code' &&
607
649
  initial.status === 'pending' &&
608
650
  initial.intent === 'action' &&
609
- initial.actionAuthorization?.method === 'hosted_passkey') {
651
+ ['hosted_passkey', 'xkova_webauthn'].includes(initial.actionAuthorization?.method ?? '')) {
610
652
  const completion = await this.completeAccountCenterHandoff(input);
611
653
  if (!completion.signingExchangeCode || !completion.signingUrl) {
612
654
  throw new Error('XKOVA Hosted action completion omitted its signing projection');
@@ -759,6 +801,10 @@ export class XKOVAMemberBffClient {
759
801
  if (transaction.completionMode !== 'provider_proof') {
760
802
  throw new Error('member authorization transaction is not a provider-proof flow');
761
803
  }
804
+ if (input.ensureMemberEnrollment &&
805
+ transaction.status === 'pending' &&
806
+ transaction.proofKind !== 'oidc')
807
+ throw new Error('external member enrollment is available only for an OIDC proof flow');
762
808
  if (!sameCallback(callback, new URL(transaction.redirectUri))) {
763
809
  throw new Error('member authorization callback does not match its redirect URI');
764
810
  }
@@ -821,6 +867,14 @@ export class XKOVAMemberBffClient {
821
867
  },
822
868
  });
823
869
  }
870
+ const enrollment = input.ensureMemberEnrollment
871
+ ? await input.ensureMemberEnrollment({
872
+ proofToken,
873
+ idempotencyKey: `enroll:${transaction.exchangeAttemptId}`,
874
+ })
875
+ : undefined;
876
+ if (enrollment && enrollment.publishableKey !== this.options.publishableKey)
877
+ throw new Error('member enrollment does not belong to this application');
824
878
  const established = await this.persistSessionWithAttempt({
825
879
  proofToken,
826
880
  attemptId: transaction.exchangeAttemptId,
@@ -828,6 +882,7 @@ export class XKOVAMemberBffClient {
828
882
  sessionStore: input.sessionStore,
829
883
  applicationData,
830
884
  applicationExpiresAt,
885
+ ...(enrollment !== undefined && { expectedAccountHolderId: enrollment.accountHolderId }),
831
886
  });
832
887
  await input.transactionStore.replace(stateHash, {
833
888
  status: 'completed',
@@ -1014,6 +1069,9 @@ export class XKOVAMemberBffClient {
1014
1069
  const material = await this.exchangeProof(input.proofToken, {
1015
1070
  attemptId: input.attemptId,
1016
1071
  });
1072
+ if (input.expectedAccountHolderId !== undefined &&
1073
+ material.accountHolderId !== input.expectedAccountHolderId)
1074
+ throw new Error('XKOVA proof exchange returned a different member from the enrolled identity');
1017
1075
  return this.persistSessionMaterial({
1018
1076
  material,
1019
1077
  reference: input.reference,
@@ -1064,8 +1122,7 @@ export class XKOVAMemberBffClient {
1064
1122
  ...(idempotencyKey !== undefined && { idempotencyKey }),
1065
1123
  });
1066
1124
  }
1067
- async recoverablePostJson(path, body, sessionId) {
1068
- const idempotencyKey = generateIdempotencyKey();
1125
+ async recoverablePostJson(path, body, sessionId, idempotencyKey = generateIdempotencyKey()) {
1069
1126
  try {
1070
1127
  return await this.postJson(path, body, sessionId, idempotencyKey);
1071
1128
  }
@@ -52,6 +52,9 @@ export declare class XKOVAMemberResources {
52
52
  credit: {
53
53
  amount: string;
54
54
  token_deployment_id: string;
55
+ token_address: string;
56
+ token_symbol: string;
57
+ chain: import("./generated/types.js").components["schemas"]["ChainDescriptor"];
55
58
  status: "pending" | "submitted" | "settled" | "requires_review";
56
59
  transaction_hash: string | null;
57
60
  } | null;
@@ -64,12 +67,15 @@ export declare class XKOVAMemberResources {
64
67
  credit: {
65
68
  amount: string;
66
69
  token_deployment_id: string;
70
+ token_address: string;
71
+ token_symbol: string;
72
+ chain: import("./generated/types.js").components["schemas"]["ChainDescriptor"];
67
73
  status: "pending" | "submitted" | "settled" | "requires_review";
68
74
  transaction_hash: string | null;
69
75
  } | null;
70
76
  }>;
71
77
  actionReadiness: (control?: XKOVARequestControl) => Promise<{
72
- authorization_method: "hosted_passkey" | "external_oidc";
78
+ authorization_method: "hosted_passkey" | "external_oidc" | "xkova_webauthn";
73
79
  setup_status: "passkey_required" | "recovery_acknowledgement_required" | "recovery_required" | "ready" | "externally_managed";
74
80
  action_authorization_enabled: boolean;
75
81
  requires_fresh_proof: true;
@@ -103,10 +109,20 @@ export declare class XKOVAMemberResources {
103
109
  }>;
104
110
  balance: (control?: XKOVARequestControl) => Promise<{
105
111
  balances: import("./generated/types.js").components["schemas"]["AccountHolderSelfBalanceEntry"][];
112
+ data_freshness: import("./generated/types.js").components["schemas"]["DataFreshness"][];
106
113
  }>;
107
114
  wallets: (control?: XKOVARequestControl) => Promise<{
108
115
  data: import("./generated/types.js").components["schemas"]["Wallet"][];
109
116
  }>;
117
+ walletAuth: (control?: XKOVARequestControl) => Promise<{
118
+ strategy: "auth_endpoint";
119
+ payload: string;
120
+ client_id: string;
121
+ project_id: string;
122
+ external_user_id: string;
123
+ expires_at: string;
124
+ expected_address?: string;
125
+ } & Record<string, never>>;
110
126
  walletReadiness: (control?: XKOVARequestControl) => Promise<{
111
127
  status: "not_registered" | "provisioning" | "ready";
112
128
  wallets: import("./generated/types.js").components["schemas"]["Wallet"][];
@@ -31,7 +31,7 @@ export class XKOVAMemberResources {
31
31
  return this.client.wallets;
32
32
  }
33
33
  self = {
34
- profile: () => this.client.accountHolders.retrieve(this.accountHolderId),
34
+ profile: () => this.client.accountHolders.selfProfile(this.context),
35
35
  productEnrollment: (product, control) => this.client.accountHolders.selfProductEnrollment(this.context, product, control),
36
36
  acceptProductEnrollment: (product, body, opts) => this.client.accountHolders.acceptSelfProductEnrollment(this.context, product, body, opts),
37
37
  actionReadiness: (control) => this.client.accountHolders.selfActionReadiness({ ...this.context, accountHolderId: this.accountHolderId }, control),
@@ -39,6 +39,7 @@ export class XKOVAMemberResources {
39
39
  verification: (control) => this.client.accountHolders.selfVerification(this.context, control),
40
40
  balance: (control) => this.client.accountHolders.selfBalance(this.context, control),
41
41
  wallets: (control) => this.client.accountHolders.listSelfWallets(this.context, control),
42
+ walletAuth: (control) => this.client.accountHolders.selfWalletAuth(this.context, control),
42
43
  walletReadiness: (control) => this.client.accountHolders.selfWalletReadiness(this.context, control),
43
44
  ensureWalletProvisioning: (opts) => this.client.accountHolders.ensureSelfWalletProvisioning(this.context, opts),
44
45
  bankBalance: (control) => this.client.accountHolders.selfBankBalance(this.context, control),
@@ -50,10 +51,10 @@ export class XKOVAMemberResources {
50
51
  requestDataExport: (opts) => this.client.accountHolders.requestSelfDataExport(this.context, opts),
51
52
  dataExport: (id, control) => this.client.accountHolders.selfDataExport(this.context, id, control),
52
53
  transactions: (query, control) => this.client.accountHolders.listSelfTransactions(this.context, query, control),
53
- avatar: () => this.client.accountHolders.avatarUrl(this.accountHolderId),
54
- uploadAvatar: (image, control) => this.client.accountHolders.uploadAvatar(this.accountHolderId, image, control),
55
- deleteAvatar: (opts) => this.client.accountHolders.deleteAvatar(this.accountHolderId, opts),
56
- confirmAvatar: (body, opts) => this.client.accountHolders.confirmAvatar(this.accountHolderId, body, opts),
57
- presignAvatarUpload: (body, opts) => this.client.accountHolders.presignAvatarUpload(this.accountHolderId, body, opts),
54
+ avatar: () => this.client.accountHolders.selfAvatarUrl(this.context),
55
+ uploadAvatar: (image, control) => this.client.accountHolders.uploadSelfAvatar(this.context, image, control),
56
+ deleteAvatar: (opts) => this.client.accountHolders.deleteSelfAvatar(this.context, opts),
57
+ confirmAvatar: (body, opts) => this.client.accountHolders.confirmSelfAvatar(this.context, body, opts),
58
+ presignAvatarUpload: (body, opts) => this.client.accountHolders.presignSelfAvatarUpload(this.context, body, opts),
58
59
  };
59
60
  }
@@ -100,6 +100,32 @@ export declare class AccountHoldersResource extends Resource {
100
100
  next_cursor?: string | null;
101
101
  has_more: boolean;
102
102
  }>;
103
+ selfProfile(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
104
+ id: import("../generated/types.js").components["schemas"]["AccountHolderId"];
105
+ tenant_id?: string;
106
+ state: "active" | "held" | "closed" | "closed_uncooperative" | "provisioning_failed";
107
+ state_reason?: string | null;
108
+ state_changed_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
109
+ tier: "tier_1" | "tier_2";
110
+ tier_2_uplift_status?: "not_requested" | "pending" | "passed" | "failed";
111
+ first_use_completed: boolean;
112
+ cip_payload?: {
113
+ name_hash?: string;
114
+ dob_hash?: string;
115
+ id_hash?: string;
116
+ address_hash?: string;
117
+ } | null;
118
+ cip_accepted_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
119
+ merchant_role_state?: "none" | "pending_review" | "approved" | "rejected" | "revoked";
120
+ auto_save_contacts?: boolean;
121
+ created_at: import("../generated/types.js").components["schemas"]["TimestampUtc"];
122
+ updated_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"];
123
+ email?: string | null;
124
+ first_name?: string | null;
125
+ last_name?: string | null;
126
+ phone?: string | null;
127
+ avatar_url?: string | null;
128
+ }>;
103
129
  selfVerification(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
104
130
  required: boolean;
105
131
  status: string;
@@ -126,6 +152,9 @@ export declare class AccountHoldersResource extends Resource {
126
152
  credit: {
127
153
  amount: string;
128
154
  token_deployment_id: string;
155
+ token_address: string;
156
+ token_symbol: string;
157
+ chain: import("../generated/types.js").components["schemas"]["ChainDescriptor"];
129
158
  status: "pending" | "submitted" | "settled" | "requires_review";
130
159
  transaction_hash: string | null;
131
160
  } | null;
@@ -138,16 +167,29 @@ export declare class AccountHoldersResource extends Resource {
138
167
  credit: {
139
168
  amount: string;
140
169
  token_deployment_id: string;
170
+ token_address: string;
171
+ token_symbol: string;
172
+ chain: import("../generated/types.js").components["schemas"]["ChainDescriptor"];
141
173
  status: "pending" | "submitted" | "settled" | "requires_review";
142
174
  transaction_hash: string | null;
143
175
  } | null;
144
176
  }>;
145
177
  selfBalance(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
146
178
  balances: import("../generated/types.js").components["schemas"]["AccountHolderSelfBalanceEntry"][];
179
+ data_freshness: import("../generated/types.js").components["schemas"]["DataFreshness"][];
147
180
  }>;
148
181
  listSelfWallets(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
149
182
  data: import("../generated/types.js").components["schemas"]["Wallet"][];
150
183
  }>;
184
+ selfWalletAuth(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
185
+ strategy: "auth_endpoint";
186
+ payload: string;
187
+ client_id: string;
188
+ project_id: string;
189
+ external_user_id: string;
190
+ expires_at: string;
191
+ expected_address?: string;
192
+ } & Record<string, never>>;
151
193
  selfWalletReadiness(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
152
194
  status: "not_registered" | "provisioning" | "ready";
153
195
  wallets: import("../generated/types.js").components["schemas"]["Wallet"][];
@@ -173,7 +215,7 @@ export declare class AccountHoldersResource extends Resource {
173
215
  selfActionReadiness(context: XKOVAMemberSessionContext & {
174
216
  accountHolderId: string;
175
217
  }, control?: XKOVARequestControl): Promise<{
176
- authorization_method: "hosted_passkey" | "external_oidc";
218
+ authorization_method: "hosted_passkey" | "external_oidc" | "xkova_webauthn";
177
219
  setup_status: "passkey_required" | "recovery_acknowledgement_required" | "recovery_required" | "ready" | "externally_managed";
178
220
  action_authorization_enabled: boolean;
179
221
  requires_fresh_proof: true;
@@ -253,6 +295,70 @@ export declare class AccountHoldersResource extends Resource {
253
295
  uploadAvatar(id: string, image: Blob, control?: XKOVARequestControl): Promise<{
254
296
  url: string | null;
255
297
  }>;
298
+ uploadSelfAvatar(context: XKOVAMemberSessionContext, image: Blob, control?: XKOVARequestControl): Promise<{
299
+ url: string | null;
300
+ }>;
301
+ private uploadAvatarImage;
302
+ selfAvatarUrl(context: XKOVAMemberSessionContext): Promise<{
303
+ url: string | null;
304
+ }>;
305
+ deleteSelfAvatar(context: XKOVAMemberSessionContext, opts?: XKOVAMutationOptions): Promise<{
306
+ id: import("../generated/types.js").components["schemas"]["AccountHolderId"];
307
+ tenant_id?: string;
308
+ state: "active" | "held" | "closed" | "closed_uncooperative" | "provisioning_failed";
309
+ state_reason?: string | null;
310
+ state_changed_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
311
+ tier: "tier_1" | "tier_2";
312
+ tier_2_uplift_status?: "not_requested" | "pending" | "passed" | "failed";
313
+ first_use_completed: boolean;
314
+ cip_payload?: {
315
+ name_hash?: string;
316
+ dob_hash?: string;
317
+ id_hash?: string;
318
+ address_hash?: string;
319
+ } | null;
320
+ cip_accepted_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
321
+ merchant_role_state?: "none" | "pending_review" | "approved" | "rejected" | "revoked";
322
+ auto_save_contacts?: boolean;
323
+ created_at: import("../generated/types.js").components["schemas"]["TimestampUtc"];
324
+ updated_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"];
325
+ email?: string | null;
326
+ first_name?: string | null;
327
+ last_name?: string | null;
328
+ phone?: string | null;
329
+ avatar_url?: string | null;
330
+ }>;
331
+ confirmSelfAvatar(context: XKOVAMemberSessionContext, body: ReqBody<'confirmAccountHolderSelfAvatar'>, opts?: XKOVAMutationOptions): Promise<{
332
+ id: import("../generated/types.js").components["schemas"]["AccountHolderId"];
333
+ tenant_id?: string;
334
+ state: "active" | "held" | "closed" | "closed_uncooperative" | "provisioning_failed";
335
+ state_reason?: string | null;
336
+ state_changed_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
337
+ tier: "tier_1" | "tier_2";
338
+ tier_2_uplift_status?: "not_requested" | "pending" | "passed" | "failed";
339
+ first_use_completed: boolean;
340
+ cip_payload?: {
341
+ name_hash?: string;
342
+ dob_hash?: string;
343
+ id_hash?: string;
344
+ address_hash?: string;
345
+ } | null;
346
+ cip_accepted_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
347
+ merchant_role_state?: "none" | "pending_review" | "approved" | "rejected" | "revoked";
348
+ auto_save_contacts?: boolean;
349
+ created_at: import("../generated/types.js").components["schemas"]["TimestampUtc"];
350
+ updated_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"];
351
+ email?: string | null;
352
+ first_name?: string | null;
353
+ last_name?: string | null;
354
+ phone?: string | null;
355
+ avatar_url?: string | null;
356
+ }>;
357
+ presignSelfAvatarUpload(context: XKOVAMemberSessionContext, body: ReqBody<'presignAccountHolderSelfAvatarUpload'>, opts?: XKOVAMutationOptions): Promise<{
358
+ upload_url: string;
359
+ object_key: string;
360
+ expires_at: import("../generated/types.js").components["schemas"]["TimestampUtc"];
361
+ }>;
256
362
  avatarUrl(id: string): Promise<{
257
363
  url: string | null;
258
364
  }>;
@@ -28,6 +28,9 @@ export class AccountHoldersResource extends Resource {
28
28
  history(id, query = {}) {
29
29
  return this.getJson(`/account-holders/${this.enc(id)}/history`, query);
30
30
  }
31
+ selfProfile(context, control) {
32
+ return this.getJson('/account-holders/self', undefined, control, memberSessionHeaders(context));
33
+ }
31
34
  selfVerification(context, control) {
32
35
  return this.getJson('/account-holders/self/verification', undefined, control, memberSessionHeaders(context));
33
36
  }
@@ -43,6 +46,9 @@ export class AccountHoldersResource extends Resource {
43
46
  listSelfWallets(context, control) {
44
47
  return this.getJson('/account-holders/self/wallets', undefined, control, memberSessionHeaders(context));
45
48
  }
49
+ selfWalletAuth(context, control) {
50
+ return this.postJson('/account-holders/self/wallet-auth', undefined, control, undefined, memberSessionHeaders(context));
51
+ }
46
52
  selfWalletReadiness(context, control) {
47
53
  return this.getJson('/account-holders/self/wallet-readiness', undefined, control, memberSessionHeaders(context));
48
54
  }
@@ -64,7 +70,7 @@ export class AccountHoldersResource extends Resource {
64
70
  }
65
71
  const readiness = status.action_readiness;
66
72
  if (!readiness ||
67
- !['hosted_passkey', 'external_oidc'].includes(readiness.authorization_method) ||
73
+ !['hosted_passkey', 'external_oidc', 'xkova_webauthn'].includes(readiness.authorization_method) ||
68
74
  ![
69
75
  'passkey_required',
70
76
  'recovery_acknowledgement_required',
@@ -109,15 +115,26 @@ export class AccountHoldersResource extends Resource {
109
115
  * member context and cookies never follow the presigned URL or a redirect.
110
116
  */
111
117
  async uploadAvatar(id, image, control = {}) {
118
+ return this.uploadAvatarImage(image, control, {
119
+ presign: (body) => this.presignAvatarUpload(id, body, control),
120
+ confirm: (object_key) => this.confirmAvatar(id, { object_key }, control),
121
+ read: () => this.avatarUrl(id),
122
+ });
123
+ }
124
+ async uploadSelfAvatar(context, image, control = {}) {
125
+ return this.uploadAvatarImage(image, control, {
126
+ presign: (body) => this.presignSelfAvatarUpload(context, body, control),
127
+ confirm: (object_key) => this.confirmSelfAvatar(context, { object_key }, control),
128
+ read: () => this.selfAvatarUrl(context),
129
+ });
130
+ }
131
+ async uploadAvatarImage(image, control, operations) {
112
132
  if (!['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.type) ||
113
133
  image.size === 0 ||
114
134
  image.size > 5 * 1024 * 1024) {
115
135
  throw new TypeError('Avatar must be a PNG, JPEG, WebP, or GIF image of at most 5 MiB');
116
136
  }
117
- const upload = await this.presignAvatarUpload(id, {
118
- content_type: image.type,
119
- content_length: image.size,
120
- }, control);
137
+ const upload = await operations.presign({ content_type: image.type, content_length: image.size });
121
138
  const target = new URL(upload.upload_url);
122
139
  if (target.username ||
123
140
  target.password ||
@@ -151,8 +168,20 @@ export class AccountHoldersResource extends Resource {
151
168
  }
152
169
  if (!response.ok)
153
170
  throw new XKOVAUploadError('Object storage rejected the avatar upload', response.status);
154
- await this.confirmAvatar(id, { object_key: upload.object_key }, control);
155
- return this.avatarUrl(id);
171
+ await operations.confirm(upload.object_key);
172
+ return operations.read();
173
+ }
174
+ selfAvatarUrl(context) {
175
+ return this.getJson('/account-holders/self/avatar', undefined, {}, memberSessionHeaders(context));
176
+ }
177
+ deleteSelfAvatar(context, opts) {
178
+ return this.deleteJson('/account-holders/self/avatar', opts, undefined, memberSessionHeaders(context));
179
+ }
180
+ confirmSelfAvatar(context, body, opts) {
181
+ return this.postJson('/account-holders/self/avatar/confirm', body, opts, undefined, memberSessionHeaders(context));
182
+ }
183
+ presignSelfAvatarUpload(context, body, opts) {
184
+ return this.postJson('/account-holders/self/avatar/upload-url', body, opts, undefined, memberSessionHeaders(context));
156
185
  }
157
186
  avatarUrl(id) {
158
187
  return this.getJson(`/account-holders/${this.enc(id)}/avatar`);
@@ -2,6 +2,8 @@ import { Resource, type ReqBody, type Query } from './base.js';
2
2
  import type { XKOVAMutationOptions } from '../client.js';
3
3
  export declare class BrandingResource extends Resource {
4
4
  get(): Promise<{
5
+ logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
6
+ has_logo_override: boolean;
5
7
  resolved: {
6
8
  brand_name: string;
7
9
  legal_name: string;
@@ -28,6 +30,8 @@ export declare class BrandingResource extends Resource {
28
30
  theme: import("../generated/types.js").components["schemas"]["TenantBrandingTheme"];
29
31
  }>;
30
32
  upsert(body: ReqBody<'upsertBranding'>, opts?: XKOVAMutationOptions): Promise<{
33
+ logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
34
+ has_logo_override: boolean;
31
35
  resolved: {
32
36
  brand_name: string;
33
37
  legal_name: string;
@@ -94,12 +98,16 @@ export declare class BrandingResource extends Resource {
94
98
  last_synced_at: string;
95
99
  created_at: string;
96
100
  }>;
101
+ /** Upload bytes to the returned URL, then confirm its opaque key as the same tenant. */
97
102
  logoUploadUrl(body: ReqBody<'brandingLogoUploadUrl'>, opts?: XKOVAMutationOptions): Promise<{
98
103
  upload_url: string;
99
104
  object_key: string;
100
105
  expires_at: string;
101
106
  }>;
107
+ /** Publish a newly issued upload. Saved logo URLs or keys are not upload keys. */
102
108
  confirmLogo(body: ReqBody<'confirmBrandingLogo'>, opts?: XKOVAMutationOptions): Promise<{
109
+ logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
110
+ has_logo_override: boolean;
103
111
  resolved: {
104
112
  brand_name: string;
105
113
  legal_name: string;
@@ -126,6 +134,8 @@ export declare class BrandingResource extends Resource {
126
134
  theme: import("../generated/types.js").components["schemas"]["TenantBrandingTheme"];
127
135
  }>;
128
136
  deleteLogo(opts?: XKOVAMutationOptions): Promise<{
137
+ logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
138
+ has_logo_override: boolean;
129
139
  resolved: {
130
140
  brand_name: string;
131
141
  legal_name: string;
@@ -22,9 +22,11 @@ export class BrandingResource extends Resource {
22
22
  verifyEmailDomain(id, opts) {
23
23
  return this.postJson(`/branding/email-domains/${this.enc(id)}/verify`, undefined, opts);
24
24
  }
25
+ /** Upload bytes to the returned URL, then confirm its opaque key as the same tenant. */
25
26
  logoUploadUrl(body, opts) {
26
27
  return this.postJson('/branding/logo/upload-url', body, opts);
27
28
  }
29
+ /** Publish a newly issued upload. Saved logo URLs or keys are not upload keys. */
28
30
  confirmLogo(body, opts) {
29
31
  return this.postJson('/branding/logo', body, opts);
30
32
  }
@@ -7,6 +7,7 @@ export declare class EscrowsResource extends Resource {
7
7
  has_more: boolean;
8
8
  }>;
9
9
  create(body: ReqBody<'createEscrow'>, opts?: XKOVAMutationOptions): Promise<{
10
+ notification_status?: "queued" | "sent" | "failed" | "cancelled";
10
11
  escrow: import("../generated/types.js").components["schemas"]["Escrow"];
11
12
  claim_token: string;
12
13
  claim_url: string;
@@ -15,6 +16,7 @@ export declare class EscrowsResource extends Resource {
15
16
  signing_expires_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
16
17
  }>;
17
18
  retrieve(id: string, query?: Query<'getEscrow'>): Promise<{
19
+ notification_status?: "queued" | "sent" | "failed" | "cancelled";
18
20
  id: import("../generated/types.js").components["schemas"]["EscrowId"];
19
21
  chain: import("../generated/types.js").components["schemas"]["ChainDescriptor"];
20
22
  fingerprint: string;
@@ -28,6 +30,7 @@ export declare class EscrowsResource extends Resource {
28
30
  on_chain_payment_id?: string | null;
29
31
  create_tx_hash?: string | null;
30
32
  release_tx_hash?: string | null;
33
+ data_freshness?: import("../generated/types.js").components["schemas"]["DataFreshness"][];
31
34
  fee_accruals?: import("../generated/types.js").components["schemas"]["EscrowFeeAccrual"][];
32
35
  net_amount?: import("../generated/types.js").components["schemas"]["Money"] | null;
33
36
  claimed_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;