Get Neuron Id For Authenticated User

I would like to get the neuron id for the authenticated user in my frontend so I can send it with the MakeProposal payload:

How can I obtain this?

1 Like

You can use sns-js to query the neurons of a specific principal.

The method has a parameter called “principal”, if you pass the authenticated user’s principal, you will get the neurons of the user.

That said, remember that the principal is different per domain. The same user in your dapp, is not the same user in NNS Dapp for example.

Therefore, you might need to ask them to add their principal in your dapp as hotkey of the neuron in NNS Dapp (assuming they have it there). Once the added as a hotkey, you can use their principal to query the neurons. Adding it as hotkey, will also give permissions to the principal in your dapp to make proposals.

3 Likes

Thanks for coming back to me.

If I understand you correctly, you’re saying that in OpenChat, to vote on things, that user has added the neuron id here:

in the app somewhere?

I’m not 100% sure what you mean by hotkey?

Ok I in the NNS:

User adds their principal and they get their voting power in the app, simple as that? If they have say 3 neurons, add a hotkey to all 3, will the Principal get the VP of all 3 neurons in OpenFPL?

Appreciate the help on this.

@Imuntaner would I need to loop through all the neurons returned for the principal that has been added as a hotkey and attempt to raise the proposal multiple times:

So the first time if an identical proposal doesn’t exist (checked in the validator_method_name) it will raise the proposal but if it does exist it should vote on the existing proposal.

Yes, correct. Simple as that.

Yes, either loop or choose one to make the proposal. Or let the user do so. Proposals cost tokens; therefore, take into account the stake of the neuron. Or add a fallback mechanism.

1 Like

But I’m right in thinking that if a user attempts to make a proposal in that loop, the first time it will add the proposal and cost them tokens. Then in the second loop the add proposal function is called again and votes are just added as the validator_method_name converts their action of raising a proposal into voting an existing one, thus not costing them tokens on the second loop?

Tokens for the people raising proposals are then returned to user if it passes correct?

I only see one loop in the code. Inside that loop, each neuron creates a proposal. I don’t see how neurons vote in a proposal.

You might want to exit the loop if the proposal is created successfully. Otherwise it will create one per neuron, no?

So yes, say I have 3 neurons and run that code it would create 3 proposal. But what I want to do is intercept in the validator_method_name and check if the proposal already exists and if so just vote for it there.

The reason is there could be a 1000 people all at the same time revaluing a player, each could think they were creating the first proposal and not voting on an existing one. So exiting the loop doesn’t solve the problem if another user goes to revalue a player aswell.

Another example might be when a game finishes, 1000 users might all add a proposal to say the score is 1-0. The first person I want to create the proposal but the 999 remaining people who say it’s 1-0 will just be voting for the initial proposal.

On the backend I need to check whenever these governance proposal are already created by another user and then if it has I will just vote for the existing one with the neurons voting power.

Thanks,
James

Oh, I see. I’m not so sure the validator_method_name was built with that in mind. I’m also not sure whether you can submit a vote for the neuron from there. It might not have permissions to do so.

This logic of checking proposals might need to happen before submitting the proposal.

Maybe @lara has better ideas. It’s an interesting problem, and I understand why doing it on the client side is not enough.

I’m happy to do it on the client side, I’m just worried about a certain situation.

So if 2 users submit some data at exactly the same time, both will create the identical proposal. Then every user who does the same thing after I can check if proposal exists, and just add votes to that proposal from the client side.

It’s stopping that identical proposal being created initially.

I’ll just ask this as it may be a stupid question but it’s burning a hole in my brain.

So I get interfacing with the SNS is easiest via the sns-js library, it uses agent-js to send the user’s principal with requests so the hotkey workflow can work.

What I don’t get is why this can’t also be done on the backend? Is the caller here not the principal the hotkey uses:

And then can I not just create an interface like I do for the ledger and somehow initialise it with the caller?

If you could explain what I am not getting it would be appreciated.

Thanks,
James

There is no such thing as stupid question, even less within IC :joy:

The reason it can’t be used from the backend is because the call needs to be signed with from the principal in the frontend. And that can only be done from the client.

Making calls from the backend works, but they won’t be signed with the principal’s user.

1 Like

Thank you for clarifying. That really clears up the design then. Since I won’t be able to raise a proposal anyway within the validator_method_name as I won’t be able to sign it with the principal’s user.

I still worry that 2 users will create identical proposals at the same time because it doesn’t exist at the time they submit it. Would I be able to solve this by checking the proposals list in the validator_method_name and reject it from there? Then when a user enters duplicated data at almost the same time as another user, one would be rejected in the validator_method_name and that rejected user would receive an error and retries with voting on the other user’s proposal?

I read that returning a value from the validator_method_name is required so I can return the existing proposal id if it fails?

Does this work? I don’t need to sign the message to get the proposals list?

1 Like

That might work. I don’t know whether you’ll get the returned value (proposal id) from the make proposal endpoint though. I’ll ask the NNS team about it.

But since if it happens it’s because the proposal is very new, you can get the latest proposals and find a similar one there.

Yes, getting the proposals list doesn’t need to be signed by a logged in user. Therefore, you can get it from the canister.

1 Like

Thank you for all your help on this.

1 Like

Yes. Here is where the Err string from the validator starts its long bucket brigade back to the InvalidProposal GovernanceError error_message.

Tangent

Trying to reject duplicates in a distributed system is possible, but fraught with peril.

A simpler alternative is to just let competing proposals through. Then, let people vote on the competing proposals for the one(s) that they like.

1 Like

It’s just I want proposal governance on loads of tiny bits of data, so I want to abstract that away from the user.

They are just performing an action to revalue something up or down. They don’t really want to see the proposal behind it, although they could.

So in my frontend I’ve currently got this code that will look for the existing proposals to revalue up and down and then either vote or create a new proposal:

const revaluePlayerUp = async (player) => {
    const functionIdUp = 1000;
    const functionIdDown = 2000;
    const proposalTitleUp = "Increase Player Value Proposal";
    const proposalUrl = "https://openfpl.xyz/governance";
    const proposalSummaryUp = `Proposal to increase the value of ${player.firstName !== "" ? player.firstName.charAt(0) + "." : ""} ${player.lastName}.`;

    const payload = IDL.encode(InitArgs, `(record { seasonId=${systemState.activeSeason.id}; gameweek=${systemState.activeGameweek} playerId=${player.id} })`);
    const neurons = await SnsGovernanceCanister.listNeurons({ principal: authClient.getPrincipal() });

    for (const neuron of neurons) {
        const neuronId = neuron.id[0].NeuronId.toString();
        
        const activeProposals = await SnsGovernanceCanister.listProposals({
            includeStatus: [SnsProposalDecisionStatus.PROPOSAL_DECISION_STATUS_OPEN]
        });

        const matchingProposalDown = activeProposals.filter(proposal => {
            const action = proposal.proposal?.action[0];
            return action?.ExecuteGenericNervousSystemFunction?.function_id === functionIdDown && IDL.decode(InitArgs, proposal.payload).playerId === player.id;
        });

        const matchingProposalUp = activeProposals.filter(proposal => {
            const action = proposal.proposal?.action[0];
            return action?.ExecuteGenericNervousSystemFunction?.function_id === functionIdUp && IDL.decode(InitArgs, proposal.payload).playerId === player.id;
        });

        if (matchingProposalDown) {
            await SnsGovernanceCanister.registerVote({
                neuronId: neuronId,
                proposalId: matchingProposalDown.id,
                vote: "NO"
            });
        }

        if (matchingProposalUp) {
            await SnsGovernanceCanister.registerVote({
                neuronId: neuronId,
                proposalId: matchingProposalUp.id,
                vote: "YES"
            });
        } else if (!matchingProposalUp && !matchingProposalDown) {
            const manageNeuronRequest = createManageNeuronRequestForProposal(
                neuronId, proposalTitleUp, proposalUrl, proposalSummaryUp, functionIdUp, payload
            );
            await SnsGovernanceCanister.manageNeuron(manageNeuronRequest);
        }
    }
};

Ah. In that case, you can choose to not show “extraneous” proposals. You can maybe choose to show the one where the most voting power has been exercised.

I don’t know if this helps but here is the UI design where this governance will start:

So clicking the green button will revalue up and red will revalue down. Each of these green button will create / vote on the appropriate player, and also vote NO on any existing revalue player down proposal.

So I don’t show proposals, I show players and the list can change.