arcraft
v0.0.32
Published
JavaScript SDK for ARC's utils in Algorand for both Node.js and browser environments
Readme
Arcraft
A comprehensive JavaScript/TypeScript SDK for working with Algorand ARC (Algorand Request for Comments) standards. Supports both Node.js and browser environments with utilities for creating, managing, and querying NFTs and blockchain data.
View the documentation here.
📋 Table of Contents
- Features
- Installation
- Supported ARC Standards
- Quick Start
- IPFS Integration
- API Reference
- Examples
- Requirements
- API Documentation
- Development
- Contributing
- License
- Links
- Acknowledgments
✨ Features
Multi-ARC Support
- ARC-3: Create and manage NFTs with external metadata on IPFS
- ARC-19: Advanced NFTs with template-based IPFS URIs and updatable metadata
- ARC-54: Standardized asset burning for accurate supply tracking
- ARC-59: Opt-in-less asset transfers via a secure inbox system
- ARC-62: On-chain circulating supply verification
- ARC-69: NFTs with embedded metadata in transaction notes
- ARC-82: Query blockchain data using standardized URI schemes
IPFS Integration
- Multiple Providers: Support for Pinata and Filebase
- Cross-Platform: Works in both Node.js and browser environments
- Seamless Upload: File and JSON metadata uploading with automatic CID generation
Core Features
- Network Support: Mainnet, Testnet, and Localnet compatibility
- Asset Management: Comprehensive Algorand Standard Asset utilities
- TypeScript Support: Full type definitions for better development experience
- Error Handling: Robust error handling and validation
📦 Installation
npm install arcraft🎯 Supported ARC Standards
ARC-3: NFT Standard with External Metadata
Traditional NFT standard where metadata is stored externally (typically on IPFS) and referenced via URL.
ARC-19: Advanced NFT Standard
Enhanced NFT standard with template-based IPFS URIs that allow for more efficient storage and updatable metadata through reserve address manipulation.
ARC-54: Asset Burning Standard
Provides a standardized smart contract for burning Algorand Standard Assets (ASAs), enabling accurate tracking of circulating supply.
ARC-59: Asset Inbox Standard
Defines an inbox-based system for sending ASAs without requiring the recipient to opt-in first, preventing "asset spam" and simplifying airdrops.
ARC-62: Circulating Supply Standard
Offers a standardized on-chain method to query an asset's true circulating supply, accounting for burned tokens and reserve holdings.
ARC-69: Embedded Metadata NFT Standard
NFT standard where metadata is embedded directly in transaction notes, eliminating the need for external storage.
ARC-82: Blockchain Data Query Standard
URI scheme standard for querying application and asset data directly from the Algorand blockchain.
🚀 Quick Start
Basic ARC-3 NFT Creation
import { Arc3, IPFS } from 'arcraft';
import algosdk, { makeBasicAccountTransactionSigner } from 'algosdk';
async function createARC3NFT() {
// Initialize IPFS provider
const ipfs = new IPFS('pinata', {
provider: 'pinata',
jwt: 'YOUR_PINATA_JWT_TOKEN',
});
// Create account from mnemonic
const account = algosdk.mnemonicToSecretKey('your mnemonic phrase here');
// Create ARC-3 NFT
const result = await Arc3.create({
name: 'My First NFT',
unitName: 'MYNFT',
creator: {
address: account.addr,
signer: makeBasicAccountTransactionSigner(account),
},
ipfs,
image: {
file: './artwork.jpg', // Node.js: file path, Browser: File object
name: 'artwork.jpg',
},
properties: {
description: 'My first NFT using Arcraft',
collection: 'Test Collection',
traits: [
{ trait_type: 'Color', value: 'Blue' },
{ trait_type: 'Rarity', value: 'Common' },
],
},
network: 'testnet',
});
console.log(`NFT Created! Asset ID: ${result.assetId}`);
}ARC-19 NFT with Updatable Metadata
import { Arc19, IPFS } from 'arcraft';
async function createARC19NFT() {
const ipfs = new IPFS('filebase', {
provider: 'filebase',
token: 'YOUR_FILEBASE_TOKEN',
});
const account = algosdk.mnemonicToSecretKey('your mnemonic phrase here');
// Create ARC-19 NFT
const result = await Arc19.create({
name: 'Updatable NFT',
unitName: 'UPNFT',
creator: {
address: account.addr,
signer: makeBasicAccountTransactionSigner(account),
},
ipfs,
image: {
file: './artwork.jpg',
name: 'artwork.jpg',
},
properties: {
description: 'An NFT with updatable metadata',
version: '1.0.0',
},
network: 'testnet',
});
console.log(`ARC-19 NFT Created! Asset ID: ${result.assetId}`);
// Later, update the NFT metadata
await Arc19.update({
manager: {
address: account.addr,
signer: makeBasicAccountTransactionSigner(account),
},
properties: {
description: 'Updated NFT description',
version: '2.0.0',
},
assetId: result.assetId,
ipfs,
network: 'testnet',
});
}ARC-69 NFT with Embedded Metadata
import { Arc69, IPFS } from 'arcraft';
async function createARC69NFT() {
const ipfs = new IPFS('pinata', {
provider: 'pinata',
jwt: 'YOUR_PINATA_JWT_TOKEN',
});
const account = algosdk.mnemonicToSecretKey('your mnemonic phrase here');
// Create ARC-69 NFT (metadata stored in transaction notes)
const result = await Arc69.create({
name: 'Embedded Metadata NFT',
unitName: 'EMNFT',
creator: {
address: account.addr,
signer: makeBasicAccountTransactionSigner(account),
},
ipfs,
image: {
file: './image.png',
name: 'image.png',
},
properties: {
standard: 'arc69',
description: 'NFT with metadata in transaction notes',
external_url: 'https://example.com',
attributes: [
{ trait_type: 'Background', value: 'Sunset' },
{ trait_type: 'Character', value: 'Robot' },
],
},
network: 'testnet',
});
console.log(`ARC-69 NFT Created! Asset ID: ${result.assetId}`);
}ARC-82 Blockchain Queries
import { Arc82 } from 'arcraft';
async function queryBlockchainData() {
// Parse an ARC-82 URI
const uri = 'algorand://app/123456?box=YWNjb3VudA==&global=dG90YWw=';
const parsed = Arc82.parse(uri);
console.log('Parsed URI:', parsed);
// Query application data
const appResult = await Arc82.queryApplication(parsed, 'mainnet');
console.log('Application Data:', appResult);
// Query asset data
const assetUri = 'algorand://asset/789012?total=true&decimals=true&url=true';
const assetParsed = Arc82.parse(assetUri);
const assetResult = await Arc82.queryAsset(assetParsed, 'mainnet');
console.log('Asset Data:', assetResult);
// Build URIs programmatically
const newAppUri = Arc82.buildAppUri(123456, {
box: ['YWNjb3VudA=='],
global: ['dG90YWw='],
tealcode: true,
});
console.log('Built URI:', newAppUri);
}ARC-54 Asset Burning
import { Arc54 } from 'arcraft';
import algosdk, { makeBasicAccountTransactionSigner } from 'algosdk';
async function burnAsset() {
const account = algosdk.mnemonicToSecretKey('your mnemonic phrase here');
const assetId = 10458941; // Example asset on Testnet
// Get burned amount before
const before = await Arc54.getBurnedAmount('testnet', assetId);
console.log(`Burned amount before: ${before}`);
// Burn 1000 units of the asset
const txId = await Arc54.burnAsset('testnet', assetId, 1000, {
address: account.addr,
signer: makeBasicAccountTransactionSigner(account),
});
console.log(`Burn transaction ID: ${txId}`);
// Get burned amount after
const after = await Arc54.getBurnedAmount('testnet', assetId);
console.log(`Burned amount after: ${after}`);
}ARC-59 Asset Inbox
import { Arc59 } from 'arcraft';
import algosdk, { makeBasicAccountTransactionSigner } from 'algosdk';
async function useAssetInbox() {
const sender = algosdk.mnemonicToSecretKey('sender mnemonic here');
const receiver = algosdk.mnemonicToSecretKey('receiver mnemonic here');
const assetId = 10458941; // Example asset on Testnet
// Send asset to receiver's inbox
const sendTxId = await Arc59.sendAsset({
network: 'testnet',
assetId,
amount: 1,
receiver: receiver.addr,
sender: {
address: sender.addr,
signer: makeBasicAccountTransactionSigner(sender),
},
});
console.log(`Send to inbox transaction ID: ${sendTxId}`);
// Receiver checks their inbox
const inboxAssets = await Arc59.getAssetsInInbox({
network: 'testnet',
receiver: receiver.addr,
});
console.log('Assets in inbox:', inboxAssets);
// Receiver claims the asset
const claimTxId = await Arc59.claimAsset({
network: 'testnet',
assetId,
receiver: {
address: receiver.addr,
signer: makeBasicAccountTransactionSigner(receiver),
},
});
console.log(`Claim transaction ID: ${claimTxId}`);
}ARC-62 Circulating Supply
import { Arc62 } from 'arcraft';
async function checkCirculatingSupply() {
const arc62AssetId = 733094741; // ARC-62 compatible asset on Testnet
const nonArc62AssetId = 10458941; // Not ARC-62 compatible
// Check an ARC-62 compatible asset
const isCompatible = await Arc62.isArc62Compatible(arc62AssetId, 'testnet');
console.log(`Is asset compatible? ${isCompatible.compatible}`);
const supply1 = await Arc62.getCirculatingSupply(arc62AssetId, 'testnet');
console.log(`On-chain circulating supply: ${supply1}`);
// Check a non-ARC-62 asset (uses fallback calculation)
const supply2 = await Arc62.getCirculatingSupply(nonArc62AssetId, 'testnet');
console.log(`Fallback circulating supply: ${supply2}`);
}🗂️ IPFS Integration
Supported Providers
Pinata
import { IPFS, uploadToPinata } from 'arcraft';
// Using IPFS class
const ipfs = new IPFS('pinata', {
provider: 'pinata',
jwt: 'YOUR_PINATA_JWT_TOKEN',
});
// Direct upload function
const result = await uploadToPinata({
file: './image.jpg', // Node.js: path, Browser: File object
name: 'my-image.jpg',
token: 'YOUR_PINATA_JWT_TOKEN',
});
console.log(`IPFS Hash: ${result.IpfsHash}`);Filebase
import { IPFS, uploadToFilebase } from 'arcraft';
// Using IPFS class
const ipfs = new IPFS('filebase', {
provider: 'filebase',
token: 'YOUR_FILEBASE_TOKEN',
});
// Direct upload function
const result = await uploadToFilebase({
file: './document.pdf',
name: 'document.pdf',
token: 'YOUR_FILEBASE_TOKEN',
});
console.log(`IPFS CID: ${result.cid}`);Universal IPFS Interface
// Both providers implement the same interface
async function uploadWithProvider(ipfs) {
// Upload file
const imageCid = await ipfs.upload(file, 'filename.jpg');
// Upload JSON metadata
const metadataCid = await ipfs.uploadJson(
{
name: 'My NFT',
description: 'A beautiful NFT',
image: `ipfs://${imageCid}`,
},
'metadata.json'
);
return { imageCid, metadataCid };
}📖 API Reference
Arc3 Class
// Create new ARC-3 NFT
const result = await Arc3.create(options);
// Load existing ARC-3 NFT
const nft = await Arc3.fromId(assetId, network);
// Check if asset is ARC-3 compliant
const isCompliant = nft.isArc3();
// Get metadata
const metadata = nft.getMetadata();
// Get image URL
const imageUrl = nft.getImageUrl();
// Get image as base64
const base64Image = await nft.getImageBase64();Arc19 Class
// Create new ARC-19 NFT
const result = await Arc19.create(options);
// Load existing ARC-19 NFT
const nft = await Arc19.fromId(assetId, network);
// Update NFT metadata
await Arc19.update(updateOptions);
// Get all metadata versions
const versions = await Arc19.getMetadataVersions(assetId, network);
// Check if URL is valid ARC-19 format
const isValid = Arc19.hasValidUrl(url);
// Resolve template URL to actual IPFS URL
const resolvedUrl = Arc19.resolveUrl(templateUrl, reserveAddress);Arc69 Class
// Create new ARC-69 NFT
const result = await Arc69.create(options);
// Load existing ARC-69 NFT
const nft = await Arc69.fromId(assetId, network);
// Update NFT metadata
await Arc69.update(updateOptions);
// Get all metadata versions
const versions = await Arc69.getMetadataVersions(assetId, network);
// Check if asset has valid ARC-69 metadata
const hasValidMetadata = await Arc69.hasValidMetadata(assetId, network);Arc82 Class
// Parse ARC-82 URI
const parsed = Arc82.parse(uri);
// Query application data
const appData = await Arc82.queryApplication(parsed, network);
// Query asset data
const assetData = await Arc82.queryAsset(parsed, network);
// Build application URI
const appUri = Arc82.buildAppUri(appId, queryParams);
// Build asset URI
const assetUri = Arc82.buildAssetUri(assetId, queryParams);
// Validate URI format
const isValid = Arc82.isValidArc82Uri(uri);
// Extract ID from URI
const id = Arc82.extractId(uri);
// Extract type from URI
const type = Arc82.extractType(uri);Arc54 Class
// Get contract info
const appInfo = Arc54.getAppInfo(network);
// Burn an asset
const txId = await Arc54.burnAsset(network, assetId, amount, sender);
// Get total burned amount for an asset
const burnedAmount = await Arc54.getBurnedAmount(network, assetId);Arc59 Class
// Send an asset to a user's inbox
const sendTxId = await Arc59.sendAsset({
network,
assetId,
amount,
receiver,
sender,
});
// Get all assets in an inbox
const assets = await Arc59.getAssetsInInbox({ network, receiver });
// Claim an asset from the inbox
const claimTxId = await Arc59.claimAsset({ network, receiver, assetId });
// Reject an asset from the inbox
const rejectTxId = await Arc59.rejectAsset({ network, receiver, assetId });Arc62 Class
// Check if an asset is ARC-62 compatible
const result = await Arc62.isArc62Compatible(assetId, network);
// result.compatible: boolean
// result.applicationId: number
// Get the circulating supply (uses on-chain method or fallback)
const supply = await Arc62.getCirculatingSupply(assetId, network);IPFS Class
// Initialize IPFS with provider
const ipfs = new IPFS(provider, config);
// Upload file
const cid = await ipfs.upload(file, fileName);
// Upload JSON
const jsonCid = await ipfs.uploadJson(jsonObject, fileName);CoreAsset Class
// Load asset by ID
const asset = await CoreAsset.fromId(assetId, network);
// Get asset properties
const creator = asset.getCreator();
const manager = asset.getManager();
const reserve = asset.getReserve();
const freeze = asset.getFreeze();
const clawback = asset.getClawback();
const name = asset.getName();
const unitName = asset.getUnitName();
const url = asset.getUrl();
const total = asset.getTotalSupply();
const decimals = asset.getDecimals();
const defaultFrozen = asset.getDefaultFrozen();
const metadataHash = asset.getMetadataHash();💡 Examples
Cross-Platform File Upload
// Node.js Environment
import { uploadToPinata } from 'arcraft';
import path from 'path';
const nodeUpload = await uploadToPinata({
file: path.resolve('./assets/image.png'),
name: 'image.png',
token: 'YOUR_TOKEN',
});
// Browser Environment
const browserUpload = async (fileInput) => {
const file = fileInput.files[0];
const result = await uploadToPinata({
file: file,
name: file.name,
token: 'YOUR_TOKEN',
});
return result;
};Batch NFT Creation
import { Arc3, IPFS } from 'arcraft';
async function createNFTCollection() {
const ipfs = new IPFS('pinata', {
provider: 'pinata',
jwt: 'YOUR_JWT',
});
const account = algosdk.mnemonicToSecretKey('your mnemonic');
const creator = {
address: account.addr,
signer: makeBasicAccountTransactionSigner(account),
};
const artworks = [
{
file: './art1.png',
name: 'Sunset Dreams',
traits: [{ trait_type: 'Theme', value: 'Nature' }],
},
{
file: './art2.png',
name: 'City Lights',
traits: [{ trait_type: 'Theme', value: 'Urban' }],
},
{
file: './art3.png',
name: 'Ocean Waves',
traits: [{ trait_type: 'Theme', value: 'Water' }],
},
];
const results = [];
for (const [index, artwork] of artworks.entries()) {
const result = await Arc3.create({
name: artwork.name,
unitName: `ART${index + 1}`,
creator,
ipfs,
image: artwork.file,
imageName: `${artwork.name.replace(/\s+/g, '_').toLowerCase()}.png`,
properties: {
description: `Artwork #${index + 1} from the Dreams Collection`,
collection: 'Dreams Collection',
edition: index + 1,
total_editions: artworks.length,
attributes: artwork.traits,
},
network: 'testnet',
});
results.push(result);
console.log(`Created NFT #${index + 1}: Asset ID ${result.assetId}`);
}
return results;
}Complex ARC-82 Queries
import { Arc82 } from 'arcraft';
async function complexBlockchainQuery() {
// Query multiple box keys and global state
const appUri = Arc82.buildAppUri(123456, {
box: [
Arc82.encodeBase64Url('user_balance'),
Arc82.encodeBase64Url('total_supply'),
Arc82.encodeBase64Url('admin_settings'),
],
global: [
Arc82.encodeBase64Url('contract_version'),
Arc82.encodeBase64Url('paused'),
],
local: [
{
key: Arc82.encodeBase64Url('user_score'),
algorandaddress: 'ALGORAND_ADDRESS_HERE',
},
],
tealcode: true,
});
console.log('Generated URI:', appUri);
const parsed = Arc82.parse(appUri);
const result = await Arc82.queryApplication(parsed, 'mainnet');
// Process results
if (result.success) {
console.log('Box storage results:', result.boxes);
console.log('Global state results:', result.global);
console.log('Local state results:', result.local);
console.log('TEAL code:', result.tealCode);
} else {
console.error('Query failed:', result.error);
}
}Metadata Version Tracking
import { Arc19, Arc69 } from 'arcraft';
async function trackMetadataVersions(assetId, network) {
try {
// Get ARC-19 metadata versions
const arc19Versions = await Arc19.getMetadataVersions(assetId, network);
console.log('ARC-19 Metadata Versions:', arc19Versions);
// Get ARC-69 metadata versions
const arc69Versions = await Arc69.getMetadataVersions(assetId, network);
console.log('ARC-69 Metadata Versions:', arc69Versions);
// Compare versions and show evolution
if (arc19Versions.length > 0) {
console.log('ARC-19 Evolution:');
arc19Versions.forEach((version, index) => {
console.log(`Version ${index + 1}:`, {
timestamp: version.timestamp,
metadataHash: version.metadataHash,
changes: version.properties,
});
});
}
} catch (error) {
console.error('Error tracking versions:', error);
}
}📋 Requirements
Environment Requirements
- Node.js: >= 18.0.0 (for Node.js usage)
- Browser: Modern browsers with ES6 modules support
- TypeScript: >= 5.0.0 (for TypeScript projects)
Algorand Requirements
- Algorand account with sufficient funds for asset creation
- Access to Algorand node (Algod) and Indexer services
- Network connectivity to chosen Algorand network (mainnet/testnet/localnet)
IPFS Provider Requirements
Choose at least one IPFS provider:
Pinata
- Pinata account and JWT token
- Sign up at pinata.cloud
Filebase
- Filebase account and API token
- Sign up at filebase.com
Development Requirements
- Git for version control
- npm or yarn package manager
- Code editor with TypeScript support (recommended: VS Code)
📚 API Documentation
TypeDoc Documentation
This package is fully documented with TypeDoc comments. You can generate and view the documentation in several ways:
View Online Documentation
Visit our comprehensive online documentation: https://satishccy.github.io/arcraft/
Generate Local Documentation
# Clone the repository
git clone https://github.com/satishccy/arcraft.git
cd arcraft
# Install dependencies
npm install
# Generate TypeDoc documentation
npm run docs
# Open the generated documentation
open docs/index.htmlIn-Editor Documentation
If you're using VS Code or another TypeScript-aware editor, you'll get:
- IntelliSense: Auto-completion with parameter hints
- Type Information: Hover over any function to see its signature
- Parameter Help: Detailed information about function parameters
- Return Types: Clear indication of what each function returns
Documentation Structure
The generated TypeDoc documentation includes:
- Classes: All main classes (CoreAsset, Arc3, Arc19, Arc69, Arc82, IPFS, etc.)
- Interfaces: Type definitions and data structures
- Functions: Utility functions and helper methods
- Enums: Constants and enumerated values
- Modules: Organized by functionality (arc3, arc19, arc69, arc82, ipfs, utils, etc.)
- Examples: Code examples for common use cases
- Cross-references: Links between related types and functions
Core API Structure
The package is organized into logical modules:
Core Classes
CoreAsset: Base class for all Algorand Standard AssetsArc3: ARC-3 NFT implementation with external metadataArc19: ARC-19 NFT implementation with template IPFS URIsArc69: ARC-69 NFT implementation with embedded metadataArc82: ARC-82 blockchain data query implementation
Utility Modules
IPFS: Universal IPFS integration supporting multiple providersutils: Algorand client utilities and helper functionsmimeUtils: Cross-platform MIME type detectionAssetFactory: Smart factory for creating appropriate asset instances
Provider Integrations
pinata: Pinata IPFS service integrationfilebase: Filebase IPFS service integration
TypeScript Support
Full TypeScript support with:
- Strict Type Checking: All functions have proper type annotations
- Interface Definitions: Clear interfaces for all data structures
- Generic Types: Type-safe operations across different asset types
- Enum Support: Strongly-typed enums for constants and options
Error Handling
Comprehensive error handling with custom error classes:
Arc82ParseError: Thrown when ARC-82 URIs cannot be parsedArc82QueryError: Thrown when blockchain queries fail- Standard JavaScript errors for network and validation issues
🛠️ Development
For local development:
# Install deps
npm install
# Build the library bundles
npm run build
# Lint and format
npm run lint:fix
npm run format
# Generate docs (optional)
npm run docsSee CONTRIBUTING.md for full details and PR guidelines. Example apps are in usecase/.
🤝 Contributing
We welcome contributions! Here's how you can help:
Getting Started
- Fork the repository
- Clone your fork:
git clone https://github.com/satishccy/arcraft.git - Install dependencies:
npm install - Create a feature branch:
git checkout -b feature/amazing-feature
Development Workflow
- Make your changes
- Add tests for new functionality
- Run tests:
npm test - Run linter:
npm run lint:fix - Format code:
npm run format - Update documentation:
npm run docs - Commit your changes:
git commit -m 'Add amazing feature' - Push to your branch:
git push origin feature/amazing-feature - Open a Pull Request
Documentation Standards
When contributing, please ensure all code is properly documented:
TypeDoc Comment Requirements
All public functions, classes, and interfaces must have:
/**
* Brief description of what the function does
* @param paramName - Description of the parameter
* @param optionalParam - Description (optional)
* @returns Description of return value
* @throws Error description when function can throw
* @example
* ```typescript
* // Usage example
* const result = await myFunction('example');
* ```
*/Documentation Guidelines
- Functions: Describe purpose, parameters, return values, and potential errors
- Classes: Describe the class purpose and main functionality
- Interfaces: Document each property and its purpose
- Examples: Include practical usage examples for complex functions
- Modules: Each file should have a module-level description
Code Style Requirements
- Follow existing TypeScript style conventions
- Use meaningful variable and function names
- Add JSDoc comments for all public APIs
- Include error handling and validation
Contribution Guidelines
- Follow the existing code style and conventions
- Add comprehensive tests for new features
- Update documentation for API changes
- Keep commits atomic and well-described
- Ensure all tests pass before submitting PR
- Update TypeDoc comments for any API changes
Areas for Contribution
- Additional ARC standard implementations
- More IPFS provider integrations
- Performance optimizations
- Browser compatibility improvements
- Documentation enhancements
- Example applications
- Test coverage improvements
- Error handling enhancements
📄 License
MIT License - see the LICENSE file for details.
🔗 Links
- Documentation: https://satishccy.github.io/arcraft/
- GitHub Repository: https://github.com/satishccy/arcraft
- npm Package: https://www.npmjs.com/package/arcraft
- Issues: https://github.com/satishccy/arcraft/issues
- TypeDoc Configuration: ./typedoc.config.json
🙏 Acknowledgments
- Algorand Foundation for the ARC standards
- The Algorand developer community
- Contributors to the algosdk-js library
- IPFS and related decentralized storage providers
- TypeDoc community for excellent documentation tooling
Built with ❤️ for the Algorand ecosystem
