From 7df78e15a8545b547ec927d2465aadb864307903 Mon Sep 17 00:00:00 2001 From: Ariel Barmat Date: Wed, 21 Sep 2022 12:04:05 -0300 Subject: [PATCH 1/2] fix: use path.join to manage path concat --- cli/helpers.ts | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/cli/helpers.ts b/cli/helpers.ts index 37f241fc4..115336eca 100644 --- a/cli/helpers.ts +++ b/cli/helpers.ts @@ -1,7 +1,8 @@ import fs from 'fs' +import path from 'path' import * as dotenv from 'dotenv' -import { utils, providers, Wallet } from 'ethers' +import { utils, BigNumber, BigNumberish, Signer } from 'ethers' import ipfsHttpClient from 'ipfs-http-client' import inquirer from 'inquirer' @@ -15,6 +16,8 @@ import { jsonToSubgraphMetadata, jsonToVersionMetadata, } from './metadata' +import { solidityKeccak256 } from 'ethers/lib/utils' +import { GraphToken } from '../build/types/GraphToken' dotenv.config() @@ -46,12 +49,14 @@ export class IPFS { export const pinMetadataToIPFS = async ( ipfs: string, type: string, - path?: string, // Only pass path or metadata, not both + filepath?: string, // Only pass path or metadata, not both metadata?: SubgraphMetadata | VersionMetadata, ): Promise => { - if (metadata == undefined && path != undefined) { + if (metadata == undefined && filepath != undefined) { if (type == 'subgraph') { - metadata = jsonToSubgraphMetadata(JSON.parse(fs.readFileSync(__dirname + path).toString())) + metadata = jsonToSubgraphMetadata( + JSON.parse(fs.readFileSync(path.join(__dirname, filepath)).toString()), + ) logger.info('Meta data:') logger.info(` Subgraph Description: ${metadata.description}`) logger.info(`Subgraph Display Name: ${metadata.displayName}`) @@ -59,7 +64,9 @@ export const pinMetadataToIPFS = async ( logger.info(` Subgraph Code Repository: ${metadata.codeRepository}`) logger.info(` Subgraph Website: ${metadata.website}`) } else if (type == 'version') { - metadata = jsonToVersionMetadata(JSON.parse(fs.readFileSync(__dirname + path).toString())) + metadata = jsonToVersionMetadata( + JSON.parse(fs.readFileSync(path.join(__dirname, filepath)).toString()), + ) logger.info('Meta data:') logger.info(` Version Description: ${metadata.description}`) logger.info(` Version Label: ${metadata.label}`) @@ -104,3 +111,23 @@ export const confirm = async (message: string, skip: boolean): Promise } return true } + +export const buildSubgraphID = (account: string, seqID: BigNumber): string => + solidityKeccak256(['address', 'uint256'], [account, seqID]) + +export const ensureGRTAllowance = async ( + owner: Signer, + spender: string, + amount: BigNumberish, + grt: GraphToken, +): Promise => { + const ownerAddress = await owner.getAddress() + const allowance = await grt.allowance(ownerAddress, spender) + const allowTokens = BigNumber.from(amount).sub(allowance) + if (allowTokens.gt(0)) { + console.log( + `\nApproving ${spender} to spend ${allowTokens} tokens on ${ownerAddress} behalf...`, + ) + await grt.connect(owner).approve(spender, amount) + } +} From 9ceabbd9d5c29825b63d04fa823e10e7758578fd Mon Sep 17 00:00:00 2001 From: Ariel Barmat Date: Wed, 21 Sep 2022 12:04:35 -0300 Subject: [PATCH 2/2] feat: add publish and mint to the CLI --- cli/commands/contracts/gns.ts | 89 ++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/cli/commands/contracts/gns.ts b/cli/commands/contracts/gns.ts index 8d8ae94ee..89c71e92f 100644 --- a/cli/commands/contracts/gns.ts +++ b/cli/commands/contracts/gns.ts @@ -5,7 +5,7 @@ import { logger } from '../../logging' import { sendTransaction } from '../../network' import { loadEnv, CLIArgs, CLIEnvironment } from '../../env' import { nameToNode } from './ens' -import { IPFS, pinMetadataToIPFS } from '../../helpers' +import { IPFS, pinMetadataToIPFS, buildSubgraphID, ensureGRTAllowance } from '../../helpers' export const setDefaultName = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { const graphAccount = cliArgs.graphAccount @@ -102,6 +102,44 @@ export const withdraw = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + // parse args + const ipfs = cliArgs.ipfs + const subgraphDeploymentID = cliArgs.subgraphDeploymentID + const versionPath = cliArgs.versionPath + const subgraphPath = cliArgs.subgraphPath + const tokens = parseGRT(cliArgs.tokens) + + // pin to IPFS + const subgraphDeploymentIDBytes = IPFS.ipfsHashToBytes32(subgraphDeploymentID) + const versionHashBytes = await pinMetadataToIPFS(ipfs, 'version', versionPath) + const subgraphHashBytes = await pinMetadataToIPFS(ipfs, 'subgraph', subgraphPath) + + // craft transaction + const GNS = cli.contracts.GNS + + // build publish tx + const publishTx = await GNS.populateTransaction.publishNewSubgraph( + subgraphDeploymentIDBytes, + versionHashBytes, + subgraphHashBytes, + ) + + // build mint tx + const subgraphID = buildSubgraphID( + cli.walletAddress, + await GNS.nextAccountSeqID(cli.walletAddress), + ) + const mintTx = await GNS.populateTransaction.mintSignal(subgraphID, tokens, 0) + + // ensure approval + await ensureGRTAllowance(cli.wallet, GNS.address, tokens, cli.contracts.GraphToken) + + // send multicall transaction + logger.info(`Publishing and minting on new subgraph for ${cli.walletAddress}...`) + await sendTransaction(cli.wallet, GNS, 'multicall', [[publishTx.data, mintTx.data]]) +} + export const gnsCommand = { command: 'gns', describe: 'GNS contract calls', @@ -172,7 +210,7 @@ export const gnsCommand = { }) .command({ command: 'publishNewVersion', - describe: 'Withdraw unlocked GRT', + describe: 'Publish a new subgraph version', builder: (yargs: Argv) => { return yargs .option('subgraphID', { @@ -307,6 +345,53 @@ export const gnsCommand = { return withdraw(await loadEnv(argv), argv) }, }) + .command({ + command: 'publishAndSignal', + describe: 'Publish a new subgraph and add initial signal', + builder: (yargs: Argv) => { + return yargs + .option('ipfs', { + description: 'ipfs endpoint. ex. https://api.thegraph.com/ipfs/', + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('subgraphDeploymentID', { + description: 'subgraph deployment ID in base58', + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('versionPath', { + description: ` filepath to metadata. With JSON format:\n + "description": "", + "label": ""`, + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('subgraphPath', { + description: ` filepath to metadata. With JSON format:\n + "description": "", + "displayName": "", + "image": "", + "codeRepository": "", + "website": "",`, + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('tokens', { + description: 'Amount of tokens to deposit', + type: 'string', + requiresArg: true, + demandOption: true, + }) + }, + handler: async (argv: CLIArgs): Promise => { + return publishAndSignal(await loadEnv(argv), argv) + }, + }) }, handler: (): void => { yargs.showHelp()