explorer: Parse Serum DEX and swap instructions for TokenHistory (#13320)

* map serum instructions on token history card

* add token swap instruction parsing

* refactor serum program and instruction data
This commit is contained in:
Josh
2020-10-31 19:21:20 -07:00
committed by GitHub
parent 52a292a75b
commit 2f657bc0ca
9 changed files with 1958 additions and 3786 deletions

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@project-serum/serum": "^0.13.9",
"@react-hook/debounce": "^3.0.0",
"@sentry/react": "^5.27.2",
"@solana/web3.js": "^0.86.2",

View File

@ -31,6 +31,15 @@ import {
IX_TITLES,
} from "components/instruction/token/types";
import { reportError } from "utils/sentry";
import { intoTransactionInstruction } from "utils/tx";
import {
isTokenSwapInstruction,
parseTokenSwapInstructionTitle,
} from "components/instruction/token-swap/types";
import {
isSerumInstruction,
parseSerumInstructionTitle,
} from "components/instruction/serum/types";
export function TokenHistoryCard({ pubkey }: { pubkey: PublicKey }) {
const address = pubkey.toBase58();
@ -301,13 +310,41 @@ const TokenTransactionRow = React.memo(
);
const tokenInstructionNames = instructions
.map((ix): string | undefined => {
.map((ix, index): string | undefined => {
let transactionInstruction;
if (details?.data?.transaction?.transaction) {
transactionInstruction = intoTransactionInstruction(
details.data.transaction.transaction,
index
);
}
if ("parsed" in ix) {
if (ix.program === "spl-token") {
return instructionTypeName(ix, tx);
} else {
return undefined;
}
} else if (
transactionInstruction &&
isSerumInstruction(transactionInstruction)
) {
try {
return parseSerumInstructionTitle(transactionInstruction);
} catch (error) {
reportError(error, { signature: tx.signature });
return undefined;
}
} else if (
transactionInstruction &&
isTokenSwapInstruction(transactionInstruction)
) {
try {
return parseTokenSwapInstructionTitle(transactionInstruction);
} catch (error) {
reportError(error, { signature: tx.signature });
return undefined;
}
} else {
if (
ix.accounts.findIndex((account) =>

View File

@ -1,9 +1,9 @@
import React from "react";
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
import { InstructionCard } from "./InstructionCard";
import { parseSerumInstructionTitle } from "utils/tx";
import { useCluster } from "providers/cluster";
import { reportError } from "utils/sentry";
import { parseSerumInstructionTitle } from "./serum/types";
export function SerumDetailsCard({
ix,
@ -33,7 +33,7 @@ export function SerumDetailsCard({
ix={ix}
index={index}
result={result}
title={title || "Unknown"}
title={`Serum: ${title || "Unknown"}`}
defaultRaw
/>
);

View File

@ -0,0 +1,40 @@
import React from "react";
import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
import { InstructionCard } from "./InstructionCard";
import { useCluster } from "providers/cluster";
import { reportError } from "utils/sentry";
import { parseTokenSwapInstructionTitle } from "./token-swap/types";
export function TokenSwapDetailsCard({
ix,
index,
result,
signature,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
signature: string;
}) {
const { url } = useCluster();
let title;
try {
title = parseTokenSwapInstructionTitle(ix);
} catch (error) {
reportError(error, {
url: url,
signature: signature,
});
}
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title={`Token Swap: ${title || "Unknown"}`}
defaultRaw
/>
);
}

View File

@ -0,0 +1,38 @@
import { MARKETS } from "@project-serum/serum";
import { TransactionInstruction } from "@solana/web3.js";
const SERUM_PROGRAM_ID = "4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn";
export function isSerumInstruction(instruction: TransactionInstruction) {
return (
instruction.programId.toBase58() === SERUM_PROGRAM_ID ||
MARKETS.some(
(market) =>
market.programId && market.programId.equals(instruction.programId)
)
);
}
const SERUM_CODE_LOOKUP: { [key: number]: string } = {
0: "Initialize Market",
1: "New Order",
2: "Match Orders",
3: "Consume Events",
4: "Cancel Order",
5: "Settle Funds",
6: "Cancel Order By Client Id",
7: "Disable Market",
8: "Sweep Fees",
};
export function parseSerumInstructionTitle(
instruction: TransactionInstruction
): string {
const code = instruction.data.slice(1, 5).readUInt32LE(0);
if (!(code in SERUM_CODE_LOOKUP)) {
throw new Error(`Unrecognized Serum instruction code: ${code}`);
}
return SERUM_CODE_LOOKUP[code];
}

View File

@ -0,0 +1,38 @@
import { TransactionInstruction } from "@solana/web3.js";
export const PROGRAM_IDS: string[] = [
"9qvG1zUp8xF1Bi4m6UdRNby1BAAuaDrUxSpv4CmRRMjL", // mainnet
"2n2dsFSgmPcZ8jkmBZLGUM2nzuFqcBGQ3JEEj6RJJcEg", // testnet
"9tdctNJuFsYZ6VrKfKEuwwbPp4SFdFw3jYBZU8QUtzeX", // testnet - legacy
"CrRvVBS4Hmj47TPU3cMukurpmCUYUrdHYxTQBxncBGqw", // testnet - legacy
"BSfTAcBdqmvX5iE2PW88WFNNp2DHhLUaBKk5WrnxVkcJ", // devnet
"H1E1G7eD5Rrcy43xvDxXCsjkRggz7MWNMLGJ8YNzJ8PM", // devnet - legacy
"CMoteLxSPVPoc7Drcggf3QPg3ue8WPpxYyZTg77UGqHo", // devnet - legacy
"EEuPz4iZA5reBUeZj6x1VzoiHfYeHMppSCnHZasRFhYo", // devnet - legacy
"5rdpyt5iGfr68qt28hkefcFyF4WtyhTwqKDmHSBG8GZx", // localnet
];
const INSTRUCTION_LOOKUP: { [key: number]: string } = {
0: "Initialize Swap",
1: "Swap",
2: "Deposit",
3: "Withdraw",
};
export function isTokenSwapInstruction(
instruction: TransactionInstruction
): boolean {
return PROGRAM_IDS.includes(instruction.programId.toBase58());
}
export function parseTokenSwapInstructionTitle(
instruction: TransactionInstruction
): string {
const code = instruction.data[0];
if (!(code in INSTRUCTION_LOOKUP)) {
throw new Error(`Unrecognized Token Swap instruction code: ${code}`);
}
return INSTRUCTION_LOOKUP[code];
}

View File

@ -24,11 +24,14 @@ import { displayTimestamp } from "utils/date";
import { InfoTooltip } from "components/common/InfoTooltip";
import { Address } from "components/common/Address";
import { Signature } from "components/common/Signature";
import { intoTransactionInstruction, isSerumInstruction } from "utils/tx";
import { intoTransactionInstruction } from "utils/tx";
import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard";
import { FetchStatus } from "providers/cache";
import { SerumDetailsCard } from "components/instruction/SerumDetailsCard";
import { Slot } from "components/common/Slot";
import { isTokenSwapInstruction } from "components/instruction/token-swap/types";
import { TokenSwapDetailsCard } from "components/instruction/TokenSwapDetailsCard";
import { isSerumInstruction } from "components/instruction/serum/types";
const AUTO_REFRESH_INTERVAL = 2000;
const ZERO_CONFIRMATION_BAILOUT = 5;
@ -467,6 +470,8 @@ function InstructionsSection({ signature }: SignatureProps) {
if (isSerumInstruction(ix)) {
return <SerumDetailsCard key={index} {...props} />;
} else if (isTokenSwapInstruction(ix)) {
return <TokenSwapDetailsCard key={index} {...props} />;
} else {
return <UnknownDetailsCard key={index} {...props} />;
}

View File

@ -17,10 +17,6 @@ import { TokenRegistry } from "tokenRegistry";
import { Cluster } from "providers/cluster";
import { SerumMarketRegistry } from "serumMarketRegistry";
export const EXTERNAL_PROGRAMS: { [key: string]: string } = {
Serum: "4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn",
};
export type ProgramName = typeof PROGRAM_IDS[keyof typeof PROGRAM_IDS];
export const PROGRAM_IDS = {
@ -127,35 +123,3 @@ export function intoParsedTransaction(tx: Transaction): ParsedTransaction {
},
};
}
export function isSerumInstruction(instruction: TransactionInstruction) {
return instruction.programId.toBase58() === EXTERNAL_PROGRAMS["Serum"];
}
const SERUM_CODE_LOOKUP: { [key: number]: string } = {
0: "Initialize Market",
1: "New Order",
2: "Match Order",
3: "Consume Events",
4: "Cancel Order",
5: "Settle Funds",
6: "Cancel Order By Client Id",
7: "Disable Market",
8: "Sweep Fees",
};
export function parseSerumInstructionTitle(
instruction: TransactionInstruction
): string {
try {
const code = instruction.data.slice(1, 5).readUInt32LE(0);
if (!(code in SERUM_CODE_LOOKUP)) {
throw new Error(`Unrecognized Serum instruction code: ${code}`);
}
return SERUM_CODE_LOOKUP[code];
} catch (error) {
throw error;
}
}