Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/marketplace/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function MarketplaceLayout({
<MarketplaceFilterProvider>
<div className="flex min-h-screen w-full flex-row overflow-hidden font-sans antialiased selection:bg-accent-lime selection:text-ink">
<MarketplaceSidebar />
<main className="flex-1 flex flex-col h-screen overflow-hidden relative bg-background-light">
<main className="flex-1 flex flex-col h-screen overflow-y-auto relative bg-background-light">
{children}
</main>
</div>
Expand Down
109 changes: 81 additions & 28 deletions app/marketplace/my-data/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
import { Transaction } from '@mysten/sui/transactions';
import { marketplaceConfig, MIST_PER_SUI } from '@/config/marketplace';
import { getMarketplaceTarget } from '@/lib/marketplace';
import { encryptFile, isSealAvailable, uint8ArrayToBase64 } from '@/lib/seal';

// Type for Walrus write files flow
interface WalrusWriteFlow {
Expand Down Expand Up @@ -81,6 +82,7 @@ export default function MyDataPage() {
previewSizeBytes: 1024 * 1024,
});


const flowRef = useRef<Awaited<ReturnType<typeof createFlow>> | null>(null);

const addLog = (step: string, status: TransactionLog['status'], message: string, details?: string) => {
Expand Down Expand Up @@ -151,20 +153,49 @@ export default function MyDataPage() {
return;
}

addLog('encode', 'processing', 'Encoding file...');

const fileBytes = await file.arrayBuffer();
const uint8Array = new Uint8Array(fileBytes);
// Step 0: Encrypt with Seal (if available)
let dataToUpload: Uint8Array;
let encryptedObj: string;

if (isSealAvailable()) {
addLog('encode', 'processing', 'Encrypting with Seal (purchase-based access)...');

// Use seller address as policy ID - only buyers with PurchaseReceipt can decrypt
console.log('[Seal] Encrypting file with purchase-based access');

try {
const { encryptedBlob, policyId } = await encryptFile(file, account.address);
const encryptedArrayBuffer = await encryptedBlob.arrayBuffer();
dataToUpload = new Uint8Array(encryptedArrayBuffer);

// Store policy ID as the encrypted object reference
encryptedObj = policyId;
setEncryptedObject(encryptedObj);

addLog('encode', 'success', 'File encrypted (only buyers can decrypt)');
console.log('[Seal] Encryption complete, encrypted size:', dataToUpload.length);
} catch (sealError) {
console.error('[Seal] Encryption failed:', sealError);
addLog('encode', 'error', `Seal encryption failed: ${sealError instanceof Error ? sealError.message : 'Unknown error'}`);
setUploadError('Failed to encrypt with Seal. Check console for details.');
setIsProcessing(false);
return;
}
} else {
// Fallback: no encryption (store raw data)
addLog('encode', 'processing', 'Encoding file (Seal not configured)...');
const fileBytes = await file.arrayBuffer();
dataToUpload = new Uint8Array(fileBytes);
encryptedObj = bytesToHex(dataToUpload).slice(0, 128);
setEncryptedObject(encryptedObj);
addLog('encode', 'success', 'File encoded (no encryption)');
}

// Step 1: Create flow and encode
const flow = await createFlow(uint8Array, file.name);
// Step 1: Create flow and encode with encrypted data
const flow = await createFlow(dataToUpload, file.name);
flowRef.current = flow;
await flow.encode();
addLog('encode', 'success', 'File encoded');

// Generate encrypted object from file content (first 64 bytes as hex)
const encryptedObj = bytesToHex(uint8Array).slice(0, 128);
setEncryptedObject(encryptedObj);
addLog('encode', 'success', 'Walrus encoding complete');

// Step 2: Register blob on-chain
setCurrentStep('register');
Expand Down Expand Up @@ -471,7 +502,7 @@ export default function MyDataPage() {
</div>
<span className="material-symbols-outlined text-5xl text-gray-600">dataset</span>
<span className="absolute top-3 left-3 rounded bg-accent-lime text-ink px-2 py-0.5 text-[10px] font-bold border border-ink">ACTIVE</span>

{/* Explorer Links */}
<div className="absolute top-3 right-3 flex gap-1">
<a
Expand All @@ -482,10 +513,10 @@ export default function MyDataPage() {
title="View on SuiScan"
>
<svg className="w-4 h-4" viewBox="0 0 234 234" fill="none">
<path d="M0 100C0 65 0 47.5 6.8 33C12.7 21.3 22.3 11.8 34 5.8C47.3 0 64.7 0 99.5 0H133.8C168.6 0 186 0 199.3 6.8C211 12.8 220.6 22.3 226.5 34C233.3 47.4 233.3 64.9 233.3 99.8V134.2C233.3 169.1 233.3 186.6 226.5 199.9C220.6 211.6 211 221.2 199.3 227.2C186 234 168.6 234 133.8 234H99.5C64.7 234 47.3 234 34 227.2C22.3 221.2 12.7 211.7 6.8 200C0 186.6 0 169.1 0 134.2V100Z" fill="#4C72FF"/>
<path d="M177 87C178.7 85.9 180.8 85.6 182.4 86.3C183.2 86.6 183.9 87.1 184.3 87.8C184.7 88.5 185 89.4 184.9 90.2L181.4 148.2C181 155.7 178.2 163.4 173.6 170.4C160.4 190.4 133.2 200 112.8 191.8C107.1 189.5 102.5 186 99.2 181.7C100 181.8 100.8 181.7 101.5 181.7C122.4 181.7 143.5 170.3 155.1 152.7C160.7 144.1 164 134.7 164.6 125.6L166.5 93.3L177 87Z" fill="white"/>
<path d="M150 63.6C151.7 62.5 153.8 62.3 155.5 62.9C156.3 63.3 156.9 63.8 157.4 64.5C157.9 65.2 158.1 66.1 158 66.9L154.5 125C154 132.5 151.3 140.1 146.7 147.1C133.5 167.2 106.3 176.7 85.9 168.5C80.1 166.2 75.6 162.7 72.3 158.4C73.1 158.4 73.9 158.4 74.6 158.4C95.6 158.4 116.6 147.1 128.2 129.4C133.9 120.8 137.1 111.4 137.7 102.3L139.6 70L150 63.6Z" fill="white"/>
<path d="M123 40.3C124.7 39.2 126.8 39 128.5 39.6C129.2 39.9 129.9 40.5 130.3 41.2C130.8 41.9 131 42.7 130.9 43.5L127.4 101.6C127 109.1 124.2 116.7 119.6 123.7C106.4 143.8 79.2 153.3 58.8 145.1C38.5 136.9 32.7 114 45.9 93.9C50.5 86.9 57.1 80.7 64.6 76.1L123 40.3Z" fill="white"/>
<path d="M0 100C0 65 0 47.5 6.8 33C12.7 21.3 22.3 11.8 34 5.8C47.3 0 64.7 0 99.5 0H133.8C168.6 0 186 0 199.3 6.8C211 12.8 220.6 22.3 226.5 34C233.3 47.4 233.3 64.9 233.3 99.8V134.2C233.3 169.1 233.3 186.6 226.5 199.9C220.6 211.6 211 221.2 199.3 227.2C186 234 168.6 234 133.8 234H99.5C64.7 234 47.3 234 34 227.2C22.3 221.2 12.7 211.7 6.8 200C0 186.6 0 169.1 0 134.2V100Z" fill="#4C72FF" />
<path d="M177 87C178.7 85.9 180.8 85.6 182.4 86.3C183.2 86.6 183.9 87.1 184.3 87.8C184.7 88.5 185 89.4 184.9 90.2L181.4 148.2C181 155.7 178.2 163.4 173.6 170.4C160.4 190.4 133.2 200 112.8 191.8C107.1 189.5 102.5 186 99.2 181.7C100 181.8 100.8 181.7 101.5 181.7C122.4 181.7 143.5 170.3 155.1 152.7C160.7 144.1 164 134.7 164.6 125.6L166.5 93.3L177 87Z" fill="white" />
<path d="M150 63.6C151.7 62.5 153.8 62.3 155.5 62.9C156.3 63.3 156.9 63.8 157.4 64.5C157.9 65.2 158.1 66.1 158 66.9L154.5 125C154 132.5 151.3 140.1 146.7 147.1C133.5 167.2 106.3 176.7 85.9 168.5C80.1 166.2 75.6 162.7 72.3 158.4C73.1 158.4 73.9 158.4 74.6 158.4C95.6 158.4 116.6 147.1 128.2 129.4C133.9 120.8 137.1 111.4 137.7 102.3L139.6 70L150 63.6Z" fill="white" />
<path d="M123 40.3C124.7 39.2 126.8 39 128.5 39.6C129.2 39.9 129.9 40.5 130.3 41.2C130.8 41.9 131 42.7 130.9 43.5L127.4 101.6C127 109.1 124.2 116.7 119.6 123.7C106.4 143.8 79.2 153.3 58.8 145.1C38.5 136.9 32.7 114 45.9 93.9C50.5 86.9 57.1 80.7 64.6 76.1L123 40.3Z" fill="white" />
</svg>
</a>
<a
Expand All @@ -496,22 +527,22 @@ export default function MyDataPage() {
title="View on SuiVision"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none">
<path d="M0 6C0 2.68629 2.68629 0 6 0H18C21.3137 0 24 2.68629 24 6V18C24 21.3137 21.3137 24 18 24H6C2.68629 24 0 21.3137 0 18V6Z" fill="#4DA2FF"/>
<path d="M6.99748 5.28362L6.99748 11.0148L8.71768 12.0008L6.99731 12.987L6.99731 18.7182L11.9972 21.584L16.9971 18.7182L16.9971 12.9866L15.2769 12.0007L16.9973 11.0147L16.9973 5.28308L11.997 2.41732L6.99748 5.28362ZM11.6464 3.42366L11.6464 7.94789L7.69961 10.2105L7.69961 5.68623L11.6464 3.42366ZM12.3482 20.5781L12.3482 16.0535L16.2954 13.7912L16.2954 18.3159L12.3482 20.5781ZM15.9441 13.1879L11.9973 15.4501L8.05048 13.1879L9.41994 12.4031L11.9973 13.8803L14.575 12.4031L15.9441 13.1879ZM11.9973 10.1208L9.41964 11.5982L8.05048 10.8134L11.9973 8.55113L15.9445 10.8134L14.575 11.5982L11.9973 10.1208Z" fill="white"/>
<path d="M0 6C0 2.68629 2.68629 0 6 0H18C21.3137 0 24 2.68629 24 6V18C24 21.3137 21.3137 24 18 24H6C2.68629 24 0 21.3137 0 18V6Z" fill="#4DA2FF" />
<path d="M6.99748 5.28362L6.99748 11.0148L8.71768 12.0008L6.99731 12.987L6.99731 18.7182L11.9972 21.584L16.9971 18.7182L16.9971 12.9866L15.2769 12.0007L16.9973 11.0147L16.9973 5.28308L11.997 2.41732L6.99748 5.28362ZM11.6464 3.42366L11.6464 7.94789L7.69961 10.2105L7.69961 5.68623L11.6464 3.42366ZM12.3482 20.5781L12.3482 16.0535L16.2954 13.7912L16.2954 18.3159L12.3482 20.5781ZM15.9441 13.1879L11.9973 15.4501L8.05048 13.1879L9.41994 12.4031L11.9973 13.8803L14.575 12.4031L15.9441 13.1879ZM11.9973 10.1208L9.41964 11.5982L8.05048 10.8134L11.9973 8.55113L15.9445 10.8134L14.575 11.5982L11.9973 10.1208Z" fill="white" />
</svg>
</a>
</div>
</div>

{/* Content */}
<div className="p-4 flex flex-col flex-1">
<h3 className="text-base font-bold text-ink mb-1 line-clamp-2">{listing.name}</h3>

{/* Object ID */}
<p className="text-[10px] text-gray-400 font-mono mb-1 truncate" title={listing.id}>
ID: {listing.id.slice(0, 8)}...{listing.id.slice(-6)}
</p>

{/* Blob ID with Walrus link */}
<div className="flex items-center gap-1 mb-3">
<p className="text-[10px] text-gray-400 font-mono truncate flex-1" title={listing.blobId}>
Expand All @@ -525,15 +556,15 @@ export default function MyDataPage() {
title="View on WalrusScan"
>
<svg className="w-3 h-3" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="10" r="8" fill="#36B5A8"/>
<ellipse cx="12" cy="22" rx="6" ry="2" fill="#36B5A8" opacity="0.5"/>
<circle cx="9" cy="8" r="1.5" fill="white"/>
<circle cx="15" cy="8" r="1.5" fill="white"/>
<path d="M9 13C9 13 10.5 15 12 15C13.5 15 15 13 15 13" stroke="white" strokeWidth="1.5" strokeLinecap="round"/>
<circle cx="12" cy="10" r="8" fill="#36B5A8" />
<ellipse cx="12" cy="22" rx="6" ry="2" fill="#36B5A8" opacity="0.5" />
<circle cx="9" cy="8" r="1.5" fill="white" />
<circle cx="15" cy="8" r="1.5" fill="white" />
<path d="M9 13C9 13 10.5 15 12 15C13.5 15 15 13 15 13" stroke="white" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</a>
</div>

{/* Stats */}
<div className="grid grid-cols-2 gap-4 py-3 border-t border-gray-200">
<div>
Expand All @@ -545,7 +576,7 @@ export default function MyDataPage() {
<p className="text-sm font-bold text-accent-lime">{formatSize(Number(listing.totalSize))}</p>
</div>
</div>

{/* Actions */}
<div className="flex gap-2 mt-auto pt-3 border-t border-gray-200">
<button className="flex-1 h-9 rounded-lg border-2 border-ink bg-white text-ink text-sm font-bold hover:bg-gray-50 transition-colors">
Expand Down Expand Up @@ -725,6 +756,28 @@ export default function MyDataPage() {
<span>{formatSize(totalSizeBytes)}</span>
</div>
</div>

{/* Seal Encryption Notice */}
<div className="pt-4 border-t border-gray-200">
<label className="block text-sm font-bold text-gray-500 uppercase mb-2 flex items-center gap-2">
<span className="material-symbols-outlined text-base">lock</span>
Purchase-Based Encryption (Seal)
</label>
<p className="text-xs text-gray-500 mb-2">
Data is encrypted with Seal threshold encryption. Only users who purchase this dataset will be able to decrypt it.
</p>
{!isSealAvailable() && (
<p className="text-xs text-amber-600 bg-amber-50 p-2 rounded-lg border border-amber-200">
⚠️ Seal not configured. Set NEXT_PUBLIC_PACKAGE_ID to enable encryption.
</p>
)}
{isSealAvailable() && (
<p className="text-xs text-green-600 bg-green-50 p-2 rounded-lg border border-green-200 flex items-center gap-1">
<span className="material-symbols-outlined text-sm">check_circle</span>
Seal encryption enabled - data will be protected
</p>
)}
</div>
</div>

<div className="space-y-4">
Expand Down
Loading