Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions core/registry/manifests.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import { manifest as balanceViewer } from "@/features/balance-viewer/manifest";
import { manifest as batchAddressValidator } from "@/features/batch-address-validator/manifest";
import { manifest as claimableBalances } from "@/features/claimable-balances/manifest";
import { manifest as claimablePredicateBuilder } from "@/features/claimable-predicate-builder/manifest";
import { manifest as contractEvents } from "@/features/contract-events/manifest";
import { manifest as effectsTimeline } from "@/features/effects-timeline/manifest";
import { manifest as explorerLinks } from "@/features/explorer-links/manifest";
import { manifest as federationResolver } from "@/features/federation-resolver/manifest";
import { manifest as feeBumpInspector } from "@/features/fee-bump-inspector/manifest";
import { manifest as feeStats } from "@/features/fee-stats/manifest";
Expand All @@ -35,9 +37,12 @@ import { manifest as networkComparison } from "@/features/network-comparison/man
import { manifest as operationBrowser } from "@/features/operation-browser/manifest";
import { manifest as operationSourceMap } from "@/features/operation-source-map/manifest";
import { manifest as orderbookViewer } from "@/features/orderbook-viewer/manifest";
import { manifest as pathPaymentFinder } from "@/features/path-payment-finder/manifest";
import { manifest as pathPaymentInspector } from "@/features/path-payment-inspector/manifest";
import { manifest as paymentCsvPreflight } from "@/features/payment-csv-preflight/manifest";
import { manifest as paymentHistory } from "@/features/payment-history/manifest";
import { manifest as paymentQr } from "@/features/payment-qr/manifest";
import { manifest as paymentUriParser } from "@/features/payment-uri-parser/manifest";
import { manifest as preconditionsExplainer } from "@/features/preconditions-explainer/manifest";
import { manifest as priceFractionLab } from "@/features/price-fraction-lab/manifest";
import { manifest as reserveCalculator } from "@/features/reserve-calculator/manifest";
Expand All @@ -47,7 +52,10 @@ import { manifest as sep7SignatureVerifier } from "@/features/sep7-signature-ver
import { manifest as sequenceInspector } from "@/features/sequence-inspector/manifest";
import { manifest as signatureVerifier } from "@/features/signature-verifier/manifest";
import { manifest as simulationExplainer } from "@/features/simulation-explainer/manifest";
import { manifest as sorobanAuthInspector } from "@/features/soroban-auth-inspector/manifest";
import { manifest as sorobanDecoder } from "@/features/soroban-decoder/manifest";
import { manifest as sorobanFeeEstimator } from "@/features/soroban-fee-estimator/manifest";
import { manifest as sorobanSpecViewer } from "@/features/soroban-spec-viewer/manifest";
import { manifest as sponsoredReserves } from "@/features/sponsored-reserves/manifest";
import { manifest as strkeyInspector } from "@/features/strkey-inspector/manifest";
import { manifest as testnetFaucet } from "@/features/testnet-faucet/manifest";
Expand Down Expand Up @@ -79,7 +87,9 @@ export const generatedManifests: FeatureManifest[] = [
batchAddressValidator,
claimableBalances,
claimablePredicateBuilder,
contractEvents,
effectsTimeline,
explorerLinks,
federationResolver,
feeBumpInspector,
feeStats,
Expand All @@ -97,9 +107,12 @@ export const generatedManifests: FeatureManifest[] = [
operationBrowser,
operationSourceMap,
orderbookViewer,
pathPaymentFinder,
pathPaymentInspector,
paymentCsvPreflight,
paymentHistory,
paymentQr,
paymentUriParser,
preconditionsExplainer,
priceFractionLab,
reserveCalculator,
Expand All @@ -109,7 +122,10 @@ export const generatedManifests: FeatureManifest[] = [
sequenceInspector,
signatureVerifier,
simulationExplainer,
sorobanAuthInspector,
sorobanDecoder,
sorobanFeeEstimator,
sorobanSpecViewer,
sponsoredReserves,
strkeyInspector,
testnetFaucet,
Expand Down
8 changes: 8 additions & 0 deletions core/registry/panels.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export const generatedPanels: Record<string, ComponentType> = {
"batch-address-validator": () => import("@/features/batch-address-validator/panel").then((mod) => mod.default as ComponentType),
"claimable-balances": () => import("@/features/claimable-balances/panel").then((mod) => mod.default as ComponentType),
"claimable-predicate-builder": () => import("@/features/claimable-predicate-builder/panel").then((mod) => mod.default as ComponentType),
"contract-events": () => import("@/features/contract-events/panel").then((mod) => mod.default as ComponentType),
"effects-timeline": () => import("@/features/effects-timeline/panel").then((mod) => mod.default as ComponentType),
"explorer-links": () => import("@/features/explorer-links/panel").then((mod) => mod.default as ComponentType),
"federation-resolver": () => import("@/features/federation-resolver/panel").then((mod) => mod.default as ComponentType),
"fee-bump-inspector": () => import("@/features/fee-bump-inspector/panel").then((mod) => mod.default as ComponentType),
"fee-stats": () => import("@/features/fee-stats/panel").then((mod) => mod.default as ComponentType),
Expand All @@ -38,9 +40,12 @@ export const generatedPanels: Record<string, ComponentType> = {
"operation-browser": () => import("@/features/operation-browser/panel").then((mod) => mod.default as ComponentType),
"operation-source-map": () => import("@/features/operation-source-map/panel").then((mod) => mod.default as ComponentType),
"orderbook-viewer": () => import("@/features/orderbook-viewer/panel").then((mod) => mod.default as ComponentType),
"path-payment-finder": () => import("@/features/path-payment-finder/panel").then((mod) => mod.default as ComponentType),
"path-payment-inspector": () => import("@/features/path-payment-inspector/panel").then((mod) => mod.default as ComponentType),
"payment-csv-preflight": () => import("@/features/payment-csv-preflight/panel").then((mod) => mod.default as ComponentType),
"payment-history": () => import("@/features/payment-history/panel").then((mod) => mod.default as ComponentType),
"payment-qr": () => import("@/features/payment-qr/panel").then((mod) => mod.default as ComponentType),
"payment-uri-parser": () => import("@/features/payment-uri-parser/panel").then((mod) => mod.default as ComponentType),
"preconditions-explainer": () => import("@/features/preconditions-explainer/panel").then((mod) => mod.default as ComponentType),
"price-fraction-lab": () => import("@/features/price-fraction-lab/panel").then((mod) => mod.default as ComponentType),
"reserve-calculator": () => import("@/features/reserve-calculator/panel").then((mod) => mod.default as ComponentType),
Expand All @@ -50,7 +55,10 @@ export const generatedPanels: Record<string, ComponentType> = {
"sequence-inspector": () => import("@/features/sequence-inspector/panel").then((mod) => mod.default as ComponentType),
"signature-verifier": () => import("@/features/signature-verifier/panel").then((mod) => mod.default as ComponentType),
"simulation-explainer": () => import("@/features/simulation-explainer/panel").then((mod) => mod.default as ComponentType),
"soroban-auth-inspector": () => import("@/features/soroban-auth-inspector/panel").then((mod) => mod.default as ComponentType),
"soroban-decoder": () => import("@/features/soroban-decoder/panel").then((mod) => mod.default as ComponentType),
"soroban-fee-estimator": () => import("@/features/soroban-fee-estimator/panel").then((mod) => mod.default as ComponentType),
"soroban-spec-viewer": () => import("@/features/soroban-spec-viewer/panel").then((mod) => mod.default as ComponentType),
"sponsored-reserves": () => import("@/features/sponsored-reserves/panel").then((mod) => mod.default as ComponentType),
"strkey-inspector": () => import("@/features/strkey-inspector/panel").then((mod) => mod.default as ComponentType),
"testnet-faucet": () => import("@/features/testnet-faucet/panel").then((mod) => mod.default as ComponentType),
Expand Down
16 changes: 16 additions & 0 deletions core/registry/registry.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import { manifest as balanceViewer } from "@/features/balance-viewer/manifest";
import { manifest as batchAddressValidator } from "@/features/batch-address-validator/manifest";
import { manifest as claimableBalances } from "@/features/claimable-balances/manifest";
import { manifest as claimablePredicateBuilder } from "@/features/claimable-predicate-builder/manifest";
import { manifest as contractEvents } from "@/features/contract-events/manifest";
import { manifest as effectsTimeline } from "@/features/effects-timeline/manifest";
import { manifest as explorerLinks } from "@/features/explorer-links/manifest";
import { manifest as federationResolver } from "@/features/federation-resolver/manifest";
import { manifest as feeBumpInspector } from "@/features/fee-bump-inspector/manifest";
import { manifest as feeStats } from "@/features/fee-stats/manifest";
Expand All @@ -36,9 +38,12 @@ import { manifest as networkComparison } from "@/features/network-comparison/man
import { manifest as operationBrowser } from "@/features/operation-browser/manifest";
import { manifest as operationSourceMap } from "@/features/operation-source-map/manifest";
import { manifest as orderbookViewer } from "@/features/orderbook-viewer/manifest";
import { manifest as pathPaymentFinder } from "@/features/path-payment-finder/manifest";
import { manifest as pathPaymentInspector } from "@/features/path-payment-inspector/manifest";
import { manifest as paymentCsvPreflight } from "@/features/payment-csv-preflight/manifest";
import { manifest as paymentHistory } from "@/features/payment-history/manifest";
import { manifest as paymentQr } from "@/features/payment-qr/manifest";
import { manifest as paymentUriParser } from "@/features/payment-uri-parser/manifest";
import { manifest as preconditionsExplainer } from "@/features/preconditions-explainer/manifest";
import { manifest as priceFractionLab } from "@/features/price-fraction-lab/manifest";
import { manifest as reserveCalculator } from "@/features/reserve-calculator/manifest";
Expand All @@ -48,7 +53,10 @@ import { manifest as sep7SignatureVerifier } from "@/features/sep7-signature-ver
import { manifest as sequenceInspector } from "@/features/sequence-inspector/manifest";
import { manifest as signatureVerifier } from "@/features/signature-verifier/manifest";
import { manifest as simulationExplainer } from "@/features/simulation-explainer/manifest";
import { manifest as sorobanAuthInspector } from "@/features/soroban-auth-inspector/manifest";
import { manifest as sorobanDecoder } from "@/features/soroban-decoder/manifest";
import { manifest as sorobanFeeEstimator } from "@/features/soroban-fee-estimator/manifest";
import { manifest as sorobanSpecViewer } from "@/features/soroban-spec-viewer/manifest";
import { manifest as sponsoredReserves } from "@/features/sponsored-reserves/manifest";
import { manifest as strkeyInspector } from "@/features/strkey-inspector/manifest";
import { manifest as testnetFaucet } from "@/features/testnet-faucet/manifest";
Expand Down Expand Up @@ -80,7 +88,9 @@ export const generatedFeatures: FeatureEntry[] = [
{ manifest: batchAddressValidator, load: async () => { const mod = await import("@/features/batch-address-validator/panel"); return mod.default as ComponentType; } },
{ manifest: claimableBalances, load: async () => { const mod = await import("@/features/claimable-balances/panel"); return mod.default as ComponentType; } },
{ manifest: claimablePredicateBuilder, load: async () => { const mod = await import("@/features/claimable-predicate-builder/panel"); return mod.default as ComponentType; } },
{ manifest: contractEvents, load: async () => { const mod = await import("@/features/contract-events/panel"); return mod.default as ComponentType; } },
{ manifest: effectsTimeline, load: async () => { const mod = await import("@/features/effects-timeline/panel"); return mod.default as ComponentType; } },
{ manifest: explorerLinks, load: async () => { const mod = await import("@/features/explorer-links/panel"); return mod.default as ComponentType; } },
{ manifest: federationResolver, load: async () => { const mod = await import("@/features/federation-resolver/panel"); return mod.default as ComponentType; } },
{ manifest: feeBumpInspector, load: async () => { const mod = await import("@/features/fee-bump-inspector/panel"); return mod.default as ComponentType; } },
{ manifest: feeStats, load: async () => { const mod = await import("@/features/fee-stats/panel"); return mod.default as ComponentType; } },
Expand All @@ -98,9 +108,12 @@ export const generatedFeatures: FeatureEntry[] = [
{ manifest: operationBrowser, load: async () => { const mod = await import("@/features/operation-browser/panel"); return mod.default as ComponentType; } },
{ manifest: operationSourceMap, load: async () => { const mod = await import("@/features/operation-source-map/panel"); return mod.default as ComponentType; } },
{ manifest: orderbookViewer, load: async () => { const mod = await import("@/features/orderbook-viewer/panel"); return mod.default as ComponentType; } },
{ manifest: pathPaymentFinder, load: async () => { const mod = await import("@/features/path-payment-finder/panel"); return mod.default as ComponentType; } },
{ manifest: pathPaymentInspector, load: async () => { const mod = await import("@/features/path-payment-inspector/panel"); return mod.default as ComponentType; } },
{ manifest: paymentCsvPreflight, load: async () => { const mod = await import("@/features/payment-csv-preflight/panel"); return mod.default as ComponentType; } },
{ manifest: paymentHistory, load: async () => { const mod = await import("@/features/payment-history/panel"); return mod.default as ComponentType; } },
{ manifest: paymentQr, load: async () => { const mod = await import("@/features/payment-qr/panel"); return mod.default as ComponentType; } },
{ manifest: paymentUriParser, load: async () => { const mod = await import("@/features/payment-uri-parser/panel"); return mod.default as ComponentType; } },
{ manifest: preconditionsExplainer, load: async () => { const mod = await import("@/features/preconditions-explainer/panel"); return mod.default as ComponentType; } },
{ manifest: priceFractionLab, load: async () => { const mod = await import("@/features/price-fraction-lab/panel"); return mod.default as ComponentType; } },
{ manifest: reserveCalculator, load: async () => { const mod = await import("@/features/reserve-calculator/panel"); return mod.default as ComponentType; } },
Expand All @@ -110,7 +123,10 @@ export const generatedFeatures: FeatureEntry[] = [
{ manifest: sequenceInspector, load: async () => { const mod = await import("@/features/sequence-inspector/panel"); return mod.default as ComponentType; } },
{ manifest: signatureVerifier, load: async () => { const mod = await import("@/features/signature-verifier/panel"); return mod.default as ComponentType; } },
{ manifest: simulationExplainer, load: async () => { const mod = await import("@/features/simulation-explainer/panel"); return mod.default as ComponentType; } },
{ manifest: sorobanAuthInspector, load: async () => { const mod = await import("@/features/soroban-auth-inspector/panel"); return mod.default as ComponentType; } },
{ manifest: sorobanDecoder, load: async () => { const mod = await import("@/features/soroban-decoder/panel"); return mod.default as ComponentType; } },
{ manifest: sorobanFeeEstimator, load: async () => { const mod = await import("@/features/soroban-fee-estimator/panel"); return mod.default as ComponentType; } },
{ manifest: sorobanSpecViewer, load: async () => { const mod = await import("@/features/soroban-spec-viewer/panel"); return mod.default as ComponentType; } },
{ manifest: sponsoredReserves, load: async () => { const mod = await import("@/features/sponsored-reserves/panel"); return mod.default as ComponentType; } },
{ manifest: strkeyInspector, load: async () => { const mod = await import("@/features/strkey-inspector/panel"); return mod.default as ComponentType; } },
{ manifest: testnetFaucet, load: async () => { const mod = await import("@/features/testnet-faucet/panel"); return mod.default as ComponentType; } },
Expand Down
43 changes: 43 additions & 0 deletions features/explorer-links/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Multi-Explorer Link Generator

Paste any Stellar identifier and get direct links on every major explorer.

## Purpose

Every explorer uses a different URL shape for the same data. Building the right link by hand for the right network is a small tax paid many times a day.

This tool classifies the identifier and generates links for all major explorers simultaneously.

## Input

Any Stellar identifier:
- Account address (G...)
- Muxed account (M...)
- Transaction hash (64 hex characters)
- Ledger sequence (numeric)
- Asset (code:issuer format)
- Contract (C...)

Network selection (mainnet or testnet).

## Output

Direct links to view the identifier on:
- Stellar Expert
- Stellar Chain
- SteExp
- Network-specific variants (testnet prefixes)

## Classification

The tool identifies the input by format:
- **Account**: G-prefix, 56 characters
- **Muxed Account**: M-prefix, 56+ characters
- **Contract**: C-prefix, 56 characters
- **Transaction**: 64 hex characters
- **Ledger**: Numeric, up to 10 digits
- **Asset**: `CODE:ISSUER` format where ISSUER is a valid account

## Local Only

All classification and link generation happens in the browser. No data is transmitted.
69 changes: 69 additions & 0 deletions features/explorer-links/__tests__/explorerLinks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, it, expect } from "vitest";
import {
generateExplorerLinks,
parseExplorerLinksInput,
} from "@/features/explorer-links/lib";

describe("explorer-links", () => {
describe("parseExplorerLinksInput", () => {
it("rejects empty input", () => {
const result = parseExplorerLinksInput("");
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toBe("empty_input");
}
});

it("accepts valid input", () => {
const result = parseExplorerLinksInput("12345");
expect(result.ok).toBe(true);
});
});

describe("generateExplorerLinks", () => {
it("identifies ledger sequences", () => {
const result = generateExplorerLinks({
identifier: "12345",
network: "mainnet",
});
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.type).toBe("ledger");
expect(result.value.links.length).toBeGreaterThan(0);
}
});

it("identifies transaction hashes", () => {
const result = generateExplorerLinks({
identifier: "a".repeat(64),
network: "mainnet",
});
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.type).toBe("transaction");
}
});

it("rejects unknown format", () => {
const result = generateExplorerLinks({
identifier: "invalid@data$format",
network: "mainnet",
});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toBe("unknown_identifier");
}
});

it("generates testnet links", () => {
const result = generateExplorerLinks({
identifier: "12345",
network: "testnet",
});
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.links[0].url).toContain("testnet");
}
});
});
});
19 changes: 19 additions & 0 deletions features/explorer-links/components/ExplorerLinksEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";

import { AlertCircle } from "lucide-react";
import { Card } from "@/core/ui/Card";

export function ExplorerLinksEmptyState() {
return (
<Card tone="muted" className="flex items-start gap-3 p-4">
<AlertCircle className="w-5 h-5 mt-0.5 flex-shrink-0" />
<div className="space-y-2">
<h3 className="font-semibold">Generate Explorer Links</h3>
<p className="text-sm text-secondary">
Paste any Stellar identifier to get direct links on all major explorers
for the selected network.
</p>
</div>
</Card>
);
}
Loading