Skip to content

Commit

Permalink
Account permission management (#276)
Browse files Browse the repository at this point in the history
* feat: account permission management

* ref: deps

* feat(account-settings): hooks are taking in now arrays

* ref(deps)

* feat(AddPermissionModal)

* feat(transferownership): added modal

* feat: remove all permission button

* feat(Modals): passing in the refetch function to modals

* ref(deps)

* fix: account management issues and improvements

* fix: deps

---------

Co-authored-by: fritzschoff <[email protected]>
Co-authored-by: max <[email protected]>
  • Loading branch information
3 people authored May 22, 2024
1 parent 3285fb9 commit f9f7a8b
Show file tree
Hide file tree
Showing 25 changed files with 997 additions and 5 deletions.
3 changes: 2 additions & 1 deletion liquidity/lib/etherscanLink/etherscanLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ export function etherscanLink({
switch (chain) {
case 'sepolia':
return `https://sepolia.etherscan.io/${isTx ? 'tx' : 'address'}/${address}`;
case 'arbitrum':
return `https://arbiscan.io/${isTx ? 'tx' : 'address'}/${address}`;
case 'optimism':
return `https://optimistic.etherscan.io/${isTx ? 'tx' : 'address'}/${address}`;
case 'base':
return `https://basescan.org/${isTx ? 'tx' : 'address'}/${address}`;
case 'base-sepolia':
return `https://sepolia.basescan.org/${isTx ? 'tx' : 'address'}/${address}`;

case 'mainnet':
default:
return `https://etherscan.io/${isTx ? 'tx' : 'address'}/${address}`;
Expand Down
1 change: 1 addition & 0 deletions liquidity/lib/useAccountInfo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useAccountPermissions';
13 changes: 13 additions & 0 deletions liquidity/lib/useAccountInfo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@snx-v3/useAccountPermissions",
"private": true,
"main": "index.ts",
"version": "0.0.1",
"dependencies": {
"@snx-v3/useAccountProxy": "workspace:*",
"@snx-v3/useBlockchain": "workspace:*",
"@snx-v3/useCoreProxy": "workspace:*",
"@tanstack/react-query": "^5.8.3",
"ethers": "^5.7.2"
}
}
43 changes: 43 additions & 0 deletions liquidity/lib/useAccountInfo/useAccountPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useAccountProxy } from '@snx-v3/useAccountProxy';
import { useNetwork } from '@snx-v3/useBlockchain';
import { useCoreProxy } from '@snx-v3/useCoreProxy';
import { useQuery } from '@tanstack/react-query';
import { utils } from 'ethers';

export function useAccountPermissions(accountId: string | undefined) {
const { data: CoreProxy } = useCoreProxy();
const { network } = useNetwork();

return useQuery({
queryKey: [`${network?.id}-${network?.preset}`, 'account-permissions', accountId],
queryFn: async function () {
if (!CoreProxy || !accountId) throw new Error('Should be disabled');
const permissions = await CoreProxy.getAccountPermissions(accountId);

return permissions.reduce(
(acc, { user, permissions }) => ({
...acc,
[user.toLowerCase()]: permissions.map((r: string) => utils.parseBytes32String(r)),
}),
{}
) as {
[key: string]: string[];
};
},
enabled: Boolean(CoreProxy?.address),
});
}

export function useAccountOwner(accountId: string | undefined) {
const { data: AccountProxy } = useAccountProxy();
const { network } = useNetwork();

return useQuery({
queryKey: [`${network?.id}-${network?.preset}`, 'account-owner', accountId],
queryFn: async function () {
if (!AccountProxy || !accountId) throw new Error('Should be disabled');
return await AccountProxy.ownerOf(accountId);
},
enabled: Boolean(AccountProxy?.address),
});
}
1 change: 1 addition & 0 deletions liquidity/lib/useManagePermissions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useManagePermissions';
12 changes: 12 additions & 0 deletions liquidity/lib/useManagePermissions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@snx-v3/useManagePermissions",
"private": true,
"main": "index.ts",
"version": "0.0.1",
"dependencies": {
"@snx-v3/useCoreProxy": "workspace:*",
"@snx-v3/useMulticall3": "workspace:*",
"@tanstack/react-query": "^5.8.3",
"ethers": "^5.7.2"
}
}
79 changes: 79 additions & 0 deletions liquidity/lib/useManagePermissions/useManagePermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { utils } from 'ethers';
import { useCoreProxy } from '@snx-v3/useCoreProxy';
import { useMutation } from '@tanstack/react-query';
import { useMulticall3 } from '@snx-v3/useMulticall3';

type Permissions = Array<string>;
const getPermissionDiff = (
existing: Permissions,
selected: Permissions
): {
grants: Permissions;
revokes: Permissions;
} => {
let grants: Permissions = [],
revokes: Permissions = [];
existing.concat(selected).forEach((permission) => {
if (!existing.includes(permission)) {
grants = [...grants, permission];
}
if (!selected.includes(permission)) {
revokes = [...revokes, permission];
}
});
return { grants, revokes };
};

export const useManagePermissions = ({
accountId,
target,
existing = [],
selected = [],
}: {
accountId: string;
target: string;
existing: Permissions;
selected: Permissions;
}) => {
const { data: CoreProxy } = useCoreProxy();
const { data: multicall } = useMulticall3();

return useMutation({
mutationFn: async () => {
if (!CoreProxy || !multicall) {
return;
}

const { grants, revokes } = getPermissionDiff(existing, selected);

try {
const grantCalls = grants.map((permission) => ({
target: CoreProxy.address,
callData: CoreProxy.interface.encodeFunctionData('grantPermission', [
accountId,
utils.formatBytes32String(permission),
target,
]),
allowFailure: false,
requireSuccess: true,
}));

const revokeCalls = revokes.map((permission) => ({
target: CoreProxy.address,
callData: CoreProxy.interface.encodeFunctionData('revokePermission', [
accountId,
utils.formatBytes32String(permission),
target,
]),
allowFailure: false,
requireSuccess: true,
}));

const tx = await multicall.aggregate3([...grantCalls, ...revokeCalls]);
await tx.wait();
} catch (error: any) {
throw error;
}
},
});
};
1 change: 1 addition & 0 deletions liquidity/lib/useTransferAccountId/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useTransferAccountId';
11 changes: 11 additions & 0 deletions liquidity/lib/useTransferAccountId/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@snx-v3/useTransferAccountId",
"private": true,
"main": "index.ts",
"version": "0.0.1",
"dependencies": {
"@snx-v3/useAccountProxy": "workspace:*",
"@snx-v3/useBlockchain": "workspace:*",
"@tanstack/react-query": "^5.8.3"
}
}
18 changes: 18 additions & 0 deletions liquidity/lib/useTransferAccountId/useTransferAccountId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useAccountProxy } from '@snx-v3/useAccountProxy';
import { useWallet } from '@snx-v3/useBlockchain';
import { useMutation } from '@tanstack/react-query';

export function useTransferAccountId(to: string, accountId: string) {
const { data: AccountProxy } = useAccountProxy();
const { activeWallet } = useWallet();

return useMutation({
mutationFn: async () => {
if (!AccountProxy) throw new Error('CoreProxy or Multicall not defined');
if (!activeWallet?.address) throw new Error('Wallet is not connected');
const tx = await AccountProxy.transferFrom(activeWallet.address, to, accountId);
const response = await tx.wait();
return response;
},
});
}
3 changes: 3 additions & 0 deletions liquidity/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@snx-v3/isBaseAndromeda": "workspace:*",
"@snx-v3/useAccountCollateral": "workspace:*",
"@snx-v3/useAccountCollateralUnlockDate": "workspace:*",
"@snx-v3/useAccountPermissions": "workspace:*",
"@snx-v3/useAccounts": "workspace:*",
"@snx-v3/useApprove": "workspace:*",
"@snx-v3/useApr": "workspace:*",
Expand All @@ -48,13 +49,15 @@
"@snx-v3/useGetUSDTokens": "workspace:*",
"@snx-v3/useLiquidityPosition": "workspace:*",
"@snx-v3/useLiquidityPositions": "workspace:*",
"@snx-v3/useManagePermissions": "workspace:*",
"@snx-v3/useMarketNamesById": "workspace:*",
"@snx-v3/useParams": "workspace:*",
"@snx-v3/usePoolConfiguration": "workspace:*",
"@snx-v3/usePoolData": "workspace:*",
"@snx-v3/usePools": "workspace:*",
"@snx-v3/useRewards": "workspace:^",
"@snx-v3/useTokenBalance": "workspace:*",
"@snx-v3/useTransferAccountId": "workspace:*",
"@snx-v3/useTransferableSynthetix": "workspace:*",
"@snx-v3/useUSDProxy": "workspace:*",
"@snx-v3/useVaultsData": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions liquidity/ui/src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { Manage } from './pages/Manage';
import { Pool } from './pages/Pool';
import { NotFoundPage } from './pages/404';
import { Pools } from './pages/Pools';
import { Settings } from './pages/Account/Settings';

export const Router = () => {
return (
<Suspense fallback={<Spinner />}>
<Routes>
<Route element={<DefaultLayout />}>
<Route path="/account/settings" element={<Settings />} />
<Route path="/positions/:collateralSymbol/:poolId" element={<Manage />} />
<Route path="/pools" element={<Pools />} />
<Route path="/pools/:poolId" element={<Pool />} />
Expand Down
43 changes: 43 additions & 0 deletions liquidity/ui/src/components/Address/Address.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CopyIcon, ExternalLinkIcon } from '@chakra-ui/icons';
import { Flex, Tooltip } from '@chakra-ui/react';
import { etherscanLink } from '@snx-v3/etherscanLink';
import { prettyString } from '@snx-v3/format';
import { useNetwork } from '@snx-v3/useBlockchain';
import { FC, useMemo } from 'react';

interface AddressProps {
address: string;
}

export const Address: FC<AddressProps> = ({ address }) => {
const { network } = useNetwork();
const link = useMemo(
() =>
etherscanLink({
chain: network?.name || '',
address,
}),
[address, network?.name]
);
return (
<Flex alignItems="center" gap={2}>
<Tooltip label={address}>{prettyString(address)}</Tooltip>
<CopyIcon
onClick={() => {
navigator.clipboard.writeText(address);
}}
cursor="pointer"
_hover={{
color: 'cyan',
}}
/>
<a target="_blank" href={link} rel="noreferrer">
<ExternalLinkIcon
_hover={{
color: 'cyan',
}}
/>
</a>
</Flex>
);
};
1 change: 1 addition & 0 deletions liquidity/ui/src/components/Address/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Address';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const permissionsList = ['ADMIN', 'BURN', 'DELEGATE', 'MINT', 'REWARDS', 'WITHDRAW'];
Loading

0 comments on commit f9f7a8b

Please sign in to comment.