Skip to content

Commit

Permalink
Detect Umbra relayer/API version change and force reload (#653)
Browse files Browse the repository at this point in the history
* Detect Umbra relayer/API version change and force reload

* Change reload alert to a more user-oriented message

* Removed superfluous typedef on apiVersionfromSettings

* Updated localhost comment for clarity
  • Loading branch information
jferas committed Apr 25, 2024
1 parent a359a8e commit 5e5239d
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 5 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/AccountReceiveTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ function useReceivedFundsTable(userAnnouncements: Ref<UserAnnouncement[]>, spend
const getFeeEstimate = async (tokenAddress: string) => {
if (isNativeToken(tokenAddress)) {
// no fee for native token
activeFee.value = { fee: '0', token: NATIVE_TOKEN.value };
activeFee.value = { umbraApiVersion: { major: 0, minor: 0, patch: 0 }, fee: '0', token: NATIVE_TOKEN.value };
return;
}
isFeeLoading.value = true;
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/components/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,24 +170,31 @@ export interface CnsQueryResponse {

// Relayer types
export type ApiError = { error: string };
export interface UmbraApiVersion {
major: number;
minor: number;
patch: number;
}

export interface TokenInfoExtended extends TokenInfo {
minSendAmount: string;
}
// Omit the TokenList.tokens type so we can override it with our own.
export interface TokenListSuccessResponse extends Omit<TokenList, 'tokens'> {
umbraApiVersion: UmbraApiVersion;
nativeTokenMinSendAmount: string;
tokens: TokenInfoExtended[];
}
export type TokenListResponse = TokenListSuccessResponse | ApiError;
export type FeeEstimate = { fee: string; token: TokenInfo };
export type FeeEstimate = { umbraApiVersion: UmbraApiVersion; fee: string; token: TokenInfo };
export type FeeEstimateResponse = FeeEstimate | ApiError;
export type WithdrawalInputs = {
stealthAddr: string;
acceptor: string;
signature: string;
sponsorFee: string;
};
export type RelayResponse = { relayTransactionHash: string } | ApiError;
export type RelayResponse = { umbraApiVersion: UmbraApiVersion; relayTransactionHash: string } | ApiError;

export type SendTableMetadataRow = {
dateSent: string;
Expand Down
23 changes: 22 additions & 1 deletion frontend/src/store/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { computed, onMounted, ref } from 'vue';
import { Dark, LocalStorage } from 'quasar';
import { isHexString } from 'src/utils/ethers';
import { i18n } from '../boot/i18n';
import { Language } from '../components/models';
import { Language, UmbraApiVersion } from '../components/models';

// Local storage key names
const settings = {
Expand All @@ -11,6 +11,7 @@ const settings = {
lastWallet: 'last-wallet',
language: 'language',
sendHistorySave: 'send-history-save',
UmbraApiVersion: 'umbra-api-version',
};

// Shared state between instances
Expand Down Expand Up @@ -119,6 +120,23 @@ export default function useSettingsStore() {
scanPrivateKey.value = undefined;
}

function getUmbraApiVersion(): UmbraApiVersion | null {
const storedVersion = LocalStorage.getItem(settings.UmbraApiVersion);
if (storedVersion) {
return storedVersion as UmbraApiVersion;
} else {
return null;
}
}

function setUmbraApiVersion(version: UmbraApiVersion) {
LocalStorage.set(settings.UmbraApiVersion, version);
}

function clearUmbraApiVersion() {
LocalStorage.remove(settings.UmbraApiVersion);
}

return {
toggleDarkMode,
toggleAdvancedMode,
Expand All @@ -137,5 +155,8 @@ export default function useSettingsStore() {
endBlock: computed(() => endBlock.value),
scanPrivateKey: computed(() => scanPrivateKey.value),
lastWallet: computed(() => lastWallet.value),
getUmbraApiVersion,
setUmbraApiVersion,
clearUmbraApiVersion,
};
}
33 changes: 32 additions & 1 deletion frontend/src/utils/umbra-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,42 @@ import {
RelayResponse,
TokenListResponse,
WithdrawalInputs,
UmbraApiVersion,
} from 'components/models';
import { jsonFetch } from 'src/utils/utils';

import useSettingsStore from 'src/store/settings';

const { getUmbraApiVersion, setUmbraApiVersion, clearUmbraApiVersion } = useSettingsStore();

export class UmbraApi {
// use 'http://localhost:3000' for baseUrl value for testing with a local Umbra API
static baseUrl = 'https://mainnet.api.umbra.cash'; // works for all networks
constructor(
readonly tokens: TokenInfoExtended[],
readonly chainId: number,
readonly nativeTokenMinSendAmount: string | undefined
) {}

static checkUmbraApiVersion(version: UmbraApiVersion) {
const apiVersionFromSettings = getUmbraApiVersion();
if (!apiVersionFromSettings) {
console.log(`UmbraAPI: no saved version setting, using backend value: ${version.major}.${version.minor}`);
setUmbraApiVersion(version);
} else {
console.log(`UmbraAPI: major.minor version fetched from backend: ${version.major}.${version.minor}`);
console.log(
`UmbraAPI: major.minor version fetched from setting: ${apiVersionFromSettings.major}.${apiVersionFromSettings.minor}`
);
if (apiVersionFromSettings.major != version.major || apiVersionFromSettings.minor != version.minor) {
console.log('UmbraAPI: version mismatch, clearing settings and going to force a page reload');
clearUmbraApiVersion();
alert('UmbraAPI: version outdated, please reload the page');
window.location.reload();
}
}
}

static async create(provider: Provider | StaticJsonRpcProvider) {
// Get API URL based on chain ID
const chainId = (await provider.getNetwork()).chainId;
Expand All @@ -37,6 +62,7 @@ export class UmbraApi {
} else {
tokens = data.tokens;
nativeMinSend = data.nativeTokenMinSendAmount;
UmbraApi.checkUmbraApiVersion(data.umbraApiVersion);
}

// Return instance, using an empty array of tokens if we could not fetch them from
Expand All @@ -48,6 +74,7 @@ export class UmbraApi {
const response = await fetch(`${UmbraApi.baseUrl}/tokens/${tokenAddress}/estimate?chainId=${this.chainId}`);
const data = (await response.json()) as FeeEstimateResponse;
if ('error' in data) throw new Error(`Could not estimate fee: ${data.error}`);
UmbraApi.checkUmbraApiVersion(data.umbraApiVersion);
return data;
}

Expand All @@ -58,12 +85,16 @@ export class UmbraApi {
const response = await fetch(url, { method: 'POST', body, headers });
const data = (await response.json()) as RelayResponse;
if ('error' in data) throw new Error(`Could not relay withdraw: ${data.error}`);
UmbraApi.checkUmbraApiVersion(data.umbraApiVersion);
return data;
}

static async isGitcoinContributor(address: string) {
return (await jsonFetch(`${this.baseUrl}/addresses/${address}/is-gitcoin-contributor`)) as {
const response = (await jsonFetch(`${this.baseUrl}/addresses/${address}/is-gitcoin-contributor`)) as {
umbraApiVersion: UmbraApiVersion;
isContributor: boolean;
};
UmbraApi.checkUmbraApiVersion(response.umbraApiVersion);
return response;
}
}

0 comments on commit 5e5239d

Please sign in to comment.