Skip to content

Commit

Permalink
feat(sign_txn): add option to automatically send transaction immediat…
Browse files Browse the repository at this point in the history
…ely after signing

Closes #101
  • Loading branch information
No-Cash-7970 committed Jan 6, 2024
1 parent 2e9932e commit 52dd1ee
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 7 deletions.
15 changes: 15 additions & 0 deletions src/app/[lang]/components/NavBar/Settings/SettingsDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ describe('Settings Dialog', () => {
await userEvent.click(screen.getByLabelText('settings.default_apar_f_use_snd'));
await userEvent.click(screen.getByLabelText('settings.default_apar_c_use_snd'));
await userEvent.click(screen.getByLabelText('settings.default_apar_r_use_snd'));
await userEvent.click(screen.getByLabelText('settings.default_auto_send'));
// XXX: Add more settings here

// Click reset button
Expand All @@ -94,6 +95,7 @@ describe('Settings Dialog', () => {
expect(screen.getByLabelText('settings.default_apar_f_use_snd')).toBeChecked();
expect(screen.getByLabelText('settings.default_apar_c_use_snd')).toBeChecked();
expect(screen.getByLabelText('settings.default_apar_r_use_snd')).toBeChecked();
expect(screen.getByLabelText('settings.default_auto_send')).toBeChecked();
// XXX: Add more settings here
});

Expand Down Expand Up @@ -313,6 +315,19 @@ describe('Settings Dialog', () => {
expect(screen.getByText('settings.saved_message')).toBeInTheDocument();
});

it('notifies when "automatically send after signing by default" setting is changed',
async () => {
render(
<ToastProvider>
<SettingsDialog open={true} />
<ToastViewport />
</ToastProvider>
);
// Change setting from unchecked (false) --> checked (true)
await userEvent.click(screen.getByLabelText('settings.default_auto_send'));
expect(screen.getByText('settings.saved_message')).toBeInTheDocument();
});

it('has "clear transaction data" button', () => {
render(
<ToastProvider>
Expand Down
12 changes: 12 additions & 0 deletions src/app/[lang]/components/NavBar/Settings/SettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function SettingsForm(props: Props) {
const [defaultApar_fUseSnd, setDefaultApar_fUseSnd] = useAtom(Settings.defaultApar_fUseSnd);
const [defaultApar_cUseSnd, setDefaultApar_cUseSnd] = useAtom(Settings.defaultApar_cUseSnd);
const [defaultApar_rUseSnd, setDefaultApar_rUseSnd] = useAtom(Settings.defaultApar_rUseSnd);
const [defaultAutoSend, setDefaultAutoSend] = useAtom(Settings.defaultAutoSend);
const setStoredTxnData = useSetAtom(storedTxnDataAtom);
const setSignedTxn = useSetAtom(storedSignedTxnAtom);
// XXX: Add more settings here
Expand Down Expand Up @@ -80,6 +81,7 @@ export default function SettingsForm(props: Props) {
setDefaultApar_fUseSnd(Settings.defaults.defaultApar_fUseSnd);
setDefaultApar_cUseSnd(Settings.defaults.defaultApar_cUseSnd);
setDefaultApar_rUseSnd(Settings.defaults.defaultApar_rUseSnd);
setDefaultAutoSend(Settings.defaults.defaultAutoSend);
// XXX: Add more settings here

// Notify user of reset
Expand Down Expand Up @@ -150,6 +152,16 @@ export default function SettingsForm(props: Props) {
onChange={(e) => {setAssetInfoGet(e.target.checked); notifySave();}}
/>

{/* Setting: Automatically send after signing by default */}
<ToggleField
name='default_auto_send'
label={t('settings.default_auto_send')}
inputClass='toggle-primary'
containerClass='mt-4'
value={defaultAutoSend}
onChange={(e) => {setDefaultAutoSend(e.target.checked); notifySave();}}
/>

<h3>{t('settings.asset_create_title')}</h3>

{/* Setting: Set manager address to the sender address by default */}
Expand Down
36 changes: 34 additions & 2 deletions src/app/[lang]/txn/sign/components/SignTxn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useSearchParams } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import algosdk, { microalgosToAlgos } from 'algosdk';
import * as algokit from '@algorandfoundation/algokit-utils';
import { useWallet } from '@txnlab/use-wallet';
Expand All @@ -25,9 +25,13 @@ import {
createTxnFromData,
storedSignedTxnAtom,
storedTxnDataAtom,
tipBtnClass,
tipContentClass,
txnDataAtoms,
} from '@/app/lib/txn-data';
import NextStepButton from './NextStepButton';
import { CheckboxField } from '@/app/[lang]/components/form';
import {defaultAutoSend as defaultAutoSendAtom} from '@/app/lib/app-settings';

type Props = {
/** Language */
Expand All @@ -37,13 +41,17 @@ type Props = {
/** Buttons for connecting to wallet and signing transaction */
export default function SignTxn({ lng }: Props) {
const { t } = useTranslation(lng || '', ['app', 'common', 'sign_txn']);
const router = useRouter();
const currentURLParams = useSearchParams();
const nodeConfig = useAtomValue(nodeConfigAtom);
const setFee = useSetAtom(txnDataAtoms.fee);
const setFirstRound = useSetAtom(txnDataAtoms.fv);
const setLastRound = useSetAtom(txnDataAtoms.lv);

// A `null` value indicates that the default value should be used because the user has not changed
// the value
const [autoSend, setAutoSend] = useState<boolean|null>(null);
const storedTxnData = useAtomValue(storedTxnDataAtom);
const defaultAutoSend = useAtomValue(defaultAutoSendAtom);
const [storedSignedTxn, setStoredSignedTxn] = useAtom(storedSignedTxnAtom);
const [hasSignTxnError, setHasSignTxnError] = useState(false);

Expand Down Expand Up @@ -137,6 +145,13 @@ export default function SignTxn({ lng }: Props) {
const signedTxn = (await signTransactions([unsignedTxn]))[0];
const signedTxnDataUrl = await bytesToBase64DataUrl(signedTxn);
setStoredSignedTxn(signedTxnDataUrl);

// If the user checked the box, or the default should be used and it is to automatically send
// transaction
if (autoSend || (autoSend === null && defaultAutoSend)) {
// Go to send-transaction page
router.push(`/${lng}/txn/send` + (currentURLParams.size ? `?${currentURLParams}` : ''));
}
};

useEffect(() => {
Expand Down Expand Up @@ -293,6 +308,23 @@ export default function SignTxn({ lng }: Props) {
{// Connected to wallet but transaction has not been signed yet
(activeAccount && !storedSignedTxn && !hasSignTxnError) &&
<div className='mt-8'>
<CheckboxField label={t('sign_txn:auto_send.label')}
name='auto_send'
id='autoSend-input'
tip={{
content: t('sign_txn:auto_send.tip'),
btnClass: tipBtnClass,
btnTitle: t('sign_txn:auto_send.tip_btn_title'),
contentClass: tipContentClass
}}
inputInsideLabel={true}
containerId='autoSend-field'
containerClass='ms-2 mb-3'
inputClass='checkbox-primary me-2'
labelClass='justify-start w-fit max-w-full'
value={autoSend ?? defaultAutoSend}
onChange={(e) => setAutoSend(e.target.checked)}
/>
<button
className='btn btn-primary btn-block min-h-[5em] h-auto'
onClick={() => signTransaction()}
Expand Down
6 changes: 6 additions & 0 deletions src/app/[lang]/txn/sign/components/SignTxnConnected.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jest.mock('../../../../lib/utils.ts', () => ({
}));
// Mock navigation hooks
jest.mock('next/navigation', () => ({
useRouter: () => ({}),
useSearchParams: () => ({toString: () => 'preset=foo'}),
}));
// Mock algokit
Expand All @@ -54,6 +55,11 @@ describe('Sign Transaction Component (Connected wallet)', () => {
expect(screen.getByText('wallet.is_connected')).toBeInTheDocument();
});

it('has "automatically send transaction" checkbox', () => {
render(<SignTxn />);
expect(screen.getByLabelText(/sign_txn:auto_send.label/)).toBeChecked();
});

it('has "sign transaction" button', () => {
render(<SignTxn />);
expect(screen.getByText(/sign_txn_btn/)).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jest.mock('../../../../lib/utils.ts', () => ({
}));
// Mock navigation hooks
jest.mock('next/navigation', () => ({
useRouter: () => ({}),
useSearchParams: () => ({toString: () => 'preset=foo'}),
}));
// Mock algokit
Expand Down
1 change: 1 addition & 0 deletions src/app/[lang]/txn/sign/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ jest.mock('react', () => ({
jest.mock('react-i18next', () => i18nextClientMock);
// Mock navigation hooks because they are used by a child components
jest.mock('next/navigation', () => ({
useRouter: () => ({}),
useSearchParams: () => ({get: () => 'foo'})
}));
// Mock the wallet provider
Expand Down
1 change: 1 addition & 0 deletions src/app/i18n/locales/en/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ settings:
default_apar_f_use_snd: Set the freeze address to the sender address by default
default_apar_c_use_snd: Set the clawback address to the sender address by default
default_apar_r_use_snd: Set the reserve address to the sender address by default
default_auto_send: Automatically send transaction after signing by default
clear_data_title: Clear Stored Data
clear_txn_data_btn: Clear saved transaction data
clear_all_data_btn: Clear ALL data
Expand Down
6 changes: 6 additions & 0 deletions src/app/i18n/locales/en/sign_txn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ sign_error: >
The transaction could not be signed because the transaction is malformed. This is usually caused
by invalid transaction information. Go back to the previous step and check for errors in the
transaction information.
auto_send:
label: Automatically send transaction after signing
tip: >
If checked, the transaction is sent immediately after it is signed without the need for clicking
the "Send" button.
tip_btn_title: More information
13 changes: 9 additions & 4 deletions src/app/i18n/locales/es/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,15 @@ settings:
default_use_sug_rounds: Establecer rondas válidas automáticamente por defecto
get_asset_info: Obtener información sobre activos cuando se introduce la identificación del activo
asset_create_title: Creación de activos
default_apar_m_use_snd: Establecer la dirección del administrador en la dirección del remitente
default_apar_f_use_snd: Establecer la dirección para congelar en la dirección del remitente
default_apar_c_use_snd: Establecer la dirección para revocar en la dirección del remitente
default_apar_r_use_snd: Establecer la dirección de reserva en la dirección del remitente
default_apar_m_use_snd: >
Establecer la dirección del administrador en la dirección del remitente por defecto
default_apar_f_use_snd: >
Establecer la dirección para congelar en la dirección del remitente por defecto
default_apar_c_use_snd: >
Establecer la dirección para revocar en la dirección del remitente por defecto
default_apar_r_use_snd: >
Establecer la dirección de reserva en la dirección del remitente por defecto
default_auto_send: Enviar automáticamente la transacción después de firmar por defecto
clear_data_title: Borrar datos almacenados
clear_txn_data_btn: Borrar datos de transacciones guardados
clear_all_data_btn: Borrar TODOS los datos
Expand Down
7 changes: 7 additions & 0 deletions src/app/i18n/locales/es/sign_txn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ sign_error: >
No se ha podido firmar la transacción porque está malformada. Esto suele deberse a que la
información de la transacción no es válida. Vuelva al paso anterior y compruebe si hay errores en
la información de la transacción.
auto_send:
label: Enviar automáticamente la transacción después de firmar
tip: >
Si está marcada, la transacción se envía inmediatamente después de ser firmada sin necesidad de
pulsar el botón «Enviar».
tip_btn_title: Más información

7 changes: 7 additions & 0 deletions src/app/lib/app-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const defaults = {
* (default: `true` - Get asset information when ID is entered)
*/
assetInfoGet: true,
/** Automatically send after signing by default?
* (default: `true` - Automatically send after signing by default)
*/
defaultAutoSend: true,
};

/** Theme mode */
Expand Down Expand Up @@ -77,3 +81,6 @@ export const defaultApar_rUseSnd =
/** Retrieve asset information when asset ID is entered? */
export const assetInfoGet =
atomWithStorage<boolean>('getAssetInfo', defaults.assetInfoGet, storage);
/** Automatically send after signing by default? */
export const defaultAutoSend =
atomWithStorage<boolean>('defaultAutoSend', defaults.defaultAutoSend, storage);
2 changes: 1 addition & 1 deletion src/app/lib/txn-data/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const MAX_APP_KEY_LENGTH = 64;
export const tipContentClass = 'text-sm rounded-md py-2 px-4 bg-accent text-accent-content'
+ ' stroke-accent fill-accent sm:max-w-sm max-w-[var(--radix-popover-content-available-width)]';
/** Class(es) for the button that triggers the tooltip for a field */
export const tipBtnClass = 'ms-3 mb-1 opacity-70';
export const tipBtnClass = 'ms-3 opacity-70';

/** Collection of keys, or "names" of the transaction presets */
export enum Preset {
Expand Down

0 comments on commit 52dd1ee

Please sign in to comment.