Skip to content

Commit

Permalink
Symmetry x Realms Integration (#2418)
Browse files Browse the repository at this point in the history
Co-authored-by: bob <[email protected]>
Co-authored-by: 0xbridges <[email protected]>
  • Loading branch information
3 people authored Aug 29, 2024
1 parent 4372c36 commit 2a5d38c
Show file tree
Hide file tree
Showing 13 changed files with 1,485 additions and 1 deletion.
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 @@ -112,6 +112,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

0 comments on commit 2a5d38c

Please sign in to comment.