# 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.
# Latest Version (Recommended for Development)
import { TurboFactory } from "https://esm.sh/@ardrive/turbo-sdk";
# Specific Version (Recommended for Production)
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
- Turbo SDK Documentation
- Browser Wallet Security Guide (opens new window)
- Arweave Developer Documentation (opens new window)
- CDN Import Best Practices (opens new window)
For more advanced implementations, see the Next.js and Vite framework guides, or explore the Turbo SDK examples (opens new window) repository.