@octwa/sdk
v1.1.1
Published
Official TypeScript SDK for integrating dApps with OctWa Wallet Extension. Implements capability-based authorization with full HFHE support.
Maintainers
Readme
Octra Web Wallet SDK
Version: 2.0.0
License: MIT
Status: Production Ready ✅
The official TypeScript SDK for integrating with the Octra blockchain through the OctWa wallet extension. Implements a capability-based authorization model with full support for HFHE (Homomorphic Fully Encrypted) transactions.
Table of Contents
- Overview
- Architecture
- Installation
- Quick Start
- Core Concepts
- API Reference
- Security
- Advanced Usage
- Error Handling
- Testing
- Migration Guide
Overview
The Octra SDK provides a stateless, deterministic transaction builder for dApps to interact with the Octra blockchain. It follows a strict security model where:
- SDK: Builds transactions deterministically (NO signing, NO private keys)
- Wallet: Final authority for signing and transaction submission
- Network: Executes transactions with HFHE encryption support
Key Features
✅ Capability-Based Authorization - Fine-grained permission model
✅ HFHE Support - Fully encrypted transaction execution
✅ Deterministic Serialization - Canonical transaction building
✅ Domain Separation - Prevents signature replay attacks
✅ Signing Mutex - Prevents race conditions and double-send
✅ Type-Safe - Full TypeScript support with comprehensive types
✅ Event System - Real-time connection and capability events
✅ Gas Estimation - Built-in gas and compute cost estimation
🚫 Intent-Based Swaps - Cross-chain swap support (currently disabled, under development)
Architecture
┌─────────┐ ┌─────────┐ ┌────────────┐ ┌──────────────┐
│ DApp │─────▶│ SDK │─────▶│ Wallet │─────▶│ Octra Network│
│ │ │ │ │ (Extension)│ │ │
└─────────┘ └─────────┘ └────────────┘ └──────────────┘
│
│ Private Keys
│ Signing
│ Nonce Validation
▼
Final AuthorityTrust Boundaries
- DApp Layer: User interface and business logic
- SDK Layer: Stateless transaction builder (this package)
- Wallet Layer: Private key custody and signing
- Network Layer: Transaction execution and state management
Installation
npm install @octra/sdkOr with yarn:
yarn add @octra/sdkOr with pnpm:
pnpm add @octra/sdkRequirements
- Node.js >= 16
- TypeScript >= 4.5 (optional, but recommended)
- OctWa Wallet Extension installed in browser
Quick Start
1. Initialize SDK
import { OctraSDK } from '@octra/sdk';
// Initialize SDK (detects wallet extension)
const sdk = await OctraSDK.init({
timeout: 3000, // Wait up to 3s for extension
});
// Check if wallet is installed
if (!sdk.isInstalled()) {
console.error('Please install OctWa wallet extension');
return;
}2. Connect to Wallet
// Request connection to a Circle
const connection = await sdk.connect({
circle: 'my-circle-id',
appName: 'My DApp',
appIcon: 'https://mydapp.com/icon.png',
});
console.log('Connected:', connection.walletPubKey);
console.log('EVM Address:', connection.evmAddress);
console.log('Network:', connection.network);3. Request Capability
// Request permission to call specific methods
const capability = await sdk.requestCapability({
circle: 'my-circle-id',
methods: ['get_balance', 'send_transaction'],
scope: 'write',
encrypted: false,
ttlSeconds: 3600, // 1 hour
});
console.log('Capability granted:', capability.id);4. Invoke Methods
// Get balance
const result = await sdk.invoke({
capabilityId: capability.id,
method: 'get_balance',
});
// Decode response
import { decodeBalanceResponse } from '@octra/sdk';
const balance = decodeBalanceResponse(result);
console.log('OCT Balance:', balance.octBalance);
console.log('ETH Balance:', balance.ethBalance);Core Concepts
Capabilities
Capabilities are cryptographically signed permissions that grant your dApp access to specific methods. They are:
- Scoped: Limited to specific methods
- Time-bound: Have expiration times
- Origin-bound: Tied to your dApp's origin
- Revocable: Can be revoked by user at any time
Capability Scopes:
read: Read-only operations (e.g.,get_balance)write: State-changing operations (e.g.,send_transaction)compute: HFHE computation operations
Invocations
Invocations are signed method calls using a capability. Each invocation:
- Uses a monotonically increasing nonce (replay protection)
- Includes domain separation (prevents signature replay)
- Is validated by the wallet before execution
- Can include encrypted payloads (HFHE)
Sessions
Sessions represent the connection state between your dApp and the wallet:
const session = sdk.getSessionState();
console.log('Connected:', session.connected);
console.log('Circle:', session.circle);
console.log('Active Capabilities:', session.activeCapabilities.length);API Reference
OctraSDK Class
Static Methods
init(options?: InitOptions): Promise<OctraSDK>
Initialize the SDK and detect wallet extension.
const sdk = await OctraSDK.init({
timeout: 3000, // Detection timeout in ms
autoCleanupExpired: true, // Auto-remove expired capabilities
skipSignatureVerification: false, // For testing only
});Instance Methods
isInstalled(): boolean
Check if wallet extension is installed.
if (!sdk.isInstalled()) {
alert('Please install OctWa wallet');
}connect(request: ConnectRequest): Promise<Connection>
Request connection to a Circle.
const connection = await sdk.connect({
circle: 'circle-id',
appName: 'My DApp',
appIcon: 'https://example.com/icon.png',
appOrigin: window.location.origin, // Auto-detected
requestedCapabilities: [ // Optional: request capabilities immediately
{
methods: ['get_balance'],
scope: 'read',
encrypted: false,
}
],
});Returns:
interface Connection {
circle: string;
sessionId: string;
walletPubKey: string;
evmAddress?: string;
network: 'testnet' | 'mainnet';
epoch: number;
branchId: string;
}disconnect(): Promise<void>
Disconnect from wallet and clear all capabilities.
await sdk.disconnect();requestCapability(request: CapabilityRequest): Promise<Capability>
Request a new capability from the user.
const capability = await sdk.requestCapability({
circle: 'circle-id',
methods: ['get_balance', 'send_transaction'],
scope: 'write',
encrypted: false,
ttlSeconds: 3600, // Optional: default 1 hour
branchId: 'main', // Optional: specific branch
});Returns:
interface Capability {
id: string;
version: 2;
circle: string;
methods: readonly string[];
scope: 'read' | 'write' | 'compute';
encrypted: boolean;
appOrigin: string;
branchId: string;
epoch: number;
issuedAt: number;
expiresAt: number;
nonceBase: number;
walletPubKey: string;
signature: string;
state: 'ACTIVE' | 'EXPIRED' | 'REVOKED';
lastNonce: number;
}renewCapability(capabilityId: string): Promise<Capability>
Renew an existing capability (extends expiration).
const renewed = await sdk.renewCapability(capability.id);revokeCapability(capabilityId: string): Promise<void>
Revoke a capability (user can also revoke from wallet UI).
await sdk.revokeCapability(capability.id);listCapabilities(): Promise<Capability[]>
List all active capabilities for current origin.
const capabilities = await sdk.listCapabilities();invoke(request: InvocationRequest): Promise<InvocationResult>
Invoke a method using a capability.
const result = await sdk.invoke({
capabilityId: capability.id,
method: 'get_balance',
payload: new TextEncoder().encode(JSON.stringify({ /* params */ })),
branchId: 'main', // Optional: override branch
});Returns:
interface InvocationResult {
success: boolean;
data?: Uint8Array | EncryptedPayload;
error?: string;
branchProofHash?: string;
merkleRoot?: string;
epochTag?: number;
}invokeCompute(request: ComputeRequest): Promise<ComputeResult>
Execute HFHE computation.
const result = await sdk.invokeCompute({
circleId: 'circle-id',
capabilityId: capability.id,
branchId: 'main',
circuitId: 'my-circuit',
encryptedInput: {
scheme: 'HFHE',
data: encryptedData,
associatedData: 'metadata',
},
computeProfile: {
gateCount: 1000,
vectorSize: 256,
depth: 10,
expectedBootstrap: 2,
},
gasLimit: 1000000,
});estimatePlainTx(payload: unknown): Promise<GasEstimate>
Estimate gas for plain transaction.
const estimate = await sdk.estimatePlainTx({ amount: 100 });
console.log('Gas units:', estimate.gasUnits);
console.log('Token cost:', estimate.tokenCost);estimateEncryptedTx(payload: EncryptedPayload): Promise<GasEstimate>
Estimate gas for encrypted transaction.
const estimate = await sdk.estimateEncryptedTx(encryptedPayload);estimateComputeCost(profile: ComputeProfile): Promise<GasEstimate>
Estimate cost for HFHE computation.
const estimate = await sdk.estimateComputeCost({
gateCount: 1000,
vectorSize: 256,
depth: 10,
expectedBootstrap: 2,
});signMessage(message: string): Promise<string>
Request user to sign an arbitrary message.
const signature = await sdk.signMessage('Hello, Octra!');getSessionState(): SessionState
Get current session state.
const state = sdk.getSessionState();
console.log('Connected:', state.connected);
console.log('Active capabilities:', state.activeCapabilities);on<E>(event: EventName, callback: EventCallback<E>): () => void
Subscribe to events. Returns unsubscribe function.
const unsubscribe = sdk.on('connect', ({ connection }) => {
console.log('Connected to:', connection.circle);
});
// Later: unsubscribe()Events:
extensionReady: Wallet extension detectedconnect: Connected to walletdisconnect: Disconnected from walletcapabilityGranted: New capability grantedcapabilityExpired: Capability expiredcapabilityRevoked: Capability revokedbranchChanged: Branch changedepochChanged: Epoch changed
Security
Canonical Serialization
All transactions use deterministic canonical serialization:
import { canonicalize, canonicalizeCapability } from '@octra/sdk';
// Canonicalize any object
const canonical = canonicalize({ b: 2, a: 1 });
// Result: '{"a":1,"b":2}' (keys sorted)
// Canonicalize capability
const capCanonical = canonicalizeCapability(capabilityPayload);Rules:
- Keys sorted lexicographically
- No whitespace
- Deterministic number formatting
- Uint8Array → hex with '0x' prefix
Domain Separation
Prevents signature replay attacks:
import {
OCTRA_CAPABILITY_PREFIX,
OCTRA_INVOCATION_PREFIX,
hashCapabilityWithDomain
} from '@octra/sdk';
// Capability signing includes domain prefix
const hash = hashCapabilityWithDomain(payload);
// Internally: hash(OCTRA_CAPABILITY_PREFIX + canonical)Signing Mutex
Automatic protection against race conditions:
// These will execute sequentially, not in parallel
const [result1, result2] = await Promise.all([
sdk.invoke({ capabilityId, method: 'method1' }),
sdk.invoke({ capabilityId, method: 'method2' }),
]);
// result1.nonce = 1, result2.nonce = 2 (guaranteed order)HFHE Encrypted Payloads
Encrypted payloads are treated as opaque blobs:
import { hashPayload } from '@octra/sdk';
// ✅ CORRECT: Hash without inspecting
const hash = hashPayload(encryptedPayload);
// ❌ WRONG: Don't inspect ciphertext
const data = JSON.parse(encryptedPayload.data); // Never do this!Nonce Management
SDK provides nonces for ordering, wallet validates:
// SDK manages nonce locally
const nonce = nonceManager.getNextNonce(capabilityId);
// On error, rollback
catch (error) {
nonceManager.resetNonce(capabilityId, nonce - 1);
}
// Wallet is final authority and validates nonceAdvanced Usage
Intent-Based Swaps
import { IntentsClient } from '@octra/sdk';
// Create intents client
const intents = new IntentsClient(sdk, 'https://api.octra.network');
intents.setCapability(capability);
// Get quote
const quote = await intents.getQuote(100); // 100 OCT
// Create intent
const intent = await intents.createIntent(
quote,
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // Target ETH address
50 // 0.5% slippage
);
// Sign and submit
const signResult = await intents.signIntent(intent);
const submitResult = await intents.submitIntent(signResult.txHash);
// Poll for fulfillment
const status = await intents.waitForFulfillment(submitResult.intentId, {
timeoutMs: 5 * 60 * 1000, // 5 minutes
pollIntervalMs: 3000, // Check every 3s
});
console.log('Swap completed:', status.ethTxHash);Response Decoding
import { decodeResponseData, decodeBalanceResponse } from '@octra/sdk';
// Generic decoding
const result = await sdk.invoke({ capabilityId, method: 'custom_method' });
const data = decodeResponseData<MyType>(result);
// Balance-specific decoding
const balanceResult = await sdk.invoke({ capabilityId, method: 'get_balance' });
const balance = decodeBalanceResponse(balanceResult);Cryptographic Utilities
import {
sha256,
hexToBytes,
bytesToHex,
verifyEd25519Signature,
verifyCapabilitySignature,
} from '@octra/sdk';
// SHA-256 hashing
const hash = await sha256(data);
// Hex conversion
const bytes = hexToBytes('0x1234abcd');
const hex = bytesToHex(bytes);
// Signature verification
const isValid = await verifyEd25519Signature(
signature,
message,
publicKey
);
// Capability verification
const isValidCap = await verifyCapabilitySignature(capability);Error Handling
All errors extend OctraError with structured information:
import {
OctraError,
NotInstalledError,
NotConnectedError,
UserRejectedError,
ValidationError,
CapabilityError,
} from '@octra/sdk';
try {
await sdk.invoke(request);
} catch (error) {
if (error instanceof UserRejectedError) {
// User cancelled - don't show error
console.log('User cancelled');
} else if (error instanceof NotConnectedError) {
// Need to connect first
await sdk.connect({ circle: 'my-circle' });
} else if (error instanceof CapabilityError) {
// Capability issue - might need to renew
console.error('Capability error:', error.message);
} else if (error instanceof OctraError) {
// Generic Octra error
console.error('Error code:', error.code);
console.error('Details:', error.details);
}
}Error Codes:
NOT_INSTALLED: Wallet extension not installedNOT_CONNECTED: Not connected to walletUSER_REJECTED: User rejected the requestTIMEOUT: Operation timed outVALIDATION_ERROR: Input validation failedCAPABILITY_ERROR: Capability issueSCOPE_VIOLATION: Method not allowed by capabilitySIGNATURE_INVALID: Invalid signatureCAPABILITY_EXPIRED: Capability expiredCAPABILITY_REVOKED: Capability revokedORIGIN_MISMATCH: Origin mismatchBRANCH_MISMATCH: Branch mismatchEPOCH_MISMATCH: Epoch mismatchNONCE_VIOLATION: Nonce violationDOMAIN_SEPARATION_ERROR: Domain separation error
Testing
Unit Tests
npm testIntegration Tests
npm run test:integrationTest with Mock Wallet
import { OctraSDK } from '@octra/sdk';
const sdk = await OctraSDK.init({
skipSignatureVerification: true, // For testing only!
});Migration Guide
From v1 to v2
Breaking Changes:
- Error Structure: Errors now include
code,layer,retryable - Domain Separation: Signatures include domain prefixes
- Canonical Serialization: All hashing uses canonical format
Migration Steps:
// v1
try {
await sdk.invoke(request);
} catch (error) {
alert(error.message);
}
// v2
try {
await sdk.invoke(request);
} catch (error) {
if (error.code === 'USER_REJECTED') {
return; // Don't show error
}
if (error.retryable) {
// Show retry button
} else {
alert(error.message);
}
}Compatibility:
- ✅ v1 capabilities still work
- ✅ v1 API unchanged
- ✅ New features are additive
File Structure
packages/sdk/
├── src/
│ ├── canonical.ts # Canonical serialization
│ ├── capability-manager.ts # Capability management
│ ├── capability-service.ts # Capability validation
│ ├── compute-service.ts # HFHE compute operations
│ ├── crypto.ts # Cryptographic utilities
│ ├── errors.ts # Error classes
│ ├── gas-service.ts # Gas estimation
│ ├── index.ts # Public API exports
│ ├── intents.ts # Intent-based swaps
│ ├── nonce-manager.ts # Nonce management
│ ├── response-utils.ts # Response decoding
│ ├── sdk.ts # Main SDK class
│ ├── session-manager.ts # Session management
│ ├── types.ts # TypeScript types
│ └── utils.ts # Utility functions
├── tests/
│ ├── crypto.test.ts # Crypto tests
│ └── sdk.test.ts # SDK tests
├── dist/ # Compiled output
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── README.md # This fileContributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure all tests pass
- Submit a pull request
License
MIT License - see LICENSE file for details
Support
- Documentation: https://docs.octra.network
- GitHub: https://github.com/octra/octwa
- Discord: https://discord.gg/octra
- Email: [email protected]
Security
For security issues, please email: [email protected]
Do NOT open public issues for security vulnerabilities.
Built with ❤️ by the Octra Team
