Skip to content

Commit

Permalink
Withdraw and Exercise for Dual Staking Options (solana-labs#1284)
Browse files Browse the repository at this point in the history
* Initial commit for reals + Staking Options

* Dual Staking Option (#1)

* In progress fix

* More instructions

* Do TODO for all in one transaction

* Validation for instruction

* Separate payer

* labels, order & tooltips

* Add tooltips

* More comment for base and quote treasury

Co-authored-by: john <[email protected]>

* Undo lint errors

* Add to tooltips

* fix yarn lock

* fix proposal enum

* alphabetic order of instructions

* fix merge

* add logo

* create helper checking account

* fix signers

* Update hooks/useGovernanceAssets.ts

Co-authored-by: Grégory NEUT <[email protected]>

* Apply suggestions from code review

Co-authored-by: Grégory NEUT <[email protected]>

* Fix version of the sdk

* build fix

* fix

* Withdraw

* outline for withdraw and exercise

* Initial commit for withdraw and exercise

* withdraw code and template for exercise

* remove logging

* Exercise

* Init fee account if needed

* Use quote mint from the state obj

* fix yarn lock

* fix lint errors

Co-authored-by: john <[email protected]>
Co-authored-by: Adrian Brzeziński <[email protected]>
Co-authored-by: Grégory NEUT <[email protected]>
  • Loading branch information
4 people authored Dec 16, 2022
1 parent c4b85df commit 69e2f90
Show file tree
Hide file tree
Showing 10 changed files with 424 additions and 9 deletions.
10 changes: 10 additions & 0 deletions hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,16 @@ export default function useGovernanceAssets() {
isVisible: canUseTransferInstruction,
packageId: PackageEnum.Dual,
},
[Instructions.DualFinanceExercise]: {
name: 'Exercise',
isVisible: canUseTransferInstruction,
packageId: PackageEnum.Dual,
},
[Instructions.DualFinanceWithdraw]: {
name: 'Withdraw',
isVisible: canUseTransferInstruction,
packageId: PackageEnum.Dual,
},

/*
███████ ██ ██ ███████ ██████ ██ ███████ ███ ██ ██████
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@civic/solana-gateway-react": "0.11.0",
"@dialectlabs/react-sdk-blockchain-solana": "1.0.0-beta.3",
"@dialectlabs/react-ui": "1.1.0-beta.5",
"@dual-finance/staking-options": "0.0.7",
"@dual-finance/staking-options": "0.0.10",
"@emotion/react": "11.9.0",
"@emotion/styled": "11.8.1",
"@everlend/general-pool": "0.0.27",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React, { useContext, useEffect, useState } from 'react'
import { ProgramAccount, Governance } from '@solana/spl-governance'
import {
UiInstruction,
DualFinanceExerciseForm,
} from '@utils/uiTypes/proposalCreationTypes'
import { NewProposalContext } from '../../../new'
import GovernedAccountSelect from '../../GovernedAccountSelect'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import Input from '@components/inputs/Input'
import { getExerciseInstruction } from '@utils/instructions/Dual'
import useWalletStore from 'stores/useWalletStore'
import { getDualFinanceExerciseSchema } from '@utils/validations'
import Tooltip from '@components/Tooltip'

const DualExercise = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const [form, setForm] = useState<DualFinanceExerciseForm>({
numTokens: 0,
soName: undefined,
baseTreasury: undefined,
quoteTreasury: undefined,
optionAccount: undefined,
})
const connection = useWalletStore((s) => s.connection)
const wallet = useWalletStore((s) => s.current)
const shouldBeGoverned = !!(index !== 0 && governance)
const { governedTokenAccountsWithoutNfts } = useGovernanceAssets()
const [governedAccount, setGovernedAccount] = useState<
ProgramAccount<Governance> | undefined
>(undefined)

const [formErrors, setFormErrors] = useState({})
const { handleSetInstructions } = useContext(NewProposalContext)
const handleSetForm = ({ propertyName, value }) => {
setFormErrors({})
setForm({ ...form, [propertyName]: value })
}
function getInstruction(): Promise<UiInstruction> {
return getExerciseInstruction({
connection,
form,
schema,
setFormErrors,
wallet,
})
}
useEffect(() => {
handleSetInstructions(
{ governedAccount: governedAccount, getInstruction },
index
)
}, [form])
useEffect(() => {
setGovernedAccount(form.baseTreasury?.governance)
}, [form.baseTreasury])
const schema = getDualFinanceExerciseSchema()

// TODO: Find the name from metaplex from a token lookup once that is
// connected to the program.
return (
<>
<Tooltip content="Identifier for the Staking Option">
<Input
label="Name"
value={form.soName}
type="text"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'soName',
})
}
error={formErrors['soName']}
/>
</Tooltip>
<Tooltip content="Option token that will be exercised.">
<GovernedAccountSelect
label="Option Account"
governedAccounts={governedTokenAccountsWithoutNfts}
onChange={(value) => {
handleSetForm({ value, propertyName: 'optionAccount' })
}}
value={form.optionAccount}
error={formErrors['optionAccount']}
shouldBeGoverned={shouldBeGoverned}
governance={governance}
></GovernedAccountSelect>
</Tooltip>
<Tooltip content="Treasury owned account providing the payment for the option.">
<GovernedAccountSelect
label="Quote Treasury"
governedAccounts={governedTokenAccountsWithoutNfts}
onChange={(value) => {
handleSetForm({ value, propertyName: 'quoteTreasury' })
}}
value={form.quoteTreasury}
error={formErrors['quoteTreasury']}
shouldBeGoverned={shouldBeGoverned}
governance={governance}
></GovernedAccountSelect>
</Tooltip>
<Tooltip content="Treasury owned account receiving the option exercise.">
<GovernedAccountSelect
label="Base Treasury"
governedAccounts={governedTokenAccountsWithoutNfts}
onChange={(value) => {
handleSetForm({ value, propertyName: 'baseTreasury' })
}}
value={form.baseTreasury}
error={formErrors['baseTreasury']}
shouldBeGoverned={shouldBeGoverned}
governance={governance}
></GovernedAccountSelect>
</Tooltip>
<Tooltip content="How many option tokens are exercised staking options.">
<Input
label="Quantity"
value={form.numTokens}
type="number"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'numTokens',
})
}
error={formErrors['numTokens']}
/>
</Tooltip>
</>
)
}

export default DualExercise
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React, { useContext, useEffect, useState } from 'react'
import { ProgramAccount, Governance } from '@solana/spl-governance'
import {
UiInstruction,
DualFinanceWithdrawForm,
} from '@utils/uiTypes/proposalCreationTypes'
import { NewProposalContext } from '../../../new'
import GovernedAccountSelect from '../../GovernedAccountSelect'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import Input from '@components/inputs/Input'
import { getWithdrawInstruction } from '@utils/instructions/Dual'
import useWalletStore from 'stores/useWalletStore'
import { getDualFinanceWithdrawSchema } from '@utils/validations'
import Tooltip from '@components/Tooltip'

const DualWithdraw = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const [form, setForm] = useState<DualFinanceWithdrawForm>({
soName: undefined,
baseTreasury: undefined,
})
const connection = useWalletStore((s) => s.connection)
const wallet = useWalletStore((s) => s.current)
const shouldBeGoverned = !!(index !== 0 && governance)
const { governedTokenAccountsWithoutNfts } = useGovernanceAssets()
const [governedAccount, setGovernedAccount] = useState<
ProgramAccount<Governance> | undefined
>(undefined)

const [formErrors, setFormErrors] = useState({})
const { handleSetInstructions } = useContext(NewProposalContext)
const handleSetForm = ({ propertyName, value }) => {
setFormErrors({})
setForm({ ...form, [propertyName]: value })
}
function getInstruction(): Promise<UiInstruction> {
return getWithdrawInstruction({
connection,
form,
schema,
setFormErrors,
wallet,
})
}
useEffect(() => {
handleSetInstructions(
{ governedAccount: governedAccount, getInstruction },
index
)
}, [form])
useEffect(() => {
setGovernedAccount(form.baseTreasury?.governance)
}, [form.baseTreasury])
const schema = getDualFinanceWithdrawSchema()

// TODO: Include this in the config instruction which can optionally be done
// if the project doesnt need to change where the tokens get returned to.
return (
<>
<Tooltip content="Identifier for the Staking Option">
<Input
label="Name"
value={form.soName}
type="text"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'soName',
})
}
error={formErrors['soName']}
/>
</Tooltip>
<Tooltip content="Treasury owned account receiving the tokens back.">
<GovernedAccountSelect
label="Base Treasury"
governedAccounts={governedTokenAccountsWithoutNfts}
onChange={(value) => {
handleSetForm({ value, propertyName: 'baseTreasury' })
}}
value={form.baseTreasury}
error={formErrors['baseTreasury']}
shouldBeGoverned={shouldBeGoverned}
governance={governance}
></GovernedAccountSelect>
</Tooltip>
</>
)
}

export default DualWithdraw
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { NewProposalContext } from '../../../new'
import GovernedAccountSelect from '../../GovernedAccountSelect'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import Input from '@components/inputs/Input'
import getConfigInstruction from '@utils/instructions/Dual'
import { getConfigInstruction } from '@utils/instructions/Dual'
import useWalletStore from 'stores/useWalletStore'
import { getDualFinanceStakingOptionSchema } from '@utils/validations'
import Tooltip from '@components/Tooltip'
Expand Down
4 changes: 4 additions & 0 deletions pages/dao/[symbol]/proposal/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ import AddKeyToDID from './components/instructions/Identity/AddKeyToDID'
import RemoveKeyFromDID from './components/instructions/Identity/RemoveKeyFromDID'
import AddServiceToDID from './components/instructions/Identity/AddServiceToDID'
import RemoveServiceFromDID from './components/instructions/Identity/RemoveServiceFromDID'
import DualWithdraw from './components/instructions/Dual/DualWithdraw'
import DualExercise from './components/instructions/Dual/DualExercise'

const TITLE_LENGTH_LIMIT = 130

Expand Down Expand Up @@ -448,6 +450,8 @@ const New = () => {
[Instructions.ClaimPendingWithdraw]: FriktionClaimPendingWithdraw,
[Instructions.DepositIntoCastle]: CastleDeposit,
[Instructions.DualFinanceStakingOption]: StakingOption,
[Instructions.DualFinanceWithdraw]: DualWithdraw,
[Instructions.DualFinanceExercise]: DualExercise,
[Instructions.MeanCreateAccount]: MeanCreateAccount,
[Instructions.MeanFundAccount]: MeanFundAccount,
[Instructions.MeanWithdrawFromAccount]: MeanWithdrawFromAccount,
Expand Down
Loading

0 comments on commit 69e2f90

Please sign in to comment.