Skip to content

Commit

Permalink
Merge pull request #1 from 0xsequence/simple-updates
Browse files Browse the repository at this point in the history
Add edit signers option
  • Loading branch information
Agusx1211 authored Apr 8, 2024
2 parents e3ccf77 + 2f99894 commit dc9a38d
Show file tree
Hide file tree
Showing 15 changed files with 1,015 additions and 52 deletions.
22 changes: 21 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AppShell, Box, Burger, Divider, Grid, Group, NativeSelect, NavLink, Title, Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconCirclePlus, IconFileImport, IconFileUpload, IconHome, IconList, IconSend2, IconWallet } from '@tabler/icons-react';
import { IconCirclePlus, IconEdit, IconFileImport, IconFileUpload, IconHome, IconList, IconListDetails, IconSend2, IconWallet } from '@tabler/icons-react';
import { Route, Routes, useLocation, useNavigate } from "react-router-dom"
import { Create } from './sections/Create';
import { Notifications } from '@mantine/notifications';
Expand All @@ -14,6 +14,9 @@ import { Transactions } from './sections/Transactions';
import { useImport } from './sections/Import';
import { ImportWallet } from './sections/ImportWallet';
import { useWallets } from './stores/db/Wallets';
import { Update } from './sections/Update';
import { Updates } from './sections/Updates';
import { UpdateDetail } from './sections/UpdateDetail';

declare var __COMMIT_HASH__: string

Expand Down Expand Up @@ -111,6 +114,20 @@ export function App() {
active={pathname === '/transactions/' + selectedWalletAddress}
disabled={!selectedWalletAddress}
/>
<NavLink
href={"#update/" + selectedWalletAddress}
label="Update Signers"
leftSection={<IconEdit size="1rem" stroke={1.5} />}
active={pathname === '/update/' + selectedWalletAddress}
disabled={!selectedWalletAddress}
/>
<NavLink
href={"#updates/" + selectedWalletAddress}
label="Pending Updates"
leftSection={<IconListDetails size="1rem" stroke={1.5} />}
active={pathname === '/updates/' + selectedWalletAddress}
disabled={!selectedWalletAddress}
/>
<Box mt="auto" />
<Box>
<Text size="sm" c="dimmed">
Expand All @@ -136,6 +153,9 @@ export function App() {
<Route path="/new-transaction/:address" element={<Send />} />
<Route path="/transactions/:address" element={<Transactions />} />
<Route path="/transaction/:subdigest" element={<Transaction />} />
<Route path="/update/:address" element={<Update />} />
<Route path="/updates/:address" element={<Updates />} />
<Route path="/do-update/:subdigest" element={<UpdateDetail />} />
</Routes>
</AppShell.Main>
</AppShell>
Expand Down
6 changes: 2 additions & 4 deletions src/components/SignerEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,9 @@ export function SignerEditor<T>(props: SignerEditorProps<T>) {
const { form, formKey } = props
const values = form.values as any

console.log("values", formKey, values[formKey])

return <>
{ (values[formKey] as SignerEditorEntry[]).map((_, index) => (
<Group key={index} mt="xs">
<Group key={index+form.getInputProps(`${formKey}.${index}.address`).value.toString()+form.getInputProps(`${formKey}.${index}.weight`)?.toString()} mt="xs">
<TextInput
label="Address"
placeholder="0x..."
Expand All @@ -67,7 +65,7 @@ export function SignerEditor<T>(props: SignerEditorProps<T>) {
</Group>
)) }
<Group justify="flex-end" mt="md">
<Button fullWidth variant="light" onClick={() => form.insertListItem(formKey, { address: '', value: 0 })}>
<Button fullWidth variant="light" onClick={() => form.insertListItem(formKey, { address: '', weight: 0 })}>
Add signer
</Button>
</Group>
Expand Down
140 changes: 140 additions & 0 deletions src/components/UpdateDiff.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { commons, universal } from "@0xsequence/core"
import { Alert, Box, Divider } from "@mantine/core"
import { IconFlag } from "@tabler/icons-react"
import { ethers } from "ethers"

export type UpdateSnapshot = {
threshold: number,
signers: {
address: string,
weight: number
}[]
}

function isUpdateSnapshot(update: any): update is UpdateSnapshot {
return (
typeof update.threshold === 'number' &&
Array.isArray(update.signers) &&
update.signers.every((signer: any) => (
typeof signer.address === 'string' &&
(typeof signer.weight === 'number' || signer.weight === 0)
))
)
}

function toUpdateSnapshot(update: UpdateSnapshot | commons.config.Config): UpdateSnapshot | undefined {
if (isUpdateSnapshot(update)) {
return update
}

try {
if ((update as any).threshold === undefined) {
throw new Error("Threshold is required")
}

const coder = universal.genericCoderFor(update.version).config
const signers = coder.signersOf(update).map((signer) => ({
address: signer.address,
weight: signer.weight
}))

return {
threshold: (update as any).threshold,
signers
}
} catch (e) {
console.error(e)
}

return undefined
}

type Entry = {
address: string,
weight: number
}

function getChanges(
oldEntries: Entry[],
newEntries: Entry[]
): { added: Entry[], removed: Entry[] } {
const added: Entry[] = [];
const removed: Entry[] = [];

// Any address that exists in the old entries but not in the new entries is removed
const tmpNewEntries = [...newEntries]
for (const oldEntry of oldEntries) {
const index = tmpNewEntries.findIndex((entry) => (
entry.address === oldEntry.address && entry.weight === oldEntry.weight
))
if (index === -1) {
removed.push(oldEntry)
} else {
tmpNewEntries.splice(index, 1)
}
}

// Any address that exists in the new entries but not in the old entries is added
const tmpOldEntries = [...oldEntries]
for (const newEntry of newEntries) {
if (!ethers.utils.isAddress(newEntry.address)) {
continue
}

const index = tmpOldEntries.findIndex((entry) => (
entry.address === newEntry.address && entry.weight === newEntry.weight
))
if (index === -1) {
added.push(newEntry)
} else {
tmpOldEntries.splice(index, 1)
}
}

return { added, removed }
}

export function UpdateDiff(props: {
from: UpdateSnapshot | commons.config.Config,
to: UpdateSnapshot | commons.config.Config
}) {
const from = toUpdateSnapshot(props.from)
const to = toUpdateSnapshot(props.to)

if (!from || !to) {
return <div>Invalid update</div>
}

const changes = getChanges(from.signers, to.signers)

const addRows = changes.added.map((signer) => `+ ${signer.address} (Weight ${signer.weight})`)
const delRows = changes.removed.map((signer) => `- ${signer.address} (Weight ${signer.weight})`)

if (from.threshold !== to.threshold) {
delRows.unshift(`- Threshold ${from.threshold}`)
addRows.unshift(`+ Threshold ${to.threshold}`)
}

return <>
{ (delRows.length !== 0 || addRows.length !== 0) && <Box mb="md">
<Box style={{ fontSize: '12px', fontFamily: 'Courier New, monospace' }}>
{ delRows.length > 0 && <Box style={{ color: "red" }} mb="md">
<h5 style={{ fontSize: '14px', margin: 0 }}>Removing</h5>
{ delRows.map((val, index) => (
<Box key={index} mb="xs">{val}</Box>
)) }
</Box> }
{ addRows.length > 0 && <Box style={{ color: "green" }} mb="md">
<h5 style={{ fontSize: '14px', margin: 0 }}>Adding</h5>
{ addRows.map((signer, index) => (
<Box key={index} mb="xs">{signer}</Box>
)) }
</Box> }
</Box>
{ delRows.length > 0 && <Alert variant="light" color="red" title="Removing Signers" icon={<IconFlag/>}>
Revoking weight only takes effect after sending a transaction on-chain, networks will be updated independently.
</Alert>}
<Divider my="md" />
</Box> }
</>
}
14 changes: 13 additions & 1 deletion src/sections/Import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ export function Import() {

const result = await importData(data)

if (result.importedSignatures.length === 0 && result.importedTransactions.length === 0) {
if (
result.importedSignatures.length === 0 &&
result.importedTransactions.length === 0 &&
result.importedUpdates.length === 0
) {
notifications.show({
title: 'No data imported',
message: 'No new transactions or signatures found',
Expand All @@ -75,6 +79,14 @@ export function Import() {
color: 'green'
})
}

if (result.importedUpdates.length > 0) {
notifications.show({
title: 'Updates imported',
message: 'Imported ' + result.importedUpdates.length + ' updates',
color: 'green'
})
}
}

setData('')
Expand Down
7 changes: 5 additions & 2 deletions src/sections/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export function Transaction() {
if (!transaction || Array.isArray(transaction)) {
return <>
{title}
Transaction {subdigest.toString()} not found, try uploading the payload
Transaction {subdigest.toString()} not found.
<Space />
Try importing data.
</>
}

Expand Down Expand Up @@ -99,6 +101,7 @@ export function StatefulTransaction(props: { transaction: TransactionsEntry, sta
const coder = universal.genericCoderFor(state.config.version)
const recovered = useRecovered(subdigest, signatures)
const weightSum = coder.config.signersOf(state.config).filter((s) => recovered.has(s.address)).reduce((acc, signer) => acc + signer.weight, 0)
const progress = Math.floor((weightSum / threshold * 100))

let canSendError = ""
if (weightSum < threshold) {
Expand Down Expand Up @@ -220,7 +223,7 @@ export function StatefulTransaction(props: { transaction: TransactionsEntry, sta
<MiniCard title="Tx Hash" value={receipt.loading ? "Loading ..." : receipt.receipt !== "" ? receipt.receipt : "--"} />
<MiniCard title="Threshold" value={threshold.toString() } />
<MiniCard title="Total Weight" value={weightSum.toString()} />
<MiniCard title="Progress" value={`${(weightSum / threshold * 100)}%`} />
<MiniCard title="Progress" value={`${progress}%`} />
</Grid>
</Box>
<Box >
Expand Down
6 changes: 3 additions & 3 deletions src/sections/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function Transactions() {
return 0
})

const rows = sorted.map((element, i) => {
const txRows = sorted.map((element, i) => {
const subdigest = subdigestOf(element)
return <Table.Tr key={i}>
<Table.Td>{subdigest}</Table.Td>
Expand All @@ -46,7 +46,7 @@ export function Transactions() {
size="compact-sm"
variant="outline"
onClick={() => {
navigate('/transaction/' + subdigest)
navigate('/updates/' + subdigest)
}}
>
Open
Expand All @@ -69,7 +69,7 @@ export function Transactions() {
<Table.Th></Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
<Table.Tbody>{txRows}</Table.Tbody>
</Table>
</>
}
Loading

0 comments on commit dc9a38d

Please sign in to comment.