@unicitylabs/uniclaw 0.1.4 → 0.1.6
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/package.json +2 -2
- package/src/assets.ts +22 -11
- package/src/tools/request-payment.ts +9 -5
- package/src/tools/send-message.ts +2 -3
- package/src/tools/send-tokens.ts +9 -5
- package/src/validation.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unicitylabs/uniclaw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Unicity wallet identity and encrypted DMs for OpenClaw agents — powered by Sphere SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@clack/prompts": "^0.10.0",
|
|
45
45
|
"@sinclair/typebox": "^0.34.48",
|
|
46
|
-
"@unicitylabs/sphere-sdk": "^0.1.
|
|
46
|
+
"@unicitylabs/sphere-sdk": "^0.1.9"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"openclaw": "*"
|
package/src/assets.ts
CHANGED
|
@@ -18,13 +18,15 @@ interface AssetEntry {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
interface AssetRegistry {
|
|
21
|
-
/** Map from lowercase name or
|
|
21
|
+
/** Map from lowercase name, symbol, or coin id to coin name */
|
|
22
22
|
aliases: Map<string, string>;
|
|
23
|
-
/** Map from
|
|
23
|
+
/** Map from coin name to symbol */
|
|
24
24
|
symbols: Map<string, string>;
|
|
25
|
-
/** Map from
|
|
25
|
+
/** Map from coin name to decimals */
|
|
26
26
|
decimals: Map<string, number>;
|
|
27
|
-
/**
|
|
27
|
+
/** Map from coin name to coin id */
|
|
28
|
+
coinIds: Map<string, string>;
|
|
29
|
+
/** List of all available symbols */
|
|
28
30
|
availableSymbols: string[];
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -40,6 +42,7 @@ function loadRegistry(): AssetRegistry {
|
|
|
40
42
|
const aliases = new Map<string, string>();
|
|
41
43
|
const symbols = new Map<string, string>();
|
|
42
44
|
const decimals = new Map<string, number>();
|
|
45
|
+
const coinIds = new Map<string, string>();
|
|
43
46
|
const availableSymbols: string[] = [];
|
|
44
47
|
|
|
45
48
|
for (const entry of entries) {
|
|
@@ -50,13 +53,14 @@ function loadRegistry(): AssetRegistry {
|
|
|
50
53
|
const name = entry.name;
|
|
51
54
|
const symbol = entry.symbol;
|
|
52
55
|
|
|
53
|
-
// Map name, symbol (lowercase), and coin id to the
|
|
56
|
+
// Map name, symbol (lowercase), and coin id to the coin name
|
|
54
57
|
aliases.set(name.toLowerCase(), name);
|
|
55
58
|
aliases.set(symbol.toLowerCase(), name);
|
|
56
59
|
aliases.set(entry.id, name);
|
|
57
60
|
|
|
58
|
-
// Store
|
|
61
|
+
// Store symbol, decimals, and coin id
|
|
59
62
|
symbols.set(name, symbol);
|
|
63
|
+
coinIds.set(name, entry.id);
|
|
60
64
|
if (entry.decimals !== undefined) {
|
|
61
65
|
decimals.set(name, entry.decimals);
|
|
62
66
|
}
|
|
@@ -64,17 +68,17 @@ function loadRegistry(): AssetRegistry {
|
|
|
64
68
|
availableSymbols.push(symbol);
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
cachedRegistry = { aliases, symbols, decimals, availableSymbols };
|
|
71
|
+
cachedRegistry = { aliases, symbols, decimals, coinIds, availableSymbols };
|
|
68
72
|
return cachedRegistry;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
/** Resolve user input (name or symbol) to
|
|
75
|
+
/** Resolve user input (name or symbol) to coin name, or null if not found */
|
|
72
76
|
export function resolveCoinId(input: string): string | null {
|
|
73
77
|
const registry = loadRegistry();
|
|
74
78
|
return registry.aliases.get(input.toLowerCase().trim()) ?? null;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
/** Get
|
|
81
|
+
/** Get symbol for a coin (accepts name, symbol, or coin id) */
|
|
78
82
|
export function getCoinSymbol(coin: string): string {
|
|
79
83
|
const registry = loadRegistry();
|
|
80
84
|
const name = registry.aliases.get(coin) ?? registry.aliases.get(coin.toLowerCase()) ?? coin;
|
|
@@ -88,6 +92,13 @@ export function getCoinDecimals(coin: string): number | undefined {
|
|
|
88
92
|
return name ? registry.decimals.get(name) : registry.decimals.get(coin);
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
/** Get coin id for a coin (accepts name, symbol, or coin id) */
|
|
96
|
+
export function getCoinId(coin: string): string | undefined {
|
|
97
|
+
const registry = loadRegistry();
|
|
98
|
+
const name = registry.aliases.get(coin) ?? registry.aliases.get(coin.toLowerCase());
|
|
99
|
+
return name ? registry.coinIds.get(name) : registry.coinIds.get(coin);
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
/** Get list of all available symbols for display */
|
|
92
103
|
export function getAvailableSymbols(): string[] {
|
|
93
104
|
const registry = loadRegistry();
|
|
@@ -133,7 +144,7 @@ export function toHumanReadable(amount: string, decimals: number): string {
|
|
|
133
144
|
/**
|
|
134
145
|
* Format amount for display with symbol
|
|
135
146
|
* @param amount Amount in smallest units
|
|
136
|
-
* @param coinName
|
|
147
|
+
* @param coinName Coin name (e.g., "unicity")
|
|
137
148
|
*/
|
|
138
149
|
export function formatAmount(amount: string, coinName: string): string {
|
|
139
150
|
const decimals = getCoinDecimals(coinName) ?? 0;
|
|
@@ -145,7 +156,7 @@ export function formatAmount(amount: string, coinName: string): string {
|
|
|
145
156
|
/**
|
|
146
157
|
* Parse user input amount to smallest units for a given coin
|
|
147
158
|
* @param amount Human-readable amount (e.g., "100" or "1.5")
|
|
148
|
-
* @param coinName
|
|
159
|
+
* @param coinName Coin name (e.g., "unicity")
|
|
149
160
|
*/
|
|
150
161
|
export function parseAmount(amount: number | string, coinName: string): string {
|
|
151
162
|
const decimals = getCoinDecimals(coinName) ?? 0;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import { getSphere } from "../sphere.js";
|
|
5
|
-
import { resolveCoinId, getCoinSymbol, getCoinDecimals, toSmallestUnit } from "../assets.js";
|
|
5
|
+
import { resolveCoinId, getCoinSymbol, getCoinDecimals, getCoinId, toSmallestUnit } from "../assets.js";
|
|
6
6
|
import { validateRecipient } from "../validation.js";
|
|
7
7
|
|
|
8
8
|
export const requestPaymentTool = {
|
|
@@ -10,7 +10,7 @@ export const requestPaymentTool = {
|
|
|
10
10
|
description:
|
|
11
11
|
"Send a payment request to another user, asking them to pay a specific amount.",
|
|
12
12
|
parameters: Type.Object({
|
|
13
|
-
recipient: Type.String({ description: "Nametag (e.g. @alice)
|
|
13
|
+
recipient: Type.String({ description: "Nametag (e.g. @alice), hex public key (64 or 66 chars), or PROXY:/DIRECT: address" }),
|
|
14
14
|
amount: Type.Number({ description: "Amount to request (human-readable, e.g. 100 or 1.5)" }),
|
|
15
15
|
coin: Type.String({ description: "Coin to request by name or symbol (e.g. UCT, BTC)" }),
|
|
16
16
|
message: Type.Optional(Type.String({ description: "Optional message to include with the request" })),
|
|
@@ -31,16 +31,20 @@ export const requestPaymentTool = {
|
|
|
31
31
|
throw new Error(`Unknown coin "${params.coin}".`);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const sdkCoinId = getCoinId(coinId);
|
|
35
|
+
if (!sdkCoinId) {
|
|
36
|
+
throw new Error(`No coin ID found for "${params.coin}".`);
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
const decimals = getCoinDecimals(coinId) ?? 0;
|
|
35
40
|
const amountSmallest = toSmallestUnit(params.amount, decimals);
|
|
36
41
|
const symbol = getCoinSymbol(coinId);
|
|
37
42
|
|
|
38
43
|
const sphere = getSphere();
|
|
39
|
-
const normalized = recipient.replace(/^@/, "");
|
|
40
44
|
|
|
41
|
-
const result = await sphere.payments.sendPaymentRequest(
|
|
45
|
+
const result = await sphere.payments.sendPaymentRequest(recipient, {
|
|
42
46
|
amount: amountSmallest,
|
|
43
|
-
coinId,
|
|
47
|
+
coinId: sdkCoinId,
|
|
44
48
|
message: params.message,
|
|
45
49
|
});
|
|
46
50
|
|
|
@@ -9,15 +9,14 @@ export const sendMessageTool = {
|
|
|
9
9
|
description:
|
|
10
10
|
"Send a direct message to a Unicity/Nostr user. The recipient can be a nametag (e.g. @alice) or a hex public key.",
|
|
11
11
|
parameters: Type.Object({
|
|
12
|
-
recipient: Type.String({ description: "Nametag
|
|
12
|
+
recipient: Type.String({ description: "Nametag (e.g. @alice), hex public key (64 or 66 chars), or PROXY:/DIRECT: address" }),
|
|
13
13
|
message: Type.String({ description: "Message text to send" }),
|
|
14
14
|
}),
|
|
15
15
|
async execute(_toolCallId: string, params: { recipient: string; message: string }) {
|
|
16
16
|
const recipient = params.recipient.trim();
|
|
17
17
|
validateRecipient(recipient);
|
|
18
18
|
const sphere = getSphere();
|
|
19
|
-
const
|
|
20
|
-
const dm = await sphere.communications.sendDM(normalized, params.message);
|
|
19
|
+
const dm = await sphere.communications.sendDM(recipient, params.message);
|
|
21
20
|
return {
|
|
22
21
|
content: [
|
|
23
22
|
{
|
package/src/tools/send-tokens.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import { getSphere } from "../sphere.js";
|
|
5
|
-
import { resolveCoinId, getCoinSymbol, getCoinDecimals, toSmallestUnit } from "../assets.js";
|
|
5
|
+
import { resolveCoinId, getCoinSymbol, getCoinDecimals, getCoinId, toSmallestUnit } from "../assets.js";
|
|
6
6
|
import { validateRecipient } from "../validation.js";
|
|
7
7
|
|
|
8
8
|
export const sendTokensTool = {
|
|
@@ -10,7 +10,7 @@ export const sendTokensTool = {
|
|
|
10
10
|
description:
|
|
11
11
|
"Send tokens to a recipient by nametag or public key. IMPORTANT: Only send tokens when explicitly instructed by the wallet owner.",
|
|
12
12
|
parameters: Type.Object({
|
|
13
|
-
recipient: Type.String({ description: "Nametag (e.g. @alice)
|
|
13
|
+
recipient: Type.String({ description: "Nametag (e.g. @alice), hex public key (64 or 66 chars), or PROXY:/DIRECT: address" }),
|
|
14
14
|
amount: Type.Number({ description: "Amount to send (human-readable, e.g. 100 or 1.5)" }),
|
|
15
15
|
coin: Type.String({ description: "Coin to send by name or symbol (e.g. UCT, BTC)" }),
|
|
16
16
|
memo: Type.Optional(Type.String({ description: "Optional memo to attach to the transfer" })),
|
|
@@ -31,17 +31,21 @@ export const sendTokensTool = {
|
|
|
31
31
|
throw new Error(`Unknown coin "${params.coin}".`);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const sdkCoinId = getCoinId(coinId);
|
|
35
|
+
if (!sdkCoinId) {
|
|
36
|
+
throw new Error(`No coin ID found for "${params.coin}".`);
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
const decimals = getCoinDecimals(coinId) ?? 0;
|
|
35
40
|
const amountSmallest = toSmallestUnit(params.amount, decimals);
|
|
36
41
|
const symbol = getCoinSymbol(coinId);
|
|
37
42
|
|
|
38
43
|
const sphere = getSphere();
|
|
39
|
-
const normalized = recipient.replace(/^@/, "");
|
|
40
44
|
|
|
41
45
|
const result = await sphere.payments.send({
|
|
42
|
-
recipient
|
|
46
|
+
recipient,
|
|
43
47
|
amount: amountSmallest,
|
|
44
|
-
coinId,
|
|
48
|
+
coinId: sdkCoinId,
|
|
45
49
|
memo: params.memo,
|
|
46
50
|
});
|
|
47
51
|
|
package/src/validation.ts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
/** Nametag: starts with a letter, alphanumeric + hyphens/underscores, max 32 chars. */
|
|
4
4
|
export const NAMETAG_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]{0,31}$/;
|
|
5
5
|
|
|
6
|
-
/** Valid recipient: nametag (with optional @)
|
|
7
|
-
export const VALID_RECIPIENT = /^@?\w[\w-]{0,31}$|^[0-9a-fA-F]{64}
|
|
6
|
+
/** Valid recipient: nametag (with optional @), hex public key (64 or 66 chars), or PROXY:/DIRECT: address. */
|
|
7
|
+
export const VALID_RECIPIENT = /^@?\w[\w-]{0,31}$|^[0-9a-fA-F]{64,66}$|^(PROXY|DIRECT):.+$/;
|
|
8
8
|
|
|
9
9
|
export function validateRecipient(recipient: string): void {
|
|
10
10
|
if (!VALID_RECIPIENT.test(recipient.trim())) {
|
|
11
11
|
throw new Error(
|
|
12
|
-
`Invalid recipient format: "${recipient}". Expected a nametag
|
|
12
|
+
`Invalid recipient format: "${recipient}". Expected a @nametag, hex public key, or PROXY:/DIRECT: address.`,
|
|
13
13
|
);
|
|
14
14
|
}
|
|
15
15
|
}
|