Complete Workflow
These examples demonstrate end-to-end workflows for both Stellar and Solana. Each example includes error handling and best practices.
Platform Examples
- Stellar
- Solana
Copy
import { AttestSDK, StellarConfig } from '@attestprotocol/sdk';
async function stellarWorkflow() {
const config: StellarConfig = {
secretKeyOrCustomSigner: process.env.STELLAR_SECRET_KEY!,
publicKey: process.env.STELLAR_PUBLIC_KEY!,
url: 'https://soroban-testnet.stellar.org'
};
const sdk = await AttestSDK.initializeStellar(config);
// Register as authority
const authResult = await sdk.registerAuthority();
if (authResult.error) return authResult.error;
// Create schema
const schemaResult = await sdk.createSchema({
schemaName: 'kyc-verification-v1',
schemaContent: 'verified:bool,level:string,score:uint8,timestamp:uint64',
revocable: true
});
if (schemaResult.error) return schemaResult.error;
const schemaUID = schemaResult.data!.schemaUID;
// Wait for indexing
await new Promise(resolve => setTimeout(resolve, 10000));
// Create attestation
const attestResult = await sdk.attest({
schemaUID: schemaUID,
subject: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
value: 'verified:true,level:enhanced,score:95,timestamp:1704067200',
reference: 'kyc-check-2024-001'
});
if (attestResult.error) return attestResult.error;
// Fetch attestation
const fetchResult = await sdk.fetchAttestation({
schemaUID: schemaUID,
subject: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
reference: 'kyc-check-2024-001'
});
return fetchResult.data;
}
Both examples follow the same workflow: initialize SDK → register authority → create schema → create attestation → fetch attestation.
Express.js API Integration
Always validate input data and implement proper authentication before production deployment.
Copy
import express from 'express';
import { AttestSDK, StellarConfig, SolanaConfig } from '@attestprotocol/sdk';
const app = express();
app.use(express.json());
let stellarSDK: any;
let solanaSDK: any;
async function initializeSDKs() {
stellarSDK = await AttestSDK.initializeStellar({
secretKeyOrCustomSigner: process.env.STELLAR_SECRET_KEY!,
publicKey: process.env.STELLAR_PUBLIC_KEY!
});
solanaSDK = await AttestSDK.initializeSolana({
walletOrSecretKey: JSON.parse(process.env.SOLANA_SECRET_KEY!)
});
}
app.post('/api/attest', async (req, res) => {
const { chain, schemaUID, subject, data, reference } = req.body;
try {
let result;
if (chain === 'stellar') {
result = await stellarSDK.attest({
schemaUID,
subject,
value: data,
reference
});
} else if (chain === 'solana') {
const balanceResult = await solanaSDK.getWalletBalance();
result = await solanaSDK.attest({
schemaData: new anchor.web3.PublicKey(schemaUID),
data,
accounts: {
recipient: new anchor.web3.PublicKey(subject),
levyReceipent: balanceResult.data!.address,
mintAccount: new anchor.web3.PublicKey('So11111111111111111111111111111111111111112')
}
});
} else {
return res.status(400).json({ error: 'Unsupported chain' });
}
if (result.error) {
return res.status(500).json({ error: result.error });
}
res.json({
success: true,
attestationId: result.data,
chain
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/api/verify/:chain/:schema/:subject', async (req, res) => {
const { chain, schema, subject } = req.params;
try {
let result;
if (chain === 'stellar') {
result = await stellarSDK.fetchAttestation({
schemaUID: schema,
subject: subject
});
} else if (chain === 'solana') {
result = await solanaSDK.fetchAttestation(subject);
} else {
return res.status(400).json({ error: 'Unsupported chain' });
}
if (result.error) {
return res.status(500).json({ error: result.error });
}
res.json({
verified: !!result.data && !result.data.revoked,
attestation: result.data,
chain
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
initializeSDKs().then(() => {
app.listen(3000);
});
Batch Operations
Batch operations enable efficient processing of multiple attestations. Use Promise.all() for parallel execution.
Multiple Attestations
Copy
async function batchAttestations() {
const sdk = await AttestSDK.initializeSolana({
walletOrSecretKey: JSON.parse(process.env.SOLANA_SECRET_KEY!)
});
await sdk.registerAuthority();
const schemaResult = await sdk.createSchema({
schemaName: 'user-verification-batch',
schemaContent: 'verified:bool,tier:string,score:u8',
revocable: true
});
if (schemaResult.error) throw schemaResult.error;
const schemaPDA = schemaResult.data!;
const walletBalance = await sdk.getWalletBalance();
const users = [
{ address: '11111111111111111111111111111111', data: 'verified:true,tier:gold,score:95' },
{ address: '22222222222222222222222222222222', data: 'verified:true,tier:silver,score:80' },
{ address: '33333333333333333333333333333333', data: 'verified:true,tier:bronze,score:65' }
];
const attestationPromises = users.map(user => sdk.attest({
schemaData: schemaPDA,
data: user.data,
accounts: {
recipient: new anchor.web3.PublicKey(user.address),
levyReceipent: walletBalance.data!.address,
mintAccount: new anchor.web3.PublicKey('So11111111111111111111111111111111111111112')
}
}));
const results = await Promise.all(attestationPromises);
return results.filter(result => !result.error).map(result => result.data);
}
When performing batch operations, consider rate limits and transaction costs. Implement retry logic for failed attestations.
React Component
Never expose private keys in client-side code. Use server-side APIs or wallet integrations for production.
Copy
import React, { useState, useEffect } from 'react';
import { AttestSDK } from '@attestprotocol/sdk';
interface AttestationFormProps {
chain: 'stellar' | 'solana';
}
// Custom hook for AttestSDK
export function useAttestSDK(chain: 'stellar' | 'solana') {
const [sdk, setSdk] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function init() {
try {
let instance;
if (chain === 'stellar') {
instance = await AttestSDK.initializeStellar(stellarConfig);
} else {
instance = await AttestSDK.initializeSolana(solanaConfig);
}
setSdk(instance);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
init();
}, [chain]);
return { sdk, loading, error };
}
Error Handling
Proper error handling is crucial for production applications. The examples below show comprehensive error management strategies.
Comprehensive Error Handler
- Error Handler Class
- Error Constants
Copy
class AttestSDKErrorHandler {
handleStellarError(error: any): string {
if (error.status === 404) {
return "Account not found. Fund your Stellar account.";
}
if (error.message?.includes('insufficient balance')) {
return "Insufficient XLM balance.";
}
if (error.message?.includes('Invalid schema UID')) {
return "Schema UID must be 64-character hex string.";
}
return `Stellar error: ${error.message || 'Unknown error'}`;
}
handleSolanaError(error: any): string {
if (error.code === 6010) {
return "Schema with this name already exists.";
}
if (error.code === 6002) {
return "Attestation is already revoked.";
}
if (error.message?.includes('Insufficient funds')) {
return "Insufficient SOL balance.";
}
return `Solana error: ${error.message || 'Unknown error'}`;
}
}
async function robustOperation() {
const errorHandler = new AttestSDKErrorHandler();
const stellarSDK = await AttestSDK.initializeStellar(config);
const result = await stellarSDK.attest({
schemaUID: 'abc123...',
subject: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
value: 'verified:true',
reference: 'test-001'
});
if (result.error) {
const errorMsg = errorHandler.handleStellarError(result.error);
throw new Error(errorMsg);
}
return result.data;
}
Environment Configuration
Use environment variables to manage configuration across different environments (development, staging, production).
Configuration Utility
Copy
interface EnvironmentConfig {
stellar: StellarConfig;
solana: SolanaConfig;
}
function getConfig(): EnvironmentConfig {
const requiredEnvVars = [
'STELLAR_SECRET_KEY',
'STELLAR_PUBLIC_KEY',
'SOLANA_SECRET_KEY'
];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
}
return {
stellar: {
secretKeyOrCustomSigner: process.env.STELLAR_SECRET_KEY!,
publicKey: process.env.STELLAR_PUBLIC_KEY!,
url: process.env.STELLAR_RPC_URL || 'https://soroban-testnet.stellar.org'
},
solana: {
walletOrSecretKey: JSON.parse(process.env.SOLANA_SECRET_KEY!),
url: process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com',
programId: process.env.SOLANA_PROGRAM_ID
}
};
}
// Usage
const config = getConfig();
const stellarSDK = await AttestSDK.initializeStellar(config.stellar);
const solanaSDK = await AttestSDK.initializeSolana(config.solana);
Testing
Always use testnet/devnet for testing. Never run tests against mainnet to avoid unnecessary costs.
Jest Integration Tests
- Jest Tests
- Test Utilities
Copy
describe('AttestSDK Integration Tests', () => {
let stellarSDK: any;
let testSchemaUID: string;
beforeAll(async () => {
stellarSDK = await AttestSDK.initializeStellar({
secretKeyOrCustomSigner: process.env.TEST_STELLAR_SECRET_KEY!,
publicKey: process.env.TEST_STELLAR_PUBLIC_KEY!
});
const authResult = await stellarSDK.registerAuthority();
expect(authResult.error).toBeUndefined();
});
test('should create schema successfully', async () => {
const result = await stellarSDK.createSchema({
schemaName: 'test-schema-jest',
schemaContent: 'test:bool,value:uint8',
revocable: true
});
expect(result.error).toBeUndefined();
expect(result.data).toBeDefined();
expect(result.data.schemaUID).toMatch(/^[a-f0-9]{64}$/);
testSchemaUID = result.data.schemaUID;
});
test('should create attestation successfully', async () => {
const result = await stellarSDK.attest({
schemaUID: testSchemaUID,
subject: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
value: 'test:true,value:42',
reference: 'jest-test-001'
});
expect(result.error).toBeUndefined();
expect(result.data).toBeDefined();
expect(typeof result.data).toBe('string');
});
test('should handle invalid schema UID', async () => {
const result = await stellarSDK.fetchSchema('invalid-uid');
expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
});
});
Production Patterns
These patterns help build robust, scalable applications with AttestProtocol.
Retry Logic
Copy
async function withRetry<T>(
operation: () => Promise<AttestSDKResponse<T>>,
maxRetries: number = 3
): Promise<AttestSDKResponse<T>> {
let lastError: any;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const result = await operation();
if (!result.error) {
return result;
}
lastError = result.error;
const errorStr = result.error.toString().toLowerCase();
if (errorStr.includes('unauthorized') ||
errorStr.includes('invalid') ||
errorStr.includes('already exists')) {
break;
}
if (attempt < maxRetries) {
const delay = 1000 * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return { error: lastError };
}
// Usage
const result = await withRetry(
() => sdk.attest(attestConfig),
3 // max retries
);
Connection Pool
Connection pooling improves performance for high-throughput applications by reusing SDK instances.
Copy
class SDKConnectionPool {
private stellarInstances: StellarAttestSDK[] = [];
private solanaInstances: SolanaAttestSDK[] = [];
private currentStellar = 0;
private currentSolana = 0;
async initialize(poolSize: number = 3) {
const stellarPromises = Array(poolSize).fill(null).map(() =>
AttestSDK.initializeStellar({
secretKeyOrCustomSigner: process.env.STELLAR_SECRET_KEY!,
publicKey: process.env.STELLAR_PUBLIC_KEY!
})
);
const solanaPromises = Array(poolSize).fill(null).map(() =>
AttestSDK.initializeSolana({
walletOrSecretKey: JSON.parse(process.env.SOLANA_SECRET_KEY!)
})
);
this.stellarInstances = await Promise.all(stellarPromises);
this.solanaInstances = await Promise.all(solanaPromises);
}
getStellarSDK(): StellarAttestSDK {
const sdk = this.stellarInstances[this.currentStellar];
this.currentStellar = (this.currentStellar + 1) % this.stellarInstances.length;
return sdk;
}
getSolanaSDK(): SolanaAttestSDK {
const sdk = this.solanaInstances[this.currentSolana];
this.currentSolana = (this.currentSolana + 1) % this.solanaInstances.length;
return sdk;
}
}
// Usage
const pool = new SDKConnectionPool();
await pool.initialize(5); // 5 instances per chain
// Use in request handler
app.get('/api/attest', async (req, res) => {
const sdk = req.query.chain === 'stellar'
? pool.getStellarSDK()
: pool.getSolanaSDK();
// Process request with SDK instance
});
Additional Resources
SDK Methods
Complete API reference
Error Reference
Error codes and solutions
Best Practices
Security and optimization tips
GitHub Examples
More code samples on GitHub