Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0ef5e07
chore(merge-main): merge latest from main with tailwind v4 to optiona…
p2arthur Nov 4, 2025
2630b66
feat(optional_sender): Add optional sender at latest squashed to solv…
p2arthur Nov 7, 2025
d1951ef
chore(package-lock): update package lock to reflect dependencies from…
p2arthur Nov 7, 2025
fe99a15
feat(search_params) add optional sender with auto populate to search …
p2arthur Nov 10, 2025
9b185fa
test(optional_sender): add optional sender to url params test
p2arthur Nov 11, 2025
184dcb6
chore: add DataProvider to txn wizard test
PatrickDinh Nov 13, 2025
589b1a7
chore: lint-fix
PatrickDinh Nov 13, 2025
e0a93ed
Merge pull request #522 from algorandfoundation/fix-test
p2arthur Nov 13, 2025
da58f63
feat(transaction-wizard): auto-populate sender from localnet dispense…
p2arthur Nov 13, 2025
1715245
fix(search_params): fix two tests that were failing only on CI/CD
p2arthur Nov 13, 2025
80d3136
fix(test): add racing condition to wait asset-opt out render before t…
p2arthur Nov 13, 2025
4733d65
fix(test): add racing condition to wait render before testing
p2arthur Nov 13, 2025
629e5d2
fix(test): add racing condition to wait render before testing
p2arthur Nov 13, 2025
d94d386
reafactor(non-null): remove non-null assertion from the transaction m…
p2arthur Nov 14, 2025
a0dd3d3
PR feedback
PatrickDinh Nov 14, 2025
a9d770b
test(optional_sender): updated tests to use kmd instead of assuming t…
p2arthur Nov 16, 2025
59aca01
fix(audit): fix audit warning blocking ci
p2arthur Nov 16, 2025
235900a
chore(optional_sender): cleanup to remove repeated tests and optional…
p2arthur Nov 16, 2025
9bd27ba
Merge branch 'feat/optional-sender-improved' into pr-feedback
p2arthur Nov 18, 2025
b26d3a5
Destructure loading from useTransactionSearchParamsBuilder
p2arthur Nov 18, 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
6 changes: 3 additions & 3 deletions package-lock.json

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

21 changes: 21 additions & 0 deletions src/features/accounts/components/transaction-sender-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AddressOrNfdLink, AddressOrNfdLinkProps } from './address-or-nfd-link'
import { cn } from '@/features/common/utils'

export type Props = AddressOrNfdLinkProps & { autoPopulated?: boolean }

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} />

{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>
)
}
4 changes: 2 additions & 2 deletions src/features/forms/components/address-form-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ 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 value = watch(field) as string
const resolvedAddress = watch(resolvedAddressField) as string

const setAddress = useCallback((address: string) => setValue(resolvedAddressField, address), [resolvedAddressField, setValue])
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { numberSchema } from '@/features/forms/data/common'
import { addressFieldSchema, commonSchema, optionalAddressFieldSchema, senderFieldSchema } from '../data/common'
import { addressFieldSchema, commonSchema, optionalAddressFieldSchema } 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 +17,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 +26,7 @@ const closeRemainderToLabel = 'Close remainder to'
const formSchema = z
.object({
...commonSchema,
...senderFieldSchema,
sender: optionalAddressFieldSchema,
closeRemainderTo: addressFieldSchema,
receiver: optionalAddressFieldSchema,
amount: numberSchema(z.number({ required_error: 'Required', invalid_type_error: 'Required' }).min(0).optional()),
Expand Down Expand Up @@ -63,7 +64,7 @@ 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),
amount: data.amount,
Expand All @@ -77,7 +78,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: transaction.sender?.autoPopulated ? undefined : transaction.sender,
closeRemainderTo: transaction.closeRemainderTo,
receiver: transaction.receiver,
amount: transaction.amount,
Expand Down Expand Up @@ -114,7 +115,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,11 @@
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,
optionalAddressFieldSchema,
} 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 @@ -16,10 +21,11 @@ import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } 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,
sender: optionalAddressFieldSchema,
...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 +53,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 +69,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: transaction.sender?.autoPopulated ? undefined : transaction.sender,
onComplete: transaction.onComplete.toString(),
extraProgramPages: transaction.extraProgramPages,
fee: transaction.fee,
Expand Down Expand Up @@ -117,7 +123,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,
optionalAddressFieldSchema,
} from '@/features/transaction-wizard/data/common'
import { z } from 'zod'
import { zfd } from 'zod-form-data'
Expand All @@ -21,10 +21,11 @@ import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } 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,
sender: optionalAddressFieldSchema,
...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: transaction.sender?.autoPopulated ? undefined : 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, optionalAddressFieldSchema } 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 @@ -15,10 +15,11 @@ import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } 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,
sender: optionalAddressFieldSchema,
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: transaction.sender?.autoPopulated ? undefined : 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, optionalAddressFieldSchema, receiverFieldSchema } from '../data/common'
import { z } from 'zod'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { zfd } from 'zod-form-data'
Expand All @@ -22,13 +22,14 @@ 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 resolveSenderAddress from '../utils/resolve-sender-address'

const clawbackTargetLabel = 'Clawback target'

export const assetClawbackFormSchema = z
.object({
...commonSchema,
...senderFieldSchema,
sender: optionalAddressFieldSchema,
...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: transaction.sender?.autoPopulated ? undefined : transaction.sender,
receiver: transaction.receiver,
clawbackTarget: transaction.clawbackTarget,
amount: transaction.amount,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bigIntSchema, numberSchema } from '@/features/forms/data/common'
import { commonSchema, optionalAddressFieldSchema, senderFieldSchema } from '../data/common'
import { commonSchema, optionalAddressFieldSchema } from '../data/common'
import { z } from 'zod'
import { useCallback, useMemo } from 'react'
import { zfd } from 'zod-form-data'
Expand All @@ -17,10 +17,11 @@ import { TransactionBuilderMode } from '../data'
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'

export const assetCreateFormSchema = z.object({
...commonSchema,
...senderFieldSchema,
sender: optionalAddressFieldSchema,
total: bigIntSchema(z.bigint({ required_error: 'Required', invalid_type_error: 'Required' }).gt(BigInt(0), 'Must be greater than 0')),
decimals: numberSchema(z.number({ required_error: 'Required', invalid_type_error: 'Required' }).min(0).max(19)),
assetName: zfd.text(z.string().optional()),
Expand Down Expand Up @@ -66,7 +67,7 @@ function FormFields({ helper }: FormFieldsProps) {
{helper.addressField({
field: 'sender',
label: 'Creator',
helpText: 'Account that creates the asset. Sends the transaction and pays the fee',
helpText: 'Account that creates the asset. Sends the transaction and pays the fee - optional for simulating',
placeholder: ZERO_ADDRESS,
})}
{helper.addressField({
Expand Down Expand Up @@ -133,7 +134,7 @@ export function AssetCreateTransactionBuilder({ mode, transaction, activeAccount
unitName: data.unitName,
total: data.total,
decimals: data.decimals,
sender: data.sender,
sender: await resolveSenderAddress(data.sender),
manager: asOptionalAddressOrNfd(data.manager),
reserve: asOptionalAddressOrNfd(data.reserve),
freeze: asOptionalAddressOrNfd(data.freeze),
Expand All @@ -155,7 +156,7 @@ export function AssetCreateTransactionBuilder({ mode, transaction, activeAccount
unitName: transaction.unitName,
total: transaction.total,
decimals: transaction.decimals,
sender: transaction.sender,
sender: transaction.sender?.autoPopulated ? undefined : transaction.sender,
manager: transaction.manager,
reserve: transaction.reserve,
freeze: transaction.freeze,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bigIntSchema } from '@/features/forms/data/common'
import { commonSchema, senderFieldSchema } from '../data/common'
import { commonSchema, optionalAddressFieldSchema } from '../data/common'
import { z } from 'zod'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { zfd } from 'zod-form-data'
Expand All @@ -25,10 +25,11 @@ import { cn } from '@/features/common/utils'
import { TransactionBuilderMode } from '../data'
import { TransactionBuilderNoteField } from './transaction-builder-note-field'
import { asAddressOrNfd } from '../mappers/as-address-or-nfd'
import resolveSenderAddress from '../utils/resolve-sender-address'

export const assetDestroyFormSchema = z.object({
...commonSchema,
...senderFieldSchema,
sender: optionalAddressFieldSchema,
asset: z
.object({
id: bigIntSchema(z.bigint({ required_error: 'Required', invalid_type_error: 'Required' }).min(1n)),
Expand Down Expand Up @@ -81,7 +82,7 @@ function FormFields({ helper, asset }: FormFieldsProps) {
{helper.addressField({
field: 'sender',
label: 'Sender',
helpText: 'The current asset manager address. Sends the transaction and pays the fee',
helpText: 'The current asset manager address. Sends the transaction and pays the fee - optional for simulating',
placeholder: ZERO_ADDRESS,
})}
<TransactionBuilderFeeField />
Expand Down Expand Up @@ -165,7 +166,7 @@ export function AssetDestroyTransactionBuilder({ mode, transaction, onSubmit, on
id: transaction?.id ?? randomGuid(),
type: BuildableTransactionType.AssetDestroy,
asset: data.asset,
sender: data.sender,
sender: await resolveSenderAddress(data.sender),
fee: data.fee,
validRounds: data.validRounds,
note: data.note,
Expand All @@ -177,7 +178,7 @@ export function AssetDestroyTransactionBuilder({ mode, transaction, onSubmit, on
if (mode === TransactionBuilderMode.Edit && transaction) {
return {
asset: transaction.asset,
sender: transaction.sender,
sender: transaction.sender?.autoPopulated ? undefined : transaction.sender,
fee: transaction.fee,
validRounds: transaction.validRounds,
note: transaction.note,
Expand Down
Loading
Loading