import chalk from 'chalk' ; import dotenv from 'dotenv' ; import { GraphQLClient } from 'graphql-request' ; import pRetry from 'p-retry' ; import { getSdk as getOnchainSdk } from './generated/chain-db-graphql' ; import { getSdk as getDiscussionSdk } from './generated/discussion-db-graphql' ; dotenv . config ( ) ; const discussionGraphqlUrl = process . env . REACT_APP_HASURA_GRAPHQL_URL ; const treasuryTopicId = process . env . TREASURY_TOPIC_ID ; const techCommitteeProposalTopicId = process . env . TECH_COMMITTEE_PROPOSAL_TOPIC_ID ; const democracyTopicId = process . env . DEMOCRACY_TOPIC_ID ; const proposalPostTypeId = process . env . HASURA_PROPOSAL_POST_TYPE_ID ; const proposalBotUserId = process . env . PROPOSAL_BOT_USER_ID ; const proposalBotUsername = process . env . PROPOSAL_BOT_USERNAME ; const proposalBotPassword = process . env . PROPOSAL_BOT_PASSWORD ; const chainDBGraphqlUrl = process . env . CHAIN_DB_GRAPHQL_URL ; const councilTopicId = process . env . COUNCIL_TOPIC_ID ; const getDescription = ( type , address ) : string => `This is a ${ type } whose proposer address (${ address }) is shown in on-chain info below. Only this user can edit this description and the title. If you own this account, login and tell us more about your proposal.` ; /**
 * Fetches the JWT from auth server for a "proposal_bot"
 * This is very simple, there's no caching and it fetches the token every single time.
 * it's ok for proposals since there are rarely more than 1 proposal per 15min.
 */ const getTokenRetried = async ( ) : Promise < string | void > => { if ( ! discussionGraphqlUrl ) { throw new pRetry . AbortError ( new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set.' ) ) ; } if ( ! proposalBotPassword || ! proposalBotUsername ) { throw new pRetry . AbortError ( new Error ( "PROPOSAL_BOT_USERNAME or PROPOSAL_BOT_PASSWORD environment variables haven't been set for the proposal bot to login." ) ) ; } const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . loginMutation ( { password : proposalBotPassword , username : proposalBotUsername } ) ; if ( data . login ?. token ) { return data ?. login ?. token ; } else { throw new pRetry . AbortError ( new Error ( `Unexpected data at proposal bot login: ${ data }` ) ) ; } } ; const getToken = async ( ) : Promise < string | void > => { return await pRetry ( getTokenRetried , { onFailedAttempt : error => { console . error ( chalk . red ( `getToken execution error: ${ error . message }` ) , `\n🤞 retrying, attempt ${ error . attemptNumber }/${ error . attemptNumber + error . retriesLeft }.` ) ; } , retries : 8 } ) ; } ; param onchainProposalId export const proposalDiscussionExists = async ( onchainProposalId ) : Promise < boolean | void > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getDiscussionProposalById ( { onchainProposalId } ) ; return ! ! data . onchain_links ?. length ; } catch ( err ) { console . error ( chalk . red ( `proposalDiscussionExists execution error with proposalId: ${ onchainProposalId }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param treasuryProposalId export const treasuryProposalDiscussionExists = async ( onchainTreasuryProposalId ) : Promise < boolean | void > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getDiscussionTreasurySpendProposalById ( { onchainTreasuryProposalId } ) ; return ! ! data . onchain_links ?. length ; } catch ( err ) { console . error ( chalk . red ( `treasuryProposalDiscussionExists execution error with treasuryProposalId: ${ onchainTreasuryProposalId }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param techCommitteeProposalId export const techCommitteeProposalDiscussionExists = async ( onchainTechCommitteeProposalId ) : Promise < boolean | void > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getDiscussionTechCommitteeProposals ( { onchainTechCommitteeProposalId } ) ; return ! ! data . onchain_links ?. length ; } catch ( err ) { console . error ( chalk . red ( `techCommitteeProposalDiscussionExists execution error with bountyId: ${ onchainTechCommitteeProposalId }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param bountyId export const bountyDiscussionExists = async ( onchainBountyId ) : Promise < boolean | void > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getDiscussionBountyById ( { onchainBountyId } ) ; return ! ! data . onchain_links ?. length ; } catch ( err ) { console . error ( chalk . red ( `bountyDiscussionExists execution error with bountyId: ${ onchainBountyId }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param onchainTipHash export const tipDiscussionExists = async ( onchainTipHash ) : Promise < boolean | void > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getDiscussionTipById ( { onchainTipId : onchainTipHash } ) ; return ! ! data . onchain_links ?. length ; } catch ( err ) { console . error ( chalk . red ( `tipDiscussionExists execution error with tip hash: ${ onchainTipHash }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param onchainMotionProposalId export const motionDiscussionExists = async ( onchainMotionProposalId ) : Promise < boolean | void > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getDiscussionMotionProposalById ( { onchainMotionProposalId } ) ; return ! ! data . onchain_links ?. length ; } catch ( err ) { console . error ( chalk . red ( `motionDiscussionExists execution error with motionProposalId: ${ onchainMotionProposalId }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param onchainProposalId export const getProposalDiscussionAssociatedReferendumId = async ( onchainProposalId ) : Promise < number | undefined > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set.' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getProposalWithNoAssociatedReferendumQuery ( { onchainProposalId } ) ; return data ?. onchain_links ?. [ 0 ] ?. id ; } catch ( err ) { console . error ( chalk . red ( `getProposalDiscussionAssociatedReferendumId execution error - Referendum already linked to proposal ${ onchainProposalId } in discussion DB.` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; return undefined ; } } ; param onchainMotionId export const getMotionDiscussionAssociatedReferendumId = async ( onchainMotionId ) : Promise < number | undefined > => { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set.' ) ; } try { const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . getMotionWithNoAssociatedReferendumQuery ( { onchainMotionId } ) ; return data ?. onchain_links ?. [ 0 ] ?. id ; } catch ( err ) { console . error ( chalk . red ( `getMotionlDiscussionAssociatedReferendumId execution error - Referendum already linked to proposal ${ onchainMotionId } in discussion DB.` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; return undefined ; } } ; param proposer param onchainProposalId export const addDiscussionPostAndProposal = async ( { proposer , onchainProposalId } ) : Promise < void > => { if ( ! democracyTopicId ) { throw new Error ( 'Please specify an environment variable for the DEMOCRACY_TOPIC_ID.' ) ; } if ( ! proposalPostTypeId ) { throw new Error ( 'Please specify an environment variable for the HASURA_PROPOSAL_POST_TYPE_ID.' ) ; } if ( ! proposalBotUserId ) { throw new Error ( 'Please specify an environment variable for the PROPOSAL_BOT_USER_ID.' ) ; } const proposalAndPostVariables = { authorId : Number ( proposalBotUserId ) , content : getDescription ( 'proposal' , proposer ) , onchainProposalId , proposerAddress : proposer , topicId : Number ( democracyTopicId ) , typeId : Number ( proposalPostTypeId ) } ; try { const token = await getToken ( ) ; if ( ! token ) { throw new Error ( 'No authorization token found for the chain-db-watcher.' ) ; } if ( ! discussionGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the REACT_APP_SERVER_URL.' ) ; } const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addPostAndProposalMutation ( proposalAndPostVariables ) ; const addedId = data ?. insert_onchain_links ?. returning [ 0 ] ?. id ; if ( addedId || addedId === 0 ) { console . log ( `${ chalk . green ( '✔︎' ) } Proposal ${ onchainProposalId } added to the database.` ) ; } } catch ( err ) { console . error ( chalk . red ( `addPostAndProposal execution error, proposal id ${ onchainProposalId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param proposer param onchainTreasuryProposalId export const addDiscussionPostAndTreasuryProposal = async ( { proposer , onchainTreasuryProposalId } ) : Promise < void > => { if ( ! treasuryTopicId ) { throw new Error ( 'Please specify an environment variable for the TREASURY_TOPIC_ID.' ) ; } if ( ! proposalPostTypeId ) { throw new Error ( 'Please specify an environment variable for the HASURA_PROPOSAL_POST_TYPE_ID.' ) ; } if ( ! proposalBotUserId ) { throw new Error ( 'Please specify an environment variable for the PROPOSAL_BOT_USER_ID.' ) ; } const proposalAndPostVariables = { authorId : Number ( proposalBotUserId ) , content : getDescription ( 'treasury proposal' , proposer ) , onchainTreasuryProposalId , proposerAddress : proposer , topicId : Number ( treasuryTopicId ) , typeId : Number ( proposalPostTypeId ) } ; try { const token = await getToken ( ) ; if ( ! token ) { throw new Error ( 'No authorization token found for the chain-db-watcher.' ) ; } if ( ! discussionGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the REACT_APP_SERVER_URL.' ) ; } const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addPostAndTreasurySpendProposalMutation ( proposalAndPostVariables ) ; const addedId = data ?. insert_onchain_links ?. returning [ 0 ] ?. id ; if ( addedId || addedId === 0 ) { console . log ( `${ chalk . green ( '✔︎' ) } Treasury proposal ${ onchainTreasuryProposalId } added to the database.` ) ; } } catch ( err ) { console . error ( chalk . red ( `addPostAndTreasuryProposal execution error, treasury proposal id ${ onchainTreasuryProposalId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param proposer param onchainBountyId export const addDiscussionPostAndBounty = async ( { proposer , onchainBountyId } ) : Promise < void > => { if ( ! treasuryTopicId ) { throw new Error ( 'Please specify an environment variable for the TREASURY_TOPIC_ID.' ) ; } if ( ! proposalPostTypeId ) { throw new Error ( 'Please specify an environment variable for the HASURA_PROPOSAL_POST_TYPE_ID.' ) ; } if ( ! proposalBotUserId ) { throw new Error ( 'Please specify an environment variable for the PROPOSAL_BOT_USER_ID.' ) ; } const proposalAndPostVariables = { authorId : Number ( proposalBotUserId ) , content : getDescription ( 'bounty proposal' , proposer ) , onchainBountyId , proposerAddress : proposer , topicId : Number ( treasuryTopicId ) , typeId : Number ( proposalPostTypeId ) } ; try { const token = await getToken ( ) ; if ( ! token ) { throw new Error ( 'No authorization token found for the chain-db-watcher.' ) ; } if ( ! discussionGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the REACT_APP_SERVER_URL.' ) ; } const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addPostAndBountyMutation ( proposalAndPostVariables ) ; const addedId = data ?. insert_onchain_links ?. returning [ 0 ] ?. id ; if ( addedId || addedId === 0 ) { console . log ( `${ chalk . green ( '✔︎' ) } Bounty ${ onchainBountyId } added to the database.` ) ; } } catch ( err ) { console . error ( chalk . red ( `addPostAndBounty execution error, Bounty id ${ onchainBountyId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param proposer param onchainBountyId export const addDiscussionPostAndTechCommitteeProposal = async ( { proposer , onchainTechCommitteeProposalId } ) : Promise < void > => { if ( ! techCommitteeProposalTopicId ) { throw new Error ( 'Please specify an environment variable for the TECH_COMMITTEE_PROPOSAL_TOPIC_ID.' ) ; } if ( ! proposalPostTypeId ) { throw new Error ( 'Please specify an environment variable for the HASURA_PROPOSAL_POST_TYPE_ID.' ) ; } if ( ! proposalBotUserId ) { throw new Error ( 'Please specify an environment variable for the PROPOSAL_BOT_USER_ID.' ) ; } const proposalAndPostVariables = { authorId : Number ( proposalBotUserId ) , content : getDescription ( 'techincal committee proposal' , proposer ) , onchainTechCommitteeProposalId , proposerAddress : proposer , topicId : Number ( techCommitteeProposalTopicId ) , typeId : Number ( proposalPostTypeId ) } ; try { const token = await getToken ( ) ; if ( ! token ) { throw new Error ( 'No authorization token found for the chain-db-watcher.' ) ; } if ( ! discussionGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the REACT_APP_SERVER_URL.' ) ; } const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addPostAndTechCommitteeProposalMutation ( proposalAndPostVariables ) ; const addedId = data ?. insert_onchain_links ?. returning [ 0 ] ?. id ; if ( addedId || addedId === 0 ) { console . log ( `${ chalk . green ( '✔︎' ) } TechCommitteeProposal ${ onchainTechCommitteeProposalId } added to the database.` ) ; } } catch ( err ) { console . error ( chalk . red ( `addDiscussionPostAndTechCommitteeProposal execution error, Bounty id ${ onchainTechCommitteeProposalId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param proposer param onchainTipHash export const addDiscussionPostAndTip = async ( { proposer , onchainTipHash } ) : Promise < void > => { if ( ! treasuryTopicId ) { throw new Error ( 'Please specify an environment variable for the TREASURY_TOPIC_ID.' ) ; } if ( ! proposalPostTypeId ) { throw new Error ( 'Please specify an environment variable for the HASURA_PROPOSAL_POST_TYPE_ID.' ) ; } if ( ! proposalBotUserId ) { throw new Error ( 'Please specify an environment variable for the PROPOSAL_BOT_USER_ID.' ) ; } const tipAndPostVariables = { authorId : Number ( proposalBotUserId ) , content : getDescription ( 'tip' , proposer ) , onchainTipId : onchainTipHash , proposerAddress : proposer , topicId : Number ( treasuryTopicId ) , typeId : Number ( proposalPostTypeId ) } ; try { const token = await getToken ( ) ; if ( ! token ) { throw new Error ( 'No authorization token found for the chain-db-watcher.' ) ; } if ( ! discussionGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the REACT_APP_SERVER_URL.' ) ; } const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addPostAndTipMutation ( tipAndPostVariables ) ; const addedId = data ?. insert_onchain_links ?. returning [ 0 ] ?. id ; if ( addedId || addedId === 0 ) { console . log ( `${ chalk . green ( '✔︎' ) } Tip ${ onchainTipHash } added to the database.` ) ; } } catch ( err ) { console . error ( chalk . red ( `addPostAndTipMutation execution error, tip id ${ onchainTipHash }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; interface TreasuryProposalUpdate { onchainMotionProposalId : number ; onchainTreasuryProposalId : number ; } param onchainMotionProposalId param onchainTreasuryProposalId export const updateTreasuryProposalWithMotion = async ( { onchainMotionProposalId , onchainTreasuryProposalId } ) : Promise < boolean | void > => { try { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } const token = await getToken ( ) ; const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addMotionIdToTreasuryProposalMutation ( { motionId : onchainMotionProposalId , treasuryProposalId : onchainTreasuryProposalId } ) ; const affectedRows = data ?. update_onchain_links ?. affected_rows ; if ( ! affectedRows ) { throw new Error ( `updateTreasuryProposalWithMotion execution error with discussion motion id ${ onchainMotionProposalId } and treasury id:${ onchainTreasuryProposalId }, affected row: ${ affectedRows }` ) ; } console . log ( `${ chalk . green ( '✔︎' ) } Motion id ${ onchainMotionProposalId } linked to treasury proposal id ${ onchainTreasuryProposalId }.` ) ; } catch ( err ) { console . error ( chalk . red ( `updateTreasuryProposalWithMotion execution error with motion id:${ onchainMotionProposalId }, treasury proposal id:${ onchainTreasuryProposalId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors' , err . response . errors ) ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available' , err . response . data ) ) ; } } ; param proposer param onchainMotionId export const addDiscussionPostAndMotion = async ( { proposer , onchainMotionProposalId } ) : Promise < void > => { if ( ! councilTopicId ) { throw new Error ( 'Please specify an environment variable for the COUNCIL_TOPIC_ID.' ) ; } if ( ! proposalPostTypeId ) { throw new Error ( 'Please specify an environment variable for the HASURA_PROPOSAL_POST_TYPE_ID.' ) ; } if ( ! proposalBotUserId ) { throw new Error ( 'Please specify an environment variable for the PROPOSAL_BOT_USER_ID.' ) ; } const motionAndPostVariables = { authorId : Number ( proposalBotUserId ) , content : getDescription ( 'motion' , proposer ) , onchainMotionProposalId , proposerAddress : proposer , topicId : Number ( councilTopicId ) , typeId : Number ( proposalPostTypeId ) } ; try { const token = await getToken ( ) ; if ( ! token ) { throw new Error ( 'No authorization token found for the chain-db-watcher.' ) ; } if ( ! discussionGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the REACT_APP_SERVER_URL.' ) ; } const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addPostAndMotionMutation ( motionAndPostVariables ) ; const addedId = data ?. insert_onchain_links ?. returning [ 0 ] ?. id ; if ( addedId || addedId === 0 ) { console . log ( `${ chalk . green ( '✔︎' ) } Motion ${ onchainMotionProposalId } added to the database.` ) ; } } catch ( err ) { console . error ( chalk . red ( `addPostAndMotion execution error, motion id ${ onchainMotionProposalId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; } } ; param referendumInfo param { string | null } referendumInfo . preimageHash param { number } referendumInfo . referendumCreationBlockNumber interface ReferendumInfo { preimageHash ? : string | null ; referendumCreationBlockNumber : number ; } export const getOnchainAssociatedProposalId = async ( { preimageHash , referendumCreationBlockNumber } ) : Promise < number | undefined > => { if ( ! chainDBGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the CHAIN_DB_GRAPHQL_URL.' ) ; } try { const client = new GraphQLClient ( chainDBGraphqlUrl , { headers : { } } ) ; const getTabledProposalsAtBlockVariables = { blockNumber : referendumCreationBlockNumber } ; const onchainSdk = getOnchainSdk ( client ) ; const data = await onchainSdk . getTabledProposalAtBlock ( getTabledProposalsAtBlockVariables ) ; if ( ! data ?. proposals ?. length ) { console . log ( chalk . yellow ( `No democracy proposal tabled at block ${ referendumCreationBlockNumber }. It must have been initiated by a council motion.` ) ) ; return undefined ; } if ( data . proposals . length > 1 ) { const candidates = data . proposals . filter ( ( proposal ) => preimageHash && proposal ?. preimage ?. hash === preimageHash ) ; if ( candidates . length === 1 ) { return candidates ?. [ 0 ] ?. proposalId ; } else { throw new Error ( `Several poposals were tabled at block: ${ referendumCreationBlockNumber }.\n
				The preimage didn't help identify a matching proposal. Preimage hash: ${ preimageHash }.` ) ; } } else { return data . proposals ?. [ 0 ] ?. proposalId ; } } catch ( err ) { console . error ( chalk . red ( `getOnchainAssociatedProposalId execution error with preimage hash: ${ preimageHash }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; return undefined ; } } ; param { string } preimageHash export const getOnchainAssociatedMotionId = async ( preimageHash , blockNumber ) : Promise < number | undefined > => { if ( ! chainDBGraphqlUrl ) { throw new Error ( 'Please specify an environment variable for the CHAIN_DB_GRAPHQL_URL.' ) ; } try { const client = new GraphQLClient ( chainDBGraphqlUrl , { headers : { } } ) ; const onchainSdk = getOnchainSdk ( client ) ; const data = await onchainSdk . getExecutedMotionsWithPreimageHash ( { blockNumber , preimageHash } ) ; if ( ! data ?. motions ?. length ) { throw new Error ( `No council motion was executed with preimage hash: ${ preimageHash }.` ) ; } return data . motions ?. [ 0 ] ?. motionProposalId ; } catch ( err ) { console . error ( chalk . red ( `getOnchainAssociatedMotionId execution error with preimage hash: ${ preimageHash }` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors\n' ) , err . response . errors ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available\n' ) , err . response . data ) ; return undefined ; } } ; param onchainProposalId param onchainReferendumId interface MatchingInfo { onchainProposalId : number ; onchainReferendumId : number ; } export const addReferendumIdToProposal = async ( { onchainProposalId , onchainReferendumId } ) : Promise < boolean | void > => { try { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } const token = await getToken ( ) ; const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addReferendumIdToProposalMutation ( { proposalId : onchainProposalId , referendumId : onchainReferendumId } ) ; return ! ! data ?. update_onchain_links ?. affected_rows ; } catch ( err ) { console . error ( chalk . red ( `addReferendumId execution error with proposalId:${ onchainProposalId }, referendumId:${ onchainReferendumId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors' , err . response . errors ) ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available' , err . response . data ) ) ; } } ; param onchainMotionId param onchainReferendumId interface MatchingMotionInfo { onchainMotionId : number ; onchainReferendumId : number ; } export const addReferendumIdToMotion = async ( { onchainMotionId , onchainReferendumId } ) : Promise < boolean | void > => { try { if ( ! discussionGraphqlUrl ) { throw new Error ( 'Environment variable for the REACT_APP_HASURA_GRAPHQL_URL not set' ) ; } const token = await getToken ( ) ; const client = new GraphQLClient ( discussionGraphqlUrl , { headers : { Authorization : `Bearer ${ token }` } } ) ; const discussionSdk = getDiscussionSdk ( client ) ; const data = await discussionSdk . addReferendumIdToMotionMutation ( { motionId : onchainMotionId , referendumId : onchainReferendumId } ) ; return ! ! data ?. update_onchain_links ?. affected_rows ; } catch ( err ) { console . error ( chalk . red ( `addReferendumIdToMotion execution error with motionId:${ onchainMotionId }, referendumId:${ onchainReferendumId }\n` ) , err ) ; err . response ?. errors && console . error ( chalk . red ( 'GraphQL response errors' , err . response . errors ) ) ; err . response ?. data && console . error ( chalk . red ( 'Response data if available' , err . response . data ) ) ; } } ; interface AddDiscussionReferendum { preimageHash : string ; referendumCreationBlockNumber : number ; referendumId : number ; } param preimageHash param referendumCreationBlockNumber param onchainReferendumId export const addDiscussionReferendum = async ( { preimageHash , referendumCreationBlockNumber , referendumId } ) : Promise < void > => { try { const associatedProposalId = await getOnchainAssociatedProposalId ( { preimageHash , referendumCreationBlockNumber } ) ; let associatedMotionId ; if ( associatedProposalId || associatedProposalId === 0 ) { const proposalAssociatedRefendumId = await getProposalDiscussionAssociatedReferendumId ( associatedProposalId ) ; const shouldUpdateProposal = ! ! proposalAssociatedRefendumId || proposalAssociatedRefendumId === 0 ; if ( shouldUpdateProposal ) { const affectedRows = await addReferendumIdToProposal ( { onchainProposalId : associatedProposalId , onchainReferendumId : Number ( referendumId ) } ) ; if ( ! affectedRows ) { throw new Error ( `addDiscussionReferendum execution error with discussion proposal id ${ associatedProposalId } and referendum id:${ referendumId }, affected row: ${ affectedRows }` ) ; } console . log ( `${ chalk . green ( '✔︎' ) } Referendum id ${ referendumId } added to the onchain_links with proposal id ${ associatedProposalId }.` ) ; } else { console . error ( chalk . red ( `✖︎ Proposal id ${ associatedProposalId . toString ( ) } related to referendum id ${ referendumId } does not exist in the discussion db, or onchain_referendum_id is not null.` ) ) ; } } else { associatedMotionId = await getOnchainAssociatedMotionId ( preimageHash , referendumCreationBlockNumber ) ; if ( ! associatedMotionId && associatedMotionId !== 0 ) { console . log ( chalk . red ( `No motion Id found on chain-db for referendum id: ${ referendumId }.` ) ) ; return ; } const motionAssociatedRefendumId = await getMotionDiscussionAssociatedReferendumId ( associatedMotionId ) ; const shouldUpdateMotion = ! ! motionAssociatedRefendumId || motionAssociatedRefendumId === 0 ; if ( shouldUpdateMotion ) { const affectedRows = await addReferendumIdToMotion ( { onchainMotionId : associatedMotionId , onchainReferendumId : Number ( referendumId ) } ) ; if ( ! affectedRows ) { throw new Error ( `addReferendumId execution error with discussion motion id ${ associatedMotionId } and referendum id:${ referendumId }, affected row: ${ affectedRows }` ) ; } console . log ( `${ chalk . green ( '✔︎' ) } Referendum id ${ referendumId } added to the onchain_links with motion id ${ associatedMotionId }.` ) ; } else { console . error ( chalk . red ( `✖︎ Motion id ${ associatedMotionId . toString ( ) } related to referendum id ${ referendumId } does not exist in the discussion db, or onchain_referendum_id is not null.` ) ) ; } } } catch ( error ) { console . error ( error ) ; } } ;