('');
+
+ const handleFileUpload = useCallback(async () => {
+ console.info('handle file upload path ', selectedFilePath);
+ invoke('upload_wasm_file', { file: selectedFilePath }).catch((e) => console.error('Failed to upload', e));
+ }, [selectedFilePath]);
+
+ const handleFileSelect = useCallback(async () => {
+ console.info('handle file select ');
+ const file = await open({
+ multiple: false,
+ directory: false,
+ });
+ console.info('handle file', file);
+ if (file) {
+ setSelectedFilePath(file);
+ }
+ console.info('handle file upload file path', file);
+ }, []);
+
+ return (
+
+ {'Upload wasm file'}
+ {`Path: ${selectedFilePath}`}
+
+ {'Select wasm file'}
+
+
+ {'Upload wasm file'}
+
+
+ );
+}
+
+export default TemplateFileUploader;
diff --git a/src/containers/floating/Settings/types.ts b/src/containers/floating/Settings/types.ts
index 500fda67d..eb4b515e8 100644
--- a/src/containers/floating/Settings/types.ts
+++ b/src/containers/floating/Settings/types.ts
@@ -7,6 +7,8 @@ export const SETTINGS_TYPES = [
'connections',
'experimental',
'releaseNotes',
+ 'ootle',
+ 'ootleWallet',
] as const;
type SettingsTuple = typeof SETTINGS_TYPES;
export type SettingsType = SettingsTuple[number];
diff --git a/src/containers/floating/TappletTransactionDialog/TappletTransactionDialog.styles.ts b/src/containers/floating/TappletTransactionDialog/TappletTransactionDialog.styles.ts
new file mode 100644
index 000000000..3e1535534
--- /dev/null
+++ b/src/containers/floating/TappletTransactionDialog/TappletTransactionDialog.styles.ts
@@ -0,0 +1,9 @@
+import styled from 'styled-components';
+
+export const ButtonsWrapper = styled.div(() => ({
+ display: 'flex',
+ justifyContent: 'right',
+ marginTop: '16px',
+ alignItems: 'center',
+ gap: '16px',
+}));
diff --git a/src/containers/floating/TappletTransactionDialog/TappletTransactionDialog.tsx b/src/containers/floating/TappletTransactionDialog/TappletTransactionDialog.tsx
new file mode 100644
index 000000000..6b441be8d
--- /dev/null
+++ b/src/containers/floating/TappletTransactionDialog/TappletTransactionDialog.tsx
@@ -0,0 +1,69 @@
+import { useTranslation } from 'react-i18next';
+
+import { useUIStore } from '@app/store/useUIStore';
+
+import { DialogContent, Dialog } from '@app/components/elements/dialog/Dialog';
+import { SquaredButton } from '@app/components/elements/buttons/SquaredButton';
+import { Typography } from '@app/components/elements/Typography';
+import { memo, useCallback, useState } from 'react';
+import { ButtonsWrapper } from './TappletTransactionDialog.styles';
+import { useTappletProviderStore } from '@app/store/useTappletProviderStore';
+
+const TappletTransactionDialog = memo(function AutoUpdateDialog() {
+ const { t } = useTranslation('setup-view', { useSuspense: false }); //TODO add transaltion
+ const open = useUIStore((s) => s.dialogToShow === 'txSimulation');
+ const setDialogToShow = useUIStore((s) => s.setDialogToShow);
+ const [maxFee, setMaxFee] = useState(0);
+ const getPendingTransaction = useTappletProviderStore((s) => s.getPendingTransaction);
+ const tx = getPendingTransaction();
+
+ const handleClose = useCallback(() => {
+ console.info('Tx cancelled');
+ console.warn('Cancel TX', tx);
+ setMaxFee(0);
+ setDialogToShow(null);
+ if (!tx) return;
+ tx.cancel();
+ }, [setDialogToShow, tx]);
+
+ const handleSubmit = useCallback(async () => {
+ const isDryRun = maxFee == 0;
+ console.warn('SUBMIT run TX', tx);
+ if (!tx) return;
+ if (isDryRun) {
+ const { balanceUpdates, txSimulation, estimatedFee } = await tx.runSimulation();
+ console.warn('SIIIIMULATION RES TX', txSimulation);
+ if (estimatedFee) setMaxFee(estimatedFee);
+ console.warn('SIIIIMULATION RES BALANCES', balanceUpdates);
+ console.warn('SIIIIMULATION RES FEE', estimatedFee);
+ return;
+ } else {
+ const result = await tx.submit();
+ console.warn('TX submit result', result);
+ setMaxFee(0);
+ setDialogToShow(null);
+ }
+ }, [maxFee, setDialogToShow, tx]);
+
+ return (
+
+ );
+});
+
+export default TappletTransactionDialog;
diff --git a/src/containers/main/Dashboard/Dashboard.tsx b/src/containers/main/Dashboard/Dashboard.tsx
index 2585a38c8..40139d90c 100644
--- a/src/containers/main/Dashboard/Dashboard.tsx
+++ b/src/containers/main/Dashboard/Dashboard.tsx
@@ -1,10 +1,16 @@
+import { useAppConfigStore } from '@app/store/useAppConfigStore';
import MiningView from './MiningView/MiningView';
import { DashboardContentContainer } from './styles';
+import { useTappletsStore } from '@app/store/useTappletsStore';
+import ActiveTappletView from '@app/components/ootle/ActiveTappletView';
export default function Dashboard() {
+ const ootleMode = useAppConfigStore((s) => s.ootle_enabled);
+ const activeTapplet = useTappletsStore((s) => s.activeTapplet);
+
return (
-
-
+
+ {ootleMode && activeTapplet ? : }
);
}
diff --git a/src/containers/main/Dashboard/styles.ts b/src/containers/main/Dashboard/styles.ts
index f2eb4566c..1f4ca6bcb 100644
--- a/src/containers/main/Dashboard/styles.ts
+++ b/src/containers/main/Dashboard/styles.ts
@@ -1,7 +1,7 @@
import styled from 'styled-components';
import * as m from 'motion/react-m';
-export const DashboardContentContainer = styled(m.div)`
+export const DashboardContentContainer = styled(m.div)<{ $ootleModeOn?: boolean }>`
display: flex;
align-items: center;
flex-direction: column;
@@ -9,6 +9,7 @@ export const DashboardContentContainer = styled(m.div)`
height: 100%;
flex-grow: 1;
position: relative;
+ pointer-events: ${({ $ootleModeOn }) => ($ootleModeOn ? 'auto' : 'none')};
`;
export const ProgressWrapper = styled.div`
diff --git a/src/store/useAppConfigStore.ts b/src/store/useAppConfigStore.ts
index 57f770653..54eeb6f9e 100644
--- a/src/store/useAppConfigStore.ts
+++ b/src/store/useAppConfigStore.ts
@@ -36,6 +36,8 @@ interface Actions {
setShowExperimentalSettings: (showExperimentalSettings: boolean) => Promise;
setP2poolStatsServerPort: (port: number | null) => Promise;
setPreRelease: (preRelease: boolean) => Promise;
+ setOotleEnabled: (enabled: boolean) => void;
+ setOotleLocalNode: (enabled: boolean) => void;
}
type AppConfigStoreState = State & Actions;
@@ -66,6 +68,8 @@ const initialState: State = {
show_experimental_settings: false,
p2pool_stats_server_port: null,
pre_release: false,
+ ootle_enabled: false,
+ ootle_local_node: false,
};
export const useAppConfigStore = create()((set) => ({
@@ -303,6 +307,25 @@ export const useAppConfigStore = create()((set) => ({
set({ pre_release: !preRelease });
});
},
+ setOotleEnabled: (enabled) => {
+ set({ ootle_enabled: enabled });
+ invoke('set_ootle_enabled', { enabled }).catch((e) => {
+ const appStateStore = useAppStateStore.getState();
+ console.error('Could not set ootle local node enabled', e);
+ appStateStore.setError('Could not change ootle local node enabled');
+ set({ ootle_local_node: !enabled });
+ });
+ },
+ setOotleLocalNode: async (enabled) => {
+ console.info('setlocalootle', enabled);
+ set({ ootle_local_node: enabled });
+ invoke('set_ootle_node_enabled', { enabled }).catch((e) => {
+ const appStateStore = useAppStateStore.getState();
+ console.error('Could not set ootle local node enabled', e);
+ appStateStore.setError('Could not change ootle local node enabled');
+ set({ ootle_local_node: !enabled });
+ });
+ },
}));
export const fetchAppConfig = async () => {
diff --git a/src/store/useOotleWalletStore.ts b/src/store/useOotleWalletStore.ts
new file mode 100644
index 000000000..f3c54db79
--- /dev/null
+++ b/src/store/useOotleWalletStore.ts
@@ -0,0 +1,94 @@
+import { create } from './create.ts';
+import { useTappletProviderStore } from './useTappletProviderStore.ts';
+import { OotleAccount } from '@app/types/ootle/account.ts';
+import { AccountInfo } from '@tari-project/typescript-bindings';
+
+interface State {
+ ootleAccount?: OotleAccount;
+ ootleAccountsList: AccountInfo[];
+}
+
+interface Actions {
+ createAccount: (name: string) => Promise;
+ setDefaultAccount: (name: string) => Promise;
+ getOotleAccountInfo: () => Promise;
+ getOotleAccountsList: () => Promise;
+}
+
+type OotleWalletStoreState = State & Actions;
+
+const initialState: State = {
+ ootleAccount: undefined,
+ ootleAccountsList: [],
+};
+
+export const useOotleWalletStore = create()((set) => ({
+ ...initialState,
+ createAccount: async (name: string) => {
+ const provider = useTappletProviderStore.getState().tappletProvider;
+ try {
+ if (!provider) {
+ return;
+ }
+
+ const responseNewAcc = await provider.createFreeTestCoins(name);
+
+ console.info('created acc: ', responseNewAcc);
+ // this needs to be set manually
+ await provider.setDefaultAccount(name);
+ const account = await provider.getAccount();
+ set({
+ ootleAccount: account,
+ });
+ } catch (error) {
+ console.error('Could not create the new Ootle account: ', error);
+ }
+ },
+ setDefaultAccount: async (name: string) => {
+ const provider = useTappletProviderStore.getState().tappletProvider;
+ try {
+ if (!provider) {
+ return;
+ }
+ await provider.setDefaultAccount(name);
+ // if tapplet uses TU Provider it gets default account
+ // this is to make sure tapplet uses the account selected by the user
+ const account = await provider.getAccount();
+ set({
+ ootleAccount: account,
+ });
+ } catch (error) {
+ console.error('Could not set the default Ootle account: ', error);
+ }
+ },
+ getOotleAccountInfo: async () => {
+ const provider = useTappletProviderStore.getState().tappletProvider;
+ try {
+ if (!provider) {
+ return;
+ }
+ // if tapplet uses TU Provider it gets default account
+ // this is to make sure tapplet uses the account selected by the user
+ const account = await provider.getAccount();
+ set({
+ ootleAccount: account,
+ });
+ } catch (error) {
+ console.error('Could not get the Ootle account info: ', error);
+ }
+ },
+ getOotleAccountsList: async () => {
+ const provider = useTappletProviderStore.getState().tappletProvider;
+ try {
+ if (!provider) {
+ return;
+ }
+ const list = await provider.getAccountsList();
+ set({
+ ootleAccountsList: list.accounts,
+ });
+ } catch (error) {
+ console.error('Could not get ootle accounts list: ', error);
+ }
+ },
+}));
diff --git a/src/store/useTappletProviderStore.ts b/src/store/useTappletProviderStore.ts
new file mode 100644
index 000000000..1632dd2e7
--- /dev/null
+++ b/src/store/useTappletProviderStore.ts
@@ -0,0 +1,333 @@
+import { create } from './create.ts';
+import { ActiveTapplet } from '@app/types/ootle/tapplet.ts';
+import { useAppStateStore } from './appStateStore.ts';
+import { TappletProvider, TappletProviderParams } from '@app/types/ootle/TappletProvider.ts';
+import { toPermission } from '@app/types/ootle/tariPermissions.ts';
+import { TransactionEvent, TUTransaction, txCheck } from '@app/types/ootle/transaction.ts';
+import { TariPermissions } from '@tari-project/tari-permissions';
+import { FinalizeResult, SubmitTransactionRequest, TransactionStatus, UpSubstates } from '@tari-project/tarijs';
+import { useOotleWalletStore } from './useOotleWalletStore.ts';
+import { AccountsGetBalancesResponse } from '@tari-project/typescript-bindings';
+import { BalanceUpdate, TxSimulation, TxSimulationResult } from '@app/types/ootle/txSimulation.ts';
+
+interface State {
+ isInitialized: boolean;
+ tappletProvider?: TappletProvider;
+ transactions: Record;
+}
+
+//TODO do we need tapp provider id at all?
+interface Actions {
+ initTappletProvider: () => Promise;
+ setTappletProvider: (id: string, launchedTapplet: ActiveTapplet) => Promise;
+ addTransaction: (event: MessageEvent) => Promise;
+ getTransactionById: (id: number) => TUTransaction | undefined;
+ getPendingTransaction: () => TUTransaction | undefined;
+ runTransaction: (event: MessageEvent) => Promise;
+}
+
+type TappletProviderStoreState = State & Actions;
+
+const initialState: State = {
+ isInitialized: false,
+ tappletProvider: undefined,
+ transactions: {},
+};
+
+export const useTappletProviderStore = create()((set, get) => ({
+ ...initialState,
+ initTappletProvider: async () => {
+ try {
+ console.info(`🌎️ [TU store][init provider]`);
+
+ const params: TappletProviderParams = {
+ id: '0',
+ permissions: { requiredPermissions: [], optionalPermissions: [] },
+ };
+ const provider: TappletProvider = TappletProvider.build(params);
+
+ set({ isInitialized: true, tappletProvider: provider });
+ } catch (error) {
+ const appStateStore = useAppStateStore.getState();
+ console.error('Error setting tapplet provider: ', error);
+ appStateStore.setError(`Error setting tapplet provider: ${error}`);
+ }
+ },
+ setTappletProvider: async (id: string, launchedTapplet: ActiveTapplet) => {
+ try {
+ // TODO tmp solution
+ const requiredPermissions = new TariPermissions();
+ const optionalPermissions = new TariPermissions();
+ if (launchedTapplet.permissions) {
+ launchedTapplet.permissions.requiredPermissions.map((p) =>
+ requiredPermissions.addPermission(toPermission(p))
+ );
+ launchedTapplet.permissions.optionalPermissions.map((p) =>
+ optionalPermissions.addPermission(toPermission(p))
+ );
+ }
+ const params: TappletProviderParams = {
+ id,
+ permissions: launchedTapplet.permissions ?? { requiredPermissions: [], optionalPermissions: [] },
+ };
+ const provider: TappletProvider = TappletProvider.build(params);
+
+ set({ isInitialized: true, tappletProvider: provider });
+ } catch (error) {
+ const appStateStore = useAppStateStore.getState();
+ console.error('Error setting tapplet provider: ', error);
+ appStateStore.setError(`Error setting tapplet provider: ${error}`);
+ }
+ },
+ addTransaction: async (event: MessageEvent) => {
+ const { methodName, args, id } = event.data;
+ const provider = get().tappletProvider;
+ const appStateStore = useAppStateStore.getState();
+
+ const runSimulation = async (): Promise