The Bituncoin Universal Wallet supports a plug-and-play add-on module system that allows developers to extend the wallet's functionality without modifying the core codebase. This guide covers the architecture, development process, and best practices for creating custom modules.
- Sandboxing: Modules run in isolated environments with limited permissions
- Event-Driven: Modules communicate through a publish-subscribe event system
- Permission-Based: Granular permission system controls module capabilities
- Version Management: Semantic versioning with automatic update support
- UI Modules: Add new screens, dialogs, or UI components
- Service Modules: Background services for monitoring, alerts, or automation
- Integration Modules: Third-party service integrations (exchanges, DeFi protocols)
- Analysis Modules: Transaction analysis, portfolio optimization, risk assessment
- Advanced Staking: Automated staking strategies across multiple protocols
- DeFi Lending: Cross-platform lending and borrowing optimization
- Portfolio Tracker: Advanced portfolio analytics and reporting
- Security Scanner: Real-time security monitoring and threat detection
- Go 1.21 or later
- Node.js 18.x or later
- Git
- Docker (for testing)
module-name/
├── manifest.json # Module metadata and configuration
├── main.go # Main module entry point
├── handlers/ # HTTP handlers and API endpoints
│ └── api.go
├── services/ # Background services
│ └── service.go
├── ui/ # Frontend components (React)
│ ├── components/
│ └── index.js
├── tests/ # Unit and integration tests
│ ├── main_test.go
│ └── integration_test.go
├── docs/ # Module documentation
│ └── README.md
└── Dockerfile # Containerization for sandboxing
# Install module development CLI
go install github.com/bituncoin/module-cli@latest
# Initialize new module
module-cli init my-module
# Test module
module-cli test
# Build module package
module-cli build{
"name": "my-custom-module",
"version": "1.0.0",
"description": "A custom module for enhanced wallet functionality",
"author": "Developer Name",
"license": "MIT",
"homepage": "https://github.com/username/my-module",
"module": {
"type": "service",
"entryPoint": "main.go",
"permissions": [
"wallet:read",
"transactions:read",
"network:access"
],
"dependencies": {
"go": ">=1.21.0",
"node": ">=18.0.0"
}
},
"ui": {
"components": ["DashboardWidget", "SettingsPanel"],
"routes": [
{
"path": "/module/my-module",
"component": "MainView"
}
]
},
"api": {
"endpoints": [
{
"path": "/api/module/my-module/data",
"method": "GET",
"handler": "GetData"
}
]
},
"events": {
"subscribes": ["wallet:updated", "transaction:new"],
"publishes": ["module:my-module:alert"]
}
}- wallet:read - Read wallet balances and addresses
- wallet:write - Modify wallet settings (no funds access)
- transactions:read - Read transaction history
- transactions:write - Create transactions (requires user approval)
- network:access - Access external APIs and networks
- storage:read - Read module-specific data
- storage:write - Write module-specific data
- ui:inject - Inject UI components
- notifications:send - Send user notifications
{
"permissions": [
{
"name": "wallet:read",
"reason": "Required to monitor balance changes for alerts"
},
{
"name": "notifications:send",
"reason": "Send alerts when conditions are met"
}
]
}type Module interface {
// Initialize the module
Init(ctx context.Context, config ModuleConfig) error
// Start the module
Start() error
// Stop the module
Stop() error
// Get module information
Info() ModuleInfo
// Handle events
HandleEvent(event Event) error
// Handle API requests
HandleAPIRequest(req APIRequest) (APIResponse, error)
}type ModuleConfig struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Settings map[string]interface{} `json:"settings"`
Permissions []string `json:"permissions"`
DataDir string `json:"dataDir"`
}type Event struct {
Type string `json:"type"`
Source string `json:"source"`
Timestamp time.Time `json:"timestamp"`
Data map[string]interface{} `json:"data"`
}
// Example event handler
func (m *MyModule) HandleEvent(event Event) error {
switch event.Type {
case "wallet:updated":
return m.handleWalletUpdate(event.Data)
case "transaction:new":
return m.handleNewTransaction(event.Data)
default:
return nil
}
}type APIRequest struct {
Method string `json:"method"`
Path string `json:"path"`
Headers map[string]string `json:"headers"`
Body map[string]interface{} `json:"body"`
UserID string `json:"userId"`
}
type APIResponse struct {
StatusCode int `json:"statusCode"`
Headers map[string]string `json:"headers"`
Body map[string]interface{} `json:"body"`
}
// Example API handler
func (m *MyModule) HandleAPIRequest(req APIRequest) (APIResponse, error) {
switch req.Path {
case "/api/module/my-module/data":
return m.handleGetData(req)
case "/api/module/my-module/settings":
return m.handleUpdateSettings(req)
default:
return APIResponse{StatusCode: 404}, nil
}
}import React, { useState, useEffect } from 'react';
import { useWallet } from '@bituncoin/wallet-hooks';
const MyModuleWidget = () => {
const { balance, transactions } = useWallet();
const [moduleData, setModuleData] = useState(null);
useEffect(() => {
// Fetch module-specific data
fetchModuleData();
}, []);
const fetchModuleData = async () => {
try {
const response = await fetch('/api/module/my-module/data');
const data = await response.json();
setModuleData(data);
} catch (error) {
console.error('Failed to fetch module data:', error);
}
};
return (
<div className="module-widget">
<h3>My Custom Module</h3>
<div className="balance-info">
Current Balance: {balance} BTC
</div>
<div className="module-data">
{moduleData && (
<pre>{JSON.stringify(moduleData, null, 2)}</pre>
)}
</div>
</div>
);
};
export default MyModuleWidget;// ui/index.js
import MyModuleWidget from './components/MyModuleWidget';
import SettingsPanel from './components/SettingsPanel';
export const components = {
MyModuleWidget,
SettingsPanel
};
export const routes = [
{
path: '/module/my-module',
component: 'MyModuleWidget'
},
{
path: '/module/my-module/settings',
component: 'SettingsPanel'
}
];package mymodule
import (
"testing"
"github.com/bituncoin/module-sdk/testing"
)
func TestModuleInit(t *testing.T) {
config := ModuleConfig{
ID: "test-module",
Name: "Test Module",
Version: "1.0.0",
}
module := &MyModule{}
err := module.Init(context.Background(), config)
if err != nil {
t.Fatalf("Failed to initialize module: %v", err)
}
info := module.Info()
if info.Name != "Test Module" {
t.Errorf("Expected module name 'Test Module', got '%s'", info.Name)
}
}
func TestHandleEvent(t *testing.T) {
module := &MyModule{}
// Initialize module...
event := Event{
Type: "wallet:updated",
Data: map[string]interface{}{
"balance": 1.5,
"currency": "BTC",
},
}
err := module.HandleEvent(event)
if err != nil {
t.Fatalf("Failed to handle event: %v", err)
}
// Assert expected behavior
}func TestModuleIntegration(t *testing.T) {
// Start test wallet instance
wallet := testing.NewTestWallet()
// Load and start module
module, err := testing.LoadModule("my-module", wallet)
if err != nil {
t.Fatalf("Failed to load module: %v", err)
}
// Test module functionality
balance, err := module.GetBalance()
if err != nil {
t.Fatalf("Failed to get balance: %v", err)
}
if balance != 0.0 {
t.Errorf("Expected initial balance 0.0, got %f", balance)
}
}func validateInput(input string) error {
if len(input) > 1000 {
return errors.New("input too long")
}
// Use allowlist validation
matched, err := regexp.MatchString(`^[a-zA-Z0-9\s\-_.]+$`, input)
if err != nil {
return err
}
if !matched {
return errors.New("invalid characters in input")
}
return nil
}func makeSecureAPICall(url string, data interface{}) error {
// Validate URL
parsedURL, err := url.Parse(url)
if err != nil {
return err
}
// Only allow HTTPS
if parsedURL.Scheme != "https" {
return errors.New("only HTTPS URLs allowed")
}
// Set reasonable timeout
client := &http.Client{
Timeout: 30 * time.Second,
}
// Make request with proper headers
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Bituncoin-Module/1.0")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}func encryptData(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
return ciphertext, nil
}# Build module
module-cli build
# This creates module-name.zip with:
# - Compiled binaries
# - UI assets
# - Manifest file
# - Documentation# Install from registry
wallet-cli module install my-module
# Install from file
wallet-cli module install ./my-module.zip
# Verify installation
wallet-cli module list# Check for updates
wallet-cli module check-updates
# Update specific module
wallet-cli module update my-module
# Update all modules
wallet-cli module update-allPublish your module to the official Bituncoin Module Registry:
# Login to registry
module-cli registry login
# Publish module
module-cli registry publish
# View module stats
module-cli registry stats my-moduleFor private or enterprise modules:
# Create private registry
module-cli registry create-private
# Publish to private registry
module-cli registry publish --registry my-private-registry- Documentation: https://docs.bituncoin.com/modules
- API Reference: https://api.bituncoin.com/modules
- Community Forum: https://forum.bituncoin.com/c/modules
- GitHub Issues: https://github.com/bituncoin/modules/issues
- Check the documentation
- Search existing issues
- Ask in the community forum
- Create a GitHub issue for bugs
- Request features via GitHub discussions
We welcome contributions to the module ecosystem:
- Fork the module SDK
- Create your feature branch
- Add tests for new functionality
- Submit a pull request
- Participate in code review
- Module SDK Version: 1.0.0
- Compatible Wallet Versions: 1.0.0+
- Go Version Requirements: 1.21+
- Node.js Version Requirements: 18+
For the latest updates and breaking changes, see the changelog. 5. Best Practices 6. Examples
Add-on modules extend the Bituncoin wallet functionality through a plug-and-play architecture:
┌─────────────────────────────────┐
│ Module Registry │
│ (Central module manager) │
└────────────┬────────────────────┘
│
├── Module 1 (Staking)
├── Module 2 (Lending)
├── Module 3 (Trading)
└── Module N (Custom)
Modules can be in one of three states:
- Disabled: Registered but not running
- Enabled: Active and processing requests
- Error: Failed to start or encountered an error
Every module must implement the Module interface:
type Module interface {
GetName() string
GetVersion() string
GetCategory() ModuleCategory
GetDescription() string
Initialize(config map[string]interface{}) error
Start() error
Stop() error
GetStatus() ModuleStatus
Execute(action string, params map[string]interface{}) (interface{}, error)
}package addons
import (
"errors"
"sync"
)
// MyCustomModule implements a custom feature
type MyCustomModule struct {
name string
version string
status ModuleStatus
config map[string]interface{}
data map[string]interface{}
mutex sync.RWMutex
}
// NewMyCustomModule creates a new instance
func NewMyCustomModule() *MyCustomModule {
return &MyCustomModule{
name: "My Custom Module",
version: "1.0.0",
status: StatusDisabled,
data: make(map[string]interface{}),
}
}// GetName returns the module name
func (m *MyCustomModule) GetName() string {
return m.name
}
// GetVersion returns the module version
func (m *MyCustomModule) GetVersion() string {
return m.version
}
// GetCategory returns the module category
func (m *MyCustomModule) GetCategory() ModuleCategory {
return CategoryUtility // or CategoryDeFi, CategoryStaking, etc.
}
// GetDescription returns module description
func (m *MyCustomModule) GetDescription() string {
return "A custom module that does something awesome"
}
// Initialize initializes the module with configuration
func (m *MyCustomModule) Initialize(config map[string]interface{}) error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.config = config
// Perform initialization tasks
// Validate config, set up resources, etc.
return nil
}
// Start starts the module
func (m *MyCustomModule) Start() error {
m.mutex.Lock()
defer m.mutex.Unlock()
// Start background tasks, connect to services, etc.
m.status = StatusEnabled
return nil
}
// Stop stops the module
func (m *MyCustomModule) Stop() error {
m.mutex.Lock()
defer m.mutex.Unlock()
// Clean up resources, close connections, etc.
m.status = StatusDisabled
return nil
}
// GetStatus returns the current status
func (m *MyCustomModule) GetStatus() ModuleStatus {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.status
}
// Execute executes a module-specific action
func (m *MyCustomModule) Execute(action string, params map[string]interface{}) (interface{}, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
switch action {
case "my_action":
return m.myAction(params)
case "another_action":
return m.anotherAction(params)
default:
return nil, errors.New("unknown action")
}
}
// Private action methods
func (m *MyCustomModule) myAction(params map[string]interface{}) (interface{}, error) {
// Implement your action logic
return map[string]string{"result": "success"}, nil
}
func (m *MyCustomModule) anotherAction(params map[string]interface{}) (interface{}, error) {
// Implement another action
return nil, nil
}Choose the appropriate category for your module:
const (
CategoryDeFi ModuleCategory = "defi" // DeFi features
CategoryStaking ModuleCategory = "staking" // Staking features
CategoryLending ModuleCategory = "lending" // Lending/borrowing
CategoryTrading ModuleCategory = "trading" // Trading features
CategoryPayment ModuleCategory = "payment" // Payment processing
CategoryAnalytics ModuleCategory = "analytics" // Data analysis
CategorySecurity ModuleCategory = "security" // Security features
CategoryUtility ModuleCategory = "utility" // Utility features
)The Execute method is the primary interface for interacting with your module. Design clear, RESTful-style actions:
// Good action naming
"list_items" // GET equivalent
"get_item" // GET by ID
"create_item" // POST equivalent
"update_item" // PUT equivalent
"delete_item" // DELETE equivalent
"calculate_fee" // Custom actionpackage main
import (
"github.com/Bituncoin/Bituncoin/addons"
)
func main() {
// Create module registry
registry := addons.NewModuleRegistry()
// Create and register your module
myModule := NewMyCustomModule()
err := registry.Register(myModule, "Your Name")
if err != nil {
panic(err)
}
// Enable the module with configuration
config := map[string]interface{}{
"setting1": "value1",
"setting2": 42,
}
err = registry.Enable("My Custom Module", config)
if err != nil {
panic(err)
}
}# Enable module
curl -X POST http://localhost:8080/api/addons/enable \
-H "Content-Type: application/json" \
-d '{
"name": "My Custom Module",
"config": {
"setting1": "value1",
"setting2": 42
}
}'
# Execute module action
curl -X POST http://localhost:8080/api/addons/execute \
-H "Content-Type: application/json" \
-d '{
"name": "My Custom Module",
"action": "my_action",
"params": {
"param1": "value1"
}
}'Always use mutexes when accessing shared data:
type MyModule struct {
data map[string]interface{}
mutex sync.RWMutex
}
func (m *MyModule) SafeRead() interface{} {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.data["key"]
}
func (m *MyModule) SafeWrite(key string, value interface{}) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.data[key] = value
}Return descriptive errors:
func (m *MyModule) Execute(action string, params map[string]interface{}) (interface{}, error) {
if m.status != StatusEnabled {
return nil, errors.New("module not enabled")
}
requiredParam, ok := params["required"]
if !ok {
return nil, errors.New("required parameter 'required' missing")
}
// Process...
}Validate configuration in Initialize:
func (m *MyModule) Initialize(config map[string]interface{}) error {
// Check required config
if _, ok := config["api_key"]; !ok {
return errors.New("api_key required in configuration")
}
// Validate types
timeout, ok := config["timeout"].(float64)
if !ok {
return errors.New("timeout must be a number")
}
if timeout < 0 {
return errors.New("timeout must be positive")
}
m.config = config
return nil
}Clean up resources in Stop:
func (m *MyModule) Stop() error {
m.mutex.Lock()
defer m.mutex.Unlock()
// Close connections
if m.connection != nil {
m.connection.Close()
}
// Cancel background tasks
if m.cancelFunc != nil {
m.cancelFunc()
}
// Clear data
m.data = make(map[string]interface{})
m.status = StatusDisabled
return nil
}Include meaningful log messages:
import "log"
func (m *MyModule) Start() error {
log.Printf("[%s] Starting module version %s", m.name, m.version)
// Start logic...
log.Printf("[%s] Module started successfully", m.name)
m.status = StatusEnabled
return nil
}package addons
type AnalyticsModule struct {
name string
version string
status ModuleStatus
metrics map[string]float64
mutex sync.RWMutex
}
func NewAnalyticsModule() *AnalyticsModule {
return &AnalyticsModule{
name: "Analytics Engine",
version: "1.0.0",
status: StatusDisabled,
metrics: make(map[string]float64),
}
}
func (a *AnalyticsModule) Execute(action string, params map[string]interface{}) (interface{}, error) {
switch action {
case "track_metric":
name := params["name"].(string)
value := params["value"].(float64)
a.mutex.Lock()
a.metrics[name] = value
a.mutex.Unlock()
return map[string]string{"status": "tracked"}, nil
case "get_metrics":
a.mutex.RLock()
defer a.mutex.RUnlock()
return a.metrics, nil
default:
return nil, errors.New("unknown action")
}
}package addons
type TradingBotModule struct {
name string
version string
status ModuleStatus
strategies map[string]TradingStrategy
mutex sync.RWMutex
}
type TradingStrategy struct {
Name string
BuySignal float64
SellSignal float64
Active bool
}
func NewTradingBotModule() *TradingBotModule {
return &TradingBotModule{
name: "Trading Bot",
version: "1.0.0",
status: StatusDisabled,
strategies: make(map[string]TradingStrategy),
}
}
func (t *TradingBotModule) Execute(action string, params map[string]interface{}) (interface{}, error) {
switch action {
case "add_strategy":
strategy := TradingStrategy{
Name: params["name"].(string),
BuySignal: params["buy_signal"].(float64),
SellSignal: params["sell_signal"].(float64),
Active: true,
}
t.mutex.Lock()
t.strategies[strategy.Name] = strategy
t.mutex.Unlock()
return strategy, nil
case "list_strategies":
t.mutex.RLock()
defer t.mutex.RUnlock()
strategies := make([]TradingStrategy, 0, len(t.strategies))
for _, s := range t.strategies {
strategies = append(strategies, s)
}
return strategies, nil
default:
return nil, errors.New("unknown action")
}
}Create comprehensive tests:
package addons
import "testing"
func TestMyModule(t *testing.T) {
module := NewMyCustomModule()
// Test initialization
config := map[string]interface{}{"key": "value"}
err := module.Initialize(config)
if err != nil {
t.Fatalf("Initialize failed: %v", err)
}
// Test start
err = module.Start()
if err != nil {
t.Fatalf("Start failed: %v", err)
}
if module.GetStatus() != StatusEnabled {
t.Error("Module should be enabled")
}
// Test actions
result, err := module.Execute("my_action", map[string]interface{}{})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
// Test stop
err = module.Stop()
if err != nil {
t.Fatalf("Stop failed: %v", err)
}
if module.GetStatus() != StatusDisabled {
t.Error("Module should be disabled")
}
}- Create a separate repository for your module
- Include comprehensive documentation
- Provide examples and tests
- Submit to the Bituncoin module registry
- Maintain and update regularly
For module development support:
- GitHub Discussions
- Developer Discord channel
- Example modules in
/addonsdirectory
Ensure your module is compatible with the Bituncoin license (GPL-3.0).