Skip to content

Commit

Permalink
Merge pull request #42 from TalismanSociety/feat/vested-multisend
Browse files Browse the repository at this point in the history
feat: vested multisend done
  • Loading branch information
chrisling-dev authored May 9, 2024
2 parents ddae17c + 5c55c5a commit bca47a9
Show file tree
Hide file tree
Showing 27 changed files with 1,517 additions and 614 deletions.
2 changes: 2 additions & 0 deletions apps/multisig/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@recoiljs/refine": "0.1.1",
"@sentry/react": "^7.37.2",
"@sentry/tracing": "^7.37.2",
"@silevis/reactgrid": "^4.1.5",
"@substrate/txwrapper-core": "^5.0.0",
"@talisman-connect/ui": "^1.1.1",
"@talismn/balances": "0.0.0-pr1378-20240327183927",
Expand All @@ -63,6 +64,7 @@
"@talismn/siws": "0.0.8",
"@talismn/ui": "workspace:^",
"@talismn/util": "^0.2.0",
"@tanstack/react-table": "^8.16.0",
"@uiw/codemirror-themes": "^4.21.13",
"@uiw/react-codemirror": "^4.21.13",
"@vercel/analytics": "^1.2.2",
Expand Down
1 change: 1 addition & 0 deletions apps/multisig/src/components/BlockInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const BlockInput: React.FC<Props> = ({ blockTime, currentBlock, minBlock,
{derivedDate && (
<Calendar
selected={derivedDate}
defaultMonth={derivedDate}
initialFocus
mode="single"
onSelect={handleDateChange}
Expand Down
15 changes: 3 additions & 12 deletions apps/multisig/src/components/FileUploadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Plus } from '@talismn/icons'
import { Button } from '@talismn/ui'
import { useRef } from 'react'
import { Button } from './ui/button'

type Props = {
label?: string
Expand All @@ -27,22 +27,13 @@ const FileUploadButton: React.FC<Props> = ({ accept, label, multiple, onFiles })
type="file"
ref={inputRef}
accept={accept}
css={{ visibility: 'hidden', height: 0, width: 0, opacity: 0 }}
className="hidden h-0 w-0 opacity-0"
multiple={multiple}
onChange={handleFileChange}
// @ts-ignore clear the input value so that the same file can be uploaded again
onClick={e => (e.target.value = null)}
/>
<Button
css={{
backgroundColor: 'var(--color-backgroundLight)',
borderRadius: 24,
height: '30px',
padding: '8px 16px',
}}
variant="secondary"
onClick={handleClick}
>
<Button variant="secondary" onClick={handleClick} size="lg">
<div
css={{
display: 'flex',
Expand Down
67 changes: 67 additions & 0 deletions apps/multisig/src/components/VestingDateRange.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useApi } from '@domains/chains/pjs-api'
import { useLatestBlockNumber } from '@domains/chains/useLatestBlockNumber'
import { expectedBlockTime } from '@domains/common/substratePolyfills'
import { VestingSchedule } from '@domains/multisig'
import { useMemo } from 'react'
import { Tooltip } from './ui/tooltip'
import { secondsToDuration } from '@util/misc'
import { cn } from '@util/tailwindcss'

export const VestingDateRange: React.FC<{
chainGenesisHash: string
vestingSchedule: VestingSchedule
className?: string
}> = ({ chainGenesisHash, className, vestingSchedule }) => {
const { api } = useApi(chainGenesisHash)
const blockNumber = useLatestBlockNumber(chainGenesisHash)
const blockTime = useMemo(() => {
if (!api) return
return expectedBlockTime(api)
}, [api])

const startDate = useMemo(() => {
if (blockNumber === undefined || blockTime === undefined) return undefined

const now = new Date()
const blocksDiff = vestingSchedule.start - blockNumber
const msDiff = blocksDiff * blockTime.toNumber()
return new Date(now.getTime() + msDiff)
}, [blockNumber, blockTime, vestingSchedule.start])

const endDate = useMemo(() => {
if (blockNumber === undefined || blockTime === undefined) return undefined

const now = new Date()
const blocksDiff = vestingSchedule.start + vestingSchedule.period - blockNumber
const msDiff = blocksDiff * blockTime.toNumber()
return new Date(now.getTime() + msDiff)
}, [blockNumber, blockTime, vestingSchedule.period, vestingSchedule.start])

const startDateString = startDate?.toLocaleDateString()
const endDateString = endDate?.toLocaleDateString()
const sameDay = startDateString === endDateString
const duration = useMemo(() => {
if (blockTime === undefined) return undefined
return vestingSchedule.period * blockTime.toNumber()
}, [blockTime, vestingSchedule.period])

return (
<Tooltip
delayDuration={300}
content={
<p className="text-[14px]">
{startDate?.toLocaleString()} &rarr; {endDate?.toLocaleString()}
</p>
}
>
<p className={cn('text-right text-offWhite text-[14px] cursor-default', className)}>
{sameDay ? `${startDateString}, ` : ''}
{sameDay ? `≈${startDate?.toLocaleTimeString()}` : startDateString} &rarr;{' '}
{sameDay ? `≈${endDate?.toLocaleTimeString()}` : endDate?.toLocaleDateString()}
{!!duration && (
<span className="text-right text-gray-200 text-[14px]">&nbsp;(&asymp;{secondsToDuration(duration)})</span>
)}
</p>
</Tooltip>
)
}
4 changes: 2 additions & 2 deletions apps/multisig/src/components/ui/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-[12px] border bg-gray-800 p-[4px] text-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'z-50 min-w-[8rem] overflow-hidden rounded-[12px] border bg-gray-900 p-[4px] text-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
Expand All @@ -79,7 +79,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-[8px] px-2 py-1.5 text-sm outline-none transition-colors focus:bg-gray-800 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-[8px] px-2 py-1.5 text-[12px] leading-none outline-none transition-colors focus:bg-gray-800 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
Expand Down
81 changes: 81 additions & 0 deletions apps/multisig/src/components/ui/table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react'

import { cn } from '@util/tailwindcss'

const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table ref={ref} className={cn('w-full caption-bottom text-[16px]', className)} {...props} />
</div>
)
)
Table.displayName = 'Table'

const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<thead ref={ref} className={cn('[&_tr]:border-b border-gray-500 bg-gray-800', className)} {...props} />
)
)
TableHeader.displayName = 'TableHeader'

const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0 border-gray-500', className)} {...props} />
)
)
TableBody.displayName = 'TableBody'

const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn('border-t border-gray-500 bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
{...props}
/>
)
)
TableFooter.displayName = 'TableFooter'

const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'border-b border-gray-500 transition-colors hover:bg-gray-800/50 data-[state=selected]:bg-muted text-[14px]',
className
)}
{...props}
/>
)
)
TableRow.displayName = 'TableRow'

const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'h-12 p-[8px] text-left align-middle font-medium text-gray-200 [&:has([role=checkbox])]:pr-0',
className
)}
{...props}
/>
)
)
TableHead.displayName = 'TableHead'

const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => (
<td ref={ref} className={cn('p-[8px] align-middle [&:has([role=checkbox])]:pr-0', className)} {...props} />
)
)
TableCell.displayName = 'TableCell'

const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
({ className, ...props }, ref) => (
<caption ref={ref} className={cn('mt-4 text-[12px] text-muted-foreground', className)} {...props} />
)
)
TableCaption.displayName = 'TableCaption'

export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
2 changes: 1 addition & 1 deletion apps/multisig/src/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const Tooltip: React.FC<React.PropsWithChildren & { content?: React.ReactNode; d
<TooltipProvider>
<TooltipRoot delayDuration={delayDuration}>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent>{content}</TooltipContent>
<TooltipContent className={!content ? 'hidden' : ''}>{content}</TooltipContent>
</TooltipRoot>
</TooltipProvider>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/multisig/src/domains/auth/AccountWatcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const AccountWatcher: React.FC = () => {
if (extensionInitiated && extensionAccounts.length > 0) {
Object.entries(authTokenBook).forEach(([address, auth]) => {
const account = extensionAccounts.find(account => account.address.toSs58() === address)
if (!account || (auth && (typeof auth === 'string' || !isJwtValid(auth.accessToken) || !auth.id))) {
if ((auth && !account) || (auth && (typeof auth === 'string' || !isJwtValid(auth.accessToken) || !auth.id))) {
setAuthTokenBook({ ...authTokenBook, [address]: undefined })
if (selectedAccount === address) setSelectedAccount(null)
}
Expand Down
8 changes: 1 addition & 7 deletions apps/multisig/src/domains/chains/pjs-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ export const pjsApiSelector = atomFamily({
get:
(_genesisHash: string) =>
async ({ get }): Promise<ApiPromise> => {
const chain = supportedChains.find(({ genesisHash }) => genesisHash === _genesisHash)
const customRpcs = get(customRpcsAtom)
const rpc = customRpcs[_genesisHash]

Expand All @@ -103,12 +102,7 @@ export const pjsApiSelector = atomFamily({
api = get(defaultPjsApiSelector(_genesisHash))
}

try {
await api.isReady
return api
} catch (e) {
throw new Error(`Failed to connect to ${chain?.chainName} chain:` + getErrorString(e))
}
return api
},
dangerouslyAllowMutability: true,
}),
Expand Down
19 changes: 17 additions & 2 deletions apps/multisig/src/domains/multisig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,21 @@ const isSubstrateTokensTokenTransfer = (argHuman: any): argHuman is SubstrateTok
}

const callToTransactionRecipient = (arg: any, chainTokens: BaseToken[]): TransactionRecipient | null => {
if (arg?.section === 'vesting' && arg?.method === 'vestedTransfer') {
const { target, dest, schedule } = arg.args
const targetAddress = Address.fromSs58(parseCallAddressArg(target ?? dest))
const vestingSchedule = callToVestingSchedule(schedule)
if (vestingSchedule && targetAddress) {
return {
address: targetAddress,
balance: {
token: chainTokens.find(t => t.type === 'substrate-native')!,
amount: vestingSchedule.totalAmount,
},
vestingSchedule,
}
}
}
if (isSubstrateNativeTokenTransfer(arg)) {
const nativeToken = chainTokens.find(t => t.type === 'substrate-native')
if (!nativeToken) throw Error(`Chain does not have a native token!`)
Expand Down Expand Up @@ -675,11 +690,11 @@ export const extrinsicToDecoded = (
}
}

// check for vested transfer/ remove proxy
// check for vested transfer
for (const arg of args) {
const obj: any = arg.toHuman()
if (obj?.section === 'vesting') {
if (obj.method === 'vestedTransfer' || obj.method === 'removeProxy') {
if (obj.method === 'vestedTransfer') {
const { target, dest, schedule } = obj.args
const targetAddress = Address.fromSs58(parseCallAddressArg(target ?? dest))
const vestingSchedule = callToVestingSchedule(schedule)
Expand Down
Loading

0 comments on commit bca47a9

Please sign in to comment.