Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c92354e
wip - making sender optional
PatrickDinh Jul 28, 2025
80ec848
Merge branch 'main' of https://github.com/algorandfoundation/algokit-…
p2arthur Aug 30, 2025
2c8d8fb
feat(optional_sender): implement basic working version of optional se…
p2arthur Aug 31, 2025
9130179
add(optional_sender) implement the use of dispenser address for empty…
p2arthur Aug 31, 2025
5b5ad14
refactor(optional_sender) refactor optional sender to working version…
p2arthur Sep 1, 2025
13011ab
feat(optional_sender): implement optional sender to 8 different trans…
p2arthur Sep 3, 2025
d4c3f71
fix(lint): fix lint errors and warning
p2arthur Sep 4, 2025
681f3bd
feat(optional_sender): implement optional sender in 4 more transactio…
p2arthur Sep 5, 2025
2eb565d
feat(optional): add optional sender support to asset reconfigure tran…
p2arthur Sep 5, 2025
35f8ec5
feat(optional_sender) finish implement optional sender to all transac…
p2arthur Sep 11, 2025
78fb4a5
test(optionalSender) add test to confirm that optional sender works
p2arthur Sep 12, 2025
6f0be92
test(optionalSender) remove expect cases where sender was required
p2arthur Sep 12, 2025
7a29bab
fix(optionalSender): fix tooltip not showing when hovering optional s…
p2arthur Sep 12, 2025
60930a8
fix(testnet_address): add testnet fee sink address to be used as defa…
p2arthur Sep 15, 2025
39ed51a
refactor(sender_address): refactor defineSender to be network aware
p2arthur Sep 15, 2025
6b170e0
fix(lint) fix linting errors
p2arthur Sep 15, 2025
3a0c3b7
refactor(reviews) updat code to follow PR reviews for better implemen…
p2arthur Sep 17, 2025
09acee5
fix(build): fix build errors
p2arthur Sep 18, 2025
c715c55
fix(type): implement transaction sender type
p2arthur Sep 22, 2025
829589b
fix(update_sender) update auto populated on updating transaction
p2arthur Sep 25, 2025
1c9d7e5
fix(update_op_sender) fix optional sender for editing transactions
p2arthur Sep 26, 2025
0c29eaf
refactor(helpers) update helper text to mention optional sender for s…
p2arthur Sep 26, 2025
bbee3ec
fix(build) fix build errors
p2arthur Sep 26, 2025
920f098
fix(conflict) fix conflict with latest from main
p2arthur Sep 26, 2025
80de8e7
fix(lint) fix lint errors
p2arthur Sep 26, 2025
0d069a1
fix(update-transaction): fix auto populate editing transactions in th…
p2arthur Sep 28, 2025
1d255a3
fix(optional-sender): fix optional address or nfd mapper to work with…
p2arthur Sep 29, 2025
d69ea30
refactor(resolve_sender): refactor resolve sender address to follow b…
p2arthur Oct 1, 2025
877dcd3
test(optional_sender): add more tests to check optional sender in the…
p2arthur Oct 2, 2025
f8f5b31
refactor(asTransactionSender): refactor asTransactionSenderHelper and…
p2arthur Oct 3, 2025
206971b
Update src/features/transaction-wizard/transaction-wizard-page.test.tsx
p2arthur Oct 3, 2025
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
1 change: 1 addition & 0 deletions src/features/accounts/components/address-or-nfd-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const AddressOrNfdLink = fixedForwardRef(
) : (
<div className="flex items-center overflow-hidden">
{link}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: no need this newline

{showCopyButton && <CopyButton value={address} />}
{showQRButton && <OpenAddressQRDialogButton address={address} />}
</div>
Expand Down
46 changes: 46 additions & 0 deletions src/features/accounts/components/transaction-sender-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Nfd } from '@/features/nfd/data/types'

import { PropsWithChildren } from 'react'
import { AddressOrNfdLink } from './address-or-nfd-link'
import { Address } from '../data/types'
import { cn } from '@/features/common/utils'

export type AddressOrNfdLinkProps = PropsWithChildren<{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this type to avoid conflicts with address-or-ndf-link.tsx. If it isn't used anywhere else, I'd just call it Props

Suggested change
export type AddressOrNfdLinkProps = PropsWithChildren<{
type Props = PropsWithChildren<{

address: Address
short?: boolean
className?: string
showCopyButton?: boolean
showQRButton?: boolean
nfd?: Nfd
autoPopulated?: boolean
}>

export default function TransactionSenderLink({
address,
short,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this empty line

showCopyButton,
showQRButton,
nfd,
autoPopulated,
}: AddressOrNfdLinkProps) {
return (
<div className="flex items-center">
<AddressOrNfdLink
address={address}
short={short}
className={cn(autoPopulated && 'text-yellow-500')}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the className prop needs to be passed in here too, I think this would become something like

className={cn(className, autoPopulated && 'text-yellow-500')}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can rewrite this bit to

export default function TransactionSenderLink(props: Props) {
  const { autoPopulated, className, ...rest } = props

  return (
    <div className="flex items-center">
      <AddressOrNfdLink className={cn(className, autoPopulated && 'text-yellow-500')} {...rest} />
      ...

showCopyButton={showCopyButton}
showQRButton={showQRButton}
nfd={nfd}
/>

{autoPopulated && (
<span className="group ml-1 cursor-help text-yellow-500">
<span>?</span>
<div className="absolute z-10 hidden rounded-sm border-2 border-gray-300/20 p-1 group-hover:block">auto populated</div>
</span>
)}
</div>
)
}
14 changes: 10 additions & 4 deletions src/features/forms/components/address-form-item.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FieldPath, useFormContext } from 'react-hook-form'
import { FormItemProps } from '@/features/forms/components/form-item'
import { TextFormItem } from './text-form-item'
import { addressFieldSchema, optionalAddressFieldSchema } from '@/features/transaction-wizard/data/common'
import { addressFieldSchema, optionalAddressFieldSchema, optionalSenderFieldShape } from '@/features/transaction-wizard/data/common'
import { useDebounce } from 'use-debounce'
import { isNfd, useLoadableForwardLookupNfdResult } from '@/features/nfd/data'
import { useCallback, useEffect } from 'react'
Expand All @@ -11,6 +11,7 @@ import { z } from 'zod'

export type AddressOrNfdFieldSchema = z.infer<typeof addressFieldSchema>
export type OptionalAddressOrNfdFieldSchema = z.infer<typeof optionalAddressFieldSchema>
export type OptionalSenderFieldSchema = z.infer<typeof optionalSenderFieldShape.sender>

export interface AddressFieldProps<TSchema extends Record<string, unknown> = Record<string, unknown>>
extends Omit<FormItemProps<TSchema>, 'children'> {
Expand Down Expand Up @@ -41,9 +42,14 @@ function ResolveNfdAddress({ nfd, onNfdResolved }: ResolveNfdAddressProps) {
}

export function AddressFormItem({ field, resolvedAddressField, label, ...props }: AddressFormItemProps) {
const { watch, setValue } = useFormContext<AddressOrNfdFieldSchema | OptionalAddressOrNfdFieldSchema>()
const value = watch(field)
const resolvedAddress = watch(resolvedAddressField)
const { watch, setValue } = useFormContext<OptionalAddressOrNfdFieldSchema>()
const rawValue = watch(field)

//type guard
const value = typeof rawValue === 'string' ? rawValue : ''

const rawResolved = watch(resolvedAddressField)
const resolvedAddress = typeof rawResolved === 'string' ? rawResolved : ''

const setAddress = useCallback((address: string) => setValue(resolvedAddressField, address), [resolvedAddressField, setValue])
useEffect(() => {
Expand Down
1 change: 0 additions & 1 deletion src/features/network/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const allWalletProviderNames: Record<WalletId, string> = {
pera: 'Pera',
exodus: 'Exodus',
lute: 'Lute',
// The below providers aren't used
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still keep this comment

custom: 'Custom',
kibisis: 'Kibisis',
walletconnect: 'Wallet Connect',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { numberSchema } from '@/features/forms/data/common'
import { addressFieldSchema, commonSchema, optionalAddressFieldSchema, senderFieldSchema } from '../data/common'
import {
addressFieldSchema,
commonSchema,
optionalAddressFieldSchema,
optionalSenderFieldShape,
} from '@/features/transaction-wizard/data/common'
import { z } from 'zod'
import { useCallback, useMemo } from 'react'
import { zfd } from 'zod-form-data'
Expand All @@ -17,6 +22,7 @@ import SvgAlgorand from '@/features/common/components/icons/algorand'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd, asOptionalAddressOrNfd } from '../mappers/as-address-or-nfd'
import { ActiveWalletAccount } from '@/features/wallet/types/active-wallet'
import resolveSenderAddress from '../utils/resolve-sender-address'

const senderLabel = 'Sender'
const receiverLabel = 'Receiver'
Expand All @@ -25,7 +31,7 @@ const closeRemainderToLabel = 'Close remainder to'
const formSchema = z
.object({
...commonSchema,
...senderFieldSchema,
...optionalSenderFieldShape,
closeRemainderTo: addressFieldSchema,
receiver: optionalAddressFieldSchema,
amount: numberSchema(z.number({ required_error: 'Required', invalid_type_error: 'Required' }).min(0).optional()),
Expand Down Expand Up @@ -63,9 +69,9 @@ export function AccountCloseTransactionBuilder({ mode, transaction, activeAccoun
onSubmit({
id: transaction?.id ?? randomGuid(),
type: BuildableTransactionType.AccountClose,
sender: data.sender,
sender: await resolveSenderAddress(data.sender),
closeRemainderTo: data.closeRemainderTo,
receiver: asOptionalAddressOrNfd(data.receiver),
receiver: { value: data.receiver.value!, resolvedAddress: data.receiver.resolvedAddress! },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why the logic for receiver needed to be updated, maybe it was related to the asOptionalAddressOrNfd function I commented on earlier, you should revert this.

amount: data.amount,
fee: data.fee,
validRounds: data.validRounds,
Expand All @@ -77,7 +83,7 @@ export function AccountCloseTransactionBuilder({ mode, transaction, activeAccoun
const defaultValues = useMemo<Partial<z.infer<typeof formData>>>(() => {
if (mode === TransactionBuilderMode.Edit && transaction) {
return {
sender: transaction.sender,
sender: asOptionalAddressOrNfd(transaction.sender),
closeRemainderTo: transaction.closeRemainderTo,
receiver: transaction.receiver,
amount: transaction.amount,
Expand Down Expand Up @@ -114,7 +120,7 @@ export function AccountCloseTransactionBuilder({ mode, transaction, activeAccoun
{helper.addressField({
field: 'sender',
label: senderLabel,
helpText: 'Account to be closed. Sends the transaction and pays the fee',
helpText: 'Account to be closed. Sends the transaction and pays the fee - optional for simulating ',
placeholder: ZERO_ADDRESS,
})}
{helper.addressField({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import algosdk from 'algosdk'
import { bigIntSchema, numberSchema } from '@/features/forms/data/common'
import { senderFieldSchema, commonSchema, onCompleteFieldSchema, onCompleteOptions } from '@/features/transaction-wizard/data/common'
import { commonSchema, onCompleteFieldSchema, onCompleteOptions, optionalSenderFieldShape } from '@/features/transaction-wizard/data/common'
import { z } from 'zod'
import { zfd } from 'zod-form-data'
import { Form } from '@/features/forms/components/form'
Expand All @@ -14,12 +14,13 @@ import { BuildAppCallTransactionResult, BuildableTransactionType } from '../mode
import { randomGuid } from '@/utils/random-guid'
import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } from '../mappers/as-address-or-nfd'
import { asAddressOrNfd, asOptionalAddressOrNfd } from '../mappers/as-address-or-nfd'
import { ActiveWalletAccount } from '@/features/wallet/types/active-wallet'
import resolveSenderAddress from '../utils/resolve-sender-address'

const formData = zfd.formData({
...commonSchema,
...senderFieldSchema,
...optionalSenderFieldShape,
...onCompleteFieldSchema,
applicationId: bigIntSchema(z.bigint({ required_error: 'Required', invalid_type_error: 'Required' })),
extraProgramPages: numberSchema(z.number().min(0).max(3).optional()),
Expand Down Expand Up @@ -47,7 +48,7 @@ export function AppCallTransactionBuilder({ mode, transaction, activeAccount, de
id: transaction?.id ?? randomGuid(),
type: BuildableTransactionType.AppCall,
applicationId: BigInt(values.applicationId),
sender: values.sender,
sender: await resolveSenderAddress(values.sender!),
onComplete: Number(values.onComplete),
extraProgramPages: values.extraProgramPages,
fee: values.fee,
Expand All @@ -63,7 +64,7 @@ export function AppCallTransactionBuilder({ mode, transaction, activeAccount, de
if (mode === TransactionBuilderMode.Edit && transaction) {
return {
applicationId: transaction.applicationId !== undefined ? BigInt(transaction.applicationId) : undefined,
sender: transaction.sender,
sender: asOptionalAddressOrNfd(transaction.sender),
onComplete: transaction.onComplete.toString(),
extraProgramPages: transaction.extraProgramPages,
fee: transaction.fee,
Expand Down Expand Up @@ -117,7 +118,7 @@ export function AppCallTransactionBuilder({ mode, transaction, activeAccount, de
{helper.addressField({
field: 'sender',
label: 'Sender',
helpText: 'Account to call from. Sends the transaction and pays the fee',
helpText: 'Account to call from. Sends the transaction and pays the fee - optional for simulating',
})}
{defaultValues.applicationId === 0n &&
helper.numberField({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import algosdk from 'algosdk'
import { numberSchema } from '@/features/forms/data/common'
import {
senderFieldSchema,
commonSchema,
onCompleteOptionsForAppCreate,
onCompleteForAppCreateFieldSchema,
optionalSenderFieldShape,
} from '@/features/transaction-wizard/data/common'
import { z } from 'zod'
import { zfd } from 'zod-form-data'
Expand All @@ -19,12 +19,13 @@ import { BuildApplicationCreateTransactionResult, BuildableTransactionType } fro
import { randomGuid } from '@/utils/random-guid'
import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } from '../mappers/as-address-or-nfd'
import { asAddressOrNfd, asOptionalAddressOrNfd } from '../mappers/as-address-or-nfd'
import { ActiveWalletAccount } from '@/features/wallet/types/active-wallet'
import resolveSenderAddress from '../utils/resolve-sender-address'

const formData = zfd.formData({
...commonSchema,
...senderFieldSchema,
...optionalSenderFieldShape,
...onCompleteForAppCreateFieldSchema,
approvalProgram: zfd.text(z.string({ required_error: 'Required', invalid_type_error: 'Required' })),
clearStateProgram: zfd.text(z.string({ required_error: 'Required', invalid_type_error: 'Required' })),
Expand Down Expand Up @@ -57,7 +58,7 @@ export function ApplicationCreateTransactionBuilder({ mode, transaction, activeA
type: BuildableTransactionType.ApplicationCreate,
approvalProgram: values.approvalProgram,
clearStateProgram: values.clearStateProgram,
sender: values.sender,
sender: await resolveSenderAddress(values.sender!),
onComplete: Number(values.onComplete),
extraProgramPages: values.extraProgramPages,
globalInts: values.globalInts,
Expand All @@ -78,7 +79,7 @@ export function ApplicationCreateTransactionBuilder({ mode, transaction, activeA
return {
approvalProgram: transaction.approvalProgram,
clearStateProgram: transaction.clearStateProgram,
sender: transaction.sender,
sender: asOptionalAddressOrNfd(transaction.sender),
onComplete: transaction.onComplete.toString(),
extraProgramPages: transaction.extraProgramPages,
globalInts: transaction.globalInts,
Expand Down Expand Up @@ -140,7 +141,7 @@ export function ApplicationCreateTransactionBuilder({ mode, transaction, activeA
{helper.addressField({
field: 'sender',
label: 'Sender',
helpText: 'Account to create from. Sends the transaction and pays the fee',
helpText: 'Account to create from. Sends the transaction and pays the fee - optional for simulating',
})}
{helper.numberField({
field: 'globalInts',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bigIntSchema } from '@/features/forms/data/common'
import { senderFieldSchema, commonSchema } from '@/features/transaction-wizard/data/common'
import { commonSchema, optionalSenderFieldShape } from '@/features/transaction-wizard/data/common'
import { z } from 'zod'
import { zfd } from 'zod-form-data'
import { Form } from '@/features/forms/components/form'
Expand All @@ -13,12 +13,13 @@ import { BuildApplicationUpdateTransactionResult, BuildableTransactionType } fro
import { randomGuid } from '@/utils/random-guid'
import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } from '../mappers/as-address-or-nfd'
import { asAddressOrNfd, asOptionalAddressOrNfd } from '../mappers/as-address-or-nfd'
import { ActiveWalletAccount } from '@/features/wallet/types/active-wallet'
import resolveSenderAddress from '../utils/resolve-sender-address'

const formData = zfd.formData({
...commonSchema,
...senderFieldSchema,
...optionalSenderFieldShape,
applicationId: bigIntSchema(z.bigint({ required_error: 'Required', invalid_type_error: 'Required' })),
approvalProgram: zfd.text(z.string({ required_error: 'Required', invalid_type_error: 'Required' })),
clearStateProgram: zfd.text(z.string({ required_error: 'Required', invalid_type_error: 'Required' })),
Expand Down Expand Up @@ -47,7 +48,7 @@ export function ApplicationUpdateTransactionBuilder({ mode, transaction, activeA
applicationId: BigInt(values.applicationId),
approvalProgram: values.approvalProgram,
clearStateProgram: values.clearStateProgram,
sender: values.sender,
sender: await resolveSenderAddress(values.sender),
fee: values.fee,
validRounds: values.validRounds,
args: values.args.map((arg) => arg.value),
Expand All @@ -63,7 +64,7 @@ export function ApplicationUpdateTransactionBuilder({ mode, transaction, activeA
applicationId: transaction.applicationId !== undefined ? BigInt(transaction.applicationId) : undefined,
approvalProgram: transaction.approvalProgram,
clearStateProgram: transaction.clearStateProgram,
sender: transaction.sender,
sender: asOptionalAddressOrNfd(transaction.sender),
fee: transaction.fee,
validRounds: transaction.validRounds,
note: transaction.note,
Expand Down Expand Up @@ -116,7 +117,7 @@ export function ApplicationUpdateTransactionBuilder({ mode, transaction, activeA
{helper.addressField({
field: 'sender',
label: 'Sender',
helpText: 'Account to update from. Sends the transaction and pays the fee',
helpText: 'Account to update from. Sends the transaction and pays the fee - optional for simulating',
})}
{helper.arrayField({
field: 'args',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bigIntSchema, decimalSchema } from '@/features/forms/data/common'
import { addressFieldSchema, commonSchema, receiverFieldSchema, senderFieldSchema } from '../data/common'
import { addressFieldSchema, commonSchema, optionalSenderFieldShape, receiverFieldSchema } from '../data/common'
import { z } from 'zod'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { zfd } from 'zod-form-data'
Expand All @@ -21,14 +21,15 @@ import { ZERO_ADDRESS } from '@/features/common/constants'
import { useDebounce } from 'use-debounce'
import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } from '../mappers/as-address-or-nfd'
import { asAddressOrNfd, asOptionalAddressOrNfd } from '../mappers/as-address-or-nfd'
import resolveSenderAddress from '../utils/resolve-sender-address'

const clawbackTargetLabel = 'Clawback target'

export const assetClawbackFormSchema = z
.object({
...commonSchema,
...senderFieldSchema,
...optionalSenderFieldShape,
...receiverFieldSchema,
clawbackTarget: addressFieldSchema,
asset: z
Expand Down Expand Up @@ -83,7 +84,7 @@ function FormFields({ helper, asset }: FormFieldsProps) {
{helper.addressField({
field: 'sender',
label: 'Sender',
helpText: 'The clawback account of the asset. Sends the transaction and pays the fee',
helpText: 'The clawback account of the asset. Sends the transaction and pays the fee - optional for simulating',
placeholder: ZERO_ADDRESS,
})}
{helper.addressField({
Expand Down Expand Up @@ -186,7 +187,7 @@ export function AssetClawbackTransactionBuilder({ mode, transaction, onSubmit, o
id: transaction?.id ?? randomGuid(),
type: BuildableTransactionType.AssetClawback,
asset: data.asset,
sender: data.sender,
sender: await resolveSenderAddress(data.sender),
receiver: data.receiver,
clawbackTarget: data.clawbackTarget,
amount: data.amount!,
Expand All @@ -201,7 +202,7 @@ export function AssetClawbackTransactionBuilder({ mode, transaction, onSubmit, o
if (mode === TransactionBuilderMode.Edit && transaction) {
return {
asset: transaction.asset,
sender: transaction.sender,
sender: asOptionalAddressOrNfd(transaction.sender),
receiver: transaction.receiver,
clawbackTarget: transaction.clawbackTarget,
amount: transaction.amount,
Expand Down
Loading