Eu criei um Gerador de Contratos Inteligentes Solidity usando as APIs da OpenAI. Neste artigo, vou explicar como eu fiz.
Você pode experimentar o gerador aqui.
https://solidity-smart-contract-using-chatgpt.vercel.app/
Primeiro, crie um aplicativo do Next.js.
yarn create next-app
O comando acima fez algumas perguntas e eu nomeei o diretório do meu projeto como my_test_project
. Agora, vá para o aplicativo do Next.js que você criou, neste caso, ele está dentro da pasta my_test_project
.
cd my_test_project
Depois de entrar na pasta, execute o aplicativo.
yarn dev
Você verá a tela de boas-vindas do aplicativo Next.js.
Abra um novo terminal ou pressione CTRL + C para encerrar o comando run dev
. Depois disso, adicione os pacotes necessários. Como eu usei o Bootstrap, adicionei também o pacote do Bootstrap.
yarn add bootstrap
yarn add next-base64
yarn add openai
yarn add sanitize-html
Adicione o Bootstrap no arquivo _app.js
.
// Adicione o CSS do Bootstrap
import 'bootstrap/dist/css/bootstrap.css'
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
Crie um arquivo .env
e cole a chave de API da OpenAI.
OPENAI_API_KEY=
Você obterá esta chave de API daqui:
https://beta.openai.com/account/api-keys
Remova todo o código do arquivo index.js
e cole este código.
import Head from "next/head";
import { useState } from "react";
import sanitizeHtml from "sanitize-html";
import Link from 'next/link';
import nextBase64 from 'next-base64';
export default function Home() {
const [userInput, setUserInput] = useState("");
const [apiOutput, setApiOutput] = useState("You will see output here");
const [userInputSelect, setUserInputSelect] = useState("");
const [checked, setChecked] = useState(false);
const [apiOutputForRemix, setApiOutputForRemix] = useState("");
const onUserChangedText = (event) => {
console.log(event.target.value);
setUserInput(event.target.value);
};
const onSelectOption = (event) => {
console.log(event.target.value);
setUserInput(event.target.value);
};
const handleChange = (event) => {
setChecked(event.target.checked);
};
const OpenInRemixButton = ({ fileUrl, fileCode }) => {
const fileCodeBase64 = nextBase64.encode(fileCode);
return (
<Link
href={fileUrl}
as={`https://remix.ethereum.org?code=${fileCodeBase64}`}
prefetch={false}
passHref
legacyBehavior
>
<a data-url={fileUrl} target="_blank" className="btn btn-outline-success my-2 my-sm-0 float-right" data-content={fileCodeBase64}>Open in Remix</a>
</Link>
);
};
const callGenerateEndpoint = async () => {
setApiOutput(`Please Wait ....`);
console.log("Calling OpenAI...");
const response = await fetch("/api/chatgpt", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ userInput, checked }),
});
const data = await response.json();
const { output } = data;
console.log("OpenAI replied...", output.text);
setApiOutputForRemix(output.text)
const formattedText = output.text.replace(/\n/g, "<br>");
const sanitizedOutput = sanitizeHtml(formattedText);
setApiOutput(`${sanitizedOutput}`);
};
return (
<>
<main
className="px-2 py-3 my-3 container"
style={{ border: "0px solid #ccc", background:"#fff" }}
>
<Head>
<title>Create Your Smart Contract</title>
<meta name="description" content="Create Smart Contract" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="row">
<div className="col-md-6 text-left" style={{ border: "1px solid #ccc", background:"#eee",marginLeft:"0px" }} >
<div className="mx-4 px-4 py-5 my-5 text-left flex-grow-1" >
<h2 className="">
Create Solidity Smart Contract
</h2>
<br></br>
<div className="">
<p className="lead mb-4">
<select
className="form-control form-control-lg"
value={userInputSelect}
onChange={onSelectOption}
>
<option>Select Smart Contract</option>
<option value="NFT">NFT</option>
<option value="NFT with Royalty">
NFT with Royalty
</option>
<option value="PaymentSplitter">
PaymentSplitter
</option>
<option value="VestingWallet">
VestingWallet
</option>
<option value="Timelock">
Timelock
</option>
<option value="Pausable">
Pausable
</option>
<option value="ReentrancyGuard">
ReentrancyGuard
</option>
<option value="Uniswap">Uniswap</option>
<option value="Twitter">Twitter</option>
<option value="Hospital OPD">
Hospital OPD
</option>
<option value="Hospital Labtest">
Hospital Labtest
</option>
<option value="Cricket Records">
Cricket Records
</option>
<option value="Football World Cup">
Football World Cup
</option>
</select>
<br></br>
<div className="d-grid gap-2 d-sm-flex justify-content-sm-center">
or
</div>
<br></br>
<textarea
name=""
className="form-control"
rows={4}
placeholder="e.g Supply Chain Management, Property ownership, Renting, Clinic Records, NFTs"
value={userInput}
onChange={onUserChangedText}
></textarea>
<br></br>
<input
type="checkbox"
checked={checked}
onChange={handleChange}
></input>{" "}
<i>Use OpenZeppelin</i>
</p>
<div className="d-grid gap-2 d-sm-flex justify-content-sm-center">
<button
type="button"
className="btn btn-primary btn-lg px-4 gap-3"
onClick={callGenerateEndpoint}
>
Create Smart Contract
</button>
</div>
<div className="d-grid gap-2 d-sm-flex justify-content-sm-center">
Powered by OpenAI GPT-3
</div>
</div>
</div>
</div>
<div className="col-md-6" style={{ border: "0px solid #000", background:"#fff",marginLeft:"0px" }} >
<nav className="navbar navbar-light" style={{ background:"#000", padding: "13px",paddingBottom:"0px"}}>
<OpenInRemixButton
fileUrl="https://remix.ethereum.org/#version=soljson-v0.7.7+commit.9e61f92b.js&optimize=false&gist=e2e5c1b5e6fb5c6f9b8d5f6b5d2ebeb1"
fileCode={apiOutputForRemix}
/>
</nav>
<div className="text-left flex-grow-1" style={{ border: "1px solid #000", background:"#000", color:"#39FF14" }}>
<div>
{apiOutput && (
<div className="output">
<div className="output-content">
<pre>
<code>
<div style={{ padding:"10px" }}
dangerouslySetInnerHTML={{
__html: apiOutput,
}}
></div>
</code>
</pre>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</main>
</>
);
}
Deixe-me explicar o código acima:
Primeiro, importei todos os pacotes necessários.
import Head from "next/head";
import { useState } from "react";
import sanitizeHtml from "sanitize-html";
import Link from 'next/link';
import nextBase64 from 'next-base64';
A importação sanitize-html
é usada para limpar o HTML, next/link
é usado para links e next-base64
é usado para codificar o código gerado e passá-lo para o URL.
export default function Home() {
const [userInput, setUserInput] = useState("");
const [apiOutput, setApiOutput] = useState("You will see output here");
const [userInputSelect, setUserInputSelect] = useState("");
const [checked, setChecked] = useState(false);
const [apiOutputForRemix, setApiOutputForRemix] = useState("");
const onUserChangedText = (event) => {
console.log(event.target.value);
setUserInput(event.target.value);
};
const onSelectOption = (event) => {
console.log(event.target.value);
setUserInput(event.target.value);
};
const handleChange = (event) => {
setChecked(event.target.checked);
};
const OpenInRemixButton = ({ fileUrl, fileCode }) => {
const fileCodeBase64 = nextBase64.encode(fileCode);
return (
<Link
href={fileUrl}
as={`https://remix.ethereum.org?code=${fileCodeBase64}`}
prefetch={false}
passHref
legacyBehavior
>
<a data-url={fileUrl} target="_blank" className="btn btn-outline-success my-2 my-sm-0 float-right" data-content={fileCodeBase64}>Open in Remix</a>
</Link>
);
};
const callGenerateEndpoint = async () => {
setApiOutput(`Please Wait ....`);
console.log("Calling OpenAI...");
const response = await fetch("/api/chatgpt", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ userInput, checked }),
});
const data = await response.json();
const { output } = data;
console.log("OpenAI replied...", output.text);
setApiOutputForRemix(output.text)
const formattedText = output.text.replace(/\n/g, "<br>");
const sanitizedOutput = sanitizeHtml(formattedText);
setApiOutput(`${sanitizedOutput}`);
};
As funções onUserChangedText
, onSelectOption
e handleChange
são usadas para coletar os campos de entrada. O OpenInRemixButton
é usado para abrir o código no IDE Ethereum do Remix e, finalmente, a função callGenerateEndpoint
chama a API.
const callGenerateEndpoint = async () => {
setApiOutput(`Please Wait ....`);
console.log("Calling OpenAI...");
const response = await fetch("/api/chatgpt", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ userInput, checked }),
});
const data = await response.json();
const { output } = data;
console.log("OpenAI replied...", output.text);
setApiOutputForRemix(output.text)
const formattedText = output.text.replace(/\n/g, "<br>");
const sanitizedOutput = sanitizeHtml(formattedText);
setApiOutput(`${sanitizedOutput}`);
};
A função callGenerateEndpoint
chama a rota api/chatgpt
que criei e que veremos mais tarde no artigo. A API retorna o output.text
. Eu formatei isso para exibi-lo e também salvá-lo como dado bruto para passar no URL.
<select className="form-control form-control-lg" value={userInputSelect} onChange={onSelectOption}>
<option>Select Smart Contract</option>
<option value="NFT">NFT</option>
<option value="NFT with Royalty">NFT with Royalty</option>
<option value="PaymentSplitter">PaymentSplitter</option>
<option value="VestingWallet">VestingWallet</option>
<option value="Timelock">Timelock</option>
<option value="Pausable">Pausable</option>
<option value="ReentrancyGuard">ReentrancyGuard</option>
<option value="Uniswap">Uniswap</option>
<option value="Twitter">Twitter</option>
<option value="Hospital OPD">Hospital OPD</option>
<option value="Hospital Labtest">Hospital Labtest</option>
<option value="Cricket Records">Cricket Records</option>
<option value="Football World Cup">Football World Cup</option>
</select>
Eu criei um dropdown e também uma área de texto. O usuário pode selecionar a opção ou escrever na área de texto.
{apiOutput && (
<div className="output">
<div className="output-content">
<pre>
<code>
<div style={{ padding:"10px" }}
dangerouslySetInnerHTML={{
__html: apiOutput,
}}
></div>
</code>
</pre>
</div>
</div>
)}
Nesse bloco do contrato inteligente, o HTML será gerado.
Agora vamos para a parte mais importante, que é a API.
Crie uma nova pasta api
dentro da pasta pages
e, dentro da pasta api
, crie um novo arquivo chamado chatgpt.js
.
import { Configuration, OpenAIApi } from 'openai';
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
var basePromptPrefix = `Create a solidity smart contract for `;
var selectOpenZeppelin = "";
var versionUser = " use solidity version 0.8.17";
const chatGPT = async (req, res) => {
if(req.body.checked == true) {
selectOpenZeppelin = ` Using OpenZeppelin`;
}
const baseCompletion = await openai.createCompletion({
model: 'text-davinci-003',
prompt: `${basePromptPrefix}${req.body.userInput}${selectOpenZeppelin}${versionUser}`,
temperature: 0.6,
max_tokens: 4000,
});
const data_output = baseCompletion.data.choices.pop();
// Enviar a saída do Prompt #2 para nossa interface do usuário, em vez do Prompt #1.
res.status(200).json({ output: data_output });
};
export default chatGPT;
A parte mais importante desta API é o "Prompt". As ferramentas de IA exigem prompts adequados do usuário para responder. Então, neste código, criamos este prompt como um prefixo.
var basePromptPrefix = `Create a solidity smart contract for `;
Esse prompt é sempre adicionado à entrada do usuário para gerar o código.
const baseCompletion = await openai.createCompletion({
model: 'text-davinci-003',
prompt: `${basePromptPrefix}${req.body.userInput}${selectOpenZeppelin}${versionUser}`,
temperature: 0.6,
max_tokens: 4000,
});
Aqui, na linha número 3, o prompt tem o prefixo basePromptPrefix
+ entrada do usuário + selectOpenZeppelin
(seleção do OpenZeppelin) + versão do Solidity. Digamos que o usuário tenha selecionado a opção "NFT" e também a opção "Use OpenZeppelin", então o valor final do prompt será:
Crie um contrato inteligente Solidity para um NFT, usando o OpenZeppelin, com a versão 0.8.17 do Solidity.
Existem diferentes modelos. Eu usei, o text-davinci-003
, que é o mais recente e preciso.
A temperatura controla a aleatoriedade da saída. Valores mais altos significam que o modelo assumirá mais riscos.
O parâmetro max_tokens define um limite máximo para o número de tokens que a API pode usar.
Essa é a explicação do código para que qualquer um possa contribuir com ele. Deixe-me saber nos comentários sobre o que você achou do código.
Você pode acessar o código aqui e também pode contribuir com ele.
https://github.com/ismailbangee/solidity_smart_contract_using_chatgpt
Saiba mais sobre o Solidity e o ChatGPT.
https://ismailsaleem.gumroad.com/
Artigo publicado por Ismail. Traduzido por Paulinho Giovannini.
Top comments (0)