Compare commits

...

10 Commits

Author SHA1 Message Date
84bb370415 feat: add workflow for pushing updates to Dappbundle repository
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
2026-01-12 18:38:32 +05:30
35dc5770b8
Merge pull request #4 from void-57/main
feat: enhance Stellar transaction submission logging with success details and clearer error messages.
2025-12-18 03:17:02 +05:30
d89a151ffe feat: enhance Stellar transaction submission logging with success details and clearer error messages. 2025-12-18 03:14:59 +05:30
05c0dad235
Merge pull request #3 from void-57/main
feat: Improve Stellar fee calculation, fix XDR parsing, and enhance send address UI with full address display and copy button.
2025-12-17 21:24:22 +05:30
7bf5065742 feat: Improve Stellar fee calculation, fix XDR parsing, and enhance send address UI with full address display and copy button. 2025-12-17 20:53:23 +05:30
1fc5b1cb33
Merge pull request #2 from void-57/main
fix: Implement robust checksum validation for Stellar secret keys and WIF keys, and refactor the CRC16-XModem function for shared use.
2025-12-17 18:28:38 +05:30
c1c7f30c3b fix: Implement robust checksum validation for Stellar secret keys and WIF keys, and refactor the CRC16-XModem function for shared use. 2025-12-17 18:25:56 +05:30
386dfdc848
Merge pull request #1 from void-57/main
RanchiMall Stellar Wallet - Complete Feature Set
2025-12-17 05:40:26 +05:30
399acbeccf feat: Refine transaction pagination logic 2025-12-17 05:26:55 +05:30
354e5bbb6e feat: Improve transaction pagination logic by fetching an extra item to detect more pages, refining totalPages calculation, and updating currency display to XLM. 2025-12-17 05:13:12 +05:30
4 changed files with 166 additions and 55 deletions

32
.github/workflows/push-dappbundle.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Workflow push to Dappbundle
on: [push]
jobs:
build:
name: Build
runs-on: self-hosted
steps:
- name: Executing remote command
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.R_HOST }}
username: ${{ secrets.P_USERNAME }}
password: ${{ secrets.P_PASSWORD }}
port: ${{ secrets.SSH_PORT }}
script: |
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle" ]; then
echo "Folder exists. Skipping Git clone."
else
echo "Folder does not exist. Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/ && git clone https://github.com/ranchimall/dappbundle.git
fi
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" ]; then
echo "Repository exists. Remove folder "
rm -r "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}"
fi
echo "Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle && git clone https://github.com/ranchimall/${{ github.event.repository.name }}
cd "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" && rm -rf .gitattributes .git .github .gitignore
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/ && git add . && git commit -m "Workflow updating files of ${{ github.event.repository.name }}" && git push "https://saketongit:${{ secrets.RM_ACCESS_TOKEN }}@github.com/ranchimall/dappbundle.git"

View File

@ -417,9 +417,14 @@
<div class="balance-display">
<div class="balance-amount" id="send-balance">0 <span class="currency">XLM</span></div>
</div>
<div class="address-display">
<span class="address-label">Address:</span>
<span class="address-value" id="send-from-address">-</span>
<div class="detail-row">
<label>From Address</label>
<div class="detail-value-wrapper">
<code id="send-from-address" class="detail-value">-</code>
<button class="input-action-btn clear-btn" onclick="copyToClipboard('send-from-address')" title="Copy address">
<i class="fa-regular fa-copy"></i>
</button>
</div>
</div>
</div>
@ -1016,7 +1021,7 @@
balance = accountInfo.balanceXlm;
const balanceEl = document.getElementById('xlm-balance');
balanceEl.innerHTML = balance.toFixed(6) + ' <span class="currency">xlm</span>';
balanceEl.innerHTML = balance.toFixed(6) + ' <span class="currency">XLM</span>';
txNextToken = null;
await loadTransactions(true);
@ -1026,7 +1031,7 @@
// Show 0 balance for inactive accounts
const balanceEl = document.getElementById('xlm-balance');
balanceEl.innerHTML = '0 <span class="currency">xlm</span>';
balanceEl.innerHTML = '0 <span class="currency">XLM</span>';
document.getElementById('tx-history').innerHTML = '<div class="tx-empty">Account is inactive or not funded yet</div>';
}
@ -1155,7 +1160,7 @@
const balanceEl = document.getElementById('send-balance');
balanceEl.innerHTML = accountInfo.balanceXlm.toFixed(7) + ' <span class="currency">XLM</span>';
document.getElementById('send-from-address').textContent = address.substring(0, 12) + '...' + address.substring(address.length - 12);
document.getElementById('send-from-address').textContent = address;
document.getElementById('send-wallet-info').style.display = 'block';
} catch (error) {
document.getElementById('send-wallet-info').style.display = 'none';
@ -1203,19 +1208,39 @@
hasMoreTransactions = true;
}
// Load only one batch of transactions
// Load transactions - fetch one extra to check if there's more
if (hasMoreTransactions) {
// Fetch ITEMS_PER_PAGE + 1 to check if there's a next page
const result = await xlmAPI.getTransactions(currentxlmAddress, {
limit: 10,
limit: ITEMS_PER_PAGE + 1,
next: txNextToken
});
allTransactions = [...allTransactions, ...result.transactions];
txNextToken = result.nextToken;
hasMoreTransactions = result.hasMore;
// Get all fetched transactions
const fetchedTransactions = result.transactions || [];
// Add all fetched transactions to our array
allTransactions = [...allTransactions, ...fetchedTransactions];
// Determine if there are more transactions:
// If we got exactly limit+1 transactions, there might be more
// Also check the API's hasMore flag
if (fetchedTransactions.length === ITEMS_PER_PAGE + 1 && result.hasMore) {
hasMoreTransactions = true;
txNextToken = result.nextToken;
} else {
hasMoreTransactions = false;
txNextToken = null;
}
}
totalPages = Math.ceil(allTransactions.length / ITEMS_PER_PAGE);
// If we have no transactions at all
if (allTransactions.length === 0) {
hasMoreTransactions = false;
}
totalPages = Math.ceil(allTransactions.length / ITEMS_PER_PAGE) || 1;
if (hasMoreTransactions) {
totalPages++; // Add one more page to show "next" is available
}
@ -1267,8 +1292,9 @@
// Update button states
document.getElementById('prev-page-btn').disabled = currentPage <= 1;
// Enable next button if we're not on last page OR if there are more transactions to load
document.getElementById('next-page-btn').disabled = currentPage >= totalPages && !hasMoreTransactions;
// Disable next button if we're on the last page AND there are no more transactions to load
const isLastPage = currentPage >= totalPages;
document.getElementById('next-page-btn').disabled = isLastPage && !hasMoreTransactions;
paginationEl.style.display = filteredTransactions.length > 0 ? 'flex' : 'none';
}
@ -1592,15 +1618,15 @@
</div>
<div class="tx-detail-row">
<span class="detail-label">Transaction Fee</span>
<span class="detail-value fee">${feeXLM.toFixed(6)} XLM</span>
<span class="detail-value fee">${feeXlm.toFixed(6)} XLM</span>
</div>
<div class="tx-detail-row highlight" style="color: var(--error-color);">
<span class="detail-label">Total Required</span>
<span class="detail-value">${totalXLM.toFixed(6)} XLM</span>
<span class="detail-value">${totalXlm.toFixed(6)} XLM</span>
</div>
<div class="tx-detail-row" style="color: var(--error-color); font-weight: 600;">
<span class="detail-label">Shortfall</span>
<span class="detail-value">${(totalXLM - currentBalance).toFixed(6)} XLM</span>
<span class="detail-value">${(totalXlm - currentBalance).toFixed(6)} XLM</span>
</div>
</div>
</div>

View File

@ -247,7 +247,9 @@
// Get fee stats
const feeStats = await server.feeStats();
const fee = feeStats.max_fee.mode || (StellarSdk.BASE_FEE || '100');
// fee_charged.mode is typically 100 stroops (0.00001 XLM)
const fee = feeStats.fee_charged?.mode || feeStats.last_ledger_base_fee || '100';
// Build transaction
let transaction = new StellarSdk.TransactionBuilder(sourceAccount, {
@ -311,12 +313,23 @@
}
try {
// Parse the XDR back to a transaction
const transaction = new StellarSdk.Transaction(transactionXDR, StellarSdk.Networks.PUBLIC);
// Parse the XDR back to a transaction using TransactionBuilder
const transaction = StellarSdk.TransactionBuilder.fromXDR(transactionXDR, StellarSdk.Networks.PUBLIC);
console.log('Submitting transaction to Stellar network...');
// Submit to network
const result = await server.submitTransaction(transaction);
console.log('✅ Transaction submitted successfully!');
console.log('Transaction Details:', {
hash: result.hash,
ledger: result.ledger,
successful: result.successful,
envelope_xdr: result.envelope_xdr,
result_xdr: result.result_xdr
});
return {
hash: result.hash,
ledger: result.ledger,
@ -324,7 +337,7 @@
txId: result.hash
};
} catch (error) {
console.error('Error submitting transaction:', error);
console.error('Error submitting transaction:', error);
// Parse Stellar error
if (error.response && error.response.data) {

View File

@ -28,6 +28,22 @@
};
}
// Calculate CRC16-XModem checksum (shared function)
function crc16XModem(data) {
let crc = 0x0000;
for (let i = 0; i < data.length; i++) {
crc ^= data[i] << 8;
for (let j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021;
} else {
crc = crc << 1;
}
}
}
return crc & 0xFFFF;
}
// --- Multi-chain Generator (BTC, FLO, XLM) ---
stellarCrypto.generateMultiChain = async function (inputWif) {
const versions = {
@ -74,19 +90,35 @@
}
const decodedBytes = new Uint8Array(decoded);
// Extract seed (skip version byte 0x90, take 32 bytes, ignore checksum)
// Validate checksum
if (decodedBytes.length < 35) {
throw new Error('Invalid Stellar secret key: too short');
}
// Extract components: [version(1)] + [seed(32)] + [checksum(2)]
const payload = decodedBytes.slice(0, 33); // version + seed
const providedChecksum = (decodedBytes[34] << 8) | decodedBytes[33]; // little-endian
// Calculate expected checksum
const expectedChecksum = crc16XModem(payload);
// Verify checksum matches
if (providedChecksum !== expectedChecksum) {
throw new Error(`Invalid Stellar secret key: checksum mismatch (expected ${expectedChecksum.toString(16)}, got ${providedChecksum.toString(16)})`);
}
// Verify version byte
if (decodedBytes[0] !== 0x90) {
throw new Error(`Invalid Stellar secret key: wrong version byte (expected 0x90, got 0x${decodedBytes[0].toString(16)})`);
}
// Extract seed (skip version byte, take 32 bytes)
const seed = decodedBytes.slice(1, 33);
privKeyHex = bytesToHex(seed);
} catch (e) {
console.warn("Invalid Stellar secret key:", e);
// Fall through to generate new key
const newKey = generateNewID();
const decode = Bitcoin.Base58.decode(newKey.privKey);
const keyWithVersion = decode.slice(0, decode.length - 4);
let key = keyWithVersion.slice(1);
if (key.length >= 33 && key[key.length - 1] === 0x01)
key = key.slice(0, key.length - 1);
privKeyHex = bytesToHex(key);
console.error("Invalid Stellar secret key:", e.message);
throw new Error(`Failed to recover Stellar secret key: ${e.message}`);
}
} else if (hexOnly && (trimmedInput.length === 64 || trimmedInput.length === 128)) {
privKeyHex =
@ -94,6 +126,36 @@
} else {
try {
const decode = Bitcoin.Base58.decode(trimmedInput);
// Validate WIF checksum
if (decode.length < 37) {
throw new Error('Invalid WIF key: too short');
}
// WIF format: [version(1)] + [private_key(32)] + [compression_flag(0-1)] + [checksum(4)]
const payload = decode.slice(0, decode.length - 4);
const providedChecksum = decode.slice(decode.length - 4);
// Calculate expected checksum using double SHA256
const hash1 = Crypto.SHA256(payload, { asBytes: true });
const hash2 = Crypto.SHA256(hash1, { asBytes: true });
const expectedChecksum = hash2.slice(0, 4);
// Verify checksum matches
let checksumMatch = true;
for (let i = 0; i < 4; i++) {
if (providedChecksum[i] !== expectedChecksum[i]) {
checksumMatch = false;
break;
}
}
if (!checksumMatch) {
const providedHex = providedChecksum.map(b => b.toString(16).padStart(2, '0')).join('');
const expectedHex = expectedChecksum.map(b => b.toString(16).padStart(2, '0')).join('');
throw new Error(`Invalid WIF key: checksum mismatch (expected ${expectedHex}, got ${providedHex})`);
}
const keyWithVersion = decode.slice(0, decode.length - 4);
let key = keyWithVersion.slice(1);
if (key.length >= 33 && key[key.length - 1] === 0x01) {
@ -102,14 +164,8 @@
}
privKeyHex = bytesToHex(key);
} catch (e) {
console.warn("Invalid WIF, generating new key:", e);
const newKey = generateNewID();
const decode = Bitcoin.Base58.decode(newKey.privKey);
const keyWithVersion = decode.slice(0, decode.length - 4);
let key = keyWithVersion.slice(1);
if (key.length >= 33 && key[key.length - 1] === 0x01)
key = key.slice(0, key.length - 1);
privKeyHex = bytesToHex(key);
console.error("Invalid WIF key:", e.message);
throw new Error(`Failed to recover from WIF key: ${e.message}`);
}
}
} else {
@ -153,22 +209,6 @@
const versionByte = 0x30; // Results in 'G' prefix for public keys
const payload = new Uint8Array([versionByte, ...pubKey]);
// Calculate CRC16-XModem checksum
function crc16XModem(data) {
let crc = 0x0000;
for (let i = 0; i < data.length; i++) {
crc ^= data[i] << 8;
for (let j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021;
} else {
crc = crc << 1;
}
}
}
return crc & 0xFFFF;
}
const checksum = crc16XModem(payload);
// Checksum is stored in little-endian format
const checksumBytes = new Uint8Array([checksum & 0xFF, (checksum >> 8) & 0xFF]);