diff --git a/src/features/transaction-wizard/data/common.ts b/src/features/transaction-wizard/data/common.ts
index dab3f6e63..afd5f7390 100644
--- a/src/features/transaction-wizard/data/common.ts
+++ b/src/features/transaction-wizard/data/common.ts
@@ -36,6 +36,7 @@ export const optionalAddressFieldSchema = z
message: invalidAddressOrNfdMessage,
}),
resolvedAddress: z.string().optional(),
+ autoPopulated: z.boolean().optional(),
})
.superRefine((field, ctx) => {
if (field.value && (!field.resolvedAddress || !isAddress(field.resolvedAddress))) {
diff --git a/src/features/transaction-wizard/mappers/as-address-or-nfd.ts b/src/features/transaction-wizard/mappers/as-address-or-nfd.ts
index 148e978b0..01f11b213 100644
--- a/src/features/transaction-wizard/mappers/as-address-or-nfd.ts
+++ b/src/features/transaction-wizard/mappers/as-address-or-nfd.ts
@@ -26,5 +26,6 @@ export const asOptionalAddressOrNfdSchema = (address?: Address) => {
return {
value: address,
resolvedAddress: address,
+ autoPopulated: false,
}
}
diff --git a/src/features/transaction-wizard/mappers/as-algosdk-transactions.ts b/src/features/transaction-wizard/mappers/as-algosdk-transactions.ts
index 791d0dd89..aa0d91d68 100644
--- a/src/features/transaction-wizard/mappers/as-algosdk-transactions.ts
+++ b/src/features/transaction-wizard/mappers/as-algosdk-transactions.ts
@@ -87,7 +87,7 @@ export const asPaymentTransactionParams = (
): PaymentParams => {
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'),
closeRemainderTo: 'closeRemainderTo' in transaction ? transaction.closeRemainderTo.resolvedAddress : undefined,
amount: algos(transaction.amount ?? 0),
note: transaction.note,
diff --git a/src/features/transaction-wizard/mappers/as-description-list-items.tsx b/src/features/transaction-wizard/mappers/as-description-list-items.tsx
index 2cfcb5ed2..427d58556 100644
--- a/src/features/transaction-wizard/mappers/as-description-list-items.tsx
+++ b/src/features/transaction-wizard/mappers/as-description-list-items.tsx
@@ -49,6 +49,7 @@ import { asAssetDisplayAmount } from '@/features/common/components/display-asset
import { AddressOrNfdLink } from '@/features/accounts/components/address-or-nfd-link'
import { DecodedAbiStruct } from '@/features/abi-methods/components/decoded-abi-struct'
import { ArgumentDefinition } from '@/features/applications/models'
+import TransactionSenderLink from '@/features/accounts/components/transaction-sender-link'
export const asDescriptionListItems = (
transaction: BuildTransactionResult,
@@ -101,7 +102,7 @@ const asPaymentTransaction = (txn: BuildPaymentTransactionResult | BuildAccountC
return [
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
...('closeRemainderTo' in params && params.closeRemainderTo
? [
@@ -141,7 +142,7 @@ const asAssetTransferTransaction = (
},
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
{
dt: 'Receiver',
@@ -193,7 +194,7 @@ const asAssetConfigTransaction = (
...('decimals' in params && params.decimals !== undefined ? [{ dt: 'Decimals', dd: params.decimals }] : []),
{
dt: transaction.type === BuildableTransactionType.AssetCreate ? 'Creator' : 'Sender',
- dd:
,
+ dd:
,
},
...('manager' in params && params.manager
? [
@@ -248,7 +249,7 @@ const asAssetFreezeTransaction = (transaction: BuildAssetFreezeTransactionResult
},
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
...('account' in params && params.account
? [
@@ -274,7 +275,7 @@ const asKeyRegistrationTransaction = (transaction: BuildKeyRegistrationTransacti
return [
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
{
dt: 'Registration',
@@ -386,7 +387,7 @@ const asAppCallTransaction = (transaction: BuildAppCallTransactionResult): Descr
},
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
...(transaction.extraProgramPages !== undefined
? [
@@ -439,7 +440,7 @@ const asMethodCallTransaction = (
},
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
...(transaction.extraProgramPages !== undefined
? [
@@ -699,7 +700,7 @@ const asApplicationCreateTransaction = (transaction: BuildApplicationCreateTrans
},
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
{
dt: 'Approval program',
@@ -762,7 +763,7 @@ const asApplicationUpdateTransaction = (transaction: BuildApplicationUpdateTrans
},
{
dt: 'Sender',
- dd:
,
+ dd:
,
},
{
dt: 'Approval program',
diff --git a/src/features/transaction-wizard/models/index.ts b/src/features/transaction-wizard/models/index.ts
index 969ee2caa..b4eb5adfd 100644
--- a/src/features/transaction-wizard/models/index.ts
+++ b/src/features/transaction-wizard/models/index.ts
@@ -65,9 +65,13 @@ export type AddressOrNfd = {
resolvedAddress: Address
}
+export type TransactionSender = AddressOrNfd & {
+ autoPopulated?: boolean
+}
+
type CommonBuildTransactionResult = {
id: string
- sender: AddressOrNfd
+ sender: TransactionSender
fee: {
setAutomatically: boolean
value?: number
diff --git a/src/features/transaction-wizard/transaction-wizard-page.test.tsx b/src/features/transaction-wizard/transaction-wizard-page.test.tsx
index 841056ac3..e3bc91010 100644
--- a/src/features/transaction-wizard/transaction-wizard-page.test.tsx
+++ b/src/features/transaction-wizard/transaction-wizard-page.test.tsx
@@ -10,6 +10,7 @@ import { setWalletAddressAndSigner } from '@/tests/utils/set-wallet-address-and-
import { addTransactionLabel } from './components/transactions-builder'
import { groupSendResultsLabel } from './components/group-send-results'
import { base64ToBytes } from '@/utils/base64-to-bytes'
+import { TransactionSignerAccount } from '@algorandfoundation/algokit-utils/types/account'
describe('transaction-wizard-page', () => {
const localnet = algorandFixture()
@@ -46,8 +47,10 @@ describe('transaction-wizard-page', () => {
})
describe('when a wallet is connected', () => {
+ let walletAccount: TransactionSignerAccount
+
beforeEach(async () => {
- await setWalletAddressAndSigner(localnet)
+ walletAccount = await setWalletAddressAndSigner(localnet)
})
describe('and a payment transaction is being sent', () => {
@@ -81,7 +84,6 @@ describe('transaction-wizard-page', () => {
})
it('succeeds when all fields have been correctly supplied', async () => {
- const { testAccount } = localnet.context
const testAccount2 = await localnet.context.generateAccount({ initialFunds: algo(0) })
await executeComponentTest(
@@ -98,7 +100,7 @@ describe('transaction-wizard-page', () => {
const senderInput = await component.findByLabelText(/Sender/)
fireEvent.input(senderInput, {
- target: { value: testAccount.addr },
+ target: { value: walletAccount.addr },
})
const receiverInput = await component.findByLabelText(/Receiver/)
@@ -144,7 +146,83 @@ describe('transaction-wizard-page', () => {
)
const result = await localnet.context.waitForIndexerTransaction(transactionId)
- expect(result.transaction.sender).toBe(testAccount.addr.toString())
+ expect(result.transaction.sender).toBe(walletAccount.addr.toString())
+ expect(result.transaction.paymentTransaction!).toMatchInlineSnapshot(`
+ TransactionPayment {
+ "amount": 500000n,
+ "closeAmount": 0n,
+ "closeRemainderTo": undefined,
+ "receiver": "${testAccount2.addr}",
+ }
+ `)
+ }
+ )
+ })
+
+ it('Can add a payment transaction without defining a sender address and the sender gets auto populated', async () => {
+ const testAccount2 = await localnet.context.generateAccount({ initialFunds: algo(0) })
+
+ await executeComponentTest(
+ () => {
+ return render(
)
+ },
+ async (component, user) => {
+ const addTransactionButton = await waitFor(() => {
+ const addTransactionButton = component.getByRole('button', { name: addTransactionLabel })
+ expect(addTransactionButton).not.toBeDisabled()
+ return addTransactionButton!
+ })
+ await user.click(addTransactionButton)
+
+ const receiverInput = await component.findByLabelText(/Receiver/)
+ fireEvent.input(receiverInput, {
+ target: { value: testAccount2.addr },
+ })
+
+ const amountInput = await component.findByLabelText(/Amount/)
+ fireEvent.input(amountInput, {
+ target: { value: '0.5' },
+ })
+
+ const addButton = await waitFor(() => {
+ const addButton = component.getByRole('button', { name: 'Add' })
+ expect(addButton).not.toBeDisabled()
+ return addButton!
+ })
+ await user.click(addButton)
+
+ const senderContent = await waitFor(() => {
+ return component.getByText(walletAccount.addr.toString())
+ })
+ expect(senderContent).toBeInTheDocument()
+
+ const sendButton = await waitFor(() => {
+ const sendButton = component.getByRole('button', { name: sendButtonLabel })
+ expect(sendButton).not.toBeDisabled()
+ return sendButton!
+ })
+ await user.click(sendButton)
+
+ const resultsDiv = await waitFor(
+ () => {
+ expect(component.queryByText('Required')).not.toBeInTheDocument()
+ return component.getByText(groupSendResultsLabel).parentElement!
+ },
+ { timeout: 10_000 }
+ )
+
+ const transactionId = await waitFor(
+ () => {
+ const transactionLink = within(resultsDiv)
+ .getAllByRole('link')
+ .find((a) => a.getAttribute('href')?.startsWith('/localnet/transaction'))!
+ return transactionLink.getAttribute('href')!.split('/').pop()!
+ },
+ { timeout: 10_000 }
+ )
+
+ const result = await localnet.context.waitForIndexerTransaction(transactionId)
+ expect(result.transaction.sender).toBe(walletAccount.addr.toString())
expect(result.transaction.paymentTransaction!).toMatchInlineSnapshot(`
TransactionPayment {
"amount": 500000n,
@@ -190,7 +268,6 @@ describe('transaction-wizard-page', () => {
})
it('succeeds when all fields have been correctly supplied', async () => {
- const { testAccount } = localnet.context
const testAccount2 = await localnet.context.generateAccount({ initialFunds: algo(0) })
await executeComponentTest(
@@ -209,7 +286,7 @@ describe('transaction-wizard-page', () => {
const senderInput = await component.findByLabelText(/Sender/)
fireEvent.input(senderInput, {
- target: { value: testAccount.addr },
+ target: { value: walletAccount.addr },
})
const closeToInput = await component.findByLabelText(/Close remainder to/)
@@ -250,13 +327,13 @@ describe('transaction-wizard-page', () => {
)
const result = await localnet.context.waitForIndexerTransaction(transactionId)
- expect(result.transaction.sender).toBe(testAccount.addr.toString())
+ expect(result.transaction.sender).toBe(walletAccount.addr.toString())
expect(result.transaction.paymentTransaction!).toMatchInlineSnapshot(`
TransactionPayment {
"amount": 0n,
"closeAmount": 9999000n,
"closeRemainderTo": "${testAccount2.addr}",
- "receiver": "${testAccount.addr}",
+ "receiver": "${walletAccount.addr}",
}
`)
}
@@ -266,7 +343,6 @@ describe('transaction-wizard-page', () => {
describe('and an application create transaction is being sent', () => {
it('succeeds when all fields have been correctly supplied', async () => {
- const { testAccount } = localnet.context
await executeComponentTest(
() => {
@@ -284,7 +360,7 @@ describe('transaction-wizard-page', () => {
const senderInput = await component.findByLabelText(/Sender/)
fireEvent.input(senderInput, {
- target: { value: testAccount.addr },
+ target: { value: walletAccount.addr },
})
const approvalProgram =
@@ -335,7 +411,7 @@ describe('transaction-wizard-page', () => {
)
const result = await localnet.context.waitForIndexerTransaction(transactionId)
- expect(result.transaction.sender).toBe(testAccount.addr.toString())
+ expect(result.transaction.sender).toBe(walletAccount.addr.toString())
expect(result.transaction.applicationTransaction!.approvalProgram).toEqual(base64ToBytes(approvalProgram))
expect(result.transaction.applicationTransaction!.clearStateProgram).toEqual(base64ToBytes(clearStateProgram))
}
@@ -343,8 +419,6 @@ describe('transaction-wizard-page', () => {
})
it('succeeds when sending an op-up transaction', async () => {
- const { testAccount } = localnet.context
-
await executeComponentTest(
() => {
return render(
)
@@ -361,7 +435,7 @@ describe('transaction-wizard-page', () => {
const senderInput = await component.findByLabelText(/Sender/)
fireEvent.input(senderInput, {
- target: { value: testAccount.addr },
+ target: { value: walletAccount.addr },
})
const approvalProgramInput = await component.findByLabelText(/Approval program/)
@@ -409,7 +483,7 @@ describe('transaction-wizard-page', () => {
)
const result = await localnet.context.waitForIndexerTransaction(transactionId)
- expect(result.transaction.sender).toBe(testAccount.addr.toString())
+ expect(result.transaction.sender).toBe(walletAccount.addr.toString())
}
)
})
@@ -417,11 +491,10 @@ describe('transaction-wizard-page', () => {
describe('and an application update transaction is being sent', () => {
it('succeeds when updating an updatable application', async () => {
- const { testAccount } = localnet.context
// First create an updatable application
const appCreateResult = await localnet.context.algorand.send.appCreate({
- sender: testAccount.addr,
+ sender: walletAccount.addr,
approvalProgram: '#pragma version 10\nint 1\nreturn',
clearStateProgram: '#pragma version 10\nint 1\nreturn',
})
@@ -443,7 +516,7 @@ describe('transaction-wizard-page', () => {
const senderInput = await component.findByLabelText(/Sender/)
fireEvent.input(senderInput, {
- target: { value: testAccount.addr },
+ target: { value: walletAccount.addr },
})
const applicationIdInput = await component.findByLabelText(/Application ID/)
@@ -495,7 +568,7 @@ describe('transaction-wizard-page', () => {
)
const result = await localnet.context.waitForIndexerTransaction(transactionId)
- expect(result.transaction.sender).toBe(testAccount.addr.toString())
+ expect(result.transaction.sender).toBe(walletAccount.addr.toString())
expect(result.transaction.applicationTransaction?.onCompletion).toBe('update')
expect(result.transaction.applicationTransaction!.approvalProgram).toEqual(base64ToBytes(program))
expect(result.transaction.applicationTransaction!.clearStateProgram).toEqual(base64ToBytes(program))
diff --git a/src/features/transaction-wizard/transaction-wizard-page.tsx b/src/features/transaction-wizard/transaction-wizard-page.tsx
index 80cac7f0d..18e6f1d5d 100644
--- a/src/features/transaction-wizard/transaction-wizard-page.tsx
+++ b/src/features/transaction-wizard/transaction-wizard-page.tsx
@@ -11,6 +11,7 @@ import { GroupSendResults, SendResults } from './components/group-send-results'
import algosdk from 'algosdk'
import { useTitle } from '@/utils/use-title'
import { useTransactionSearchParamsBuilder } from './utils/use-transaction-search-params-builder'
+import { PageLoader } from '../common/components/page-loader'
export const transactionWizardPageTitle = 'Transaction Wizard'
export const transactionTypeLabel = 'Transaction type'
@@ -18,9 +19,9 @@ export const sendButtonLabel = 'Send'
export function TransactionWizardPage() {
const [sendResults, setSendResults] = useState
(undefined)
- const searchParamsTransactions = useTransactionSearchParamsBuilder()
+ const { transactions: searchParamsTransactions, loading: transactionsLoading } = useTransactionSearchParamsBuilder()
useTitle('Transaction Wizard')
-
+
const renderTransactionResults = useCallback((result: SendTransactionResults, simulateResponse?: algosdk.modelsv2.SimulateResponse) => {
const sentTransactions = asTransactionFromSendResult(result)
const transactionsGraphData = asTransactionsGraphData(sentTransactions)
@@ -57,13 +58,17 @@ export function TransactionWizardPage() {
Create and send transactions to the selected network using a connected wallet.
-
{transactionGroupLabel}}
- onSendTransactions={sendTransactions}
- onSimulated={renderSimulateResult}
- onReset={reset}
- />
+ {transactionsLoading ? (
+
+ ) : (
+ {transactionGroupLabel}}
+ onSendTransactions={sendTransactions}
+ onSimulated={renderSimulateResult}
+ onReset={reset}
+ />
+ )}
{sendResults && }
diff --git a/src/features/transaction-wizard/utils/resolve-sender-address.ts b/src/features/transaction-wizard/utils/resolve-sender-address.ts
new file mode 100644
index 000000000..d82cad0dd
--- /dev/null
+++ b/src/features/transaction-wizard/utils/resolve-sender-address.ts
@@ -0,0 +1,46 @@
+import {
+ TESTNET_FEE_SINK_ADDRESS,
+ MAINNET_FEE_SINK_ADDRESS,
+ networkConfigAtom,
+ BETANET_FEE_SINK_ADDRESS,
+ FNET_FEE_SINK_ADDRESS,
+} from '@/features/network/data'
+import { TransactionSender } from '../models'
+import { settingsStore } from '@/features/settings/data'
+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