Skip to content

Commit

Permalink
feat: implement versioned transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
sohrab- committed Dec 27, 2022
1 parent ae813cd commit 8f780e7
Show file tree
Hide file tree
Showing 24 changed files with 264 additions and 57 deletions.
70 changes: 63 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@solana/wallet-adapter-react": "^0.15.26",
"@solana/wallet-adapter-react-ui": "^0.9.25",
"@solana/wallet-adapter-solflare": "^0.6.20",
"@solana/web3.js": "^1.63.1",
"@solana/web3.js": "^1.70.3",
"assert": "^2.0.0",
"axios": "^0.27.2",
"bignumber.js": "^9.0.2",
Expand Down
36 changes: 36 additions & 0 deletions src/components/client/TransactionHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CheckIcon, ChevronDownIcon } from "@chakra-ui/icons";
import {
Alert,
AlertDescription,
Expand Down Expand Up @@ -38,6 +39,7 @@ import {
FaShareAlt,
} from "react-icons/fa";
import { DEFAULT_TRANSACTION_RUN, EMPTY_TRANSACTION } from "utils/state";
import { TRANSACTION_VERSIONS } from "utils/ui-constants";

export const TransactionHeader: React.FC<{
resultsRef: React.RefObject<HTMLDivElement>;
Expand Down Expand Up @@ -106,6 +108,40 @@ export const TransactionHeader: React.FC<{
return (
<>
<Flex alignItems="center">
<Menu>
<Tooltip label="Transaction version">
<MenuButton
as={Button}
mr="2"
minW="120px"
rightIcon={<ChevronDownIcon />}
>
{
TRANSACTION_VERSIONS.find(
({ id }) => id === transaction.version
)?.name
}
</MenuButton>
</Tooltip>
{/* avoid z-index issues with it rendering before other compoents that may clash with it */}
<MenuList fontSize="md" zIndex="modal">
{TRANSACTION_VERSIONS.map(({ id, name, description }, index) => (
<MenuItem
key={index}
icon={transaction.version === id ? <CheckIcon /> : undefined}
command={description}
onClick={() => {
setSession((state) => {
state.transaction.version = id;
});
}}
>
{name}
</MenuItem>
))}
</MenuList>
</Menu>

<RpcEndpointMenu
menuButtonProps={{ minW: "250px", maxW: "250px" }}
menuListProps={{ fontSize: "md" }}
Expand Down
7 changes: 7 additions & 0 deletions src/components/palette/preview/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const Preview: React.FC<{
const {
source,
sourceValue,
version,
name,
rpcEndpoint,
instructions,
Expand Down Expand Up @@ -128,6 +129,12 @@ export const Preview: React.FC<{
</Tag>
)}

<Tooltip label={`Transaction version: ${version}`}>
<Tag mr="1" fontSize="xs">
{version}
</Tag>
</Tooltip>

<Spacer />

{interactive && (
Expand Down
5 changes: 3 additions & 2 deletions src/hooks/useGetWeb3Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useInterval } from "@chakra-ui/react";
import {
Connection,
SignatureStatus,
TransactionResponse,
VersionedTransactionResponse,
} from "@solana/web3.js";
import { useConfigStore } from "hooks/useConfigStore";
import { useWeb3Connection } from "hooks/useWeb3Connection";
Expand All @@ -21,7 +21,7 @@ export const useGetWeb3Transaction = ({
}: {
connection?: Connection;
onStatus?: (status: SignatureStatus) => void;
onSuccess?: (response: TransactionResponse) => void;
onSuccess?: (response: VersionedTransactionResponse) => void;
onTimeout?: () => void;
onError?: (error: Error) => void;
}): {
Expand Down Expand Up @@ -65,6 +65,7 @@ export const useGetWeb3Transaction = ({
status?.confirmationStatus === transactionOptions.finality
) {
const transaction = await activeConnection.getTransaction(signature, {
maxSupportedTransactionVersion: 0,
commitment: transactionOptions.finality,
});

Expand Down
81 changes: 61 additions & 20 deletions src/hooks/useSendWeb3Transaction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { useWallet } from "@solana/wallet-adapter-react";
import { Connection, PublicKey, Signer } from "@solana/web3.js";
import {
Connection,
PublicKey,
Signer,
TransactionMessage,
VersionedTransaction,
} from "@solana/web3.js";
import { useConfigStore } from "hooks/useConfigStore";
import { useSessionStoreWithUndo } from "hooks/useSessionStore";
import { useWeb3Connection } from "hooks/useWeb3Connection";
import { mapITransactionToWeb3Transaction } from "mappers/internal-to-web3js";
import { mapITransactionToWeb3Instructions } from "mappers/internal-to-web3js";
import { ITransaction } from "types/internal";
import { sentryCaptureException } from "utils/sentry";

Expand All @@ -28,7 +34,7 @@ export const useSendWeb3Transaction = ({

const defaultConnection = useWeb3Connection();
const activeConnection = connection || defaultConnection;
const { sendTransaction } = useWallet();
const { publicKey: payerKey, sendTransaction, wallet } = useWallet();

// send the transaction to the chain
const send = async (transaction: ITransaction) => {
Expand All @@ -40,32 +46,67 @@ export const useSendWeb3Transaction = ({
return;
}

try {
const web3Transaction = mapITransactionToWeb3Transaction(transaction);
if (!payerKey) {
onError && onError(new Error("Wallet not connected"));
return;
}

// add additional signers
const signerPubkeys = Object.values(transaction.instructions.map)
.flatMap((instruction) =>
(instruction.anchorAccounts || []).concat(
Object.values(instruction.accounts.map)
if (
!wallet?.adapter?.supportedTransactionVersions?.has(transaction.version)
) {
onError &&
onError(
new Error(
`Wallet does not support versioned transactions or ${transaction.version} specifically`
)
)
.filter((account) => account.isSigner && keypairs[account.pubkey])
.map((account) => account.pubkey);
);
return;
}

try {
const message = new TransactionMessage({
instructions: mapITransactionToWeb3Instructions(transaction),
payerKey,
recentBlockhash: (await activeConnection.getLatestBlockhash())
.blockhash,
});

const additionalSigners = [...new Set(signerPubkeys)].map(
(pubkey) =>
({
publicKey: new PublicKey(pubkey),
secretKey: keypairs[pubkey],
} as Signer)
const web3Transaction = new VersionedTransaction(
transaction.version === 0
? message.compileToV0Message()
: message.compileToLegacyMessage()
);

// add additional signers
const signerPubkeys = [
...new Set(
Object.values(transaction.instructions.map)
.flatMap((instruction) =>
(instruction.anchorAccounts || []).concat(
Object.values(instruction.accounts.map)
)
)
.filter((account) => account.isSigner && keypairs[account.pubkey])
.map((account) => account.pubkey)
),
];

if (signerPubkeys) {
web3Transaction.sign(
signerPubkeys.map(
(pubkey) =>
({
publicKey: new PublicKey(pubkey),
secretKey: keypairs[pubkey],
} as Signer)
)
);
}

const signature = await sendTransaction(
web3Transaction,
activeConnection,
{
signers: additionalSigners || undefined,
skipPreflight: transactionOptions.skipPreflight,
maxRetries: transactionOptions.maxRetries,
preflightCommitment: transactionOptions.preflightCommitment,
Expand Down
2 changes: 2 additions & 0 deletions src/library/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ITransactionExt } from "types/external";
export const EXAMPLES: Record<string, ITransactionExt> = {
systemProgramCreateAccount: {
version: "1.0.0",
txnVersion: "0",
network: "devnet",
name: "System Program: Create Account",
description: `This transaction demonstrates how to use Solana's native [System Program](https://docs.solana.com/developing/runtime-facilities/programs#system-program) to create a Solana account.
Expand Down Expand Up @@ -76,6 +77,7 @@ We are putting in 140000 lamports, which is the amount to make a 64 byte account

systemProgramTransfer: {
version: "1.0.0",
txnVersion: "0",
network: "devnet",
name: "System Program: Transfer",
description: `This transaction demonstrates how to use Solana's native [System Program](https://docs.solana.com/developing/runtime-facilities/programs#system-program) to transfer some funds between accounts.
Expand Down
3 changes: 3 additions & 0 deletions src/mappers/external-to-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "types/internal";
import { Identifiable, SortableCollection } from "types/sortable";
import { toSortableCollection } from "utils/sortable";
import { toTransactionVersion } from "utils/web3js";
import { v4 as uuid } from "uuid";

const mapToSortable = <T>(item: T): T & Identifiable => ({
Expand Down Expand Up @@ -92,10 +93,12 @@ export const mapIInstructionExtToIInstruction = ({
// TODO fail if not versionCompatible
export const mapITransactionExtToITransaction = ({
name,
txnVersion,
description,
instructions,
}: ITransactionExt): ITransaction => ({
name,
version: toTransactionVersion(txnVersion),
description,
instructions: mapToSortableCollection(
instructions.map(mapIInstructionExtToIInstruction)
Expand Down
Loading

0 comments on commit 8f780e7

Please sign in to comment.