Skip to content

Commit 32fcc32

Browse files
authored
Merge branch 'master' into use-textdecoder-decoding-code-param-payload
2 parents e9142af + 114b15e commit 32fcc32

File tree

34 files changed

+695
-96
lines changed

34 files changed

+695
-96
lines changed

.env.local

Lines changed: 0 additions & 9 deletions
This file was deleted.

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Contributing
22

3-
Everyone is welcome to contribute to Remix's codebase and please join our [Discord](https://discord.gg/Sacg4gchvS).
3+
Everyone is welcome to contribute to Remix's codebase and please join our [Discord](https://discord.gg/4b2rE9U4D2).
44

55
## Development
66
Remix libraries work closely with [Remix IDE](https://remix.ethereum.org). Each library has a README to explain its application.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
[![GitHub contributors](https://img.shields.io/github/contributors/remix-project-org/remix-project?style=flat&logo=github)](https://github.com/remix-project-org/remix-project/graphs/contributors)
1313
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/remix-project-org/awesome-remix)
1414
[![GitHub](https://img.shields.io/github/license/remix-project-org/remix-project)](https://github.com/remix-project-org/remix-project/blob/master/LICENSE)
15-
[![Discord](https://img.shields.io/badge/join-discord-brightgreen.svg?style=flat&logo=discord)](https://discord.gg/Sacg4gchvS)
15+
[![Discord](https://img.shields.io/badge/join-discord-brightgreen.svg?style=flat&logo=discord)](https://discord.gg/4b2rE9U4D2)
1616
[![X Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=x&color=green)](https://x.com/ethereumremix)
1717

1818
</div>
@@ -266,5 +266,6 @@ parameters:
266266
- Official documentation: https://remix-ide.readthedocs.io/en/latest/
267267
- Curated list of Remix resources: https://github.com/remix-project-org/awesome-remix
268268
- Medium: https://medium.com/remix-ide
269+
- Linkedin: https://www.linkedin.com/company/ethereum-remix
269270
- X: https://x.com/ethereumremix
270-
- Join Discord: https://discord.gg/Sacg4gchvS
271+
- Join Discord: https://discord.gg/4b2rE9U4D2

apps/contract-verification/src/app/ContractVerificationPluginClient.ts

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { PluginClient } from '@remixproject/plugin'
22
import { createClient } from '@remixproject/plugin-webview'
3+
34
import EventManager from 'events'
4-
import { VERIFIERS, type ChainSettings, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier } from './types'
5+
import { VERIFIERS, type ChainSettings,Chain, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier, SubmittedContract, SubmittedContracts, VerificationReceipt } from './types'
56
import { mergeChainSettingsWithDefaults, validConfiguration } from './utils'
67
import { getVerifier } from './Verifiers'
8+
import { CompilerAbstract } from '@remix-project/remix-solidity'
79

810
export class ContractVerificationPluginClient extends PluginClient {
911
public internalEvents: EventManager
1012

1113
constructor() {
1214
super()
13-
this.methods = ['lookupAndSave']
15+
this.methods = ['lookupAndSave', 'verifyOnDeploy']
1416
this.internalEvents = new EventManager()
1517
createClient(this)
1618
this.onload()
@@ -62,8 +64,179 @@ export class ContractVerificationPluginClient extends PluginClient {
6264
}
6365
}
6466

67+
verifyOnDeploy = async (data: any): Promise<void> => {
68+
try {
69+
await this.call('terminal', 'log', { type: 'log', value: 'Verification process started...' })
70+
71+
const { chainId, currentChain, contractAddress, contractName, compilationResult, constructorArgs, etherscanApiKey } = data
72+
73+
if (!currentChain) {
74+
await this.call('terminal', 'log', { type: 'error', value: 'Chain data was not provided for verification.' })
75+
return
76+
}
77+
78+
const userSettings = this.getUserSettingsFromLocalStorage()
79+
80+
if (etherscanApiKey) {
81+
if (!userSettings.chains[chainId]) {
82+
userSettings.chains[chainId] = { verifiers: {} }
83+
}
84+
85+
if (!userSettings.chains[chainId].verifiers.Etherscan) {
86+
userSettings.chains[chainId].verifiers.Etherscan = {}
87+
}
88+
userSettings.chains[chainId].verifiers.Etherscan.apiKey = etherscanApiKey
89+
90+
if (!userSettings.chains[chainId].verifiers.Routescan) {
91+
userSettings.chains[chainId].verifiers.Routescan = {}
92+
}
93+
if (!userSettings.chains[chainId].verifiers.Routescan.apiKey){
94+
userSettings.chains[chainId].verifiers.Routescan.apiKey = "placeholder"
95+
}
96+
97+
window.localStorage.setItem("contract-verification:settings", JSON.stringify(userSettings))
98+
99+
}
100+
101+
const submittedContracts: SubmittedContracts = JSON.parse(window.localStorage.getItem('contract-verification:submitted-contracts') || '{}')
102+
103+
const filePath = Object.keys(compilationResult.data.contracts).find(path =>
104+
compilationResult.data.contracts[path][contractName]
105+
)
106+
if (!filePath) throw new Error(`Could not find file path for contract ${contractName}`)
107+
108+
const submittedContract: SubmittedContract = {
109+
id: `${chainId}-${contractAddress}`,
110+
address: contractAddress,
111+
chainId: chainId,
112+
filePath: filePath,
113+
contractName: contractName,
114+
abiEncodedConstructorArgs: constructorArgs,
115+
date: new Date().toISOString(),
116+
receipts: []
117+
}
118+
119+
const compilerAbstract: CompilerAbstract = compilationResult
120+
const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings)
121+
122+
const verificationPromises = []
123+
124+
if (validConfiguration(chainSettings, 'Sourcify')) {
125+
verificationPromises.push(this._verifyWithProvider('Sourcify', submittedContract, compilerAbstract, chainId, chainSettings))
126+
}
127+
128+
if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('routescan'))) {
129+
verificationPromises.push(this._verifyWithProvider('Routescan', submittedContract, compilerAbstract, chainId, chainSettings))
130+
}
131+
132+
if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.url.includes('blockscout'))) {
133+
verificationPromises.push(this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings))
134+
}
135+
136+
if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.includes('etherscan'))) {
137+
if (etherscanApiKey) {
138+
verificationPromises.push(this._verifyWithProvider('Etherscan', submittedContract, compilerAbstract, chainId, chainSettings))
139+
} else {
140+
await this.call('terminal', 'log', { type: 'warn', value: 'Etherscan verification skipped: API key not found in global Settings.' })
141+
}
142+
}
143+
144+
await Promise.all(verificationPromises)
145+
146+
submittedContracts[submittedContract.id] = submittedContract
147+
window.localStorage.setItem('contract-verification:submitted-contracts', JSON.stringify(submittedContracts))
148+
this.internalEvents.emit('submissionUpdated')
149+
150+
} catch (error) {
151+
await this.call('terminal', 'log', { type: 'error', value: `An unexpected error occurred during verification: ${error.message}` })
152+
}
153+
}
154+
155+
private _verifyWithProvider = async (
156+
providerName: VerifierIdentifier,
157+
submittedContract: SubmittedContract,
158+
compilerAbstract: CompilerAbstract,
159+
chainId: string,
160+
chainSettings: ChainSettings
161+
): Promise<void> => {
162+
let receipt: VerificationReceipt
163+
const verifierSettings = chainSettings.verifiers[providerName]
164+
const verifier = getVerifier(providerName, verifierSettings)
165+
166+
try {
167+
if (validConfiguration(chainSettings, providerName)) {
168+
169+
await this.call('terminal', 'log', { type: 'log', value: `Verifying with ${providerName}...` })
170+
171+
if (providerName === 'Etherscan' || providerName === 'Routescan' || providerName === 'Blockscout') {
172+
await new Promise(resolve => setTimeout(resolve, 10000))
173+
}
174+
175+
if (verifier && typeof verifier.verify === 'function') {
176+
const result = await verifier.verify(submittedContract, compilerAbstract)
177+
178+
receipt = {
179+
receiptId: result.receiptId || undefined,
180+
verifierInfo: { name: providerName, apiUrl: verifier.apiUrl },
181+
status: result.status,
182+
message: result.message,
183+
lookupUrl: result.lookupUrl,
184+
contractId: submittedContract.id,
185+
isProxyReceipt: false,
186+
failedChecks: 0
187+
}
188+
189+
const successMessage = `${providerName} verification successful.`
190+
await this.call('terminal', 'log', { type: 'info', value: successMessage })
191+
192+
if (result.lookupUrl) {
193+
const textMessage = `${result.lookupUrl}`
194+
await this.call('terminal', 'log', { type: 'info', value: textMessage })
195+
}
196+
} else {
197+
throw new Error(`${providerName} verifier is not properly configured or does not support direct verification.`)
198+
}
199+
}
200+
} catch (e) {
201+
if (e.message.includes('Unable to locate ContractCode')) {
202+
const checkUrl = `${verifier.explorerUrl}/address/${submittedContract.address}`;
203+
const friendlyMessage = `Initial verification failed, possibly due to a sync delay. Please check the status manually.`
204+
205+
await this.call('terminal', 'log', { type: 'warn', value: `${providerName}: ${friendlyMessage}` })
206+
207+
const textMessage = `Check Manually: ${checkUrl}`
208+
await this.call('terminal', 'log', { type: 'info', value: textMessage })
209+
210+
receipt = {
211+
verifierInfo: { name: providerName, apiUrl: verifier?.apiUrl || 'N/A' },
212+
status: 'failed',
213+
message: 'Failed initially (sync delay), check manually.',
214+
contractId: submittedContract.id,
215+
isProxyReceipt: false,
216+
failedChecks: 0
217+
}
218+
219+
} else {
220+
receipt = {
221+
verifierInfo: { name: providerName, apiUrl: verifier?.apiUrl || 'N/A' },
222+
status: 'failed',
223+
message: e.message,
224+
contractId: submittedContract.id,
225+
isProxyReceipt: false,
226+
failedChecks: 0
227+
}
228+
await this.call('terminal', 'log', { type: 'error', value: `${providerName} verification failed: ${e.message}` })
229+
}
230+
231+
} finally {
232+
if (receipt) {
233+
submittedContract.receipts.push(receipt)
234+
}
235+
}
236+
}
237+
65238
private getUserSettingsFromLocalStorage(): ContractVerificationSettings {
66-
const fallbackSettings = { chains: {} };
239+
const fallbackSettings = { chains: {} }
67240
try {
68241
const settings = window.localStorage.getItem("contract-verification:settings")
69242
return settings ? JSON.parse(settings) : fallbackSettings

apps/contract-verification/src/app/app.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,18 @@ const App = () => {
6868
.then((data) => setChains(data))
6969
.catch((error) => console.error('Failed to fetch chains.json:', error))
7070

71+
const submissionUpdatedListener = () => {
72+
const latestSubmissions = window.localStorage.getItem('contract-verification:submitted-contracts')
73+
if (latestSubmissions) {
74+
setSubmittedContracts(JSON.parse(latestSubmissions))
75+
}
76+
}
77+
plugin.internalEvents.on('submissionUpdated', submissionUpdatedListener)
78+
7179
// Clean up on unmount
7280
return () => {
7381
plugin.off('compilerArtefacts' as any, 'compilationSaved')
82+
plugin.internalEvents.removeListener('submissionUpdated', submissionUpdatedListener)
7483
}
7584
}, [])
7685

@@ -167,4 +176,4 @@ const App = () => {
167176
)
168177
}
169178

170-
export default App
179+
export default App

apps/learneth/src/pages/StepDetail/index.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,40 @@ function StepDetailPage() {
7272
)
7373
}
7474

75+
const VideoRenderer = ({
76+
node,
77+
src,
78+
alt,
79+
...props
80+
}: {
81+
node?: any;
82+
src?: string;
83+
alt?: string;
84+
[key: string]: any;
85+
}) => {
86+
if (alt === 'youtube') {
87+
/*
88+
<iframe width="560" height="315" src="https://www.youtube.com/embed/Eh1qgOurDxU?si=lz1JypmIJZ15OY4g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
89+
*/
90+
return (
91+
<div className="position-relative overflow-hidden" style={{ paddingBottom: '56.25%', maxWidth: '100%', height: '0' }}>
92+
<iframe
93+
style={{ width: '100%', height: '100%', position: 'absolute', top: 0, left: 0 }}
94+
src={src}
95+
title="YouTube video player"
96+
frameBorder="0"
97+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
98+
allowFullScreen
99+
/>
100+
</div>
101+
);
102+
}
103+
if (alt === 'video') {
104+
return <video controls src={src} style={{ maxWidth: '100%' }} />;
105+
}
106+
return <img src={src} alt={alt} {...props} />;
107+
};
108+
75109
return (
76110
<div className='pb-4'>
77111
<div className="fixed-top">
@@ -104,7 +138,7 @@ function StepDetailPage() {
104138
</>
105139
)}
106140
<div className="container-fluid">
107-
<Markdown rehypePlugins={[rehypeRaw]}>{clonedStep.markdown?.content}</Markdown>
141+
<Markdown components={{ img:VideoRenderer }} rehypePlugins={[rehypeRaw]}>{clonedStep.markdown?.content}</Markdown>
108142
</div>
109143
{clonedStep.test?.content ? (
110144
<>

0 commit comments

Comments
 (0)