From 678f3ae8eabe417653687f60bc1ffb6cbde14df6 Mon Sep 17 00:00:00 2001 From: Tania Markina Date: Thu, 16 Apr 2026 14:10:36 +0300 Subject: [PATCH 1/5] changin prefilled value for interact tab --- .../__tests__/wallet-prefill-provider.spec.ts | 77 +++++++++++-------- .../providers/wallet-prefill-provider.ts | 18 +++-- .../ui/InteractInstruction.tsx | 11 ++- 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts b/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts index 4f5e96dfc..da9136833 100644 --- a/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts +++ b/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts @@ -65,12 +65,7 @@ const INSTRUCTION_WITH_TWO_SIGNERS: InstructionData = { name: 'testInstruction', }; -const EMPTY_INSTRUCTION: InstructionData = { - accounts: [], - args: [], - docs: [], - name: 'testInstruction', -}; +const makeRef = (val?: string): { current: string | undefined } => ({ current: val }); describe('createWalletPrefillDependency', () => { it('should fill signer accounts with wallet address', () => { @@ -83,9 +78,13 @@ describe('createWalletPrefillDependency', () => { const { form, fieldNames } = result.current; const walletPublicKey = PublicKey.default; - const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER_AND_NON_SIGNER, walletPublicKey, { - account: fieldNames.account, - }); + const dependency = createWalletPrefillDependency( + INSTRUCTION_WITH_SIGNER_AND_NON_SIGNER, + { + account: fieldNames.account, + }, + makeRef(), + ); const walletAddress = walletPublicKey.toBase58(); dependency.onValueChange(walletPublicKey, form); @@ -103,9 +102,13 @@ describe('createWalletPrefillDependency', () => { ); const { form, fieldNames } = result.current; - const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER, null, { - account: fieldNames.account, - }); + const dependency = createWalletPrefillDependency( + INSTRUCTION_WITH_SIGNER, + { + account: fieldNames.account, + }, + makeRef(), + ); const setValueSpy = vi.spyOn(form, 'setValue'); dependency.onValueChange(null, form); @@ -123,9 +126,13 @@ describe('createWalletPrefillDependency', () => { const { form, fieldNames } = result.current; const walletPublicKey = PublicKey.default; - const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_NESTED_SIGNER, walletPublicKey, { - account: fieldNames.account, - }); + const dependency = createWalletPrefillDependency( + INSTRUCTION_WITH_NESTED_SIGNER, + { + account: fieldNames.account, + }, + makeRef(), + ); const walletAddress = walletPublicKey.toBase58(); dependency.onValueChange(walletPublicKey, form); @@ -133,16 +140,6 @@ describe('createWalletPrefillDependency', () => { expect(form.getValues('accounts.testInstruction.group.nestedSigner')).toBe(walletAddress); }); - it('should return correct dependency id and getValue', () => { - const walletPublicKey = PublicKey.default; - const dependency = createWalletPrefillDependency(EMPTY_INSTRUCTION, walletPublicKey, { - account: () => 'accounts.testInstruction.test' as any, - }); - - expect(dependency.id).toBe('wallet'); - expect(dependency.getValue()).toBe(walletPublicKey); - }); - it('should ignore non-PublicKey values in onValueChange', () => { const { result } = renderHook(() => useInstructionForm({ @@ -152,9 +149,13 @@ describe('createWalletPrefillDependency', () => { ); const { form, fieldNames } = result.current; - const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER, null, { - account: fieldNames.account, - }); + const dependency = createWalletPrefillDependency( + INSTRUCTION_WITH_SIGNER, + { + account: fieldNames.account, + }, + makeRef(), + ); const setValueSpy = vi.spyOn(form, 'setValue'); dependency.onValueChange('not-a-public-key', form); @@ -176,9 +177,13 @@ describe('createWalletPrefillDependency', () => { form.setValue('accounts.testInstruction.signer', PREFILLED_ADDRESS); const walletPublicKey = PublicKey.default; - const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER, walletPublicKey, { - account: fieldNames.account, - }); + const dependency = createWalletPrefillDependency( + INSTRUCTION_WITH_SIGNER, + { + account: fieldNames.account, + }, + makeRef(), + ); dependency.onValueChange(walletPublicKey, form); @@ -198,9 +203,13 @@ describe('createWalletPrefillDependency', () => { const walletPublicKey = PublicKey.default; const walletAddress = walletPublicKey.toBase58(); - const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_TWO_SIGNERS, walletPublicKey, { - account: fieldNames.account, - }); + const dependency = createWalletPrefillDependency( + INSTRUCTION_WITH_TWO_SIGNERS, + { + account: fieldNames.account, + }, + makeRef(), + ); dependency.onValueChange(walletPublicKey, form); diff --git a/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts b/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts index 3ed05125a..6e94c9953 100644 --- a/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts +++ b/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts @@ -1,10 +1,10 @@ import type { InstructionData } from '@entities/idl'; import { PublicKey } from '@solana/web3.js'; +import type { MutableRefObject } from 'react'; import type { FieldPath, UseFormReturn } from 'react-hook-form'; import type { FormValue, InstructionFormData, InstructionFormFieldNames } from '../../use-instruction-form'; import { isPrefilledAccount, WALLET_ACCOUNT_PATTERNS } from '../const'; -import type { ExternalDependency } from '../types'; import { traverseInstructionAccounts } from './traverse-accounts'; /** @@ -13,9 +13,9 @@ import { traverseInstructionAccounts } from './traverse-accounts'; */ export function createWalletPrefillDependency( instruction: InstructionData, - publicKey: PublicKey | null, fieldNames: Pick, -): ExternalDependency { + lastPrefillAddressRef: MutableRefObject, +): { onValueChange: (value: unknown, form: UseFormReturn) => void } { const signerPaths: FieldPath[] = []; traverseInstructionAccounts(instruction, (account, parentGroup) => { @@ -43,22 +43,26 @@ export function createWalletPrefillDependency( }); return { - getValue: () => publicKey, - id: 'wallet', onValueChange: (value: unknown, form: UseFormReturn) => { if (!value || !(value instanceof PublicKey)) return; const walletAddress = value.toBase58(); + const lastPrefillAddress = lastPrefillAddressRef.current; for (const path of signerPaths) { - const currentValue = form.getValues(path); - if (!currentValue || String(currentValue).trim() === '') { + const currentValue = String(form.getValues(path) ?? '').trim(); + const isEmpty = currentValue === ''; + const hasPreviousWallet = lastPrefillAddress !== undefined && currentValue === lastPrefillAddress; + + if (isEmpty || hasPreviousWallet) { form.setValue(path, walletAddress as unknown as FormValue, { shouldDirty: false, shouldValidate: false, }); } } + + lastPrefillAddressRef.current = walletAddress; }, }; } diff --git a/app/features/idl/interactive-idl/ui/InteractInstruction.tsx b/app/features/idl/interactive-idl/ui/InteractInstruction.tsx index 000f0a23f..db260ce7d 100644 --- a/app/features/idl/interactive-idl/ui/InteractInstruction.tsx +++ b/app/features/idl/interactive-idl/ui/InteractInstruction.tsx @@ -8,6 +8,7 @@ import { Button } from '@shared/ui/button'; import { Card } from '@shared/ui/card'; import { Tooltip, TooltipContent, TooltipTrigger } from '@shared/ui/tooltip'; import { useWallet } from '@solana/wallet-adapter-react'; +import { useEffect, useRef } from 'react'; import { Loader, Send } from 'react-feather'; import { Control, Controller, FieldPath } from 'react-hook-form'; @@ -50,12 +51,18 @@ export function InteractInstruction({ const pdas = usePdas({ form, idl, instruction }); const getAutocompleteItems = createGetAutocompleteItems({ pdas, publicKey }); - const walletPrefillDependency = createWalletPrefillDependency(instruction, publicKey, fieldNames); + const lastPrefillAddressRef = useRef(undefined); + useEffect(() => { + createWalletPrefillDependency(instruction, fieldNames, lastPrefillAddressRef).onValueChange(publicKey, form); + // instruction and fieldNames are stable for the lifetime of this component instance + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [publicKey, form]); + const knownAccountsPrefillDependency = createKnownAccountsPrefillDependency(instruction, fieldNames); const pdaPrefillDependency = createPdaPrefillDependency(idl, instruction, fieldNames); useFormPrefill({ config: { - externalDependencies: [walletPrefillDependency, knownAccountsPrefillDependency, pdaPrefillDependency], + externalDependencies: [knownAccountsPrefillDependency, pdaPrefillDependency], }, form, }); From 0d23b4f53d59962d4b959572baf3dc52d6173125 Mon Sep 17 00:00:00 2001 From: Tania Markina Date: Thu, 16 Apr 2026 14:32:04 +0300 Subject: [PATCH 2/5] chore: build info --- bench/BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/BUILD.md b/bench/BUILD.md index d83aae058..7947912c6 100644 --- a/bench/BUILD.md +++ b/bench/BUILD.md @@ -18,7 +18,7 @@ | Dynamic | `/address/[address]/idl` | 130 kB | 1.27 MB | | Dynamic | `/address/[address]/instructions` | 10 kB | 1.13 MB | | Dynamic | `/address/[address]/metadata` | 10 kB | 1.03 MB | -| Dynamic | `/address/[address]/nftoken-collection-nfts` | 10 kB | 1.10 MB | +| Dynamic | `/address/[address]/nftoken-collection-nfts` | 10 kB | 1.08 MB | | Dynamic | `/address/[address]/program-multisig` | 10 kB | 1.08 MB | | Dynamic | `/address/[address]/rewards` | 10 kB | 1.02 MB | | Dynamic | `/address/[address]/security` | 10 kB | 1.08 MB | From 62218642be206fcc951b20f188fa20093ab41977 Mon Sep 17 00:00:00 2001 From: Tania Markina Date: Thu, 16 Apr 2026 17:14:36 +0300 Subject: [PATCH 3/5] add tests --- .../__tests__/wallet-prefill-provider.spec.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts b/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts index da9136833..297b0ecd3 100644 --- a/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts +++ b/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts @@ -190,6 +190,33 @@ describe('createWalletPrefillDependency', () => { expect(form.getValues('accounts.testInstruction.signer')).toBe(PREFILLED_ADDRESS); }); + it('should overwrite a signer field that still contains the previous wallet address', () => { + const { result } = renderHook(() => + useInstructionForm({ + instruction: INSTRUCTION_WITH_SIGNER, + onSubmit: vi.fn(), + }), + ); + const { form, fieldNames } = result.current; + + const walletAAddress = Keypair.generate().publicKey.toBase58(); + const walletB = Keypair.generate().publicKey; + + form.setValue('accounts.testInstruction.signer', walletAAddress); + const ref = makeRef(walletAAddress); + + const dependency = createWalletPrefillDependency( + INSTRUCTION_WITH_SIGNER, + { + account: fieldNames.account, + }, + ref, + ); + dependency.onValueChange(walletB, form); + + expect(form.getValues('accounts.testInstruction.signer')).toBe(walletB.toBase58()); + }); + it('should fill only empty signer fields when some are already filled', () => { const { result } = renderHook(() => useInstructionForm({ From 61c44cbb09e7ddd23dd8fdfada6544c16cdf1108 Mon Sep 17 00:00:00 2001 From: Tania Markina Date: Mon, 20 Apr 2026 13:28:35 +0200 Subject: [PATCH 4/5] update architecture --- .../__tests__/wallet-prefill-provider.spec.ts | 84 ++++++------------- .../providers/wallet-prefill-provider.ts | 30 +++---- .../ui/InteractInstruction.tsx | 11 +-- 3 files changed, 41 insertions(+), 84 deletions(-) diff --git a/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts b/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts index 297b0ecd3..3aa616ad0 100644 --- a/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts +++ b/app/features/idl/interactive-idl/model/form-prefill/providers/__tests__/wallet-prefill-provider.spec.ts @@ -65,8 +65,6 @@ const INSTRUCTION_WITH_TWO_SIGNERS: InstructionData = { name: 'testInstruction', }; -const makeRef = (val?: string): { current: string | undefined } => ({ current: val }); - describe('createWalletPrefillDependency', () => { it('should fill signer accounts with wallet address', () => { const { result } = renderHook(() => @@ -78,13 +76,9 @@ describe('createWalletPrefillDependency', () => { const { form, fieldNames } = result.current; const walletPublicKey = PublicKey.default; - const dependency = createWalletPrefillDependency( - INSTRUCTION_WITH_SIGNER_AND_NON_SIGNER, - { - account: fieldNames.account, - }, - makeRef(), - ); + const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER_AND_NON_SIGNER, null, { + account: fieldNames.account, + }); const walletAddress = walletPublicKey.toBase58(); dependency.onValueChange(walletPublicKey, form); @@ -102,13 +96,9 @@ describe('createWalletPrefillDependency', () => { ); const { form, fieldNames } = result.current; - const dependency = createWalletPrefillDependency( - INSTRUCTION_WITH_SIGNER, - { - account: fieldNames.account, - }, - makeRef(), - ); + const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER, null, { + account: fieldNames.account, + }); const setValueSpy = vi.spyOn(form, 'setValue'); dependency.onValueChange(null, form); @@ -126,13 +116,9 @@ describe('createWalletPrefillDependency', () => { const { form, fieldNames } = result.current; const walletPublicKey = PublicKey.default; - const dependency = createWalletPrefillDependency( - INSTRUCTION_WITH_NESTED_SIGNER, - { - account: fieldNames.account, - }, - makeRef(), - ); + const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_NESTED_SIGNER, null, { + account: fieldNames.account, + }); const walletAddress = walletPublicKey.toBase58(); dependency.onValueChange(walletPublicKey, form); @@ -149,13 +135,9 @@ describe('createWalletPrefillDependency', () => { ); const { form, fieldNames } = result.current; - const dependency = createWalletPrefillDependency( - INSTRUCTION_WITH_SIGNER, - { - account: fieldNames.account, - }, - makeRef(), - ); + const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER, null, { + account: fieldNames.account, + }); const setValueSpy = vi.spyOn(form, 'setValue'); dependency.onValueChange('not-a-public-key', form); @@ -165,7 +147,7 @@ describe('createWalletPrefillDependency', () => { expect(setValueSpy).not.toHaveBeenCalled(); }); - it('should not overwrite existing values', () => { + it('should not overwrite user-typed values', () => { const { result } = renderHook(() => useInstructionForm({ instruction: INSTRUCTION_WITH_SIGNER, @@ -174,16 +156,12 @@ describe('createWalletPrefillDependency', () => { ); const { form, fieldNames } = result.current; - form.setValue('accounts.testInstruction.signer', PREFILLED_ADDRESS); + form.setValue('accounts.testInstruction.signer', PREFILLED_ADDRESS, { shouldDirty: true }); const walletPublicKey = PublicKey.default; - const dependency = createWalletPrefillDependency( - INSTRUCTION_WITH_SIGNER, - { - account: fieldNames.account, - }, - makeRef(), - ); + const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER, null, { + account: fieldNames.account, + }); dependency.onValueChange(walletPublicKey, form); @@ -202,22 +180,17 @@ describe('createWalletPrefillDependency', () => { const walletAAddress = Keypair.generate().publicKey.toBase58(); const walletB = Keypair.generate().publicKey; - form.setValue('accounts.testInstruction.signer', walletAAddress); - const ref = makeRef(walletAAddress); + form.setValue('accounts.testInstruction.signer', walletAAddress, { shouldDirty: false }); - const dependency = createWalletPrefillDependency( - INSTRUCTION_WITH_SIGNER, - { - account: fieldNames.account, - }, - ref, - ); + const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_SIGNER, null, { + account: fieldNames.account, + }); dependency.onValueChange(walletB, form); expect(form.getValues('accounts.testInstruction.signer')).toBe(walletB.toBase58()); }); - it('should fill only empty signer fields when some are already filled', () => { + it('should fill only non-dirty signer fields when some are already user-typed', () => { const { result } = renderHook(() => useInstructionForm({ instruction: INSTRUCTION_WITH_TWO_SIGNERS, @@ -226,17 +199,14 @@ describe('createWalletPrefillDependency', () => { ); const { form, fieldNames } = result.current; - form.setValue('accounts.testInstruction.signer1', PREFILLED_ADDRESS); + // Simulate user typing into signer1 + form.setValue('accounts.testInstruction.signer1', PREFILLED_ADDRESS, { shouldDirty: true }); const walletPublicKey = PublicKey.default; const walletAddress = walletPublicKey.toBase58(); - const dependency = createWalletPrefillDependency( - INSTRUCTION_WITH_TWO_SIGNERS, - { - account: fieldNames.account, - }, - makeRef(), - ); + const dependency = createWalletPrefillDependency(INSTRUCTION_WITH_TWO_SIGNERS, null, { + account: fieldNames.account, + }); dependency.onValueChange(walletPublicKey, form); diff --git a/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts b/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts index 6e94c9953..8496ea2d5 100644 --- a/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts +++ b/app/features/idl/interactive-idl/model/form-prefill/providers/wallet-prefill-provider.ts @@ -1,10 +1,10 @@ import type { InstructionData } from '@entities/idl'; import { PublicKey } from '@solana/web3.js'; -import type { MutableRefObject } from 'react'; -import type { FieldPath, UseFormReturn } from 'react-hook-form'; +import type { UseFormReturn } from 'react-hook-form'; import type { FormValue, InstructionFormData, InstructionFormFieldNames } from '../../use-instruction-form'; import { isPrefilledAccount, WALLET_ACCOUNT_PATTERNS } from '../const'; +import type { ExternalDependency } from '../types'; import { traverseInstructionAccounts } from './traverse-accounts'; /** @@ -13,10 +13,10 @@ import { traverseInstructionAccounts } from './traverse-accounts'; */ export function createWalletPrefillDependency( instruction: InstructionData, + publicKey: PublicKey | null, fieldNames: Pick, - lastPrefillAddressRef: MutableRefObject, -): { onValueChange: (value: unknown, form: UseFormReturn) => void } { - const signerPaths: FieldPath[] = []; +): ExternalDependency { + const signerPaths: ReturnType[] = []; traverseInstructionAccounts(instruction, (account, parentGroup) => { // Skip accounts that have known addresses (e.g., system program, token program) @@ -43,26 +43,20 @@ export function createWalletPrefillDependency( }); return { + getValue: () => publicKey, + id: 'wallet', onValueChange: (value: unknown, form: UseFormReturn) => { if (!value || !(value instanceof PublicKey)) return; const walletAddress = value.toBase58(); - const lastPrefillAddress = lastPrefillAddressRef.current; for (const path of signerPaths) { - const currentValue = String(form.getValues(path) ?? '').trim(); - const isEmpty = currentValue === ''; - const hasPreviousWallet = lastPrefillAddress !== undefined && currentValue === lastPrefillAddress; - - if (isEmpty || hasPreviousWallet) { - form.setValue(path, walletAddress as unknown as FormValue, { - shouldDirty: false, - shouldValidate: false, - }); - } + if (form.getFieldState(path).isDirty) continue; + form.setValue(path, walletAddress as unknown as FormValue, { + shouldDirty: false, + shouldValidate: false, + }); } - - lastPrefillAddressRef.current = walletAddress; }, }; } diff --git a/app/features/idl/interactive-idl/ui/InteractInstruction.tsx b/app/features/idl/interactive-idl/ui/InteractInstruction.tsx index db260ce7d..000f0a23f 100644 --- a/app/features/idl/interactive-idl/ui/InteractInstruction.tsx +++ b/app/features/idl/interactive-idl/ui/InteractInstruction.tsx @@ -8,7 +8,6 @@ import { Button } from '@shared/ui/button'; import { Card } from '@shared/ui/card'; import { Tooltip, TooltipContent, TooltipTrigger } from '@shared/ui/tooltip'; import { useWallet } from '@solana/wallet-adapter-react'; -import { useEffect, useRef } from 'react'; import { Loader, Send } from 'react-feather'; import { Control, Controller, FieldPath } from 'react-hook-form'; @@ -51,18 +50,12 @@ export function InteractInstruction({ const pdas = usePdas({ form, idl, instruction }); const getAutocompleteItems = createGetAutocompleteItems({ pdas, publicKey }); - const lastPrefillAddressRef = useRef(undefined); - useEffect(() => { - createWalletPrefillDependency(instruction, fieldNames, lastPrefillAddressRef).onValueChange(publicKey, form); - // instruction and fieldNames are stable for the lifetime of this component instance - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [publicKey, form]); - + const walletPrefillDependency = createWalletPrefillDependency(instruction, publicKey, fieldNames); const knownAccountsPrefillDependency = createKnownAccountsPrefillDependency(instruction, fieldNames); const pdaPrefillDependency = createPdaPrefillDependency(idl, instruction, fieldNames); useFormPrefill({ config: { - externalDependencies: [knownAccountsPrefillDependency, pdaPrefillDependency], + externalDependencies: [walletPrefillDependency, knownAccountsPrefillDependency, pdaPrefillDependency], }, form, }); From 27263305c3714808793a6799e0942a587048384c Mon Sep 17 00:00:00 2001 From: Tania Markina Date: Mon, 20 Apr 2026 14:55:05 +0200 Subject: [PATCH 5/5] retrigger test