Merge branch 'master' into pastedCodeSafety

pull/5344/head
STetsing 2 months ago committed by GitHub
commit 2a4e5fd15a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 64
      .circleci/config.yml
  2. 6
      .github/workflows/pr-reminder.yml
  3. 8
      CONTRIBUTING.md
  4. 2
      README.md
  5. 15
      apps/circuit-compiler/src/app/components/container.tsx
  6. 2
      apps/circuit-compiler/src/index.html
  7. 2
      apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts
  8. 2
      apps/contract-verification/src/app/Verifiers/BlockscoutVerifier.ts
  9. 22
      apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts
  10. 14
      apps/contract-verification/src/app/Verifiers/RoutescanVerifier.ts
  11. 4
      apps/contract-verification/src/app/Verifiers/index.ts
  12. 50
      apps/contract-verification/src/app/components/AccordionReceipt.tsx
  13. 1
      apps/contract-verification/src/app/components/NavMenu.tsx
  14. 28
      apps/contract-verification/src/app/components/SearchableChainDropdown.tsx
  15. 2
      apps/contract-verification/src/app/layouts/Default.tsx
  16. 4
      apps/contract-verification/src/app/types/VerificationTypes.ts
  17. 112
      apps/contract-verification/src/app/utils/default-apis.json
  18. 11
      apps/contract-verification/src/app/utils/default-settings.ts
  19. 28
      apps/contract-verification/src/app/views/LookupView.tsx
  20. 4
      apps/contract-verification/src/app/views/ReceiptsView.tsx
  21. 6
      apps/contract-verification/src/app/views/SettingsView.tsx
  22. 13
      apps/contract-verification/src/app/views/VerifyView.tsx
  23. 5
      apps/doc-viewer/src/app/App.tsx
  24. 2
      apps/learneth/README.md
  25. 9
      apps/quick-dapp/src/actions/index.ts
  26. 4
      apps/quick-dapp/src/components/DeployPanel/index.tsx
  27. 53
      apps/quick-dapp/src/components/ImageUpload/index.tsx
  28. 1
      apps/remix-dapp/src/assets/instance.json
  29. 2
      apps/remix-dapp/src/components/ContractGUI/index.tsx
  30. 2
      apps/remix-dapp/src/components/DappTop/index.tsx
  31. 4
      apps/remix-dapp/src/components/Home/mobile.tsx
  32. 4
      apps/remix-dapp/src/components/Home/pc.tsx
  33. 2
      apps/remix-dapp/src/components/UiTerminal/index.tsx
  34. 2
      apps/remix-dapp/src/locales/en/terminal.json
  35. 2
      apps/remix-dapp/src/locales/en/udapp.json
  36. 4
      apps/remix-dapp/src/utils/txRunner.ts
  37. 35
      apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts
  38. 25
      apps/remix-ide-e2e/src/commands/hidePopupPanel.ts
  39. 5
      apps/remix-ide-e2e/src/commands/hideToolTips.ts
  40. 3
      apps/remix-ide-e2e/src/commands/refreshPage.ts
  41. 36
      apps/remix-ide-e2e/src/commands/setupMetamask.ts
  42. 4
      apps/remix-ide-e2e/src/helpers/init.ts
  43. 52
      apps/remix-ide-e2e/src/tests/ai_panel.test.ts
  44. 3
      apps/remix-ide-e2e/src/tests/circom.test.ts
  45. 38
      apps/remix-ide-e2e/src/tests/contract_verification.test.ts
  46. 33
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  47. 4
      apps/remix-ide-e2e/src/tests/matomo.test.ts
  48. 433
      apps/remix-ide-e2e/src/tests/metamask.test.ts
  49. 123
      apps/remix-ide-e2e/src/tests/providers.test.ts
  50. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts
  51. 116
      apps/remix-ide-e2e/src/tests/script-runner.test.ts
  52. 1
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  53. 11
      apps/remix-ide-e2e/src/tests/url.test.ts
  54. 1
      apps/remix-ide-e2e/src/types/index.d.ts
  55. 2
      apps/remix-ide/ci/browser_test.sh
  56. 33
      apps/remix-ide/ci/metamask.sh
  57. 12
      apps/remix-ide/src/app.js
  58. 6
      apps/remix-ide/src/app/components/hidden-panel.tsx
  59. 2
      apps/remix-ide/src/app/components/panel.ts
  60. 123
      apps/remix-ide/src/app/components/popup-panel.tsx
  61. 4
      apps/remix-ide/src/app/components/preload.tsx
  62. 2
      apps/remix-ide/src/app/components/side-panel.tsx
  63. 8
      apps/remix-ide/src/app/panels/terminal.tsx
  64. 4
      apps/remix-ide/src/app/plugins/electron/remixAIDesktopPlugin.tsx
  65. 2
      apps/remix-ide/src/app/plugins/matomo.ts
  66. 4
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  67. 43
      apps/remix-ide/src/app/plugins/remixAIPlugin.tsx
  68. 2
      apps/remix-ide/src/app/plugins/vyper-compilation-details.tsx
  69. 38
      apps/remix-ide/src/app/providers/abstract-provider.tsx
  70. 2
      apps/remix-ide/src/app/tabs/compile-and-run.ts
  71. 8
      apps/remix-ide/src/app/tabs/compile-tab.js
  72. 3
      apps/remix-ide/src/app/tabs/locales/en/quickDapp.json
  73. 5
      apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json
  74. 2
      apps/remix-ide/src/app/tabs/locales/en/solUmlGen.json
  75. 2
      apps/remix-ide/src/app/tabs/locales/en/solidity.json
  76. 2
      apps/remix-ide/src/app/tabs/locales/en/terminal.json
  77. 2
      apps/remix-ide/src/app/tabs/locales/es/solUmlGen.json
  78. 2
      apps/remix-ide/src/app/tabs/locales/fr/solUmlGen.json
  79. 2
      apps/remix-ide/src/app/tabs/locales/it/solUmlGen.json
  80. 10
      apps/remix-ide/src/app/tabs/locales/ru/home.json
  81. 2
      apps/remix-ide/src/app/tabs/locales/ru/solidity.json
  82. 399
      apps/remix-ide/src/app/tabs/script-runner-ui.tsx
  83. 2
      apps/remix-ide/src/app/tabs/web3-provider.js
  84. 64
      apps/remix-ide/src/app/udapp/run-tab.tsx
  85. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-cerulean.min.css
  86. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css
  87. 3
      apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css
  88. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css
  89. 2
      apps/remix-ide/src/assets/css/themes/remix-black_undtds.css
  90. 2
      apps/remix-ide/src/assets/css/themes/remix-candy_ikhg4m.css
  91. 2
      apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css
  92. 3
      apps/remix-ide/src/assets/css/themes/remix-hacker_owl.css
  93. 2
      apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css
  94. 2
      apps/remix-ide/src/assets/css/themes/remix-midcentury_hrzph3.css
  95. 2
      apps/remix-ide/src/assets/css/themes/remix-unicorn.css
  96. 2
      apps/remix-ide/src/assets/css/themes/remix-violet.css
  97. 446
      apps/remix-ide/src/assets/fontawesome/css/all.css
  98. BIN
      apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.ttf
  99. BIN
      apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.woff2
  100. 77
      apps/remix-ide/src/assets/img/aiLogo.svg
  101. Some files were not shown because too many files have changed in this diff Show More

@ -111,7 +111,7 @@ jobs:
resource_class:
xlarge
working_directory: ~/remix-project
parallelism: 10
parallelism: 15
steps:
- run: ldd --version
- checkout
@ -132,7 +132,7 @@ jobs:
yarn add node-pty
yarn --ignore-optional
yarn add @remix-project/remix-ws-templates
./rundist.bash
./rundist.bash test_only
- run:
name: "Run tests"
command: |
@ -627,7 +627,7 @@ jobs:
remix-ide-browser:
docker:
- image: cimg/node:20.0.0-browsers
- image: cimg/node:20.17.0-browsers
resource_class:
xlarge
working_directory: ~/remix-project
@ -640,7 +640,10 @@ jobs:
type: string
jobsize:
type: string
parallelism: 10
parallelism:
type: integer
default: 1
parallelism: << parameters.parallelism >>
steps:
- checkout
- attach_workspace:
@ -688,7 +691,7 @@ jobs:
remix-test-plugins:
docker:
- image: cimg/node:20.0.0-browsers
- image: cimg/node:20.17.0-browsers
resource_class:
xlarge
working_directory: ~/remix-project
@ -781,38 +784,51 @@ workflows:
script: ["flaky.sh"]
job: ["nogroup"]
jobsize: ["1"]
parallelism: [1]
build_all:
unless: << pipeline.parameters.run_flaky_tests >>
jobs:
- build
- build-desktop:
filters:
branches:
only: [/.*desktop.*/, 'remix_beta']
- build-desktop
- build-remixdesktop-mac:
requires:
- build-desktop
matrix:
parameters:
arch: ["arm64", "x64"]
filters:
branches:
only: [/.*desktop.*/]
- test-remixdesktop-mac:
requires:
- build-desktop
filters:
branches:
only: [/.*desktop.*/]
- build-remixdesktop-windows:
requires:
- build-desktop
filters:
branches:
only: [/.*desktop.*/]
- sign-remixdesktop-windows:
requires:
- build-remixdesktop-windows
- build-remixdesktop-linux:
requires:
- build-desktop
filters:
branches:
only: [/.*desktop.*/]
- test-remixdesktop-linux:
requires:
- build-desktop
- test-remixdesktop-windows:
requires:
- build-desktop
filters:
branches:
only: [/.*desktop.*/]
- uploadartifacts:
requires:
- build-remixdesktop-mac
@ -823,7 +839,7 @@ workflows:
- test-remixdesktop-mac
filters:
branches:
only: [/.*desktop.*/]
only: [/.*desktop-master.*/]
- build-plugin:
matrix:
parameters:
@ -850,16 +866,33 @@ workflows:
requires:
- build
matrix:
alias: browser-tests
parameters:
browser: ["chrome", "firefox"]
script: ["browser_test.sh"]
job: ["0","1","2","3","4","5","6","7","8","9"]
jobsize: ["10"]
parallelism: [15]
- remix-ide-browser:
requires:
- build
matrix:
alias: metamask
parameters:
browser: ["chrome"]
script: ["metamask.sh"]
job: ["0"]
jobsize: ["10"]
parallelism: [1]
filters:
branches:
only: [/.*metamask.*/, 'master', 'remix_live', 'remix_beta']
- tests-passed:
requires:
- lint
- remix-libs
- remix-ide-browser
- browser-tests
- metamask
- plugins
- predeploy:
@ -872,7 +905,8 @@ workflows:
requires:
- lint
- remix-libs
- remix-ide-browser
- browser-tests
- metamask
- plugins
- predeploy
filters:
@ -884,7 +918,8 @@ workflows:
requires:
- lint
- remix-libs
- remix-ide-browser
- browser-tests
- metamask
- plugins
- predeploy
filters:
@ -896,7 +931,8 @@ workflows:
requires:
- lint
- remix-libs
- remix-ide-browser
- browser-tests
- metamask
- plugins
- predeploy
filters:

@ -3,7 +3,7 @@ name: PRs reviews reminder
on:
schedule:
- cron: "0 8 * * 1-5"
- cron: '0 9 * * 1-5'
- cron: '58 9 * * 1-5'
workflow_dispatch:
jobs:
@ -17,9 +17,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-11-04T18:00:00Z'
freeze-date: '2024-12-23T18:00:00Z'
- name: Reminder for standup
if: github.event.schedule == '0 9 * * 1-5'
if: github.event.schedule == '58 9 * * 1-5'
uses: Aniket-Engg/pr-reviews-reminder-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -5,13 +5,13 @@ Everyone is very welcome to contribute on the codebase of Remix. Please join our
## Development
Remix libraries work closely with [Remix IDE](https://remix.ethereum.org). Each library has a readme to explain its application.
When you add a code in any library, please ensure you add related unit tests.
When you add code in any library, please ensure you add related unit tests.
## Coding style
Please conform to [standard](https://standardjs.com/) for code styles.
## Submitting Pull Request
## Submitting Pull Requests
Please follow GitHub's standard model of making changes & submitting pull request which is very well explained [here](https://guides.github.com/activities/forking/). Make sure your code works fine locally before submitting a pull request.
## Internationalization
@ -20,14 +20,14 @@ Remix now supports Internationalization. Everyone is welcome to contribute to th
### How to make a string support intl?
First, put the string in the locale file located under `apps/remix-ide/src/app/tabs/locales/en`.
Each json file corresponds to a module. If the module does not exist, then create a new json and import it in the `index.js`.
Then you can replace the string with a intl component. The `id` prop will be the key of this string.
Then you can replace the string with an intl component. The `id` prop will be the key of this string.
```jsx
<label className="py-2 align-self-center m-0" style={{fontSize: "1.2rem"}}>
- Learn
+ <FormattedMessage id="home.learn" />
</label>
```
In some cases, jsx maybe not be acceptable, you can use `intl.formatMessage` .
In some cases, jsx may not be acceptable, you can use `intl.formatMessage` .
```jsx
<input
ref={searchInputRef}

@ -178,7 +178,7 @@ You need to have
### Splitting tests with groups
Groups can be used to group tests in a test file together. The advantage is you can avoid running long test files when you want to focus on a specific set of tests within a test file.x
Groups can be used to group tests in a test file together. The advantage is you can avoid running long test files when you want to focus on a specific set of tests within a test file.
These groups only apply to the test file, not across all test files. So for example group1 in the ballot is not related to a group1 in another test file.

@ -73,14 +73,20 @@ export function Container () {
full circom error: ${JSON.stringify(report, null, 2)}
explain why the error occurred and how to fix it.
`
await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message)
await circuitApp.plugin.call('popupPanel' as any, 'showPopupPanel', true)
setTimeout(async () => {
await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message)
}, 500)
} else {
const message = `
error message: ${error}
full circom error: ${JSON.stringify(report, null, 2)}
explain why the error occurred and how to fix it.
`
await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message)
await circuitApp.plugin.call('popupPanel' as any, 'showPopupPanel', true)
setTimeout(async () => {
await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message)
}, 500)
}
} else {
const error = report.message
@ -89,7 +95,10 @@ export function Container () {
full circom error: ${JSON.stringify(report, null, 2)}
explain why the error occurred and how to fix it.
`
await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message)
await circuitApp.plugin.call('popupPanel' as any, 'showPopupPanel', true)
setTimeout(async () => {
await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message)
}, 500)
}
}

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous"/> -->
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> -->
<link rel="stylesheet" integrity="ha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
<link rel="stylesheet" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>

@ -3,7 +3,7 @@ import type { LookupResponse, SubmittedContract, VerificationResponse } from '..
// Optional function definitions
export interface AbstractVerifier {
verifyProxy(submittedContract: SubmittedContract): Promise<VerificationResponse>
verifyProxy?(submittedContract: SubmittedContract): Promise<VerificationResponse>
checkVerificationStatus?(receiptId: string): Promise<VerificationResponse>
checkProxyVerificationStatus?(receiptId: string): Promise<VerificationResponse>
}

@ -26,7 +26,7 @@ export class BlockscoutVerifier extends EtherscanVerifier {
super(apiUrl, apiUrl, undefined)
}
getContractCodeUrl(address: string): string {
getContractCodeUrl(address: string, chainId: string): string {
const url = new URL(this.explorerUrl + `/address/${address}`)
url.searchParams.append('tab', 'contract')
return url.href

@ -74,9 +74,10 @@ export class EtherscanVerifier extends AbstractVerifier {
}
const verificationResponse: EtherscanRpcResponse = await response.json()
const lookupUrl = this.getContractCodeUrl(submittedContract.address, submittedContract.chainId)
if (verificationResponse.result.includes('already verified')) {
return { status: 'already verified', receiptId: null, lookupUrl: this.getContractCodeUrl(submittedContract.address) }
return { status: 'already verified', receiptId: null, lookupUrl }
}
if (verificationResponse.status !== '1' || verificationResponse.message !== 'OK') {
@ -84,7 +85,6 @@ export class EtherscanVerifier extends AbstractVerifier {
throw new Error(verificationResponse.result)
}
const lookupUrl = this.getContractCodeUrl(submittedContract.address)
return { status: 'pending', receiptId: verificationResponse.result, lookupUrl }
}
@ -117,6 +117,10 @@ export class EtherscanVerifier extends AbstractVerifier {
const verificationResponse: EtherscanRpcResponse = await response.json()
if (verificationResponse.message === 'Smart-contract not found or is not verified') {
return { status: 'failed', receiptId: null, message: verificationResponse.message }
}
if (verificationResponse.status !== '1' || verificationResponse.message !== 'OK') {
console.error('Error on Etherscan API proxy verification at ' + this.apiUrl + '\nStatus: ' + verificationResponse.status + '\nMessage: ' + verificationResponse.message + '\nResult: ' + verificationResponse.result)
throw new Error(verificationResponse.result)
@ -144,7 +148,7 @@ export class EtherscanVerifier extends AbstractVerifier {
const checkStatusResponse: EtherscanCheckStatusResponse = await response.json()
if (checkStatusResponse.result.startsWith('Fail - Unable to verify')) {
if (checkStatusResponse.result.startsWith('Fail - Unable to verify') || (checkStatusResponse.result as string) === 'Error: contract does not exist') {
return { status: 'failed', receiptId, message: checkStatusResponse.result }
}
if (checkStatusResponse.result === 'Pending in queue') {
@ -229,23 +233,23 @@ export class EtherscanVerifier extends AbstractVerifier {
const lookupResponse: EtherscanGetSourceCodeResponse = await response.json()
if (lookupResponse.result[0].ABI === 'Contract source code not verified' || !lookupResponse.result[0].SourceCode || (lookupResponse.result as unknown as string) === 'Contract source code not verified') {
return { status: 'not verified' }
}
if (lookupResponse.status !== '1' || !lookupResponse.message.startsWith('OK')) {
const errorResponse = lookupResponse as unknown as EtherscanRpcResponse
console.error('Error on Etherscan API lookup at ' + this.apiUrl + '\nStatus: ' + errorResponse.status + '\nMessage: ' + errorResponse.message + '\nResult: ' + errorResponse.result)
throw new Error(errorResponse.result)
}
if (lookupResponse.result[0].ABI === 'Contract source code not verified' || !lookupResponse.result[0].SourceCode) {
return { status: 'not verified' }
}
const lookupUrl = this.getContractCodeUrl(contractAddress)
const lookupUrl = this.getContractCodeUrl(contractAddress, chainId)
const { sourceFiles, targetFilePath } = this.processReceivedFiles(lookupResponse.result[0], contractAddress, chainId)
return { status: 'verified', lookupUrl, sourceFiles, targetFilePath }
}
getContractCodeUrl(address: string): string {
getContractCodeUrl(address: string, chainId: string): string {
const url = new URL(this.explorerUrl + `/address/${address}#code`)
return url.href
}

@ -0,0 +1,14 @@
import { EtherscanVerifier } from './EtherscanVerifier'
export class RoutescanVerifier extends EtherscanVerifier {
LOOKUP_STORE_DIR = 'routescan-verified'
// Routescan does not support proxy verification
verifyProxy = undefined
checkProxyVerificationStatus = undefined
getContractCodeUrl(address: string, chainId: string): string {
const url = new URL(this.explorerUrl + `/address/${address}/contract/${chainId}/code`)
return url.href
}
}

@ -3,11 +3,13 @@ import { AbstractVerifier } from './AbstractVerifier'
import { BlockscoutVerifier } from './BlockscoutVerifier'
import { EtherscanVerifier } from './EtherscanVerifier'
import { SourcifyVerifier } from './SourcifyVerifier'
import { RoutescanVerifier } from './RoutescanVerifier'
export { AbstractVerifier } from './AbstractVerifier'
export { BlockscoutVerifier } from './BlockscoutVerifier'
export { SourcifyVerifier } from './SourcifyVerifier'
export { EtherscanVerifier } from './EtherscanVerifier'
export { RoutescanVerifier } from './RoutescanVerifier'
export function getVerifier(identifier: VerifierIdentifier, settings: VerifierSettings): AbstractVerifier {
switch (identifier) {
@ -26,5 +28,7 @@ export function getVerifier(identifier: VerifierIdentifier, settings: VerifierSe
return new EtherscanVerifier(settings.apiUrl, settings.explorerUrl, settings.apiKey)
case 'Blockscout':
return new BlockscoutVerifier(settings.apiUrl)
case 'Routescan':
return new RoutescanVerifier(settings.apiUrl, settings.explorerUrl, settings.apiKey)
}
}

@ -33,7 +33,11 @@ export const AccordionReceipt: React.FC<AccordionReceiptProps> = ({ contract, in
</button>
<div className="small w-100 text-uppercase overflow-hidden text-nowrap">
<CustomTooltip placement="bottom" tooltipClasses=" text-break" tooltipText={`Contract: ${contract.contractName}, Address: ${contract.address}, Chain: ${chainName}, Proxy: ${contract.proxyAddress}`}>
<CustomTooltip
placement="bottom"
tooltipClasses=" text-break"
tooltipText={`Contract: ${contract.contractName}, Address: ${contract.address}, Chain: ${chainName}, Proxy: ${contract.proxyAddress}`}
>
<span>
{contract.contractName} at {shortenAddress(contract.address)} {contract.proxyAddress ? 'with proxy' : ''}
</span>
@ -88,16 +92,40 @@ const ReceiptsBody = ({ receipts }: { receipts: VerificationReceipt[] }) => {
return (
<ul className="list-group">
{receipts.map((receipt) => (
<li className="list-group-item">
<CustomTooltip placement="top" tooltipClasses=" text-break" tooltipText={`API: ${receipt.verifierInfo.apiUrl}`}>
<span className="font-weight-bold medium">{receipt.verifierInfo.name}</span>
</CustomTooltip>
<CustomTooltip placement="top" tooltipClasses=" text-break" tooltipTextClasses="text-capitalize" tooltipText={`Status: ${receipt.status}${receipt.message ? `, Message: ${receipt.message}` : ''}`}>
<span className="ml-2">{['verified', 'partially verified', 'already verified'].includes(receipt.status) ? <i className="fas fa-check"></i> : receipt.status === 'fully verified' ? <i className="fas fa-check-double"></i> : receipt.status === 'failed' ? <i className="fas fa-xmark"></i> : ['pending', 'awaiting implementation verification'].includes(receipt.status) ? <i className="fas fa-spinner fa-spin"></i> : <i className="fas fa-question"></i>}</span>
</CustomTooltip>
<span className="ml-2">{!!receipt.lookupUrl && <a href={receipt.lookupUrl} target="_blank" className="fa fas fa-arrow-up-right-from-square"></a>}</span>
<li
key={`${receipt.contractId}-${receipt.verifierInfo.name}${receipt.isProxyReceipt ? '-proxy' : ''}-${receipt.receiptId}`}
className="list-group-item d-flex flex-row align-items-center"
>
<CustomTooltip
placement="top"
tooltipClasses=" text-break"
tooltipTextClasses="text-capitalize"
tooltipText={`Status: ${receipt.status}${receipt.message ? `, Message: ${receipt.message}` : ''}`}
>
<span className="mr-2">
{['verified', 'partially verified', 'already verified'].includes(receipt.status) ?
<i className="fas fa-check text-success px-1"></i> :
receipt.status === 'fully verified' ?
<i className="fas fa-check-double text-success px-1"></i> :
receipt.status === 'failed' ?
<i className="fas fa-xmark text-warning px-1"></i> :
['pending', 'awaiting implementation verification'].includes(receipt.status) ?
<i className="fas fa-spinner fa-spin"></i> :
<i className="fas fa-question"></i>
}
</span>
</CustomTooltip>
<div className="d-flex flex-row w-100 justify-content-between">
<CustomTooltip placement="top" tooltipClasses=" text-break" tooltipText={`API: ${receipt.verifierInfo.apiUrl}`}>
<span className="font-weight-bold pr-2">{receipt.verifierInfo.name}</span>
</CustomTooltip>
<div className="ml-1">
{!!receipt.lookupUrl && receipt.verifierInfo.name === 'Blockscout' ?
<CopyToClipboard classList="pr-0 py-0" tip="Copy code URL" content={receipt.lookupUrl} direction="top" /> :
!!receipt.lookupUrl && <a href={receipt.lookupUrl} target="_blank" className="fa fas fa-arrow-up-right-from-square"></a>
}
</div>
</div>
</li>
))}
</ul>

@ -10,6 +10,7 @@ interface NavItemProps {
const NavItem: React.FC<NavItemProps> = ({ to, icon, title }) => {
return (
<NavLink
data-id={`${title}Tab`}
to={to}
className={({ isActive }) => 'text-decoration-none d-flex px-1 py-1 flex-column justify-content-center small ' + (isActive ? "bg-light border-top border-left border-right" : "border-0 bg-transparent")}
>

@ -37,7 +37,6 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
const [searchTerm, setSearchTerm] = useState(selectedChain ? getChainDescriptor(selectedChain) : '')
const [isOpen, setIsOpen] = useState(false)
const [filteredOptions, setFilteredOptions] = useState<Chain[]>(dropdownChains)
const dropdownRef = useRef<HTMLDivElement>(null)
const fuse = new Fuse(dropdownChains, {
@ -45,14 +44,7 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
threshold: 0.3,
})
useEffect(() => {
if (searchTerm === '') {
setFilteredOptions(dropdownChains)
} else {
const result = fuse.search(searchTerm)
setFilteredOptions(result.map(({ item }) => item))
}
}, [searchTerm, dropdownChains])
const filteredOptions = searchTerm ? fuse.search(searchTerm).map(({ item }) => item) : dropdownChains
// Close dropdown when user clicks outside
useEffect(() => {
@ -98,16 +90,14 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
{' '}
{/* Add ref here */}
<label htmlFor={id}>{label}</label>
<input type="text" value={searchTerm} onChange={handleInputChange} onClick={openDropdown} placeholder="Select a chain" className="form-control" />
{isOpen && (
<ul className="dropdown-menu show w-100 bg-light" style={{ maxHeight: '400px', overflowY: 'auto' }}>
{filteredOptions.map((chain) => (
<li key={chain.chainId} onClick={() => handleOptionClick(chain)} className={`dropdown-item text-dark ${selectedChain?.chainId === chain.chainId ? 'active' : ''}`} style={{ cursor: 'pointer', whiteSpace: 'normal' }}>
{getChainDescriptor(chain)}
</li>
))}
</ul>
)}
<input type="text" value={searchTerm} onChange={handleInputChange} onClick={openDropdown} data-id="chainDropdownbox" placeholder="Select a chain" className="form-control" />
<ul className="dropdown-menu show w-100 bg-light" style={{ maxHeight: '400px', overflowY: 'auto', display: isOpen ? 'initial' : 'none' }}>
{filteredOptions.map((chain) => (
<li key={chain.chainId} onClick={() => handleOptionClick(chain)} data-id={chain.chainId} className={`dropdown-item text-dark ${selectedChain?.chainId === chain.chainId ? 'active' : ''}`} style={{ cursor: 'pointer', whiteSpace: 'normal' }}>
{getChainDescriptor(chain)}
</li>
))}
</ul>
</div>
)
}

@ -13,7 +13,7 @@ export const DefaultLayout = ({ children, title, description }: PropsWithChildre
<div className="d-flex flex-column h-100">
<NavMenu />
<div className="py-4 px-3 flex-grow-1 bg-light" style={{ overflowY: 'auto' }}>
<div>
<div data-id={`${title}Description`}>
<p className="text-center" style={{ fontSize: '0.8rem' }}>
{description}
</p>

@ -17,8 +17,8 @@ export interface Chain {
infoURL?: string
}
export type VerifierIdentifier = 'Sourcify' | 'Etherscan' | 'Blockscout'
export const VERIFIERS: VerifierIdentifier[] = ['Sourcify', 'Etherscan', 'Blockscout']
export type VerifierIdentifier = 'Sourcify' | 'Etherscan' | 'Blockscout' | 'Routescan'
export const VERIFIERS: VerifierIdentifier[] = ['Sourcify', 'Etherscan', 'Blockscout', 'Routescan']
export interface VerifierInfo {
name: VerifierIdentifier

@ -572,5 +572,117 @@
"81247166294": {
"apiUrl": "https://testnet.otoscan.io"
}
},
"Routescan": {
"mainnetExplorerUrl": "https://routescan.io",
"testnetExplorerUrl": "https://testnet.routescan.io",
"apiUrl": "https://api.routescan.io/v2/network/${CHAIN_TYPE}/evm/${CHAIN_ID}/etherscan",
"8453": { "type": "mainnet" },
"167000": { "type": "mainnet" },
"357": { "type": "mainnet" },
"1": { "type": "mainnet" },
"19": { "type": "mainnet" },
"10": { "type": "mainnet" },
"81457": { "type": "mainnet" },
"53935": { "type": "mainnet" },
"432204": { "type": "mainnet" },
"480": { "type": "mainnet" },
"14": { "type": "mainnet" },
"5000": { "type": "mainnet" },
"254": { "type": "mainnet" },
"43114": { "type": "mainnet" },
"7777777": { "type": "mainnet" },
"324": { "type": "mainnet" },
"7560": { "type": "mainnet" },
"185": { "type": "mainnet" },
"888888888": { "type": "mainnet" },
"34443": { "type": "mainnet" },
"88888": { "type": "mainnet" },
"20240603": { "type": "mainnet" },
"6119": { "type": "mainnet" },
"291": { "type": "mainnet" },
"252": { "type": "mainnet" },
"1088": { "type": "mainnet" },
"8008": { "type": "mainnet" },
"288": { "type": "mainnet" },
"65536": { "type": "mainnet" },
"424": { "type": "mainnet" },
"183": { "type": "mainnet" },
"33979": { "type": "mainnet" },
"10849": { "type": "mainnet" },
"2044": { "type": "mainnet" },
"8888": { "type": "mainnet" },
"1853": { "type": "mainnet" },
"56288": { "type": "mainnet" },
"710420": { "type": "mainnet" },
"4337": { "type": "mainnet" },
"333000333": { "type": "mainnet" },
"3011": { "type": "mainnet" },
"1234": { "type": "mainnet" },
"504441": { "type": "mainnet" },
"7887": { "type": "mainnet" },
"7979": { "type": "mainnet" },
"10507": { "type": "mainnet" },
"5566": { "type": "mainnet" },
"151": { "type": "mainnet" },
"62707": { "type": "mainnet" },
"70953": { "type": "mainnet" },
"64165": { "type": "testnet" },
"49321": { "type": "testnet" },
"80084": { "type": "testnet" },
"84532": { "type": "testnet" },
"70805": { "type": "testnet" },
"421614": { "type": "testnet" },
"11155111": { "type": "testnet" },
"1946": { "type": "testnet" },
"17000": { "type": "testnet" },
"11155420": { "type": "testnet" },
"16": { "type": "testnet" },
"168587773": { "type": "testnet" },
"919": { "type": "testnet" },
"999999999": { "type": "testnet" },
"4801": { "type": "testnet" },
"2233": { "type": "testnet" },
"114": { "type": "testnet" },
"4460": { "type": "testnet" },
"2522": { "type": "testnet" },
"20241133": { "type": "testnet" },
"233": { "type": "testnet" },
"28122024": { "type": "testnet" },
"10888": { "type": "testnet" },
"80008": { "type": "testnet" },
"3397901": { "type": "testnet" },
"9728": { "type": "testnet" },
"1687": { "type": "testnet" },
"28882": { "type": "testnet" },
"88882": { "type": "testnet" },
"43113": { "type": "testnet" },
"164": { "type": "testnet" },
"111557560": { "type": "testnet" },
"167009": { "type": "testnet" },
"920637907288165": { "type": "testnet" },
"153": { "type": "testnet" },
"335": { "type": "testnet" },
"432201": { "type": "testnet" },
"9270": { "type": "testnet" },
"7589": { "type": "testnet" },
"686669576": { "type": "testnet" },
"431234": { "type": "testnet" },
"3939": { "type": "testnet" },
"26659": { "type": "testnet" },
"3012": { "type": "testnet" },
"555666": { "type": "testnet" },
"7210": { "type": "testnet" },
"173750": { "type": "testnet" },
"7222": { "type": "testnet" },
"779672": { "type": "testnet" },
"749": { "type": "testnet" },
"167008": { "type": "testnet" },
"31335": { "type": "testnet" },
"80085": { "type": "testnet" },
"10880": { "type": "testnet" },
"55551": { "type": "testnet" },
"25043": { "type": "testnet" },
"8082": { "type": "testnet" }
}
}

@ -13,6 +13,17 @@ export function mergeChainSettingsWithDefaults(chainId: string, userSettings: Co
let defaultsForVerifier: VerifierSettings
if (verifierId === 'Sourcify') {
defaultsForVerifier = DEFAULT_APIS['Sourcify']
} else if (verifierId === 'Routescan') {
const routescanDefaults = DEFAULT_APIS['Routescan']
if (!routescanDefaults[chainId]) {
defaultsForVerifier = {}
} else {
const explorerUrl = routescanDefaults[chainId]?.type === 'mainnet' ? routescanDefaults.mainnetExplorerUrl : routescanDefaults.testnetExplorerUrl
const apiUrl = routescanDefaults.apiUrl.replace('${CHAIN_TYPE}', routescanDefaults[chainId]?.type).replace('${CHAIN_ID}', chainId)
defaultsForVerifier = { explorerUrl, apiUrl }
}
} else {
defaultsForVerifier = DEFAULT_APIS[verifierId][chainId] ?? {}
}

@ -9,6 +9,7 @@ import { getVerifier } from '../Verifiers'
import { useNavigate } from 'react-router-dom'
import { VerifyFormContext } from '../VerifyFormContext'
import { useSourcifySupported } from '../hooks/useSourcifySupported'
import { CopyToClipboard } from '@remix-ui/clipboard'
export const LookupView = () => {
const { settings, clientInstance } = useContext(AppContext)
@ -61,9 +62,9 @@ export const LookupView = () => {
}
const sendToMatomo = async (eventAction: string, eventName: string) => {
await clientInstance.call('matomo' as any, 'track', ['trackEvent', 'ContractVerification', eventAction, eventName]);
await clientInstance.call('matomo' as any, 'track', ['trackEvent', 'ContractVerification', eventAction, eventName])
}
const handleOpenInRemix = async (lookupResponse: LookupResponse) => {
for (const source of lookupResponse.sourceFiles ?? []) {
try {
@ -74,7 +75,7 @@ export const LookupView = () => {
}
try {
await clientInstance.call('fileManager', 'open', lookupResponse.targetFilePath)
await sendToMatomo('lookup', "openInRemix On: " + selectedChain)
await sendToMatomo('lookup', 'openInRemix On: ' + selectedChain)
} catch (err) {
console.error(`Error focusing file ${lookupResponse.targetFilePath}: ${err.message}`)
}
@ -84,20 +85,13 @@ export const LookupView = () => {
<>
<form onSubmit={handleLookup}>
<SearchableChainDropdown label="Chain" id="network-dropdown" selectedChain={selectedChain} setSelectedChain={setSelectedChain} />
<ContractAddressInput
label="Contract Address"
id="contract-address"
contractAddress={contractAddress}
setContractAddress={setContractAddress}
contractAddressError={contractAddressError}
setContractAddressError={setContractAddressError}
/>
<ContractAddressInput label="Contract Address" id="contract-address" contractAddress={contractAddress} setContractAddress={setContractAddress} contractAddressError={contractAddressError} setContractAddressError={setContractAddressError} />
<button type="submit" className="btn w-100 btn-primary" disabled={submitDisabled}>
Lookup
</button>
</form>
<div className="pt-3">
{ chainSettings &&
{chainSettings &&
VERIFIERS.map((verifierId) => {
if (!validConfiguration(chainSettings, verifierId)) {
return (
@ -131,8 +125,9 @@ export const LookupView = () => {
return (
<div key={verifierId} className="pt-4">
<div>
<span className="font-weight-bold">{verifierId}</span> <span className="text-secondary">{chainSettings.verifiers[verifierId].apiUrl}</span>
<div className="d-flex align-items-center">
<span className="font-weight-bold">{verifierId}&nbsp;</span>
<span className="text-secondary d-inline-block text-truncate mw-100">{chainSettings.verifiers[verifierId].apiUrl}</span>
</div>
{!!loadingVerifiers[verifierId] && (
<div className="pt-2 d-flex justify-content-center">
@ -146,7 +141,7 @@ export const LookupView = () => {
<span className="font-weight-bold" style={{ textTransform: 'capitalize' }}>
{lookupResults[verifierId].status}
</span>{' '}
{!!lookupResults[verifierId].lookupUrl && <a href={lookupResults[verifierId].lookupUrl} target="_blank" className="fa fas fa-arrow-up-right-from-square"></a>}
{!!lookupResults[verifierId].lookupUrl && verifierId === 'Blockscout' ? <CopyToClipboard tip="Copy code URL" content={lookupResults[verifierId].lookupUrl} direction="top" /> : !!lookupResults[verifierId].lookupUrl && <a href={lookupResults[verifierId].lookupUrl} target="_blank" className="fa fas fa-arrow-up-right-from-square"></a>}
</div>
{!!lookupResults[verifierId].sourceFiles && lookupResults[verifierId].sourceFiles.length > 0 && (
<div className="pt-2 d-flex flex-row justify-content-center">
@ -159,8 +154,7 @@ export const LookupView = () => {
)}
</div>
)
})
}
})}
</div>
</>
)

@ -9,8 +9,8 @@ export const ReceiptsView = () => {
return (
<div>
{contracts.length > 0 ? contracts.map((contract, index) => (
<AccordionReceipt contract={contract} index={index} />
)) : <div className="text-center mt-5">No contracts submitted for verification</div>}
<AccordionReceipt key={contract.id} contract={contract} index={index} />
)) : <div className="text-center mt-5" data-id="noContractsSubmitted">No contracts submitted for verification</div>}
</div>
)
}

@ -47,6 +47,12 @@ export const SettingsView = () => {
<span className="font-weight-bold">Blockscout - {selectedChain.name}</span>
<ConfigInput label="Instance URL" id="blockscout-api-url" secret={false} initialValue={chainSettings.verifiers['Blockscout']?.apiUrl ?? ''} saveResult={(result) => handleChange('Blockscout', 'apiUrl', result)} />
</div>
<div className="p-2 my-2 border">
<span className="font-weight-bold">Routescan - {selectedChain.name}</span>
<ConfigInput label="API Key (optional)" id="routescan-api-key" secret={true} initialValue={chainSettings.verifiers['Routescan']?.apiKey ?? ''} saveResult={(result) => handleChange('Routescan', 'apiKey', result)} />
<ConfigInput label="API URL" id="routescan-api-url" secret={false} initialValue={chainSettings.verifiers['Routescan']?.apiUrl ?? ''} saveResult={(result) => handleChange('Routescan', 'apiUrl', result)} />
<ConfigInput label="Explorer URL" id="routescan-explorer-url" secret={false} initialValue={chainSettings.verifiers['Routescan']?.explorerUrl ?? ''} saveResult={(result) => handleChange('Routescan', 'explorerUrl', result)} />
</div>
</div>
)}
</>

@ -41,14 +41,13 @@ export const VerifyView = () => {
setEnabledVerifiers({ ...enabledVerifiers, [verifierId]: checked })
}
const sendToMatomo = async (eventAction: string, eventName: string) => {
await clientInstance.call("matomo" as any, 'track', ['trackEvent', 'ContractVerification', eventAction, eventName]);
}
const handleVerify = async (e) => {
e.preventDefault()
const { triggerFilePath, filePath, contractName } = selectedContract
const compilerAbstract = compilationOutput[triggerFilePath]
if (!compilerAbstract) {
@ -68,9 +67,7 @@ export const VerifyView = () => {
name: verifierId as VerifierIdentifier,
}
receipts.push({ verifierInfo, status: 'pending', contractId, isProxyReceipt: false, failedChecks: 0 })
if (enabledVerifiers.Blockscout) await sendToMatomo('verify', "verifyWith: Blockscout On: " + selectedChain + " IsProxy: " + (hasProxy && !proxyAddress))
if (enabledVerifiers.Etherscan) await sendToMatomo('verify', "verifyWithEtherscan On: " + selectedChain + " IsProxy: " + (hasProxy && !proxyAddress))
if (enabledVerifiers.Sourcify) await sendToMatomo('verify', "verifyWithSourcify On: " + selectedChain + " IsProxy: " + (hasProxy && !proxyAddress))
await sendToMatomo('verify', `verifyWith${verifierId} On: ${selectedChain?.chainId} IsProxy: ${!!(hasProxy && proxyAddress)}`)
}
const newSubmittedContract: SubmittedContract = {
@ -235,7 +232,7 @@ export const VerifyView = () => {
<label
htmlFor={`verifier-${verifierId}`}
className={`m-0 form-check-label custom-control-label large font-weight-bold${!disabledVerifier ? '' : ' text-secondary'}`}
style={{ fontSize: '1rem', lineHeight: '1.5', color: 'var(--text)' }}
style={{ fontSize: '1rem', color: 'var(--text)' }}
>
{verifierId}
</label>
@ -256,7 +253,7 @@ export const VerifyView = () => {
</span>
</CustomTooltip>
) : (
<span className="text-secondary">{chainSettings.verifiers[verifierId].apiUrl}</span>
<span className="text-secondary d-inline-block text-truncate mw-100">{chainSettings.verifiers[verifierId].apiUrl}</span>
)}
</div>
</div>
@ -269,7 +266,7 @@ export const VerifyView = () => {
!selectedContract ? "Please select the contract (compile if needed)." :
((hasProxy && !!proxyAddressError) || (hasProxy && !proxyAddress)) ? "Please provide a valid proxy contract address." :
"Please provide all necessary data to verify") // Is not expected to be a case
: "Verify with selected tools"}>
: "Verify with selected tools"}>
<button type="submit" className="w-100 btn btn-primary mt-3" disabled={submitDisabled}>
Verify
</button>

@ -15,11 +15,12 @@ export default function App() {
const edit = () => {
if (!client.mdFile) return
client.call('fileManager', 'open' as any, client.mdFile)
client.call('fileManager', 'switchFile' as any, client.mdFile) //@TODO check why this doesn't work
}
return (
<>
<div className="m-5 p-2">
<button className="btn btn-secondary mb-2" onClick={edit}>EDIT</button>
<div className="bg-light p-5">
<button className="btn btn-sm border mb-2" onClick={edit}>EDIT</button>
<ReactMarkdown children={contents} remarkPlugins={[remarkGfm]} />
</div>
</>

@ -72,7 +72,7 @@ Each step is a directory containing:
- a readme describing the step, what to do.
- sol files:
- these can be sol files and test sol files. The test files should be name yoursolname_test.sol
- these can be sol files and test sol files. The test files should be named yoursolname_test.sol
- ANSWER files: these are named yoursolname_answer.sol and can be used to show the solution or the correct answer. The plugin will load the
file in the IDE when a user clicks on 'Show Answer'
- js files

@ -171,6 +171,7 @@ export const deploy = async (payload: any, callback: any) => {
...instance,
shortname: payload.shortname,
shareTo: payload.shareTo,
showLogo: !!logo,
})
const files: Record<string, string> = {
@ -192,7 +193,9 @@ export const deploy = async (payload: any, callback: any) => {
files[`dir/${path}`] = resp.data;
}
files['dir/assets/logo.png'] = logo
if (logo) {
files['dir/assets/logo.png'] = logo
}
files['dir/CORS'] = '*'
files['dir/index.html'] = files['dir/index.html'].replace(
'assets/css/themes/remix-dark_tvx1s2.css',
@ -341,7 +344,7 @@ export const initInstance = async ({
}
: { A: ids };
const logo = await axios.get('https://dev.remix-dapp.pages.dev/logo.png', { responseType: 'arraybuffer' })
// const logo = await axios.get('https://dev.remix-dapp.pages.dev/logo.png', { responseType: 'arraybuffer' })
await dispatch({
type: 'SET_INSTANCE',
@ -353,7 +356,7 @@ export const initInstance = async ({
natSpec,
solcVersion: getVersion(solcVersion),
...lowLevel,
logo: logo.data,
// logo: logo.data,
},
});
};

@ -51,6 +51,8 @@ function DeployPanel(): JSX.Element {
<div className="col-3 d-inline-block">
<h3 className="mb-3" data-id="quick-dapp-admin">QuickDapp <FormattedMessage id="quickDapp.admin" /></h3>
<Button
size="sm"
style={{ height: 32 }}
data-id="resetFunctions"
onClick={() => {
resetInstance();
@ -59,6 +61,8 @@ function DeployPanel(): JSX.Element {
<FormattedMessage id="quickDapp.resetFunctions" />
</Button>
<Button
size="sm"
style={{ height: 32, width: 100 }}
data-id="deleteDapp"
className="ml-3"
onClick={() => {

@ -29,20 +29,47 @@ const ImageUpload = () => {
}
return (
<div className="col-3 pr-0">
<div className="col-3 pr-0 my-2 d-flex justify-content-center align-items-center">
<input data-id="uploadLogo" className="d-none" type="file" accept="image/*" onChange={handleImageChange} id="upload-button" />
<CustomTooltip
placement="right"
tooltipText={intl.formatMessage({ id: 'quickDapp.uploadLogoTooltip' })}
>
<label htmlFor="upload-button" className="cursor_pointer d-flex justify-content-center align-items-center position-relative" style={{ height: 170 }}>
{logo ? (
<img src={preview} alt="preview" style={{ width: 120, height: 120 }} />
) : (
<i className="fas fa-upload" style={{ fontSize: 120 }}></i>
)}
</label>
</CustomTooltip>
{logo ? (
<div className='position-absolute'>
<img src={preview} alt="preview" style={{ width: 95, height: 95 }} />
<CustomTooltip
placement="right"
tooltipText={intl.formatMessage({ id: 'quickDapp.deleteLogoTooltip' })}
>
<span
className="btn position-absolute"
style={{
top: -30,
right: -30,
}}
onClick={(event) => {
event.preventDefault()
// @ts-expect-error
document.getElementById('upload-button').value = ''
dispatch({ type: 'SET_INSTANCE', payload: { logo: '' } })
}}
>
<i className="fas fa-times text-dark"></i>
</span>
</CustomTooltip>
</div>
) : (
<div className="bg-light" style={{ height: 158.5, width: 158.5 }}>
<div style={{ padding: 15 }}>
<div className='mt-2' style={{ fontSize: 15, lineHeight: '18px' }}>A logo is optional and should be<br/> 95px 95px.</div>
<label htmlFor="upload-button" className='text-center d-block'>
<CustomTooltip
placement="right"
tooltipText={intl.formatMessage({ id: 'quickDapp.addLogoTooltip' })}
>
<div className='mt-4 btn btn-primary btn-sm' style={{ height: 32, width: 100, lineHeight: '22px' }}>Select Logo</div>
</CustomTooltip>
</label>
</div>
</div>
)}
</div>
)
}

@ -492,6 +492,7 @@
],
"noTerminal": false,
"verified": true,
"showLogo": true,
"solcVersion": {
"version": "0.8.26",
"canReceive": true

@ -26,7 +26,7 @@ export function ContractGUI(props: any) {
setTitle(props.funcABI.type === 'receive' ? '(receive)' : '(fallback)');
}
setBasicInput('');
// we have the reset the fields before reseting the previous references.
// we have the reset the fields before resetting the previous references.
if (basicInputRef.current) basicInputRef.current.value = '';
multiFields.current
.filter((el) => el !== null && el !== undefined)

@ -30,7 +30,7 @@ const DappTop: React.FC = () => {
const shareTitle = encodeURIComponent('Hello everyone, this is my dapp!');
return (
<div className="col-10 p-3 bg-light w-auto d-flex justify-content-between">
<div className={`${instance.showLogo ? 'col-10' : 'col-12'} p-3 bg-light w-auto d-flex justify-content-between`}>
<div>
{title && <h1 data-id="dappTitle">{title}</h1>}
{details && <span data-id="dappInstructions">{details}</span>}

@ -18,9 +18,9 @@ const MobilePage: React.FC = () => {
} col-xl-9 col-lg-8 col-md-7 pr-0`}
>
<div className="mx-3 my-2 row">
<div className="col-2 text-center px-0 d-flex align-items-center">
{instance.showLogo && <div className="col-2 text-center px-0 d-flex align-items-center">
<img src="/assets/logo.png" style={{ width: 55, height: 55 }} />
</div>
</div>}
<DappTop />
</div>
<UniversalDappUI />

@ -24,9 +24,9 @@ const PCPage: React.FC = () => {
>
<div className="col-xl-9 col-lg-8 col-md-7 d-inline-block pr-0">
<div className="mx-3 my-2 row">
<div className="col-2 text-center">
{instance.showLogo && <div className="col-2 text-center">
<img src="/assets/logo.png" style={{ width: 95, height: 95 }} />
</div>
</div>}
<DappTop />
</div>
<UniversalDappUI />

@ -26,7 +26,7 @@ export const RemixUiTerminal = (props: any) => {
const messagesEndRef = useRef<any>(null);
const typeWriterIndexes = useRef<any>([]);
// terminal dragable
// terminal draggable
const panelRef = useRef(null);
const terminalMenu = useRef(null);

@ -12,7 +12,7 @@
"terminal.welcomeText5": "Execute JavaScript scripts",
"terminal.welcomeText6": "Input a script directly in the command line interface",
"terminal.welcomeText7": "Select a Javascript file in the file explorer and then run `remix.execute()` or `remix.exeCurrent()` in the command line interface",
"terminal.welcomeText8": "Right click on a JavaScript file in the file explorer and then click `Run`",
"terminal.welcomeText8": "Right-click on a JavaScript file in the file explorer and then click `Run`",
"terminal.welcomeText9": "The following libraries are accessible",
"terminal.welcomeText10": "Type the library name to see available commands",
"terminal.text1": "This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.",

@ -112,7 +112,7 @@
"udapp.llIError4": "The calldata should be a valid hexadecimal value.",
"udapp.llIError5": "'Fallback' function is not defined",
"udapp.llIError6": "Both 'receive' and 'fallback' functions are not defined",
"udapp.llIError7": "Please define a 'Fallback' function to send calldata and a either 'Receive' or payable 'Fallback' to send ethers",
"udapp.llIError7": "Please define a 'Fallback' function to send calldata and an either 'Receive' or payable 'Fallback' to send ethers",
"udapp.copy": "Copy",
"udapp._comment_mainnet.tsx": "libs/remix-ui/run-tab/src/lib/components/mainnet.tsx",

@ -39,7 +39,7 @@ async function tryTillReceiptAvailable(txhash: Bytes) {
if (!receipt.to && !receipt.contractAddress) {
// this is a contract creation and the receipt doesn't contain a contract address. we have to keep polling...
console.log(
'this is a contract creation and the receipt does nott contain a contract address. we have to keep polling...'
'this is a contract creation and the receipt does not contain a contract address. we have to keep polling...'
);
return receipt;
} else return receipt;
@ -267,7 +267,7 @@ export class TxRunner {
};
} catch (error: any) {
console.log(
`Send transaction failed: ${error.message} . if you use an injected provider, please check it is properly unlocked. `
`Send transaction failed: ${error.message || error.error} . if you use an injected provider, please check it is properly unlocked. `
);
return { error };
}

@ -4,25 +4,22 @@ import EventEmitter from 'events'
class HideMetaMaskPopup extends EventEmitter {
command(this: NightwatchBrowser) {
browser
.pause(5000)
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
suppressNotFoundErrors: true,
timeout: 2000
}, (okVisible) => {
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
} else {
console.log('popover found... closing')
browser.click('button[data-testid="popover-close"]')
}
})
.waitForElementNotPresent({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
timeout: 2000
.perform((done) => {
browser.execute(function () {
function addStyle(styleString) {
const style = document.createElement('style')
style.textContent = styleString
document.head.append(style)
}
addStyle(`
#popover-content {
display:none !important
}
.popover-container {
display:none !important;
}
`)
}, [], done())
})
.perform((done) => {
done()

@ -0,0 +1,25 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class HidePopupPanel extends EventEmitter {
command(this: NightwatchBrowser) {
browser
.perform((done) => {
browser.execute(function () {
return localStorage.getItem('did_show_popup_panel')
}, [], function (result) {
if (!result.value) {
browser.waitForElementVisible('*[data-id="popupPanelToggle"]')
.click('*[data-id="popupPanelToggle"]')
}
done()
})
})
.perform((done) => {
done()
this.emit('complete')
})
}
}
module.exports = HidePopupPanel

@ -1,4 +1,4 @@
import {NightwatchBrowser} from 'nightwatch'
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class HideToolTips extends EventEmitter {
@ -16,6 +16,9 @@ class HideToolTips extends EventEmitter {
.popover {
display:none !important;
}
#scamDetails {
display:none !important;
}
`)
}, [], done())
})

@ -20,6 +20,9 @@ class RefreshPage extends EventEmitter {
.popover {
display:none !important;
}
#scamDetails {
display:none !important;
}
`)
}, [], done())
})

@ -50,26 +50,24 @@ function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password:
.click('button[data-testid="pin-extension-next"]')
.waitForElementVisible('button[data-testid="pin-extension-done"]')
.click('button[data-testid="pin-extension-done"]')
.pause(5000)
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
suppressNotFoundErrors: true,
timeout: 3000
}, (okVisible) => {
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
} else {
console.log('popover found... closing')
browser.click('button[data-testid="popover-close"]')
}
})
.waitForElementNotPresent({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
timeout: 3000
.perform((done) => {
browser.execute(function () {
function addStyle(styleString) {
const style = document.createElement('style')
style.textContent = styleString
document.head.append(style)
}
addStyle(`
#popover-content {
display:none !important
}
.popover-container {
display:none !important;
}
`)
}, [], done())
})
.saveScreenshot('./reports/screenshots/metamask.png')
.click('[data-testid="network-display"]')
.click('.mm-modal-content label.toggle-button--off') // show test networks

@ -14,6 +14,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.url(url || 'http://127.0.0.1:8080')
.pause(5000)
.switchBrowserTab(0)
.hidePopupPanel()
.perform((done) => {
if (!loadPlugin) return done()
browser
@ -39,6 +40,9 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.popover {
display:none !important;
}
#scamDetails {
display:none !important;
}
`);
}, [], done())
})

@ -0,0 +1,52 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import examples from '../examples/example-contracts'
const sources = [
{ 'Untitled.sol': { content: examples.ballot.content } }
]
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Add Ballot': function (browser: NightwatchBrowser) {
browser
.addFile('Untitled.sol', sources[0]['Untitled.sol'])
},
'Explain the contract': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="explain-editor"]')
.click('*[data-id="explain-editor"]')
.waitForElementVisible('*[data-id="popupPanelPluginsContainer"]')
.waitForElementVisible('*[data-id="aichat-view"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="aichat-view" and contains(.,"Explain the current code")]'
})
},
'close the popup': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="popupPanelToggle"]')
.click('*[data-id="popupPanelToggle"]')
.waitForElementNotVisible('*[data-id="popupPanelPluginsContainer"]')
},
'Add a bad contract': function (browser: NightwatchBrowser) {
browser
.addFile('Bad.sol', { content: 'errors' })
.clickLaunchIcon('solidity')
.waitForElementVisible('.ask-remix-ai-button')
.click('.ask-remix-ai-button')
.waitForElementVisible('*[data-id="popupPanelPluginsContainer"]')
.waitForElementVisible('*[data-id="aichat-view"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="aichat-view" and contains(.,"Explain the error")]'
})
}
}

@ -5,6 +5,7 @@ import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
browser.globals.asyncHookTimeout = 30000000;
init(browser, done)
},
@ -195,7 +196,7 @@ module.exports = {
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.pause(7000)
.pause(10000)
.journalLastChildIncludes('newZkey')
.pause(25000)
.journalLastChildIncludes('setup done.')

@ -0,0 +1,38 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
declare global {
interface Window { testplugin: { name: string, url: string }; }
}
const tests = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, null)
},
'Should load contract verification plugin #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('pluginManager')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtoncontract-verification"]')
.clickLaunchIcon('contract-verification')
.pause(5000)
.frame(0)
.waitForElementVisible('*[data-id="VerifyDescription"]')
},
'Should select a chain by searching #group1': function (browser: NightwatchBrowser) {
browser
.click('[data-id="chainDropdownbox"]')
.sendKeys('[data-id="chainDropdownbox"]', 's')
.sendKeys('[data-id="chainDropdownbox"]', 'c')
.sendKeys('[data-id="chainDropdownbox"]', 'r')
.click('[data-id="534351"]')
.assert.attributeContains('[data-id="chainDropdownbox"]', 'value', "Scroll Sepolia Testnet (534351)")
}
}
module.exports = {
...tests
};

@ -19,16 +19,17 @@ module.exports = {
},
'Should load the test file #group1': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(4000) // wait for the compiler to finish
.addFile('contracts/3_BallotHover.sol', {
content: BallotWithARefToOwner
})
.scrollToLine(37)
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[contains(text(),'BallotHoverTest')]"
checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest')
checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0')
checkEditorHoverContent(browser, path, 'contracts/3_BallotHover.sol 10:0')
checkEditorHoverContent(browser, path, '@title Ballot')
},
'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) {
@ -88,13 +89,13 @@ module.exports = {
},
'Add token file #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js')
.click('*[data-id="scConfigExpander"]')
.setValue('#evmVersionSelector', 'berlin') // set target EVM for parser to berlin
.addFile('contracts/mytoken.sol', {
content: myToken
}).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js')
.click('*[data-id="scConfigExpander"]')
.setValue('#evmVersionSelector', 'berlin') // set target EVM for parser to berlin
.addFile('contracts/mytoken.sol', {
content: myToken
}).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
// here we change quickly between files to test the files being parsed correctly when switching between them
'Should show ERC20 hover over contract in editor #group1': function (browser: NightwatchBrowser) {
@ -103,8 +104,12 @@ module.exports = {
const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Errors, IERC20Metadata, IERC20, Context'
checkEditorHoverContent(browser, path, expectedContent, 25)
},
'Go back to ballot file': function (browser: NightwatchBrowser) {
browser.openFile('contracts/3_Ballot.sol')
'Go back to ballot file #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewDivDraggableItem.deps"]')
.click('*[data-id="treeViewDivDraggableItem.deps"]')
.openFile('contracts/3_BallotHover.sol')
.scrollToLine(37)
.useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]")
},
'Should show hover over function in editor again #group1': function (browser: NightwatchBrowser) {

@ -433,13 +433,13 @@ module.exports = {
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]')
},
'verify Matomo events are tracked on app start #group4 #lfaky': function (browser: NightwatchBrowser) {
'verify Matomo events are tracked on app start #group4': function (browser: NightwatchBrowser) {
browser
.execute(function () {
return (window as any)._paq
}, [], (res) => {
const expectedEvents = [
["trackEvent", "Preload", "start"],
["trackEvent", "App", "Preload", "start"],
["trackEvent", "Storage", "activate", "indexedDB"],
["trackEvent", "App", "load"],
];

@ -0,0 +1,433 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import examples from '../examples/example-contracts'
const passphrase = process.env.account_passphrase
const password = process.env.account_password
const extension_id = 'nkbihfbeogaeaoehlefnkodbefgpgknn'
const extension_url = `chrome-extension://${extension_id}/home.html`
const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
const checkAlerts = function (browser: NightwatchBrowser) {
browser.isVisible({
selector: '//*[contains(.,"not have enough")]',
locateStrategy: 'xpath',
suppressNotFoundErrors: true,
timeout: 3000
}, (okVisible) => {
if (okVisible.value) {
browser.assert.fail('Not enough ETH in test account!!')
browser.end()
}
})
}
const localsCheck = {
to: {
value: '0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB',
type: 'address'
}
}
const tests = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.useCss().switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.click('*[data-id="landingPageStartSolidity"]')
.clickLaunchIcon('udapp')
.switchEnvironment('injected-MetaMask')
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]')
})
.switchBrowserTab(0) // back to remix
},
'Should add a contract file #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.addFile('Greet.sol', sources[0]['Greet.sol'])
.clickLaunchIcon('udapp')
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]', 45000) // wait for the contract to compile
},
'Should deploy contract on Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
browser.clearConsole().waitForElementPresent('*[data-id="runTabSelectAccount"] option', 45000)
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.clearConsole()
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
checkAlerts(browser)
browser
.maximizeWindow()
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
},
'Should run low level interaction (fallback function) on Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
browser.clearConsole().waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.clickInstance(0)
.clearConsole()
.waitForElementPresent('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]')
.click('*[data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction"]')
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.maximizeWindow()
.hideMetaMaskPopup()
.pause(3000)
.scrollAndClick('[data-testid="page-container-footer-next"]')
.pause(2000)
.switchBrowserTab(0) // back to remix
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'transact to HelloWorld.(fallback) pending')]"
})
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
},
'Should run transaction (greet function) on Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
browser.clearConsole().waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.clearConsole()
.waitForElementPresent('*[data-title="string _message"]')
.setValue('*[data-title="string _message"]', 'test')
.waitForElementVisible('*[data-id="greet - transact (not payable)"]')
.click('*[data-id="greet - transact (not payable)"]')
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.maximizeWindow()
.hideMetaMaskPopup()
.pause(3000)
.scrollAndClick('[data-testid="page-container-footer-next"]')
.pause(2000)
.switchBrowserTab(0) // back to remix
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'transact to HelloWorld.greet pending')]"
})
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
},
'Should deploy faulty contract on Sepolia Test Network using MetaMask and show error in terminal #group1': function (browser: NightwatchBrowser) {
browser
.clearConsole()
.clickLaunchIcon('filePanel')
.addFile('faulty.sol', sources[0]['faulty.sol'])
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.waitForElementVisible('*[data-id="udappNotifyModalDialogModalBody-react"]', 60000)
.click('[data-id="udappNotify-modal-footer-cancel-react"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'errored')]"
})
},
'Should deploy contract on Sepolia Test Network using MetaMask again #group1': function (browser: NightwatchBrowser) {
browser.clearConsole().waitForElementPresent('*[data-id="runTabSelectAccount"] option', 45000)
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.clearConsole()
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
checkAlerts(browser)
browser
.maximizeWindow()
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
},
// main network tests
'Should connect to Ethereum Main Network using MetaMask #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.switchBrowserTab(1)
.click('[data-testid="network-display"]')
.click('div[data-testid="Ethereum Mainnet"]') // switch to mainnet
.useCss().switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.click('*[data-id="landingPageStartSolidity"]')
.clickLaunchIcon('udapp')
.switchEnvironment('injected-MetaMask')
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Main (1) network')
},
'Should deploy contract on Ethereum Main Network using MetaMask #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="runTabSelectAccount"] option')
.clickLaunchIcon('filePanel')
.addFile('Greet.sol', sources[0]['Greet.sol'])
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementVisible('*[data-id="udappNotifyModalDialogModalBody-react"]', 65000)
.modalFooterOKClick('udappNotify')
.pause(10000)
.assert.containsText('*[data-id="udappNotifyModalDialogModalBody-react"]', 'You are about to create a transaction on Main Network. Confirm the details to send the info to your provider.')
.modalFooterCancelClick('udappNotify')
},
// debug transaction
'Should deploy Ballot to Sepolia using metamask #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.switchBrowserTab(1)
.click('[data-testid="network-display"]')
.click('div[data-testid="Sepolia"]') // switch to sepolia
.useCss().switchBrowserTab(0)
.addFile('BallotTest.sol', examples.ballot)
.clickLaunchIcon('udapp')
.clearConsole()
.clearTransactions()
.clickLaunchIcon('udapp')
.waitForElementVisible('input[placeholder="bytes32[] proposalNames"]')
.pause(2000)
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.pause(1000)
.click('*[data-id="Deploy - transact (not payable)"]') // deploy ballot
.pause(1000)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'creation of')]"
})
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-log' and contains(., 'pending')]"
})
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.maximizeWindow()
.hideMetaMaskPopup()
.pause(3000)
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.scrollAndClick('[data-testid="page-container-footer-next"]')
.pause(2000)
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
},
'do transaction #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.clearConsole()
.clickInstance(0)
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '"0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"' })
.pause(5000)
.perform((done) => { // call delegate
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.maximizeWindow()
.hideMetaMaskPopup()
.pause(5000)
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.scrollAndClick('[data-testid="page-container-footer-next"]')
.pause(2000)
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed',
'decoded input': { 'address to': '0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB' }
})
},
'Should debug Sepolia transaction with source highlighting MetaMask #group1': function (browser: NightwatchBrowser) {
let txhash
browser.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('pluginManager') // load debugger and source verification
.clickLaunchIcon('udapp')
.perform((done) => {
browser.getLastTransactionHash((hash) => {
txhash = hash
done()
})
})
.pause(5000)
.perform((done) => {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('debugger')
.waitForElementVisible('*[data-id="debuggerTransactionInput"]')
.setValue('*[data-id="debuggerTransactionInput"]', txhash) // debug tx
.pause(2000)
.click('*[data-id="debuggerTransactionStartButton"]')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.checkVariableDebug('soliditylocals', localsCheck)
.perform(() => done())
})
},
'Call web3.eth.getAccounts() using Injected Provider (Metamask) #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser
.executeScriptInTerminal('web3.eth.getAccounts()')
.journalLastChildIncludes('["0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f"]')
},
// EIP 712 tests
'Test EIP 712 Signature with Injected Provider (Metamask) #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('udapp')
.waitForElementPresent('i[id="remixRunSignMsg"]')
.click('i[id="remixRunSignMsg"]')
.waitForElementVisible('*[data-id="signMessageTextarea"]', 120000)
.click('*[data-id="sign-eip-712"]')
.waitForElementVisible('*[data-id="udappNotify-modal-footer-ok-react"]')
.modalFooterOKClick('udappNotify')
.pause(1000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('"primaryType": "AuthRequest",') !== -1, 'EIP 712 data file must be opened')
})
.setEditorValue(JSON.stringify(EIP712_Example, null, '\t'))
.pause(5000)
.clickLaunchIcon('filePanel')
.rightClick('li[data-id="treeViewLitreeViewItemEIP-712-data.json"]')
.click('*[data-id="contextMenuItemsignTypedData"]')
.pause(1000)
.perform((done) => { // call delegate
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.maximizeWindow()
.hideMetaMaskPopup()
.pause(1000)
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.scrollAndClick('button[data-testid="page-container-footer-next"]') // confirm
.switchBrowserTab(0) // back to remix
.perform(() => done())
})
})
.pause(1000)
.journalChildIncludes('0xec72bbabeb47a3a766af449674a45a91a6e94e35ebf0ae3c644b66def7bd387f1c0b34d970c9f4a1e9398535e5860b35e82b2a8931b7c9046b7766a53e66db3d1b')
}
}
const branch = process.env.CIRCLE_BRANCH
const runTestsConditions = branch && (branch === 'master' || branch === 'remix_live' || branch.includes('remix_beta') || branch.includes('metamask'))
if (!checkBrowserIsChrome(browser)) {
module.exports = {}
} else {
module.exports = {
...(branch ? (runTestsConditions ? tests : {}) : tests)
};
}
const EIP712_Example = {
domain: {
chainId: 11155111,
name: "Example App",
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
version: "1",
},
message: {
prompt: "Welcome! In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way.",
createdAt: 1718570375196,
},
primaryType: 'AuthRequest',
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
AuthRequest: [
{ name: 'prompt', type: 'string' },
{ name: 'createdAt', type: 'uint256' },
],
},
}
const sources = [
{
'Greet.sol': {
content:
`
pragma solidity ^0.8.0;
contract HelloWorld {
string public message;
fallback () external {
message = 'Hello World!';
}
function greet(string memory _message) public {
message = _message;
}
}`
},
'faulty.sol': {
content: `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Test {
error O_o(uint256);
constructor() {
revert O_o(block.timestamp);
}
}`
}
}
]

@ -3,54 +3,119 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
'Should switch to ganache provider, set a custom URL and fail to connect #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('udapp')
.switchEnvironment('ganache-provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => {})
}, [], () => { })
.clearValue('*[data-id="ganache-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="ganache-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084')
.modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="ganache-providerModalDialogModalBody-react"]', 'Error while connecting to the provider')
.modalFooterOKClick('ganache-provider')
.waitForElementNotVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-danger' and contains(., 'missing response')]"
})
.waitForElementPresent({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
.pause(1000)
},
'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.switchEnvironment('ganache-provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
'Should switch to ganache provider, use the default ganache URL and succeed to connect #group1': function (browser: NightwatchBrowser) {
browser
.switchEnvironment('vm-cancun')
.pause(2000)
.switchEnvironment('ganache-provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
.waitForElementVisible({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
},
'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
'Should switch to foundry provider, set a custom URL and fail to connect #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.switchEnvironment('foundry-provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => {})
.clearValue('*[data-id="foundry-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="foundry-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084')
.modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="foundry-providerModalDialogModalBody-react"]', 'Error while connecting to the provider')
.modalFooterOKClick('foundry-provider')
.waitForElementNotVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.pause(1000)
},
'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) {
.switchEnvironment('foundry-provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => { })
.clearValue('*[data-id="foundry-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="foundry-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084')
.modalFooterOKClick('foundry-provider')
.pause(1000)
},
'Should switch to foundry provider, use the default foundry URL and succeed to connect #group1': !function (browser: NightwatchBrowser) {
browser.switchEnvironment('foundry-provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
},
'Should switch to custom provider #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('udapp')
.switchEnvironment('ganache-provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => { })
.clearValue('*[data-id="ganache-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="ganache-providerModalDialogModalBody-react"] input', 'https://scroll-rpc.publicnode.com')
.modalFooterOKClick('ganache-provider')
.pause(100)
.waitForElementPresent({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
.pause(1000)
},
'execute script #group2': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('filePanel')
.addFile('testScript.ts', { content: testScript })
.clearConsole()
.pause(10000)
.waitForElementVisible('*[data-id="play-editor"]')
.click('*[data-id="play-editor"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: "//span[@class='text-danger' and contains(., 'exceed maximum block range')]"
})
.waitForElementPresent({ selector: `[data-id="selected-provider-ganache-provider"]`, timeout: 5000 })
}
}
const testScript = `
// Importing necessary libraries from Ethers.js for interaction with Ethereum blockchain.
import { ethers } from "hardhat";
// https://scroll-rpc.publicnode.com
async function main() {
// Setting up provider (RPC URL) to interact with your chosen Ethereum chain,
const [deployer] = await ethers.getSigners();
try{
let provider;
if(!provider){
provider=ethers.provider;
}
const contractAddress = "0x2bC16Bf30435fd9B3A3E73Eb759176C77c28308D"; // Replace with your smart contract's address.
// Retrieving all events of a specific kind from the blockchain
let logs = await provider.getLogs({address:contractAddress, fromBlock: '0x332f23',toBlock: '0x384410', topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']});
console.log("Got Logs ",logs)
}catch(error){
}
}
main()`

@ -240,7 +240,7 @@ const tests = {
.journalLastChildIncludes('["0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f"]')
},
'Test EIP 712 Signature with Injected Provider (Metamask) #group1 #flaky': function (browser: NightwatchBrowser) {
'Test EIP 712 Signature with Injected Provider (Metamask) #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('i[id="remixRunSignMsg"]')
.click('i[id="remixRunSignMsg"]')
.waitForElementVisible('*[data-id="signMessageTextarea"]', 120000)

@ -0,0 +1,116 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should load default script runner': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('scriptRunnerBridge')
.waitForElementVisible('[data-id="sr-loaded-default"]')
.waitForElementVisible('[data-id="dependency-ethers-^5"]')
.waitForElementVisible('[data-id="sr-toggle-ethers6"]')
},
'Should load script runner ethers6': function (browser: NightwatchBrowser) {
browser
.click('[data-id="sr-toggle-ethers6"]')
.waitForElementVisible('[data-id="sr-loaded-ethers6"]')
.waitForElementPresent('[data-id="dependency-ethers-^6"]')
},
'Should have config file in .remix/script.config.json': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('[data-path=".remix"]')
.waitForElementVisible('[data-id="treeViewDivDraggableItem.remix/script.config.json"]')
.openFile('.remix/script.config.json')
},
'check config file content': function (browser: NightwatchBrowser) {
browser
.getEditorValue((content) => {
console.log(JSON.parse(content))
const parsed = JSON.parse(content)
browser.assert.ok(parsed.defaultConfig === 'ethers6', 'config file content is correct')
})
},
'execute ethers6 script': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="treeViewUltreeViewMenu"]') // make sure we create the file at the root folder
.addFile('deployWithEthersJs.js', { content: deployWithEthersJs })
.pause(1000)
.click('[data-id="treeViewDivtreeViewItemcontracts"]')
.openFile('contracts/2_Owner.sol')
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]')
.executeScriptInTerminal('remix.execute(\'deployWithEthersJs.js\')')
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000)
},
'switch workspace it should be default again': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.pause(2000)
.waitForElementVisible('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-semaphore"]')
.scrollAndClick('*[data-id="create-semaphore"]')
.modalFooterOKClick('TemplatesSelection')
.clickLaunchIcon('scriptRunnerBridge')
.waitForElementVisible('[data-id="sr-loaded-default"]')
.waitForElementVisible('[data-id="dependency-ethers-^5"]')
.waitForElementVisible('[data-id="sr-toggle-ethers6"]')
},
'switch to default workspace that should be on ethers6': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
.clickLaunchIcon('scriptRunnerBridge')
.waitForElementVisible('[data-id="sr-loaded-ethers6"]')
.waitForElementPresent('[data-id="dependency-ethers-^6"]')
}
}
const deployWithEthersJs = `
import { ethers } from 'ethers'
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} args list of constructor' parameters
* @param {Number} accountIndex account index from the exposed account
* @return {Contract} deployed contract
*
*/
const deploy = async (contractName: string, args: Array<any>, accountIndex?: number): Promise<ethers.Contract> => {
console.log(\`deploying \${contractName}\`)
// Note that the script needs the ABI which is generated from the compilation artifact.
// Make sure contract is compiled and artifacts are generated
const artifactsPath = \`contracts/artifacts/\${contractName}.json\` // Change this for different path
const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
// 'web3Provider' is a remix global variable object
const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex)
const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
const contract = await factory.deploy(...args)
// The contract is NOT deployed yet; we must wait until it is mined
await contract.waitForDeployment()
return contract
}
(async () => {
try {
const contract = await deploy('Owner', [])
console.log(\`address: \${await contract.getAddress()}\`)
} catch (e) {
console.log(e.message)
}
})()`

@ -224,6 +224,7 @@ module.exports = {
.createContract('42, 24')
.openFile('Storage.sol')
.clickLaunchIcon('udapp')
.waitForElementVisible('*[data-title="uint256 p"]', 10000)
.createContract('102') // this creation will fail if the component hasn't been properly reset.
.clickInstance(1)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '24' })

@ -95,19 +95,10 @@ module.exports = {
browser
.url('http://127.0.0.1:8080/#address=0xdac17f958d2ee523a2206206994597c13d831ec7')
.refreshPage()
.pause(7000)
.pause(2000)
.currentWorkspaceIs('code-sample')
.waitForElementVisible('*[data-id=treeViewLitreeViewItemsepolia]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsepolia/0xdac17f958d2ee523a2206206994597c13d831ec7/contracts/MetaMultiSigWallet.sol"]')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'contract MetaMultiSigWallet {') !== -1)
})
.waitForElementVisible('*[data-id=treeViewLitreeViewItemmainnet]')
.click('*[data-id=treeViewLitreeViewItemmainnet]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]')
.click('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]')
.click('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]')
.getEditorValue((content) => {

@ -74,6 +74,7 @@ declare module 'nightwatch' {
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
waitForElementNotContainsText: (id: string, value: string, timeout: number = 10000) => NightwatchBrowser
hideToolTips: (this: NightwatchBrowser) => NightwatchBrowser
hidePopupPanel: (this: NightwatchBrowser) => NightwatchBrowser
enableClipBoard: () => NightwatchBrowser
addFileSnekmate: (name: string, content: NightwatchContractContent) => NightwatchBrowser
selectFiles: (selelectedElements: any[]) => NightwatchBrowser

@ -15,7 +15,7 @@ sleep 5
# grep -IRiL "@disabled" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test" | xargs -I {} basename {} .test.js | grep -E "\b[${2}]"
# TESTFILES=$(grep -IRiL "@disabled" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test" | xargs -I {} basename {} .test.js | grep -E "\b[$2]" | circleci tests split --split-by=timings )
node apps/remix-ide/ci/splice_tests.js $2 $3
TESTFILES=$(node apps/remix-ide/ci/splice_tests.js $2 $3 | circleci tests split --split-by=timings)
TESTFILES=$(node apps/remix-ide/ci/splice_tests.js $2 $3 | grep -v 'metamask' | circleci tests split --split-by=timings)
for TESTFILE in $TESTFILES; do
npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch-${1}.js dist/apps/remix-ide-e2e/src/tests/${TESTFILE}.js --env=$1 || npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch-${1}.js dist/apps/remix-ide-e2e/src/tests/${TESTFILE}.js --env=$1 || TEST_EXITCODE=1
done

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -e
TESTFILES=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "metamask" | sort )
# count test files
fileCount=$(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "metamask" | wc -l )
# if fileCount is 0
if [ $fileCount -eq 0 ]
then
echo "No metamask tests found"
exit 0
fi
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID"
TEST_EXITCODE=0
npx ganache &
npx http-server -p 9090 --cors='*' ./node_modules &
yarn run serve:production &
sleep 5
for TESTFILE in $TESTFILES; do
npx nightwatch --config dist/apps/remix-ide-e2e/nightwatch-${1}.js $TESTFILE --env=$1 || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ]
then
exit 1
fi

@ -12,6 +12,7 @@ import { SidePanel } from './app/components/side-panel'
import { StatusBar } from './app/components/status-bar'
import { HiddenPanel } from './app/components/hidden-panel'
import { PinnedPanel } from './app/components/pinned-panel'
import { PopupPanel } from './app/components/popup-panel'
import { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel'
@ -77,6 +78,7 @@ const remixLib = require('@remix-project/remix-lib')
import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search'
import { ScriptRunnerUIPlugin } from './app/tabs/script-runner-ui'
import { ElectronProvider } from './app/files/electronProvider'
const Storage = remixLib.Storage
@ -246,6 +248,9 @@ class AppComponent {
//----- search
const search = new SearchPlugin()
//---------------- Script Runner UI Plugin -------------------------
const scriptRunnerUI = new ScriptRunnerUIPlugin(this.engine)
//---- templates
const templates = new TemplatesPlugin()
@ -396,6 +401,7 @@ class AppComponent {
pluginStateLogger,
matomo,
templateSelection,
scriptRunnerUI,
remixAI
])
@ -445,6 +451,7 @@ class AppComponent {
this.sidePanel = new SidePanel()
this.hiddenPanel = new HiddenPanel()
this.pinnedPanel = new PinnedPanel()
this.popupPanel = new PopupPanel()
const pluginManagerComponent = new PluginManagerComponent(appManager, this.engine)
const filePanel = new FilePanel(appManager, contentImport)
@ -452,7 +459,7 @@ class AppComponent {
const landingPage = new LandingPage(appManager, this.menuicons, fileManager, filePanel, contentImport)
this.settings = new SettingsTab(Registry.getInstance().get('config').api, editor, appManager)
this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, this.statusBar, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel])
this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, this.statusBar, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel, this.popupPanel])
// CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
@ -535,6 +542,7 @@ class AppComponent {
await this.appManager.activatePlugin(['statusBar'])
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['pinnedPanel'])
await this.appManager.activatePlugin(['popupPanel'])
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin([
@ -649,7 +657,7 @@ class AppComponent {
})
// activate solidity plugin
this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy'])
this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy', 'scriptRunnerBridge'])
}
}

@ -24,10 +24,16 @@ export class HiddenPanel extends AbstractPanel {
addView(profile: any, view: any): void {
super.removeView(profile)
this.renderComponent()
super.addView(profile, view)
this.renderComponent()
}
removeView(profile: any): void {
super.removeView(profile)
this.renderComponent()
}
updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins} />
}

@ -29,7 +29,7 @@ export class AbstractPanel extends HostPlugin {
view: view,
active: false,
pinned: false,
class: 'plugItIn active'
class: 'plugItIn active ' + (profile.location === "sidePanel" ? 'pb-2' : ''),
}
}

@ -0,0 +1,123 @@
import React from 'react' // eslint-disable-line
import { AbstractPanel } from './panel'
import { PluginRecord, RemixPluginPanel } from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper'
import { EventEmitter } from 'events'
import { AppAction, appActionTypes, AppState } from '@remix-ui/app'
const profile = {
name: 'popupPanel',
displayName: 'Popup Panel',
description: 'Remix IDE popup panel',
version: packageJson.version,
events: [],
methods: ['addView', 'removeView', 'showContent', 'showPopupPanel']
}
type popupPanelState = {
plugins: Record<string, PluginRecord>
}
export class PopupPanel extends AbstractPanel {
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => { }
appStateDispatch: React.Dispatch<AppAction> = () => { }
constructor(config) {
super(profile)
this.event = new EventEmitter()
}
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
}
setAppStateDispatch(appStateDispatch: React.Dispatch<AppAction>) {
this.appStateDispatch = appStateDispatch
}
onActivation() {
this.renderComponent()
}
focus(name) {
this.emit('focusChanged', name)
super.focus(name)
this.renderComponent()
}
addView(profile, view) {
super.addView(profile, view)
this.renderComponent()
this.showContent(profile.name) // should be handled by some click
}
removeView(profile) {
super.removeView(profile)
this.renderComponent()
}
async showContent(name) {
super.showContent(name)
this.renderComponent()
}
async showPopupPanel(show) {
this.appStateDispatch({
type: appActionTypes.setShowPopupPanel,
payload: show
})
this.renderComponent()
}
renderComponent() {
this.dispatch({
plugins: this.plugins
})
}
render() {
return (
<PluginViewWrapper useAppContext={true} plugin={this} />
)
}
updateComponent(state: popupPanelState, appState: Partial<AppState>) {
return (
<div
className={`px-0 bg-light border-info ${appState?.showPopupPanel ? 'd-flex' : 'd-none'}`}
style={{
maxHeight: '100rem',
minWidth: '22rem',
width: '30%',
height: '80%',
position: 'fixed',
bottom: '2rem',
right: '1.5rem',
zIndex: 200,
boxShadow: "0 1px 7px var(--secondary)"
}}
data-id="popupPanelPluginsContainer"
>
<div className='d-flex w-100 flex-column'>
<RemixPluginPanel
header={
<span id='popupPanelToggle' className='d-flex flex-row'>
<button
data-id='popupPanelToggle'
className='btn fas fa-angle-double-down'
onClick={async () => {
await this.showPopupPanel(false)
}}
>
</button>
</span>
}
plugins={state.plugins} />
</div>
</div>
)
}
}

@ -10,7 +10,7 @@ import './styles/preload.css'
import isElectron from 'is-electron'
const _paq = (window._paq = window._paq || [])
_paq.push(['trackEvent', 'Preload', 'start'])
_paq.push(['trackEvent', 'App', 'Preload', 'start'])
export const Preload = (props: any) => {
const [tip, setTip] = useState<string>('')
@ -40,7 +40,7 @@ export const Preload = (props: any) => {
})
})
.catch((err) => {
_paq.push(['trackEvent', 'Preload', 'error', err && err.message])
_paq.push(['trackEvent', 'App', 'PreloadError', err && err.message])
console.error('Error loading Remix:', err)
setError(true)
})

@ -12,7 +12,7 @@ const sidePanel = {
displayName: 'Side Panel',
description: 'Remix IDE side panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView', 'focus']
methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView', 'focus', 'showContent']
}
export class SidePanel extends AbstractPanel {

@ -117,10 +117,10 @@ class Terminal extends Plugin {
}
onDeactivation() {
this.off('scriptRunner', 'log')
this.off('scriptRunner', 'info')
this.off('scriptRunner', 'warn')
this.off('scriptRunner', 'error')
this.off('scriptRunnerBridge', 'log')
this.off('scriptRunnerBridge', 'info')
this.off('scriptRunnerBridge', 'warn')
this.off('scriptRunnerBridge', 'error')
}
logHtml(html) {

@ -8,7 +8,7 @@ const desktop_profile = {
displayName: 'RemixAI Desktop',
maintainedBy: 'Remix',
description: 'RemixAI provides AI services to Remix IDE Desktop.',
documentation: 'https://remix-ide.readthedocs.io/en/latest/remixai.html',
documentation: 'https://remix-ide.readthedocs.io/en/latest/ai.html',
icon: 'assets/img/remix-logo-blue.png',
methods: ['initializeModelBackend', 'code_completion', 'code_insertion', 'code_generation', 'code_explaining', 'error_explaining', 'solidity_answer'],
}
@ -30,4 +30,4 @@ export class remixAIDesktopPlugin extends ElectronPlugin {
// super(dek)
// this.methods = ['downloadModel']
// }
// }
// }

@ -11,7 +11,7 @@ const profile = {
version: '1.0.0'
}
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'dgit', 'contract-verification']
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'scriptRunnerBridge', 'dgit', 'contract-verification']
export class Matomo extends Plugin {

@ -161,7 +161,7 @@ export default class CodeParserCompiler {
"*": ["evm.gasEstimates"]
}
},
"evmVersion": state.evmVersion && state.evmVersion.toString() || "cancun",
"evmVersion": state.evmVersion && state.evmVersion.toString() || undefined,
}
}
@ -266,4 +266,4 @@ export default class CodeParserCompiler {
return result
}
}
}

@ -5,6 +5,7 @@ import { RemixAITab, ChatApi } from '@remix-ui/remix-ai'
import React, { useCallback } from 'react';
import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, CodeExplainAgent } from '@remix/remix-ai-core';
import { CustomRemixApi } from '@remix-api'
import { PluginViewWrapper } from '@remix-ui/helper'
type chatRequestBufferT<T> = {
[key in keyof T]: T[key]
@ -12,7 +13,7 @@ type chatRequestBufferT<T> = {
const profile = {
name: 'remixAI',
displayName: 'Remix AI',
displayName: 'RemixAI',
methods: ['code_generation', 'code_completion',
"solidity_answer", "code_explaining",
"code_insertion", "error_explaining", "vulnerability_check",
@ -21,8 +22,8 @@ const profile = {
icon: 'assets/img/remix-logo-blue.png',
description: 'RemixAI provides AI services to Remix IDE.',
kind: '',
location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/remixai.html',
location: 'popupPanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/ai.html',
version: packageJson.version,
maintainedBy: 'Remix'
}
@ -37,6 +38,7 @@ export class RemixAIPlugin extends ViewPlugin {
chatRequestBuffer: chatRequestBufferT<any> = null
agent: CodeExplainAgent
useRemoteInferencer:boolean = false
dispatch: any
constructor(inDesktop:boolean) {
super(profile)
@ -46,6 +48,7 @@ export class RemixAIPlugin extends ViewPlugin {
}
onActivation(): void {
if (this.isOnDesktop) {
console.log('Activating RemixAIPlugin on desktop')
// this.on(this.remixDesktopPluginName, 'activated', () => {
@ -117,6 +120,11 @@ export class RemixAIPlugin extends ViewPlugin {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" })
return
}
if (prompt.trimStart().startsWith('gpt') || prompt.trimStart().startsWith('sol-gpt')) {
params.terminal_output = true
params.stream_result = false
params.return_stream_response = false
}
const newPrompt = await this.agent.chatCommand(prompt)
let result
@ -126,6 +134,8 @@ export class RemixAIPlugin extends ViewPlugin {
result = await this.remoteInferencer.solidity_answer(newPrompt)
}
if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result })
if (prompt.trimStart().startsWith('gpt') || prompt.trimStart().startsWith('sol-gpt')) params.terminal_output = false
return result
}
@ -219,13 +229,38 @@ export class RemixAIPlugin extends ViewPlugin {
return ""
}
}
isChatRequestPending(){
return this.chatRequestBuffer != null
}
setDispatch(dispatch) {
this.dispatch = dispatch
this.renderComponent()
}
renderComponent () {
this.dispatch({
plugin: this,
})
}
render() {
return <div
id='ai-view'
className='h-100 d-flex'
data-id='aichat-view'
style={{
minHeight: 'max-content',
}}
>
<PluginViewWrapper plugin={this} />
</div>
}
updateComponent(state) {
return (
<RemixAITab plugin={this}></RemixAITab>
<RemixAITab plugin={state.plugin}></RemixAITab>
)
}
}

@ -142,7 +142,7 @@ export class VyperCompilationDetailsPlugin extends ViewPlugin {
}
render() {
return (
<div id="compileDetails">
<div className="d-flex h-100 w-100 m-0 p-5 bg-light" id="compileDetails">
<PluginViewWrapper plugin={this} />
</div>
)

@ -26,8 +26,8 @@ export type RejectRequest = (error: JsonDataResult) => void
export type SuccessRequest = (data: JsonDataResult) => void
export interface IProvider {
options: {[id: string]: any}
init(): Promise<{[id: string]: any}>
options: { [id: string]: any }
init(): Promise<{ [id: string]: any }>
body(): JSX.Element
sendAsync(data: JsonDataRequest): Promise<JsonDataResult>
}
@ -38,7 +38,7 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
defaultUrl: string
connected: boolean
nodeUrl: string
options: {[id: string]: any} = {}
options: { [id: string]: any } = {}
constructor(profile, blockchain, defaultUrl) {
super(profile)
@ -102,24 +102,16 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
sendAsync(data: JsonDataRequest): Promise<JsonDataResult> {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (!this.provider) return reject({ jsonrpc: '2.0', id: data.id, error: { message: 'provider node set', code: -32603 } } as JsonDataResult)
if (!this.provider) return reject({ jsonrpc: '2.0', id: data.id, error: { message: 'provider not set', code: -32603 } } as JsonDataResult)
this.sendAsyncInternal(data, resolve, reject)
})
}
private async switchAway(showError) {
private async switchAway(showError: boolean, msg: string) {
if (!this.provider) return
this.provider = null
this.connected = false
if (showError) {
const modalContent: AlertModal = {
id: this.profile.name,
title: this.profile.displayName,
message: `Error while connecting to the provider, provider not connected`
}
this.call('notification', 'alert', modalContent)
this.call('terminal', 'log', { type: 'error', value: 'Error while querying the provider: ' + msg })
}
await this.call('udapp', 'setEnvironmentMode', { context: 'vm-cancun' })
return
}
@ -130,10 +122,22 @@ export abstract class AbstractProvider extends Plugin implements IProvider {
resolve({ jsonrpc: '2.0', result, id: data.id })
} catch (error) {
if (error && error.message && error.message.includes('SERVER_ERROR')) {
this.switchAway(true)
try {
// replace escaped quotes with normal quotes
const errorString = String(error.message).replace(/\\"/g, '"');
const messageMatches = Array.from(errorString.matchAll(/"message":"(.*?)"/g));
// Extract the message values
const messages = messageMatches.map(match => match[1]);
if (messages && messages.length > 0) {
this.switchAway(true, messages[0])
} else {
this.switchAway(true, error.message ? error.message : error.error ? error.error : error)
}
} catch (error) {
this.switchAway(true, error.message ? error.message : error.error ? error.error : error)
}
}
error.code = -32603
reject({ jsonrpc: '2.0', error, id: data.id })
reject({ jsonrpc: '2.0', error: { message: error.message, code: -32603 }, id: data.id })
}
} else {
const result = data.method === 'net_listening' ? 'canceled' : []

@ -62,7 +62,7 @@ export class CompileAndRun extends Plugin {
if (clearAllInstances) {
await this.call('udapp', 'clearAllInstances')
}
await this.call('scriptRunner', 'execute', content, fileName)
await this.call('scriptRunnerBridge', 'execute', content, fileName)
} catch (e) {
this.call('notification', 'toast', e.message || e)
}

@ -99,13 +99,17 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
* This function is used by remix-plugin compiler API.
* @param {object} settings {evmVersion, optimize, runs, version, language}
*/
setCompilerConfig (settings) {
async setCompilerConfig (settings) {
super.setCompilerConfig(settings)
this.renderComponent()
// @todo(#2875) should use loading compiler return value to check whether the compiler is loaded instead of "setInterval"
const value = JSON.stringify(settings, null, '\t')
let pluginInfo
pluginInfo = await this.call('udapp', 'showPluginDetails')
this.call('notification', 'toast', compilerConfigChangedToastMsg(this.currentRequest.from, value))
if (this.currentRequest.from === 'udapp') {
this.call('notification', 'toast', compilerConfigChangedToastMsg((pluginInfo ? pluginInfo.displayName : this.currentRequest.from ), value))
}
}
compile (fileName) {

@ -32,7 +32,8 @@
"quickDapp.text4": "Deployed successfully!",
"quickDapp.text5": "Click the link below to view your dapp",
"quickDapp.text6": "Teardown successfully!",
"quickDapp.uploadLogoTooltip": "Click here to change logo",
"quickDapp.addLogoTooltip": "Click here to add a logo",
"quickDapp.deleteLogoTooltip": "Click here to delete the logo",
"quickDapp.dappTitle": "Dapp Title",
"quickDapp.dappInstructions": "Dapp Instructions",
"quickDapp.functionTitle": "Title of function",

@ -2,11 +2,12 @@
"remixUiTabs.tooltipText1": "Run script (CTRL + SHIFT + S)",
"remixUiTabs.tooltipText2": "Compile CTRL + S",
"remixUiTabs.tooltipText3": "Select .sol or .yul file to compile OR a .ts or .js file to run",
"remixUiTabs.tooltipText4": "To explain a contract, choose a .sol file",
"remixUiTabs.tooltipText4": "To explain a contract, choose a .sol, .vy or .circom file",
"remixUiTabs.tooltipText5": "Explain the contract(s) in current file [BETA]",
"remixUiTabs.tooltipText6": "Enable RemixAI Copilot [BETA]",
"remixUiTabs.tooltipText7": "Disable RemixAI Copilot [BETA]",
"remixUiTabs.tooltipText8": "Remix AI Tools Documentation",
"remixUiTabs.tooltipText8": "RemixAI Tools Documentation",
"remixUiTabs.tooltipText9": "Configure scripting dependencies",
"remixUiTabs.tooltipTextDisabledCopilot": "To use RemixAI Copilot, choose a .sol file",
"remixUiTabs.zoomOut": "Zoom out",
"remixUiTabs.zoomIn": "Zoom in"

@ -4,7 +4,7 @@
"solUmlGen.pngDownloadTooltip": "Download UML diagram as a PNG file",
"solUmlGen.pdfDownloadTooltip": "Download UML diagram as a PDF file",
"solUmlGen.text1": "To view your contract as a UML Diagram",
"solUmlGen.text2": "Right click on your contract file",
"solUmlGen.text2": "Right-click on your contract file",
"solUmlGen.clickOn": "Click on",
"solUmlGen.generateUML": "Generate UML"
}

@ -19,7 +19,7 @@
"solidity.compilationDetails": "Compilation Details",
"solidity.language": "Language",
"solidity.evmVersion": "EVM Version",
"solidity.enableOptimization": "Enable optimization",
"solidity.enableOptimization": "Optimization",
"solidity.useConfigurationFile": "Use configuration file",
"solidity.change": "Change",
"solidity.compile": "Compile",

@ -12,7 +12,7 @@
"terminal.welcomeText5": "Execute JavaScript scripts",
"terminal.welcomeText6": "Input a script directly in the command line interface",
"terminal.welcomeText7": "Select a Javascript file in the file explorer and then run `remix.execute()` or `remix.exeCurrent()` in the command line interface",
"terminal.welcomeText8": "Right click on a JavaScript file in the file explorer and then click `Run`",
"terminal.welcomeText8": "Right-click on a JavaScript file in the file explorer and then click `Run`",
"terminal.welcomeText9": "The following libraries are accessible",
"terminal.welcomeText10": "Type the library name to see available commands",
"terminal.text1": "This type of command has been deprecated and is not functioning anymore. Please run remix.help() to list available commands.",

@ -4,7 +4,7 @@
"solUmlGen.pngDownloadTooltip": "Download UML diagram as a PNG file",
"solUmlGen.pdfDownloadTooltip": "Download UML diagram as a PDF file",
"solUmlGen.text1": "To view your contract as a UML Diagram",
"solUmlGen.text2": "Right click on your contract file",
"solUmlGen.text2": "Right-click on your contract file",
"solUmlGen.clickOn": "Click on",
"solUmlGen.generateUML": "Generate UML"
}

@ -4,7 +4,7 @@
"solUmlGen.pngDownloadTooltip": "Download UML diagram as a PNG file",
"solUmlGen.pdfDownloadTooltip": "Download UML diagram as a PDF file",
"solUmlGen.text1": "To view your contract as a UML Diagram",
"solUmlGen.text2": "Right click on your contract file",
"solUmlGen.text2": "Right-click on your contract file",
"solUmlGen.clickOn": "Click on",
"solUmlGen.generateUML": "Generate UML"
}

@ -4,7 +4,7 @@
"solUmlGen.pngDownloadTooltip": "Download UML diagram as a PNG file",
"solUmlGen.pdfDownloadTooltip": "Download UML diagram as a PDF file",
"solUmlGen.text1": "To view your contract as a UML Diagram",
"solUmlGen.text2": "Right click on your contract file",
"solUmlGen.text2": "Right-click on your contract file",
"solUmlGen.clickOn": "Click on",
"solUmlGen.generateUML": "Generate UML"
}

@ -1,4 +1,5 @@
{
"home.home": "Главная",
"home.scamAlert": "Предупреждение о мошенничестве",
"home.scamAlertText": "Единственный URL, который использует Remix, - это remix.ethereum.org",
"home.scamAlertText2": "Остерегайтесь видеороликов, рекламирующих \"ботов-передовиков ликвидности\"",
@ -6,6 +7,9 @@
"home.learnMore": "Узнать больше",
"home.here": "здесь",
"home.featured": "Рекомендуемые",
"home.learnEthPromoTitle": "LearnEth: Учебники внутри Remix",
"home.learnEthPromoButton": "Начать обучение",
"home.learnEthPromoText": "Ознакомьтесь с уроками по Remix, Solidity и другим проектам Web3. Отлично подходит для всех уровней навыков.",
"home.jumpIntoWeb3": "Перейти в WEB3",
"home.jumpIntoWeb3More": "Подробнее",
"home.jumpIntoWeb3Text": "Remix IDE является частью проекта Remix, широкий выбор инструментов которого, может быть использован для всего путешествия по разработке контракта пользователями любого уровня знаний. Узнайте больше на сайте проекта Remix.",
@ -36,6 +40,7 @@
"home.ozerc1155TemplateDesc": "Создайте ERC1155 токен, импортируя библиотеку OpenZeppelin.",
"home.gnosisSafeMultisigTemplateDesc": "Создайте кошельки с мульти-подписью с использованием этого шаблона.",
"home.zeroxErc20TemplateDesc": "Создайте токен ERC20, импортируя контракт с 0xProject.",
"home.learnEthPluginDesc": "Узнайте о Remix, Solidity и других проектах Web3.",
"home.learn": "Обучение",
"home.learnEth1": "Основы Remix",
"home.learnEth1Desc": "Введение в интерфейс Remix-а и основные операции.",
@ -65,5 +70,8 @@
"home.resources": "Источники",
"home.connectToLocalhost": "Подключиться к локальному хосту",
"home.seeAllTutorials": "Посмотреть все уроки",
"home.maintainedByRemix": "Поддерживается Remix"
"home.maintainedByRemix": "Поддерживается Remix",
"home.gitCloneTooltip": "Клонировать репозиторий Github в новую рабочую область",
"home.gistTooltip": "Открыть репозиторий Gist",
"home.newFileTooltip": "Добавить новый файл в рабочую область"
}

@ -16,7 +16,7 @@
"solidity.compilationDetails": "Подробности компиляции",
"solidity.language": "Язык",
"solidity.evmVersion": "Версия EVM",
"solidity.enableOptimization": "Включить оптимизацию",
"solidity.enableOptimization": "Оптимизация",
"solidity.useConfigurationFile": "Использовать файл настроек",
"solidity.change": "Изменить",
"solidity.compile": "Скомпилировать",

@ -0,0 +1,399 @@
import { IframePlugin, IframeProfile, ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import { customScriptRunnerConfig, ProjectConfiguration, ScriptRunnerConfig, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line
import { Profile } from '@remixproject/plugin-utils'
import { Engine, Plugin } from '@remixproject/engine'
import axios from 'axios'
import { AppModal } from '@remix-ui/app'
import { isArray } from 'lodash'
import { PluginViewWrapper } from '@remix-ui/helper'
import { CustomRemixApi } from '@remix-api'
const profile = {
name: 'scriptRunnerBridge',
displayName: 'Script configuration',
methods: ['execute'],
events: ['log', 'info', 'warn', 'error'],
icon: 'assets/img/solid-gear-circle-play.svg',
description: 'Configure the dependencies for running scripts.',
kind: '',
location: 'sidePanel',
version: packageJson.version,
maintainedBy: 'Remix'
}
const configFileName = '.remix/script.config.json'
let baseUrl = 'https://remix-project-org.github.io/script-runner-generator'
const customBuildUrl = 'http://localhost:4000/build' // this will be used when the server is ready
interface IScriptRunnerState {
customConfig: customScriptRunnerConfig
configurations: ProjectConfiguration[]
activeConfig: ProjectConfiguration
enableCustomScriptRunner: boolean
}
export class ScriptRunnerUIPlugin extends ViewPlugin {
engine: Engine
dispatch: React.Dispatch<any> = () => { }
workspaceScriptRunnerDefaults: Record<string, string>
customConfig: ScriptRunnerConfig
configurations: ProjectConfiguration[]
activeConfig: ProjectConfiguration
enableCustomScriptRunner: boolean
plugin: Plugin<any, CustomRemixApi>
scriptRunnerProfileName: string
constructor(engine: Engine) {
super(profile)
this.engine = engine
this.workspaceScriptRunnerDefaults = {}
this.plugin = this
this.enableCustomScriptRunner = false // implement this later
}
async onActivation() {
this.on('filePanel', 'setWorkspace', async (workspace: string) => {
this.activeConfig = null
this.customConfig =
{
defaultConfig: 'default',
customConfig: {
baseConfiguration: 'default',
dependencies: []
}
}
await this.loadCustomConfig()
await this.loadConfigurations()
this.renderComponent()
})
this.plugin.on('fileManager', 'fileSaved', async (file: string) => {
if (file === configFileName && this.enableCustomScriptRunner) {
await this.loadCustomConfig()
this.renderComponent()
}
})
await this.loadCustomConfig()
await this.loadConfigurations()
this.renderComponent()
}
render() {
return (
<div id="scriptrunnerTab">
<PluginViewWrapper plugin={this} />
</div>
)
}
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
this.renderComponent()
}
renderComponent() {
this.dispatch({
customConfig: this.customConfig,
configurations: this.configurations,
activeConfig: this.activeConfig,
enableCustomScriptRunner: this.enableCustomScriptRunner
})
}
updateComponent(state: IScriptRunnerState) {
return (
<ScriptRunnerUI
customConfig={state.customConfig}
configurations={state.configurations}
activeConfig={state.activeConfig}
enableCustomScriptRunner={state.enableCustomScriptRunner}
activateCustomScriptRunner={this.activateCustomScriptRunner.bind(this)}
saveCustomConfig={this.saveCustomConfig.bind(this)}
openCustomConfig={this.openCustomConfig.bind(this)}
loadScriptRunner={this.selectScriptRunner.bind(this)} />
)
}
async selectScriptRunner(config: ProjectConfiguration) {
if (await this.loadScriptRunner(config))
await this.saveCustomConfig(this.customConfig)
}
async loadScriptRunner(config: ProjectConfiguration): Promise<boolean> {
const profile: Profile = await this.plugin.call('manager', 'getProfile', 'scriptRunner')
this.scriptRunnerProfileName = profile.name
const testPluginName = localStorage.getItem('test-plugin-name')
const testPluginUrl = localStorage.getItem('test-plugin-url')
let url = `${baseUrl}?template=${config.name}&timestamp=${Date.now()}`
if (testPluginName === 'scriptRunner') {
// if testpluginurl has template specified only use that
if (testPluginUrl.indexOf('template') > -1) {
url = testPluginUrl
} else {
baseUrl = `//${new URL(testPluginUrl).host}`
url = `${baseUrl}?template=${config.name}&timestamp=${Date.now()}`
}
}
//console.log('loadScriptRunner', profile)
const newProfile: IframeProfile = {
...profile,
name: profile.name + config.name,
location: 'hiddenPanel',
url: url
}
let result = null
try {
this.setIsLoading(config.name, true)
const plugin: IframePlugin = new IframePlugin(newProfile)
if (!this.engine.isRegistered(newProfile.name)) {
await this.engine.register(plugin)
}
await this.plugin.call('manager', 'activatePlugin', newProfile.name)
this.activeConfig = config
this.on(newProfile.name, 'log', this.log.bind(this))
this.on(newProfile.name, 'info', this.info.bind(this))
this.on(newProfile.name, 'warn', this.warn.bind(this))
this.on(newProfile.name, 'error', this.error.bind(this))
this.on(newProfile.name, 'dependencyError', this.dependencyError.bind(this))
this.customConfig.defaultConfig = config.name
this.setErrorStatus(config.name, false, '')
result = true
} catch (e) {
console.log('Error loading script runner: ', newProfile.name, e)
const iframe = document.getElementById(`plugin-${newProfile.name}`);
if (iframe) {
await this.call('hiddenPanel', 'removeView', newProfile)
}
delete (this.engine as any).manager.profiles[newProfile.name]
delete (this.engine as any).plugins[newProfile.name]
console.log('Error loading script runner: ', newProfile.name, e)
this.setErrorStatus(config.name, true, e)
result = false
}
this.setIsLoading(config.name, false)
this.renderComponent()
return result
}
async execute(script: string, filePath: string) {
this.call('terminal', 'log', { value: `running ${filePath} ...`, type: 'info' })
if (!this.scriptRunnerProfileName || !this.engine.isRegistered(`${this.scriptRunnerProfileName}${this.activeConfig.name}`)) {
if (!await this.loadScriptRunner(this.activeConfig)) {
console.error('Error loading script runner')
return
}
}
try {
this.setIsLoading(this.activeConfig.name, true)
await this.call(`${this.scriptRunnerProfileName}${this.activeConfig.name}`, 'execute', script, filePath)
} catch (e) {
console.error('Error executing script', e)
}
this.setIsLoading(this.activeConfig.name, false)
}
async setErrorStatus(name: string, status: boolean, error: string) {
this.configurations.forEach((config) => {
if (config.name === name) {
config.errorStatus = status
config.error = error
}
})
this.renderComponent()
}
async setIsLoading(name: string, status: boolean) {
if (status) {
this.emit('statusChanged', {
key: 'loading',
type: 'info',
title: 'loading...'
})
} else {
this.emit('statusChanged', {
key: 'none'
})
}
this.configurations.forEach((config) => {
if (config.name === name) {
config.isLoading = status
}
})
this.renderComponent()
}
async dependencyError(data: any) {
console.log('Script runner dependency error: ', data)
let message = `Error loading dependencies: `
if (isArray(data.data)) {
data.data.forEach((data: any) => {
message += `${data}`
})
}
const modal: AppModal = {
id: 'TemplatesSelection',
title: 'Missing dependencies',
message: `${message} \n\n You may need to setup a script engine for this workspace to load the correct dependencies. Do you want go to setup now?`,
okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }),
cancelLabel: 'ignore'
}
const modalResult = await this.plugin.call('notification' as any, 'modal', modal)
if (modalResult) {
await this.plugin.call('menuicons', 'select', 'scriptRunnerBridge')
} else {
}
}
async log(data: any) {
this.emit('log', data)
}
async warn(data: any) {
this.emit('warn', data)
}
async error(data: any) {
this.emit('error', data)
}
async info(data: any) {
this.emit('info', data)
}
async loadCustomConfig(): Promise<void> {
try {
const content = await this.plugin.call('fileManager', 'readFile', configFileName)
const parsed = JSON.parse(content)
this.customConfig = parsed
} catch (e) {
this.customConfig = {
defaultConfig: 'default',
customConfig: {
baseConfiguration: 'default',
dependencies: []
}
}
}
}
async openCustomConfig() {
try {
await this.plugin.call('fileManager', 'open', '.remix/script.config.json')
} catch (e) {
}
}
async loadConfigurations() {
try {
const response = await axios.get(`${baseUrl}/projects.json?timestamp=${Date.now()}`);
this.configurations = response.data;
// find the default otherwise pick the first one as the active
this.configurations.forEach((config) => {
if (config.name === (this.customConfig.defaultConfig)) {
this.activeConfig = config;
}
});
if (!this.activeConfig) {
this.activeConfig = this.configurations[0];
}
} catch (error) {
console.error("Error fetching the projects data:", error);
}
}
async saveCustomConfig(content: ScriptRunnerConfig) {
if (content.customConfig.dependencies.length === 0 && content.defaultConfig === 'default') {
try {
const exists = await this.plugin.call('fileManager', 'exists', '.remix/script.config.json')
if (exists) {
await this.plugin.call('fileManager', 'remove', '.remix/script.config.json')
}
} catch (e) {
}
return
}
await this.plugin.call('fileManager', 'writeFile', '.remix/script.config.json', JSON.stringify(content, null, 2))
}
async activateCustomScriptRunner(config: customScriptRunnerConfig) {
try {
const result = await axios.post(customBuildUrl, config)
if (result.data.hash) {
const newConfig: ProjectConfiguration = {
name: result.data.hash,
title: 'Custom configuration',
publish: true,
description: `Extension of ${config.baseConfiguration}`,
dependencies: config.dependencies,
replacements: {},
errorStatus: false,
error: '',
isLoading: false
};
this.configurations.push(newConfig)
this.renderComponent()
await this.loadScriptRunner(result.data.hash)
}
return result.data.hash
} catch (error) {
let message
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log('Error status:', error.response.status);
console.log('Error data:', error.response.data); // This should give you the output being sent
console.log('Error headers:', error.response.headers);
if (error.response.data.error) {
if (isArray(error.response.data.error)) {
const message = `${error.response.data.error[0]}`
this.plugin.call('notification', 'alert', {
id: 'scriptalert',
message,
title: 'Error'
})
throw new Error(message)
}
message = `${error.response.data.error}`
}
message = `Uknown error: ${error.response.data}`
this.plugin.call('notification', 'alert', {
id: 'scriptalert',
message,
title: 'Error'
})
throw new Error(message)
} else if (error.request) {
// The request was made but no response was received
console.log('No response received:', error.request);
throw new Error('No response received')
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error message:', error.message);
throw new Error(error.message)
}
}
}
}

@ -86,7 +86,7 @@ export class Web3ProviderModule extends Plugin {
try {
resultFn(null, await provider.sendAsync(payload))
} catch (e) {
resultFn(e.error ? new Error(e.error) : new Error(e))
resultFn(e.error ? e.error : e)
}
} else {
reject(new Error('User denied permission'))

@ -35,7 +35,8 @@ const profile = {
'setEnvironmentMode',
'clearAllInstances',
'addInstance',
'resolveContractAndAddInstance'
'resolveContractAndAddInstance',
'showPluginDetails'
]
}
@ -87,6 +88,10 @@ export class RunTab extends ViewPlugin {
})
}
showPluginDetails() {
return profile
}
async setEnvironmentMode(env) {
const canCall = await this.askUserPermission('setEnvironmentMode', 'change the environment used')
if (canCall) {
@ -194,22 +199,7 @@ export class RunTab extends ViewPlugin {
if (options['fork']) this.fork = options['fork']
}
},
provider: {
sendAsync (payload) {
return udapp.call(name, 'sendAsync', payload)
},
async request (payload) {
try {
const requestResult = await udapp.call(name, 'sendAsync', payload)
if (requestResult.error) {
throw new Error(requestResult.error.message)
}
return requestResult.result
} catch (err) {
throw new Error(err.message)
}
}
}
provider: new Provider(udapp, name)
})
}
@ -302,3 +292,43 @@ export class RunTab extends ViewPlugin {
this.addInstance(address, contractObject.abi, contractObject.name)
}
}
class Provider {
udapp: RunTab
name: string
constructor(udapp, name) {
this.udapp = udapp
this.name = name
}
sendAsync (payload) {
return this.udapp.call(this.name, 'sendAsync', payload)
}
request (payload): Promise<any> {
return new Promise((resolve, reject) => {
this.udapp.call(this.name, 'sendAsync', payload).then((response) => {
if (response.error) {
reject(response.error.message)
} else {
resolve(response.result? response.result : response)
}
}).catch((err) => {
if (typeof err === 'string') {
reject(err)
} else if (err.error && err.error.message) {
reject(err.error.message)
} else if (err.error && typeof err.error === 'string') {
reject(err.error)
} else {
let e
try {
e = JSON.stringify(err)
} catch (e) {
reject('unknown error')
return
}
reject(e)
}
})
})
}
}

@ -36,6 +36,7 @@
--body-bg: #fff;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--brand-dark-blue: #222496;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;
@ -3131,7 +3132,8 @@ input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-
.custom-control-label {
position:relative;
margin-bottom:0;
vertical-align:top
vertical-align:top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position:absolute;

@ -37,6 +37,7 @@
--body-bg: #060606;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--brand-dark-blue: #222496;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;
@ -3132,7 +3133,8 @@ input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-
.custom-control-label {
position:relative;
margin-bottom:0;
vertical-align:top
vertical-align:top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position:absolute;

@ -36,6 +36,7 @@
--body-bg: #fff;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--brand-dark-blue: #222496;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;
@ -2892,7 +2893,7 @@ input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-
background-color:#ecf0f1
}
.custom-control-label {
position:relative; margin-bottom:0; vertical-align:top
position:relative; margin-bottom:0; vertical-align:top; padding-top: 0.15rem;
}
.custom-control-label::before {
position:absolute; top:0px; left:-1.5rem; display:block; width:1rem; height:1rem; pointer-events:none; content:""; background-color:#fff; border:#b4bcc2 solid 1px

@ -39,6 +39,7 @@
--body-bg:#fff;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--brand-dark-blue: #222496;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;
@ -3134,7 +3135,8 @@ input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-
.custom-control-label {
position:relative;
margin-bottom:0;
vertical-align:top
vertical-align:top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position:absolute;

@ -25,6 +25,7 @@
--text: #babbcc;
--body-bg: #1a1a1a;
--custom-select: #252525;
--brand-dark-blue: #222496;
--text-bg-mark: #a5a5a5;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
@ -3243,6 +3244,7 @@ input[type="submit"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -25,6 +25,7 @@
--body-bg: #d5efff;
--text-bg-mark: #fcf8e3;
--custom-select: #ffffff;
--brand-dark-blue: #222496;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
@ -3553,6 +3554,7 @@ input[type="button"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -25,6 +25,7 @@
--text-background: #222336;
--text-bg-mark: #8388b2;
--custom-select: #35384c;
--brand-dark-blue: #222496;
--runtab: #8A93B0;
--body-bg: #222336;
--breakpoint-xs: 0;
@ -3238,6 +3239,7 @@ input[type="submit"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -27,7 +27,7 @@
--body-bg: #011628;
--custom-select: #252525;
--text-bg-mark: #a5a5a5;
--custom-select: #011627;
--brand-dark-blue: #222496;
--text-background: #011626;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
@ -3252,6 +3252,7 @@ input[type="submit"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -25,6 +25,7 @@
--body-bg: #eef1f6;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--brand-dark-blue: #222496;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
@ -3549,6 +3550,7 @@ input[type="button"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -25,6 +25,7 @@
--body-bg: #DBE2E0;
--text-bg-mark: #fcf8e3;
--custom-select: #eeede9;
--brand-dark-blue: #222496;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
@ -3555,6 +3556,7 @@ input[type="button"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -25,6 +25,7 @@
--body-bg: #f1eef6;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--brand-dark-blue: #222496;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
@ -3553,6 +3554,7 @@ input[type="button"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -25,6 +25,7 @@
--body-bg: #f1eef6;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--brand-dark-blue: #222496;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
@ -3549,6 +3550,7 @@ input[type="button"].btn-block {
position: relative;
margin-bottom: 0;
vertical-align: top;
padding-top: 0.15rem;
}
.custom-control-label::before {
position: absolute;

@ -2,26 +2,28 @@
font-family: var(--fa-style-family, "Font Awesome 6 Pro");
font-weight: var(--fa-style, 900); }
.fa,
.fa-classic,
.fa-sharp,
.fas,
.fa-solid,
.far,
.fa-regular,
.fasr,
.fa-brands,
.fas,
.far,
.fab,
.fal,
.fa-light,
.fasl,
.fat,
.fa-thin,
.fast,
.fad,
.fa-duotone,
.fass,
.fasr,
.fasl,
.fast,
.fasds,
.fa-light,
.fa-thin,
.fa-duotone,
.fa-sharp,
.fa-sharp-duotone,
.fa-sharp-solid,
.fab,
.fa-brands {
.fa-classic,
.fa {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: var(--fa-display, inline-block);
@ -31,14 +33,14 @@
text-rendering: auto; }
.fas,
.fa-classic,
.fa-solid,
.far,
.fa-regular,
.fal,
.fa-light,
.fat,
.fa-thin {
.fa-solid,
.fa-regular,
.fa-light,
.fa-thin,
.fa-classic {
font-family: 'Font Awesome 6 Pro'; }
.fab,
@ -50,6 +52,14 @@
.fa-duotone {
font-family: 'Font Awesome 6 Duotone'; }
.fasds,
.fa-sharp-duotone {
font-family: 'Font Awesome 6 Sharp Duotone'; }
.fasds,
.fa-sharp-duotone {
font-weight: 900; }
.fass,
.fasr,
.fasl,
@ -133,7 +143,7 @@
position: relative; }
.fa-li {
left: calc(var(--fa-li-width, 2em) * -1);
left: calc(-1 * var(--fa-li-width, 2em));
position: absolute;
text-align: center;
width: var(--fa-li-width, 2em);
@ -155,118 +165,71 @@
margin-left: var(--fa-pull-margin, 0.3em); }
.fa-beat {
-webkit-animation-name: fa-beat;
animation-name: fa-beat;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
animation-name: fa-beat;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
.fa-bounce {
-webkit-animation-name: fa-bounce;
animation-name: fa-bounce;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1));
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); }
animation-name: fa-bounce;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); }
.fa-fade {
-webkit-animation-name: fa-fade;
animation-name: fa-fade;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
animation-name: fa-fade;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
.fa-beat-fade {
-webkit-animation-name: fa-beat-fade;
animation-name: fa-beat-fade;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
animation-name: fa-beat-fade;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
.fa-flip {
-webkit-animation-name: fa-flip;
animation-name: fa-flip;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
animation-name: fa-flip;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
.fa-shake {
-webkit-animation-name: fa-shake;
animation-name: fa-shake;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
animation-timing-function: var(--fa-animation-timing, linear); }
animation-name: fa-shake;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, linear); }
.fa-spin {
-webkit-animation-name: fa-spin;
animation-name: fa-spin;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 2s);
animation-duration: var(--fa-animation-duration, 2s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
animation-timing-function: var(--fa-animation-timing, linear); }
animation-name: fa-spin;
animation-delay: var(--fa-animation-delay, 0s);
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 2s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, linear); }
.fa-spin-reverse {
--fa-animation-direction: reverse; }
.fa-pulse,
.fa-spin-pulse {
-webkit-animation-name: fa-spin;
animation-name: fa-spin;
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, steps(8));
animation-timing-function: var(--fa-animation-timing, steps(8)); }
animation-name: fa-spin;
animation-direction: var(--fa-animation-direction, normal);
animation-duration: var(--fa-animation-duration, 1s);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-timing-function: var(--fa-animation-timing, steps(8)); }
@media (prefers-reduced-motion: reduce) {
.fa-beat,
@ -278,219 +241,97 @@
.fa-shake,
.fa-spin,
.fa-spin-pulse {
-webkit-animation-delay: -1ms;
animation-delay: -1ms;
-webkit-animation-duration: 1ms;
animation-duration: 1ms;
-webkit-animation-iteration-count: 1;
animation-iteration-count: 1;
-webkit-transition-delay: 0s;
transition-delay: 0s;
-webkit-transition-duration: 0s;
transition-duration: 0s; } }
@-webkit-keyframes fa-beat {
0%, 90% {
-webkit-transform: scale(1);
transform: scale(1); }
45% {
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
transform: scale(var(--fa-beat-scale, 1.25)); } }
animation-delay: -1ms;
animation-duration: 1ms;
animation-iteration-count: 1;
transition-delay: 0s;
transition-duration: 0s; } }
@keyframes fa-beat {
0%, 90% {
-webkit-transform: scale(1);
transform: scale(1); }
transform: scale(1); }
45% {
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
transform: scale(var(--fa-beat-scale, 1.25)); } }
@-webkit-keyframes fa-bounce {
0% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
10% {
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
30% {
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
50% {
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
57% {
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
64% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
100% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); } }
transform: scale(var(--fa-beat-scale, 1.25)); } }
@keyframes fa-bounce {
0% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
transform: scale(1, 1) translateY(0); }
10% {
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
30% {
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
50% {
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
57% {
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
64% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
transform: scale(1, 1) translateY(0); }
100% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); } }
@-webkit-keyframes fa-fade {
50% {
opacity: var(--fa-fade-opacity, 0.4); } }
transform: scale(1, 1) translateY(0); } }
@keyframes fa-fade {
50% {
opacity: var(--fa-fade-opacity, 0.4); } }
@-webkit-keyframes fa-beat-fade {
0%, 100% {
opacity: var(--fa-beat-fade-opacity, 0.4);
-webkit-transform: scale(1);
transform: scale(1); }
50% {
opacity: 1;
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
@keyframes fa-beat-fade {
0%, 100% {
opacity: var(--fa-beat-fade-opacity, 0.4);
-webkit-transform: scale(1);
transform: scale(1); }
transform: scale(1); }
50% {
opacity: 1;
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
@-webkit-keyframes fa-flip {
50% {
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
@keyframes fa-flip {
50% {
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
@-webkit-keyframes fa-shake {
0% {
-webkit-transform: rotate(-15deg);
transform: rotate(-15deg); }
4% {
-webkit-transform: rotate(15deg);
transform: rotate(15deg); }
8%, 24% {
-webkit-transform: rotate(-18deg);
transform: rotate(-18deg); }
12%, 28% {
-webkit-transform: rotate(18deg);
transform: rotate(18deg); }
16% {
-webkit-transform: rotate(-22deg);
transform: rotate(-22deg); }
20% {
-webkit-transform: rotate(22deg);
transform: rotate(22deg); }
32% {
-webkit-transform: rotate(-12deg);
transform: rotate(-12deg); }
36% {
-webkit-transform: rotate(12deg);
transform: rotate(12deg); }
40%, 100% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); } }
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
@keyframes fa-shake {
0% {
-webkit-transform: rotate(-15deg);
transform: rotate(-15deg); }
transform: rotate(-15deg); }
4% {
-webkit-transform: rotate(15deg);
transform: rotate(15deg); }
transform: rotate(15deg); }
8%, 24% {
-webkit-transform: rotate(-18deg);
transform: rotate(-18deg); }
transform: rotate(-18deg); }
12%, 28% {
-webkit-transform: rotate(18deg);
transform: rotate(18deg); }
transform: rotate(18deg); }
16% {
-webkit-transform: rotate(-22deg);
transform: rotate(-22deg); }
transform: rotate(-22deg); }
20% {
-webkit-transform: rotate(22deg);
transform: rotate(22deg); }
transform: rotate(22deg); }
32% {
-webkit-transform: rotate(-12deg);
transform: rotate(-12deg); }
transform: rotate(-12deg); }
36% {
-webkit-transform: rotate(12deg);
transform: rotate(12deg); }
transform: rotate(12deg); }
40%, 100% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); } }
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
transform: rotate(0deg); } }
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
transform: rotate(360deg); } }
.fa-rotate-90 {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
transform: rotate(90deg); }
.fa-rotate-180 {
-webkit-transform: rotate(180deg);
transform: rotate(180deg); }
transform: rotate(180deg); }
.fa-rotate-270 {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
transform: rotate(270deg); }
.fa-flip-horizontal {
-webkit-transform: scale(-1, 1);
transform: scale(-1, 1); }
transform: scale(-1, 1); }
.fa-flip-vertical {
-webkit-transform: scale(1, -1);
transform: scale(1, -1); }
transform: scale(1, -1); }
.fa-flip-both,
.fa-flip-horizontal.fa-flip-vertical {
-webkit-transform: scale(-1, -1);
transform: scale(-1, -1); }
transform: scale(-1, -1); }
.fa-rotate-by {
-webkit-transform: rotate(var(--fa-rotate-angle, 0));
transform: rotate(var(--fa-rotate-angle, 0)); }
transform: rotate(var(--fa-rotate-angle, 0)); }
.fa-stack {
display: inline-block;
@ -1799,6 +1640,7 @@ readers do not read off random characters that represent icons */
.fa-diamond-half::before { content: "\e5b7"; }
.fa-diamond-half-stroke::before { content: "\e5b8"; }
.fa-diamond-turn-right::before { content: "\f5eb"; }
.fa-diamonds-4::before { content: "\e68b"; }
.fa-dice::before { content: "\f522"; }
.fa-dice-d10::before { content: "\f6cd"; }
.fa-dice-d12::before { content: "\f6ce"; }
@ -2383,6 +2225,7 @@ readers do not read off random characters that represent icons */
.fa-globe-pointer::before { content: "\e60e"; }
.fa-globe-snow::before { content: "\f7a3"; }
.fa-globe-stand::before { content: "\f5f6"; }
.fa-globe-wifi::before { content: "\e685"; }
.fa-glove-boxing::before { content: "\f438"; }
.fa-goal-net::before { content: "\e3ab"; }
.fa-golf-ball::before { content: "\f450"; }
@ -2687,6 +2530,7 @@ readers do not read off random characters that represent icons */
.fa-humidity::before { content: "\f750"; }
.fa-hundred-points::before { content: "\e41c"; }
.fa-hurricane::before { content: "\f751"; }
.fa-hydra::before { content: "\e686"; }
.fa-hyphen::before { content: "\2d"; }
.fa-i::before { content: "\49"; }
.fa-i-cursor::before { content: "\f246"; }
@ -2854,6 +2698,7 @@ readers do not read off random characters that represent icons */
.fa-lightbulb-exclamation::before { content: "\f671"; }
.fa-lightbulb-exclamation-on::before { content: "\e1ca"; }
.fa-lightbulb-gear::before { content: "\e5fd"; }
.fa-lightbulb-message::before { content: "\e687"; }
.fa-lightbulb-on::before { content: "\f672"; }
.fa-lightbulb-slash::before { content: "\f673"; }
.fa-lighthouse::before { content: "\e612"; }
@ -3208,6 +3053,7 @@ readers do not read off random characters that represent icons */
.fa-octagon-minus::before { content: "\f308"; }
.fa-octagon-plus::before { content: "\f301"; }
.fa-octagon-xmark::before { content: "\f2f0"; }
.fa-octopus::before { content: "\e688"; }
.fa-oil-can::before { content: "\f613"; }
.fa-oil-can-drip::before { content: "\e205"; }
.fa-oil-temp::before { content: "\f614"; }
@ -4196,9 +4042,12 @@ readers do not read off random characters that represent icons */
.fa-table::before { content: "\f0ce"; }
.fa-table-cells::before { content: "\f00a"; }
.fa-table-cells-column-lock::before { content: "\e678"; }
.fa-table-cells-column-unlock::before { content: "\e690"; }
.fa-table-cells-large::before { content: "\f009"; }
.fa-table-cells-lock::before { content: "\e679"; }
.fa-table-cells-row-lock::before { content: "\e67a"; }
.fa-table-cells-row-unlock::before { content: "\e691"; }
.fa-table-cells-unlock::before { content: "\e692"; }
.fa-table-columns::before { content: "\f0db"; }
.fa-table-layout::before { content: "\e290"; }
.fa-table-list::before { content: "\f00b"; }
@ -4311,9 +4160,11 @@ readers do not read off random characters that represent icons */
.fa-theta::before { content: "\f69e"; }
.fa-thought-bubble::before { content: "\e32e"; }
.fa-thumb-tack::before { content: "\f08d"; }
.fa-thumb-tack-slash::before { content: "\e68f"; }
.fa-thumbs-down::before { content: "\f165"; }
.fa-thumbs-up::before { content: "\f164"; }
.fa-thumbtack::before { content: "\f08d"; }
.fa-thumbtack-slash::before { content: "\e68f"; }
.fa-thunderstorm::before { content: "\f76c"; }
.fa-thunderstorm-moon::before { content: "\f76d"; }
.fa-thunderstorm-sun::before { content: "\f76e"; }
@ -4556,6 +4407,7 @@ readers do not read off random characters that represent icons */
.fa-user-alt::before { content: "\f406"; }
.fa-user-alt-slash::before { content: "\f4fa"; }
.fa-user-astronaut::before { content: "\f4fb"; }
.fa-user-beard-bolt::before { content: "\e689"; }
.fa-user-bounty-hunter::before { content: "\e2bf"; }
.fa-user-chart::before { content: "\f6a3"; }
.fa-user-check::before { content: "\f4fc"; }
@ -4584,6 +4436,7 @@ readers do not read off random characters that represent icons */
.fa-user-hard-hat::before { content: "\f82c"; }
.fa-user-headset::before { content: "\f82d"; }
.fa-user-helmet-safety::before { content: "\f82c"; }
.fa-user-hoodie::before { content: "\e68a"; }
.fa-user-injured::before { content: "\f728"; }
.fa-user-large::before { content: "\f406"; }
.fa-user-large-slash::before { content: "\f4fa"; }
@ -4833,7 +4686,7 @@ readers do not read off random characters that represent icons */
border-width: 0; }
/*!
* Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com
* Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Commercial License)
* Copyright 2024 Fonticons, Inc.
*/
@ -4954,6 +4807,9 @@ readers do not read off random characters that represent icons */
.fa-jxl:before {
content: "\e67b"; }
.fa-dart-lang:before {
content: "\e693"; }
.fa-hire-a-helper:before {
content: "\f3b0"; }
@ -5893,6 +5749,9 @@ readers do not read off random characters that represent icons */
.fa-twitch:before {
content: "\f1e8"; }
.fa-flutter:before {
content: "\e694"; }
.fa-ravelry:before {
content: "\f2d9"; }
@ -6428,7 +6287,7 @@ readers do not read off random characters that represent icons */
content: "\f3f6"; }
/*!
* Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com
* Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Commercial License)
* Copyright 2024 Fonticons, Inc.
*/
@ -6448,7 +6307,7 @@ readers do not read off random characters that represent icons */
font-weight: 300; }
/*!
* Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com
* Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Commercial License)
* Copyright 2024 Fonticons, Inc.
*/
@ -6468,7 +6327,7 @@ readers do not read off random characters that represent icons */
font-weight: 400; }
/*!
* Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com
* Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Commercial License)
* Copyright 2024 Fonticons, Inc.
*/
@ -6488,7 +6347,7 @@ readers do not read off random characters that represent icons */
font-weight: 900; }
/*!
* Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com
* Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Commercial License)
* Copyright 2024 Fonticons, Inc.
*/
@ -7505,6 +7364,9 @@ readers do not read off random characters that represent icons */
.fad.fa-trillium::after, .fa-duotone.fa-trillium::after {
content: "\e588\e588"; }
.fad.fa-table-cells-unlock::after, .fa-duotone.fa-table-cells-unlock::after {
content: "\e692\e692"; }
.fad.fa-music-slash::after, .fa-duotone.fa-music-slash::after {
content: "\f8d1\f8d1"; }
@ -8084,6 +7946,9 @@ readers do not read off random characters that represent icons */
.fad.fa-phone-square-alt::after, .fa-duotone.fa-phone-square-alt::after {
content: "\f87b\f87b"; }
.fad.fa-user-beard-bolt::after, .fa-duotone.fa-user-beard-bolt::after {
content: "\e689\e689"; }
.fad.fa-cart-plus::after, .fa-duotone.fa-cart-plus::after {
content: "\f217\f217"; }
@ -9581,6 +9446,9 @@ readers do not read off random characters that represent icons */
.fad.fa-coffin-cross::after, .fa-duotone.fa-coffin-cross::after {
content: "\e051\e051"; }
.fad.fa-octopus::after, .fa-duotone.fa-octopus::after {
content: "\e688\e688"; }
.fad.fa-spell-check::after, .fa-duotone.fa-spell-check::after {
content: "\f891\f891"; }
@ -9770,6 +9638,12 @@ readers do not read off random characters that represent icons */
.fad.fa-passport::after, .fa-duotone.fa-passport::after {
content: "\f5ab\f5ab"; }
.fad.fa-thumbtack-slash::after, .fa-duotone.fa-thumbtack-slash::after {
content: "\e68f\e68f"; }
.fad.fa-thumb-tack-slash::after, .fa-duotone.fa-thumb-tack-slash::after {
content: "\e68f\e68f"; }
.fad.fa-inbox-in::after, .fa-duotone.fa-inbox-in::after {
content: "\f310\f310"; }
@ -11090,6 +10964,9 @@ readers do not read off random characters that represent icons */
.fad.fa-ufo-beam::after, .fa-duotone.fa-ufo-beam::after {
content: "\e048\e048"; }
.fad.fa-hydra::after, .fa-duotone.fa-hydra::after {
content: "\e686\e686"; }
.fad.fa-circle-caret-up::after, .fa-duotone.fa-circle-caret-up::after {
content: "\f331\f331"; }
@ -11783,6 +11660,9 @@ readers do not read off random characters that represent icons */
.fad.fa-podium::after, .fa-duotone.fa-podium::after {
content: "\f680\f680"; }
.fad.fa-diamonds-4::after, .fa-duotone.fa-diamonds-4::after {
content: "\e68b\e68b"; }
.fad.fa-memo-circle-check::after, .fa-duotone.fa-memo-circle-check::after {
content: "\e1d9\e1d9"; }
@ -15038,6 +14918,9 @@ readers do not read off random characters that represent icons */
.fad.fa-pipe-valve::after, .fa-duotone.fa-pipe-valve::after {
content: "\e439\e439"; }
.fad.fa-lightbulb-message::after, .fa-duotone.fa-lightbulb-message::after {
content: "\e687\e687"; }
.fad.fa-arrow-up-from-arc::after, .fa-duotone.fa-arrow-up-from-arc::after {
content: "\e4b4\e4b4"; }
@ -15554,6 +15437,9 @@ readers do not read off random characters that represent icons */
.fad.fa-oil-well::after, .fa-duotone.fa-oil-well::after {
content: "\e532\e532"; }
.fad.fa-table-cells-column-unlock::after, .fa-duotone.fa-table-cells-column-unlock::after {
content: "\e690\e690"; }
.fad.fa-person-simple::after, .fa-duotone.fa-person-simple::after {
content: "\e220\e220"; }
@ -15851,6 +15737,9 @@ readers do not read off random characters that represent icons */
.fad.fa-toolbox::after, .fa-duotone.fa-toolbox::after {
content: "\f552\f552"; }
.fad.fa-globe-wifi::after, .fa-duotone.fa-globe-wifi::after {
content: "\e685\e685"; }
.fad.fa-envelope-dot::after, .fa-duotone.fa-envelope-dot::after {
content: "\e16f\e16f"; }
@ -16586,6 +16475,9 @@ readers do not read off random characters that represent icons */
.fad.fa-transporter-2::after, .fa-duotone.fa-transporter-2::after {
content: "\e044\e044"; }
.fad.fa-user-hoodie::after, .fa-duotone.fa-user-hoodie::after {
content: "\e68a\e68a"; }
.fad.fa-hands-holding-diamond::after, .fa-duotone.fa-hands-holding-diamond::after {
content: "\f47c\f47c"; }
@ -19121,6 +19013,9 @@ readers do not read off random characters that represent icons */
.fad.fa-brain-circuit::after, .fa-duotone.fa-brain-circuit::after {
content: "\e0c6\e0c6"; }
.fad.fa-table-cells-row-unlock::after, .fa-duotone.fa-table-cells-row-unlock::after {
content: "\e691\e691"; }
.fad.fa-user-injured::after, .fa-duotone.fa-user-injured::after {
content: "\f728\f728"; }
@ -19394,6 +19289,7 @@ readers do not read off random characters that represent icons */
.fak.fa-fa-dock-l::before, .fa-kit.fa-fa-dock-l::before { content: "\e007"; }
.fak.fa-fa-dock-r::before, .fa-kit.fa-fa-dock-r::before { content: "\e006"; }
.fak.fa-lexon::before, .fa-kit.fa-lexon::before { content: "\e004"; }
.fak.fa-solid-gear-circle-play::before, .fa-kit.fa-solid-gear-circle-play::before { content: "\e009"; }
.fak.fa-solidity-mono::before, .fa-kit.fa-solidity-mono::before { content: "\e005"; }
.fak.fa-ts-logo::before, .fa-kit.fa-ts-logo::before { content: "\e003"; }
.fak.fa-vyper2::before, .fa-kit.fa-vyper2::before { content: "\e002"; }

@ -0,0 +1,77 @@
<svg width="1080" height="1080" viewBox="0 0 1080 1080" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_62_13806)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M243.632 1074.71C243.014 1074.6 242.439 1074.32 241.962 1073.92C234.393 1067.49 170.155 1012.98 156.436 1001.34C155.758 1000.76 155.338 999.934 155.267 999.041C155.191 998.157 155.49 997.272 156.069 996.594C166.824 984.128 213.289 930.238 213.289 930.238C213.289 930.238 136.374 884.225 116.785 872.51C115.815 871.923 115.202 870.875 115.163 869.736C115.124 868.596 115.683 867.513 116.628 866.876C132.253 856.218 184.848 820.367 184.848 820.367C184.848 820.367 143.189 796.562 129.723 788.866C128.778 788.325 128.154 787.351 128.057 786.265C127.955 785.188 128.395 784.118 129.226 783.407C150.037 765.75 253.071 678.318 253.071 678.318C253.071 678.318 120.643 568.156 101.261 552.032C99.9274 550.923 99.6631 548.979 100.649 547.553C112.476 530.493 180.345 432.538 180.345 432.538C180.345 432.538 81.023 322.558 61.4772 300.915C60.7298 300.088 60.4511 298.952 60.7139 297.871C60.9764 296.791 61.7588 295.914 62.8047 295.531C89.0112 285.876 217.225 238.624 217.225 238.624C217.225 238.624 243.762 97.0242 248.974 69.227C249.176 68.154 249.883 67.2436 250.88 66.7932C251.877 66.3438 253.027 66.4057 253.965 66.9628C277.983 81.2078 398.947 152.954 398.947 152.954C398.947 152.954 464.447 41.4414 478.238 17.9661C478.8 17.0021 479.805 16.3832 480.922 16.3181C482.039 16.2539 483.108 16.745 483.783 17.6328C500.981 40.2573 585.732 151.762 585.732 151.762C585.732 151.762 716.782 81.8336 742.178 68.2874C743.147 67.7743 744.298 67.7605 745.274 68.2579C746.249 68.7553 746.922 69.6978 747.066 70.7796C750.766 97.4147 768.733 226.986 768.733 226.986C768.733 226.986 930.559 247.282 963.181 251.372C964.362 251.521 965.375 252.284 965.842 253.38C966.31 254.474 966.161 255.735 965.451 256.689C946.661 281.981 856.82 402.908 856.82 402.908C856.82 402.908 958.366 483.76 977.854 499.277C978.641 499.903 979.105 500.851 979.116 501.857C979.128 502.863 978.686 503.821 977.913 504.465C956.879 522.007 837.76 621.353 837.76 621.353L840.361 769.536C840.361 769.536 950.632 834.064 970.301 845.574C971.084 846.032 971.648 846.787 971.862 847.667C972.077 848.548 971.927 849.479 971.444 850.245C959.731 868.857 896.099 969.963 896.099 969.963C896.099 969.963 970.865 1044 987.455 1060.44C988.211 1061.19 988.568 1062.24 988.41 1063.31C988.255 1064.36 987.611 1065.27 986.669 1065.77C965.466 1077 867.657 1128.83 867.657 1128.83C867.657 1128.83 908.489 1214.19 916.057 1230.01C916.755 1231.46 916.313 1233.21 915.012 1234.16C900.776 1244.56 823.272 1301.2 812.11 1309.36C811.266 1309.97 810.182 1310.16 809.178 1309.86C785.939 1302.94 515.724 1222.5 515.724 1222.5C515.724 1222.5 348.111 1100.64 336.681 1092.33C336.268 1092.04 335.803 1091.84 335.324 1091.75C327.065 1090.22 252.859 1076.43 243.632 1074.71Z" fill="#222496"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M154.261 999.55L211.042 931.338C211.042 931.338 134.129 885.32 114.546 873.596C113.566 873.016 112.953 871.967 112.919 870.829C112.886 869.68 113.436 868.609 114.38 867.962C130.006 857.308 182.604 821.464 182.604 821.464C182.604 821.464 140.948 797.658 127.482 789.96C126.535 789.414 125.911 788.443 125.811 787.361C125.709 786.279 126.146 785.208 126.978 784.505C147.792 766.834 250.823 679.405 250.823 679.405C250.823 679.405 118.398 569.241 99.0156 553.121C97.6824 552.016 97.4169 550.064 98.4053 548.647C110.227 531.579 178.099 433.63 178.099 433.63C178.099 433.63 78.7798 323.645 59.2336 302.002C58.4884 301.177 58.2017 300.039 58.4672 298.957C58.7338 297.886 59.517 297.004 60.5601 296.625C86.763 286.964 214.983 239.719 214.983 239.719C214.983 239.719 241.518 98.1174 246.727 70.317C246.929 69.246 247.641 68.331 248.637 67.8848C249.632 67.4274 250.784 67.4945 251.724 68.0523C275.739 82.2983 396.704 154.042 396.704 154.042C396.704 154.042 462.203 42.5274 475.991 19.0554C476.557 18.096 477.564 17.4713 478.679 17.4044C479.792 17.3374 480.867 17.8397 481.541 18.721L488.245 27.5452L503.432 226.655L638.418 238.927L536.398 388.415L659.614 382.235L541.976 593.684L530.764 836.96L274.271 1104.43L154.261 999.55Z" fill="#2F63D1"/>
<ellipse cx="562.952" cy="985.462" rx="11.4792" ry="6.78319" transform="rotate(60 562.952 985.462)" fill="#B9F3FF"/>
<ellipse cx="585.067" cy="970.117" rx="11.4792" ry="3.69524" transform="rotate(60 585.067 970.117)" fill="#74E8FF"/>
<path d="M522.73 768.855V375.5H869.678L792.144 705.156L711.264 779.9L522.73 768.855Z" fill="url(#paint0_linear_62_13806)"/>
<path d="M545.599 760.506V375.5H198.651L276.185 705.156L357.065 779.9L545.599 760.506Z" fill="url(#paint1_linear_62_13806)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M165.466 403.392C302.125 355.422 474.484 429.608 539.188 476.462L480.619 17.3984L379.659 146.249L254.713 67.5999L241.884 246.094L62.832 297.969L165.466 403.392Z" fill="#2F63D1"/>
<path d="M565.962 132.304L480.619 17.3984L539.188 476.462C597.756 425.145 781.27 378.848 883.904 374.386L973.709 255.576L800.235 231.591L756.169 211.511V61.4641L639.591 138.44L565.962 132.304Z" fill="#222496"/>
<path d="M717.787 315.353C717.787 385.91 560.987 397.932 538.049 397.932C515.111 397.932 351.302 384.865 357.953 315.353C359.358 300.671 465.858 348.803 537.87 348.803C609.882 348.803 717.787 299.982 717.787 315.353Z" fill="#252773"/>
<path d="M856.05 466.132C806.158 516.024 765.042 444.604 748.822 428.384C732.602 412.164 626.012 265.5 679.867 221.05C691.242 211.661 816.509 256.793 867.429 307.713C918.349 358.633 866.919 455.263 856.05 466.132Z" fill="#252773" fill-opacity="0.32"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M214.781 528.645C240.56 340.364 331.35 317.227 542.858 324.882C672.596 329.578 780.994 300.874 823.392 390.627C854.249 462.633 854.249 538.814 854.249 538.814L901.357 531.269C901.357 531.269 933.524 487.297 873.483 357.234C825.386 253.05 727.906 270.659 542.858 270.659C328.136 270.66 224.635 257.102 186.294 372.886C155.177 466.866 172.657 533.118 172.657 533.118C172.657 533.118 198.422 531.104 214.781 528.645Z" fill="#46D0FF"/>
<path d="M870.313 568.843C870.313 568.843 905.92 514.415 851.046 384.371C807.089 280.202 711.791 308.27 542.671 308.27C346.43 308.271 252.121 283.212 217.08 398.98C188.641 492.946 204.332 570.692 204.332 570.692" stroke="#29C0F4" stroke-width="25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M910.424 311.537L874.083 408.681C872.608 412.624 874.839 416.982 878.901 418.093L911.092 426.897C915.196 428.02 919.368 425.338 920.049 421.139L936.885 317.416C937.513 313.549 934.914 309.895 931.054 309.218L918.368 306.994C914.972 306.398 911.633 308.307 910.424 311.537Z" fill="#03D5FE"/>
<path d="M958.156 289.86C958.156 305.673 945.333 318.493 929.514 318.493C913.696 318.493 900.873 305.673 900.873 289.86C900.873 274.046 913.696 261.227 929.514 261.227C945.333 261.227 958.156 274.046 958.156 289.86Z" fill="#7FE5FF"/>
<path d="M910.424 311.537L874.083 408.681C872.608 412.624 874.839 416.982 878.901 418.093L911.092 426.897C915.196 428.02 919.368 425.338 920.049 421.139L936.885 317.416C937.513 313.549 934.914 309.895 931.054 309.218L918.368 306.994C914.972 306.398 911.633 308.307 910.424 311.537Z" fill="white" fill-opacity="0.32"/>
<path d="M958.156 289.86C958.156 305.673 945.333 318.493 929.514 318.493C913.696 318.493 900.873 305.673 900.873 289.86C900.873 274.046 913.696 261.227 929.514 261.227C945.333 261.227 958.156 274.046 958.156 289.86Z" fill="white" fill-opacity="0.32"/>
<path d="M164.075 319.884L200.416 417.029C201.891 420.972 199.66 425.33 195.598 426.441L163.407 435.245C159.303 436.368 155.131 433.686 154.45 429.487L137.614 325.763C136.986 321.896 139.585 318.243 143.445 317.566L156.131 315.341C159.527 314.745 162.866 316.655 164.075 319.884Z" fill="#03D5FE"/>
<path d="M116.343 298.207C116.343 314.021 129.166 326.84 144.985 326.84C160.803 326.84 173.626 314.021 173.626 298.207C173.626 282.394 160.803 269.574 144.985 269.574C129.166 269.574 116.343 282.394 116.343 298.207Z" fill="#7FE5FF"/>
<path d="M164.075 319.884L200.416 417.029C201.891 420.972 199.66 425.33 195.598 426.441L163.407 435.245C159.303 436.368 155.131 433.686 154.45 429.487L137.614 325.763C136.986 321.896 139.585 318.243 143.445 317.566L156.131 315.341C159.527 314.745 162.866 316.655 164.075 319.884Z" fill="white" fill-opacity="0.32"/>
<path d="M116.343 298.207C116.343 314.021 129.166 326.84 144.985 326.84C160.803 326.84 173.626 314.021 173.626 298.207C173.626 282.394 160.803 269.574 144.985 269.574C129.166 269.574 116.343 282.394 116.343 298.207Z" fill="white" fill-opacity="0.32"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M817.838 672.654C817.838 672.654 858.785 537.91 811.604 381.191C811.604 381.191 910.242 373.886 947.161 398.765C971.679 415.292 989.826 472.937 979.882 546.77C972.463 601.858 936.353 638.341 911.889 654.663C885.655 672.164 830.694 672.675 817.838 672.654Z" fill="#7FE5FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M904.192 384.41C921.06 387.19 936.629 391.665 947.162 398.769C971.68 415.296 989.827 472.941 979.883 546.774C972.464 601.862 936.353 638.345 911.89 654.667C909.445 656.297 906.695 657.789 903.719 659.136C946.83 529.006 914.015 413.662 904.192 384.41Z" fill="#46D0FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M969.057 524.311C969.057 524.311 975.676 481.083 956.955 429.507C955.893 426.579 952.654 425.067 949.727 426.129C946.799 427.192 945.285 430.43 946.348 433.357C963.98 481.93 957.9 522.621 957.9 522.621C957.434 525.7 959.556 528.578 962.634 529.043C965.713 529.51 968.591 527.388 969.057 524.311Z" fill="#7FE5FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M964.603 562.474C964.603 562.474 967.01 557.577 967.99 547.819C968.302 544.719 966.039 541.951 962.941 541.64C959.842 541.329 957.075 543.592 956.763 546.69C956.03 553.977 954.394 557.667 954.394 557.667C953.067 560.486 954.278 563.849 957.095 565.176C959.912 566.502 963.276 565.292 964.603 562.474Z" fill="#7FE5FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M257.492 378.07C257.492 378.07 211.004 507.589 279.465 701.67C279.465 701.67 131.521 668.521 110.893 624.987C72.8177 544.634 100.945 434.897 117.928 418.428C138.184 398.786 235.97 380.605 257.492 378.07Z" fill="#7FE5FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M169.372 667.29C142.49 655.654 118.605 641.263 110.892 624.986C72.8168 544.633 100.945 434.896 117.928 418.427C126.05 410.55 146.172 402.784 168.751 396.142C153.398 429.283 129.164 512.994 169.372 667.29Z" fill="#47D0FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M118.306 441.607C118.306 441.607 100.064 484.057 105.62 542.033C105.918 545.133 108.676 547.409 111.776 547.112C114.876 546.814 117.15 544.056 116.854 540.956C111.603 486.175 128.682 446.044 128.682 446.044C129.906 443.181 128.576 439.863 125.712 438.638C122.85 437.413 119.531 438.743 118.306 441.607Z" fill="#7FE5FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.52 570.949C108.52 570.949 107.328 578.232 114.124 591.271C115.562 594.033 118.973 595.106 121.734 593.667C124.496 592.228 125.569 588.818 124.131 586.056C119.856 577.855 119.685 573.034 119.74 572.139C119.742 572.092 119.745 572.068 119.745 572.068C120.093 569.193 117.882 566.384 114.791 566.014C111.698 565.646 108.888 567.857 108.52 570.949Z" fill="#7FE5FF"/>
<path d="M649.442 993.36C649.442 1028.81 633.393 1060.5 608.166 1081.58C588.226 1098.24 562.552 1108.27 534.537 1108.27C507.904 1108.27 483.387 1099.21 463.902 1084C436.957 1062.97 419.631 1030.19 419.631 993.36C419.631 929.9 471.076 878.455 534.537 878.455C597.997 878.455 649.442 929.9 649.442 993.36Z" fill="#252773"/>
<path d="M622.668 993.36C622.668 1042.03 583.21 1081.49 534.537 1081.49C485.863 1081.49 446.405 1042.03 446.405 993.36C446.405 944.687 485.863 905.229 534.537 905.229C583.21 905.229 622.668 944.687 622.668 993.36Z" fill="#01026D"/>
<path d="M601.472 993.36C601.472 1030.33 571.504 1060.3 534.537 1060.3C497.57 1060.3 467.602 1030.33 467.602 993.36C467.602 956.393 497.57 926.425 534.537 926.425C571.504 926.425 601.472 956.393 601.472 993.36Z" fill="#03D5FE"/>
<path d="M580.276 993.36C580.276 1018.62 559.798 1039.1 534.537 1039.1C509.276 1039.1 488.798 1018.62 488.798 993.36C488.798 968.099 509.276 947.621 534.537 947.621C559.798 947.621 580.276 968.099 580.276 993.36Z" fill="#74E8FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M313.3 1145.64C303.718 1143.94 292.13 1142.33 279.727 1140.6L279.726 1140.6C245.407 1135.82 204.854 1130.17 183.317 1119.56C170.44 1198.14 171.819 1281.17 299.08 1249.4C302.881 1248.46 305.654 1245.05 305.817 1241.14C306.843 1216.5 309.196 1183.05 313.3 1145.64Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M770.277 1249.4C897.538 1281.17 898.918 1198.14 886.041 1119.56C864.504 1130.17 823.95 1135.82 789.632 1140.6L789.631 1140.6C777.228 1142.33 765.64 1143.94 756.058 1145.64C760.161 1183.05 762.515 1216.5 763.54 1241.14C763.703 1245.05 766.477 1248.46 770.277 1249.4Z" fill="white"/>
<path d="M901.007 1104.92C906.625 1078.7 882.6 1020.69 873.676 1012.33C870.553 1009.4 864.704 1008.06 857.025 1007.91C827.957 1007.35 772.675 1023.84 739.902 1035.75C746.997 1073.22 752.272 1111.12 756.058 1145.64C765.64 1143.94 777.228 1142.33 789.631 1140.6L789.632 1140.6C823.95 1135.82 864.504 1130.17 886.041 1119.56C894.307 1115.49 899.772 1110.68 901.007 1104.92Z" fill="#252773"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M279.727 1140.6L279.726 1140.6C245.407 1135.82 204.854 1130.17 183.317 1119.56C175.051 1115.49 169.586 1110.68 168.35 1104.92" fill="#40B7E2"/>
<path d="M195.682 1012.33C186.757 1020.69 162.732 1078.7 168.35 1104.92C169.586 1110.68 175.051 1115.49 183.317 1119.56C204.854 1130.17 245.407 1135.82 279.726 1140.6L279.727 1140.6C292.13 1142.33 303.718 1143.94 313.3 1145.64C317.086 1111.12 322.361 1073.22 329.455 1035.75C296.683 1023.84 241.401 1007.35 212.333 1007.91C204.654 1008.06 198.804 1009.4 195.682 1012.33Z" fill="#252773"/>
<path d="M756.058 1145.64C752.272 1111.12 746.997 1073.22 739.902 1035.75C772.675 1023.84 827.957 1007.35 857.025 1007.91C825.139 945.778 758.557 875.025 722.514 841.641H346.844C310.8 875.025 244.219 945.778 212.333 1007.91C241.401 1007.35 296.683 1023.84 329.455 1035.75C322.361 1073.22 317.086 1111.12 313.3 1145.64L379.47 1165.16L463.902 1084C436.957 1062.97 419.631 1030.19 419.631 993.36C419.631 929.9 471.076 878.455 534.537 878.455C597.997 878.455 649.442 929.9 649.442 993.36C649.442 1028.81 633.393 1060.5 608.166 1081.58L697.971 1165.16L756.058 1145.64Z" fill="white"/>
<path d="M372.501 888.758C354.265 929.264 340.599 981.375 330.57 1034.34C324.567 1031.88 314.131 1027.18 302.13 1024.05C324.045 961.958 336.458 922.143 372.501 888.758Z" fill="#F8F8F8"/>
<path d="M696.598 888.758C714.835 929.264 728.501 981.375 738.53 1034.34C744.533 1031.88 754.969 1027.18 766.97 1024.05C745.055 961.958 732.642 922.143 696.598 888.758Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M733.5 835.04L740.447 821.394C743.987 814.44 738.589 806.264 730.802 806.774C719.229 807.532 719.381 808.575 699.999 809.679C651.355 812.451 584.839 815.607 534.184 815.607C483.53 815.607 417.013 812.451 368.37 809.679C349.157 808.585 349.429 807.55 337.871 806.794C330.02 806.28 324.618 814.583 328.288 821.543L335.468 835.16C337.075 838.208 340.107 840.215 343.542 840.469C369.079 842.358 461.349 850.087 534.498 850.087C607.547 850.087 699.62 842.379 725.349 840.477C728.836 840.219 731.914 838.155 733.5 835.04Z" fill="#252773"/>
<path d="M368.37 809.679C417.013 812.451 483.53 815.607 534.184 815.607C584.839 815.607 651.355 812.451 699.999 809.679L678.577 750.744C677.136 746.78 673.369 744.141 669.15 744.141H399.218C395 744.141 391.232 746.78 389.791 750.744L368.37 809.679Z" fill="url(#paint2_linear_62_13806)"/>
<path d="M380.957 774.33C379.595 779.241 383.106 784.156 388.196 784.42C433.839 786.781 487.656 787.531 533.851 787.531C580.045 787.531 634.682 789.52 680.436 787.532C685.531 787.311 689.043 782.389 687.674 777.476L675.172 732.615C673.761 728.62 670.074 725.961 665.945 725.961H401.756C397.628 725.961 393.94 728.62 392.53 732.615L380.957 774.33Z" fill="#D9D9D9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M326.121 305.055C259.847 305.055 206.121 358.78 206.121 425.055V659.878C206.121 726.152 259.847 779.878 326.121 779.878H743.568C809.843 779.878 863.568 726.152 863.568 659.878V425.055C863.568 358.781 809.843 305.055 743.568 305.055H326.121ZM351.866 353.059C307.683 353.059 271.866 388.876 271.866 433.059V652.917C271.866 697.1 307.683 732.917 351.866 732.917H717.824C762.007 732.917 797.824 697.1 797.824 652.917V433.059C797.824 388.876 762.007 353.059 717.824 353.059H351.866Z" fill="white"/>
<rect x="271.866" y="353.062" width="525.958" height="379.858" rx="80" fill="url(#paint3_radial_62_13806)"/>
<rect x="277.866" y="359.062" width="513.958" height="367.858" rx="74" stroke="url(#paint4_linear_62_13806)" stroke-opacity="0.32" stroke-width="12"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M457.177 582.156C457.177 582.156 425.533 544.994 366.765 538.982C362.781 538.575 359.226 541.48 358.819 545.462C358.411 549.443 361.311 553.004 365.293 553.412C417.752 558.775 446.175 591.603 446.175 591.603C448.78 594.64 453.363 594.99 456.397 592.382C459.431 589.773 459.782 585.193 457.177 582.156Z" fill="#222496"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M616.002 582.156C616.002 582.156 647.647 544.994 706.415 538.982C710.398 538.575 713.954 541.48 714.361 545.462C714.768 549.443 711.869 553.004 707.886 553.412C655.427 558.775 627.005 591.603 627.005 591.603C624.4 594.64 619.816 594.99 616.783 592.382C613.749 589.773 613.397 585.193 616.002 582.156Z" fill="#222496"/>
</g>
<defs>
<linearGradient id="paint0_linear_62_13806" x1="504.67" y1="375.298" x2="838.931" y2="558.421" gradientUnits="userSpaceOnUse">
<stop stop-color="#D5CDFB"/>
<stop offset="1" stop-color="#DBD4FC"/>
</linearGradient>
<linearGradient id="paint1_linear_62_13806" x1="198.931" y1="375.297" x2="522.238" y2="571.176" gradientUnits="userSpaceOnUse">
<stop stop-color="#F6F5FC"/>
<stop offset="1" stop-color="#DBD6F4"/>
</linearGradient>
<linearGradient id="paint2_linear_62_13806" x1="534.184" y1="744.141" x2="534.406" y2="835.015" gradientUnits="userSpaceOnUse">
<stop offset="0.359682" stop-color="#D9D9D9"/>
<stop offset="1" stop-color="#FDFDFD"/>
</linearGradient>
<radialGradient id="paint3_radial_62_13806" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(422.432 470.962) rotate(66.7746) scale(285.06 382.801)">
<stop offset="0.219155" stop-color="#D6F8FF"/>
<stop offset="1" stop-color="#74E8FF"/>
</radialGradient>
<linearGradient id="paint4_linear_62_13806" x1="534.845" y1="353.062" x2="534.845" y2="732.921" gradientUnits="userSpaceOnUse">
<stop stop-color="#526178" stop-opacity="0.16"/>
<stop offset="1" stop-color="#97B4DE"/>
</linearGradient>
<clipPath id="clip0_62_13806">
<rect width="1080" height="1080" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save