@yallet/rwa-sdk 0.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.
Files changed (58) hide show
  1. package/README.md +365 -0
  2. package/dist/credential-nft-server.d.ts +75 -0
  3. package/dist/credential-nft-server.d.ts.map +1 -0
  4. package/dist/credential-nft-server.js +122 -0
  5. package/dist/credential-nft-server.js.map +1 -0
  6. package/dist/credential-nft.d.ts +31 -0
  7. package/dist/credential-nft.d.ts.map +1 -0
  8. package/dist/credential-nft.js +38 -0
  9. package/dist/credential-nft.js.map +1 -0
  10. package/dist/encryption.d.ts +107 -0
  11. package/dist/encryption.d.ts.map +1 -0
  12. package/dist/encryption.js +176 -0
  13. package/dist/encryption.js.map +1 -0
  14. package/dist/index.d.ts +56 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +66 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/metadata.d.ts +161 -0
  19. package/dist/metadata.d.ts.map +1 -0
  20. package/dist/metadata.js +180 -0
  21. package/dist/metadata.js.map +1 -0
  22. package/dist/minting.d.ts +63 -0
  23. package/dist/minting.d.ts.map +1 -0
  24. package/dist/minting.js +194 -0
  25. package/dist/minting.js.map +1 -0
  26. package/dist/payloads.d.ts +120 -0
  27. package/dist/payloads.d.ts.map +1 -0
  28. package/dist/payloads.js +118 -0
  29. package/dist/payloads.js.map +1 -0
  30. package/dist/registry.d.ts +77 -0
  31. package/dist/registry.d.ts.map +1 -0
  32. package/dist/registry.js +173 -0
  33. package/dist/registry.js.map +1 -0
  34. package/dist/sdk.d.ts +158 -0
  35. package/dist/sdk.d.ts.map +1 -0
  36. package/dist/sdk.js +346 -0
  37. package/dist/sdk.js.map +1 -0
  38. package/dist/storage.d.ts +80 -0
  39. package/dist/storage.d.ts.map +1 -0
  40. package/dist/storage.js +284 -0
  41. package/dist/storage.js.map +1 -0
  42. package/dist/types.d.ts +278 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +19 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +64 -0
  47. package/src/credential-nft-server.ts +201 -0
  48. package/src/credential-nft.ts +64 -0
  49. package/src/encryption.ts +308 -0
  50. package/src/index.ts +151 -0
  51. package/src/metadata.ts +336 -0
  52. package/src/minting.ts +266 -0
  53. package/src/payloads.ts +238 -0
  54. package/src/registry.ts +236 -0
  55. package/src/sdk.ts +507 -0
  56. package/src/storage.ts +364 -0
  57. package/src/types.ts +318 -0
  58. package/wasm/README.md +33 -0
package/src/sdk.ts ADDED
@@ -0,0 +1,507 @@
1
+ /**
2
+ * Yallet RWA SDK - Main SDK Class
3
+ *
4
+ * High-level interface for minting encrypted RWA assets as Solana cNFTs
5
+ */
6
+
7
+ import type {
8
+ SDKConfig,
9
+ MintRequest,
10
+ MintResult,
11
+ RegisteredUser,
12
+ UserRegistry,
13
+ InvoiceData,
14
+ NoteData,
15
+ FileData,
16
+ PhotoData,
17
+ EncryptFunction,
18
+ TreeConfig,
19
+ Signer,
20
+ } from './types.js';
21
+ import { AssetType } from './types.js';
22
+ import { ECIESEncryptor, createWasmEncryptor, encryptAsset } from './encryption.js';
23
+ import {
24
+ buildInvoicePayload,
25
+ buildFilePayload,
26
+ buildMessagePayload,
27
+ buildPhotoPayload,
28
+ } from './payloads.js';
29
+ import { uploadEncryptedAsset, uploadMetadata, type UploadOptions } from './storage.js';
30
+ import { generateNFTMetadata, generateInvoiceMetadata } from './metadata.js';
31
+ import { CNFTMinter, createMinter } from './minting.js';
32
+ import { InMemoryRegistry, createRegistry, type RegistryOptions } from './registry.js';
33
+ import type { Keypair } from '@metaplex-foundation/umi';
34
+
35
+ // ======================================
36
+ // RWA SDK Class
37
+ // ======================================
38
+
39
+ export class YalletRWASDK {
40
+ private config: SDKConfig;
41
+ private registry: UserRegistry;
42
+ private minter: CNFTMinter;
43
+ private encryptor: ECIESEncryptor | ReturnType<typeof createWasmEncryptor>;
44
+ private uploadOptions: UploadOptions;
45
+
46
+ constructor(
47
+ config: SDKConfig,
48
+ options: {
49
+ registry?: UserRegistry | RegistryOptions;
50
+ wasmEncryptFn?: EncryptFunction;
51
+ uploadOptions?: UploadOptions;
52
+ } = {}
53
+ ) {
54
+ this.config = config;
55
+
56
+ // Initialize registry
57
+ if (options.registry) {
58
+ if ('getUser' in options.registry) {
59
+ this.registry = options.registry;
60
+ } else {
61
+ this.registry = createRegistry(options.registry);
62
+ }
63
+ } else {
64
+ this.registry = new InMemoryRegistry();
65
+ }
66
+
67
+ // Initialize encryptor
68
+ if (options.wasmEncryptFn) {
69
+ this.encryptor = createWasmEncryptor(options.wasmEncryptFn);
70
+ } else {
71
+ this.encryptor = new ECIESEncryptor();
72
+ }
73
+
74
+ // Initialize minter
75
+ this.minter = createMinter(config);
76
+
77
+ // Upload options
78
+ this.uploadOptions = {
79
+ network: config.network,
80
+ apiKey: config.arweaveApiKey,
81
+ endpoint: config.arweaveEndpoint,
82
+ ...options.uploadOptions,
83
+ };
84
+ }
85
+
86
+ // ======================================
87
+ // Configuration
88
+ // ======================================
89
+
90
+ /**
91
+ * Set the signer for minting transactions
92
+ *
93
+ * @param keypair - Umi keypair for signing
94
+ */
95
+ setSigner(keypair: Keypair): void {
96
+ this.minter.setSigner(keypair);
97
+ }
98
+
99
+ /**
100
+ * Set the Merkle tree configuration
101
+ *
102
+ * @param treeConfig - Tree configuration
103
+ */
104
+ setTreeConfig(treeConfig: TreeConfig): void {
105
+ this.minter.setTreeConfig(treeConfig);
106
+ }
107
+
108
+ /**
109
+ * Get the user registry
110
+ */
111
+ getRegistry(): UserRegistry {
112
+ return this.registry;
113
+ }
114
+
115
+ // ======================================
116
+ // User Management
117
+ // ======================================
118
+
119
+ /**
120
+ * Register a user with their xidentity
121
+ *
122
+ * @param user - User to register
123
+ */
124
+ async registerUser(user: RegisteredUser): Promise<void> {
125
+ await this.registry.registerUser(user);
126
+ }
127
+
128
+ /**
129
+ * Get a registered user by Solana address
130
+ *
131
+ * @param solanaAddress - User's Solana address
132
+ * @returns Registered user or null
133
+ */
134
+ async getUser(solanaAddress: string): Promise<RegisteredUser | null> {
135
+ return this.registry.getUser(solanaAddress);
136
+ }
137
+
138
+ // ======================================
139
+ // Minting Methods
140
+ // ======================================
141
+
142
+ /**
143
+ * Mint an encrypted invoice to a recipient.
144
+ * Uses extension-compatible payload (invoice bundle) so the recipient's wallet can decrypt and display.
145
+ *
146
+ * @param invoice - Invoice data
147
+ * @param recipientAddress - Recipient's Solana address
148
+ * @param options - Name, description, and optional pdf/sender/recipient for extension compatibility
149
+ * @returns Mint result
150
+ */
151
+ async mintInvoice(
152
+ invoice: InvoiceData,
153
+ recipientAddress: string,
154
+ options: {
155
+ name?: string;
156
+ description?: string;
157
+ pdfBase64?: string;
158
+ senderProfile?: Record<string, unknown>;
159
+ recipientProfile?: Record<string, unknown>;
160
+ uuid?: string;
161
+ } = {}
162
+ ): Promise<MintResult> {
163
+ const bundle = buildInvoicePayload(invoice, {
164
+ uuid: options.uuid,
165
+ pdfBase64: options.pdfBase64,
166
+ senderProfile: options.senderProfile,
167
+ recipientProfile: options.recipientProfile,
168
+ });
169
+ return this.mint({
170
+ assetType: AssetType.INVOICE,
171
+ assetData: bundle as unknown as InvoiceData,
172
+ recipientAddress,
173
+ name: options.name || `Invoice #${invoice.invoiceId}`,
174
+ description: options.description,
175
+ uuid: options.uuid,
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Mint an encrypted file to a recipient (extension-compatible payload).
181
+ */
182
+ async mintFile(
183
+ file: FileData,
184
+ recipientAddress: string,
185
+ options: { name?: string; description?: string; uuid?: string } = {}
186
+ ): Promise<MintResult> {
187
+ const payload = buildFilePayload(file, { id: options.uuid });
188
+ return this.mint({
189
+ assetType: AssetType.FILE,
190
+ assetData: payload as unknown as FileData,
191
+ recipientAddress,
192
+ name: options.name || file.filename,
193
+ description: options.description,
194
+ uuid: options.uuid,
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Mint an encrypted message/note to a recipient (extension-compatible payload, type always 'sent').
200
+ */
201
+ async mintMessage(
202
+ note: NoteData,
203
+ recipientAddress: string,
204
+ options: {
205
+ name?: string;
206
+ description?: string;
207
+ uuid?: string;
208
+ recipientId?: string | null;
209
+ recipientName?: string | null;
210
+ replyTo?: string | null;
211
+ } = {}
212
+ ): Promise<MintResult> {
213
+ const payload = buildMessagePayload(note, {
214
+ uuid: options.uuid,
215
+ recipientId: options.recipientId,
216
+ recipientName: options.recipientName,
217
+ replyTo: options.replyTo,
218
+ });
219
+ return this.mint({
220
+ assetType: AssetType.NOTE,
221
+ assetData: payload as unknown as NoteData,
222
+ recipientAddress,
223
+ name: options.name || note.title,
224
+ description: options.description,
225
+ uuid: options.uuid,
226
+ });
227
+ }
228
+
229
+ /**
230
+ * Mint an encrypted picture/photo to a recipient (extension-compatible payload).
231
+ */
232
+ async mintPhoto(
233
+ photo: PhotoData,
234
+ recipientAddress: string,
235
+ options: { name?: string; description?: string; uuid?: string; thumbnail?: string } = {}
236
+ ): Promise<MintResult> {
237
+ const payload = buildPhotoPayload(photo, {
238
+ id: options.uuid,
239
+ thumbnail: options.thumbnail,
240
+ });
241
+ return this.mint({
242
+ assetType: AssetType.PHOTO,
243
+ assetData: payload as unknown as PhotoData,
244
+ recipientAddress,
245
+ name: options.name || photo.filename,
246
+ description: options.description,
247
+ uuid: options.uuid,
248
+ });
249
+ }
250
+
251
+ /**
252
+ * Mint an encrypted asset to a recipient
253
+ *
254
+ * This is the main minting method that handles the full flow:
255
+ * 1. Look up recipient's xidentity from registry
256
+ * 2. Encrypt asset with recipient's xidentity
257
+ * 3. Upload encrypted data to Arweave
258
+ * 4. Generate and upload NFT metadata
259
+ * 5. Mint cNFT to recipient
260
+ *
261
+ * @param request - Mint request
262
+ * @returns Mint result
263
+ */
264
+ async mint(request: MintRequest): Promise<MintResult> {
265
+ const {
266
+ assetType,
267
+ assetData,
268
+ recipientAddress,
269
+ name,
270
+ description,
271
+ attributes = [],
272
+ // New fields
273
+ uuid,
274
+ previousToken,
275
+ schema,
276
+ mimeType,
277
+ dataSize,
278
+ } = request;
279
+
280
+ try {
281
+ // 1. Look up recipient's xidentity
282
+ const recipient = await this.registry.getUser(recipientAddress);
283
+
284
+ if (!recipient) {
285
+ return {
286
+ success: false,
287
+ error: `Recipient not registered: ${recipientAddress}. User must register their xidentity first.`,
288
+ };
289
+ }
290
+
291
+ console.log(`[RWA SDK] Minting ${assetType} to ${recipientAddress}`);
292
+
293
+ // 2. Encrypt asset with recipient's xidentity
294
+ console.log('[RWA SDK] Encrypting asset...');
295
+ const encryptedPayload = await encryptAsset(
296
+ assetData,
297
+ assetType,
298
+ recipient.xidentity,
299
+ this.encryptor,
300
+ { uuid, previousToken, schema }
301
+ );
302
+
303
+ // 3. Upload encrypted data to Arweave
304
+ console.log('[RWA SDK] Uploading encrypted data to Arweave...');
305
+ const dataUploadResult = await uploadEncryptedAsset(
306
+ encryptedPayload,
307
+ {
308
+ assetType,
309
+ direction: 'received',
310
+ network: this.config.network,
311
+ },
312
+ this.uploadOptions
313
+ );
314
+
315
+ console.log('[RWA SDK] Encrypted data uploaded:', dataUploadResult.id);
316
+
317
+ // 4. Generate NFT metadata with new fields
318
+ const nftMetadata = generateNFTMetadata({
319
+ name,
320
+ description,
321
+ assetType,
322
+ encryptedDataUri: dataUploadResult.arweaveUri,
323
+ owner: recipientAddress,
324
+ direction: 'received',
325
+ additionalAttributes: attributes,
326
+ // New fields from encryption result
327
+ uuid: encryptedPayload.uuid,
328
+ timestamp: Math.floor(encryptedPayload.timestamp / 1000), // Convert to seconds
329
+ previousToken,
330
+ schema,
331
+ mimeType,
332
+ dataSize: dataSize || encryptedPayload.dataSize,
333
+ });
334
+
335
+ // 5. Upload metadata to Arweave
336
+ console.log('[RWA SDK] Uploading metadata to Arweave...');
337
+ const metadataUploadResult = await uploadMetadata(
338
+ nftMetadata as unknown as Record<string, unknown>,
339
+ this.uploadOptions
340
+ );
341
+
342
+ console.log('[RWA SDK] Metadata uploaded:', metadataUploadResult.id);
343
+
344
+ // 6. Mint cNFT
345
+ console.log('[RWA SDK] Minting cNFT...');
346
+ const mintResult = await this.minter.mint({
347
+ metadataUri: metadataUploadResult.arweaveUri,
348
+ name,
349
+ recipient: recipientAddress,
350
+ });
351
+
352
+ if (!mintResult.success) {
353
+ return { ...mintResult, uuid: encryptedPayload.uuid };
354
+ }
355
+
356
+ console.log('[RWA SDK] cNFT minted successfully!');
357
+
358
+ return {
359
+ success: true,
360
+ assetId: mintResult.assetId,
361
+ signature: mintResult.signature,
362
+ arweaveTxId: dataUploadResult.id,
363
+ metadataArweaveTxId: metadataUploadResult.id,
364
+ uuid: encryptedPayload.uuid,
365
+ };
366
+ } catch (err) {
367
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
368
+ console.error('[RWA SDK] Minting failed:', errorMessage);
369
+
370
+ return {
371
+ success: false,
372
+ error: errorMessage,
373
+ };
374
+ }
375
+ }
376
+
377
+ // ======================================
378
+ // Batch Minting
379
+ // ======================================
380
+
381
+ /**
382
+ * Mint multiple invoices to different recipients
383
+ *
384
+ * @param invoices - Array of invoice requests
385
+ * @returns Array of mint results
386
+ */
387
+ async mintInvoiceBatch(
388
+ invoices: Array<{
389
+ invoice: InvoiceData;
390
+ recipientAddress: string;
391
+ name?: string;
392
+ }>
393
+ ): Promise<MintResult[]> {
394
+ const results: MintResult[] = [];
395
+
396
+ for (const item of invoices) {
397
+ const result = await this.mintInvoice(
398
+ item.invoice,
399
+ item.recipientAddress,
400
+ { name: item.name }
401
+ );
402
+ results.push(result);
403
+ }
404
+
405
+ return results;
406
+ }
407
+
408
+ // ======================================
409
+ // Utility Methods
410
+ // ======================================
411
+
412
+ /**
413
+ * Check if a user is registered
414
+ *
415
+ * @param solanaAddress - User's Solana address
416
+ * @returns true if registered
417
+ */
418
+ async isUserRegistered(solanaAddress: string): Promise<boolean> {
419
+ const user = await this.registry.getUser(solanaAddress);
420
+ return user !== null;
421
+ }
422
+
423
+ /**
424
+ * Get the current tree configuration
425
+ */
426
+ async getTreeConfig(): Promise<TreeConfig | null> {
427
+ return this.minter.getTreeConfig();
428
+ }
429
+
430
+ /**
431
+ * Encrypt and upload without minting
432
+ * Useful for testing or when you want to handle minting separately
433
+ *
434
+ * @param assetData - Asset data to encrypt
435
+ * @param assetType - Type of asset
436
+ * @param recipientXidentity - Recipient's xidentity (base64)
437
+ * @returns Upload results
438
+ */
439
+ async encryptAndUpload(
440
+ assetData: unknown,
441
+ assetType: AssetType,
442
+ recipientXidentity: string
443
+ ): Promise<{
444
+ encryptedDataUri: string;
445
+ metadataUri: string;
446
+ }> {
447
+ // Encrypt
448
+ const encryptedPayload = await encryptAsset(
449
+ assetData,
450
+ assetType,
451
+ recipientXidentity,
452
+ this.encryptor
453
+ );
454
+
455
+ // Upload encrypted data
456
+ const dataUploadResult = await uploadEncryptedAsset(
457
+ encryptedPayload,
458
+ {
459
+ assetType,
460
+ direction: 'received',
461
+ network: this.config.network,
462
+ },
463
+ this.uploadOptions
464
+ );
465
+
466
+ // Generate and upload metadata
467
+ const nftMetadata = generateNFTMetadata({
468
+ name: `Encrypted ${assetType}`,
469
+ assetType,
470
+ encryptedDataUri: dataUploadResult.arweaveUri,
471
+ owner: 'pending', // Will be set during minting
472
+ direction: 'received',
473
+ });
474
+
475
+ const metadataUploadResult = await uploadMetadata(
476
+ nftMetadata as unknown as Record<string, unknown>,
477
+ this.uploadOptions
478
+ );
479
+
480
+ return {
481
+ encryptedDataUri: dataUploadResult.arweaveUri,
482
+ metadataUri: metadataUploadResult.arweaveUri,
483
+ };
484
+ }
485
+ }
486
+
487
+ // ======================================
488
+ // Factory Function
489
+ // ======================================
490
+
491
+ /**
492
+ * Create a new RWA SDK instance
493
+ *
494
+ * @param config - SDK configuration
495
+ * @param options - Additional options
496
+ * @returns YalletRWASDK instance
497
+ */
498
+ export function createRWASDK(
499
+ config: SDKConfig,
500
+ options?: {
501
+ registry?: UserRegistry | RegistryOptions;
502
+ wasmEncryptFn?: EncryptFunction;
503
+ uploadOptions?: UploadOptions;
504
+ }
505
+ ): YalletRWASDK {
506
+ return new YalletRWASDK(config, options);
507
+ }