Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/watcher/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ const nextConfig = {
unoptimized: true,
},
output: 'export',
webpack: function (config) {
config.experiments = {
...config.experiments,
asyncWebAssembly: true,
syncWebAssembly: true,
layers: true,
};
// Required for `output: 'export'` — ensures the .wasm file lands in
// the static assets output (so Next's static export copies it into out/).
config.output.webassemblyModuleFilename = 'static/wasm/[modulehash].wasm';
return config;
},
typescript: {
ignoreBuildErrors: true,
},
Expand Down
1 change: 1 addition & 0 deletions apps/watcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@rosen-ui/constants": "^1.0.0",
"@rosen-ui/swr-helpers": "^0.2.2",
"@rosen-ui/utils": "^1.0.3",
"ergo-lib-wasm-browser": "^0.24.1",
"lodash-es": "^4.17.21",
"moment": "^2.29.4",
"next": "^15.5.12",
Expand Down
28 changes: 23 additions & 5 deletions apps/watcher/src/app/actions/@form/withdraw/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
ApiWithdrawRequestBody,
ApiWithdrawResponse,
} from '@/types/api';
import { validateErgoAddress } from '@/utils/validateErgoAddress';

import { ConfirmationModal } from '../../ConfirmationModal';
import {
Expand Down Expand Up @@ -94,8 +95,7 @@ const WithdrawForm = () => {
amount: '',
},
});
const { handleSubmit, control, resetField, register, watch, formState } =
formMethods;
const { handleSubmit, control, resetField, watch, formState } = formMethods;

const formData = watch();

Expand All @@ -104,6 +104,25 @@ const WithdrawForm = () => {
name: 'tokenId',
});

const { field: addressField } = useController({
control,
name: 'address',
rules: {
validate: async (value) => {
try {
if (!value) {
return 'Address cannot be empty';
}
const isValid = await validateErgoAddress(value);
if (isValid) return;
return 'Invalid Address';
} catch {
return 'Something went wrong! please try again';
}
},
},
});

const selectedToken = useMemo(
() => tokens?.find((token) => token.tokenId === tokenIdField.value),
[tokens, tokenIdField.value],
Expand Down Expand Up @@ -198,9 +217,7 @@ const WithdrawForm = () => {
<TextField
label="Address"
disabled={disabled}
{...register('address', {
required: 'Address is required',
})}
{...addressField}
error={!!formMethods.formState.errors.address}
helperText={
formMethods.formState.isValidating ? (
Expand All @@ -210,6 +227,7 @@ const WithdrawForm = () => {
)
}
onBlur={(e) => {
addressField.onBlur(); // preserve RHF touched-state tracking
const trimmed = e.target.value.trim();
formMethods.setValue('address', trimmed, {
shouldDirty: true,
Expand Down
23 changes: 23 additions & 0 deletions apps/watcher/src/utils/validateErgoAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Validates an Ergo address (mainnet or testnet) using ergo-lib-wasm-browser.
* Lazy-loads the WASM module on first invocation to keep the initial bundle small.
*
* Approximates the validation behavior of @rosen-bridge/address-codec used by
* the Rosen app, adapted for browser execution (Watcher has no server boundary).
* Network-agnostic: accepts both mainnet and testnet Ergo addresses.
*
* @param address - The base58-encoded Ergo address to validate.
* @returns true if the address is a valid Ergo address (mainnet or testnet),
* false otherwise.
*/
export const validateErgoAddress = async (
address: string,
): Promise<boolean> => {
try {
const { Address } = await import('ergo-lib-wasm-browser');
Address.from_base58(address);
return true;
} catch {
return false;
}
};
Loading