explorer: Serum DEX instruction full decoding and instruction cards (#13330)
* map serum instructions in tokenhistory card * add token swap instruction parsing * add serum instruction builders * add new serum instruction detail cards * fix decode bug on cancel order by client id * avoid parsing unsupported instructions
This commit is contained in:
parent
44b12a1594
commit
cef0d5879f
|
@ -3,24 +3,81 @@ import { TransactionInstruction, SignatureResult } from "@solana/web3.js";
|
||||||
import { InstructionCard } from "./InstructionCard";
|
import { InstructionCard } from "./InstructionCard";
|
||||||
import { useCluster } from "providers/cluster";
|
import { useCluster } from "providers/cluster";
|
||||||
import { reportError } from "utils/sentry";
|
import { reportError } from "utils/sentry";
|
||||||
import { parseSerumInstructionTitle } from "./serum/types";
|
import {
|
||||||
|
decodeCancelOrder,
|
||||||
|
decodeCancelOrderByClientId,
|
||||||
|
decodeConsumeEvents,
|
||||||
|
decodeInitializeMarket,
|
||||||
|
decodeMatchOrders,
|
||||||
|
decodeNewOrder,
|
||||||
|
decodeSettleFunds,
|
||||||
|
parseSerumInstructionCode,
|
||||||
|
parseSerumInstructionKey,
|
||||||
|
parseSerumInstructionTitle,
|
||||||
|
SERUM_DECODED_MAX,
|
||||||
|
} from "./serum/types";
|
||||||
|
import { NewOrderDetailsCard } from "./serum/NewOrderDetailsCard";
|
||||||
|
import { MatchOrdersDetailsCard } from "./serum/MatchOrdersDetailsCard";
|
||||||
|
import { InitializeMarketDetailsCard } from "./serum/InitializeMarketDetailsCard";
|
||||||
|
import { ConsumeEventsDetailsCard } from "./serum/ConsumeEventsDetails";
|
||||||
|
import { CancelOrderDetailsCard } from "./serum/CancelOrderDetails";
|
||||||
|
import { CancelOrderByClientIdDetailsCard } from "./serum/CancelOrderByClientIdDetails";
|
||||||
|
import { SettleFundsDetailsCard } from "./serum/SettleFundsDetailsCard";
|
||||||
|
|
||||||
export function SerumDetailsCard({
|
export function SerumDetailsCard(props: {
|
||||||
ix,
|
|
||||||
index,
|
|
||||||
result,
|
|
||||||
signature,
|
|
||||||
}: {
|
|
||||||
ix: TransactionInstruction;
|
ix: TransactionInstruction;
|
||||||
index: number;
|
index: number;
|
||||||
result: SignatureResult;
|
result: SignatureResult;
|
||||||
signature: string;
|
signature: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { ix, index, result, signature } = props;
|
||||||
|
|
||||||
const { url } = useCluster();
|
const { url } = useCluster();
|
||||||
|
|
||||||
let title;
|
let title;
|
||||||
try {
|
try {
|
||||||
title = parseSerumInstructionTitle(ix);
|
title = parseSerumInstructionTitle(ix);
|
||||||
|
const code = parseSerumInstructionCode(ix);
|
||||||
|
|
||||||
|
if (code <= SERUM_DECODED_MAX) {
|
||||||
|
switch (parseSerumInstructionKey(ix)) {
|
||||||
|
case "initializeMarket":
|
||||||
|
return (
|
||||||
|
<InitializeMarketDetailsCard
|
||||||
|
info={decodeInitializeMarket(ix)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "newOrder":
|
||||||
|
return <NewOrderDetailsCard info={decodeNewOrder(ix)} {...props} />;
|
||||||
|
case "matchOrders":
|
||||||
|
return (
|
||||||
|
<MatchOrdersDetailsCard info={decodeMatchOrders(ix)} {...props} />
|
||||||
|
);
|
||||||
|
case "consumeEvents":
|
||||||
|
return (
|
||||||
|
<ConsumeEventsDetailsCard
|
||||||
|
info={decodeConsumeEvents(ix)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "cancelOrder":
|
||||||
|
return (
|
||||||
|
<CancelOrderDetailsCard info={decodeCancelOrder(ix)} {...props} />
|
||||||
|
);
|
||||||
|
case "cancelOrderByClientId":
|
||||||
|
return (
|
||||||
|
<CancelOrderByClientIdDetailsCard
|
||||||
|
info={decodeCancelOrderByClientId(ix)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "settleFunds":
|
||||||
|
return (
|
||||||
|
<SettleFundsDetailsCard info={decodeSettleFunds(ix)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reportError(error, {
|
reportError(error, {
|
||||||
url: url,
|
url: url,
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { InstructionCard } from "../InstructionCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { CancelOrderByClientId } from "./types";
|
||||||
|
|
||||||
|
export function CancelOrderByClientIdDetailsCard(props: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
result: SignatureResult;
|
||||||
|
info: CancelOrderByClientId;
|
||||||
|
}) {
|
||||||
|
const { ix, index, result, info } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstructionCard
|
||||||
|
ix={ix}
|
||||||
|
index={index}
|
||||||
|
result={result}
|
||||||
|
title="Serum: Cancel Order By Client Id"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>Market</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.market} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Open Orders</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.openOrders} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Request Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.requestQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Owner</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.owner} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Client Id</td>
|
||||||
|
<td className="text-lg-right">{info.clientId.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
</InstructionCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { InstructionCard } from "../InstructionCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { CancelOrder } from "./types";
|
||||||
|
|
||||||
|
export function CancelOrderDetailsCard(props: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
result: SignatureResult;
|
||||||
|
info: CancelOrder;
|
||||||
|
}) {
|
||||||
|
const { ix, index, result, info } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstructionCard
|
||||||
|
ix={ix}
|
||||||
|
index={index}
|
||||||
|
result={result}
|
||||||
|
title="Serum: Cancel Order"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>Program</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.programId} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Market</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.market} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Open Orders</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.openOrders} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Request Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.requestQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Owner</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.owner} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Side</td>
|
||||||
|
<td className="text-lg-right">{info.side}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Open Orders Slot</td>
|
||||||
|
<td className="text-lg-right">{info.openOrdersSlot}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Order Id</td>
|
||||||
|
<td className="text-lg-right">{info.orderId.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
</InstructionCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { InstructionCard } from "../InstructionCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { ConsumeEvents } from "./types";
|
||||||
|
|
||||||
|
export function ConsumeEventsDetailsCard(props: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
result: SignatureResult;
|
||||||
|
info: ConsumeEvents;
|
||||||
|
}) {
|
||||||
|
const { ix, index, result, info } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstructionCard
|
||||||
|
ix={ix}
|
||||||
|
index={index}
|
||||||
|
result={result}
|
||||||
|
title="Serum: Consume Events"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>Program</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.programId} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Market</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.market} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Event Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.eventQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Open Orders Accounts</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
{info.openOrdersAccounts.map((account, index) => {
|
||||||
|
return <Address pubkey={account} key={index} alignRight link />;
|
||||||
|
})}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Limit</td>
|
||||||
|
<td className="text-lg-right">{info.limit}</td>
|
||||||
|
</tr>
|
||||||
|
</InstructionCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { InstructionCard } from "../InstructionCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { InitializeMarket } from "./types";
|
||||||
|
|
||||||
|
export function InitializeMarketDetailsCard(props: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
result: SignatureResult;
|
||||||
|
info: InitializeMarket;
|
||||||
|
}) {
|
||||||
|
const { ix, index, result, info } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstructionCard
|
||||||
|
ix={ix}
|
||||||
|
index={index}
|
||||||
|
result={result}
|
||||||
|
title="Serum: Initialize Market"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>Program</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.programId} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Market</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.market} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Request Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.requestQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Event Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.eventQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Bids</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.bids} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Asks</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.asks} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Base Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.baseVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.quoteVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Base Mint</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.baseMint} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Mint</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.quoteMint} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Base Lot Size</td>
|
||||||
|
<td className="text-lg-right">{info.baseLotSize.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Lot Size</td>
|
||||||
|
<td className="text-lg-right">{info.quoteLotSize.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Fee Rate Bps</td>
|
||||||
|
<td className="text-lg-right">{info.feeRateBps}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Dust THreshold</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
{info.quoteDustThreshold.toString(10)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Vault Signer Nonce</td>
|
||||||
|
<td className="text-lg-right">{info.vaultSignerNonce.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
</InstructionCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { InstructionCard } from "../InstructionCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { MatchOrders } from "./types";
|
||||||
|
|
||||||
|
export function MatchOrdersDetailsCard(props: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
result: SignatureResult;
|
||||||
|
info: MatchOrders;
|
||||||
|
}) {
|
||||||
|
const { ix, index, result, info } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstructionCard
|
||||||
|
ix={ix}
|
||||||
|
index={index}
|
||||||
|
result={result}
|
||||||
|
title="Serum: Match Orders"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>Program</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.programId} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Market</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.market} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Request Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.requestQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Event Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.eventQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Bids</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.bids} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Asks</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.asks} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Base Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.baseVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.quoteVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Limit</td>
|
||||||
|
<td className="text-lg-right">{info.limit}</td>
|
||||||
|
</tr>
|
||||||
|
</InstructionCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { InstructionCard } from "../InstructionCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { NewOrder } from "./types";
|
||||||
|
|
||||||
|
export function NewOrderDetailsCard(props: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
result: SignatureResult;
|
||||||
|
info: NewOrder;
|
||||||
|
}) {
|
||||||
|
const { ix, index, result, info } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstructionCard
|
||||||
|
ix={ix}
|
||||||
|
index={index}
|
||||||
|
result={result}
|
||||||
|
title="Serum: New Order"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>Program</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.programId} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Market</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.market} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Open Orders</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.openOrders} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Request Queue</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.requestQueue} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Payer</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.payer} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Owner</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.owner} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Base Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.baseVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.quoteVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Side</td>
|
||||||
|
<td className="text-lg-right">{info.side}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Order Type</td>
|
||||||
|
<td className="text-lg-right">{info.orderType}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Limit Price</td>
|
||||||
|
<td className="text-lg-right">{info.limitPrice.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Max Quantity</td>
|
||||||
|
<td className="text-lg-right">{info.maxQuantity.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Client Id</td>
|
||||||
|
<td className="text-lg-right">{info.clientId.toString(10)}</td>
|
||||||
|
</tr>
|
||||||
|
</InstructionCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { InstructionCard } from "../InstructionCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { SettleFunds } from "./types";
|
||||||
|
|
||||||
|
export function SettleFundsDetailsCard(props: {
|
||||||
|
ix: TransactionInstruction;
|
||||||
|
index: number;
|
||||||
|
result: SignatureResult;
|
||||||
|
info: SettleFunds;
|
||||||
|
}) {
|
||||||
|
const { ix, index, result, info } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstructionCard
|
||||||
|
ix={ix}
|
||||||
|
index={index}
|
||||||
|
result={result}
|
||||||
|
title="Serum: Settle Funds"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>Program</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.programId} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Market</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.market} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Open Orders</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.openOrders} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Owner</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.owner} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Base Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.baseVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Vault</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.quoteVault} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Base Wallet</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.baseWallet} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Quote Wallet</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.quoteWallet} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Vault Signer</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.vaultSigner} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{info.referrerQuoteWallet && (
|
||||||
|
<tr>
|
||||||
|
<td>Referrer Quote Wallet</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={info.referrerQuoteWallet} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</InstructionCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,8 +1,298 @@
|
||||||
import { MARKETS } from "@project-serum/serum";
|
/* eslint-disable @typescript-eslint/no-redeclare */
|
||||||
import { TransactionInstruction } from "@solana/web3.js";
|
|
||||||
|
import { decodeInstruction, MARKETS } from "@project-serum/serum";
|
||||||
|
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import BN from "bn.js";
|
||||||
|
import { coerce, enums, number, optional, pick, StructType } from "superstruct";
|
||||||
|
import { BigNumValue } from "validators/bignum";
|
||||||
|
import { Pubkey } from "validators/pubkey";
|
||||||
|
|
||||||
const SERUM_PROGRAM_ID = "4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn";
|
const SERUM_PROGRAM_ID = "4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn";
|
||||||
|
|
||||||
|
export const SERUM_DECODED_MAX = 6;
|
||||||
|
|
||||||
|
export type Side = StructType<typeof Side>;
|
||||||
|
export const Side = enums(["buy", "sell"]);
|
||||||
|
|
||||||
|
export type OrderType = StructType<typeof OrderType>;
|
||||||
|
export const OrderType = enums(["limit", "ioc", "postOnly"]);
|
||||||
|
|
||||||
|
export type InitializeMarket = {
|
||||||
|
market: PublicKey;
|
||||||
|
requestQueue: PublicKey;
|
||||||
|
eventQueue: PublicKey;
|
||||||
|
bids: PublicKey;
|
||||||
|
asks: PublicKey;
|
||||||
|
baseVault: PublicKey;
|
||||||
|
quoteVault: PublicKey;
|
||||||
|
baseMint: PublicKey;
|
||||||
|
quoteMint: PublicKey;
|
||||||
|
baseLotSize: BN;
|
||||||
|
quoteLotSize: BN;
|
||||||
|
feeRateBps: number;
|
||||||
|
vaultSignerNonce: BN;
|
||||||
|
quoteDustThreshold: BN;
|
||||||
|
programId: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InitializeMarketDecode = pick({
|
||||||
|
baseLotSize: BigNumValue,
|
||||||
|
quoteLotSize: BigNumValue,
|
||||||
|
feeRateBps: number(),
|
||||||
|
quoteDustThreshold: BigNumValue,
|
||||||
|
vaultSignerNonce: BigNumValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function decodeInitializeMarket(
|
||||||
|
ix: TransactionInstruction
|
||||||
|
): InitializeMarket {
|
||||||
|
const decoded = coerce(
|
||||||
|
decodeInstruction(ix.data).initializeMarket,
|
||||||
|
InitializeMarketDecode
|
||||||
|
);
|
||||||
|
|
||||||
|
let initializeMarket: InitializeMarket = {
|
||||||
|
market: ix.keys[0].pubkey,
|
||||||
|
requestQueue: ix.keys[1].pubkey,
|
||||||
|
eventQueue: ix.keys[2].pubkey,
|
||||||
|
bids: ix.keys[3].pubkey,
|
||||||
|
asks: ix.keys[4].pubkey,
|
||||||
|
baseVault: ix.keys[5].pubkey,
|
||||||
|
quoteVault: ix.keys[6].pubkey,
|
||||||
|
baseMint: ix.keys[7].pubkey,
|
||||||
|
quoteMint: ix.keys[8].pubkey,
|
||||||
|
programId: ix.programId,
|
||||||
|
baseLotSize: decoded.baseLotSize as BN,
|
||||||
|
quoteLotSize: decoded.quoteLotSize as BN,
|
||||||
|
feeRateBps: decoded.feeRateBps,
|
||||||
|
quoteDustThreshold: decoded.quoteDustThreshold as BN,
|
||||||
|
vaultSignerNonce: decoded.vaultSignerNonce as BN,
|
||||||
|
};
|
||||||
|
|
||||||
|
return initializeMarket;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NewOrder = {
|
||||||
|
market: PublicKey;
|
||||||
|
openOrders: PublicKey;
|
||||||
|
requestQueue: PublicKey;
|
||||||
|
payer: PublicKey;
|
||||||
|
owner: PublicKey;
|
||||||
|
baseVault: PublicKey;
|
||||||
|
quoteVault: PublicKey;
|
||||||
|
programId: PublicKey;
|
||||||
|
feeDiscountPubkey?: PublicKey;
|
||||||
|
side: Side;
|
||||||
|
limitPrice: BN;
|
||||||
|
maxQuantity: BN;
|
||||||
|
orderType: OrderType;
|
||||||
|
clientId: BN;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewOrderDecode = pick({
|
||||||
|
side: Side,
|
||||||
|
limitPrice: BigNumValue,
|
||||||
|
maxQuantity: BigNumValue,
|
||||||
|
orderType: OrderType,
|
||||||
|
clientId: BigNumValue,
|
||||||
|
feeDiscountPubkey: optional(Pubkey),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function decodeNewOrder(ix: TransactionInstruction): NewOrder {
|
||||||
|
const decoded = coerce(decodeInstruction(ix.data).newOrder, NewOrderDecode);
|
||||||
|
|
||||||
|
let newOrder: NewOrder = {
|
||||||
|
market: ix.keys[0].pubkey,
|
||||||
|
openOrders: ix.keys[1].pubkey,
|
||||||
|
requestQueue: ix.keys[2].pubkey,
|
||||||
|
payer: ix.keys[3].pubkey,
|
||||||
|
owner: ix.keys[4].pubkey,
|
||||||
|
baseVault: ix.keys[5].pubkey,
|
||||||
|
quoteVault: ix.keys[6].pubkey,
|
||||||
|
programId: ix.programId,
|
||||||
|
side: decoded.side as Side,
|
||||||
|
limitPrice: decoded.limitPrice as BN,
|
||||||
|
maxQuantity: decoded.maxQuantity as BN,
|
||||||
|
orderType: decoded.orderType as OrderType,
|
||||||
|
clientId: decoded.clientId as BN,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (decoded.feeDiscountPubkey) {
|
||||||
|
newOrder.feeDiscountPubkey = decoded.feeDiscountPubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MatchOrders = {
|
||||||
|
market: PublicKey;
|
||||||
|
requestQueue: PublicKey;
|
||||||
|
eventQueue: PublicKey;
|
||||||
|
bids: PublicKey;
|
||||||
|
asks: PublicKey;
|
||||||
|
baseVault: PublicKey;
|
||||||
|
quoteVault: PublicKey;
|
||||||
|
limit: number;
|
||||||
|
programId: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MatchOrdersDecode = pick({
|
||||||
|
limit: number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function decodeMatchOrders(ix: TransactionInstruction): MatchOrders {
|
||||||
|
const decoded = coerce(
|
||||||
|
decodeInstruction(ix.data).matchOrders,
|
||||||
|
MatchOrdersDecode
|
||||||
|
);
|
||||||
|
|
||||||
|
const matchOrders: MatchOrders = {
|
||||||
|
market: ix.keys[0].pubkey,
|
||||||
|
requestQueue: ix.keys[1].pubkey,
|
||||||
|
eventQueue: ix.keys[2].pubkey,
|
||||||
|
bids: ix.keys[3].pubkey,
|
||||||
|
asks: ix.keys[4].pubkey,
|
||||||
|
baseVault: ix.keys[5].pubkey,
|
||||||
|
quoteVault: ix.keys[6].pubkey,
|
||||||
|
programId: ix.programId,
|
||||||
|
limit: decoded.limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return matchOrders;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConsumeEvents = {
|
||||||
|
market: PublicKey;
|
||||||
|
eventQueue: PublicKey;
|
||||||
|
openOrdersAccounts: PublicKey[];
|
||||||
|
limit: number;
|
||||||
|
programId: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConsumeEventsDecode = pick({
|
||||||
|
limit: number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function decodeConsumeEvents(ix: TransactionInstruction): ConsumeEvents {
|
||||||
|
const decoded = coerce(
|
||||||
|
decodeInstruction(ix.data).consumeEvents,
|
||||||
|
ConsumeEventsDecode
|
||||||
|
);
|
||||||
|
|
||||||
|
const consumeEvents: ConsumeEvents = {
|
||||||
|
openOrdersAccounts: ix.keys.slice(0, -2).map((k) => k.pubkey),
|
||||||
|
market: ix.keys[ix.keys.length - 3].pubkey,
|
||||||
|
eventQueue: ix.keys[ix.keys.length - 2].pubkey,
|
||||||
|
programId: ix.programId,
|
||||||
|
limit: decoded.limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return consumeEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CancelOrder = {
|
||||||
|
market: PublicKey;
|
||||||
|
openOrders: PublicKey;
|
||||||
|
owner: PublicKey;
|
||||||
|
requestQueue: PublicKey;
|
||||||
|
side: "buy" | "sell";
|
||||||
|
orderId: BN;
|
||||||
|
openOrdersSlot: number;
|
||||||
|
programId: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CancelOrderDecode = pick({
|
||||||
|
side: Side,
|
||||||
|
orderId: BigNumValue,
|
||||||
|
openOrdersSlot: number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function decodeCancelOrder(ix: TransactionInstruction): CancelOrder {
|
||||||
|
const decoded = coerce(
|
||||||
|
decodeInstruction(ix.data).cancelOrder,
|
||||||
|
CancelOrderDecode
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelOrder: CancelOrder = {
|
||||||
|
market: ix.keys[0].pubkey,
|
||||||
|
openOrders: ix.keys[1].pubkey,
|
||||||
|
requestQueue: ix.keys[2].pubkey,
|
||||||
|
owner: ix.keys[3].pubkey,
|
||||||
|
programId: ix.programId,
|
||||||
|
openOrdersSlot: decoded.openOrdersSlot,
|
||||||
|
orderId: decoded.orderId as BN,
|
||||||
|
side: decoded.side,
|
||||||
|
};
|
||||||
|
|
||||||
|
return cancelOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CancelOrderByClientId = {
|
||||||
|
market: PublicKey;
|
||||||
|
openOrders: PublicKey;
|
||||||
|
owner: PublicKey;
|
||||||
|
requestQueue: PublicKey;
|
||||||
|
clientId: BN;
|
||||||
|
programId: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CancelOrderByClientIdDecode = pick({
|
||||||
|
clientId: BigNumValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function decodeCancelOrderByClientId(
|
||||||
|
ix: TransactionInstruction
|
||||||
|
): CancelOrderByClientId {
|
||||||
|
const decoded = coerce(
|
||||||
|
decodeInstruction(ix.data).cancelOrderByClientId,
|
||||||
|
CancelOrderByClientIdDecode
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelOrderByClientId: CancelOrderByClientId = {
|
||||||
|
market: ix.keys[0].pubkey,
|
||||||
|
openOrders: ix.keys[1].pubkey,
|
||||||
|
requestQueue: ix.keys[2].pubkey,
|
||||||
|
owner: ix.keys[3].pubkey,
|
||||||
|
programId: ix.programId,
|
||||||
|
clientId: decoded.clientId as BN,
|
||||||
|
};
|
||||||
|
|
||||||
|
return cancelOrderByClientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SettleFunds = {
|
||||||
|
market: PublicKey;
|
||||||
|
openOrders: PublicKey;
|
||||||
|
owner: PublicKey;
|
||||||
|
baseVault: PublicKey;
|
||||||
|
quoteVault: PublicKey;
|
||||||
|
baseWallet: PublicKey;
|
||||||
|
quoteWallet: PublicKey;
|
||||||
|
vaultSigner: PublicKey;
|
||||||
|
programId: PublicKey;
|
||||||
|
referrerQuoteWallet?: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function decodeSettleFunds(ix: TransactionInstruction): SettleFunds {
|
||||||
|
let settleFunds: SettleFunds = {
|
||||||
|
market: ix.keys[0].pubkey,
|
||||||
|
openOrders: ix.keys[1].pubkey,
|
||||||
|
owner: ix.keys[2].pubkey,
|
||||||
|
baseVault: ix.keys[3].pubkey,
|
||||||
|
quoteVault: ix.keys[4].pubkey,
|
||||||
|
baseWallet: ix.keys[5].pubkey,
|
||||||
|
quoteWallet: ix.keys[6].pubkey,
|
||||||
|
vaultSigner: ix.keys[7].pubkey,
|
||||||
|
programId: ix.programId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ix.keys.length > 9) {
|
||||||
|
settleFunds.referrerQuoteWallet = ix.keys[9].pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return settleFunds;
|
||||||
|
}
|
||||||
|
|
||||||
export function isSerumInstruction(instruction: TransactionInstruction) {
|
export function isSerumInstruction(instruction: TransactionInstruction) {
|
||||||
return (
|
return (
|
||||||
instruction.programId.toBase58() === SERUM_PROGRAM_ID ||
|
instruction.programId.toBase58() === SERUM_PROGRAM_ID ||
|
||||||
|
@ -13,6 +303,19 @@ export function isSerumInstruction(instruction: TransactionInstruction) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseSerumInstructionKey(
|
||||||
|
instruction: TransactionInstruction
|
||||||
|
): string {
|
||||||
|
const decoded = decodeInstruction(instruction.data);
|
||||||
|
const keys = Object.keys(decoded);
|
||||||
|
|
||||||
|
if (keys.length < 1) {
|
||||||
|
throw new Error("Serum instruction key not decoded");
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys[0];
|
||||||
|
}
|
||||||
|
|
||||||
const SERUM_CODE_LOOKUP: { [key: number]: string } = {
|
const SERUM_CODE_LOOKUP: { [key: number]: string } = {
|
||||||
0: "Initialize Market",
|
0: "Initialize Market",
|
||||||
1: "New Order",
|
1: "New Order",
|
||||||
|
@ -26,10 +329,14 @@ const SERUM_CODE_LOOKUP: { [key: number]: string } = {
|
||||||
9: "New Order",
|
9: "New Order",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function parseSerumInstructionCode(instruction: TransactionInstruction) {
|
||||||
|
return instruction.data.slice(1, 5).readUInt32LE(0);
|
||||||
|
}
|
||||||
|
|
||||||
export function parseSerumInstructionTitle(
|
export function parseSerumInstructionTitle(
|
||||||
instruction: TransactionInstruction
|
instruction: TransactionInstruction
|
||||||
): string {
|
): string {
|
||||||
const code = instruction.data.slice(1, 5).readUInt32LE(0);
|
const code = parseSerumInstructionCode(instruction);
|
||||||
|
|
||||||
if (!(code in SERUM_CODE_LOOKUP)) {
|
if (!(code in SERUM_CODE_LOOKUP)) {
|
||||||
throw new Error(`Unrecognized Serum instruction code: ${code}`);
|
throw new Error(`Unrecognized Serum instruction code: ${code}`);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { coercion, struct, Struct } from "superstruct";
|
import { coercion, struct, Struct } from "superstruct";
|
||||||
import BN from "bn.js";
|
import BN from "bn.js";
|
||||||
|
|
||||||
const BigNumValue = struct("BigNum", (value) => value instanceof BN);
|
export const BigNumValue = struct("BigNum", (value) => value instanceof BN);
|
||||||
export const BigNum: Struct<BN, any> = coercion(BigNumValue, (value) => {
|
export const BigNum: Struct<BN, any> = coercion(BigNumValue, (value) => {
|
||||||
if (typeof value === "string") return new BN(value, 10);
|
if (typeof value === "string") return new BN(value, 10);
|
||||||
throw new Error("invalid big num");
|
throw new Error("invalid big num");
|
||||||
|
|
Loading…
Reference in New Issue