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

Symmetry x Realms Integration #2418

Merged
merged 4 commits into from
Aug 29, 2024
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
242 changes: 242 additions & 0 deletions components/instructions/programs/symmetryV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import { Connection } from "@solana/web3.js"
import { BasketsSDK } from "@symmetry-hq/baskets-sdk"
import BufferLayout from 'buffer-layout'

const targetCompositionLayout = BufferLayout.seq(
BufferLayout.u8(),
15,
'targetComposition'
);
const targetWeightsLayout = BufferLayout.seq(
BufferLayout.u32(),
15,
'targetWeights'
);
const rebalanceAndLpLayout = BufferLayout.seq(
BufferLayout.u8(),
2,
'rebalanceAndLp'
);

export const SYMMETRY_V2_INSTRUCTIONS = {
"2KehYt3KsEQR53jYcxjbQp2d2kCp4AkuQW68atufRwSr": {
78: {
name: 'Symmetry: Withdraw from Basket',
accounts: [
{ name: 'Withdrawer' },
{ name: 'Basket Address' },
{ name: 'Symmetry PDA' },
{ name: 'Withdraw Temporary State Account' },
{ name: 'Withdrawer Basket Token Account' },
{ name: 'Basket Token Mint' },
{ name: 'System Program' },
{ name: 'Token Program' },
{ name: 'Rent Program' },
{ name: 'Account' },
],
getDataUI: async (connection: Connection, data: Uint8Array) => {

//@ts-ignore
const { amount, rebalance } = BufferLayout.struct([
BufferLayout.nu64('amount'),
BufferLayout.nu64('rebalance')
]).decode(Buffer.from(data), 8)

return (
<>
<p>Withdraw Amount: {amount / 10**6}</p>
<p>Rebalance to USDC: {rebalance === 2 ? 'Yes' : 'No - Withdraw Assets Directly'}</p>
</>
)
},
},
251: {
name: 'Symmetry: Deposit into Basket',
accounts: [
{ name: 'Depositor' },
{ name: 'Basket Address' },
{ name: 'Basket Token Mint' },
{ name: 'Symmetry Token List' },
{ name: 'Symmetry PDA' },
{ name: 'USDC PDA Account' },
{ name: 'Depositor USDC Account' },
{ name: 'Manager USDC Account' },
{ name: 'Symmetry Fee Account' },
{ name: 'Host Platform USDC Account' },
{ name: 'Depositor Basket Token Account' },
{ name: 'Temporary State Account for Deposit' },
{ name: 'System Program' },
{ name: 'Token Program' },
{ name: 'Rent' },
{ name: 'Associated Token Program' },
{ name: 'Account' },
],
getDataUI: async (connection: Connection, data: Uint8Array) => {

//@ts-ignore
const { amount, rebalance } = BufferLayout.struct([
BufferLayout.nu64('amount')
]).decode(Buffer.from(data), 8)

return (
<>
<p>USDC Deposit Amount: {amount / 10**6}</p>
</>
)
},
},
38: {
name: 'Symmetry: Edit Basket',
accounts: [
{ name: 'Basket Manager' },
{ name: 'Basket Address' },
{ name: 'Symmetry Token List' },
{ name: 'Manager Fee Receiver Address' },
],
getDataUI: async (connection: Connection, data: Uint8Array) => {

//@ts-ignore
const { managerFee, rebalanceInterval, rebalanceThreshold, rebalanceSlippage, lpOffsetThreshold, rebalanceAndLp, numOfTokens, targetComposition, targetWeights } = BufferLayout.struct([
BufferLayout.u16('managerFee'),
BufferLayout.nu64('rebalanceInterval'),
BufferLayout.u16('rebalanceThreshold'),
BufferLayout.u16('rebalanceSlippage'),
BufferLayout.u16('lpOffsetThreshold'),
rebalanceAndLpLayout,
BufferLayout.u8('numOfTokens'),
targetCompositionLayout,
targetWeightsLayout,
]).decode(Buffer.from(data), 8)

const basketsSdk = await BasketsSDK.init(connection);
const tokenData = basketsSdk.getTokenListData();
let usdcIncluded = false;
let totalWeight = 0; targetWeights.map(w => totalWeight += w);

let composition = targetComposition.map((tokenId, i) => {
let token = tokenData.filter(x => x.id == tokenId)[0]
if(token.id === 0) {
if(!usdcIncluded) {
usdcIncluded = true;
return {
...token,
weight: targetWeights[i] / totalWeight * 100
}
}
} else {
return {
...token,
weight: targetWeights[i] / totalWeight * 100
}
}
}).filter(x => x != null)

return (
<>
<p>Manager Fee: {managerFee / 100}%</p>
<p>Rebalance Check Interval: {rebalanceInterval / 60} minutes</p>
<p>Rebalance Trigger Threshold: {rebalanceThreshold / 100}%</p>
<p>Maximum Slippage Allowed During Rebalancing: {rebalanceSlippage / 100}%</p>
<p>Liquidity Provision Threshold: {lpOffsetThreshold / 100}%</p>
<p>Rebalancing Enabled: {rebalanceAndLp[0] === 0 ? "Yes" : "No"}</p>
<p>Liquidity Provision Enabled: {rebalanceAndLp[1] === 0 ? "No" : "Yes"}</p>
<p>Basket Composition Size: {numOfTokens} Tokens</p>
<div className="text-sm">
Basket Composition:
{
composition.map((compItem, i) => {
return <div className="flex items-center">
<p className="text-sm">{compItem.weight}% {compItem.symbol} <a className="text-blue" target="_blank" href={"https://solscan.io/token/"+compItem.tokenMint}>({compItem.tokenMint.slice(0,6)}...)</a></p>
</div>
})
}
</div>
</>
)
},
},
47: {
name: 'Symmetry: Create Basket',
accounts: [
{ name: 'Manager' },
{ name: 'Token List' },
{ name: 'Basket Address' },
{ name: 'Symmetry PDA' },
{ name: 'Basket Token Mint' },
{ name: 'Symmetry Fee Collector' },
{ name: 'Metadata Account' },
{ name: 'Metadata Program' },
{ name: 'System Program' },
{ name: 'Token Program' },
{ name: 'Rent' },
{ name: 'Host Platform' },
{ name: 'Fee Collector Address' },
{ name: 'Account' },
],
getDataUI: async (connection: Connection, data: Uint8Array) => {
//@ts-ignore
const { managerFee, hostFee, basketType,rebalanceInterval, rebalanceThreshold, rebalanceSlippage, lpOffsetThreshold, rebalanceAndLp, numOfTokens, targetComposition, targetWeights, } = BufferLayout.struct([
BufferLayout.u16('managerFee'),
BufferLayout.u16('hostFee'),
BufferLayout.u8('basketType'),
BufferLayout.nu64('rebalanceInterval'),
BufferLayout.u16('rebalanceThreshold'),
BufferLayout.u16('rebalanceSlippage'),
BufferLayout.u16('lpOffsetThreshold'),
rebalanceAndLpLayout,
BufferLayout.u8('numOfTokens'),
targetCompositionLayout,
targetWeightsLayout,
]).decode(Buffer.from(data), 8)


let basketsSdk = await BasketsSDK.init(connection);
let tokenData = basketsSdk.getTokenListData();
let usdcIncluded = false;
let totalWeight = 0; targetWeights.map(w => totalWeight += w);

let composition = targetComposition.map((tokenId, i) => {
let token = tokenData.filter(x => x.id == tokenId)[0]
if(token.id === 0) {
if(!usdcIncluded) {
usdcIncluded = true;
return {
...token,
weight: targetWeights[i] / totalWeight * 100
}
}
} else
return {
...token,
weight: targetWeights[i] / totalWeight * 100
}
}).filter(x => x != null)

return (
<>
<p>Manager Fee: {managerFee / 100}%</p>
<p>Host Platform Fee: {hostFee / 100}%</p>
<p>Basket Type: {basketType === 0 ? "Bundle" : basketType === 1 ? "Portfolio" : "Private"}</p>
<p>Rebalance Check Interval: {rebalanceInterval / 60} minutes</p>
<p>Rebalance Trigger Threshold: {rebalanceThreshold / 100}%</p>
<p>Maximum Slippage Allowed During Rebalancing: {rebalanceSlippage / 100}%</p>
<p>Liquidity Provision Threshold: {lpOffsetThreshold / 100}%</p>
<p>Rebalancing Enabled: {rebalanceAndLp[0] === 0 ? "Yes" : "No"}</p>
<p>Liquidity Provision Enabled: {rebalanceAndLp[1] === 0 ? "No" : "Yes"}</p>
<p>Basket Composition Size: {numOfTokens} Tokens</p>
<div className="text-sm">
Basket Composition:
{
composition.map((compItem, i) => {
return <div className="flex items-center">
<p className="text-sm">{compItem.weight}% {compItem.symbol} <a className="text-blue" target="_blank" href={"https://solscan.io/token/"+compItem.tokenMint}>({compItem.tokenMint.slice(0,6)}...)</a></p>
</div>
})
}
</div>
</>
)
},
},
},
}
2 changes: 2 additions & 0 deletions components/instructions/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { STAKE_INSTRUCTIONS } from './programs/stake'
import dayjs from 'dayjs'
import { JUPITER_REF } from './programs/jupiterRef'
import { STAKE_SANCTUM_INSTRUCTIONS } from './programs/stakeSanctum'
import { SYMMETRY_V2_INSTRUCTIONS } from './programs/symmetryV2'

/**
* Default governance program id instance
Expand Down Expand Up @@ -516,6 +517,7 @@ export const INSTRUCTION_DESCRIPTORS = {
...STAKE_INSTRUCTIONS,
...STAKE_SANCTUM_INSTRUCTIONS,
...JUPITER_REF,
...SYMMETRY_V2_INSTRUCTIONS,
}

export async function getInstructionDescriptor(
Expand Down
28 changes: 28 additions & 0 deletions hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ export default function useGovernanceAssets() {
name: 'Solend',
image: '/img/solend.png',
},
[PackageEnum.Symmetry]: {
name: 'Symmetry',
image: '/img/symmetry.png',
},
[PackageEnum.Squads]: {
name: 'Squads',
image: '/img/squads.png',
Expand Down Expand Up @@ -713,6 +717,30 @@ export default function useGovernanceAssets() {
name: 'Withdraw Funds',
packageId: PackageEnum.Solend,
},

/*
███████ ██ ██ ███ ███ ███ ███ ███████ ████████ ██████ ██ ██
██ ██ ██ ████ ████ ████ ████ ██ ██ ██ ██ ██ ██
███████ ████ ██ ████ ██ ██ ████ ██ █████ ██ ██████ ████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███████ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██
*/
[Instructions.SymmetryCreateBasket]: {
name: 'Create Basket',
packageId: PackageEnum.Symmetry,
},
[Instructions.SymmetryEditBasket]: {
name: 'Edit Basket',
packageId: PackageEnum.Symmetry,
},
[Instructions.SymmetryDeposit]: {
name: 'Deposit into Basket',
packageId: PackageEnum.Symmetry,
},
[Instructions.SymmetryWithdraw]: {
name: 'Withdraw from Basket',
packageId: PackageEnum.Symmetry,
},
/*
███████ ██████ ██ ██ █████ ██████ ███████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"@sqds/mesh": "1.0.6",
"@switchboard-xyz/sbv2-lite": "0.2.4",
"@switchboard-xyz/solana.js": "3.2.5",
"@symmetry-hq/baskets-sdk": "0.0.45",
"@tailwindcss/forms": "0.5.3",
"@tailwindcss/line-clamp": "0.4.2",
"@tanstack/react-query": "4.14.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Modal from "@components/Modal"
import Input from "@components/inputs/Input"
import { useEffect, useState } from "react"


const AddTokenToBasketModal = ({
open,
onClose,
supportedTokens,
onSelect
}:{
open: boolean,
onClose: any,
supportedTokens: any,
onSelect: any
}) => {
const [allTokens, setAllTokens] = useState(supportedTokens);
const [searchValue, setSearchValue] = useState('');

useEffect(() => {
if(searchValue.length > 0){
const filteredTokens = supportedTokens.filter((token: any) => {
return token.name.toLowerCase().includes(searchValue.toLowerCase()) || token.symbol.toLowerCase().includes(searchValue.toLowerCase())
})
setAllTokens(filteredTokens)
} else {
setAllTokens(supportedTokens)
}

}, [searchValue, supportedTokens])
return <>
{
open &&
<Modal
isOpen={open}
onClose={() => onClose()}
>
<h2 className="text-fgd-1 mb-8 text-center">Select a Token</h2>
<input className="w-full p-2 text-lg rounded-md bg-bkg-1 text-fgd-1 text-center" placeholder="Search for tokens" value={searchValue} onChange={(e) => setSearchValue(e.target.value)} type="text" />
<div className='flex flex-col max-h-64 mt-2 overflow-scroll gap-2'>
{
allTokens.map((token, i) => {
return (
<div onClick={() => onSelect(token)} key={i} className='flex w-full gap-2 items-center justify-between bg-bkg-1 hover:bg-bkg-3 cursor-pointer p-2 rounded-md'>
<div className="flex flex-col">
<p className='text-xs text-fgd-3'>
{
token.name
}
</p>
<p className='text-sm font-bold'>
{
token.symbol
}
</p>
</div>
<a rel="noreferrer" href={`https://solscan.io/token/${token.tokenMint}`} target="_blank" className="text-xs text-blue-500 underline">
{
token.tokenMint.slice(0, 6) + '...' + token.tokenMint.slice(-6)
}
</a>
</div>
)
})
}
</div>
</Modal>
}
</>

}

export default AddTokenToBasketModal;
Loading
Loading