Skip to content

Commit 67c9a7a

Browse files
v2.3.0
1 parent e3ba02a commit 67c9a7a

11 files changed

Lines changed: 49 additions & 45 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# MultiClaw v2.2.2
1+
# MultiClaw v2.3.0
22

33
![Tests](https://github.com/primer-systems/multiclaw/actions/workflows/test.yml/badge.svg)
44

@@ -251,6 +251,10 @@ pip install multiclaw # CLI only
251251

252252
## Changelog
253253

254+
### v2.3.0
255+
- **Fix:** Amount display now uses 6 decimals consistently (sub-cent payments no longer show as 0.00)
256+
- **Fix:** Removed redundant "$" prefix from amount displays (shows "0.002000 USDC" instead of "$0.00 USDC")
257+
254258
### v2.2.2
255259
- **Fix:** Windows taskbar now shows MultiClaw icon instead of Python icon (pip install)
256260
- **Docs:** SKILL.md updated to v2-first format (CAIP-2 networks, `PAYMENT-SIGNATURE` header)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "multiclaw"
7-
version = "2.2.2"
7+
version = "2.3.0"
88
description = "Desktop x402 payment manager for AI agents"
99
readme = "README.md"
1010
requires-python = ">=3.11"

src/multiclaw/commands/policy.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _list(self) -> CommandResult:
8686
daily = policy.daily_limit_micro / 1_000_000
8787
per_req = policy.per_request_max_micro / 1_000_000
8888
auto = policy.auto_approve_below_micro / 1_000_000 if policy.auto_approve_below_micro else 0
89-
lines.append(f" {policy.name} daily: ${daily:.2f} max: ${per_req:.2f} auto: ${auto:.2f}")
89+
lines.append(f" {policy.name} daily: ${daily:.6f} max: ${per_req:.6f} auto: ${auto:.6f}")
9090

9191
return CommandResult.ok("\n".join(lines), data={"policies": [
9292
{
@@ -120,9 +120,9 @@ def _show(self, identifier: str) -> CommandResult:
120120

121121
lines = [
122122
f"Policy: {policy.name}",
123-
f" Daily Limit: ${daily:.2f}",
124-
f" Per Request Max: ${per_req:.2f}",
125-
f" Auto-approve: ${auto:.2f}",
123+
f" Daily Limit: ${daily:.6f}",
124+
f" Per Request Max: ${per_req:.6f}",
125+
f" Auto-approve: ${auto:.6f}",
126126
f" Allowed Domains: {', '.join(policy.allowed_domains) if policy.allowed_domains else 'All'}",
127127
f" Blocked Domains: {', '.join(policy.blocked_domains) if policy.blocked_domains else 'None'}",
128128
f" Networks: {', '.join(str(n) for n in policy.networks) if policy.networks else 'All'}",
@@ -271,7 +271,7 @@ def _edit(self, args: list[str]) -> CommandResult:
271271
if value < 0:
272272
return CommandResult.fail(f"Daily limit cannot be negative: {args[i + 1]}")
273273
policy.daily_limit_micro = int(value * 1_000_000)
274-
changes.append(f"daily limit: ${value:.2f}")
274+
changes.append(f"daily limit: ${value:.6f}")
275275
except ValueError:
276276
return CommandResult.fail(f"Invalid value for --day: {args[i + 1]}")
277277
i += 2
@@ -281,7 +281,7 @@ def _edit(self, args: list[str]) -> CommandResult:
281281
if value < 0:
282282
return CommandResult.fail(f"Per-transaction max cannot be negative: {args[i + 1]}")
283283
policy.per_request_max_micro = int(value * 1_000_000)
284-
changes.append(f"per-txn max: ${value:.2f}")
284+
changes.append(f"per-txn max: ${value:.6f}")
285285
except ValueError:
286286
return CommandResult.fail(f"Invalid value for --txn: {args[i + 1]}")
287287
i += 2
@@ -291,7 +291,7 @@ def _edit(self, args: list[str]) -> CommandResult:
291291
if value < 0:
292292
return CommandResult.fail(f"Auto-approve threshold cannot be negative: {args[i + 1]}")
293293
policy.auto_approve_below_micro = int(value * 1_000_000)
294-
changes.append(f"auto-approve: ${value:.2f}")
294+
changes.append(f"auto-approve: ${value:.6f}")
295295
except ValueError:
296296
return CommandResult.fail(f"Invalid value for --auto: {args[i + 1]}")
297297
i += 2

src/multiclaw/models/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,8 @@ def from_dict(cls, data: dict) -> "Agent":
437437
return cls(**data)
438438

439439
def format_spent_today(self) -> str:
440-
"""Format today's spending as dollars with USDC indicator."""
441-
return f"${self.spent_today_micro / 1_000_000:.2f} USDC"
440+
"""Format today's spending as USDC with 6 decimal precision."""
441+
return f"{self.spent_today_micro / 1_000_000:.6f} USDC"
442442

443443
def decrypt_auth_key(self, password: str) -> str:
444444
"""

src/multiclaw/models/policy.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,18 +141,18 @@ def _domain_matches(self, domain_entry: str, host: str) -> bool:
141141
return host == entry or host.endswith("." + entry)
142142

143143
def format_daily_limit(self) -> str:
144-
"""Format daily limit as dollars with USDC indicator."""
145-
return f"${self.daily_limit_micro / 1_000_000:.2f} USDC"
144+
"""Format daily limit as USDC with 6 decimal precision."""
145+
return f"{self.daily_limit_micro / 1_000_000:.6f} USDC"
146146

147147
def format_per_request_max(self) -> str:
148-
"""Format per-request max as dollars with USDC indicator."""
149-
return f"${self.per_request_max_micro / 1_000_000:.2f} USDC"
148+
"""Format per-request max as USDC with 6 decimal precision."""
149+
return f"{self.per_request_max_micro / 1_000_000:.6f} USDC"
150150

151151
def format_auto_approve(self) -> str:
152-
"""Format auto-approve threshold as dollars with USDC indicator."""
152+
"""Format auto-approve threshold as USDC with 6 decimal precision."""
153153
if self.auto_approve_below_micro is None:
154154
return "—"
155-
return f"${self.auto_approve_below_micro / 1_000_000:.2f} USDC"
155+
return f"{self.auto_approve_below_micro / 1_000_000:.6f} USDC"
156156

157157
def format_domain_restrictions(self) -> str:
158158
"""Format domain restrictions for display."""

src/multiclaw/models/transaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ def from_dict(cls, data: dict) -> "Transaction":
142142
return cls(**data)
143143

144144
def format_amount(self) -> str:
145-
"""Format amount as dollars with USDC indicator."""
146-
return f"${self.amount_micro / 1_000_000:.2f} USDC"
145+
"""Format amount as USDC with 6 decimal precision."""
146+
return f"{self.amount_micro / 1_000_000:.6f} USDC"
147147

148148
def format_amount_precise(self) -> str:
149149
"""Format amount with full 6-decimal precision for formal documents."""

src/multiclaw/services/signing.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ def _check_daily_reset(self, agent: "Agent") -> bool:
413413
if self._policy_store:
414414
self._policy_store.update_agent(agent)
415415
if old_spent > 0:
416-
logger.info(f"Reset daily spending for agent {agent.id}: was ${old_spent/1_000_000:.2f}")
416+
logger.info(f"Reset daily spending for agent {agent.id}: was {old_spent/1_000_000:.6f} USDC")
417417
return True
418418
return False
419419

@@ -1263,7 +1263,7 @@ def handle_sign_request(
12631263
}
12641264

12651265
if policy.per_request_max_micro and amount_micro > policy.per_request_max_micro:
1266-
reason = f"Exceeds per-request maximum (${amount_micro/1_000_000:.2f} > ${policy.per_request_max_micro/1_000_000:.2f} USDC)"
1266+
reason = f"Exceeds per-request maximum ({amount_micro/1_000_000:.6f} > {policy.per_request_max_micro/1_000_000:.6f} USDC)"
12671267
self._emit_activity(f"Request from {agent.name}: {reason}", True)
12681268
server_stats.rejected += 1
12691269
# Create rejection receipt for audit trail
@@ -1283,7 +1283,7 @@ def handle_sign_request(
12831283
if policy.daily_limit_micro:
12841284
remaining = policy.daily_limit_micro - agent.spent_today_micro
12851285
if amount_micro > remaining:
1286-
reason = f"Would exceed daily limit (${amount_micro/1_000_000:.2f} > ${remaining/1_000_000:.2f} USDC remaining)"
1286+
reason = f"Would exceed daily limit ({amount_micro/1_000_000:.6f} > {remaining/1_000_000:.6f} USDC remaining)"
12871287
self._emit_activity(f"Request from {agent.name}: {reason}", True)
12881288
server_stats.rejected += 1
12891289
# Create rejection receipt for audit trail
@@ -1327,7 +1327,7 @@ def handle_sign_request(
13271327
self._pending_requests[request_id] = request
13281328

13291329
self._emit_activity(
1330-
f"Payment request from {agent.name}: ${amount_micro/1_000_000:.2f} USDC - awaiting approval",
1330+
f"Payment request from {agent.name}: {amount_micro/1_000_000:.6f} USDC - awaiting approval",
13311331
False
13321332
)
13331333
self._emit_approval_needed(request)
@@ -1388,7 +1388,7 @@ def approve_request(self, request_id: str) -> dict:
13881388
del self._pending_requests[request_id]
13891389
return {
13901390
"status": "error",
1391-
"error": f"Amount ${request.amount_micro/1_000_000:.2f} exceeds current per-request limit ${policy.per_request_max_micro/1_000_000:.2f}",
1391+
"error": f"Amount {request.amount_micro/1_000_000:.6f} USDC exceeds current per-request limit {policy.per_request_max_micro/1_000_000:.6f} USDC",
13921392
"code": "EXCEEDS_PER_REQUEST_MAX"
13931393
}
13941394

@@ -1398,7 +1398,7 @@ def approve_request(self, request_id: str) -> dict:
13981398
del self._pending_requests[request_id]
13991399
return {
14001400
"status": "error",
1401-
"error": f"Amount ${request.amount_micro/1_000_000:.2f} exceeds remaining daily limit ${remaining/1_000_000:.2f}",
1401+
"error": f"Amount {request.amount_micro/1_000_000:.6f} USDC exceeds remaining daily limit {remaining/1_000_000:.6f} USDC",
14021402
"code": "EXCEEDS_DAILY_LIMIT"
14031403
}
14041404

@@ -1661,7 +1661,7 @@ def _sign_payment(
16611661
self._emit_transaction_updated(tx.id)
16621662

16631663
self._emit_activity(
1664-
f"Signed ${amount_micro/1_000_000:.2f} USDC payment for {agent.name} ({agent.id}) from {wallet_id}",
1664+
f"Signed {amount_micro/1_000_000:.6f} USDC payment for {agent.name} ({agent.id}) from {wallet_id}",
16651665
False
16661666
)
16671667
self._emit_request_signed(agent.name, agent.id, wallet_id, amount_micro)

src/multiclaw/ui/dialogs.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,9 +1829,9 @@ def __init__(self, agent: Agent, current_policy: Optional[SpendPolicy] = None, p
18291829
daily = limits.get('dailyLimit') or 0
18301830
per_req = limits.get('perRequestMax') or 0
18311831
auto = limits.get('autoApproveBelow') # Can be None for manual-only
1832-
auth_layout.addRow("Daily Limit:", QLabel(f"${daily/divisor:.2f} {currency}"))
1833-
auth_layout.addRow("Per Request Max:", QLabel(f"${per_req/divisor:.2f} {currency}"))
1834-
auto_text = f"${auto/divisor:.2f} {currency}" if auto is not None else "Manual only"
1832+
auth_layout.addRow("Daily Limit:", QLabel(f"{daily/divisor:.6f} {currency}"))
1833+
auth_layout.addRow("Per Request Max:", QLabel(f"{per_req/divisor:.6f} {currency}"))
1834+
auto_text = f"{auto/divisor:.6f} {currency}" if auto is not None else "Manual only"
18351835
auth_layout.addRow("Auto-approve Below:", QLabel(auto_text))
18361836

18371837
networks = auth.get('networks', [])
@@ -2301,7 +2301,7 @@ def __init__(
23012301
addr_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
23022302
source_layout.addRow("Address:", addr_label)
23032303

2304-
balance_label = QLabel(f"${balance:.2f} USDC")
2304+
balance_label = QLabel(f"{balance:.6f} USDC")
23052305
balance_label.setStyleSheet("font-weight: bold;")
23062306
source_layout.addRow("Balance:", balance_label)
23072307

@@ -2429,7 +2429,7 @@ def _execute_withdraw(self):
24292429
confirm = QMessageBox.question(
24302430
self,
24312431
"Confirm Transfer",
2432-
f"Send ${amount:.2f} USDC to:\n\n"
2432+
f"Send {amount:.6f} USDC to:\n\n"
24332433
f"{dest[:20]}...{dest[-8:]}\n\n"
24342434
"This will use gas from the source wallet.\n"
24352435
"Continue?",
@@ -2539,7 +2539,7 @@ def _execute_withdraw(self):
25392539
self,
25402540
"Transaction Sent",
25412541
f"USDC transfer submitted!\n\n"
2542-
f"Amount: ${amount:.2f} USDC\n"
2542+
f"Amount: {amount:.6f} USDC\n"
25432543
f"To: {dest[:16]}...{dest[-8:]}\n\n"
25442544
f"Transaction: {tx_hash_hex[:16]}...\n\n"
25452545
f"View on explorer:\n{explorer_url}"
@@ -2656,7 +2656,7 @@ def _setup_ui(self):
26562656

26572657
btn_row.addStretch()
26582658

2659-
self.total_label = QLabel("Selected: $0.00 USDC")
2659+
self.total_label = QLabel("Selected: 0.000000 USDC")
26602660
self.total_label.setStyleSheet(f"font-weight: bold; color: {Theme.LIME_DIM};")
26612661
btn_row.addWidget(self.total_label)
26622662

@@ -2773,10 +2773,10 @@ def _refresh_balances(self):
27732773

27742774
# Donor list - show name + USDC balance or "--" if failed
27752775
if usdc_failed:
2776-
item = QListWidgetItem(f"{short_addr}{name_display} $-- USDC (RPC error)")
2776+
item = QListWidgetItem(f"{short_addr}{name_display} -- USDC (RPC error)")
27772777
item.setForeground(Qt.GlobalColor.red)
27782778
else:
2779-
item = QListWidgetItem(f"{short_addr}{name_display} ${usdc_bal:.6f} USDC")
2779+
item = QListWidgetItem(f"{short_addr}{name_display} {usdc_bal:.6f} USDC")
27802780
# Gray out if confirmed zero
27812781
if usdc_bal == 0:
27822782
item.setForeground(Qt.GlobalColor.gray)
@@ -2802,7 +2802,7 @@ def _refresh_balances(self):
28022802
'usdc_failed': True,
28032803
}
28042804
# Add to donor list with error indicator
2805-
item = QListWidgetItem(f"{short_addr}{name_display} $-- USDC (RPC error)")
2805+
item = QListWidgetItem(f"{short_addr}{name_display} -- USDC (RPC error)")
28062806
item.setData(Qt.ItemDataRole.UserRole, address)
28072807
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
28082808
item.setCheckState(Qt.CheckState.Unchecked)
@@ -2860,8 +2860,8 @@ def _update_totals(self):
28602860
total += self._balances[addr]['usdc']
28612861
count += 1
28622862

2863-
self.total_label.setText(f"Selected: {count} addresses, ${total:.6f} USDC")
2864-
self.sweep_btn.setText(f"Sweep ${total:.2f} USDC")
2863+
self.total_label.setText(f"Selected: {count} addresses, {total:.6f} USDC")
2864+
self.sweep_btn.setText(f"Sweep {total:.6f} USDC")
28652865
self.sweep_btn.setEnabled(count > 0 and total > 0)
28662866

28672867
def _get_selected_donors(self) -> list:
@@ -2914,7 +2914,7 @@ def _execute_sweep(self):
29142914
confirm = QMessageBox.question(
29152915
self,
29162916
"Confirm Sweep",
2917-
f"Sweep ${total_usdc:.6f} USDC from {len(donors)} address(es)\n\n"
2917+
f"Sweep {total_usdc:.6f} USDC from {len(donors)} address(es)\n\n"
29182918
f"To: {recipient_addr[:16]}...{recipient_addr[-8:]}\n"
29192919
f"Gas paid by: {sponsor_addr[:16]}...{sponsor_addr[-8:]}\n\n"
29202920
"This will execute multiple transactions. Continue?",

src/multiclaw/ui/main_window.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ def _apply_core_settings(self):
648648
def on_approval_needed(self, request: SigningRequest):
649649
"""Handle a signing request that needs manual approval."""
650650
self.update_activity(
651-
f"Approval needed: {request.agent_name} ({request.agent_id}) requests ${request.amount_micro/1_000_000:.2f} USDC",
651+
f"Approval needed: {request.agent_name} ({request.agent_id}) requests {request.amount_micro/1_000_000:.6f} USDC",
652652
is_warning=True
653653
)
654654

@@ -657,7 +657,7 @@ def on_approval_needed(self, request: SigningRequest):
657657
if hasattr(self, 'tray') and self.tray.isVisible():
658658
self.tray.showMessage(
659659
"Payment Approval Required",
660-
f"{request.agent_name} is requesting ${request.amount_micro/1_000_000:.2f} USDC",
660+
f"{request.agent_name} is requesting {request.amount_micro/1_000_000:.6f} USDC",
661661
QSystemTrayIcon.MessageIcon.Information,
662662
5000
663663
)
@@ -686,7 +686,7 @@ def show_approval_dialog(self, request: SigningRequest):
686686
self.activateWindow()
687687
self.raise_()
688688

689-
amount_str = f"${request.amount_micro/1_000_000:.2f} USDC"
689+
amount_str = f"{request.amount_micro/1_000_000:.6f} USDC"
690690
# Prefer request_url (full URL) over resource (often path-only)
691691
if request.request_url:
692692
resource_str = f"\nURL: {request.request_url}"

src/multiclaw/ui/tabs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,14 +1964,14 @@ def on_balance_updated(self, address: str, balances: dict):
19641964
if addr_item and addr_item.data(Qt.ItemDataRole.UserRole) == address:
19651965
balance_item = self.table.item(row, 3)
19661966
if balance_item:
1967-
balance_item.setText(f"${usdc_balance:.2f}")
1967+
balance_item.setText(f"${usdc_balance:.6f}")
19681968
# Store full precision balance for withdrawal dialog
19691969
balance_item.setData(Qt.ItemDataRole.UserRole, usdc_balance)
19701970
break
19711971

19721972
network_name = NETWORKS.get(selected_chain, {})
19731973
network_display = network_name.display_name if hasattr(network_name, 'display_name') else str(selected_chain)
1974-
self.activity.emit(f"Balance: {format_address(address)} = ${usdc_balance:.2f} USDC ({network_display})", False)
1974+
self.activity.emit(f"Balance: {format_address(address)} = {usdc_balance:.6f} USDC ({network_display})", False)
19751975

19761976
def _on_network_changed(self, index: int):
19771977
"""Handle network dropdown selection change."""

0 commit comments

Comments
 (0)