From 1f7f9e383ca3516169a50edca7eacb8f9004b5b9 Mon Sep 17 00:00:00 2001 From: brenzi Date: Thu, 12 Dec 2024 14:09:27 +0100 Subject: [PATCH 1/7] session proxies (#100) * WIP * add session proxy works * guessing with NonTransfer session key works * fetch note history with sessionproxy if available * cleanup * do everything with session proxy if available * allow optout of session keys * logic for modifying session proxy role * modify proxy role possible * fix --- components/overlays/ChooseWalletOverlay.vue | 77 +++- components/overlays/SessionProxiesOverlay.vue | 384 ++++++++++++++++++ components/tabs/MessagingTab.vue | 10 +- components/tabs/VouchersTab.vue | 2 + components/tabs/WalletTab.vue | 40 +- configs/incognitee.ts | 2 + lib/sessionProxyStorage.ts | 34 ++ package.json | 4 +- pages/index.vue | 156 ++++++- store/account.ts | 78 ++++ store/notes.ts | 5 +- yarn.lock | 44 +- 12 files changed, 763 insertions(+), 73 deletions(-) create mode 100644 components/overlays/SessionProxiesOverlay.vue create mode 100644 lib/sessionProxyStorage.ts diff --git a/components/overlays/ChooseWalletOverlay.vue b/components/overlays/ChooseWalletOverlay.vue index 1a9ac0c..bf2b322 100644 --- a/components/overlays/ChooseWalletOverlay.vue +++ b/components/overlays/ChooseWalletOverlay.vue @@ -76,15 +76,37 @@ +
+

+ your currently selected account is {{ accountStore.getAddress }} +

+
+ +
+
-

+

please allow this app to read your balance by signing the upcoming request in your extension

-

this window will close once a balance could be fetched

+

+ this window will close once a balance could be fetched +

@@ -94,15 +116,35 @@ import { connectExtension, extensionAccounts, - injectorForAddress, } from "~/lib/signerExtensionUtils"; import OverlayDialog from "~/components/overlays/OverlayDialog.vue"; -import { defineProps, computed, ref, watch } from "vue"; +import { computed, defineProps, ref, watch } from "vue"; import { useAccount } from "~/store/account.ts"; +import { encodeAddress } from "@polkadot/util-crypto"; +import { SessionProxyRole } from "~/lib/sessionProxyStorage"; const accountStore = useAccount(); +const currentExtensionAccount = ref(""); const selectedExtensionAccount = ref(""); +const selectedExtensionAccountIsNew = computed(() => { + try { + const selectedAddressEncoded = encodeAddress( + selectedExtensionAccount.value, + accountStore.getSs58Format, + ); + console.log( + "comparing", + currentExtensionAccount.value, + " to ", + selectedAddressEncoded, + ); + return selectedAddressEncoded !== currentExtensionAccount.value; + } catch (e) { + return false; + } +}); + const props = defineProps({ createTestingAccount: { type: Function, @@ -124,7 +166,23 @@ const props = defineProps({ type: Boolean, required: false, }, + changeSessionAuthorization: { + type: Function, + required: false, + }, }); + +watch( + () => props.show, + (show) => { + if (show) { + console.log("current extension account: ", accountStore.getAddress); + currentExtensionAccount.value = accountStore.getAddress; + //selectedExtensionAccount.value = ""; + } + }, +); + const hasCreateTestingAccountFn = computed( () => typeof props.createTestingAccount === "function", ); @@ -139,10 +197,15 @@ const closeProxy = () => { }; watch(selectedExtensionAccount, async (selectedAddress) => { - if (selectedAddress) { + if (selectedAddress && selectedExtensionAccountIsNew.value) { props.onExtensionAccountChange(selectedAddress); } }); - + diff --git a/components/overlays/SessionProxiesOverlay.vue b/components/overlays/SessionProxiesOverlay.vue new file mode 100644 index 0000000..2851369 --- /dev/null +++ b/components/overlays/SessionProxiesOverlay.vue @@ -0,0 +1,384 @@ + + + + diff --git a/components/tabs/MessagingTab.vue b/components/tabs/MessagingTab.vue index 899144e..b125602 100644 --- a/components/tabs/MessagingTab.vue +++ b/components/tabs/MessagingTab.vue @@ -402,6 +402,7 @@ import { useNotes } from "@/store/notes.ts"; import { Note, NoteDirection } from "@/lib/notes"; import { divideBigIntToFloat } from "@/helpers/numbers"; import NoteDetailsOverlay from "~/components/overlays/NoteDetailsOverlay.vue"; +import { SessionProxyRole } from "~/lib/sessionProxyStorage"; const identityLut = [...polkadotPeopleIdentities, ...wellKnownIdentities]; @@ -580,7 +581,6 @@ const submitSendForm = () => { const sendPrivately = async () => { console.log("sending message on incognitee"); txStatus.value = "βŒ› sending message privately on incognitee"; - const amount = BigInt(0); const account = accountStore.account; if ( accountStore.getDecimalBalanceTransferable(incogniteeSidechain.value) < @@ -608,16 +608,18 @@ const sendPrivately = async () => { ); await incogniteeStore.api - .trustedBalanceTransfer( + .trustedSendNote( account, incogniteeStore.shard, incogniteeStore.fingerprint, accountStore.getAddress, conversationAddress.value, - amount, note, { signer: accountStore.injector?.signer, + delegate: accountStore.sessionProxyForRole( + SessionProxyRole.NonTransfer, + ), nonce: nonce, }, ) @@ -636,7 +638,7 @@ const handleTopResult = (result, successMsg?) => { txStatus.value = "πŸ˜€ included in sidechain block: " + result.status.asInSidechainBlock; } - //update history to see successfuly action immediately + //update history to see successful action immediately props.updateNotes(); return; } diff --git a/components/tabs/VouchersTab.vue b/components/tabs/VouchersTab.vue index 99241ec..bbafb42 100644 --- a/components/tabs/VouchersTab.vue +++ b/components/tabs/VouchersTab.vue @@ -378,6 +378,7 @@ import { forgetAllVouchersForShard, forgetVoucherForShard, } from "~/lib/voucherStorage"; +import { SessionProxyRole } from "~/lib/sessionProxyStorage"; const accountStore = useAccount(); const incogniteeStore = useIncognitee(); @@ -488,6 +489,7 @@ const fundNewVoucher = async () => { note, { signer: accountStore.injector?.signer, + delegate: accountStore.sessionProxyForRole(SessionProxyRole.Any), nonce: nonce, }, ) diff --git a/components/tabs/WalletTab.vue b/components/tabs/WalletTab.vue index b16a6ed..08a85af 100644 --- a/components/tabs/WalletTab.vue +++ b/components/tabs/WalletTab.vue @@ -2,7 +2,7 @@
- -
{ txStatus.value = "πŸ˜€ included in sidechain block: " + result.status.asInSidechainBlock; } - //update history to see successfuly action immediately + //update history to see successful action immediately props.updateNotes(); return; } @@ -1284,6 +1279,7 @@ const unshield = async () => { amount, { signer: accountStore.injector?.signer, + delegate: accountStore.sessionProxyForRole(SessionProxyRole.Any), nonce: nonce, }, ) @@ -1332,6 +1328,7 @@ const sendPrivately = async () => { note, { signer: accountStore.injector?.signer, + delegate: accountStore.sessionProxyForRole(SessionProxyRole.Any), nonce: nonce, }, ) @@ -1359,6 +1356,9 @@ const submitGuess = async () => { guess.value, { signer: accountStore.injector?.signer, + delegate: accountStore.sessionProxyForRole( + SessionProxyRole.NonTransfer, + ), nonce: nonce, }, ) @@ -1431,7 +1431,7 @@ const closePrivacyInfo = () => { const showObtainTokenOverlay = ref(false); const openObtainTokenOverlay = () => { - if (!enableActions.value) { + if (!props?.enableActions) { console.error("network not live"); return; } @@ -1443,7 +1443,7 @@ const closeObtainTokenOverlay = () => { const showShieldOverlay = ref(false); const openShieldOverlay = () => { - if (!enableActions.value) { + if (!props?.enableActions) { console.error("network not live"); return; } @@ -1458,7 +1458,7 @@ const closeShieldOverlay = () => { const showFaucetOverlay = ref(false); const openFaucetOverlay = () => { - if (!enableActions.value) { + if (!props?.enableActions) { console.error("network not live"); return; } @@ -1470,7 +1470,7 @@ const closeFaucetOverlay = () => { const showUnshieldOverlay = ref(false); const openUnshieldOverlay = () => { - if (!enableActions.value) { + if (!props?.enableActions) { console.error("network not live"); return; } @@ -1484,7 +1484,7 @@ const closeUnshieldOverlay = () => { }; const showReceiveOverlay = ref(false); const openReceiveOverlay = () => { - if (!enableActions.value) { + if (!props?.enableActions) { console.error("network not live"); return; } @@ -1495,7 +1495,7 @@ const closeReceiveOverlay = () => { }; const showPrivateSendOverlay = ref(false); const openPrivateSendOverlay = () => { - if (!enableActions.value) { + if (!props?.enableActions) { console.error("network not live"); return; } @@ -1517,7 +1517,7 @@ const closePrivateSendOverlay = () => { const showGuessTheNumberOverlay = ref(false); const openGuessTheNumberOverlay = () => { - if (!enableActions.value) { + if (!props?.enableActions) { console.error("network not live"); return; } @@ -1599,10 +1599,10 @@ const props = defineProps({ type: Function, required: true, }, -}); - -const enableActions = computed(() => { - return isLive.value || forceLive.value; + enableActions: { + type: Boolean, + required: true, + }, }); diff --git a/configs/incognitee.ts b/configs/incognitee.ts index e96cf10..9d8ed98 100644 --- a/configs/incognitee.ts +++ b/configs/incognitee.ts @@ -5,3 +5,5 @@ export const INCOGNITEE_GTN_GUESS_FEE = 0.1; // TEER or PAS, decimals are respec export const INCOGNITEE_UNSHIELDING_FEE = 3 * INCOGNITEE_TX_FEE; // TEER or PAS, decimals are respected export const INCOGNITEE_SHIELDING_FEE_FRACTION = 0.00175; // 0.175% of the shielding amount + +export const INCOGNITEE_SESSION_PROXY_DEPOSIT = 0.5; // TEER or PAS, decimals are respected diff --git a/lib/sessionProxyStorage.ts b/lib/sessionProxyStorage.ts new file mode 100644 index 0000000..9729649 --- /dev/null +++ b/lib/sessionProxyStorage.ts @@ -0,0 +1,34 @@ +export enum SessionProxyRole { + ReadBalance = "readBalance", + ReadAny = "readAny", + NonTransfer = "nonTransfer", + Any = "any", + TransferAllowance = "transferAllowance", +} + +export const sessionProxyRoleOrder = [ + SessionProxyRole.ReadBalance, + SessionProxyRole.ReadAny, + SessionProxyRole.NonTransfer, + SessionProxyRole.TransferAllowance, + SessionProxyRole.Any, +]; + +export function isRoleAtLeast( + role1: SessionProxyRole, + role2: SessionProxyRole, +): boolean { + return roleOrder.indexOf(role1) >= roleOrder.indexOf(role2); +} + +export class SessionProxyCredentials { + role: SessionProxyRole; + expiry: Date; + seed: Uint8Array; + + constructor(role: SessionProxyRole, expiry: Date, seed: Uint8Array) { + this.role = role; + this.expiry = expiry; + this.seed = seed; + } +} diff --git a/package.json b/package.json index fcc34cd..986b5b3 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "vue-router": "^4.3.2" }, "dependencies": { - "@encointer/util": "^0.18.0-alpha.2", - "@encointer/worker-api": "^0.18.0-alpha.2", + "@encointer/util": "^0.18.0", + "@encointer/worker-api": "^0.18.0", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@headlessui/vue": "^1.7.22", "@heroicons/vue": "^2.1.3", diff --git a/pages/index.vue b/pages/index.vue index 51448a8..007c60a 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -12,6 +12,7 @@ ref="walletTabRef" :updateNotes="updateNotes" :fetchOlderBucket="fetchOlderBucket" + :enableActions="enableActions" />
@@ -97,6 +98,14 @@ + + + @@ -111,7 +121,8 @@ import WalletTab from "~/components/tabs/WalletTab.vue"; import VouchersTab from "~/components/tabs/VouchersTab.vue"; import ChooseWalletOverlay from "~/components/overlays/ChooseWalletOverlay.vue"; -import { computed } from "vue"; +import SessionProxiesOverlay from "~/components/overlays/SessionProxiesOverlay.vue"; +import { computed, onMounted, onUnmounted, ref, watch } from "vue"; import { chainConfigs } from "@/configs/chains.ts"; import { useAccount } from "@/store/account.ts"; import { useIncognitee } from "@/store/incognitee.ts"; @@ -126,7 +137,6 @@ import { mnemonicToMiniSecret, } from "@polkadot/util-crypto"; import { useInterval } from "@vueuse/core"; -import { onUnmounted, onMounted, ref, watch } from "vue"; import { useRouter } from "vue-router"; import { eventBus } from "@/helpers/eventBus"; import { @@ -134,16 +144,17 @@ import { injectorForAddress, } from "@/lib/signerExtensionUtils"; import { - loadEnv, - shieldingTarget, - incogniteeSidechain, incogniteeShard, + incogniteeSidechain, isLive, + loadEnv, + shieldingTarget, } from "@/lib/environmentConfig"; import { useSystemHealth } from "@/store/systemHealth"; import { useNotes } from "~/store/notes"; import { formatMoment } from "~/helpers/date"; import { Note, NoteDirection } from "~/lib/notes"; +import { SessionProxyRole } from "@/lib/sessionProxyStorage.ts"; import MessagingTab from "~/components/tabs/MessagingTab.vue"; import SwapTab from "~/components/tabs/SwapTab.vue"; import GovTab from "~/components/tabs/GovTab.vue"; @@ -168,9 +179,11 @@ const shieldingTargetApi = ref(null); const isProd = computed( () => chainConfigs[shieldingTarget.value].faucetUrl === undefined, ); + const onExtensionAccountChange = async (selectedAddress) => { dropSubscriptions(); console.log("user selected extension account:", selectedAddress); + accountStore.resetState(); accountStore.setAccount(selectedAddress.toString()); accountStore.setInjector(await injectorForAddress(accountStore.getAddress)); isUpdatingIncogniteeBalance.value = false; @@ -205,7 +218,7 @@ const fetchIncogniteeBalance = async () => { ); } getterMap[accountStore.account] = - await incogniteeStore.api.accountInfoGetter( + await incogniteeStore.api.accountInfoAndSessionProxiesGetter( accountStore.account, incogniteeStore.shard, { signer: injector?.signer }, @@ -226,10 +239,14 @@ const fetchIncogniteeBalance = async () => { await getterMap[accountStore.account] .send() - .then((accountInfo) => { + .then((accountInfoAndSessionProxies) => { + const accountInfo = accountInfoAndSessionProxies.account_info; + const proxies = accountInfoAndSessionProxies.session_proxies; console.debug( `current account info L2: ${accountInfo} on shard ${incogniteeStore.shard}`, ); + console.debug(`session proxies: ${proxies}`); + storeSessionProxies(proxies); accountStore.setBalanceFree( BigInt(accountInfo.data.free), incogniteeSidechain.value, @@ -241,6 +258,15 @@ const fetchIncogniteeBalance = async () => { isFetchingIncogniteeBalance.value = false; isUpdatingIncogniteeBalance.value = false; isChoosingAccount.value = false; + if ( + proxies.length == 0 && + accountStore.hasInjector && + !accountStore.hasDeclinedSessionProxy && + accountStore.getDecimalBalanceFree(incogniteeSidechain.value) > 0 + ) { + openAuthorizeSessionOverlay(); + } + //openAuthorizeSessionOverlay(); }) .catch((err) => { console.error(`[fetchIncogniteeBalance] error ${err}`); @@ -248,6 +274,18 @@ const fetchIncogniteeBalance = async () => { }); }; +const storeSessionProxies = (proxies) => { + for (const proxy of proxies) { + const localKeyring = new Keyring({ type: "sr25519" }); + const seed = hexToU8a(proxy.seed.toString()); + const account = localKeyring.addFromSeed(seed); + accountStore.addSessionProxy( + account, + seed, + proxy.role.toString() as SessionProxyRole, + ); + } +}; const fetchNetworkStatus = async () => { const promises = []; if (shieldingTargetApi.value?.isReady) { @@ -335,10 +373,9 @@ const fetchOlderBucket = async () => { /// returns the date as moment before which all notes have been purged from sidechain state const oldestMomentInNoteBuckets = computed(() => { - console.log( - "oldest moment is " + noteBucketsInfo.value?.first.unwrap().begins_at, - ); - return noteBucketsInfo.value?.first.unwrap().begins_at?.toNumber(); + const beginsAt = noteBucketsInfo.value?.first.unwrap().begins_at; + console.log("oldest moment is " + beginsAt?.toNumber()); + return beginsAt ? beginsAt.toNumber() : NaN; }); const bucketsCount = computed(() => { @@ -352,10 +389,12 @@ const bucketsCount = computed(() => { const unfetchedBucketsCount = computed(() => { if (!noteBucketsInfo.value) return 0; - return ( - firstNoteBucketIndexFetched.value ? firstNoteBucketIndexFetched.value - - noteBucketsInfo.value.first.unwrap().index : noteBucketsInfo.value.last.unwrap().index - noteBucketsInfo.value.first.unwrap().index +1 - ); + return firstNoteBucketIndexFetched.value + ? firstNoteBucketIndexFetched.value - + noteBucketsInfo.value.first.unwrap().index + : noteBucketsInfo.value.last.unwrap().index - + noteBucketsInfo.value.first.unwrap().index + + 1; }); const fetchIncogniteeNotes = async ( @@ -372,10 +411,17 @@ const fetchIncogniteeNotes = async ( return; } const mapKey = `notesFor:${accountStore.account}:${bucketIndex}`; + const sessionProxy = accountStore.sessionProxyForRole( + SessionProxyRole.ReadAny, + ); + console.debug( + "[fetchIncogniteeNotes] sessionProxy: " + sessionProxy?.address, + ); const injector = accountStore.hasInjector ? accountStore.injector : null; + console.debug("[fetchIncogniteeNotes] injector: " + injector); try { if (!getterMap[mapKey]) { - if (injector) { + if (injector && sessionProxy == null) { if (skip_if_signer_needed) { console.log( "skipping automated fetchIncogniteeNotes because signer is needed", @@ -391,7 +437,7 @@ const fetchIncogniteeNotes = async ( accountStore.account, bucketIndex, incogniteeStore.shard, - { signer: injector?.signer }, + { delegate: sessionProxy, signer: injector?.signer }, ); } else { console.debug(`fetching incognitee notes using cached getter`); @@ -521,6 +567,43 @@ const fetchIncogniteeNotes = async ( `[${formatMoment(note.timestamp?.toNumber())}] unknown relation to transfer: ${typedCall}`, ); } + } else if (call.isSendNote) { + const typedCall = call.asSendNote; + console.debug( + `[${formatMoment(note.timestamp?.toNumber())}] send note: ${typedCall}`, + ); + const from = encodeAddress( + typedCall[0], + accountStore.getSs58Format, + ); + const to = encodeAddress(typedCall[1], accountStore.getSs58Format); + if (from === accountStore.getAddress) { + noteStore.addNote( + new Note( + "Outgoing Note", + NoteDirection.Outgoing, + to, + BigInt(0), + new Date(note.timestamp?.toNumber()), + typedCall[2].toString(), + ), + ); + } else if (to === accountStore.getAddress) { + noteStore.addNote( + new Note( + "Incoming Note", + NoteDirection.Incoming, + from, + BigInt(0), + new Date(note.timestamp?.toNumber()), + typedCall[2].toString(), + ), + ); + } else { + console.error( + `[${formatMoment(note.timestamp?.toNumber())}] unknown relation to transfer: ${typedCall}`, + ); + } } else if (call.isGuessTheNumber) { const typedCall = call.asGuessTheNumber.asGuess; console.debug( @@ -536,6 +619,25 @@ const fetchIncogniteeNotes = async ( null, ), ); + } else if (call.isAddSessionProxy) { + const typedCall = call.asAddSessionProxy; + const proxy = encodeAddress( + typedCall[1], + accountStore.getSs58Format, + ); + console.debug( + `[${formatMoment(note.timestamp?.toNumber())}] add session proxy: ${typedCall}`, + ); + noteStore.addNote( + new Note( + `Add Session Proxy (${typedCall[2].role})`, + NoteDirection.None, + proxy, + null, + new Date(note.timestamp?.toNumber()), + null, + ), + ); } else { console.error( `[${formatMoment(note.timestamp?.toNumber())}] unknown call: ${call}`, @@ -787,6 +889,12 @@ const dropSubscriptions = () => { accountStore.setInjector(null); }; +const changeSessionProxies = () => { + closeChooseWalletOverlay(); + isUpdatingIncogniteeBalance.value = false; + openAuthorizeSessionOverlay(); +}; + const createTestingAccount = async () => { await cryptoWaitReady().then(() => { dropSubscriptions(); @@ -826,6 +934,20 @@ const closeNewWalletOverlay = () => { showNewWalletOverlay.value = false; }; +const showAuthorizeSessionOverlay = ref(false); +const openAuthorizeSessionOverlay = () => { + if (!enableActions.value) { + console.error("network not live"); + return; + } + showAuthorizeSessionOverlay.value = true; +}; +const closeAuthorizeSessionOverlay = (optout: boolean) => { + if (optout) { + accountStore.declineSessionProxy(); + } + showAuthorizeSessionOverlay.value = false; +}; const showChooseWalletOverlay = ref(false); const openChooseWalletOverlay = () => { if (!enableActions.value) { diff --git a/store/account.ts b/store/account.ts index 5aebd56..575de1f 100644 --- a/store/account.ts +++ b/store/account.ts @@ -5,6 +5,10 @@ import type { InjectedExtension } from "@polkadot/extension-inject/types"; import { ChainId } from "@/configs/chains"; import { encodeAddress } from "@polkadot/util-crypto"; import { divideBigIntToFloat, formatDecimalBalance } from "@/helpers/numbers"; +import { + SessionProxyRole, + sessionProxyRoleOrder, +} from "@/lib/sessionProxyStorage.ts"; export const useAccount = defineStore("account", { state: () => ({ @@ -12,6 +16,12 @@ export const useAccount = defineStore("account", { account: null, // optional signer extension injector: null, + // optional session proxy credentials + sessionProxies: >{}, + // optional session proxy minisecrets + sessionProxySeeds: >{}, + // remember if the user has declined creating a proxy + sessionProxyDeclined: false, // free balance per chain balanceFree: >{}, // reserved balance per chain @@ -54,6 +64,53 @@ export const useAccount = defineStore("account", { hasInjector({ injector }): boolean { return injector != null; }, + hasSessionProxyForRole({ + sessionProxies, + }): (role: SessionProxyRole) => boolean { + return (role: SessionProxyRole): boolean => { + return sessionProxies[role] != null; + }; + }, + hasDeclinedSessionProxy({ sessionProxyDeclined }): boolean { + return sessionProxyDeclined; + }, + /// Returns the weakest session proxy which is authorized for at least the requested role + sessionProxyForRole({ + sessionProxies, + }): (role: SessionProxyRole) => AddressOrPair | null { + return (role: SessionProxyRole): AddressOrPair | null => { + const startIndex = sessionProxyRoleOrder.indexOf(role); + if (startIndex === -1) return null; + for (let i = startIndex; i < sessionProxyRoleOrder.length; i++) { + const currentRole = sessionProxyRoleOrder[i]; + if (sessionProxies[currentRole]) { + return sessionProxies[currentRole]; + } + } + return null; + }; + }, + /// Returns the most powerful session proxy + sessionProxyBest({ + sessionProxies, + }): () => [AddressOrPair | null, SessionProxyRole | null] { + return (): [AddressOrPair | null, SessionProxyRole | null] => { + for (let i = sessionProxyRoleOrder.length - 1; i >= 0; i--) { + const currentRole = sessionProxyRoleOrder[i]; + if (sessionProxies[currentRole]) { + return [sessionProxies[currentRole], currentRole]; + } + } + return [null, null]; + }; + }, + sessionProxySeed({ + sessionProxySeeds, + }): (proxy: AddressOrPair) => Uint8Array { + return (proxy: AddressOrPair): Uint8Array => { + return sessionProxySeeds[proxy]; + }; + }, formatBalanceFree({ balanceFree, decimals }) { return (chain: ChainId): string => { if (!balanceFree[chain]) return "0.000"; @@ -117,12 +174,33 @@ export const useAccount = defineStore("account", { }, }, actions: { + /// call this when account changes to clear all account-related state + resetState() { + this.sessionProxyDeclined = false; + this.sessionProxies = {}; + this.injector = null; + this.BalanceFree = {}; + this.BalanceReserved = {}; + this.BalanceFrozen = {}; + }, setAccount(account: AddressOrPair) { this.account = account; }, setInjector(injector: InjectedExtension) { this.injector = injector; }, + /// sticky decline for adding session proxies + declineSessionProxy() { + this.sessionProxyDeclined = true; + }, + addSessionProxy( + sessionProxy: AddressOrPair, + seed: Uint8Array, + role: SessionProxyRole, + ) { + this.sessionProxies[role] = sessionProxy; + this.sessionProxySeeds[sessionProxy] = seed; + }, setBalanceFree(balance: BigInt, chain: ChainId) { this.balanceFree[chain] = balance; }, diff --git a/store/notes.ts b/store/notes.ts index ce4e934..2e6737f 100644 --- a/store/notes.ts +++ b/store/notes.ts @@ -17,7 +17,10 @@ export const useNotes = defineStore("notes", { }, getFinancialNotes() { return this.getSortedNotesNewestFirst.filter( - (note) => (note.amount > 0) | note.category.includes("Guess"), + (note) => + (note.amount > 0) | + note.category.includes("Guess") | + note.category.includes("Session Proxy"), ); }, getConversationCounterparties() { diff --git a/yarn.lock b/yarn.lock index 2d55e21..900f629 100644 --- a/yarn.lock +++ b/yarn.lock @@ -531,32 +531,32 @@ __metadata: languageName: node linkType: hard -"@encointer/node-api@npm:^0.18.0-alpha.2": - version: 0.18.0-alpha.2 - resolution: "@encointer/node-api@npm:0.18.0-alpha.2" +"@encointer/node-api@npm:^0.18.0": + version: 0.18.0 + resolution: "@encointer/node-api@npm:0.18.0" dependencies: - "@encointer/types": "npm:^0.18.0-alpha.2" + "@encointer/types": "npm:^0.18.0" "@polkadot/api": "npm:^11.2.1" tslib: "npm:^2.6.2" - checksum: 10c0/383ac0109d83e5b908e0a33c648dee678773a674b86234f03f0694f5de7d370dac20bcde45f49335a2e5ddbfcd7ed62422fe8528b00cc7a9cd1acecae57616b3 + checksum: 10c0/e9cbf01b15a622b5d4a5c1b30e0257ce3d5aaeb5decf9aee05f6cc9c178c36b3efe1bf454422becf0150f86eedbc4f12948387e01e3e6e0a7489d8e3eb9c50e9 languageName: node linkType: hard -"@encointer/types@npm:^0.18.0-alpha.2": - version: 0.18.0-alpha.2 - resolution: "@encointer/types@npm:0.18.0-alpha.2" +"@encointer/types@npm:^0.18.0": + version: 0.18.0 + resolution: "@encointer/types@npm:0.18.0" dependencies: "@polkadot/api": "npm:^11.2.1" "@polkadot/types": "npm:^11.2.1" "@polkadot/types-codec": "npm:^11.2.1" "@polkadot/util": "npm:^12.6.2" - checksum: 10c0/dc4ba196329103de0cd3326f0d72eefee57b1dc347ceb59dfbac37828cb837c47a581bad10b4b483b7536fb5a8fd8a29fe055862a70092703507ab9169b528ac + checksum: 10c0/6001f1c4f76e55013e685c3b0ecb3da5cede271450e115974fbe2df68ccd868f9b2b67947e3bb38a8072e4666020c9cb394c9887f15be28fae6ec1b9b44add90 languageName: node linkType: hard -"@encointer/util@npm:^0.18.0-alpha.2": - version: 0.18.0-alpha.2 - resolution: "@encointer/util@npm:0.18.0-alpha.2" +"@encointer/util@npm:^0.18.0": + version: 0.18.0 + resolution: "@encointer/util@npm:0.18.0" dependencies: "@babel/runtime": "npm:^7.18.9" "@polkadot/util": "npm:^12.6.2" @@ -564,17 +564,17 @@ __metadata: "@types/bn.js": "npm:^5.1.5" assert: "npm:^2.0.0" bn.js: "npm:^5.2.1" - checksum: 10c0/128a8b3835afd403cace1de6ea102bdfd88c64e3a360ea09ccfeeb513c365d978699d44110f3a2ff4b5197594ea1065bb3a179c1cc0774ba6d89ed983ebec95f + checksum: 10c0/ad51ce72bb6acca6313f18fd02b33fc6365806b779c8acc653dc784ee9d6661a90d332aac0188e7afc0c0076daaf36ca7fde4ba9b917bfa09c8cdf376925a1cc languageName: node linkType: hard -"@encointer/worker-api@npm:^0.18.0-alpha.2": - version: 0.18.0-alpha.2 - resolution: "@encointer/worker-api@npm:0.18.0-alpha.2" +"@encointer/worker-api@npm:^0.18.0": + version: 0.18.0 + resolution: "@encointer/worker-api@npm:0.18.0" dependencies: - "@encointer/node-api": "npm:^0.18.0-alpha.2" - "@encointer/types": "npm:^0.18.0-alpha.2" - "@encointer/util": "npm:^0.18.0-alpha.2" + "@encointer/node-api": "npm:^0.18.0" + "@encointer/types": "npm:^0.18.0" + "@encointer/util": "npm:^0.18.0" "@peculiar/webcrypto": "npm:^1.4.6" "@polkadot/api": "npm:^11.2.1" "@polkadot/keyring": "npm:^12.6.2" @@ -589,7 +589,7 @@ __metadata: tslib: "npm:^2.6.2" peerDependencies: "@polkadot/x-randomvalues": ^12.3.2 - checksum: 10c0/3d2f835527d75c6b1b31550c0e7dbaeb98c97248d2bffc6b766fea8ac219d52e50ed57832c44a77c07f9e055c3eb7cf6f93a6cb14f27ba33600e9d5113df7b17 + checksum: 10c0/0c0332ff48f3e11b47817d6eff9e8b1bf75ec1c014e350f65733390a72fe92da6c750a1d43a3ddc2c6f87bc2a7f2403feaf0dd3fcd70a72a6ef44c5f41a3c1ee languageName: node linkType: hard @@ -10006,8 +10006,8 @@ __metadata: version: 0.0.0-use.local resolution: "nuxt-app@workspace:." dependencies: - "@encointer/util": "npm:^0.18.0-alpha.2" - "@encointer/worker-api": "npm:^0.18.0-alpha.2" + "@encointer/util": "npm:^0.18.0" + "@encointer/worker-api": "npm:^0.18.0" "@esbuild-plugins/node-globals-polyfill": "npm:^0.2.3" "@headlessui/vue": "npm:^1.7.22" "@heroicons/vue": "npm:^2.1.3" From 8816197408bfaa6a93a319f8be28ae5fcfc3a686 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 13 Dec 2024 13:51:29 +0100 Subject: [PATCH 2/7] fix initial session proxy creation --- components/overlays/SessionProxiesOverlay.vue | 6 +++++- components/tabs/MessagingTab.vue | 4 +++- lib/wellKnownIdentites.ts | 12 ++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/components/overlays/SessionProxiesOverlay.vue b/components/overlays/SessionProxiesOverlay.vue index 2851369..77b655e 100644 --- a/components/overlays/SessionProxiesOverlay.vue +++ b/components/overlays/SessionProxiesOverlay.vue @@ -207,6 +207,10 @@ const createSessionProxy = async () => { }; const addSessionProxyFromSeed = async (seed: Uint8Array) => { + const localKeyring = new Keyring({ type: "sr25519", ss58Format: 42 }); + const sessionProxy = localKeyring.addFromSeed(seed, { + name: "fresh", + }); const injector = accountStore.hasInjector ? accountStore.injector : null; const role = incogniteeStore.api.createType( "SessionProxyRole", @@ -217,7 +221,7 @@ const addSessionProxyFromSeed = async (seed: Uint8Array) => { const expiry = Math.floor(expiryDate.getTime()); console.log( "create session proxy " + - proxy.address + + sessionProxy.address + " with role: " + role + " expiry update to: " + diff --git a/components/tabs/MessagingTab.vue b/components/tabs/MessagingTab.vue index b125602..0ffa316 100644 --- a/components/tabs/MessagingTab.vue +++ b/components/tabs/MessagingTab.vue @@ -528,7 +528,9 @@ watch(isInitializing, () => { const filteredLut = computed(() => { if (!conversationAddress.value) return []; return identityLut.filter((entry) => - entry.username.includes(conversationAddress.value), + entry.username + .toLowerCase() + .includes(conversationAddress.value.toLowerCase()), ); }); diff --git a/lib/wellKnownIdentites.ts b/lib/wellKnownIdentites.ts index cc46b67..9a0a501 100644 --- a/lib/wellKnownIdentites.ts +++ b/lib/wellKnownIdentites.ts @@ -16,4 +16,16 @@ export const identities = [ username: "Tester2", address: "13ueD4C3hvyagpap431WTPnNr4spEYCVv8gKr8gKDz88j9f5", }, + { + username: "ExtensionTester1", + address: "1414CvA9uDs9tASKCizPo35JM2a1uWvHqAZtY9dWP45UGzj3", + }, + { + username: "ExtensionTester2", + address: "12mU141fbeoK1N2zd5UEWLgWXfEEL1TAgCNCDe3yppMQy4JM", + }, + { + username: "ExtensionTester3", + address: "15JmfZMWYeTEdBDCErte22Sup5FA6PMMkS9A3KeLkaf4a4HM", + }, ]; From 651ed6843e18f71378ca513bfabf312ed4f5aa8b Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Fri, 13 Dec 2024 18:40:07 +0100 Subject: [PATCH 3/7] fix session proxy role downgrade cache inconsistency --- components/overlays/SessionProxiesOverlay.vue | 9 +++++++++ store/account.ts | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/components/overlays/SessionProxiesOverlay.vue b/components/overlays/SessionProxiesOverlay.vue index 77b655e..e3d2c45 100644 --- a/components/overlays/SessionProxiesOverlay.vue +++ b/components/overlays/SessionProxiesOverlay.vue @@ -273,6 +273,9 @@ const modifySessionProxyRole = async ( " expiry update to: " + expiry, ); + // clear the cached proxy entry at old role. it will be added again with the new role + // at the next fetchIncogniteeBalance + accountStore.removeProxyForRole(bestSessionProxyRole.value); const nonce = new u32( new TypeRegistry(), accountStore.nonce[incogniteeSidechain.value], @@ -360,6 +363,12 @@ watch( isSignerBusy.value = false; [bestSessionProxy.value, bestSessionProxyRole.value] = accountStore.sessionProxyBest(); + console.log( + "best session proxy: ", + bestSessionProxy.value.address, + " role: ", + bestSessionProxyRole.value, + ); if (bestSessionProxyRole.value !== null) { selectedSessionProxyRole.value = bestSessionProxyRole.value; } else { diff --git a/store/account.ts b/store/account.ts index 575de1f..2f3a595 100644 --- a/store/account.ts +++ b/store/account.ts @@ -201,6 +201,10 @@ export const useAccount = defineStore("account", { this.sessionProxies[role] = sessionProxy; this.sessionProxySeeds[sessionProxy] = seed; }, + removeProxyForRole(role: SessionProxyRole) { + delete this.sessionProxies[role]; + delete this.sessionProxySeeds[role]; + }, setBalanceFree(balance: BigInt, chain: ChainId) { this.balanceFree[chain] = balance; }, From c9eba93fbb9a92b40cea644f44b9aef5edcadda3 Mon Sep 17 00:00:00 2001 From: Alex <32063118+alex-byteport@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:25:10 +0100 Subject: [PATCH 4/7] New sidebar with navigation added: UI changes and small fixes (#101) * new sidebar with navigation added. ui changes and small fixes. * prettier * fix warnings * fix dangling session proxies hwn changing role --------- Co-authored-by: Alexey Popov Co-authored-by: Alain Brenzikofer --- app.vue | 11 +- assets/img/.DS_Store | Bin 6148 -> 8196 bytes assets/img/incognitee-full-logo.svg | 1 + components/Logo/incognitee-logo.vue | 2 +- components/overlays/SessionProxiesOverlay.vue | 3 - components/tabs/MessagingTab.vue | 78 ++- components/tabs/SwapTab.vue | 32 +- components/tabs/VouchersTab.vue | 613 +++++++++--------- components/tabs/WalletTab.vue | 17 +- components/ui/CampaignBanner.vue | 2 +- components/ui/HealthIndicator.vue | 8 +- components/ui/InfoBanner.vue | 2 +- components/ui/NetworkSelector.vue | 4 +- components/ui/PrivateMessageHistory.vue | 17 +- components/ui/PrivateTxHistory.vue | 6 +- components/ui/Sidebar.vue | 222 +++++++ helpers/eventBus.ts | 6 +- layouts/default.vue | 277 ++------ package.json | 3 +- pages/index.vue | 27 +- public/.DS_Store | Bin 6148 -> 8196 bytes public/img/.DS_Store | Bin 6148 -> 6148 bytes public/img/index/.DS_Store | Bin 8196 -> 10244 bytes 23 files changed, 728 insertions(+), 603 deletions(-) create mode 100644 assets/img/incognitee-full-logo.svg create mode 100644 components/ui/Sidebar.vue diff --git a/app.vue b/app.vue index 5cd504e..d3971a3 100644 --- a/app.vue +++ b/app.vue @@ -1,7 +1,16 @@ diff --git a/helpers/eventBus.ts b/helpers/eventBus.ts index 34c3187..a3a3a11 100644 --- a/helpers/eventBus.ts +++ b/helpers/eventBus.ts @@ -1,5 +1,7 @@ import mitt from "mitt"; -const eventBus = mitt(); +type Events = { + toggleSidebar: void; +}; -export { eventBus }; +export const eventBus = mitt(); diff --git a/layouts/default.vue b/layouts/default.vue index 94f1200..96f312d 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,221 +1,58 @@ diff --git a/package.json b/package.json index 986b5b3..dffd6e9 100644 --- a/package.json +++ b/package.json @@ -75,5 +75,6 @@ "vue-qrcode-reader": "^5.5.4", "vue3-form-wizard": "^0.2.3", "vuetify": "^3.7.0" - } + }, + "packageManager": "yarn@4.5.3+sha512.3003a14012e2987072d244c720506549c1aab73ee728208f1b2580a9fd67b92d61ba6b08fe93f6dce68fd771e3af1e59a0afa28dd242dd0940d73b95fedd4e90" } diff --git a/pages/index.vue b/pages/index.vue index 007c60a..d75bb38 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -154,7 +154,10 @@ import { useSystemHealth } from "@/store/systemHealth"; import { useNotes } from "~/store/notes"; import { formatMoment } from "~/helpers/date"; import { Note, NoteDirection } from "~/lib/notes"; -import { SessionProxyRole } from "@/lib/sessionProxyStorage.ts"; +import { + SessionProxyRole, + sessionProxyRoleOrder, +} from "@/lib/sessionProxyStorage.ts"; import MessagingTab from "~/components/tabs/MessagingTab.vue"; import SwapTab from "~/components/tabs/SwapTab.vue"; import GovTab from "~/components/tabs/GovTab.vue"; @@ -275,16 +278,18 @@ const fetchIncogniteeBalance = async () => { }; const storeSessionProxies = (proxies) => { - for (const proxy of proxies) { - const localKeyring = new Keyring({ type: "sr25519" }); - const seed = hexToU8a(proxy.seed.toString()); - const account = localKeyring.addFromSeed(seed); - accountStore.addSessionProxy( - account, - seed, - proxy.role.toString() as SessionProxyRole, - ); - } + // Add the first entry for each role in proxies to the store + sessionProxyRoleOrder.forEach((role) => { + const proxy = proxies.find((p) => p.role.toString() === role); + if (proxy) { + const localKeyring = new Keyring({ type: "sr25519" }); + const seed = hexToU8a(proxy.seed.toString()); + const account = localKeyring.addFromSeed(seed); + accountStore.addSessionProxy(account, seed, role); + } else { + accountStore.removeProxyForRole(role); + } + }); }; const fetchNetworkStatus = async () => { const promises = []; diff --git a/public/.DS_Store b/public/.DS_Store index 2df834dece04146da8756a60971fb2b38947670a..fc8968c52b3704873d6c5af5d5ec7c19f482f730 100644 GIT binary patch literal 8196 zcmeHML2DC16n@*px}gfGwonSfg4ebtQCou`F^va75RB+SrOhU3vbfm^*`%qKLQWp^ z<`3}V!Gi}c9{dIV1N{O1i=Om*Gt;=)BGnA7zPXjhJpWq0lc%h*i+v7 zYSgrb0mHzFWPr~H2bE<(%Yl;e=s+V=0LUb|Wx+M_0Euz5END4UQbJ)v<{m}PPX|WW^)04w(;jn zue$KBC%-hT;;TaycL7bbV_k^ZnI~61Yo9_`>S*i^tTbR@4^QonKArU{W+4f-1aM(Y z?{J!4Ev)*k5I)9n;Aybm$d;?3)#gq*bN6+0tR+e%O|L!s?%9hw8=sltA+{xIzZ`Ej zqqesJrFq%`fxAPpcwu=7xSdGcOarLsqMjeK@CVD7*o;{j_v5k*FK#FM@g-(qF6PIY zdZy9EkRkad5|RmMwZ;OMgKUD9+Yj5C|4(k)_rh)_^VOP3Bu~wrwrzXPeo}6Vez{xe z_QGPN^N8>6V}aeH>x%optM|9k3!5V7R=l9zkpQn&hsxuJUQiSLqUZ&+P-4q!z)q!7 zTj{0!{q_8clf827V8z+b<}$ezXCt3KI7r!x7cX7ExAoL-1>z+u4vqDxfA#z;E-Z3Z0urz z{U)&FA%Ze?p2$5LU6ylLx{$h9Lbdy|I6pEncv(&i8@X$vHWp!_B15MGJF7TJJe(N; z&H(;q*~OSU#T(w&QA=r9@_?vAzmL|1cYKC=uwG|BM?+C=q$pkzV;aN2u`-ZQvoCS} ze{1^p|6}#hOl}x34E$XNtYoQNDq@bKUlQ@@IM;Siw^6yU-attSK_k;~NSTg94*y|@ fYX_=~DQG!R5AQM8V`#RW+@`AI-6JCh1>GK)(L46ZRUF|)9;v2(CbmS>CB;oyuHkgTpYw6N4sFg7-+ z)lsN6G&BZs%!~|bYdJYYmG!NI;kyiA_osNK0rX=n8~XiKLB_Dru;MOzIwrIK>qlpi0-V8C$FTayE_j!4@`Q1 z_wLV875nv7+_nAtotD_IwnP>EoKo-Ly85SV_%S&VfGi2@+=+;<|V{ZBx9=)$5kEZR}p6c^{9yxDNA(D z@ClVZGZ!+n456ikPaX3cpKY0QJo6-_DJMq;CX;XWRu*4%*z3g6`ncL@g}wgT+E3oh zZ0^k5d_JFF$lt3Rh+(B4^as72p!*^Jx+_Gh`s{m9eLt)Z_ZH9J5>Y=0qk2~ggtaDnTf0(y|#X1{xACsAd!7g z=|(LP@ys&L^N0q4x)dN5^jTcRYzkzout$chb&s@KH;)s|fV!CfXfi8^tK1ZB!xyy7 z^)#hV_s9=(iAN^5#<`KrWq3zr^b*huSlomSHGJ#S$H<2hd>|{aZq*#Lbde#4@IcRu zYmetXlUIxF?@m3oFCw-#wpRT0%}p8H#cf3T=JkzKaI*nunZf-9It>SylltpMx?i8t zJC~9vHFW5Kp2cHKr+T$LgJ~GZa+ID1F3mSPZ%3dFQl} zcWJ7&24awlNr++)$z{>9z;aVN8MhOxgZ(Z=D^Dm{8v*t6Ohl@yk;bg zZpz%%bm{T){k0^1jPdl`&Ehd*I=yFjz^9r%Zn}Nt%2AgVIp68Y}cz>@Z{~p0*gM~lIffli*k4c!0S0R zP0w>C8g?XnCi>MsfIjKdF+VLQn6>_KRk8J(_}19tt~n2!vIl0>c>V(D?d<>mPZ^aP z&UwIj;50qp<;s=v4o2inv#s%2yN~NWE^f>>QdElI#w`3?9>?PcAID#bpC#)X`b+V% ek)l!@a#9>ro#y}Re+IbyzuW(7hg<*0{r_Ld>lRu7 delta 183 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aKK3KVBx1o9aeN*VGPG8sx3Ds$2| z7EWWI*ucA)or8sg(QvYYpxER+0t+Vh3C@}vB2+y&T%=-htmuTz@nRW_lRt|0Ox`Rp zbMgnN$&=SgcT7%~nKoHf_LU$r&|n~t;06+|APYAZerKM{uM)@sv<~D9hRN|fQzze* J$Vb!q8~~OlF}eT% From 1cc2f23a60ea46055c8fd730412423d86975a607 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Tue, 17 Dec 2024 10:14:36 +0100 Subject: [PATCH 5/7] fix teerdays and grey out swap in sidebar --- components/tabs/TeerDaysTab.vue | 1093 ++++++++++++++++--------------- components/tabs/WalletTab.vue | 16 +- components/ui/Sidebar.vue | 35 +- 3 files changed, 592 insertions(+), 552 deletions(-) diff --git a/components/tabs/TeerDaysTab.vue b/components/tabs/TeerDaysTab.vue index 0218d42..35757d1 100644 --- a/components/tabs/TeerDaysTab.vue +++ b/components/tabs/TeerDaysTab.vue @@ -1,268 +1,283 @@