Skip to main content
Glama

OpenZeppelin Contracts MCP Server

Official
by OpenZeppelin
App.svelte16.8 kB
<script lang="ts"> import { createEventDispatcher, tick } from 'svelte'; import hljs from './highlightjs'; import { defaultOverrides, type Overrides } from './overrides'; import ERC20Controls from './ERC20Controls.svelte'; import ERC721Controls from './ERC721Controls.svelte'; import ERC1155Controls from './ERC1155Controls.svelte'; import StablecoinControls from './StablecoinControls.svelte'; import RealWorldAssetControls from './RealWorldAssetControls.svelte'; import AccountControls from './AccountControls.svelte'; import GovernorControls from './GovernorControls.svelte'; import CustomControls from './CustomControls.svelte'; import CopyIcon from '../common/icons/CopyIcon.svelte'; import CheckIcon from '../common/icons/CheckIcon.svelte'; import RemixIcon from '../common/icons/RemixIcon.svelte'; import DownloadIcon from '../common/icons/DownloadIcon.svelte'; import ZipIcon from '../common/icons/ZipIcon.svelte'; import FileIcon from '../common/icons/FileIcon.svelte'; import Dropdown from '../common/Dropdown.svelte'; import OverflowMenu from '../common/OverflowMenu.svelte'; import Tooltip from '../common/Tooltip.svelte'; import type { KindedOptions, Kind, Contract, OptionsErrorMessages } from '@openzeppelin/wizard'; import { ContractBuilder, buildGeneric, printContract, sanitizeKind, OptionsError } from '@openzeppelin/wizard'; import { getImports } from '@openzeppelin/wizard/get-imports'; import { postConfig } from '../common/post-config'; import { remixURL } from './remix'; import { saveAs } from 'file-saver'; import { injectHyperlinks } from './inject-hyperlinks'; import type { InitialOptions } from '../common/initial-options'; import type { AiFunctionCall } from '../../api/ai-assistant/types/assistant'; import ErrorDisabledActionButtons from '../common/ErrorDisabledActionButtons.svelte'; import { createWiz, mergeAiAssistanceOptions } from '../common/Wiz.svelte'; import type { Language } from '../common/languages-types'; const dispatch = createEventDispatcher(); async function allowRendering() { showCode = false; await tick(); showCode = true; } export let initialTab: string | undefined = 'ERC20'; export let tab: Kind = sanitizeKind(initialTab); $: { tab = sanitizeKind(tab); allowRendering(); dispatch('tab-change', tab); } export let initialOpts: InitialOptions = {}; /** * Allows ecosystem Wizard apps that inherit the Solidity Wizard UI to override specific features. * See @type Overrides for details. * * For Solidity itself, defaultOverrides must be used. */ export let overrides: Overrides = defaultOverrides; const WizSolidity = overrides.aiAssistant?.svelteComponent ?? createWiz<'solidity'>(); let initialValuesSet = false; let allOpts: { [k in Kind]?: Required<KindedOptions[k]> } = {}; let errors: { [k in Kind]?: OptionsErrorMessages } = {}; let contract: Contract = new ContractBuilder(initialOpts.name ?? 'MyToken'); let showCode = true; $: opts = allOpts[tab]; $: { if (opts) { overrides.sanitizeOmittedFeatures(opts); if (!initialValuesSet) { opts.name = initialOpts.name ?? opts.name; switch (opts.kind) { case 'ERC20': opts.premint = initialOpts.premint ?? opts.premint; case 'ERC721': opts.symbol = initialOpts.symbol ?? opts.symbol; break; case 'ERC1155': case 'Stablecoin': case 'RealWorldAsset': case 'Account': case 'Governor': case 'Custom': } initialValuesSet = true; } try { contract = buildGeneric(opts); errors[tab] = undefined; } catch (e: unknown) { if (e instanceof OptionsError) { errors[tab] = e.messages; } else { throw e; } } allowRendering(); } } $: code = printContract(contract); $: highlightedCode = injectHyperlinks(hljs.highlight('solidity', code).value); $: hasErrors = errors[tab] !== undefined; $: showButtons = getButtonVisibilities(overrides, opts); interface ButtonVisibilities { openInRemix: boolean; downloadHardhat: boolean; downloadFoundry: boolean; } const getButtonVisibilities = (overrides: Overrides, opts?: KindedOptions[Kind]): ButtonVisibilities => { const result = { openInRemix: true, downloadHardhat: true, downloadFoundry: true, }; switch (opts?.kind) { case 'Governor': result.downloadHardhat = false; result.downloadFoundry = false; break; case 'Stablecoin': case 'RealWorldAsset': result.openInRemix = false; result.downloadHardhat = false; result.downloadFoundry = false; break; } if (overrides.omitZipHardhat) { result.downloadHardhat = false; } if (overrides.omitZipFoundry) { result.downloadFoundry = false; } return result; }; const getSolcSources = (contract: Contract) => { const sources = getImports(contract); sources[contract.name] = { content: code }; return sources; }; const language: Language = overrides.postConfigLanguage ?? 'solidity'; let copied = false; const copyHandler = async () => { await navigator.clipboard.writeText(code); copied = true; if (opts) { await postConfig(opts, 'copy', language); } setTimeout(() => { copied = false; }, 1000); }; const remixHandler = async (e: MouseEvent) => { e.preventDefault(); if ((e.target as Element)?.classList.contains('disabled')) return; const { printContractVersioned } = await import('@openzeppelin/wizard/print-versioned'); const versionedCode = printContractVersioned(contract); window.open( remixURL(versionedCode, !!opts?.upgradeable, overrides?.remix?.url).toString(), '_blank', 'noopener,noreferrer', ); if (opts) { await postConfig(opts, 'remix', language); } }; const downloadNpmHandler = async () => { const blob = new Blob([code], { type: 'text/plain' }); if (opts) { saveAs(blob, opts.name + '.sol'); await postConfig(opts, 'download-file', language); } }; const zipHardhatModule = import('@openzeppelin/wizard/zip-env-hardhat'); const downloadHardhatHandler = async () => { const { zipHardhat } = await zipHardhatModule; const zip = await zipHardhat(contract, opts); const blob = await zip.generateAsync({ type: 'blob' }); saveAs(blob, 'project.zip'); if (opts) { await postConfig(opts, 'download-hardhat', language); } }; const zipFoundryModule = import('@openzeppelin/wizard/zip-env-foundry'); const downloadFoundryHandler = async () => { const { zipFoundry } = await zipFoundryModule; const zip = await zipFoundry(contract, opts); const blob = await zip.generateAsync({ type: 'blob' }); saveAs(blob, 'project.zip'); if (opts) { await postConfig(opts, 'download-foundry', language); } }; const applyFunctionCall = ({ detail: aiFunctionCall }: CustomEvent<AiFunctionCall<'solidity'>>) => { tab = sanitizeKind(aiFunctionCall.name); allOpts = mergeAiAssistanceOptions(allOpts, aiFunctionCall); }; $: wizLanguage = (overrides.aiAssistant?.language ?? 'solidity') as 'solidity'; </script> <div class="container flex flex-col gap-4 p-4 rounded-3xl"> <WizSolidity language={wizLanguage} bind:currentOpts={opts} bind:currentCode={code} on:function-call-response={applyFunctionCall} experimentalContracts={['Stablecoin', 'RealWorldAsset']} sampleMessages={['Make a token with supply of 10 million', 'What does mintable do?', 'Make a contract for a DAO']} /> <div class="header flex flex-row justify-between"> <div class="tab overflow-hidden whitespace-nowrap"> <OverflowMenu> <button class:selected={tab === 'ERC20'} on:click={() => (tab = 'ERC20')}> ERC20 </button> <button class:selected={tab === 'ERC721'} on:click={() => (tab = 'ERC721')}> ERC721 </button> <button class:selected={tab === 'ERC1155'} on:click={() => (tab = 'ERC1155')}> ERC1155 </button> <button class:selected={tab === 'Stablecoin'} on:click={() => (tab = 'Stablecoin')}> Stablecoin* </button> <button class:selected={tab === 'RealWorldAsset'} on:click={() => (tab = 'RealWorldAsset')}> Real-World Asset* </button> {#if !overrides.omitTabs.includes('Account')} <button class:selected={tab === 'Account'} on:click={() => (tab = 'Account')}> Account </button> {/if} <button class:selected={tab === 'Governor'} on:click={() => (tab = 'Governor')}> Governor </button> <button class:selected={tab === 'Custom'} on:click={() => (tab = 'Custom')}> Custom </button> </OverflowMenu> </div> {#if hasErrors} <ErrorDisabledActionButtons /> {:else} <div class="action flex flex-row gap-2 shrink-0"> <button class="action-button p-3 min-w-[40px]" on:click={copyHandler} title="Copy to Clipboard"> {#if copied} <CheckIcon /> {:else} <CopyIcon /> {/if} </button> {#if showButtons.openInRemix} <Tooltip let:trigger disabled={!(opts?.upgradeable === 'transparent')} theme="light-red border" hideOnClick={false} interactive > <button use:trigger class="action-button with-text" class:disabled={opts?.upgradeable === 'transparent'} on:click={remixHandler} > <RemixIcon /> {overrides?.remix?.label ?? 'Open in Remix'} </button> <div slot="content"> Transparent upgradeable contracts are not supported on Remix. Try using Remix with UUPS upgradability or use Hardhat or Foundry with <a href="https://docs.openzeppelin.com/upgrades-plugins/" target="_blank" rel="noopener noreferrer" >OpenZeppelin Upgrades</a >. <br /> <!-- svelte-ignore a11y-invalid-attribute --> <a href="#" on:click={remixHandler}>Open in Remix anyway</a>. </div> </Tooltip> {/if} <Dropdown let:active> <button class="action-button with-text" class:active slot="button"> <DownloadIcon /> Download </button> <button class="download-option" on:click={downloadNpmHandler}> <FileIcon /> <div class="download-option-content"> <p>Single file</p> <p>Requires installation of npm package (<code>@openzeppelin/contracts</code>).</p> <p>Simple to receive updates.</p> </div> </button> {#if showButtons.downloadHardhat} <button class="download-option" on:click={downloadHardhatHandler}> <ZipIcon /> <div class="download-option-content"> <p>Development Package (Hardhat)</p> <p>Sample Hardhat project to get started with development and testing.</p> </div> </button> {/if} {#if showButtons.downloadFoundry} <button class="download-option" on:click={downloadFoundryHandler}> <ZipIcon /> <div class="download-option-content"> <p>Development Package (Foundry)</p> <p>Sample Foundry project to get started with development and testing.</p> </div> </button> {/if} </Dropdown> </div> {/if} </div> <div class="flex flex-row grow"> <div class="controls rounded-l-3xl min-w-72 w-72 max-w-[calc(100vw-420px)] flex flex-col shrink-0 justify-between h-[calc(100vh-84px)] overflow-auto resize-x" > <div class:hidden={tab !== 'ERC20'}> <ERC20Controls bind:opts={allOpts.ERC20} errors={errors.ERC20} omitFeatures={overrides.omitFeatures.get('ERC20')} /> </div> <div class:hidden={tab !== 'ERC721'}> <ERC721Controls bind:opts={allOpts.ERC721} /> </div> <div class:hidden={tab !== 'ERC1155'}> <ERC1155Controls bind:opts={allOpts.ERC1155} /> </div> <div class:hidden={tab !== 'Stablecoin'}> <StablecoinControls bind:opts={allOpts.Stablecoin} errors={errors.Stablecoin} omitFeatures={overrides.omitFeatures.get('Stablecoin')} /> </div> <div class:hidden={tab !== 'RealWorldAsset'}> <RealWorldAssetControls bind:opts={allOpts.RealWorldAsset} errors={errors.RealWorldAsset} omitFeatures={overrides.omitFeatures.get('RealWorldAsset')} /> </div> <div class:hidden={tab !== 'Account'}> <AccountControls bind:opts={allOpts.Account} errors={errors.Account} /> </div> <div class:hidden={tab !== 'Governor'}> <GovernorControls bind:opts={allOpts.Governor} errors={errors.Governor} /> </div> <div class:hidden={tab !== 'Custom'}> <CustomControls bind:opts={allOpts.Custom} /> </div> </div> <div class="output rounded-r-3xl flex flex-col grow overflow-auto h-[calc(100vh-84px)] relative"> <pre class="flex flex-col grow basis-0 overflow-auto"> {#if showCode} <code class="hljs -solidity grow overflow-auto p-4 {hasErrors ? 'no-select' : ''}" >{@html highlightedCode}</code > {/if} </pre> </div> </div> </div> <style lang="postcss"> .button-bg:hover { transform: translateX(-2px); transition: transform 300ms; } .hide-deploy { transform: translateX(-320px); transition: transform 0.45s; } .hide-deploy button { background-color: white; border: 1px solid white; } .hide-deploy:hover { transform: translatex(-318px); } .container { background-color: var(--gray-1); min-width: 32rem; } .header { font-size: var(--text-small); } .tab { color: var(--gray-5); } .tab button, :global(.overflow-btn) { padding: var(--size-2) var(--size-4); border-radius: 20px; cursor: pointer; transition: background-color ease-in-out 0.2s; } .tab button { } .tab button, :global(.overflow-btn) { border: 0; background-color: transparent; } .tab button:hover, :global(.overflow-btn):hover { background-color: var(--gray-2); } .tab button.selected { background-color: var(--solidity-blue-2); color: white; order: -1; } :global(.overflow-menu) button.selected { order: unset; } :global(.action-button) { padding: 7px; border-radius: 20px; transition: background-color ease-in-out 0.2s; background-color: var(--gray-1); border: 1px solid var(--gray-3); color: var(--gray-6); cursor: pointer; &:hover { background-color: var(--gray-2); } &:active, &.active { background-color: var(--gray-2); } :global(.icon) { margin: 0 var(--size-1); } } :global(.action-button.disabled) { color: var(--gray-4); } :global(.with-text) { padding-right: var(--size-3); } .controls { background-color: white; padding: var(--size-4); } .controls-footer { display: flex; flex-direction: row; justify-content: flex-end; color: var(--gray-5); margin-top: var(--size-3); padding: 0 var(--size-2); font-size: var(--text-small); & > * + * { margin-left: var(--size-3); } :global(.icon) { margin-right: 0.2em; opacity: 0.8; } a { color: inherit; text-decoration: none; &:hover { color: var(--text-color); } } } .download-option { display: flex; padding: var(--size-2); text-align: left; background: none; border: 1px solid transparent; border-radius: 4px; cursor: pointer; :global(.icon) { margin-top: var(--icon-adjust); } :not(:hover) + & { border-top: 1px solid var(--gray-2); } &:hover, &:focus { background-color: var(--gray-1); border: 1px solid var(--gray-3); } & div { display: block; } } .download-option-content { margin-left: var(--size-3); font-size: var(--text-small); & > :first-child { margin-bottom: var(--size-2); color: var(--gray-6); font-weight: bold; } & > :not(:first-child) { margin-top: var(--size-1); color: var(--gray-5); } } </style>

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OpenZeppelin/contracts-wizard'

If you have feedback or need assistance with the MCP directory API, please join our Discord server