explorer: add Pyth instruction support (#23551)

This commit is contained in:
Richard Patel
2022-03-11 14:59:16 +01:00
committed by GitHub
parent 9b591286d7
commit 3d021cffa3
16 changed files with 1547 additions and 112 deletions

View File

@@ -0,0 +1,60 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { AddMappingParams } from "./program";
export default function AddMappingDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: AddMappingParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Add Mapping Account"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Mapping Account</td>
<td className="text-lg-end">
<Address pubkey={info.mappingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Next Mapping Account</td>
<td className="text-lg-end">
<Address pubkey={info.nextMappingPubkey} alignRight link />
</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,70 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { AddPriceParams, PriceType } from "./program";
export default function AddPriceDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: AddPriceParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Add Price Account"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Product Account</td>
<td className="text-lg-end">
<Address pubkey={info.productPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Price Account</td>
<td className="text-lg-end">
<Address pubkey={info.pricePubkey} alignRight link />
</td>
</tr>
<tr>
<td>Exponent</td>
<td className="text-lg-end">{info.exponent}</td>
</tr>
<tr>
<td>Price Type</td>
<td className="text-lg-end">{PriceType[info.priceType]}</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,60 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { AddProductParams } from "./program";
export default function AddProductDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: AddProductParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Add Product"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Mapping Account</td>
<td className="text-lg-end">
<Address pubkey={info.mappingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Product Account</td>
<td className="text-lg-end">
<Address pubkey={info.productPubkey} alignRight link />
</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,53 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { AggregatePriceParams } from "./program";
export default function AggregatePriceDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: AggregatePriceParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Update Price"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Price Account</td>
<td className="text-lg-end">
<Address pubkey={info.pricePubkey} alignRight link />
</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,55 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { BasePublisherOperationParams } from "./program";
export default function BasePublisherOperationCard({
ix,
index,
result,
operationName,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
operationName: string;
info: BasePublisherOperationParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title={`Pyth: ${operationName}`}
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Price Account</td>
<td className="text-lg-end">
<Address pubkey={info.pricePubkey} alignRight link />
</td>
</tr>
<tr>
<td>Publisher</td>
<td className="text-lg-end">
<Address pubkey={info.publisherPubkey} alignRight link />
</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,53 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { InitMappingParams } from "./program";
export default function InitMappingDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: InitMappingParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Init Mapping Account"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Mapping Account</td>
<td className="text-lg-end">
<Address pubkey={info.mappingPubkey} alignRight link />
</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,63 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { InitPriceParams, PriceType } from "./program";
export default function InitPriceDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: InitPriceParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Init Price Account"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Price Account</td>
<td className="text-lg-end">
<Address pubkey={info.pricePubkey} alignRight link />
</td>
</tr>
<tr>
<td>Exponent</td>
<td className="text-lg-end">{info.exponent}</td>
</tr>
<tr>
<td>Price Type</td>
<td className="text-lg-end">{PriceType[info.priceType]}</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,123 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { useCluster } from "providers/cluster";
import { reportError } from "utils/sentry";
import { InstructionCard } from "../InstructionCard";
import { PythInstruction } from "./program";
import UpdatePriceDetailsCard from "./UpdatePriceDetailsCard";
import BasePublisherOperationCard from "./BasePublisherOperationCard";
import AddProductDetailsCard from "./AddProductDetailsCard";
import AddPriceDetailsCard from "./AddPriceDetailsCard";
import UpdateProductDetailsCard from "./UpdateProductDetailsCard";
import InitMappingDetailsCard from "./InitMappingDetailsCard";
import AddMappingDetailsCard from "./AddMappingDetailsCard";
import AggregatePriceDetailsCard from "./AggregatePriceDetailsCard";
import InitPriceDetailsCard from "./InitPriceDetailsCard";
export function PythDetailsCard(props: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
signature: string;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
const { url } = useCluster();
const { ix, index, result, signature, innerCards, childIndex } = props;
try {
let ixType = PythInstruction.decodeInstructionType(ix);
switch (ixType) {
case "InitMapping":
return (
<InitMappingDetailsCard
info={PythInstruction.decodeInitMapping(ix)}
{...props}
/>
);
case "AddMapping":
return (
<AddMappingDetailsCard
info={PythInstruction.decodeAddMapping(ix)}
{...props}
/>
);
case "AddProduct":
return (
<AddProductDetailsCard
info={PythInstruction.decodeAddProduct(ix)}
{...props}
/>
);
case "UpdateProduct":
return (
<UpdateProductDetailsCard
info={PythInstruction.decodeUpdateProduct(ix)}
{...props}
/>
);
case "AddPrice":
return (
<AddPriceDetailsCard
info={PythInstruction.decodeAddPrice(ix)}
{...props}
/>
);
case "AddPublisher":
return (
<BasePublisherOperationCard
operationName="Add Publisher"
info={PythInstruction.decodeAddPublisher(ix)}
{...props}
/>
);
case "DeletePublisher":
return (
<BasePublisherOperationCard
operationName="Delete Publisher"
info={PythInstruction.decodeDeletePublisher(ix)}
{...props}
/>
);
case "UpdatePrice":
return (
<UpdatePriceDetailsCard
info={PythInstruction.decodeUpdatePrice(ix)}
{...props}
/>
);
case "AggregatePrice":
return (
<AggregatePriceDetailsCard
info={PythInstruction.decodeAggregatePrice(ix)}
{...props}
/>
);
case "InitPrice":
return (
<InitPriceDetailsCard
info={PythInstruction.decodeInitPrice(ix)}
{...props}
/>
);
}
} catch (error) {
reportError(error, {
url: url,
signature: signature,
});
}
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title={`Pyth: Unknown`}
innerCards={innerCards}
childIndex={childIndex}
defaultRaw
/>
);
}

View File

@@ -0,0 +1,58 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { SetMinPublishersParams } from "./program";
export default function SetMinPublishersDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: SetMinPublishersParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Set Minimum Number Of Publishers"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Price Account</td>
<td className="text-lg-end">
<Address pubkey={info.pricePubkey} alignRight link />
</td>
</tr>
<tr>
<td>Min Publishers</td>
<td className="text-lg-end">{info.minPublishers}</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,73 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { InstructionCard } from "../InstructionCard";
import { TradingStatus, UpdatePriceParams } from "./program";
export default function UpdatePriceDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: UpdatePriceParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Update Price"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Publisher</td>
<td className="text-lg-end">
<Address pubkey={info.publisherPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Price Account</td>
<td className="text-lg-end">
<Address pubkey={info.pricePubkey} alignRight link />
</td>
</tr>
<tr>
<td>Status</td>
<td className="text-lg-end">{TradingStatus[info.status]}</td>
</tr>
<tr>
<td>Price</td>
<td className="text-lg-end">{info.price}</td>
</tr>
<tr>
<td>Conf</td>
<td className="text-lg-end">{info.conf}</td>
</tr>
<tr>
<td>Publish Slot</td>
<td className="text-lg-end">{info.publishSlot}</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,82 @@
import React from "react";
import { SignatureResult, TransactionInstruction } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { Copyable } from "components/common/Copyable";
import { InstructionCard } from "../InstructionCard";
import { UpdateProductParams } from "./program";
export default function UpdateProductDetailsCard({
ix,
index,
result,
info,
innerCards,
childIndex,
}: {
ix: TransactionInstruction;
index: number;
result: SignatureResult;
info: UpdateProductParams;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
const attrsJSON = JSON.stringify(
Object.fromEntries(info.attributes),
null,
2
);
function Content() {
return (
<Copyable text={attrsJSON}>
<pre className="d-inline-block text-start mb-0">{attrsJSON}</pre>
</Copyable>
);
}
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Pyth: Update Product"
innerCards={innerCards}
childIndex={childIndex}
>
<tr>
<td>Program</td>
<td className="text-lg-end">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Funding Account</td>
<td className="text-lg-end">
<Address pubkey={info.fundingPubkey} alignRight link />
</td>
</tr>
<tr>
<td>Product Account</td>
<td className="text-lg-end">
<Address pubkey={info.productPubkey} alignRight link />
</td>
</tr>
<tr>
<td>
Attributes <span className="text-muted">(JSON)</span>
</td>
<td className="text-lg-end">
<div className="d-none d-lg-flex align-items-center justify-content-end">
<Content />
</div>
<div className="d-flex d-lg-none align-items-center">
<Content />
</div>
</td>
</tr>
</InstructionCard>
);
}

View File

@@ -0,0 +1,482 @@
import * as BufferLayout from "@solana/buffer-layout";
import {
InstructionType,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { Layout, uint8ArrayToBuffer } from "@solana/buffer-layout";
/**
* An enumeration of valid PythInstructionTypes
*/
export type PythInstructionType =
| "InitMapping"
| "AddMapping"
| "AddProduct"
| "UpdateProduct"
| "AddPrice"
| "AddPublisher"
| "DeletePublisher"
| "UpdatePrice"
| "AggregatePrice"
| "InitPrice"
| "InitTest"
| "UpdateTest"
| "SetMinPublishers";
export function headerLayout(property: string = "header") {
return BufferLayout.struct(
[BufferLayout.u32("version"), BufferLayout.u32("type")],
property
);
}
function decodeData(type: InstructionType, buffer: Buffer): any {
let data;
try {
data = type.layout.decode(buffer);
} catch (err) {
throw new Error("invalid instruction; " + err);
}
if (data.header.type !== type.index) {
throw new Error(
`invalid instruction; instruction index mismatch ${data.header.type} != ${type.index}`
);
}
return data;
}
/**
* An uint8 length-prefixed UTF-8 string.
*/
class LPString extends Layout {
getSpan(b: Uint8Array, offset?: number): number {
return 1 + b[offset || 0];
}
decode(b: Uint8Array, offset?: number): string {
if (offset === undefined) {
offset = 0;
}
return uint8ArrayToBuffer(b)
.slice(offset + 1, offset + b[offset] + 1)
.toString("utf-8");
}
}
/**
* A list that fills up all the available space with its elements.
*/
class GreedyList extends Layout {
private element: Layout;
constructor(element: Layout, property?: string) {
super(-1, property);
this.element = element;
}
getSpan(b: Uint8Array, offset?: number): number {
return b.length - (offset || 0);
}
decode(b: Uint8Array, offset?: number): string[] {
if (offset === undefined) {
offset = 0;
}
const strs = [];
while (offset < b.length) {
strs.push(this.element.decode(b, offset));
offset += this.element.getSpan(b, offset);
}
return strs;
}
}
/**
* An enumeration of valid Pyth instruction layouts
* @internal
*/
export const PYTH_INSTRUCTION_LAYOUTS: {
[type in PythInstructionType]: InstructionType;
} = Object.freeze({
InitMapping: {
index: 0,
layout: BufferLayout.struct([headerLayout()]),
},
AddMapping: {
index: 1,
layout: BufferLayout.struct([headerLayout()]),
},
AddProduct: {
index: 2,
layout: BufferLayout.struct([headerLayout()]),
},
UpdateProduct: {
index: 3,
layout: BufferLayout.struct([
headerLayout(),
new GreedyList(
BufferLayout.struct([
new LPString(-1, "key"),
new LPString(-1, "value"),
]),
"attributes"
),
]),
},
AddPrice: {
index: 4,
layout: BufferLayout.struct([
headerLayout(),
BufferLayout.s32("exponent"),
BufferLayout.u32("priceType"),
]),
},
AddPublisher: {
index: 5,
layout: BufferLayout.struct([
headerLayout(),
BufferLayout.blob(32, "publisherPubkey"),
]),
},
DeletePublisher: {
index: 6,
layout: BufferLayout.struct([
headerLayout(),
BufferLayout.blob(32, "publisherPubkey"),
]),
},
UpdatePrice: {
index: 7,
layout: BufferLayout.struct([
headerLayout(),
BufferLayout.u32("status"),
BufferLayout.u32("unused1"),
BufferLayout.ns64("price"),
BufferLayout.nu64("conf"),
BufferLayout.nu64("publishSlot"),
]),
},
AggregatePrice: {
index: 8,
layout: BufferLayout.struct([headerLayout()]),
},
InitPrice: {
index: 9,
layout: BufferLayout.struct([
headerLayout(),
BufferLayout.s32("exponent"),
BufferLayout.u32("priceType"),
]),
},
InitTest: {
index: 10,
layout: BufferLayout.struct([headerLayout()]),
},
UpdateTest: {
index: 11,
layout: BufferLayout.struct([headerLayout()]),
},
SetMinPublishers: {
index: 12,
layout: BufferLayout.struct([
headerLayout(),
BufferLayout.u8("minPublishers"),
BufferLayout.blob(3, "unused1"),
]),
},
});
export enum PriceType {
Unknown = 0,
Price,
}
export enum TradingStatus {
Unknown = 0,
Trading,
Halted,
Auction,
}
export type InitMappingParams = {
fundingPubkey: PublicKey;
mappingPubkey: PublicKey;
};
export type AddMappingParams = {
fundingPubkey: PublicKey;
mappingPubkey: PublicKey;
nextMappingPubkey: PublicKey;
};
export type AddProductParams = {
fundingPubkey: PublicKey;
mappingPubkey: PublicKey;
productPubkey: PublicKey;
};
export type UpdateProductParams = {
fundingPubkey: PublicKey;
productPubkey: PublicKey;
attributes: Map<String, String>;
};
export type AddPriceParams = {
fundingPubkey: PublicKey;
productPubkey: PublicKey;
pricePubkey: PublicKey;
exponent: number;
priceType: PriceType;
};
export type BasePublisherOperationParams = {
signerPubkey: PublicKey;
pricePubkey: PublicKey;
publisherPubkey: PublicKey;
};
export type UpdatePriceParams = {
publisherPubkey: PublicKey;
pricePubkey: PublicKey;
status: TradingStatus;
price: number;
conf: number;
publishSlot: number;
};
export type AggregatePriceParams = {
fundingPubkey: PublicKey;
pricePubkey: PublicKey;
};
export type InitPriceParams = {
fundingPubkey: PublicKey;
pricePubkey: PublicKey;
exponent: number;
priceType: PriceType;
};
export type SetMinPublishersParams = {
fundingPubkey: PublicKey;
pricePubkey: PublicKey;
minPublishers: number;
};
/**
* Pyth Instruction class
*/
export class PythInstruction {
/**
* Decode a Pyth instruction and retrieve the instruction type.
*/
static decodeInstructionType(
instruction: TransactionInstruction
): PythInstructionType {
const header = headerLayout().decode(instruction.data);
if (header.version !== 2) {
throw new Error(`Unsupported Pyth version: ${header.version}`);
}
const typeIndex = header.type;
let type: PythInstructionType | undefined;
for (const [ixType, layout] of Object.entries(PYTH_INSTRUCTION_LAYOUTS)) {
if (layout.index === typeIndex) {
type = ixType as PythInstructionType;
break;
}
}
if (!type) {
throw new Error("Instruction type incorrect; not a PythInstruction");
}
return type;
}
/**
* Decode an "init mapping" instruction and retrieve the instruction params.
*/
static decodeInitMapping(
instruction: TransactionInstruction
): InitMappingParams {
decodeData(PYTH_INSTRUCTION_LAYOUTS.InitMapping, instruction.data);
return {
fundingPubkey: instruction.keys[0].pubkey,
mappingPubkey: instruction.keys[1].pubkey,
};
}
/**
* Decode an "add mapping" instruction and retrieve the instruction params.
*/
static decodeAddMapping(
instruction: TransactionInstruction
): AddMappingParams {
decodeData(PYTH_INSTRUCTION_LAYOUTS.AddMapping, instruction.data);
return {
fundingPubkey: instruction.keys[0].pubkey,
mappingPubkey: instruction.keys[1].pubkey,
nextMappingPubkey: instruction.keys[2].pubkey,
};
}
/**
* Decode an "add product" instruction and retrieve the instruction params.
*/
static decodeAddProduct(
instruction: TransactionInstruction
): AddProductParams {
decodeData(PYTH_INSTRUCTION_LAYOUTS.AddProduct, instruction.data);
return {
fundingPubkey: instruction.keys[0].pubkey,
mappingPubkey: instruction.keys[1].pubkey,
productPubkey: instruction.keys[2].pubkey,
};
}
/**
* Decode an "add product" instruction and retrieve the instruction params.
*/
static decodeUpdateProduct(
instruction: TransactionInstruction
): UpdateProductParams {
const { attributes } = decodeData(
PYTH_INSTRUCTION_LAYOUTS.UpdateProduct,
instruction.data
);
return {
fundingPubkey: instruction.keys[0].pubkey,
productPubkey: instruction.keys[1].pubkey,
attributes: new Map(
attributes.map((kv: { key: string; value: string }) => [
kv.key,
kv.value,
])
),
};
}
/**
* Decode an "add price" instruction and retrieve the instruction params.
*/
static decodeAddPrice(instruction: TransactionInstruction): AddPriceParams {
const { exponent, priceType } = decodeData(
PYTH_INSTRUCTION_LAYOUTS.AddPrice,
instruction.data
);
return {
fundingPubkey: instruction.keys[0].pubkey,
productPubkey: instruction.keys[1].pubkey,
pricePubkey: instruction.keys[2].pubkey,
exponent,
priceType,
};
}
/**
* Decode an "add publisher" instruction and retrieve the instruction params.
*/
static decodeAddPublisher(
instruction: TransactionInstruction
): BasePublisherOperationParams {
const { publisherPubkey } = decodeData(
PYTH_INSTRUCTION_LAYOUTS.AddPublisher,
instruction.data
);
return {
signerPubkey: instruction.keys[0].pubkey,
pricePubkey: instruction.keys[1].pubkey,
publisherPubkey: PublicKey.decode(publisherPubkey),
};
}
/**
* Decode an "delete publisher" instruction and retrieve the instruction params.
*/
static decodeDeletePublisher(
instruction: TransactionInstruction
): BasePublisherOperationParams {
const { publisherPubkey } = decodeData(
PYTH_INSTRUCTION_LAYOUTS.DeletePublisher,
instruction.data
);
return {
signerPubkey: instruction.keys[0].pubkey,
pricePubkey: instruction.keys[1].pubkey,
publisherPubkey: PublicKey.decode(publisherPubkey),
};
}
/**
* Decode an "update price" instruction and retrieve the instruction params.
*/
static decodeUpdatePrice(
instruction: TransactionInstruction
): UpdatePriceParams {
const { status, price, conf, publishSlot } = decodeData(
PYTH_INSTRUCTION_LAYOUTS.UpdatePrice,
instruction.data
);
return {
publisherPubkey: instruction.keys[0].pubkey,
pricePubkey: instruction.keys[1].pubkey,
status,
price,
conf,
publishSlot,
};
}
/**
* Decode an "aggregate price" instruction and retrieve the instruction params.
*/
static decodeAggregatePrice(
instruction: TransactionInstruction
): AggregatePriceParams {
decodeData(PYTH_INSTRUCTION_LAYOUTS.AggregatePrice, instruction.data);
return {
fundingPubkey: instruction.keys[0].pubkey,
pricePubkey: instruction.keys[1].pubkey,
};
}
/**
* Decode an "init price" instruction and retrieve the instruction params.
*/
static decodeInitPrice(instruction: TransactionInstruction): InitPriceParams {
const { exponent, priceType } = decodeData(
PYTH_INSTRUCTION_LAYOUTS.InitPrice,
instruction.data
);
return {
fundingPubkey: instruction.keys[0].pubkey,
pricePubkey: instruction.keys[1].pubkey,
exponent,
priceType,
};
}
/**
* Decode an "set min publishers" instruction and retrieve the instruction params.
*/
static decodeSetMinPublishers(
instruction: TransactionInstruction
): SetMinPublishersParams {
const { minPublishers } = decodeData(
PYTH_INSTRUCTION_LAYOUTS.SetMinPublishers,
instruction.data
);
return {
fundingPubkey: instruction.keys[0].pubkey,
pricePubkey: instruction.keys[1].pubkey,
minPublishers,
};
}
}

View File

@@ -0,0 +1,13 @@
import { TransactionInstruction } from "@solana/web3.js";
export const PROGRAM_IDS: string[] = [
"gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s", // devnet
"8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz", // testnet
"FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH", // mainnet
];
export function isPythInstruction(
instruction: TransactionInstruction
): boolean {
return PROGRAM_IDS.includes(instruction.programId.toBase58());
}

View File

@@ -41,6 +41,8 @@ import { isWormholeInstruction } from "components/instruction/wormhole/types";
import { AssociatedTokenDetailsCard } from "components/instruction/AssociatedTokenDetailsCard";
import { isMangoInstruction } from "components/instruction/mango/types";
import { MangoDetailsCard } from "components/instruction/MangoDetails";
import { isPythInstruction } from "components/instruction/pyth/types";
import { PythDetailsCard } from "components/instruction/pyth/PythDetailsCard";
export type InstructionDetailsProps = {
tx: ParsedTransaction;
@@ -222,6 +224,8 @@ function renderInstructionCard({
return <TokenLendingDetailsCard key={key} {...props} />;
} else if (isWormholeInstruction(transactionIx)) {
return <WormholeDetailsCard key={key} {...props} />;
} else if (isPythInstruction(transactionIx)) {
return <PythDetailsCard key={key} {...props} />;
} else {
return <UnknownDetailsCard key={key} {...props} />;
}