Skip to content

Commit

Permalink
chore: kickoff LLD integration of wallet sync
Browse files Browse the repository at this point in the history
  • Loading branch information
gre committed Jul 11, 2024
1 parent 802329e commit 4b95926
Show file tree
Hide file tree
Showing 15 changed files with 389 additions and 136 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
import { useWatchWalletSync, WalletSyncUserState } from "../hooks/useWatchWalletSync";

export const WalletSyncContext = React.createContext<WalletSyncUserState>({
visualPending: false,
walletSyncError: null,
onUserRefresh: () => {},
});

export const useWalletSyncUserState = () => React.useContext(WalletSyncContext);

export default function WalletSyncProvider({ children }: { children: React.ReactNode }) {
const walletSyncState = useWatchWalletSync();
return (
<WalletSyncContext.Provider value={walletSyncState}>{children}</WalletSyncContext.Provider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function useDestroyTrustchain() {
onSuccess: () => {
dispatch(setFlow({ flow: Flow.ManageBackup, step: Step.BackupDeleted }));
dispatch(resetTrustchainStore());
// TODO also delete the wallet sync data
},
onError: () => dispatch(setFlow({ flow: Flow.ManageBackup, step: Step.BackupDeletionError })),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { getEnv } from "@ledgerhq/live-env";
import { getSdk } from "@ledgerhq/trustchain/index";
import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess";
import Transport from "@ledgerhq/hw-transport";
import { trustchainLifecycle } from "@ledgerhq/live-wallet/walletsync";
import { useStore } from "react-redux";
import { walletSelector } from "~/renderer/reducers/wallet";

export function runWithDevice<T>(
deviceId: string | undefined,
Expand All @@ -28,7 +31,11 @@ export function useTrustchainSdk() {
const name = `${platformMap[platform] || platform}${hash ? " " + hash : ""}`;
return { applicationId, name };
}, []);
const sdk = getSdk(isMockEnv, defaultContext);
const store = useStore();
const lifecycle = trustchainLifecycle({
getCurrentWSState: () => walletSelector(store.getState()).wsState,
});
const sdk = getSdk(isMockEnv, defaultContext, lifecycle);

return sdk;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector, useStore } from "react-redux";
import { CloudSyncSDK, UpdateEvent } from "@ledgerhq/live-wallet/cloudsync/index";
import walletsync, {
liveSlug,
DistantState,
walletSyncWatchLoop,
LocalState,
} from "@ledgerhq/live-wallet/walletsync/index";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
import { useTrustchainSdk } from "./useTrustchainSdk";
import { walletSelector } from "~/renderer/reducers/wallet";
import { Trustchain } from "@ledgerhq/trustchain/types";
import {
memberCredentialsSelector,
trustchainSelector,
setTrustchain,
} from "@ledgerhq/trustchain/store";
import { TrustchainEjected } from "@ledgerhq/trustchain/errors";
import { State } from "~/renderer/reducers";
import { cache as bridgeCache } from "~/renderer/bridge/cache";
import { setAccountNames, walletSyncUpdate } from "@ledgerhq/live-wallet/store";
import { replaceAccounts } from "~/renderer/actions/accounts";

function localStateSelector(state: State): LocalState {
return {
accounts: {
list: state.accounts,
},
accountNames: state.wallet.accountNames,
};
}

function latestDistantStateSelector(state: State): DistantState | null {
return walletSelector(state).wsState.data;
}

export type WalletSyncUserState = {
visualPending: boolean;
walletSyncError: Error | null;
onUserRefresh: () => void;
};

export function useWatchWalletSync(): WalletSyncUserState {
const memberCredentials = useSelector(memberCredentialsSelector);
const trustchain = useSelector(trustchainSelector);

const trustchainSdk = useTrustchainSdk();

const store = useStore();
const dispatch = useDispatch();

const getCurrentVersion = useCallback(() => {
const state = walletSelector(store.getState());
return state.wsState.version;
}, [store]);

const onTrustchainRefreshNeeded = useCallback(
async (trustchain: Trustchain) => {
try {
if (!memberCredentials) return;
const newTrustchain = await trustchainSdk.restoreTrustchain(trustchain, memberCredentials);
setTrustchain(newTrustchain);
} catch (e) {
if (e instanceof TrustchainEjected) {
// TODO: eject the trustchain
// dispatch(resetTrustchainStore());
}
}
},
[trustchainSdk, memberCredentials],
);

const saveNewUpdate = useCallback(
async (event: UpdateEvent<DistantState>) => {
switch (event.type) {
case "new-data": {
const state = store.getState();
const version = event.version;
const data = event.data;
const wsState = walletSelector(state).wsState;
const localState = localStateSelector(state);
const ctx = { getAccountBridge, bridgeCache, blacklistedTokenIds: [] };

console.log("<- incoming data to resolve", data);
const resolved = await walletsync.resolveIncomingDistantState(
ctx,
localState,
wsState.data,
data,
);

if (resolved.hasChanges) {
console.log("resolved as", resolved);

const s = store.getState();
const localState = localStateSelector(s);
const newLocalState = walletsync.applyUpdate(localState, resolved.update);
console.log("new update applied as", newLocalState);

dispatch(setAccountNames(newLocalState.accountNames));
dispatch(walletSyncUpdate(data, version));
dispatch(replaceAccounts(newLocalState.accounts.list));
} else {
console.log("resolved. no changes to apply.");
}
break;
}
case "pushed-data": {
dispatch(walletSyncUpdate(event.data, event.version));
break;
}
case "deleted-data": {
console.log("deleted data");
dispatch(walletSyncUpdate(null, 0));
break;
}
}
},
[store, dispatch],
);

const walletSyncSdk = useMemo(
() =>
new CloudSyncSDK({
slug: liveSlug,
schema: walletsync.schema,
trustchainSdk,
getCurrentVersion,
saveNewUpdate,
onTrustchainRefreshNeeded,
}),
[trustchainSdk, getCurrentVersion, saveNewUpdate, onTrustchainRefreshNeeded],
);

const [visualPending, setVisualPending] = useState(true);
const [walletSyncError, setWalletSyncError] = useState<Error | null>(null);
const [onUserRefresh, setOnUserRefresh] = useState<() => void>(() => () => {});

// pull and push wallet sync loop
useEffect(() => {
if (!trustchain || !memberCredentials) {
setOnUserRefresh(() => () => {});
return;
}

const { unsubscribe, onUserRefreshIntent } = walletSyncWatchLoop({
walletSyncSdk,
trustchain,
memberCredentials,
setVisualPending,
getState: () => store.getState(),
localStateSelector,
latestDistantStateSelector,
onError: e => setWalletSyncError(e && e instanceof Error ? e : new Error(String(e))),
onStartPolling: () => setWalletSyncError(null),
});

setOnUserRefresh(() => onUserRefreshIntent);

return unsubscribe;
}, [store, trustchainSdk, walletSyncSdk, trustchain, memberCredentials]);

const state = useMemo(
() => ({ visualPending, walletSyncError, onUserRefresh }),
[visualPending, walletSyncError, onUserRefresh],
);

return state;
}
Loading

0 comments on commit 4b95926

Please sign in to comment.