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.
View the frontend showcase here.
- Features
- Installation
- Supported ARC Standards
- Quick Start
- IPFS Integration
- API Reference
- Examples
- Requirements
- API Documentation
- Development
- Contributing
- License
- Links
- Acknowledgments
- 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
- 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
- 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
npm install arcraftTraditional NFT standard where metadata is stored externally (typically on IPFS) and referenced via URL.
Enhanced NFT standard with template-based IPFS URIs that allow for more efficient storage and updatable metadata through reserve address manipulation.
Provides a standardized smart contract for burning Algorand Standard Assets (ASAs), enabling accurate tracking of circulating supply.
Defines an inbox-based system for sending ASAs without requiring the recipient to opt-in first, preventing "asset spam" and simplifying airdrops.
Offers a standardized on-chain method to query an asset's true circulating supply, accounting for burned tokens and reserve holdings.
NFT standard where metadata is embedded directly in transaction notes, eliminating the need for external storage.
URI scheme standard for querying application and asset data directly from the Algorand blockchain.
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}`);
}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',
});
}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}`);
}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);
}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}`);
}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}`);
}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}`);
}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}`);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}`);// 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 };
}// 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();// 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);// 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);// 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);// 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);// 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 });// 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);// 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);// 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();// 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;
};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;
}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);
}
}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);
}
}- Node.js: >= 18.0.0 (for Node.js usage)
- Browser: Modern browsers with ES6 modules support
- TypeScript: >= 5.0.0 (for TypeScript projects)
- 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)
Choose at least one IPFS provider:
- Pinata account and JWT token
- Sign up at pinata.cloud
- Filebase account and API token
- Sign up at filebase.com
- Git for version control
- npm or yarn package manager
- Code editor with TypeScript support (recommended: VS Code)
This package is fully documented with TypeDoc comments. You can generate and view the documentation in several ways:
Visit our comprehensive online documentation: https://satishccy.github.io/arcraft/docs/
# 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.htmlIf 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
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
The package is organized into logical modules:
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
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
pinata: Pinata IPFS service integrationfilebase: Filebase IPFS service integration
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
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
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/.
We welcome contributions! Here's how you can help:
- 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
- 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
When contributing, please ensure all code is properly documented:
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');
* ```
*/- 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
- Follow existing TypeScript style conventions
- Use meaningful variable and function names
- Add JSDoc comments for all public APIs
- Include error handling and validation
- 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
- Additional ARC standard implementations
- More IPFS provider integrations
- Performance optimizations
- Browser compatibility improvements
- Documentation enhancements
- Example applications
- Test coverage improvements
- Error handling enhancements
MIT License - see the LICENSE file for details.
- Documentation: https://satishccy.github.io/arcraft/docs/
- Frontend Showcase: https://satishccy.github.io/arcraft/usecase/
- 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
- 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