Skip to content

Commit

Permalink
chore: make syncing more resilient
Browse files Browse the repository at this point in the history
  • Loading branch information
cjkoepke committed Jan 16, 2025
1 parent 31770f9 commit f6f39b2
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 112 deletions.
8 changes: 2 additions & 6 deletions lib/src/@types/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IAssetAmountMetadata } from "@sundaeswap/asset";

import { WalletBalanceMap } from "../classes/WalletBalanceMap.class.js";
import { IWalletObserverSync } from "./observer.js";

/**
* A list of observer events that a client can hook into.
Expand All @@ -24,12 +25,7 @@ export interface IWalletObserverEventValues<
[EWalletObserverEvents.SYNCING_WALLET_START]: undefined;
[EWalletObserverEvents.SYNCING_WALLET_END]:
| undefined
| {
balanceMap: WalletBalanceMap<T>;
network: number;
usedAddresses: string[];
unusedAddresses: string[];
};
| IWalletObserverSync<T>;
[EWalletObserverEvents.CONNECT_WALLET_START]: undefined;
[EWalletObserverEvents.CONNECT_WALLET_END]: undefined;
[EWalletObserverEvents.GET_BALANCE_MAP_START]: undefined;
Expand Down
11 changes: 6 additions & 5 deletions lib/src/@types/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ export type TGetPeerConnectInstance = () => {
export interface IWalletObserverSync<
AssetMetadata extends IAssetAmountMetadata = IAssetAmountMetadata,
> {
balanceMap: WalletBalanceMap<AssetMetadata>;
usedAddresses: string[];
unusedAddresses: string[];
utxos?: TransactionUnspentOutput[];
network: number;
balanceMap: Error | WalletBalanceMap<AssetMetadata>;
usedAddresses: Error | string[];
unusedAddresses: Error | string[];
utxos?: Error | TransactionUnspentOutput[];
collateral?: Error | TransactionUnspentOutput[];
network: Error | number;
}
122 changes: 83 additions & 39 deletions lib/src/classes/WalletObserver.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,16 @@ export class WalletObserver<
this._performingSync = true;
this.dispatch(EWalletObserverEvents.SYNCING_WALLET_START);

let newNetwork: number;
try {
let newNetwork = await this.getNetwork();
if (newNetwork instanceof Error) {
await this.syncApi();
newNetwork = await this.getNetwork();
} catch (e) {
try {
await this.syncApi();
newNetwork = await this.getNetwork();
} catch (e) {

if (newNetwork instanceof Error) {
this.dispatch(EWalletObserverEvents.SYNCING_WALLET_END);
this.dispatch(EWalletObserverEvents.CONNECT_WALLET_END);
this._performingSync = false;
throw e;
throw newNetwork;
}
}

Expand Down Expand Up @@ -312,9 +310,16 @@ export class WalletObserver<

this.activeWallet = extension;
if (this._options.persistence && api) {
const addresses = await this.getUsedAddresses();
if (addresses instanceof Error) {
addresses.cause =
"Could not get a list of used addresses from the wallet when trying to save the connection.";
throw addresses;
}

const seed: IWalletObserverSeed = {
activeWallet: extension,
mainAddress: (await this.getUsedAddresses())[0],
mainAddress: addresses[0],
};

window.localStorage.setItem(
Expand Down Expand Up @@ -375,20 +380,27 @@ export class WalletObserver<
*
* @returns {Promise<WalletBalanceMap<AssetMetadata>>} - A promise that resolves to a map of asset amounts keyed by asset IDs.
*/
getBalanceMap = async (): Promise<WalletBalanceMap<AssetMetadata>> => {
getBalanceMap = async (): Promise<
WalletBalanceMap<AssetMetadata> | Error
> => {
if (!this.api) {
throw new Error("Attempted to query balance without an API instance.");
}

const start = performance.now();

this.dispatch(EWalletObserverEvents.GET_BALANCE_MAP_START);
const [cbor, { Serialization }, typedHex] = await Promise.all([
this.api.getBalance(),
const [{ Serialization }, typedHex] = await Promise.all([
getCardanoCore(),
getCardanoUtil(),
]);

let cbor: string;
try {
cbor = await this.api.getBalance();
} catch (e) {
return e as Error;
}
const data = Serialization.Value.fromCbor(typedHex(cbor));
const multiassetKeys = data.multiasset()?.keys() ?? [];

Expand Down Expand Up @@ -421,24 +433,30 @@ export class WalletObserver<
if (this._options.debug) {
console.log(`getBalanceMap: ${end - start}ms`);
}

return balanceMap;
};

/**
* Gets the current network connection.
*
* @returns {Promise<number>} The network ID.
* @returns {Promise<number | Error>} The network ID or an Error from the wallet.
*/
getNetwork = async (): Promise<number> => {
getNetwork = async (): Promise<number | Error> => {
if (!this.api) {
throw new Error("Attempted to query network without an API instance.");
}

const start = performance.now();

const val = await this.api.getNetworkId();
this.network = val;
let val: number;
try {
val = await this.api.getNetworkId();
} catch (e) {
return e as Error;
}

this.network = val;
const end = performance.now();
if (this._options.debug) {
console.log(`getNetwork: ${end - start}ms`);
Expand All @@ -451,7 +469,7 @@ export class WalletObserver<
*
* @returns {Promise<string[]>} The list of addresses.
*/
getUsedAddresses = async (): Promise<string[]> => {
getUsedAddresses = async (): Promise<string[] | Error> => {
if (!this.api) {
throw new Error(
"Attempted to query used addresses without an API instance.",
Expand All @@ -460,12 +478,18 @@ export class WalletObserver<

const start = performance.now();

const [cbor, { Cardano }, typedHex] = await Promise.all([
this.api.getUsedAddresses(),
const [{ Cardano }, typedHex] = await Promise.all([
getCardanoCore(),
getCardanoUtil(),
]);

let cbor: string[];
try {
cbor = await this.api.getUsedAddresses();
} catch (e) {
return e as Error;
}

const data = cbor.map((val) =>
Cardano.Address.fromBytes(typedHex(val)).toBech32(),
);
Expand All @@ -480,9 +504,9 @@ export class WalletObserver<
/**
* Gets a list of unused addresses, encoded as Bech32.
*
* @returns {Promise<string[]>} The list of addresses.
* @returns {Promise<string[] | Error>} The list of addresses or an Error returned by the wallet.
*/
getUnusedAddresses = async (): Promise<string[]> => {
getUnusedAddresses = async (): Promise<string[] | Error> => {
if (!this.api) {
throw new Error(
"Attempted to query unused addresses without an API instance.",
Expand All @@ -491,12 +515,18 @@ export class WalletObserver<

const start = performance.now();

const [cbor, { Cardano }, typedHex] = await Promise.all([
this.api.getUnusedAddresses(),
const [{ Cardano }, typedHex] = await Promise.all([
getCardanoCore(),
getCardanoUtil(),
]);

let cbor: string[];
try {
cbor = await this.api.getUnusedAddresses();
} catch (e) {
return e as Error;
}

const data = cbor.map((val) =>
Cardano.Address.fromBytes(typedHex(val)).toBech32(),
);
Expand All @@ -513,19 +543,27 @@ export class WalletObserver<
*
* @returns {Promise<TransactionUnspentOutput[]>} The list of TransactionUnspentOutputs.
*/
getUtxos = async (): Promise<TransactionUnspentOutput[] | undefined> => {
getUtxos = async (): Promise<
TransactionUnspentOutput[] | Error | undefined
> => {
if (!this.api) {
throw new Error("Attempted to query UTXOs without an API instance.");
}

const start = performance.now();

const [cbor, { Serialization }, typedHex] = await Promise.all([
this.api.getUtxos(),
const [{ Serialization }, typedHex] = await Promise.all([
getCardanoCore(),
getCardanoUtil(),
]);

let cbor: string[] | null;
try {
cbor = await this.api.getUtxos();
} catch (e) {
return e as Error;
}

const data = cbor?.map((val) => {
const txOutput = Serialization.TransactionUnspentOutput.fromCbor(
typedHex(val),
Expand All @@ -547,30 +585,36 @@ export class WalletObserver<
/**
* Gets a list of wallet UTXOs suitable for collateral.
*
* @returns {Promise<TransactionUnspentOutput[]>} The list of TransactionUnspentOutputs.
* @returns {Promise<TransactionUnspentOutput[] | Error | undefined>} The list of TransactionUnspentOutputs, if there are any, or an Error.
*/
getCollateral = async (): Promise<TransactionUnspentOutput[] | undefined> => {
getCollateral = async (): Promise<
TransactionUnspentOutput[] | Error | undefined
> => {
if (!this.api) {
throw new Error("Attempted to query UTXOs without an API instance.");
}

const start = performance.now();

const [cbor, { Serialization }, typedHex] = await Promise.all([
(async () => {
const funcCall =
this.api?.getCollateral ||
(this.api?.experimental.getCollateral as GetCollateral);
if (typeof funcCall !== "function") {
return [];
}

return await funcCall();
})(),
const [{ Serialization }, typedHex] = await Promise.all([
getCardanoCore(),
getCardanoUtil(),
]);

let cbor: string[] | null;
try {
const funcCall =
this.api?.getCollateral ||
(this.api?.experimental.getCollateral as GetCollateral);
if (typeof funcCall !== "function") {
cbor = [];
} else {
cbor = await funcCall();
}
} catch (e) {
return e as Error;
}

const data = cbor?.map((val) => {
const txOutput = Serialization.TransactionUnspentOutput.fromCbor(
typedHex(val),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const WalletObserverProvider: FC<

const derivedState = useDerivedState(observerRef.current, {
usedAddresses: state.usedAddresses,
unusedAddresses: state.unusedAddresses,
});

// Memoize the context value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ import { useWalletObserverState } from "../useWalletObserverState.js";

export const useDerivedState = (
observer: WalletObserver,
state: Pick<ReturnType<typeof useWalletObserverState>, "usedAddresses">,
state: Pick<
ReturnType<typeof useWalletObserverState>,
"usedAddresses" | "unusedAddresses"
>,
) => {
const [stakeAddress, setStakeAddress] = useState<string>();

useEffect(() => {
if (state.usedAddresses.length === 0) {
return;
}

observer.getUtils().then((utils) => {
setStakeAddress(utils.getBech32StakingAddress(state.usedAddresses[0]));
setStakeAddress(
utils.getBech32StakingAddress(
state.usedAddresses[0] || state.unusedAddresses[0],
),
);
});
}, [observer, state.usedAddresses[0]]);
}, [observer, state.usedAddresses[0], state.unusedAddresses[0]]);

const memoizedDerivedState = useMemo(() => {
let mainAddress = state.usedAddresses[0];
let mainAddress = state.usedAddresses[0] || state.unusedAddresses[0];
const persistentCache = window.localStorage.getItem(
WalletObserver.PERSISTENCE_CACHE_KEY,
);
Expand All @@ -35,7 +38,12 @@ export const useDerivedState = (
stakeAddress,
mainAddress,
};
}, [observer, state.usedAddresses[0], stakeAddress]);
}, [
observer,
state.usedAddresses[0],
state.unusedAddresses[0],
stakeAddress,
]);

return memoizedDerivedState;
};
Loading

0 comments on commit f6f39b2

Please sign in to comment.