npm package: @fusion-wallet/wallet-direct
Description: Wallet direct WASM package for Flutter Fusion Wallet
Author: Fusion Wallet Team
Latest version: 1.1.4

# @fusion-wallet/wallet-direct

A WebAssembly package for wallet direct functionality, built with Rust and wasm-pack. This package provides P2P wallet connectivity using libp2p with automatic WASM initialization and enhanced session management.

## Installation

```bash
npm install @fusion-wallet/wallet-direct
```

## Usage

### Simple Usage (Recommended)

```javascript
import WalletDirectWrapper from '@fusion-wallet/wallet-direct';

async function useWallet() {
  // No need to call init() - it's handled automatically!
  const wallet = new WalletDirectWrapper();
  
  // Set up session start callback
  wallet.onStartSession(() => {
    console.log('Session started!');
    // Handle session initialization
  });
  
  // Set up listeners for registration and verification
  await wallet.setRegisterListener(Promise.resolve((data) => {
    console.log('Register event:', data);
    return {
      signature: new TextEncoder().encode("signature"),
      challenge: new TextEncoder().encode("challenge"),
      domain: window.location.hostname,
      public_key: new TextEncoder().encode("public_key"),
    };
  }));
  
  await wallet.setVerifyListener(Promise.resolve((data) => {
    console.log('Verify event:', data);
    return {
      signature: new TextEncoder().encode("signature"),
      challenge: new TextEncoder().encode("challenge"),
      domain: window.location.hostname,
    };
  }));
  
  // Connect to a relay
  await wallet.connect("your-relay-address");
  
  // Get P2P address
  const p2pAddr = await wallet.get_p2p_addr();
  console.log('P2P Address:', p2pAddr);
  
  // Send messages to connected peers
  const response = await wallet.send_message(1, new TextEncoder().encode("Hello"));
  console.log('Message response:', response);
  
  // Disconnect when done
  await wallet.disconnect();
  
  // Clean up resources
  await wallet.free();
}

### Listener Lifecycle and Best Practices

Listeners in the wallet-direct package have a specific lifecycle that you should understand:

#### Setting Listeners
- Listeners are set using `setRegisterListener()` and `setVerifyListener()`
- Each listener should return a Promise that resolves to the appropriate credential object
- Listeners are called automatically when the wallet receives specific protocol events

#### Listener Cleanup
- **Automatic Cleanup**: Listeners are automatically cleaned up when you call `free()` or `disconnect()`
- **Manual Cleanup**: You can set listeners to `null` to remove them before calling cleanup methods
- **Memory Management**: Always call `free()` to prevent memory leaks

#### Best Practices
```javascript
// Good: Proper listener setup and cleanup
const wallet = new WalletDirectWrapper();

try {
  // Set up listeners
  await wallet.setRegisterListener(Promise.resolve((data) => {
    console.log('Registration requested:', data);
    return {
      signature: new TextEncoder().encode("signature"),
      challenge: new TextEncoder().encode("challenge"),
      domain: window.location.hostname,
      public_key: new TextEncoder().encode("public_key"),
    };
  }));

  await wallet.setVerifyListener(Promise.resolve((data) => {
    console.log('Verification requested:', data);
    return {
      signature: new TextEncoder().encode("signature"),
      challenge: new TextEncoder().encode("challenge"),
      domain: window.location.hostname,
    };
  }));

  // Use the wallet...
  await wallet.connect("your-relay-address");
  
} finally {
  // Always clean up, even if errors occur
  await wallet.disconnect();
  await wallet.free();
}
```

#### Common Pitfalls to Avoid
- **Forgetting to call `free()`**: This can lead to memory leaks
- **Not handling listener errors**: Always wrap listener logic in try-catch blocks
- **Setting listeners after connection**: Set listeners before calling `connect()`
- **Multiple listener instances**: Don't create multiple wallet instances without cleaning up the previous ones
```

### Alternative Usage with Convenience Functions

```javascript
import { createWalletDirect, closeWalletDirect } from '@fusion-wallet/wallet-direct';

async function useWallet() {
  // Create instance using convenience function
  const wallet = await createWalletDirect();
  
  // Set up listeners
  await wallet.setRegisterListener(Promise.resolve((data) => {
    console.log('Register event:', data);
    return {
      signature: new TextEncoder().encode("signature"),
      challenge: new TextEncoder().encode("challenge"),
      domain: window.location.hostname,
      public_key: new TextEncoder().encode("public_key"),
    };
  }));
  
  await wallet.connect("your-relay-address");
  const p2pAddr = wallet.get_p2p_addr();
  
  // Use convenience function to close and cleanup
  await closeWalletDirect(wallet);
}
```

### Advanced Usage (Direct WASM Access)

```javascript
import { WalletDirect } from '@fusion-wallet/wallet-direct';

async function useWallet() {
  // For advanced users who want direct access to the WASM module
  const wallet = new WalletDirect();
  
  // Set up listeners
  await wallet.setRegisterListener(Promise.resolve((data) => {
    console.log('Register event:', data);
    return {
      signature: new TextEncoder().encode("signature"),
      challenge: new TextEncoder().encode("challenge"),
      domain: window.location.hostname,
      public_key: new TextEncoder().encode("public_key"),
    };
  }));
  
  await wallet.connect("your-relay-address");
  
  // Send custom messages
  const response = await wallet.send_message(1, new TextEncoder().encode("Custom message"));
  
  // Disconnect and cleanup
  await wallet.disconnect();
  wallet.free();
}
```

## API Reference

### `WalletDirectWrapper` (Default Export)

A wrapper class that automatically handles WASM initialization and provides a simplified API.

#### Constructor
```javascript
const wallet = new WalletDirectWrapper();
```

#### Methods
- `setRegisterListener(callback)`: Set registration callback that returns credentials
- `setVerifyListener(callback)`: Set verification callback that returns credentials
- `onStartSession(callback)`: Set callback for session start events
- `connect(relayAddr)`: Connect to relay server
- `get_p2p_addr()`: Get P2P address for this node
- `send_message(messageId, data)`: Send message to connected peers
- `disconnect()`: Disconnect from relay and close connections
- `free()`: Clean up resources

### Listener Management

Listeners are automatically cleaned up when you call `free()` or `disconnect()`. However, for proper resource management, you should:

1. **Always call `free()`** when you're done with a wallet instance
2. **Call `disconnect()`** before `free()` to properly close connections
3. **Set listeners to `null`** if you want to remove them before cleanup

```javascript
// Proper cleanup example
const wallet = new WalletDirectWrapper();

// Set up listeners
await wallet.setRegisterListener(Promise.resolve((data) => {
    // Your registration logic
}));

// Use the wallet...

// Clean up when done
await wallet.disconnect();  // Close connections
await wallet.free();        // Clean up resources and listeners
```

### Session Management

The wallet supports session management through the `onStartSession` callback:

```javascript
const wallet = new WalletDirectWrapper();

// Set up session start callback
wallet.onStartSession(() => {
    console.log('Session started!');
    // Handle session initialization
    // You can perform any setup needed when a session begins
});

// Set up other listeners
await wallet.setRegisterListener(Promise.resolve((data) => {
    return {
        signature: new TextEncoder().encode("signature"),
        challenge: new TextEncoder().encode("challenge"),
        domain: window.location.hostname,
        public_key: new TextEncoder().encode("public_key"),
    };
}));

await wallet.connect("your-relay-address");
```

The `onStartSession` callback is triggered when a new session is established with a peer. This is useful for:
- Initializing session-specific state
- Setting up UI elements for an active session
- Logging session events
- Performing any setup required when a session begins

### `createWalletDirect()`

Convenience function that returns a promise resolving to a `WalletDirect` instance.

```javascript
const wallet = await createWalletDirect();
```

### `closeWalletDirect(wallet)`

Convenience function that properly disconnects and cleans up a wallet instance.

```javascript
await closeWalletDirect(wallet);
```

### Wallet Action Functions

The package provides specific functions for creating wallet action data vectors that can be sent via the `send_message` method:

#### `create_notification_delegate(canister_id, delegate_principal, domain, app_name)`

Creates a notification delegate request data vector.

```javascript
import { create_notification_delegate } from '@fusion-wallet/wallet-direct';

const data = create_notification_delegate(
    'rrkah-fqaaa-aaaaa-aaaaq-cai',  // canister_id
    'test-principal-123',           // delegate_principal
    'example.com',                  // domain
    'Test App'                      // app_name
);

// Send the notification delegate request
const response = await wallet.send_message(0, data);
```

#### `create_delegate_payment(token_id, amount, delegate_principal)`

Creates a delegate payment request data vector.

```javascript
import { create_delegate_payment } from '@fusion-wallet/wallet-direct';

const data = create_delegate_payment(
    'ryjl3-tyaaa-aaaaa-aaaba-cai',  // token_id
    0.001,                          // amount
    'test-principal-456'            // delegate_principal
);

// Send the delegate payment request
const response = await wallet.send_message(2, data);
```

#### `create_send_message(message_type, message_data, origin)`

Creates a send message request data vector.

```javascript
import { create_send_message } from '@fusion-wallet/wallet-direct';

const data = create_send_message(
    'custom_message',               // message_type
    'Hello from wallet-direct!',    // message_data
    'wallet-direct-example'         // origin
);

// Send the message request
const response = await wallet.send_message(3, data);
```

#### Complete Example with Wallet Actions

```javascript
import WalletDirectWrapper, { 
    create_notification_delegate, 
    create_delegate_payment, 
    create_send_message 
} from '@fusion-wallet/wallet-direct';

async function useWalletActions() {
    const wallet = new WalletDirectWrapper();
    
    try {
        // Connect to relay
        await wallet.connect("your-relay-address");
        
        // Send notification delegate request
        const notificationData = create_notification_delegate(
            'rrkah-fqaaa-aaaaa-aaaaq-cai',
            'test-principal-123',
            'example.com',
            'Test App'
        );
        const notificationResponse = await wallet.send_message(0, notificationData);
        console.log('Notification response:', notificationResponse);
        
        // Send delegate payment request
        const paymentData = create_delegate_payment(
            'ryjl3-tyaaa-aaaaa-aaaba-cai',
            0.001,
            'test-principal-456'
        );
        const paymentResponse = await wallet.send_message(2, paymentData);
        console.log('Payment response:', paymentResponse);
        
        // Send custom message request
        const messageData = create_send_message(
            'custom_message',
            'Hello from wallet-direct!',
            'wallet-direct-example'
        );
        const messageResponse = await wallet.send_message(3, messageData);
        console.log('Message response:', messageResponse);
        
    } finally {
        await wallet.disconnect();
        await wallet.free();
    }
}
```

### `WalletDirect` (Advanced)

The original WASM class for advanced users who want direct access.

#### Methods
- `setRegisterListener(callback)`: Set registration callback
- `setVerifyListener(callback)`: Set verification callback
- `connect(relayAddr)`: Connect to relay server
- `get_p2p_addr()`: Get P2P address
- `send_message(messageId, data)`: Send message to peers
- `disconnect()`: Disconnect from relay
- `free()`: Clean up resources

## TypeScript Support

The package includes comprehensive TypeScript definitions:

```typescript
import { WalletDirectWrapper, VerifyCredential, RegisterCredential } from '@fusion-wallet/wallet-direct';

interface VerifyCredential {
  signature: Uint8Array;
  challenge: Uint8Array;
  domain: string;
}

interface RegisterCredential {
  signature: Uint8Array;
  challenge: Uint8Array;
  domain: string;
  public_key: Uint8Array;
}

// Listener function types
type RegisterListener = (data: any) => Promise<RegisterCredential>;
type VerifyListener = (data: any) => Promise<VerifyCredential>;
type StartSessionListener = () => Promise<any> | any;

// Wallet instance with proper typing
const wallet = new WalletDirectWrapper();

// Type-safe session start callback
wallet.onStartSession(() => {
  console.log('Session started!');
  // Handle session initialization
});

// Type-safe listener setup
await wallet.setRegisterListener(Promise.resolve((data) => {
  return {
    signature: new TextEncoder().encode("signature"),
    challenge: new TextEncoder().encode("challenge"),
    domain: window.location.hostname,
    public_key: new TextEncoder().encode("public_key"),
  };
}));

await wallet.setVerifyListener(Promise.resolve((data) => {
  return {
    signature: new TextEncoder().encode("signature"),
    challenge: new TextEncoder().encode("challenge"),
    domain: window.location.hostname,
  };
}));
```

## Features

- **Automatic Initialization**: No need to call `init()` manually
- **Promise-based API**: All methods return promises for better async handling
- **Enhanced Session Management**: Automatic state tracking and connection management
- **Message Passing**: Send custom messages to connected peers
- **TypeScript Support**: Full type definitions included
- **Multiple Usage Patterns**: Choose the approach that fits your needs
- **Connection Lifecycle**: Proper connect/disconnect/free lifecycle management
- **Listener Management**: Automatic cleanup and proper resource management

## Troubleshooting

### Listener Issues

**Problem**: Listeners not being called
- **Solution**: Ensure listeners are set before calling `connect()`
- **Solution**: Check that listener functions return the correct credential format

**Problem**: Memory leaks or listeners persisting
- **Solution**: Always call `free()` when done with a wallet instance
- **Solution**: Use try-finally blocks to ensure cleanup happens even on errors

**Problem**: Multiple listeners interfering with each other
- **Solution**: Create separate wallet instances for different use cases
- **Solution**: Clean up previous instances before creating new ones

**Problem**: Listener errors not being handled
- **Solution**: Wrap listener logic in try-catch blocks
- **Solution**: Return proper error responses from listeners

**Problem**: Session start callback not being triggered
- **Solution**: Ensure `onStartSession` is called before `connect()`
- **Solution**: Check that the callback function is properly defined
- **Solution**: Verify that a session is actually being established with a peer

## Development

### Prerequisites

- Rust (latest stable)
- wasm-pack (`cargo install wasm-pack`)
- Node.js (>=16.0.0)

### Building

```bash
# Build for web target (default)
npm run build

# Build for Node.js
npm run build:node

# Build for bundler
npm run build:bundler

# Clean build artifacts
npm run clean
```

### Development Server

```bash
# Start development server with Vite
npm run dev

# Build for production
npm run build:web

# Preview production build
npm run preview
```

### Publishing

```bash
npm publish
```

The `prepublishOnly` script will automatically build the WASM files before publishing.

## Package Structure

```
wallet-direct/
├── pkg/                    # Generated WASM files
│   ├── wallet_direct.js    # Main JavaScript module
│   ├── wallet_direct.d.ts  # TypeScript definitions
│   ├── wallet_direct_bg.wasm # WebAssembly binary
│   └── package.json        # Inner package.json
├── src/                    # Rust source code
│   ├── lib.rs             # Main library entry point
│   ├── runtime_wasm.rs    # WASM runtime implementation
│   └── node.rs            # Node behavior implementation
├── example/               # Example application
│   ├── index.html         # Example HTML
│   ├── main.js            # Example JavaScript
│   └── style.css          # Example styles
├── Cargo.toml             # Rust dependencies
├── package.json           # NPM package configuration
├── index.js               # Main entry point with auto-initialization
├── index.d.ts             # TypeScript definitions for index.js
├── vite.config.js         # Vite configuration
├── tailwind.config.js     # Tailwind CSS configuration
├── postcss.config.js      # PostCSS configuration
├── build.sh               # Build script
├── validate-package.sh    # Package validation script
└── README.md              # This file
```

## License

MIT