@unicitylabs/uniclaw 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +279 -0
- package/openclaw.plugin.json +26 -0
- package/package.json +59 -0
- package/src/assets.ts +150 -0
- package/src/channel.ts +456 -0
- package/src/cli-prompter.ts +66 -0
- package/src/config.ts +32 -0
- package/src/index.ts +234 -0
- package/src/resources/unicity-ids.testnet.json +122 -0
- package/src/setup.ts +79 -0
- package/src/sphere.ts +220 -0
- package/src/tools/get-balance.ts +33 -0
- package/src/tools/get-transaction-history.ts +42 -0
- package/src/tools/list-payment-requests.ts +82 -0
- package/src/tools/list-tokens.ts +51 -0
- package/src/tools/request-payment.ts +62 -0
- package/src/tools/respond-payment-request.ts +58 -0
- package/src/tools/send-message.ts +30 -0
- package/src/tools/send-tokens.ts +63 -0
- package/src/tools/top-up.ts +81 -0
- package/src/validation.ts +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Unicity Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Uniclaw - Unicity wallet and encrypted DMs for [OpenClaw](https://github.com/openclaw/openclaw) agents
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="uniclaw.png" alt="Uniclaw" width="300" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@unicitylabs/uniclaw"><img src="https://img.shields.io/npm/v/@unicitylabs/uniclaw" alt="npm version" /></a>
|
|
9
|
+
<a href="https://github.com/unicitynetwork/uniclaw/blob/main/LICENSE"><img src="https://img.shields.io/github/license/unicitynetwork/uniclaw" alt="license" /></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
**Uniclaw** is an [OpenClaw](https://github.com/openclaw/openclaw) plugin that gives your AI agent a Unicity wallet identity and the ability to send and receive encrypted direct messages over Unicity's private Nostr relay network, powered by the [Unicity Sphere SDK](https://github.com/unicitylabs/sphere-sdk).
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Wallet identity** — Auto-generates a Unicity wallet on first run (BIP-32 HD wallet with mnemonic backup)
|
|
19
|
+
- **Nametag minting** — Register a human-readable `@nametag` for your agent on the Unicity network
|
|
20
|
+
- **Encrypted DMs** — Send and receive direct messages over Unicity's private Nostr relays
|
|
21
|
+
- **Token management** — Send/receive tokens, check balances, view transaction history
|
|
22
|
+
- **Payment requests** — Request payments from other users, accept/reject/pay incoming requests
|
|
23
|
+
- **Faucet top-up** — Request test tokens on testnet via built-in faucet tool
|
|
24
|
+
- **Agent tools** — 9 tools for messaging, wallet operations, and payments (see [Agent Tools](#agent-tools))
|
|
25
|
+
- **OpenClaw channel** — Full channel plugin with inbound/outbound message handling, status reporting, and DM access control
|
|
26
|
+
- **Interactive setup** — `openclaw uniclaw setup` wizard and `openclaw onboard` integration
|
|
27
|
+
- **CLI commands** — `openclaw uniclaw init`, `status`, `send`, and `listen` for wallet management
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Install the plugin
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
openclaw plugins install @unicitylabs/uniclaw
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Run interactive setup
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
openclaw uniclaw setup
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This walks you through choosing a nametag, owner, and network, then writes the config for you.
|
|
44
|
+
|
|
45
|
+
Alternatively, Uniclaw integrates with OpenClaw's onboarding wizard:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
openclaw onboard
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Start the gateway
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
openclaw gateway start
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
On first start, Uniclaw auto-generates a wallet and mints your chosen nametag. The mnemonic backup is saved to `~/.openclaw/unicity/mnemonic.txt` (owner-only permissions).
|
|
58
|
+
|
|
59
|
+
That's it. Your agent can now send and receive encrypted DMs on the Unicity network.
|
|
60
|
+
|
|
61
|
+
## Manual Configuration
|
|
62
|
+
|
|
63
|
+
If you prefer to edit config directly, add to `~/.openclaw/openclaw.json`:
|
|
64
|
+
|
|
65
|
+
```json5
|
|
66
|
+
{
|
|
67
|
+
// Plugin settings (identity, owner, network)
|
|
68
|
+
"plugins": {
|
|
69
|
+
"entries": {
|
|
70
|
+
"uniclaw": {
|
|
71
|
+
"enabled": true,
|
|
72
|
+
"config": {
|
|
73
|
+
"nametag": "my-agent", // Optional: register a @nametag
|
|
74
|
+
"owner": "alice", // Nametag or pubkey of the trusted human owner
|
|
75
|
+
"network": "testnet", // testnet (default) | mainnet | dev
|
|
76
|
+
"additionalRelays": [ // Optional: extra Nostr relays
|
|
77
|
+
"wss://custom-relay.example.com"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// Channel settings (DM access control)
|
|
85
|
+
"channels": {
|
|
86
|
+
"uniclaw": {
|
|
87
|
+
"enabled": true,
|
|
88
|
+
"dmPolicy": "open", // open | pairing | allowlist | disabled
|
|
89
|
+
"allowFrom": ["@trusted-user"] // Required when dmPolicy is "allowlist"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Config changes take effect on the next gateway restart — no need to reinstall the plugin.
|
|
96
|
+
|
|
97
|
+
### Owner trust model
|
|
98
|
+
|
|
99
|
+
The `owner` field identifies the human who controls the agent. When set:
|
|
100
|
+
|
|
101
|
+
- **Only the owner** can give the agent commands, change its behavior, or instruct it to perform actions via DMs.
|
|
102
|
+
- **Anyone else** can chat with the agent — negotiate deals, discuss topics, ask questions — but the agent will not follow operational commands from non-owner senders.
|
|
103
|
+
- Owner matching works by nametag or public key (case-insensitive, `@` prefix optional).
|
|
104
|
+
|
|
105
|
+
## CLI Commands
|
|
106
|
+
|
|
107
|
+
### Interactive setup
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
openclaw uniclaw setup
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Prompts for nametag, owner, and network, then writes the config file. Run this once to get started, or re-run to change settings.
|
|
114
|
+
|
|
115
|
+
### Initialize wallet
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
openclaw uniclaw init
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Creates a new wallet (if one doesn't exist), displays the public key and address, and mints the configured nametag. The mnemonic is automatically saved to `~/.openclaw/unicity/mnemonic.txt` (owner-only permissions).
|
|
122
|
+
|
|
123
|
+
### Check status
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
openclaw uniclaw status
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Shows network, public key, address, and nametag.
|
|
130
|
+
|
|
131
|
+
## Agent Tools
|
|
132
|
+
|
|
133
|
+
Once the plugin is loaded, the agent has access to the following tools:
|
|
134
|
+
|
|
135
|
+
### Messaging
|
|
136
|
+
|
|
137
|
+
| Tool | Description |
|
|
138
|
+
|------|-------------|
|
|
139
|
+
| `uniclaw_send_message` | Send an encrypted DM to a nametag or public key |
|
|
140
|
+
|
|
141
|
+
### Wallet & Balances
|
|
142
|
+
|
|
143
|
+
| Tool | Description |
|
|
144
|
+
|------|-------------|
|
|
145
|
+
| `uniclaw_get_balance` | Check token balances (optionally filtered by coin) |
|
|
146
|
+
| `uniclaw_list_tokens` | List individual tokens with status and creation time |
|
|
147
|
+
| `uniclaw_get_transaction_history` | View recent transactions (sent/received) |
|
|
148
|
+
|
|
149
|
+
### Transfers & Payments
|
|
150
|
+
|
|
151
|
+
| Tool | Description |
|
|
152
|
+
|------|-------------|
|
|
153
|
+
| `uniclaw_send_tokens` | Transfer tokens to a recipient (requires owner instruction) |
|
|
154
|
+
| `uniclaw_request_payment` | Send a payment request to another user |
|
|
155
|
+
| `uniclaw_list_payment_requests` | View incoming/outgoing payment requests |
|
|
156
|
+
| `uniclaw_respond_payment_request` | Pay, accept, or reject a payment request |
|
|
157
|
+
| `uniclaw_top_up` | Request test tokens from the faucet (testnet only) |
|
|
158
|
+
|
|
159
|
+
Recipients can be specified as a `@nametag` or a 64-character hex public key.
|
|
160
|
+
|
|
161
|
+
**Examples:**
|
|
162
|
+
|
|
163
|
+
> "Send a message to @alice saying hello"
|
|
164
|
+
>
|
|
165
|
+
> "What's my balance?"
|
|
166
|
+
>
|
|
167
|
+
> "Send 100 UCT to @bob for the pizza"
|
|
168
|
+
>
|
|
169
|
+
> "Top up 50 USDU from the faucet"
|
|
170
|
+
|
|
171
|
+
### Receive messages
|
|
172
|
+
|
|
173
|
+
When the gateway is running, incoming DMs, token transfers, and payment requests are automatically routed to the agent's reply pipeline. The agent receives the event, processes it, and replies are delivered back as encrypted DMs.
|
|
174
|
+
|
|
175
|
+
## Architecture
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
┌─────────────────────────────────────────────────┐
|
|
179
|
+
│ OpenClaw Gateway │
|
|
180
|
+
│ │
|
|
181
|
+
│ ┌────────────┐ ┌──────────┐ ┌───────────┐ │
|
|
182
|
+
│ │ Uniclaw │──▶│ Sphere │──▶│ Unicity │ │
|
|
183
|
+
│ │ Plugin │◀──│ SDK │◀──│ Relays │ │
|
|
184
|
+
│ └────────────┘ └──────────┘ └───────────┘ │
|
|
185
|
+
│ │ │
|
|
186
|
+
│ ▼ │
|
|
187
|
+
│ ┌───────────┐ │
|
|
188
|
+
│ │ Agent │ │
|
|
189
|
+
│ │ Pipeline │ │
|
|
190
|
+
│ └───────────┘ │
|
|
191
|
+
└─────────────────────────────────────────────────┘
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- **Plugin service** starts the Sphere SDK, creates/loads the wallet, and connects to Unicity relays
|
|
195
|
+
- **Gateway adapter** listens for inbound DMs, token transfers, and payment requests, dispatching them through OpenClaw's reply pipeline
|
|
196
|
+
- **Outbound adapter** delivers agent replies as encrypted DMs
|
|
197
|
+
- **Agent tools** (9 tools) allow the agent to send messages, manage tokens, and handle payments
|
|
198
|
+
|
|
199
|
+
## Data Storage
|
|
200
|
+
|
|
201
|
+
| Path | Contents |
|
|
202
|
+
|------|----------|
|
|
203
|
+
| `~/.openclaw/unicity/` | Wallet data (keys, state) |
|
|
204
|
+
| `~/.openclaw/unicity/mnemonic.txt` | Mnemonic backup (mode 0600) |
|
|
205
|
+
| `~/.openclaw/unicity/tokens/` | Token storage |
|
|
206
|
+
| `~/.openclaw/unicity/trustbase.json` | Cached BFT trustbase (auto-downloaded) |
|
|
207
|
+
|
|
208
|
+
## Environment Variables
|
|
209
|
+
|
|
210
|
+
| Variable | Description | Default |
|
|
211
|
+
|----------|-------------|---------|
|
|
212
|
+
| `UNICLAW_TRUSTBASE_URL` | Override the BFT trustbase download URL | GitHub raw URL |
|
|
213
|
+
| `UNICLAW_FAUCET_URL` | Override the faucet API endpoint | `https://faucet.unicity.network/api/v1/faucet/request` |
|
|
214
|
+
|
|
215
|
+
## Development
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Install dependencies
|
|
219
|
+
npm install
|
|
220
|
+
|
|
221
|
+
# Run tests
|
|
222
|
+
npm test
|
|
223
|
+
|
|
224
|
+
# Run tests in watch mode
|
|
225
|
+
npm run test:watch
|
|
226
|
+
|
|
227
|
+
# Run E2E tests (requires network, skipped in CI)
|
|
228
|
+
npm run test:e2e
|
|
229
|
+
|
|
230
|
+
# Lint
|
|
231
|
+
npm run lint
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Project Structure
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
uniclaw/
|
|
238
|
+
├── src/
|
|
239
|
+
│ ├── index.ts # Plugin entry point & registration
|
|
240
|
+
│ ├── config.ts # Configuration schema & validation
|
|
241
|
+
│ ├── validation.ts # Shared validation (nametag regex, recipient format)
|
|
242
|
+
│ ├── sphere.ts # Sphere SDK singleton lifecycle
|
|
243
|
+
│ ├── channel.ts # Channel plugin (7 adapters + onboarding)
|
|
244
|
+
│ ├── assets.ts # Asset registry & decimal conversion
|
|
245
|
+
│ ├── setup.ts # Interactive setup wizard
|
|
246
|
+
│ ├── cli-prompter.ts # WizardPrompter adapter for CLI
|
|
247
|
+
│ ├── resources/
|
|
248
|
+
│ │ └── unicity-ids.testnet.json # Fungible asset metadata
|
|
249
|
+
│ └── tools/
|
|
250
|
+
│ ├── send-message.ts # Send encrypted DMs
|
|
251
|
+
│ ├── get-balance.ts # Check wallet balances
|
|
252
|
+
│ ├── list-tokens.ts # List individual tokens
|
|
253
|
+
│ ├── get-transaction-history.ts # View transaction history
|
|
254
|
+
│ ├── send-tokens.ts # Transfer tokens
|
|
255
|
+
│ ├── request-payment.ts # Request payment from a user
|
|
256
|
+
│ ├── list-payment-requests.ts # View payment requests
|
|
257
|
+
│ ├── respond-payment-request.ts # Pay/accept/reject requests
|
|
258
|
+
│ └── top-up.ts # Testnet faucet
|
|
259
|
+
├── test/
|
|
260
|
+
│ ├── config.test.ts
|
|
261
|
+
│ ├── assets.test.ts
|
|
262
|
+
│ ├── sphere.test.ts
|
|
263
|
+
│ ├── sphere.integration.test.ts
|
|
264
|
+
│ ├── channel.test.ts
|
|
265
|
+
│ ├── index.test.ts
|
|
266
|
+
│ ├── tools/ # One test file per tool
|
|
267
|
+
│ └── e2e/
|
|
268
|
+
│ └── wallet.test.ts # End-to-end wallet + DM + transfer tests
|
|
269
|
+
├── openclaw.plugin.json # Plugin manifest
|
|
270
|
+
├── package.json
|
|
271
|
+
├── vitest.config.ts
|
|
272
|
+
├── vitest.e2e.config.ts
|
|
273
|
+
├── LICENSE
|
|
274
|
+
└── README.md
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "uniclaw",
|
|
3
|
+
"channels": ["uniclaw"],
|
|
4
|
+
"configSchema": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"network": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"enum": ["testnet", "mainnet", "dev"],
|
|
11
|
+
"default": "testnet"
|
|
12
|
+
},
|
|
13
|
+
"nametag": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
},
|
|
16
|
+
"owner": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Nametag or public key of the trusted human owner. Only the owner can give commands to the agent."
|
|
19
|
+
},
|
|
20
|
+
"additionalRelays": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"items": { "type": "string" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unicitylabs/uniclaw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unicity wallet identity and encrypted DMs for OpenClaw agents — powered by Sphere SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/unicitynetwork/uniclaw.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/unicitynetwork/uniclaw#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/unicitynetwork/uniclaw/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"unicity",
|
|
18
|
+
"openclaw",
|
|
19
|
+
"nostr",
|
|
20
|
+
"wallet",
|
|
21
|
+
"agent",
|
|
22
|
+
"dm",
|
|
23
|
+
"sphere-sdk"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"src/**/*",
|
|
27
|
+
"openclaw.plugin.json",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"openclaw": {
|
|
32
|
+
"extensions": [
|
|
33
|
+
"./src/index.ts"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"test:coverage": "vitest run --coverage",
|
|
40
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
41
|
+
"lint": "oxlint src/ test/"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@clack/prompts": "^0.10.0",
|
|
45
|
+
"@sinclair/typebox": "^0.34.48",
|
|
46
|
+
"@unicitylabs/sphere-sdk": "^0.1.4"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"openclaw": "*"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"openclaw": "latest",
|
|
53
|
+
"oxlint": "^1.43.0",
|
|
54
|
+
"vitest": "^4.0.18"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/assets.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Asset registry — loads coin metadata from unicity-ids JSON. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
interface AssetEntry {
|
|
11
|
+
network: string;
|
|
12
|
+
assetKind: string;
|
|
13
|
+
name: string;
|
|
14
|
+
symbol?: string;
|
|
15
|
+
decimals?: number;
|
|
16
|
+
description?: string;
|
|
17
|
+
id: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface AssetRegistry {
|
|
21
|
+
/** Map from lowercase name or symbol to faucet coin name */
|
|
22
|
+
aliases: Map<string, string>;
|
|
23
|
+
/** Map from faucet coin name to display symbol */
|
|
24
|
+
symbols: Map<string, string>;
|
|
25
|
+
/** Map from faucet coin name to decimals */
|
|
26
|
+
decimals: Map<string, number>;
|
|
27
|
+
/** List of all available symbols for display */
|
|
28
|
+
availableSymbols: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let cachedRegistry: AssetRegistry | null = null;
|
|
32
|
+
|
|
33
|
+
function loadRegistry(): AssetRegistry {
|
|
34
|
+
if (cachedRegistry) return cachedRegistry;
|
|
35
|
+
|
|
36
|
+
const jsonPath = join(__dirname, "resources", "unicity-ids.testnet.json");
|
|
37
|
+
const raw = readFileSync(jsonPath, "utf-8");
|
|
38
|
+
const entries: AssetEntry[] = JSON.parse(raw);
|
|
39
|
+
|
|
40
|
+
const aliases = new Map<string, string>();
|
|
41
|
+
const symbols = new Map<string, string>();
|
|
42
|
+
const decimals = new Map<string, number>();
|
|
43
|
+
const availableSymbols: string[] = [];
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
// Only include fungible assets
|
|
47
|
+
if (entry.assetKind !== "fungible") continue;
|
|
48
|
+
if (!entry.symbol) continue;
|
|
49
|
+
|
|
50
|
+
const name = entry.name;
|
|
51
|
+
const symbol = entry.symbol;
|
|
52
|
+
|
|
53
|
+
// Map both name and symbol (lowercase) to the faucet coin name
|
|
54
|
+
aliases.set(name.toLowerCase(), name);
|
|
55
|
+
aliases.set(symbol.toLowerCase(), name);
|
|
56
|
+
|
|
57
|
+
// Store display symbol and decimals
|
|
58
|
+
symbols.set(name, symbol);
|
|
59
|
+
if (entry.decimals !== undefined) {
|
|
60
|
+
decimals.set(name, entry.decimals);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
availableSymbols.push(symbol);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
cachedRegistry = { aliases, symbols, decimals, availableSymbols };
|
|
67
|
+
return cachedRegistry;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Resolve user input (name or symbol) to faucet coin name, or null if not found */
|
|
71
|
+
export function resolveCoinId(input: string): string | null {
|
|
72
|
+
const registry = loadRegistry();
|
|
73
|
+
return registry.aliases.get(input.toLowerCase().trim()) ?? null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Get display symbol for a faucet coin name */
|
|
77
|
+
export function getCoinSymbol(coinName: string): string {
|
|
78
|
+
const registry = loadRegistry();
|
|
79
|
+
return registry.symbols.get(coinName) ?? coinName.toUpperCase();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Get decimals for a faucet coin name */
|
|
83
|
+
export function getCoinDecimals(coinName: string): number | undefined {
|
|
84
|
+
const registry = loadRegistry();
|
|
85
|
+
return registry.decimals.get(coinName);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Get list of all available symbols for display */
|
|
89
|
+
export function getAvailableSymbols(): string[] {
|
|
90
|
+
const registry = loadRegistry();
|
|
91
|
+
return registry.availableSymbols;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// Currency Conversion Utilities
|
|
96
|
+
// =============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert human-readable amount to smallest unit string
|
|
100
|
+
* @example toSmallestUnit("1.5", 18) => "1500000000000000000"
|
|
101
|
+
*/
|
|
102
|
+
export function toSmallestUnit(amount: number | string, decimals: number): string {
|
|
103
|
+
if (!amount) return "0";
|
|
104
|
+
|
|
105
|
+
const str = amount.toString();
|
|
106
|
+
const [integer, fraction = ""] = str.split(".");
|
|
107
|
+
|
|
108
|
+
// Pad fraction to exact decimal places, truncate if longer
|
|
109
|
+
const paddedFraction = fraction.padEnd(decimals, "0").slice(0, decimals);
|
|
110
|
+
|
|
111
|
+
// Remove leading zeros but keep at least one digit
|
|
112
|
+
const result = (integer + paddedFraction).replace(/^0+/, "") || "0";
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert smallest unit string to human-readable amount
|
|
118
|
+
* @example toHumanReadable("1500000000000000000", 18) => "1.5"
|
|
119
|
+
*/
|
|
120
|
+
export function toHumanReadable(amount: string, decimals: number): string {
|
|
121
|
+
if (!amount || amount === "0") return "0";
|
|
122
|
+
|
|
123
|
+
const str = amount.padStart(decimals + 1, "0");
|
|
124
|
+
const integer = str.slice(0, -decimals) || "0";
|
|
125
|
+
const fraction = str.slice(-decimals).replace(/0+$/, "");
|
|
126
|
+
|
|
127
|
+
return fraction ? `${integer}.${fraction}` : integer;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Format amount for display with symbol
|
|
132
|
+
* @param amount Amount in smallest units
|
|
133
|
+
* @param coinName Faucet coin name (e.g., "unicity")
|
|
134
|
+
*/
|
|
135
|
+
export function formatAmount(amount: string, coinName: string): string {
|
|
136
|
+
const decimals = getCoinDecimals(coinName) ?? 0;
|
|
137
|
+
const symbol = getCoinSymbol(coinName);
|
|
138
|
+
const readable = toHumanReadable(amount, decimals);
|
|
139
|
+
return `${readable} ${symbol}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Parse user input amount to smallest units for a given coin
|
|
144
|
+
* @param amount Human-readable amount (e.g., "100" or "1.5")
|
|
145
|
+
* @param coinName Faucet coin name (e.g., "unicity")
|
|
146
|
+
*/
|
|
147
|
+
export function parseAmount(amount: number | string, coinName: string): string {
|
|
148
|
+
const decimals = getCoinDecimals(coinName) ?? 0;
|
|
149
|
+
return toSmallestUnit(amount, decimals);
|
|
150
|
+
}
|