An open API service indexing awesome lists of open source software.

https://github.com/tetherto/wdk-cli


https://github.com/tetherto/wdk-cli

cli wdk

Last synced: 3 days ago
JSON representation

Awesome Lists containing this project

README

          

# @tetherto/wdk-cli

[![npm version](https://img.shields.io/npm/v/%40tetherto%2Fwdk-cli?style=flat-square)](https://www.npmjs.com/package/@tetherto/wdk-cli)
[![npm downloads](https://img.shields.io/npm/dw/%40tetherto%2Fwdk-cli?style=flat-square)](https://www.npmjs.com/package/@tetherto/wdk-cli)
[![license](https://img.shields.io/npm/l/%40tetherto%2Fwdk-cli?style=flat-square)](https://github.com/tetherto/wdk-cli/blob/main/LICENSE)

A multi-chain crypto wallet for AI agents, built on [Wallet Development Kit (WDK)](https://wallet.tether.io/). Designed to be operated by AI agents (e.g. Claude, ChatGPT, OpenClaw).

> **AI agents:** see [`SKILL.md`](./SKILL.md) for the complete operational guide — commands, workflows, error handling, and safety rules.

## Architecture

```
┌───────────┐ ┌───────────────────────────────────┐
│ wdk-cli │──IPC──▶│ wdk-daemon │
│ (CLI) │ │ (wallet daemon) │
└───────────┘ │ │
│ Holds WDK instances in memory │
┌───────────┐ │ Handles all crypto operations: │
│ wdk-mcp │──IPC──▶│ derivation, signing, balances │
│ (MCP) │ │ Auto-locks after TTL expires │
└───────────┘ │ │
│ Signs tx locally, then submits │
└───────────┬───────────────────────┘

│ submits signed tx

┌──────────────┐
│ Blockchain │
│ (RPC/P2P) │
└──────────────┘
```

**wdk-daemon** (Wallet Daemon):
- Starts empty — wallets unlocked individually via socket requests
- Holds WDK instances in memory — owns all cryptographic operations
- Listens on a Unix socket (`daemon.sock`, 0600 permissions)
- Exposes: `unlock_wallet`, `lock_wallet`, `get_address`, `get_balance`, `estimate_fee`, `send`, `list_wallets`, `status`, `lock`
- Per-wallet TTL — each wallet has its own timeout (default: 5 min, `--ttl 0` for unlimited)
- Auto-exits when last wallet is locked

**wdk-cli** (CLI):
- Thin client — no crypto, no keys
- Parses user commands, sends requests to daemon, formats and displays results
- Only interface for passphrase-protected operations: unlock wallet, export seed, delete wallet

**wdk-mcp** (MCP Server):
- Thin client — no crypto, no keys
- Exposes structured wallet tools to AI agents via [Model Context Protocol](https://modelcontextprotocol.io/)
- Routes all operations through the daemon

## Features

- **Wallet** — Multiple named wallets with per-wallet passphrases and BIP-39 seed phrases, encrypted at rest with AES-256-GCM. Background daemon holds keys in memory after unlock, with per-wallet TTL
- **Network** — Bitcoin, Ethereum, Polygon, Arbitrum, Base, BSC, Avalanche, Solana, Tron, Spark, Smart Account (ERC-4337) + testnets. Add custom networks with `network create`
- **Token** — Built-in registry of tokens per network (symbol, decimals, address, indexer/MoonPay/Bitfinex mappings). Add your own with `token add`
- **Get** — Derive wallet addresses, check balances, and view transaction history across all networks
- **Send** — Native and token transfers with fee estimation and dry-run preview. Decimal amounts by default
- **Buy/Sell** — On/off ramp via MoonPay (buy crypto with fiat, sell crypto for fiat)
- **Config** — Per-network configuration with env var overrides

## Requirements

- Node.js >= 22.18.0

## Install

```bash
npm install -g @tetherto/wdk-cli
```

From source:

```bash
git clone https://github.com/tetherto/wdk-cli.git
cd wdk-cli
npm install
npm link # makes `wdk` available globally
```

## Quick Start

```bash
# Create wallets (each has its own passphrase)
wdk wallet create --name trading --words 24
wdk wallet create --name savings --words 12

# List all wallets
wdk wallet list

# Unlock wallets individually (starts daemon on first unlock)
wdk wallet unlock --name trading --ttl 0 # unlimited session
wdk wallet unlock --name savings --ttl 60 # 60 min session

# Set default wallet
wdk wallet default --name trading

# Get all wallet addresses across networks
wdk get address --all

# Get address for a specific network
wdk get address --network ethereum

# Use a specific wallet
wdk get address --network ethereum --wallet trading

# Check all balances across networks (with USD totals)
wdk get balance --all

# Check balance for a specific wallet
wdk get balance --all --wallet savings

# Send 1.23456 ETH (decimal default; add --base-units to send in wei)
wdk send --to 0x000000000000000000000000000000000000dEaD --amount 1.23456 --network ethereum

# Send 100.5 USDT (registered token ticker — see `wdk token list`)
wdk send --to 0x... --amount 100.5 --token usdt --network ethereum --wallet trading

# Show network details and config
wdk network info --network ethereum

# List supported networks
wdk network list

# Lock a single wallet
wdk wallet lock --name trading

# Lock all wallets when done
wdk wallet lock --all
```

## Commands

### Wallet

Wallet commands that require passphrase input (create, import, unlock, export, delete) are interactive by default. Set `WDK_PASSPHRASE` env var to skip the interactive prompt for automation and `--json` output.

```bash
wdk wallet create --name [--words 12|24] # Create a new wallet with a generated seed phrase
wdk wallet import --name # Import a wallet from an existing seed phrase
wdk wallet export --name # Display the seed phrase of an existing wallet
wdk wallet list # List all wallets with lock/default status
wdk wallet unlock --name [--ttl ] # Unlock a wallet for signing transactions
wdk wallet lock --name # Lock a single wallet
wdk wallet lock --all # Lock all wallets (stops daemon)
wdk wallet delete --name # Delete a wallet (requires passphrase)
wdk wallet default --name # Set the default wallet
wdk wallet rename --name --new-name # Rename a wallet
```

Supports **multiple named wallets** with **per-wallet passphrases**. Each wallet is stored as `~/.config/wdk-cli/wallets//seed.enc`. The first wallet created is auto-set as default. Use `--wallet ` on other commands to target a specific wallet (defaults to the default wallet).

Passphrase is optional (empty for none). If provided, it encrypts the seed phrase with AES-256-GCM + scrypt.

`wdk wallet unlock` unlocks a single wallet and starts the daemon if not running. Each wallet has its own TTL — use `--ttl 0` for unlimited session, ideal for AI agent environments. The daemon auto-exits when the last wallet is locked.

### Networks

```bash
wdk network list # List all networks (built-in + custom)
wdk network list --testnet # Show only testnets
wdk network list --mainnet # Show only mainnets
wdk network info --network # Show network details and config
wdk network delete --name # Delete a custom network (requires unlocked wallet)
```

#### Adding Custom Networks

`wdk network create ` takes a single argument — either an inline JSON string or a path to a JSON file. Requires an unlocked wallet.

```bash
# Inline JSON
wdk network create '{"network":"optimism","module":"@tetherto/wdk-wallet-evm","indexerSlug":"ethereum","config":{"provider":"https://mainnet.optimism.io","chainId":10}}'

# Or from a file
wdk network create ./optimism.json
```

Example spec file:

```json
{
"network": "optimism",
"module": "@tetherto/wdk-wallet-evm",
"displayName": "Optimism",
"testnet": false,
"indexerSlug": "ethereum",
"config": { "provider": "https://mainnet.optimism.io", "chainId": 10 },
"tokens": [
{
"token": "eth",
"symbol": "ETH",
"decimals": 18,
"isNative": true,
"metadata": { "moonpaySlug": "eth", "bitfinexSlug": "tETHUSD" }
},
{
"token": "usdt",
"symbol": "USDT",
"decimals": 6,
"isNative": false,
"address": "0x...",
"metadata": { "indexerSlug": "usdt", "moonpaySlug": "usdt", "bitfinexSlug": "tUSTUSD" }
}
]
}
```

**Spec fields:**

| Field | Required | Description |
|-------|----------|-------------|
| `network` | Yes | Network identifier (lowercase, hyphens allowed) |
| `module` | Yes | Wallet module: `@tetherto/wdk-wallet-evm`, `@tetherto/wdk-wallet-btc`, `@tetherto/wdk-wallet-solana`, `@tetherto/wdk-wallet-spark`, `@tetherto/wdk-wallet-tron`, `@tetherto/wdk-wallet-evm-erc-4337` |
| `displayName` | No | Human-readable name (defaults to `network`) |
| `testnet` | No | `true` to mark as a testnet |
| `indexerSlug` | Required for indexer support | Chain slug used by the WDK indexer API (e.g. `ethereum`, `polygon`, `bitcoin`). If omitted, the indexer is disabled for this network and `wdk get history` will error with `NETWORK_NOT_SUPPORTED`. For most networks the slug equals the network name; smart-account / forked networks point at the underlying chain (e.g. `smart-account-ethereum` uses `"indexerSlug": "ethereum"`). |
| `config` | No | SDK config object (provider URL, chainId, etc.). May be set later via `wdk config set --network `. |
| `tokens` | No | Token registry entries to register atomically with the network. At most one entry may have `isNative: true`. Each entry's shape matches the [Tokens](#tokens) spec. |

The spec is parsed and validated atomically: if any token in `tokens[]` fails validation, the network is not created (full rollback). Unknown top-level fields pass through silently (so you can annotate specs with comments, tags, owner, etc.).

Deleting a custom network (`wdk network delete --name `) also removes its registered tokens.

### Tokens

The CLI ships with a registry (`wdk.tokens.json`) of all known tokens per network — symbol, decimals, contract address, and provider mappings (indexer code, MoonPay asset code, Bitfinex pair). The `--token` flag on `get balance` / `send` / `get history` / `buy` / `sell` resolves against this registry.

```bash
wdk token list # All tokens, grouped by network
wdk token list --network ethereum # Filter to one network
wdk token info --network ethereum --token usdt # Show full entry

# Add a token from inline JSON or a file
wdk token add '{"network":"ethereum","token":"dai","symbol":"DAI","decimals":18,"isNative":false,"address":"0x..."}'
wdk token add ./dai-on-ethereum.json

wdk token delete --network --token # Remove a custom entry
```

`wdk token add ` takes a single argument — inline JSON or a path to a JSON file.

**Token entry fields — and why each one is needed:**

| Field | Required | What the CLI does with it |
|-------|----------|---------------------------|
| `network` | Yes | Parent network the token belongs to. Must already exist. |
| `token` | Yes | Registry key (lowercased) used by `--token ` flags across the CLI. |
| `symbol` | Yes | Display label shown in `get balance`, `send` previews, fee lines, and tables (e.g. `1.5 ETH`, `100 USDT`). |
| `decimals` | Yes | Used to convert between the CLI's decimal `--amount` (e.g. `1.5`) and the SDK's base units (wei / satoshi / lamport). Integer 0–24. |
| `isNative` | Yes | Routes transfers through the native-coin path (no contract call) vs the token-contract path. Each network can have **at most one** native entry. |
| `address` | If `!isNative` | Contract / mint address used by the SDK to call `transfer` / `getBalance` on the right token. Required for ERC-20 / SPL / TRC-20; omit for native. |
| `metadata.indexerSlug` | No | Asset slug sent to the WDK indexer (`/api/v1/{chain}/{indexerSlug}/{addr}/token-transfers`). Without it, this token is skipped by `wdk get history`. **Requires the network to also have `indexerSlug` set** — a token slug alone doesn't enable the indexer; the network's `indexerSlug` is what tells the CLI the indexer is available for that chain. |
| `metadata.moonpaySlug` | No | MoonPay asset code used in the buy/sell URL. Without it, `wdk buy`/`wdk sell` rejects the token. |
| `metadata.bitfinexSlug` | No | Bitfinex pair symbol used to fetch a USD price. Without it, `wdk get balance` shows the balance but no USD column for that token. |

Provider mappings (`metadata.*Slug`) are all optional. Omit them when the integration doesn't apply — the rest of the CLI keeps working; only the specific feature is disabled for that token. Unknown top-level fields pass through silently so you can annotate entries with comments, tags, owner, etc.

Example full entry (file or inline):

```json
{
"network": "optimism",
"token": "usdt",
"symbol": "USDT",
"decimals": 6,
"isNative": false,
"address": "0x...",
"metadata": {
"indexerSlug": "usdt",
"moonpaySlug": "usdt",
"bitfinexSlug": "tUSTUSD"
}
}
```

Custom entries (added via `token add`) live under `customTokens..` and survive `wdk config reset --all`. Built-in entries can be **overridden** by adding a custom entry with the same ticker — a yellow warning is shown when this happens. `token delete` only removes custom entries; the built-in falls through after deletion.

### Get

```bash
wdk get address --network [--index ] # Derive wallet address
wdk get address --all # All mainnet addresses
wdk get address --all --testnet # All addresses incl. testnets
wdk get balance --network ethereum # Native ETH balance
wdk get balance --network ethereum --token usdt # ERC-20 by registered ticker
wdk get balance --all # All mainnet balances with USD
wdk get balance --all --testnet # All balances incl. testnets
wdk get history --network ethereum # All supported tokens
wdk get history --network ethereum --token xaut --limit 50 # XAUT transfers, last 50
wdk get history --network ethereum --from-date 2026-01-01 --to-date 2026-03-31 # Date range filter
```

`--token` accepts a registered token ticker (e.g. `usdt`, `eth`, `xaut`). See `wdk token list` for available tokens. Use `wdk token add` to register a new token.

Wallets are derived deterministically from your seed phrase using HD paths (BIP-84 for BTC, BIP-44 for EVM/Solana) — no local state is stored. `get address` works without a provider configured (local derivation only), while `get balance` requires a provider connection.

`get history` uses the [WDK Indexer API](https://github.com/tetherto/wdk-indexer-http). Configure with `WDK_INDEXER_BASE_URL` / `WDK_INDEXER_API_KEY` env vars, or use `wdk config set` for `indexer.baseUrl` and `indexer.apiKey`. If using a proxy provider that includes the API key, only the base URL is needed.

### Send

```bash
wdk send --to

--amount --network # Native (decimal, e.g. 1.5)
wdk send --to
--amount --token --network ethereum # ERC-20 by registered ticker
wdk send --to
--amount --token --network solana # SPL by registered ticker
wdk send --to
--amount --base-units --network ethereum # Opt-in: raw base units
wdk send --to
--amount --network ethereum --dry-run # preview without sending
```

`--amount` is decimal by default (e.g. `1.5` for 1.5 ETH, `0.001` for 0.001 BTC, `1.23456789012345678` for full 18-decimal ETH precision). The CLI converts using the token's registered decimals. If the value has more decimal places than the token allows (e.g. `1.12345678` with 6-decimal USDT), it's rejected with `INVALID_AMOUNT`. Pass `--base-units` to interpret the value as raw base units (wei/satoshi/lamport) — useful for scripts that already have BigInt amounts. Fee estimation runs before confirmation; use `--dry-run` to preview the transaction with fee and USD estimates without sending.

### Buy / Sell (On/Off Ramp)

```bash
# Buy crypto with fiat
wdk buy --network ethereum --token usdt # Opens MoonPay widget
wdk buy --network ethereum --token eth --fiat-amount 100 # Buy $100 of ETH
wdk buy --network bitcoin --token btc --crypto-amount 0.05 # Buy 0.05 BTC

# Sell crypto for fiat
wdk sell --network ethereum --token usdt # Opens MoonPay sell widget
wdk sell --network ethereum --token eth --fiat-amount 200 # Sell ETH for $200
wdk sell --network polygon --token usdt --crypto-amount 50 # Sell 50 USDT on Polygon
```

Uses MoonPay as the fiat provider. All three config values are required:

```bash
wdk config set --key ramp.moonpay.apiKey --value
wdk config set --key ramp.moonpay.signUrl --value
wdk config set --key ramp.moonpay.environment --value sandbox # or production
```

**Options:**

| Flag | Description |
|------|-------------|
| `--network ` | Blockchain network (required) |
| `--token ` | Crypto asset code, e.g. `usdt`, `eth`, `btc` (required) |
| `--module ` | Fiat provider (default: `moonpay`) |
| `--fiat-currency ` | Fiat currency code (default: `usd`) |
| `--fiat-amount ` | Fiat amount (mutually exclusive with `--crypto-amount`) |
| `--crypto-amount ` | Crypto amount (mutually exclusive with `--fiat-amount`) |

Supported tokens are derived from the registry — any token with `metadata.moonpaySlug` set in `wdk.tokens.json` (or a custom token added via `wdk token add`). Environment validation prevents using production MoonPay with testnet networks (and vice versa).

Configure via `wdk config set --key ramp.moonpay.apiKey --value `, `ramp.moonpay.signUrl`, and `ramp.moonpay.environment`.

### Configuration

Config read commands (`get`, `path`) work without a wallet. Write operations (`set`, `reset`) require an unlocked wallet. All config commands support `--json`.

```bash
# Get
wdk config get --all # Show all config
wdk config get --key ramp.moonpay.apiKey # Show a specific value
wdk config get --network ethereum # Show Ethereum config
wdk config get --key provider --network ethereum # Show a network-specific value

# Set
wdk config set --key ramp.moonpay.apiKey --value pk_test_... # Set a value
wdk config set --key provider --value --network ethereum # Network-scoped value
wdk config set --key ramp.moonpay --value '{"apiKey":"...","signUrl":"...","environment":"sandbox"}' # JSON object
wdk config set --value '{"provider":"https://...","transferMaxFee":5000}' --network optimism # Full network config

# Reset
wdk config reset --key provider --network ethereum # Reset a key to default
wdk config reset --all # Reset everything to factory defaults
# (preserves defaultWallet, customNetworks, customTokens)

# Path
wdk config path # Config file path
```

Values passed to `--value` support JSON — objects and arrays are parsed and stored as structured data. Use `--network` to scope config to a specific network. When `--key` is omitted with `--network`, the entire network config is set.

Network configuration is passed directly to the wallet SDK. Refer to each [wallet module's documentation](https://docs.wdk.tether.io/sdk/wallet-modules) for supported config keys. Default values are in [`wdk.config.json`](wdk.config.json).

### Global Flags

| Flag | Description |
|------|-------------|
| `--index ` | Account index (default: 0) |
| `--wallet ` | Wallet name (uses default wallet if omitted) |
| `--json` | Machine-readable JSON output |
| `--verbose` | Debug logging |

## Supported Networks

All built-in networks are defined in [`wdk.config.json`](wdk.config.json). Run `wdk network list` to see all available networks.

Additional networks can be added with `wdk network create`. See [Adding Custom Networks](#adding-custom-networks).

## Non-Interactive Mode

All commands support `--json` for machine-parseable output. Commands that require passphrase input (wallet create, import, unlock, export, delete, and config set/reset) can be run non-interactively by setting the `WDK_PASSPHRASE` environment variable.

```bash
# CI/CD: create and unlock a wallet without interactive prompts
WDK_PASSPHRASE=mypass wdk wallet create --name ci-wallet --json
WDK_PASSPHRASE=mypass wdk wallet unlock --name ci-wallet --ttl 0 --json

# Docker: unlock at container start
WDK_PASSPHRASE=$WALLET_PASS wdk wallet unlock --name default --ttl 0 --json

# Scripting: check balance and parse output
wdk get balance --network ethereum --json | jq '.balance'
```

## Environment Variables

| Variable | Description |
|----------|-------------|
| `WDK_PASSPHRASE` | Wallet passphrase (skip interactive prompt) |
| `WDK_INDEXER_API_KEY` | Indexer API key (avoids storing secrets in config file) |

## Security

- Seed phrases encrypted at rest (AES-256-GCM + scrypt), per-wallet passphrases with unique salt per wallet
- Seeds and passphrases never accepted as CLI arguments
- Private keys and seeds never leave the daemon process
- Unix socket with 0600 permissions (same-user access only, like ssh-agent)
- No telemetry, no analytics, no external data collection

### Data flow

1. **Unlock**: User unlocks a wallet by name → passphrase sent to daemon over Unix socket → daemon decrypts seed via scrypt + AES-256-GCM → initializes WDK instance in RAM → starts per-wallet TTL timer
2. **Request**: CLI/MCP sends operation (e.g. `get_address`) with wallet name over Unix socket → daemon performs crypto operation → returns result only
3. **Lock**: Individual wallet locked → WDK instance disposed and cleared. When last wallet locks → daemon exits

### Encrypted wallet files

Each wallet is stored in its own directory as `~/.config/wdk-cli/wallets//seed.enc`:

```json
{ "version": 1, "salt": "...", "iv": "...", "tag": "...", "ciphertext": "..." }
```

Per-wallet passphrase with unique random salt → unique derived key per wallet.

## AI Agent Integration

AI agents interact with wdk-wallet in two ways, depending on their environment:

### MCP — for sandboxed AI models

For AI models that run as applications with limited system access (Claude Desktop, OpenClaw, etc.). The model can only interact with the wallet through structured MCP tools — it cannot run commands or access the filesystem.

```bash
wdk mcp setup --ai-tool claude-desktop # Configure for Claude Desktop
wdk mcp setup --ai-tool claude-code # Configure for Claude Code
wdk mcp setup --ai-tool openclaw # Configure for OpenClaw

wdk mcp remove --ai-tool claude-desktop # Remove configuration
wdk mcp verify-setup --ai-tool claude-code # Verify config and test MCP server
wdk mcp list # Show status across all AI tools
```

Setup auto-detects the Node.js path, validates the MCP server, and writes the config. For Claude Code and OpenClaw, it uses their native CLI (`claude mcp add`, `openclaw mcp set`). For Claude Desktop, it writes the config file directly.

**MCP Tools:**

| Tool | Parameters | Description |
|------|-----------|-------------|
| `get_networks` | `testnet?`, `mainnet?` | List all supported blockchain networks |
| `list_tokens` | `network?` | List registered tokens (omit network for every network) |
| `get_token` | `network`, `token` | Get a single registered token entry (symbol, decimals, address, provider mappings) |
| `get_address` | `network?`, `index?`, `testnet?`, `wallet?` | Get wallet address (omit network for all) |
| `get_balance` | `network?`, `token?`, `index?`, `testnet?`, `wallet?` | Get balance with USD values (omit network for all). `token` is a registered ticker. |
| `get_history` | `network`, `token?`, `limit?`, `index?`, `fromDate?`, `toDate?`, `wallet?` | Transaction history (requires indexer API) |
| `send_token` | `to`, `amount`, `baseUnits?`, `network`, `token?`, `index?`, `dryRun?`, `wallet?` | Send tokens. `amount` is decimal by default; set `baseUnits=true` to interpret as base units. Returns dry-run preview by default; set `dryRun=false` to execute |
| `buy_crypto` | `network`, `token`, `fiatCurrency?`, `fiatAmount?`, `cryptoAmount?`, `index?`, `wallet?` | Buy crypto with fiat. Returns a signed MoonPay URL. |
| `sell_crypto` | `network`, `token`, `fiatCurrency?`, `fiatAmount?`, `cryptoAmount?`, `index?`, `wallet?` | Sell crypto for fiat. Returns a signed MoonPay URL. |

All wallet-dependent tools accept an optional `wallet` parameter (uses default wallet if omitted).

**Important: `send_token` requires two calls.** The tool defaults to `dryRun=true` (preview mode). AI agents must:
1. Call `send_token` first to get a fee/amount preview
2. Show the preview to the user and wait for confirmation
3. Call `send_token` again with `dryRun=false` only after the user confirms

The AI model interacts exclusively through these structured tools — it cannot run shell commands, access the filesystem, or read private keys. All operations route through the daemon over a Unix socket.

### CLI — for local AI agents

For AI agents with full system access (Claude Code, OpenClaw, custom agents). The agent runs `wdk` commands directly with `--json` for machine-parseable output.

```bash
wdk get balance --network ethereum --json
wdk send --to 0xRECIPIENT --amount 1.23456 --network ethereum --dry-run --json # decimal accepted (up to token precision)
wdk send --to 0xRECIPIENT --amount 100.5 --token usdt --network ethereum --json # decimal USDT by ticker
```

The `SKILL.md` file contains complete instructions for AI agents — commands, workflows, error handling, and amount conversions. Feed it as context to your agent.

### Before using either mode

Create and unlock wallets first:
```bash
wdk wallet create --name default --words 24
wdk wallet unlock --name default --ttl 0
```

Both MCP and CLI route through the daemon — the agent never has access to keys or seeds.

## Development

```bash
npm test # Run unit tests
npm run test:integration # Run integration tests (spawns CLI in isolated temp dir)
npm run format # Format with Prettier
npm run format:check # Check formatting
```

This project is plain JavaScript (ESM) — no build step. Source under `src/` is run directly via the `bin/*.mjs` entry points.

## License

This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Support

For support, please open an issue on the GitHub repository.