refactor transaction history retrieval and enhance transaction detail formatting
This commit is contained in:
parent
897aacf04e
commit
2934f06846
23
index.html
23
index.html
@ -1088,11 +1088,6 @@
|
|||||||
if (privateKey && recipientAddress && validators.isPrivateKey(privateKey) && validators.isSuiAddress(recipientAddress)) {
|
if (privateKey && recipientAddress && validators.isPrivateKey(privateKey) && validators.isSuiAddress(recipientAddress)) {
|
||||||
try {
|
try {
|
||||||
const wallet = await suiCrypto.generateMultiChain(privateKey);
|
const wallet = await suiCrypto.generateMultiChain(privateKey);
|
||||||
if (wallet.SUI.address.toLowerCase() === recipientAddress.toLowerCase()) {
|
|
||||||
recipientInput.classList.add('error');
|
|
||||||
errors.push('Cannot send to the same address');
|
|
||||||
hasErrors = true;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error validating addresses:', error);
|
console.error('Error validating addresses:', error);
|
||||||
}
|
}
|
||||||
@ -1625,6 +1620,8 @@
|
|||||||
|
|
||||||
currentAddress = address;
|
currentAddress = address;
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
|
// Clear the cache when searching for a new address
|
||||||
|
cache = {};
|
||||||
|
|
||||||
// Get balance and display
|
// Get balance and display
|
||||||
const balance = await suiBlockchainAPI.getBalance(address, '0x2::sui::SUI');
|
const balance = await suiBlockchainAPI.getBalance(address, '0x2::sui::SUI');
|
||||||
@ -1689,19 +1686,13 @@
|
|||||||
|
|
||||||
resultsDiv.innerHTML = '<div class="transaction-loading"><div class="loading-container"><div class="loading-spinner"><i class="fas"></i></div><div class="loading-text">Loading transactions...</div></div></div>';
|
resultsDiv.innerHTML = '<div class="transaction-loading"><div class="loading-container"><div class="loading-spinner"><i class="fas"></i></div><div class="loading-text">Loading transactions...</div></div></div>';
|
||||||
|
|
||||||
let useCursor = cursor;
|
|
||||||
if (page > 1 && cache[page - 1]) {
|
|
||||||
useCursor = cache[page - 1].nextCursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const result = await suiBlockchainAPI.getTransactionHistory(currentAddress, useCursor, 10);
|
const result = await suiBlockchainAPI.getTransactionHistory(currentAddress, page, 10);
|
||||||
const hasNextPage = result.hasNextPage;
|
const hasNextPage = result.hasNextPage;
|
||||||
const nextCursor = result.nextCursor;
|
|
||||||
|
|
||||||
// Store in cache
|
// Store in cache
|
||||||
cache[page] = { details: result.txs, hasNextPage, nextCursor };
|
cache[page] = { details: result.txs, hasNextPage, nextCursor: null };
|
||||||
|
|
||||||
// Render transactions
|
// Render transactions
|
||||||
renderTransactionPage(cache[page]);
|
renderTransactionPage(cache[page]);
|
||||||
@ -1753,11 +1744,11 @@
|
|||||||
<div class="tx-top-row">
|
<div class="tx-top-row">
|
||||||
<div class="tx-left">
|
<div class="tx-left">
|
||||||
<div class="tx-icon">
|
<div class="tx-icon">
|
||||||
<i class="fas fa-${tx.direction === 'Sent' ? 'arrow-up' : 'arrow-down'}"></i>
|
<i class="fas fa-${tx.direction === 'Self' ? 'exchange-alt' : tx.direction === 'Sent' ? 'arrow-up' : 'arrow-down'}"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="tx-main-info">
|
<div class="tx-main-info">
|
||||||
<div class="tx-direction-label">${tx.direction}</div>
|
<div class="tx-direction-label">${tx.direction === 'Self' ? 'Self Transfer' : tx.direction}</div>
|
||||||
<div class="tx-amount ${tx.direction.toLowerCase()}">${tx.direction === 'Sent' ? '-' : '+'}${tx.amountSui} SUI</div>
|
<div class="tx-amount ${tx.direction.toLowerCase()}">${tx.direction === 'Sent' ? '-' : tx.direction === 'Received' ? '+' : ''}${tx.amount} ${tx.symbol}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tx-meta">
|
<div class="tx-meta">
|
||||||
|
|||||||
@ -566,7 +566,7 @@ body {
|
|||||||
.main-content {
|
.main-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
padding-bottom: 140px; /* Space for mobile navigation */
|
padding-bottom: 140px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@ -1468,6 +1468,9 @@ body {
|
|||||||
color: #10b981;
|
color: #10b981;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tx-amount.self {
|
||||||
|
color: #8b5cf6;
|
||||||
|
}
|
||||||
.tx-details {
|
.tx-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -1986,7 +1989,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tx-icon {
|
.tx-icon {
|
||||||
display: none; /* Hide icon on mobile to save space */
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pagination Mobile */
|
/* Pagination Mobile */
|
||||||
|
|||||||
@ -15,72 +15,284 @@ const suiBlockchainAPI = {
|
|||||||
return json?.result?.totalBalance || 0;
|
return json?.result?.totalBalance || 0;
|
||||||
},
|
},
|
||||||
// Get Transaction History
|
// Get Transaction History
|
||||||
async getTransactionHistory(address, cursor = null, limit = 10) {
|
async getTransactionHistory(address, page = 1, limit = 10) {
|
||||||
const SUI_RPC_URL = "https://fullnode.mainnet.sui.io:443";
|
const SUI_RPC_URL = "https://fullnode.mainnet.sui.io:443";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//Query transaction digests using ToAddress filter
|
const requiredTransactions = page * limit;
|
||||||
const res = await fetch(SUI_RPC_URL, {
|
const smartBatchSize = requiredTransactions + 20;
|
||||||
method: "POST",
|
console.log(
|
||||||
headers: { "Content-Type": "application/json" },
|
`Page ${page}: Smart batching - need ${requiredTransactions}, fetching ${smartBatchSize}`
|
||||||
body: JSON.stringify({
|
);
|
||||||
jsonrpc: "2.0",
|
|
||||||
id: 1,
|
|
||||||
method: "suix_queryTransactionBlocks",
|
|
||||||
params: [
|
|
||||||
{
|
|
||||||
filter: { ToAddress: address },
|
|
||||||
},
|
|
||||||
cursor,
|
|
||||||
limit,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const json = await res.json();
|
// Get both sent (FromAddress) and received (ToAddress) transactions
|
||||||
if (json.error) throw new Error(json.error.message);
|
let allFromDigests = [];
|
||||||
|
let allToDigests = [];
|
||||||
|
let fromCursor = null;
|
||||||
|
let toCursor = null;
|
||||||
|
let fromHasMore = true;
|
||||||
|
let toHasMore = true;
|
||||||
|
|
||||||
const digests = json.result?.data?.map((d) => d.digest) || [];
|
// Keep a set of unique digests while fetching.
|
||||||
const nextCursor = json.result?.nextCursor || null;
|
const uniqueDigestSet = new Set();
|
||||||
const hasNextPage = !!json.result?.hasNextPage;
|
const digestToTimestamp = new Map(); // Store timestamps from queryTransactionBlocks
|
||||||
|
let safetyCounter = 0;
|
||||||
|
const MAX_FETCH_ROUNDS = 10;
|
||||||
|
|
||||||
//Fetch detailed information for each transaction
|
while (
|
||||||
const details = await Promise.all(
|
(fromHasMore || toHasMore) &&
|
||||||
digests.map(async (digest) => {
|
uniqueDigestSet.size < smartBatchSize &&
|
||||||
try {
|
safetyCounter < MAX_FETCH_ROUNDS
|
||||||
const detailRes = await fetch(SUI_RPC_URL, {
|
) {
|
||||||
|
safetyCounter++;
|
||||||
|
const requests = [];
|
||||||
|
|
||||||
|
if (fromHasMore) {
|
||||||
|
requests.push(
|
||||||
|
fetch(SUI_RPC_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
id: 1,
|
id: 1,
|
||||||
method: "sui_getTransactionBlock",
|
method: "suix_queryTransactionBlocks",
|
||||||
params: [
|
params: [
|
||||||
digest,
|
|
||||||
{
|
{
|
||||||
showInput: true,
|
filter: { FromAddress: address },
|
||||||
showEffects: true,
|
options: { showInput: true, showEffects: true },
|
||||||
showBalanceChanges: true,
|
|
||||||
showEvents: true,
|
|
||||||
},
|
},
|
||||||
|
fromCursor,
|
||||||
|
Math.min(25, smartBatchSize - allFromDigests.length),
|
||||||
|
true,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
});
|
})
|
||||||
const detailJson = await detailRes.json();
|
);
|
||||||
return detailJson.result;
|
}
|
||||||
} catch (err) {
|
|
||||||
console.warn(`Failed to fetch details for digest ${digest}:`, err);
|
// Fetch TO transactions if we still have more
|
||||||
return null;
|
if (toHasMore) {
|
||||||
}
|
requests.push(
|
||||||
})
|
fetch(SUI_RPC_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 2,
|
||||||
|
method: "suix_queryTransactionBlocks",
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
filter: { ToAddress: address },
|
||||||
|
options: { showInput: true, showEffects: true },
|
||||||
|
},
|
||||||
|
toCursor,
|
||||||
|
Math.min(25, smartBatchSize - allToDigests.length),
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = await Promise.all(requests);
|
||||||
|
let responseIndex = 0;
|
||||||
|
|
||||||
|
// Process FROM response
|
||||||
|
if (fromHasMore && responses[responseIndex]) {
|
||||||
|
const fromJson = await responses[responseIndex].json();
|
||||||
|
responseIndex++;
|
||||||
|
const newFromDigests =
|
||||||
|
fromJson.result?.data?.map((d) => d.digest) || [];
|
||||||
|
allFromDigests.push(...newFromDigests);
|
||||||
|
|
||||||
|
// Store timestamps from queryTransactionBlocks
|
||||||
|
fromJson.result?.data?.forEach((tx) => {
|
||||||
|
if (tx.digest && tx.timestampMs) {
|
||||||
|
digestToTimestamp.set(tx.digest, Number(tx.timestampMs));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add to unique set
|
||||||
|
newFromDigests.forEach((dg) => uniqueDigestSet.add(dg));
|
||||||
|
fromCursor = fromJson.result?.nextCursor;
|
||||||
|
fromHasMore =
|
||||||
|
!!fromJson.result?.hasNextPage && newFromDigests.length > 0;
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: FROM batch - got ${newFromDigests.length}, total ${allFromDigests.length}, unique ${uniqueDigestSet.size}, hasMore: ${fromHasMore}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process TO response
|
||||||
|
if (toHasMore && responses[responseIndex]) {
|
||||||
|
const toJson = await responses[responseIndex].json();
|
||||||
|
const newToDigests = toJson.result?.data?.map((d) => d.digest) || [];
|
||||||
|
allToDigests.push(...newToDigests);
|
||||||
|
|
||||||
|
// Store timestamps from queryTransactionBlocks
|
||||||
|
toJson.result?.data?.forEach((tx) => {
|
||||||
|
if (tx.digest && tx.timestampMs) {
|
||||||
|
digestToTimestamp.set(tx.digest, Number(tx.timestampMs));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add to unique set
|
||||||
|
newToDigests.forEach((dg) => uniqueDigestSet.add(dg));
|
||||||
|
toCursor = toJson.result?.nextCursor;
|
||||||
|
toHasMore = !!toJson.result?.hasNextPage && newToDigests.length > 0;
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: TO batch - got ${newToDigests.length}, total ${allToDigests.length}, unique ${uniqueDigestSet.size}, hasMore: ${toHasMore}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requests.length === 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Final totals - FROM=${allFromDigests.length}, TO=${allToDigests.length} digests`
|
||||||
|
);
|
||||||
|
// Use the unique set as the deduplicated result
|
||||||
|
const uniqueDigests = [...uniqueDigestSet];
|
||||||
|
|
||||||
|
const mightHaveMorePages = fromHasMore || toHasMore;
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: HasNextPage - FROM: ${fromHasMore}, TO: ${toHasMore}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove null results
|
let digests = uniqueDigests;
|
||||||
const validDetails = details.filter(Boolean);
|
console.log(
|
||||||
|
`Page ${page}: Got ${digests.length} unique digests from API`
|
||||||
|
);
|
||||||
|
|
||||||
// Sort by timestamp descending (newest first)
|
const neededForPage = page * limit;
|
||||||
validDetails.sort((a, b) => (b.timestampMs || 0) - (a.timestampMs || 0));
|
|
||||||
|
// Fetch ALL transaction details first, then sort globally by timestamp
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Fetching details for ALL ${digests.length} transactions for global time-sort`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert Set to Array for processing
|
||||||
|
const allUniqueDigests = Array.from(uniqueDigestSet);
|
||||||
|
const allDetails = [];
|
||||||
|
const BATCH_SIZE = 20;
|
||||||
|
|
||||||
|
// Fetch transaction details for ALL unique digests
|
||||||
|
for (let i = 0; i < allUniqueDigests.length; i += BATCH_SIZE) {
|
||||||
|
const batch = allUniqueDigests.slice(i, i + BATCH_SIZE);
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Processing batch ${Math.floor(i / BATCH_SIZE) + 1}: ${
|
||||||
|
batch.length
|
||||||
|
} transactions`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use sui_multiGetTransactionBlocks
|
||||||
|
const batchRes = await fetch(SUI_RPC_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 1,
|
||||||
|
method: "sui_multiGetTransactionBlocks",
|
||||||
|
params: [
|
||||||
|
batch, // Array of digests
|
||||||
|
{
|
||||||
|
showInput: true,
|
||||||
|
showRawInput: false,
|
||||||
|
showEffects: true,
|
||||||
|
showEvents: true,
|
||||||
|
showObjectChanges: false,
|
||||||
|
showBalanceChanges: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!batchRes.ok) {
|
||||||
|
console.warn(`HTTP ${batchRes.status} for batch starting at ${i}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchJson = await batchRes.json();
|
||||||
|
|
||||||
|
if (batchJson.error) {
|
||||||
|
console.warn(`Batch API error:`, batchJson.error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out null results and add missing timestamps
|
||||||
|
const validResults = batchJson.result.filter(Boolean);
|
||||||
|
|
||||||
|
// Ensure every transaction has a timestamp
|
||||||
|
validResults.forEach((tx) => {
|
||||||
|
if (!tx.timestampMs && digestToTimestamp.has(tx.digest)) {
|
||||||
|
tx.timestampMs = digestToTimestamp.get(tx.digest);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
allDetails.push(...validResults);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Batch ${Math.floor(i / BATCH_SIZE) + 1}: Got ${
|
||||||
|
validResults.length
|
||||||
|
}/${batch.length} valid transactions`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Batch fetch error for batch starting at ${i}:`, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + BATCH_SIZE < allUniqueDigests.length) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Fetched ${allDetails.length} total transaction details`
|
||||||
|
);
|
||||||
|
|
||||||
|
allDetails.sort((a, b) => {
|
||||||
|
const t1 = Number(a.timestampMs || 0);
|
||||||
|
const t2 = Number(b.timestampMs || 0);
|
||||||
|
return t2 - t1; // Newest first
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Globally sorted ${allDetails.length} transactions by timestamp`
|
||||||
|
);
|
||||||
|
|
||||||
|
const startIndex = (page - 1) * limit;
|
||||||
|
const endIndex = page * limit;
|
||||||
|
const validDetails = allDetails.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Showing ${validDetails.length} transactions for this page`
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (validDetails.length === 0 && page > 1) {
|
||||||
|
console.warn(`Page ${page}: No transactions available for this page`);
|
||||||
|
return {
|
||||||
|
txs: [],
|
||||||
|
hasNextPage: false,
|
||||||
|
nextCursor: {
|
||||||
|
from: fromCursor,
|
||||||
|
to: toCursor,
|
||||||
|
fromHasMore,
|
||||||
|
toHasMore,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: Returning ${validDetails.length} transactions (globally sorted)`
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const totalAvailableTransactions = allDetails.length;
|
||||||
|
const hasNextPage =
|
||||||
|
endIndex < totalAvailableTransactions || mightHaveMorePages;
|
||||||
|
console.log(
|
||||||
|
`Page ${page}: hasNextPage = ${hasNextPage} (total: ${totalAvailableTransactions}, endIndex: ${endIndex}, mightHaveMorePages: ${mightHaveMorePages})`
|
||||||
|
);
|
||||||
|
|
||||||
// Transform to the expected format
|
// Transform to the expected format
|
||||||
const transactions = validDetails.map((tx) => {
|
const transactions = validDetails.map((tx) => {
|
||||||
@ -88,78 +300,113 @@ const suiBlockchainAPI = {
|
|||||||
const balanceChanges = tx.balanceChanges || [];
|
const balanceChanges = tx.balanceChanges || [];
|
||||||
const status = tx.effects?.status?.status || "unknown";
|
const status = tx.effects?.status?.status || "unknown";
|
||||||
|
|
||||||
// Find recipient and amount from transaction inputs first
|
|
||||||
let to = "Unknown";
|
let to = "Unknown";
|
||||||
let amountMist = 0;
|
let amountRaw = 0;
|
||||||
|
let coinType = "0x2::sui::SUI"; // Default to SUI
|
||||||
|
|
||||||
const inputs = tx.transaction?.data?.transaction?.inputs || [];
|
let maxChangeAmount = 0;
|
||||||
|
for (const change of balanceChanges) {
|
||||||
|
const changeAmount = Math.abs(Number(change.amount || 0));
|
||||||
|
const changeOwner = change.owner?.AddressOwner;
|
||||||
|
const changeCoinType = change.coinType || "0x2::sui::SUI";
|
||||||
|
|
||||||
// Find the address input (recipient)
|
if (
|
||||||
const addressInput = inputs.find(
|
changeOwner &&
|
||||||
(input) => input.type === "pure" && input.valueType === "address"
|
changeOwner.toLowerCase() !== from.toLowerCase() &&
|
||||||
);
|
changeAmount > maxChangeAmount
|
||||||
|
) {
|
||||||
// Find the amount input
|
to = changeOwner;
|
||||||
const amountInput = inputs.find(
|
amountRaw = changeAmount;
|
||||||
(input) => input.type === "pure" && input.valueType === "u64"
|
coinType = changeCoinType;
|
||||||
);
|
maxChangeAmount = changeAmount;
|
||||||
|
|
||||||
if (addressInput) {
|
|
||||||
to = addressInput.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amountInput) {
|
|
||||||
amountMist = parseInt(amountInput.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ((to === "Unknown" || amountMist === 0) && status === "success") {
|
|
||||||
// For successful transactions, check balance changes for different owner
|
|
||||||
for (const change of balanceChanges) {
|
|
||||||
if (
|
|
||||||
change.owner?.AddressOwner &&
|
|
||||||
change.owner.AddressOwner.toLowerCase() !== from.toLowerCase()
|
|
||||||
) {
|
|
||||||
if (to === "Unknown") {
|
|
||||||
to = change.owner.AddressOwner;
|
|
||||||
}
|
|
||||||
if (amountMist === 0) {
|
|
||||||
amountMist = Math.abs(Number(change.amount || 0));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If still no amount found, get from gas changes (for failed transactions or gas-only)
|
if (to === "Unknown" || amountRaw === 0) {
|
||||||
if (amountMist === 0) {
|
const inputs = tx.transaction?.data?.transaction?.inputs || [];
|
||||||
const gasChange = balanceChanges.find(
|
|
||||||
|
// Find the address input (recipient)
|
||||||
|
const addressInput = inputs.find(
|
||||||
|
(input) => input.type === "pure" && input.valueType === "address"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find the amount input
|
||||||
|
const amountInput = inputs.find(
|
||||||
|
(input) => input.type === "pure" && input.valueType === "u64"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addressInput && to === "Unknown") {
|
||||||
|
to = addressInput.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountInput && amountRaw === 0) {
|
||||||
|
amountRaw = parseInt(amountInput.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountRaw === 0) {
|
||||||
|
const senderChange = balanceChanges.find(
|
||||||
(change) =>
|
(change) =>
|
||||||
change.owner?.AddressOwner?.toLowerCase() === from.toLowerCase()
|
change.owner?.AddressOwner?.toLowerCase() === from.toLowerCase()
|
||||||
);
|
);
|
||||||
if (gasChange) {
|
if (senderChange) {
|
||||||
const totalChange = Math.abs(Number(gasChange.amount || 0));
|
const totalChange = Math.abs(Number(senderChange.amount || 0));
|
||||||
const gasEstimate = 1500000; // Typical gas cost in MIST
|
const gasEstimate = 1500000;
|
||||||
|
|
||||||
// Only use balance change as amount if it's significantly more than gas
|
|
||||||
if (totalChange > gasEstimate * 2) {
|
if (totalChange > gasEstimate * 2) {
|
||||||
amountMist = totalChange - gasEstimate;
|
amountRaw = totalChange - gasEstimate;
|
||||||
} else if (status !== "success") {
|
} else if (status !== "success") {
|
||||||
// For failed transactions, show the intended amount
|
// For failed transactions
|
||||||
amountMist = totalChange;
|
amountRaw = totalChange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const amountSui = (amountMist / 1e9).toFixed(6);
|
|
||||||
const datetime = tx.timestampMs
|
const datetime = tx.timestampMs
|
||||||
? new Date(Number(tx.timestampMs)).toLocaleString()
|
? new Date(Number(tx.timestampMs)).toLocaleString()
|
||||||
: "N/A";
|
: "N/A";
|
||||||
const timestamp = Number(tx.timestampMs || 0);
|
const timestamp = Number(tx.timestampMs || 0);
|
||||||
|
|
||||||
// Determine direction based on address
|
// Determine direction
|
||||||
const direction =
|
let direction = "Other";
|
||||||
from.toLowerCase() === address.toLowerCase() ? "Sent" : "Received";
|
|
||||||
|
// Direct check for self-transfer (same from and to address)
|
||||||
|
if (
|
||||||
|
from.toLowerCase() === address.toLowerCase() &&
|
||||||
|
to.toLowerCase() === address.toLowerCase()
|
||||||
|
) {
|
||||||
|
direction = "Self";
|
||||||
|
} else {
|
||||||
|
// Check balance changes to determine if this address gained or lost value
|
||||||
|
for (const change of balanceChanges) {
|
||||||
|
if (
|
||||||
|
change.owner?.AddressOwner?.toLowerCase() ===
|
||||||
|
address.toLowerCase()
|
||||||
|
) {
|
||||||
|
const changeAmount = Number(change.amount || 0);
|
||||||
|
if (changeAmount > 0) {
|
||||||
|
direction = "Received";
|
||||||
|
} else if (changeAmount < 0) {
|
||||||
|
const hasPositiveChange = balanceChanges.some(
|
||||||
|
(c) =>
|
||||||
|
c.owner?.AddressOwner?.toLowerCase() ===
|
||||||
|
address.toLowerCase() && Number(c.amount || 0) > 0
|
||||||
|
);
|
||||||
|
direction = hasPositiveChange ? "Self" : "Sent";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === "Other") {
|
||||||
|
if (from.toLowerCase() === address.toLowerCase()) {
|
||||||
|
direction =
|
||||||
|
to.toLowerCase() === address.toLowerCase() ? "Self" : "Sent";
|
||||||
|
} else if (to.toLowerCase() === address.toLowerCase()) {
|
||||||
|
direction = "Received";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Format status
|
// Format status
|
||||||
const statusText =
|
const statusText =
|
||||||
@ -175,11 +422,31 @@ const suiBlockchainAPI = {
|
|||||||
? tx?.effects?.status?.error || "Transaction failed"
|
? tx?.effects?.status?.error || "Transaction failed"
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
|
||||||
|
let decimals = 1e9;
|
||||||
|
let symbol = "SUI";
|
||||||
|
|
||||||
|
if (coinType) {
|
||||||
|
if (coinType.includes("usdc") || coinType.includes("USDC")) {
|
||||||
|
decimals = 1e6;
|
||||||
|
symbol = "USDC";
|
||||||
|
} else if (coinType === "0x2::sui::SUI") {
|
||||||
|
decimals = 1e9;
|
||||||
|
symbol = "SUI";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const amountFormatted = (amountRaw / decimals).toFixed(6);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
digest: tx.digest,
|
digest: tx.digest,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
amountSui,
|
amount: amountFormatted,
|
||||||
|
amountRaw,
|
||||||
|
coinType: coinType || "0x2::sui::SUI",
|
||||||
|
symbol: symbol,
|
||||||
datetime,
|
datetime,
|
||||||
timestamp,
|
timestamp,
|
||||||
direction,
|
direction,
|
||||||
@ -192,7 +459,12 @@ const suiBlockchainAPI = {
|
|||||||
return {
|
return {
|
||||||
txs: transactions,
|
txs: transactions,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
nextCursor,
|
nextCursor: {
|
||||||
|
from: fromCursor,
|
||||||
|
to: toCursor,
|
||||||
|
fromHasMore,
|
||||||
|
toHasMore,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error fetching transaction history:", e);
|
console.error("Error fetching transaction history:", e);
|
||||||
@ -436,7 +708,6 @@ const suiBlockchainAPI = {
|
|||||||
const totalChange = Math.abs(Number(gasChange.amount || 0));
|
const totalChange = Math.abs(Number(gasChange.amount || 0));
|
||||||
const gasEstimate = 1500000;
|
const gasEstimate = 1500000;
|
||||||
|
|
||||||
|
|
||||||
if (totalChange > gasEstimate * 2) {
|
if (totalChange > gasEstimate * 2) {
|
||||||
amount = totalChange - gasEstimate;
|
amount = totalChange - gasEstimate;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user