diff --git a/README.md b/README.md
index efae697..a544e41 100755
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ A PHP-based Telegram tip bot for the Salvium (Monero fork) cryptocurrency.
## Commands
- `/withdraw
` withdraws `SAL1`
- `/withdraw ` withdraws `SAL1` or a token asset, for example `CULT`/`salCULT`
-- `/unsweep ` admin-only command that self-transfers unlocked wallet funds into multiple smaller outputs
+- `/unsweep [amount]` admin-only command that self-transfers unlocked wallet funds into multiple smaller outputs
## Withdrawal fees
- `WITHDRAWAL_FEE` is charged in `SAL1` for `SAL1` withdrawals.
diff --git a/src/salvium_tipbot_commands.php b/src/salvium_tipbot_commands.php
index 519b7d6..abf4bf8 100644
--- a/src/salvium_tipbot_commands.php
+++ b/src/salvium_tipbot_commands.php
@@ -266,9 +266,9 @@ class SalviumTipBotCommands {
}
try {
- [$assetType, $outputCount] = $this->parseUnsweepArgs($args);
+ [$assetType, $outputCount, $requestedAmountAtomic] = $this->parseUnsweepArgs($args);
} catch (Throwable $e) {
- return "Usage: /unsweep ";
+ return "Usage: /unsweep [amount]";
}
$maxOutputs = max(2, (int)($this->config['MAX_UNSWEEP_OUTPUTS'] ?? 100));
@@ -315,7 +315,11 @@ class SalviumTipBotCommands {
$reserveAtomic = $this->humanToAtomic($this->config['UNSWEEP_SAL1_FEE_RESERVE'] ?? 0.5);
}
- $spendableAtomic = $unlockedAtomic - $reserveAtomic;
+ $spendableAtomic = $requestedAmountAtomic ?? ($unlockedAtomic - $reserveAtomic);
+ if ($requestedAmountAtomic !== null && $spendableAtomic > ($unlockedAtomic - $reserveAtomic)) {
+ return "Requested amount exceeds spendable unlocked {$assetType}. Max is " . $this->atomicToHuman(max(0, $unlockedAtomic - $reserveAtomic)) . " {$assetType}.";
+ }
+
if ($spendableAtomic <= 0) {
return "No spendable {$assetType} after fee reserve of " . $this->atomicToHuman($reserveAtomic) . " {$assetType}.";
}
@@ -346,8 +350,9 @@ class SalviumTipBotCommands {
$totalAtomic = $amountPerOutputAtomic * $outputCount;
$reserveText = $reserveAtomic > 0 ? " Reserved " . $this->atomicToHuman($reserveAtomic) . " {$assetType} for fee/change." : "";
+ $sourceText = $requestedAmountAtomic !== null ? " Requested " . $this->atomicToHuman($requestedAmountAtomic) . " {$assetType}." : "";
return "Unswept " . $this->atomicToHuman($totalAtomic) . " {$assetType} into {$outputCount} outputs of "
- . $this->atomicToHuman($amountPerOutputAtomic) . " {$assetType}.{$reserveText} "
+ . $this->atomicToHuman($amountPerOutputAtomic) . " {$assetType}.{$sourceText}{$reserveText} "
. $this->formatTxids($txids);
}
@@ -492,7 +497,7 @@ class SalviumTipBotCommands {
}
private function parseUnsweepArgs(array $args): array {
- if (count($args) !== 3 || !$this->looksLikeAssetType($args[1]) || !ctype_digit((string)$args[2])) {
+ if (!in_array(count($args), [3, 4], true) || !$this->looksLikeAssetType($args[1]) || !ctype_digit((string)$args[2])) {
throw new InvalidArgumentException("Invalid /unsweep arguments");
}
@@ -501,7 +506,18 @@ class SalviumTipBotCommands {
throw new InvalidArgumentException("Invalid /unsweep output count");
}
- return [$this->parseAssetType($args[1]), $outputCount];
+ $requestedAmountAtomic = null;
+ if (count($args) === 4) {
+ if (!is_numeric($args[3]) || (float)$args[3] <= 0) {
+ throw new InvalidArgumentException("Invalid /unsweep amount");
+ }
+ $requestedAmountAtomic = $this->humanToAtomic($args[3]);
+ if ($requestedAmountAtomic <= 0) {
+ throw new InvalidArgumentException("Invalid /unsweep amount");
+ }
+ }
+
+ return [$this->parseAssetType($args[1]), $outputCount, $requestedAmountAtomic];
}
private function atomicSubtractNonNegative(string $left, string $right): string {