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

Added Heartbeat transaction #376

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
15 changes: 7 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"@txnlab/use-wallet": "^3.11.0",
"@txnlab/use-wallet-react": "^3.11.0",
"@xstate/react": "^4.1.1",
"algosdk": "2.9.0",
"algosdk": "2.10.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^1.0.0",
Expand Down
2 changes: 2 additions & 0 deletions src/features/blocks/data/synced-round.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { atom, useAtomValue } from 'jotai'
import { Round } from './types'

// TODO: HB - FNet Block 4590103, has a hb transaction (3HUZDAXUGHS44RJ4BQ6HOMWZCGOJW5WWOQ5CR7XHSZHFP2ZQCDOA), uncomment this to test block polling
// export const syncedRoundAtom = atom<Round | undefined>(4590102)
export const syncedRoundAtom = atom<Round | undefined>(undefined)

export const useSyncedRound = () => {
Expand Down
4 changes: 3 additions & 1 deletion src/features/common/components/badge.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/features/common/utils'
import { CircleDollarSign, SquareArrowRight, Bolt, Snowflake, ShieldCheck, Key, Parentheses } from 'lucide-react'
import { CircleDollarSign, SquareArrowRight, Bolt, Snowflake, ShieldCheck, Key, Parentheses, HeartPulse } from 'lucide-react'
import { TransactionType } from '@/features/transactions/models'

const badgeVariants = cva(
Expand All @@ -20,6 +20,7 @@ const badgeVariants = cva(
[TransactionType.AssetFreeze]: 'border-transparent bg-asset-freeze text-primary-foreground',
[TransactionType.StateProof]: 'border-transparent bg-state-proof text-primary-foreground',
[TransactionType.KeyReg]: 'border-transparent bg-key-registration text-primary-foreground',
[TransactionType.Heartbeat]: 'border-transparent bg-heartbeat text-primary-foreground',
},
},
defaultVariants: {
Expand All @@ -40,6 +41,7 @@ const transactionTypeBadgeIcon = new Map([
[TransactionType.AssetFreeze.toString(), <Snowflake className={iconClasses} />],
[TransactionType.StateProof.toString(), <ShieldCheck className={iconClasses} />],
[TransactionType.KeyReg.toString(), <Key className={iconClasses} />],
[TransactionType.Heartbeat.toString(), <HeartPulse className={iconClasses} />],
])

function Badge({ className, variant, children, ...props }: BadgeProps) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useMemo } from 'react'
import {
transactionFeeLabel,
transactionIdLabel,
transactionRekeyToLabel,
transactionTypeLabel,
} from '@/features/transactions/components/transaction-info'
import { asTransactionLinkTextComponent, TransactionLink } from '@/features/transactions/components/transaction-link'
import { transactionSenderLabel } from '@/features/transactions/components/labels'
import { AccountLink } from '@/features/accounts/components/account-link'
import { cn } from '@/features/common/utils'
import { DescriptionList } from '@/features/common/components/description-list'
import { TransactionTypeDescriptionDetails } from '@/features/transactions/components/transaction-type-description-details'
import { DisplayAlgo } from '@/features/common/components/display-algo'
import { HeartbeatTransaction } from '@/features/transactions/models'
import { heartbeatAddressLabel } from '@/features/transactions/components/heartbeat-transaction-info'

type Props = {
transaction: HeartbeatTransaction
isSimulated: boolean
}

// TODO: HB - Present the tooltip data we want to show

export function HeartbeatTransactionTooltipContent({ transaction, isSimulated }: Props) {
const items = useMemo(
() => [
{
dt: transactionIdLabel,
dd: isSimulated ? asTransactionLinkTextComponent(transaction.id, true) : <TransactionLink transactionId={transaction.id} />,
},
{
dt: transactionTypeLabel,
dd: <TransactionTypeDescriptionDetails transaction={transaction} />,
},
{
dt: transactionSenderLabel,
dd: <AccountLink address={transaction.sender} />,
},
{
dt: heartbeatAddressLabel,
dd: <AccountLink address={transaction.address} />,
},
{
dt: transactionFeeLabel,
dd: <DisplayAlgo amount={transaction.fee} />,
},
...(transaction.rekeyTo
? [
{
dt: transactionRekeyToLabel,
dd: <AccountLink address={transaction.rekeyTo} />,
},
]
: []),
],
[isSimulated, transaction]
)

return (
<div className={cn('p-4')}>
<DescriptionList items={items} />
</div>
)
}
6 changes: 6 additions & 0 deletions src/features/transactions-graph/components/horizontal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { StateProofTransactionTooltipContent } from './state-proof-transaction-t
import PointerRight from '@/features/common/components/svg/pointer-right'
import { SubHorizontalTitle } from '@/features/transactions-graph/components/sub-horizontal-title'
import { RenderAsyncAtom } from '@/features/common/components/render-async-atom'
import { HeartbeatTransactionTooltipContent } from './heartbeat-transaction-tooltip-content'

function ConnectionsFromAncestorsToAncestorsNextSiblings({ ancestors }: { ancestors: HorizontalModel[] }) {
return ancestors
Expand All @@ -42,6 +43,7 @@ const colorClassMap = {
[TransactionType.AssetFreeze]: { border: 'border-asset-freeze', text: 'text-asset-freeze' },
[TransactionType.KeyReg]: { border: 'border-key-registration', text: 'text-key-registration' },
[TransactionType.StateProof]: { border: 'border-state-proof', text: 'text-state-proof' },
[TransactionType.Heartbeat]: { border: 'border-heartbeat', text: 'text-heartbeat' },
}

function Circle({ className, text }: { className?: string; text?: string | number }) {
Expand Down Expand Up @@ -81,6 +83,7 @@ function VectorLabelText({ type }: { type: LabelType }) {
if (type === LabelType.KeyReg) return <span>Key Reg</span>
if (type === LabelType.StateProof) return <span>State Proof</span>
if (type === LabelType.Clawback) return <span>Clawback</span>
if (type === LabelType.Heartbeat) return <span>Heartbeat</span>
return undefined
}

Expand Down Expand Up @@ -370,6 +373,9 @@ export function Horizontal({ horizontal, verticals, bgClassName, isSimulated }:
{transaction.type === TransactionType.StateProof && (
<StateProofTransactionTooltipContent transaction={transaction} isSimulated={isSimulated} />
)}
{transaction.type === TransactionType.Heartbeat && (
<HeartbeatTransactionTooltipContent transaction={transaction} isSimulated={isSimulated} />
)}
</TooltipContent>
</Tooltip>
)
Expand Down
18 changes: 18 additions & 0 deletions src/features/transactions-graph/mappers/horizontals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AssetFreezeTransaction,
AssetTransferTransaction,
AssetTransferTransactionSubType,
HeartbeatTransaction,
InnerAppCallTransaction,
InnerAssetConfigTransaction,
InnerAssetFreezeTransaction,
Expand Down Expand Up @@ -90,6 +91,8 @@ const getTransactionRepresentations = (
return getPaymentTransactionRepresentations(transaction, verticals, parent)
case TransactionType.StateProof:
return getStateProofTransactionRepresentations(transaction, verticals)
case TransactionType.Heartbeat:
return getHeartbeatTransactionRepresentations(transaction, verticals)
}
}

Expand Down Expand Up @@ -299,6 +302,21 @@ const getStateProofTransactionRepresentations = (transaction: StateProofTransact
]
}

const getHeartbeatTransactionRepresentations = (transaction: HeartbeatTransaction, verticals: Vertical[]): Representation[] => {
const from = calculateFromWithoutParent(transaction.sender, verticals)

return [
{
fromVerticalIndex: from.verticalId,
fromAccountIndex: from.accountNumber,
type: RepresentationType.Point,
label: {
type: LabelType.Heartbeat,
},
},
]
}

const asTransactionGraphRepresentation = (from: RepresentationFromTo, to: RepresentationFromTo, description: Label): Representation => {
if (from.verticalId === to.verticalId) {
return {
Expand Down
2 changes: 2 additions & 0 deletions src/features/transactions-graph/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export enum LabelType {
KeyReg = 'KeyReg',
StateProof = 'StateProof',
Clawback = 'Clawback',
Heartbeat = 'Heartbeat',
}

export type Label =
Expand Down Expand Up @@ -73,6 +74,7 @@ export type Label =
| { type: LabelType.AssetFreeze }
| { type: LabelType.KeyReg }
| { type: LabelType.StateProof }
| { type: LabelType.Heartbeat }

export type Vector = {
type: RepresentationType.Vector
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Card, CardContent } from '@/features/common/components/card'
import { cn } from '@/features/common/utils'
import { TransactionNote } from './transaction-note'
import { HeartbeatTransaction, SignatureType } from '../models'
import { MultisigDetails } from './multisig-details'
import { LogicsigDetails } from './logicsig-details'
import { TransactionViewTabs } from './transaction-view-tabs'
import { HeartbeatTransactionInfo } from './heartbeat-transaction-info'

type Props = {
transaction: HeartbeatTransaction
}

export function HeartbeatTransactionDetails({ transaction }: Props) {
return (
<Card>
<CardContent className={cn('space-y-4')}>
<HeartbeatTransactionInfo transaction={transaction} />
<TransactionViewTabs transaction={transaction} />
{transaction.note && <TransactionNote note={transaction.note} />}
{transaction.signature?.type === SignatureType.Multi && <MultisigDetails signature={transaction.signature} />}
{transaction.signature?.type === SignatureType.Logic && <LogicsigDetails signature={transaction.signature} />}
</CardContent>
</Card>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { cn } from '@/features/common/utils'
import { useMemo } from 'react'
import { HeartbeatTransaction } from '../models'
import { DescriptionList } from '@/features/common/components/description-list'
import { AccountLink } from '@/features/accounts/components/account-link'
import { transactionSenderLabel } from './labels'

type Props = {
transaction: HeartbeatTransaction
}

export const heartbeatAddressLabel = 'Address'

// TODO: HB - Present the data we want to show

export function HeartbeatTransactionInfo({ transaction }: Props) {
const items = useMemo(
() => [
{
dt: transactionSenderLabel,
dd: <AccountLink address={transaction.sender} showCopyButton={true} />,
},
{
dt: heartbeatAddressLabel,
dd: <AccountLink address={transaction.address} showCopyButton={true} />,
},
],
[transaction.address, transaction.sender]
)

return (
<div className={cn('space-y-1')}>
<h2>Heartbeat</h2>
<DescriptionList items={items} />
</div>
)
}
3 changes: 3 additions & 0 deletions src/features/transactions/components/transaction-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AssetFreezeTransactionDetails } from './asset-freeze-transaction-detail
import { StateProofTransactionDetails } from './state-proof-transaction-details'
import { KeyRegTransactionDetails } from './key-reg-transaction-details'
import { TransactionInfo } from './transaction-info'
import { HeartbeatTransactionDetails } from './heartbeat-transaction-details'

type Props = {
transaction: Transaction | InnerTransaction
Expand All @@ -30,6 +31,8 @@ export function TransactionDetails({ transaction }: Props) {
<StateProofTransactionDetails transaction={transaction} />
) : transaction.type === TransactionType.KeyReg ? (
<KeyRegTransactionDetails transaction={transaction} />
) : transaction.type === TransactionType.Heartbeat ? (
<HeartbeatTransactionDetails transaction={transaction} />
) : undefined}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { asInnerAssetConfigTransaction } from './asset-config-transaction-mapper
import { asInnerAssetFreezeTransaction } from './asset-freeze-transaction-mappers'
import { asInnerKeyRegTransaction } from './key-reg-transaction-mappers'
import { AsyncMaybeAtom } from '@/features/common/data/types'
import { asInnerStateProofTransaction } from './state-proof-transaction-mappers'
import { Atom } from 'jotai/index'
import { GroupId, GroupResult } from '@/features/groups/data/types'
import { Round } from '@/features/blocks/data/types'
Expand Down Expand Up @@ -181,10 +180,6 @@ const asInnerTransaction = (
if (transactionResult['tx-type'] === AlgoSdkTransactionType.keyreg) {
return asInnerKeyRegTransaction(networkTransactionId, index, transactionResult)
}
// I don't believe it's possible to have an inner stpf transaction, handling just in case.
if (transactionResult['tx-type'] === AlgoSdkTransactionType.stpf) {
return asInnerStateProofTransaction(networkTransactionId, index, transactionResult)
}

throw new Error(`Unsupported inner transaction type: ${transactionResult['tx-type']}`)
}
18 changes: 18 additions & 0 deletions src/features/transactions/mappers/heartbeat-transaction-mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { HeartbeatTransaction, TransactionType } from '../models'
import { mapCommonTransactionProperties } from './transaction-common-properties-mappers'
import { invariant } from '@/utils/invariant'

export const asHeartbeatTransaction = (transactionResult: TransactionResult): HeartbeatTransaction => {
invariant(transactionResult['heartbeat-transaction'], 'heartbeat-transaction is not set')
const heartbeat = transactionResult['heartbeat-transaction']

return {
id: transactionResult.id,
type: TransactionType.Heartbeat,
subType: undefined,
address: heartbeat['hb-address'],
// TODO: HB - Map only the data we want to show
...mapCommonTransactionProperties(transactionResult),
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { InnerStateProofTransaction, StateProofTransaction, TransactionType } from '../models'
import { asInnerTransactionId, mapCommonTransactionProperties } from './transaction-common-properties-mappers'
import { StateProofTransaction, TransactionType } from '../models'
import { mapCommonTransactionProperties } from './transaction-common-properties-mappers'

export const asStateProofTransaction = (transactionResult: TransactionResult): StateProofTransaction => {
return {
Expand All @@ -10,15 +10,3 @@ export const asStateProofTransaction = (transactionResult: TransactionResult): S
...mapCommonTransactionProperties(transactionResult),
}
}

export const asInnerStateProofTransaction = (
networkTransactionId: string,
index: string,
transactionResult: TransactionResult
): InnerStateProofTransaction => {
const { id: _id, ...rest } = asStateProofTransaction(transactionResult)
return {
...asInnerTransactionId(networkTransactionId, index),
...rest,
}
}
4 changes: 4 additions & 0 deletions src/features/transactions/mappers/transaction-mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { GroupId, GroupResult } from '@/features/groups/data/types'
import { Round } from '@/features/blocks/data/types'
import { getGroupResultAtom } from '@/features/groups/data/group-result'
import { DecodedAbiMethod } from '@/features/abi-methods/models'
import { asHeartbeatTransaction } from './heartbeat-transaction-mappers'

export const asTransaction = (
transactionResult: TransactionResult,
Expand Down Expand Up @@ -45,6 +46,9 @@ export const asTransaction = (
case algosdk.TransactionType.keyreg: {
return asKeyRegTransaction(transactionResult)
}
case algosdk.TransactionType.hb: {
return asHeartbeatTransaction(transactionResult)
}
default:
throw new Error(`Unknown transaction type ${transactionResult['tx-type']}`)
}
Expand Down
Loading
Loading