Skip to content

Commit 96afd55

Browse files
authored
Integrate Orca instructions (#126)
* Integrate Orca instructions
1 parent 7be1d52 commit 96afd55

33 files changed

Lines changed: 3765 additions & 32 deletions

actions/executeTransaction.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const executeTransaction = async (
1515
{ connection, wallet, programId }: RpcContext,
1616
proposal: ProgramAccount<Proposal>,
1717
instruction: ProgramAccount<ProposalTransaction>,
18+
additionalSigner?: Keypair,
1819
) => {
1920
const signers: Keypair[] = [];
2021
const instructions: TransactionInstruction[] = [];
@@ -40,6 +41,10 @@ export const executeTransaction = async (
4041

4142
transaction.add(...instructions);
4243

44+
if (additionalSigner) {
45+
signers.push(additionalSigner);
46+
}
47+
4348
await sendTransaction({
4449
transaction,
4550
wallet,

components/SecretKeyInput.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { TrashIcon } from '@heroicons/react/outline';
2+
import { LockClosedIcon } from '@heroicons/react/solid';
3+
import { Keypair } from '@solana/web3.js';
4+
import { useState } from 'react';
5+
6+
const SecretKeyInput = ({
7+
value,
8+
onChange,
9+
placeholder,
10+
className,
11+
}: {
12+
value: Keypair | null;
13+
onChange: (value: Keypair | null) => void;
14+
placeholder: string;
15+
className?: string;
16+
}) => {
17+
const [message, setMessage] = useState<string | null>(null);
18+
const [secretKey, setSecretKey] = useState<string>(
19+
value ? value.secretKey.toString() : '',
20+
);
21+
22+
return (
23+
<div>
24+
<div className={`flex flex-col relative ${className ?? ''}`}>
25+
<LockClosedIcon className="w-5 h-5 absolute left-2.5 top-2.5 text-fgd-3" />
26+
27+
<input
28+
type="text"
29+
placeholder={placeholder}
30+
className="pl-10 pr-10 focus:outline-none focus:border-primary-light bg-bkg-1 p-3 w-full border border-fgd-3 text-sm text-fgd-1 rounded-md h-10"
31+
value={secretKey}
32+
onChange={(evt) => {
33+
const secretKey = evt.target.value;
34+
setSecretKey(secretKey);
35+
36+
try {
37+
const formattedSecretKey = Uint8Array.from(JSON.parse(secretKey));
38+
const keypair = Keypair.fromSecretKey(formattedSecretKey);
39+
40+
onChange(keypair);
41+
setMessage(null);
42+
} catch {
43+
setMessage('Secret key is incorrect');
44+
}
45+
}}
46+
/>
47+
48+
{value ? (
49+
<TrashIcon
50+
className="w-5 h-5 absolute right-2.5 top-2.5 text-fgd-3 pointer hover:text-white"
51+
onClick={() => {
52+
onChange(null);
53+
setMessage(null);
54+
setSecretKey('');
55+
}}
56+
/>
57+
) : null}
58+
</div>
59+
60+
{message ? <span className="text-red">{message}</span> : null}
61+
</div>
62+
);
63+
};
64+
65+
export default SecretKeyInput;

components/SelectOrcaWhirlpool.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { WhirlpoolName, Whirlpools } from '@tools/sdk/orca/configuration';
2+
import Select from './inputs/Select';
3+
4+
const SelectOrcaWhirlpool = ({
5+
title,
6+
whirlpools,
7+
selectedValue,
8+
onSelect,
9+
}: {
10+
title: string;
11+
whirlpools: Whirlpools;
12+
selectedValue?: WhirlpoolName;
13+
onSelect: (whirlpoolName: WhirlpoolName) => void;
14+
}) => {
15+
return (
16+
<div className="flex flex-col">
17+
<div className="mb-0.5">{title}</div>
18+
19+
<Select
20+
className="flex flex-col"
21+
value={selectedValue}
22+
onChange={(v: WhirlpoolName) => onSelect(v)}
23+
>
24+
{Object.entries(whirlpools).map(
25+
([whirlpoolName, { displayName }], i) => (
26+
<Select.Option
27+
key={whirlpoolName + i}
28+
value={whirlpoolName}
29+
className="space-y-0.5 text-xs text-fgd-3"
30+
>
31+
{`${displayName}`}
32+
</Select.Option>
33+
),
34+
)}
35+
</Select>
36+
</div>
37+
);
38+
};
39+
40+
export default SelectOrcaWhirlpool;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { WhirlpoolPositionInfo } from '@tools/sdk/orca/configuration';
2+
import Select from './inputs/Select';
3+
4+
const SelectOrcaWhirlpoolPosition = ({
5+
title,
6+
positionsInfo,
7+
selectedValue,
8+
onSelect,
9+
}: {
10+
title: string;
11+
positionsInfo: WhirlpoolPositionInfo[];
12+
selectedValue?: WhirlpoolPositionInfo;
13+
onSelect: (positionInfo: WhirlpoolPositionInfo | null) => void;
14+
}) => {
15+
const getPositionDisplay = (positionInfo: WhirlpoolPositionInfo) => (
16+
<div className="flex flex-col flex-wrap space-y-2">
17+
<div className="flex w-full justify-between items-center flex-wrap">
18+
<strong>
19+
Price range {positionInfo.tokenAName} per {positionInfo.tokenBName}
20+
</strong>
21+
<span>
22+
{positionInfo.uiLowerPrice} - {positionInfo.uiUpperPrice}
23+
</span>
24+
</div>
25+
26+
<div className="flex w-full justify-between items-center flex-wrap">
27+
<strong>Pubkey</strong>
28+
<span className="overflow-auto text-xs">
29+
{positionInfo.publicKey.toBase58()}
30+
</span>
31+
</div>
32+
33+
<div className="flex w-full justify-between items-center flex-wrap">
34+
<strong>Liquidity</strong>
35+
<span>{positionInfo.liquidity}</span>
36+
</div>
37+
38+
<div className="flex w-full justify-between items-center flex-wrap">
39+
<strong>Position Mint</strong>
40+
<span className="overflow-auto text-xs">
41+
{positionInfo.positionMint.toBase58()}
42+
</span>
43+
</div>
44+
</div>
45+
);
46+
47+
return (
48+
<div className="flex flex-col">
49+
<div className="mb-0.5">{title}</div>
50+
51+
{positionsInfo.length ? (
52+
<Select
53+
className="flex flex-col w-full"
54+
value={
55+
selectedValue ? (
56+
<>
57+
Position with price range {selectedValue.uiLowerPrice}/
58+
{selectedValue.uiUpperPrice} ({selectedValue.tokenAName} per
59+
{selectedValue.tokenBName})
60+
</>
61+
) : null
62+
}
63+
onChange={(positionPublicKey: string) =>
64+
onSelect(
65+
positionsInfo.find(
66+
(positionInfo) =>
67+
positionInfo.publicKey.toBase58() === positionPublicKey,
68+
) ?? null,
69+
)
70+
}
71+
>
72+
{positionsInfo.map((positionInfo) => (
73+
<Select.Option
74+
key={positionInfo.publicKey.toBase58()}
75+
value={positionInfo.publicKey.toBase58()}
76+
className="space-y-0.5 text-xs text-fgd-3"
77+
>
78+
{getPositionDisplay(positionInfo)}
79+
</Select.Option>
80+
))}
81+
</Select>
82+
) : (
83+
<div>No position available</div>
84+
)}
85+
</div>
86+
);
87+
};
88+
89+
export default SelectOrcaWhirlpoolPosition;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ReactNode } from 'react-markdown/lib/react-markdown';
2+
import Input from './Input';
3+
4+
const InputNumberWithMaxButton = ({
5+
value = '',
6+
label,
7+
onChange,
8+
className,
9+
error = '',
10+
disabled,
11+
min,
12+
max,
13+
}: {
14+
value: ReactNode;
15+
label: string;
16+
onChange: (nb: number) => void;
17+
className?: string;
18+
error?: string;
19+
disabled?: boolean;
20+
min?: number;
21+
max?: number;
22+
}) => {
23+
return (
24+
<div className="flex flex-col">
25+
<Input
26+
label={label}
27+
value={value}
28+
className={className}
29+
disabled={disabled}
30+
onChange={(event) => onChange(event.target.value)}
31+
error={error}
32+
type="number"
33+
min={min}
34+
max={max}
35+
/>
36+
37+
{typeof max !== 'undefined' ? (
38+
<div
39+
className="hover:text-white text-fgd-2 text-xs pointer mt-2"
40+
onClick={() => {
41+
onChange(max);
42+
}}
43+
>
44+
Max: {max}
45+
</div>
46+
) : null}
47+
</div>
48+
);
49+
};
50+
51+
export default InputNumberWithMaxButton;

components/instructions/ExecuteInstructionButton.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import useWalletStore, {
1414
EnhancedProposalState,
1515
} from 'stores/useWalletStore';
1616
import { ProgramAccount } from '@solana/spl-governance';
17-
import { PublicKey } from '@solana/web3.js';
17+
import { Keypair, PublicKey } from '@solana/web3.js';
1818
import Tooltip from '@components/Tooltip';
1919
import { getProgramVersionForRealm } from '@models/registry/api';
2020
import useTransactionSignature from '@hooks/useTransactionSignature';
@@ -32,11 +32,15 @@ export function ExecuteInstructionButton({
3232
playing,
3333
setPlaying,
3434
proposalInstruction,
35+
additionalSigner,
36+
disabled,
3537
}: {
3638
proposal: ProgramAccount<EnhancedProposal>;
3739
proposalInstruction: ProgramAccount<ProposalTransaction>;
3840
playing: PlayState;
41+
additionalSigner?: Keypair;
3942
setPlaying: React.Dispatch<React.SetStateAction<PlayState>>;
43+
disabled?: boolean;
4044
}) {
4145
const { realmInfo } = useRealm();
4246
const wallet = useWalletStore((s) => s.current);
@@ -77,7 +81,12 @@ export function ExecuteInstructionButton({
7781
setPlaying(PlayState.Playing);
7882

7983
try {
80-
await executeTransaction(rpcContext, proposal, proposalInstruction);
84+
await executeTransaction(
85+
rpcContext,
86+
proposal,
87+
proposalInstruction,
88+
additionalSigner,
89+
);
8190
await fetchRealm(realmInfo?.programId, realmInfo?.realmId);
8291
} catch (error) {
8392
notify({
@@ -134,7 +143,11 @@ export function ExecuteInstructionButton({
134143
InstructionExecutionStatus.Error
135144
) {
136145
return (
137-
<Button small disabled={!connected} onClick={onExecuteInstruction}>
146+
<Button
147+
small
148+
disabled={!connected || disabled}
149+
onClick={onExecuteInstruction}
150+
>
138151
Execute
139152
</Button>
140153
);

0 commit comments

Comments
 (0)