Skip to content

Commit c225357

Browse files
ksrichardsdbondi
andauthored
feat(wallet-daemon): track authored templates and show on UI (tari-project#1333)
Description --- When a transaction is finalized and contains a template that belongs to any of our account, I add it to a new SQLite table in wallet daemon mapped by account's key index. Additionally a new wallet daemon frontend page in order to see what are the authored templates. ![image](https://github.com/user-attachments/assets/014e9d0d-013a-4bb9-9ef5-5f092176ab96) ![image](https://github.com/user-attachments/assets/653c0df1-a521-4449-b05e-ae8ae8e04fe2) Motivation and Context --- It was hard sometimes to see what templates we authored/created with an account. How Has This Been Tested? --- 1. Start a fresh swarm 2. Create an account in wallet daemon 3. Publish a template 4. Check new template on templates page What process can a PR reviewer use to test or verify this change? --- Breaking Changes --- - [ ] None - [x] Requires data directory to be deleted - [ ] Other - Please specify <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a dedicated "Templates" section in the wallet application, allowing users to view their authored templates. - Enhanced the API to support secure, paginated template listings and integrated it with a background service for timely updates. - Updated the user interface with a new menu item, secured route, and breadcrumb entry for easy navigation to the Templates section. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Stan Bondi <sdbondi@users.noreply.github.com>
1 parent 5df7f1f commit c225357

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1474
-650
lines changed

applications/tari_dan_wallet_daemon/src/handlers/templates.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
// SPDX-License-Identifier: BSD-3-Clause
33

44
use tari_dan_wallet_sdk::{apis::jwt::JrpcPermission, network::WalletNetworkInterface};
5-
use tari_wallet_daemon_client::types::{TemplatesGetRequest, TemplatesGetResponse};
5+
use tari_wallet_daemon_client::types::{
6+
AuthoredTemplate,
7+
TemplatesGetRequest,
8+
TemplatesGetResponse,
9+
TemplatesListAuthoredRequest,
10+
TemplatesListAuthoredResponse,
11+
};
612

713
use crate::handlers::HandlerContext;
814

@@ -21,3 +27,22 @@ pub async fn handle_get(
2127

2228
Ok(TemplatesGetResponse { template_definition })
2329
}
30+
31+
pub async fn handle_list_owned(
32+
context: &HandlerContext,
33+
token: Option<String>,
34+
req: TemplatesListAuthoredRequest,
35+
) -> Result<TemplatesListAuthoredResponse, anyhow::Error> {
36+
let sdk = context.wallet_sdk().clone();
37+
sdk.jwt_api().check_auth(token, &[JrpcPermission::TemplatesRead])?;
38+
39+
let (templates, total_templates) =
40+
context
41+
.wallet_sdk()
42+
.template_api()
43+
.list_authored_templates(req.key_index, req.page, req.page_size)?;
44+
Ok(TemplatesListAuthoredResponse {
45+
templates: templates.iter().map(AuthoredTemplate::from).collect(),
46+
total_templates,
47+
})
48+
}

applications/tari_dan_wallet_daemon/src/jrpc_server.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,11 @@ async fn handler(
177177
"list" => call_handler(context, value, token, substates::handle_list).await,
178178
_ => Ok(value.method_not_found(&value.method)),
179179
},
180-
Some(("templates", "get")) => call_handler(context, value, token, templates::handle_get).await,
180+
Some(("templates", method)) => match method {
181+
"get" => call_handler(context, value, token, templates::handle_get).await,
182+
"list_authored" => call_handler(context, value, token, templates::handle_list_owned).await,
183+
_ => Ok(value.method_not_found(&value.method)),
184+
},
181185
Some(("nfts", method)) => match method {
182186
"mint_account_nft" => call_handler(context, value, token, nfts::handle_mint_account_nft).await,
183187
"get" => call_handler(context, value, token, nfts::handle_get_nft).await,

applications/tari_dan_wallet_daemon/src/services/mod.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ mod webauthn;
1212
pub use webauthn::*;
1313

1414
mod session_store;
15+
mod template_monitor;
16+
1517
// -------------------------------- Spawn -------------------------------- //
1618
use anyhow::anyhow;
1719
use futures::{future, future::BoxFuture, FutureExt};
@@ -23,7 +25,10 @@ use tokio::{sync::oneshot, task::JoinHandle};
2325
use transaction_service::TransactionService;
2426
pub use transaction_service::TransactionServiceHandle;
2527

26-
use crate::{notify::Notify, services::account_monitor::AccountMonitor};
28+
use crate::{
29+
notify::Notify,
30+
services::{account_monitor::AccountMonitor, template_monitor::TemplateMonitor},
31+
};
2732

2833
type Reply<T> = oneshot::Sender<T>;
2934

@@ -40,13 +45,20 @@ where
4045
let (transaction_service, transaction_service_handle) =
4146
TransactionService::new(notify.clone(), wallet_sdk.clone(), shutdown_signal.clone());
4247
let transaction_service_join_handle = tokio::spawn(transaction_service.run());
48+
let template_monitor = TemplateMonitor::new(notify.clone(), wallet_sdk.clone(), shutdown_signal.clone());
49+
let template_monitor_join_handle = tokio::spawn(template_monitor.run());
4350
let (account_monitor, account_monitor_handle) = AccountMonitor::new(notify, wallet_sdk, shutdown_signal);
4451
let account_monitor_join_handle = tokio::spawn(account_monitor.run());
4552

4653
Services {
4754
account_monitor_handle,
4855
transaction_service_handle,
49-
services_fut: try_select_any([transaction_service_join_handle, account_monitor_join_handle]).boxed(),
56+
services_fut: try_select_any([
57+
transaction_service_join_handle,
58+
account_monitor_join_handle,
59+
template_monitor_join_handle,
60+
])
61+
.boxed(),
5062
}
5163
}
5264

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2025 The Tari Project
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
use log::error;
5+
use tari_common_types::types::PublicKey;
6+
use tari_dan_common_types::optional::IsNotFoundError;
7+
use tari_dan_wallet_sdk::{
8+
apis::key_manager,
9+
models::TransactionStatus,
10+
network::WalletNetworkInterface,
11+
storage::WalletStore,
12+
DanWalletSdk,
13+
};
14+
use tari_engine_types::commit_result::TransactionResult;
15+
use tari_shutdown::ShutdownSignal;
16+
17+
use crate::{notify::Notify, services::WalletEvent};
18+
19+
const LOG_TARGET: &str = "tari::dan::wallet_daemon::template_monitor";
20+
21+
pub struct TemplateMonitor<TStore, TNetworkInterface> {
22+
notify: Notify<WalletEvent>,
23+
wallet_sdk: DanWalletSdk<TStore, TNetworkInterface>,
24+
shutdown_signal: ShutdownSignal,
25+
}
26+
27+
impl<TStore, TNetworkInterface> TemplateMonitor<TStore, TNetworkInterface>
28+
where
29+
TStore: WalletStore,
30+
TNetworkInterface: WalletNetworkInterface,
31+
TNetworkInterface::Error: IsNotFoundError,
32+
{
33+
pub fn new(
34+
notify: Notify<WalletEvent>,
35+
wallet_sdk: DanWalletSdk<TStore, TNetworkInterface>,
36+
shutdown_signal: ShutdownSignal,
37+
) -> Self {
38+
Self {
39+
notify,
40+
wallet_sdk,
41+
shutdown_signal,
42+
}
43+
}
44+
45+
async fn handle_wallet_event(&self, event: WalletEvent) -> anyhow::Result<()> {
46+
if let WalletEvent::TransactionFinalized(event) = event {
47+
if matches!(event.status, TransactionStatus::Accepted) {
48+
if let TransactionResult::Accept(diff) = event.finalize.result {
49+
let templates_iter = diff.up_iter().filter_map(|(id, value)| {
50+
if let Some(template_address) = id.as_template() {
51+
if let Some(template) = value.clone().into_substate_value().as_template() {
52+
if let Some(key_index) = self.get_key_index_for_public_key(&template.author) {
53+
return Some((key_index, template_address));
54+
}
55+
}
56+
}
57+
None
58+
});
59+
for (key_index, template_addr) in templates_iter {
60+
if let Err(error) = self
61+
.wallet_sdk
62+
.template_api()
63+
.add_authored_template(key_index, template_addr.as_hash())
64+
.await
65+
{
66+
error!(target: LOG_TARGET, "Error saving template to authored ({template_addr:?}): {}", error);
67+
}
68+
}
69+
}
70+
}
71+
}
72+
Ok(())
73+
}
74+
75+
fn get_key_index_for_public_key(&self, author_public_key: &PublicKey) -> Option<u64> {
76+
if let Ok((key_index, _)) = self
77+
.wallet_sdk
78+
.key_manager_api()
79+
.get_key_for_public_key(key_manager::TRANSACTION_BRANCH, author_public_key)
80+
{
81+
return Some(key_index);
82+
}
83+
None
84+
}
85+
86+
pub async fn run(mut self) -> anyhow::Result<()> {
87+
let mut events_subscription = self.notify.subscribe();
88+
loop {
89+
tokio::select! {
90+
_ = self.shutdown_signal.wait() => {
91+
break Ok(());
92+
}
93+
94+
Ok(event) = events_subscription.recv() => {
95+
if let Err(error) = self.handle_wallet_event(event).await {
96+
error!(target: LOG_TARGET, "Error handling event: {}", error);
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}

applications/tari_dan_wallet_web_ui/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

applications/tari_dan_wallet_web_ui/src/App.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { useEffect } from "react";
3939
import { useAuthMethod } from "./api/hooks/useAuth";
4040
import AccessToken from "./routes/AccessToken/AccessToken";
4141
import { jwtDecode } from "jwt-decode";
42+
import Templates from "./routes/Templates/Templates";
4243

4344
export const breadcrumbRoutes = [
4445
{
@@ -101,6 +102,11 @@ export const breadcrumbRoutes = [
101102
path: "/settings",
102103
dynamic: false,
103104
},
105+
{
106+
label: "Templates",
107+
path: "/templates",
108+
dynamic: false,
109+
},
104110
];
105111

106112
const isTokenExpired = (token: any) => {
@@ -229,6 +235,10 @@ function App() {
229235
path="settings"
230236
element={<GuardedRoute isAuthenticated={isAuthenticated} redirect="/settings" component={SettingsPage} />}
231237
/>
238+
<Route
239+
path="templates"
240+
element={<GuardedRoute isAuthenticated={isAuthenticated} redirect="/templates" component={Templates} />}
241+
/>
232242
<Route path="*" element={<ErrorPage />} />
233243
</Route>
234244
</Routes>

applications/tari_dan_wallet_web_ui/src/Components/CopyToClipboard.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
2121
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2222

23-
import {useState} from "react";
23+
import { useState } from "react";
2424
import IconButton from "@mui/material/IconButton";
2525
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
2626
import Tooltip from "@mui/material/Tooltip";
@@ -29,11 +29,11 @@ interface CopyProps {
2929
copy: string;
3030
floatright?: boolean;
3131
title?: string;
32-
iconWidth?: string,
33-
iconHeight?: string,
32+
iconWidth?: string;
33+
iconHeight?: string;
3434
}
3535

36-
const CopyToClipboard = ({ copy, floatright, title, iconWidth="16px", iconHeight="16px" }: CopyProps) => {
36+
const CopyToClipboard = ({ copy, floatright, title, iconWidth = "16px", iconHeight = "16px" }: CopyProps) => {
3737
const [tooltip, setTooltip] = useState<string | null>(null);
3838
const handleClick = async (copyThis: string) => {
3939
try {

applications/tari_dan_wallet_web_ui/src/Components/MenuItems.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,13 @@ import { NavLink } from "react-router-dom";
2424
import ListItemButton from "@mui/material/ListItemButton";
2525
import ListItemIcon from "@mui/material/ListItemIcon";
2626
import ListItemText from "@mui/material/ListItemText";
27-
import {
28-
IoHomeOutline,
29-
IoHome,
30-
IoBarChartOutline,
31-
IoBarChart,
32-
IoKeyOutline,
33-
IoKey,
34-
IoWalletOutline,
35-
IoWallet,
36-
IoTicketOutline,
37-
IoTicket,
38-
IoSettingsOutline,
39-
IoSettings,
40-
} from "react-icons/io5";
27+
import { IoHome, IoHomeOutline, IoSettings, IoSettingsOutline } from "react-icons/io5";
4128
import Tooltip from "@mui/material/Tooltip";
4229
import Fade from "@mui/material/Fade";
4330
import ThemeSwitcher from "./ThemeSwitcher";
4431
import Box from "@mui/material/Box";
4532
import { useTheme } from "@mui/material/styles";
33+
import { LuLayoutTemplate } from "react-icons/lu";
4634

4735
function MainListItems() {
4836
const theme = useTheme();
@@ -89,6 +77,12 @@ function MainListItems() {
8977
// activeIcon: <IoTicket style={activeIconStyle} />,
9078
// link: 'access-tokens',
9179
// },
80+
{
81+
title: "Templates",
82+
icon: <LuLayoutTemplate style={iconStyle} />,
83+
activeIcon: <LuLayoutTemplate style={activeIconStyle} />,
84+
link: "templates",
85+
},
9286
{
9387
title: "Settings",
9488
icon: <IoSettingsOutline style={iconStyle} />,
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
// Copyright 2025 The Tari Project
22
// SPDX-License-Identifier: BSD-3-Clause
33

4-
import {useQuery} from "@tanstack/react-query";
5-
import {authGetMethod} from "../../utils/json_rpc";
6-
import {ApiError} from "../helpers/types";
4+
import { useQuery } from "@tanstack/react-query";
5+
import { authGetMethod } from "../../utils/json_rpc";
6+
import { ApiError } from "../helpers/types";
77

88
export const useAuthMethod = () => {
9-
return useQuery({
10-
queryKey: ["auth_method"],
11-
queryFn: () => {
12-
return authGetMethod();
13-
},
14-
onError: (error: ApiError) => {
15-
error;
16-
},
17-
});
18-
};
9+
return useQuery({
10+
queryKey: ["auth_method"],
11+
queryFn: () => {
12+
return authGetMethod();
13+
},
14+
onError: (error: ApiError) => {
15+
error;
16+
},
17+
});
18+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2025 The Tari Project
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
import { useQuery } from "@tanstack/react-query";
5+
import { ApiError } from "../helpers/types";
6+
import { templatesListAuthored } from "../../utils/json_rpc";
7+
import { TemplatesListAuthoredRequest } from "@tari-project/typescript-bindings";
8+
9+
export const useListTemplatesAuthored = (request: TemplatesListAuthoredRequest) => {
10+
return useQuery({
11+
queryKey: ["templates_list_authored", request],
12+
queryFn: () => {
13+
return templatesListAuthored(request);
14+
},
15+
onError: (error: ApiError) => {
16+
error;
17+
},
18+
refetchInterval: false,
19+
notifyOnChangeProps: ["data", "error"],
20+
retryOnMount: false,
21+
retry: false,
22+
});
23+
};

0 commit comments

Comments
 (0)