@yallet/rwa-sdk
v0.2.0
Published
Yallet RWA SDK - Mint encrypted invoices and assets as Solana cNFTs
Downloads
134
Maintainers
Readme
Yallet RWA SDK
SDK for minting encrypted RWA (Real World Assets) as Solana compressed NFTs (cNFTs).
Features
- 🔐 End-to-end encryption using xidentity (X25519) public keys
- 📦 Permanent storage on Arweave via ArDrive Turbo
- 🌳 Compressed NFTs via Metaplex Bubblegum (low cost)
- 👥 User registry for xidentity management
- 📄 Multiple asset types: Invoices, Notes (messages), Files, Photos, Contacts
- 🔗 Chrome extension compatible: Payloads match Yallet extension decrypt format
- 🧩 Optional WASM: Use acegf WASM for encryption (see wasm/README.md)
Installation
npm install @yallet/rwa-sdkQuick Start
import { createRWASDK, AssetType } from '@yallet/rwa-sdk';
// 1. Create SDK instance
const sdk = createRWASDK({
rpcEndpoint: 'https://api.mainnet-beta.solana.com',
network: 'mainnet',
merkleTreeAddress: 'YOUR_MERKLE_TREE_ADDRESS',
arweaveApiKey: 'YOUR_ARWEAVE_API_KEY', // Optional for free tier
});
// 2. Set up signer (for minting transactions)
import { keypairIdentity } from '@metaplex-foundation/umi';
sdk.setSigner(yourKeypair);
// 3. Register users with their xidentity
await sdk.registerUser({
solanaAddress: 'CUSTOMER_WALLET_ADDRESS',
xidentity: 'BASE64_XIDENTITY_PUBLIC_KEY', // From Yallet wallet
name: 'Customer Name',
registeredAt: Date.now(),
});
// 4. Mint encrypted invoice
const result = await sdk.mintInvoice(
{
invoiceId: 'INV-2024-001',
amount: 1500.00,
currency: 'USD',
date: new Date().toISOString(),
dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
items: [
{ description: 'Consulting Services', quantity: 10, unitPrice: 150, amount: 1500 }
],
},
'CUSTOMER_WALLET_ADDRESS',
{ name: 'Invoice #INV-2024-001' }
);
console.log('Minted:', result.assetId);
console.log('Signature:', result.signature);Architecture
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────┤
│ Yallet RWA SDK │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Registry │ │ Encryption │ │ Minting │ │
│ │ (xidentity) │ │ (ECIES) │ │ (Bubblegum) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ │
│ │ Storage │ │
│ │ (Arweave) │ │
│ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Solana Blockchain │ Arweave Network │
│ (cNFT Ownership) │ (Encrypted Data Storage) │
└─────────────────────────────────────────────────────────────┘User Registration Flow
Before minting assets to a user, they must register their xidentity:
// User provides their xidentity (from Yallet wallet)
// The xidentity is a base64-encoded X25519 public key
await sdk.registerUser({
solanaAddress: 'USER_WALLET_ADDRESS',
xidentity: 'ZzFXyDpLw7GMu3PiPopkM7hHBRWTupjcP7qxSJO2SHU=',
name: 'John Doe',
email: '[email protected]', // Optional
registeredAt: Date.now(),
});Getting xidentity from Yallet Wallet
Users can export their xidentity from Yallet wallet:
- Open Yallet → Settings → Export xidentity
- The xidentity is safe to share (it's a public key)
- Only the wallet owner can decrypt assets encrypted with their xidentity
API Reference
createRWASDK(config, options?)
Create a new SDK instance.
const sdk = createRWASDK({
// Required
rpcEndpoint: 'https://api.mainnet-beta.solana.com',
network: 'mainnet', // or 'devnet'
// Merkle Tree (required for minting)
merkleTreeAddress: 'YOUR_TREE_ADDRESS',
merkleTreeAuthority: 'TREE_AUTHORITY', // Optional
// Arweave (optional)
arweaveEndpoint: 'https://turbo.irys.xyz',
arweaveApiKey: 'YOUR_API_KEY',
// Backend API (optional, for tree management)
backendApiUrl: 'https://api.yourdomain.com',
});sdk.registerUser(user)
Register a user with their xidentity.
await sdk.registerUser({
solanaAddress: string, // User's Solana wallet address
xidentity: string, // Base64-encoded X25519 public key
name?: string, // Display name
email?: string, // Email address
metadata?: object, // Custom metadata
});sdk.mintInvoice(invoice, recipientAddress, options?)
Mint an encrypted invoice to a registered user (payload matches Yallet extension decrypt format). Optionally pass pdfBase64, senderProfile, recipientProfile for extension display.
sdk.mintFile(file, recipientAddress, options?)
Mint an encrypted file (FileData: filename, mimeType, content base64, size).
sdk.mintMessage(note, recipientAddress, options?)
Mint an encrypted message/note (NoteData: title, content), with optional replyTo. Payload type is always sent (institution sends to users).
sdk.mintPhoto(photo, recipientAddress, options?)
Mint an encrypted photo (PhotoData: filename, mimeType, content base64).
Example: mintInvoice
const result = await sdk.mintInvoice(
{
invoiceId: 'INV-001',
amount: 100.00,
currency: 'USD',
date: '2024-01-15T00:00:00Z',
dueDate: '2024-02-15T00:00:00Z',
description: 'Monthly subscription',
items: [
{ description: 'Service', quantity: 1, unitPrice: 100, amount: 100 }
],
},
'RECIPIENT_ADDRESS',
{ name: 'Invoice #INV-001', pdfBase64: '...' } // optional PDF
);sdk.mint(request)
Mint any asset type.
const result = await sdk.mint({
assetType: AssetType.NOTE,
assetData: {
title: 'Meeting Notes',
content: 'Discussion points...',
tags: ['meeting', 'project-x'],
},
recipientAddress: 'RECIPIENT_ADDRESS',
name: 'Meeting Notes - Jan 15',
});Asset Types
| Type | Description | Data Structure |
|------|-------------|----------------|
| INVOICE | Invoice | InvoiceData |
| NOTE | Message / note | NoteData |
| FILE | File | FileData |
| PHOTO | Photo | PhotoData |
| CONTACT | Contact | ContactData |
Payload builders (extension JSON–compatible)
To customize the payload before encryption, use the same builder functions the Yallet extension expects:
import {
buildInvoicePayload,
buildFilePayload,
buildMessagePayload,
buildPhotoPayload,
} from '@yallet/rwa-sdk';
const bundle = buildInvoicePayload(invoice, { pdfBase64, senderProfile, recipientProfile });
const filePayload = buildFilePayload(fileData);
const notePayload = buildMessagePayload(noteData, { replyTo: previousNftId });
const photoPayload = buildPhotoPayload(photoData);Custom User Registry
For production, implement a database-backed registry:
import { createRWASDK } from '@yallet/rwa-sdk';
const sdk = createRWASDK(config, {
registry: {
type: 'http',
httpConfig: {
baseUrl: 'https://api.yourdomain.com/registry',
apiKey: 'YOUR_API_KEY',
},
},
});Or implement the UserRegistry interface:
import type { UserRegistry, RegisteredUser } from '@yallet/rwa-sdk';
class MyDatabaseRegistry implements UserRegistry {
async getUser(solanaAddress: string): Promise<RegisteredUser | null> {
// Query your database
}
async registerUser(user: RegisteredUser): Promise<void> {
// Insert into database
}
// ... other methods
}
const sdk = createRWASDK(config, {
registry: new MyDatabaseRegistry(),
});Server-side Mint (Yault credential NFT)
For Yault or other apps that prefer server-side mint (no registry, no signer):
import { mintCredentialNftViaServer, RWA_UPLOAD_AND_MINT_ENDPOINTS } from '@yallet/rwa-sdk';
const result = await mintCredentialNftViaServer(
recipientSolanaAddress,
{ user_cred, mnemonic, index, label },
{
xidentity: recipientXidentity, // from recipient-addresses API
dev: true, // use built-in dev endpoint
uploadAndMintApiUrl: undefined, // override if needed
network: 'devnet',
}
);Dev mode
When dev: true and uploadAndMintApiUrl is not set, the SDK uses RWA_UPLOAD_AND_MINT_ENDPOINTS.dev (api.yallet.xyz). For production, use dev: false or pass uploadAndMintApiUrl explicitly.
WASM encryption (Yallet extension–compatible)
For full compatibility with Yallet extension decryption, use acegf WASM encryption:
- In acegf-wallet, run
wasm-pack build --target web, then copy the generatedpkg/acegf_bg.wasmandpkg/acegf.jsinto this SDK'swasm/directory (or your deploy path). - In your app, initialize WASM and pass the encrypt function into the SDK:
import init from './wasm/acegf.js'; // or your path
import { createRWASDK, createWasmEncryptor } from '@yallet/rwa-sdk';
const acegf = await init(); // optional wasm path: init('/path/to/acegf_bg.wasm')
const sdk = createRWASDK(config, {
wasmEncryptFn: createWasmEncryptor(acegf.acegf_encrypt_for_xidentity),
});Without wasmEncryptFn, the SDK uses built-in pure JS ECIES; production should use WASM. See wasm/README.md.
Encryption ↔ extension decrypt (inverse)
The built-in ECIES in this SDK is aligned with dev.yallet.chrome-extension (acegf WASM) so that:
- Encrypt (SDK / Yault):
shared_secret = ECDH(ephemeral, xidentity)→dh_key = SHA256(shared_secret)→encrypted_aes_key = AES-GCM(dh_key).encrypt(iv, random_aes_key)→encrypted_data = AES-GCM(aes_key).encrypt(iv, plaintext). Payload on Arweave:{ encrypted: { ephemeral_pub, encrypted_aes_key, iv, encrypted_data }, metadata }. - Decrypt (extension):
dh_key = SHA256(ECDH(ephemeral_pub, recipient_private))→aes_key = AES-GCM(dh_key).decrypt(iv, encrypted_aes_key)→plaintext = AES-GCM(aes_key).decrypt(iv, encrypted_data)→ decode base64, optional gzip, thenenvelope.data(e.g. note{ title, content, metadata }).
Envelope shape: { version, type, timestamp, uuid, schema?, previousToken?, data: asset }. The extension expects envelope.data to have e.g. title and content for notes. Server-side credential mint uses the same envelope and note shape so the extension can decrypt and show the note.
Merkle Tree Setup
Before minting, you need a Merkle tree. Create one using Metaplex:
import { createTree } from '@metaplex-foundation/mpl-bubblegum';
const { tree } = await createTree(umi, {
maxDepth: 14, // 16,384 NFTs capacity
maxBufferSize: 64,
});
sdk.setTreeConfig({
treeAddress: tree.publicKey,
treeAuthority: umi.identity.publicKey,
maxDepth: 14,
capacity: 16384,
used: 0,
remaining: 16384,
usagePercent: 0,
});Error Handling
const result = await sdk.mintInvoice(invoice, recipientAddress);
if (!result.success) {
console.error('Minting failed:', result.error);
// Handle error
} else {
console.log('Success! Asset ID:', result.assetId);
}License
MIT
