@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/minting.ts ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Yallet RWA SDK - Minting Module
3
+ *
4
+ * Handles cNFT minting using Metaplex Bubblegum
5
+ */
6
+
7
+ import {
8
+ createUmi,
9
+ publicKey,
10
+ keypairIdentity,
11
+ type Umi,
12
+ type PublicKey,
13
+ type Keypair,
14
+ } from '@metaplex-foundation/umi';
15
+ import { createUmi as createUmiDefaults } from '@metaplex-foundation/umi-bundle-defaults';
16
+ import {
17
+ mplBubblegum,
18
+ mintV1,
19
+ findLeafAssetIdPda,
20
+ type MetadataArgsArgs,
21
+ } from '@metaplex-foundation/mpl-bubblegum';
22
+ import { Connection, PublicKey as SolanaPublicKey } from '@solana/web3.js';
23
+
24
+ import type {
25
+ TreeConfig,
26
+ MintResult,
27
+ Signer,
28
+ SDKConfig,
29
+ } from './types.js';
30
+ import { AssetType } from './types.js';
31
+
32
+ // ======================================
33
+ // Minter Class
34
+ // ======================================
35
+
36
+ export class CNFTMinter {
37
+ private umi: Umi;
38
+ private connection: Connection;
39
+ private config: SDKConfig;
40
+ private treeConfig: TreeConfig | null = null;
41
+
42
+ constructor(config: SDKConfig) {
43
+ this.config = config;
44
+ this.connection = new Connection(config.rpcEndpoint);
45
+
46
+ // Initialize Umi
47
+ this.umi = createUmiDefaults(config.rpcEndpoint).use(mplBubblegum());
48
+ }
49
+
50
+ /**
51
+ * Set the signer for minting transactions
52
+ *
53
+ * @param signer - Signer with publicKey and signTransaction
54
+ */
55
+ setSigner(keypair: Keypair): void {
56
+ this.umi = this.umi.use(keypairIdentity(keypair));
57
+ }
58
+
59
+ /**
60
+ * Set the Merkle tree configuration
61
+ *
62
+ * @param treeConfig - Tree configuration
63
+ */
64
+ setTreeConfig(treeConfig: TreeConfig): void {
65
+ this.treeConfig = treeConfig;
66
+ }
67
+
68
+ /**
69
+ * Get or fetch tree configuration
70
+ */
71
+ async getTreeConfig(): Promise<TreeConfig | null> {
72
+ if (this.treeConfig) {
73
+ return this.treeConfig;
74
+ }
75
+
76
+ // Try to fetch from configured address
77
+ if (this.config.merkleTreeAddress) {
78
+ // For now, return a basic config
79
+ // In production, you'd fetch the actual tree data
80
+ return {
81
+ treeAddress: this.config.merkleTreeAddress,
82
+ treeAuthority: this.config.merkleTreeAuthority || this.config.merkleTreeAddress,
83
+ maxDepth: 14,
84
+ capacity: 16384,
85
+ used: 0,
86
+ remaining: 16384,
87
+ usagePercent: 0,
88
+ };
89
+ }
90
+
91
+ // Try to fetch from backend API
92
+ if (this.config.backendApiUrl) {
93
+ try {
94
+ const response = await fetch(
95
+ `${this.config.backendApiUrl}/api/cnft/active-tree?network=${this.config.network}`
96
+ );
97
+
98
+ if (response.ok) {
99
+ const data = (await response.json()) as {
100
+ available?: boolean;
101
+ tree?: {
102
+ address: string;
103
+ authority: string;
104
+ maxDepth: number;
105
+ capacity: number;
106
+ used: number;
107
+ remaining: number;
108
+ usagePercent: string;
109
+ };
110
+ };
111
+ if (data.available && data.tree) {
112
+ this.treeConfig = {
113
+ treeAddress: data.tree.address,
114
+ treeAuthority: data.tree.authority,
115
+ maxDepth: data.tree.maxDepth,
116
+ capacity: data.tree.capacity,
117
+ used: data.tree.used,
118
+ remaining: data.tree.remaining,
119
+ usagePercent: parseFloat(data.tree.usagePercent),
120
+ };
121
+ return this.treeConfig;
122
+ }
123
+ }
124
+ } catch (err) {
125
+ console.warn('Failed to fetch tree from backend:', err);
126
+ }
127
+ }
128
+
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * Mint a compressed NFT
134
+ *
135
+ * @param params - Minting parameters
136
+ * @returns Mint result
137
+ */
138
+ async mint(params: {
139
+ metadataUri: string;
140
+ name: string;
141
+ symbol?: string;
142
+ recipient: string;
143
+ sellerFeeBasisPoints?: number;
144
+ creators?: Array<{ address: string; share: number }>;
145
+ }): Promise<MintResult> {
146
+ const {
147
+ metadataUri,
148
+ name,
149
+ symbol = 'YRWA',
150
+ recipient,
151
+ sellerFeeBasisPoints = 0,
152
+ creators = [],
153
+ } = params;
154
+
155
+ // Get tree configuration
156
+ const treeConfig = await this.getTreeConfig();
157
+
158
+ if (!treeConfig) {
159
+ return {
160
+ success: false,
161
+ error: 'No Merkle tree configured. Please set a tree address or backend API.',
162
+ };
163
+ }
164
+
165
+ try {
166
+ const merkleTree = publicKey(treeConfig.treeAddress);
167
+ const leafOwner = publicKey(recipient);
168
+
169
+ // Prepare metadata args
170
+ const metadataArgs: MetadataArgsArgs = {
171
+ name,
172
+ symbol,
173
+ uri: metadataUri,
174
+ sellerFeeBasisPoints,
175
+ primarySaleHappened: false,
176
+ isMutable: true,
177
+ editionNonce: null,
178
+ tokenStandard: null,
179
+ collection: null,
180
+ uses: null,
181
+ tokenProgramVersion: 0, // Original
182
+ creators: creators.map((c) => ({
183
+ address: publicKey(c.address),
184
+ verified: false,
185
+ share: c.share,
186
+ })),
187
+ };
188
+
189
+ // Build and send mint instruction
190
+ const { signature } = await mintV1(this.umi, {
191
+ merkleTree,
192
+ leafOwner,
193
+ metadata: metadataArgs,
194
+ }).sendAndConfirm(this.umi);
195
+
196
+ // Get the leaf index to compute asset ID
197
+ // Note: In production, you'd parse the transaction logs
198
+ const signatureStr = Buffer.from(signature).toString('base64');
199
+
200
+ // Notify backend of successful mint (if configured)
201
+ if (this.config.backendApiUrl) {
202
+ try {
203
+ await fetch(`${this.config.backendApiUrl}/api/cnft/mint-complete`, {
204
+ method: 'POST',
205
+ headers: { 'Content-Type': 'application/json' },
206
+ body: JSON.stringify({
207
+ network: this.config.network,
208
+ treeAddress: treeConfig.treeAddress,
209
+ }),
210
+ });
211
+ } catch (err) {
212
+ console.warn('Failed to notify backend of mint:', err);
213
+ }
214
+ }
215
+
216
+ return {
217
+ success: true,
218
+ signature: signatureStr,
219
+ // Asset ID would be computed from the leaf index
220
+ // For now, we return the signature
221
+ };
222
+ } catch (err) {
223
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
224
+ return {
225
+ success: false,
226
+ error: `Minting failed: ${errorMessage}`,
227
+ };
228
+ }
229
+ }
230
+ }
231
+
232
+ // ======================================
233
+ // Helper Functions
234
+ // ======================================
235
+
236
+ /**
237
+ * Create a new CNFTMinter instance
238
+ *
239
+ * @param config - SDK configuration
240
+ * @returns CNFTMinter instance
241
+ */
242
+ export function createMinter(config: SDKConfig): CNFTMinter {
243
+ return new CNFTMinter(config);
244
+ }
245
+
246
+ /**
247
+ * Estimate minting cost
248
+ *
249
+ * @param connection - Solana connection
250
+ * @returns Estimated cost in lamports
251
+ */
252
+ export async function estimateMintCost(connection: Connection): Promise<number> {
253
+ // cNFT minting is very cheap - typically around 0.00001 SOL
254
+ // The main cost is the priority fee
255
+ const recentPrioritizationFees = await connection.getRecentPrioritizationFees();
256
+ const avgFee = recentPrioritizationFees.reduce(
257
+ (sum, fee) => sum + fee.prioritizationFee,
258
+ 0
259
+ ) / (recentPrioritizationFees.length || 1);
260
+
261
+ // Base transaction fee + priority fee
262
+ const baseFee = 5000; // 0.000005 SOL
263
+ const priorityFee = Math.ceil(avgFee);
264
+
265
+ return baseFee + priorityFee;
266
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Yallet RWA SDK - Payload builders
3
+ *
4
+ * Build JSON payloads that match chrome-extension decryption expectations
5
+ * for invoice / statements / receipts / files / messages / pictures.
6
+ */
7
+
8
+ import type {
9
+ InvoiceData,
10
+ InvoiceItem,
11
+ NoteData,
12
+ FileData,
13
+ PhotoData,
14
+ } from './types.js';
15
+
16
+ // ======================================
17
+ // Extension-compatible types (output)
18
+ // ======================================
19
+
20
+ /** Line item shape expected by extension */
21
+ export interface ExtensionLineItem {
22
+ name: string;
23
+ price: number;
24
+ qty: number;
25
+ unit: string;
26
+ total: number;
27
+ }
28
+
29
+ /** Invoice payload bundle (extension decrypts and uses invoice + optional pdf) */
30
+ export interface InvoicePayloadBundle {
31
+ invoice: ExtensionInvoicePayload;
32
+ pdf?: string;
33
+ senderProfile?: Record<string, unknown>;
34
+ recipientProfile?: Record<string, unknown>;
35
+ }
36
+
37
+ export interface ExtensionInvoicePayload {
38
+ id?: string;
39
+ uuid?: string;
40
+ invoiceNo: string;
41
+ type?: 'sent' | 'received';
42
+ status?: string;
43
+ billTo?: string;
44
+ poNo?: string;
45
+ dueDate?: string;
46
+ lineItems: ExtensionLineItem[];
47
+ discountRate?: number;
48
+ discountAmount?: number;
49
+ taxRate?: number;
50
+ subtotal: number;
51
+ taxAmount?: number;
52
+ total: number;
53
+ currency: string;
54
+ paymentMethod?: string;
55
+ paymentTerms?: string;
56
+ notePayee?: string;
57
+ createdAt: string;
58
+ }
59
+
60
+ /** File payload (extension expects name, fileData base64, type, etc.) */
61
+ export interface ExtensionFilePayload {
62
+ id?: string;
63
+ name: string;
64
+ originalName?: string;
65
+ description?: string;
66
+ tag?: string | null;
67
+ size: number;
68
+ type: string;
69
+ fileData: string;
70
+ createdAt: string;
71
+ }
72
+
73
+ /** Note/Message payload (extension: note with title, content, type, replyTo) */
74
+ export interface ExtensionNotePayload {
75
+ id?: string;
76
+ uuid?: string;
77
+ title: string;
78
+ content: string;
79
+ tag?: string | null;
80
+ type?: 'private' | 'sent' | 'received';
81
+ recipientId?: string | null;
82
+ recipientName?: string | null;
83
+ replyTo?: string | null;
84
+ createdAt: string;
85
+ }
86
+
87
+ /** Photo payload (extension: imageData base64, mimeType, name) */
88
+ export interface ExtensionPhotoPayload {
89
+ id?: string;
90
+ name: string;
91
+ description?: string;
92
+ tag?: string | null;
93
+ imageData: string;
94
+ mimeType: string;
95
+ dataUrl?: string;
96
+ thumbnail?: string;
97
+ createdAt: string;
98
+ }
99
+
100
+ // ======================================
101
+ // Helpers
102
+ // ======================================
103
+
104
+ function toExtensionLineItems(items: InvoiceItem[] | undefined): ExtensionLineItem[] {
105
+ if (!items?.length) return [];
106
+ return items.map((li) => ({
107
+ name: li.name ?? li.description,
108
+ price: li.unitPrice ?? 0,
109
+ qty: li.qty ?? li.quantity,
110
+ unit: li.unit ?? 'units',
111
+ total: li.total ?? li.amount ?? (li.quantity * (li.unitPrice ?? 0)),
112
+ }));
113
+ }
114
+
115
+ // ======================================
116
+ // Payload builders
117
+ // ======================================
118
+
119
+ /**
120
+ * Build invoice payload bundle for minting.
121
+ * Matches chrome-extension format so the extension can decrypt and display.
122
+ */
123
+ export function buildInvoicePayload(
124
+ invoice: InvoiceData,
125
+ options?: {
126
+ id?: string;
127
+ uuid?: string;
128
+ pdfBase64?: string;
129
+ senderProfile?: Record<string, unknown>;
130
+ recipientProfile?: Record<string, unknown>;
131
+ }
132
+ ): InvoicePayloadBundle {
133
+ const lineItems = toExtensionLineItems(invoice.items);
134
+ const subtotal = invoice.subtotal ?? invoice.amount ?? 0;
135
+ const total = invoice.total ?? invoice.amount ?? subtotal;
136
+
137
+ const extensionInvoice: ExtensionInvoicePayload = {
138
+ id: options?.id,
139
+ uuid: options?.uuid,
140
+ invoiceNo: invoice.invoiceId,
141
+ type: 'sent',
142
+ status: 'submitted',
143
+ billTo: invoice.billTo,
144
+ poNo: invoice.poNo,
145
+ dueDate: invoice.dueDate,
146
+ lineItems,
147
+ discountRate: invoice.discountRate ?? 0,
148
+ discountAmount: invoice.discountAmount,
149
+ taxRate: invoice.taxRate ?? 0,
150
+ subtotal,
151
+ taxAmount: invoice.taxAmount,
152
+ total,
153
+ currency: invoice.currency,
154
+ paymentMethod: invoice.paymentMethod,
155
+ paymentTerms: invoice.paymentTerms,
156
+ notePayee: invoice.notePayee ?? invoice.notes,
157
+ createdAt: invoice.date ?? new Date().toISOString(),
158
+ };
159
+
160
+ const bundle: InvoicePayloadBundle = {
161
+ invoice: extensionInvoice,
162
+ };
163
+ if (options?.pdfBase64) bundle.pdf = options.pdfBase64;
164
+ if (options?.senderProfile) bundle.senderProfile = options.senderProfile;
165
+ if (options?.recipientProfile) bundle.recipientProfile = options.recipientProfile;
166
+ return bundle;
167
+ }
168
+
169
+ /**
170
+ * Build file payload for minting.
171
+ * Matches chrome-extension format (name, fileData base64, type, size).
172
+ */
173
+ export function buildFilePayload(
174
+ file: FileData,
175
+ options?: { id?: string }
176
+ ): ExtensionFilePayload {
177
+ return {
178
+ id: options?.id,
179
+ name: file.filename,
180
+ originalName: file.filename,
181
+ description: (file.metadata as { description?: string })?.description,
182
+ tag: null,
183
+ size: file.size,
184
+ type: file.mimeType,
185
+ fileData: file.content,
186
+ createdAt: (file.metadata as { createdAt?: string })?.createdAt ?? new Date().toISOString(),
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Build message/note payload for minting.
192
+ * Matches chrome-extension format (title, content, replyTo). Type is always 'sent' (institution sends to users).
193
+ */
194
+ export function buildMessagePayload(
195
+ note: NoteData,
196
+ options?: {
197
+ id?: string;
198
+ uuid?: string;
199
+ recipientId?: string | null;
200
+ recipientName?: string | null;
201
+ replyTo?: string | null;
202
+ }
203
+ ): ExtensionNotePayload {
204
+ return {
205
+ id: options?.id,
206
+ uuid: options?.uuid,
207
+ title: note.title,
208
+ content: note.content,
209
+ tag: note.tags?.length ? note.tags.join(', ') : null,
210
+ type: 'sent',
211
+ recipientId: options?.recipientId ?? null,
212
+ recipientName: options?.recipientName ?? null,
213
+ replyTo: options?.replyTo ?? null,
214
+ createdAt: (note.metadata as { createdAt?: string })?.createdAt ?? new Date().toISOString(),
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Build picture/photo payload for minting.
220
+ * Matches chrome-extension format (imageData base64, mimeType, name).
221
+ */
222
+ export function buildPhotoPayload(
223
+ photo: PhotoData,
224
+ options?: { id?: string; thumbnail?: string; dataUrl?: string }
225
+ ): ExtensionPhotoPayload {
226
+ const dataUrl = options?.dataUrl ?? (photo.content ? `data:${photo.mimeType};base64,${photo.content}` : undefined);
227
+ return {
228
+ id: options?.id,
229
+ name: photo.filename,
230
+ description: (photo.metadata as { description?: string })?.description,
231
+ tag: null,
232
+ imageData: photo.content,
233
+ mimeType: photo.mimeType,
234
+ dataUrl,
235
+ thumbnail: options?.thumbnail,
236
+ createdAt: (photo.metadata as { createdAt?: string })?.createdAt ?? new Date().toISOString(),
237
+ };
238
+ }