commit
2695aafc82
@ -0,0 +1,3 @@ |
||||
{ |
||||
"extends": "../../.eslintrc.json", |
||||
} |
@ -0,0 +1 @@ |
||||
# Remix Dapp |
@ -0,0 +1,9 @@ |
||||
{ |
||||
"name": "remix-dapp", |
||||
"version": "1.0.0", |
||||
"main": "index.js", |
||||
"license": "MIT", |
||||
"dependencies": { |
||||
"webpack-manifest-plugin": "^5.0.0" |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
{ |
||||
"name": "remix-dapp", |
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json", |
||||
"sourceRoot": "apps/remix-dapp/src", |
||||
"projectType": "application", |
||||
"implicitDependencies": [], |
||||
"targets": { |
||||
"build": { |
||||
"executor": "@nrwl/webpack:webpack", |
||||
"outputs": ["{options.outputPath}"], |
||||
"defaultConfiguration": "development", |
||||
"dependsOn": ["install"], |
||||
"options": { |
||||
"compiler": "babel", |
||||
"outputPath": "dist/apps/remix-dapp", |
||||
"index": "apps/remix-dapp/src/index.html", |
||||
"baseHref": "./", |
||||
"main": "apps/remix-dapp/src/main.tsx", |
||||
"tsConfig": "apps/remix-dapp/tsconfig.app.json", |
||||
"assets": ["apps/remix-dapp/src/assets/instance.json", "apps/remix-dapp/src/assets/logo.png"], |
||||
"styles": ["apps/remix-dapp/src/App.css"], |
||||
"scripts": [], |
||||
"webpackConfig": "apps/remix-dapp/webpack.config.js" |
||||
}, |
||||
"configurations": { |
||||
"development": { |
||||
}, |
||||
"production": { |
||||
} |
||||
} |
||||
}, |
||||
"lint": { |
||||
"executor": "@nrwl/linter:eslint", |
||||
"outputs": ["{options.outputFile}"], |
||||
"options": { |
||||
"lintFilePatterns": ["apps/remix-dapp/**/*.ts"], |
||||
"eslintConfig": "apps/remix-dapp/.eslintrc" |
||||
} |
||||
}, |
||||
"install": { |
||||
"executor": "nx:run-commands", |
||||
"options": { |
||||
"commands": [ |
||||
"cd apps/remix-dapp && yarn" |
||||
], |
||||
"parallel": false |
||||
} |
||||
}, |
||||
"serve": { |
||||
"executor": "@nrwl/webpack:dev-server", |
||||
"defaultConfiguration": "development", |
||||
"options": { |
||||
"buildTarget": "remix-dapp:build", |
||||
"hmr": true, |
||||
"baseHref": "/" |
||||
}, |
||||
"configurations": { |
||||
"development": { |
||||
"buildTarget": "remix-dapp:build:development", |
||||
"port": 2026 |
||||
}, |
||||
"production": { |
||||
"buildTarget": "remix-dapp:build:production" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"tags": [] |
||||
} |
@ -0,0 +1,25 @@ |
||||
.dragbar_terminal { |
||||
left: 0px; |
||||
top: 0px; |
||||
height: 0.3em; |
||||
} |
||||
|
||||
.dragbar_terminal:hover, |
||||
.dragbar_terminal.ondrag { |
||||
background-color: var(--secondary); |
||||
cursor: row-resize; |
||||
} |
||||
|
||||
.label_item { |
||||
word-break: break-all; |
||||
max-width: 90%; |
||||
} |
||||
.label_key { |
||||
max-width: 65%; |
||||
min-width: fit-content; |
||||
} |
||||
.label_value { |
||||
min-width: 10%; |
||||
} |
||||
|
||||
|
@ -0,0 +1,65 @@ |
||||
import React, { useEffect, useReducer } from 'react'; |
||||
import { IntlProvider } from 'react-intl'; |
||||
import { ToastContainer } from 'react-toastify'; |
||||
import 'react-toastify/dist/ReactToastify.css'; |
||||
import { AppContext } from './contexts'; |
||||
import { appInitialState, appReducer } from './reducers/state'; |
||||
import { initDispatch, initInstance, updateState } from './actions'; |
||||
import enJson from './locales/en'; |
||||
import zhJson from './locales/zh'; |
||||
import esJson from './locales/es'; |
||||
import frJson from './locales/fr'; |
||||
import itJson from './locales/it'; |
||||
import './App.css'; |
||||
import { isMobile } from './utils/tools'; |
||||
import MobilePage from './components/Home/mobile'; |
||||
import PCPage from './components/Home/pc'; |
||||
|
||||
const localeMap: Record<string, any> = { |
||||
zh: zhJson, |
||||
en: enJson, |
||||
fr: frJson, |
||||
it: itJson, |
||||
es: esJson, |
||||
}; |
||||
|
||||
function App(): JSX.Element { |
||||
const [appState, dispatch] = useReducer(appReducer, appInitialState); |
||||
const selectedLocaleCode = appState.settings.selectedLocaleCode; |
||||
useEffect(() => { |
||||
updateState(appState); |
||||
}, [appState]); |
||||
useEffect(() => { |
||||
initDispatch(dispatch); |
||||
updateState(appState); |
||||
initInstance(); |
||||
}, []); |
||||
|
||||
return ( |
||||
<AppContext.Provider |
||||
value={{ |
||||
dispatch, |
||||
appState, |
||||
}} |
||||
> |
||||
<IntlProvider |
||||
locale={selectedLocaleCode} |
||||
messages={localeMap[selectedLocaleCode]} |
||||
> |
||||
{isMobile() ? <MobilePage /> : <PCPage />} |
||||
<ToastContainer |
||||
position="bottom-right" |
||||
newestOnTop |
||||
closeOnClick |
||||
rtl={false} |
||||
pauseOnFocusLoss |
||||
draggable |
||||
pauseOnHover |
||||
theme="colored" |
||||
/> |
||||
</IntlProvider> |
||||
</AppContext.Provider> |
||||
); |
||||
} |
||||
|
||||
export default App; |
@ -0,0 +1,234 @@ |
||||
import axios from 'axios'; |
||||
import Web3 from 'web3'; |
||||
import { ethers } from 'ethers'; |
||||
import BN from 'bn.js'; |
||||
import { execution } from '@remix-project/remix-lib'; |
||||
import { toBytes, addHexPrefix } from '@ethereumjs/util'; |
||||
import { toast } from 'react-toastify'; |
||||
import txRunner from '../utils/txRunner'; |
||||
import metamask from '../utils/metamask'; |
||||
import walletConnect from '../utils/walletConnect'; |
||||
import buildData from '../utils/buildData'; |
||||
|
||||
const { txFormat, txHelper: { makeFullTypeDefinition } } = execution; |
||||
|
||||
const decodeInputParams = (data: any, abi: any) => { |
||||
data = toBytes(addHexPrefix(data)); |
||||
if (!data.length) data = new Uint8Array(32 * abi.inputs.length); |
||||
|
||||
const inputTypes = []; |
||||
for (let i = 0; i < abi.inputs.length; i++) { |
||||
const type = abi.inputs[i].type; |
||||
inputTypes.push( |
||||
type.indexOf('tuple') === 0 |
||||
? makeFullTypeDefinition(abi.inputs[i]) |
||||
: type |
||||
); |
||||
} |
||||
const abiCoder = new ethers.utils.AbiCoder(); |
||||
const decoded = abiCoder.decode(inputTypes, data); |
||||
const ret: any = {}; |
||||
for (const k in abi.inputs) { |
||||
ret[abi.inputs[k].type + ' ' + abi.inputs[k].name] = decoded[k]; |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
let dispatch: any, state: any; |
||||
|
||||
export const initDispatch = (_dispatch: any) => { |
||||
dispatch = _dispatch; |
||||
}; |
||||
|
||||
export const updateState = (_state: any) => { |
||||
state = _state; |
||||
}; |
||||
|
||||
export const setProvider = async (payload: any) => { |
||||
await dispatch({ |
||||
type: 'SET_SETTINGS', |
||||
payload: { loadedAccounts: {} }, |
||||
}); |
||||
const { provider, networkName } = payload; |
||||
const chainId = |
||||
'0x' + Number(networkName.match(/\(([^)]+)\)/)[1]).toString(16); |
||||
if (provider === 'metamask') { |
||||
const web3Provider: any = window.ethereum; |
||||
await metamask.addCustomNetwork(chainId); |
||||
await web3Provider.request({ method: 'eth_requestAccounts' }); |
||||
txRunner.setProvider(web3Provider); |
||||
txRunner.getAccounts(); |
||||
} |
||||
|
||||
if (provider === 'walletconnect') { |
||||
txRunner.setProvider(walletConnect as any); |
||||
walletConnect.subscribeToEvents(); |
||||
} |
||||
}; |
||||
|
||||
export const initInstance = async () => { |
||||
const resp = await axios.get('/assets/instance.json'); |
||||
await dispatch({ type: 'SET_INSTANCE', payload: resp.data }); |
||||
await dispatch({ |
||||
type: 'SET_SETTINGS', |
||||
payload: { networkName: resp.data.network }, |
||||
}); |
||||
await setProvider({ |
||||
networkName: resp.data.network, |
||||
provider: window.ethereum ? 'metamask' : 'walletconnect', |
||||
}); |
||||
updateInstanceBalance(resp.data.address); |
||||
setInterval(() => { |
||||
updateInstanceBalance(resp.data.address); |
||||
}, 30000); |
||||
}; |
||||
|
||||
export const updateInstanceBalance = async (address: string) => { |
||||
const balance = await txRunner.getBalanceInEther(address); |
||||
await dispatch({ type: 'SET_INSTANCE', payload: { balance } }); |
||||
}; |
||||
|
||||
export const saveSettings = async (payload: any) => { |
||||
await dispatch({ type: 'SET_SETTINGS', payload }); |
||||
}; |
||||
|
||||
export const log = async (payload: any) => { |
||||
const journalBlocks = state.terminal.journalBlocks; |
||||
const { message, style } = payload; |
||||
if (style === 'text-log') { |
||||
toast.info(message[0]); |
||||
} else if (style === 'text-danger') { |
||||
toast.error(message[0]); |
||||
} else { |
||||
toast.success('success'); |
||||
} |
||||
await dispatch({ |
||||
type: 'SET_TERMINAL', |
||||
payload: { |
||||
journalBlocks: [...journalBlocks, payload], |
||||
}, |
||||
}); |
||||
}; |
||||
|
||||
export const runTransactions = async (payload: any) => { |
||||
console.log(payload); |
||||
const { sendValue, sendUnit, gasLimit, selectedAccount } = state.settings; |
||||
const { address, decodedResponse, name } = state.instance; |
||||
const value = Web3.utils.toWei(sendValue, sendUnit); |
||||
|
||||
const tx = { |
||||
to: address, |
||||
data: '', |
||||
from: selectedAccount, |
||||
value, |
||||
}; |
||||
|
||||
const isFunction = payload.funcABI.type === 'function'; |
||||
|
||||
if (isFunction) { |
||||
const { dataHex, error } = buildData(payload.funcABI, payload.inputsValues); |
||||
|
||||
if (error) { |
||||
await log({ |
||||
message: [`${payload.logMsg} errored: ${error}`], |
||||
style: 'text-danger', |
||||
}); |
||||
return; |
||||
} |
||||
|
||||
tx.data = dataHex as string; |
||||
} else { |
||||
tx.data = payload.inputsValues; |
||||
} |
||||
|
||||
if (payload.lookupOnly) { |
||||
await log({ |
||||
message: [`${payload.logMsg}`], |
||||
style: 'text-log', |
||||
}); |
||||
} else { |
||||
await log({ |
||||
message: [`${payload.logMsg} pending ... `], |
||||
style: 'text-log', |
||||
}); |
||||
} |
||||
|
||||
const resp: any = await txRunner.runTx( |
||||
tx, |
||||
'0x' + new BN(gasLimit, 10).toString(16), |
||||
payload.lookupOnly |
||||
); |
||||
|
||||
if (resp.error) { |
||||
await log({ |
||||
message: [`${payload.logMsg} errored: ${resp.error}`], |
||||
style: 'text-danger', |
||||
}); |
||||
return; |
||||
} |
||||
|
||||
if (payload.lookupOnly) { |
||||
await dispatch({ |
||||
type: 'SET_INSTANCE', |
||||
payload: { |
||||
decodedResponse: { |
||||
...decodedResponse, |
||||
[payload.funcIndex]: txFormat.decodeResponse(resp, payload.funcABI), |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
await log({ |
||||
message: [ |
||||
{ |
||||
tx: { |
||||
to: tx.to, |
||||
isCall: true, |
||||
from: tx.from, |
||||
envMode: 'injected', |
||||
input: tx.data, |
||||
hash: 'call' + (tx.from || '') + tx.to + tx.data, |
||||
}, |
||||
resolvedData: { |
||||
contractName: name, |
||||
to: address, |
||||
fn: payload.funcABI.name, |
||||
params: isFunction |
||||
? decodeInputParams( |
||||
tx.data?.replace('0x', '').substring(8), |
||||
payload.funcABI |
||||
) |
||||
: tx.data, |
||||
decodedReturnValue: txFormat.decodeResponse(resp, payload.funcABI), |
||||
}, |
||||
}, |
||||
], |
||||
style: '', |
||||
name: 'knownTransaction', |
||||
provider: 'injected', |
||||
}); |
||||
} else { |
||||
await log({ |
||||
message: [ |
||||
{ |
||||
tx: { ...resp.tx, receipt: resp.receipt }, |
||||
receipt: resp.receipt, |
||||
resolvedData: { |
||||
contractName: name, |
||||
to: address, |
||||
fn: payload.funcABI.name, |
||||
params: isFunction |
||||
? decodeInputParams( |
||||
tx.data?.replace('0x', '').substring(8), |
||||
payload.funcABI |
||||
) |
||||
: tx.data, |
||||
}, |
||||
}, |
||||
], |
||||
style: '', |
||||
name: 'knownTransaction', |
||||
provider: 'injected', |
||||
}); |
||||
} |
||||
}; |
@ -0,0 +1,507 @@ |
||||
{ |
||||
"name": "MyToken", |
||||
"address": "0x0d52590858e2a79278e665c21f9caf98d8b4b618", |
||||
"network": "Sepolia (11155111) network", |
||||
"title": "Owner", |
||||
"details": "Set & change owner", |
||||
"abi": { |
||||
"0x095ea7b3": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "spender", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "value", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "approve", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bool", |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x095ea7b3", |
||||
"title": "this is title", |
||||
"details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." |
||||
}, |
||||
"0x42966c68": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "value", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "burn", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x42966c68", |
||||
"details": "Destroys a `value` amount of tokens from the caller. See {ERC20-_burn}." |
||||
}, |
||||
"0x79cc6790": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "account", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "value", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "burnFrom", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x79cc6790", |
||||
"details": "Destroys a `value` amount of tokens from `account`, deducting from the caller's allowance. See {ERC20-_burn} and {ERC20-allowance}. Requirements: - the caller must have allowance for ``accounts``'s tokens of at least `value`." |
||||
}, |
||||
"0x40c10f19": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "to", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "amount", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "mint", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x40c10f19", |
||||
"details": "" |
||||
}, |
||||
"0x8456cb59": { |
||||
"inputs": [], |
||||
"name": "pause", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x8456cb59", |
||||
"details": "" |
||||
}, |
||||
"0xd505accf": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "owner", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "address", |
||||
"name": "spender", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "value", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "deadline", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"internalType": "uint8", |
||||
"name": "v", |
||||
"type": "uint8" |
||||
}, |
||||
{ |
||||
"internalType": "bytes32", |
||||
"name": "r", |
||||
"type": "bytes32" |
||||
}, |
||||
{ |
||||
"internalType": "bytes32", |
||||
"name": "s", |
||||
"type": "bytes32" |
||||
} |
||||
], |
||||
"name": "permit", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0xd505accf", |
||||
"details": "Sets `value` as the allowance of `spender` over ``owner``'s tokens, given ``owner``'s signed approval. IMPORTANT: The same issues {IERC20-approve} has related to transaction ordering also apply here. Emits an {Approval} event. Requirements: - `spender` cannot be the zero address. - `deadline` must be a timestamp in the future. - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` over the EIP712-formatted function arguments. - the signature must use ``owner``'s current nonce (see {nonces}). For more information on the signature format, see the https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP section]. CAUTION: See Security Considerations above." |
||||
}, |
||||
"0x715018a6": { |
||||
"inputs": [], |
||||
"name": "renounceOwnership", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x715018a6", |
||||
"details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." |
||||
}, |
||||
"0xa9059cbb": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "to", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "value", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "transfer", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bool", |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0xa9059cbb", |
||||
"details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." |
||||
}, |
||||
"0x23b872dd": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "from", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "address", |
||||
"name": "to", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "value", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"name": "transferFrom", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bool", |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x23b872dd", |
||||
"details": "See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." |
||||
}, |
||||
"0xf2fde38b": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "newOwner", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "transferOwnership", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0xf2fde38b", |
||||
"details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." |
||||
}, |
||||
"0x3f4ba83a": { |
||||
"inputs": [], |
||||
"name": "unpause", |
||||
"outputs": [], |
||||
"stateMutability": "nonpayable", |
||||
"type": "function", |
||||
"id": "0x3f4ba83a", |
||||
"details": "" |
||||
}, |
||||
"0xdd62ed3e": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "owner", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "address", |
||||
"name": "spender", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "allowance", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0xdd62ed3e", |
||||
"details": "See {IERC20-allowance}." |
||||
}, |
||||
"0x70a08231": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "account", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "balanceOf", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x70a08231", |
||||
"details": "See {IERC20-balanceOf}." |
||||
}, |
||||
"0x313ce567": { |
||||
"inputs": [], |
||||
"name": "decimals", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint8", |
||||
"name": "", |
||||
"type": "uint8" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x313ce567", |
||||
"details": "Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the default value returned by this function, unless it's overridden. NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}." |
||||
}, |
||||
"0x3644e515": { |
||||
"inputs": [], |
||||
"name": "DOMAIN_SEPARATOR", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bytes32", |
||||
"name": "", |
||||
"type": "bytes32" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x3644e515", |
||||
"details": "Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}." |
||||
}, |
||||
"0x84b0196e": { |
||||
"inputs": [], |
||||
"name": "eip712Domain", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bytes1", |
||||
"name": "fields", |
||||
"type": "bytes1" |
||||
}, |
||||
{ |
||||
"internalType": "string", |
||||
"name": "name", |
||||
"type": "string" |
||||
}, |
||||
{ |
||||
"internalType": "string", |
||||
"name": "version", |
||||
"type": "string" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "chainId", |
||||
"type": "uint256" |
||||
}, |
||||
{ |
||||
"internalType": "address", |
||||
"name": "verifyingContract", |
||||
"type": "address" |
||||
}, |
||||
{ |
||||
"internalType": "bytes32", |
||||
"name": "salt", |
||||
"type": "bytes32" |
||||
}, |
||||
{ |
||||
"internalType": "uint256[]", |
||||
"name": "extensions", |
||||
"type": "uint256[]" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x84b0196e", |
||||
"details": "See {IERC-5267}." |
||||
}, |
||||
"0x06fdde03": { |
||||
"inputs": [], |
||||
"name": "name", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "string", |
||||
"name": "", |
||||
"type": "string" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x06fdde03", |
||||
"details": "Returns the name of the token." |
||||
}, |
||||
"0x7ecebe00": { |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "owner", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"name": "nonces", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x7ecebe00", |
||||
"details": "Returns the current nonce for `owner`. This value must be included whenever a signature is generated for {permit}. Every successful call to {permit} increases ``owner``'s nonce by one. This prevents a signature from being used multiple times." |
||||
}, |
||||
"0x8da5cb5b": { |
||||
"inputs": [], |
||||
"name": "owner", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x8da5cb5b", |
||||
"details": "Returns the address of the current owner." |
||||
}, |
||||
"0x5c975abb": { |
||||
"inputs": [], |
||||
"name": "paused", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bool", |
||||
"name": "", |
||||
"type": "bool" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x5c975abb", |
||||
"details": "Returns true if the contract is paused, and false otherwise." |
||||
}, |
||||
"0x95d89b41": { |
||||
"inputs": [], |
||||
"name": "symbol", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "string", |
||||
"name": "", |
||||
"type": "string" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x95d89b41", |
||||
"details": "Returns the symbol of the token, usually a shorter version of the name." |
||||
}, |
||||
"0x18160ddd": { |
||||
"inputs": [], |
||||
"name": "totalSupply", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function", |
||||
"id": "0x18160ddd", |
||||
"details": "See {IERC20-totalSupply}." |
||||
} |
||||
}, |
||||
"items": { |
||||
"A": [ |
||||
"0x79cc6790", |
||||
"0x715018a6", |
||||
"0xa9059cbb", |
||||
"0x23b872dd", |
||||
"0xf2fde38b", |
||||
"0x3f4ba83a", |
||||
"0xdd62ed3e" |
||||
], |
||||
"B": [ |
||||
"0x3644e515", |
||||
"0x84b0196e", |
||||
"0x06fdde03", |
||||
"0x7ecebe00", |
||||
"0x8da5cb5b", |
||||
"0x5c975abb", |
||||
"0x95d89b41", |
||||
"0x18160ddd" |
||||
], |
||||
"C": [ |
||||
"0x70a08231", |
||||
"0x8456cb59", |
||||
"0xd505accf", |
||||
"0x40c10f19", |
||||
"0x42966c68", |
||||
"0x095ea7b3", |
||||
"0x313ce567" |
||||
] |
||||
}, |
||||
"containers": [ |
||||
"A", |
||||
"B", |
||||
"C" |
||||
], |
||||
"shortname": "remix-dapp", |
||||
"shareTo": [ |
||||
"twitter", |
||||
"facebook" |
||||
], |
||||
"noTerminal": false, |
||||
"verified": true, |
||||
"solcVersion": { |
||||
"version": "0.8.26", |
||||
"canReceive": true |
||||
}, |
||||
"fallback": { |
||||
"stateMutability": "payable", |
||||
"type": "fallback" |
||||
}, |
||||
"receive": { |
||||
"stateMutability": "payable", |
||||
"type": "receive" |
||||
} |
||||
} |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,361 @@ |
||||
import React, { useEffect, useRef, useState } from 'react'; |
||||
import { FormattedMessage, useIntl } from 'react-intl'; |
||||
import * as remixLib from '@remix-project/remix-lib'; |
||||
import { CopyToClipboard } from '@remix-ui/clipboard' |
||||
import { CustomTooltip } from '@remix-ui/helper'; |
||||
|
||||
const { txFormat, txHelper } = remixLib.execution; |
||||
export function ContractGUI(props: any) { |
||||
const [title, setTitle] = useState<string>(''); |
||||
const [basicInput, setBasicInput] = useState<string>(''); |
||||
const [toggleContainer, setToggleContainer] = useState<boolean>(false); |
||||
const [buttonOptions, setButtonOptions] = useState<{ |
||||
title: string; |
||||
content: string; |
||||
classList: string; |
||||
dataId: string; |
||||
}>({ title: '', content: '', classList: '', dataId: '' }); |
||||
const multiFields = useRef<Array<HTMLInputElement | null>>([]); |
||||
const basicInputRef = useRef<any>(); |
||||
const intl = useIntl(); |
||||
|
||||
useEffect(() => { |
||||
if (props.funcABI.name) { |
||||
setTitle(props.funcABI.name); |
||||
} else { |
||||
setTitle(props.funcABI.type === 'receive' ? '(receive)' : '(fallback)'); |
||||
} |
||||
setBasicInput(''); |
||||
// we have the reset the fields before reseting the previous references.
|
||||
if (basicInputRef.current) basicInputRef.current.value = ''; |
||||
multiFields.current |
||||
.filter((el) => el !== null && el !== undefined) |
||||
.forEach((el: any) => (el.value = '')); |
||||
multiFields.current = []; |
||||
}, [props.funcABI]); |
||||
|
||||
useEffect(() => { |
||||
if (props.lookupOnly) { |
||||
// // call. stateMutability is either pure or view
|
||||
setButtonOptions({ |
||||
title: title + ' - call', |
||||
content: 'call', |
||||
classList: 'btn-info', |
||||
dataId: title + ' - call', |
||||
}); |
||||
} else if ( |
||||
props.funcABI.stateMutability === 'payable' || |
||||
props.funcABI.payable |
||||
) { |
||||
// // transact. stateMutability = payable
|
||||
setButtonOptions({ |
||||
title: title + ' - transact (payable)', |
||||
content: 'transact', |
||||
classList: 'btn-danger', |
||||
dataId: title + ' - transact (payable)', |
||||
}); |
||||
} else { |
||||
// // transact. stateMutability = nonpayable
|
||||
setButtonOptions({ |
||||
title: title + ' - transact (not payable)', |
||||
content: 'transact', |
||||
classList: 'btn-warning', |
||||
dataId: title + ' - transact (not payable)', |
||||
}); |
||||
} |
||||
}, [props.lookupOnly, props.funcABI, title]); |
||||
|
||||
const getEncodedCall = () => { |
||||
const multiString = getMultiValsString(multiFields.current); |
||||
// copy-to-clipboard icon is only visible for method requiring input params
|
||||
if (!multiString) { |
||||
return intl.formatMessage({ id: 'udapp.getEncodedCallError' }); |
||||
} |
||||
const multiJSON = JSON.parse('[' + multiString + ']'); |
||||
|
||||
const encodeObj = txFormat.encodeData(props.funcABI, multiJSON, null); |
||||
|
||||
if (encodeObj.error) { |
||||
console.error(encodeObj.error); |
||||
return encodeObj.error; |
||||
} else { |
||||
return encodeObj.data; |
||||
} |
||||
}; |
||||
|
||||
const getEncodedParams = () => { |
||||
try { |
||||
const multiString = getMultiValsString(multiFields.current); |
||||
// copy-to-clipboard icon is only visible for method requiring input params
|
||||
if (!multiString) { |
||||
return intl.formatMessage({ id: 'udapp.getEncodedCallError' }); |
||||
} |
||||
const multiJSON = JSON.parse('[' + multiString + ']'); |
||||
return txHelper.encodeParams(props.funcABI, multiJSON); |
||||
} catch (e) { |
||||
console.error(e); |
||||
} |
||||
}; |
||||
|
||||
const switchMethodViewOn = () => { |
||||
setToggleContainer(true); |
||||
makeMultiVal(); |
||||
}; |
||||
|
||||
const switchMethodViewOff = () => { |
||||
setToggleContainer(false); |
||||
const multiValString = getMultiValsString(multiFields.current); |
||||
|
||||
if (multiValString) setBasicInput(multiValString); |
||||
}; |
||||
|
||||
const getMultiValsString = (fields: (HTMLInputElement | null)[]) => { |
||||
const valArray = fields as HTMLInputElement[]; |
||||
let ret = ''; |
||||
const valArrayTest = []; |
||||
|
||||
for (let j = 0; j < valArray.length; j++) { |
||||
if (ret !== '') ret += ','; |
||||
let elVal = valArray[j] ? valArray[j].value : ''; |
||||
|
||||
valArrayTest.push(elVal); |
||||
elVal = elVal.replace(/(^|,\s+|,)(\d+)(\s+,|,|$)/g, '$1"$2"$3'); // replace non quoted number by quoted number
|
||||
elVal = elVal.replace( |
||||
/(^|,\s+|,)(0[xX][0-9a-fA-F]+)(\s+,|,|$)/g, |
||||
'$1"$2"$3' |
||||
); // replace non quoted hex string by quoted hex string
|
||||
if (elVal) { |
||||
try { |
||||
JSON.parse(elVal); |
||||
} catch (e) { |
||||
elVal = '"' + elVal + '"'; |
||||
} |
||||
} |
||||
ret += elVal; |
||||
} |
||||
const valStringTest = valArrayTest.join(''); |
||||
|
||||
if (valStringTest) { |
||||
return ret; |
||||
} else { |
||||
return ''; |
||||
} |
||||
}; |
||||
|
||||
const makeMultiVal = () => { |
||||
const inputString = basicInput; |
||||
|
||||
if (inputString) { |
||||
const inputJSON = txFormat.parseFunctionParams(inputString); |
||||
const multiInputs = multiFields.current as HTMLInputElement[]; |
||||
|
||||
for (let k = 0; k < multiInputs.length; k++) { |
||||
if (inputJSON[k]) { |
||||
multiInputs[k].value = JSON.stringify(inputJSON[k]); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
const handleActionClick = async () => { |
||||
props.clickCallBack(props.funcABI.inputs, basicInput); |
||||
}; |
||||
|
||||
const handleBasicInput = (e: { target: { value: any } }) => { |
||||
const value = e.target.value; |
||||
|
||||
setBasicInput(value); |
||||
}; |
||||
|
||||
const handleExpandMultiClick = () => { |
||||
const valsString = getMultiValsString(multiFields.current); |
||||
|
||||
if (valsString) { |
||||
props.clickCallBack(props.funcABI.inputs, valsString); |
||||
} else { |
||||
props.clickCallBack(props.funcABI.inputs, ''); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<div |
||||
className={`${ |
||||
props.funcABI.inputs && props.funcABI.inputs.length > 0 |
||||
? 'udapp_hasArgs' |
||||
: '' |
||||
}`}
|
||||
> |
||||
<div className={`${toggleContainer ? 'd-none' : 'd-flex'} w-100 pt-2`}> |
||||
<CustomTooltip |
||||
delay={0} |
||||
placement={'right'} |
||||
tooltipClasses="text-wrap" |
||||
tooltipId="remixUdappInstanceButtonTooltip" |
||||
tooltipText={ |
||||
props.inputs !== '' && basicInput === '' |
||||
? intl.formatMessage({ id: 'udapp.tooltipText12' }) |
||||
: buttonOptions.title |
||||
} |
||||
> |
||||
<div |
||||
className="d-flex p-0 wrapperElement" |
||||
onClick={handleActionClick} |
||||
data-id={buttonOptions.dataId} |
||||
data-title={buttonOptions.title} |
||||
> |
||||
<button |
||||
className={`text-nowrap overflow-hidden text-truncate ${props.widthClass} btn btn-sm ${buttonOptions.classList}`} |
||||
data-id={buttonOptions.dataId} |
||||
data-title={buttonOptions.title} |
||||
disabled={ |
||||
props.disabled || (props.inputs !== '' && basicInput === '') |
||||
} |
||||
style={{ |
||||
width: 100, |
||||
minWidth: 80, |
||||
borderRadius: 3, |
||||
}} |
||||
> |
||||
{title} |
||||
</button> |
||||
</div> |
||||
</CustomTooltip> |
||||
<input |
||||
className="form-control" |
||||
data-id={ |
||||
props.funcABI.type === 'fallback' || |
||||
props.funcABI.type === 'receive' |
||||
? `'(${props.funcABI.type}')` |
||||
: 'multiParamManagerBasicInputField' |
||||
} |
||||
placeholder={props.inputs} |
||||
onChange={handleBasicInput} |
||||
data-title={ |
||||
props.funcABI.type === 'fallback' || |
||||
props.funcABI.type === 'receive' |
||||
? `'(${props.funcABI.type}')` |
||||
: props.inputs |
||||
} |
||||
ref={basicInputRef} |
||||
style={{ |
||||
height: '2rem', |
||||
visibility: !( |
||||
(props.funcABI.inputs && props.funcABI.inputs.length > 0) || |
||||
props.funcABI.type === 'fallback' || |
||||
props.funcABI.type === 'receive' |
||||
) |
||||
? 'hidden' |
||||
: 'visible', |
||||
}} |
||||
/> |
||||
<i |
||||
className="fas fa-angle-down udapp_methCaret" |
||||
onClick={switchMethodViewOn} |
||||
style={{ |
||||
lineHeight: 2, |
||||
visibility: !( |
||||
props.funcABI.inputs && props.funcABI.inputs.length > 0 |
||||
) |
||||
? 'hidden' |
||||
: 'visible', |
||||
}} |
||||
></i> |
||||
</div> |
||||
<div className={`${toggleContainer ? 'd-flex' : 'd-none'} w-100`}> |
||||
<div className="w-100 text-dark"> |
||||
<div |
||||
onClick={switchMethodViewOff} |
||||
className="d-flex justify-content-between align-items-center pt-2" |
||||
> |
||||
<div className="run-instance-multi-title" style={{ fontSize: 12 }}> |
||||
{title} |
||||
</div> |
||||
<i className="fas fa-angle-up udapp_methCaret"></i> |
||||
</div> |
||||
<div> |
||||
{props.funcABI.inputs.map((inp: any, index: number) => { |
||||
return ( |
||||
<div |
||||
className="udapp_multiArg d-flex align-items-center justify-content-end mt-2" |
||||
key={index} |
||||
> |
||||
<label htmlFor={inp.name}> {inp.name}: </label> |
||||
<CustomTooltip |
||||
placement="left-end" |
||||
tooltipId="udappContractActionsTooltip" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={inp.name} |
||||
> |
||||
<input |
||||
ref={(el) => { |
||||
multiFields.current[index] = el; |
||||
}} |
||||
className="form-control" |
||||
placeholder={inp.type} |
||||
data-id={`multiParamManagerInput${inp.name}`} |
||||
onChange={handleBasicInput} |
||||
/> |
||||
</CustomTooltip> |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
<div className="d-flex udapp_group udapp_multiArg d-flex align-items-center justify-content-end mt-2"> |
||||
<CopyToClipboard |
||||
tip={intl.formatMessage({ id: 'udapp.copyCalldata' })} |
||||
icon="fa-clipboard" |
||||
direction={'bottom'} |
||||
getContent={getEncodedCall} |
||||
> |
||||
<button className="btn"> |
||||
<i |
||||
id="copyCalldata" |
||||
className="mr-2 far fa-copy" |
||||
aria-hidden="true" |
||||
></i> |
||||
<label htmlFor="copyCalldata">Calldata</label> |
||||
</button> |
||||
</CopyToClipboard> |
||||
<CopyToClipboard |
||||
tip={intl.formatMessage({ id: 'udapp.copyParameters' })} |
||||
icon="fa-clipboard" |
||||
direction={'bottom'} |
||||
getContent={getEncodedParams} |
||||
> |
||||
<button className="btn"> |
||||
<i |
||||
id="copyParameters" |
||||
className="mr-2 far fa-copy" |
||||
aria-hidden="true" |
||||
></i> |
||||
<label htmlFor="copyParameters"> |
||||
<FormattedMessage id="udapp.parameters" /> |
||||
</label> |
||||
</button> |
||||
</CopyToClipboard> |
||||
<CustomTooltip |
||||
placement={'right'} |
||||
tooltipClasses="text-nowrap" |
||||
tooltipId="remixUdappInstanceButtonTooltip" |
||||
tooltipText={buttonOptions.title} |
||||
> |
||||
<div onClick={handleExpandMultiClick}> |
||||
<button |
||||
type="button" |
||||
data-id={buttonOptions.dataId} |
||||
className={`btn ${buttonOptions.classList}`} |
||||
disabled={ |
||||
props.disabled || (props.inputs !== '' && basicInput === '') |
||||
} |
||||
style={{ width: 80 }} |
||||
> |
||||
{buttonOptions.content} |
||||
</button> |
||||
</div> |
||||
</CustomTooltip> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,74 @@ |
||||
import React, { useContext, useEffect } from 'react'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
const DappTop: React.FC = () => { |
||||
const { |
||||
appState: { instance }, |
||||
} = useContext(AppContext); |
||||
const { shareTo, title, details } = instance; |
||||
|
||||
const getBoxPositionOnWindowCenter = (width: number, height: number) => ({ |
||||
left: |
||||
window.outerWidth / 2 + |
||||
(window.screenX || window.screenLeft || 0) - |
||||
width / 2, |
||||
top: |
||||
window.outerHeight / 2 + |
||||
(window.screenY || window.screenTop || 0) - |
||||
height / 2, |
||||
}); |
||||
let windowConfig: any = { |
||||
width: 600, |
||||
height: 400, |
||||
}; |
||||
windowConfig = Object.assign( |
||||
windowConfig, |
||||
getBoxPositionOnWindowCenter(windowConfig.width, windowConfig.height) |
||||
); |
||||
|
||||
const shareUrl = encodeURIComponent(window.origin); |
||||
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> |
||||
{title && <h1 data-id="dappTitle">{title}</h1>} |
||||
{details && <span data-id="dappInstructions">{details}</span>} |
||||
</div> |
||||
{shareTo && ( |
||||
<div> |
||||
{shareTo.includes('twitter') && ( |
||||
<i |
||||
className="fab fa-twitter btn" |
||||
onClick={() => { |
||||
window.open( |
||||
`https://twitter.com/intent/tweet?url=${shareUrl}&text=${shareTitle}`, |
||||
'', |
||||
Object.keys(windowConfig) |
||||
.map((key) => `${key}=${windowConfig[key]}`) |
||||
.join(', ') |
||||
); |
||||
}} |
||||
/> |
||||
)} |
||||
{shareTo.includes('facebook') && ( |
||||
<i |
||||
className="fab fa-facebook btn" |
||||
onClick={() => { |
||||
window.open( |
||||
`https://www.facebook.com/sharer/sharer.php?u=${shareUrl}`, |
||||
'', |
||||
Object.keys(windowConfig) |
||||
.map((key) => `${key}=${windowConfig[key]}`) |
||||
.join(', ') |
||||
); |
||||
}} |
||||
/> |
||||
)} |
||||
</div> |
||||
)} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default DappTop; |
@ -0,0 +1,62 @@ |
||||
// eslint-disable-next-line no-use-before-define
|
||||
import React, { useContext, useEffect, useState } from 'react'; |
||||
import Draggable from 'react-draggable'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
const DragBar = () => { |
||||
const { appState, dispatch } = useContext(AppContext); |
||||
const { hidden, height } = appState.terminal; |
||||
const [dragState, setDragState] = useState<boolean>(false); |
||||
const [dragBarPosY, setDragBarPosY] = useState<number>(0); |
||||
const nodeRef = React.useRef(null); // fix for strictmode
|
||||
|
||||
function stopDrag(e: any, data: any) { |
||||
const h = window.innerHeight - data.y; |
||||
dispatch({ type: 'SET_TERMINAL', payload: { height: h, hidden: false } }); |
||||
setDragBarPosY(window.innerHeight - h - 5); |
||||
setDragState(false); |
||||
} |
||||
const handleResize = () => { |
||||
setDragBarPosY(window.innerHeight - height - 5); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
handleResize(); |
||||
}, [hidden]); |
||||
|
||||
useEffect(() => { |
||||
window.addEventListener('resize', handleResize); |
||||
// TODO: not a good way to wait on the ref doms element to be rendered of course
|
||||
setTimeout(() => { |
||||
handleResize(); |
||||
}, 2000); |
||||
return () => { |
||||
window.removeEventListener('resize', handleResize); |
||||
}; |
||||
}, []); |
||||
|
||||
function startDrag() { |
||||
setDragState(true); |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={`position-absolute ${dragState ? '' : 'd-none'}`}></div> |
||||
<Draggable |
||||
nodeRef={nodeRef} |
||||
position={{ x: 0, y: dragBarPosY }} |
||||
onStart={startDrag} |
||||
onStop={stopDrag} |
||||
axis="y" |
||||
> |
||||
<div |
||||
ref={nodeRef} |
||||
className={`position-absolute w-100 dragbar_terminal ${ |
||||
dragState ? 'ondrag' : '' |
||||
}`}
|
||||
></div> |
||||
</Draggable> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default DragBar; |
@ -0,0 +1,88 @@ |
||||
import React, { useContext, useEffect, useState } from 'react'; |
||||
import { UniversalDappUI } from '../../components/UniversalDappUI'; |
||||
import { SettingsUI } from '../../components/SettingsUI'; |
||||
import DappTop from '../../components/DappTop'; |
||||
import { AppContext } from '../../contexts'; |
||||
import TxList from '../../components/UiTerminal/TxList'; |
||||
|
||||
const MobilePage: React.FC = () => { |
||||
const { |
||||
appState: { instance }, |
||||
} = useContext(AppContext); |
||||
const [active, setActive] = useState('functions'); |
||||
|
||||
return <div> |
||||
<div |
||||
className={`${ |
||||
active === 'functions' ? '' : 'd-none' |
||||
} 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"> |
||||
<img src="/assets/logo.png" style={{ width: 55, height: 55 }} /> |
||||
</div> |
||||
<DappTop /> |
||||
</div> |
||||
<UniversalDappUI /> |
||||
</div> |
||||
{!instance.noTerminal && ( |
||||
<div className={`${active === 'transactions' ? '' : 'd-none'}`}> |
||||
<TxList /> |
||||
</div> |
||||
)} |
||||
<div |
||||
className={`${ |
||||
active === 'settings' ? '' : 'd-none' |
||||
} col-xl-3 col-lg-4 col-md-5 pl-0`}
|
||||
> |
||||
<SettingsUI /> |
||||
</div> |
||||
<ul |
||||
className="nav nav-pills justify-content-center fixed-bottom row bg-light" |
||||
style={{ zIndex: 'auto' }} |
||||
> |
||||
<li |
||||
className={`nav-item col text-center p-2`} |
||||
onClick={() => { |
||||
setActive('functions'); |
||||
}} |
||||
> |
||||
<span |
||||
className={`${active === 'functions' ? 'active' : ''} nav-link`} |
||||
> |
||||
Functions |
||||
</span> |
||||
</li> |
||||
{!instance.noTerminal && ( |
||||
<li |
||||
className={`nav-item col text-center p-2`} |
||||
onClick={() => { |
||||
setActive('transactions'); |
||||
}} |
||||
> |
||||
<span |
||||
className={`${ |
||||
active === 'transactions' ? 'active' : '' |
||||
} nav-link`}
|
||||
> |
||||
Transactions |
||||
</span> |
||||
</li> |
||||
)} |
||||
<li |
||||
className={`${ |
||||
active === 'settings' ? 'active' : '' |
||||
} nav-item col text-center p-2`}
|
||||
onClick={() => { |
||||
setActive('settings'); |
||||
}} |
||||
> |
||||
<span className={`${active === 'settings' ? 'active' : ''} nav-link`}> |
||||
Settings |
||||
</span> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
}; |
||||
|
||||
export default MobilePage; |
@ -0,0 +1,47 @@ |
||||
import React, { useContext, useEffect } from 'react'; |
||||
import { UniversalDappUI } from '../../components/UniversalDappUI'; |
||||
import { SettingsUI } from '../../components/SettingsUI'; |
||||
import RemixUiTerminal from '../../components/UiTerminal'; |
||||
import DragBar from '../../components/DragBar'; |
||||
import DappTop from '../../components/DappTop'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
const PCPage: React.FC = () => { |
||||
const { |
||||
appState: { terminal, instance }, |
||||
} = useContext(AppContext); |
||||
const { height } = terminal; |
||||
|
||||
return <div> |
||||
<div |
||||
className="row m-0 pt-3" |
||||
style={{ |
||||
height: instance.noTerminal |
||||
? window.innerHeight |
||||
: window.innerHeight - height - 5, |
||||
overflowY: 'auto', |
||||
}} |
||||
> |
||||
<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"> |
||||
<img src="/assets/logo.png" style={{ width: 95, height: 95 }} /> |
||||
</div> |
||||
<DappTop /> |
||||
</div> |
||||
<UniversalDappUI /> |
||||
</div> |
||||
<div className="col-xl-3 col-lg-4 col-md-5 d-inline-block pl-0"> |
||||
<SettingsUI /> |
||||
</div> |
||||
</div> |
||||
{!instance.noTerminal && ( |
||||
<> |
||||
<DragBar /> |
||||
<RemixUiTerminal /> |
||||
</> |
||||
)} |
||||
</div> |
||||
}; |
||||
|
||||
export default PCPage; |
@ -0,0 +1,58 @@ |
||||
import React, { useContext, useEffect } from 'react'; |
||||
import { FormattedMessage, useIntl } from 'react-intl'; |
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'; |
||||
import { CopyToClipboard } from '@remix-ui/clipboard' |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
export function AccountUI() { |
||||
const intl = useIntl(); |
||||
const { appState, dispatch } = useContext(AppContext); |
||||
const { selectedAccount, loadedAccounts, isRequesting, provider } = |
||||
appState.settings; |
||||
const accounts = Object.keys(loadedAccounts); |
||||
|
||||
const setAccount = (account: string) => { |
||||
dispatch({ type: 'SET_SETTINGS', payload: { selectedAccount: account } }); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
if (!selectedAccount && accounts.length > 0) setAccount(accounts[0]); |
||||
}, [accounts, selectedAccount]); |
||||
|
||||
return provider === 'metamask' ? ( |
||||
<div className="d-block mt-2"> |
||||
<label> |
||||
<FormattedMessage id="udapp.account" /> |
||||
{isRequesting && ( |
||||
<FontAwesomeIcon className="ml-2" icon={faSpinner} pulse /> |
||||
)} |
||||
</label> |
||||
<div className="d-flex align-items-center"> |
||||
<select |
||||
id="txorigin" |
||||
data-id="runTabSelectAccount" |
||||
name="txorigin" |
||||
className="form-control overflow-hidden w-100 font-weight-normal custom-select pr-4" |
||||
value={selectedAccount || ''} |
||||
onChange={(e) => { |
||||
setAccount(e.target.value); |
||||
}} |
||||
> |
||||
{accounts.map((value, index) => ( |
||||
<option value={value} key={index}> |
||||
{loadedAccounts[value]} |
||||
</option> |
||||
))} |
||||
</select> |
||||
<div style={{ marginLeft: -5 }}> |
||||
<CopyToClipboard |
||||
tip={intl.formatMessage({ id: 'udapp.copyAccount' })} |
||||
content={selectedAccount} |
||||
direction="top" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) : null; |
||||
} |
@ -0,0 +1,35 @@ |
||||
import React, { useContext } from 'react'; |
||||
import { CustomTooltip } from '@remix-ui/helper'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
export function GasPriceUI() { |
||||
const { appState, dispatch } = useContext(AppContext); |
||||
const handleGasLimit = (e: any) => { |
||||
dispatch({ type: 'settings/save', payload: { gasLimit: e.target.value } }); |
||||
}; |
||||
|
||||
const gasLimit = appState.settings.gasLimit; |
||||
|
||||
return ( |
||||
<div className="d-block mt-2"> |
||||
<label> |
||||
<FormattedMessage id="udapp.gasLimit" /> |
||||
</label> |
||||
<CustomTooltip |
||||
placement={'top'} |
||||
tooltipClasses="text-nowrap" |
||||
tooltipId="remixGasPriceTooltip" |
||||
tooltipText={<FormattedMessage id="udapp.tooltipText4" />} |
||||
> |
||||
<input |
||||
type="number" |
||||
className="form-control w-75" |
||||
id="gasLimit" |
||||
value={gasLimit} |
||||
onChange={handleGasLimit} |
||||
/> |
||||
</CustomTooltip> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,94 @@ |
||||
import React, { useContext } from 'react'; |
||||
import { NetworkUI } from './network'; |
||||
import { AccountUI } from './account'; |
||||
import { GasPriceUI } from './gasPrice'; |
||||
import { ValueUI } from './value'; |
||||
import { LocaleUI } from './locale'; |
||||
// import { ThemeUI } from './theme';
|
||||
import { FormattedMessage, useIntl } from 'react-intl'; |
||||
import { CopyToClipboard } from '@remix-ui/clipboard' |
||||
import { AppContext } from '../../contexts'; |
||||
import { CustomTooltip, shortenAddress } from '@remix-ui/helper'; |
||||
import { isMobile } from '../../utils/tools'; |
||||
import { LowLevelInteractions } from '../UniversalDappUI/lowLevelInteractions'; |
||||
|
||||
export function SettingsUI() { |
||||
const intl = useIntl(); |
||||
const { appState } = useContext(AppContext); |
||||
const { balance, name, address, verified } = appState.instance; |
||||
|
||||
return ( |
||||
<div className="px-4"> |
||||
<div className="bg-light mt-2 mb-4 p-3"> |
||||
<div className="d-flex justify-content-between align-items-center"> |
||||
<div className="bg-transparent m-0 p-0 border-0 alert alert-secondary"> |
||||
<div className="input-group-prepend"> |
||||
<span |
||||
className="input-group-text border-0 p-0 bg-transparent text-uppercase" |
||||
style={{ fontSize: 11 }} |
||||
> |
||||
{name} at {shortenAddress(address)} |
||||
</span> |
||||
<span className="btn p-0"> |
||||
<CopyToClipboard |
||||
tip={intl.formatMessage({ id: 'udapp.copy' })} |
||||
content={address} |
||||
direction={'top'} |
||||
/> |
||||
</span> |
||||
</div> |
||||
<div className="input-group-prepend"> |
||||
<div |
||||
className="input-group-text border-0 p-0 bg-transparent text-uppercase" |
||||
style={{ fontSize: 11 }} |
||||
> |
||||
<FormattedMessage id="udapp.balance" />: {balance} ETH |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<LocaleUI /> |
||||
</div> |
||||
</div> |
||||
<div className="bg-light mt-3 mb-4 p-3"> |
||||
<NetworkUI /> |
||||
<AccountUI /> |
||||
<GasPriceUI /> |
||||
<ValueUI /> |
||||
{/* <ThemeUI /> */} |
||||
</div> |
||||
<LowLevelInteractions /> |
||||
<div className="p-2 w-auto d-flex justify-content-between align-items-center"> |
||||
<span> |
||||
QuickDapp by{' '} |
||||
<a href={`https://remix.ethereum.org`} target="_blank"> |
||||
<CustomTooltip |
||||
placement="top" |
||||
tooltipId="remix" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText="Remix IDE" |
||||
> |
||||
<img |
||||
className="" |
||||
src="https://remix.ethereum.org/assets/img/remix-logo-blue.png" |
||||
style={{ height: '1.5rem' }} |
||||
alt="" |
||||
/> |
||||
</CustomTooltip> |
||||
</a> |
||||
</span> |
||||
{!isMobile() && verified && ( |
||||
<a |
||||
href={`https://remix.ethereum.org/address/${address}`} |
||||
className="btn btn-primary" |
||||
role="button" |
||||
aria-disabled="true" |
||||
target="_blank" |
||||
data-id="viewSourceCode" |
||||
> |
||||
<FormattedMessage id="udapp.viewSourceCode" /> |
||||
</a> |
||||
)} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,53 @@ |
||||
import { useContext, useEffect } from 'react'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
const localeMap: Record<string, string> = { |
||||
zh: 'Chinese Simplified - 简体中文', |
||||
en: 'English - English', |
||||
fr: 'French - Français', |
||||
it: 'Italian - Italiano', |
||||
es: 'Spanish - Español', |
||||
}; |
||||
|
||||
export function LocaleUI() { |
||||
const { appState, dispatch } = useContext(AppContext); |
||||
const { selectedLocaleCode } = appState.settings; |
||||
const localeCodeList = Object.keys(localeMap); |
||||
|
||||
useEffect(() => { |
||||
const defaultLocaleCode = |
||||
localStorage.getItem('selectedLocaleCode') || 'en'; |
||||
setLocaleCode(defaultLocaleCode); |
||||
}, []); |
||||
|
||||
const setLocaleCode = (localeCode: string) => { |
||||
dispatch({ |
||||
type: 'SET_SETTINGS', |
||||
payload: { selectedLocaleCode: localeCode }, |
||||
}); |
||||
localStorage.setItem('selectedLocaleCode', localeCode); |
||||
}; |
||||
|
||||
return ( |
||||
<div className="d-block"> |
||||
<div className="d-flex align-items-center"> |
||||
<select |
||||
id="txorigin" |
||||
data-id="localeSelectOptions" |
||||
name="txorigin" |
||||
className="form-control overflow-hidden w-100 font-weight-normal custom-select pr-4" |
||||
value={selectedLocaleCode || localeCodeList[0]} |
||||
onChange={(e) => { |
||||
setLocaleCode(e.target.value); |
||||
}} |
||||
> |
||||
{localeCodeList.map((localeCode) => ( |
||||
<option value={localeCode} key={localeCode} data-id={`localeOption${localeCode}`}> |
||||
{localeCode.toUpperCase()} |
||||
</option> |
||||
))} |
||||
</select> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,47 @@ |
||||
import React, { useContext } from 'react'; |
||||
import { AppContext } from '../../contexts'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import { setProvider } from '../../actions'; |
||||
|
||||
export function NetworkUI() { |
||||
const { appState, dispatch } = useContext(AppContext); |
||||
const { networkName, provider } = appState.settings; |
||||
return ( |
||||
<div className=""> |
||||
<label> |
||||
<FormattedMessage id="udapp.environment" /> |
||||
</label> |
||||
<div className="d-flex align-items-center"> |
||||
<select |
||||
id="txorigin" |
||||
data-id="runTabSelectAccount" |
||||
name="txorigin" |
||||
value={provider} |
||||
className="form-control overflow-hidden w-100 font-weight-normal custom-select pr-4" |
||||
onChange={(e) => { |
||||
dispatch({ |
||||
type: 'SET_SETTINGS', |
||||
payload: { provider: e.target.value }, |
||||
}); |
||||
setProvider({ provider: e.target.value, networkName }); |
||||
}} |
||||
> |
||||
<option value={'metamask'} key={'metamask'}> |
||||
MetaMask |
||||
</option> |
||||
<option value={'walletconnect'} key={'walletconnect'}> |
||||
WalletConnect |
||||
</option> |
||||
</select> |
||||
</div> |
||||
<div className="position-relative w-100" data-id="settingsNetworkEnv"> |
||||
<span className="badge badge-secondary">{networkName}</span> |
||||
</div> |
||||
{provider === 'walletconnect' && ( |
||||
<div className="mt-2"> |
||||
<w3m-button /> |
||||
</div> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,88 @@ |
||||
import { useEffect, useState } from 'react'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
const themeMap: Record<string, any> = { |
||||
Dark: { quality: 'dark', url: 'assets/css/themes/remix-dark_tvx1s2.css' }, |
||||
Light: { quality: 'light', url: 'assets/css/themes/remix-light_powaqg.css' }, |
||||
Violet: { quality: 'light', url: 'assets/css/themes/remix-violet.css' }, |
||||
Unicorn: { quality: 'light', url: 'assets/css/themes/remix-unicorn.css' }, |
||||
Midcentury: { |
||||
quality: 'light', |
||||
url: 'assets/css/themes/remix-midcentury_hrzph3.css', |
||||
}, |
||||
Black: { quality: 'dark', url: 'assets/css/themes/remix-black_undtds.css' }, |
||||
Candy: { quality: 'light', url: 'assets/css/themes/remix-candy_ikhg4m.css' }, |
||||
HackerOwl: { quality: 'dark', url: 'assets/css/themes/remix-hacker_owl.css' }, |
||||
Cerulean: { |
||||
quality: 'light', |
||||
url: 'assets/css/themes/bootstrap-cerulean.min.css', |
||||
}, |
||||
Flatly: { |
||||
quality: 'light', |
||||
url: 'assets/css/themes/bootstrap-flatly.min.css', |
||||
}, |
||||
Spacelab: { |
||||
quality: 'light', |
||||
url: 'assets/css/themes/bootstrap-spacelab.min.css', |
||||
}, |
||||
Cyborg: { |
||||
quality: 'dark', |
||||
url: 'assets/css/themes/bootstrap-cyborg.min.css', |
||||
}, |
||||
}; |
||||
|
||||
export function ThemeUI() { |
||||
const [theme, setTheme] = useState( |
||||
localStorage.getItem('selectedTheme') || 'Dark' |
||||
); |
||||
const themeList = Object.keys(themeMap); |
||||
|
||||
useEffect(() => { |
||||
selectTheme(theme); |
||||
}, []); |
||||
|
||||
const selectTheme = (selectedTheme: string) => { |
||||
localStorage.setItem('selectedTheme', selectedTheme); |
||||
setTheme(selectedTheme); |
||||
|
||||
const themeLinkEle = document.getElementById('theme-link'); |
||||
if (themeLinkEle) { |
||||
themeLinkEle.remove(); |
||||
} |
||||
const nextTheme = themeMap[selectedTheme]; // Theme
|
||||
document.documentElement.style.setProperty('--theme', nextTheme.quality); |
||||
|
||||
const theme = document.createElement('link'); |
||||
theme.setAttribute('rel', 'stylesheet'); |
||||
theme.setAttribute('href', 'https://remix.ethereum.org/' + nextTheme.url); |
||||
theme.setAttribute('id', 'theme-link'); |
||||
|
||||
document.head.insertBefore(theme, document.head.firstChild); |
||||
}; |
||||
|
||||
return ( |
||||
<div className="d-block mt-2"> |
||||
<label> |
||||
<FormattedMessage id="udapp.themes" /> |
||||
</label> |
||||
<div className="d-flex align-items-center"> |
||||
<select |
||||
id="txorigin" |
||||
data-id="runTabSelectAccount" |
||||
name="txorigin" |
||||
className="form-control overflow-hidden w-100 font-weight-normal custom-select pr-4" |
||||
value={theme} |
||||
onChange={(e) => { |
||||
selectTheme(e.target.value); |
||||
}} |
||||
> |
||||
{themeList.map((item) => ( |
||||
<option value={item} key={item}> |
||||
{item} - {themeMap[item].quality} |
||||
</option> |
||||
))} |
||||
</select> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,108 @@ |
||||
import React, { useContext, useEffect, useRef } from 'react'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import { BN } from 'bn.js'; |
||||
import { CustomTooltip } from '@remix-ui/helper'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
export function ValueUI() { |
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const inputValue = useRef<HTMLInputElement>({} as HTMLInputElement); |
||||
|
||||
const { appState, dispatch } = useContext(AppContext); |
||||
const { sendValue, sendUnit } = appState.settings; |
||||
|
||||
useEffect(() => { |
||||
if (sendValue !== inputValue.current.value) { |
||||
inputValue.current.value = sendValue; |
||||
} |
||||
}, [sendValue]); |
||||
|
||||
const validateValue = (e: { target: { value: any } }) => { |
||||
const value = e.target.value; |
||||
|
||||
if (!value) { |
||||
// assign 0 if given value is
|
||||
// - empty
|
||||
inputValue.current.value = '0'; |
||||
dispatch({ type: 'SET_SETTINGS', payload: { sendValue: '0' } }); |
||||
return; |
||||
} |
||||
|
||||
let v; |
||||
try { |
||||
v = new BN(value, 10); |
||||
dispatch({ |
||||
type: 'SET_SETTINGS', |
||||
payload: { sendValue: v.toString(10) }, |
||||
}); |
||||
} catch (e) { |
||||
// assign 0 if given value is
|
||||
// - not valid (for ex 4345-54)
|
||||
// - contains only '0's (for ex 0000) copy past or edit
|
||||
inputValue.current.value = '0'; |
||||
dispatch({ type: 'SET_SETTINGS', payload: { sendValue: '0' } }); |
||||
} |
||||
|
||||
if (v?.lt(0)) { |
||||
inputValue.current.value = '0'; |
||||
dispatch({ type: 'SET_SETTINGS', payload: { sendValue: '0' } }); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<div className="d-block mt-2"> |
||||
<label data-id="remixDRValueLabel"> |
||||
<FormattedMessage id="udapp.value" /> |
||||
</label> |
||||
<div className="d-flex flex-row"> |
||||
<CustomTooltip |
||||
placement={'top-start'} |
||||
tooltipClasses="text-nowrap" |
||||
tooltipId="remixValueTooltip" |
||||
tooltipText={<FormattedMessage id="udapp.tooltipText5" />} |
||||
> |
||||
<input |
||||
ref={inputValue} |
||||
type="number" |
||||
min="0" |
||||
pattern="^[0-9]" |
||||
step="1" |
||||
className="form-control w-75" |
||||
id="value" |
||||
data-id="dandrValue" |
||||
onChange={validateValue} |
||||
value={sendValue} |
||||
/> |
||||
</CustomTooltip> |
||||
|
||||
<select |
||||
name="unit" |
||||
value={sendUnit} |
||||
className="form-control p-1 w-25 ml-2 udapp_col2_2 custom-select" |
||||
id="unit" |
||||
onChange={(e: any) => { |
||||
dispatch({ |
||||
type: 'SET_SETTINGS', |
||||
payload: { |
||||
sendUnit: e.target.value, |
||||
}, |
||||
}); |
||||
}} |
||||
> |
||||
<option data-unit="wei" value="wei"> |
||||
Wei |
||||
</option> |
||||
<option data-unit="gwei" value="gwei"> |
||||
Gwei |
||||
</option> |
||||
<option data-unit="finney" value="finney"> |
||||
Finney |
||||
</option> |
||||
<option data-unit="ether" value="ether"> |
||||
Ether |
||||
</option> |
||||
</select> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,29 @@ |
||||
import React from 'react'; |
||||
|
||||
const CheckTxStatus = ({ tx, type }: any) => { |
||||
if (tx.status === 1 || tx.status === '0x1' || tx.status === true) { |
||||
return ( |
||||
<i className="remix_ui_terminal_txStatus d-flex mr-3 remix_ui_terminal_succeeded fas fa-check-circle"></i> |
||||
); |
||||
} |
||||
if (type === 'call' || type === 'unknownCall' || type === 'unknown') { |
||||
return ( |
||||
<i className="remix_ui_terminal_txStatus d-flex mr-3 justify-content-center align-items-center font-weight-bold text-uppercase rounded-circle remix_ui_terminal_call"> |
||||
call |
||||
</i> |
||||
); |
||||
} else if (tx.status === 0 || tx.status === '0x0' || tx.status === false) { |
||||
return ( |
||||
<i className="remix_ui_terminal_txStatus d-flex mr-3 remix_ui_terminal_failed fas fa-times-circle"></i> |
||||
); |
||||
} else { |
||||
return ( |
||||
<i |
||||
className="remix_ui_terminal_txStatus d-flex mr-3 fas fa-circle-thin" |
||||
title="Status not available" |
||||
></i> |
||||
); |
||||
} |
||||
}; |
||||
|
||||
export default CheckTxStatus; |
@ -0,0 +1,83 @@ |
||||
import React from 'react'; |
||||
import { shortenHexData } from '@remix-ui/helper'; |
||||
import { execution } from '@remix-project/remix-lib'; |
||||
const typeConversion = execution.typeConversion; |
||||
|
||||
const Context = ({ opts, provider }: { opts: any; provider: string }) => { |
||||
const data = opts.tx || ''; |
||||
const from = opts.from ? shortenHexData(opts.from) : ''; |
||||
let to = opts.to; |
||||
if (data.to) to = to + ' ' + shortenHexData(data.to); |
||||
const val = data.value; |
||||
let hash = data.hash ? shortenHexData(data.hash) : ''; |
||||
const input = data.input ? shortenHexData(data.input) : ''; |
||||
const logs = opts.logs?.decoded?.length ? opts.logs.decoded.length : 0; |
||||
const block = data.receipt |
||||
? data.receipt.blockNumber |
||||
: data.blockNumber || ''; |
||||
const i = data.receipt |
||||
? data.receipt.transactionIndex |
||||
: data.transactionIndex; |
||||
const value = val ? typeConversion.toInt(val) : 0; |
||||
|
||||
if (data.resolvedData) { |
||||
return ( |
||||
<div> |
||||
<span> |
||||
<span className="remix_ui_terminal_tx font-weight-bold mr-3"> |
||||
[block:{block.toString()} txIndex:{i ? i.toString() : '-'}] |
||||
</span> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">from:</span> {from} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">to:</span> {to} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">value:</span> {value} wei |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">data:</span> {input} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">logs:</span> {logs} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">hash:</span> {hash} |
||||
</div> |
||||
</span> |
||||
</div> |
||||
); |
||||
} else { |
||||
hash = shortenHexData(data.blockHash); |
||||
return ( |
||||
<div> |
||||
<span> |
||||
<span className="remix_ui_terminal_tx font-weight-bold mr-3"> |
||||
[block:{block.toString()} txIndex:{i ? i.toString() : '-'}] |
||||
</span> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">from:</span> {from} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">to:</span> {to} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">value:</span> {value} wei |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">data:</span> {input} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">logs:</span> {logs} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">hash:</span> {hash} |
||||
</div> |
||||
</span> |
||||
</div> |
||||
); |
||||
} |
||||
}; |
||||
|
||||
export default Context; |
@ -0,0 +1,84 @@ |
||||
import React from 'react'; |
||||
import { shortenHexData } from '@remix-ui/helper'; |
||||
import CheckTxStatus from './ChechTxStatus'; |
||||
import showTable from './Table'; |
||||
import { execution } from '@remix-project/remix-lib'; |
||||
|
||||
const typeConversion = execution.typeConversion; |
||||
|
||||
const RenderCall = ({ |
||||
tx, |
||||
resolvedData, |
||||
logs, |
||||
index, |
||||
showTableHash, |
||||
txDetails, |
||||
}: any) => { |
||||
const to = resolvedData.contractName + '.' + resolvedData.fn; |
||||
const from = tx.from ? tx.from : ' - '; |
||||
const input = tx.input ? shortenHexData(tx.input) : ''; |
||||
const txType = 'call'; |
||||
|
||||
return ( |
||||
<span id={`tx${tx.hash}`} key={index}> |
||||
<div |
||||
className="remix_ui_terminal_log d-flex align-items-center pt-2" |
||||
onClick={(event) => txDetails(event, tx)} |
||||
> |
||||
<CheckTxStatus tx={tx} type={txType} /> |
||||
<span> |
||||
<span className="remix_ui_terminal_tx font-weight-bold">[call]</span> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">from:</span> {from} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">to:</span> {to} |
||||
</div> |
||||
<div className="remix_ui_terminal_txItem"> |
||||
<span className="font-weight-bold">data:</span> {input} |
||||
</div> |
||||
</span> |
||||
<i |
||||
className={`remix_ui_terminal_arrow d-flex ml-2 fas ${ |
||||
showTableHash.includes(tx.hash) ? 'fa-angle-up' : 'fa-angle-down' |
||||
}`}
|
||||
></i> |
||||
</div> |
||||
{showTableHash.includes(tx.hash) |
||||
? showTable( |
||||
{ |
||||
hash: tx.hash, |
||||
isCall: tx.isCall, |
||||
contractAddress: tx.contractAddress, |
||||
data: tx, |
||||
from, |
||||
to, |
||||
gas: tx.gas, |
||||
input: tx.input, |
||||
'decoded input': resolvedData?.params |
||||
? JSON.stringify( |
||||
typeConversion.stringify(resolvedData.params), |
||||
null, |
||||
'\t' |
||||
) |
||||
: ' - ', |
||||
'decoded output': resolvedData?.decodedReturnValue |
||||
? JSON.stringify( |
||||
typeConversion.stringify(resolvedData.decodedReturnValue), |
||||
null, |
||||
'\t' |
||||
) |
||||
: ' - ', |
||||
val: tx.value, |
||||
logs, |
||||
transactionCost: tx.transactionCost, |
||||
executionCost: tx.executionCost, |
||||
}, |
||||
showTableHash |
||||
) |
||||
: null} |
||||
</span> |
||||
); |
||||
}; |
||||
|
||||
export default RenderCall; |
@ -0,0 +1,76 @@ |
||||
import React from 'react'; |
||||
import CheckTxStatus from './ChechTxStatus'; |
||||
import Context from './Context'; |
||||
import showTable from './Table'; |
||||
import { execution } from '@remix-project/remix-lib'; |
||||
const typeConversion = execution.typeConversion; |
||||
|
||||
const RenderKnownTransactions = ({ |
||||
tx, |
||||
receipt, |
||||
resolvedData, |
||||
logs, |
||||
index, |
||||
showTableHash, |
||||
txDetails, |
||||
provider, |
||||
}: any) => { |
||||
const from = tx.from; |
||||
const to = resolvedData.contractName + '.' + resolvedData.fn; |
||||
const txType = 'knownTx'; |
||||
const options = { from, to, tx, logs }; |
||||
return ( |
||||
<span id={`tx${tx.hash}`} key={index}> |
||||
<div |
||||
className="remix_ui_terminal_log d-flex align-items-center pt-2" |
||||
onClick={(event) => txDetails(event, tx)} |
||||
> |
||||
<CheckTxStatus tx={receipt} type={txType} /> |
||||
<Context opts={options} provider={provider} /> |
||||
<i |
||||
className={`remix_ui_terminal_arrow d-flex ml-2 fas ${ |
||||
showTableHash.includes(tx.hash) ? 'fa-angle-up' : 'fa-angle-down' |
||||
}`}
|
||||
></i> |
||||
</div> |
||||
{showTableHash.includes(tx.hash) |
||||
? showTable( |
||||
{ |
||||
hash: tx.hash, |
||||
status: receipt !== null ? receipt.status : null, |
||||
isCall: tx.isCall, |
||||
contractAddress: receipt.contractAddress, |
||||
blockHash: tx.blockHash, |
||||
blockNumber: tx.blockNumber, |
||||
data: tx, |
||||
from, |
||||
to, |
||||
gas: tx.gas, |
||||
input: tx.input, |
||||
'decoded input': resolvedData?.params |
||||
? JSON.stringify( |
||||
typeConversion.stringify(resolvedData.params), |
||||
null, |
||||
'\t' |
||||
) |
||||
: ' - ', |
||||
'decoded output': resolvedData?.decodedReturnValue |
||||
? JSON.stringify( |
||||
typeConversion.stringify(resolvedData.decodedReturnValue), |
||||
null, |
||||
'\t' |
||||
) |
||||
: ' - ', |
||||
logs, |
||||
val: tx.value, |
||||
transactionCost: tx.transactionCost, |
||||
executionCost: tx.executionCost, |
||||
}, |
||||
showTableHash |
||||
) |
||||
: null} |
||||
</span> |
||||
); |
||||
}; |
||||
|
||||
export default RenderKnownTransactions; |
@ -0,0 +1,327 @@ |
||||
import React from 'react'; |
||||
import { FormattedMessage, useIntl } from 'react-intl'; |
||||
import { CopyToClipboard } from '@remix-ui/clipboard' |
||||
import { shortenHexData } from '@remix-ui/helper'; |
||||
import { execution } from '@remix-project/remix-lib'; |
||||
const typeConversion = execution.typeConversion; |
||||
|
||||
const showTable = (opts: any, showTableHash: any) => { |
||||
const intl = useIntl(); |
||||
let msg = ''; |
||||
let toHash; |
||||
const data = opts.data; |
||||
if (data.to && opts.to !== data.to) { |
||||
toHash = opts.to + ' ' + data.to; |
||||
} else { |
||||
toHash = opts.to; |
||||
} |
||||
let callWarning = ''; |
||||
if (opts.isCall) { |
||||
callWarning = intl.formatMessage({ id: 'terminal.callWarning' }); |
||||
} |
||||
if (!opts.isCall) { |
||||
if (opts.status !== undefined && opts.status !== null) { |
||||
if (opts.status === 0 || opts.status === '0x0' || opts.status === false) { |
||||
msg = intl.formatMessage({ id: 'terminal.msg1' }); |
||||
} else if ( |
||||
opts.status === 1 || |
||||
opts.status === '0x1' || |
||||
opts.status === true |
||||
) { |
||||
msg = intl.formatMessage({ id: 'terminal.msg2' }); |
||||
} |
||||
} else { |
||||
msg = intl.formatMessage({ id: 'terminal.msg3' }); |
||||
} |
||||
} |
||||
|
||||
let stringified = ' - '; |
||||
if (opts.logs?.decoded) { |
||||
stringified = typeConversion.stringify(opts.logs.decoded); |
||||
} |
||||
const val = opts.val != null ? typeConversion.toInt(opts.val) : 0; |
||||
const gasInt = opts.gas != null ? typeConversion.toInt(opts.gas) : 0; |
||||
return ( |
||||
<table |
||||
className={`mt-1 mb-2 mr-4 align-self-center ${ |
||||
showTableHash.includes(opts.hash) ? 'active' : '' |
||||
}`}
|
||||
id="txTable" |
||||
data-id={`txLoggerTable${opts.hash}`} |
||||
> |
||||
<tbody> |
||||
{opts.status !== undefined ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.status" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableStatus${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
>{`${opts.status} ${msg}`}</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.hash && !opts.isCall ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.transactionHash" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableHash${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts.hash} |
||||
<CopyToClipboard content={opts.hash} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.blockHash ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.blockHash" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableContractAddress${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts.blockHash} |
||||
<CopyToClipboard content={opts.blockHash} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.blockNumber ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.blockNumber" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableContractAddress${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts.blockNumber.toString()} |
||||
<CopyToClipboard content={opts.blockNumber.toString()} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.contractAddress ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.contractAddress" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableContractAddress${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts.contractAddress} |
||||
<CopyToClipboard content={opts.contractAddress} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.from ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
from |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableFrom${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts.from} |
||||
<CopyToClipboard content={opts.from} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.to ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
to |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableTo${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{toHash} |
||||
<CopyToClipboard content={data.to ? data.to : toHash} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.gas ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
gas |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableGas${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{gasInt} gas |
||||
<CopyToClipboard content={opts.gas} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.transactionCost ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.transactionCost" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableTransactionCost${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts.transactionCost} gas {callWarning} |
||||
<CopyToClipboard content={opts.transactionCost} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.executionCost ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.executionCost" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableExecutionHash${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts.executionCost} gas {callWarning} |
||||
<CopyToClipboard content={opts.executionCost} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.input ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.input" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableHash${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{shortenHexData(opts.input)} |
||||
<CopyToClipboard content={opts.input} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts['decoded input'] ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.decodedInput" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableHash${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts['decoded input'].trim()} |
||||
<CopyToClipboard content={opts['decoded input']} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts['decoded output'] ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.decodedOutput" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableHash${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{opts['decoded output']} |
||||
<CopyToClipboard content={opts['decoded output']} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.logs ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
<FormattedMessage id="terminal.logs" /> |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableHash${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{JSON.stringify(stringified, null, '\t')} |
||||
<CopyToClipboard |
||||
content={JSON.stringify(stringified, null, '\t')} |
||||
/> |
||||
<CopyToClipboard content={JSON.stringify(opts.logs.raw || '0')} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
{opts.val ? ( |
||||
<tr className="remix_ui_terminal_tr"> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-shared={`key_${opts.hash}`} |
||||
> |
||||
value |
||||
</td> |
||||
<td |
||||
className="remix_ui_terminal_td" |
||||
data-id={`txLoggerTableHash${opts.hash}`} |
||||
data-shared={`pair_${opts.hash}`} |
||||
> |
||||
{val} wei |
||||
<CopyToClipboard content={`${val} wei`} /> |
||||
</td> |
||||
</tr> |
||||
) : null} |
||||
</tbody> |
||||
</table> |
||||
); |
||||
}; |
||||
export default showTable; |
@ -0,0 +1,209 @@ |
||||
import React, { |
||||
useState, |
||||
useEffect, |
||||
useRef, |
||||
type SyntheticEvent, |
||||
useContext, |
||||
} from 'react'; |
||||
import RenderCall from './RenderCall'; |
||||
import RenderKnownTransactions from './RenderKnownTransactions'; |
||||
import parse from 'html-react-parser'; |
||||
|
||||
import { KNOWN_TRANSACTION } from './types'; |
||||
|
||||
import './index.css'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { |
||||
clipboardData: DataTransfer; |
||||
} |
||||
|
||||
export const TxList = (props: any) => { |
||||
const { appState } = useContext(AppContext); |
||||
const { journalBlocks } = appState.terminal; |
||||
|
||||
const [showTableHash, setShowTableHash] = useState<any[]>([]); |
||||
|
||||
const messagesEndRef = useRef<any>(null); |
||||
const typeWriterIndexes = useRef<any>([]); |
||||
|
||||
const scrollToBottom = () => { |
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
scrollToBottom(); |
||||
}, [journalBlocks.length]); |
||||
|
||||
const txDetails = (event: any, tx: any) => { |
||||
if (showTableHash.includes(tx.hash)) { |
||||
const index = showTableHash.indexOf(tx.hash); |
||||
if (index > -1) { |
||||
setShowTableHash((prevState) => prevState.filter((x) => x !== tx.hash)); |
||||
} |
||||
} else { |
||||
setShowTableHash((prevState) => [...prevState, tx.hash]); |
||||
} |
||||
scrollToBottom(); |
||||
}; |
||||
|
||||
const classNameBlock = 'remix_ui_terminal_block px-4 py-1 text-break'; |
||||
|
||||
return ( |
||||
<div |
||||
id="journal" |
||||
className="remix_ui_terminal_journal d-flex flex-column pt-3 pb-4 px-2 ml-2 mr-0 mt-auto" |
||||
data-id="terminalJournal" |
||||
> |
||||
{journalBlocks?.map((x: any, index: number) => { |
||||
if (x.name === KNOWN_TRANSACTION) { |
||||
return x.message.map((trans: any) => { |
||||
return ( |
||||
<div |
||||
className={classNameBlock} |
||||
data-id={`block_tx${trans.tx.hash}`} |
||||
key={index} |
||||
> |
||||
{trans.tx.isCall ? ( |
||||
<RenderCall |
||||
tx={trans.tx} |
||||
resolvedData={trans.resolvedData} |
||||
logs={trans.logs} |
||||
index={index} |
||||
showTableHash={showTableHash} |
||||
txDetails={txDetails} |
||||
/> |
||||
) : ( |
||||
<RenderKnownTransactions |
||||
tx={trans.tx} |
||||
receipt={trans.receipt} |
||||
resolvedData={trans.resolvedData} |
||||
logs={trans.logs} |
||||
index={index} |
||||
showTableHash={showTableHash} |
||||
txDetails={txDetails} |
||||
provider={x.provider} |
||||
/> |
||||
)} |
||||
</div> |
||||
); |
||||
}); |
||||
} else if (Array.isArray(x.message)) { |
||||
return x.message.map((msg: any, i: number) => { |
||||
if (React.isValidElement(msg)) { |
||||
return ( |
||||
<div className="px-4 block" data-id="block" key={i}> |
||||
<span className={x.style}>{msg}</span> |
||||
</div> |
||||
); |
||||
} else if (typeof msg === 'object') { |
||||
if (msg.value && isHtml(msg.value)) { |
||||
return ( |
||||
<div className={classNameBlock} data-id="block" key={i}> |
||||
<span className={x.style}>{parse(msg.value)} </span> |
||||
</div> |
||||
); |
||||
} |
||||
let stringified; |
||||
try { |
||||
stringified = JSON.stringify(msg); |
||||
} catch (e) { |
||||
console.error(e); |
||||
stringified = '< value not displayable >'; |
||||
} |
||||
return ( |
||||
<div className={classNameBlock} data-id="block" key={i}> |
||||
<span className={x.style}>{stringified} </span> |
||||
</div> |
||||
); |
||||
} else { |
||||
// typeWriterIndexes: we don't want to rerender using typewriter when the react component updates
|
||||
if (x.typewriter && !typeWriterIndexes.current.includes(index)) { |
||||
typeWriterIndexes.current.push(index); |
||||
return ( |
||||
<div className={classNameBlock} data-id="block" key={index}> |
||||
<span |
||||
ref={(element) => { |
||||
typewrite(element, msg ? msg.toString() : null, () => { |
||||
scrollToBottom(); |
||||
}); |
||||
}} |
||||
className={x.style} |
||||
></span> |
||||
</div> |
||||
); |
||||
} else { |
||||
return ( |
||||
<div className={classNameBlock} data-id="block" key={i}> |
||||
<span className={x.style}> |
||||
{msg ? msg.toString() : null} |
||||
</span> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
}); |
||||
} else { |
||||
// typeWriterIndexes: we don't want to rerender using typewriter when the react component updates
|
||||
if (x.typewriter && !typeWriterIndexes.current.includes(index)) { |
||||
typeWriterIndexes.current.push(index); |
||||
return ( |
||||
<div className={classNameBlock} data-id="block" key={index}> |
||||
{' '} |
||||
<span |
||||
ref={(element) => { |
||||
typewrite(element, x.message, () => { |
||||
scrollToBottom(); |
||||
}); |
||||
}} |
||||
className={x.style} |
||||
></span> |
||||
</div> |
||||
); |
||||
} else { |
||||
if (typeof x.message !== 'function') { |
||||
return ( |
||||
<div className={classNameBlock} data-id="block" key={index}> |
||||
{' '} |
||||
<span className={x.style}> {x.message}</span> |
||||
</div> |
||||
); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
})} |
||||
<div ref={messagesEndRef} /> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const typewrite = (elementsRef: any, message: any, callback: any) => { |
||||
(() => { |
||||
let count = 0; |
||||
const id = setInterval(() => { |
||||
if (!elementsRef) return; |
||||
count++; |
||||
elementsRef.innerText = message.substr(0, count); |
||||
// scroll when new line ` <br>
|
||||
if (elementsRef.lastChild.tagName === `BR`) callback(); |
||||
if (message.length === count) { |
||||
clearInterval(id); |
||||
callback(); |
||||
} |
||||
}, 5); |
||||
})(); |
||||
}; |
||||
|
||||
function isHtml(value: any) { |
||||
if (!value.indexOf) return false; |
||||
return ( |
||||
value.indexOf('<div') !== -1 || |
||||
value.indexOf('<span') !== -1 || |
||||
value.indexOf('<p') !== -1 || |
||||
value.indexOf('<label') !== -1 || |
||||
value.indexOf('<b') !== -1 |
||||
); |
||||
} |
||||
|
||||
export default TxList; |
@ -0,0 +1,92 @@ |
||||
.remix_ui_terminal_toggleTerminal { |
||||
cursor: pointer; |
||||
} |
||||
.remix_ui_terminal_toggleTerminal:hover { |
||||
color: var(--secondary); |
||||
} |
||||
.remix_ui_terminal_container { |
||||
overflow-y: auto; |
||||
font-family: monospace; |
||||
background-repeat: no-repeat; |
||||
background-position: center 15%; |
||||
background-size: auto calc(75% - 1.7em); |
||||
} |
||||
.remix_ui_terminal_journal { |
||||
font-family: monospace; |
||||
overflow-y: scroll; |
||||
} |
||||
.remix_ui_terminal_block { |
||||
white-space: pre-wrap; |
||||
font-family: monospace; |
||||
line-height: 2ch; |
||||
} |
||||
.remix_ui_terminal_block > pre { |
||||
max-height: 200px; |
||||
} |
||||
.remix_ui_terminal_txItem { |
||||
color: var(--text-info); |
||||
margin-right: 5px; |
||||
float: left; |
||||
} |
||||
.remix_ui_terminal_log { |
||||
cursor: pointer; |
||||
} |
||||
.remix_ui_terminal_log:hover { |
||||
opacity: 0.8; |
||||
} |
||||
.remix_ui_terminal_txStatus { |
||||
font-size: 20px; |
||||
float: left; |
||||
} |
||||
.remix_ui_terminal_succeeded { |
||||
color: var(--success); |
||||
} |
||||
.remix_ui_terminal_failed { |
||||
color: var(--danger); |
||||
} |
||||
.remix_ui_terminal_arrow { |
||||
color: var(--text-info); |
||||
font-size: 20px; |
||||
cursor: pointer; |
||||
} |
||||
.remix_ui_terminal_arrow:hover { |
||||
color: var(--secondary); |
||||
} |
||||
.remix_ui_terminal_call { |
||||
font-size: 7px; |
||||
min-width: 20px; |
||||
min-height: 20px; |
||||
color: var(--text-info); |
||||
} |
||||
|
||||
.remix_ui_terminal_tx { |
||||
color: var(--text-info); |
||||
float: left; |
||||
} |
||||
|
||||
.remix_ui_terminal_tr, |
||||
.remix_ui_terminal_td { |
||||
border-collapse: collapse; |
||||
font-size: 10px; |
||||
color: var(--text-info); |
||||
border: 1px solid var(--text-info); |
||||
transition: |
||||
max-height 0.3s, |
||||
padding 0.3s; |
||||
} |
||||
table .active { |
||||
transition: |
||||
max-height 0.6s, |
||||
padding 0.6s; |
||||
} |
||||
.remix_ui_terminal_tr, |
||||
.remix_ui_terminal_td { |
||||
padding: 4px; |
||||
vertical-align: baseline; |
||||
} |
||||
.remix_ui_terminal_td:first-child { |
||||
min-width: 30%; |
||||
width: 30%; |
||||
align-items: baseline; |
||||
font-weight: bold; |
||||
} |
@ -0,0 +1,174 @@ |
||||
import React, { |
||||
useState, |
||||
useEffect, |
||||
useRef, |
||||
type SyntheticEvent, |
||||
useContext, |
||||
} from 'react'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
// import { CommentCount, DiscussionEmbed } from 'disqus-react';
|
||||
import { CustomTooltip } from '@remix-ui/helper'; |
||||
import TxList from './TxList'; |
||||
|
||||
import './index.css'; |
||||
import { AppContext } from '../../contexts'; |
||||
|
||||
export interface ClipboardEvent<T = Element> extends SyntheticEvent<T, any> { |
||||
clipboardData: DataTransfer; |
||||
} |
||||
|
||||
export const RemixUiTerminal = (props: any) => { |
||||
const { appState, dispatch } = useContext(AppContext); |
||||
const { journalBlocks, height, hidden } = appState.terminal; |
||||
|
||||
const [display, setDisplay] = useState('transaction'); |
||||
|
||||
const messagesEndRef = useRef<any>(null); |
||||
const typeWriterIndexes = useRef<any>([]); |
||||
|
||||
// terminal dragable
|
||||
const panelRef = useRef(null); |
||||
const terminalMenu = useRef(null); |
||||
|
||||
const scrollToBottom = () => { |
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
scrollToBottom(); |
||||
}, [journalBlocks.length]); |
||||
|
||||
const handleClearConsole = () => { |
||||
typeWriterIndexes.current = []; |
||||
dispatch({ type: 'SET_TERMINAL', payload: { journalBlocks: [] } }); |
||||
}; |
||||
/* start of autoComplete */ |
||||
|
||||
const handleToggleTerminal = () => { |
||||
dispatch({ |
||||
type: 'SET_TERMINAL', |
||||
payload: { hidden: !hidden, height: hidden ? 250 : 35 }, |
||||
}); |
||||
}; |
||||
|
||||
return ( |
||||
<div className="fixed-bottom" style={{ height }}> |
||||
<div |
||||
id="terminal-view" |
||||
className="h-100 d-flex" |
||||
data-id="terminalContainer-view" |
||||
> |
||||
<div |
||||
style={{ fontSize: 12 }} |
||||
className="d-flex position-relative flex-column flex-grow-1" |
||||
ref={panelRef} |
||||
> |
||||
<div className="z-2 d-flex"> |
||||
<div |
||||
className="d-flex w-100 align-items-center position-relative border-top border-dark bg-light" |
||||
ref={terminalMenu} |
||||
style={{ height: 35 }} |
||||
data-id="terminalToggleMenu" |
||||
> |
||||
<CustomTooltip |
||||
placement="top" |
||||
tooltipId="terminalToggle" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={ |
||||
!hidden ? ( |
||||
<FormattedMessage id="terminal.hideTerminal" /> |
||||
) : ( |
||||
<FormattedMessage id="terminal.showTerminal" /> |
||||
) |
||||
} |
||||
> |
||||
<i |
||||
className={`mx-2 remix_ui_terminal_toggleTerminal fas ${ |
||||
!hidden ? 'fa-angle-double-down' : 'fa-angle-double-up' |
||||
}`}
|
||||
data-id="terminalToggleIcon" |
||||
onClick={handleToggleTerminal} |
||||
></i> |
||||
</CustomTooltip> |
||||
<div |
||||
className="mx-2 remix_ui_terminal_toggleTerminal" |
||||
role="button" |
||||
id="clearConsole" |
||||
data-id="terminalClearConsole" |
||||
onClick={handleClearConsole} |
||||
> |
||||
<CustomTooltip |
||||
placement="top" |
||||
tooltipId="terminalClear" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={<FormattedMessage id="terminal.clearConsole" />} |
||||
> |
||||
<i className="fas fa-ban" aria-hidden="true"></i> |
||||
</CustomTooltip> |
||||
</div> |
||||
{/* <div |
||||
className="pl-2 remix_ui_terminal_toggleTerminal" |
||||
onClick={() => { |
||||
setDisplay('transaction'); |
||||
}} |
||||
> |
||||
{ |
||||
journalBlocks.filter( |
||||
(item: any) => item.name === 'knownTransaction' |
||||
).length |
||||
}{' '} |
||||
Transactions |
||||
</div> |
||||
{shortname && ( |
||||
<div |
||||
className="pl-3 remix_ui_terminal_toggleTerminal" |
||||
onClick={() => { |
||||
setDisplay('comment'); |
||||
}} |
||||
> |
||||
<CommentCount |
||||
shortname={shortname} |
||||
config={{ |
||||
url: window.origin, |
||||
identifier: `${address}|${document.domain}`, |
||||
title: name, |
||||
}} |
||||
> |
||||
Comments |
||||
</CommentCount> |
||||
</div> |
||||
)} */} |
||||
</div> |
||||
</div> |
||||
<div |
||||
tabIndex={-1} |
||||
className="remix_ui_terminal_container d-flex h-100 m-0 flex-column" |
||||
data-id="terminalContainer" |
||||
> |
||||
<div |
||||
className={`position-relative flex-column-reverse h-100 ${ |
||||
display === 'transaction' ? 'd-flex' : 'd-none' |
||||
}`}
|
||||
> |
||||
<TxList /> |
||||
</div> |
||||
{/* {shortname && ( |
||||
<div className={`p-3 ${display === 'comment' ? '' : 'd-none'}`}> |
||||
<DiscussionEmbed |
||||
shortname={shortname} |
||||
config={{ |
||||
url: window.origin, |
||||
identifier: `${address}|${document.domain}`, |
||||
title: name, |
||||
}} |
||||
/> |
||||
</div> |
||||
)} */} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default RemixUiTerminal; |
@ -0,0 +1,32 @@ |
||||
export interface ROOTS { |
||||
steps: any; |
||||
cmd: string; |
||||
gidx: number; |
||||
idx: number; |
||||
} |
||||
|
||||
export const KNOWN_TRANSACTION = 'knownTransaction'; |
||||
export const UNKNOWN_TRANSACTION = 'unknownTransaction'; |
||||
export const EMPTY_BLOCK = 'emptyBlock'; |
||||
export const NEW_TRANSACTION = 'newTransaction'; |
||||
export const NEW_BLOCK = 'newBlock'; |
||||
export const NEW_CALL = 'newCall'; |
||||
|
||||
export const HTML = 'html'; |
||||
export const LOG = 'log'; |
||||
export const TYPEWRITERLOG = 'typewriterlog'; |
||||
export const TYPEWRITERWARNING = 'typewriterwarning'; |
||||
export const TYPEWRITERSUCCESS = 'typewritersuccess'; |
||||
export const INFO = 'info'; |
||||
export const WARN = 'warn'; |
||||
export const ERROR = 'error'; |
||||
export const SCRIPT = 'script'; |
||||
export const CLEAR_CONSOLE = 'clearconsole'; |
||||
export const LISTEN_ON_NETWORK = 'listenOnNetWork'; |
||||
export const CMD_HISTORY = 'cmdHistory'; |
||||
|
||||
export interface RemixUiTerminalProps { |
||||
plugin: any; |
||||
onReady: (api: any) => void; |
||||
visible: boolean; |
||||
} |
@ -0,0 +1,68 @@ |
||||
export const getKeyOf = (item: any) => { |
||||
return Object.keys(item)[0]; |
||||
}; |
||||
|
||||
export const getValueOf = (item: any) => { |
||||
return Object.values(item)[0]; |
||||
}; |
||||
|
||||
export const Objectfilter = (obj: any, filterValue: any) => |
||||
obj.filter((item: any) => Object.keys(item)[0].includes(filterValue)); |
||||
|
||||
export const matched = (arr: [], value: string) => |
||||
arr |
||||
.map((x) => Object.keys(x).some((x) => x.startsWith(value))) |
||||
.some((x) => x); |
||||
|
||||
const findDeep = ( |
||||
object: any, |
||||
fn: any, |
||||
found = { break: false, value: undefined }, |
||||
) => { |
||||
if (typeof object !== 'object' || object === null) return; |
||||
for (const i in object) { |
||||
if (found.break) break; |
||||
let el = object[i]; |
||||
if (el?.innerText != null) el = el.innerText; |
||||
if (fn(el, i, object)) { |
||||
found.value = el; |
||||
found.break = true; |
||||
break; |
||||
} else { |
||||
findDeep(el, fn, found); |
||||
} |
||||
} |
||||
return found.value; |
||||
}; |
||||
|
||||
export const find = (args: any, query: any) => { |
||||
query = query.trim(); |
||||
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
|
||||
const isMatch = !!findDeep(args, function check(value: null | undefined) { |
||||
if (value === undefined || value === null) return false; |
||||
if (typeof value === 'function') return false; |
||||
if (typeof value === 'object') return false; |
||||
const contains = String(value).includes(query.trim()); |
||||
return contains; |
||||
}); |
||||
return isMatch; |
||||
}; |
||||
|
||||
export const wrapScript = (script: string) => { |
||||
const isKnownScript = ['remix.', 'console.', 'git', 'gpt'].some((prefix) => |
||||
script.trim().startsWith(prefix), |
||||
); |
||||
if (isKnownScript) return script; |
||||
return ` |
||||
try { |
||||
const ret = ${script}; |
||||
if (ret instanceof Promise) { |
||||
ret.then((result) => { console.log(result) }).catch((error) => { console.log(error) }) |
||||
} else { |
||||
console.log(ret) |
||||
} |
||||
} catch (e) { |
||||
console.log(e.message) |
||||
} |
||||
`;
|
||||
}; |
@ -0,0 +1,41 @@ |
||||
.udapp_methCaret { |
||||
margin-left: 4px; |
||||
cursor: pointer; |
||||
font-size: 16px; |
||||
} |
||||
|
||||
.udapp_multiArg label { |
||||
margin: 0 4px 0 0; |
||||
font-size: 10px; |
||||
} |
||||
.udapp_hasArgs .udapp_multiArg input { |
||||
border-left: 1px solid #dddddd; |
||||
width: 67%; |
||||
} |
||||
.udapp_hasArgs input { |
||||
font-size: 10px !important; |
||||
border-radius: 3px; |
||||
border-top-left-radius: 0 !important; |
||||
border-bottom-left-radius: 0 !important; |
||||
} |
||||
.udapp_hasArgs button { |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
font-size: 11px; |
||||
border-top-right-radius: 0 !important; |
||||
border-bottom-right-radius: 0 !important; |
||||
border-right: 0; |
||||
} |
||||
.udapp_intro { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
display: -webkit-box; |
||||
white-space: pre-wrap; |
||||
-webkit-line-clamp: 2; |
||||
-webkit-box-orient: vertical; |
||||
} |
||||
.udapp_intro:hover { |
||||
-webkit-line-clamp: inherit; |
||||
} |
||||
|
@ -0,0 +1,247 @@ |
||||
import React, { useContext, useState } from 'react'; |
||||
import * as remixLib from '@remix-project/remix-lib'; |
||||
import { ContractGUI } from '../ContractGUI'; |
||||
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' |
||||
import { BN } from 'bn.js'; |
||||
import './index.css'; |
||||
import { AppContext } from '../../contexts'; |
||||
import { runTransactions } from '../../actions'; |
||||
|
||||
const txHelper = remixLib.execution.txHelper; |
||||
|
||||
const getFuncABIInputs = (funABI: any) => { |
||||
if (!funABI.inputs) { |
||||
return ''; |
||||
} |
||||
return txHelper.inputParametersDeclarationToString(funABI.inputs); |
||||
}; |
||||
|
||||
export interface FuncABI { |
||||
name: string; |
||||
type: string; |
||||
inputs: { name: string; type: string }[]; |
||||
stateMutability: string; |
||||
payable?: boolean; |
||||
constant?: any; |
||||
} |
||||
|
||||
export function UniversalDappUI(props: any) { |
||||
const { appState } = useContext(AppContext); |
||||
const instance = appState.instance; |
||||
|
||||
const address = instance.address; |
||||
const { abi: contractABI, items, containers } = instance; |
||||
const [expandPath, setExpandPath] = useState<string[]>([]); |
||||
|
||||
const runTransaction = ( |
||||
lookupOnly: boolean, |
||||
funcABI: FuncABI, |
||||
valArr: { name: string; type: string }[] | null, |
||||
inputsValues: string, |
||||
funcIndex?: number |
||||
) => { |
||||
const functionName = |
||||
funcABI.type === 'function' ? funcABI.name : `(${funcABI.type})`; |
||||
const logMsg = `${lookupOnly ? 'call' : 'transact'} to ${ |
||||
instance.name |
||||
}.${functionName}`;
|
||||
|
||||
runTransactions({ |
||||
lookupOnly, |
||||
funcABI, |
||||
inputsValues, |
||||
name: instance.name, |
||||
contractABI, |
||||
address, |
||||
logMsg, |
||||
funcIndex, |
||||
}); |
||||
}; |
||||
|
||||
const extractDataDefault = (item: any[] | any, parent?: any) => { |
||||
const ret: any = {}; |
||||
|
||||
if (BN.isBN(item)) { |
||||
ret.self = item.toString(10); |
||||
ret.children = []; |
||||
} else { |
||||
if (item instanceof Array) { |
||||
ret.children = item.map((item, index) => { |
||||
return { key: index, value: item }; |
||||
}); |
||||
ret.self = 'Array'; |
||||
ret.isNode = true; |
||||
ret.isLeaf = false; |
||||
} else if (item instanceof Object) { |
||||
ret.children = Object.keys(item).map((key) => { |
||||
return { key, value: item[key] }; |
||||
}); |
||||
ret.self = 'Object'; |
||||
ret.isNode = true; |
||||
ret.isLeaf = false; |
||||
} else { |
||||
ret.self = item; |
||||
ret.children = null; |
||||
ret.isNode = false; |
||||
ret.isLeaf = true; |
||||
} |
||||
} |
||||
return ret; |
||||
}; |
||||
|
||||
const handleExpand = (path: string) => { |
||||
if (expandPath.includes(path)) { |
||||
const filteredPath = expandPath.filter((value) => value !== path); |
||||
|
||||
setExpandPath(filteredPath); |
||||
} else { |
||||
setExpandPath([...expandPath, path]); |
||||
} |
||||
}; |
||||
|
||||
const label = (key: string | number, value: string) => { |
||||
return ( |
||||
<div className="d-flex mt-2 flex-row label_item"> |
||||
<label className="small font-weight-bold mb-0 pr-1 text-break label_key"> |
||||
{key}: |
||||
</label> |
||||
<label className="m-0 label_value">{value}</label> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const renderData = ( |
||||
item: any[], |
||||
parent: any, |
||||
key: string | number, |
||||
keyPath: string |
||||
) => { |
||||
const data = extractDataDefault(item, parent); |
||||
const children = (data.children || []).map( |
||||
(child: { value: any[]; key: string }, index: any) => { |
||||
return renderData( |
||||
child.value, |
||||
data, |
||||
child.key, |
||||
keyPath + '/' + child.key |
||||
); |
||||
} |
||||
); |
||||
|
||||
if (children && children.length > 0) { |
||||
return ( |
||||
<TreeViewItem |
||||
id={`treeViewItem${key}`} |
||||
key={keyPath} |
||||
label={label(key, data.self)} |
||||
onClick={() => { |
||||
handleExpand(keyPath); |
||||
}} |
||||
expand={expandPath.includes(keyPath)} |
||||
> |
||||
<TreeView id={`treeView${key}`} key={keyPath}> |
||||
{children} |
||||
</TreeView> |
||||
</TreeViewItem> |
||||
); |
||||
} else { |
||||
return ( |
||||
<TreeViewItem |
||||
id={key.toString()} |
||||
key={keyPath} |
||||
label={label(key, data.self)} |
||||
onClick={() => { |
||||
handleExpand(keyPath); |
||||
}} |
||||
expand={expandPath.includes(keyPath)} |
||||
/> |
||||
); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<div className="row m-0"> |
||||
{containers.map((id: any) => { |
||||
return ( |
||||
<div className="col-md" key={id}> |
||||
{items[id].map((funcId: any, index: any) => { |
||||
const funcABI = contractABI[funcId]; |
||||
if (funcABI.type !== 'function') return null; |
||||
const isConstant = |
||||
funcABI.constant !== undefined ? funcABI.constant : false; |
||||
const lookupOnly = |
||||
funcABI.stateMutability === 'view' || |
||||
funcABI.stateMutability === 'pure' || |
||||
isConstant; |
||||
const inputs = getFuncABIInputs(funcABI); |
||||
return ( |
||||
<div |
||||
className="p-2 bg-light mb-2" |
||||
data-id={`function${funcId}`} |
||||
key={funcId} |
||||
> |
||||
<div className="w-100 mb-2"> |
||||
<div> |
||||
{funcABI.title && <h3 data-id={`functionTitle${funcId}`}>{funcABI.title}</h3>} |
||||
<ContractGUI |
||||
funcABI={funcABI} |
||||
clickCallBack={( |
||||
valArray: { name: string; type: string }[], |
||||
inputsValues: string |
||||
) => { |
||||
runTransaction( |
||||
lookupOnly, |
||||
funcABI, |
||||
valArray, |
||||
inputsValues, |
||||
funcId |
||||
); |
||||
}} |
||||
inputs={inputs} |
||||
lookupOnly={lookupOnly} |
||||
key={funcId} |
||||
/> |
||||
{funcABI.details && ( |
||||
<div className="pt-2 udapp_intro" data-id={`functionInstructions${funcId}`}> |
||||
{funcABI.details} |
||||
</div> |
||||
)} |
||||
{lookupOnly && ( |
||||
<div className="udapp_value" data-id="udapp_value"> |
||||
<TreeView id="treeView"> |
||||
{Object.keys( |
||||
instance.decodedResponse || {} |
||||
).map((key) => { |
||||
const funcIndex = funcId; |
||||
const response = |
||||
instance.decodedResponse[key]; |
||||
|
||||
return key === funcIndex |
||||
? Object.keys(response || {}).map( |
||||
(innerkey, _index) => { |
||||
return renderData( |
||||
instance.decodedResponse[key][ |
||||
innerkey |
||||
], |
||||
response, |
||||
innerkey, |
||||
innerkey |
||||
); |
||||
} |
||||
) |
||||
: null; |
||||
})} |
||||
</TreeView> |
||||
</div> |
||||
)} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,211 @@ |
||||
import React, { useContext, useState } from 'react'; |
||||
import { FormattedMessage, useIntl } from 'react-intl'; |
||||
import { CustomTooltip, is0XPrefixed, isHexadecimal, isNumeric } from '@remix-ui/helper'; |
||||
import { AppContext } from '../../contexts'; |
||||
import './index.css'; |
||||
import { runTransactions } from '../../actions'; |
||||
|
||||
export interface FuncABI { |
||||
name: string; |
||||
type: string; |
||||
inputs: { name: string; type: string }[]; |
||||
stateMutability: string; |
||||
payable?: boolean; |
||||
constant?: any; |
||||
} |
||||
|
||||
export function LowLevelInteractions(props: any) { |
||||
const { appState } = useContext(AppContext); |
||||
const { instance, settings } = appState; |
||||
|
||||
const address = instance.address; |
||||
const contractABI = instance.abi; |
||||
const intl = useIntl(); |
||||
const [llIError, setLlIError] = useState<string>(''); |
||||
const [calldataValue, setCalldataValue] = useState<string>(''); |
||||
|
||||
const sendData = () => { |
||||
setLlIError(''); |
||||
const fallback = instance.fallback; |
||||
const receive = instance.receive; |
||||
const args = { |
||||
funcABI: fallback || receive, |
||||
address, |
||||
contractName: instance.name, |
||||
contractABI, |
||||
}; |
||||
const amount = settings.sendValue; |
||||
|
||||
if (amount !== '0') { |
||||
// check for numeric and receive/fallback
|
||||
if (!isNumeric(amount)) { |
||||
setLlIError(intl.formatMessage({ id: 'udapp.llIError1' })); |
||||
return; |
||||
} else if ( |
||||
!receive && |
||||
!(fallback && fallback.stateMutability === 'payable') |
||||
) { |
||||
setLlIError(intl.formatMessage({ id: 'udapp.llIError2' })); |
||||
return; |
||||
} |
||||
} |
||||
let calldata = calldataValue; |
||||
|
||||
if (calldata) { |
||||
if (calldata.length < 4 && is0XPrefixed(calldata)) { |
||||
setLlIError(intl.formatMessage({ id: 'udapp.llIError3' })); |
||||
return; |
||||
} else { |
||||
if (is0XPrefixed(calldata)) { |
||||
calldata = calldata.substr(2, calldata.length); |
||||
} |
||||
if (!isHexadecimal(calldata)) { |
||||
setLlIError(intl.formatMessage({ id: 'udapp.llIError4' })); |
||||
return; |
||||
} |
||||
} |
||||
if (!fallback) { |
||||
setLlIError(intl.formatMessage({ id: 'udapp.llIError5' })); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
if (!receive && !fallback) { |
||||
setLlIError(intl.formatMessage({ id: 'udapp.llIError6' })); |
||||
return; |
||||
} |
||||
|
||||
// we have to put the right function ABI:
|
||||
// if receive is defined and that there is no calldata => receive function is called
|
||||
// if fallback is defined => fallback function is called
|
||||
if (receive && !calldata) args.funcABI = receive; |
||||
else if (fallback) args.funcABI = fallback; |
||||
|
||||
if (!args.funcABI) { |
||||
setLlIError(intl.formatMessage({ id: 'udapp.llIError7' })); |
||||
return; |
||||
} |
||||
runTransaction(false, args.funcABI, null, calldataValue); |
||||
}; |
||||
|
||||
const runTransaction = ( |
||||
lookupOnly: boolean, |
||||
funcABI: FuncABI, |
||||
valArr: { name: string; type: string }[] | null, |
||||
inputsValues: string, |
||||
funcIndex?: number |
||||
) => { |
||||
const functionName = |
||||
funcABI.type === 'function' ? funcABI.name : `(${funcABI.type})`; |
||||
const logMsg = `${lookupOnly ? 'call' : 'transact'} to ${ |
||||
instance.name |
||||
}.${functionName}`;
|
||||
|
||||
runTransactions({ |
||||
lookupOnly, |
||||
funcABI, |
||||
inputsValues, |
||||
name: instance.name, |
||||
contractABI, |
||||
address, |
||||
logMsg, |
||||
funcIndex, |
||||
}); |
||||
}; |
||||
|
||||
const handleCalldataChange = (e: { target: { value: any } }) => { |
||||
const value = e.target.value; |
||||
|
||||
setCalldataValue(value); |
||||
}; |
||||
|
||||
return ( |
||||
<div |
||||
className={`border-dark bg-light col mb-2`} |
||||
data-shared="universalDappUiInstance" |
||||
> |
||||
<div data-id="universalDappUiContractActionWrapper"> |
||||
<div> |
||||
<div className="d-flex flex-column"> |
||||
<div className="d-flex flex-row justify-content-between mt-2"> |
||||
<div className="py-2 d-flex justify-content-start flex-grow-1"> |
||||
<FormattedMessage id="udapp.lowLevelInteractions" /> |
||||
</div> |
||||
<CustomTooltip |
||||
placement={'bottom-end'} |
||||
tooltipClasses="text-wrap" |
||||
tooltipId="receiveEthDocstoolTip" |
||||
tooltipText={<FormattedMessage id="udapp.tooltipText8" />} |
||||
> |
||||
{ |
||||
// receive method added to solidity v0.6.x. use this as diff.
|
||||
instance.solcVersion.canReceive === false ? ( |
||||
<a |
||||
href={`https://solidity.readthedocs.io/en/v${instance.solcVersion.version}/contracts.html`} |
||||
target="_blank" |
||||
rel="noreferrer" |
||||
> |
||||
<i |
||||
aria-hidden="true" |
||||
className="fas fa-info my-2 mr-1" |
||||
></i> |
||||
</a> |
||||
) : ( |
||||
<a |
||||
href={`https://solidity.readthedocs.io/en/v${instance.solcVersion.version}/contracts.html#receive-ether-function`} |
||||
target="_blank" |
||||
rel="noreferrer" |
||||
> |
||||
<i |
||||
aria-hidden="true" |
||||
className="fas fa-info my-2 mr-1" |
||||
></i> |
||||
</a> |
||||
) |
||||
} |
||||
</CustomTooltip> |
||||
</div> |
||||
<div className="d-flex flex-column align-items-start"> |
||||
<label className="">CALLDATA</label> |
||||
<div className="d-flex justify-content-end w-100 align-items-center"> |
||||
<CustomTooltip |
||||
placement="bottom" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipId="deployAndRunLLTxCalldataInputTooltip" |
||||
tooltipText={<FormattedMessage id="udapp.tooltipText9" />} |
||||
> |
||||
<input |
||||
id="deployAndRunLLTxCalldata" |
||||
onChange={handleCalldataChange} |
||||
className="form-control" |
||||
/> |
||||
</CustomTooltip> |
||||
<CustomTooltip |
||||
placement="right" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipId="deployAndRunLLTxCalldataTooltip" |
||||
tooltipText={<FormattedMessage id="udapp.tooltipText10" />} |
||||
> |
||||
<button |
||||
id="deployAndRunLLTxSendTransaction" |
||||
data-id="pluginManagerSettingsDeployAndRunLLTxSendTransaction" |
||||
className="btn p-0 w-50 border-warning text-warning" |
||||
onClick={sendData} |
||||
style={{ height: 32 }} |
||||
> |
||||
Transact |
||||
</button> |
||||
</CustomTooltip> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<label id="deployAndRunLLTxError" className="text-danger my-2"> |
||||
{llIError} |
||||
</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,3 @@ |
||||
import { createContext } from 'react' |
||||
|
||||
export const AppContext = createContext<any>({}) |
@ -0,0 +1,4 @@ |
||||
html, body, #root { |
||||
height: 100%; |
||||
} |
||||
|
@ -0,0 +1,17 @@ |
||||
<!doctype html> |
||||
<html lang="en"> |
||||
<head> |
||||
<link rel="stylesheet" href="https://remix.ethereum.org/assets/css/themes/remix-dark_tvx1s2.css" id="theme-link"> |
||||
<meta charset="UTF-8" /> |
||||
<link rel="icon" type="image/svg+xml" href="https://remix.ethereum.org/assets/img/remixLogo.webp" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<title>RemixDapp</title> |
||||
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script> |
||||
</head> |
||||
<body> |
||||
<script> |
||||
var global = window |
||||
</script> |
||||
<div id="root"></div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,14 @@ |
||||
function readAndCombineJsonFiles() { |
||||
// @ts-expect-error
|
||||
const dataContext = require.context('./', true, /\.json$/) |
||||
|
||||
let combinedData = {} |
||||
dataContext.keys().forEach((key) => { |
||||
const jsonData = dataContext(key) |
||||
combinedData = { ...combinedData, ...jsonData } |
||||
}) |
||||
|
||||
return combinedData |
||||
} |
||||
|
||||
export default readAndCombineJsonFiles(); |
@ -0,0 +1,44 @@ |
||||
{ |
||||
"terminal.listen": "listen on all transactions", |
||||
"terminal.listenVM": "Listen on all transactions is disabled for VM environment", |
||||
"terminal.listenTitle": "If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you", |
||||
"terminal.search": "Search with transaction hash or address", |
||||
"terminal.used": "used", |
||||
"terminal.debug": "Debug", |
||||
"terminal.welcomeText1": "Welcome to", |
||||
"terminal.welcomeText2": "Your files are stored in", |
||||
"terminal.welcomeText3": "You can use this terminal to", |
||||
"terminal.welcomeText4": "Check transactions details and start debugging", |
||||
"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.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.", |
||||
"terminal.hideTerminal": "Hide Terminal", |
||||
"terminal.showTerminal": "Show Terminal", |
||||
"terminal.clearConsole": "Clear console", |
||||
"terminal.pendingTransactions": "Pending Transactions", |
||||
"terminal.toasterMsg1": "no content to execute", |
||||
"terminal.toasterMsg2": "provider for path {fileName} not found", |
||||
"terminal.vmMode": "VM mode", |
||||
"terminal.vmModeMsg": "Cannot debug this call. Debugging calls is only possible in Remix VM mode.", |
||||
"terminal.ok": "Ok", |
||||
"terminal.cancel": "Cancel", |
||||
"terminal.callWarning": "(Cost only applies when called by a contract)", |
||||
"terminal.msg1": "Transaction mined but execution failed", |
||||
"terminal.msg2": "Transaction mined and execution succeed", |
||||
"terminal.msg3": "Status not available at the moment", |
||||
"terminal.status": "status", |
||||
"terminal.transactionHash": "transaction hash", |
||||
"terminal.blockHash": "block hash", |
||||
"terminal.blockNumber": "block number", |
||||
"terminal.contractAddress": "contract address", |
||||
"terminal.transactionCost": "transaction cost", |
||||
"terminal.executionCost": "execution cost", |
||||
"terminal.input": "input", |
||||
"terminal.decodedInput": "decoded input", |
||||
"terminal.decodedOutput": "decoded output", |
||||
"terminal.logs": "logs" |
||||
} |
@ -0,0 +1,155 @@ |
||||
{ |
||||
"udapp.displayName": "Deploy & run transactions", |
||||
|
||||
"udapp._comment_gasPrice.tsx": "libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx", |
||||
"udapp.gasLimit": "Gas limit", |
||||
"udapp.tooltipText4": "The default gas limit is 3M. Adjust as needed.", |
||||
|
||||
"udapp._comment_value.tsx": "libs/remix-ui/run-tab/src/lib/components/value.tsx", |
||||
"udapp.value": "Value", |
||||
"udapp.tooltipText5": "Enter an amount and choose its unit", |
||||
|
||||
"udapp._comment_contractDropdownUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx", |
||||
"udapp.contract": "Contract", |
||||
"udapp.compiledBy": "compiled by {compilerName}", |
||||
"udapp.warningEvmVersion": "Please make sure that the current network is compatible with this evm version: {evmVersion}. Otherwise any deployment will fail.", |
||||
"udapp.infoSyncCompiledContractTooltip": "Click here to import contracts compiled from an external framework.This action is enabled when Remix is connected to an external framework (hardhat, truffle, foundry) through remixd.", |
||||
"udapp.remixIpfsUdappTooltip": "Publishing the source code and metadata to IPFS facilitates source code verification using Sourcify and will greatly foster contract adoption (auditing, debugging, calling it, etc...)", |
||||
"udapp.deploy": "Deploy", |
||||
"udapp.publishTo": "Publish to", |
||||
"udapp.atAddress": "At Address", |
||||
"udapp.atAddressOptionsTitle1": "address of contract", |
||||
"udapp.atAddressOptionsTitle2": "Interact with the deployed contract - requires the .abi file or compiled .sol file to be selected in the editor (with the same compiler configuration)", |
||||
"udapp.atAddressOptionsTitle3": "Compile a *.sol file or select a *.abi file.", |
||||
"udapp.atAddressOptionsTitle4": "To interact with a deployed contract, either enter its address and compile its source *.sol file (with the same compiler settings) or select its .abi file in the editor. ", |
||||
"udapp.contractOptionsTitle1": "Please compile *.sol file to deploy or access a contract", |
||||
"udapp.contractOptionsTitle2": "Select a compiled contract to deploy or to use with At Address.", |
||||
"udapp.contractOptionsTitle3": "Select and compile *.sol file to deploy or access a contract.", |
||||
"udapp.contractOptionsTitle4": "When there is a compiled .sol file, choose the contract to deploy or to use with At Address.", |
||||
"udapp.checkSumWarning": "It seems you are not using a checksumed address.A checksummed address is an address that contains uppercase letters, as specified in {a}.Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.", |
||||
"udapp.isOverSizePromptEip170": "Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fail if the current network has activated the eip 170. More info: {a}", |
||||
"udapp.isOverSizePromptEip3860": "Contract creation init code exceeds the allowed max code size of 49152 bytes. The deployment will likely fail if the current network has activated the eip 3860. More info: {a}", |
||||
"udapp.thisContractMayBeAbstract": "This contract may be abstract, it may not implement an abstract parent's methods completely or it may not invoke an inherited contract's constructor correctly.", |
||||
"udapp.noCompiledContracts": "No compiled contracts", |
||||
"udapp.addressOfContract": "Address of contract", |
||||
"udapp.loadContractFromAddress": "Load contract from Address", |
||||
"udapp.ok": "OK", |
||||
"udapp.alert": "Alert", |
||||
"udapp.proceed": "Proceed", |
||||
"udapp.cancel": "Cancel", |
||||
"udapp.abiFileSelected": "ABI file selected", |
||||
"udapp.evmVersion": "evm version", |
||||
"udapp.addressNotValid": "The address is not valid", |
||||
|
||||
"udapp._comment_account.tsx": "libs/remix-ui/run-tab/src/lib/components/account.tsx", |
||||
"udapp.account": "Account", |
||||
"udapp.locales": "Language", |
||||
"udapp.themes": "Themes", |
||||
"udapp.signAMessage": "Sign a message", |
||||
"udapp.enterAMessageToSign": "Enter a message to sign", |
||||
"udapp.hash": "hash", |
||||
"udapp.signature": "signature", |
||||
"udapp.injectedTitle": "Unfortunately it's not possible to create an account using injected provider. Please create the account directly from your provider (i.e metamask or other of the same type).", |
||||
"udapp.createNewAccount": "Create a new account", |
||||
"udapp.web3Title": "Creating an account is possible only in Personal mode. Please go to Settings to enable it.", |
||||
"udapp.defaultTitle": "Unfortunately it's not possible to create an account using an external wallet ({selectExEnv}).", |
||||
"udapp.text1": "Please provide a Passphrase for the account creation", |
||||
"udapp.tooltipText1": "Account list is empty, please make sure the current provider is properly connected to remix", |
||||
"udapp.modalTitle1": "Passphrase to sign a message", |
||||
"udapp.modalMessage1": "Enter your passphrase for this account to sign the message", |
||||
"udapp.copyAccount": "Copy account to clipboard", |
||||
"udapp.signMsgUsingAccount": "Sign a message using this account", |
||||
|
||||
"udapp._comment_environment.tsx": "libs/remix-ui/run-tab/src/lib/components/environment.tsx", |
||||
"udapp.environment": "Environment", |
||||
"udapp.environmentDocs": "Click for docs about Environment", |
||||
"udapp.tooltipText2": "Open chainlist.org and get the connection specs of the chain you want to interact with.", |
||||
"udapp.tooltipText3": "Click to open a bridge for converting L1 mainnet ETH to the selected network currency.", |
||||
|
||||
"udapp._comment_instanceContainerUI.tsx": "libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx", |
||||
"udapp.deployedContracts": "Deployed Contracts", |
||||
"udapp.deployAndRunClearInstances": "Clear instances list and reset recorder", |
||||
"udapp.deployAndRunNoInstanceText": "Currently you have no contract instances to interact with.", |
||||
"udapp.tooltipText6": "Autogenerated generic user interfaces for interaction with deployed contracts", |
||||
|
||||
"udapp._comment_recorderCardUI.tsx": "libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx", |
||||
"udapp.transactionsRecorded": "Transactions recorded", |
||||
"udapp.transactionsCountTooltip": "The number of recorded transactions", |
||||
"udapp.transactionSaveTooltip1": "No transactions to save", |
||||
"udapp.transactionSaveTooltip2": "Save {count} transaction as scenario file", |
||||
"udapp.transactionSaveTooltip3": "Save {count} transactions as scenario file", |
||||
"udapp.transactionsWalkthroughTooltip": "Start walkthrough tour for recorder.", |
||||
"udapp.infoRecorderTooltip": "Save transactions (deployed contracts and function executions) and replay them in another environment e.g Transactions created in Remix VM can be replayed in the Injected Provider.", |
||||
"udapp.livemodeRecorderTooltip": "If contracts are updated after recording transactions, checking this box will run recorded transactions with the latest copy of the compiled contracts", |
||||
"udapp.livemodeRecorderLabel": "Run transactions using the latest compilation result", |
||||
"udapp.runRecorderTooltip": "Run transaction(s) from the current scenario file", |
||||
"udapp.save": "Save", |
||||
"udapp.run": "Run", |
||||
|
||||
"udapp._comment_contractGUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx", |
||||
"udapp.parameters": "Parameters", |
||||
"udapp.copyParameters": "Copy encoded input parameters to clipboard", |
||||
"udapp.copyCalldata": "Copy calldata to clipboard", |
||||
"udapp.deployWithProxy": "Deploy with Proxy", |
||||
"udapp.upgradeWithProxy": "Upgrade with Proxy", |
||||
"udapp.getEncodedCallError": "cannot encode empty arguments", |
||||
"udapp.proxyAddressError1": "proxy address cannot be empty", |
||||
"udapp.proxyAddressError2": "not a valid contract address", |
||||
"udapp.tooltipText11": "Proxy address cannot be empty", |
||||
"udapp.tooltipText12": "Input required", |
||||
"udapp.tooltipText13": "Deployed {date}", |
||||
|
||||
"udapp._comment_universalDappUI.tsx": "libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx", |
||||
"udapp.tooltipText7": "Remove from the list", |
||||
"udapp.tooltipText8": "Click for docs about using 'receive'/'fallback'", |
||||
"udapp.tooltipText9": "The Calldata to send to fallback function of the contract.", |
||||
"udapp.tooltipText10": "Send data to contract.", |
||||
"udapp.balance": "Balance", |
||||
"udapp.lowLevelInteractions": "Low level interactions", |
||||
"udapp.llIError1": "Value to send should be a number", |
||||
"udapp.llIError2": "In order to receive Ether transfer the contract should have either 'receive' or payable 'fallback' function", |
||||
"udapp.llIError3": "The calldata should be a valid hexadecimal value with size of at least one byte.", |
||||
"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.copy": "Copy", |
||||
|
||||
"udapp._comment_mainnet.tsx": "libs/remix-ui/run-tab/src/lib/components/mainnet.tsx", |
||||
"udapp.mainnetText1": "You are about to create a transaction on {name} Network. Confirm the details to send the info to your provider.", |
||||
"udapp.mainnetText2": "The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to {name} Network.", |
||||
"udapp.amount": "Amount", |
||||
"udapp.gasEstimation": "Gas estimation", |
||||
"udapp.maxPriorityFee": "Max Priority fee", |
||||
"udapp.maxFee": "Max fee (Not less than base fee {baseFeePerGas} Gwei)", |
||||
"udapp.contractCreation": "Contract Creation", |
||||
"udapp.transactionFee": "Transaction is invalid. Max fee should not be less than Base fee", |
||||
"udapp.title1": "Represents the part of the tx fee that goes to the miner.", |
||||
"udapp.title2": "Represents the maximum amount of fee that you will pay for this transaction. The minimun needs to be set to base fee.", |
||||
"udapp.gasPrice": "Gas price", |
||||
"udapp.gweiText": "visit {a} for current gas price info.", |
||||
"udapp.maxTransactionFee": "Max transaction fee", |
||||
"udapp.mainnetText3": "Do not show this warning again.", |
||||
|
||||
"udapp._comment_run-tab.tsx": "libs/remix-ui/run-tab/src/lib/run-tab.tsx", |
||||
"udapp.gasEstimationPromptText": "Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?", |
||||
|
||||
"udapp._comment_custom-dropdown.tsx": "libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx", |
||||
"udapp.enterProxyAddress": "Enter Proxy Address", |
||||
|
||||
"udapp._comment_provider": "apps/remix-ide/src/app/providers", |
||||
"udapp.customVmForkProviderText": "Please provide information about the custom fork. If the node URL is not provided, the VM will start with an empty state.", |
||||
"udapp.nodeUrl": "Node URL", |
||||
"udapp.blockNumber": "Block number (or \"latest\")", |
||||
"udapp.externalHttpProviderText1": "Note: To use Geth & https://remix.ethereum.org, configure it to allow requests from Remix:(see <a>Geth Docs on rpc server</a>)", |
||||
"udapp.externalHttpProviderText2": "To run Remix & a local Geth test node, use this command: (see <a>Geth Docs on Dev mode</a>)", |
||||
"udapp.externalHttpProviderText3": "<b>WARNING:</b> It is not safe to use the --http.corsdomain flag with a wildcard: <b>--http.corsdomain *</b>", |
||||
"udapp.externalHttpProviderText4": "For more info: <a>Remix Docs on External HTTP Provider</a>", |
||||
"udapp.foundryProviderText1": "Note: To run Anvil on your system, run:", |
||||
"udapp.foundryProviderText2": "For more info, visit: <a>Foundry Documentation</a>", |
||||
"udapp.ganacheProviderText1": "Note: To run Ganache on your system, run:", |
||||
"udapp.ganacheProviderText2": "For more info, visit: <a>Ganache Documentation</a>", |
||||
"udapp.hardhatProviderText1": "Note: To run Hardhat network node on your system, go to hardhat project folder and run command:", |
||||
"udapp.hardhatProviderText2": "For more info, visit: <a>Hardhat Documentation</a>", |
||||
"udapp.viewSourceCode": "View Source Code" |
||||
} |
@ -0,0 +1,18 @@ |
||||
import enJson from '../en'; |
||||
|
||||
function readAndCombineJsonFiles() { |
||||
// @ts-expect-error
|
||||
const dataContext = require.context('./', true, /\.json$/) |
||||
|
||||
let combinedData = {} |
||||
dataContext.keys().forEach((key) => { |
||||
const jsonData = dataContext(key) |
||||
combinedData = { ...combinedData, ...jsonData } |
||||
}) |
||||
|
||||
return combinedData |
||||
} |
||||
|
||||
// There may have some un-translated content. Always fill in the gaps with EN JSON.
|
||||
// No need for a defaultMessage prop when render a FormattedMessage component.
|
||||
export default Object.assign({}, enJson, readAndCombineJsonFiles()); |
@ -0,0 +1,43 @@ |
||||
{ |
||||
"terminal.listen": "escuchar en todas las transacciones", |
||||
"terminal.listenTitle": "Si está selccionado, Remix escuchará en todas las transacciones minadas en el entorno actual y no solo las transacciones creadas por ti", |
||||
"terminal.search": "Buscar con el hash de transacción o dirección", |
||||
"terminal.used": "usado", |
||||
"terminal.debug": "Depurar", |
||||
"terminal.welcomeText1": "Bienvenido a", |
||||
"terminal.welcomeText2": "Sus archivos están guardados en", |
||||
"terminal.welcomeText3": "Puedes usar este terminal para", |
||||
"terminal.welcomeText4": "Revisa los detalles de las transacciones y empieza a depurar", |
||||
"terminal.welcomeText5": "Ejecutar scripts de JavaScript", |
||||
"terminal.welcomeText6": "Introduce un script directamente en la interfaz de línea de comandos", |
||||
"terminal.welcomeText7": "Selecciona un archivo Javascript en el explorador de archivos y luego ejecuta `remix.execute()` o `remix.exeCurrent()` en la interfaz de línea de comandos", |
||||
"terminal.welcomeText8": "Haz clic derecho en un archivo JavaScript en el explorador de archivos y luego haz clic en `Ejecutar`", |
||||
"terminal.welcomeText9": "Las siguientes librerías son accesibles", |
||||
"terminal.welcomeText10": "Escriba el nombre de la librería para ver los comandos disponibles", |
||||
"terminal.text1": "Este tipo de comando ha quedado obsoleto y ya no está funcionando. Por favor, ejecute remix.help() para listar los comandos disponibles.", |
||||
"terminal.hideTerminal": "Ocultar Terminal", |
||||
"terminal.showTerminal": "Mostrar Terminal", |
||||
"terminal.clearConsole": "Limpiar la consola", |
||||
"terminal.pendingTransactions": "Transacciones Pendientes", |
||||
"terminal.toasterMsg1": "no hay contenido para ejecutar", |
||||
"terminal.toasterMsg2": "proveedor para la ruta {fileName} no encontrado", |
||||
"terminal.vmMode": "Modo MV", |
||||
"terminal.vmModeMsg": "No se puede depurar esta llamada. La depuración de llamadas sólo es posible en el modo MV Remix.", |
||||
"terminal.ok": "Aceptar", |
||||
"terminal.cancel": "Cancelar", |
||||
"terminal.callWarning": "(El costo solo aplica cuando es llamado por un contrato)", |
||||
"terminal.msg1": "Transacción minada pero la ejecución falló", |
||||
"terminal.msg2": "Transacción minada y ejecución exitosa", |
||||
"terminal.msg3": "Estado no disponible en este momento", |
||||
"terminal.status": "estado", |
||||
"terminal.transactionHash": "hash de transacción", |
||||
"terminal.blockHash": "hash de bloque", |
||||
"terminal.blockNumber": "número de bloque", |
||||
"terminal.contractAddress": "dirección del contrato", |
||||
"terminal.transactionCost": "coste de transacción", |
||||
"terminal.executionCost": "costo de ejecución", |
||||
"terminal.input": "entrada", |
||||
"terminal.decodedInput": "entrada descodificada", |
||||
"terminal.decodedOutput": "salida descodificada", |
||||
"terminal.logs": "registros" |
||||
} |
@ -0,0 +1,141 @@ |
||||
{ |
||||
"udapp.displayName": "Publicar y ejecutar transacciones", |
||||
"udapp._comment_gasPrice.tsx": "libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx", |
||||
"udapp.gasLimit": "Límite de gas", |
||||
"udapp.tooltipText4": "El límite de gas por defecto es de 3M. Ajuste según sea necesario.", |
||||
"udapp._comment_value.tsx": "libs/remix-ui/run-tab/src/lib/components/value.tsx", |
||||
"udapp.value": "Valor", |
||||
"udapp.tooltipText5": "Introduzca una cantidad y elija su unidad", |
||||
"udapp._comment_contractDropdownUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx", |
||||
"udapp.contract": "Contrato", |
||||
"udapp.compiledBy": "compilado por {compilerName}", |
||||
"udapp.warningEvmVersion": "Por favor, asegúrese de que la red actual es compatible con esta versión evm: {evmVersion}. De lo contrario, cualquier publicación fallará.", |
||||
"udapp.infoSyncCompiledContractTooltip": "Haz clic aquí para importar contratos compilados por un framework externo. Esta acción es activada cuando Remix está conectado a un framework externo (hardhat, truffle, foundry) a través de remixd.", |
||||
"udapp.remixIpfsUdappTooltip": "Publicar el código fuente y los metadatos en IPFS facilita la verificación del código fuente usando Sourcify y fomentará enormemente la adopción del contrato (auditoría, depuración, el llamarlo, etc...)", |
||||
"udapp.deploy": "Publicar", |
||||
"udapp.publishTo": "Publicar en", |
||||
"udapp.atAddress": "En la Dirección", |
||||
"udapp.atAddressOptionsTitle1": "dirección del contrato", |
||||
"udapp.atAddressOptionsTitle2": "Interactuar con el contrato publicado - requiere que el archivo .abi o el archivo compilado .sol sea seleccionado en el editor (con la misma configuración del compilador)", |
||||
"udapp.atAddressOptionsTitle3": "Compila un archivo *.sol o seleccione un archivo *.abi.", |
||||
"udapp.atAddressOptionsTitle4": "Para interactuar con un contrato publicado, ingrese su dirección y compile su archivo fuente *.sol (con la misma configuración del compilador) o seleccione su archivo .abi en el editor. ", |
||||
"udapp.contractOptionsTitle1": "Por favor compile el archivo *.sol para publicar o acceder a un contrato", |
||||
"udapp.contractOptionsTitle2": "Seleccione un contrato compilado para publicar o usar En la Dirección.", |
||||
"udapp.contractOptionsTitle3": "Seleccione y compile el archivo *.sol para publicar o acceder a un contrato.", |
||||
"udapp.contractOptionsTitle4": "Cuando hay un archivo .sol compilado, elija el contrato para publicar o utilizar con En la Dirección.", |
||||
"udapp.checkSumWarning": "Parece que no está utilizando una dirección de suma comprobada. Una dirección de suma comprobada es una dirección que contiene letras mayúsculas, como se especifica en {a}. Las direcciones de sumas comprobadas están destinadas a ayudar a evitar que los usuarios envíen transacciones a una dirección incorrecta.", |
||||
"udapp.isOverSizePromptEip170": "La inicialización de la creación de contratos devuelve información con una longitud de más de 24576 bytes. La publicación probablemente fallará si la red actual ha activado el eip 170. Más información en: {a}", |
||||
"udapp.isOverSizePromptEip3860": "El código de inicio de creación del contrato excede el tamaño máximo de código permitido de 49152 bytes. La publicación probablemente fallará si la red actual ha activado el eip 3860. Más información en: {a}", |
||||
"udapp.thisContractMayBeAbstract": "Este contrato podría ser abstracto, podría no implementar los métodos de un padre abstracto completamente o podría no invocar correctamente al constructor de un contrato heredado.", |
||||
"udapp.noCompiledContracts": "No hay contratos compilados", |
||||
"udapp.addressOfContract": "Dirección del contrato", |
||||
"udapp.loadContractFromAddress": "Cargar contrato desde la Dirección", |
||||
"udapp.ok": "ACEPTAR", |
||||
"udapp.alert": "Alerta", |
||||
"udapp.proceed": "Continuar", |
||||
"udapp.cancel": "Cancelar", |
||||
"udapp.abiFileSelected": "Archivo ABI seleccionado", |
||||
"udapp.evmVersion": "versión de evm", |
||||
"udapp.addressNotValid": "La dirección no es válida", |
||||
"udapp._comment_account.tsx": "libs/remix-ui/run-tab/src/lib/components/account.tsx", |
||||
"udapp.account": "Cuenta", |
||||
"udapp.locales": "Idioma", |
||||
"udapp.themes": "Temas", |
||||
"udapp.signAMessage": "Firmar un mensaje", |
||||
"udapp.enterAMessageToSign": "Introduce un mensaje para firmar", |
||||
"udapp.hash": "hash", |
||||
"udapp.signature": "firma", |
||||
"udapp.injectedTitle": "Lamentablemente no es posible crear una cuenta usando un proveedor inyectado. Por favor, crea la cuenta directamente desde tu proveedor (es decir, metamask u otra del mismo tipo).", |
||||
"udapp.createNewAccount": "Crear una nueva cuenta", |
||||
"udapp.web3Title": "La creación de una cuenta solo es posible en el modo Personal. Por favor, ve a Ajustes para habilitarla.", |
||||
"udapp.defaultTitle": "Lamentablemente no es posible crear una cuenta usando una billetera externa ({selectExEnv}).", |
||||
"udapp.text1": "Por favor proporcione una frase de contraseña para la creación de la cuenta", |
||||
"udapp.tooltipText1": "La lista de cuentas está vacía, por favor asegúrese de que el proveedor actual está conectado correctamente a remix", |
||||
"udapp.modalTitle1": "Frase de contraseña para firmar un mensaje", |
||||
"udapp.modalMessage1": "Introduce su frase de contraseña para esta cuenta para firmar el mensaje", |
||||
"udapp.copyAccount": "Copiar cuenta al portapapeles", |
||||
"udapp.signMsgUsingAccount": "Firmar un mensaje usando esta cuenta", |
||||
"udapp._comment_environment.tsx": "libs/remix-ui/run-tab/src/lib/components/environment.tsx", |
||||
"udapp.environment": "Entorno", |
||||
"udapp.environmentDocs": "Haga clic para ver la documentación sobre El Entorno", |
||||
"udapp.tooltipText2": "Abra chainlist.org y obtenga las especificaciones de conexión de la cadena con la que desea interactuar.", |
||||
"udapp.tooltipText3": "Haz clic para abrir un puente para convertir ETH de L1 mainnet a la moneda de red seleccionada.", |
||||
"udapp._comment_instanceContainerUI.tsx": "libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx", |
||||
"udapp.deployedContracts": "Contratos Publicados", |
||||
"udapp.deployAndRunClearInstances": "Limpiar lista de instancias y restablecer grabadora", |
||||
"udapp.deployAndRunNoInstanceText": "Actualmente no tiene instancias de contrato con las que interactuar.", |
||||
"udapp.tooltipText6": "Interfaces de usuario genéricas generadas automáticamente para la interacción con los contratos publicados", |
||||
"udapp._comment_recorderCardUI.tsx": "libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx", |
||||
"udapp.transactionsRecorded": "Transacciones grabadas", |
||||
"udapp.transactionsCountTooltip": "El número de transacciones grabadas", |
||||
"udapp.transactionSaveTooltip1": "No hay transacciones para guardar", |
||||
"udapp.transactionSaveTooltip2": "Guardar {count} transacción como archivo de escenario", |
||||
"udapp.transactionSaveTooltip3": "Guardar {count} transacciones como archivos de escenario", |
||||
"udapp.infoRecorderTooltip": "Guardar transacciones (contratos publicados y ejecuciones de funciones) y reproducirlas en otro entorno. Por ejemplo: las transacciones creadas en la MV de Remix pueden reproducirse en el Proveedor Inyectado.", |
||||
"udapp.livemodeRecorderTooltip": "Si los contratos se actualizan después de grabar las transacciones, marcando esta casilla ejecutará las transacciones grabadas con la última copia de los contratos compilados", |
||||
"udapp.livemodeRecorderLabel": "Ejecutar transacciones usando el último resultado de compilación", |
||||
"udapp.runRecorderTooltip": "Ejecutar transacción(es) desde el archivo de escenario actual", |
||||
"udapp.save": "Guardar", |
||||
"udapp.run": "Ejecutar", |
||||
"udapp._comment_contractGUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx", |
||||
"udapp.parameters": "Parámetros", |
||||
"udapp.copyParameters": "Copiar parámetros de entrada codificados al portapapeles", |
||||
"udapp.copyCalldata": "Copiar calldata al portapapeles", |
||||
"udapp.deployWithProxy": "Publicar con Proxy", |
||||
"udapp.upgradeWithProxy": "Actualizar con Proxy", |
||||
"udapp.getEncodedCallError": "no se pueden codificar argumentos vacíos", |
||||
"udapp.proxyAddressError1": "la dirección del proxy no puede estar vacía", |
||||
"udapp.proxyAddressError2": "no es una dirección de contrato válida", |
||||
"udapp.tooltipText11": "La dirección del Proxy no puede estar vacía", |
||||
"udapp.tooltipText12": "Entrada requerida", |
||||
"udapp.tooltipText13": "Publicado en {date}", |
||||
"udapp._comment_universalDappUI.tsx": "libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx", |
||||
"udapp.tooltipText7": "Eliminar de la lista", |
||||
"udapp.tooltipText8": "Haga clic para ver los documentos sobre el uso de 'receive'/'fallback'", |
||||
"udapp.tooltipText9": "El datos de llamada a enviar a la función fallback del contrato.", |
||||
"udapp.tooltipText10": "Enviar datos al contrato.", |
||||
"udapp.balance": "Saldo", |
||||
"udapp.lowLevelInteractions": "Interacciones de bajo nivel", |
||||
"udapp.llIError1": "El valor a enviar debe ser un número", |
||||
"udapp.llIError2": "Para recibir la transferencia de Ether el contrato debe tener una función de 'receive' o 'fallback' payable", |
||||
"udapp.llIError3": "Los datos de llamada deben ser un valor hexadecimal válido con el tamaño de al menos un byte.", |
||||
"udapp.llIError4": "Los datos de llamada deben ser un valor hexadecimal válido.", |
||||
"udapp.llIError5": "La función 'fallback' no está definida", |
||||
"udapp.llIError6": "Las funciones 'receive' y 'fallback' no están definidas", |
||||
"udapp.llIError7": "Por favor defina una función 'Fallback' para enviar calldata y un 'Receice' o payable 'Fallback' para enviar ethers", |
||||
"udapp.copy": "Copiar", |
||||
"udapp._comment_mainnet.tsx": "libs/remix-ui/run-tab/src/lib/components/mainnet.tsx", |
||||
"udapp.mainnetText1": "Estás a punto de crear una transacción en la Red {name}. Confirma los detalles para enviar la información a tu proveedor.", |
||||
"udapp.mainnetText2": "El proveedor para muchos usuarios es MetaMask. El proveedor le pedirá que firme la transacción antes de enviarla a la Red {name}.", |
||||
"udapp.amount": "Cantidad", |
||||
"udapp.gasEstimation": "Estimación de gas", |
||||
"udapp.maxPriorityFee": "Tarifa Máxima de Prioridad", |
||||
"udapp.maxFee": "Comisión máxima (no menos de la comisión base {baseFeePerGas} Gwei)", |
||||
"udapp.contractCreation": "Creación de Contrato", |
||||
"udapp.transactionFee": "La transacción no es válida. La comisión máxima no debe ser menor que la Comisión Base", |
||||
"udapp.title1": "Representa la parte de la comisión de tx que va al minero.", |
||||
"udapp.title2": "Representa la cantidad máxima de comisión que usted pagará por esta transacción. El mínimo debe estar establecido en la comisión base.", |
||||
"udapp.gasPrice": "Precio gas", |
||||
"udapp.gweiText": "visita {a} para obtener el precio actual del gas.", |
||||
"udapp.maxTransactionFee": "Comisión de transacción máxima", |
||||
"udapp.mainnetText3": "No volver a mostrar esta advertencia.", |
||||
"udapp._comment_run-tab.tsx": "libs/remix-ui/run-tab/src/lib/run-tab.tsx", |
||||
"udapp.gasEstimationPromptText": "La estimación del gas ha fallado con el siguiente mensaje (ver abajo). La ejecución de la transacción probablemente fallará. ¿Desea forzar el envío?", |
||||
"udapp._comment_custom-dropdown.tsx": "libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx", |
||||
"udapp.enterProxyAddress": "Introduzca la Dirección del Proxy", |
||||
"udapp._comment_provider": "apps/remix-ide/src/app/providers", |
||||
"udapp.customVmForkProviderText": "Por favor, proporcione información sobre el fork personalizado. Si la URL del nodo no se proporciona, la MV comenzará con un estado vacío.", |
||||
"udapp.nodeUrl": "URL del Nodo", |
||||
"udapp.blockNumber": "Número de bloque (o \"último\")", |
||||
"udapp.externalHttpProviderText1": "Nota: Para usar Geth & https://remix.ethereum.org, configúrelo para permitir solicitudes de Remix:(ver <a>Documentación de Geth en el servidor rpc</a>)", |
||||
"udapp.externalHttpProviderText2": "Para ejecutar Remix y un nodo de prueba Geth local, utilice este comando: (vea <a>Documentación de Geth en el modo Desarrollador</a>)", |
||||
"udapp.externalHttpProviderText3": "<b>ALERTA:</b> No es seguro usar la bandera --http.corsdomain con un comodín: <b>--http.corsdomain *</b>", |
||||
"udapp.externalHttpProviderText4": "Para más información en: <a>Documentación de Remix en Provedor HTTP Externo</a>", |
||||
"udapp.foundryProviderText1": "Nota: Para ejecutar Anvil en su sistema, ejecute:", |
||||
"udapp.foundryProviderText2": "Para más información, visite: <a>Documentación de Foundry</a>", |
||||
"udapp.ganacheProviderText1": "Nota: Para ejecutar Ganache en su sistema, ejecute:", |
||||
"udapp.ganacheProviderText2": "Para más información, visite: <a>Documentación de Ganache</a>", |
||||
"udapp.hardhatProviderText1": "Nota: Para ejecutar el nodo de red Hardhat en su sistema, vaya a la carpeta del proyecto hardhat y ejecute el comando:", |
||||
"udapp.hardhatProviderText2": "Para más información, visite: <a>Documentación de Hardhat</a>", |
||||
"udapp.viewSourceCode": "Ver código fuente" |
||||
} |
@ -0,0 +1,18 @@ |
||||
import enJson from '../en'; |
||||
|
||||
function readAndCombineJsonFiles() { |
||||
// @ts-expect-error
|
||||
const dataContext = require.context('./', true, /\.json$/) |
||||
|
||||
let combinedData = {} |
||||
dataContext.keys().forEach((key) => { |
||||
const jsonData = dataContext(key) |
||||
combinedData = { ...combinedData, ...jsonData } |
||||
}) |
||||
|
||||
return combinedData |
||||
} |
||||
|
||||
// There may have some un-translated content. Always fill in the gaps with EN JSON.
|
||||
// No need for a defaultMessage prop when render a FormattedMessage component.
|
||||
export default Object.assign({}, enJson, readAndCombineJsonFiles()); |
@ -0,0 +1,43 @@ |
||||
{ |
||||
"terminal.listen": "écouter toutes les transactions", |
||||
"terminal.listenTitle": "Si cette case est cochée, Remix écoutera toutes les transactions minées dans l'environnement actuel et pas seulement les transactions créées par vous", |
||||
"terminal.search": "Rechercher avec le hash de la transaction ou l'adrresse", |
||||
"terminal.used": "utilisé", |
||||
"terminal.debug": "Débogage", |
||||
"terminal.welcomeText1": "Bienvenue dans", |
||||
"terminal.welcomeText2": "Vos fichiers sont stockés dans", |
||||
"terminal.welcomeText3": "Vous pouvez utiliser ce terminal pour", |
||||
"terminal.welcomeText4": "Vérifiez les détails des transactions et commencez à déboguer", |
||||
"terminal.welcomeText5": "Exécuter des scripts JavaScript", |
||||
"terminal.welcomeText6": "Entrez un script directement dans l'interface en ligne de commande", |
||||
"terminal.welcomeText7": "Sélectionnez un fichier Javascript dans l'explorateur de fichiers, puis exécutez `remix.execute()` ou `remix.exeCurrent()` dans l'interface en ligne de commande", |
||||
"terminal.welcomeText8": "Faites un clic droit sur un fichier JavaScript dans l'explorateur de fichiers, puis cliquez sur `Run`", |
||||
"terminal.welcomeText9": "Les bibliothèques suivantes sont accessibles", |
||||
"terminal.welcomeText10": "Tapez le nom de la bibliothèque pour voir les commandes disponibles", |
||||
"terminal.text1": "Ce type de commande a été déprécié et ne fonctionne plus. Veuillez lancer remix.help() pour lister les commandes disponibles.", |
||||
"terminal.hideTerminal": "Masquer le terminal", |
||||
"terminal.showTerminal": "Afficher le terminal", |
||||
"terminal.clearConsole": "Effacer la console", |
||||
"terminal.pendingTransactions": "Transactions en cours", |
||||
"terminal.toasterMsg1": "aucun contenu à exécuter", |
||||
"terminal.toasterMsg2": "provider pour le chemin {fileName} introuvable", |
||||
"terminal.vmMode": "Mode VM", |
||||
"terminal.vmModeMsg": "Impossible de déboguer ce call. Les calls de débogage ne sont possibles qu'en mode VM de Remix.", |
||||
"terminal.ok": "Ok", |
||||
"terminal.cancel": "Annuler", |
||||
"terminal.callWarning": "(Les frais ne s'appliquent qu'en cas d'appel par contrat)", |
||||
"terminal.msg1": "Transaction minée mais exécution échouée", |
||||
"terminal.msg2": "Transaction minée et exécutée avec succès", |
||||
"terminal.msg3": "Statut non disponible pour le moment", |
||||
"terminal.status": "statut", |
||||
"terminal.transactionHash": "hash de transaction", |
||||
"terminal.blockHash": "Hash du block", |
||||
"terminal.blockNumber": "numéro du block", |
||||
"terminal.contractAddress": "Adresse du contrat", |
||||
"terminal.transactionCost": "Coût de la transaction", |
||||
"terminal.executionCost": "coût d'exécution", |
||||
"terminal.input": "entrée", |
||||
"terminal.decodedInput": "entrée décodée", |
||||
"terminal.decodedOutput": "sortie décodée", |
||||
"terminal.logs": "logs" |
||||
} |
@ -0,0 +1,18 @@ |
||||
import enJson from '../en'; |
||||
|
||||
function readAndCombineJsonFiles() { |
||||
// @ts-expect-error
|
||||
const dataContext = require.context('./', true, /\.json$/) |
||||
|
||||
let combinedData = {} |
||||
dataContext.keys().forEach((key) => { |
||||
const jsonData = dataContext(key) |
||||
combinedData = { ...combinedData, ...jsonData } |
||||
}) |
||||
|
||||
return combinedData |
||||
} |
||||
|
||||
// There may have some un-translated content. Always fill in the gaps with EN JSON.
|
||||
// No need for a defaultMessage prop when render a FormattedMessage component.
|
||||
export default Object.assign({}, enJson, readAndCombineJsonFiles()); |
@ -0,0 +1,43 @@ |
||||
{ |
||||
"terminal.listen": "ascolta su tutte le transazioni", |
||||
"terminal.listenTitle": "Se è selezionato, Remix ascolterà tutte le transazioni generate nell'environment attuale e non solo quelle create da te", |
||||
"terminal.search": "Cercare con hash o indirizzo della transazione", |
||||
"terminal.used": "usato", |
||||
"terminal.debug": "Debug", |
||||
"terminal.welcomeText1": "Benvenuto a", |
||||
"terminal.welcomeText2": "I tuoi file sono memorizzati in", |
||||
"terminal.welcomeText3": "Puoi usare questo terminale per", |
||||
"terminal.welcomeText4": "Controllare i dettagli delle transazioni e avviare il debug", |
||||
"terminal.welcomeText5": "Esegui gli script JavaScript", |
||||
"terminal.welcomeText6": "Immettere uno script direttamente nell'interfaccia della riga di comando", |
||||
"terminal.welcomeText7": "Seleziona un file Javascript nel file explorer ed esegui `remix.execute()` o `remix.exeCurrent()` nell'interfaccia Command Line (Terminale)", |
||||
"terminal.welcomeText8": "Fare clic con il tasto destro su un file JavaScript nell'esplora file e quindi fare clic su `Run`", |
||||
"terminal.welcomeText9": "Le seguenti librerie sono accessibili", |
||||
"terminal.welcomeText10": "Digitare il nome della libreria per vedere i comandi disponibili", |
||||
"terminal.text1": "Questo tipo di comando è stato deprecato e non è più funzionante. Si prega di eseguire remix.help() per elencare i comandi disponibili.", |
||||
"terminal.hideTerminal": "Nascondi Terminale", |
||||
"terminal.showTerminal": "Mostra Terminale", |
||||
"terminal.clearConsole": "Pulisci console", |
||||
"terminal.pendingTransactions": "Transazioni in corso", |
||||
"terminal.toasterMsg1": "Nessun contenuto da eseguire", |
||||
"terminal.toasterMsg2": "provider del percorso {fileName} non trovato", |
||||
"terminal.vmMode": "Modalità VM", |
||||
"terminal.vmModeMsg": "Impossibile eseguire il debug di questa chiamata. Il debug delle chiamate è possibile solo in modalità VM Remix.", |
||||
"terminal.ok": "Ok", |
||||
"terminal.cancel": "Annulla", |
||||
"terminal.callWarning": "(Il costo si applica solo quando viene invocato da un contratto)", |
||||
"terminal.msg1": "Transazione minata ma esecuzione fallita", |
||||
"terminal.msg2": "Transazione minata ed esecuzione riuscita", |
||||
"terminal.msg3": "Status non disponibile al momento", |
||||
"terminal.status": "Status", |
||||
"terminal.transactionHash": "hash della transazione", |
||||
"terminal.blockHash": "hash del blocco", |
||||
"terminal.blockNumber": "numero del blocco", |
||||
"terminal.contractAddress": "indirizzo del contratto", |
||||
"terminal.transactionCost": "costo della transazione", |
||||
"terminal.executionCost": "costo di esecuzione", |
||||
"terminal.input": "input", |
||||
"terminal.decodedInput": "input decodificato", |
||||
"terminal.decodedOutput": "output decodificato", |
||||
"terminal.logs": "Log" |
||||
} |
@ -0,0 +1,141 @@ |
||||
{ |
||||
"udapp.displayName": "Distribuisci ed esegui transazioni", |
||||
"udapp._comment_gasPrice.tsx": "libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx", |
||||
"udapp.gasLimit": "Limite di gas", |
||||
"udapp.tooltipText4": "Il limite di gas predefinito è 3M. Regolare come necessario.", |
||||
"udapp._comment_value.tsx": "libs/remix-ui/run-tab/src/lib/components/value.tsx", |
||||
"udapp.value": "Valore", |
||||
"udapp.tooltipText5": "Inserisci un importo e scegli la sua unità", |
||||
"udapp._comment_contractDropdownUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx", |
||||
"udapp.contract": "Contratto", |
||||
"udapp.compiledBy": "compilato da {compilerName}", |
||||
"udapp.warningEvmVersion": "Assicurarsi che la rete corrente sia compatibile con questa versione della evm: {evmVersion}. In caso contrario, la distribuzione non andrà a buon fine.", |
||||
"udapp.infoSyncCompiledContractTooltip": "Fare clic qui per importare contratti compilati da un framework esterno. Questa azione è abilitata quando Remix è collegato a un framework esterno (hardhat, truffle, foundry) attraverso remixd.", |
||||
"udapp.remixIpfsUdappTooltip": "La pubblicazione del codice sorgente e dei metadati su IPFS facilita la verifica del codice sorgente con Sourcify e favorisce notevolmente l'adozione del contratto (auditing, debug, invocarlo, ecc.)", |
||||
"udapp.deploy": "Distribuisci", |
||||
"udapp.publishTo": "Pubblica su", |
||||
"udapp.atAddress": "All'Indirizzo", |
||||
"udapp.atAddressOptionsTitle1": "indirizzo del contratto", |
||||
"udapp.atAddressOptionsTitle2": "Interagire con il contratto distribuito - richiede che il file .abi o il file .sol compilato sia selezionato nell'editor (con la stessa configurazione del compilatore)", |
||||
"udapp.atAddressOptionsTitle3": "Compila un file *.sol o seleziona un file *.abi.", |
||||
"udapp.atAddressOptionsTitle4": "Per interagire con un contratto distribuito, è necessario inserire il suo indirizzo e compilare il file sorgente *.sol (con le stesse impostazioni del compilatore) oppure selezionare il file .abi nell'editor. ", |
||||
"udapp.contractOptionsTitle1": "Si prega di compilare *.sol file per distribuire o accedere a un contratto", |
||||
"udapp.contractOptionsTitle2": "Selezionare un contratto compilato da distribuire o da utilizzare con At Address.", |
||||
"udapp.contractOptionsTitle3": "Selezionare e compilare il file *.sol per distribuire o accedere a un contratto.", |
||||
"udapp.contractOptionsTitle4": "Quando c'è un file .sol compilato, scegliere il contratto da distribuire o da usare con At Address.", |
||||
"udapp.checkSumWarning": "Sembra che non si stia utilizzando un indirizzo con checksum. Un indirizzo con checksum è un indirizzo che contiene lettere maiuscole, come specificato in {a}. Gli indirizzi con checksum sono pensati per evitare che gli utenti inviino transazioni all'indirizzo sbagliato.", |
||||
"udapp.isOverSizePromptEip170": "L'inizializzazione della creazione del contratto restituisce dati di lunghezza superiore a 24576 byte. La distribuzione probabilmente fallirà se la rete corrente ha attivato l'eip 170. Ulteriori informazioni: {a}", |
||||
"udapp.isOverSizePromptEip3860": "Il codice di avvio della creazione del contratto supera la dimensione massima consentita di 49152 byte. È probabile che la distribuzione fallisca se la rete corrente ha attivato l'eip 3860. Ulteriori informazioni: {a}", |
||||
"udapp.thisContractMayBeAbstract": "Questo contratto potrebbe essere astratto, potrebbe non implementare completamente i metodi di un contratto genitore astratto o potrebbe non invocare correttamente il costruttore di un contratto ereditato.", |
||||
"udapp.noCompiledContracts": "Nessun contratto compilato", |
||||
"udapp.addressOfContract": "Indirizzo del contratto", |
||||
"udapp.loadContractFromAddress": "Caricare il contratto dall'indirizzo", |
||||
"udapp.ok": "OK", |
||||
"udapp.alert": "Avviso", |
||||
"udapp.proceed": "Procedi", |
||||
"udapp.cancel": "Annulla", |
||||
"udapp.abiFileSelected": "File ABI selezionato", |
||||
"udapp.evmVersion": "versione EVM", |
||||
"udapp.addressNotValid": "L'indirizzo non è valido.", |
||||
"udapp._comment_account.tsx": "libs/remix-ui/run-tab/src/lib/components/account.tsx", |
||||
"udapp.account": "Account", |
||||
"udapp.locales": "Lingua", |
||||
"udapp.themes": "Temi", |
||||
"udapp.signAMessage": "Firma un messaggio", |
||||
"udapp.enterAMessageToSign": "Inserire un messaggio da firmare", |
||||
"udapp.hash": "hash", |
||||
"udapp.signature": "firma", |
||||
"udapp.injectedTitle": "Purtroppo non è possibile creare un account utilizzando un provider iniettato. Per favore crea l'account direttamente dal tuo provider (cioè metamask o altro dello stesso tipo).", |
||||
"udapp.createNewAccount": "Crea un nuovo account", |
||||
"udapp.web3Title": "La creazione di un account è possibile solo in modalità personale. Vai su Impostazioni per abilitarlo.", |
||||
"udapp.defaultTitle": "Purtroppo non è possibile creare un account utilizzando un portafoglio esterno ({selectExEnv}).", |
||||
"udapp.text1": "Fornisci una frase segreta per la creazione dell'account", |
||||
"udapp.tooltipText1": "L'elenco degli account è vuoto, assicurati che il provider corrente sia collegato correttamente a Remix", |
||||
"udapp.modalTitle1": "Passphrase per firmare un messaggio", |
||||
"udapp.modalMessage1": "Inserisci la tua frase segreta per questo account per firmare il messaggio", |
||||
"udapp.copyAccount": "Copia account negli appunti", |
||||
"udapp.signMsgUsingAccount": "Firma un messaggio utilizzando questo account", |
||||
"udapp._comment_environment.tsx": "libs/remix-ui/run-tab/src/lib/components/environment.tsx", |
||||
"udapp.environment": "Ambiente", |
||||
"udapp.environmentDocs": "Fare clic per i documenti sull'Ambiente", |
||||
"udapp.tooltipText2": "Aprire chainlist.org e ottenere le specifiche di connessione della chain con cui si desidera interagire.", |
||||
"udapp.tooltipText3": "Fare clic per aprire un bridge per convertire ETH mainnet L1 nella valuta di rete selezionata.", |
||||
"udapp._comment_instanceContainerUI.tsx": "libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx", |
||||
"udapp.deployedContracts": "Contratti Distribuiti", |
||||
"udapp.deployAndRunClearInstances": "Cancellare l'elenco delle istanze e ripristinare il registratore", |
||||
"udapp.deployAndRunNoInstanceText": "Attualmente non si dispone di istanze contrattuali con cui interagire.", |
||||
"udapp.tooltipText6": "Interfacce utente generiche generate automaticamente per interagire con gli smart contratti deploiati", |
||||
"udapp._comment_recorderCardUI.tsx": "libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx", |
||||
"udapp.transactionsRecorded": "Transazioni registrate", |
||||
"udapp.transactionsCountTooltip": "Il numero di transazioni registrate", |
||||
"udapp.transactionSaveTooltip1": "Nessuna transazione da salvare", |
||||
"udapp.transactionSaveTooltip2": "Salvare la transazione {count} come file di scenario", |
||||
"udapp.transactionSaveTooltip3": "Salvare le transazioni {count} come file di scenario", |
||||
"udapp.infoRecorderTooltip": "Salvare le transazioni (contratti distribuiti ed esecuzioni di funzioni) e riprodurle in un altro ambiente, ad esempio le transazioni create nella macchina virtuale Remix possono essere riprodotte in Injected Provider.", |
||||
"udapp.livemodeRecorderTooltip": "Se i contratti vengono aggiornati dopo la registrazione delle transazioni, selezionando questa casella si eseguono le transazioni registrate con l'ultima copia dei contratti compilati", |
||||
"udapp.livemodeRecorderLabel": "Eseguire le transazioni utilizzando l'ultimo risultato della compilazione", |
||||
"udapp.runRecorderTooltip": "Eseguire una o più transazioni dal file di scenario attuale", |
||||
"udapp.save": "Salva", |
||||
"udapp.run": "Esegui", |
||||
"udapp._comment_contractGUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx", |
||||
"udapp.parameters": "Parametri", |
||||
"udapp.copyParameters": "Copiare i parametri di ingresso codificati negli appunti", |
||||
"udapp.copyCalldata": "Copiare calldata negli appunti", |
||||
"udapp.deployWithProxy": "Distribuisci con Proxy", |
||||
"udapp.upgradeWithProxy": "Aggiorna con Proxy", |
||||
"udapp.getEncodedCallError": "non è possibile codificare argomenti vuoti", |
||||
"udapp.proxyAddressError1": "l'indirizzo proxy non può essere vuoto", |
||||
"udapp.proxyAddressError2": "indirizzo di contratto non valido", |
||||
"udapp.tooltipText11": "L'indirizzo proxy non può essere vuoto", |
||||
"udapp.tooltipText12": "Input richiesto", |
||||
"udapp.tooltipText13": "Deploiato", |
||||
"udapp._comment_universalDappUI.tsx": "libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx", |
||||
"udapp.tooltipText7": "Rimuovi dalla lista", |
||||
"udapp.tooltipText8": "Fare clic per i documenti sull'uso di 'receive'/'fallback'", |
||||
"udapp.tooltipText9": "Il Calldata per inviare alla funzione di fallback del contratto.", |
||||
"udapp.tooltipText10": "Invia dati al contratto.", |
||||
"udapp.balance": "Saldo", |
||||
"udapp.lowLevelInteractions": "Interazioni di livello basso", |
||||
"udapp.llIError1": "Il valore da inviare deve essere un numero", |
||||
"udapp.llIError2": "Per ricevere il trasferimento di Ether il contratto dovrebbe avere una funzione di 'receive' o di 'fallback' da pagare", |
||||
"udapp.llIError3": "Calldata deve essere un valore esadecimale valido con dimensioni di almeno un byte.", |
||||
"udapp.llIError4": "Calldata deve essere un valore esadecimale valido.", |
||||
"udapp.llIError5": "La funzione 'Fallback' non è definita", |
||||
"udapp.llIError6": "Le funzioni 'receive' e 'fallback' non sono definite", |
||||
"udapp.llIError7": "Definisci una funzione 'Fallback' per inviare Calldata e una 'Receive' o 'Fallback' payable per inviare ETH", |
||||
"udapp.copy": "Copia", |
||||
"udapp._comment_mainnet.tsx": "libs/remix-ui/run-tab/src/lib/components/mainnet.tsx", |
||||
"udapp.mainnetText1": "Stai per creare una transazione su {name} Network. Conferma i dettagli per inviare le informazioni al tuo provider.", |
||||
"udapp.mainnetText2": "Il provider per molti utenti è MetaMask. Il provider ti chiederà di firmare la transazione prima che venga inviata alla rete {name}.", |
||||
"udapp.amount": "Importo", |
||||
"udapp.gasEstimation": "Stima del gas", |
||||
"udapp.maxPriorityFee": "Tariffa massima prioritaria", |
||||
"udapp.maxFee": "Commissione massima (Non meno della commissione base {baseFeePerGas} Gwei).", |
||||
"udapp.contractCreation": "Creazione Contratto", |
||||
"udapp.transactionFee": "Transazione non valida. La commissione massima non dovrebbe essere inferiore alla commissione base", |
||||
"udapp.title1": "Rappresenta la parte della commissione della transazione che va al minatore.", |
||||
"udapp.title2": "Rappresenta l'importo massimo della commissione che pagherai per questa transazione. Il minimo deve essere impostato su Base Fee.", |
||||
"udapp.gasPrice": "Prezzo del gas", |
||||
"udapp.gweiText": "visita {a} per informazioni sul prezzo corrente del gas.", |
||||
"udapp.maxTransactionFee": "Tariffa massima transazione", |
||||
"udapp.mainnetText3": "Non mostrare più questo avviso", |
||||
"udapp._comment_run-tab.tsx": "libs/remix-ui/run-tab/src/lib/run-tab.tsx", |
||||
"udapp.gasEstimationPromptText": "Stima del gas errata con il seguente messaggio (vedi sotto). Probabilmente l'esecuzione della transazione fallirà. Vuoi forzare l'invio?", |
||||
"udapp._comment_custom-dropdown.tsx": "libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx", |
||||
"udapp.enterProxyAddress": "Inserisci Indirizzo Proxy", |
||||
"udapp._comment_provider": "apps/remix-ide/src/app/providers", |
||||
"udapp.customVmForkProviderText": "Fornisci informazioni sul fork personalizzato. Se l'URL del nodo non è fornito, la VM inizierà con uno stato vuoto.", |
||||
"udapp.nodeUrl": "Url del nodo", |
||||
"udapp.blockNumber": "Numero blocco (o \"latest\" - \"ultimo\")", |
||||
"udapp.externalHttpProviderText1": "Nota: Per utilizzare Geth & https://remix.ethereum.org, configuralo per consentire le richieste da Remix:(vedi <a>Geth Docs sul server rpc</a>)", |
||||
"udapp.externalHttpProviderText2": "Per eseguire Remix & un nodo di prova Geth locale, usa questo comando: (vedi <a>Geth Docs on Dev mode</a>)", |
||||
"udapp.externalHttpProviderText3": "<b>ATTENZIONE:</b> Non è sicuro usare il flag --http.corsdomain con un wildcard: <b>--http.corsdomain *</b>", |
||||
"udapp.externalHttpProviderText4": "Per ulteriori informazioni: <a>Remix Docs on External HTTP Provider</a>", |
||||
"udapp.foundryProviderText1": "Nota: Per eseguire Anvil sul sistema, eseguire:", |
||||
"udapp.foundryProviderText2": "Per ulteriori informazioni, visita: <a>Foundry Documentation</a>", |
||||
"udapp.ganacheProviderText1": "Nota: Per eseguire Ganache sul sistema, eseguire:", |
||||
"udapp.ganacheProviderText2": "Per ulteriori informazioni, visita: <a>Ganache Documentation</a>", |
||||
"udapp.hardhatProviderText1": "Nota: Per eseguire il nodo di rete Hardhat sul sistema, vai alla cartella del progetto hardhat ed esegui il comando:", |
||||
"udapp.hardhatProviderText2": "Per ulteriori informazioni, visita: <a>Hardhat Documentation</a>", |
||||
"udapp.viewSourceCode": "Visualizza il codice sorgente" |
||||
} |
@ -0,0 +1,18 @@ |
||||
import enJson from '../en'; |
||||
|
||||
function readAndCombineJsonFiles() { |
||||
// @ts-expect-error
|
||||
const dataContext = require.context('./', true, /\.json$/) |
||||
|
||||
let combinedData = {} |
||||
dataContext.keys().forEach((key) => { |
||||
const jsonData = dataContext(key) |
||||
combinedData = { ...combinedData, ...jsonData } |
||||
}) |
||||
|
||||
return combinedData |
||||
} |
||||
|
||||
// There may have some un-translated content. Always fill in the gaps with EN JSON.
|
||||
// No need for a defaultMessage prop when render a FormattedMessage component.
|
||||
export default Object.assign({}, enJson, readAndCombineJsonFiles()); |
@ -0,0 +1,43 @@ |
||||
{ |
||||
"terminal.listen": "监听所有交易", |
||||
"terminal.listenTitle": "如果选中,Remix 将监听在当前环境中挖掘到的所有交易,而不仅仅是您创建的交易", |
||||
"terminal.search": "按交易哈希或地址搜索", |
||||
"terminal.used": "已使用", |
||||
"terminal.debug": "调试", |
||||
"terminal.welcomeText1": "欢迎使用", |
||||
"terminal.welcomeText2": "您的文件储存在", |
||||
"terminal.welcomeText3": "您可使用此终端", |
||||
"terminal.welcomeText4": "查看交易详情并启动调试", |
||||
"terminal.welcomeText5": "执行 JavaScript 脚本", |
||||
"terminal.welcomeText6": "直接在命令行界面输入脚本", |
||||
"terminal.welcomeText7": "在文件浏览器中选择一个 Javascript 文件,然后在命令行界面运行 `remix.execute()` 或 `remix.exeCurrent()` ", |
||||
"terminal.welcomeText8": "在文件浏览器中右键点击一个 JavaScript 文件,然后点击 `Run`", |
||||
"terminal.welcomeText9": "可以访问以下库", |
||||
"terminal.welcomeText10": "输入库名查看可用的指令", |
||||
"terminal.text1": "这类命令已被弃用,不再起作用。请运行 remix.help() 列出可用命令。", |
||||
"terminal.hideTerminal": "隐藏终端", |
||||
"terminal.showTerminal": "展示终端", |
||||
"terminal.clearConsole": "清空控制台", |
||||
"terminal.pendingTransactions": "待处理交易", |
||||
"terminal.toasterMsg1": "没有要执行的内容", |
||||
"terminal.toasterMsg2": "找不到路径 {fileName} 的 provider", |
||||
"terminal.vmMode": "VM 模式", |
||||
"terminal.vmModeMsg": "无法调试此调用。调试调用只能在 Remix VM 模式下进行。", |
||||
"terminal.ok": "确认", |
||||
"terminal.cancel": "取消", |
||||
"terminal.callWarning": "(当被一个合约调用是才需要费用)", |
||||
"terminal.msg1": "交易已打包但执行失败", |
||||
"terminal.msg2": "交易已打包且执行成功", |
||||
"terminal.msg3": "暂无状态信息", |
||||
"terminal.status": "状态", |
||||
"terminal.transactionHash": "交易哈希", |
||||
"terminal.blockHash": "区块哈希", |
||||
"terminal.blockNumber": "区块号", |
||||
"terminal.contractAddress": "合约地址", |
||||
"terminal.transactionCost": "交易成本", |
||||
"terminal.executionCost": "执行成本", |
||||
"terminal.input": "输入", |
||||
"terminal.decodedInput": "解码输入", |
||||
"terminal.decodedOutput": "解码输出", |
||||
"terminal.logs": "日志" |
||||
} |
@ -0,0 +1,141 @@ |
||||
{ |
||||
"udapp.displayName": "部署 & 发交易", |
||||
"udapp._comment_gasPrice.tsx": "libs/remix-ui/run-tab/src/lib/components/gasPrice.tsx", |
||||
"udapp.gasLimit": "Gas 上限", |
||||
"udapp.tooltipText4": "默认 Gas 限制为 3M。根据需要进行调整。", |
||||
"udapp._comment_value.tsx": "libs/remix-ui/run-tab/src/lib/components/value.tsx", |
||||
"udapp.value": "以太币数量", |
||||
"udapp.tooltipText5": "输入金额并选择单位", |
||||
"udapp._comment_contractDropdownUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx", |
||||
"udapp.contract": "合约", |
||||
"udapp.compiledBy": "由 {compilerName} 编译", |
||||
"udapp.warningEvmVersion": "请确保当前网络与此 evm 版本兼容:{evmVersion}。否则任何部署都会失败。", |
||||
"udapp.infoSyncCompiledContractTooltip": "单击此处导入从外部框架编译的合约。{br}当 Remix 通过 remixd 连接到外部{br}框架 (hardhat、truffle、foundry) 时启用此操作。", |
||||
"udapp.remixIpfsUdappTooltip": "将源代码和元数据发布到 IPFS 有助于{br}使用 Sourcify 验证源代码,并将极大地促进{br}合约采用(审计、调试、调用等)", |
||||
"udapp.deploy": "部署", |
||||
"udapp.publishTo": "发布到", |
||||
"udapp.atAddress": "At Address", |
||||
"udapp.atAddressOptionsTitle1": "合约地址", |
||||
"udapp.atAddressOptionsTitle2": "与已部署的合约交互 - 需要在编辑器中选择 .abi 文件或{br}编译的 .sol 文件{br}(具有相同的编译器配置)", |
||||
"udapp.atAddressOptionsTitle3": "编译一个 *.sol 文件或选中一个 *.abi 文件。", |
||||
"udapp.atAddressOptionsTitle4": "要与已部署的合约进行交互,{br} 输入其地址并编译其源 *.sol 文件{br}(使用相同的编译器设置)或在编辑器中选择其 .abi 文件。", |
||||
"udapp.contractOptionsTitle1": "请编译 *.sol 文件以部署或访问合约", |
||||
"udapp.contractOptionsTitle2": "选择要部署或与 At Address 一起使用的已编译合约。", |
||||
"udapp.contractOptionsTitle3": "选择并编译 *.sol 文件以部署或访问合约。", |
||||
"udapp.contractOptionsTitle4": "当有编译的 .sol 文件时,选择 {br} 合约进行部署或与 AtAddress 一起使用。", |
||||
"udapp.checkSumWarning": "您似乎没有使用 checksumed address 。{br} checksumed address 是包含大写字母的地址,如 {a} 中所指定。{br} checksumed address 旨在帮助防止用户将交易发送到错误地址。", |
||||
"udapp.isOverSizePromptEip170": "合约创建初始化返回长度超过24576字节的数据。部署可能会失败。 {br}更多信息:{a}", |
||||
"udapp.isOverSizePromptEip3860": "合约创建初始化代码超出了允许的最大代码大小 49152 字节。如果当前网络已激活 eip 3860,则部署可能会失败。更多信息:{a}", |
||||
"udapp.thisContractMayBeAbstract": "这个合约可能是抽象的,它可能没有完全实现抽象父类的方法,或者它可能没有正确调用继承合约的构造函数。", |
||||
"udapp.noCompiledContracts": "没有已编译的合约", |
||||
"udapp.addressOfContract": "合约地址", |
||||
"udapp.loadContractFromAddress": "加载此地址的合约", |
||||
"udapp.ok": "确认", |
||||
"udapp.alert": "警告", |
||||
"udapp.proceed": "继续", |
||||
"udapp.cancel": "取消", |
||||
"udapp.abiFileSelected": "已选择 ABI 文件", |
||||
"udapp.evmVersion": "evm 版本", |
||||
"udapp.addressNotValid": "该地址无效", |
||||
"udapp._comment_account.tsx": "libs/remix-ui/run-tab/src/lib/components/account.tsx", |
||||
"udapp.account": "账户", |
||||
"udapp.locales": "语言", |
||||
"udapp.themes": "主题", |
||||
"udapp.signAMessage": "给一个消息签名", |
||||
"udapp.enterAMessageToSign": "输入一个需要签名的消息", |
||||
"udapp.hash": "哈希", |
||||
"udapp.signature": "签名", |
||||
"udapp.injectedTitle": "很遗憾,使用 injected provider 无法创建账户。请直接在 injected provider(如 metamask 或其他同类型程序)中创建账户。", |
||||
"udapp.createNewAccount": "创建一个新账户", |
||||
"udapp.web3Title": "只有在个人模式下才能创建账户。请前往 \"设置\" 启用。", |
||||
"udapp.defaultTitle": "很遗憾,无法使用外部钱包 ({selectExEnv}) 创建帐户。", |
||||
"udapp.text1": "请提供用于创建帐户的密码", |
||||
"udapp.tooltipText1": "帐户列表为空,请确保当前 provider 已正确连接到 remix", |
||||
"udapp.modalTitle1": "用于签署消息的密码", |
||||
"udapp.modalMessage1": "输入此帐户的密码以签署消息", |
||||
"udapp.copyAccount": "将帐户复制到剪贴板", |
||||
"udapp.signMsgUsingAccount": "使用此帐户签署消息", |
||||
"udapp._comment_environment.tsx": "libs/remix-ui/run-tab/src/lib/components/environment.tsx", |
||||
"udapp.environment": "环境", |
||||
"udapp.environmentDocs": "点击查看环境文档", |
||||
"udapp.tooltipText2": "打开 chainlist.org 并获取您想要交互的链的连接规范。", |
||||
"udapp.tooltipText3": "单击可打开用于将 L1 主网 ETH 转换为所选网络货币的桥接器。", |
||||
"udapp._comment_instanceContainerUI.tsx": "libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx", |
||||
"udapp.deployedContracts": "已部署的合约", |
||||
"udapp.deployAndRunClearInstances": "清空合约实例并重置交易记录", |
||||
"udapp.deployAndRunNoInstanceText": "当前您没有可交互的合约实例.", |
||||
"udapp.tooltipText6": "自动生成的通用用户界面,用于与已部署的合约进行交互", |
||||
"udapp._comment_recorderCardUI.tsx": "libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx", |
||||
"udapp.transactionsRecorded": "已记录的交易", |
||||
"udapp.transactionsCountTooltip": "已记录的交易数", |
||||
"udapp.transactionSaveTooltip1": "没有可保存的交易", |
||||
"udapp.transactionSaveTooltip2": "将 {count} 笔交易保存到一个场景文件", |
||||
"udapp.transactionSaveTooltip3": "将 {count} 笔交易保存到一个场景文件", |
||||
"udapp.infoRecorderTooltip": "保存交易 (合约部署和方法执行) {br}然后在另一个环境中回放,比如在 Remix VM {br}创建的交易可以在 Injected Provider 中回放。", |
||||
"udapp.livemodeRecorderTooltip": "如果记录交易后合约有更新,{br}选中这个复选框就会以最新的{br}合约编译结果执行已记录的交易", |
||||
"udapp.livemodeRecorderLabel": "用最新的编译结果执行交易", |
||||
"udapp.runRecorderTooltip": "从当前场景文件中执行交易", |
||||
"udapp.save": "保存", |
||||
"udapp.run": "执行", |
||||
"udapp._comment_contractGUI.tsx": "libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx", |
||||
"udapp.parameters": "参数", |
||||
"udapp.copyParameters": "复制已编码的输入参数到粘贴板", |
||||
"udapp.copyCalldata": "复制 calldata 到粘贴板", |
||||
"udapp.deployWithProxy": "使用代理部署", |
||||
"udapp.upgradeWithProxy": "使用代理升级", |
||||
"udapp.getEncodedCallError": "无法对空参数进行编码", |
||||
"udapp.proxyAddressError1": "代理地址不能为空", |
||||
"udapp.proxyAddressError2": "不是有效的合约地址", |
||||
"udapp.tooltipText11": "代理地址不能为空", |
||||
"udapp.tooltipText12": "必填", |
||||
"udapp.tooltipText13": "已部署 {date}", |
||||
"udapp._comment_universalDappUI.tsx": "libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx", |
||||
"udapp.tooltipText7": "从列表中删除", |
||||
"udapp.tooltipText8": "点击查看有关使用 'receive'/'fallback' 的文档", |
||||
"udapp.tooltipText9": "发送到合约 fallback 函数的 Calldata。", |
||||
"udapp.tooltipText10": "将数据发送到合约。", |
||||
"udapp.balance": "余额", |
||||
"udapp.lowLevelInteractions": "低级交互", |
||||
"udapp.llIError1": "要发送的值应该是一个数字", |
||||
"udapp.llIError2": "为了接收以太币传输,合约应该具有 'receive' 或可支付的 'fallback' 功能", |
||||
"udapp.llIError3": "calldata 应是有效的十六进制值,大小至少为 1 个字节。", |
||||
"udapp.llIError4": "calldata 应该是有效的十六进制值。", |
||||
"udapp.llIError5": "'Fallback' 函数未定义", |
||||
"udapp.llIError6": "'receive' 和 'fallback' 函数都未定义", |
||||
"udapp.llIError7": "定义一个 'Fallback' 函数来发送 calldata ,并且定义一个 'Receive' 或 可支付的 'Fallback' 来发送以太币", |
||||
"udapp.copy": "复制", |
||||
"udapp._comment_mainnet.tsx": "libs/remix-ui/run-tab/src/lib/components/mainnet.tsx", |
||||
"udapp.mainnetText1": "您即将在 {name} 网络上创建一笔交易。确认详细信息,将信息发送给您的 provider 。", |
||||
"udapp.mainnetText2": "许多用户的 provider 是 MetaMask。在将交易发送到 {name} 网络之前,provider 会要求您签署交易。", |
||||
"udapp.amount": "金额", |
||||
"udapp.gasEstimation": "Gas 预算", |
||||
"udapp.maxPriorityFee": "最高优先权费用", |
||||
"udapp.maxFee": "最高费用(不低于基本费用{baseFeePerGas} Gwei)", |
||||
"udapp.contractCreation": "合约创建", |
||||
"udapp.transactionFee": "交易无效。最高费用不应低于基本费用", |
||||
"udapp.title1": "代表支付给矿工的交易费用部分。", |
||||
"udapp.title2": "代表您为此交易支付的最高费用金额。最低费用需要设置为基本费用。", |
||||
"udapp.gasPrice": "Gas 价格", |
||||
"udapp.gweiText": "请访问 {a} 以获取当前的 Gas 价格信息。", |
||||
"udapp.maxTransactionFee": "最高交易费用", |
||||
"udapp.mainnetText3": "不要再显示此警告。", |
||||
"udapp._comment_run-tab.tsx": "/libs/remix-ui/run-tab/src/lib/run-tab.tsx", |
||||
"udapp.gasEstimationPromptText": "Gas 预算错误并显示以下消息(见下文)。交易执行可能会失败。您想强制发送吗?", |
||||
"udapp._comment_custom-dropdown.tsx": "libs/remix-ui/helper/src/lib/components/custom-dropdown.tsx", |
||||
"udapp.enterProxyAddress": "输入代理地址", |
||||
"udapp._comment_provider": "apps/remix-ide/src/app/providers", |
||||
"udapp.customVmForkProviderText": "请提供有关 custom fork 的信息。如果未提供节点 URL,VM 将以空状态启动。", |
||||
"udapp.nodeUrl": "节点 URL", |
||||
"udapp.blockNumber": "块号 (默认 \"latest\")", |
||||
"udapp.externalHttpProviderText1": "注意: 要用 Geth & https://remix.ethereum.org, 配置使它允许来自 Remix 的请求:(看 <a>Geth rpc 服务的文档</a>)", |
||||
"udapp.externalHttpProviderText2": "要运行 Remix & 一个本地的 Geth 测试节点, 使用这个命令: (看 <a>Geth 开发模式文档</a>)", |
||||
"udapp.externalHttpProviderText3": "<b>警告:</b> --http.corsdomain 使用通配符很不安全: <b>--http.corsdomain *</b>", |
||||
"udapp.externalHttpProviderText4": "更多信息: <a>Remix External HTTP Provider 文档</a>", |
||||
"udapp.foundryProviderText1": "注意: 要在您的系统里运行 Anvil, 运行:", |
||||
"udapp.foundryProviderText2": "更多信息, 访问: <a>Foundry 文档</a>", |
||||
"udapp.ganacheProviderText1": "注意: 要在您的系统里运行 Ganache, 运行:", |
||||
"udapp.ganacheProviderText2": "更多信息, 访问: <a>Ganache 文档</a>", |
||||
"udapp.hardhatProviderText1": "注意: 要在您的系统里运行 Hardhat 网络节点, 进到 hardhat 项目目录并且运行命令:", |
||||
"udapp.hardhatProviderText2": "更多信息, 访问: <a>Hardhat 文档</a>", |
||||
"udapp.viewSourceCode": "查看源码" |
||||
} |
@ -0,0 +1,9 @@ |
||||
import React from 'react'; |
||||
import ReactDOM from 'react-dom/client'; |
||||
import App from './App'; |
||||
import './index.css'; |
||||
|
||||
const root = ReactDOM.createRoot( |
||||
document.getElementById('root') as HTMLElement |
||||
); |
||||
root.render(<App />); |
@ -0,0 +1,51 @@ |
||||
export const appInitialState: any = { |
||||
instance: { |
||||
name: '', |
||||
address: '', |
||||
balance: 0, |
||||
network: '', |
||||
decodedResponse: {}, |
||||
abi: [], |
||||
solcVersion: {}, |
||||
containers: [] |
||||
}, |
||||
settings: { |
||||
sendValue: '0', |
||||
sendUnit: 'wei', |
||||
gasLimit: 3000000, |
||||
networkName: 'Goerli', |
||||
loadedAccounts: {}, |
||||
isRequesting: false, |
||||
isSuccessful: false, |
||||
error: null, |
||||
selectedAccount: '', |
||||
selectedLocaleCode: 'en', |
||||
provider: window.ethereum ? 'metamask' : 'walletconnect', |
||||
}, |
||||
terminal: { journalBlocks: [], hidden: false, height: 250 }, |
||||
}; |
||||
|
||||
export const appReducer = (state = appInitialState, action: any): any => { |
||||
switch (action.type) { |
||||
case 'SET_INSTANCE': |
||||
return { |
||||
...state, |
||||
instance: { ...state.instance, ...action.payload }, |
||||
}; |
||||
|
||||
case 'SET_SETTINGS': |
||||
return { |
||||
...state, |
||||
settings: { ...state.settings, ...action.payload }, |
||||
}; |
||||
|
||||
case 'SET_TERMINAL': |
||||
return { |
||||
...state, |
||||
terminal: { ...state.terminal, ...action.payload }, |
||||
}; |
||||
|
||||
default: |
||||
throw new Error(); |
||||
} |
||||
}; |
@ -0,0 +1,13 @@ |
||||
export interface RequestArguments { |
||||
readonly method: string; |
||||
readonly params?: readonly unknown[] | object; |
||||
readonly id?: string; |
||||
} |
||||
|
||||
export type Chain = { |
||||
chainId: number; |
||||
name: string; |
||||
currency: string; |
||||
explorerUrl: string; |
||||
rpcUrl: string; |
||||
}; |
@ -0,0 +1,41 @@ |
||||
import { execution } from '@remix-project/remix-lib'; |
||||
|
||||
const { txFormat: { parseFunctionParams }, txHelper: { encodeFunctionId, encodeParams: encodeParamsHelper } } = execution |
||||
|
||||
const buildData = (funAbi: any, params: string) => { |
||||
let funArgs = []; |
||||
let data: Buffer | string = ''; |
||||
let dataHex = ''; |
||||
|
||||
if (params.indexOf('raw:0x') === 0) { |
||||
// in that case we consider that the input is already encoded and *does not* contain the method signature
|
||||
dataHex = params.replace('raw:0x', ''); |
||||
data = Buffer.from(dataHex, 'hex'); |
||||
} else { |
||||
try { |
||||
if (params.length > 0) { |
||||
funArgs = parseFunctionParams(params); |
||||
} |
||||
} catch (e) { |
||||
return { error: 'Error encoding arguments: ' + e }; |
||||
} |
||||
try { |
||||
data = encodeParamsHelper(funAbi, funArgs); |
||||
dataHex = data.toString(); |
||||
} catch (e) { |
||||
return { error: 'Error encoding arguments: ' + e }; |
||||
} |
||||
if (data.slice(0, 9) === 'undefined') { |
||||
dataHex = data.slice(9); |
||||
} |
||||
if (data.slice(0, 2) === '0x') { |
||||
dataHex = data.slice(2); |
||||
} |
||||
} |
||||
|
||||
dataHex = encodeFunctionId(funAbi) + dataHex; |
||||
|
||||
return { dataHex }; |
||||
}; |
||||
|
||||
export default buildData; |
@ -0,0 +1,151 @@ |
||||
export const mainnet = { |
||||
chainId: 1, |
||||
name: 'Ethereum', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://etherscan.io', |
||||
rpcUrl: 'https://cloudflare-eth.com', |
||||
}; |
||||
|
||||
export const sepolia = { |
||||
chainId: 11155111, |
||||
name: 'Sepolia', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://sepolia.etherscan.io', |
||||
rpcUrl: 'https://sepolia.infura.io/v3/b6bf7d3508c941499b10025c0776eaf8', |
||||
}; |
||||
|
||||
export const goerli = { |
||||
chainId: 5, |
||||
name: 'Goerli', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://goerli.etherscan.io', |
||||
rpcUrl: 'https://rpc.ankr.com/eth_goerli', |
||||
}; |
||||
|
||||
export const arbitrum = { |
||||
chainId: 42161, |
||||
name: 'Arbitrum', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://arbiscan.io', |
||||
rpcUrl: 'https://arb1.arbitrum.io/rpc', |
||||
}; |
||||
|
||||
export const arbitrumGoerli = { |
||||
chainId: 421613, |
||||
name: 'Arbitrum Goerli', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://goerli.arbiscan.io', |
||||
rpcUrl: 'https://goerli-rollup.arbitrum.io/rpc', |
||||
}; |
||||
|
||||
export const avalanche = { |
||||
chainId: 43114, |
||||
name: 'Avalanche', |
||||
currency: 'AVAX', |
||||
explorerUrl: 'https://snowtrace.io', |
||||
rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', |
||||
}; |
||||
|
||||
export const bsc = { |
||||
chainId: 56, |
||||
name: 'Binance Smart Chain', |
||||
currency: 'BNB', |
||||
explorerUrl: 'https://bscscan.com', |
||||
rpcUrl: 'https://rpc.ankr.com/bsc', |
||||
}; |
||||
|
||||
export const optimism = { |
||||
chainId: 10, |
||||
name: 'Optimism', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://optimistic.etherscan.io', |
||||
rpcUrl: 'https://mainnet.optimism.io', |
||||
}; |
||||
|
||||
export const optimismGoerli = { |
||||
chainId: 420, |
||||
name: 'Optimism Goerli', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://goerli-optimism.etherscan.io', |
||||
rpcUrl: 'https://goerli.optimism.io', |
||||
}; |
||||
|
||||
export const polygon = { |
||||
chainId: 137, |
||||
name: 'Polygon', |
||||
currency: 'MATIC', |
||||
explorerUrl: 'https://polygonscan.com', |
||||
rpcUrl: 'https://polygon-rpc.com', |
||||
}; |
||||
|
||||
export const polygonMumbai = { |
||||
chainId: 80001, |
||||
name: 'Polygon Mumbai', |
||||
currency: 'MATIC', |
||||
explorerUrl: 'https://mumbai.polygonscan.com', |
||||
rpcUrl: 'https://rpc.ankr.com/polygon_mumbai', |
||||
}; |
||||
|
||||
export const gnosis = { |
||||
chainId: 100, |
||||
name: 'Gnosis', |
||||
currency: 'xDAI', |
||||
explorerUrl: 'https://gnosis.blockscout.com', |
||||
rpcUrl: 'https://rpc.gnosischain.com', |
||||
}; |
||||
|
||||
export const zkSync = { |
||||
chainId: 324, |
||||
name: 'ZkSync', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://explorer.zksync.io', |
||||
rpcUrl: 'https://mainnet.era.zksync.io', |
||||
}; |
||||
|
||||
export const zora = { |
||||
chainId: 7777777, |
||||
name: 'Zora', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://explorer.zora.energy', |
||||
rpcUrl: 'https://rpc.zora.energy', |
||||
}; |
||||
|
||||
export const celo = { |
||||
chainId: 42220, |
||||
name: 'Celo', |
||||
currency: 'CELO', |
||||
explorerUrl: 'https://explorer.celo.org/mainnet', |
||||
rpcUrl: 'https://forno.celo.org', |
||||
}; |
||||
|
||||
export const base = { |
||||
chainId: 8453, |
||||
name: 'Base', |
||||
currency: 'BASE', |
||||
explorerUrl: 'https://basescan.org', |
||||
rpcUrl: 'https://mainnet.base.org', |
||||
}; |
||||
|
||||
export const aurora = { |
||||
chainId: 1313161554, |
||||
name: 'Aurora', |
||||
currency: 'ETH', |
||||
explorerUrl: 'https://explorer.aurora.dev', |
||||
rpcUrl: 'https://mainnet.aurora.dev', |
||||
}; |
||||
|
||||
export const ronin = { |
||||
chainId: 2020, |
||||
name: 'Ronin', |
||||
currency: 'RON', |
||||
explorerUrl: 'https://app.roninchain.com', |
||||
rpcUrl: 'https://api.roninchain.com/rpc', |
||||
}; |
||||
|
||||
export const saigon = { |
||||
chainId: 2021, |
||||
name: 'Saigon Testnet', |
||||
currency: 'RON', |
||||
explorerUrl: 'https://saigon-explorer.roninchain.com', |
||||
rpcUrl: 'https://saigon-testnet.roninchain.com/rpc', |
||||
}; |
@ -0,0 +1,53 @@ |
||||
import { |
||||
arbitrum, |
||||
arbitrumGoerli, |
||||
mainnet, |
||||
polygon, |
||||
polygonMumbai, |
||||
optimism, |
||||
optimismGoerli, |
||||
goerli, |
||||
sepolia, |
||||
ronin, |
||||
saigon, |
||||
aurora, |
||||
avalanche, |
||||
base, |
||||
bsc, |
||||
celo, |
||||
gnosis, |
||||
zkSync, |
||||
zora, |
||||
} from './chains'; |
||||
|
||||
export const constants = { |
||||
chains: [ |
||||
arbitrum, |
||||
arbitrumGoerli, |
||||
mainnet, |
||||
polygon, |
||||
polygonMumbai, |
||||
optimism, |
||||
optimismGoerli, |
||||
goerli, |
||||
sepolia, |
||||
ronin, |
||||
saigon, |
||||
aurora, |
||||
avalanche, |
||||
base, |
||||
bsc, |
||||
celo, |
||||
gnosis, |
||||
zkSync, |
||||
zora, |
||||
], |
||||
}; |
||||
|
||||
export const PROJECT_ID = 'a4ba105e642ae64cdb5b9a86debc0a66'; |
||||
export const METADATA = { |
||||
name: 'Quick Dapp', |
||||
description: 'Quick Dapp', |
||||
url: window.origin, |
||||
icons: ['https://remix.ethereum.org/favicon.ico'], |
||||
}; |
@ -0,0 +1,96 @@ |
||||
import { RequestArguments } from '../types'; |
||||
|
||||
class MetaMask { |
||||
async addCustomNetwork(chainId: any) { |
||||
const paramsObj: any = { chainId }; |
||||
if (chainId === '0xa') { |
||||
paramsObj.chainName = 'Optimism'; |
||||
paramsObj.rpcUrls = ['https://mainnet.optimism.io']; |
||||
} |
||||
if (chainId === '0xa4b1') { |
||||
paramsObj.chainName = 'Arbitrum One'; |
||||
paramsObj.rpcUrls = ['https://arb1.arbitrum.io/rpc']; |
||||
} |
||||
if (chainId === '0x50877ed6') { |
||||
paramsObj.chainName = 'SKALE Chaos Testnet'; |
||||
paramsObj.rpcUrls = [ |
||||
'https://staging-v3.skalenodes.com/v1/staging-fast-active-bellatrix', |
||||
]; |
||||
paramsObj.nativeCurrency = { |
||||
name: 'sFUEL', |
||||
symbol: 'sFUEL', |
||||
decimals: 18, |
||||
}; |
||||
} |
||||
|
||||
const { chainName, rpcUrls, nativeCurrency, blockExplorerUrls } = paramsObj; |
||||
try { |
||||
await (window as any).ethereum.request({ |
||||
method: 'wallet_switchEthereumChain', |
||||
params: [{ chainId: chainId }], |
||||
}); |
||||
} catch (switchError: any) { |
||||
// This error code indicates that the chain has not been added to MetaMask.
|
||||
if (switchError.code === 4902) { |
||||
try { |
||||
const paramsObj: Record<string, any> = { |
||||
chainId: chainId, |
||||
chainName: chainName, |
||||
rpcUrls: rpcUrls, |
||||
}; |
||||
if (nativeCurrency) paramsObj.nativeCurrency = nativeCurrency; |
||||
if (blockExplorerUrls) |
||||
paramsObj.blockExplorerUrls = blockExplorerUrls; |
||||
await (window as any).ethereum.request({ |
||||
method: 'wallet_addEthereumChain', |
||||
params: [paramsObj], |
||||
}); |
||||
|
||||
await (window as any).ethereum.request({ |
||||
method: 'wallet_switchEthereumChain', |
||||
params: [{ chainId: chainId }], |
||||
}); |
||||
} catch (addError) { |
||||
// handle "add" error
|
||||
} |
||||
} |
||||
// handle other "switch" errors
|
||||
} |
||||
} |
||||
|
||||
async sendAsync(data: RequestArguments) { |
||||
return new Promise((resolve) => { |
||||
//@ts-expect-error sendAsync is a legacy function we know MetaMask supports it
|
||||
window.ethereum.sendAsync(data, (error, response) => { |
||||
if (error) { |
||||
if ( |
||||
error.data && |
||||
error.data.originalError && |
||||
error.data.originalError.data |
||||
) { |
||||
resolve({ |
||||
jsonrpc: '2.0', |
||||
error: error.data.originalError, |
||||
id: data.id, |
||||
}); |
||||
} else if (error.data && error.data.message) { |
||||
resolve({ |
||||
jsonrpc: '2.0', |
||||
error: error.data && error.data, |
||||
id: data.id, |
||||
}); |
||||
} else { |
||||
resolve({ |
||||
jsonrpc: '2.0', |
||||
error, |
||||
id: data.id, |
||||
}); |
||||
} |
||||
} |
||||
return resolve(response); |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
export default new MetaMask(); |
@ -0,0 +1,17 @@ |
||||
export function isMobile() { |
||||
const userAgentInfo = navigator.userAgent; |
||||
const mobileAgents = [ |
||||
'Android', |
||||
'iPhone', |
||||
'SymbianOS', |
||||
'Windows Phone', |
||||
'iPad', |
||||
'iPod', |
||||
]; |
||||
for (let v = 0; v < mobileAgents.length; v++) { |
||||
if (userAgentInfo.indexOf(mobileAgents[v]) > 0) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
@ -0,0 +1,277 @@ |
||||
import Web3, { |
||||
FMT_NUMBER, |
||||
type EthExecutionAPI, |
||||
type SupportedProviders, |
||||
FMT_BYTES, |
||||
type Bytes, |
||||
} from 'web3'; |
||||
import { addHexPrefix, toBytes } from '@ethereumjs/util'; |
||||
import { execution } from '@remix-project/remix-lib'; |
||||
import { toBigInt } from 'web3-utils'; |
||||
import { saveSettings } from '../actions'; |
||||
|
||||
const web3 = new Web3(); |
||||
|
||||
export const shortenAddress = (address: string, etherBalance?: string) => { |
||||
const len = address.length; |
||||
|
||||
return ( |
||||
address.slice(0, 5) + |
||||
'...' + |
||||
address.slice(len - 5, len) + |
||||
(etherBalance ? ' (' + etherBalance.toString() + ' ether)' : '') |
||||
); |
||||
}; |
||||
|
||||
async function pause() { |
||||
return await new Promise((resolve, reject) => { |
||||
setTimeout(resolve, 1000); |
||||
}); |
||||
} |
||||
|
||||
async function tryTillReceiptAvailable(txhash: Bytes) { |
||||
try { |
||||
const receipt = await web3.eth.getTransactionReceipt(txhash, { |
||||
number: FMT_NUMBER.NUMBER, |
||||
bytes: FMT_BYTES.HEX, |
||||
}); |
||||
if (receipt) { |
||||
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...' |
||||
); |
||||
return receipt; |
||||
} else return receipt; |
||||
} |
||||
} catch (e) { |
||||
/* empty */ |
||||
} |
||||
await pause(); |
||||
// eslint-disable-next-line @typescript-eslint/return-await
|
||||
return await tryTillReceiptAvailable(txhash); |
||||
} |
||||
|
||||
async function tryTillTxAvailable(txhash: Bytes) { |
||||
try { |
||||
const tx = await web3.eth.getTransaction(txhash, { |
||||
number: FMT_NUMBER.NUMBER, |
||||
bytes: FMT_BYTES.HEX, |
||||
}); |
||||
if (tx?.blockHash) return tx; |
||||
return tx; |
||||
} catch (e) { |
||||
/* empty */ |
||||
} |
||||
// eslint-disable-next-line @typescript-eslint/return-await
|
||||
return await tryTillTxAvailable(txhash); |
||||
} |
||||
|
||||
export class TxRunner { |
||||
lastBlock: any; |
||||
currentFork: string; |
||||
listenOnLastBlockId: any; |
||||
mainNetGenesisHash: string | undefined; |
||||
blockGasLimit: any; |
||||
blockGasLimitDefault: any; |
||||
|
||||
constructor() { |
||||
this.lastBlock = null; |
||||
this.blockGasLimitDefault = 4300000; |
||||
this.blockGasLimit = this.blockGasLimitDefault; |
||||
this.currentFork = 'shanghai'; |
||||
this.mainNetGenesisHash = |
||||
'0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'; |
||||
|
||||
this.listenOnLastBlock(); |
||||
|
||||
setInterval(() => { |
||||
this.getAccounts(); |
||||
}, 30000); |
||||
} |
||||
|
||||
setProvider( |
||||
provider: string | SupportedProviders<EthExecutionAPI> | undefined |
||||
) { |
||||
web3.setProvider(provider); |
||||
} |
||||
|
||||
getAccounts() { |
||||
saveSettings({ isRequesting: true }); |
||||
void web3.eth |
||||
.getAccounts() |
||||
.then(async (accounts) => { |
||||
const loadedAccounts: any = {}; |
||||
for (const account of accounts) { |
||||
const balance = await this.getBalanceInEther(account); |
||||
loadedAccounts[account] = shortenAddress(account, balance); |
||||
} |
||||
saveSettings({ loadedAccounts, isRequesting: false }); |
||||
}) |
||||
.catch((err) => { |
||||
console.log(err); |
||||
saveSettings({ isRequesting: false }); |
||||
}); |
||||
} |
||||
|
||||
async getBalanceInEther(address: string) { |
||||
const balance = await web3.eth.getBalance(address); |
||||
return Web3.utils.fromWei(balance.toString(10), 'ether'); |
||||
} |
||||
|
||||
async getGasPrice() { |
||||
return await web3.eth.getGasPrice(); |
||||
} |
||||
|
||||
async runTx(tx: any, gasLimit: any, useCall: boolean) { |
||||
if (useCall) { |
||||
const returnValue = await web3.eth.call({ ...tx, gas: gasLimit }); |
||||
|
||||
return toBytes(addHexPrefix(returnValue)); |
||||
} |
||||
|
||||
const network = await this.detectNetwork(); |
||||
|
||||
const txCopy = { |
||||
...tx, |
||||
type: undefined, |
||||
maxFeePerGas: undefined, |
||||
gasPrice: undefined, |
||||
}; |
||||
|
||||
if (network?.lastBlock) { |
||||
if (network.lastBlock.baseFeePerGas) { |
||||
// the sending stack (web3.js / metamask need to have the type defined)
|
||||
// this is to avoid the following issue: https://github.com/MetaMask/metamask-extension/issues/11824
|
||||
txCopy.type = '0x2'; |
||||
txCopy.maxFeePerGas = Math.ceil( |
||||
Number( |
||||
( |
||||
toBigInt(network.lastBlock.baseFeePerGas) + |
||||
toBigInt(network.lastBlock.baseFeePerGas) / BigInt(3) |
||||
).toString() |
||||
) |
||||
); |
||||
} else { |
||||
txCopy.type = '0x1'; |
||||
txCopy.gasPrice = undefined; |
||||
} |
||||
} |
||||
|
||||
try { |
||||
const gasEstimation = await web3.eth.estimateGas(txCopy); |
||||
tx.gas = !gasEstimation ? gasLimit : gasEstimation; |
||||
return await this._executeTx(tx, network); |
||||
} catch (error) { |
||||
console.log(error); |
||||
return { error }; |
||||
} |
||||
} |
||||
|
||||
async detectNetwork() { |
||||
const id = Number(await web3.eth.net.getId()); |
||||
let name = ''; |
||||
if (id === 1) name = 'Main'; |
||||
else if (id === 3) name = 'Ropsten'; |
||||
else if (id === 4) name = 'Rinkeby'; |
||||
else if (id === 5) name = 'Goerli'; |
||||
else if (id === 42) name = 'Kovan'; |
||||
else if (id === 11155111) name = 'Sepolia'; |
||||
else name = 'Custom'; |
||||
|
||||
if (id === 1) { |
||||
const block = await web3.eth.getBlock(0); |
||||
if (block && block.hash !== this.mainNetGenesisHash) name = 'Custom'; |
||||
return { |
||||
id, |
||||
name, |
||||
lastBlock: this.lastBlock, |
||||
currentFork: this.currentFork, |
||||
}; |
||||
} else { |
||||
return { |
||||
id, |
||||
name, |
||||
lastBlock: this.lastBlock, |
||||
currentFork: this.currentFork, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
stopListenOnLastBlock() { |
||||
if (this.listenOnLastBlockId) clearInterval(this.listenOnLastBlockId); |
||||
this.listenOnLastBlockId = null; |
||||
} |
||||
|
||||
async _updateChainContext() { |
||||
try { |
||||
const block = await web3.eth.getBlock('latest'); |
||||
// we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506
|
||||
this.blockGasLimit = block?.gasLimit |
||||
? Math.floor( |
||||
Number(web3.utils.toNumber(block.gasLimit)) - |
||||
(5 * Number(web3.utils.toNumber(block.gasLimit))) / 1024 |
||||
) |
||||
: web3.utils.toNumber(this.blockGasLimitDefault); |
||||
this.lastBlock = block; |
||||
try { |
||||
this.currentFork = execution.forkAt( |
||||
await web3.eth.net.getId(), |
||||
block.number |
||||
); |
||||
} catch (e) { |
||||
this.currentFork = 'merge'; |
||||
console.log( |
||||
`unable to detect fork, defaulting to ${this.currentFork}..` |
||||
); |
||||
console.error(e); |
||||
} |
||||
} catch (e) { |
||||
console.error(e); |
||||
this.blockGasLimit = this.blockGasLimitDefault; |
||||
} |
||||
} |
||||
|
||||
listenOnLastBlock() { |
||||
this.listenOnLastBlockId = setInterval(() => { |
||||
void this._updateChainContext(); |
||||
}, 15000); |
||||
} |
||||
|
||||
async _executeTx(tx: any, network: any) { |
||||
if (network?.lastBlock?.baseFeePerGas) { |
||||
// the sending stack (web3.js / metamask need to have the type defined)
|
||||
// this is to avoid the following issue: https://github.com/MetaMask/metamask-extension/issues/11824
|
||||
tx.type = '0x2'; |
||||
} |
||||
|
||||
let currentDateTime = new Date(); |
||||
const start = currentDateTime.getTime() / 1000; |
||||
|
||||
try { |
||||
const { transactionHash } = await web3.eth.sendTransaction( |
||||
tx, |
||||
undefined, |
||||
{ checkRevertBeforeSending: false, ignoreGasPricing: true } |
||||
); |
||||
const receipt = await tryTillReceiptAvailable(transactionHash); |
||||
tx = await tryTillTxAvailable(transactionHash); |
||||
|
||||
currentDateTime = new Date(); |
||||
const end = currentDateTime.getTime() / 1000; |
||||
console.log('tx duration', end - start); |
||||
return { |
||||
receipt, |
||||
tx, |
||||
transactionHash: receipt ? receipt.transactionHash : null, |
||||
}; |
||||
} catch (error: any) { |
||||
console.log( |
||||
`Send transaction failed: ${error.message} . if you use an injected provider, please check it is properly unlocked. ` |
||||
); |
||||
return { error }; |
||||
} |
||||
} |
||||
} |
||||
|
||||
export default new TxRunner(); |
@ -0,0 +1,129 @@ |
||||
import { createWeb3Modal, defaultConfig } from '@web3modal/ethers5/react'; |
||||
import { |
||||
constants, |
||||
PROJECT_ID as projectId, |
||||
METADATA as metadata, |
||||
} from './constants'; |
||||
import EventManager from 'events'; |
||||
import { Chain, RequestArguments } from '../types'; |
||||
import txRunner from './txRunner'; |
||||
import { saveSettings } from '../actions'; |
||||
|
||||
class WalletConnect { |
||||
web3modal: ReturnType<typeof createWeb3Modal>; |
||||
ethersConfig: ReturnType<typeof defaultConfig>; |
||||
chains: Chain[]; |
||||
currentChain?: number; |
||||
internalEvents: EventManager; |
||||
currentAccount?: string; |
||||
|
||||
constructor() { |
||||
this.internalEvents = new EventManager(); |
||||
const ethersConfig = defaultConfig({ |
||||
metadata, |
||||
rpcUrl: 'https://cloudflare-eth.com', |
||||
}); |
||||
|
||||
this.web3modal = createWeb3Modal({ |
||||
projectId, |
||||
chains: constants.chains, |
||||
metadata, |
||||
ethersConfig, |
||||
}); |
||||
this.ethersConfig = ethersConfig; |
||||
this.chains = constants.chains; |
||||
} |
||||
|
||||
subscribeToEvents() { |
||||
this.web3modal.subscribeProvider(({ address, isConnected, chainId }) => { |
||||
if (isConnected) { |
||||
txRunner.getAccounts(); |
||||
if (address !== this.currentAccount) { |
||||
this.currentAccount = address; |
||||
} |
||||
if (this.currentChain !== chainId) { |
||||
this.currentChain = chainId; |
||||
} |
||||
} else { |
||||
saveSettings({ loadedAccounts: {} }); |
||||
this.currentAccount = ''; |
||||
this.currentChain = 0; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
async sendAsync(data: RequestArguments) { |
||||
const address = this.web3modal.getAddress(); |
||||
const provider = this.web3modal.getWalletProvider(); |
||||
if (address && provider) { |
||||
if (data.method === 'eth_accounts') { |
||||
return { |
||||
jsonrpc: '2.0', |
||||
result: [address], |
||||
id: data.id, |
||||
}; |
||||
} else { |
||||
//@ts-expect-error this flag does not correspond to EIP-1193 but was introduced by MetaMask
|
||||
if (provider.isMetamask && provider.sendAsync) { |
||||
return new Promise((resolve) => { |
||||
//@ts-expect-error sendAsync is a legacy function we know MetaMask supports it
|
||||
provider.sendAsync(data, (error, response) => { |
||||
if (error) { |
||||
if ( |
||||
error.data && |
||||
error.data.originalError && |
||||
error.data.originalError.data |
||||
) { |
||||
resolve({ |
||||
jsonrpc: '2.0', |
||||
error: error.data.originalError, |
||||
id: data.id, |
||||
}); |
||||
} else if (error.data && error.data.message) { |
||||
resolve({ |
||||
jsonrpc: '2.0', |
||||
error: error.data && error.data, |
||||
id: data.id, |
||||
}); |
||||
} else { |
||||
resolve({ |
||||
jsonrpc: '2.0', |
||||
error, |
||||
id: data.id, |
||||
}); |
||||
} |
||||
} |
||||
return resolve(response); |
||||
}); |
||||
}); |
||||
} else { |
||||
try { |
||||
const message = await provider.request(data); |
||||
return { jsonrpc: '2.0', result: message, id: data.id }; |
||||
} catch (e: any) { |
||||
return { |
||||
jsonrpc: '2.0', |
||||
error: { message: e.message, code: -32603 }, |
||||
id: data.id, |
||||
}; |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
const err = `Cannot make ${data.method} request. Remix client is not connected to walletconnect client`; |
||||
console.error(err); |
||||
return { |
||||
jsonrpc: '2.0', |
||||
error: { message: err, code: -32603 }, |
||||
id: data.id, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
async deactivate() { |
||||
console.log('deactivating walletconnect plugin...'); |
||||
await this.web3modal.disconnect(); |
||||
} |
||||
} |
||||
|
||||
export default new WalletConnect(); |
@ -0,0 +1 @@ |
||||
/// <reference types="vite/client" />
|
@ -0,0 +1,23 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": [ |
||||
"jest.config.ts", |
||||
"**/*.spec.ts", |
||||
"**/*.test.ts", |
||||
"**/*.spec.tsx", |
||||
"**/*.test.tsx", |
||||
"**/*.spec.js", |
||||
"**/*.test.js", |
||||
"**/*.spec.jsx", |
||||
"**/*.test.jsx" |
||||
], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"jsx": "react-jsx", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.app.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,108 @@ |
||||
const {composePlugins, withNx} = require('@nrwl/webpack') |
||||
const webpack = require('webpack') |
||||
const TerserPlugin = require('terser-webpack-plugin') |
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') |
||||
const { WebpackManifestPlugin } = require('webpack-manifest-plugin') |
||||
|
||||
// Nx plugins for webpack.
|
||||
module.exports = composePlugins(withNx(), (config) => { |
||||
// Update the webpack config as needed here.
|
||||
// e.g. `config.plugins.push(new MyPlugin())`
|
||||
// add fallback for node modules
|
||||
config.resolve.fallback = { |
||||
...config.resolve.fallback, |
||||
crypto: require.resolve('crypto-browserify'), |
||||
stream: require.resolve('stream-browserify'), |
||||
path: require.resolve('path-browserify'), |
||||
http: require.resolve('stream-http'), |
||||
https: require.resolve('https-browserify'), |
||||
constants: require.resolve('constants-browserify'), |
||||
os: false, //require.resolve("os-browserify/browser"),
|
||||
timers: false, // require.resolve("timers-browserify"),
|
||||
zlib: require.resolve('browserify-zlib'), |
||||
fs: false, |
||||
module: false, |
||||
tls: false, |
||||
net: false, |
||||
readline: false, |
||||
child_process: false, |
||||
buffer: require.resolve('buffer/'), |
||||
vm: require.resolve('vm-browserify'), |
||||
} |
||||
|
||||
// add externals
|
||||
config.externals = { |
||||
...config.externals, |
||||
solc: 'solc', |
||||
} |
||||
|
||||
// add public path
|
||||
config.output.publicPath = './' |
||||
|
||||
// add copy & provide plugin
|
||||
config.plugins.push( |
||||
new webpack.ProvidePlugin({ |
||||
Buffer: ['buffer', 'Buffer'], |
||||
url: ['url', 'URL'], |
||||
process: 'process/browser', |
||||
}) |
||||
) |
||||
|
||||
// set the define plugin to load the WALLET_CONNECT_PROJECT_ID
|
||||
config.plugins.push( |
||||
new webpack.DefinePlugin({ |
||||
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID), |
||||
}) |
||||
) |
||||
|
||||
config.plugins.push( |
||||
new WebpackManifestPlugin({ |
||||
fileName: 'manifest.json', |
||||
publicPath: '/', |
||||
generate: (seed, files, entrypoints) => { |
||||
const manifest = files.reduce((manifest, { name, path }) => { |
||||
manifest[name] = path; |
||||
return manifest; |
||||
}, seed); |
||||
return manifest; |
||||
}, |
||||
filter: (file) => { |
||||
return !(file.path.includes('assets') || file.path.includes('.map')); |
||||
}, |
||||
}) |
||||
) |
||||
|
||||
// souce-map loader
|
||||
config.module.rules.push({ |
||||
test: /\.js$/, |
||||
use: ['source-map-loader'], |
||||
enforce: 'pre', |
||||
}) |
||||
|
||||
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
|
||||
|
||||
// set minimizer
|
||||
config.optimization.minimizer = [ |
||||
new TerserPlugin({ |
||||
parallel: true, |
||||
terserOptions: { |
||||
ecma: 2015, |
||||
compress: false, |
||||
mangle: false, |
||||
format: { |
||||
comments: false, |
||||
}, |
||||
}, |
||||
extractComments: false, |
||||
}), |
||||
new CssMinimizerPlugin(), |
||||
] |
||||
|
||||
config.watchOptions = { |
||||
ignored: /node_modules/, |
||||
} |
||||
|
||||
config.experiments.syncWebAssembly = true |
||||
|
||||
return config |
||||
}) |
@ -0,0 +1,34 @@ |
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. |
||||
# yarn lockfile v1 |
||||
|
||||
|
||||
source-list-map@^2.0.1: |
||||
version "2.0.1" |
||||
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" |
||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== |
||||
|
||||
source-map@^0.6.1: |
||||
version "0.6.1" |
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" |
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== |
||||
|
||||
tapable@^2.0.0: |
||||
version "2.2.1" |
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" |
||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== |
||||
|
||||
webpack-manifest-plugin@^5.0.0: |
||||
version "5.0.0" |
||||
resolved "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz#084246c1f295d1b3222d36e955546433ca8df803" |
||||
integrity sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw== |
||||
dependencies: |
||||
tapable "^2.0.0" |
||||
webpack-sources "^2.2.0" |
||||
|
||||
webpack-sources@^2.2.0: |
||||
version "2.3.1" |
||||
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd" |
||||
integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA== |
||||
dependencies: |
||||
source-list-map "^2.0.1" |
||||
source-map "^0.6.1" |
@ -0,0 +1,492 @@ |
||||
'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 = { |
||||
'@disabled': true, |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080', false) |
||||
}, |
||||
'confirm Matomo #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser |
||||
.execute(function () { |
||||
localStorage.removeItem('config-v0.8:.remix.config') |
||||
localStorage.setItem('showMatomo', 'true') |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.execute(function () { |
||||
return (window as any)._paq |
||||
}, [], (res) => { |
||||
console.log('_paq', res) |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.pause(1000) |
||||
.click('[data-id="matomoModal-modal-footer-ok-react"]') // submitted
|
||||
.execute(function () { |
||||
return (window as any)._paq |
||||
}, [], (res) => { |
||||
console.log('_paq', res) |
||||
}) |
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.waitForElementVisible('*[data-id="beginnerbtn"]', 10000) |
||||
.pause(1000) |
||||
.click('[data-id="beginnerbtn"]') |
||||
.waitForElementNotPresent('*[data-id="beginnerbtn"]') |
||||
.waitForElementVisible({ |
||||
selector: `//*[contains(text(), 'Welcome to Remix IDE')]`, |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.refreshPage() |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.clickLaunchIcon('settings') |
||||
.verify.elementPresent('[id="settingsMatomoAnalytics"]:checked') |
||||
.execute(function () { |
||||
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == true |
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics is enabled') |
||||
}) |
||||
}, |
||||
'decline Matomo #group1': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
localStorage.removeItem('config-v0.8:.remix.config') |
||||
localStorage.setItem('showMatomo', 'true') |
||||
localStorage.removeItem('matomo-analytics-consent') |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.execute(function () { |
||||
return (window as any)._paq |
||||
}, [], (res) => { |
||||
console.log('_paq', res) |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
|
||||
.execute(function () { |
||||
return (window as any)._paq |
||||
}, [], (res) => { |
||||
console.log('_paq', res) |
||||
}) |
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.pause(2000) |
||||
.waitForElementNotPresent('*[data-id="beginnerbtn"]', 10000) |
||||
.clickLaunchIcon('settings') |
||||
.waitForElementNotPresent('[id="settingsMatomoAnalytics"]:checked') |
||||
.execute(function () { |
||||
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == false |
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics is disabled') |
||||
}) |
||||
}, |
||||
'blur matomo #group2': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
localStorage.removeItem('config-v0.8:.remix.config') |
||||
localStorage.setItem('showMatomo', 'true') |
||||
localStorage.removeItem('matomo-analytics-consent') |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '*[data-id="matomoModalModalDialogModalBody-react"]', |
||||
abortOnFailure: true |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModal-modal-close"]') |
||||
.click('[data-id="matomoModal-modal-close"]') |
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.pause(2000) |
||||
.waitForElementNotPresent('*[data-id="beginnerbtn"]', 10000) |
||||
.clickLaunchIcon('settings') |
||||
.waitForElementNotPresent('[id="settingsMatomoAnalytics"]:checked') |
||||
.execute(function () { |
||||
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == undefined |
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics is undefined') |
||||
}) |
||||
}, |
||||
'matomo should reappear #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.refreshPage() |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '*[data-id="matomoModalModalDialogModalBody-react"]', |
||||
abortOnFailure: true |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModal-modal-close"]') |
||||
.click('[data-id="matomoModal-modal-close"]') |
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
}, |
||||
'change settings #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('settings') |
||||
.waitForElementVisible('*[data-id="label-matomo-settings"]') |
||||
.pause(1000) |
||||
.click('*[data-id="label-matomo-settings"]') |
||||
.refreshPage() |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
}, |
||||
'should get enter dialog again #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible('*[data-id="beginnerbtn"]', 10000) |
||||
.pause(1000) |
||||
.click('[data-id="beginnerbtn"]') |
||||
.waitForElementNotPresent('*[data-id="beginnerbtn"]') |
||||
.waitForElementVisible({ |
||||
selector: `//*[contains(text(), 'Welcome to Remix IDE')]`, |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible('*[id="remixTourSkipbtn"]') |
||||
.click('*[id="remixTourSkipbtn"]') |
||||
.clickLaunchIcon('settings') |
||||
.waitForElementPresent('[id="settingsMatomoAnalytics"]:checked') |
||||
.execute(function () { |
||||
return JSON.parse(window.localStorage.getItem('config-v0.8:.remix.config'))['settings/matomo-analytics'] == true |
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics is enabled') |
||||
}) |
||||
}, |
||||
'decline Matomo and check timestamp #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
localStorage.removeItem('config-v0.8:.remix.config') |
||||
localStorage.setItem('showMatomo', 'true') |
||||
localStorage.removeItem('matomo-analytics-consent') |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
// output the contents of the storage
|
||||
.execute(function () { |
||||
return { |
||||
consent: window.localStorage.getItem('matomo-analytics-consent'), |
||||
config: window.localStorage.getItem('config-v0.8:.remix.config'), |
||||
showMatomo: window.localStorage.getItem('showMatomo') |
||||
} |
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
|
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.pause(2000) |
||||
.execute(function () { |
||||
|
||||
const timestamp = window.localStorage.getItem('matomo-analytics-consent'); |
||||
if (timestamp) { |
||||
|
||||
const consentDate = new Date(Number(timestamp)); |
||||
// validate it is actually a date
|
||||
if (isNaN(consentDate.getTime())) { |
||||
return false; |
||||
} |
||||
const now = new Date(); |
||||
console.log('timestamp', timestamp, consentDate, now.getTime()) |
||||
const diffInMinutes = (now.getTime() - consentDate.getTime()) / (1000 * 60); |
||||
console.log('diffInMinutes', diffInMinutes) |
||||
return diffInMinutes < 2; |
||||
} |
||||
return false; |
||||
|
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is set') |
||||
}) |
||||
}, |
||||
'check old timestamp and reappear Matomo #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
const oldTimestamp = new Date() |
||||
oldTimestamp.setMonth(oldTimestamp.getMonth() - 7) |
||||
localStorage.setItem('matomo-analytics-consent', oldTimestamp.getTime().toString()) |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.execute(function () { |
||||
|
||||
const timestamp = window.localStorage.getItem('matomo-analytics-consent'); |
||||
if (timestamp) { |
||||
|
||||
const consentDate = new Date(Number(timestamp)); |
||||
// validate it is actually a date
|
||||
if (isNaN(consentDate.getTime())) { |
||||
return false; |
||||
} |
||||
// validate it's older than 6 months
|
||||
const now = new Date(); |
||||
const diffInMonths = (now.getFullYear() - consentDate.getFullYear()) * 12 + now.getMonth() - consentDate.getMonth(); |
||||
console.log('timestamp', timestamp, consentDate, now.getTime()) |
||||
console.log('diffInMonths', diffInMonths) |
||||
return diffInMonths > 6; |
||||
} |
||||
return false; |
||||
|
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is set') |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
|
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
}, |
||||
'check recent timestamp and do not reappear Matomo #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
const recentTimestamp = new Date() |
||||
recentTimestamp.setMonth(recentTimestamp.getMonth() - 1) |
||||
localStorage.setItem('matomo-analytics-consent', recentTimestamp.getTime().toString()) |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
// check if timestamp is younger than 6 months
|
||||
.execute(function () { |
||||
|
||||
const timestamp = window.localStorage.getItem('matomo-analytics-consent'); |
||||
if (timestamp) { |
||||
|
||||
const consentDate = new Date(Number(timestamp)); |
||||
// validate it is actually a date
|
||||
if (isNaN(consentDate.getTime())) { |
||||
return false; |
||||
} |
||||
// validate it's younger than 2 months
|
||||
const now = new Date(); |
||||
const diffInMonths = (now.getFullYear() - consentDate.getFullYear()) * 12 + now.getMonth() - consentDate.getMonth(); |
||||
console.log('timestamp', timestamp, consentDate, now.getTime()) |
||||
console.log('diffInMonths', diffInMonths) |
||||
return diffInMonths < 2; |
||||
} |
||||
return false; |
||||
|
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is set') |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.pause(2000) |
||||
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
}, |
||||
'accept Matomo and check timestamp #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
localStorage.removeItem('config-v0.8:.remix.config') |
||||
localStorage.setItem('showMatomo', 'true') |
||||
localStorage.removeItem('matomo-analytics-consent') |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.click('[data-id="matomoModal-modal-footer-ok-react"]') // accept
|
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.pause(2000) |
||||
.execute(function () { |
||||
|
||||
const timestamp = window.localStorage.getItem('matomo-analytics-consent'); |
||||
if (timestamp) { |
||||
|
||||
const consentDate = new Date(Number(timestamp)); |
||||
// validate it is actually a date
|
||||
if (isNaN(consentDate.getTime())) { |
||||
return false; |
||||
} |
||||
const now = new Date(); |
||||
console.log('timestamp', timestamp, consentDate, now.getTime()) |
||||
const diffInMinutes = (now.getTime() - consentDate.getTime()) / (1000 * 60); |
||||
console.log('diffInMinutes', diffInMinutes) |
||||
return diffInMinutes < 1; |
||||
} |
||||
return false; |
||||
|
||||
}, [], (res) => { |
||||
console.log('res', res) |
||||
browser.assert.ok((res as any).value, 'matomo analytics consent timestamp is to a recent date') |
||||
}) |
||||
}, |
||||
'check old timestamp and do not reappear Matomo after accept #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
const oldTimestamp = new Date() |
||||
oldTimestamp.setMonth(oldTimestamp.getMonth() - 7) |
||||
localStorage.setItem('matomo-analytics-consent', oldTimestamp.getTime().toString()) |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.pause(2000) |
||||
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
}, |
||||
'check recent timestamp and do not reappear Matomo after accept #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
const recentTimestamp = new Date() |
||||
recentTimestamp.setMonth(recentTimestamp.getMonth() - 1) |
||||
localStorage.setItem('matomo-analytics-consent', recentTimestamp.getTime().toString()) |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementPresent({ |
||||
selector: `//*[@data-id='compilerloaded']`, |
||||
locateStrategy: 'xpath', |
||||
timeout: 120000 |
||||
}) |
||||
.pause(2000) |
||||
.waitForElementNotPresent('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
}, |
||||
'when there is a recent timestamp but no config the dialog should reappear #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
localStorage.removeItem('config-v0.8:.remix.config') |
||||
const recentTimestamp = new Date() |
||||
recentTimestamp.setMonth(recentTimestamp.getMonth() - 1) |
||||
localStorage.setItem('matomo-analytics-consent', recentTimestamp.getTime().toString()) |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.click('[data-id="matomoModal-modal-footer-cancel-react"]') // cancel
|
||||
.waitForElementNotVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
}, |
||||
'when there is a old timestamp but no config the dialog should reappear #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
browser.execute(function () { |
||||
localStorage.removeItem('config-v0.8:.remix.config') |
||||
const oldTimestamp = new Date() |
||||
oldTimestamp.setMonth(oldTimestamp.getMonth() - 7) |
||||
localStorage.setItem('matomo-analytics-consent', oldTimestamp.getTime().toString()) |
||||
}, []) |
||||
.refreshPage() |
||||
.perform(done()) |
||||
}) |
||||
.waitForElementVisible('*[data-id="matomoModalModalDialogModalBody-react"]') |
||||
.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) { |
||||
browser |
||||
.execute(function () { |
||||
return (window as any)._paq |
||||
}, [], (res) => { |
||||
const expectedEvents = [ |
||||
["trackEvent", "Preload", "start"], |
||||
["trackEvent", "Storage", "activate", "indexedDB"], |
||||
["trackEvent", "App", "load"], |
||||
]; |
||||
|
||||
const actualEvents = (res as any).value; |
||||
|
||||
const areEventsPresent = expectedEvents.every(expectedEvent => |
||||
actualEvents.some(actualEvent => |
||||
JSON.stringify(actualEvent) === JSON.stringify(expectedEvent) |
||||
) |
||||
); |
||||
|
||||
browser.assert.ok(areEventsPresent, 'Matomo events are tracked correctly'); |
||||
}) |
||||
}, |
||||
|
||||
'@sources': function () { |
||||
return sources |
||||
}, |
||||
'Add Ballot #group4': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.addFile('Untitled.sol', sources[0]['Untitled.sol']) |
||||
}, |
||||
'Deploy Ballot #group4': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) |
||||
.clickLaunchIcon('solidity') |
||||
.waitForElementVisible('*[data-id="compilerContainerCompileBtn"]') |
||||
.click('*[data-id="compilerContainerCompileBtn"]') |
||||
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['Ballot']) |
||||
}, |
||||
'verify Matomo compiler events are tracked #group4': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.execute(function () { |
||||
return (window as any)._paq |
||||
}, [], (res) => { |
||||
const expectedEvent = ["trackEvent", "compiler", "compiled"]; |
||||
const actualEvents = (res as any).value; |
||||
|
||||
const isEventPresent = actualEvents.some(actualEvent => |
||||
actualEvent[0] === expectedEvent[0] && |
||||
actualEvent[1] === expectedEvent[1] && |
||||
actualEvent[2] === expectedEvent[2] && |
||||
actualEvent[3].startsWith("with_version_") |
||||
); |
||||
|
||||
browser.assert.ok(isEventPresent, 'Matomo compiler events are tracked correctly'); |
||||
}) |
||||
}, |
||||
} |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"vyper.openaigptMessage": "vyper code: {content}\n error message: {messageText}\n explain why the error occurred and how to fix it." |
||||
} |
@ -0,0 +1,124 @@ |
||||
# How run and write tests for Remix Desktop |
||||
|
||||
|
||||
### Basic procedure of local testing |
||||
|
||||
- create a release executable of desktop |
||||
- run local remix-ide |
||||
- run the test |
||||
|
||||
### Executables |
||||
|
||||
Testing runs through nightwatch that treats an electron application as a special version of chrome, which it is. It basically calls the executable which is built by the ./rundist.bash script which creates executables based on the current channel configuration package.json, ie "version": "1.0.8-insiders" |
||||
|
||||
Executables are stored in the ./release directory. Without that executable you cannot run tests. You cannot call tests on local development instance that is launched by yarn start:dev. You need to create an exec first. |
||||
|
||||
This is done by running ./rundist.bash |
||||
|
||||
Normally when you would do a 'real' release you would package remix IDE into the distributable but for local e2e this not necessary because it will use the remix IDE that is being served. |
||||
|
||||
|
||||
``` |
||||
switch (type) { |
||||
case 'Windows_NT': |
||||
binaryPath = `./release/win-unpacked/Remix-Desktop-${channel}.exe`; |
||||
break; |
||||
case 'Darwin': |
||||
binaryPath = arch === 'x64' ? |
||||
`release/mac/Remix-Desktop-${channel}.app/Contents/MacOS/Remix-Desktop-${channel}` : |
||||
`release/mac-arm64/Remix-Desktop-${channel}.app/Contents/MacOS/Remix-Desktop-${channel}`; |
||||
break; |
||||
case 'Linux': |
||||
binaryPath = "release/linux-unpacked/remixdesktop"; |
||||
break; |
||||
``` |
||||
|
||||
### Local testing |
||||
|
||||
In order to facilitate local testing nightwatch will boot the executable with the --e2e-local flag when running locally ( so outside of CIRCLE CI ). This means the electron app with load the local running Remix IDE. |
||||
|
||||
So to start testing locally |
||||
- run the IDE with 'yarn serve' as you would normally do. |
||||
- build your release. You will always need to this when the Desktop app itself changes its code. |
||||
- ./rundist.bash |
||||
- in apps/remixdesktop: |
||||
- yarn build:e2e |
||||
- run the test you want, refer to the actual JS that is build, not the TS, ie |
||||
yarn test --test ./build-e2e/remixdesktop/test/tests/app/compiler.test.js |
||||
|
||||
### Hot reload on local tests |
||||
|
||||
When Remix IDE changes the electron window that is open will hot reload just like a browser. But when you change electron code you need to rebuild the release. |
||||
|
||||
### Filesystem & native dialogs |
||||
|
||||
Because you cannot access anything outside of the chrome context with nightwatch, like filesystem dialogs, the filesystem plugin in electron behaves different when it comes to e2e. |
||||
|
||||
These functions have been replaced with special versions: |
||||
``` |
||||
selectFolder |
||||
openFolder |
||||
openFolderInSameWindow |
||||
``` |
||||
|
||||
Basically when the app tries to open a folder where your 'workspace' is located it creates a random directory if you haven't specified a specific dir: |
||||
``` |
||||
const randomdir = path.join(os.homedir(), 'remix-tests' + Date.now().toString()) |
||||
``` |
||||
So that means when the test loads a template it will put the contents of it in a random dir. |
||||
|
||||
When you know which directory you need to open you can call it on the window in the test: |
||||
``` |
||||
browser.executeAsync(function (dir, done) { |
||||
(window as any).electronAPI.openFolderInSameWindow(dir + '/hello_foundry/').then(done) |
||||
}, [dir], () => { |
||||
console.log('done window opened') |
||||
}) |
||||
``` |
||||
|
||||
You can see this behavior in action too when using |
||||
``` |
||||
yarn start:dev --e2e-local |
||||
``` |
||||
|
||||
### Window handling |
||||
|
||||
The electron app always starts in a mode where there is no 'workspace'. You need to open one and that creates a new window unless you call openFolderInSameWindow. |
||||
|
||||
When you open a folder which is not in the same window, so ie you load a template electron creates a new window, so you need to switch to it: |
||||
|
||||
``` |
||||
.windowHandles(function (result) { |
||||
console.log(result.value) |
||||
browser.switchWindow(result.value[1]) |
||||
``` |
||||
|
||||
### OS filtering |
||||
|
||||
Remember tests will run on Machine OS instances, you will need to filter tests that use certain features of an OS |
||||
|
||||
``` |
||||
module.exports = { |
||||
...process.platform.startsWith('win')?{}:tests |
||||
} |
||||
``` |
||||
|
||||
### hiding tooltips |
||||
|
||||
Always use this, it avoids trouble: |
||||
``` |
||||
rowser.hideToolTips() |
||||
``` |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue