-
+ {heading ?
: null}
-
-
- {tabContent.map((tc) => (
-
- {tc.content}
-
- ))}
-
-
+
+ {tabContent.map((tc) => (
+
+ {tc.content}
+
+ ))}
+
);
};
diff --git a/src/constants/navItems.tsx b/src/constants/navItems.tsx
index 81794293..4d467da0 100644
--- a/src/constants/navItems.tsx
+++ b/src/constants/navItems.tsx
@@ -117,3 +117,15 @@ export const XDR_NAV_ITEMS = [
],
},
];
+
+export const SMART_CONTRACTS_NAV_ITEMS = [
+ {
+ instruction: "Smart Contract Tools",
+ navItems: [
+ {
+ route: Routes.SMART_CONTRACTS_CONTRACT_EXPLORER,
+ label: "Contract Explorer",
+ },
+ ],
+ },
+];
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index 19a854cd..dae40ce9 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -83,4 +83,5 @@ export enum Routes {
TO_XDR = "/xdr/to",
// Smart Contracts
SMART_CONTRACTS = "/smart-contracts",
+ SMART_CONTRACTS_CONTRACT_EXPLORER = "/smart-contracts/contract-explorer",
}
diff --git a/src/constants/settings.ts b/src/constants/settings.ts
index b46aea29..92d871aa 100644
--- a/src/constants/settings.ts
+++ b/src/constants/settings.ts
@@ -120,3 +120,5 @@ export const OPERATION_TRUSTLINE_CLEAR_FLAGS = [
];
export const GITHUB_URL = "https://github.com/stellar/laboratory";
+export const STELLAR_EXPERT = "https://stellar.expert/explorer";
+export const STELLAR_EXPERT_API = "https://api.stellar.expert/explorer";
diff --git a/src/helpers/formatNumber.ts b/src/helpers/formatNumber.ts
new file mode 100644
index 00000000..702a3516
--- /dev/null
+++ b/src/helpers/formatNumber.ts
@@ -0,0 +1,2 @@
+export const formatNumber = (num: number) =>
+ new Intl.NumberFormat("en-US").format(num);
diff --git a/src/helpers/getBlockExplorerLink.ts b/src/helpers/getBlockExplorerLink.ts
index 10a63882..5344f66d 100644
--- a/src/helpers/getBlockExplorerLink.ts
+++ b/src/helpers/getBlockExplorerLink.ts
@@ -1,15 +1,9 @@
+import { STELLAR_EXPERT } from "@/constants/settings";
+
export type BlockExplorer = "stellar.expert" | "stellarchain.io";
export const getBlockExplorerLink = (explorer: BlockExplorer) => {
switch (explorer) {
- case "stellar.expert": {
- return {
- mainnet: "https://stellar.expert/explorer/public",
- testnet: "https://stellar.expert/explorer/testnet",
- futurenet: "",
- custom: "",
- };
- }
case "stellarchain.io":
return {
mainnet: "https://stellarchain.io",
@@ -18,10 +12,11 @@ export const getBlockExplorerLink = (explorer: BlockExplorer) => {
custom: "",
};
// defaults to stellar.expert
+ case "stellar.expert":
default:
return {
- mainnet: "https://stellar.expert/explorer/public",
- testnet: "https://stellar.expert/explorer/testnet",
+ mainnet: `${STELLAR_EXPERT}/public`,
+ testnet: `${STELLAR_EXPERT}/testnet`,
futurenet: "",
custom: "",
};
diff --git a/src/helpers/stellarExpertAccountLink.ts b/src/helpers/stellarExpertAccountLink.ts
new file mode 100644
index 00000000..d10fe803
--- /dev/null
+++ b/src/helpers/stellarExpertAccountLink.ts
@@ -0,0 +1,16 @@
+import { STELLAR_EXPERT } from "@/constants/settings";
+import { NetworkType } from "@/types/types";
+
+export const stellarExpertAccountLink = (
+ accountAddress: string,
+ networkId: NetworkType,
+) => {
+ // Not supported networks
+ if (["futurenet", "custom"].includes(networkId)) {
+ return "";
+ }
+
+ const network = networkId === "mainnet" ? "public" : "testnet";
+
+ return `${STELLAR_EXPERT}/${network}/account/${accountAddress}`;
+};
diff --git a/src/query/external/useSEContractInfo.ts b/src/query/external/useSEContractInfo.ts
new file mode 100644
index 00000000..abd846de
--- /dev/null
+++ b/src/query/external/useSEContractInfo.ts
@@ -0,0 +1,36 @@
+import { useQuery } from "@tanstack/react-query";
+import { STELLAR_EXPERT_API } from "@/constants/settings";
+import { ContractInfoApiResponse, NetworkType } from "@/types/types";
+
+export const useSEContractInfo = ({
+ networkId,
+ contractId,
+}: {
+ networkId: NetworkType;
+ contractId: string;
+}) => {
+ const query = useQuery
({
+ queryKey: ["useSEContractInfo", networkId, contractId],
+ queryFn: async () => {
+ // Not supported networks
+ if (["futurenet", "custom"].includes(networkId)) {
+ return null;
+ }
+
+ const network = networkId === "mainnet" ? "public" : "testnet";
+
+ try {
+ const response = await fetch(
+ `${STELLAR_EXPERT_API}/${network}/contract/${contractId}`,
+ );
+
+ return await response.json();
+ } catch (e: any) {
+ throw `Something went wrong. ${e}`;
+ }
+ },
+ enabled: false,
+ });
+
+ return query;
+};
diff --git a/src/store/createStore.ts b/src/store/createStore.ts
index 86033b90..790a5812 100644
--- a/src/store/createStore.ts
+++ b/src/store/createStore.ts
@@ -178,6 +178,15 @@ export interface Store {
resetXdr: () => void;
resetJsonString: () => void;
};
+
+ // Smart Contracts
+ smartContracts: {
+ explorer: {
+ contractId: string;
+ };
+ updateExplorerContractId: (contractId: string) => void;
+ resetExplorerContractId: () => void;
+ };
}
interface CreateStoreOptions {
@@ -255,6 +264,12 @@ const initXdrState = {
type: XDR_TYPE_TRANSACTION_ENVELOPE,
};
+const initSmartContractsState = {
+ explorer: {
+ contractId: "",
+ },
+};
+
// Store
export const createStore = (options: CreateStoreOptions) =>
create()(
@@ -480,6 +495,7 @@ export const createStore = (options: CreateStoreOptions) =>
state.transaction.feeBump = initTransactionState.feeBump;
}),
},
+ // XDR
xdr: {
...initXdrState,
updateXdrBlob: (blob: string) =>
@@ -505,6 +521,18 @@ export const createStore = (options: CreateStoreOptions) =>
state.xdr.type = initXdrState.type;
}),
},
+ // Smart Contracts
+ smartContracts: {
+ ...initSmartContractsState,
+ updateExplorerContractId: (contractId) =>
+ set((state) => {
+ state.smartContracts.explorer.contractId = contractId;
+ }),
+ resetExplorerContractId: () =>
+ set((state) => {
+ state.smartContracts.explorer.contractId = "";
+ }),
+ },
})),
{
url: options.url,
@@ -549,6 +577,11 @@ export const createStore = (options: CreateStoreOptions) =>
jsonString: true,
type: true,
},
+ smartContracts: {
+ explorer: {
+ contractId: true,
+ },
+ },
};
},
},
diff --git a/src/styles/globals.scss b/src/styles/globals.scss
index 9aa31339..62323f79 100644
--- a/src/styles/globals.scss
+++ b/src/styles/globals.scss
@@ -69,6 +69,18 @@
}
}
+ .Link--external {
+ gap: pxToRem(4px);
+ align-items: center;
+
+ svg {
+ // GitHub icon
+ g[clip-path="url(#github_svg__a)"] path {
+ fill: currentColor !important;
+ }
+ }
+ }
+
// ===========================================================================
// Layout
// ===========================================================================
diff --git a/src/types/types.ts b/src/types/types.ts
index fc0098a1..d0458d50 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -351,3 +351,35 @@ export interface SavedRpcMethod extends LocalStorageSavedItem {
shareableUrl: string | undefined;
payload: AnyObject;
}
+
+// =============================================================================
+// Smart Contract Explorer
+// =============================================================================
+export type ContractInfoApiResponse = {
+ contract: string;
+ created: number;
+ creator: string;
+ account?: string;
+ payments?: number;
+ trades?: number;
+ wasm?: string;
+ storage_entries?: number;
+ validation?: {
+ status?: "verified" | "unverified";
+ repository?: string;
+ commit?: string;
+ package?: string;
+ make?: string;
+ ts?: number;
+ };
+ versions?: number;
+ salt?: string;
+ asset?: string;
+ code?: string;
+ issuer?: string;
+ functions?: {
+ invocations: number;
+ subinvocations: number;
+ function: string;
+ }[];
+};
diff --git a/tests/submitTransactionPage.test.ts b/tests/submitTransactionPage.test.ts
index 8f5bf416..afcb2c65 100644
--- a/tests/submitTransactionPage.test.ts
+++ b/tests/submitTransactionPage.test.ts
@@ -1,3 +1,4 @@
+import { STELLAR_EXPERT } from "@/constants/settings";
import { test, expect, Page } from "@playwright/test";
test.describe("Submit Transaction Page", () => {
@@ -493,7 +494,7 @@ const testBlockExplorerLink = async ({
// Assert the StellarExpert URL
expect(newTabUrl).toBe(
- `https://stellar.expert/explorer/${network?.toLowerCase()}/tx/${hash}`,
+ `${STELLAR_EXPERT}/${network?.toLowerCase()}/tx/${hash}`,
);
const [stellarChainPage] = await Promise.all([