0.7.48
This commit is contained in:
@@ -218,6 +218,16 @@
|
||||
<div class="form-text">Leave all unchecked for generic discovery. Check several surfaces to scan once and keep candidates matching any selected target.</div>
|
||||
<div class="form-text">Use this to find corpus signatures for non-swap decoders without promoting unverified events. Leave all unchecked to request target='any'.</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="demo3TargetInstructionNameInput" class="form-label">Target instruction name</label>
|
||||
<input id="demo3TargetInstructionNameInput" type="text" class="form-control font-monospace" placeholder="withdraw, raydium_cpmm.deposit" />
|
||||
<div class="form-text">Optional exact instruction-name filter. Accepts comma/space separated names.</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="demo3TargetDiscriminatorHexInput" class="form-label">Target discriminator hex</label>
|
||||
<input id="demo3TargetDiscriminatorHexInput" type="text" class="form-control font-monospace" placeholder="b712469c946da122, f223c68952e1f2b6" />
|
||||
<div class="form-text">Optional first 8 bytes of instruction data, matching the Solscan instruction filter.</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="demo3HttpRoleInput" class="form-label">HTTP role</label>
|
||||
<input id="demo3HttpRoleInput" type="text" class="form-control" value="history_backfill" />
|
||||
@@ -335,6 +345,7 @@
|
||||
<th>Kind</th>
|
||||
<th>Confidence</th>
|
||||
<th>Data prefix</th>
|
||||
<th>Discriminator</th>
|
||||
<th>Verified pool</th>
|
||||
<th>Token A</th>
|
||||
<th>Token B</th>
|
||||
@@ -346,7 +357,7 @@
|
||||
</thead>
|
||||
<tbody id="demo3OnchainCandidateTableBody">
|
||||
<tr>
|
||||
<td colspan="12" class="text-body-secondary">No on-chain candidate.</td>
|
||||
<td colspan="13" class="text-body-secondary">No on-chain candidate.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -416,7 +416,7 @@
|
||||
Aucun jeu de candles chargé.
|
||||
</div>
|
||||
</div>
|
||||
<div id="demoPipeline2Chart" class="w-100 border rounded bg-body" style="height: 560px;"></div>
|
||||
<div id="demoPipeline2Chart" class="w-100 border rounded bg-body" style="height: 680px; min-height: 680px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,14 @@ scanOrder: string | null,
|
||||
* Optional target event family used to find non-swap signatures.
|
||||
*/
|
||||
targetEvent: string | null,
|
||||
/**
|
||||
* Optional instruction name filter, e.g. `withdraw` or `raydium_cpmm.withdraw`.
|
||||
*/
|
||||
targetInstructionName: string | null,
|
||||
/**
|
||||
* Optional instruction discriminator filter as 16-char lower hex, comma-separated when needed.
|
||||
*/
|
||||
targetDiscriminatorHex: string | null,
|
||||
/**
|
||||
* Whether transactions containing swap-like logs should be skipped.
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,10 @@ instructionName: string | null,
|
||||
* Prefix of the raw base58 instruction data, useful for audit grouping.
|
||||
*/
|
||||
instructionDataPrefix: string | null,
|
||||
/**
|
||||
* First eight instruction-data bytes as lower hex.
|
||||
*/
|
||||
instructionDiscriminatorHex: string | null,
|
||||
/**
|
||||
* Candidate pool address.
|
||||
*/
|
||||
|
||||
@@ -187,6 +187,20 @@ function validateOnchainRequest(request: Demo3OnchainDexDiscoveryRequest): void
|
||||
}
|
||||
throw new Error("Program id filter must be a valid Solana program id, or empty when using a preset that resolves it.");
|
||||
}
|
||||
validateDiscriminatorFilter(request.targetDiscriminatorHex);
|
||||
}
|
||||
|
||||
function validateDiscriminatorFilter(value: string | null): void {
|
||||
if (value === null || value.trim() === "") {
|
||||
return;
|
||||
}
|
||||
const tokens = value.split(/[\s,]+/).map((token) => token.trim()).filter((token) => token !== "");
|
||||
for (const token of tokens) {
|
||||
const normalized = token.startsWith("0x") || token.startsWith("0X") ? token.slice(2) : token;
|
||||
if (!/^[0-9a-fA-F]{16}$/.test(normalized)) {
|
||||
throw new Error(`Target discriminator '${token}' must be exactly 8 bytes / 16 hex characters.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function numberValueOrNull(value: string): number | null {
|
||||
@@ -320,6 +334,8 @@ function readOnchainRequest(): Demo3OnchainDexDiscoveryRequest {
|
||||
maxPages: intValue("demo3MaxPagesInput", 1),
|
||||
scanOrder: valueOrNull(byId<HTMLSelectElement>("demo3ScanOrderSelect").value),
|
||||
targetEvent: readTargetEventFilter(),
|
||||
targetInstructionName: valueOrNull(byId<HTMLInputElement>("demo3TargetInstructionNameInput").value),
|
||||
targetDiscriminatorHex: valueOrNull(byId<HTMLInputElement>("demo3TargetDiscriminatorHexInput").value),
|
||||
excludeSwaps: byId<HTMLInputElement>("demo3ExcludeSwapsInput").checked,
|
||||
includeFailed: byId<HTMLInputElement>("demo3IncludeFailedInput").checked,
|
||||
httpRole: byId<HTMLInputElement>("demo3HttpRoleInput").value.trim() || "history_backfill",
|
||||
@@ -375,8 +391,10 @@ function renderOnchainResult(result: Demo3OnchainDexDiscoveryResult): void {
|
||||
byId<HTMLElement>("demo3SummaryRejectedCandidateCount").textContent = String(result.targetRejectedCandidateCount);
|
||||
byId<HTMLElement>("demo3SummaryCandidateCount").textContent = String(result.candidateCount);
|
||||
const targetEvent = targetEventLabel(result.request.targetEvent);
|
||||
const targetInstruction = result.request.targetInstructionName ?? "any";
|
||||
const targetDiscriminator = result.request.targetDiscriminatorHex ?? "any";
|
||||
const sourceText = result.resolvedSignatureAddresses.length === 0 ? result.resolvedSignatureAddress : result.resolvedSignatureAddresses.join(",");
|
||||
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / program=${result.resolvedProgramId} / source=${result.resolvedSignatureSource}:${sourceText} / target=${targetEvent} / order=${result.request.scanOrder ?? "newest_first"}`;
|
||||
byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / program=${result.resolvedProgramId} / source=${result.resolvedSignatureSource}:${sourceText} / target=${targetEvent} / instr=${targetInstruction} / disc=${targetDiscriminator} / order=${result.request.scanOrder ?? "newest_first"}`;
|
||||
byId<HTMLElement>("demo3UniqueSignatureText").textContent = result.uniqueBackfillSignatures.length === 0 ? "-" : result.uniqueBackfillSignatures.join(", ");
|
||||
byId<HTMLElement>("demo3NextBeforeText").textContent = result.nextBeforeByAddress.length === 0 ? "-" : result.nextBeforeByAddress.map((cursor) => `${cursor.address}:${cursor.nextBeforeSignature ?? "-"}`).join(" | ");
|
||||
renderRejectedSummary(result);
|
||||
@@ -402,7 +420,7 @@ function renderRejectedSummary(result: Demo3OnchainDexDiscoveryResult): void {
|
||||
function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): void {
|
||||
const body = byId<HTMLTableSectionElement>("demo3OnchainCandidateTableBody");
|
||||
if (candidates.length === 0) {
|
||||
body.innerHTML = '<tr><td colspan="12" class="text-body-secondary">No on-chain candidate.</td></tr>';
|
||||
body.innerHTML = '<tr><td colspan="13" class="text-body-secondary">No on-chain candidate.</td></tr>';
|
||||
return;
|
||||
}
|
||||
body.innerHTML = candidates.map((candidate) => {
|
||||
@@ -428,6 +446,7 @@ function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): vo
|
||||
<td><span class="badge text-bg-info">${escapeHtml(candidate.candidateKind)}</span></td>
|
||||
<td><span class="badge text-bg-${candidate.confidence === "high" ? "success" : candidate.confidence === "medium" ? "warning" : "secondary"}">${escapeHtml(candidate.confidence)}</span></td>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.instructionDataPrefix ?? "")}">${escapeHtml(shortText(candidate.instructionDataPrefix, 14))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.instructionDiscriminatorHex ?? "")}">${escapeHtml(shortText(candidate.instructionDiscriminatorHex, 16))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(verifiedPool ?? "")}">${escapeHtml(shortText(verifiedPool, 14))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.tokenAMint ?? "")}">${escapeHtml(shortText(candidate.tokenAMint, 14))}</td>
|
||||
<td class="font-monospace" title="${escapeHtml(candidate.tokenBMint ?? "")}">${escapeHtml(shortText(candidate.tokenBMint, 14))}</td>
|
||||
@@ -469,7 +488,7 @@ async function discoverOnchain(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
setStatus("running", "text-bg-warning");
|
||||
appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' source='${request.signatureSource ?? "program_id"}:${request.sourceAddresses.join(",")}' target='${targetEventLabel(request.targetEvent)}' pages='${request.maxPages}' order='${request.scanOrder ?? "newest_first"}' before='${request.beforeSignature ?? ""}' until='${request.untilSignature ?? ""}' excludeSwaps='${request.excludeSwaps}' role='${request.httpRole}'`);
|
||||
appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' source='${request.signatureSource ?? "program_id"}:${request.sourceAddresses.join(",")}' target='${targetEventLabel(request.targetEvent)}' instruction='${request.targetInstructionName ?? ""}' discriminator='${request.targetDiscriminatorHex ?? ""}' pages='${request.maxPages}' order='${request.scanOrder ?? "newest_first"}' before='${request.beforeSignature ?? ""}' until='${request.untilSignature ?? ""}' excludeSwaps='${request.excludeSwaps}' role='${request.httpRole}'`);
|
||||
try {
|
||||
const payload = await invoke<Demo3OnchainDexDiscoveryPayload>("demo3_discover_onchain_dex_pairs", { request });
|
||||
lastResultJson = payload.resultJson;
|
||||
|
||||
@@ -186,6 +186,32 @@ function renderCatalogTextareas(
|
||||
pairsTextarea.value = JSON.stringify(catalog.pairs, null, 2);
|
||||
}
|
||||
|
||||
|
||||
function toChartNumber(value: number | string | null | undefined): number {
|
||||
if (value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (typeof value === "number") {
|
||||
return Number.isFinite(value) ? value : 0;
|
||||
}
|
||||
|
||||
const parsed = Number.parseFloat(value);
|
||||
if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function calculateVisibleWindowStart(totalCandles: number): number {
|
||||
if (totalCandles <= 90) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0, ((totalCandles - 90) / totalCandles) * 100);
|
||||
}
|
||||
|
||||
function parseCandlesJson(raw: string): PairCandle[] {
|
||||
if (raw.trim() === "") {
|
||||
return [];
|
||||
@@ -239,16 +265,18 @@ function renderCandlesChart(
|
||||
);
|
||||
|
||||
const ohlcData = sorted.map((candle) => [
|
||||
candle.open_price_quote_per_base,
|
||||
candle.close_price_quote_per_base,
|
||||
candle.low_price_quote_per_base,
|
||||
candle.high_price_quote_per_base,
|
||||
toChartNumber(candle.open_price_quote_per_base),
|
||||
toChartNumber(candle.close_price_quote_per_base),
|
||||
toChartNumber(candle.low_price_quote_per_base),
|
||||
toChartNumber(candle.high_price_quote_per_base),
|
||||
]);
|
||||
|
||||
const volumeData = sorted.map((candle) =>
|
||||
parseVolume(candle.quote_volume_raw, candle.trade_count),
|
||||
);
|
||||
|
||||
const zoomStart = calculateVisibleWindowStart(sorted.length);
|
||||
|
||||
chartMeta.textContent =
|
||||
`Pair #${pairId.toString()} • timeframe ${timeframeSeconds.toString()}s • ${sorted.length} candles`;
|
||||
|
||||
@@ -256,7 +284,8 @@ function renderCandlesChart(
|
||||
animation: false,
|
||||
legend: {
|
||||
data: ["OHLC", "Volume"],
|
||||
top: 0,
|
||||
top: 4,
|
||||
left: 16,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
@@ -268,8 +297,8 @@ function renderCandlesChart(
|
||||
link: [{ xAxisIndex: "all" }],
|
||||
},
|
||||
grid: [
|
||||
{ left: 60, right: 24, top: 40, height: "58%" },
|
||||
{ left: 60, right: 24, top: "74%", height: "16%" },
|
||||
{ left: 76, right: 32, top: 52, height: "58%" },
|
||||
{ left: 76, right: 32, top: "77%", height: "12%" },
|
||||
],
|
||||
xAxis: [
|
||||
{
|
||||
@@ -277,7 +306,9 @@ function renderCandlesChart(
|
||||
data: categoryData,
|
||||
boundaryGap: true,
|
||||
axisLine: { onZero: false },
|
||||
splitLine: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
splitLine: { show: true },
|
||||
axisLabel: { hideOverlap: true, margin: 14 },
|
||||
min: "dataMin",
|
||||
max: "dataMax",
|
||||
},
|
||||
@@ -287,9 +318,9 @@ function renderCandlesChart(
|
||||
data: categoryData,
|
||||
boundaryGap: true,
|
||||
axisLine: { onZero: false },
|
||||
axisTick: { show: false },
|
||||
axisTick: { alignWithLabel: true },
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
axisLabel: { hideOverlap: true, margin: 10 },
|
||||
min: "dataMin",
|
||||
max: "dataMax",
|
||||
},
|
||||
@@ -297,27 +328,33 @@ function renderCandlesChart(
|
||||
yAxis: [
|
||||
{
|
||||
scale: true,
|
||||
splitNumber: 5,
|
||||
splitArea: { show: false },
|
||||
axisLabel: { margin: 12 },
|
||||
},
|
||||
{
|
||||
gridIndex: 1,
|
||||
scale: true,
|
||||
splitNumber: 2,
|
||||
axisLabel: { margin: 12 },
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
xAxisIndex: [0, 1],
|
||||
start: 0,
|
||||
filterMode: "none",
|
||||
start: zoomStart,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
type: "slider",
|
||||
xAxisIndex: [0, 1],
|
||||
bottom: 6,
|
||||
start: 0,
|
||||
filterMode: "none",
|
||||
bottom: 8,
|
||||
height: 22,
|
||||
start: zoomStart,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
@@ -326,11 +363,12 @@ function renderCandlesChart(
|
||||
name: "OHLC",
|
||||
type: "candlestick",
|
||||
data: ohlcData,
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
barMinWidth: 4,
|
||||
barMaxWidth: 16,
|
||||
itemStyle: {
|
||||
color: "#16a34a",
|
||||
color0: "#dc2626",
|
||||
borderColor: "#15803d",
|
||||
borderColor0: "#b91c1c",
|
||||
borderWidth: 1.4,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -339,9 +377,13 @@ function renderCandlesChart(
|
||||
xAxisIndex: 1,
|
||||
yAxisIndex: 1,
|
||||
data: volumeData,
|
||||
barMinWidth: 2,
|
||||
barMaxWidth: 10,
|
||||
},
|
||||
],
|
||||
}, true);
|
||||
|
||||
window.setTimeout(() => chart.resize(), 0);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
@@ -468,6 +510,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
const chart = echarts.init(safeChartElement);
|
||||
setEmptyChart(chart, safeChartMeta, "Aucune candle disponible.");
|
||||
window.addEventListener("resize", () => chart.resize());
|
||||
const chartCollapse = document.querySelector<HTMLDivElement>("#demoPipeline2ChartCollapse");
|
||||
chartCollapse?.addEventListener("shown.bs.collapse", () => {
|
||||
window.setTimeout(() => chart.resize(), 0);
|
||||
});
|
||||
|
||||
clearLogButton.addEventListener("click", () => {
|
||||
logTextarea.value = "";
|
||||
|
||||
Reference in New Issue
Block a user