Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow proposal creation only on premium networks #1120

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
8 changes: 1 addition & 7 deletions apps/ui/src/components/Ui/SelectorNetwork.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ const props = defineProps<{
};
}>();

const { networks, getUsage } = useOffchainNetworksList(
props.definition.networkId
);
const { networks } = useOffchainNetworksList(props.definition.networkId);

const options = computed(() => {
const networksListKind = props.definition.networksListKind;
Expand Down Expand Up @@ -87,10 +85,6 @@ const options = computed(() => {
})
.filter(network => !network.readOnly);
});

onMounted(() => {
getUsage();
});
</script>

<template>
Expand Down
40 changes: 30 additions & 10 deletions apps/ui/src/composables/useOffchainNetworksList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@ import { getNetwork } from '@/networks';
import { ChainId, NetworkID } from '@/types';

const usage = ref<Record<ChainId, number | undefined> | null>(null);
const premiumChainIds = ref<Set<ChainId>>(new Set());
const loaded = ref(false);

export function useOffchainNetworksList(
networkId: NetworkID,
hideUnused = false
) {
async function getUsage() {
if (loaded.value) return;

const network = getNetwork(networkId);

usage.value = await network.api.getNetworksUsage();
}

const networks = computed(() => {
const rawNetworks = Object.values(snapshotJsNetworks).filter(
({ chainId }) => typeof chainId === 'number'
Expand All @@ -37,8 +30,35 @@ export function useOffchainNetworksList(
});
});

async function load() {
if (loaded.value) return;

const network = getNetwork(networkId);
const networks = await network.api.getNetworks();

usage.value = Object.keys(networks).reduce(
(acc, chainId) => {
acc[chainId] = networks[chainId].spaces_count;
return acc;
},
{} as Record<ChainId, number>
);

Object.keys(networks).forEach(chainId => {
if (networks[chainId].premium) {
premiumChainIds.value.add(Number(chainId));
}
});

loaded.value = true;
}

onMounted(() => {
load();
});

return {
getUsage,
networks
networks,
premiumChainIds
};
}
2 changes: 1 addition & 1 deletion apps/ui/src/networks/common/graphqlApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ export function createApi(
loadStrategy: async () => {
return null;
},
getNetworksUsage: async () => {
getNetworks: async () => {
return {};
},
loadSettings: async () => {
Expand Down
11 changes: 7 additions & 4 deletions apps/ui/src/networks/offchain/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import {
ALIASES_QUERY,
LEADERBOARD_QUERY,
NETWORKS_USAGE_QUERY,
NETWORKS_QUERY,
PROPOSAL_QUERY,
PROPOSALS_QUERY,
RANKING_QUERY,
Expand Down Expand Up @@ -816,15 +816,18 @@ export function createApi(

return formatStrategy(data.strategy as ApiStrategy);
},
getNetworksUsage: async () => {
getNetworks: async () => {
const { data } = await apollo.query({
query: NETWORKS_USAGE_QUERY
query: NETWORKS_QUERY
});

return Object.fromEntries(
data.networks.map((network: any) => [
Number(network.id),
network.spacesCount
{
spaces_count: network.spacesCount,
premium: network.premium
}
])
);
},
Expand Down
3 changes: 2 additions & 1 deletion apps/ui/src/networks/offchain/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,12 @@ export const STRATEGY_QUERY = gql`
${STRATEGY_FRAGMENT}
`;

export const NETWORKS_USAGE_QUERY = gql`
export const NETWORKS_QUERY = gql`
query Networks {
networks {
id
spacesCount
premium
}
}
`;
Expand Down
4 changes: 3 additions & 1 deletion apps/ui/src/networks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,9 @@ export type NetworkApi = {
): Promise<Statement[]>;
loadStrategies(): Promise<StrategyTemplate[]>;
loadStrategy(address: string): Promise<StrategyTemplate | null>;
getNetworksUsage(): Promise<Record<ChainId, number | undefined>>;
getNetworks(): Promise<
Record<ChainId, { spaces_count: number; premium: boolean }>
>;
loadSettings(): Promise<Setting[]>;
};

Expand Down
6 changes: 1 addition & 5 deletions apps/ui/src/views/My/Explore.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const protocol = ref<ExplorePageProtocol>(DEFAULT_PROTOCOL);
const network = ref<string>(DEFAULT_NETWORK);
const category = ref<SpaceCategory>(DEFAULT_CATEGORY);

const { networks: offchainNetworks, getUsage } = useOffchainNetworksList(
const { networks: offchainNetworks } = useOffchainNetworksList(
metadataNetwork,
true
);
Expand Down Expand Up @@ -138,10 +138,6 @@ watch(
);

watchEffect(() => setTitle('Explore'));

onMounted(() => {
getUsage();
});
</script>

<template>
Expand Down
126 changes: 87 additions & 39 deletions apps/ui/src/views/Space/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const { get: getPropositionPower, fetch: fetchPropositionPower } =
const { strategiesWithTreasuries } = useTreasuries(props.space);
const termsStore = useTermsStore();
const timestamp = useTimestamp({ interval: 1000 });
const { networks, premiumChainIds } = useOffchainNetworksList(
props.space.network
);

const modalOpen = ref(false);
const modalOpenTerms = ref(false);
Expand Down Expand Up @@ -171,7 +174,11 @@ const formErrors = computed(() => {
);
});
const canSubmit = computed(() => {
if (Object.keys(formErrors.value).length > 0) return false;
if (
(unsupportedProposalNetworks.value.length && !proposal.value?.proposalId) ||
Object.keys(formErrors.value).length > 0
)
return false;

return web3.value.account
? propositionPower.value?.canPropose
Expand Down Expand Up @@ -216,6 +223,24 @@ const proposalMaxEnd = computed(() => {
);
});

const unsupportedProposalNetworks = computed(() => {
if (!props.space.snapshot_chain_id) return [];

const ids = new Set<number>([
props.space.snapshot_chain_id,
...props.space.strategies_params.map(strategy => Number(strategy.network)),
...props.space.strategies_params.flatMap(strategy =>
Array.isArray(strategy.params?.strategies)
? strategy.params.strategies.map(param => Number(param.network))
: []
)
]);

return Array.from(ids)
.filter(n => !premiumChainIds.value.has(n))
.map(chainId => networks.value.find(n => n.chainId === chainId));
});

async function handleProposeClick() {
if (!proposal.value) return;

Expand Down Expand Up @@ -433,50 +458,73 @@ watchEffect(() => {
<div class="flex items-stretch md:flex-row flex-col w-full md:h-full">
<div class="flex-1 grow min-w-0">
<UiContainer class="pt-5 !max-w-[710px] mx-0 md:mx-auto s-box">
<MessageVotingPower
v-if="propositionPower"
class="mb-4"
:voting-power="propositionPower"
action="propose"
@fetch-voting-power="handleFetchPropositionPower"
/>
<UiAlert
v-if="
propositionPower &&
spaceType === 'default' &&
proposalLimitReached
"
v-if="unsupportedProposalNetworks[0] && !proposal?.proposalId"
type="error"
class="mb-4"
>
<span
>Please verify your space to publish more proposals.
<a
:href="VERIFIED_URL"
target="_blank"
class="text-rose-500 dark:text-neutral-100 font-semibold"
>Verify space</a
>.</span
>
<div>
You cannot create proposals. This space is configured with
<b>{{ unsupportedProposalNetworks[0].name }}</b
>, a non-premium network. Change to a
<AppLink
to="https://help.snapshot.box/en/articles/10478752-what-are-the-premium-networks"
>premium network
<IH-arrow-sm-right class="inline-block -rotate-45" />
</AppLink>
or upgrade <b>{{ unsupportedProposalNetworks[0].name }}</b> to
continue.
</div>
</UiAlert>
<UiAlert
v-else-if="
propositionPower && spaceType !== 'turbo' && proposalLimitReached
"
type="error"
class="mb-4"
>
<span
>You can publish up to {{ MAX_1D_PROPOSALS.verified }} proposals
per day and {{ MAX_30D_PROPOSALS.verified }} proposals per month.
<a
:href="TURBO_URL"
target="_blank"
class="text-rose-500 dark:text-neutral-100 font-semibold"
>Increase limit</a
>.</span
<template v-else>
<MessageVotingPower
v-if="propositionPower"
class="mb-4"
:voting-power="propositionPower"
action="propose"
@fetch-voting-power="handleFetchPropositionPower"
/>
<UiAlert
v-if="
propositionPower &&
spaceType === 'default' &&
proposalLimitReached
"
type="error"
class="mb-4"
>
</UiAlert>
<span
>Please verify your space to publish more proposals.
<a
:href="VERIFIED_URL"
target="_blank"
class="text-rose-500 dark:text-neutral-100 font-semibold"
>Verify space</a
>.</span
>
</UiAlert>
<UiAlert
v-else-if="
propositionPower &&
spaceType !== 'turbo' &&
proposalLimitReached
"
type="error"
class="mb-4"
>
<span
>You can publish up to {{ MAX_1D_PROPOSALS.verified }} proposals
per day and {{ MAX_30D_PROPOSALS.verified }} proposals per
month.
<a
:href="TURBO_URL"
target="_blank"
class="text-rose-500 dark:text-neutral-100 font-semibold"
>Increase limit</a
>.</span
>
</UiAlert>
</template>
<div v-if="guidelines">
<h4 class="mb-2 eyebrow">Guidelines</h4>
<a :href="guidelines" target="_blank" class="block mb-4">
Expand Down