#Sharing Workflows

Complete examples for implementing private sharing with access grants and public sharing with shareable links.

#Private Sharing with Access Grants

Share assets with specific wallet addresses with granular permissions.

#Basic Team Sharing

import { Walbucket } from '@walbucket/sdk';

const walbucket = new Walbucket({
  apiKey: process.env.WALBUCKET_API_KEY!,
  network: 'testnet',
  sponsorPrivateKey: process.env.SPONSOR_PRIVATE_KEY!,
});

async function shareWithTeam(assetId: string) {
  const team = [
    {
      address: '0x123...',
      role: 'viewer',
      name: 'Alice',
    },
    {
      address: '0x456...',
      role: 'editor',
      name: 'Bob',
    },
    {
      address: '0x789...',
      role: 'admin',
      name: 'Charlie',
    },
  ];
  
  for (const member of team) {
    const permissions = {
      canRead: true,
      canWrite: member.role === 'editor' || member.role === 'admin',
      canAdmin: member.role === 'admin',
    };
    
    await walbucket.shareAsset(assetId, member.address, permissions);
    console.log(`Shared with ${member.name} (${member.role})`);
  }
}
typescript

#Time-Limited Client Access

async function shareWithClient(
  assetId: string,
  clientAddress: string,
  durationDays: number
) {
  const expiresAt = Date.now() + (durationDays * 24 * 60 * 60 * 1000);
  
  await walbucket.shareAsset(assetId, clientAddress, {
    canRead: true,
    canWrite: false,
    expiresAt,
  });
  
  const expiryDate = new Date(expiresAt).toLocaleDateString();
  console.log(`Shared with client (expires: ${expiryDate})`);
  
  return expiresAt;
}

// Share for 14 days
await shareWithClient(assetId, clientAddress, 14);
typescript

#Password-Protected Sharing

import { createHash } from 'crypto';

async function shareWithPassword(
  assetId: string,
  recipientAddress: string,
  password: string
) {
  // Hash the password
  const passwordHash = createHash('sha256')
    .update(password)
    .digest('hex');
  
  await walbucket.shareAsset(assetId, recipientAddress, {
    canRead: true,
    passwordHash,
  });
  
  console.log('Shared with password protection');
  // Send password to recipient through secure channel
}
typescript

#Managing Access Grants

async function manageProjectAccess(projectAssets: string[]) {
  // Share all project assets with team
  const teamMember = '0x123...';
  
  for (const assetId of projectAssets) {
    await walbucket.shareAsset(assetId, teamMember, {
      canRead: true,
      canWrite: true,
    });
  }
  
  console.log(`Shared ${projectAssets.length} assets`);
  
  // View all grants
  const grants = await walbucket.listAccessGrants();
  const teamGrants = grants.filter(g => g.grantedTo === teamMember);
  
  console.log(`Team member has access to ${teamGrants.length} assets`);
  
  return teamGrants;
}
typescript

#Revoking Access

async function revokeTeamMemberAccess(memberAddress: string) {
  // Get all grants for this member
  const grants = await walbucket.listAccessGrants();
  const memberGrants = grants.filter(g => 
    g.grantedTo === memberAddress && g.isActive
  );
  
  console.log(`Revoking ${memberGrants.length} grants...`);
  
  // Revoke all grants
  for (const grant of memberGrants) {
    await walbucket.revokeShare(grant.id);
    console.log(`Revoked: ${grant.assetId}`);
  }
  
  console.log('All access revoked');
}
typescript

Create public links for wider distribution.

async function createPublicLink(assetId: string) {
  // Generate unique token
  const shareToken = crypto.randomUUID();
  
  // Create link
  await walbucket.createShareableLink(assetId, {
    shareToken,
    canRead: true,
  });
  
  // Construct URL
  const shareUrl = `${window.location.origin}/share/${shareToken}`;
  
  console.log('Share this link:', shareUrl);
  return shareUrl;
}
typescript
async function createExpiringLink(
  assetId: string,
  duration: '1h' | '24h' | '7d' | '30d'
) {
  const durations = {
    '1h': 60 * 60 * 1000,
    '24h': 24 * 60 * 60 * 1000,
    '7d': 7 * 24 * 60 * 60 * 1000,
    '30d': 30 * 24 * 60 * 60 * 1000,
  };
  
  const shareToken = crypto.randomUUID();
  const expiresAt = Date.now() + durations[duration];
  
  await walbucket.createShareableLink(assetId, {
    shareToken,
    canRead: true,
    expiresAt,
  });
  
  const shareUrl = `${window.location.origin}/share/${shareToken}`;
  const expiryDate = new Date(expiresAt);
  
  return {
    url: shareUrl,
    expiresAt: expiryDate,
    duration,
  };
}

// Usage
const link = await createExpiringLink(assetId, '7d');
console.log(`Link expires: ${link.expiresAt.toLocaleString()}`);
typescript
import { createHash } from 'crypto';

async function createProtectedLink(
  assetId: string,
  password: string
) {
  const shareToken = crypto.randomUUID();
  const passwordHash = createHash('sha256')
    .update(password)
    .digest('hex');
  
  await walbucket.createShareableLink(assetId, {
    shareToken,
    canRead: true,
    passwordHash,
  });
  
  const shareUrl = `${window.location.origin}/share/${shareToken}`;
  
  return {
    url: shareUrl,
    password, // Share password separately
  };
}
typescript
async function getLinkStats() {
  const links = await walbucket.listShareableLinks();
  
  const stats = {
    total: links.length,
    active: links.filter(l => l.isActive).length,
    expired: links.filter(l => 
      l.expiresAt && l.expiresAt < Date.now()
    ).length,
    totalAccesses: links.reduce((sum, l) => sum + l.accessCount, 0),
  };
  
  // Find most accessed links
  const topLinks = links
    .sort((a, b) => b.accessCount - a.accessCount)
    .slice(0, 5);
  
  console.log('Link Statistics:');
  console.log(`Total: ${stats.total}`);
  console.log(`Active: ${stats.active}`);
  console.log(`Expired: ${stats.expired}`);
  console.log(`Total Accesses: ${stats.totalAccesses}`);
  
  console.log('\nTop 5 Links:');
  topLinks.forEach((link, i) => {
    console.log(`${i + 1}. ${link.shareToken}: ${link.accessCount} accesses`);
  });
  
  return { stats, topLinks };
}
typescript
// In your share page route
async function handleLinkAccess(shareToken: string) {
  try {
    // Find the link
    const links = await walbucket.listShareableLinks();
    const link = links.find(l => l.shareToken === shareToken);
    
    if (!link) {
      throw new Error('Link not found');
    }
    
    // Check if active
    if (!link.isActive) {
      throw new Error('Link has been deactivated');
    }
    
    // Check expiration
    if (link.expiresAt && link.expiresAt < Date.now()) {
      throw new Error('Link has expired');
    }
    
    // Track access (user-pays mode)
    await walbucket.trackLinkAccess(link.id);
    
    // Retrieve the asset
    const asset = await walbucket.retrieve(link.assetId);
    
    return asset;
  } catch (error) {
    console.error('Access denied:', error.message);
    throw error;
  }
}
typescript
async function rotateShareLink(oldLinkId: string, assetId: string) {
  // Deactivate old link
  await walbucket.deactivateShareableLink(oldLinkId);
  console.log('Old link deactivated');
  
  // Create new link
  const shareToken = crypto.randomUUID();
  await walbucket.createShareableLink(assetId, {
    shareToken,
    canRead: true,
    expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000), // 7 days
  });
  
  const newUrl = `${window.location.origin}/share/${shareToken}`;
  console.log('New link created:', newUrl);
  
  return newUrl;
}
typescript

#Complete Sharing UI Example

import { useState } from 'react';
import { Walbucket } from '@walbucket/sdk';

function ShareDialog({ assetId, walbucket }) {
  const [shareType, setShareType] = useState<'private' | 'public'>('private');
  const [recipientAddress, setRecipientAddress] = useState('');
  const [duration, setDuration] = useState('7d');
  const [permissions, setPermissions] = useState({
    canRead: true,
    canWrite: false,
    canAdmin: false,
  });
  
  async function handlePrivateShare() {
    const expiresAt = duration !== 'never' 
      ? Date.now() + getDurationMs(duration)
      : undefined;
    
    await walbucket.shareAsset(assetId, recipientAddress, {
      ...permissions,
      expiresAt,
    });
    
    alert('Shared successfully!');
  }
  
  async function handlePublicShare() {
    const shareToken = crypto.randomUUID();
    const expiresAt = duration !== 'never'
      ? Date.now() + getDurationMs(duration)
      : undefined;
    
    await walbucket.createShareableLink(assetId, {
      shareToken,
      canRead: true,
      expiresAt,
    });
    
    const url = `${window.location.origin}/share/${shareToken}`;
    
    // Copy to clipboard
    await navigator.clipboard.writeText(url);
    alert('Link copied to clipboard!');
  }
  
  return (
    <div className="share-dialog">
      <h3>Share Asset</h3>
      
      {/* Share Type Selector */}
      <div className="share-type">
        <button 
          onClick={() => setShareType('private')}
          className={shareType === 'private' ? 'active' : ''}
        >
          Private (Specific Users)
        </button>
        <button 
          onClick={() => setShareType('public')}
          className={shareType === 'public' ? 'active' : ''}
        >
          Public Link
        </button>
      </div>
      
      {shareType === 'private' ? (
        <div className="private-share">
          <input
            type="text"
            placeholder="Recipient Wallet Address (0x...)"
            value={recipientAddress}
            onChange={(e) => setRecipientAddress(e.target.value)}
          />
          
          <div className="permissions">
            <label>
              <input
                type="checkbox"
                checked={permissions.canRead}
                onChange={(e) => setPermissions({
                  ...permissions,
                  canRead: e.target.checked
                })}
              />
              Read
            </label>
            <label>
              <input
                type="checkbox"
                checked={permissions.canWrite}
                onChange={(e) => setPermissions({
                  ...permissions,
                  canWrite: e.target.checked
                })}
              />
              Write
            </label>
            <label>
              <input
                type="checkbox"
                checked={permissions.canAdmin}
                onChange={(e) => setPermissions({
                  ...permissions,
                  canAdmin: e.target.checked
                })}
              />
              Admin
            </label>
          </div>
          
          <select value={duration} onChange={(e) => setDuration(e.target.value)}>
            <option value="1h">1 Hour</option>
            <option value="24h">24 Hours</option>
            <option value="7d">7 Days</option>
            <option value="30d">30 Days</option>
            <option value="never">Never Expires</option>
          </select>
          
          <button onClick={handlePrivateShare}>
            Share with User
          </button>
        </div>
      ) : (
        <div className="public-share">
          <select value={duration} onChange={(e) => setDuration(e.target.value)}>
            <option value="1h">1 Hour</option>
            <option value="24h">24 Hours</option>
            <option value="7d">7 Days</option>
            <option value="30d">30 Days</option>
            <option value="never">Never Expires</option>
          </select>
          
          <button onClick={handlePublicShare}>
            Create & Copy Link
          </button>
        </div>
      )}
    </div>
  );
}

function getDurationMs(duration: string): number {
  const durations = {
    '1h': 60 * 60 * 1000,
    '24h': 24 * 60 * 60 * 1000,
    '7d': 7 * 24 * 60 * 60 * 1000,
    '30d': 30 * 24 * 60 * 60 * 1000,
  };
  return durations[duration] || 0;
}
typescript

#Best Practices

#Choose the Right Sharing Method

// Use private sharing (access grants) when:
// - Sharing with known wallet addresses
// - Need granular permissions (write, admin)
// - Want to easily revoke access
// - Tracking who has access

// Use public links when:
// - Sharing with unknown users
// - Need a simple URL to share
// - Want to track link usage
// - Temporary public access needed
typescript

#Security Considerations

// Always use expiration for sensitive content
const expiresAt = Date.now() + (24 * 60 * 60 * 1000); // 24 hours

// Use password protection for public links
const passwordHash = createHash('sha256').update('secret').digest('hex');

// Audit access regularly
const grants = await walbucket.listAccessGrants();
grants.forEach(grant => {
  if (grant.expiresAt && grant.expiresAt < Date.now()) {
    console.warn('Expired grant still active:', grant.id);
  }
});
typescript