Skip to content

Conversation

@p2arthur
Copy link
Contributor

@p2arthur p2arthur commented Nov 7, 2025

feat(optional sender) - squashed

This PR replaces the previous one that contained unsigned commits.
I’ve taken the latest version from that branch, squashed all commits into a single signed commit, and reopened it here to maintain a clean history.

Both this PR and the old one have been moved to draft.
I’ll be pushing new commits here to address all review comments and suggestions made earlier today on the previous PR.

Please continue the discussion and review process on this new PR moving forward. 🚀

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 7, 2025

Deploying algokit-lora with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8355622
Status: ✅  Deploy successful!
Preview URL: https://3192c9d7.algokit-lora.pages.dev
Branch Preview URL: https://feat-optional-sender-improve.algokit-lora.pages.dev

View logs

@p2arthur p2arthur requested a review from PatrickDinh November 11, 2025 06:58
@p2arthur p2arthur marked this pull request as ready for review November 14, 2025 03:49
Copy link
Contributor

@PatrickDinh PatrickDinh left a comment

Choose a reason for hiding this comment

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

Thanks @p2arthur for the work. Above are some comments from me. I created this PR too #523 to address some other minor comments.

@@ -0,0 +1,33 @@
import { Nfd } from '@/features/nfd/data/types'

Copy link
Contributor

Choose a reason for hiding this comment

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

remove empty line

Comment on lines 8 to 16
export type Props = PropsWithChildren<{
address: string | Address
short?: boolean
className?: string
showCopyButton?: boolean
showQRButton?: boolean
nfd?: Nfd
autoPopulated?: boolean
}>
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be type Props = AddressOrNfdLinkProps & { autoPopulated?: boolean }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Much better!

Comment on lines 138 to 141
manager: asAddressOrNfd(data.manager.value!),
reserve: asAddressOrNfd(data.reserve.value!),
freeze: asAddressOrNfd(data.freeze.value!),
clawback: asAddressOrNfd(data.clawback.value!),
Copy link
Contributor

Choose a reason for hiding this comment

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

why do you use asAddressOrNfd here? If I recall correctly these values can be optional

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, back to optional.

manager: asOptionalAddressOrNfd(data.manager), reserve: asOptionalAddressOrNfd(data.reserve), freeze: asOptionalAddressOrNfd(data.freeze), clawback: asOptionalAddressOrNfd(data.clawback),

Comment on lines 221 to 224
manager: asAddressOrNfd(data.manager.value!),
reserve: asAddressOrNfd(data.reserve.value!),
freeze: asAddressOrNfd(data.freeze.value!),
clawback: asAddressOrNfd(data.clawback.value!),
Copy link
Contributor

Choose a reason for hiding this comment

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

same here, why use asAddressOrNfd?

}
}

return {
Copy link
Contributor

Choose a reason for hiding this comment

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

When the sender can't be resolved, the value is set to '', is it intended? Have you checked the behaviour of the transaction wizard when the value is ''?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Checking for that case like this now

`const val = data?.value ?? ''
const res = data?.resolvedAddress ?? ''

const isEmpty = !val && !res

if (isEmpty) {`

)

const result = await localnet.context.waitForIndexerTransaction(transactionId)
expect(result.transaction.sender).toBe(testAccount.addr.toString())
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 should compare it with the kmd account (from account manager) because I'm not sure is the testAccount is guarantee to be the same.

"version": "4.1.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz",
"integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==",
"version": "4.1.17",
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 shouldn't update package-lock in this PR. If it needs to be updated, it should be a separate PR that deals with dependencies

<p>Create and send transactions to the selected network using a connected wallet.</p>
<TransactionsBuilder
defaultTransactions={searchParamsTransactions}
key={searchParamsTransactions.transactions.map((t) => t.id).join('|')} // rerender when it gets populated
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is a good idea, you should wrap this in a loading spinner when it's loading. Have a look at RenderLoadable for inspiration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a PageLoader before rendering the TransactionBuilder. That removes the update based on key change (hacky) approach.

if (errors && errors.length > 0) {
for (const error of errors) {
toast.error(error)
let cancelled = false
Copy link
Contributor

Choose a reason for hiding this comment

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

why is cancelled needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed together with the unused error handling

import { betanetId, mainnetId, testnetId, fnetId, localnetId } from '@/features/network/data'
import { algorandClient } from '@/features/common/data/algo-client'

export default async function resolveSenderAddress(data?: { value?: string; resolvedAddress?: string }): Promise<TransactionSender> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't pick this up in the previous round, I think we should avoid using export default. We use named export everywhere else in this project.

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 data shouldn't be undefined

export default async function resolveSenderAddress(data?: { value?: string; resolvedAddress?: string }): Promise<TransactionSender> {
const { id: networkId } = settingsStore.get(networkConfigAtom)

const val = data?.value ?? ''
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 simplify this function by

export async function resolveSenderAddress(data: { value?: string; resolvedAddress?: string }): Promise<TransactionSender> {
    if (data.value) {
        return data
    }

    // The rest
}

Makes the logic simple, if the data has value, just return it.

Copy link
Contributor

Choose a reason for hiding this comment

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

No need data? because data can't be undefined

if (errors && errors.length > 0) {
for (const error of errors) {
toast.error(error)
let mounted = true
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe I missed something but I don't understand the purpose of the mounted variable. You init it with true and never change the value. Maybe it isn't needed

for (const error of errors) {
toast.error(error)
let mounted = true
const loadTransactions = async () => {
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 should refactor the async logic inside useEffect. Elsewhere we use jotai atom to deal with loading data. Have a look at the transaction-page.tsx for example. I am aware that it needs to few pieces to setup so if you need a hand, please let me know.

const senderInput = await component.findByLabelText(/Sender/)
fireEvent.input(senderInput, {
target: { value: testAccount.addr },
target: { value: walletAccount.addr },
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you change testAccount to walletAccount here? I thought that walletAccount is only used when the sender isn't set. I don't expect the see any changes in this test to be honest. It shouldn't be affected by the optional sender feature you are working on.

@p2arthur p2arthur force-pushed the feat/optional-sender-improved branch from 235900a to cc92c31 Compare November 19, 2025 21:05
const val = data?.value ?? ''
const res = data?.resolvedAddress ?? ''

const isEmpty = !val && !res
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to account for the "" case

…o use case wallet variables instead of test suite walletAddress
type: BuildableTransactionType.AssetReconfigure,
asset: data.asset,
sender: data.sender,

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

const receiverLabel = 'Receiver'

export const paymentFormSchema = z.object({
sender: optionalAddressFieldSchema,
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 moving the sender above ...commonSchema can caused unwanted bahaviour because this will be overwritten by commonSchema

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. Even though there aren't any properties that would overlap, setting the sender: optionalAddress afterwards makes a lot more sense.

return {
sender: transaction.sender.resolvedAddress,
receiver: transaction.receiver ? transaction.receiver.resolvedAddress : transaction.sender.resolvedAddress,
receiver: transaction.receiver ? transaction.receiver.resolvedAddress : (transaction.sender?.resolvedAddress ?? 'LOL IDK'),
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you need to do this, the sender can't be undefined

import { betanetId, mainnetId, testnetId, fnetId, localnetId } from '@/features/network/data'
import { algorandClient } from '@/features/common/data/algo-client'

export async function resolveSenderAddress(data: { value?: string; resolvedAddress?: string }): Promise<TransactionSender> {
Copy link
Contributor

Choose a reason for hiding this comment

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

let's rename to resolveTransactionSender to match the returned type

export default async function resolveSenderAddress(data?: { value?: string; resolvedAddress?: string }): Promise<TransactionSender> {
const { id: networkId } = settingsStore.get(networkConfigAtom)

const val = data?.value ?? ''
Copy link
Contributor

Choose a reason for hiding this comment

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

No need data? because data can't be undefined

})

it.each([
// Missing required field cases
Copy link
Contributor

Choose a reason for hiding this comment

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

You should remove these comments too if you removed the test case

expect(await screen.findByText(assetId)).toBeInTheDocument()
// Find the yellow sender link (auto-populated)
const senderLinks = await screen.findAllByText(localnetDispenderAccount.addr.toString())
expect(senderLinks.some((link) => link.className.includes('text-yellow-500'))).toBe(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if we need to go this far in the test to be honest, it's just the visual effect. In the future, if we need to change the color, we will need to update all the tests

Comment on lines 351 to 353
useEffect(() => {
setTransactions(defaultTransactions ?? [])
}, [defaultTransactions])
Copy link
Contributor

Choose a reason for hiding this comment

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

can you confirm if the useEffect is needed. This component isn't rendered until the loadable atom resolves data

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not needed. Removed it

return groupedParams.filter((entry) => Object.keys(entry).length > 0)
}

const autopopulateAddress = atomFamily((searchParamsString: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you review the names in this file? They don't match with the purpose.

Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need an atomFamily here, please refer to the implementation of the useLoadableTransactionAtom. If you have questions, please reach out to me.

"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"postman.settings.dotenv-detection-notification-visibility": false
Copy link
Contributor

Choose a reason for hiding this comment

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

why was this added?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants