diff --git a/components/instructions/programs/credix.tsx b/components/instructions/programs/credix.tsx new file mode 100644 index 0000000000..9d283c4111 --- /dev/null +++ b/components/instructions/programs/credix.tsx @@ -0,0 +1,91 @@ +import { struct, u8, nu64 } from 'buffer-layout'; +import { AccountMetaData } from '@solana/spl-governance'; +import { Connection } from '@solana/web3.js'; +import { CredixConfiguration } from '@tools/sdk/credix/configuration'; +import { ANCHOR_DISCRIMINATOR_LAYOUT } from '@utils/helpers'; +import { nativeBNToUiAmount } from '@tools/sdk/units'; +import { USDC_DECIMALS } from '@uxd-protocol/uxd-client'; +import { BN } from '@project-serum/anchor'; + +export const CREDIX_PROGRAM_INSTRUCTIONS = { + [CredixConfiguration.credixProgramId.toBase58()]: { + [CredixConfiguration.instructionsCode.deposit]: { + name: 'Credix - Deposit USDC', + accounts: [ + 'Investor', + 'Gateway Token', + 'Global Market State', + 'Signing Authority', + 'Investor Token Account', + 'Liquidity Pool Token Account', + 'LP Token Mint', + 'Investor Lp Token Account', + 'Credix Pass', + 'Base Token Mint', + 'Associated Token Program', + 'Rent', + 'Token Program', + 'System Program', + ], + getDataUI: async ( + _connection: Connection, + data: Uint8Array, + _accounts: AccountMetaData[], + ) => { + const dataLayout = struct([ + u8('instruction'), + ...ANCHOR_DISCRIMINATOR_LAYOUT, + nu64('amount'), + ]); + + const { amount } = dataLayout.decode(Buffer.from(data)) as any; + + const uiAmount = nativeBNToUiAmount(new BN(amount), USDC_DECIMALS); + + return

{`USDC Amount: ${uiAmount.toString()}`}

; + }, + }, + [CredixConfiguration.instructionsCode.withdraw]: { + name: 'Credix - Withdraw USDC', + accounts: [ + 'Investor', + 'Gateway Token', + 'Global Market State', + 'Signing Authority', + 'Investor Lp Token Account', + 'Investor Token Account', + 'Liquidity Pool Token Account', + 'Treasury Pool Token Account', + 'LP TokenMint', + 'Credix Pass', + 'Base Token Mint', + 'Associated Token Program', + 'Token Program', + ], + getDataUI: async ( + _connection: Connection, + data: Uint8Array, + _accounts: AccountMetaData[], + ) => { + const dataLayout = struct([ + u8('instruction'), + ...ANCHOR_DISCRIMINATOR_LAYOUT, + nu64('baseWithdrawalAmount'), + ]); + + const { baseWithdrawalAmount } = dataLayout.decode( + Buffer.from(data), + ) as any; + + const uiBaseWithdrawalAmount = nativeBNToUiAmount( + new BN(baseWithdrawalAmount), + USDC_DECIMALS, + ); + + return ( +

{`Base USDC Withdrawal Amount: ${uiBaseWithdrawalAmount.toString()}`}

+ ); + }, + }, + }, +}; diff --git a/components/instructions/tools.tsx b/components/instructions/tools.tsx index f33bc5c663..ece95a23ef 100644 --- a/components/instructions/tools.tsx +++ b/components/instructions/tools.tsx @@ -31,6 +31,7 @@ import { DELTAFI_PROGRAM_INSTRUCTIONS } from './programs/deltafi'; import { ORCA_PROGRAM_INSTRUCTIONS } from './programs/orca'; import { COMPUTE_BUDGET_INSTRUCTIONS } from './programs/computeBudgetProgram'; import { MERCURIAL_PROGRAM_INSTRUCTIONS } from './programs/mercurial'; +import { CREDIX_PROGRAM_INSTRUCTIONS } from './programs/credix'; /** * Default governance program id instance @@ -137,6 +138,7 @@ export const INSTRUCTION_DESCRIPTORS = { ...ORCA_PROGRAM_INSTRUCTIONS, ...COMPUTE_BUDGET_INSTRUCTIONS, ...MERCURIAL_PROGRAM_INSTRUCTIONS, + ...CREDIX_PROGRAM_INSTRUCTIONS, }; export async function getInstructionDescriptor( diff --git a/hooks/useGovernanceAssets.ts b/hooks/useGovernanceAssets.ts index 04328f64bc..3316808694 100644 --- a/hooks/useGovernanceAssets.ts +++ b/hooks/useGovernanceAssets.ts @@ -229,6 +229,10 @@ export default function useGovernanceAssets() { name: 'Mercurial', image: '/img/mercurial.png', }, + [PackageEnum.Credix]: { + name: 'Credix', + image: '/img/credix.jpeg', + }, }; const instructions: Instructions = { @@ -681,6 +685,18 @@ export default function useGovernanceAssets() { packageId: PackageEnum.Native, tag: 'beta', }, + [InstructionEnum.CredixDeposit]: { + name: 'Deposit', + isVisible: canUseAnyInstruction, + packageId: PackageEnum.Credix, + tag: 'beta', + }, + [InstructionEnum.CredixWithdraw]: { + name: 'Withdraw', + isVisible: canUseAnyInstruction, + packageId: PackageEnum.Credix, + tag: 'beta', + }, }; const availableInstructions = Object.entries(instructions) diff --git a/package.json b/package.json index 51e169be4f..145b290745 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "@blockworks-foundation/mango-client": "^3.3.16", "@blockworks-foundation/voter-stake-registry-client": "^0.2.0", + "@credix/credix-client": "1.0.0", "@emotion/react": "^11.1.5", "@emotion/styled": "^11.3.0", "@friktion-labs/friktion-sdk": "1.1.113", diff --git a/pages/dao/[symbol]/proposal/components/instructions/Credix/Deposit.tsx b/pages/dao/[symbol]/proposal/components/instructions/Credix/Deposit.tsx new file mode 100644 index 0000000000..74f57019a1 --- /dev/null +++ b/pages/dao/[symbol]/proposal/components/instructions/Credix/Deposit.tsx @@ -0,0 +1,96 @@ +import * as yup from 'yup'; +import useInstructionFormBuilder from '@hooks/useInstructionFormBuilder'; +import credixConfiguration from '@tools/sdk/credix/configuration'; +import { GovernedMultiTypeAccount, tryGetMint } from '@utils/tokens'; +import { CredixDepositForm } from '@utils/uiTypes/proposalCreationTypes'; +import Input from '@components/inputs/Input'; +import { uiAmountToNativeBN } from '@tools/sdk/units'; + +const schema = yup.object().shape({ + governedAccount: yup + .object() + .nullable() + .required('Governed account is required'), + uiAmount: yup + .number() + .typeError('Amount has to be a number') + .required('Amount is required'), +}); + +const CredixDeposit = ({ + index, + governedAccount, +}: { + index: number; + governedAccount?: GovernedMultiTypeAccount; +}) => { + const { + form, + handleSetForm, + formErrors, + } = useInstructionFormBuilder({ + index, + initialFormValues: { + governedAccount, + }, + schema, + buildInstruction: async function ({ + cluster, + governedAccountPubkey, + form, + connection, + wallet, + }) { + if (cluster !== 'mainnet') { + throw new Error('Other cluster than mainnet are not supported yet.'); + } + + const client = credixConfiguration.getClient({ + connection, + wallet: (wallet as unknown) as any, + }); + + // the market for which we want to deposit in the liquidity pool + const market = await client.fetchMarket('credix-marketplace'); + + if (!market) { + throw new Error( + 'Cannot load market information about credix-marketplace', + ); + } + + const marketMintInfo = await tryGetMint(connection, market.baseMintPK); + + if (!marketMintInfo) { + throw new Error( + 'Cannot load information about credix market base mint', + ); + } + + const amount = uiAmountToNativeBN( + form.uiAmount!, + marketMintInfo.account.decimals, + ); + + return market.depositIx(amount.toNumber(), governedAccountPubkey); + }, + }); + + return ( + { + handleSetForm({ + value: evt.target.value, + propertyName: 'uiAmount', + }); + }} + error={formErrors['uiAmount']} + /> + ); +}; + +export default CredixDeposit; diff --git a/pages/dao/[symbol]/proposal/components/instructions/Credix/Withdraw.tsx b/pages/dao/[symbol]/proposal/components/instructions/Credix/Withdraw.tsx new file mode 100644 index 0000000000..1bef944afb --- /dev/null +++ b/pages/dao/[symbol]/proposal/components/instructions/Credix/Withdraw.tsx @@ -0,0 +1,96 @@ +import * as yup from 'yup'; +import useInstructionFormBuilder from '@hooks/useInstructionFormBuilder'; +import credixConfiguration from '@tools/sdk/credix/configuration'; +import { GovernedMultiTypeAccount, tryGetMint } from '@utils/tokens'; +import { CredixWithdrawForm } from '@utils/uiTypes/proposalCreationTypes'; +import Input from '@components/inputs/Input'; +import { uiAmountToNativeBN } from '@tools/sdk/units'; + +const schema = yup.object().shape({ + governedAccount: yup + .object() + .nullable() + .required('Governed account is required'), + uiAmount: yup + .number() + .typeError('Amount has to be a number') + .required('Amount is required'), +}); + +const CredixWithdraw = ({ + index, + governedAccount, +}: { + index: number; + governedAccount?: GovernedMultiTypeAccount; +}) => { + const { + form, + handleSetForm, + formErrors, + } = useInstructionFormBuilder({ + index, + initialFormValues: { + governedAccount, + }, + schema, + buildInstruction: async function ({ + cluster, + governedAccountPubkey, + form, + connection, + wallet, + }) { + if (cluster !== 'mainnet') { + throw new Error('Other cluster than mainnet are not supported yet.'); + } + + const client = credixConfiguration.getClient({ + connection, + wallet: (wallet as unknown) as any, + }); + + // the market for which we want to deposit in the liquidity pool + const market = await client.fetchMarket('credix-marketplace'); + + if (!market) { + throw new Error( + 'Cannot load market information about credix-marketplace', + ); + } + + const marketMintInfo = await tryGetMint(connection, market.baseMintPK); + + if (!marketMintInfo) { + throw new Error( + 'Cannot load information about credix market base mint', + ); + } + + const amount = uiAmountToNativeBN( + form.uiAmount!, + marketMintInfo.account.decimals, + ); + + return market.withdrawIx(amount.toNumber(), governedAccountPubkey); + }, + }); + + return ( + { + handleSetForm({ + value: evt.target.value, + propertyName: 'uiAmount', + }); + }} + error={formErrors['uiAmount']} + /> + ); +}; + +export default CredixWithdraw; diff --git a/pages/dao/[symbol]/proposal/components/instructions/SelectedInstruction.tsx b/pages/dao/[symbol]/proposal/components/instructions/SelectedInstruction.tsx index 33758dd88d..0c077c8039 100644 --- a/pages/dao/[symbol]/proposal/components/instructions/SelectedInstruction.tsx +++ b/pages/dao/[symbol]/proposal/components/instructions/SelectedInstruction.tsx @@ -82,6 +82,8 @@ import OrcaWhirlpoolSwap from './Orca/WhirlpoolSwap'; import MercurialPoolDeposit from './Mercurial/PoolDeposit'; import MercurialPoolWithdraw from './Mercurial/PoolWithdraw'; import NativeIncreaseComputingBudget from './Native/IncreaseComputingBudget'; +import CredixDeposit from './Credix/Deposit'; +import CredixWithdraw from './Credix/Withdraw'; const SelectedInstruction = ({ itxType, @@ -564,6 +566,10 @@ const SelectedInstruction = ({ governedAccount={governedAccount} /> ); + case InstructionEnum.CredixDeposit: + return ; + case InstructionEnum.CredixWithdraw: + return ; default: return null; } diff --git a/public/img/credix.jpeg b/public/img/credix.jpeg new file mode 100644 index 0000000000..238019d710 Binary files /dev/null and b/public/img/credix.jpeg differ diff --git a/tools/sdk/credix/configuration.ts b/tools/sdk/credix/configuration.ts new file mode 100644 index 0000000000..b8cb81b0a3 --- /dev/null +++ b/tools/sdk/credix/configuration.ts @@ -0,0 +1,28 @@ +import { CredixClient } from '@credix/credix-client'; +import { Wallet } from '@marinade.finance/marinade-ts-sdk'; +import { Connection, PublicKey } from '@solana/web3.js'; + +export class CredixConfiguration { + public static readonly credixProgramId = new PublicKey( + 'CRDx2YkdtYtGZXGHZ59wNv1EwKHQndnRc1gT4p8i2vPX', + ); + + public static readonly instructionsCode = { + deposit: 202, + withdraw: 241, + }; + + public getClient({ + connection, + wallet, + }: { + connection: Connection; + wallet: Wallet; + }): CredixClient { + return new CredixClient(connection, wallet, { + programId: CredixConfiguration.credixProgramId, + }); + } +} + +export default new CredixConfiguration(); diff --git a/utils/uiTypes/proposalCreationTypes.ts b/utils/uiTypes/proposalCreationTypes.ts index 4467c5ea80..79f22eaab2 100644 --- a/utils/uiTypes/proposalCreationTypes.ts +++ b/utils/uiTypes/proposalCreationTypes.ts @@ -608,6 +608,16 @@ export interface NativeIncreaseComputingBudgetForm { computingBudget?: number; } +export interface CredixDepositForm { + governedAccount?: GovernedMultiTypeAccount; + uiAmount?: number; +} + +export interface CredixWithdrawForm { + governedAccount?: GovernedMultiTypeAccount; + uiAmount?: number; +} + export enum InstructionEnum { Transfer, ProgramUpgrade, @@ -693,6 +703,8 @@ export enum InstructionEnum { MercurialPoolDeposit, MercurialPoolWithdraw, NativeIncreaseComputingBudget, + CredixDeposit, + CredixWithdraw, } export enum PackageEnum { @@ -712,6 +724,7 @@ export enum PackageEnum { Deltafi, Orca, Mercurial, + Credix, } export type createParams = [ diff --git a/yarn.lock b/yarn.lock index 15c85cd3f8..76bb28a226 100644 --- a/yarn.lock +++ b/yarn.lock @@ -638,6 +638,20 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@credix/credix-client@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@credix/credix-client/-/credix-client-1.0.0.tgz#5275f2f6cf9719a081dc5edc909646ab62ea4ab0" + integrity sha512-QZ58Y2YdP4fWdXkIK9qvWngX87pEcbqe1hCcHLPfrLpQlWOXj58HOAf+971tcW7f6GyA5+Z4TtcPi00jIkZQ5w== + dependencies: + "@identity.com/solana-gateway-ts" "^0.8.2" + "@project-serum/anchor" "^0.24.2" + "@saberhq/anchor-contrib" "^1.13.32" + "@solana/spl-token" "^0.1.8" + "@solana/web3.js" "^1.43.4" + big.js "^6.1.1" + bn.js "^5.2.0" + node-irr "^2.0.3" + "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz" @@ -905,6 +919,17 @@ resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@identity.com/solana-gateway-ts@^0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/@identity.com/solana-gateway-ts/-/solana-gateway-ts-0.8.2.tgz#a8de5929e94a98b64cbdc38b4745f3fb20ae58aa" + integrity sha512-xFFoZtlF9XLHZ/ZS59QdVTDAtG7GJU2CqpXzwHwG7bPhhhXJ2zTHqlJ9oax2Faf9UKjyzp7F7TWfMScpTeOkEQ== + dependencies: + "@solana/web3.js" "^1.22.0" + async-retry "^1.3.3" + bn.js "^5.2.0" + borsh "^0.4.0" + ramda "^0.27.1" + "@isaacs/string-locale-compare@^1.0.1", "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz" @@ -2674,6 +2699,28 @@ superstruct "^0.14.2" tweetnacl "^1.0.0" +"@solana/web3.js@^1.43.4": + version "1.56.2" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.56.2.tgz#5212e8b147ebc216ea5a7aa99d5b555ebe41f9bd" + integrity sha512-ByWfNA8H/1EB4g0749uhkQ0zZZAQealzRmmT3UMIv3xe0DeHwnrzQUavBtAlHNMrKqLHu8kd+XtPci6zreMjjA== + dependencies: + "@babel/runtime" "^7.12.5" + "@noble/ed25519" "^1.7.0" + "@noble/hashes" "^1.1.2" + "@noble/secp256k1" "^1.6.3" + "@solana/buffer-layout" "^4.0.0" + bigint-buffer "^1.1.5" + bn.js "^5.0.0" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.1" + fast-stable-stringify "^1.0.0" + jayson "^3.4.4" + js-sha3 "^0.8.0" + node-fetch "2" + rpc-websockets "^7.5.0" + superstruct "^0.14.2" + "@solendprotocol/solend-sdk@^0.4.9": version "0.4.9" resolved "https://registry.npmjs.org/@solendprotocol/solend-sdk/-/solend-sdk-0.4.9.tgz" @@ -3669,6 +3716,13 @@ async-limiter@~1.0.0: resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-retry@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" + integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== + dependencies: + retry "0.13.1" + async@^2.6.2: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz" @@ -9390,6 +9444,11 @@ node-int64@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" +node-irr@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/node-irr/-/node-irr-2.0.3.tgz#c7409c5f10dcef9ccb0aace29203700804b4c8aa" + integrity sha512-hNPnizd9LLnvQ3zyCGM31+TjVLIdAvtiJCDcKqmiGCPTUGfi+Kyzfi37oXlyMSkV5cIb6ZHoaReLjBtG8I+t+Q== + node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz" @@ -10815,6 +10874,11 @@ quick-lru@^5.1.1: resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +ramda@^0.27.1: + version "0.27.2" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" + integrity sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA== + range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" @@ -11254,16 +11318,16 @@ retry-axios@^2.6.0: resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-2.6.0.tgz#d4dc5c8a8e73982e26a705e46a33df99a28723e0" integrity sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ== +retry@0.13.1, retry@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + retry@^0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"