eas-poll-action-module
Version:
A helper library for creating and voting on polls with the EasPollActionModule Open Action.
321 lines (320 loc) • 13.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVoteForActor = exports.getVoteCountForOption = exports.getVoteCount = exports.createVoteForActorQueryVariables = exports.createVoteCountForOptionQueryVariables = exports.createVoteCountQueryVariables = exports.createVoteActionRequest = exports.createPollActionModuleInput = void 0;
const client_1 = require("@lens-protocol/client");
const ethers_1 = require("ethers");
const eas_sdk_1 = require("@ethereum-attestation-service/eas-sdk");
const types_1 = require("./lib/types");
const constants_1 = require("./lib/constants");
const POLYGON_MAINNET_CHAIN_ID = 137;
/**
* Creates an OpenActionModuleInput for initializing a poll on the EAS Poll Action Module.
*
* @param poll The poll to create.
*/
const createPollActionModuleInput = (poll) => {
if (poll.options.length < 2 || poll.options.length > 4) {
throw new Error("There must be between 2 and 4 poll options");
}
if (poll.endTimestamp && poll.endTimestamp < Math.floor(Date.now() / 1000)) {
throw new Error("Poll end timestamp must be in the future");
}
while (poll.options.length < 4) {
poll.options.push("");
}
const data = (0, client_1.encodeData)([
{
type: "tuple",
name: "poll",
components: [
{ type: "bytes32[4]", name: "options" },
{ type: "bool", name: "followersOnly" },
{ type: "uint40", name: "endTimestamp" },
{ type: "bool", name: "signatureRequired" },
{
type: "tuple",
name: "gateParams",
components: [
{ type: "address", name: "tokenAddress" },
{ type: "uint256", name: "minThreshold" },
],
},
],
},
], [
[
poll.options.map(ethers_1.encodeBytes32String),
poll.followersOnly ?? false,
poll.endTimestamp ?? 0,
poll.signatureRequired ?? false,
poll.gateParams ? [poll.gateParams.tokenAddress, poll.gateParams.minThreshold] : [eas_sdk_1.ZERO_ADDRESS, 0],
],
]);
return {
unknownOpenAction: {
address: constants_1.EAS_POLL_ACTION_MODULE_ADDRESS,
data,
},
};
};
exports.createPollActionModuleInput = createPollActionModuleInput;
/**
* Creates an ActOnOpenActionRequest for voting on a poll on the EAS Poll Action Module.
*
* @param vote The vote to create.
* @param signer Optional Signer to use for creating a signed vote attestation. If not provided, an unsigned vote attestation will be created.
*/
const createVoteActionRequest = async (vote, signer) => {
let data;
if (signer && (0, types_1.isSignedVote)(vote)) {
data = await encodeSignedVoteAttestationData(signer, vote);
}
else {
data = await encodeVoteAttestationData(vote);
}
return {
actOn: {
unknownOpenAction: {
address: constants_1.EAS_POLL_ACTION_MODULE_ADDRESS,
data,
},
},
for: vote.publicationId,
};
};
exports.createVoteActionRequest = createVoteActionRequest;
/**
* Creates graphql query variables for getting the vote count of a poll on the EAS Poll Action Module.
*
* @param publicationId The full ID of the publication as a hex string (e.g. 0xd8-0x01).
* @param testnet Whether to use the testnet schema.
*/
const createVoteCountQueryVariables = (publicationId, testnet = false) => {
const pollId = buildPollId(publicationId);
return {
schemaId: testnet ? constants_1.EAS_VOTE_SCHEMA_UID_TESTNET : constants_1.EAS_VOTE_SCHEMA_UID,
pollId,
};
};
exports.createVoteCountQueryVariables = createVoteCountQueryVariables;
/**
* Creates graphql query variables for getting the vote count of a specific poll option on the EAS Poll Action Module.
*
* @param publicationId The full ID of the publication as a hex string (e.g. 0xd8-0x01).
* @param optionIndex The index of the option to get the vote count for.
* @param testnet Whether to use the testnet schema.
*/
const createVoteCountForOptionQueryVariables = (publicationId, optionIndex, testnet = false) => {
const pollId = buildPollId(publicationId);
const optionIndexAbi = `{"name":"optionIndex","type":"uint8","value":${optionIndex}}`;
return {
schemaId: testnet ? constants_1.EAS_VOTE_SCHEMA_UID_TESTNET : constants_1.EAS_VOTE_SCHEMA_UID,
pollId,
optionIndex: optionIndexAbi,
};
};
exports.createVoteCountForOptionQueryVariables = createVoteCountForOptionQueryVariables;
const createVoteForActorQueryVariables = (publicationId, actorProfileId, testnet = false) => {
const profileId = parseInt(publicationId.split("-")[0]).toString();
const pubId = parseInt(publicationId.split("-")[1]).toString();
const data = (0, client_1.encodeData)([
{ name: "publicationProfileId", type: "uint256" },
{ name: "publicationId", type: "uint256" },
{ name: "actorProfileId", type: "uint256" },
], [profileId, pubId, actorProfileId]);
return {
schemaId: testnet ? constants_1.EAS_VOTE_SCHEMA_UID_TESTNET : constants_1.EAS_VOTE_SCHEMA_UID,
data,
};
};
exports.createVoteForActorQueryVariables = createVoteForActorQueryVariables;
/**
* Gets the vote count of a poll on the EAS Poll Action Module.
*
* @param variables The graphql query variables.
* @param testnet Whether to use the testnet schema.
*
* @see createVoteCountQueryVariables
*/
const getVoteCount = async (variables, testnet = false) => {
const response = await fetch(testnet ? constants_1.EAS_GRAPHQL_ENDPOINT_TESTNET : constants_1.EAS_GRAPHQL_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: constants_1.GET_VOTE_COUNT_QUERY,
variables,
}),
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.statusText}`);
}
const { data } = await response.json();
return data.groupByAttestation[0]._count._all;
};
exports.getVoteCount = getVoteCount;
/**
* Gets the vote count of a specific poll option on the EAS Poll Action Module.
*
* @param variables The graphql query variables.
* @param testnet Whether to use the testnet schema.
*
* @see createVoteCountForOptionQueryVariables
*/
const getVoteCountForOption = async (variables, testnet = false) => {
const response = await fetch(testnet ? constants_1.EAS_GRAPHQL_ENDPOINT_TESTNET : constants_1.EAS_GRAPHQL_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: constants_1.GET_VOTE_COUNT_FOR_OPTION_QUERY,
variables,
}),
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.statusText}`);
}
const { data } = await response.json();
return data.groupByAttestation[0]._count._all;
};
exports.getVoteCountForOption = getVoteCountForOption;
const getVoteForActor = async (variables, testnet = false) => {
const response = await fetch(testnet ? constants_1.EAS_GRAPHQL_ENDPOINT_TESTNET : constants_1.EAS_GRAPHQL_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: constants_1.GET_VOTE_FOR_ACTOR_QUERY,
variables,
}),
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.statusText}`);
}
const { data } = await response.json();
const attestations = data.attestations;
const moduleData = (0, client_1.decodeData)(constants_1.EAS_VOTE_ABI, attestations[0].data);
const attestationData = {
publicationProfileId: moduleData[0],
publicationId: moduleData[1],
actorProfileId: moduleData[2],
actorProfileOwner: moduleData[3],
transactionExecutor: moduleData[4],
optionIndex: parseInt(moduleData[5]),
timestamp: parseInt(moduleData[6]),
};
return {
attester: attestations[0].attester,
id: attestations[0].id,
revoked: attestations[0].revoked,
data: attestationData,
};
};
exports.getVoteForActor = getVoteForActor;
const buildPollId = (publicationId) => {
const profileId = parseInt(publicationId.split("-")[0]).toString();
const pubId = parseInt(publicationId.split("-")[1]).toString();
return (0, client_1.encodeData)([
{ name: "publicationProfileId", type: "uint256" },
{ name: "publicationId", type: "uint256" },
], [profileId, pubId]);
};
const encodeVoteAttestationData = async (vote) => {
return (0, client_1.encodeData)([
{
type: "tuple",
components: [
{ type: "uint256", name: "publicationProfileId" },
{ type: "uint256", name: "publicationId" },
{ type: "uint256", name: "actorProfileId" },
{ type: "address", name: "actorProfileOwner" },
{ type: "address", name: "transactionExecutor" },
{ type: "uint8", name: "optionIndex" },
{ type: "uint40", name: "timestamp" },
],
name: "vote",
},
], [["0", "0", "0", eas_sdk_1.ZERO_ADDRESS, eas_sdk_1.ZERO_ADDRESS, vote.optionIndex.toString(), "0"]]);
};
const encodeSignedVoteAttestationData = async (signer, vote) => {
if (!vote.publicationId || !vote.actorProfileId || !vote.actorProfileOwner || !vote.transactionExecutor) {
throw new Error("Signed votes must have publicationId, actorProfileId, actorProfileOwner, and transactionExecutor");
}
const network = await signer.provider?.getNetwork();
if (!network) {
throw new Error("Signer is not connected to a network");
}
const isMainnet = network.matches(POLYGON_MAINNET_CHAIN_ID);
const publicationProfileId = parseInt(vote.publicationId.split("-")[0]);
const publicationId = parseInt(vote.publicationId.split("-")[1]);
const actorProfileId = parseInt(vote.actorProfileId);
const transactionExecutor = await signer.getAddress();
const timestamp = Math.floor(Date.now() / 1000);
const schemaEncoder = new eas_sdk_1.SchemaEncoder(constants_1.EAS_VOTE_SCHEMA);
const encodedData = schemaEncoder.encodeData([
{ name: "publicationProfileId", value: publicationProfileId, type: "uint256" },
{ name: "publicationId", value: publicationId, type: "uint256" },
{ name: "actorProfileId", value: actorProfileId, type: "uint256" },
{ name: "actorProfileOwner", value: vote.actorProfileOwner, type: "address" },
{ name: "transactionExecutor", value: transactionExecutor, type: "address" },
{ name: "optionIndex", value: vote.optionIndex, type: "uint8" },
{ name: "timestamp", value: timestamp, type: "uint40" },
]);
const eas = new eas_sdk_1.EAS(isMainnet ? constants_1.EAS_ADDRESS : constants_1.EAS_ADDRESS_TESTNET);
eas.connect(signer);
const account = await signer.getAddress();
const nonce = await eas.getNonce(account);
const delegated = await eas.getDelegated();
const response = await delegated.signDelegatedAttestation({
schema: isMainnet ? constants_1.EAS_VOTE_SCHEMA_UID : constants_1.EAS_VOTE_SCHEMA_UID_TESTNET,
data: encodedData,
nonce: nonce,
revocable: true,
recipient: constants_1.EAS_POLL_ACTION_MODULE_ADDRESS,
expirationTime: eas_sdk_1.NO_EXPIRATION,
refUID: eas_sdk_1.ZERO_BYTES32,
value: 0n,
deadline: eas_sdk_1.NO_EXPIRATION,
}, signer);
const signature = response.signature;
return (0, client_1.encodeData)([
{
type: "tuple",
components: [
{ type: "uint256", name: "publicationProfileId" },
{ type: "uint256", name: "publicationId" },
{ type: "uint256", name: "actorProfileId" },
{ type: "address", name: "actorProfileOwner" },
{ type: "address", name: "transactionExecutor" },
{ type: "uint8", name: "optionIndex" },
{ type: "uint40", name: "timestamp" },
],
name: "vote",
},
{
type: "tuple",
components: [
{ type: "uint8", name: "v" },
{ type: "bytes32", name: "r" },
{ type: "bytes32", name: "s" },
],
name: "signature",
},
{ type: "uint64", name: "deadline" },
], [
[
publicationProfileId.toString(),
publicationId.toString(),
actorProfileId.toString(),
vote.actorProfileOwner,
transactionExecutor,
vote.optionIndex.toString(),
timestamp.toString(),
],
[signature.v.toString(), signature.r, signature.s],
eas_sdk_1.NO_EXPIRATION.toString(),
]);
};