diff --git a/frontend/src/routes/(front)/add-node/+page.js b/frontend/src/routes/(front)/add-node/+page.js
new file mode 100644
index 0000000..8e87ac6
--- /dev/null
+++ b/frontend/src/routes/(front)/add-node/+page.js
@@ -0,0 +1,27 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ data }) {
+ /* prettier-ignore */
+ const metaDefaults = {
+ title: 'Add Monero Node',
+ description: 'You can use this page to add known remote node to the system so my bots can monitor it.',
+ keywords: 'monero, monero node, monero public node, monero wallet'
+ };
+
+ return {
+ meta: {
+ title: metaDefaults.title,
+ description: metaDefaults.description,
+ keywords: metaDefaults.keywords,
+ image:
+ 'https://vcl-og-img.ditatompel.com/' + encodeURIComponent(metaDefaults.title) + '.png?md=0',
+ // Article
+ article: { publishTime: '', modifiedTime: '', author: '' },
+ // Twitter
+ twitter: {
+ title: metaDefaults.title,
+ description: metaDefaults.description,
+ image: metaDefaults.image
+ }
+ }
+ };
+}
diff --git a/frontend/src/routes/(front)/add-node/+page.svelte b/frontend/src/routes/(front)/add-node/+page.svelte
new file mode 100644
index 0000000..42f6d1e
--- /dev/null
+++ b/frontend/src/routes/(front)/add-node/+page.svelte
@@ -0,0 +1,115 @@
+
+
+
+
+
+
{data.meta.description}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/routes/(front)/remote-nodes/+page.js b/frontend/src/routes/(front)/remote-nodes/+page.js
new file mode 100644
index 0000000..6656793
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/+page.js
@@ -0,0 +1,27 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ data }) {
+ /* prettier-ignore */
+ const metaDefaults = {
+ title: 'Monero (XMR)',
+ description: 'Monero is private, decentralized cryptocurrency that keeps your finances confidential and secure.',
+ keywords: 'monero,xmr,monero node,xmrnode,cryptocurrency'
+ };
+
+ return {
+ meta: {
+ title: metaDefaults.title,
+ description: metaDefaults.description,
+ keywords: metaDefaults.keywords,
+ image:
+ 'https://vcl-og-img.ditatompel.com/' + encodeURIComponent(metaDefaults.title) + '.png?md=0',
+ // Article
+ article: { publishTime: '', modifiedTime: '', author: '' },
+ // Twitter
+ twitter: {
+ title: metaDefaults.title,
+ description: metaDefaults.description,
+ image: metaDefaults.image
+ }
+ }
+ };
+}
diff --git a/frontend/src/routes/(front)/remote-nodes/+page.svelte b/frontend/src/routes/(front)/remote-nodes/+page.svelte
new file mode 100644
index 0000000..e96e0f4
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/+page.svelte
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+
+ Remote node can be used by people who, for their own reasons (usually because of hardware
+ requirements, disk space, or technical abilities), cannot/don't want to run their own node and
+ prefer to relay on one publicly available on the Monero network.
+
+
+ Using an open node will allow to make a transaction instantaneously, without the need to
+ download the blockchain and sync to the Monero network first, but at the cost of the control
+ over your privacy. the Monero community suggests to always run your own node to
+ obtain the maximum possible privacy and to help decentralize the network.
+
+
+
+
+
+
+
+
+
+
Info
+
+
+ Uptime percentage calculated is the last 1 month uptime.
+
+
+ Est. Fee here is just fee estimation / byte from
+ get_fee_estimate
RPC call method.
+
+
+ Malicious actors who running remote nodes still can return high fee only if you about to create a transactions .
+
+ The best and safest way is running your own node !
+
+ Nodes with 0% uptime within 1 month with more than 300 check attempt will be removed. You
+ can always add your node again latter.
+
+
+ You can filter remote node by selecting on nettype ,
+ protocol , country ,
+ tor , and online status option.
+
+
+ If you know one or more remote node that we don't currently monitor, please add them using this form .
+
+
+ I deliberately cut the long Tor addresses, click the 👁 torhostname... to see the full Tor address.
+
+
+ You can found larger remote nodes database from monero.fail .
+
+
+ If you are developer or power user who like to fetch Monero remote node above in JSON
+ format, you can read Public API Monero Remote Node List blog post for more detailed information.
+
+
+
+
+
+
+
diff --git a/frontend/src/routes/(front)/remote-nodes/api-handler.js b/frontend/src/routes/(front)/remote-nodes/api-handler.js
new file mode 100644
index 0000000..5726f3c
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/api-handler.js
@@ -0,0 +1,22 @@
+import { PUBLIC_API_ENDPOINT } from '$env/static/public';
+
+/** @param {import('@vincjo/datatables/remote/state')} state */
+export async function loadApiData(state) {
+ const response = await fetch(`${PUBLIC_API_ENDPOINT}/monero/remote-node-dt?${getParams(state)}`);
+ const json = await response.json();
+
+ state.setTotalRows(json.data.total ?? 0);
+
+ return json.data.nodes ?? [];
+}
+
+const getParams = ({ pageNumber, offset, rowsPerPage, sort, filters }) => {
+ let params = `page=${pageNumber}&limit=${rowsPerPage}`;
+ if (sort) {
+ params += `&sort=${sort.orderBy}&dir=${sort.direction}`;
+ }
+ if (filters) {
+ params += filters.map(({ filterBy, value }) => `&${filterBy}=${value}`).join('');
+ }
+ return params;
+};
diff --git a/frontend/src/routes/(front)/remote-nodes/components/CountryCellWithAsn.svelte b/frontend/src/routes/(front)/remote-nodes/components/CountryCellWithAsn.svelte
new file mode 100644
index 0000000..d0c7eb5
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/CountryCellWithAsn.svelte
@@ -0,0 +1,38 @@
+
+
+{#if cc != ''}
+ {#if city !== ''}
+ {city},
+ {/if}
+ {country_name}
+
+{/if}
+
+{#if asn !== 0}
+ AS{asn} ({asn_name} )
+{/if}
+
+
diff --git a/frontend/src/routes/(front)/remote-nodes/components/EstimateFeeCell.svelte b/frontend/src/routes/(front)/remote-nodes/components/EstimateFeeCell.svelte
new file mode 100644
index 0000000..ffd9996
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/EstimateFeeCell.svelte
@@ -0,0 +1,10 @@
+
+
+{#if estimate_fee !== majority_fee}
+ {estimate_fee} (CAUTION!)
+{:else}
+ {estimate_fee}
+{/if}
diff --git a/frontend/src/routes/(front)/remote-nodes/components/HostPortCell.svelte b/frontend/src/routes/(front)/remote-nodes/components/HostPortCell.svelte
new file mode 100644
index 0000000..be3f93a
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/HostPortCell.svelte
@@ -0,0 +1,47 @@
+
+
+{#if is_tor}
+ modalAlert(hostname, port)}
+ >
+ 👁 {hostname}
+ .onion:{port}
+ (TOR)
+{:else}
+ {hostname}:{port}
+ {#if ip !== ''}
+ {ip}
+ {/if}
+{/if}
diff --git a/frontend/src/routes/(front)/remote-nodes/components/NetTypeCell.svelte b/frontend/src/routes/(front)/remote-nodes/components/NetTypeCell.svelte
new file mode 100644
index 0000000..a3e612b
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/NetTypeCell.svelte
@@ -0,0 +1,15 @@
+
+
+{#if nettype === 'stagenet'}
+ {nettype}
+{:else if nettype === 'testnet'}
+ {nettype}
+{:else}
+ {nettype}
+{/if}
+ {height}
diff --git a/frontend/src/routes/(front)/remote-nodes/components/ProtocolCell.svelte b/frontend/src/routes/(front)/remote-nodes/components/ProtocolCell.svelte
new file mode 100644
index 0000000..a929c38
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/ProtocolCell.svelte
@@ -0,0 +1,16 @@
+
+
+{#if protocol === 'http'}
+ {protocol}
+{:else}
+ {protocol}
+{/if}
+
+{#if cors}
+ (CORS 💪)
+{/if}
diff --git a/frontend/src/routes/(front)/remote-nodes/components/SelectAnonymityFilter.svelte b/frontend/src/routes/(front)/remote-nodes/components/SelectAnonymityFilter.svelte
new file mode 100644
index 0000000..f64fd82
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/SelectAnonymityFilter.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+ All
+ {#each uniqueValues as value}
+ {#if value === true}
+ TOR
+ {:else}
+ CLEARNET
+ {/if}
+ {/each}
+
+
diff --git a/frontend/src/routes/(front)/remote-nodes/components/SelectFilter.svelte b/frontend/src/routes/(front)/remote-nodes/components/SelectFilter.svelte
new file mode 100644
index 0000000..06e5abb
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/SelectFilter.svelte
@@ -0,0 +1,22 @@
+
+
+
+
+ All
+ {#each uniqueValues as value}
+ {#if value === ''}
+ UNKNOWN
+ {:else}
+ {value.toUpperCase()}
+ {/if}
+ {/each}
+
+
diff --git a/frontend/src/routes/(front)/remote-nodes/components/SelectStatusFilter.svelte b/frontend/src/routes/(front)/remote-nodes/components/SelectStatusFilter.svelte
new file mode 100644
index 0000000..f26904f
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/SelectStatusFilter.svelte
@@ -0,0 +1,20 @@
+
+
+
+
+ All
+ {#each uniqueValues as value}
+ {#if value === true}
+ ONLINE
+ {:else}
+ OFFLINE
+ {/if}
+ {/each}
+
+
diff --git a/frontend/src/routes/(front)/remote-nodes/components/StatusCell.svelte b/frontend/src/routes/(front)/remote-nodes/components/StatusCell.svelte
new file mode 100644
index 0000000..457d077
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/StatusCell.svelte
@@ -0,0 +1,20 @@
+
+
+{#if is_available}
+
Online
+{:else}
+
Offline
+{/if}
+
+{#each statuses as status}
+ {#if status === 1}
+
•
+ {:else if status === 0}
+
•
+ {:else}
+
•
+ {/if}
+{/each}
diff --git a/frontend/src/routes/(front)/remote-nodes/components/UptimeCell.svelte b/frontend/src/routes/(front)/remote-nodes/components/UptimeCell.svelte
new file mode 100644
index 0000000..51ff44e
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/UptimeCell.svelte
@@ -0,0 +1,13 @@
+
+
+{#if uptime > 98}
+
{uptime}%
+{:else if uptime < 98 && uptime > 80}
+
{uptime}%
+{:else if uptime < 80 && uptime > 75}
+
{uptime}%
+{:else}
+
{uptime}%
+{/if}
diff --git a/frontend/src/routes/(front)/remote-nodes/components/index.js b/frontend/src/routes/(front)/remote-nodes/components/index.js
new file mode 100644
index 0000000..af86b3b
--- /dev/null
+++ b/frontend/src/routes/(front)/remote-nodes/components/index.js
@@ -0,0 +1,10 @@
+export { default as CountryCellWithAsn } from './CountryCellWithAsn.svelte';
+export { default as EstimateFeeCell } from './EstimateFeeCell.svelte';
+export { default as HostPortCell } from './HostPortCell.svelte';
+export { default as NetTypeCell } from './NetTypeCell.svelte';
+export { default as ProtocolCell } from './ProtocolCell.svelte';
+export { default as SelectAnonymityFilter } from './SelectAnonymityFilter.svelte';
+export { default as SelectFilter } from './SelectFilter.svelte';
+export { default as SelectStatusFilter } from './SelectStatusFilter.svelte';
+export { default as StatusCell } from './StatusCell.svelte';
+export { default as UptimeCell } from './UptimeCell.svelte';
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index ea6d79b..c89e14f 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -1,20 +1,20 @@
+
{#if isLoading}
{/if}
+