import fetch from 'cross-fetch' ; import { Interface } from '@ethersproject/abi' ; import { Contract } from '@ethersproject/contracts' ; import { namehash } from '@ethersproject/hash' ; import { jsonToGraphQLQuery } from 'json-to-graphql-query' ; import Ajv from 'ajv' ; import addFormats from 'ajv-formats' ; import Multicaller from './utils/multicaller' ; import getProvider from './utils/provider' ; import validations from './validations' ; import { signMessage , getBlockNumber } from './utils/web3' ; import { getHash , verify } from './sign/utils' ; import gateways from './gateways.json' ; import networks from './networks.json' ; import voting from './voting' ; export const SNAPSHOT_SUBGRAPH_URL = { '1' : 'https://api.thegraph.com/subgraphs/name/snapshot-labs/snapshot' , '4' : 'https://api.thegraph.com/subgraphs/name/snapshot-labs/snapshot-rinkeby' , '42' : 'https://api.thegraph.com/subgraphs/name/snapshot-labs/snapshot-kovan' } ; export async function call ( provider , abi , call , options ? ) { const contract = new Contract ( call [ 0 ] , abi , provider ) ; try { const params = call [ 2 ] || [ ] ; return await contract [ call [ 1 ] ] ( ... params , options || { } ) ; } catch ( e ) { return Promise . reject ( e ) ; } } export async function multicall ( network , provider , abi , calls , options ? ) { const multicallAbi = [ 'function aggregate(tuple(address target, bytes callData)[] calls) view returns (uint256 blockNumber, bytes[] returnData)' ] ; const multi = new Contract ( networks [ network ] . multicall , multicallAbi , provider ) ; const itf = new Interface ( abi ) ; try { const max = options ?. limit || 500 ; const pages = Math . ceil ( calls . length / max ) ; const promises = [ ] ; Array . from ( Array ( pages ) ) . forEach ( ( x , i ) => { const callsInPage = calls . slice ( max * i , max * ( i + 1 ) ) ; promises . push ( multi . aggregate ( callsInPage . map ( ( call ) => [ call [ 0 ] . toLowerCase ( ) , itf . encodeFunctionData ( call [ 1 ] , call [ 2 ] ) ] ) , options || { } ) ) ; } ) ; let results = await Promise . all ( promises ) ; results = results . reduce ( ( prev , [ , res ] ) => prev . concat ( res ) , [ ] ) ; return results . map ( ( call , i ) => itf . decodeFunctionResult ( calls [ i ] [ 1 ] , call ) ) ; } catch ( e ) { return Promise . reject ( e ) ; } } export async function subgraphRequest ( url , query , options = { } ) { const res = await fetch ( url , { method : 'POST' , headers : { Accept : 'application/json' , 'Content-Type' : 'application/json' , ... options ?. headers } , body : JSON . stringify ( { query : jsonToGraphQLQuery ( { query } ) } ) } ) ; const { data } = await res . json ( ) ; return data || { } ; } export function getUrl ( uri , gateway = gateways [ 0 ] ) { const ipfsGateway = `https://${ gateway }` ; if ( ! uri ) return null ; if ( ! uri . includes ( 'ipfs' ) && ! uri . includes ( 'ipns' ) && ! uri . includes ( 'http' ) ) return `${ ipfsGateway }/ipfs/${ uri }` ; const uriScheme = uri . split ( '://' ) [ 0 ] ; if ( uriScheme === 'ipfs' ) return uri . replace ( 'ipfs://' , `${ ipfsGateway }/ipfs/` ) ; if ( uriScheme === 'ipns' ) return uri . replace ( 'ipns://' , `${ ipfsGateway }/ipns/` ) ; return uri ; } export async function getJSON ( uri ) { const url = getUrl ( uri ) ; return fetch ( url ) . then ( ( res ) => res . json ( ) ) ; } export async function ipfsGet ( gateway , ipfsHash , protocolType = 'ipfs' ) { const url = `https://${ gateway }/${ protocolType }/${ ipfsHash }` ; return fetch ( url ) . then ( ( res ) => res . json ( ) ) ; } export async function sendTransaction ( web3 , contractAddress , abi , action , params , overrides = { } ) { const signer = web3 . getSigner ( ) ; const contract = new Contract ( contractAddress , abi , web3 ) ; const contractWithSigner = contract . connect ( signer ) ; return await contractWithSigner [ action ] ( ... params , overrides ) ; } export async function getScores ( space , strategies , network , addresses , snapshot = 'latest' , scoreApiUrl = 'https://score.snapshot.org/api/scores' ) { try { const params = { space , network , snapshot , strategies , addresses } ; const res = await fetch ( scoreApiUrl , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( { params } ) } ) ; const obj = await res . json ( ) ; return obj . result . scores ; } catch ( e ) { return Promise . reject ( e ) ; } } export function validateSchema ( schema , data ) { const ajv = new Ajv ( { allErrors : true , allowUnionTypes : true , $data : true } ) ; addFormats ( ajv ) ; const validate = ajv . compile ( schema ) ; const valid = validate ( data ) ; return valid ? valid : validate . errors ; } export async function getSpaceUri ( id , network = '1' ) { const abi = 'function text(bytes32 node, string calldata key) external view returns (string memory)' ; const address = networks [ network ] . ensResolver || networks [ '1' ] . ensResolver ; let uri = false ; try { const hash = namehash ( id ) ; const provider = getProvider ( network ) ; uri = await call ( provider , [ abi ] , [ address , 'text' , [ hash , 'snapshot' ] ] ) ; } catch ( e ) { console . log ( 'getSpaceUriFromTextRecord failed' , id , e ) ; } return uri ; } export function clone ( item ) { return JSON . parse ( JSON . stringify ( item ) ) ; } export async function sleep ( time ) { return new Promise ( ( resolve ) => { setTimeout ( resolve , time ) ; } ) ; } export function getNumberWithOrdinal ( n ) { const s = [ 'th' , 'st' , 'nd' , 'rd' ] , v = n % 100 ; return n + ( s [ ( v - 20 ) % 10 ] || s [ v ] || s [ 0 ] ) ; } export default { call , multicall , subgraphRequest , ipfsGet , getUrl , getJSON , sendTransaction , getScores , validateSchema , getSpaceUri , clone , sleep , getNumberWithOrdinal , voting , getProvider , signMessage , getBlockNumber , Multicaller , validations , getHash , verify } ;