@idos-network/client 1.5.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -3,28 +3,38 @@ import type {
3
3
  EncryptionPasswordStore,
4
4
  PublicEncryptionProfile,
5
5
  } from "@idos-network/enclave";
6
- import type { KwilSigner } from "@idos-network/kwil-js";
7
6
 
8
7
  import {
9
- buildInsertableIDOSCredential,
8
+ buildEphemeralSignedCredentialContentReference,
9
+ buildPreliminaryIDOSCredential,
10
+ mapPreliminaryToIDOSCredential,
10
11
  matchLevelOrHigher,
12
+ type PreliminaryIDOSCredential,
11
13
  recordFilter,
14
+ type BaseLevel,
15
+ type Addon,
12
16
  } from "@idos-network/credentials/utils";
13
17
  import {
14
18
  createClientKwilSigner,
19
+ createKgwAuthenticatedBlobGateway,
20
+ createNodeKwilClient,
15
21
  createWebKwilClient,
22
+ isMmTokenAuth,
16
23
  type KwilActionClient,
24
+ mmTokenCredentialContentUri,
17
25
  signNearMessage,
18
26
  type Wallet,
19
27
  } from "@idos-network/kwil-infra";
20
28
  import {
21
29
  type AddAttributeInput,
22
30
  type AddWalletInput,
31
+ type AddWalletMessageInput,
23
32
  addWallet,
33
+ addWalletMessage as requestAddWalletMessage,
24
34
  addWallets,
25
35
  addAttribute as createAttribute,
26
- type DagMessageInput,
27
- dagMessage,
36
+ createPreliminaryCredential,
37
+ type CreatePreliminaryCredentialInput,
28
38
  dwgMessage,
29
39
  type GetAccessGrantsGrantedInput,
30
40
  type GetWalletsOutput,
@@ -41,6 +51,8 @@ import {
41
51
  type idOSCredential,
42
52
  type idOSCredentialListItem,
43
53
  type idOSDelegatedWriteGrant,
54
+ type DelegatedWriteGrantBaseParams,
55
+ toDelegatedWriteGrantBaseParams,
44
56
  type idOSGrant,
45
57
  type idOSUser,
46
58
  type idOSUserAttribute,
@@ -48,18 +60,24 @@ import {
48
60
  removeCredential,
49
61
  removeWallet,
50
62
  revokeAccessGrant,
51
- type ShareCredentialInput,
52
- shareCredential,
63
+ type SharePreliminaryCredentialInput,
64
+ sharePreliminaryCredential,
53
65
  type WalletType,
54
66
  } from "@idos-network/kwil-infra/actions";
67
+ import { NodeKwil, type KwilSigner } from "@idos-network/kwil-js";
68
+ import {
69
+ BlobGateway,
70
+ createBlobContentReference,
71
+ requireAccessTokenForUkycContent,
72
+ resolveCredentialEncryptedContent,
73
+ } from "@idos-network/utils/blob-gateway";
55
74
  import {
56
75
  base64Decode,
57
76
  base64Encode,
58
77
  hexEncodeSha256Hash,
59
78
  utf8Decode,
60
- utf8Encode,
61
79
  } from "@idos-network/utils/codecs";
62
- import { LocalStorageStore, type Store } from "@idos-network/utils/store";
80
+ import { LocalStorageStore, MemoryStore, type Store } from "@idos-network/utils/store";
63
81
  import invariant from "tiny-invariant";
64
82
 
65
83
  import { IframeEnclave } from "./enclave/iframe-enclave";
@@ -70,6 +88,52 @@ type Properties<T> = {
70
88
  [K in keyof T as Exclude<T[K], Function> extends never ? never : K]: T[K];
71
89
  };
72
90
 
91
+ function isBrowserRuntime(): boolean {
92
+ return typeof window !== "undefined";
93
+ }
94
+
95
+ function usesNodeKwil(kwilClient: KwilActionClient): boolean {
96
+ return kwilClient.client instanceof NodeKwil;
97
+ }
98
+
99
+ function idleBlobGateway(url: string): BlobGateway {
100
+ if (!isBrowserRuntime()) {
101
+ return new BlobGateway({ url });
102
+ }
103
+
104
+ return new BlobGateway({
105
+ url,
106
+ // Browser blob reads need the KGW HttpOnly session cookie. That only works if the
107
+ // blob gateway is covered by the cookie's Domain (typically same host as nodeUrl/KGW).
108
+ fetchFn: (input, init) => fetch(input, { ...init, credentials: "include" }),
109
+ });
110
+ }
111
+
112
+ function blobGatewayForSigner(idle: idOSClientIdle, kwilSigner: KwilSigner): BlobGateway {
113
+ if (usesNodeKwil(idle.kwilClient)) {
114
+ return createKgwAuthenticatedBlobGateway({
115
+ url: idle.blobGateway.url,
116
+ kwilClient: idle.kwilClient,
117
+ signer: kwilSigner,
118
+ });
119
+ }
120
+
121
+ return isMmTokenAuth(kwilSigner)
122
+ ? idle.blobGateway.withAccessToken(kwilSigner.accessToken)
123
+ : idle.blobGateway;
124
+ }
125
+
126
+ function contentUriForWallet(
127
+ walletType: WalletType,
128
+ kwilSigner: KwilSigner,
129
+ credentialId: string,
130
+ ): string | undefined {
131
+ if (walletType !== "MM") {
132
+ return undefined;
133
+ }
134
+ return mmTokenCredentialContentUri(kwilSigner, credentialId);
135
+ }
136
+
73
137
  export type idOSClient =
74
138
  | idOSClientConfiguration
75
139
  | idOSClientIdle
@@ -80,6 +144,7 @@ export class idOSClientConfiguration<Provider extends BaseProvider = IframeEncla
80
144
  readonly state: "configuration";
81
145
  readonly chainId?: string;
82
146
  readonly nodeUrl: string;
147
+ readonly blobGatewayUrl?: string;
83
148
  readonly enclaveOptions: Omit<Provider["options"], "mode">;
84
149
  readonly store: Store;
85
150
  readonly enclaveProvider: BaseProvider;
@@ -87,6 +152,12 @@ export class idOSClientConfiguration<Provider extends BaseProvider = IframeEncla
87
152
  constructor(params: {
88
153
  chainId?: string;
89
154
  nodeUrl: string;
155
+ /**
156
+ * Optional blob gateway base URL. Defaults to `nodeUrl`.
157
+ * In the browser this must share KGW's session-cookie Domain
158
+ * (typically the same host), because reads use `credentials: "include"`.
159
+ */
160
+ blobGatewayUrl?: string;
90
161
  enclaveOptions: Omit<Provider["options"], "mode">;
91
162
  enclaveProvider?: new (options: Omit<Provider["options"], "mode">) => Provider;
92
163
  store?: Store;
@@ -94,8 +165,9 @@ export class idOSClientConfiguration<Provider extends BaseProvider = IframeEncla
94
165
  this.state = "configuration";
95
166
  this.chainId = params.chainId;
96
167
  this.nodeUrl = params.nodeUrl;
168
+ this.blobGatewayUrl = params.blobGatewayUrl;
97
169
  this.enclaveOptions = params.enclaveOptions;
98
- this.store = params.store ?? new LocalStorageStore();
170
+ this.store = params.store ?? (isBrowserRuntime() ? new LocalStorageStore() : new MemoryStore());
99
171
 
100
172
  // TODO: This is a mess because of types...
101
173
  if (params.enclaveProvider) {
@@ -120,23 +192,37 @@ export class idOSClientIdle {
120
192
  readonly store: Store;
121
193
  readonly kwilClient: KwilActionClient;
122
194
  readonly enclaveProvider: BaseProvider;
195
+ readonly blobGateway: BlobGateway;
123
196
 
124
- constructor(store: Store, kwilClient: KwilActionClient, enclaveProvider: BaseProvider) {
197
+ constructor(
198
+ store: Store,
199
+ kwilClient: KwilActionClient,
200
+ enclaveProvider: BaseProvider,
201
+ blobGateway: BlobGateway,
202
+ ) {
125
203
  this.state = "idle";
126
204
  this.store = store;
127
205
  this.kwilClient = kwilClient;
128
206
  this.enclaveProvider = enclaveProvider;
207
+ this.blobGateway = blobGateway;
129
208
  }
130
209
 
131
210
  static async fromConfig(params: idOSClientConfiguration<BaseProvider>): Promise<idOSClientIdle> {
132
- const kwilClient = await createWebKwilClient({
211
+ const createKwilClient = isBrowserRuntime() ? createWebKwilClient : createNodeKwilClient;
212
+ const kwilClient = await createKwilClient({
133
213
  nodeUrl: params.nodeUrl,
134
214
  chainId: params.chainId,
135
215
  });
136
216
 
137
217
  await params.enclaveProvider.load();
138
218
 
139
- return new idOSClientIdle(params.store, kwilClient, params.enclaveProvider);
219
+ const blobGatewayUrl = params.blobGatewayUrl ?? params.nodeUrl;
220
+ return new idOSClientIdle(
221
+ params.store,
222
+ kwilClient,
223
+ params.enclaveProvider,
224
+ idleBlobGateway(blobGatewayUrl),
225
+ );
140
226
  }
141
227
 
142
228
  async addressHasProfile(address: string): Promise<boolean> {
@@ -170,6 +256,7 @@ export class idOSClientIdle {
170
256
  walletIdentifier,
171
257
  walletPublicKey,
172
258
  walletType,
259
+ blobGatewayForSigner(this, kwilSigner),
173
260
  );
174
261
  }
175
262
 
@@ -188,6 +275,7 @@ export class idOSClientWithUserSigner implements Omit<Properties<idOSClientIdle>
188
275
  readonly walletIdentifier: string;
189
276
  readonly walletPublicKey: string | undefined;
190
277
  readonly walletType: WalletType;
278
+ readonly blobGateway: BlobGateway;
191
279
 
192
280
  constructor(
193
281
  idOSClientIdle: idOSClientIdle,
@@ -196,6 +284,7 @@ export class idOSClientWithUserSigner implements Omit<Properties<idOSClientIdle>
196
284
  walletIdentifier: string,
197
285
  walletPublicKey: string | undefined,
198
286
  walletType: WalletType,
287
+ blobGateway: BlobGateway,
199
288
  ) {
200
289
  this.state = "with-user-signer";
201
290
  this.store = idOSClientIdle.store;
@@ -206,13 +295,20 @@ export class idOSClientWithUserSigner implements Omit<Properties<idOSClientIdle>
206
295
  this.walletIdentifier = walletIdentifier;
207
296
  this.walletPublicKey = walletPublicKey;
208
297
  this.walletType = walletType;
298
+ this.blobGateway = blobGateway;
209
299
  // @ts-expect-error - TODO: Fix this
210
300
  this.enclaveProvider.setSigner(this.signer);
211
301
  }
212
302
 
213
303
  async logOut(): Promise<idOSClientIdle> {
214
304
  this.kwilClient.setSigner(undefined);
215
- return new idOSClientIdle(this.store, this.kwilClient, this.enclaveProvider);
305
+ await this.enclaveProvider.reset();
306
+ return new idOSClientIdle(
307
+ this.store,
308
+ this.kwilClient,
309
+ this.enclaveProvider,
310
+ idleBlobGateway(this.blobGateway.url),
311
+ );
216
312
  }
217
313
 
218
314
  async hasProfile(): Promise<boolean> {
@@ -268,6 +364,7 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
268
364
  readonly walletPublicKey: string | undefined;
269
365
  readonly walletType: WalletType;
270
366
  readonly user: idOSUser;
367
+ readonly blobGateway: BlobGateway;
271
368
 
272
369
  constructor(idOSClientWithUserSigner: idOSClientWithUserSigner, user: idOSUser) {
273
370
  this.state = "logged-in";
@@ -280,19 +377,85 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
280
377
  this.walletPublicKey = idOSClientWithUserSigner.walletPublicKey;
281
378
  this.walletType = idOSClientWithUserSigner.walletType;
282
379
  this.user = user;
380
+ this.blobGateway = idOSClientWithUserSigner.blobGateway;
283
381
  }
284
382
 
285
383
  async logOut(): Promise<idOSClientIdle> {
286
384
  this.kwilClient.setSigner(undefined);
287
- return new idOSClientIdle(this.store, this.kwilClient, this.enclaveProvider);
385
+ await this.enclaveProvider.reset();
386
+ return new idOSClientIdle(
387
+ this.store,
388
+ this.kwilClient,
389
+ this.enclaveProvider,
390
+ idleBlobGateway(this.blobGateway.url),
391
+ );
288
392
  }
289
393
 
290
394
  async requestDWGMessage(params: idOSDelegatedWriteGrant): Promise<string> {
291
395
  return dwgMessage(this.kwilClient, params).then((res) => res.message);
292
396
  }
293
397
 
398
+ async createCredential(
399
+ publicNotes: string,
400
+ plaintextContent: Uint8Array<ArrayBufferLike>,
401
+ // In case of credential creation on users behalf, this would be a issuer key
402
+ issuerSigningSecretKey?: Uint8Array,
403
+ ): Promise<Omit<idOSCredential, "user_id" | "content" | "inserter_type" | "inserter_id">> {
404
+ const credentialId = crypto.randomUUID();
405
+ const preliminaryCredential: PreliminaryIDOSCredential = {
406
+ ...(await buildPreliminaryIDOSCredential({
407
+ publicNotes,
408
+ plaintextContent,
409
+ recipientEncryptionPublicKey: base64Decode(this.user.recipient_encryption_public_key),
410
+ issuerSigningSecretKey,
411
+ contentUri: contentUriForWallet(this.walletType, this.kwilSigner, credentialId),
412
+ })),
413
+ id: credentialId,
414
+ };
415
+ requireAccessTokenForUkycContent(
416
+ preliminaryCredential.contentUri,
417
+ this.blobGateway.hasAccessToken,
418
+ );
419
+
420
+ const requestId = crypto.randomUUID();
421
+
422
+ const payload: CreatePreliminaryCredentialInput = {
423
+ request_id: requestId,
424
+ credential_id: preliminaryCredential.id,
425
+ issuer_auth_public_key: preliminaryCredential.issuerAuthPublicKey,
426
+ encryptor_public_key: preliminaryCredential.encryptorPublicKey,
427
+ content_uri: preliminaryCredential.contentUri,
428
+ content_size: preliminaryCredential.contentSize,
429
+ content_hash: hexEncodeSha256Hash(plaintextContent),
430
+ public_notes: preliminaryCredential.publicNotes,
431
+ public_notes_signature: preliminaryCredential.publicNotesSignature,
432
+ broader_signature: preliminaryCredential.broaderSignature,
433
+ };
434
+
435
+ await createPreliminaryCredential(this.kwilClient, payload);
436
+
437
+ await this.blobGateway.uploadCredentialBlobs({
438
+ requestId,
439
+ original: preliminaryCredential.encryptedContent,
440
+ });
441
+
442
+ return mapPreliminaryToIDOSCredential(preliminaryCredential);
443
+ }
444
+
294
445
  async removeCredential(id: string): Promise<{ id: string }> {
446
+ const credential = await this.getCredentialById(id);
447
+ invariant(credential, `"idOSCredential" with id ${id} not found`);
448
+
449
+ const contentUri = credential.content_uri ?? null;
450
+ requireAccessTokenForUkycContent(contentUri, this.blobGateway.hasAccessToken);
451
+
295
452
  await removeCredential(this.kwilClient, { id });
453
+
454
+ if (!contentUri) {
455
+ return { id };
456
+ }
457
+
458
+ await this.blobGateway.deleteCredentialBlob({ credentialId: id });
296
459
  return { id };
297
460
  }
298
461
 
@@ -300,8 +463,10 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
300
463
  return getCredentialOwned(this.kwilClient, { id }).then((res) => res[0]);
301
464
  }
302
465
 
303
- async shareCredential(credential: ShareCredentialInput): Promise<ShareCredentialInput> {
304
- await shareCredential(this.kwilClient, credential);
466
+ async shareCredential(
467
+ credential: SharePreliminaryCredentialInput,
468
+ ): Promise<SharePreliminaryCredentialInput> {
469
+ await sharePreliminaryCredential(this.kwilClient, credential);
305
470
  return credential;
306
471
  }
307
472
 
@@ -326,13 +491,7 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
326
491
  const credential = await this.getCredentialById(id);
327
492
 
328
493
  invariant(credential, `"idOSCredential" with id ${id} not found`);
329
-
330
- await this.enclaveProvider.ensureUserEncryptionProfile();
331
-
332
- const plaintext = await this.enclaveProvider.decrypt(
333
- base64Decode(credential.content),
334
- base64Decode(credential.encryptor_public_key),
335
- );
494
+ const plaintext = await this.#decryptCredentialContent(credential);
336
495
 
337
496
  return hexEncodeSha256Hash(plaintext);
338
497
  }
@@ -341,19 +500,24 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
341
500
  const credential = await this.getCredentialById(id);
342
501
 
343
502
  invariant(credential, `"idOSCredential" with id ${id} not found`);
344
-
345
- await this.enclaveProvider.ensureUserEncryptionProfile();
346
-
347
- const plaintext = await this.enclaveProvider.decrypt(
348
- base64Decode(credential.content),
349
- base64Decode(credential.encryptor_public_key),
350
- );
503
+ const plaintext = await this.#decryptCredentialContent(credential);
351
504
 
352
505
  return utf8Decode(plaintext);
353
506
  }
354
507
 
355
- async requestDAGMessage(params: DagMessageInput): Promise<string> {
356
- return dagMessage(this.kwilClient, params).then((res) => res.message);
508
+ async getCredentialWithEncryptedContent(id: string): Promise<idOSCredential> {
509
+ const credential = await this.getCredentialById(id);
510
+ invariant(credential, `"idOSCredential" with id ${id} not found`);
511
+ return this.#credentialWithInlineContent(credential);
512
+ }
513
+
514
+ async getCredentialSharedContent(id: string): Promise<string> {
515
+ const credential = await this.getCredentialShared(id);
516
+
517
+ invariant(credential, `Shared credential with id ${id} not found`);
518
+ const plaintext = await this.#decryptCredentialContent(credential);
519
+
520
+ return utf8Decode(plaintext);
357
521
  }
358
522
 
359
523
  async getGrants(
@@ -385,6 +549,11 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
385
549
  return { id };
386
550
  }
387
551
 
552
+ async addWalletMessage(params: AddWalletMessageInput): Promise<string> {
553
+ const result = await requestAddWalletMessage(this.kwilClient, params);
554
+ return result.message;
555
+ }
556
+
388
557
  async addWallet(params: AddWalletInput): Promise<AddWalletInput> {
389
558
  await addWallet(this.kwilClient, params);
390
559
 
@@ -485,8 +654,8 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
485
654
  omit: Parameters<typeof recordFilter>[2];
486
655
  };
487
656
  credentialLevelOrHigherFilter?: {
488
- userLevel: "basic" | "plus";
489
- requiredAddons: ("liveness" | "email" | "phoneNumber")[];
657
+ userLevel: BaseLevel;
658
+ requiredAddons: Addon[];
490
659
  };
491
660
  }): Promise<idOSCredentialListItem[]> {
492
661
  const credentials = await this.getAllCredentials();
@@ -563,7 +732,9 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
563
732
  );
564
733
 
565
734
  const matches = await this.enclaveProvider.filterCredentials(
566
- fullCredentials,
735
+ await Promise.all(
736
+ fullCredentials.map((credential) => this.#credentialWithInlineContent(credential)),
737
+ ),
567
738
  privateFieldFilters,
568
739
  );
569
740
 
@@ -586,49 +757,90 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
586
757
  consumerAuthPublicKey: string;
587
758
  lockedUntil?: number;
588
759
  },
589
- ): Promise<ShareCredentialInput> {
760
+ ): Promise<SharePreliminaryCredentialInput> {
590
761
  const credential = await this.getCredentialById(credentialId);
591
- const contentHash = await this.getCredentialContentSha256Hash(credentialId);
592
762
 
593
763
  invariant(credential, `"idOSCredential" with id ${credentialId} not found`);
594
764
 
595
- const plaintextContent = utf8Decode(
596
- await this.enclaveProvider.decrypt(
597
- base64Decode(credential.content),
598
- base64Decode(credential.encryptor_public_key),
599
- ),
600
- );
765
+ const plaintextContent = await this.#decryptCredentialContent(credential);
766
+ const contentHash = hexEncodeSha256Hash(plaintextContent);
601
767
 
602
768
  await this.enclaveProvider.ensureUserEncryptionProfile();
603
769
 
604
770
  const { content, encryptorPublicKey } = await this.enclaveProvider.encrypt(
605
- utf8Encode(plaintextContent),
771
+ plaintextContent,
606
772
  base64Decode(consumerEncryptionPublicKey),
607
773
  );
608
774
 
609
- const insertableCredential: ShareCredentialInput = {
610
- ...credential,
611
- ...buildInsertableIDOSCredential(
612
- credential.user_id,
613
- "",
614
- base64Encode(content),
615
- base64Encode(encryptorPublicKey),
616
- ),
617
- original_credential_id: credential.id,
618
- id: crypto.randomUUID(),
619
- grantee_wallet_identifier: consumerAuthPublicKey,
775
+ // User issues the shared copy: sign content_uri with an ephemeral user-side key.
776
+ // Copy issuer_auth_public_key need not match the original credential's issuer.
777
+ const copyId = crypto.randomUUID();
778
+ const ukycContentUri = contentUriForWallet(this.walletType, this.kwilSigner, copyId);
779
+ requireAccessTokenForUkycContent(ukycContentUri, this.blobGateway.hasAccessToken);
780
+ const copyReference = ukycContentUri
781
+ ? { uri: ukycContentUri, size: content.byteLength }
782
+ : await createBlobContentReference(content);
783
+ const signedReference = buildEphemeralSignedCredentialContentReference("", copyReference.uri);
784
+
785
+ // Integrator-supplied. ed25519 @caller is lowercase hex; after exact grantee
786
+ // matching, uppercase hex would store a grant the consumer cannot read. Other
787
+ // identifier encodings (NEAR, XRPL, Stellar, MM) are case-sensitive, so leave them.
788
+ const granteeWalletIdentifier = /^[0-9a-fA-F]{64}$/.test(consumerAuthPublicKey)
789
+ ? consumerAuthPublicKey.toLowerCase()
790
+ : consumerAuthPublicKey;
791
+
792
+ const preliminaryCredential: SharePreliminaryCredentialInput = {
793
+ ...signedReference,
794
+ request_id: crypto.randomUUID(),
795
+ copy_id: copyId,
796
+ original_id: credential.id,
797
+ content_uri: copyReference.uri,
798
+ content_size: copyReference.size,
799
+ encryptor_public_key: base64Encode(encryptorPublicKey),
800
+ grantee_wallet_identifier: granteeWalletIdentifier,
620
801
  locked_until: lockedUntil,
621
802
  content_hash: contentHash,
622
803
  };
623
804
 
624
- await this.shareCredential(insertableCredential);
805
+ await this.shareCredential(preliminaryCredential);
806
+ await this.blobGateway.uploadCredentialBlobs({
807
+ requestId: preliminaryCredential.request_id,
808
+ copy: content,
809
+ });
810
+
811
+ return preliminaryCredential;
812
+ }
813
+
814
+ async #credentialWithInlineContent(credential: idOSCredential): Promise<idOSCredential> {
815
+ if (credential.content) {
816
+ return credential;
817
+ }
818
+
819
+ return {
820
+ ...credential,
821
+ content: base64Encode(await this.#getCredentialEncryptedContent(credential)),
822
+ content_uri: null,
823
+ content_size: null,
824
+ };
825
+ }
826
+
827
+ async #decryptCredentialContent(credential: idOSCredential): Promise<Uint8Array> {
828
+ await this.enclaveProvider.ensureUserEncryptionProfile();
829
+
830
+ return this.enclaveProvider.decrypt(
831
+ await this.#getCredentialEncryptedContent(credential),
832
+ base64Decode(credential.encryptor_public_key),
833
+ );
834
+ }
625
835
 
626
- return insertableCredential;
836
+ async #getCredentialEncryptedContent(credential: idOSCredential): Promise<Uint8Array> {
837
+ return resolveCredentialEncryptedContent(credential, this.blobGateway);
627
838
  }
628
839
  }
629
840
 
630
841
  export type {
631
842
  idOSDelegatedWriteGrant,
843
+ DelegatedWriteGrantBaseParams,
632
844
  idOSCredential,
633
845
  idOSCredentialListItem,
634
846
  idOSGrant,
@@ -637,14 +849,21 @@ export type {
637
849
  idOSWallet,
638
850
  };
639
851
 
640
- export { signNearMessage };
852
+ export { signNearMessage, toDelegatedWriteGrantBaseParams };
641
853
 
642
854
  export function createIDOSClient(params: {
643
855
  nodeUrl: string;
856
+ /**
857
+ * Optional blob gateway base URL. Defaults to `nodeUrl`.
858
+ * In the browser this must share KGW's session-cookie Domain
859
+ * (typically the same host), because reads use `credentials: "include"`.
860
+ */
861
+ blobGatewayUrl?: string;
644
862
  enclaveOptions: Omit<IframeEnclave["options"], "mode">;
645
863
  }): idOSClientConfiguration<IframeEnclave> {
646
864
  return new idOSClientConfiguration({
647
865
  nodeUrl: params.nodeUrl,
866
+ blobGatewayUrl: params.blobGatewayUrl,
648
867
  enclaveOptions: params.enclaveOptions,
649
868
  });
650
869
  }