Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dual GSO config and withdraw instructions #1769

Merged
merged 16 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 107 additions & 1 deletion components/instructions/programs/dual.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
StakingOptions,
STAKING_OPTIONS_PK,
} from '@dual-finance/staking-options'
import { BN, BorshInstructionCoder } from '@coral-xyz/anchor'
import { BN, BorshInstructionCoder, Idl } from '@coral-xyz/anchor'
import { AccountMetaData } from '@solana/spl-governance'
import { tryGetMint } from '@utils/tokens'
import { getMintDecimalAmountFromNatural } from '@tools/sdk/units'
import { GSO_PK } from '@dual-finance/gso'
import gsoIdl from '@dual-finance/gso/lib/gso.json'

interface configV2Instruction {
lotSize: BN
Expand All @@ -24,6 +26,16 @@ interface configV3Instruction {
subscriptionPeriodEnd: BN
}

interface configGsoInstruction {
lotSize: BN
numTokens: BN
optionExpiration: BN
projectName: string
subscriptionPeriodEnd: BN
lockupRatioTokensPerMillion: BN
strikePrice: BN
}

interface initStrikeReversibleInstruction {
strike: BN
}
Expand Down Expand Up @@ -367,6 +379,100 @@ const INSTRUCTIONS = {
},
}

const GSO_INSTRUCTIONS = {
173: {
name: 'Lockup Staking Option Config',
accounts: [
{ name: 'authority' },
{ name: 'gsoState' },
{ name: 'soAuthority' },
{ name: 'soState' },
{ name: 'soBaseVault' },
{ name: 'soBaseAccount' },
{ name: 'soQuoteAccount' },
{ name: 'soBaseMint' },
{ name: 'soQuoteMint' },
{ name: 'soOptionMint' },
{ name: 'stakingOptionsProgram' },
{ name: 'xBaseMint' },
{ name: 'baseVault' },
{ name: 'tokenProgram' },
{ name: 'systemProgram' },
{ name: 'rent' },
],
getDataUI: async (
connection: Connection,
data: Uint8Array,
accounts: AccountMetaData[]
) => {
const decodedInstructionData = new BorshInstructionCoder(
gsoIdl as Idl
).decode(Buffer.from(data))?.data as configGsoInstruction

const baseMint = await tryGetMint(connection, accounts[8].pubkey)

const rawAmount = decodedInstructionData.numTokens
const tokenAmount = baseMint
? getMintDecimalAmountFromNatural(baseMint.account, rawAmount)
: rawAmount

return (
<div className="space-y-3">
<div>Num Tokens: {tokenAmount.toNumber()}</div>
<div>
Expiration:{' '}
{new Date(
decodedInstructionData.optionExpiration.toNumber() * 1_000
).toDateString()}
</div>
<div>
Subscription Period End:{' '}
{new Date(
decodedInstructionData.subscriptionPeriodEnd.toNumber() * 1_000
).toDateString()}
</div>
<div>Strike: { decodedInstructionData.strikePrice.toNumber() }</div>
<div>Lockup Ratio: { decodedInstructionData.lockupRatioTokensPerMillion.toNumber() / 1_000_000 }</div>
<div>Lot size: {decodedInstructionData.lotSize.toNumber()}</div>
<div>SoName: {decodedInstructionData.projectName}</div>
</div>
)
},
},
245: {
name: 'Lockup Staking Option Name Token',
accounts: [
{ name: 'authority' },
{ name: 'soState' },
{ name: 'gsoState' },
{ name: 'xBaseMint' },
{ name: 'xBaseMetadata' },
{ name: 'tokenMetadataProgram' },
{ name: 'soAuthority' },
{ name: 'soOptionMint' },
{ name: 'optionMetadata' },
{ name: 'stakingOptionsProgram' },
{ name: 'systemProgram' },
{ name: 'rent' },
],
},
183: {
name: 'Lockup Staking Option Withdraw',
accounts: [
{ name: 'authority' },
{ name: 'gsoState' },
{ name: 'soAuthority' },
{ name: 'userBaseAccount' },
{ name: 'soBaseVault' },
{ name: 'soState' },
{ name: 'stakingOptionsProgram' },
{ name: 'tokenProgram' },
{ name: 'systemProgram' },
],
},
}

export const DUAL_INSTRUCTIONS = {
[STAKING_OPTIONS_PK.toBase58()]: INSTRUCTIONS,
[GSO_PK.toBase58()]: GSO_INSTRUCTIONS,
}
10 changes: 10 additions & 0 deletions hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,11 @@ export default function useGovernanceAssets() {
isVisible: canUseTransferInstruction,
packageId: PackageEnum.Dual,
},
[Instructions.DualFinanceGso]: {
name: 'Lockup Staking Option',
isVisible: canUseTransferInstruction,
packageId: PackageEnum.Dual,
},
[Instructions.DualFinanceLiquidityStakingOption]: {
name: 'Liquidity Staking Option',
isVisible: canUseTransferInstruction,
Expand All @@ -393,6 +398,11 @@ export default function useGovernanceAssets() {
isVisible: canUseTransferInstruction,
packageId: PackageEnum.Dual,
},
[Instructions.DualFinanceGsoWithdraw]: {
name: 'Lockup Staking Option Withdraw',
isVisible: canUseTransferInstruction,
packageId: PackageEnum.Dual,
},
[Instructions.DualFinanceWithdraw]: {
name: 'Withdraw',
isVisible: canUseTransferInstruction,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@dialectlabs/react-sdk-blockchain-solana": "1.0.0-beta.3",
"@dialectlabs/react-ui": "1.1.0-beta.5",
"@dual-finance/airdrop": "0.2.2",
"@dual-finance/gso": "0.0.14",
"@dual-finance/staking-options": "0.0.26",
"@emotion/react": "11.9.0",
"@emotion/styled": "11.8.1",
Expand Down
226 changes: 226 additions & 0 deletions pages/dao/[symbol]/proposal/components/instructions/Dual/DualGso.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import React, { useContext, useEffect, useState } from 'react'
import { ProgramAccount, Governance } from '@solana/spl-governance'
import {
UiInstruction,
DualFinanceGsoForm,
} from '@utils/uiTypes/proposalCreationTypes'
import { NewProposalContext } from '../../../new'
import GovernedAccountSelect from '../../GovernedAccountSelect'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import Input from '@components/inputs/Input'
import { getConfigGsoInstruction } from '@utils/instructions/Dual'
import { getDualFinanceGsoSchema } from '@utils/validations'
import Tooltip from '@components/Tooltip'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'

const DualGso = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const [form, setForm] = useState<DualFinanceGsoForm>({
soName: undefined,
optionExpirationUnixSeconds: 0,
numTokens: 0,
lotSize: 0,
baseTreasury: undefined,
quoteTreasury: undefined,
payer: undefined,
strike: 0,
subscriptionPeriodEnd: 0,
lockupRatio: 0
})
const connection = useLegacyConnectionContext()
const wallet = useWalletOnePointOh()
const shouldBeGoverned = !!(index !== 0 && governance)
const { assetAccounts } = 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 })
}
const schema = getDualFinanceGsoSchema()
useEffect(() => {
function getInstruction(): Promise<UiInstruction> {
return getConfigGsoInstruction({
connection,
form,
schema,
setFormErrors,
wallet,
})
}
handleSetInstructions(
{ governedAccount: governedAccount, getInstruction },
index
)
}, [
form,
governedAccount,
handleSetInstructions,
index,
connection,
schema,
wallet,
])
useEffect(() => {
setGovernedAccount(form.baseTreasury?.governance)
}, [form.baseTreasury])

return (
<>
<Tooltip content="Custom name to identify 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 providing the assets for the option. When the recipient exercises, these are the tokens they receive.">
<GovernedAccountSelect
label="Base Treasury"
governedAccounts={assetAccounts}
onChange={(value) => {
handleSetForm({ value, propertyName: 'baseTreasury' })
}}
value={form.baseTreasury}
error={formErrors['baseTreasury']}
governance={governance}
type="token"
></GovernedAccountSelect>
</Tooltip>
<Tooltip content="Treasury owned account receiving payment for the option exercise. This is where payments from exercise accumulate.">
<GovernedAccountSelect
label="Quote Treasury"
governedAccounts={assetAccounts}
onChange={(value) => {
handleSetForm({ value, propertyName: 'quoteTreasury' })
}}
value={form.quoteTreasury}
error={formErrors['quoteTreasury']}
governance={governance}
type="token"
></GovernedAccountSelect>
</Tooltip>
<Tooltip content="How many tokens are in the staking options. Units are in atoms of the base token.">
<Input
label="Quantity"
value={form.numTokens}
type="number"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'numTokens',
})
}
error={formErrors['numTokens']}
/>
</Tooltip>
<Tooltip content="Date in unix seconds for option expiration">
<Input
label="Expiration"
value={form.optionExpirationUnixSeconds}
type="number"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'optionExpirationUnixSeconds',
})
}
error={formErrors['optionExpirationUnixSeconds']}
/>
</Tooltip>
<Tooltip content="Date in unix seconds when users can no longer subscribe">
<Input
label="Subscription Period"
value={form.subscriptionPeriodEnd}
type="number"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'subscriptionPeriodEnd',
})
}
error={formErrors['subscriptionPeriodEnd']}
/>
</Tooltip>
<Tooltip content="Strike price for the staking option. Units are quote atoms per lot.">
<Input
label="Strike"
value={form.strike}
type="number"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'strike',
})
}
error={formErrors['strike']}
/>
</Tooltip>
<Tooltip content="Lockup Ratio for GSO. This determines how many tokens are needed to earn an option.">
<Input
label="Lockup Ratio"
value={form.lockupRatio}
type="number"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'lockupRatio',
})
}
error={formErrors['lockupRatio']}
/>
</Tooltip>
<Tooltip content="Lot size in base atoms. This is the min size of an option.">
<Input
label="Lot Size"
value={form.lotSize}
type="number"
onChange={(evt) =>
handleSetForm({
value: evt.target.value,
propertyName: 'lotSize',
})
}
error={formErrors['lotSize']}
/>
</Tooltip>
<Tooltip content="Rent payer. Should be the governance wallet with same governance as base treasury">
<GovernedAccountSelect
label="Payer Account"
governedAccounts={assetAccounts.filter(
(x) =>
x.isSol &&
form.baseTreasury?.governance &&
x.governance.pubkey.equals(form.baseTreasury.governance.pubkey)
)}
onChange={(value) => {
handleSetForm({ value, propertyName: 'payer' })
}}
value={form.payer}
error={formErrors['payer']}
shouldBeGoverned={shouldBeGoverned}
governance={governance}
></GovernedAccountSelect>
</Tooltip>
</>
)
}

export default DualGso
Loading
Loading