Skip to content

Commit

Permalink
Support Paseo and account store cleanup (#156)
Browse files Browse the repository at this point in the history
- Support for Paseo
- Wrap some wallet extension calls in try-catch to prevent failures when
non are there
- Add a display to the account so one can more easily pick out accounts
that are not providers
- Make the account store a single store with three derived stores
instead of trying to process them all separately
  • Loading branch information
wilwade authored Mar 27, 2024
1 parent 9fa61ce commit cc8514c
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 83 deletions.
26 changes: 23 additions & 3 deletions scripts/run_e2e_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ case "${CHAIN}" in
LOCAL_NODE_BLOCK_SEALING="manual"
fi
;;
"paseo_local")
PROVIDER_URL="ws://127.0.0.1:9944"
NPM_RUN_COMMAND="test:relay"
CHAIN_ENVIRONMENT="paseo-local"
;;
"paseo_testnet")
PROVIDER_URL="wss://0.rpc.testnet.amplica.io"
NPM_RUN_COMMAND="test:relay"
CHAIN_ENVIRONMENT="paseo-testnet"

read -p "Enter the seed phrase for the Frequency Rococo account funding source: " FUNDING_ACCOUNT_SEED_PHRASE
;;
"rococo_local")
PROVIDER_URL="ws://127.0.0.1:9944"
NPM_RUN_COMMAND="test:relay"
Expand All @@ -64,7 +76,6 @@ case "${CHAIN}" in
CHAIN_ENVIRONMENT="rococo-testnet"

read -p "Enter the seed phrase for the Frequency Rococo account funding source: " FUNDING_ACCOUNT_SEED_PHRASE

;;
esac

Expand All @@ -73,7 +84,16 @@ then
echo "Frequency is not running."
echo "The intended use case of running integration tests with a chain environment"
echo "of \"rococo-local\" is to run the tests against a locally running Frequency"
echo "chain with locally running Polkadot relay nodes."
echo "chain with locally running Rococo relay nodes."
exit 1
fi

if [ "${CHAIN_ENVIRONMENT}" = "paseo-local" ]
then
echo "Frequency is not running."
echo "The intended use case of running integration tests with a chain environment"
echo "of \"paseo-local\" is to run the tests against a locally running Frequency"
echo "chain with locally running Paseo relay nodes."
exit 1
fi

Expand All @@ -82,7 +102,7 @@ case ${LOCAL_NODE_BLOCK_SEALING} in
"instant") \
docker run --rm -p 9944:9944 -p 30333:30333 --platform=linux/amd64 \
--name vitest-node --detach \
frequencychain/instant-seal-node:latest &
frequencychain/standalone-node:latest &
;;
"manual") ${RUNDIR}/init.sh start-frequency-manual >& frequency.log &
;;
Expand Down
6 changes: 5 additions & 1 deletion src/components/Capacity.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { dotApi, storeChainInfo } from '$lib/stores';
import { user } from '$lib/stores/userStore';
import { activityLog } from '$lib/stores/activityLogStore';
import { getCapacityInfo, type CapacityDetails } from '$lib/polkadotApi';
import { balanceToHuman } from '$lib/utils.js';
import ListCard from './ListCard.svelte';
Expand All @@ -9,6 +10,9 @@
let capacityDetails: CapacityDetails;
$: {
// Easy way to tag a subscription onto this action.
// This way we update the capacity information when the log updates
const _triggerReloadOnLogUpdate = $activityLog;
if ($user?.msaId && $user.msaId !== 0 && $dotApi.api) {
getCapacityInfo($dotApi.api, $user.msaId).then((info) => (capacityDetails = info));
}
Expand All @@ -19,7 +23,7 @@
let errMsg: string = '';
$: {
if (!$user.injectedAccount) {
if (!$user.injectedAccount && !$user.keyringPair) {
errMsg = 'No transaction signing address selected';
} else if (!$user.msaId) {
errMsg = 'No MSA ID. Please create one.';
Expand Down
16 changes: 11 additions & 5 deletions src/components/HowToTransact.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@
<p>To do that:</p>
<ol class="ordered-list">
<li>
<a href="https://faucet.rococo.frequency.xyz/" target="_blank" rel="noopener noreferrer" class="underline">
Get XRQCY tokens for Frequency Testnet (Rococo)
</a>
and follow the instructions using your desired wallet address to get XRQCY tokens.
Get XRQCY tokens for the <a
href="https://faucet.paseo.frequency.xyz/"
target="_blank"
rel="noopener noreferrer"
class="underline">Frequency Testnet (Paseo)</a
>
or
<a href="https://faucet.rococo.frequency.xyz/" target="_blank" rel="noopener noreferrer" class="underline"
>Frequency Testnet (Rococo)</a
> and follow the instructions using your desired wallet address to get XRQCY tokens.
</li>
<li>
Once that succeeds, verify the tokens have made it to your wallet by selecting or re-selecting the address above.
Expand All @@ -27,7 +33,7 @@
rel="noopener noreferrer"
class="underline"
>
Rococo Accounts page via the Polkadot UI.
Accounts page for the correct network via the Polkadot UI.
</a>
.
</li>
Expand Down
3 changes: 0 additions & 3 deletions src/lib/connections.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import type { ApiPromise } from '@polkadot/api/promise';
// @ts-ignore
// import { SignerResult } from "@polkadot/types";
import { waitFor } from '$lib/utils';

import type { KeyringPair } from '@polkadot/keyring/types';
import type { InjectedExtension } from '@polkadot/extension-inject/types';
Expand Down
101 changes: 50 additions & 51 deletions src/lib/stores/accountsStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { writable } from 'svelte/store';
import { derived, writable, type Readable } from 'svelte/store';
import { Keyring, type ApiPromise } from '@polkadot/api';
import type { web3Enable, web3Accounts } from '@polkadot/extension-dapp';
import { getMsaInfo } from '$lib/polkadotApi';
Expand All @@ -19,6 +19,7 @@ export class Account {
msaId?: number;
isProvider: boolean = false;
providerName?: string;
display?: string;

constructor() {
this.address = '';
Expand All @@ -27,10 +28,25 @@ export class Account {

export type Accounts = Map<SS58Address, Readonly<Account>>;

export const providerAccountsStore = writable<Accounts>(new Map<SS58Address, Readonly<Account>>());
export const nonProviderAccountsStore = writable<Accounts>(new Map<SS58Address, Readonly<Account>>());
export const unusedKeyAccountsStore = writable<Accounts>(new Map<SS58Address, Readonly<Account>>());
// Helper functions to filter all accounts to just the accounts for that store
const isProvider = ([_key, account]: [string, Account]) => account.isProvider;
const isNotProviderMsa = ([_key, account]: [string, Account]) => !account.isProvider;
const isUnused = ([_key, account]: [string, Account]) => !account.isProvider && !account.msaId;

// Stores all accounts
export const allAccountsStore = writable<Accounts>(new Map<SS58Address, Readonly<Account>>());
// Filtered down to just provider accounts
export const providerAccountsStore = derived<Readable<Accounts>, Accounts>(allAccountsStore, (all, setFn) => {
setFn(new Map([...all.entries()].filter(isProvider)));
});
// These accounts don't even have an MSA
export const unusedKeyAccountsStore = derived<Readable<Accounts>, Accounts>(allAccountsStore, (all, setFn) => {
setFn(new Map([...all.entries()].filter(isUnused)));
});
// These accounts have an MSA, but are not a provider
export const nonProviderAccountsStore = derived<Readable<Accounts>, Accounts>(allAccountsStore, (all, setFn) => {
setFn(new Map([...all.entries()].filter(isNotProviderMsa)));
});

export async function fetchAccountsForNetwork(
selectedNetwork: NetworkInfo,
Expand All @@ -40,9 +56,6 @@ export async function fetchAccountsForNetwork(
): Promise<void> {
console.log('fetchAccountsForNetwork() - ', selectedNetwork);

const providerAccounts: Accounts = new Map<SS58Address, Account>();
const nonProviderAccounts: Accounts = new Map<SS58Address, Account>();
const unusedKeyAccounts: Accounts = new Map<SS58Address, Account>();
const allAccounts: Accounts = new Map<SS58Address, Account>();

// If the network is localhost, add the default test accounts for the chain
Expand All @@ -60,56 +73,42 @@ export async function fetchAccountsForNetwork(
account.keyringPair = keyRingPair;
account.isProvider = msaInfo.isProvider;
account.providerName = providerNameToHuman(msaInfo.providerName);
account.display = accountName;
allAccounts.set(account.address, account);
if (account.isProvider) {
providerAccounts.set(account.address, account);
} else {
if (account.msaId === 0) {
unusedKeyAccounts.set(account.address, account);
}
nonProviderAccounts.set(account.address, account);
}
})
);
}
// Check if the Polkadot{.js} wallet extension is installed.
if (isFunction(thisWeb3Accounts) && isFunction(thisWeb3Enable)) {
const extensions = await thisWeb3Enable('Frequency parachain provider dashboard');
if (!extensions || !extensions.length) {
alert('Polkadot{.js} extension not found; please install it first.');
throw new Error('Polkadot{.js} extension not found; please install it first.');
}
try {
// Check if the Polkadot{.js} wallet extension is installed.
if (isFunction(thisWeb3Accounts) && isFunction(thisWeb3Enable)) {
const extensions = await thisWeb3Enable('Frequency parachain provider dashboard');
if (!extensions || !extensions.length) {
alert('Polkadot{.js} extension not found; please install it first.');
throw new Error('Polkadot{.js} extension not found; please install it first.');
}

// If so, add the wallet accounts for the selected network (chain)
const walletAccounts = await thisWeb3Accounts();
await Promise.all(
walletAccounts.map(async (walletAccount: InjectedAccountWithMeta) => {
// include only the accounts allowed for this chain
if (!walletAccount.meta.genesisHash || selectedNetwork.genesisHash === walletAccount.meta.genesisHash) {
const account = new Account();
account.network = selectedNetwork;
account.address = walletAccount.address;
account.injectedAccount = walletAccount;
const msaInfo: MsaInfo = await getMsaInfo(apiPromise, account.address);
account.msaId = msaInfo.msaId;
account.isProvider = msaInfo.isProvider;
account.providerName = providerNameToHuman(msaInfo.providerName);
allAccounts.set(account.address, account);
if (account.isProvider) {
providerAccounts.set(account.address, account);
} else {
if (account.msaId === 0) {
unusedKeyAccounts.set(account.address, account);
}
nonProviderAccounts.set(account.address, account);
// If so, add the wallet accounts for the selected network (chain)
const walletAccounts = await thisWeb3Accounts();
await Promise.all(
walletAccounts.map(async (walletAccount: InjectedAccountWithMeta) => {
// include only the accounts allowed for this chain
if (!walletAccount.meta.genesisHash || selectedNetwork.genesisHash === walletAccount.meta.genesisHash) {
const account = new Account();
account.network = selectedNetwork;
account.address = walletAccount.address;
account.injectedAccount = walletAccount;
const msaInfo: MsaInfo = await getMsaInfo(apiPromise, account.address);
account.msaId = msaInfo.msaId;
account.isProvider = msaInfo.isProvider;
account.providerName = providerNameToHuman(msaInfo.providerName);
account.display = walletAccount.meta.name;
allAccounts.set(account.address, account);
}
}
})
);
})
);
}
} catch (e) {
console.error('Unable to load extension accounts', e);
}

providerAccountsStore.set(providerAccounts);
nonProviderAccountsStore.set(nonProviderAccounts);
unusedKeyAccountsStore.set(unusedKeyAccounts);
allAccountsStore.set(allAccounts);
}
7 changes: 6 additions & 1 deletion src/lib/stores/networksStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ export const allNetworks = writable<NetworkInfo[]>([
genesisHash: '0x4a587bf17a404e3572747add7aab7bbe56e805a5479c6c436f07f36fcc8d3ae1',
},
{
name: 'TESTNET',
name: 'TESTNET_ROCOCO',
endpoint: 'wss://rpc.rococo.frequency.xyz',
genesisHash: '0x0c33dfffa907de5683ae21cc6b4af899b5c4de83f3794ed75b2dc74e1b088e72',
},
{
name: 'TESTNET_PASEO',
endpoint: 'wss://0.rpc.testnet.amplica.io',
genesisHash: '0x203c6838fc78ea3660a2f298a58d859519c72a5efdc0f194abd6f0d5ce1838e0',
},
{ name: 'LOCALHOST', endpoint: 'ws://127.0.0.1:9944' },
{ name: 'CUSTOM' },
]);
25 changes: 15 additions & 10 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { formatBalance, hexToString } from '@polkadot/util';
import type { NetworkInfo } from './stores/networksStore';
import type { Account } from './stores/accountsStore';
import { activityLog } from './stores/activityLogStore';
import { isFunction } from '@polkadot/util';
import { TxnStatus, type Activity } from './storeTypes';

export async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
Expand Down Expand Up @@ -66,7 +64,10 @@ export function formatNetwork(network: NetworkInfo): string {
}

export function formatAccount(account: Account): string {
return `${account.providerName}${account.providerName && ':'} ${account.address}`;
if (account.isProvider) {
return `${account.providerName || `Provider #${account.msaId}`}: ${account.address}`;
}
return `${account.display}${account.display && ':'} ${account.address}`;
}

// create a URL-encoded mailto URL string using the provided parameters.
Expand Down Expand Up @@ -99,14 +100,18 @@ export const balanceToHuman = (balance: bigint, token: string): string => {
};

export const getExtension = async (account: Account) => {
const extension = await import('@polkadot/extension-dapp');
const thisWeb3FromSource = extension.web3FromSource;
const thisWeb3Enable = extension.web3Enable;
if (isFunction(thisWeb3FromSource) && isFunction(thisWeb3Enable)) {
const extensions = await thisWeb3Enable('Frequency parachain provider dashboard: Adding Keys');
if (extensions.length !== 0 && account.injectedAccount?.meta.source) {
return await thisWeb3FromSource(account.injectedAccount.meta.source);
try {
const extension = await import('@polkadot/extension-dapp');
const thisWeb3FromSource = extension.web3FromSource;
const thisWeb3Enable = extension.web3Enable;
if (isFunction(thisWeb3FromSource) && isFunction(thisWeb3Enable)) {
const extensions = await thisWeb3Enable('Frequency parachain provider dashboard: Adding Keys');
if (extensions.length !== 0 && account.injectedAccount?.meta.source) {
return await thisWeb3FromSource(account.injectedAccount.meta.source);
}
}
} catch (e) {
console.error('Error getting extension:', e);
}
return undefined;
};
7 changes: 4 additions & 3 deletions src/routes/faq/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
<h2 class="section-title">FAQ's</h2>

<FAQItem>
<span slot="question">What is the difference between Mainnet and Testnet (Rococo)?</span>
<span slot="question">What is the difference between Mainnet and Testnets?</span>
<div slot="answer" class="column">
<p>
The Frequency Mainnet is the production Frequency blockchain network. The Frequency Rococo Testnet, which works
with the Polkadot Rococo Testnet, is for developers to test and debug applications without risking real assets.
The Frequency Mainnet is the production Frequency blockchain network. The Frequency Testnets, which work with
the Polkadot Rococo Testnet and Paseo Testnet relay chains, are for developers to test and debug applications
without risking real assets.
</p>
<p>What about the other options?</p>
<ul class="unordered-list">
Expand Down
Loading

0 comments on commit cc8514c

Please sign in to comment.