diff --git a/src/features/transaction-wizard/components/transactions-table.tsx b/src/features/transaction-wizard/components/transactions-table.tsx
index 81ea93243..4226add97 100644
--- a/src/features/transaction-wizard/components/transactions-table.tsx
+++ b/src/features/transaction-wizard/components/transactions-table.tsx
@@ -216,6 +216,7 @@ const getTableColumns = ({
return transactionPositions.get(transaction.id)!
},
},
+
{
header: 'Type',
accessorFn: (item) => asTransactionLabelFromBuildableTransactionType(item.type),
diff --git a/src/features/transaction-wizard/data/common.ts b/src/features/transaction-wizard/data/common.ts
index dab3f6e63..5c7149753 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))) {
@@ -48,6 +49,12 @@ export const optionalAddressFieldSchema = z
})
export const senderFieldSchema = { sender: addressFieldSchema }
+
+// TODO Arthur - Added this shape to make the sender optional in the forms that required it
+export const optionalSenderFieldShape = {
+ sender: optionalAddressFieldSchema,
+} as const
+
export const receiverFieldSchema = { receiver: addressFieldSchema }
export const noteFieldSchema = { note: zfd.text(z.string().optional()) }
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..a6cced05d 100644
--- a/src/features/transaction-wizard/mappers/as-address-or-nfd.ts
+++ b/src/features/transaction-wizard/mappers/as-address-or-nfd.ts
@@ -1,5 +1,5 @@
import { Address } from '@/features/accounts/data/types'
-import { AddressOrNfd } from '../models'
+import { AddressOrNfd, TransactionSender } from '../models'
import { ActiveWalletAccount } from '@/features/wallet/types/active-wallet'
export const asAddressOrNfd = (addressOrAccount: Address | ActiveWalletAccount): AddressOrNfd => {
@@ -16,15 +16,24 @@ export const asAddressOrNfd = (addressOrAccount: Address | ActiveWalletAccount):
} satisfies AddressOrNfd
}
-export const asOptionalAddressOrNfd = (addressOrNfdSchema: Partial
) => {
- return addressOrNfdSchema.value && addressOrNfdSchema.resolvedAddress
- ? ({ value: addressOrNfdSchema.value, resolvedAddress: addressOrNfdSchema.resolvedAddress } satisfies AddressOrNfd)
- : undefined
+export const asTransactionSender = (transactionSender?: TransactionSender): TransactionSender => {
+ const emptySender: TransactionSender = {
+ value: undefined,
+ resolvedAddress: undefined,
+ autoPopulated: false,
+ }
+ if (!transactionSender) return emptySender
+ if (transactionSender.autoPopulated) return emptySender
+
+ return transactionSender.value && transactionSender.resolvedAddress
+ ? { value: transactionSender.value, resolvedAddress: transactionSender.resolvedAddress, autoPopulated: transactionSender.autoPopulated }
+ : emptySender
}
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..14f4c0494 100644
--- a/src/features/transaction-wizard/mappers/as-algosdk-transactions.ts
+++ b/src/features/transaction-wizard/mappers/as-algosdk-transactions.ts
@@ -86,8 +86,8 @@ export const asPaymentTransactionParams = (
transaction: BuildPaymentTransactionResult | BuildAccountCloseTransactionResult
): PaymentParams => {
return {
- sender: transaction.sender.resolvedAddress,
- receiver: transaction.receiver ? transaction.receiver.resolvedAddress : transaction.sender.resolvedAddress,
+ sender: 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,
@@ -123,7 +123,7 @@ export const asMethodCallParams = async (transaction: BuildMethodCallTransaction
)
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
appId: BigInt(transaction.applicationId),
method: transaction.methodDefinition.abiMethod,
args: args,
@@ -158,7 +158,7 @@ const asMethodCallTransactions = async (transaction: BuildMethodCallTransactionR
export const asAppCallTransactionParams = (transaction: BuildAppCallTransactionResult): AppCallParams => {
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
appId: BigInt(transaction.applicationId),
args: transaction.args.map((arg) => base64ToBytes(arg)),
onComplete: transaction.onComplete,
@@ -181,7 +181,7 @@ const asAppCallTransaction = async (transaction: BuildAppCallTransactionResult):
export const asApplicationCreateTransactionParams = (transaction: BuildApplicationCreateTransactionResult): AppCreateParams => {
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
args: transaction.args.map((arg) => base64ToBytes(arg)),
onComplete: transaction.onComplete,
approvalProgram: base64ToBytes(transaction.approvalProgram),
@@ -206,7 +206,7 @@ const asApplicationCreateTransaction = async (transaction: BuildApplicationCreat
export const asApplicationUpdateTransactionParams = (transaction: BuildApplicationUpdateTransactionResult): AppUpdateParams => {
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
appId: BigInt(transaction.applicationId),
args: transaction.args.map((arg) => base64ToBytes(arg)),
approvalProgram: base64ToBytes(transaction.approvalProgram),
@@ -239,8 +239,8 @@ export const asAssetTransferTransactionParams = (
amount = BigInt(convertedAmount.toString())
}
return {
- sender: transaction.sender.resolvedAddress,
- receiver: 'receiver' in transaction ? transaction.receiver.resolvedAddress : transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
+ receiver: 'receiver' in transaction ? transaction.receiver.resolvedAddress : transaction.sender.resolvedAddress!,
clawbackTarget: 'clawbackTarget' in transaction ? transaction.clawbackTarget.resolvedAddress : undefined,
closeAssetTo: 'closeRemainderTo' in transaction ? transaction.closeRemainderTo.resolvedAddress : undefined,
assetId: BigInt(transaction.asset.id),
@@ -259,7 +259,7 @@ const asAssetTransferTransaction = async (
): Promise => {
if (
transaction.type === BuildableTransactionType.AssetClawback &&
- (!transaction.asset.clawback || transaction.sender.resolvedAddress !== transaction.asset.clawback)
+ (!transaction.asset.clawback || transaction.sender.resolvedAddress! !== transaction.asset.clawback)
) {
throw new Error('Invalid clawback transaction')
}
@@ -270,7 +270,7 @@ const asAssetTransferTransaction = async (
export const asAssetCreateTransactionParams = (transaction: BuildAssetCreateTransactionResult): AssetCreateParams => {
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
total: transaction.total,
decimals: transaction.decimals,
assetName: transaction.assetName,
@@ -294,7 +294,7 @@ const asAssetCreateTransaction = async (transaction: BuildAssetCreateTransaction
const asAssetReconfigureTransactionParams = (transaction: BuildAssetReconfigureTransactionResult): AssetConfigParams => {
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
assetId: BigInt(transaction.asset.id),
manager: transaction.manager ? transaction.manager.resolvedAddress : undefined,
reserve: transaction.reserve ? transaction.reserve.resolvedAddress : undefined,
@@ -312,7 +312,7 @@ const asAssetReconfigureTransaction = async (transaction: BuildAssetReconfigureT
const asAssetDestroyTransactionParams = (transaction: BuildAssetDestroyTransactionResult): AssetDestroyParams => {
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
assetId: BigInt(transaction.asset.id),
}
}
@@ -338,7 +338,7 @@ export const asAssetConfigTransactionParams = (
export const asAssetFreezeTransactionParams = (transaction: BuildAssetFreezeTransactionResult): AssetFreezeParams => {
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
assetId: BigInt(transaction.asset.id),
account: transaction.freezeTarget.resolvedAddress,
frozen: transaction.frozen,
@@ -348,7 +348,7 @@ export const asAssetFreezeTransactionParams = (transaction: BuildAssetFreezeTran
}
}
const asAssetFreezeTransaction = async (transaction: BuildAssetFreezeTransactionResult): Promise => {
- if (!transaction.asset.freeze || transaction.sender.resolvedAddress !== transaction.asset.freeze) {
+ if (!transaction.asset.freeze || transaction.sender.resolvedAddress! !== transaction.asset.freeze) {
throw new Error('Invalid freeze transaction')
}
@@ -365,7 +365,7 @@ export const asKeyRegistrationTransactionParams = (
invariant(transaction.stateProofKey, 'State proof key is required')
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
voteKey: Uint8Array.from(Buffer.from(transaction.voteKey, 'base64')),
selectionKey: Uint8Array.from(Buffer.from(transaction.selectionKey, 'base64')),
stateProofKey: Uint8Array.from(Buffer.from(transaction.stateProofKey, 'base64')),
@@ -379,7 +379,7 @@ export const asKeyRegistrationTransactionParams = (
}
return {
- sender: transaction.sender.resolvedAddress,
+ sender: transaction.sender.resolvedAddress!,
}
}
@@ -391,10 +391,10 @@ const asKeyRegistrationTransaction = async (transaction: BuildKeyRegistrationTra
: algorandClient.createTransaction.offlineKeyRegistration(params))
}
-const asFee = (fee: BuildAssetCreateTransactionResult['fee']) =>
+export const asFee = (fee: BuildAssetCreateTransactionResult['fee']) =>
!fee.setAutomatically && fee.value != null ? { staticFee: algos(fee.value) } : undefined
-const asValidRounds = (validRounds: BuildAssetCreateTransactionResult['validRounds']) =>
+export const asValidRounds = (validRounds: BuildAssetCreateTransactionResult['validRounds']) =>
!validRounds.setAutomatically && validRounds.firstValid && validRounds.lastValid
? {
firstValidRound: validRounds.firstValid,
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 cad3f0ef5..b7cfe2431 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..dcfd882ca 100644
--- a/src/features/transaction-wizard/models/index.ts
+++ b/src/features/transaction-wizard/models/index.ts
@@ -65,9 +65,15 @@ export type AddressOrNfd = {
resolvedAddress: Address
}
+export type TransactionSender = {
+ value: Address | Nfd | undefined
+ resolvedAddress: Address | undefined
+ 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..91f6d09d7 100644
--- a/src/features/transaction-wizard/transaction-wizard-page.test.tsx
+++ b/src/features/transaction-wizard/transaction-wizard-page.test.tsx
@@ -156,6 +156,83 @@ describe('transaction-wizard-page', () => {
}
)
})
+
+ it('Can add a payment transaction without defining a sender address and the sender gets auto populated', async () => {
+ const { testAccount } = localnet.context
+ 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(testAccount.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(testAccount.addr.toString())
+ expect(result.transaction.paymentTransaction!).toMatchInlineSnapshot(`
+ TransactionPayment {
+ "amount": 500000n,
+ "closeAmount": 0n,
+ "closeRemainderTo": undefined,
+ "receiver": "${testAccount2.addr}",
+ }
+ `)
+ }
+ )
+ })
})
describe('and a close account transaction is being sent', () => {
@@ -212,11 +289,96 @@ describe('transaction-wizard-page', () => {
target: { value: testAccount.addr },
})
+ const receiverInput = await component.findByLabelText(/Receiver/)
+ fireEvent.input(receiverInput, {
+ target: { value: testAccount.addr },
+ })
+
const closeToInput = await component.findByLabelText(/Close remainder to/)
fireEvent.input(closeToInput, {
target: { value: testAccount2.addr },
})
+ const amountInput = await component.findByLabelText(/Amount to pay/)
+ fireEvent.input(amountInput, { target: { value: '0' } })
+
+ const addButton = await waitFor(() => {
+ const addButton = component.getByRole('button', { name: 'Add' })
+ expect(addButton).not.toBeDisabled()
+ return addButton!
+ })
+ await user.click(addButton)
+
+ 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(testAccount.addr.toString())
+ expect(result.transaction.paymentTransaction!).toMatchInlineSnapshot(`
+ TransactionPayment {
+ "amount": 0n,
+ "closeAmount": 9999000n,
+ "closeRemainderTo": "${testAccount2.addr}",
+ "receiver": "${testAccount.addr}",
+ }
+ `)
+ }
+ )
+ })
+
+ it('can simulate a close account transaction without defining a sender address', async () => {
+ const { testAccount } = localnet.context
+ 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)
+
+ await selectOption(component.baseElement, user, transactionTypeLabel, 'Account Close (pay)')
+
+ const receiverInput = await component.findByLabelText(/Receiver/)
+ fireEvent.input(receiverInput, {
+ target: { value: testAccount.addr },
+ })
+
+ const closeToInput = await component.findByLabelText(/Close remainder to/)
+ fireEvent.input(closeToInput, {
+ target: { value: testAccount2.addr },
+ })
+
+ const amountInput = await component.findByLabelText(/Amount to pay/)
+ fireEvent.input(amountInput, { target: { value: '0' } })
+
const addButton = await waitFor(() => {
const addButton = component.getByRole('button', { name: 'Add' })
expect(addButton).not.toBeDisabled()
@@ -413,6 +575,78 @@ describe('transaction-wizard-page', () => {
}
)
})
+
+ it('succeeds when sending an op-up transaction', async () => {
+ const { testAccount } = localnet.context
+
+ 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)
+
+ await selectOption(component.baseElement, user, transactionTypeLabel, 'Application Create (appl)')
+
+ const senderInput = await component.findByLabelText(/Sender/)
+ fireEvent.input(senderInput, {
+ target: { value: testAccount.addr },
+ })
+
+ const approvalProgramInput = await component.findByLabelText(/Approval program/)
+ fireEvent.input(approvalProgramInput, {
+ target: { value: 'CoEBQw==' },
+ })
+
+ const clearStateProgramInput = await component.findByLabelText(/Clear state program/)
+ fireEvent.input(clearStateProgramInput, {
+ target: { value: 'CoEBQw==' },
+ })
+
+ await selectOption(component.baseElement, user, /On complete/, 'Delete')
+
+ const addButton = await waitFor(() => {
+ const addButton = component.getByRole('button', { name: 'Add' })
+ expect(addButton).not.toBeDisabled()
+ return addButton!
+ })
+ await user.click(addButton)
+
+ 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(testAccount.addr.toString())
+ }
+ )
+ })
})
describe('and an application update transaction is being sent', () => {
diff --git a/src/features/transaction-wizard/transaction-wizard-page.tsx b/src/features/transaction-wizard/transaction-wizard-page.tsx
index f12f3ff08..249d20fef 100644
--- a/src/features/transaction-wizard/transaction-wizard-page.tsx
+++ b/src/features/transaction-wizard/transaction-wizard-page.tsx
@@ -19,6 +19,7 @@ export const sendButtonLabel = 'Send'
export function TransactionWizardPage() {
const [sendResults, setSendResults] = useState(undefined)
const searchParamsTransactions = useTransactionSearchParamsBuilder()
+
useTitle('Transaction Wizard')
const renderTransactionResults = useCallback((result: SendTransactionResults, simulateResponse?: algosdk.modelsv2.SimulateResponse) => {
@@ -45,6 +46,7 @@ export function TransactionWizardPage() {
async (result: SimulateResult) => {
renderTransactionResults(result, result.simulateResponse)
},
+
[renderTransactionResults]
)
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..b70a1930a
--- /dev/null
+++ b/src/features/transaction-wizard/utils/resolve-sender-address.ts
@@ -0,0 +1,48 @@
+import { OptionalSenderFieldSchema } from '@/features/forms/components/address-form-item'
+import {
+ TESTNET_FEE_SINK_ADDRESS,
+ MAINNET_FEE_SINK_ADDRESS,
+ networkConfigAtom,
+ BETANET_FEE_SINK_ADDRESS,
+ FNET_FEE_SINK_ADDRESS,
+} from '@/features/network/data'
+import { AddressOrNfd, 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: T
+): Promise {
+ const { id: networkId } = settingsStore.get(networkConfigAtom)
+
+ const val = data?.value ?? ''
+ const res = data?.resolvedAddress ?? ''
+
+ const isEmpty = !val && !res
+
+ if (isEmpty) {
+ if (networkId === mainnetId) {
+ return { value: MAINNET_FEE_SINK_ADDRESS, resolvedAddress: MAINNET_FEE_SINK_ADDRESS, autoPopulated: true }
+ }
+ if (networkId === localnetId) {
+ const address = (await algorandClient.account.localNetDispenser()).addr.toString()
+ return { value: address, resolvedAddress: address, autoPopulated: true }
+ }
+ if (networkId === fnetId) {
+ return { value: FNET_FEE_SINK_ADDRESS, resolvedAddress: FNET_FEE_SINK_ADDRESS, autoPopulated: true }
+ }
+ if (networkId === betanetId) {
+ return { value: BETANET_FEE_SINK_ADDRESS, resolvedAddress: BETANET_FEE_SINK_ADDRESS, autoPopulated: true }
+ }
+ if (networkId === testnetId) {
+ return { value: TESTNET_FEE_SINK_ADDRESS, resolvedAddress: TESTNET_FEE_SINK_ADDRESS, autoPopulated: true }
+ }
+ }
+
+ return {
+ value: val || res,
+ resolvedAddress: res || val,
+ autoPopulated: false,
+ }
+}
diff --git a/src/features/transaction-wizard/utils/transactions-url-search-params.test.tsx b/src/features/transaction-wizard/utils/transactions-url-search-params.test.tsx
index ca11a63da..d7d4050e8 100644
--- a/src/features/transaction-wizard/utils/transactions-url-search-params.test.tsx
+++ b/src/features/transaction-wizard/utils/transactions-url-search-params.test.tsx
@@ -193,11 +193,6 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
{
key: 'receiver[0]',
mode: 'missing',
@@ -209,12 +204,7 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0: The number NaN cannot be converted to a BigInt because it is not an integer',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value',
- },
+
{
key: 'receiver[0]',
mode: 'invalid',
@@ -366,11 +356,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'total[0]',
mode: 'missing',
@@ -382,12 +368,7 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: decimals',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value',
- },
+
{
key: 'total[0]',
mode: 'invalid',
@@ -568,11 +549,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'assetid[0]',
mode: 'missing',
@@ -584,12 +561,7 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: asset-id',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value',
- },
+
{
key: 'assetid[0]',
mode: 'invalid',
@@ -757,11 +729,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'assetid[0]',
mode: 'missing',
@@ -778,12 +746,7 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: asset-id',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value',
- },
+
{
key: 'assetid[0]',
mode: 'invalid',
@@ -970,11 +933,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'receiver[0]',
mode: 'missing',
@@ -996,12 +955,7 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: asset-id',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value',
- },
+
{
key: 'receiver[0]',
mode: 'invalid',
@@ -1196,11 +1150,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'assetid[0]',
mode: 'missing',
@@ -1217,12 +1167,6 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: asset-id',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value, sender.value',
- },
{
key: 'assetid[0]',
mode: 'invalid',
@@ -1418,11 +1362,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'assetid[0]',
mode: 'missing',
@@ -1439,12 +1379,7 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: asset-id',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value',
- },
+
{
key: 'assetid[0]',
mode: 'invalid',
@@ -1663,11 +1598,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'freezeto[0]',
mode: 'missing',
@@ -1689,12 +1620,6 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: asset-id',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value, sender.value',
- },
{
key: 'freezeto[0]',
mode: 'invalid',
@@ -1956,11 +1881,7 @@ describe('Render transactions page with search params', () => {
it.each([
// Missing required field cases
- {
- key: 'sender[0]',
- mode: 'missing',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-resolvedAddress',
- },
+
{
key: 'receiver[0]',
mode: 'missing',
@@ -1992,12 +1913,6 @@ describe('Render transactions page with search params', () => {
expected: 'Error in transaction at index 0 in the following fields: asset-id',
},
// Invalid field value cases
- {
- key: 'sender[0]',
- mode: 'invalid',
- value: 'invalid-address',
- expected: 'Error in transaction at index 0 in the following fields: sender-value, sender-value, sender.value',
- },
{
key: 'receiver[0]',
mode: 'invalid',
diff --git a/src/features/transaction-wizard/utils/transform-search-params-transactions.ts b/src/features/transaction-wizard/utils/transform-search-params-transactions.ts
index 55d5bee81..29d7d80b3 100644
--- a/src/features/transaction-wizard/utils/transform-search-params-transactions.ts
+++ b/src/features/transaction-wizard/utils/transform-search-params-transactions.ts
@@ -28,6 +28,7 @@ import { randomGuid } from '@/utils/random-guid'
import algosdk from 'algosdk'
import { microAlgo } from '@algorandfoundation/algokit-utils'
import Decimal from 'decimal.js'
+import { asTransactionSender } from '../mappers/as-address-or-nfd'
// This is a workaround to make the online field a boolean instead of a string.
// A string type is used in the form schema because of the value of radio buttons cant be boolean
@@ -38,10 +39,7 @@ const keyRegFormSchema = keyRegistrationFormSchema.innerType().extend({
const transformKeyRegistrationTransaction = (params: BaseSearchParamTransaction): BuildKeyRegistrationTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.KeyRegistration,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
online: Boolean(params.votekey),
fee: params.fee ? { setAutomatically: false, value: microAlgo(Number(params.fee)).algo } : { setAutomatically: true },
voteKey: params.votekey,
@@ -60,10 +58,8 @@ const transformKeyRegistrationTransaction = (params: BaseSearchParamTransaction)
const transformPaymentTransaction = (params: BaseSearchParamTransaction): BuildPaymentTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.Payment,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
receiver: {
value: params.receiver,
resolvedAddress: params.receiver,
@@ -85,10 +81,8 @@ const defaultOptionalAddress = {
const transformAssetCreateTransaction = (params: BaseSearchParamTransaction): BuildAssetCreateTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetCreate,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
total: BigInt(params.total),
decimals: Number(params.decimals),
assetName: params.assetname,
@@ -132,10 +126,8 @@ const transformAssetCreateTransaction = (params: BaseSearchParamTransaction): Bu
const transformAssetOptInTransaction = (params: BaseSearchParamTransaction): BuildAssetOptInTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetOptIn,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
asset: {
id: BigInt(params.assetid),
decimals: params.decimals ? Number(params.decimals) : undefined,
@@ -153,10 +145,8 @@ const transformAssetOptInTransaction = (params: BaseSearchParamTransaction): Bui
const transformAssetOptOutTransaction = (params: BaseSearchParamTransaction): BuildAssetOptOutTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetOptOut,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
asset: {
id: BigInt(params.assetid),
decimals: params.decimals ? Number(params.decimals) : undefined,
@@ -179,10 +169,8 @@ const transformAssetOptOutTransaction = (params: BaseSearchParamTransaction): Bu
const transformAssetTransferTransaction = (params: BaseSearchParamTransaction): BuildAssetTransferTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetTransfer,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
receiver: {
value: params.receiver,
resolvedAddress: params.receiver,
@@ -206,10 +194,8 @@ const transformAssetTransferTransaction = (params: BaseSearchParamTransaction):
const transformAssetReconfigureTransaction = (params: BaseSearchParamTransaction): BuildAssetReconfigureTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetReconfigure,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
asset: {
id: BigInt(params.assetid),
decimals: params.decimals ? Number(params.decimals) : undefined,
@@ -252,10 +238,8 @@ const transformAssetReconfigureTransaction = (params: BaseSearchParamTransaction
const transformAssetFreezeTransaction = (params: BaseSearchParamTransaction): BuildAssetFreezeTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetFreeze,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
freezeTarget: {
value: params.freezeto,
resolvedAddress: params.freezeto,
@@ -279,10 +263,8 @@ const transformAssetFreezeTransaction = (params: BaseSearchParamTransaction): Bu
const transformAssetDestroyTransaction = (params: BaseSearchParamTransaction): BuildAssetDestroyTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetDestroy,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
asset: {
id: BigInt(params.assetid),
decimals: params.decimals ? Number(params.decimals) : undefined,
@@ -300,10 +282,8 @@ const transformAssetDestroyTransaction = (params: BaseSearchParamTransaction): B
const transformAssetClawbackTransaction = (params: BaseSearchParamTransaction): BuildAssetClawbackTransactionResult => ({
id: randomGuid(),
type: BuildableTransactionType.AssetClawback,
- sender: {
- value: params.sender,
- resolvedAddress: params.sender,
- },
+ sender: asTransactionSender({ value: params.sender!, resolvedAddress: params.sender!, autoPopulated: false }),
+
receiver: {
value: params.receiver,
resolvedAddress: params.receiver,