Use split transfers for token withdrawals
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
-- Token transfer_split can return multiple transaction hashes for one withdrawal.
|
||||
-- Store comma-separated txids in the existing operational column.
|
||||
|
||||
ALTER TABLE withdrawals MODIFY txid TEXT NULL;
|
||||
@@ -92,18 +92,35 @@ if (!filter_var($config['WITHDRAWALS_ENABLED'] ?? false, FILTER_VALIDATE_BOOL))
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $wallet->transfer([
|
||||
$destinations = [
|
||||
[
|
||||
'address' => $withdrawal['address'],
|
||||
'amount' => (int)round($amountToSend * 1e8)
|
||||
]
|
||||
], 0, [0], 0, 16, true, $assetType);
|
||||
];
|
||||
|
||||
if ($result && isset($result['tx_hash'])) {
|
||||
$db->updateWithdrawalTxid($withdrawal['id'], $result['tx_hash']);
|
||||
$result = $assetType === 'SAL1'
|
||||
? $wallet->transfer($destinations, 0, [], 0, 16, true, $assetType)
|
||||
: $wallet->transferSplit($destinations, 0, [], 0, null, true, $assetType);
|
||||
|
||||
$txids = [];
|
||||
if (is_array($result)) {
|
||||
if (!empty($result['tx_hash'])) {
|
||||
$txids[] = $result['tx_hash'];
|
||||
}
|
||||
if (!empty($result['tx_hash_list']) && is_array($result['tx_hash_list'])) {
|
||||
$txids = array_merge($txids, $result['tx_hash_list']);
|
||||
}
|
||||
}
|
||||
$txids = array_values(array_unique(array_filter($txids)));
|
||||
|
||||
if (!empty($txids)) {
|
||||
$txidText = implode(',', $txids);
|
||||
$db->updateWithdrawalTxid($withdrawal['id'], $txidText);
|
||||
$db->updateWithdrawalStatus($withdrawal['id'], 'sent');
|
||||
$feeText = $feeAssetType !== null && $withdrawalFee > 0 ? " Fee: {$withdrawalFee} {$feeAssetType}." : "";
|
||||
sendMessage($chatId, "Withdrawal of {$amountToSend} {$assetType} sent.{$feeText} TxID: {$result['tx_hash']}");
|
||||
$txLabel = count($txids) === 1 ? "TxID: {$txidText}" : "TxIDs: {$txidText}";
|
||||
sendMessage($chatId, "Withdrawal of {$amountToSend} {$assetType} sent.{$feeText} {$txLabel}");
|
||||
} else {
|
||||
$db->updateWithdrawalStatus($withdrawal['id'], 'failed');
|
||||
$returned = $refundText();
|
||||
|
||||
@@ -59,7 +59,7 @@ class SalviumTipBotDB {
|
||||
CREATE TABLE withdrawals (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
txid VARCHAR(64),
|
||||
txid TEXT,
|
||||
asset_type VARCHAR(16) NOT NULL DEFAULT 'SAL1',
|
||||
address VARCHAR(128) NOT NULL,
|
||||
amount DECIMAL(20, 12) NOT NULL,
|
||||
@@ -430,24 +430,37 @@ class SalviumTipBotDB {
|
||||
* Accepts an array of TxIDs and returns an associative array [txid => username]
|
||||
*/
|
||||
public function getTxOwners(array $txids): array {
|
||||
$txids = array_values(array_filter(array_map('strval', $txids), fn($txid) => trim($txid) !== ''));
|
||||
if (empty($txids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Create the correct number of placeholders (?) for the IN clause
|
||||
$placeholders = str_repeat('?,', count($txids) - 1) . '?';
|
||||
$conditions = implode(' OR ', array_fill(0, count($txids), "(w.txid = ? OR FIND_IN_SET(?, w.txid) > 0)"));
|
||||
$params = [];
|
||||
foreach ($txids as $txid) {
|
||||
$params[] = $txid;
|
||||
$params[] = $txid;
|
||||
}
|
||||
|
||||
// Efficient JOIN query to get usernames for these specific TxIDs
|
||||
$sql = "SELECT w.txid, u.username
|
||||
FROM withdrawals w
|
||||
JOIN users u ON w.user_id = u.id
|
||||
WHERE w.txid IN ($placeholders)";
|
||||
WHERE {$conditions}";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($txids);
|
||||
$stmt->execute($params);
|
||||
|
||||
// FETCH_KEY_PAIR returns an array like: ['txid123' => 'user_name', 'txid456' => 'other_user']
|
||||
return $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$owners = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
foreach (explode(',', (string)$row['txid']) as $storedTxid) {
|
||||
$storedTxid = trim($storedTxid);
|
||||
if (in_array($storedTxid, $txids, true)) {
|
||||
$owners[$storedTxid] = $row['username'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $owners;
|
||||
}
|
||||
|
||||
public function logMessage(int $chatId, string $chatName, string $username, string $message, string $response): void {
|
||||
|
||||
@@ -95,6 +95,13 @@ private function _callRpc(string $method, array $params = []): array|false {
|
||||
|
||||
curl_close($ch);
|
||||
$decoded = json_decode($response, true);
|
||||
if (isset($decoded['error'])) {
|
||||
$message = $decoded['error']['message'] ?? 'unknown error';
|
||||
$code = $decoded['error']['code'] ?? 'unknown code';
|
||||
error_log("RPC Error {$method}: {$code} {$message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return $decoded['result'] ?? false;
|
||||
}
|
||||
public function getWalletBalance(): array|false {
|
||||
@@ -133,7 +140,7 @@ public function getNewSubaddress(int $accountIndex = 0, ?string $label = null):
|
||||
return $result['addresses'] ?? false;
|
||||
}
|
||||
|
||||
public function transfer(array $destinations, int $accountIndex = 0, array $subaddrIndices = [0], int $priority = 0, int $ringSize = 16, bool $getTxKey = true, string $assetType = 'SAL1'): array|false {
|
||||
private function buildTransferParams(array $destinations, int $accountIndex, array $subaddrIndices, int $priority, ?int $ringSize, bool $getTxKey, string $assetType): array {
|
||||
$params = [
|
||||
'destinations' => array_map(function ($dest) use ($assetType) {
|
||||
return [
|
||||
@@ -146,15 +153,31 @@ public function getNewSubaddress(int $accountIndex = 0, ?string $label = null):
|
||||
'dest_asset' => $assetType,
|
||||
'tx_type' => 3,
|
||||
'account_index' => $accountIndex,
|
||||
// 'subaddr_indices' => $subaddrIndices,
|
||||
'priority' => $priority,
|
||||
'ring_size' => $ringSize,
|
||||
'get_tx_key' => $getTxKey
|
||||
];
|
||||
|
||||
if (!empty($subaddrIndices)) {
|
||||
$params['subaddr_indices'] = $subaddrIndices;
|
||||
}
|
||||
|
||||
if ($ringSize !== null) {
|
||||
$params['ring_size'] = $ringSize;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public function transfer(array $destinations, int $accountIndex = 0, array $subaddrIndices = [], int $priority = 0, ?int $ringSize = 16, bool $getTxKey = true, string $assetType = 'SAL1'): array|false {
|
||||
$params = $this->buildTransferParams($destinations, $accountIndex, $subaddrIndices, $priority, $ringSize, $getTxKey, $assetType);
|
||||
return $this->_callRpc('transfer', $params);
|
||||
}
|
||||
|
||||
public function transferSplit(array $destinations, int $accountIndex = 0, array $subaddrIndices = [], int $priority = 0, ?int $ringSize = null, bool $getTxKey = true, string $assetType = 'SAL1'): array|false {
|
||||
$params = $this->buildTransferParams($destinations, $accountIndex, $subaddrIndices, $priority, $ringSize, $getTxKey, $assetType);
|
||||
return $this->_callRpc('transfer_split', $params);
|
||||
}
|
||||
|
||||
public function getTransfers(string $inOrOut = 'in', bool $pending = false, bool $failed = false): array|false {
|
||||
$params = [
|
||||
$inOrOut => true,
|
||||
|
||||
Reference in New Issue
Block a user