# Using Turbo SDK with Vanilla HTML

Firefox Compatibility

Some compatibility issues have been reported with the Turbo SDK in Firefox browsers. At this time the below framework examples may not behave as expected in Firefox.

# Overview

This guide demonstrates how to integrate the @ardrive/turbo-sdk directly into vanilla HTML pages using CDN imports. No build tools, bundlers, or polyfills are required - just modern ES modules support in browsers.

Note

Vanilla HTML implementation is the simplest way to get started with the Turbo SDK. It's perfect for prototyping, simple applications, or when you want to avoid build complexity.

# Prerequisites

  • Modern browser with ES modules support (Chrome 61+, Firefox 60+, Safari 10.1+, Edge 16+)
  • Basic understanding of HTML, CSS, and JavaScript
  • HTTPS hosting for production (required for browser wallet integrations)

# Quick Start

# Minimal Example

Create a basic HTML file with Turbo SDK integration:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Turbo SDK Example</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
      }
      .section {
        margin: 20px 0;
        padding: 20px;
        border: 1px solid #ddd;
        border-radius: 8px;
      }
      .loading {
        color: #666;
        font-style: italic;
      }
      .error {
        color: red;
      }
      .success {
        color: green;
      }
      button {
        background: #007cba;
        color: white;
        border: none;
        padding: 10px 20px;
        border-radius: 4px;
        cursor: pointer;
        margin: 5px;
      }
      button:hover {
        background: #005a87;
      }
      button:disabled {
        background: #ccc;
        cursor: not-allowed;
      }
    </style>
  </head>
  <body>
    <h1>Turbo SDK - Vanilla HTML Demo</h1>

    <div class="section">
      <h2>Current Rates</h2>
      <div id="rates" class="loading">Loading rates...</div>
    </div>

    <div class="section">
      <h2>Upload File</h2>
      <form id="uploadForm">
        <input type="file" id="fileInput" accept="*/*" required />
        <br /><br />
        <button type="submit" id="uploadBtn">Upload File</button>
      </form>
      <div id="uploadStatus"></div>
    </div>

    <script type="module">
      import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk";

      // Initialize Turbo client
      const turbo = TurboFactory.unauthenticated();

      // Fetch and display rates
      async function loadRates() {
        try {
          const rates = await turbo.getFiatRates();
          const ratesDiv = document.getElementById("rates");

          const ratesText = Object.entries(rates.fiat)
            .map(
              ([currency, rate]) =>
                `${currency.toUpperCase()}: $${rate} per GiB`
            )
            .join("<br>");

          ratesDiv.innerHTML = ratesText;
        } catch (error) {
          document.getElementById(
            "rates"
          ).innerHTML = `<span class="error">Error loading rates: ${error.message}</span>`;
        }
      }

      // Handle file upload
      document
        .getElementById("uploadForm")
        .addEventListener("submit", async (e) => {
          e.preventDefault();

          const fileInput = document.getElementById("fileInput");
          const uploadBtn = document.getElementById("uploadBtn");
          const statusDiv = document.getElementById("uploadStatus");

          if (!fileInput.files.length) {
            statusDiv.innerHTML =
              '<span class="error">Please select a file</span>';
            return;
          }

          const file = fileInput.files[0];
          uploadBtn.disabled = true;
          statusDiv.innerHTML =
            '<span class="loading">Preparing upload...</span>';

          try {
            // Show upload cost first
            const costs = await turbo.getUploadCosts({ bytes: [file.size] });
            const cost = costs[0];

            statusDiv.innerHTML = `
                    <p>Upload cost: ${cost.winc} winc</p>
                    <p>File size: ${file.size.toLocaleString()} bytes</p>
                    <p class="error">Note: This example cannot complete uploads without wallet authentication.</p>
                    <p>See wallet integration examples below for full upload functionality.</p>
                `;
          } catch (error) {
            statusDiv.innerHTML = `<span class="error">Error: ${error.message}</span>`;
          } finally {
            uploadBtn.disabled = false;
          }
        });

      // Load rates on page load
      loadRates();
    </script>
  </body>
</html>

# CDN Import Options

Use esm.sh for best compatibility

The unpkg.com CDN has known issues with ES module exports for complex packages like Turbo SDK.

import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk";
import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk@1.20.0";

# Alternative CDN Providers

// jsDelivr
import { TurboFactory } from "https://cdn.jsdelivr.net/npm/@ardrive/turbo-sdk@1.20.0/+esm";

// SkyPack
import { TurboFactory } from "https://cdn.skypack.dev/@ardrive/turbo-sdk@1.20.0";

// unpkg.com (not recommended - has ES module issues)
import { TurboFactory } from "https://unpkg.com/@ardrive/turbo-sdk@1.20.0";

# Wallet Integration Examples

WARNING

Never expose private keys in browser applications! Always use browser wallet integrations.

# Uploading with Wander

Deprecation Notice

The signature API used by ArConnect wallets is deprecated and will be removed. Visit Wander wallet documentation (opens new window) for alternatives.

Complete HTML page with Wander wallet integration:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Turbo SDK - Wander Wallet</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        max-width: 600px;
        margin: 0 auto;
        padding: 20px;
        background: #f5f5f5;
      }
      .container {
        background: white;
        padding: 30px;
        border-radius: 10px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }
      .wallet-section {
        border: 2px solid #e0e0e0;
        border-radius: 8px;
        padding: 20px;
        margin: 20px 0;
      }
      .connected {
        border-color: #4caf50;
        background-color: #f9fff9;
      }
      button {
        background: #000;
        color: white;
        border: none;
        padding: 12px 24px;
        border-radius: 6px;
        cursor: pointer;
        font-size: 16px;
        margin: 5px;
      }
      button:hover {
        background: #333;
      }
      button:disabled {
        background: #ccc;
        cursor: not-allowed;
      }
      .status {
        margin: 10px 0;
        padding: 10px;
        border-radius: 4px;
      }
      .success {
        background: #d4edda;
        color: #155724;
        border: 1px solid #c3e6cb;
      }
      .error {
        background: #f8d7da;
        color: #721c24;
        border: 1px solid #f5c6cb;
      }
      .info {
        background: #d1ecf1;
        color: #0c5460;
        border: 1px solid #bee5eb;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>⚡ Turbo SDK + Wander Wallet</h1>

      <div id="walletSection" class="wallet-section">
        <h2>Wander Wallet Connection</h2>
        <p>
          Connect your Wander wallet to upload files to Arweave using your AR
          balance.
        </p>
        <button id="connectBtn" onclick="connectWanderWallet()">
          Connect Wander Wallet
        </button>
        <div id="walletStatus"></div>
      </div>

      <div id="uploadSection" style="display: none; margin-top: 20px;">
        <h3>📁 File Upload</h3>
        <input
          type="file"
          id="fileInput"
          accept="*/*"
          style="margin: 10px 0;"
        />
        <br />
        <button onclick="uploadFile()">Upload to Arweave</button>
        <div id="uploadStatus"></div>
      </div>
    </div>

    <script type="module">
      import {
        TurboFactory,
        ArconnectSigner,
      } from "https://esm.sh/@ardrive/turbo-sdk";

      let connectedAddress = null;
      let turboClient = null;

      // Connect to Wander wallet
      async function connectWanderWallet() {
        const statusDiv = document.getElementById("walletStatus");
        const connectBtn = document.getElementById("connectBtn");

        try {
          if (!window.arweaveWallet) {
            statusDiv.innerHTML = `
                        <div class="error">
                            Wander wallet is not installed!<br>
                            <a href="https://wander.arweave.dev/" target="_blank">Install Wander Wallet</a>
                        </div>
                    `;
            return;
          }

          connectBtn.disabled = true;
          statusDiv.innerHTML =
            '<div class="info">Connecting to Wander wallet...</div>';

          // Required permissions for Turbo SDK
          const permissions = [
            "ACCESS_ADDRESS",
            "ACCESS_PUBLIC_KEY",
            "SIGN_TRANSACTION",
            "SIGNATURE",
          ];

          // Connect to wallet
          await window.arweaveWallet.connect(permissions);

          // Get wallet address
          connectedAddress = await window.arweaveWallet.getActiveAddress();

          // Create authenticated Turbo client
          const signer = new ArconnectSigner(window.arweaveWallet);
          turboClient = TurboFactory.authenticated({ signer });

          // Update UI
          document.getElementById("walletSection").classList.add("connected");
          statusDiv.innerHTML = `
                    <div class="success">
                        ✅ Connected to Wander Wallet<br>
                        <strong>Address:</strong> ${connectedAddress.slice(
                          0,
                          8
                        )}...${connectedAddress.slice(-8)}
                    </div>
                `;
          connectBtn.style.display = "none";
          document.getElementById("uploadSection").style.display = "block";
        } catch (error) {
          console.error("Wander wallet connection failed:", error);
          statusDiv.innerHTML = `<div class="error">Connection failed: ${error.message}</div>`;
        } finally {
          connectBtn.disabled = false;
        }
      }

      // Upload file function
      async function uploadFile() {
        const fileInput = document.getElementById("fileInput");
        const statusDiv = document.getElementById("uploadStatus");

        if (!fileInput.files.length) {
          statusDiv.innerHTML =
            '<div class="error">Please select a file first</div>';
          return;
        }

        if (!turboClient) {
          statusDiv.innerHTML =
            '<div class="error">Please connect Wander wallet first</div>';
          return;
        }

        const file = fileInput.files[0];
        let uploadStartTime = Date.now();

        statusDiv.innerHTML = '<div class="info">Preparing upload...</div>';

        try {
          // Get upload cost first
          const costs = await turboClient.getUploadCosts({
            bytes: [file.size],
          });
          const cost = costs[0];

          statusDiv.innerHTML = `
                    <div class="info">
                        Upload cost: ${cost.winc} winc<br>
                        Starting upload...
                    </div>
                `;

          // Upload with comprehensive progress tracking
          const result = await turboClient.uploadFile({
            fileStreamFactory: () => file.stream(),
            fileSizeFactory: () => file.size,
            dataItemOpts: {
              tags: [
                {
                  name: "Content-Type",
                  value: file.type || "application/octet-stream",
                },
                { name: "App-Name", value: "Turbo-HTML-Wander-Demo" },
                { name: "File-Name", value: file.name },
                { name: "Upload-Timestamp", value: new Date().toISOString() },
              ],
            },
            events: {
              onProgress: ({ totalBytes, processedBytes, step }) => {
                const percent = Math.round((processedBytes / totalBytes) * 100);
                const elapsed = Math.round(
                  (Date.now() - uploadStartTime) / 1000
                );
                statusDiv.innerHTML = `
                                <div class="info">
                                    <strong>${step}:</strong> ${percent}%<br>
                                    Progress: ${processedBytes.toLocaleString()} / ${totalBytes.toLocaleString()} bytes<br>
                                    Elapsed: ${elapsed}s
                                </div>
                            `;
              },
              onError: ({ error, step }) => {
                console.error(`Error during ${step}:`, error);
                statusDiv.innerHTML = `<div class="error">Error during ${step}: ${error.message}</div>`;
              },
            },
          });

          const totalTime = Math.round((Date.now() - uploadStartTime) / 1000);
          // Use original file size for display (result object doesn't contain size info)
          const displayBytes = file.size;
          statusDiv.innerHTML = `
                    <div class="success">
                        <strong>🎉 Upload Successful!</strong><br>
                        <strong>Transaction ID:</strong> <code>${
                          result.id
                        }</code><br>
                        <strong>File Size:</strong> ${displayBytes.toLocaleString()} bytes<br>
                        <strong>Upload Time:</strong> ${totalTime}s<br>
                        <strong>Timestamp:</strong> ${new Date(
                          result.timestamp
                        ).toLocaleString()}<br>
                        <strong>View File:</strong> <a href="https://arweave.net/${
                          result.id
                        }" target="_blank">arweave.net/${result.id}</a><br>
                        <strong>Explorer:</strong> <a href="https://viewblock.io/arweave/tx/${
                          result.id
                        }" target="_blank">ViewBlock</a>
                    </div>
                `;
        } catch (error) {
          console.error("Upload failed:", error);
          statusDiv.innerHTML = `<div class="error">Upload failed: ${error.message}</div>`;
        }
      }

      // Make functions available globally for onclick handlers
      window.connectWanderWallet = connectWanderWallet;
      window.uploadFile = uploadFile;
    </script>
  </body>
</html>

# Advanced Features

Create a standalone cost calculator:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Arweave Upload Cost Calculator</title>
    <style>
      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        max-width: 500px;
        margin: 50px auto;
        padding: 20px;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
      }
      .calculator {
        background: rgba(255, 255, 255, 0.1);
        border-radius: 15px;
        padding: 30px;
        backdrop-filter: blur(10px);
        border: 1px solid rgba(255, 255, 255, 0.2);
      }
      h1 {
        text-align: center;
        margin-bottom: 30px;
      }
      .input-group {
        margin: 20px 0;
      }
      label {
        display: block;
        margin-bottom: 8px;
        font-weight: bold;
      }
      input,
      select {
        width: 100%;
        padding: 12px;
        border: none;
        border-radius: 8px;
        font-size: 16px;
        box-sizing: border-box;
      }
      .results {
        background: rgba(255, 255, 255, 0.1);
        border-radius: 10px;
        padding: 20px;
        margin-top: 20px;
      }
      .result-item {
        display: flex;
        justify-content: space-between;
        margin: 10px 0;
        padding: 8px 0;
        border-bottom: 1px solid rgba(255, 255, 255, 0.2);
      }
      .loading {
        text-align: center;
        opacity: 0.7;
      }
    </style>
  </head>
  <body>
    <div class="calculator">
      <h1>💰 Upload Cost Calculator</h1>

      <div class="input-group">
        <label for="fileSize">File Size:</label>
        <input
          type="number"
          id="fileSize"
          placeholder="Enter size in MB"
          min="0"
          step="0.1"
        />
      </div>

      <div class="input-group">
        <label for="currency">Currency:</label>
        <select id="currency">
          <option value="usd">USD</option>
          <option value="eur">EUR</option>
          <option value="gbp">GBP</option>
        </select>
      </div>

      <div id="results" class="results">
        <div class="loading">Enter file size to see costs...</div>
      </div>
    </div>

    <script type="module">
      import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk";

      const turbo = TurboFactory.unauthenticated();
      let rates = null;

      // Load exchange rates
      async function loadRates() {
        try {
          rates = await turbo.getFiatRates();
        } catch (error) {
          console.error("Failed to load rates:", error);
        }
      }

      // Calculate costs
      async function calculateCosts() {
        const fileSize = parseFloat(document.getElementById("fileSize").value);
        const currency = document.getElementById("currency").value;
        const resultsDiv = document.getElementById("results");

        if (!fileSize || fileSize <= 0) {
          resultsDiv.innerHTML =
            '<div class="loading">Enter a valid file size...</div>';
          return;
        }

        if (!rates) {
          resultsDiv.innerHTML = '<div class="loading">Loading rates...</div>';
          return;
        }

        try {
          const bytes = Math.round(fileSize * 1024 * 1024); // Convert MB to bytes

          // Get costs from Turbo
          const costs = await turbo.getUploadCosts({ bytes: [bytes] });
          const cost = costs[0];

          // Get fiat estimate
          const fiatEstimate = await turbo.getFiatEstimateForBytes({
            byteCount: bytes,
            currency: currency,
          });

          resultsDiv.innerHTML = `
                    <div class="result-item">
                        <span>File Size:</span>
                        <span>${fileSize} MB (${bytes.toLocaleString()} bytes)</span>
                    </div>
                    <div class="result-item">
                        <span>Winston Credits:</span>
                        <span>${cost.winc} winc</span>
                    </div>
                    <div class="result-item">
                        <span>Fiat Cost:</span>
                        <span>${fiatEstimate.currency.toUpperCase()} $${fiatEstimate.amount.toFixed(
            4
          )}</span>
                    </div>
                    <div class="result-item">
                        <span>Rate per GiB:</span>
                        <span>${currency.toUpperCase()} $${
            rates.fiat[currency]
          }</span>
                    </div>
                `;
        } catch (error) {
          resultsDiv.innerHTML = `<div style="color: #ff6b6b;">Error: ${error.message}</div>`;
        }
      }

      // Event listeners
      document
        .getElementById("fileSize")
        .addEventListener("input", calculateCosts);
      document
        .getElementById("currency")
        .addEventListener("change", calculateCosts);

      // Initialize
      loadRates();
    </script>
  </body>
</html>

# Multi-File Upload Interface

Advanced interface for multiple file uploads:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Turbo SDK - Bulk Upload</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
      }
      .drop-zone {
        border: 3px dashed #ccc;
        border-radius: 10px;
        padding: 50px;
        text-align: center;
        margin: 20px 0;
        cursor: pointer;
        transition: all 0.3s ease;
      }
      .drop-zone:hover,
      .drop-zone.dragover {
        border-color: #007cba;
        background-color: #f0f8ff;
      }
      .file-list {
        margin: 20px 0;
      }
      .file-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
        margin: 5px 0;
      }
      .file-item.uploading {
        background-color: #fff3cd;
      }
      .file-item.success {
        background-color: #d4edda;
      }
      .file-item.error {
        background-color: #f8d7da;
      }
      .progress-bar {
        width: 200px;
        height: 10px;
        background-color: #f0f0f0;
        border-radius: 5px;
        overflow: hidden;
      }
      .progress-fill {
        height: 100%;
        background-color: #007cba;
        transition: width 0.3s ease;
      }
      button {
        background: #007cba;
        color: white;
        border: none;
        padding: 12px 24px;
        border-radius: 6px;
        cursor: pointer;
        margin: 5px;
      }
      button:hover {
        background: #005a87;
      }
      button:disabled {
        background: #ccc;
        cursor: not-allowed;
      }
      .controls {
        text-align: center;
        margin: 20px 0;
      }
    </style>
  </head>
  <body>
    <h1>📁 Turbo SDK - Bulk File Upload</h1>

    <div class="drop-zone" id="dropZone">
      <p>📤 Drag and drop files here or click to select</p>
      <input type="file" id="fileInput" multiple style="display: none;" />
    </div>

    <div class="controls">
      <button id="uploadBtn" onclick="uploadAllFiles()" disabled>
        Upload All Files
      </button>
      <button onclick="clearFiles()">Clear All</button>
      <div id="totalCost" style="margin-top: 10px; font-weight: bold;"></div>
    </div>

    <div id="fileList" class="file-list"></div>

    <script type="module">
      // Note: This is a demo interface - you'll need to add wallet connection
      // for actual uploads. See wallet integration examples above.

      import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk";

      const turbo = TurboFactory.unauthenticated();
      let selectedFiles = [];
      let totalCostWinc = 0;

      // Setup drag and drop
      const dropZone = document.getElementById("dropZone");
      const fileInput = document.getElementById("fileInput");

      dropZone.addEventListener("click", () => fileInput.click());
      dropZone.addEventListener("dragover", (e) => {
        e.preventDefault();
        dropZone.classList.add("dragover");
      });
      dropZone.addEventListener("dragleave", () => {
        dropZone.classList.remove("dragover");
      });
      dropZone.addEventListener("drop", (e) => {
        e.preventDefault();
        dropZone.classList.remove("dragover");
        handleFiles(e.dataTransfer.files);
      });

      fileInput.addEventListener("change", (e) => {
        handleFiles(e.target.files);
      });

      async function handleFiles(files) {
        for (let file of files) {
          if (
            !selectedFiles.find(
              (f) => f.name === file.name && f.size === file.size
            )
          ) {
            selectedFiles.push(file);
            await addFileToList(file);
          }
        }
        updateControls();
      }

      async function addFileToList(file) {
        const fileList = document.getElementById("fileList");
        const fileItem = document.createElement("div");
        fileItem.className = "file-item";
        fileItem.id = `file-${selectedFiles.length - 1}`;

        try {
          // Calculate cost for this file
          const costs = await turbo.getUploadCosts({ bytes: [file.size] });
          const cost = costs[0];
          totalCostWinc += parseInt(cost.winc);

          fileItem.innerHTML = `
                    <div>
                        <strong>${file.name}</strong><br>
                        <small>${(file.size / 1024 / 1024).toFixed(2)} MB • ${
            cost.winc
          } winc</small>
                    </div>
                    <div>
                        <div class="progress-bar">
                            <div class="progress-fill" style="width: 0%"></div>
                        </div>
                        <button onclick="removeFile(${
                          selectedFiles.length - 1
                        })">Remove</button>
                    </div>
                `;
        } catch (error) {
          fileItem.innerHTML = `
                    <div>
                        <strong>${file.name}</strong><br>
                        <small>Error calculating cost</small>
                    </div>
                    <div>
                        <button onclick="removeFile(${
                          selectedFiles.length - 1
                        })">Remove</button>
                    </div>
                `;
        }

        fileList.appendChild(fileItem);
      }

      function removeFile(index) {
        selectedFiles.splice(index, 1);
        document.getElementById("fileList").innerHTML = "";
        totalCostWinc = 0;

        // Rebuild file list
        selectedFiles.forEach((file, i) => {
          addFileToList(file);
        });
        updateControls();
      }

      function clearFiles() {
        selectedFiles = [];
        totalCostWinc = 0;
        document.getElementById("fileList").innerHTML = "";
        updateControls();
      }

      function updateControls() {
        const uploadBtn = document.getElementById("uploadBtn");
        const totalCostDiv = document.getElementById("totalCost");

        uploadBtn.disabled = selectedFiles.length === 0;
        totalCostDiv.innerHTML =
          selectedFiles.length > 0
            ? `Total Cost: ${totalCostWinc.toLocaleString()} winc (${
                selectedFiles.length
              } files)`
            : "";
      }

      async function uploadAllFiles() {
        alert(
          "Bulk upload requires wallet connection. See wallet integration examples above for implementation details."
        );

        // Demo progress animation
        selectedFiles.forEach((file, index) => {
          const fileItem = document.getElementById(`file-${index}`);
          const progressFill = fileItem.querySelector(".progress-fill");

          fileItem.classList.add("uploading");

          let progress = 0;
          const interval = setInterval(() => {
            progress += Math.random() * 20;
            if (progress >= 100) {
              progress = 100;
              clearInterval(interval);
              fileItem.classList.remove("uploading");
              fileItem.classList.add("success");
            }
            progressFill.style.width = progress + "%";
          }, 300);
        });
      }

      // Make functions available globally for onclick handlers
      window.removeFile = removeFile;
      window.clearFiles = clearFiles;
      window.uploadAllFiles = uploadAllFiles;
    </script>
  </body>
</html>

# Deployment Considerations

# 1. HTTPS Requirement

Browser wallet integrations require HTTPS in production:

<!-- Production deployment checklist -->
<!-- ✅ HTTPS enabled (required for wallet connections) -->
<!-- ✅ CSP headers configured for external CDN imports -->
<!-- ✅ Proper error handling for network failures -->

# 2. Content Security Policy

Configure CSP headers to allow CDN imports:

<meta
  http-equiv="Content-Security-Policy"
  content="
    default-src 'self';
    script-src 'self' 'unsafe-inline' https://esm.sh https://unpkg.com https://cdn.jsdelivr.net;
    connect-src 'self' https://upload.ardrive.io https://payment.ardrive.io https://arweave.net;
    img-src 'self' data:;
"
/>

# 3. Error Handling

Implement comprehensive error handling:

// Network error handling
async function robustApiCall(apiFunction, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await apiFunction();
    } catch (error) {
      console.error(`Attempt ${i + 1} failed:`, error);
      if (i === retries - 1) throw error;
      await new Promise((resolve) =>
        setTimeout(resolve, 1000 * Math.pow(2, i))
      );
    }
  }
}

// Usage example
const rates = await robustApiCall(() => turbo.getFiatRates());

# 4. Performance Optimization

Optimize for production environments:

<!-- Preload CDN resources -->
<link rel="modulepreload" href="https://esm.sh/@ardrive/turbo-sdk@1.20.0" />

<!-- Use specific versions in production -->
<script type="module">
  import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk@1.20.0";
  // Production code here
</script>

# Best Practices

# 1. User Experience

  • Loading States: Always show loading indicators during API calls
  • Error Recovery: Provide clear error messages with recovery options
  • Progress Tracking: Show upload progress for large files
  • Wallet Detection: Guide users to install wallets if missing

# 2. Security

  • Never expose private keys in browser applications
  • Validate user inputs before API calls
  • Use HTTPS for all production deployments
  • Implement CSP headers to prevent XSS attacks

# 3. Performance

  • Cache API responses where appropriate (rates, costs)
  • Use specific CDN versions in production
  • Implement retry logic for network failures
  • Optimize file handling for large uploads

# 4. Development

  • Use development endpoints during testing
  • Test wallet integrations across different browsers
  • Validate upload functionality with small files first
  • Monitor API rate limits and implement backoff

# Troubleshooting Common Issues

# CDN Import Errors

If you encounter errors like:

  • The requested module does not provide an export named 'TurboFactory'
  • Module resolution failures

Solution: Use esm.sh instead of unpkg.com:

// ❌ Problematic
import { TurboFactory } from "https://unpkg.com/@ardrive/turbo-sdk";

// ✅ Working
import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk";

# Function Scope Issues

If onclick handlers throw ReferenceError: function is not defined:

Solution: Use explicit global assignment:

// ❌ Problematic
window.myFunction = async function() { ... }

// ✅ Working
async function myFunction() { ... }
window.myFunction = myFunction;

# Upload Result Properties

If upload results have undefined properties:

Solution: Use original file size for display:

// ❌ Problematic - these properties don't exist in result object
const totalBytes = result.totalBytes || result.dataSizeBytes;

// ✅ Correct - use original file size
const displayBytes = originalFile.size;

// Available result properties: id, timestamp, winc, version,
// deadlineHeight, dataCaches, fastFinalityIndexes, public, signature, owner

# Upload Cost Properties

If cost calculations fail:

Solution: Use correct cost object structure:

// ❌ Problematic - adjustedBytes doesn't exist in cost objects
const cost = costs[0];
console.log(`Adjusted: ${cost.adjustedBytes.toLocaleString()}`);

// ✅ Correct - use available properties
const cost = costs[0];
console.log(`Cost: ${cost.winc} winc`);
console.log(`File size: ${originalFile.size.toLocaleString()} bytes`);
// Available cost properties: winc (string), adjustments (array)

# Testing Your Implementation

# 1. Basic Functionality Test

// Test CDN import
console.log("Testing Turbo SDK import...");
import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk";
const turbo = TurboFactory.unauthenticated();
console.log("✅ SDK imported successfully");

// Test rate fetching
const rates = await turbo.getFiatRates();
console.log("✅ Rates fetched:", rates);

# 2. Wallet Integration Test

  • Connect to MetaMask/Wander wallet
  • Verify address display
  • Test small file upload (< 1MB)
  • Check transaction ID and Arweave confirmation

# 3. Production Readiness

  • Test with HTTPS hosting
  • Verify CSP headers don't block resources
  • Test error scenarios (network offline, wallet disconnected)
  • Validate cross-browser compatibility

# API Object Structures

Understanding the actual structure of API responses helps prevent common errors:

# Upload Cost Object

const costs = await turbo.getUploadCosts({ bytes: [file.size] });
const cost = costs[0];

// Actual structure:
{
  winc: "15763844",        // string - cost in Winston credits
  adjustments: []          // array - currently empty
}
// Note: adjustedBytes property does NOT exist

# Upload Result Object

const result = await turboClient.uploadFile({...});

// Actual structure:
{
  id: "K2wbyN85FlZMBYyfn4fnk5A-fp-pmnrQWpgfuKI4UCc",
  timestamp: 1752094894060,
  winc: "0",
  version: "0.2.0",
  deadlineHeight: 1708455,
  dataCaches: ["arweave.net"],
  fastFinalityIndexes: ["arweave.net"],
  public: "wZgP9Rpfh0nXb5EdzBUg7y2LFMkBp2ADX3gdZhLXHYbz...",
  signature: "k31SCZwxerhuEojUZ7rnLMr0T6CwgVp1_dKDZc7UdvV...",
  owner: "cF0H0SKdnaDTqWKY9iJKBktTpdEWgb3GnlndE7ABv0Q"
}
// Note: totalBytes and dataSizeBytes properties do NOT exist

# Fiat Rates Object

const rates = await turbo.getFiatRates();

// Actual structure:
{
  winc: "2257047178957",              // string - current winc rate
  fiat: {                            // object - fiat currency rates
    aud: 25.13395879439,             // number - AUD per GiB
    brl: 91.578363344626,            // number - BRL per GiB
    cad: 22.482075685955,            // number - CAD per GiB
    eur: 13.996049738963,            // number - EUR per GiB
    gbp: 12.080800827315,            // number - GBP per GiB
    hkd: 129.06624536489,            // number - HKD per GiB
    inr: 1409.435250458,             // number - INR per GiB
    jpy: 2406.336355749,             // number - JPY per GiB
    sgd: 21.058978043112,            // number - SGD per GiB
    usd: 16.428221816529             // number - USD per GiB
  },
  adjustments: []                    // array - currently empty
}

# Progress Event Object

events: {
  onProgress: (progressEvent) => {
    // Actual structure:
    {
      processedBytes: 4326,          // number - bytes processed so far
      totalBytes: 8652,              // number - total bytes to process
      step: "signing"                // string - current step: "signing" or "upload"
    }
  }
}

# Additional Resources


For more advanced implementations, see the Next.js and Vite framework guides, or explore the Turbo SDK examples (opens new window) repository.