Merge branch 'master' of https://github.com/ethereum/remix-project into desktop-master-git

pull/4991/head
bunsenstraat 3 months ago
commit bafdc6f39c
  1. 17
      .circleci/config.yml
  2. 2
      .github/workflows/pr-reminder.yml
  3. 962
      apps/circuit-compiler/src/app/actions/constant.ts
  4. 161
      apps/circuit-compiler/src/app/actions/index.ts
  5. 18
      apps/circuit-compiler/src/app/app.tsx
  6. 6
      apps/circuit-compiler/src/app/components/actions.tsx
  7. 7
      apps/circuit-compiler/src/app/components/compileBtn.tsx
  8. 36
      apps/circuit-compiler/src/app/components/configToggler.tsx
  9. 4
      apps/circuit-compiler/src/app/components/configurations.tsx
  10. 52
      apps/circuit-compiler/src/app/components/container.tsx
  11. 86
      apps/circuit-compiler/src/app/components/feedback.tsx
  12. 39
      apps/circuit-compiler/src/app/components/generateProof.tsx
  13. 2
      apps/circuit-compiler/src/app/components/options.tsx
  14. 44
      apps/circuit-compiler/src/app/components/r1csBtn.tsx
  15. 110
      apps/circuit-compiler/src/app/components/setupExports.tsx
  16. 34
      apps/circuit-compiler/src/app/components/setupExportsBtn.tsx
  17. 50
      apps/circuit-compiler/src/app/components/toggler.tsx
  18. 80
      apps/circuit-compiler/src/app/components/witness.tsx
  19. 36
      apps/circuit-compiler/src/app/components/witnessToggler.tsx
  20. 90
      apps/circuit-compiler/src/app/reducers/state.ts
  21. 17
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  22. 45
      apps/circuit-compiler/src/app/types/index.ts
  23. 2
      apps/circuit-compiler/src/css/app.css
  24. 2
      apps/circuit-compiler/src/profile.json
  25. 1
      apps/doc-gen/tsconfig.json
  26. 2
      apps/etherscan/src/app/utils/networks.ts
  27. 3
      apps/quick-dapp/.eslintrc
  28. 1
      apps/quick-dapp/README.md
  29. 11
      apps/quick-dapp/package.json
  30. 70
      apps/quick-dapp/project.json
  31. 127
      apps/quick-dapp/src/App.css
  32. 71
      apps/quick-dapp/src/App.tsx
  33. 426
      apps/quick-dapp/src/actions/index.ts
  34. BIN
      apps/quick-dapp/src/assets/edit-dapp.png
  35. 132
      apps/quick-dapp/src/components/ContractGUI/index.tsx
  36. 107
      apps/quick-dapp/src/components/CreateInstance/index.tsx
  37. 329
      apps/quick-dapp/src/components/DeployPanel/index.tsx
  38. 159
      apps/quick-dapp/src/components/DeployPanel/theme.tsx
  39. 86
      apps/quick-dapp/src/components/EditInstance/index.tsx
  40. 76
      apps/quick-dapp/src/components/EditableText/index.tsx
  41. 50
      apps/quick-dapp/src/components/ImageUpload/index.tsx
  42. 31
      apps/quick-dapp/src/components/LoadingScreen/index.tsx
  43. 70
      apps/quick-dapp/src/components/MultipleContainers/components/Container/Container.tsx
  44. 13
      apps/quick-dapp/src/components/MultipleContainers/components/Container/Remove.tsx
  45. 2
      apps/quick-dapp/src/components/MultipleContainers/components/Container/index.ts
  46. 29
      apps/quick-dapp/src/components/MultipleContainers/components/Item/Action.tsx
  47. 18
      apps/quick-dapp/src/components/MultipleContainers/components/Item/Handle.tsx
  48. 112
      apps/quick-dapp/src/components/MultipleContainers/components/Item/Item.tsx
  49. 3
      apps/quick-dapp/src/components/MultipleContainers/components/Item/index.ts
  50. 3
      apps/quick-dapp/src/components/MultipleContainers/components/index.ts
  51. 669
      apps/quick-dapp/src/components/MultipleContainers/index.tsx
  52. 153
      apps/quick-dapp/src/components/MultipleContainers/multipleContainersKeyboardCoordinates.ts
  53. 3
      apps/quick-dapp/src/contexts/index.ts
  54. 13
      apps/quick-dapp/src/index.css
  55. 16
      apps/quick-dapp/src/index.html
  56. 9
      apps/quick-dapp/src/main.tsx
  57. 7
      apps/quick-dapp/src/polyfills.ts
  58. 19
      apps/quick-dapp/src/profile.json
  59. 33
      apps/quick-dapp/src/reducers/state.ts
  60. 24
      apps/quick-dapp/src/remix-client.ts
  61. 23
      apps/quick-dapp/tsconfig.app.json
  62. 16
      apps/quick-dapp/tsconfig.json
  63. 90
      apps/quick-dapp/webpack.config.js
  64. 70
      apps/quick-dapp/yarn.lock
  65. 19
      apps/remix-ide-e2e/src/commands/pinGrid.ts
  66. 2
      apps/remix-ide-e2e/src/commands/refreshPage.ts
  67. 27
      apps/remix-ide-e2e/src/commands/renamePath.ts
  68. 34
      apps/remix-ide-e2e/src/commands/switchEnvironment.ts
  69. 64
      apps/remix-ide-e2e/src/commands/verifyLoad.ts
  70. 76
      apps/remix-ide-e2e/src/helpers/init.ts
  71. 2
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  72. 43
      apps/remix-ide-e2e/src/tests/circom.test.ts
  73. 103
      apps/remix-ide-e2e/src/tests/dgit_github.test.ts
  74. 19
      apps/remix-ide-e2e/src/tests/dgit_local.test.ts
  75. 2
      apps/remix-ide-e2e/src/tests/erc721.test.ts
  76. 5
      apps/remix-ide-e2e/src/tests/file_explorer_context_menu.test.ts
  77. 20
      apps/remix-ide-e2e/src/tests/grid.test.ts
  78. 8
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  79. 322
      apps/remix-ide-e2e/src/tests/quickDapp.test.ts
  80. 80
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  81. 5
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  82. 3
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  83. 6
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  84. 12
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  85. 15
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  86. 7
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  87. 6
      apps/remix-ide-e2e/yarn.lock
  88. 2
      apps/remix-ide/project.json
  89. 13
      apps/remix-ide/src/app/files/dgitProvider.ts
  90. 8
      apps/remix-ide/src/app/files/fileManager.ts
  91. 1
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  92. 5
      apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx
  93. 23
      apps/remix-ide/src/app/plugins/templates-selection/templates.ts
  94. 13
      apps/remix-ide/src/app/tabs/locales/en/circuit.json
  95. 2
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  96. 3
      apps/remix-ide/src/app/tabs/locales/en/git.json
  97. 2
      apps/remix-ide/src/app/tabs/locales/en/home.json
  98. 42
      apps/remix-ide/src/app/tabs/locales/en/quickDapp.json
  99. 1
      apps/remix-ide/src/app/tabs/locales/en/terminal.json
  100. 1
      apps/remix-ide/src/app/tabs/locales/es/terminal.json
  101. Some files were not shown because too many files have changed in this diff Show More

@ -16,6 +16,7 @@ jobs:
xlarge
working_directory: ~/remix-project
steps:
- run: sudo apt update && sudo apt install zstd
- checkout
- restore_cache:
keys:
@ -44,8 +45,8 @@ jobs:
key: soljson-v7-{{ checksum "soljson-versions.txt" }}
paths:
- dist/apps/remix-ide/assets/js/soljson
- run: mkdir persist && zip -0 -r persist/dist.zip dist
- run: mkdir persist && tar -cf - dist | zstd -1 -o persist/dist.tar.zst
- persist_to_workspace:
root: .
paths:
@ -607,11 +608,13 @@ jobs:
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- run: sudo apt update && sudo apt install python3-pip -y zstd
- run: zstd -d persist/dist.tar.zst -o persist/dist.tar
- run: tar -xf persist/dist.tar
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules || yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- run: mkdir node_modules/hardhat && wget https://unpkg.com/hardhat/console.sol -O node_modules/hardhat/console.sol
- run: ls -la ./dist/apps/remix-ide/assets/js
- run: sudo apt update && sudo apt install python3-pip -y
- when:
condition:
equal: [ "chrome", << parameters.browser >> ]
@ -642,7 +645,7 @@ jobs:
tests-passed:
machine:
image: ubuntu-2004:202010-01
image: default
steps:
- run: echo done
@ -663,7 +666,9 @@ jobs:
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- run: sudo apt update && sudo apt install zstd
- run: zstd -d persist/dist.tar.zst -o persist/dist.tar
- run: tar -xf persist/dist.tar
- run: unzip ./persist/plugin-<< parameters.plugin >>.zip
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules || yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- browser-tools/install-browser-tools:

@ -14,4 +14,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-08-12T18:00:00Z'
freeze-date: '2024-09-09T18:00:00Z'

@ -0,0 +1,962 @@
export const GROTH16_VERIFIER = `// SPDX-License-Identifier: GPL-3.0
/*
Copyright 2021 0KIMS association.
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
snarkJS is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
snarkJS is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/
pragma solidity >=0.7.0 <0.9.0;
contract Groth16Verifier {
// Scalar field size
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// Verification Key data
uint256 constant alphax = <%= vk_alpha_1[0] %>;
uint256 constant alphay = <%= vk_alpha_1[1] %>;
uint256 constant betax1 = <%= vk_beta_2[0][1] %>;
uint256 constant betax2 = <%= vk_beta_2[0][0] %>;
uint256 constant betay1 = <%= vk_beta_2[1][1] %>;
uint256 constant betay2 = <%= vk_beta_2[1][0] %>;
uint256 constant gammax1 = <%= vk_gamma_2[0][1] %>;
uint256 constant gammax2 = <%= vk_gamma_2[0][0] %>;
uint256 constant gammay1 = <%= vk_gamma_2[1][1] %>;
uint256 constant gammay2 = <%= vk_gamma_2[1][0] %>;
uint256 constant deltax1 = <%= vk_delta_2[0][1] %>;
uint256 constant deltax2 = <%= vk_delta_2[0][0] %>;
uint256 constant deltay1 = <%= vk_delta_2[1][1] %>;
uint256 constant deltay2 = <%= vk_delta_2[1][0] %>;
<% for (let i=0; i<IC.length; i++) { %>
uint256 constant IC<%=i%>x = <%=IC[i][0]%>;
uint256 constant IC<%=i%>y = <%=IC[i][1]%>;
<% } %>
// Memory data
uint16 constant pVk = 0;
uint16 constant pPairing = 128;
uint16 constant pLastMem = 896;
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[<%=IC.length-1%>] calldata _pubSignals) public view returns (bool) {
assembly {
function checkField(v) {
if iszero(lt(v, q)) {
mstore(0, 0)
return(0, 0x20)
}
}
// G1 function to multiply a G1 value(x,y) to value in an address
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn, 32), y)
mstore(add(mIn, 64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
mstore(add(mIn, 64), mload(pR))
mstore(add(mIn, 96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
}
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
let _pPairing := add(pMem, pPairing)
let _pVk := add(pMem, pVk)
mstore(_pVk, IC0x)
mstore(add(_pVk, 32), IC0y)
// Compute the linear combination vk_x
<% for (let i = 1; i <= nPublic; i++) { %>
g1_mulAccC(_pVk, IC<%=i%>x, IC<%=i%>y, calldataload(add(pubSignals, <%=(i-1)*32%>)))
<% } %>
// -A
mstore(_pPairing, calldataload(pA))
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
// B
mstore(add(_pPairing, 64), calldataload(pB))
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
// alpha1
mstore(add(_pPairing, 192), alphax)
mstore(add(_pPairing, 224), alphay)
// beta2
mstore(add(_pPairing, 256), betax1)
mstore(add(_pPairing, 288), betax2)
mstore(add(_pPairing, 320), betay1)
mstore(add(_pPairing, 352), betay2)
// vk_x
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
// gamma2
mstore(add(_pPairing, 448), gammax1)
mstore(add(_pPairing, 480), gammax2)
mstore(add(_pPairing, 512), gammay1)
mstore(add(_pPairing, 544), gammay2)
// C
mstore(add(_pPairing, 576), calldataload(pC))
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
// delta2
mstore(add(_pPairing, 640), deltax1)
mstore(add(_pPairing, 672), deltax2)
mstore(add(_pPairing, 704), deltay1)
mstore(add(_pPairing, 736), deltay2)
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
isOk := and(success, mload(_pPairing))
}
let pMem := mload(0x40)
mstore(0x40, add(pMem, pLastMem))
// Validate that all evaluations ∈ F
<% for (let i=0; i<IC.length; i++) { %>
checkField(calldataload(add(_pubSignals, <%=i*32%>)))
<% } %>
// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
mstore(0, isValid)
return(0, 0x20)
}
}
}`
export const PLONK_VERIFIER = `// SPDX-License-Identifier: GPL-3.0
/*
Copyright 2021 0KIMS association.
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
snarkJS is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
snarkJS is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/
pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
contract PlonkVerifier {
// Omega
uint256 constant w1 = <%=w%>;
// Scalar field size
uint256 constant q = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
uint256 constant qf = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// [1]_1
uint256 constant G1x = 1;
uint256 constant G1y = 2;
// [1]_2
uint256 constant G2x1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant G2x2 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant G2y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant G2y2 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
// Verification Key data
uint32 constant n = <%=2**power%>;
uint16 constant nPublic = <%=nPublic%>;
uint16 constant nLagrange = <%=Math.max(nPublic, 1)%>;
uint256 constant Qmx = <%=Qm[0]%>;
uint256 constant Qmy = <%=Qm[0] == "0" ? "0" : Qm[1]%>;
uint256 constant Qlx = <%=Ql[0]%>;
uint256 constant Qly = <%=Ql[0] == "0" ? "0" : Ql[1]%>;
uint256 constant Qrx = <%=Qr[0]%>;
uint256 constant Qry = <%=Qr[0] == "0" ? "0" : Qr[1]%>;
uint256 constant Qox = <%=Qo[0]%>;
uint256 constant Qoy = <%=Qo[0] == "0" ? "0" : Qo[1]%>;
uint256 constant Qcx = <%=Qc[0]%>;
uint256 constant Qcy = <%=Qc[0] == "0" ? "0" : Qc[1]%>;
uint256 constant S1x = <%=S1[0]%>;
uint256 constant S1y = <%=S1[0] == "0" ? "0" : S1[1]%>;
uint256 constant S2x = <%=S2[0]%>;
uint256 constant S2y = <%=S2[0] == "0" ? "0" : S2[1]%>;
uint256 constant S3x = <%=S3[0]%>;
uint256 constant S3y = <%=S3[0] == "0" ? "0" : S3[1]%>;
uint256 constant k1 = <%=k1%>;
uint256 constant k2 = <%=k2%>;
uint256 constant X2x1 = <%=X_2[0][0]%>;
uint256 constant X2x2 = <%=X_2[0][1]%>;
uint256 constant X2y1 = <%=X_2[1][0]%>;
uint256 constant X2y2 = <%=X_2[1][1]%>;
// Proof calldata
// Byte offset of every parameter of the calldata
// Polynomial commitments
uint16 constant pA = 4 + 0;
uint16 constant pB = 4 + 64;
uint16 constant pC = 4 + 128;
uint16 constant pZ = 4 + 192;
uint16 constant pT1 = 4 + 256;
uint16 constant pT2 = 4 + 320;
uint16 constant pT3 = 4 + 384;
uint16 constant pWxi = 4 + 448;
uint16 constant pWxiw = 4 + 512;
// Opening evaluations
uint16 constant pEval_a = 4 + 576;
uint16 constant pEval_b = 4 + 608;
uint16 constant pEval_c = 4 + 640;
uint16 constant pEval_s1 = 4 + 672;
uint16 constant pEval_s2 = 4 + 704;
uint16 constant pEval_zw = 4 + 736;
// Memory data
// Challenges
uint16 constant pAlpha = 0;
uint16 constant pBeta = 32;
uint16 constant pGamma = 64;
uint16 constant pXi = 96;
uint16 constant pXin = 128;
uint16 constant pBetaXi = 160;
uint16 constant pV1 = 192;
uint16 constant pV2 = 224;
uint16 constant pV3 = 256;
uint16 constant pV4 = 288;
uint16 constant pV5 = 320;
uint16 constant pU = 352;
uint16 constant pPI = 384;
uint16 constant pEval_r0 = 416;
uint16 constant pD = 448;
uint16 constant pF = 512;
uint16 constant pE = 576;
uint16 constant pTmp = 640;
uint16 constant pAlpha2 = 704;
uint16 constant pZh = 736;
uint16 constant pZhInv = 768;
<% for (let i=1; i<=Math.max(nPublic, 1); i++) { %>
uint16 constant pEval_l<%=i%> = <%=768+i*32%>;
<% } %>
<% let pLastMem = 800+32*Math.max(nPublic,1) %>
uint16 constant lastMem = <%=pLastMem%>;
function verifyProof(uint256[24] calldata _proof, uint256[<%=nPublic%>] calldata _pubSignals) public view returns (bool) {
assembly {
/////////
// Computes the inverse using the extended euclidean algorithm
/////////
function inverse(a, q) -> inv {
let t := 0
let newt := 1
let r := q
let newr := a
let quotient
let aux
for { } newr { } {
quotient := sdiv(r, newr)
aux := sub(t, mul(quotient, newt))
t:= newt
newt:= aux
aux := sub(r,mul(quotient, newr))
r := newr
newr := aux
}
if gt(r, 1) { revert(0,0) }
if slt(t, 0) { t:= add(t, q) }
inv := t
}
///////
// Computes the inverse of an array of values
// See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations
//////
function inverseArray(pVals, n) {
let pAux := mload(0x40) // Point to the next free position
let pIn := pVals
let lastPIn := add(pVals, mul(n, 32)) // Read n elements
let acc := mload(pIn) // Read the first element
pIn := add(pIn, 32) // Point to the second element
let inv
for { } lt(pIn, lastPIn) {
pAux := add(pAux, 32)
pIn := add(pIn, 32)
}
{
mstore(pAux, acc)
acc := mulmod(acc, mload(pIn), q)
}
acc := inverse(acc, q)
// At this point pAux pint to the next free position we subtract 1 to point to the last used
pAux := sub(pAux, 32)
// pIn points to the n+1 element, we subtract to point to n
pIn := sub(pIn, 32)
lastPIn := pVals // We don't process the first element
for { } gt(pIn, lastPIn) {
pAux := sub(pAux, 32)
pIn := sub(pIn, 32)
}
{
inv := mulmod(acc, mload(pAux), q)
acc := mulmod(acc, mload(pIn), q)
mstore(pIn, inv)
}
// pIn points to first element, we just set it.
mstore(pIn, acc)
}
function checkField(v) {
if iszero(lt(v, q)) {
mstore(0, 0)
return(0,0x20)
}
}
function checkInput() {
checkField(calldataload(pEval_a))
checkField(calldataload(pEval_b))
checkField(calldataload(pEval_c))
checkField(calldataload(pEval_s1))
checkField(calldataload(pEval_s2))
checkField(calldataload(pEval_zw))
}
function calculateChallenges(pMem, pPublic) {
let beta
let aux
let mIn := mload(0x40) // Pointer to the next free memory position
// Compute challenge.beta & challenge.gamma
mstore(mIn, Qmx)
mstore(add(mIn, 32), Qmy)
mstore(add(mIn, 64), Qlx)
mstore(add(mIn, 96), Qly)
mstore(add(mIn, 128), Qrx)
mstore(add(mIn, 160), Qry)
mstore(add(mIn, 192), Qox)
mstore(add(mIn, 224), Qoy)
mstore(add(mIn, 256), Qcx)
mstore(add(mIn, 288), Qcy)
mstore(add(mIn, 320), S1x)
mstore(add(mIn, 352), S1y)
mstore(add(mIn, 384), S2x)
mstore(add(mIn, 416), S2y)
mstore(add(mIn, 448), S3x)
mstore(add(mIn, 480), S3y)
<%for (let i=0; i<nPublic;i++) {%>
mstore(add(mIn, <%= 512 + i*32 %>), calldataload(add(pPublic, <%=i*32%>)))
<%}%>
mstore(add(mIn, <%= 512 + nPublic*32 + 0 %> ), calldataload(pA))
mstore(add(mIn, <%= 512 + nPublic*32 + 32 %> ), calldataload(add(pA, 32)))
mstore(add(mIn, <%= 512 + nPublic*32 + 64 %> ), calldataload(pB))
mstore(add(mIn, <%= 512 + nPublic*32 + 96 %> ), calldataload(add(pB, 32)))
mstore(add(mIn, <%= 512 + nPublic*32 + 128 %> ), calldataload(pC))
mstore(add(mIn, <%= 512 + nPublic*32 + 160 %> ), calldataload(add(pC, 32)))
beta := mod(keccak256(mIn, <%= 704 + 32 * nPublic %>), q)
mstore(add(pMem, pBeta), beta)
// challenges.gamma
mstore(add(pMem, pGamma), mod(keccak256(add(pMem, pBeta), 32), q))
// challenges.alpha
mstore(mIn, mload(add(pMem, pBeta)))
mstore(add(mIn, 32), mload(add(pMem, pGamma)))
mstore(add(mIn, 64), calldataload(pZ))
mstore(add(mIn, 96), calldataload(add(pZ, 32)))
aux := mod(keccak256(mIn, 128), q)
mstore(add(pMem, pAlpha), aux)
mstore(add(pMem, pAlpha2), mulmod(aux, aux, q))
// challenges.xi
mstore(mIn, aux)
mstore(add(mIn, 32), calldataload(pT1))
mstore(add(mIn, 64), calldataload(add(pT1, 32)))
mstore(add(mIn, 96), calldataload(pT2))
mstore(add(mIn, 128), calldataload(add(pT2, 32)))
mstore(add(mIn, 160), calldataload(pT3))
mstore(add(mIn, 192), calldataload(add(pT3, 32)))
aux := mod(keccak256(mIn, 224), q)
mstore( add(pMem, pXi), aux)
// challenges.v
mstore(mIn, aux)
mstore(add(mIn, 32), calldataload(pEval_a))
mstore(add(mIn, 64), calldataload(pEval_b))
mstore(add(mIn, 96), calldataload(pEval_c))
mstore(add(mIn, 128), calldataload(pEval_s1))
mstore(add(mIn, 160), calldataload(pEval_s2))
mstore(add(mIn, 192), calldataload(pEval_zw))
let v1 := mod(keccak256(mIn, 224), q)
mstore(add(pMem, pV1), v1)
// challenges.beta * challenges.xi
mstore(add(pMem, pBetaXi), mulmod(beta, aux, q))
// challenges.xi^n
<%for (let i=0; i<power;i++) {%>
aux:= mulmod(aux, aux, q)
<%}%>
mstore(add(pMem, pXin), aux)
// Zh
aux:= mod(add(sub(aux, 1), q), q)
mstore(add(pMem, pZh), aux)
mstore(add(pMem, pZhInv), aux) // We will invert later together with lagrange pols
// challenges.v^2, challenges.v^3, challenges.v^4, challenges.v^5
aux := mulmod(v1, v1, q)
mstore(add(pMem, pV2), aux)
aux := mulmod(aux, v1, q)
mstore(add(pMem, pV3), aux)
aux := mulmod(aux, v1, q)
mstore(add(pMem, pV4), aux)
aux := mulmod(aux, v1, q)
mstore(add(pMem, pV5), aux)
// challenges.u
mstore(mIn, calldataload(pWxi))
mstore(add(mIn, 32), calldataload(add(pWxi, 32)))
mstore(add(mIn, 64), calldataload(pWxiw))
mstore(add(mIn, 96), calldataload(add(pWxiw, 32)))
mstore(add(pMem, pU), mod(keccak256(mIn, 128), q))
}
function calculateLagrange(pMem) {
let w := 1
<% for (let i=1; i<=Math.max(nPublic, 1); i++) { %>
mstore(
add(pMem, pEval_l<%=i%>),
mulmod(
n,
mod(
add(
sub(
mload(add(pMem, pXi)),
w
),
q
),
q
),
q
)
)
<% if (i<Math.max(nPublic, 1)) { %>
w := mulmod(w, w1, q)
<% } %>
<% } %>
inverseArray(add(pMem, pZhInv), <%=Math.max(nPublic, 1)+1%> )
let zh := mload(add(pMem, pZh))
w := 1
<% for (let i=1; i<=Math.max(nPublic, 1); i++) { %>
<% if (i==1) { %>
mstore(
add(pMem, pEval_l1 ),
mulmod(
mload(add(pMem, pEval_l1 )),
zh,
q
)
)
<% } else { %>
mstore(
add(pMem, pEval_l<%=i%>),
mulmod(
w,
mulmod(
mload(add(pMem, pEval_l<%=i%>)),
zh,
q
),
q
)
)
<% } %>
<% if (i<Math.max(nPublic, 1)) { %>
w := mulmod(w, w1, q)
<% } %>
<% } %>
}
function calculatePI(pMem, pPub) {
let pl := 0
<% for (let i=0; i<nPublic; i++) { %>
pl := mod(
add(
sub(
pl,
mulmod(
mload(add(pMem, pEval_l<%=i+1%>)),
calldataload(add(pPub, <%=i*32%>)),
q
)
),
q
),
q
)
<% } %>
mstore(add(pMem, pPI), pl)
}
function calculateR0(pMem) {
let e1 := mload(add(pMem, pPI))
let e2 := mulmod(mload(add(pMem, pEval_l1)), mload(add(pMem, pAlpha2)), q)
let e3a := addmod(
calldataload(pEval_a),
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q),
q)
e3a := addmod(e3a, mload(add(pMem, pGamma)), q)
let e3b := addmod(
calldataload(pEval_b),
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q),
q)
e3b := addmod(e3b, mload(add(pMem, pGamma)), q)
let e3c := addmod(
calldataload(pEval_c),
mload(add(pMem, pGamma)),
q)
let e3 := mulmod(mulmod(e3a, e3b, q), e3c, q)
e3 := mulmod(e3, calldataload(pEval_zw), q)
e3 := mulmod(e3, mload(add(pMem, pAlpha)), q)
let r0 := addmod(e1, mod(sub(q, e2), q), q)
r0 := addmod(r0, mod(sub(q, e3), q), q)
mstore(add(pMem, pEval_r0) , r0)
}
function g1_set(pR, pP) {
mstore(pR, mload(pP))
mstore(add(pR, 32), mload(add(pP,32)))
}
function g1_setC(pR, x, y) {
mstore(pR, x)
mstore(add(pR, 32), y)
}
function g1_calldataSet(pR, pP) {
mstore(pR, calldataload(pP))
mstore(add(pR, 32), calldataload(add(pP, 32)))
}
function g1_acc(pR, pP) {
let mIn := mload(0x40)
mstore(mIn, mload(pR))
mstore(add(mIn,32), mload(add(pR, 32)))
mstore(add(mIn,64), mload(pP))
mstore(add(mIn,96), mload(add(pP, 32)))
let success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0,0x20)
}
}
function g1_mulAcc(pR, pP, s) {
let success
let mIn := mload(0x40)
mstore(mIn, mload(pP))
mstore(add(mIn,32), mload(add(pP, 32)))
mstore(add(mIn,64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
return(0,0x20)
}
mstore(add(mIn,64), mload(pR))
mstore(add(mIn,96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0,0x20)
}
}
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn,32), y)
mstore(add(mIn,64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
return(0,0x20)
}
mstore(add(mIn,64), mload(pR))
mstore(add(mIn,96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0,0x20)
}
}
function g1_mulSetC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn,32), y)
mstore(add(mIn,64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0,0x20)
}
}
function g1_mulSet(pR, pP, s) {
g1_mulSetC(pR, mload(pP), mload(add(pP, 32)), s)
}
function calculateD(pMem) {
let _pD:= add(pMem, pD)
let gamma := mload(add(pMem, pGamma))
let mIn := mload(0x40)
mstore(0x40, add(mIn, 256)) // d1, d2, d3 & d4 (4*64 bytes)
g1_setC(_pD, Qcx, Qcy)
g1_mulAccC(_pD, Qmx, Qmy, mulmod(calldataload(pEval_a), calldataload(pEval_b), q))
g1_mulAccC(_pD, Qlx, Qly, calldataload(pEval_a))
g1_mulAccC(_pD, Qrx, Qry, calldataload(pEval_b))
g1_mulAccC(_pD, Qox, Qoy, calldataload(pEval_c))
let betaxi := mload(add(pMem, pBetaXi))
let val1 := addmod(
addmod(calldataload(pEval_a), betaxi, q),
gamma, q)
let val2 := addmod(
addmod(
calldataload(pEval_b),
mulmod(betaxi, k1, q),
q), gamma, q)
let val3 := addmod(
addmod(
calldataload(pEval_c),
mulmod(betaxi, k2, q),
q), gamma, q)
let d2a := mulmod(
mulmod(mulmod(val1, val2, q), val3, q),
mload(add(pMem, pAlpha)),
q
)
let d2b := mulmod(
mload(add(pMem, pEval_l1)),
mload(add(pMem, pAlpha2)),
q
)
// We'll use mIn to save d2
g1_calldataSet(add(mIn, 192), pZ)
g1_mulSet(
mIn,
add(mIn, 192),
addmod(addmod(d2a, d2b, q), mload(add(pMem, pU)), q))
val1 := addmod(
addmod(
calldataload(pEval_a),
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q),
q), gamma, q)
val2 := addmod(
addmod(
calldataload(pEval_b),
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q),
q), gamma, q)
val3 := mulmod(
mulmod(mload(add(pMem, pAlpha)), mload(add(pMem, pBeta)), q),
calldataload(pEval_zw), q)
// We'll use mIn + 64 to save d3
g1_mulSetC(
add(mIn, 64),
S3x,
S3y,
mulmod(mulmod(val1, val2, q), val3, q))
// We'll use mIn + 128 to save d4
g1_calldataSet(add(mIn, 128), pT1)
g1_mulAccC(add(mIn, 128), calldataload(pT2), calldataload(add(pT2, 32)), mload(add(pMem, pXin)))
let xin2 := mulmod(mload(add(pMem, pXin)), mload(add(pMem, pXin)), q)
g1_mulAccC(add(mIn, 128), calldataload(pT3), calldataload(add(pT3, 32)) , xin2)
g1_mulSetC(add(mIn, 128), mload(add(mIn, 128)), mload(add(mIn, 160)), mload(add(pMem, pZh)))
mstore(add(add(mIn, 64), 32), mod(sub(qf, mload(add(add(mIn, 64), 32))), qf))
mstore(add(mIn, 160), mod(sub(qf, mload(add(mIn, 160))), qf))
g1_acc(_pD, mIn)
g1_acc(_pD, add(mIn, 64))
g1_acc(_pD, add(mIn, 128))
}
function calculateF(pMem) {
let p := add(pMem, pF)
g1_set(p, add(pMem, pD))
g1_mulAccC(p, calldataload(pA), calldataload(add(pA, 32)), mload(add(pMem, pV1)))
g1_mulAccC(p, calldataload(pB), calldataload(add(pB, 32)), mload(add(pMem, pV2)))
g1_mulAccC(p, calldataload(pC), calldataload(add(pC, 32)), mload(add(pMem, pV3)))
g1_mulAccC(p, S1x, S1y, mload(add(pMem, pV4)))
g1_mulAccC(p, S2x, S2y, mload(add(pMem, pV5)))
}
function calculateE(pMem) {
let s := mod(sub(q, mload(add(pMem, pEval_r0))), q)
s := addmod(s, mulmod(calldataload(pEval_a), mload(add(pMem, pV1)), q), q)
s := addmod(s, mulmod(calldataload(pEval_b), mload(add(pMem, pV2)), q), q)
s := addmod(s, mulmod(calldataload(pEval_c), mload(add(pMem, pV3)), q), q)
s := addmod(s, mulmod(calldataload(pEval_s1), mload(add(pMem, pV4)), q), q)
s := addmod(s, mulmod(calldataload(pEval_s2), mload(add(pMem, pV5)), q), q)
s := addmod(s, mulmod(calldataload(pEval_zw), mload(add(pMem, pU)), q), q)
g1_mulSetC(add(pMem, pE), G1x, G1y, s)
}
function checkPairing(pMem) -> isOk {
let mIn := mload(0x40)
mstore(0x40, add(mIn, 576)) // [0..383] = pairing data, [384..447] = pWxi, [448..512] = pWxiw
let _pWxi := add(mIn, 384)
let _pWxiw := add(mIn, 448)
let _aux := add(mIn, 512)
g1_calldataSet(_pWxi, pWxi)
g1_calldataSet(_pWxiw, pWxiw)
// A1
g1_mulSet(mIn, _pWxiw, mload(add(pMem, pU)))
g1_acc(mIn, _pWxi)
mstore(add(mIn, 32), mod(sub(qf, mload(add(mIn, 32))), qf))
// [X]_2
mstore(add(mIn,64), X2x2)
mstore(add(mIn,96), X2x1)
mstore(add(mIn,128), X2y2)
mstore(add(mIn,160), X2y1)
// B1
g1_mulSet(add(mIn, 192), _pWxi, mload(add(pMem, pXi)))
let s := mulmod(mload(add(pMem, pU)), mload(add(pMem, pXi)), q)
s := mulmod(s, w1, q)
g1_mulSet(_aux, _pWxiw, s)
g1_acc(add(mIn, 192), _aux)
g1_acc(add(mIn, 192), add(pMem, pF))
mstore(add(pMem, add(pE, 32)), mod(sub(qf, mload(add(pMem, add(pE, 32)))), qf))
g1_acc(add(mIn, 192), add(pMem, pE))
// [1]_2
mstore(add(mIn,256), G2x2)
mstore(add(mIn,288), G2x1)
mstore(add(mIn,320), G2y2)
mstore(add(mIn,352), G2y1)
let success := staticcall(sub(gas(), 2000), 8, mIn, 384, mIn, 0x20)
isOk := and(success, mload(mIn))
}
let pMem := mload(0x40)
mstore(0x40, add(pMem, lastMem))
checkInput()
calculateChallenges(pMem, _pubSignals)
calculateLagrange(pMem)
calculatePI(pMem, _pubSignals)
calculateR0(pMem)
calculateD(pMem)
calculateF(pMem)
calculateE(pMem)
let isValid := checkPairing(pMem)
mstore(0x40, sub(pMem, lastMem))
mstore(0, isValid)
return(0,0x20)
}
}
}`
export const PTAU_LIST = [
{
name: "final_8.ptau",
power: 8,
maxConstraint: "246",
ipfsHash: "QmNwT4UN6gT7vdDPNjmpShEVVbhi6C7tR6Y98X4aCT7sbq",
blake2bHash: null
},
{
name: "final_9.ptau",
power: 9,
maxConstraint: "512",
ipfsHash: "QmSBtobA8c27Yf2ypTpNtLxxxuadFzyQGksg7un2kN56yN",
blake2bHash: null
},
{
name: "final_10.ptau",
power: 10,
maxConstraint: "1k",
ipfsHash: "QmQ4dLmm3rRJKAm8CiVUedKu6xFNYsFD4UMVoSrD59jgHa",
blake2bHash: null
},
{
name: "final_11.ptau",
power: 11,
maxConstraint: "2k",
ipfsHash: "QmdRRJBznWaRnnpSnVcsdJ2RFQLxpVYbeMec3Fwf246MqU",
blake2bHash: null
},
{
name: "final_12.ptau",
power: 12,
maxConstraint: "4k",
ipfsHash: "Qmdge3jmta3qhGa61Da5UwVBcib6rgkqqHugQCgoT9hutS",
blake2bHash: null
},
{
name: "final_13.ptau",
power: 13,
maxConstraint: "8k",
ipfsHash: "QmUyXX6qYoExJ5pxyYiukmBSDkwqavUBBHaH2JDYVJX454",
blake2bHash: null
},
{
name: "final_14.ptau",
power: 14,
maxConstraint: "16k",
ipfsHash: "QmTiT4eiYz5KF7gQrDsgfCSTRv3wBPYJ4bRN1MmTRshpnW",
blake2bHash: null
},
{
name: "final_15.ptau",
power: 15,
maxConstraint: "32k",
ipfsHash: "QmeWPz6XMLX8HZwkRiiUffC5ziuiaHwNex6KUshpyLFpDg",
blake2bHash: null
},
{
name: "final_16.ptau",
power: 16,
maxConstraint: "64k",
ipfsHash: "QmVEbbVbBAgciBwGrNCx5HUJhQtatLVSEt54DZcrodYM3G",
blake2bHash: null
},
{
name: "final_17.ptau",
power: 17,
maxConstraint: "128k",
ipfsHash: "QmciCq5JcZQyTLvC9GRanrLBi82ZmSriq1Fr5zANkGHebf",
blake2bHash: null
},
{
name: "final_18.ptau",
power: 18,
maxConstraint: "256k",
ipfsHash: "QmRXbrYrrX2oA1FFRFivNi1TjhKoWv2x15g8PRFyV64UZP",
blake2bHash: null
},
{
name: "final_20.ptau",
power: 20,
maxConstraint: "1M",
ipfsHash: "QmQbrzmoZn5Ku6ENJmwZ1DMN1taAPgwLnp5TMfuSAz6tfh",
blake2bHash: null
}]

@ -1,5 +1,8 @@
import * as snarkjs from 'snarkjs'
import type { CircomPluginClient } from "../services/circomPluginClient"
import { Actions, AppState } from "../types"
import { ActionPayloadTypes, AppState, ICircuitAppContext } from "../types"
import { GROTH16_VERIFIER, PLONK_VERIFIER } from './constant'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
export const compileCircuit = async (plugin: CircomPluginClient, appState: AppState) => {
try {
@ -9,35 +12,163 @@ export const compileCircuit = async (plugin: CircomPluginClient, appState: AppSt
console.log('Existing circuit compilation in progress')
}
} catch (e) {
plugin.emit('statusChanged', { key: 'error', title: e.message, type: 'error' })
plugin.internalEvents.emit('circuit_compiling_errored', e)
console.error(e)
}
}
export const generateR1cs = async (plugin: CircomPluginClient, appState: AppState) => {
export const computeWitness = async (plugin: CircomPluginClient, appState: AppState, dispatch: ICircuitAppContext['dispatch'], status: string, witnessValues: Record<string, string>) => {
try {
if (appState.status !== "generating") {
await plugin.generateR1cs(appState.filePath, { version: appState.version, prime: appState.primeValue })
if (status !== "computing") {
const input = JSON.stringify(witnessValues)
const witness = await plugin.computeWitness(input)
if (appState.exportWtnsJson) {
const wtns = await snarkjs.wtns.exportJson(witness)
const wtnsJson = wtns.map(wtn => wtn.toString())
const fileName = extractNameFromKey(appState.filePath)
const writePath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '.wtn.json')}`
await plugin.call('fileManager', 'writeFile', writePath, JSON.stringify(wtnsJson, null, 2))
plugin._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'wtns.exportJson', writePath])
}
} else {
console.log('Existing r1cs generation in progress')
console.log('Existing witness computation in progress')
}
} catch (e) {
plugin.internalEvents.emit('circuit_generating_r1cs_errored', e)
console.error('Generating R1CS failed: ', e)
plugin.emit('statusChanged', { key: 'error', title: e.message, type: 'error' })
plugin.internalEvents.emit('circuit_computing_witness_errored', e)
console.error('Computing witness failed: ', e)
}
}
export const computeWitness = async (plugin: CircomPluginClient, status: string, witnessValues: Record<string, string>) => {
export const runSetupAndExport = async (plugin: CircomPluginClient, appState: AppState, dispatch: ICircuitAppContext['dispatch']) => {
try {
if (status !== "computing") {
const input = JSON.stringify(witnessValues)
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'exporting' })
dispatch({ type: 'SET_SETUP_EXPORT_FEEDBACK', payload: null })
plugin.emit('statusChanged', { key: 'none' })
const ptau_final = `https://ipfs-cluster.ethdevops.io/ipfs/${appState.ptauList.find(ptau => ptau.name === appState.ptauValue)?.ipfsHash}`
await plugin.generateR1cs(appState.filePath, { version: appState.version, prime: appState.primeValue })
await plugin.computeWitness(input)
} else {
console.log('Existing witness computation in progress')
const fileName = extractNameFromKey(appState.filePath)
const readPath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '.r1cs')}`
// @ts-ignore
const r1csBuffer = await plugin.call('fileManager', 'readFile', readPath, { encoding: null })
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer)
const zkey_final = { type: "mem" }
if (appState.provingScheme === 'groth16') {
plugin._paq.push(['trackEvent', 'circuit-compiler', 'runSetupAndExport', 'provingScheme', 'groth16'])
await snarkjs.zKey.newZKey(r1cs, ptau_final, zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
if (appState.exportVerificationKey) {
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/groth16/zk/keys/verification_key.json`, JSON.stringify(vKey, null, 2))
plugin._paq.push(['trackEvent', 'circuit-compiler', 'runSetupAndExport', 'zKey.exportVerificationKey', `${extractParentFromKey(appState.filePath)}/groth16/zk/keys/verification_key.json`])
}
if (appState.exportVerificationContract) {
const templates = { groth16: GROTH16_VERIFIER }
const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/groth16/zk/build/zk_verifier.sol`, solidityContract)
plugin._paq.push(['trackEvent', 'circuit-compiler', 'runSetupAndExport', 'zKey.exportSolidityVerifier', `${extractParentFromKey(appState.filePath)}/groth16/zk/build/zk_verifier.sol`])
}
dispatch({ type: 'SET_ZKEY', payload: zkey_final })
dispatch({ type: 'SET_VERIFICATION_KEY', payload: vKey })
} else if (appState.provingScheme === 'plonk') {
plugin._paq.push(['trackEvent', 'circuit-compiler', 'runSetupAndExport', 'provingScheme', 'plonk'])
await snarkjs.plonk.setup(r1cs, ptau_final, zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
if (appState.exportVerificationKey) {
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/plonk/zk/keys/verification_key.json`, JSON.stringify(vKey, null, 2))
plugin._paq.push(['trackEvent', 'circuit-compiler', 'runSetupAndExport', 'zKey.exportVerificationKey', `${extractParentFromKey(appState.filePath)}/plonk/zk/keys/verification_key.json`])
}
if (appState.exportVerificationContract) {
const templates = { plonk: PLONK_VERIFIER }
const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/plonk/zk/build/zk_verifier.sol`, solidityContract)
plugin._paq.push(['trackEvent', 'circuit-compiler', 'runSetupAndExport', 'zKey.exportSolidityVerifier', `${extractParentFromKey(appState.filePath)}/plonk/zk/build/zk_verifier.sol`])
}
dispatch({ type: 'SET_ZKEY', payload: zkey_final })
dispatch({ type: 'SET_VERIFICATION_KEY', payload: vKey })
}
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_SETUP_EXPORT_STATUS', payload: 'done' })
} catch (e) {
plugin.internalEvents.emit('circuit_computing_witness_errored', e)
console.error('Computing witness failed: ', e)
plugin._paq.push(['trackEvent', 'circuit-compiler', 'runSetupAndExport', 'error', e.message])
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'errored' })
console.error(e)
}
}
export const generateProof = async (plugin: CircomPluginClient, appState: AppState, dispatch: ICircuitAppContext['dispatch']) => {
try {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'proving' })
dispatch({ type: 'SET_PROOF_FEEDBACK', payload: null })
plugin.emit('statusChanged', { key: 'none' })
const fileName = extractNameFromKey(appState.filePath)
const r1csPath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '.r1cs')}`
// @ts-ignore
const r1csBuffer = await plugin.call('fileManager', 'readFile', r1csPath, { encoding: null })
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer)
const wtnsPath = r1csPath.replace('.r1cs', '.wtn')
// @ts-ignore
const wtnsBuffer = await plugin.call('fileManager', 'readFile', wtnsPath, { encoding: null })
// @ts-ignore
const wtns = new Uint8Array(wtnsBuffer)
const zkey_final = appState.zKey
const vKey = appState.verificationKey
await snarkjs.wtns.check(r1cs, wtns, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
if (appState.provingScheme === 'groth16') {
const { proof, publicSignals } = await snarkjs.groth16.prove(zkey_final, wtns, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/groth16/zk/build/proof.json`, JSON.stringify(proof, null, 2))
plugin.call('terminal', 'log', { type: 'log', value: 'zk proof validity ' + verified })
plugin._paq.push(['trackEvent', 'circuit-compiler', 'generateProof', 'groth16.prove', verified])
if (appState.exportVerifierCalldata) {
const calldata = await snarkjs.groth16.exportSolidityCallData(proof, publicSignals)
plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/groth16/zk/build/verifierCalldata.json`, calldata)
plugin._paq.push(['trackEvent', 'circuit-compiler', 'generateProof', 'groth16.exportSolidityCallData', `${extractParentFromKey(appState.filePath)}/groth16/zk/build/verifierCalldata.json`])
}
} else if (appState.provingScheme === 'plonk') {
const { proof, publicSignals } = await snarkjs.plonk.prove(zkey_final, wtns, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
const verified = await snarkjs.plonk.verify(vKey, publicSignals, proof, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/plonk/zk/build/proof.json`, JSON.stringify(proof, null, 2))
plugin.call('terminal', 'log', { type: 'log', value: 'zk proof validity ' + verified })
plugin._paq.push(['trackEvent', 'circuit-compiler', 'generateProof', 'plonk.prove', verified])
if (appState.exportVerifierCalldata) {
const calldata = await snarkjs.plonk.exportSolidityCallData(proof, publicSignals)
plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/plonk/zk/build/verifierCalldata.json`, calldata)
plugin._paq.push(['trackEvent', 'circuit-compiler', 'generateProof', 'plonk.exportSolidityCallData', `${extractParentFromKey(appState.filePath)}/plonk/zk/build/verifierCalldata.json`])
}
}
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_PROOF_FEEDBACK', payload: null })
} catch (e) {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'errored' })
dispatch({ type: 'SET_PROOF_FEEDBACK', payload: e.message })
console.error(e)
}
}
function zkLogger(plugin: CircomPluginClient, dispatch: ICircuitAppContext['dispatch'], dispatchType: keyof ActionPayloadTypes) {
return {
info: (...args) => plugin.call('terminal', 'log', { type: 'log', value: args.join(' ') }),
debug: (...args) => plugin.call('terminal', 'log', { type: 'log', value: args.join(' ') }),
error: (...args) => {
plugin.call('terminal', 'log', { type: 'error', value: args.join(' ') })
dispatch({ type: dispatchType as any, payload: args.join(' ') })
plugin.emit('statusChanged', { key: args.length, title: `You have ${args.length} problem${args.length === 1 ? '' : 's'}`, type: 'error' })
}
}
}

@ -50,16 +50,11 @@ function App() {
})
plugin.internalEvents.on('circuit_compiling_errored', compilerErrored)
// r1cs events
plugin.internalEvents.on('circuit_generating_r1cs_start', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'generating' }))
plugin.internalEvents.on('circuit_generating_r1cs_done', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' }))
plugin.internalEvents.on('circuit_generating_r1cs_errored', compilerErrored)
// witness events
plugin.internalEvents.on('circuit_computing_witness_start', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'computing' }))
plugin.internalEvents.on('circuit_computing_witness_done', () => {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: null })
})
plugin.internalEvents.on('circuit_computing_witness_errored', compilerErrored)
@ -86,6 +81,7 @@ function App() {
if (appState.autoCompile) await compileCircuit(plugin, appState)
})()
setIsContentChanged(false)
if (appState.setupExportStatus === 'done') dispatch({ type: 'SET_SETUP_EXPORT_STATUS', payload: 'update' })
}
}, [appState.autoCompile, isContentChanged])
@ -103,6 +99,12 @@ function App() {
dispatch({ type: 'SET_SIGNAL_INPUTS', payload: [] })
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: null })
dispatch({ type: 'SET_SETUP_EXPORT_FEEDBACK', payload: null })
dispatch({ type: 'SET_PROOF_FEEDBACK', payload: null })
dispatch({ type: 'SET_SETUP_EXPORT_STATUS', payload: null })
dispatch({ type: 'SET_VERIFICATION_KEY', payload: null })
dispatch({ type: 'SET_ZKEY', payload: null })
}
}, [appState.filePath])
@ -118,9 +120,9 @@ function App() {
try {
const report = JSON.parse(err.message)
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: report })
} catch (e) {
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: err.message })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: err.message })
}
}

@ -1,11 +1,9 @@
import { CompileBtn } from "./compileBtn";
import { R1CSBtn } from "./r1csBtn";
import { CompileBtn } from "./compileBtn"
export function CircuitActions () {
return (
<div className="pb-3">
<div className="pb-2">
<CompileBtn />
<R1CSBtn />
</div>
)
}

@ -20,18 +20,15 @@ export function CompileBtn () {
}
>
<button
className="btn btn-primary btn-block d-block w-100 text-break mb-1 mt-3"
className="btn btn-primary btn-block d-block w-100 text-break mb-1 mt-1"
onClick={() => { compileCircuit(plugin, appState) }}
disabled={(appState.filePath === "") || (appState.status === "compiling") || (appState.status === "generating")}
disabled={(appState.filePath === "") || (appState.status === "compiling")}
data-id="compile_circuit_btn"
>
<div className="d-flex align-items-center justify-content-center">
<RenderIf condition={appState.status === 'compiling'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={appState.status === 'compiling'}>
<i className="fas fa-sync mr-2" aria-hidden="true"></i>
</RenderIfNot>
<div className="text-truncate overflow-hidden text-nowrap">
<span>
<FormattedMessage id="circuit.compile" />

@ -1,36 +0,0 @@
import { useState } from "react"
import { FormattedMessage } from "react-intl"
import { RenderIf, RenderIfNot } from "@remix-ui/helper"
export function ConfigToggler ({ children }: { children: JSX.Element }) {
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const toggleConfigurations = () => {
setToggleExpander(!toggleExpander)
}
return (
<div>
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations}>
<div className="d-flex">
<label className="mt-1 circuit_config_section">
<FormattedMessage id="circuit.advancedConfigurations" />
</label>
</div>
<div>
<span data-id="scConfigExpander" onClick={toggleConfigurations}>
<RenderIf condition={toggleExpander}>
<i className="fas fa-angle-down" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={toggleExpander}>
<i className="fas fa-angle-right" aria-hidden="true"></i>
</RenderIfNot>
</span>
</div>
</div>
<RenderIf condition={toggleExpander}>
{ children }
</RenderIf>
</div>
)
}

@ -4,9 +4,9 @@ import { ConfigurationsProps, PrimeValue } from "../types"
export function Configurations ({primeValue, setPrimeValue, versionValue}: ConfigurationsProps) {
return (
<div className="pb-2 border-bottom flex-column">
<div className="flex-column">
<div className="flex-column d-flex">
<div className="mb-2 ml-0">
<div className="ml-0">
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.prime" />
</label>

@ -1,16 +1,17 @@
import { useContext } from 'react'
import { CustomTooltip, RenderIf } from '@remix-ui/helper'
import {FormattedMessage} from 'react-intl'
import { FormattedMessage } from 'react-intl'
import { CircuitAppContext } from '../contexts'
import { CompileOptions } from './options'
import { VersionList } from './versions'
import { ConfigToggler } from './configToggler'
import { Toggler } from './toggler'
import { Configurations } from './configurations'
import { CircuitActions } from './actions'
import { WitnessToggler } from './witnessToggler'
import { WitnessSection } from './witness'
import { CompilerFeedback } from './feedback'
import { CompilerReport, PrimeValue } from '../types'
import { SetupExports } from './setupExports'
import { GenerateProof } from './generateProof'
export function Container () {
const circuitApp = useContext(CircuitAppContext)
@ -113,17 +114,48 @@ export function Container () {
</CustomTooltip>
<VersionList setVersion={handleVersionSelect} versionList={circuitApp.appState.versionList} currentVersion={circuitApp.appState.version} />
<CompileOptions setCircuitAutoCompile={handleCircuitAutoCompile} setCircuitHideWarnings={handleCircuitHideWarnings} autoCompile={circuitApp.appState.autoCompile} hideWarnings={circuitApp.appState.hideWarnings} />
<ConfigToggler>
<Toggler title='circuit.advancedConfigurations' dataId=''>
<Configurations setPrimeValue={handlePrimeChange} primeValue={circuitApp.appState.primeValue} versionValue={circuitApp.appState.version} />
</ConfigToggler>
</Toggler>
<CircuitActions />
<RenderIf condition={circuitApp.appState.status !== 'compiling'}>
<CompilerFeedback feedback={circuitApp.appState.compilerFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
<RenderIf condition={circuitApp.appState.signalInputs.length > 0}>
<Toggler
title='circuit.setupExports'
dataId='setup_exports_toggler'
show={!circuitApp.appState.setupExportStatus}
icon={ circuitApp.appState.setupExportStatus === 'done' ? 'fas fa-check-circle text-success' : circuitApp.appState.setupExportStatus === 'update' ? 'fas fa-exclamation-triangle text-warning' : null }
iconTooltip={ circuitApp.appState.setupExportStatus === 'update' ? 'circom file content changed, please compile and re-run setup to update exported keys.' : null }
>
<>
<SetupExports />
<RenderIf condition={circuitApp.appState.status !== 'exporting'}>
<CompilerFeedback feedback={circuitApp.appState.setupExportFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
</>
</Toggler>
</RenderIf>
<RenderIf condition={circuitApp.appState.signalInputs.length > 0}>
<WitnessToggler>
<WitnessSection plugin={circuitApp.plugin} signalInputs={circuitApp.appState.signalInputs} status={circuitApp.appState.status} />
</WitnessToggler>
<Toggler title='circuit.computeWitness' dataId='witness_toggler' show={!!circuitApp.appState.setupExportStatus}>
<>
<WitnessSection />
<RenderIf condition={circuitApp.appState.status !== 'computing'}>
<CompilerFeedback feedback={circuitApp.appState.computeFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
</>
</Toggler>
</RenderIf>
<RenderIf condition={(circuitApp.appState.status !== 'compiling') && (circuitApp.appState.status !== 'computing') && (circuitApp.appState.status !== 'generating')}>
<CompilerFeedback feedback={circuitApp.appState.feedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
<RenderIf condition={circuitApp.appState.signalInputs.length > 0}>
<Toggler title='circuit.generateProof' dataId='generate_proof_toggler' show={!!circuitApp.appState.setupExportStatus}>
<>
<GenerateProof />
<RenderIf condition={circuitApp.appState.status !== 'proving'}>
<CompilerFeedback feedback={circuitApp.appState.proofFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
</>
</Toggler>
</RenderIf>
</div>
</div>

@ -1,11 +1,11 @@
import { useState } from 'react'
import { CompilerFeedbackProps, CompilerReport } from '../types'
import { RenderIf } from '@remix-ui/helper'
import {CopyToClipboard} from '@remix-ui/clipboard'
import { CopyToClipboard } from '@remix-ui/clipboard'
import { FeedbackAlert } from './feedbackAlert'
export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openErrorLocation, askGPT }: CompilerFeedbackProps) {
const [ showException, setShowException ] = useState<boolean>(true)
const [showException, setShowException] = useState<boolean>(true)
const handleCloseException = () => {
setShowException(false)
@ -23,46 +23,50 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
return (
<div>
<div className="circuit_errors_box py-4">
<RenderIf condition={ (typeof feedback === "string") && showException }>
<div className="circuit_feedback error alert alert-danger" data-id="circuit_feedback">
<span> <>{ feedback }</> </span>
<div className="close" data-id="renderer" onClick={handleCloseException}>
<i className="fas fa-times"></i>
</div>
<div className="d-flex pt-1 flex-row-reverse">
<span className="ml-3 pt-1 py-1" >
<CopyToClipboard content={feedback} className="p-0 m-0 far fa-copy error" direction={'top'} />
</span>
</div>
</div>
</RenderIf>
<RenderIf condition={ Array.isArray(feedback) }>
<>
{
Array.isArray(feedback) && feedback.map((response, index) => (
<div key={index} onClick={() => handleOpenError(response)}>
<RenderIf condition={response.type === 'Error'}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message + (response.labels[0] ? ": " + response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : '')}
askGPT={ () => handleAskGPT(response) } />
</div>
</RenderIf>
<RenderIf condition={(response.type === 'Warning') && !hideWarnings}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message}
askGPT={() => { handleAskGPT(response) }} />
</div>
</RenderIf>
{
(feedback && typeof feedback === 'string') || (Array.isArray(feedback) && feedback.length > 0) ? (
<div className="circuit_errors_box">
<RenderIf condition={ (typeof feedback === "string") && showException }>
<div className="circuit_feedback error alert alert-danger" data-id="circuit_feedback">
<span> <>{ feedback }</> </span>
<div className="close" data-id="renderer" onClick={handleCloseException}>
<i className="fas fa-times"></i>
</div>
)
)
}
</>
</RenderIf>
</div>
<div className="d-flex pt-1 flex-row-reverse">
<span className="ml-3 pt-1 py-1" >
<CopyToClipboard content={feedback} className="p-0 m-0 far fa-copy error" direction={'top'} />
</span>
</div>
</div>
</RenderIf>
<RenderIf condition={ Array.isArray(feedback) }>
<>
{
Array.isArray(feedback) && feedback.map((response, index) => (
<div key={index} onClick={() => handleOpenError(response)}>
<RenderIf condition={response.type === 'Error'}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message + (response.labels[0] ? ": " + response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : '')}
askGPT={ () => handleAskGPT(response) } />
</div>
</RenderIf>
<RenderIf condition={(response.type === 'Warning') && !hideWarnings}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message}
askGPT={() => { handleAskGPT(response) }} />
</div>
</RenderIf>
</div>
)
)
}
</>
</RenderIf>
</div>
) : <></>
}
</div>
)
}

@ -0,0 +1,39 @@
import { RenderIf } from "@remix-ui/helper"
import { FormattedMessage } from "react-intl"
import { generateProof } from "../actions"
import { CircuitAppContext } from "../contexts"
import { useContext } from "react"
export function GenerateProof () {
const circuitApp = useContext(CircuitAppContext)
const status = circuitApp.appState.status
return (
<div className="flex-column d-flex">
<div className="mt-2 custom-control custom-checkbox">
<input
className="custom-control-input"
type="checkbox"
title="Export Verifier Calldata"
id="circuitExportVerifierCalldata"
onChange={() => circuitApp.dispatch({ type: 'SET_EXPORT_VERIFIER_CALLDATA', payload: !circuitApp.appState.exportVerifierCalldata })}
checked={circuitApp.appState.exportVerifierCalldata}
/>
<label className="form-check-label custom-control-label pt-1" htmlFor="circuitExportVerifierCalldata">
<FormattedMessage id="circuit.exportVerifierCalldata" />
</label>
</div>
<button
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-1"
onClick={() => generateProof(circuitApp.plugin, circuitApp.appState, circuitApp.dispatch)}
disabled={(status === "compiling") || (status === "computing") || (status === "proving") || (status === "exporting")}
data-id="compute_witness_btn"
>
<RenderIf condition={status === 'proving'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<FormattedMessage id="circuit.generateProof" />
</button>
</div>
)
}

@ -4,7 +4,7 @@ import { CompileOptionsProps } from '../types'
export function CompileOptions ({autoCompile, hideWarnings, setCircuitAutoCompile, setCircuitHideWarnings}: CompileOptionsProps) {
return (
<div className='pb-2'>
<div>
<div className="mt-2 custom-control custom-checkbox">
<input
className="custom-control-input"

@ -1,44 +0,0 @@
import { CustomTooltip, RenderIf, RenderIfNot } from "@remix-ui/helper";
import { useContext } from "react";
import { CircuitAppContext } from "../contexts";
import { FormattedMessage } from "react-intl";
import { generateR1cs } from "../actions";
export function R1CSBtn () {
const { plugin, appState } = useContext(CircuitAppContext)
return (
<button
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-2"
onClick={() => { generateR1cs(plugin, appState) }}
disabled={(appState.filePath === "") || (appState.status === "compiling") || (appState.status === "generating") || (appState.status === "computing")}
data-id="generate_r1cs_btn"
>
<CustomTooltip
placement="auto"
tooltipId="overlay-tooltip-compile"
tooltipText={
<div className="text-left">
<div>
Outputs the constraints in r1cs format
</div>
</div>
}
>
<div className="d-flex align-items-center justify-content-center">
<RenderIf condition={appState.status === 'generating'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={appState.status === 'generating'}>
<i className="fas fa-sync mr-2" aria-hidden="true"></i>
</RenderIfNot>
<div className="text-truncate overflow-hidden text-nowrap">
<span>
<FormattedMessage id="circuit.generateR1cs" />
</span>
</div>
</div>
</CustomTooltip>
</button>
)
}

@ -0,0 +1,110 @@
import { CustomTooltip } from "@remix-ui/helper"
import { FormattedMessage } from "react-intl"
import { SetupExportsBtn } from "./setupExportsBtn"
import { useContext } from "react"
import { CircuitAppContext } from "../contexts"
import { runSetupAndExport } from "../actions"
export function SetupExports () {
const circuitApp = useContext(CircuitAppContext)
return (
<div className="flex-column">
<div className="flex-column d-flex">
<div className="mb-1 ml-0">
<label className="circuit_inner_label form-check-label">
<FormattedMessage id="circuit.provingScheme" />
</label>
<div className="radio custom-control custom-radio mb-1 form-check">
<input
type="radio"
className="align-middle custom-control-input"
name="circuitProvingScheme"
id="groth16ProvingScheme"
onClick={() => circuitApp.dispatch({ type: 'SET_PROVING_SCHEME', payload: 'groth16' })}
value='groth16'
checked={circuitApp.appState.provingScheme === 'groth16'}
readOnly
/>
<label className="form-check-label custom-control-label" data-id="groth16ProvingScheme" htmlFor="groth16ProvingScheme" style={{ paddingTop: '0.125rem' }}>
Groth16
</label>
</div>
<div className="radio custom-control custom-radio form-check">
<input
type="radio"
className="align-middle custom-control-input"
name="circuitProvingScheme"
id="plonkProvingScheme"
onClick={() => circuitApp.dispatch({ type: 'SET_PROVING_SCHEME', payload: 'plonk' })}
value='plonk'
checked={circuitApp.appState.provingScheme === 'plonk'}
readOnly
/>
<label className="form-check-label custom-control-label" data-id="plonkProvingScheme" htmlFor="plonkProvingScheme" style={{ paddingTop: '0.125rem' }}>
Plonk
</label>
</div>
</div>
<div className="mb-1 ml-0">
<label className="circuit_inner_label form-check-label">
<FormattedMessage id="circuit.ptau" />
</label>
<CustomTooltip
placement={"auto"}
tooltipId="circuitPtauTooltip"
tooltipClasses="text-nowrap"
tooltipText={<span>{'To choose the from the list of ptau files'}</span>}
>
<div className="mb-1">
<select
value={circuitApp.appState.ptauValue}
className="custom-select"
style={{
pointerEvents: 'auto'
}}
onChange={(e) => circuitApp.dispatch({ type: 'SET_PTAU_VALUE', payload: e.target.value })}
data-id="circuitPtauSelect"
>
{
circuitApp.appState.ptauList.map((ptau, index) => {
return (
<option key={index} value={ptau.name} data-id={`dropdown-item-${ptau.name}`}>{`${ptau.name} (${ptau.maxConstraint} max constr.)`}</option>
)
})
}
</select>
</div>
</CustomTooltip>
<div className="mt-2 custom-control custom-checkbox">
<input
className="custom-control-input"
type="checkbox"
title="Export Verifier Contract"
id="circuitExportVerifierContract"
onChange={() => circuitApp.dispatch({ type: 'SET_EXPORT_VERIFICATION_CONTRACT', payload: !circuitApp.appState.exportVerificationContract })}
checked={circuitApp.appState.exportVerificationContract}
/>
<label className="form-check-label custom-control-label pt-1" htmlFor="circuitExportVerifierContract">
<FormattedMessage id="circuit.exportVerifierContract" />
</label>
</div>
<div className="mt-2 custom-control custom-checkbox">
<input
className="custom-control-input"
type="checkbox"
title="Export Verification Key"
id="circuitExportVerificationKey"
onChange={() => circuitApp.dispatch({ type: 'SET_EXPORT_VERIFICATION_KEY', payload: !circuitApp.appState.exportVerificationKey })}
checked={circuitApp.appState.exportVerificationKey}
/>
<label className="form-check-label custom-control-label pt-1" htmlFor="circuitExportVerificationKey">
<FormattedMessage id="circuit.exportVerificationKey" />
</label>
</div>
<SetupExportsBtn handleRunSetup={() => runSetupAndExport(circuitApp.plugin, circuitApp.appState, circuitApp.dispatch)} status={circuitApp.appState.status} />
</div>
</div>
</div>
)
}

@ -0,0 +1,34 @@
import { CustomTooltip, RenderIf } from "@remix-ui/helper"
import { FormattedMessage } from "react-intl"
import { CompilerStatus } from "../types"
export function SetupExportsBtn ({ handleRunSetup, status }: { handleRunSetup: () => Promise<void>, status: CompilerStatus }) {
return <button
className="btn btn-secondary btn-block d-block w-100 text-break mt-2"
onClick={handleRunSetup}
data-id="runSetupBtn"
>
<CustomTooltip
placement="auto"
tooltipId="overlay-tooltip-compile"
tooltipText={
<div className="text-left">
<div>
Click to setup and export verification keys
</div>
</div>
}
>
<div className="d-flex align-items-center justify-content-center">
<RenderIf condition={status === 'exporting'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<div className="text-truncate overflow-hidden text-nowrap">
<span>
<FormattedMessage id="circuit.runSetup" />
</span>
</div>
</div>
</CustomTooltip>
</button>
}

@ -0,0 +1,50 @@
import { useEffect, useState } from "react"
import { FormattedMessage } from "react-intl"
import { CustomTooltip, RenderIf, RenderIfNot } from "@remix-ui/helper"
export function Toggler ({ children, title, dataId, show = false, icon, iconTooltip }: { children: JSX.Element, title: string, dataId: string, show?: boolean, icon?: string, iconTooltip?: string }) {
const [toggleExpander, setToggleExpander] = useState<boolean>(show)
useEffect(() => {
setToggleExpander(show)
}, [show])
const toggleConfigurations = () => {
setToggleExpander(!toggleExpander)
}
return (
<div className="pt-2 border-top pb-2">
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations} data-id={dataId}>
<div className="d-flex">
<label className="mt-1 circuit_config_section">
<FormattedMessage id={title} />
{ icon ? iconTooltip ? (
<CustomTooltip
placement="auto"
tooltipId="rerunSetupWarningTooltip"
tooltipClasses="text-nowrap"
tooltipText={iconTooltip}
>
<span className={`${icon} border-0 p-0 ml-2`} aria-hidden="true"></span>
</CustomTooltip>) :
<span className={`${icon} border-0 p-0 ml-2`} aria-hidden="true"></span> : null }
</label>
</div>
<div>
<span data-id="scConfigExpander" onClick={toggleConfigurations}>
<RenderIf condition={toggleExpander}>
<i className="fas fa-angle-down" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={toggleExpander}>
<i className="fas fa-angle-right" aria-hidden="true"></i>
</RenderIfNot>
</span>
</div>
</div>
<RenderIf condition={toggleExpander}>
{ children }
</RenderIf>
</div>
)
}

@ -1,12 +1,14 @@
import { RenderIf, RenderIfNot } from "@remix-ui/helper";
import { FormattedMessage } from "react-intl";
import { CompilerStatus } from "../types";
import { computeWitness } from "../actions";
import { useState } from "react";
import type { CircomPluginClient } from "../services/circomPluginClient";
import { useContext, useState } from "react";
import * as remixLib from '@remix-project/remix-lib'
import { CircuitAppContext } from "../contexts";
export function WitnessSection ({ plugin, signalInputs, status }: {plugin: CircomPluginClient, signalInputs: string[], status: CompilerStatus}) {
export function WitnessSection () {
const circuitApp = useContext(CircuitAppContext)
const { signalInputs, status, exportWtnsJson } = circuitApp.appState
const { plugin, dispatch, appState } = circuitApp
const [witnessValues, setWitnessValues] = useState<Record<string, string>>({})
const handleSignalInput = (e: any) => {
@ -34,37 +36,45 @@ export function WitnessSection ({ plugin, signalInputs, status }: {plugin: Circo
}
return (
<div className="pb-2 border-bottom flex-column">
<div className="flex-column d-flex">
<RenderIf condition={signalInputs.length > 0}>
<>
{
signalInputs.map((input, index) => (
<div className="mb-2 ml-0" key={index}>
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.signalInput" /> { input }
</label>
<input className="form-control m-0 txinput" placeholder={input} name={input} onChange={handleSignalInput} data-id={`circuit_input_${input}`} />
</div>
))
}
<button
className="btn btn-sm btn-secondary"
onClick={() => { computeWitness(plugin, status, witnessValues) }}
disabled={(status === "compiling") || (status === "generating") || (status === "computing")}
data-id="compute_witness_btn"
>
<RenderIf condition={status === 'computing'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={status === 'computing'}>
<i className="fas fa-sync mr-2" aria-hidden="true"></i>
</RenderIfNot>
<FormattedMessage id="circuit.compute" />
</button>
</>
</RenderIf>
</div>
<div className="flex-column d-flex">
<RenderIf condition={signalInputs.length > 0}>
<>
{
signalInputs.map((input, index) => (
<div className="mb-2 ml-0" key={index}>
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.signalInput" /> { input }
</label>
<input className="form-control m-0 txinput" placeholder={input} name={input} onChange={handleSignalInput} data-id={`circuit_input_${input}`} />
</div>
))
}
<div className="custom-control custom-checkbox">
<input
className="custom-control-input"
type="checkbox"
title="Export Witness As JSON"
id="circuitExportWtnsJson"
onChange={() => { dispatch({ type: 'SET_EXPORT_WTNS_JSON', payload: !exportWtnsJson }) }}
checked={exportWtnsJson}
/>
<label className="form-check-label custom-control-label pt-1" htmlFor="circuitExportWtnsJson">
<FormattedMessage id="circuit.exportWtnsJson" />
</label>
</div>
<button
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-1"
onClick={() => { computeWitness(plugin, appState, dispatch, status, witnessValues) }}
disabled={(status === "compiling") || (status === "computing")}
data-id="compute_witness_btn"
>
<RenderIf condition={status === 'computing'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<FormattedMessage id="circuit.compute" />
</button>
</>
</RenderIf>
</div>
)
}

@ -1,36 +0,0 @@
import { useState } from "react"
import { FormattedMessage } from "react-intl"
import { RenderIf, RenderIfNot } from "@remix-ui/helper"
export function WitnessToggler ({ children }: { children: JSX.Element }) {
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const toggleConfigurations = () => {
setToggleExpander(!toggleExpander)
}
return (
<div>
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations} data-id="witness_toggler">
<div className="d-flex">
<label className="mt-1 circuit_config_section">
<FormattedMessage id="circuit.computeWitness" />
</label>
</div>
<div>
<span data-id="scConfigExpander" onClick={toggleConfigurations}>
<RenderIf condition={toggleExpander}>
<i className="fas fa-angle-down" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={toggleExpander}>
<i className="fas fa-angle-right" aria-hidden="true"></i>
</RenderIfNot>
</span>
</div>
</div>
<RenderIf condition={toggleExpander}>
{ children }
</RenderIf>
</div>
)
}

@ -1,3 +1,4 @@
import { PTAU_LIST } from '../actions/constant'
import { Actions, AppState } from '../types'
import { compiler_list } from 'circom_wasm'
@ -11,7 +12,20 @@ export const appInitialState: AppState = {
autoCompile: false,
hideWarnings: false,
signalInputs: [],
feedback: null
compilerFeedback: null,
computeFeedback: null,
proofFeedback: null,
setupExportFeedback: null,
setupExportStatus: null,
provingScheme: 'groth16',
ptauList: PTAU_LIST,
ptauValue: "final_14.ptau",
exportVerificationContract: true,
exportVerificationKey: true,
exportVerifierCalldata: true,
exportWtnsJson: false,
verificationKey: null,
zKey: null
}
export const appReducer = (state = appInitialState, action: Actions): AppState => {
@ -62,7 +76,25 @@ export const appReducer = (state = appInitialState, action: Actions): AppState =
case 'SET_COMPILER_FEEDBACK':
return {
...state,
feedback: action.payload
compilerFeedback: action.payload
}
case 'SET_COMPUTE_FEEDBACK':
return {
...state,
computeFeedback: action.payload
}
case 'SET_SETUP_EXPORT_FEEDBACK':
return {
...state,
setupExportFeedback: action.payload
}
case 'SET_PROOF_FEEDBACK':
return {
...state,
proofFeedback: action.payload
}
case 'SET_FILE_PATH_TO_ID':
@ -71,6 +103,60 @@ export const appReducer = (state = appInitialState, action: Actions): AppState =
filePathToId: action.payload
}
case 'SET_PROVING_SCHEME':
return {
...state,
provingScheme: action.payload
}
case 'SET_PTAU_VALUE':
return {
...state,
ptauValue: action.payload
}
case 'SET_EXPORT_VERIFICATION_CONTRACT':
return {
...state,
exportVerificationContract: action.payload
}
case 'SET_EXPORT_VERIFICATION_KEY':
return {
...state,
exportVerificationKey: action.payload
}
case 'SET_EXPORT_VERIFIER_CALLDATA':
return {
...state,
exportVerifierCalldata: action.payload
}
case 'SET_SETUP_EXPORT_STATUS':
return {
...state,
setupExportStatus: action.payload
}
case 'SET_EXPORT_WTNS_JSON':
return {
...state,
exportWtnsJson: action.payload
}
case 'SET_VERIFICATION_KEY':
return {
...state,
verificationKey: action.payload
}
case 'SET_ZKEY':
return {
...state,
zKey: action.payload
}
default:
throw new Error()
}

@ -20,7 +20,7 @@ export class CircomPluginClient extends PluginClient {
private lastParsedFiles: Record<string, string> = {}
private lastCompiledFile: string = ''
private compiler: typeof compilerV215 & typeof compilerV216 & typeof compilerV217 & typeof compilerV218
private _paq = {
public _paq = {
push: (args) => {
this.call('matomo' as any, 'track', args)
}
@ -197,24 +197,15 @@ export class CircomPluginClient extends PluginClient {
}
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_generating_r1cs_start')
this.emit('statusChanged', { key: 'loading', title: 'Generating...', type: 'info' })
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
this.internalEvents.emit('circuit_parsing_errored', parseErrors)
this.logCompilerReport(parseErrors)
return
} else if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors)
this.logCompilerReport(parseErrors)
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'r1cs generated successfully', type: 'success' })
}
if (compilationConfig) {
const { prime, version } = compilationConfig
@ -232,7 +223,6 @@ export class CircomPluginClient extends PluginClient {
this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed'])
throw new Error(r1csErrors)
} else {
this.internalEvents.emit('circuit_generating_r1cs_done')
const fileName = extractNameFromKey(path)
const writePath = extractParentFromKey(path) + "/.bin/" + fileName.replace('circom', 'r1cs')
@ -247,7 +237,7 @@ export class CircomPluginClient extends PluginClient {
}
}
async computeWitness (input: string): Promise<void> {
async computeWitness (input: string): Promise<Uint8Array> {
this.internalEvents.emit('circuit_computing_witness_start')
this.emit('statusChanged', { key: 'loading', title: 'Computing...', type: 'info' })
const wasmPath = this.lastCompiledCircuitPath
@ -259,9 +249,10 @@ export class CircomPluginClient extends PluginClient {
const witness = this.compiler ? await this.compiler.generate_witness(dataRead, input) : await generate_witness(dataRead, input)
// @ts-ignore
await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, true)
this._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'Witness computing successful'])
this._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'compiler.generate_witness', wasmPath.replace('.wasm', '.wtn')])
this.internalEvents.emit('circuit_computing_witness_done')
this.emit('statusChanged', { key: 'succeed', title: 'witness computed successfully', type: 'success' })
return witness
}
async resolveDependencies(filePath: string, fileContent: string, output?: Record<string, string>, depPath: string = '', blackPath: string[] = []): Promise<Record<string, string>> {

@ -2,7 +2,19 @@ import { compiler_list } from 'circom_wasm'
import { Dispatch } from 'react'
import type { CircomPluginClient } from '../services/circomPluginClient'
export type CompilerStatus = "compiling" | "generating" | "computing" | "idle" | "errored" | "warning"
export type CompilerStatus = "compiling" | "computing" | "idle" | "errored" | "warning" | "exporting" | "proving"
export type ProvingScheme = 'groth16' | 'plonk'
export type SetupExportStatus = 'done' | 'update'
export type PtauFile = {
name: string,
power: number,
maxConstraint: string,
ipfsHash: string,
blake2bHash: string
}
export interface ICircuitAppContext {
appState: AppState
dispatch: Dispatch<Actions>,
@ -17,8 +29,20 @@ export interface ActionPayloadTypes {
SET_AUTO_COMPILE: boolean,
SET_HIDE_WARNINGS: boolean,
SET_SIGNAL_INPUTS: string[],
SET_COMPILER_FEEDBACK: string | CompilerReport[]
SET_FILE_PATH_TO_ID: Record<number, string>
SET_COMPILER_FEEDBACK: string | CompilerReport[],
SET_COMPUTE_FEEDBACK: string | CompilerReport[],
SET_PROOF_FEEDBACK: string | CompilerReport[],
SET_SETUP_EXPORT_FEEDBACK: string | CompilerReport[],
SET_FILE_PATH_TO_ID: Record<number, string>,
SET_PROVING_SCHEME: ProvingScheme,
SET_PTAU_VALUE: string,
SET_EXPORT_VERIFICATION_CONTRACT: boolean,
SET_EXPORT_VERIFICATION_KEY: boolean,
SET_EXPORT_VERIFIER_CALLDATA: boolean,
SET_EXPORT_WTNS_JSON: boolean,
SET_SETUP_EXPORT_STATUS: SetupExportStatus,
SET_VERIFICATION_KEY: Record<string, any>,
SET_ZKEY: any
}
export interface Action<T extends keyof ActionPayloadTypes> {
type: T
@ -37,7 +61,20 @@ export interface AppState {
autoCompile: boolean,
hideWarnings: boolean,
signalInputs: string[],
feedback: string | CompilerReport[]
compilerFeedback: string | CompilerReport[],
computeFeedback: string | CompilerReport[],
proofFeedback: string | CompilerReport[],
setupExportFeedback: string | CompilerReport[],
setupExportStatus: SetupExportStatus,
provingScheme: ProvingScheme,
ptauList: Array<PtauFile>,
ptauValue: string,
exportVerificationContract: boolean,
exportVerificationKey: boolean,
exportVerifierCalldata: boolean,
exportWtnsJson: boolean,
verificationKey: Record<string, any>,
zKey: Uint8Array
}
export type CompilationConfig = {

@ -37,8 +37,6 @@ body {
text-transform: uppercase;
}
.circuit_errors_box {
padding-left: 5px;
padding-right: 5px;
word-break: break-word;
}
.circuit_feedback.success,

@ -4,7 +4,7 @@
"displayName": "Circom ZKP compiler",
"events": [],
"version": "2.0.0",
"methods": ["init", "parse", "compile", "generateR1cs"],
"methods": ["init", "parse", "compile"],
"canActivate": [],
"url": "",
"description": "Enables circuit compilation and computing a witness for ZK proofs",

@ -2,6 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2015", "DOM", "ES2016", "ES2018"],
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true

@ -25,6 +25,7 @@ export const scanAPIurls = {
11155111: 'https://api-sepolia.etherscan.io/api',
97: 'https://api-testnet.bscscan.com/api',
80001: 'https://api-testnet.polygonscan.com/api',
80002: 'https://api-amoy.polygonscan.com/api',
4002: 'https://api-testnet.ftmscan.com/api',
421611: 'https://api-testnet.arbiscan.io/api',
42170: 'https://api-nova.arbiscan.io/api',
@ -38,6 +39,7 @@ export const scanAPIurls = {
84531: 'https://api-goerli.basescan.org/api',
84532: "https://api-sepolia.basescan.org/api",
1442: 'https://api-testnet-zkevm.polygonscan.com/api',
2442: 'https://api-cardona-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
534351: 'https://api-sepolia.scrollscan.com/api',
1115: 'https://api.test.btcs.network/api',

@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc.json",
}

@ -0,0 +1 @@
# Remix QuickDapp Plugin

@ -0,0 +1,11 @@
{
"name": "quick-dapp",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@drafish/surge-client": "^1.1.5"
}
}

@ -0,0 +1,70 @@
{
"name": "quick-dapp",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/quick-dapp/src",
"projectType": "application",
"implicitDependencies": [],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "development",
"dependsOn": ["install"],
"options": {
"compiler": "babel",
"outputPath": "dist/apps/quick-dapp",
"index": "apps/quick-dapp/src/index.html",
"baseHref": "./",
"main": "apps/quick-dapp/src/main.tsx",
"polyfills": "apps/quick-dapp/src/polyfills.ts",
"tsConfig": "apps/quick-dapp/tsconfig.app.json",
"assets": ["apps/quick-dapp/src/profile.json", "apps/quick-dapp/src/assets/edit-dapp.png"],
"styles": ["apps/quick-dapp/src/index.css"],
"scripts": [],
"webpackConfig": "apps/quick-dapp/webpack.config.js"
},
"configurations": {
"development": {
},
"production": {
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/quick-dapp/**/*.ts"],
"eslintConfig": "apps/quick-dapp/.eslintrc"
}
},
"install": {
"executor": "nx:run-commands",
"options": {
"commands": [
"cd apps/quick-dapp && yarn"
],
"parallel": false
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "quick-dapp:build",
"hmr": true,
"baseHref": "/"
},
"configurations": {
"development": {
"buildTarget": "quick-dapp:build:development",
"port": 2025
},
"production": {
"buildTarget": "quick-dapp:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,127 @@
/* You can add global styles to this file, and also import other style files */
.item-wrapper {
transform: translate3d(var(--translate-x, 0), var(--translate-y, 0), 0)
scaleX(var(--scale-x, 1)) scaleY(var(--scale-y, 1));
transform-origin: 0 0;
touch-action: manipulation;
&:hover {
.item-remove {
visibility: visible;
}
}
}
.item-remove {
visibility: hidden;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
background-color: var(--gray-dark);
.fas {
color: var(--text-bg-mark);
}
}
.item-action {
touch-action: none;
outline: none !important;
appearance: none;
background-color: transparent;
-webkit-tap-highlight-color: transparent;
@media (hover: hover) {
&:hover {
background-color: var(--light);
}
}
.fas {
color: var(--text-bg-mark);
}
}
.bg-light {
.item-action {
@media (hover: hover) {
&:hover {
background-color: var(--dark);
}
}
}
}
.container {
flex-direction: column;
&.placeholder {
justify-content: center;
align-items: center;
cursor: pointer;
}
}
.container-header {
&:hover {
.container-actions > * {
opacity: 1 !important;
}
}
}
.container-actions {
> *:first-child:not(:last-child) {
opacity: 0;
&:focus-visible {
opacity: 1;
}
}
}
.instance-input {
background-color: var(--custom-select) !important;
font-size: 10px;
}
.has-args {
border-top-right-radius: 0;
border-bottom-right-radius: 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;
}
.cursor_pointer {
cursor: pointer;
}
.cursor_pointer:hover {
color: var(--secondary);
}
.custom-dropdown-items {
padding: 0.25rem 0.25rem;
border-radius: .25rem;
background: var(--custom-select);
}
.custom-dropdown-items a {
border-radius: .25rem;
text-transform: none;
text-decoration: none;
font-weight: normal;
font-size: 0.875rem;
padding: 0.25rem 0.25rem;
width: auto;
color: var(--text);
}

@ -0,0 +1,71 @@
import React, { useEffect, useReducer, useState } from 'react';
import { IntlProvider } from 'react-intl'
import CreateInstance from './components/CreateInstance';
import EditInstance from './components/EditInstance';
import DeployPanel from './components/DeployPanel';
import LoadingScreen from './components/LoadingScreen';
import { appInitialState, appReducer } from './reducers/state';
import {
connectRemix,
initDispatch,
updateState,
selectTheme,
} from './actions';
import { AppContext } from './contexts';
import remixClient from './remix-client';
import './App.css';
function App(): JSX.Element {
const [locale, setLocale] = useState<{code: string; messages: any}>({
code: 'en',
messages: null,
})
const [appState, dispatch] = useReducer(appReducer, appInitialState);
useEffect(() => {
updateState(appState);
}, [appState]);
useEffect(() => {
initDispatch(dispatch);
updateState(appState);
connectRemix().then(() => {
remixClient.call('theme', 'currentTheme').then((theme: any) => {
selectTheme(theme.name);
});
remixClient.on('theme', 'themeChanged', (theme: any) => {
selectTheme(theme.name);
});
// @ts-ignore
remixClient.call('locale', 'currentLocale').then((locale: any) => {
setLocale(locale)
})
// @ts-ignore
remixClient.on('locale', 'localeChanged', (locale: any) => {
setLocale(locale)
})
});
}, []);
return (
<AppContext.Provider
value={{
dispatch,
appState,
}}
>
<IntlProvider locale={locale.code} messages={locale.messages}>
{Object.keys(appState.instance.abi).length > 0 ? (
<div className="row m-0 pt-3">
<EditInstance />
<DeployPanel />
</div>
) : (
<div className="row m-0 pt-3">
<CreateInstance />
</div>
)}
<LoadingScreen />
</IntlProvider>
</AppContext.Provider>
);
}
export default App;

@ -0,0 +1,426 @@
import axios from 'axios';
import { omitBy } from 'lodash';
import semver from 'semver';
import { execution } from '@remix-project/remix-lib';
import SurgeClient from '@drafish/surge-client';
import remixClient from '../remix-client';
import { themeMap } from '../components/DeployPanel/theme';
const { encodeFunctionId } = execution.txHelper;
const surgeClient = new SurgeClient({
// surge backend doesn't support cross-domain, that's why the proxy goes
// here is the codebase of proxy: https://github.com/drafish/vercel-proxy
proxy: 'https://vercel-proxy-bice-six.vercel.app',
onError: (err: Error) => {
console.log(err);
},
});
const getVersion = (solcVersion) => {
let version = '0.8.25'
try {
const arr = solcVersion.split('+')
if (arr && arr[0]) version = arr[0]
if (semver.lt(version, '0.6.0')) {
return { version: version, canReceive: false };
} else {
return { version: version, canReceive: true };
}
} catch (e) {
return { version, canReceive: true };
}
};
let dispatch: any, state: any;
export const initDispatch = (_dispatch: any) => {
dispatch = _dispatch;
};
export const updateState = (_state: any) => {
state = _state;
};
export const connectRemix = async () => {
await dispatch({
type: 'SET_LOADING',
payload: {
screen: true,
},
});
await remixClient.onload();
// @ts-expect-error
await remixClient.call('layout', 'minimize', 'terminal', true);
await dispatch({
type: 'SET_LOADING',
payload: {
screen: false,
},
});
};
export const saveDetails = async (payload: any) => {
const { abi, userInput, natSpec } = state.instance;
await dispatch({
type: 'SET_INSTANCE',
payload: {
abi: {
...abi,
[payload.id]: {
...abi[payload.id],
details:
natSpec.checked && !payload.details
? natSpec.methods[payload.id]
: payload.details,
},
},
userInput: {
...omitBy(userInput, (item) => item === ''),
methods: omitBy(
{
...userInput.methods,
[payload.id]: payload.details,
},
(item) => item === ''
),
},
},
});
};
export const saveTitle = async (payload: any) => {
const { abi } = state.instance;
await dispatch({
type: 'SET_INSTANCE',
payload: {
abi: {
...abi,
[payload.id]: { ...abi[payload.id], title: payload.title },
},
},
});
};
export const getInfoFromNatSpec = async (value: boolean) => {
const { abi, userInput, natSpec } = state.instance;
const input = value
? {
...natSpec,
...userInput,
methods: { ...natSpec.methods, ...userInput.methods },
}
: userInput;
Object.keys(abi).forEach((id) => {
abi[id].details = input.methods[id] || '';
});
await dispatch({
type: 'SET_INSTANCE',
payload: {
abi,
title: input.title || '',
details: input.details || '',
natSpec: { ...natSpec, checked: value },
},
});
};
export const deploy = async (payload: any, callback: any) => {
const surgeToken = localStorage.getItem('__SURGE_TOKEN');
const surgeEmail = localStorage.getItem('__SURGE_EMAIL');
let isLogin = false;
if (surgeToken && surgeEmail === payload.email) {
try {
await surgeClient.whoami();
isLogin = true;
} catch (error) {
/* empty */
}
}
if (!isLogin) {
try {
await surgeClient.login({
user: payload.email,
password: payload.password,
});
localStorage.setItem('__SURGE_EMAIL', payload.email);
localStorage.setItem('__SURGE_PASSWORD', payload.password);
localStorage.setItem('__DISQUS_SHORTNAME', payload.shortname);
} catch (error: any) {
callback({ code: 'ERROR', error: error.message });
return;
}
}
const { data } = await axios.get(
// It's the json file contains all the static files paths of dapp-template.
// It's generated through the build process automatically.
'https://dev.remix-dapp.pages.dev/manifest.json'
);
let paths: any = [];
Object.keys(data).forEach((key) => {
if (data[key].src === 'index.html') {
const { src, file, css, assets } = data[key];
paths = paths.concat([src, file, ...css, ...assets]);
} else {
paths.push(data[key].file);
}
});
const { logo, ...instance } = state.instance;
const instanceJson = JSON.stringify({
...instance,
shortname: payload.shortname,
shareTo: payload.shareTo,
})
const files: Record<string, string> = {
'dir/instance.json': instanceJson,
};
// console.log(
// JSON.stringify({
// ...instance,
// shareTo: payload.shareTo,
// })
// );
for (let index = 0; index < paths.length; index++) {
const path = paths[index];
// download all the static files from the dapp-template domain.
// here is the codebase of dapp-template: https://github.com/drafish/remix-dapp
const resp = await axios.get(`https://dev.remix-dapp.pages.dev/${path}`);
files[`dir/${path}`] = resp.data;
}
files['dir/logo.png'] = logo
files['dir/CORS'] = '*'
files['dir/index.html'] = files['dir/index.html'].replace(
'assets/css/themes/remix-dark_tvx1s2.css',
themeMap[instance.theme].url
);
try {
await surgeClient.publish({
files,
domain: `${payload.subdomain}.surge.sh`,
onProgress: ({
id,
progress,
file,
}: {
id: string;
progress: number;
file: string;
}) => {
// console.log({ id, progress, file });
},
onTick: (tick: string) => {},
});
} catch ({ message }: any) {
if (message === '403') {
callback({ code: 'ERROR', error: 'this domain belongs to someone else' });
} else {
callback({ code: 'ERROR', error: 'gateway timeout, please try again' });
}
return;
}
try {
// some times deployment might fail even if it says successfully, that's why we need to do the double check.
const instanceResp = await axios.get(`https://${payload.subdomain}.surge.sh/instance.json`);
if (instanceResp.status === 200 && JSON.stringify(instanceResp.data) === instanceJson) {
callback({ code: 'SUCCESS', error: '' });
return;
}
} catch (error) {}
callback({ code: 'ERROR', error: 'deploy failed, please try again' });
return;
};
export const teardown = async (payload: any, callback: any) => {
const surgeToken = localStorage.getItem('__SURGE_TOKEN');
const surgeEmail = localStorage.getItem('__SURGE_EMAIL');
let isLogin = false;
if (surgeToken && surgeEmail === payload.email) {
try {
await surgeClient.whoami();
isLogin = true;
} catch (error) {
/* empty */
}
}
if (!isLogin) {
try {
await surgeClient.login({
user: payload.email,
password: payload.password,
});
localStorage.setItem('__SURGE_EMAIL', payload.email);
localStorage.setItem('__SURGE_PASSWORD', payload.password);
localStorage.setItem('__DISQUS_SHORTNAME', payload.shortname);
} catch (error: any) {
callback({ code: 'ERROR', error: error.message });
return;
}
}
try {
await surgeClient.teardown(`${payload.subdomain}.surge.sh`);
} catch ({ message }: any) {
if (message === '403') {
callback({ code: 'ERROR', error: 'this domain belongs to someone else' });
} else {
callback({ code: 'ERROR', error: 'gateway timeout, please try again' });
}
return;
}
callback({ code: 'SUCCESS', error: '' });
return;
}
export const initInstance = async ({
methodIdentifiers,
devdoc,
solcVersion,
...payload
}: any) => {
const functionHashes: any = {};
const natSpec: any = { checked: false, methods: {} };
if (methodIdentifiers && devdoc) {
for (const fun in methodIdentifiers) {
functionHashes[`0x${methodIdentifiers[fun]}`] = fun;
}
natSpec.title = devdoc.title;
natSpec.details = devdoc.details;
Object.keys(functionHashes).forEach((hash) => {
const method = functionHashes[hash];
if (devdoc.methods[method]) {
const { details, params, returns } = devdoc.methods[method];
const detailsStr = details ? `@dev ${details}` : '';
const paramsStr = params
? Object.keys(params)
.map((key) => `@param ${key} ${params[key]}`)
.join('\n')
: '';
const returnsStr = returns
? Object.keys(returns)
.map(
(key) =>
`@return${/^_\d$/.test(key) ? '' : ' ' + key} ${returns[key]}`
)
.join('\n')
: '';
natSpec.methods[hash] = [detailsStr, paramsStr, returnsStr]
.filter((str) => str !== '')
.join('\n');
}
});
}
const abi: any = {};
const lowLevel: any = {}
payload.abi.forEach((item: any) => {
if (item.type === 'function') {
item.id = encodeFunctionId(item);
abi[item.id] = item;
}
if (item.type === 'fallback') {
lowLevel.fallback = item;
}
if (item.type === 'receive') {
lowLevel.receive = item;
}
});
const ids = Object.keys(abi);
const items =
ids.length > 2
? {
A: ids.slice(0, ids.length / 2 + 1),
B: ids.slice(ids.length / 2 + 1),
}
: { A: ids };
const logo = await axios.get('https://dev.remix-dapp.pages.dev/logo.png', { responseType: 'arraybuffer' })
await dispatch({
type: 'SET_INSTANCE',
payload: {
...payload,
abi,
items,
containers: Object.keys(items),
natSpec,
solcVersion: getVersion(solcVersion),
...lowLevel,
logo: logo.data,
},
});
};
export const resetInstance = async () => {
const abi = state.instance.abi;
const ids = Object.keys(abi);
ids.forEach((id) => {
abi[id] = { ...abi[id], title: '', details: '' };
});
const items =
ids.length > 1
? {
A: ids.slice(0, ids.length / 2 + 1),
B: ids.slice(ids.length / 2 + 1),
}
: { A: ids };
await dispatch({
type: 'SET_INSTANCE',
payload: {
items,
containers: Object.keys(items),
title: '',
details: '',
abi,
},
});
};
export const emptyInstance = async () => {
await dispatch({
type: 'SET_INSTANCE',
payload: {
name: '',
address: '',
network: '',
abi: {},
items: {},
containers: [],
title: '',
details: '',
theme: 'Dark',
userInput: { methods: {} },
natSpec: { checked: false, methods: {} },
},
});
};
export const selectTheme = async (selectedTheme: string) => {
await dispatch({ type: 'SET_INSTANCE', payload: { theme: selectedTheme } });
const linkEles = document.querySelectorAll('link');
const nextTheme = themeMap[selectedTheme]; // Theme
for (const link of linkEles) {
if (link.href.indexOf('/assets/css/themes/') > 0) {
link.href = 'https://remix.ethereum.org/' + nextTheme.url;
document.documentElement.style.setProperty('--theme', nextTheme.quality);
break;
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,132 @@
import React, { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { execution } from '@remix-project/remix-lib';
import { saveDetails, saveTitle } from '../../actions';
const txHelper = execution.txHelper;
const getFuncABIInputs = (funABI: any) => {
if (!funABI.inputs) {
return '';
}
return txHelper.inputParametersDeclarationToString(funABI.inputs);
};
export function ContractGUI(props: { funcABI: any, funcId: any }) {
const intl = useIntl()
const isConstant =
props.funcABI.constant !== undefined ? props.funcABI.constant : false;
const lookupOnly =
props.funcABI.stateMutability === 'view' ||
props.funcABI.stateMutability === 'pure' ||
isConstant;
const inputs = getFuncABIInputs(props.funcABI);
const [title, setTitle] = useState<string>('');
const [buttonOptions, setButtonOptions] = useState<{
title: string;
content: string;
classList: string;
dataId: string;
}>({ title: '', content: '', classList: '', dataId: '' });
useEffect(() => {
if (props.funcABI.name) {
setTitle(props.funcABI.name);
} else {
setTitle(props.funcABI.type === 'receive' ? '(receive)' : '(fallback)');
}
}, [props.funcABI]);
useEffect(() => {
if (lookupOnly) {
setButtonOptions({
title: title + ' - call',
content: 'call',
classList: 'btn-info',
dataId: title + ' - call',
});
} else if (
props.funcABI.stateMutability === 'payable' ||
props.funcABI.payable
) {
setButtonOptions({
title: title + ' - transact (payable)',
content: 'transact',
classList: 'btn-danger',
dataId: title + ' - transact (payable)',
});
} else {
setButtonOptions({
title: title + ' - transact (not payable)',
content: 'transact',
classList: 'btn-warning',
dataId: title + ' - transact (not payable)',
});
}
}, [lookupOnly, props.funcABI, title]);
return (
<div className={`d-inline-block`} style={{ width: '90%' }}>
<div className="p-2">
<input
data-id={`functionTitle${props.funcId}`}
className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.functionTitle' })}
value={props.funcABI.title}
onChange={({ target: { value } }) => {
saveTitle({ id: props.funcABI.id, title: value });
}}
/>
</div>
<div className="p-2 d-flex">
<div
className="d-flex p-0 wrapperElement"
data-id={buttonOptions.dataId}
data-title={buttonOptions.title}
>
<button
disabled
className={`text-nowrap overflow-hidden text-truncate btn btn-sm ${
buttonOptions.classList
} ${
props.funcABI.inputs && props.funcABI.inputs.length > 0
? 'has-args'
: ''
}`}
data-id={buttonOptions.dataId}
data-title={buttonOptions.title}
style={{ pointerEvents: 'none', width: 100 }}
>
{title}
</button>
</div>
<input
disabled
className="instance-input w-100 p-2 border-0 rounded-right"
data-id={'multiParamManagerBasicInputField'}
placeholder={inputs}
data-title={inputs}
style={{
height: '2rem',
visibility: !(
props.funcABI.inputs && props.funcABI.inputs.length > 0
)
? 'hidden'
: 'visible',
}}
/>
</div>
<div className="p-2">
<textarea
data-id={`functionInstructions${props.funcId}`}
className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.functionInstructions' })}
value={props.funcABI.details}
onChange={({ target: { value } }) => {
saveDetails({ id: props.funcABI.id, details: value });
}}
/>
</div>
</div>
);
}

@ -0,0 +1,107 @@
import React, { useState } from 'react';
import { Alert, Button, Form } from 'react-bootstrap';
import { FormattedMessage, useIntl } from 'react-intl';
import { initInstance } from '../../actions';
const CreateInstance: React.FC = () => {
const intl = useIntl()
const [formVal, setFormVal] = useState({
address: '',
abi: [],
name: '',
network: '',
});
const [error, setError] = useState('')
return (
<Form
className="w-50 m-auto"
onSubmit={(e: any) => {
e.preventDefault();
initInstance({ ...formVal });
}}
>
<Form.Group className="mb-2" controlId="formAddress">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.address" /></Form.Label>
<Form.Control
type="address"
placeholder={intl.formatMessage({ id: 'quickDapp.enterAddress' })}
value={formVal.address}
onChange={(e) => {
setFormVal({ ...formVal, address: e.target.value });
}}
/>
</Form.Group>
<Form.Group className="mb-2" controlId="formAbi">
<Form.Label className="text-uppercase mb-0">abi</Form.Label>
<Form.Control
as="textarea"
rows={3}
type="abi"
placeholder={intl.formatMessage({ id: 'quickDapp.enterAbi' })}
onChange={(e) => {
setError('')
let abi = [];
if (e.target.value !== '') {
try {
abi = JSON.parse(e.target.value);
} catch (error) {
setError(error.toString())
}
}
setFormVal({ ...formVal, abi });
}}
/>
{error && <Form.Text className='text-danger'>
{error}
</Form.Text>}
</Form.Group>
<Form.Group className="mb-2" controlId="formName">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.name" /></Form.Label>
<Form.Control
type="name"
placeholder={intl.formatMessage({ id: 'quickDapp.enterName' })}
value={formVal.name}
onChange={(e) => {
setFormVal({ ...formVal, name: e.target.value });
}}
/>
</Form.Group>
<Form.Group className="mb-2" controlId="formNetwork">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.network" /></Form.Label>
<Form.Control
type="network"
placeholder={intl.formatMessage({ id: 'quickDapp.enterNetwork' })}
value={formVal.network}
onChange={(e) => {
setFormVal({ ...formVal, network: e.target.value });
}}
/>
</Form.Group>
<Button
variant="primary"
type="submit"
className="mt-2"
data-id="createDapp"
disabled={
!formVal.address ||
!formVal.name ||
!formVal.network ||
!formVal.abi.length
}
>
<FormattedMessage id="quickDapp.submit" />
</Button>
<Alert className="mt-4" variant="info" data-id="quickDappTooltips">
<FormattedMessage id="quickDapp.text1" />
<br />
<FormattedMessage id="quickDapp.text2" />
</Alert>
<img src='./assets/edit-dapp.png' />
</Form>
);
};
export default CreateInstance;

@ -0,0 +1,329 @@
import React, { useContext, useState, useEffect } from 'react';
import { Form, Button, Alert, InputGroup } from 'react-bootstrap';
import { FormattedMessage, useIntl } from 'react-intl';
import {
deploy,
teardown,
emptyInstance,
resetInstance,
getInfoFromNatSpec,
} from '../../actions';
import { ThemeUI } from './theme';
import { CustomTooltip } from '@remix-ui/helper';
import { AppContext } from '../../contexts';
function DeployPanel(): JSX.Element {
const intl = useIntl()
const { appState, dispatch } = useContext(AppContext);
const { verified, natSpec, noTerminal } = appState.instance;
const [formVal, setFormVal] = useState<any>({
email: localStorage.getItem('__SURGE_EMAIL') || '',
password: localStorage.getItem('__SURGE_PASSWORD') || '',
subdomain: '',
shortname: localStorage.getItem('__DISQUS_SHORTNAME') || '',
shareTo: [],
});
const setShareTo = (type: string) => {
let shareTo = formVal.shareTo;
if (formVal.shareTo.includes(type)) {
shareTo = shareTo.filter((item: string) => item !== type);
} else {
shareTo.push(type);
}
setFormVal({ ...formVal, shareTo });
};
const [deployState, setDeployState] = useState({
code: '',
error: '',
loading: false,
});
const [teardownState, setTeardownState] = useState({
code: '',
error: '',
loading: false,
});
useEffect(() => {
window.scrollTo(0, document.body.scrollHeight);
}, [deployState, teardownState])
return (
<div className="col-3 d-inline-block">
<h3 className="mb-3" data-id="quick-dapp-admin">QuickDapp <FormattedMessage id="quickDapp.admin" /></h3>
<Button
data-id="resetFunctions"
onClick={() => {
resetInstance();
}}
>
<FormattedMessage id="quickDapp.resetFunctions" />
</Button>
<Button
data-id="deleteDapp"
className="ml-3"
onClick={() => {
emptyInstance();
}}
>
<FormattedMessage id="quickDapp.deleteDapp" />
</Button>
<Alert variant="info" className="my-2">
<FormattedMessage
id="quickDapp.text3"
values={{
a: (chunks) => (
<a target="_blank" href="https://surge.sh/help/">
{chunks}
</a>
),
}}
/>
</Alert>
<Form
onSubmit={(e) => {
e.preventDefault();
setDeployState({ code: '', error: '', loading: true });
deploy(formVal, (state: any) => {
setDeployState({ ...state, loading: false });
});
}}
>
<Form.Group className="mb-2" controlId="formEmail">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.email" /></Form.Label>
<Form.Control
data-id="surgeEmail"
type="email"
placeholder={intl.formatMessage({ id: 'quickDapp.surgeEmail' })}
required
value={formVal.email}
onChange={(e) => {
setFormVal({ ...formVal, email: e.target.value });
}}
/>
</Form.Group>
<Form.Group className="mb-2" controlId="formPassword">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.password" /></Form.Label>
<Form.Control
data-id="surgePassword"
type="password"
placeholder={intl.formatMessage({ id: 'quickDapp.surgePassword' })}
required
value={formVal.password}
onChange={(e) => {
setFormVal({ ...formVal, password: e.target.value });
}}
/>
</Form.Group>
<Form.Group className="mb-2" controlId="formPassword">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.subdomain" /></Form.Label>
<InputGroup>
<InputGroup.Text>https://</InputGroup.Text>
<Form.Control
data-id="surgeSubdomain"
type="subdomain"
placeholder={intl.formatMessage({ id: 'quickDapp.uniqueSubdomain' })}
required
value={formVal.subdomain}
onChange={(e) => {
setFormVal({ ...formVal, subdomain: e.target.value });
}}
/>
<InputGroup.Text>.surge.sh</InputGroup.Text>
</InputGroup>
</Form.Group>
{/* <Form.Group className="mb-3" controlId="formShortname">
<Form.Label>Disqus Shortname (Optional)</Form.Label>
<Form.Control
type="shortname"
placeholder="Disqus Shortname"
value={formVal.shortname}
onChange={(e) => {
setFormVal({ ...formVal, shortname: e.target.value });
}}
/>
</Form.Group> */}
<Form.Group className="mb-2" controlId="formShareTo">
<Form.Label className="text-uppercase mb-0">
<FormattedMessage id="quickDapp.shareTo" />
</Form.Label>
<br />
<div className="d-inline-flex align-items-center custom-control custom-checkbox">
<input
id="shareToTwitter"
className="form-check-input custom-control-input"
type="checkbox"
name="group1"
value="twitter"
onChange={(e) => {
setShareTo(e.target.value);
}}
checked={formVal.shareTo.includes('twitter')}
/>
<label
htmlFor="shareToTwitter"
className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }}
>
Twitter
</label>
</div>
<div className="d-inline-flex align-items-center custom-control custom-checkbox ml-3">
<input
id="shareToFacebook"
className="form-check-input custom-control-input"
type="checkbox"
name="group1"
value="facebook"
onChange={(e) => {
setShareTo(e.target.value);
}}
checked={formVal.shareTo.includes('facebook')}
/>
<label
htmlFor="shareToFacebook"
className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }}
>
Facebook
</label>
</div>
</Form.Group>
<Form.Group className="mb-2" controlId="formShareTo">
<Form.Label className="text-uppercase mb-0">
<FormattedMessage id="quickDapp.useNatSpec" />
</Form.Label>
<br />
<span
data-id="useNatSpec"
id="useNatSpec"
className="btn ai-switch pl-0 py-0"
onClick={async () => {
getInfoFromNatSpec(!natSpec.checked);
}}
>
<CustomTooltip
placement="top"
tooltipText={intl.formatMessage({ id: 'quickDapp.useNatSpecTooltip' })}
>
<i
className={
natSpec.checked
? 'fas fa-toggle-on fa-lg'
: 'fas fa-toggle-off fa-lg'
}
></i>
</CustomTooltip>
</span>
</Form.Group>
<Form.Group className="mb-2" controlId="formVerified">
<Form.Label className="text-uppercase mb-0">
<FormattedMessage id="quickDapp.verifiedByEtherscan" />
</Form.Label>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="verifiedByEtherscan"
className="form-check-input custom-control-input"
type="checkbox"
onChange={(e) => {
dispatch({
type: 'SET_INSTANCE',
payload: { verified: e.target.checked },
});
}}
checked={verified}
/>
<label
htmlFor="verifiedByEtherscan"
className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }}
>
<FormattedMessage id="quickDapp.verified" />
</label>
</div>
</Form.Group>
<Form.Group className="mb-2" controlId="formNoTerminal">
<Form.Label className="text-uppercase mb-0">
<FormattedMessage id="quickDapp.noTerminal" />
</Form.Label>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input
id="noTerminal"
className="form-check-input custom-control-input"
type="checkbox"
onChange={(e) => {
dispatch({
type: 'SET_INSTANCE',
payload: { noTerminal: e.target.checked },
});
}}
checked={noTerminal}
/>
<label
htmlFor="noTerminal"
className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }}
>
<FormattedMessage id="quickDapp.no" />
</label>
</div>
</Form.Group>
<ThemeUI />
<Button
data-id="deployDapp"
variant="primary"
type="submit"
className="mt-3"
disabled={!formVal.email || !formVal.password || !formVal.subdomain}
>
{deployState.loading && (
<i className="fas fa-spinner fa-spin mr-1"></i>
)}
<FormattedMessage id="quickDapp.deploy" />
</Button>
<Button
data-id="teardownDapp"
variant="primary"
className="mt-3 ml-3"
disabled={!formVal.email || !formVal.password || !formVal.subdomain}
// hide this button for now, just for e2e use
style={{ display: 'none' }}
onClick={() => {
setTeardownState({ code: '', error: '', loading: true });
teardown(formVal, (state) => {
setTeardownState({ ...state, loading: false });
})
}}
>
{teardownState.loading && (
<i className="fas fa-spinner fa-spin mr-1"></i>
)}
<FormattedMessage id="quickDapp.teardown" />
</Button>
{deployState.code !== '' && (
<Alert variant={deployState.code === 'SUCCESS' ? "success" : "danger"} className="mt-4" data-id="deployResult">
{deployState.code === 'SUCCESS' ? <>
<FormattedMessage id="quickDapp.text4" /> <br /> <FormattedMessage id="quickDapp.text5" />
<br />
<a
data-id="dappUrl"
target="_blank"
href={`https://${formVal.subdomain}.surge.sh`}
>{`https://${formVal.subdomain}.surge.sh`}</a>
</> : deployState.error}
</Alert>
)}
{teardownState.code !== '' && (
<Alert variant={teardownState.code === 'SUCCESS' ? "success" : "danger"} className="mt-4" data-id="teardownResult">
{teardownState.code === 'SUCCESS' ? <FormattedMessage id="quickDapp.text6" /> : teardownState.error}
</Alert>
)}
</Form>
</div>
);
}
export default DeployPanel;

@ -0,0 +1,159 @@
import { Ref, useContext, useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { AppContext } from '../../contexts';
import { selectTheme } from '../../actions';
import { Dropdown } from 'react-bootstrap';
import React from 'react';
export 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',
},
};
const CustomToggle = React.forwardRef(
(
{
children,
onClick,
icon,
className = '',
}: {
children: React.ReactNode;
onClick: (e: any) => void;
icon: string;
className: string;
},
ref: Ref<HTMLButtonElement>
) => (
<button
ref={ref}
onClick={(e) => {
e.preventDefault();
onClick(e);
}}
id="dropdown-custom-components"
data-id="selectThemesOptions"
className={className.replace('dropdown-toggle', '')}
>
<div className="d-flex">
<div className="mr-auto text-nowrap overflow-hidden">{children}</div>
{icon && (
<div className="pr-1">
<i className={`${icon} pr-1`}></i>
</div>
)}
<div>
<i className="fad fa-sort-circle"></i>
</div>
</div>
</button>
)
);
const CustomMenu = React.forwardRef(
(
{
children,
style,
'data-id': dataId,
className,
'aria-labelledby': labeledBy,
}: {
children: React.ReactNode;
style?: React.CSSProperties;
'data-id'?: string;
className: string;
'aria-labelledby'?: string;
},
ref: Ref<HTMLDivElement>
) => {
const height = window.innerHeight * 0.6;
return (
<div
ref={ref}
style={style}
className={className}
aria-labelledby={labeledBy}
data-id={dataId}
>
<ul
className="overflow-auto list-unstyled mb-0"
style={{ maxHeight: height + 'px' }}
>
{children}
</ul>
</div>
);
}
);
export function ThemeUI() {
const { appState } = useContext(AppContext);
const { theme } = appState.instance;
const themeList = Object.keys(themeMap);
useEffect(() => {
selectTheme(theme);
}, []);
return (
<div className="d-block">
<label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.themes" /></label>
<Dropdown className="w-100">
<Dropdown.Toggle
as={CustomToggle}
className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control"
icon={''}
>
{theme} - {themeMap[theme].quality}
</Dropdown.Toggle>
<Dropdown.Menu
as={CustomMenu}
className="w-100 custom-dropdown-items"
data-id="custom-dropdown-items"
>
{themeList.map((item) => (
<Dropdown.Item
key={item}
onClick={() => {
selectTheme(item);
}}
data-id={`dropdown-item-${item}`}
>
{theme === item && (
<span className="fas fa-check text-success mr-2"></span>
)}
{item} - {themeMap[item].quality}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
</div>
);
}

@ -0,0 +1,86 @@
import { useContext } from 'react';
import { omitBy } from 'lodash';
import { useIntl } from 'react-intl';
import { MultipleContainers } from '../MultipleContainers';
import { AppContext } from '../../contexts';
import ImageUpload from '../ImageUpload'
function EditInstance(): JSX.Element {
const intl = useIntl()
const { appState, dispatch } = useContext(AppContext);
const { abi, items, containers, title, details, userInput, natSpec } =
appState.instance;
return (
<div className="col-9 d-inline-block row">
<div className="row">
<ImageUpload />
<div className="col-9 pl-0">
<div className="my-2 p-3 bg-light">
<input
data-id="dappTitle"
className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.dappTitle' })}
value={title}
onChange={({ target: { value } }) => {
dispatch({
type: 'SET_INSTANCE',
payload: {
title: natSpec.checked && !value ? natSpec.title : value,
userInput: omitBy(
{ ...userInput, title: value },
(item) => item === ''
),
},
});
}}
/>
</div>
<div className="my-2 p-3 bg-light">
<textarea
data-id="dappInstructions"
className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.dappInstructions' })}
value={details}
onChange={({ target: { value } }) => {
dispatch({
type: 'SET_INSTANCE',
payload: {
details: natSpec.checked && !value ? natSpec.details : value,
userInput: omitBy(
{ ...userInput, details: value },
(item) => item === ''
),
},
});
}}
/>
</div>
</div>
</div>
<MultipleContainers
abi={abi}
items={items}
containers={containers}
setItemsAndContainers={(
newItems: any = items,
newContainers: any = containers
) => {
dispatch({
type: 'SET_INSTANCE',
payload: {
items: newItems,
containers: newContainers,
},
});
}}
handle
scrollable
containerStyle={{
maxHeight: '90vh',
}}
/>
</div>
);
}
export default EditInstance;

@ -0,0 +1,76 @@
import React, { useEffect, useState } from 'react';
const EditableText = ({
value,
onSave,
textarea,
placeholder,
}: {
value: string;
onSave: (str: string) => void;
textarea?: boolean;
placeholder?: string;
}) => {
const [isEditing, setIsEditing] = useState(false);
const [tempText, setTempText] = useState(value);
useEffect(() => {
setTempText(value);
}, [value]);
const handleEdit = () => {
setIsEditing(true);
};
const handleSave = () => {
onSave(tempText);
setIsEditing(false);
};
const handleCancel = () => {
setIsEditing(false);
};
const handleChange = (event: {
target: { value: React.SetStateAction<string> };
}) => {
setTempText(event.target.value);
};
const InputElement = textarea ? 'textarea' : 'input';
const TextElement = textarea ? 'span' : 'h1';
return isEditing ? (
<>
<InputElement
className="form-control"
placeholder={placeholder}
value={tempText}
onChange={handleChange}
style={{ height: textarea ? 100 : 'auto' }}
/>
<div className="d-flex justify-content-end">
<i
className="fas ml-2 mt-2 fa-save cursor_pointer"
onClick={handleSave}
/>
<i
className="fas ml-2 mt-2 fa-ban cursor_pointer"
onClick={handleCancel}
/>
</div>
</>
) : (
<div className="d-flex justify-content-between align-items-center">
<TextElement className="udapp_intro">
{value ? value : placeholder}
</TextElement>
<i
className="fas fa-edit ml-2 float-right cursor_pointer"
onClick={handleEdit}
/>
</div>
);
};
export default EditableText;

@ -0,0 +1,50 @@
import React, { useContext, useEffect, useState } from 'react'
import { useIntl } from 'react-intl';
import { CustomTooltip } from '@remix-ui/helper';
import { AppContext } from '../../contexts'
const ImageUpload = () => {
const intl = useIntl()
const { appState, dispatch } = useContext(AppContext)
const { logo } = appState.instance
const [preview, setPreview] = useState(null)
useEffect(() => {
if (logo) {
const base64data = btoa(new Uint8Array(logo).reduce((data, byte) => data + String.fromCharCode(byte), ''))
setPreview('data:image/jpeg;base64,' + base64data)
} else {
setPreview(null)
}
}, [logo])
const handleImageChange = (e) => {
if (e.target.files && e.target.files[0]) {
const reader: any = new FileReader()
reader.onloadend = () => {
dispatch({ type: 'SET_INSTANCE', payload: { logo: reader.result } })
}
reader.readAsArrayBuffer(e.target.files[0])
}
}
return (
<div className="col-3 pr-0">
<input data-id="uploadLogo" className="d-none" type="file" accept="image/*" onChange={handleImageChange} id="upload-button" />
<CustomTooltip
placement="right"
tooltipText={intl.formatMessage({ id: 'quickDapp.uploadLogoTooltip' })}
>
<label htmlFor="upload-button" className="cursor_pointer d-flex justify-content-center align-items-center position-relative" style={{ height: 170 }}>
{logo ? (
<img src={preview} alt="preview" style={{ width: 120, height: 120 }} />
) : (
<i className="fas fa-upload" style={{ fontSize: 120 }}></i>
)}
</label>
</CustomTooltip>
</div>
)
}
export default ImageUpload

@ -0,0 +1,31 @@
import React, { useContext } from 'react';
import BounceLoader from 'react-spinners/BounceLoader';
import { AppContext } from '../../contexts';
const LoadingScreen: React.FC = () => {
const { appState } = useContext(AppContext);
const loading = appState.loading.screen;
return loading ? (
<div
className="w-100 h-100 position-fixed bg-dark z-3"
style={{
top: 0,
opacity: 0.8
}}
>
<BounceLoader
color="#a7b0ae"
size={100}
className="position-absolute m-0"
style={{
top: '40%',
left: '50%',
transform: 'translate(-50%,-50%)',
}}
/>
</div>
) : null;
};
export default LoadingScreen;

@ -0,0 +1,70 @@
import React, { forwardRef } from 'react';
import { Handle } from '../Item';
import { Remove } from './Remove'
export interface Props {
children: React.ReactNode;
columns?: number;
label?: string;
style?: React.CSSProperties;
hover?: boolean;
handleProps?: React.HTMLAttributes<any>;
placeholder?: boolean;
onClick?(): void;
onRemove?(): void;
}
export const Container = forwardRef<HTMLDivElement, Props>(
(
{
children,
columns = 1,
handleProps,
hover,
onClick,
onRemove,
label,
placeholder,
style,
...props
}: Props,
ref
) => {
return (
<div
{...props}
ref={ref}
style={
{
...style,
'--columns': columns,
} as React.CSSProperties
}
className={`col pr-0 d-flex rounded container ${hover && 'hover'} ${
placeholder && 'placeholder'
}`}
onClick={onClick}
tabIndex={onClick ? 0 : undefined}
>
{label ? (
<div
className={`px-2 py-1 d-flex align-items-center justify-content-between container-header`}
>
{label}
<div className={`d-flex container-actions`}>
<Remove onClick={onRemove} data-id={`remove${label.replace(/\s*/g,"")}`} />
<Handle {...handleProps} data-id={`handle${label.replace(/\s*/g,"")}`} />
</div>
</div>
) : null}
{placeholder ? (
children
) : (
<ul className="p-0 m-0 list-unstyled" style={{ overflowY: 'auto' }} data-id={`container${label.replace(/\s*/g,"")}`}>
{children}
</ul>
)}
</div>
);
}
);

@ -0,0 +1,13 @@
import React from 'react';
import { Action, Props as ActionProps } from '../Item/Action';
export function Remove(props: ActionProps) {
return (
<Action
{...props}
>
<i className="fas fa-times"></i>
</Action>
);
}

@ -0,0 +1,2 @@
export { Container } from './Container'
export type { Props as ContainerProps } from './Container'

@ -0,0 +1,29 @@
import React, { forwardRef, CSSProperties } from 'react';
export interface Props extends React.HTMLAttributes<HTMLButtonElement> {
active?: {
fill: string;
background: string;
};
cursor?: CSSProperties['cursor'];
}
export const Action = forwardRef<HTMLButtonElement, Props>(
({ active, className, cursor, style, ...props }, ref) => {
return (
<button
ref={ref}
{...props}
className={`d-flex align-items-center justify-content-center border-0 rounded p-3 item-action`}
tabIndex={0}
style={
{
...style,
cursor,
width: 12,
} as CSSProperties
}
/>
);
}
);

@ -0,0 +1,18 @@
import React, { forwardRef } from 'react';
import { Action, Props as ActionProps } from './Action';
export const Handle = forwardRef<HTMLButtonElement, ActionProps>(
(props, ref) => {
return (
<Action
ref={ref}
cursor="grab"
data-cypress="draggable-handle"
{...props}
>
<i className="fas fa-grip-vertical"></i>
</Action>
);
}
);

@ -0,0 +1,112 @@
import React, { useEffect } from 'react';
import type { DraggableSyntheticListeners } from '@dnd-kit/core';
import type { Transform } from '@dnd-kit/utilities';
import { Handle } from './Handle';
export interface Props {
dragOverlay?: boolean;
disabled?: boolean;
dragging?: boolean;
handle?: boolean;
handleProps?: any;
height?: number;
index?: number;
fadeIn?: boolean;
transform?: Transform | null;
listeners?: DraggableSyntheticListeners;
sorting?: boolean;
style?: React.CSSProperties;
transition?: string | null;
wrapperStyle?: React.CSSProperties;
children: React.ReactNode;
onRemove?(): void;
id?: any;
}
export const Item = React.memo(
React.forwardRef<HTMLLIElement, Props>(
(
{
dragOverlay,
dragging,
disabled,
fadeIn,
handle,
handleProps,
height,
index,
listeners,
onRemove,
sorting,
style,
transition,
transform,
children,
wrapperStyle,
id,
...props
},
ref
) => {
useEffect(() => {
if (!dragOverlay) {
return;
}
document.body.style.cursor = 'grabbing';
return () => {
document.body.style.cursor = '';
};
}, [dragOverlay]);
return (
<li
className={`position-relative mb-3 list-unstyled item-wrapper`}
style={
{
...wrapperStyle,
transition: [transition, wrapperStyle?.transition]
.filter(Boolean)
.join(', '),
'--translate-x': transform
? `${Math.round(transform.x)}px`
: undefined,
'--translate-y': transform
? `${Math.round(transform.y)}px`
: undefined,
'--scale-x': transform?.scaleX
? `${transform.scaleX}`
: undefined,
'--scale-y': transform?.scaleY
? `${transform.scaleY}`
: undefined,
'--index': index,
} as React.CSSProperties
}
ref={ref}
>
<div
style={style}
data-cypress="draggable-item"
{...(!handle ? listeners : undefined)}
{...props}
tabIndex={!handle ? 0 : undefined}
>
<div className="border-dark bg-light d-flex">
{children}
<Handle {...handleProps} {...listeners} data-id={`handle${id}`} />
</div>
<button
data-id={`remove${id}`}
className={`d-flex justify-content-center align-items-center position-absolute border-0 rounded-circle item-remove`}
onClick={onRemove}
>
<i className="fas fa-times"></i>
</button>
</div>
</li>
);
}
)
);

@ -0,0 +1,3 @@
export { Item } from './Item';
export { Action } from './Action';
export { Handle } from './Handle';

@ -0,0 +1,3 @@
export { Container } from './Container';
export type { ContainerProps } from './Container';
export { Item, Action, Handle } from './Item';

@ -0,0 +1,669 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal, unstable_batchedUpdates } from 'react-dom';
import { FormattedMessage, useIntl } from 'react-intl';
import {
CancelDrop,
closestCenter,
pointerWithin,
rectIntersection,
CollisionDetection,
DndContext,
DragOverlay,
DropAnimation,
getFirstCollision,
KeyboardSensor,
MouseSensor,
TouchSensor,
Modifiers,
UniqueIdentifier,
useSensors,
useSensor,
MeasuringStrategy,
KeyboardCoordinateGetter,
defaultDropAnimationSideEffects,
} from '@dnd-kit/core';
import {
AnimateLayoutChanges,
SortableContext,
useSortable,
arrayMove,
defaultAnimateLayoutChanges,
verticalListSortingStrategy,
SortingStrategy,
horizontalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { coordinateGetter as multipleContainersCoordinateGetter } from './multipleContainersKeyboardCoordinates';
import { Item, Container, ContainerProps } from './components';
import { ContractGUI } from '../ContractGUI';
export default {
title: 'Presets/Sortable/Multiple Containers',
};
// This function is used to animate layout changes.
const animateLayoutChanges: AnimateLayoutChanges = (args) =>
defaultAnimateLayoutChanges({ ...args, wasDragging: true });
// This is a container component that can be dragged and dropped.
function DroppableContainer({
children,
columns = 1,
disabled,
id,
items,
style,
...props
}: ContainerProps & {
disabled?: boolean;
id: UniqueIdentifier;
items: UniqueIdentifier[];
style?: React.CSSProperties;
}) {
const {
active,
attributes,
isDragging,
listeners,
over,
setNodeRef,
transition,
transform,
} = useSortable({
id,
data: {
type: 'container',
children: items,
},
animateLayoutChanges,
});
const isOverContainer = over
? (id === over.id && active?.data.current?.type !== 'container') ||
items.includes(over.id)
: false;
// Return the container.
return (
<Container
ref={disabled ? undefined : setNodeRef}
style={{
...style,
transition,
transform: CSS.Translate.toString(transform),
opacity: isDragging ? 0.5 : undefined,
}}
hover={isOverContainer}
handleProps={{
...attributes,
...listeners,
}}
columns={columns}
{...props}
>
{children}
</Container>
);
}
// This setting is used for drop animation.
const dropAnimation: DropAnimation = {
sideEffects: defaultDropAnimationSideEffects({
styles: {
active: {
opacity: '0.5',
},
},
}),
};
// This type is used to define the items.
type Items = Record<UniqueIdentifier, UniqueIdentifier[]>;
// This interface is used to define the props for the MultipleContainers component.
interface Props {
adjustScale?: boolean;
cancelDrop?: CancelDrop;
columns?: number;
containerStyle?: React.CSSProperties;
coordinateGetter?: KeyboardCoordinateGetter;
getItemStyles?(args: {
value: UniqueIdentifier;
index: number;
overIndex: number;
isDragging: boolean;
containerId: UniqueIdentifier;
isSorting: boolean;
isDragOverlay: boolean;
}): React.CSSProperties;
wrapperStyle?(args: { index: number }): React.CSSProperties;
itemCount?: number;
abi?: any;
items: Items;
containers: any;
setItemsAndContainers: (item?: any, containers?: any) => void;
handle?: boolean;
strategy?: SortingStrategy;
modifiers?: Modifiers;
scrollable?: boolean;
vertical?: boolean;
}
const PLACEHOLDER_ID = 'placeholder';
const empty: UniqueIdentifier[] = [];
// This is a complex component. It allows items in multiple containers to be sorted using drag and drop.
// The containers themselves can also be sorted. The DndContext component from the @dnd-kit/core package provides the drag and drop context.
// The MultipleContainers component is the main component, it handles the sorting logic when an item is dragged over another item or when an item is dropped.
export function MultipleContainers({
adjustScale = false,
cancelDrop,
columns,
handle = false,
items,
containers,
setItemsAndContainers,
abi,
containerStyle,
coordinateGetter = multipleContainersCoordinateGetter,
getItemStyles = () => ({}),
wrapperStyle = () => ({}),
modifiers,
strategy = verticalListSortingStrategy,
vertical = false,
scrollable,
}: Props) {
const intl = useIntl()
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
const lastOverId = useRef<UniqueIdentifier | null>(null);
const recentlyMovedToNewContainer = useRef(false);
const isSortingContainer = activeId ? containers.includes(activeId) : false;
/**
* Custom collision detection strategy optimized for multiple containers
*
* - First, find any droppable containers intersecting with the pointer.
* - If there are none, find intersecting containers with the active draggable.
* - If there are no intersecting containers, return the last matched intersection
*
*/
const collisionDetectionStrategy: CollisionDetection = useCallback(
(args) => {
if (activeId && activeId in items) {
return closestCenter({
...args,
droppableContainers: args.droppableContainers.filter(
(container) => container.id in items
),
});
}
// Start by finding any intersecting droppable
const pointerIntersections = pointerWithin(args);
const intersections =
pointerIntersections.length > 0
? // If there are droppables intersecting with the pointer, return those
pointerIntersections
: rectIntersection(args);
let overId = getFirstCollision(intersections, 'id');
if (overId != null) {
if (overId in items) {
const containerItems = items[overId];
// If a container is matched and it contains items (columns 'A', 'B', 'C')
if (containerItems.length > 0) {
// Return the closest droppable within that container
overId = closestCenter({
...args,
droppableContainers: args.droppableContainers.filter(
(container) =>
container.id !== overId &&
containerItems.includes(container.id)
),
})[0]?.id;
}
}
lastOverId.current = overId;
return [{ id: overId }];
}
// When a draggable item moves to a new container, the layout may shift
// and the `overId` may become `null`. We manually set the cached `lastOverId`
// to the id of the draggable item that was moved to the new container, otherwise
// the previous `overId` will be returned which can cause items to incorrectly shift positions
if (recentlyMovedToNewContainer.current) {
lastOverId.current = activeId;
}
// If no droppable is matched, return the last match
return lastOverId.current ? [{ id: lastOverId.current }] : [];
},
[activeId, items]
);
const [clonedItems, setClonedItems] = useState<Items | null>(null);
const sensors = useSensors(
useSensor(MouseSensor),
useSensor(TouchSensor),
useSensor(KeyboardSensor, {
coordinateGetter,
})
);
const findContainer = (id: UniqueIdentifier) => {
if (id in items) {
return id;
}
return Object.keys(items).find((key) => items[key].includes(id));
};
const getIndex = (id: UniqueIdentifier) => {
const container = findContainer(id);
if (!container) {
return -1;
}
const index = items[container].indexOf(id);
return index;
};
const onDragCancel = () => {
if (clonedItems) {
// Reset items to their original state in case items have been
// Dragged across containers
setItemsAndContainers(clonedItems);
}
setActiveId(null);
setClonedItems(null);
};
useEffect(() => {
requestAnimationFrame(() => {
recentlyMovedToNewContainer.current = false;
});
}, [items]);
return (
<DndContext
sensors={sensors}
collisionDetection={collisionDetectionStrategy}
measuring={{
droppable: {
strategy: MeasuringStrategy.Always,
},
}}
onDragStart={({ active }) => {
setActiveId(active.id);
setClonedItems(items);
}}
onDragOver={({ active, over }) => {
const overId = over?.id;
if (overId == null || active.id in items) {
return;
}
const overContainer = findContainer(overId);
const activeContainer = findContainer(active.id);
if (!overContainer || !activeContainer) {
return;
}
if (activeContainer !== overContainer) {
const activeItems = items[activeContainer];
const overItems = items[overContainer];
const overIndex = overItems.indexOf(overId);
const activeIndex = activeItems.indexOf(active.id);
let newIndex: number;
if (overId in items) {
newIndex = overItems.length + 1;
} else {
const isBelowOverItem =
over &&
active.rect.current.translated &&
active.rect.current.translated.top >
over.rect.top + over.rect.height;
const modifier = isBelowOverItem ? 1 : 0;
newIndex =
overIndex >= 0 ? overIndex + modifier : overItems.length + 1;
}
recentlyMovedToNewContainer.current = true;
setItemsAndContainers({
...items,
[activeContainer]: items[activeContainer].filter(
(item) => item !== active.id
),
[overContainer]: [
...items[overContainer].slice(0, newIndex),
items[activeContainer][activeIndex],
...items[overContainer].slice(
newIndex,
items[overContainer].length
),
],
});
}
}}
onDragEnd={({ active, over }) => {
if (active.id in items && over?.id) {
const activeIndex = containers.indexOf(active.id);
const overIndex = containers.indexOf(over.id);
setItemsAndContainers(
undefined,
arrayMove(containers, activeIndex, overIndex)
);
}
const activeContainer = findContainer(active.id);
if (!activeContainer) {
setActiveId(null);
return;
}
const overId = over?.id;
if (overId == null) {
setActiveId(null);
return;
}
if (overId === PLACEHOLDER_ID) {
const newContainerId = getNextContainerId();
unstable_batchedUpdates(() => {
setItemsAndContainers(
{
...items,
[activeContainer]: items[activeContainer].filter(
(id) => id !== activeId
),
[newContainerId]: [active.id],
},
[...containers, newContainerId]
);
setActiveId(null);
});
return;
}
const overContainer = findContainer(overId);
if (overContainer) {
const activeIndex = items[activeContainer].indexOf(active.id);
const overIndex = items[overContainer].indexOf(overId);
if (activeIndex !== overIndex) {
setItemsAndContainers({
...items,
[overContainer]: arrayMove(
items[overContainer],
activeIndex,
overIndex
),
});
}
}
setActiveId(null);
}}
cancelDrop={cancelDrop}
onDragCancel={onDragCancel}
modifiers={modifiers}
>
<div
className="row pt-0"
style={{
boxSizing: 'border-box',
padding: 20,
gridAutoFlow: vertical ? 'row' : 'column',
}}
>
<SortableContext
items={[...containers, PLACEHOLDER_ID]}
strategy={
vertical
? verticalListSortingStrategy
: horizontalListSortingStrategy
}
>
{containers.map((containerId: any) => (
<DroppableContainer
key={containerId}
id={containerId}
label={`${intl.formatMessage({ id: 'quickDapp.column' })} ${containerId}`}
columns={columns}
items={items[containerId]}
style={containerStyle}
onRemove={() => handleRemove(containerId)}
>
<SortableContext items={items[containerId]} strategy={strategy}>
{items[containerId].map((value, index) => {
return (
<SortableItem
disabled={isSortingContainer}
key={value}
id={value}
abi={abi}
index={index}
handle={handle}
style={getItemStyles}
wrapperStyle={wrapperStyle}
containerId={containerId}
getIndex={getIndex}
onRemove={() => {
setItemsAndContainers({
...items,
[containerId]: items[containerId].filter(
(id) => id !== value
),
});
}}
/>
);
})}
</SortableContext>
</DroppableContainer>
))}
{containers.length < 3 && (
<DroppableContainer
id={PLACEHOLDER_ID}
key={PLACEHOLDER_ID}
disabled={isSortingContainer}
items={empty}
onClick={handleAddColumn}
placeholder
>
+ <FormattedMessage id='quickDapp.addColumn' />
</DroppableContainer>
)}
</SortableContext>
</div>
{createPortal(
<DragOverlay adjustScale={adjustScale} dropAnimation={dropAnimation}>
{activeId
? containers.includes(activeId)
? renderContainerDragOverlay(activeId)
: renderSortableItemDragOverlay(activeId)
: null}
</DragOverlay>,
document.body
)}
</DndContext>
);
function renderSortableItemDragOverlay(id: UniqueIdentifier) {
return (
<Item
id={id}
handle={handle}
style={getItemStyles({
containerId: findContainer(id) as UniqueIdentifier,
overIndex: -1,
index: getIndex(id),
value: id,
isSorting: true,
isDragging: true,
isDragOverlay: true,
})}
wrapperStyle={wrapperStyle({ index: 0 })}
dragOverlay
>
<ContractGUI funcABI={abi[id]} funcId={id} />
</Item>
);
}
function renderContainerDragOverlay(containerId: UniqueIdentifier) {
return (
<Container label={`Column ${containerId}`} columns={columns}>
{items[containerId].map((item, index) => (
<Item
key={item}
id={item}
handle={handle}
style={getItemStyles({
containerId,
overIndex: -1,
index: getIndex(item),
value: item,
isDragging: false,
isSorting: false,
isDragOverlay: false,
})}
wrapperStyle={wrapperStyle({ index })}
>
<ContractGUI funcABI={abi[item]} funcId={item} />
</Item>
))}
</Container>
);
}
function handleRemove(containerID: UniqueIdentifier) {
const newContainers = containers.filter((id: any) => id !== containerID);
const newItems: any = {};
newContainers.forEach((id: string) => {
newItems[id] = items[id];
});
setItemsAndContainers(newItems, newContainers);
}
function handleAddColumn() {
const newContainerId = getNextContainerId();
unstable_batchedUpdates(() => {
setItemsAndContainers(
{
...items,
[newContainerId]: [],
},
[...containers, newContainerId]
);
});
}
function getNextContainerId() {
const containerIds = Object.keys(items);
const lastContainerId = containerIds[containerIds.length - 1];
return String.fromCharCode(lastContainerId.charCodeAt(0) + 1);
}
}
interface SortableItemProps {
containerId: UniqueIdentifier;
id: UniqueIdentifier;
abi: any;
index: number;
handle: boolean;
disabled?: boolean;
style(args: any): React.CSSProperties;
getIndex(id: UniqueIdentifier): number;
wrapperStyle({ index }: { index: number }): React.CSSProperties;
onRemove?: () => void;
}
// The SortableItem component represents an individual item that can be dragged and dropped.
function SortableItem({
disabled,
id,
abi,
index,
handle,
style,
containerId,
getIndex,
wrapperStyle,
onRemove,
}: SortableItemProps) {
const {
setNodeRef,
setActivatorNodeRef,
listeners,
isDragging,
isSorting,
over,
overIndex,
transform,
transition,
} = useSortable({
id,
});
const mounted = useMountStatus();
const mountedWhileDragging = isDragging && !mounted;
return (
<Item
id={id}
ref={disabled ? undefined : setNodeRef}
dragging={isDragging}
sorting={isSorting}
handle={handle}
handleProps={handle ? { ref: setActivatorNodeRef } : undefined}
index={index}
wrapperStyle={wrapperStyle({ index })}
style={style({
index,
value: id,
isDragging,
isSorting,
overIndex: over ? getIndex(over.id) : overIndex,
containerId,
})}
transition={transition}
transform={transform}
fadeIn={mountedWhileDragging}
listeners={listeners}
onRemove={onRemove}
>
<ContractGUI funcABI={abi[id]} funcId={id} />
</Item>
);
}
// The useMountStatus function is used to track the mount status of a component.
function useMountStatus() {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => setIsMounted(true), 500);
return () => clearTimeout(timeout);
}, []);
return isMounted;
}

@ -0,0 +1,153 @@
import {
closestCorners,
getFirstCollision,
KeyboardCode,
DroppableContainer,
KeyboardCoordinateGetter,
} from '@dnd-kit/core';
// Define the directions that can be used with the keyboard
const directions: string[] = [
KeyboardCode.Down,
KeyboardCode.Right,
KeyboardCode.Up,
KeyboardCode.Left,
];
// This is a custom coordinate getter for keyboard events.
// It's used to handle keyboard navigation when moving items around in a drag and drop context.
// It determines the next position of an item based on the direction of the keyboard event.
// The function filters droppable containers based on their position relative to the currently dragged item,
// then finds the closest container in the direction of the keyboard event and returns its coordinates.
export const coordinateGetter: KeyboardCoordinateGetter = (
event,
{ context: { active, droppableRects, droppableContainers, collisionRect } }
) => {
// If the key pressed is one of the defined directions
if (directions.includes(event.code)) {
// Prevent the default browser behaviour
event.preventDefault();
// If there is no active draggable or collision rectangle, return
if (!active || !collisionRect) {
return;
}
// Create an array to store the droppable containers that meet the criteria
const filteredContainers: DroppableContainer[] = [];
// For each enabled droppable container
droppableContainers.getEnabled().forEach((entry) => {
// If the container is not defined or it is disabled, return
if (!entry || entry?.disabled) {
return;
}
// Get the rectangle of the droppable container
const rect = droppableRects.get(entry.id);
// If the rectangle is not defined, return
if (!rect) {
return;
}
// Get the data of the droppable container
const data = entry.data.current;
// If the data is defined
if (data) {
const { type, children } = data;
// If the droppable container is of type 'container' and it has children
if (type === 'container' && children?.length > 0) {
// If the active draggable is not of type 'container', return
if (active.data.current?.type !== 'container') {
return;
}
}
}
// Depending on the direction of the keyboard event
switch (event.code) {
// If the direction is down and the top of the collision rectangle is above the top of the container rectangle
case KeyboardCode.Down:
if (collisionRect.top < rect.top) {
// Add the container to the array of filtered containers
filteredContainers.push(entry);
}
break;
// If the direction is up and the top of the collision rectangle is below the top of the container rectangle
case KeyboardCode.Up:
if (collisionRect.top > rect.top) {
// Add the container to the array of filtered containers
filteredContainers.push(entry);
}
break;
// If the direction is left and the left of the collision rectangle is to the right of the right of the container rectangle
case KeyboardCode.Left:
if (collisionRect.left >= rect.left + rect.width) {
// Add the container to the array of filtered containers
filteredContainers.push(entry);
}
break;
// If the direction is right and the right of the collision rectangle is to the left of the left of the container rectangle
case KeyboardCode.Right:
if (collisionRect.left + collisionRect.width <= rect.left) {
// Add the container to the array of filtered containers
filteredContainers.push(entry);
}
break;
}
});
// Get the closest corners of the collision rectangle and the filtered containers
const collisions = closestCorners({
active,
collisionRect: collisionRect,
droppableRects,
droppableContainers: filteredContainers,
pointerCoordinates: null,
});
// Get the id of the first collision
const closestId = getFirstCollision(collisions, 'id');
// If there is a closest id
if (closestId != null) {
// Get the droppable container with the closest id
const newDroppable = droppableContainers.get(closestId);
// Get the node and rectangle of the droppable container
const newNode = newDroppable?.node.current;
const newRect = newDroppable?.rect.current;
// If there is a node and rectangle
if (newNode && newRect) {
// If the droppable container is the placeholder
if (newDroppable.id === 'placeholder') {
// Return the center coordinates of the droppable container
return {
x: newRect.left + (newRect.width - collisionRect.width) / 2,
y: newRect.top + (newRect.height - collisionRect.height) / 2,
};
}
// If the droppable container is of type 'container'
if (newDroppable.data.current?.type === 'container') {
// Return specific coordinates within the droppable container
return {
x: newRect.left + 20,
y: newRect.top + 74,
};
}
// Otherwise, return the top left coordinates of the droppable container
return {
x: newRect.left,
y: newRect.top,
};
}
}
}
// If none of the above conditions are met, return undefined
return undefined;
};

@ -0,0 +1,3 @@
import { createContext } from 'react'
export const AppContext = createContext<any>({})

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>QuickDapp</title>
<link rel="stylesheet" integrity="ha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous" href="https://remix.ethereum.org/assets/fontawesome/css/all.css">
</head>
<body>
<script>
var global = window
</script>
<div id="root"></div>
</body>
</html>

@ -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,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable'
import 'regenerator-runtime/runtime'

@ -0,0 +1,19 @@
{
"name": "quick-dapp",
"displayName": "Quick Dapp",
"description": "Edit & deploy a Dapp",
"version": "1.0.0",
"methods": [
"edit"
],
"kind": "none",
"icon": "assets/img/quickDappLogo.webp",
"location": "mainPanel",
"url": "plugins/quick-dapp/index.html",
"repo": "https://github.com/ethereum/remix-project/tree/master/apps/quick-dapp",
"maintainedBy": "Remix",
"authorContact": "https://github.com/drafish",
"targets": [
"remix"
]
}

@ -0,0 +1,33 @@
export const appInitialState: any = {
loading: { screen: true },
instance: {
name: '',
address: '',
network: '',
abi: {},
items: {},
containers: [],
theme: 'Dark',
userInput: { methods: {} },
natSpec: { checked: false, methods: {} },
},
};
export const appReducer = (state = appInitialState, action: any): any => {
switch (action.type) {
case 'SET_LOADING':
return {
...state,
loading: { ...state.loading, ...action.payload },
};
case 'SET_INSTANCE':
return {
...state,
instance: { ...state.instance, ...action.payload },
};
default:
throw new Error();
}
};

@ -0,0 +1,24 @@
import { PluginClient } from '@remixproject/plugin';
import { createClient } from '@remixproject/plugin-webview';
import { initInstance } from './actions';
class RemixClient extends PluginClient {
constructor() {
super();
createClient(this);
}
edit({ address, abi, network, name, devdoc, methodIdentifiers, solcVersion }: any): void {
initInstance({
address,
abi,
network,
name,
devdoc,
methodIdentifiers,
solcVersion,
});
}
}
export default new RemixClient();

@ -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,90 @@
const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-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),
})
)
// 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,70 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@dnd-kit/accessibility@^3.1.0":
version "3.1.0"
resolved "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz#1054e19be276b5f1154ced7947fc0cb5d99192e0"
integrity sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==
dependencies:
tslib "^2.0.0"
"@dnd-kit/core@^6.1.0":
version "6.1.0"
resolved "https://registry.npmjs.org/@dnd-kit/core/-/core-6.1.0.tgz#e81a3d10d9eca5d3b01cbf054171273a3fe01def"
integrity sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==
dependencies:
"@dnd-kit/accessibility" "^3.1.0"
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/sortable@^8.0.0":
version "8.0.0"
resolved "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-8.0.0.tgz#086b7ac6723d4618a4ccb6f0227406d8a8862a96"
integrity sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==
dependencies:
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/utilities@^3.2.2":
version "3.2.2"
resolved "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b"
integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==
dependencies:
tslib "^2.0.0"
"@drafish/surge-client@^1.1.5":
version "1.1.5"
resolved "https://registry.npmjs.org/@drafish/surge-client/-/surge-client-1.1.5.tgz#7663f336dcd23bdc490deb9be01b9f83fab35e04"
integrity sha512-kWzs5PlnWDh4sl+WlNkbkNG+o3SNecW7xXvcM4WnQaQe6CB8anwXXmGtp5JEw3zJxKTKRB5W2xViyEszhh89ZQ==
dependencies:
buffer "^6.0.3"
pako "^2.1.0"
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
pako@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
tslib@^2.0.0:
version "2.6.3"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==

@ -3,11 +3,20 @@ import EventEmitter from 'events'
class pinGrid extends EventEmitter {
command (this: NightwatchBrowser, provider: string, status: boolean): NightwatchBrowser {
this.api.useCss().waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-another-chain"]`)
.click(`[data-id="dropdown-item-another-chain"]`)
.waitForElementVisible(`[data-id="${provider}-${status ? 'unpinned' : 'pinned'}"]`)
this.api.useCss()
.perform((done) => {
// check if the providers plugin is loaded.
this.api.isVisible({ selector: '[data-id="remixUIGSDeploy using a Browser Extension."]', suppressNotFoundErrors: true}, (result) => {
if (!result.value) {
this.api.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-another-chain"]`)
.click(`[data-id="dropdown-item-another-chain"]`)
.perform(() => done())
} else done()
})
})
.waitForElementVisible(`[data-id="${provider}-${status ? 'unpinned' : 'pinned'}"]`, 60000)
.click(`[data-id="${provider}-${status ? 'unpinned' : 'pinned'}"]`)
.perform((done) => {
done()

@ -1,4 +1,4 @@
import {NightwatchBrowser} from 'nightwatch'
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class RefreshPage extends EventEmitter {

@ -2,7 +2,7 @@ import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch'
class RenamePath extends EventEmitter {
command (this: NightwatchBrowser, path: string, newFileName: string, renamedPath: string) {
command(this: NightwatchBrowser, path: string, newFileName: string, renamedPath: string) {
this.api.perform((done) => {
renamePath(this.api, path, newFileName, renamedPath, () => {
done()
@ -13,9 +13,9 @@ class RenamePath extends EventEmitter {
}
}
function renamePath (browser: NightwatchBrowser, path: string, newFileName: string, renamedPath: string, done: VoidFunction) {
function renamePath(browser: NightwatchBrowser, path: string, newFileName: string, renamedPath: string, done: VoidFunction) {
browser.execute(function (path: string) {
function contextMenuClick (element) {
function contextMenuClick(element) {
const evt = element.ownerDocument.createEvent('MouseEvents')
const RIGHT_CLICK_BUTTON_CODE = 2 // the same for FF and IE
@ -32,15 +32,18 @@ function renamePath (browser: NightwatchBrowser, path: string, newFileName: stri
}
contextMenuClick(document.querySelector('[data-path="' + path + '"]'))
}, [path], function () {
browser
.click('#menuitemrename')
.sendKeys('[data-input-path="' + path + '"]', newFileName)
.sendKeys('[data-input-path="' + path + '"]', browser.Keys.ENTER)
.waitForElementNotPresent('[data-path="' + path + '"]')
.waitForElementPresent('[data-path="' + renamedPath + '"]')
.perform(() => {
done()
})
try {
browser
.click('#menuitemrename')
.sendKeys('[data-input-path="' + path + '"]', newFileName)
.sendKeys('[data-input-path="' + path + '"]', browser.Keys.ENTER)
.waitForElementNotPresent('[data-path="' + path + '"]')
.waitForElementPresent('[data-path="' + renamedPath + '"]');
} catch (error) {
console.error('An error occurred:', error.message);
} finally {
done(); // Ensure done is called even if there's an error
}
})
}

@ -4,13 +4,35 @@ import EventEmitter from 'events'
class switchEnvironment extends EventEmitter {
command (this: NightwatchBrowser, provider: string): NightwatchBrowser {
this.api.useCss().waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`)
.click(`[data-id="dropdown-item-${provider}"]`)
.perform((done) => {
done()
this.emit('complete')
})
this.api.isPresent({ selector: `[data-id="selected-provider-${provider}"]`, suppressNotFoundErrors: true, timeout: 5000}, (result) => {
if (result.value) {
done()
} else {
browser.perform(() => {
this.api
.click('[data-id="settingsSelectEnvOptions"] button') // open dropdown
.isPresent({ selector: `[data-id="dropdown-item-${provider}"]`, suppressNotFoundErrors: true, timeout: 5000}, (result) => {
console.log(result)
this.api.click('[data-id="settingsSelectEnvOptions"] button') // close dropdown
if (!result.value) {
this.api.pinGrid(provider, true)
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`)
.click(`[data-id="dropdown-item-${provider}"]`)
.perform(() => done())
} else {
browser.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`)
.click(`[data-id="dropdown-item-${provider}"]`)
.perform(() => done())
}
})
})
}
})
}).perform(() => this.emit('complete'))
return this
}
}

@ -2,37 +2,37 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class VerifyLoad extends EventEmitter {
command(this: NightwatchBrowser) {
browser.waitForElementPresent({
selector: "//span[@data-id='typesloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='editorloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='workspaceloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='apploaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='compilerloaded']",
locateStrategy: 'xpath',
timeout: 120000
})
.perform((done) => {
done()
this.emit('complete')
})
}
command(this: NightwatchBrowser) {
browser.waitForElementPresent({
selector: "//span[@data-id='typesloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='editorloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='workspaceloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='apploaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='compilerloaded']",
locateStrategy: 'xpath',
timeout: 120000
})
.perform((done) => {
done()
this.emit('complete')
})
}
}
module.exports = VerifyLoad
module.exports = VerifyLoad

@ -1,3 +1,4 @@
/* eslint-disable prefer-rest-params */
import { NightwatchBrowser } from 'nightwatch'
require('dotenv').config()
@ -7,6 +8,7 @@ type LoadPlugin = {
url: string
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, loadPlugin?: LoadPlugin, hideToolTips: boolean = true): void {
browser
.url(url || 'http://127.0.0.1:8080')
@ -26,50 +28,50 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.verifyLoad()
.enableClipBoard()
.perform((done) => {
browser.execute(function () { // hide tooltips
function addStyle(styleString) {
const style = document.createElement('style');
style.textContent = styleString;
document.head.append(style);
}
browser.execute(function () { // hide tooltips
function addStyle(styleString) {
const style = document.createElement('style');
style.textContent = styleString;
document.head.append(style);
}
addStyle(`
addStyle(`
.popover {
display:none !important;
}
`);
}, [], done())
})
.perform(() => {
browser.execute(function () {
(window as any).logs = [];
(console as any).browserLog = console.log;
(console as any).browserError = console.error
console.log = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserLog(...arguments)
}
console.error = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserError(...arguments)
}
})
})
.perform(() => {
if (preloadPlugins) {
initModules(browser, () => {
browser
.pause(4000)
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')
.perform(() => { callback() })
})
} else {
callback()
}, [], done())
})
.perform(() => {
browser.execute(function () {
(window as any).logs = [];
(console as any).browserLog = console.log;
(console as any).browserError = console.error
console.log = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserLog(...arguments)
}
console.error = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserError(...arguments)
}
})
})
.perform(() => {
if (preloadPlugins) {
initModules(browser, () => {
browser
.pause(4000)
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')
.perform(() => { callback() })
})
} else {
callback()
}
})
}
function initModules(browser: NightwatchBrowser, callback: VoidFunction) {

@ -102,6 +102,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.modalFooterOKClick('TemplatesSelection')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')

@ -72,17 +72,36 @@ module.exports = {
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
},
'Should generate R1CS for a simple circuit #group2': function (browser: NightwatchBrowser) {
'Should run Groth16 setup and export for a simple circuit using the GUI #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementPresent('button[data-id="generate_r1cs_btn"]')
.waitForElementVisible('button[data-id="generate_r1cs_btn"]')
.click('button[data-id="generate_r1cs_btn"]')
.waitForElementVisible('[data-id="setup_exports_toggler"]')
.waitForElementPresent('[data-id="groth16ProvingScheme"]')
.click('[data-id="groth16ProvingScheme"]')
.waitForElementVisible('[data-id="circuitPtauSelect"]')
.click('[data-id="circuitPtauSelect"]')
.waitForElementVisible('[data-id="dropdown-item-final_8.ptau"]')
.click('[data-id="dropdown-item-final_8.ptau"]')
.click('[data-id="runSetupBtn"]')
.waitForElementVisible('[data-id="setup_exports_toggler"] .fa-check-circle')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.r1cs"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.r1cs"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/groth16/zk/keys/verification_key.json"]')
},
'Should run Plonk setup and export for a simple circuit using the GUI #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementVisible('[data-id="setup_exports_toggler"]')
.click('[data-id="setup_exports_toggler"]')
.waitForElementPresent('[data-id="plonkProvingScheme"]')
.click('[data-id="plonkProvingScheme"]')
.click('[data-id="runSetupBtn"]')
.waitForElementVisible('[data-id="setup_exports_toggler"] .fa-check-circle')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/plonk/zk/keys/verification_key.json"]')
},
'Should compile a simple circuit using CTRL + S from the editor #group3': function (browser: NightwatchBrowser) {
browser
@ -176,15 +195,11 @@ module.exports = {
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.pause(2000)
.journalLastChildIncludes('Generating R1CS for circuits/calculate_hash.circom')
.pause(5000)
.journalLastChildIncludes('Everything went okay')
.pause(7000)
.journalLastChildIncludes('newZkey')
.pause(25000)
.journalLastChildIncludes('setup done.')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/keys/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/keys/zkey_final.txt"]')
},
'Should run groth16 zkproof script for hash checker #group5': function (browser: NightwatchBrowser) {
browser
@ -214,15 +229,11 @@ module.exports = {
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.pause(2000)
.journalLastChildIncludes('Generating R1CS for circuits/calculate_hash.circom')
.pause(5000)
.journalLastChildIncludes('Everything went okay')
.pause(7000)
.journalLastChildIncludes('plonk setup')
.pause(10000)
.journalLastChildIncludes('setup done')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys/zkey_final.txt"]')
},
'Should run plonk zkproof script for hash checker #group6': function (browser: NightwatchBrowser) {
browser

@ -18,20 +18,36 @@ module.exports = {
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
.click('*[data-id="initgit-btn"]')
},
'launch github login via FE #group1 #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filepanel-login-github"]')
.click('*[data-id="filepanel-login-github"]')
},
'login to github #group1 #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="github-panel"]')
.waitForElementVisible('*[data-id="gitubUsername"]')
.setValue('*[data-id="githubToken"]', process.env.dgit_token)
.setValue('*[data-id="gitubUsername"]', 'git')
.setValue('*[data-id="githubEmail"]', 'git@example.com')
.click('*[data-id="saveGitHubCredentials"]')
},
'check if the settings are loaded #group1 #group2': function (browser: NightwatchBrowser) {
browser.
click('*[data-id="github-panel"]')
browser
.waitForElementVisible('*[data-id="connected-as-bunsenstraat"]')
.waitForElementVisible('*[data-id="connected-img-bunsenstraat"]')
.waitForElementVisible('*[data-id="connected-link-bunsenstraat"]')
},
'check the FE for the auth user #group1 #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filepanel-connected-img-bunsenstraat"]')
},
'clone a repository #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.click('*[data-id="clone-panel"]')
.click({
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="fetch-repositories"]',
@ -191,10 +207,25 @@ module.exports = {
locateStrategy: 'xpath'
})
},
'disconnect github #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="github-panel"]')
.click('*[data-id="github-panel"]')
.waitForElementVisible('*[data-id="disconnect-github"]')
.click('*[data-id="disconnect-github"]')
.waitForElementNotPresent('*[data-id="connected-as-bunsenstraat"]')
},
'check the FE for the disconnected auth user #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementNotPresent('*[data-id="filepanel-connected-img-bunsenstraat"]')
.waitForElementVisible('*[data-id="filepanel-login-github"]')
},
'add a remote #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="remotes-panel"]')
.click('*[data-id="remotes-panel"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="fetch-repositories"]',
@ -310,4 +341,70 @@ module.exports = {
}
})
},
// pagination test
'clone repo #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="clone-panel"]')
.click('*[data-id="clone-panel"]')
.waitForElementVisible('*[data-id="clone-url"]')
.setValue('*[data-id="clone-url"]', 'https://github.com/ethereum/awesome-remix')
.waitForElementVisible('*[data-id="clone-branch"]')
.setValue('*[data-id="clone-branch"]', 'master')
.waitForElementVisible('*[data-id="clone-btn"]')
.click('*[data-id="clone-btn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.md"]')
},
'Update settings for git #group3': function (browser: NightwatchBrowser) {
browser.
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="github-panel"]')
.click('*[data-id="github-panel"]')
.setValue('*[data-id="githubToken"]', 'invalidtoken')
.setValue('*[data-id="gitubUsername"]', 'git')
.setValue('*[data-id="githubEmail"]', 'git@example.com')
.click('*[data-id="saveGitHubCredentials"]')
.modalFooterOKClick('github-credentials-error')
},
'check the commits panel for pagination #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="commits-panel"]')
.click('*[data-id="commits-panel"]')
.elements('xpath', '//*[@data-id="commits-current-branch-master"]//*[@data-type="commit-summary"]', function (result) {
console.log('Number of commit-summary elements:', (result.value as any).length);
browser.assert.ok((result.value as any).length == 1)
})
},
'load more commits #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="load-more-commits"]')
.click('*[data-id="load-more-commits"]')
.waitForElementVisible('*[data-id="loader-indicator"]')
.waitForElementNotPresent('*[data-id="loader-indicator"]')
.elements('xpath', '//*[@data-id="commits-current-branch-master"]//*[@data-type="commit-summary"]', function (result) {
console.log('Number of commit-summary elements:', (result.value as any).length);
browser.assert.ok((result.value as any).length > 2)
})
},
'load more branches from remote #group3': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="branches-panel"]')
.waitForElementVisible({
selector: '//*[@data-id="branches-panel-content-remote-branches"]',
locateStrategy: 'xpath'
})
.elements('xpath', '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-type="branches-branch"]', function (result) {
console.log('Number of branches elements:', (result.value as any).length);
browser.assert.ok((result.value as any).length == 1)
})
.waitForElementVisible('*[data-id="remote-sync-origin"]')
.click('*[data-id="remote-sync-origin"]')
.waitForElementVisible('*[data-id="loader-indicator"]')
.waitForElementNotPresent('*[data-id="loader-indicator"]')
.elements('xpath', '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-type="branches-branch"]', function (result) {
console.log('Number of branches elements:', (result.value as any).length);
browser.assert.ok((result.value as any).length > 2)
})
}
}

@ -36,6 +36,9 @@ module.exports = {
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
.click('*[data-id="initgit-btn"]')
.waitForElementVisible('*[data-id="github-panel"]')
.click('*[data-id="github-panel"]')
.waitForElementVisible('*[data-id="gitubUsername"]')
.setValue('*[data-id="gitubUsername"]', 'git')
.setValue('*[data-id="githubEmail"]', 'git@example.com')
.click('*[data-id="saveGitHubCredentials"]')
@ -241,6 +244,8 @@ module.exports = {
'check if the branch is in the filePanel #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="workspaceGitBranchesDropdown"]')
.pause(1000)
.click('[data-id="workspaceGitBranchesDropdown"]')
.expect.element('[data-id="workspaceGit-testbranch"]').text.to.contain('✓ ')
},
@ -314,18 +319,30 @@ module.exports = {
},
'switch back to master #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-branch-master']",
locateStrategy: 'xpath',
})
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-branch-master']",
locateStrategy: 'xpath',
})
.pause(1000)
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-branch-master']",
locateStrategy: 'xpath',
abortOnFailure: false,
suppressNotFoundErrors: true
})
.waitForElementVisible({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-current-branch-master']",
locateStrategy: 'xpath',
timeout: 60000
})
},
'check if test file is gone #group2': function (browser: NightwatchBrowser) {
browser
.pause()
.pause(2000)
.clickLaunchIcon('filePanel')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.txt"]')
},

@ -22,6 +22,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')

@ -18,6 +18,7 @@ module.exports = {
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
@ -93,7 +94,7 @@ module.exports = {
.rightClick('*[data-id="treeViewUltreeViewMenu"]')
.saveScreenshot('./reports/screenshot/file_explorer_context_menu.png')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemCopy_README.txt"]', 7000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME1.txt"]', 7000)
},
'Should copy file and paste in contracts with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
@ -103,7 +104,7 @@ module.exports = {
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Copy_README.txt"]', 7000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/README1.txt"]', 7000)
},
// folder copy paste tests
'Should copy folder and paste in root with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {

@ -32,5 +32,25 @@ module.exports = {
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementNotPresent(`[data-id="dropdown-item-vm-sepolia-fork"]`)
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
},
'remember pin upon reload': function (browser: NightwatchBrowser) {
browser
.pinGrid('vm-paris', true)
.click('[data-id="settingsSelectEnvOptions"] button') // open the dropdown
.waitForElementPresent(`[data-id="dropdown-item-vm-paris"]`)
.refreshPage()
.waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts"]') // wait loaded
.clickLaunchIcon('udapp')
.click('[data-id="settingsSelectEnvOptions"] button') // open the dropdown
.waitForElementPresent(`[data-id="dropdown-item-vm-paris"]`)
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
.pinGrid('vm-paris', false)
.click('[data-id="settingsSelectEnvOptions"] button') // open the dropdown
.waitForElementNotPresent(`[data-id="dropdown-item-vm-paris"]`)
.refreshPage()
.waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts"]') // wait loaded
.clickLaunchIcon('udapp')
.click('[data-id="settingsSelectEnvOptions"] button') // open the dropdown
.waitForElementNotPresent(`[data-id="dropdown-item-vm-paris"]`)
}
}

@ -179,6 +179,14 @@ module.exports = {
},
'Should select another provider #group1': async function (browser: NightwatchBrowser) {
await browser
.frameParent()
.useCss()
.clickLaunchIcon('udapp')
.pinGrid('vm-berlin', true)
.clickLaunchIcon('localPlugin')
.useXpath()
.frame(0)
await clickAndCheckLog(browser, 'udapp:setEnvironmentMode', null, null, { context: 'vm-berlin' })
await browser
.frameParent()

@ -0,0 +1,322 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import path from 'path'
import axios from 'axios'
import crypto from 'crypto'
import init from '../helpers/init'
const passphrase = process.env.account_passphrase
const password = process.env.account_password
const extension_id = 'nkbihfbeogaeaoehlefnkodbefgpgknn'
const extension_url = `chrome-extension://${extension_id}/home.html`
const address = '0x3b3f6501A7fE68d22eFbc07d4424D4b9115C3038'
const surgeEmail = 'e2e@remix.org'
const surgePassword = 'remixe2e'
const surgeSubdomain = 'remixe2e'
const logoFilePath = path.resolve(__dirname, '../../../remix-ide/assets/img/remixLogo.webp')
const logoHash = 'ba8db45b3af49365bd482c7037dacaf1c549dc73c070ad963922adfeece4f37d'
const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
const tests = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.useCss().switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.click('*[data-id="landingPageStartSolidity"]')
.clickLaunchIcon('udapp')
.switchEnvironment('injected-MetaMask')
.waitForElementPresent('*[data-id="settingsNetworkEnv"]')
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network')
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(0) // back to remix
},
'Should load quick-dapp plugin #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.useCss()
.addFile('Storage.sol', sources[0]['Storage.sol'])
.clickLaunchIcon('udapp')
.clickLaunchIcon('udapp')
.addAtAddressInstance(address, true, true, false)
.waitForElementPresent(`*[data-id="unpinnedInstance${address}"]`)
.clickInstance(0)
.click('*[data-id="instanceEditIcon"]')
.pause(5000)
.frame(0)
.assert.containsText('*[data-id="quick-dapp-admin"]', 'QuickDapp Admin')
},
'Should edit and deploy a dapp to surge.sh #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.click('.container.placeholder')
.perform((done) => {
browser.findElement('*[data-id="containerColumnC"]', (el: any) => {
browser.dragAndDrop('*[data-id="handle0x6057361d"]', el.value.getId())
.dragAndDrop('*[data-id="handleColumnA"]', el.value.getId())
.click('*[data-id="remove0x1003e2d2"]')
.click('*[data-id="removeColumnA"]')
.perform(() => done())
})
})
.setValue('input[data-id="surgeEmail"]', surgeEmail)
.setValue('input[data-id="surgePassword"]', surgePassword)
.setValue('input[data-id="surgeSubdomain"]', surgeSubdomain)
.setValue('input[data-id="functionTitle0x6057361d"]', 'Function Store Title')
.setValue('input[data-id="functionTitle0x2e64cec1"]', 'Function Retrive Title')
.execute((function() {
document.querySelector('input[data-id="uploadLogo"]').classList.remove('d-none');
}))
.setValue('input[data-id="uploadLogo"]', logoFilePath)
.execute((function() {
document.querySelector('input[data-id="uploadLogo"]').classList.add('d-none');
}))
.click('[for="shareToTwitter"]')
.click('[for="shareToFacebook"]')
.click('*[data-id="useNatSpec"]')
.click('[for="verifiedByEtherscan"]')
.click('[for="noTerminal"]')
.click('*[data-id="selectThemesOptions"]')
.click('*[data-id="dropdown-item-Light"]')
.click('*[data-id="deployDapp"]')
.waitForElementVisible('*[data-id="deployResult"]', 20000)
.perform((done) => {
browser.getAttribute('*[data-id="deployResult"]', 'class', function (result) {
// @ts-expect-error
if (result.value.includes('alert-danger')) {
browser.click('*[data-id="deployDapp"]').waitForElementVisible('*[data-id="deployResult"]', 20000).perform(() => done())
} else {
done()
}
})
})
.assert.containsText('*[data-id="deployResult"]', `https://${surgeSubdomain}.surge.sh`)
},
'Should load and call dapp successfully #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser
.switchBrowserTab(1).url(`https://${surgeSubdomain}.surge.sh`)
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(1)
.setValue('input[placeholder="uint256 num"]', '11')
.pause(1000)
.click('*[data-id="store - transact (not payable)"]')
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
suppressNotFoundErrors: true,
timeout: 3000
}, (okVisible) => {
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
} else {
browser.click('button[data-testid="popover-close"]').click('.transaction-status-label--unapproved')
}
})
.waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx
.perform(() => done())
})
})
.switchBrowserTab(1) // back to dapp
.waitForElementVisible('.Toastify__toast--success', 60000)
.assert.containsText('.Toastify__toast--success', 'success')
.click('*[data-id="retrieve - call"]')
.waitForElementVisible('*[data-id="treeViewDiv0"]', 20000)
.assert.containsText('*[data-id="treeViewDiv0"]', 'uint256: 11')
.perform((done) => {
axios.get(`https://${surgeSubdomain}.surge.sh/logo.png?t=${new Date().getTime()}`, { responseType: 'arraybuffer' }).then((resp) => {
const hash = crypto.createHash('sha256');
hash.update(resp.data);
const hashValue = hash.digest('hex');
console.log('Hash:', hashValue);
browser.assert.strictEqual(hashValue, logoHash, 'Hash values match!').perform(() => done())
})
})
.assert.containsText('*[data-id="functionTitle0x6057361d"]', 'Function Store Title')
.assert.containsText('*[data-id="functionTitle0x2e64cec1"]', 'Function Retrive Title')
.assert.containsText('*[data-id="dappTitle"]', 'Storage')
.assert.containsText('*[data-id="dappInstructions"]', 'Store & retrieve value in a variable')
.assert.elementPresent('.fa-twitter.btn', 'Twitter icon should be present')
.assert.elementPresent('.fa-facebook.btn', 'Facebook icon should be present')
.checkElementStyle(':root', '--secondary', '#b3bcc483')
.element('css selector', '#terminal-view', function (result) {
browser.assert.strictEqual(result.status, -1, 'terminal should not shown')
})
.element('css selector', '*[data-id="function0x1003e2d2"]', function (result) {
browser.assert.strictEqual(result.status, -1, 'function add should not shown')
})
.getLocation('*[data-id="function0x6057361d"]', function (result: any) {
const funcStoreLocation = result.value
browser.getLocation('*[data-id="function0x2e64cec1"]', function (result: any) {
const funcRetriveLocation = result.value
browser.assert.strictEqual(funcStoreLocation.y, funcRetriveLocation.y, 'Both functions should be on the same horizontal line')
browser.assert.ok(funcStoreLocation.x > funcRetriveLocation.x, 'Function Store should be on the right of Function Retrive')
})
})
.getAttribute('a[data-id="viewSourceCode"]', 'href', function (result) {
browser.assert.strictEqual(result.value, `https://remix.ethereum.org/address/${address}`, 'view source code url should match')
})
},
'Should reset and delete and submit dapp params #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.switchBrowserTab(0).frame(0)
.click('*[data-id="resetFunctions"]')
.assert.elementPresent('*[data-id="remove0x1003e2d2"]', 'Function add should be present again')
.click('*[data-id="deleteDapp"]')
.assert.containsText('*[data-id="quickDappTooltips"]', 'QuickDapp only work for Injected Provider currently')
.setValue('input[id="formAddress"]', address)
.setValue('textarea[id="formAbi"]', abi)
.setValue('input[id="formName"]', 'Storage')
.setValue('input[id="formNetwork"]', 'Sepolia (11155111) network')
.click('*[data-id="createDapp"]')
.assert.containsText('*[data-id="quick-dapp-admin"]', 'QuickDapp Admin')
},
'Should teardown dapp successfully #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.setValue('input[data-id="surgeSubdomain"]', surgeSubdomain)
.execute((function() {
// @ts-expect-error
document.querySelector('*[data-id="teardownDapp"]').style.display = 'inline-block';
}))
.pause(500)
.click('*[data-id="teardownDapp"]')
.waitForElementVisible('*[data-id="teardownResult"]', 30000)
.assert.containsText('*[data-id="teardownResult"]', 'Teardown successfully!')
}
}
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = branch === 'master';
module.exports = {
...(branch ? (isMasterBranch ? tests : {}) : tests),
};
const sources = [
{
'Storage.sol': {
content:
`
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
function add(uint256 num) public {
number = number + num;
}
}`
}
}
]
const abi = JSON.stringify([
{
"inputs": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
}
],
"name": "add",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "retrieve",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
}
],
"name": "store",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
])

@ -2,10 +2,9 @@
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import { join } from 'path'
import { ChildProcess, spawn } from 'child_process'
import { ChildProcess, exec, spawn } from 'child_process'
import { homedir } from 'os'
import kill from 'tree-kill'
import treeKill from 'tree-kill'
let remixd: ChildProcess
const assetsTestContract = `import "./contract.sol";
@ -50,15 +49,30 @@ const sources = [
}
]
module.exports = {
'@disabled': true,
before: function (browser, done) {
init(browser, done)
},
after: function (browser) {
browser.perform((done) => {
console.log('remixd', remixd.pid)
kill(remixd.pid)
try {
console.log('remixd pid', remixd.pid);
treeKill(remixd.pid, 'SIGKILL', (err) => {
console.log('remixd killed', err)
})
console.log('Service disconnected successfully.');
} catch (error) {
console.error('Failed to disconnect service:', error);
}
try {
resetGitToHead()
} catch (error) {
console.error('Failed to restore git changes:', error);
}
done()
})
},
@ -66,12 +80,14 @@ module.exports = {
'@sources': function () {
return sources
},
'run Remixd tests #group1': function (browser) {
'run Remixd tests #group1': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
try {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) {
console.error(err)
// fail
browser.assert.fail('Failed to start remixd')
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
@ -86,7 +102,12 @@ module.exports = {
remix try to resolve it against the node_modules and installed_contracts folder.
*/
browser.perform(async (done) => {
try{
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) {
console.error(err)
browser.assert.fail('Failed to start remixd')
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
@ -97,7 +118,12 @@ module.exports = {
},
'Import from node_modules and reference a github import #group3': function (browser) {
browser.perform(async (done) => {
try{
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) {
console.error(err)
browser.assert.fail('Failed to start remixd')
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
@ -117,7 +143,12 @@ module.exports = {
'Should listen on compilation result from hardhat #group4': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
try{
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide/hardhat-boilerplate'))
} catch (err) {
console.error(err)
browser.assert.fail('Failed to start remixd')
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
@ -188,7 +219,12 @@ module.exports = {
browser.perform(async (done) => {
console.log('working directory', homedir() + '/foundry_tmp/hello_foundry')
try{
remixd = await spawnRemixd(join(homedir(), '/foundry_tmp/hello_foundry'))
} catch (err) {
console.error(err)
browser.assert.fail('Failed to start remixd')
}
connectRemixd(browser, done)
})
.perform(async (done) => {
@ -249,7 +285,12 @@ module.exports = {
'Should disable git when running remixd #group9': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
try{
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/hardhat'))
} catch (err) {
console.error(err)
browser.assert.fail('Failed to start remixd')
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
@ -281,6 +322,7 @@ module.exports = {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) {
console.error(err)
browser.assert.fail('Failed to start remixd')
}
console.log('working directory', process.cwd())
connectRemixd(browser, done)
@ -322,8 +364,11 @@ function runTests(browser: NightwatchBrowser, done: any) {
.setEditorValue('contract test1Changed { function get () returns (uint) { return 10; }}')
.testEditorValue('contract test1Changed { function get () returns (uint) { return 10; }}')
.setEditorValue('contract test1 { function get () returns (uint) { return 10; }}')
.waitForElementVisible('[data-path="folder1"]')
.waitForElementVisible('[data-path="folder1/contract_' + browserName + '.sol"]')
.click('[data-path="folder1/contract_' + browserName + '.sol"]') // rename a file and check
.pause(1000)
.renamePath('folder1/contract_' + browserName + '.sol', 'renamed_contract_' + browserName, 'folder1/renamed_contract_' + browserName + '.sol')
.pause(1000)
.removeFile('folder1/contract_' + browserName + '_toremove.sol', 'localhost')
@ -558,3 +603,26 @@ async function installSlither(): Promise<void> {
console.log(e)
}
}
function resetGitToHead() {
if (process.env.CIRCLECI) {
console.log("Running on CircleCI, resetting Git to HEAD...");
} else {
console.log("Not running on CircleCI, skipping Git reset.");
return
}
const command = 'git reset --hard HEAD && git clean -fd';
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Error executing command: ${command}\n${error.message}`);
return;
}
if (stderr) {
console.error(`Error output from command: ${command}\n${stderr}`);
return;
}
console.log(`Git reset to HEAD successfully.\n${stdout}`);
});
}

@ -163,16 +163,17 @@ module.exports = {
.click('.remixui_compilerConfigSection')
.setValue('#evmVersionSelector', 'london')
.click('*[data-id="compilerContainerCompileBtn"]')
.pause(5000)
.clickLaunchIcon('udapp')
.switchEnvironment('vm-london')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.click('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]')
.openFile('scripts/deploy_with_web3.ts')
.click('[data-id="play-editor"]')
.waitForElementPresent('[data-id="treeViewDivDraggableItem.states/vm-london/state.json"]')
.click('[data-id="treeViewDivDraggableItem.states/vm-london/state.json"]')
.pause(100000)
.pause(1000)
.getEditorValue((content) => {
browser
.assert.ok(content.includes('"latestBlockNumber": "0x1"'), 'State is saved')

@ -129,8 +129,7 @@ module.exports = {
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/yann300/remix-reward')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.waitForElementPresent('.fa-spinner')
.pause(5000)
.waitForElementNotPresent('.fa-spinner')
.waitForElementNotPresent('.fa-spinner', 120000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.git"]')
.waitForElementContainsText('[data-id="workspacesSelect"]', 'remix-reward')
.clickLaunchIcon('solidity')

@ -48,7 +48,7 @@ module.exports = {
// Check warning count
.waitForElementVisible('span#ssaRemixtab')
.click('span#ssaRemixtab')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '3')
.verify.elementPresent('input[name="showLibWarnings"]')
.verify.not.elementPresent('input[name="showLibWarnings"]:checked')
.verify.elementPresent('label[id="headingshowLibWarnings"]')
@ -56,10 +56,10 @@ module.exports = {
.pause(1000)
.waitForElementVisible('span#ssaRemixtab')
.click('span#ssaRemixtab')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '386')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '388')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '3')
.end()
}
}

@ -17,13 +17,13 @@ module.exports = {
browser.clickLaunchIcon('pluginManager')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonvyper"]')
.clickLaunchIcon('vyper')
.pause(5000)
// @ts-ignore
.frame(0)
},
'Should clone the Vyper repo #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-repository"]')
browser
.waitForElementVisible('button[data-id="add-repository"]')
.click('button[data-id="add-repository"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementVisible({
@ -31,11 +31,10 @@ module.exports = {
locateStrategy: 'xpath',
timeout: 120000
})
.currentWorkspaceIs('snekmate')
.waitForElementVisible({
selector: "//*[@data-id='treeViewLitreeViewItemsrc' and contains(.,'src')]",
selector: "//*[contains(., 'Vyper repository cloned')]",
locateStrategy: 'xpath',
timeout: 1200000
timeout: 120000
})
},
// 'Add vyper file to run tests #group1': function (browser: NightwatchBrowser) {
@ -123,6 +122,7 @@ module.exports = {
.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.waitForElementVisible('[data-id="compile"]')
.click('[data-id="compile"]')
.waitForElementVisible({
selector:'[data-id="compilation-details"]',

@ -43,6 +43,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.modalFooterOKClick('TemplatesSelection')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
@ -115,6 +117,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementPresent('*[data-id="treeViewUltreeViewMenu"]')
@ -136,6 +140,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc20')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
@ -195,6 +201,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
@ -254,6 +262,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc1155')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc1155' })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
@ -484,6 +494,7 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'sometestworkspace')
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'sometestworkspace' })
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -510,6 +521,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_db_test')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_db_test' })
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -538,6 +551,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'multisig cookbook')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'multisig cookbook' })
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('[data-id="PermissionHandler-modal-footer-ok-react"]', 300000)
.click('[data-id="PermissionHandler-modal-footer-ok-react"]')

@ -53,6 +53,8 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.click('[data-id="initGitRepositoryLabel"]')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
@ -423,6 +425,9 @@ module.exports = {
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
.click('*[data-id="initgit-btn"]')
.waitForElementVisible('*[data-id="github-panel"]')
.click('*[data-id="github-panel"]')
.waitForElementVisible('*[data-id="gitubUsername"]')
.setValue('*[data-id="gitubUsername"]', 'git')
.setValue('*[data-id="githubEmail"]', 'git@example.com')
.click('*[data-id="saveGitHubCredentials"]')
@ -430,6 +435,8 @@ module.exports = {
},
'check source controle panel #group5': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/tests/MyToken_test.sol']",
locateStrategy: 'xpath'

@ -923,9 +923,9 @@ ejs@3.1.8:
jake "^10.8.5"
elliptic@^6.5.4:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
version "6.5.7"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
dependencies:
bn.js "^4.11.9"
brorand "^1.1.0"

@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/remix-ide/src",
"projectType": "application",
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth"],
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth", "quick-dapp"],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",

@ -595,15 +595,22 @@ class DGitProvider extends Plugin<any, CustomRemixApi> {
auth: input.token
})
const user = await octokit.request('GET /user')
const user = await octokit.request('GET /user', {
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
})
const emails = await octokit.request('GET /user/emails')
const scopes = user.headers['x-oauth-scopes'] || ''
return {
user: user.data,
user: {
...user.data, isConnected:
user.data.login !== undefined && user.data.login !== null && user.data.login !== ''
},
emails: emails.data,
scopes: scopes && scopes.split(',')
scopes: scopes && scopes.split(',').map(scope => scope.trim())
}
} catch (e) {
return null

@ -40,7 +40,6 @@ const errorMsg = {
const createError = (err) => {
return new Error(`${errorMsg[err.code]} ${err.message || ''}`)
}
const _paq = (window._paq = window._paq || [])
class FileManager extends Plugin {
mode: string
openedFiles: any
@ -216,11 +215,6 @@ class FileManager extends Plugin {
} else {
const ret = await this.setFileContent(path, data, options)
this.emit('fileAdded', path)
// Temporary solution to tracking scripts execution for zk in matomo
if (path === 'scripts/groth16/zk/keys/zkey_final.txt' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'groth16', 'zk trusted setup done'])
if (path === 'scripts/groth16/zk/build/zk_verifier.sol' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'groth16', 'zk proof done'])
if (path === 'scripts/plonk/zk/keys/zkey_final.txt' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'plonk', 'zk trusted setup done'])
if (path === 'scripts/plonk/zk/build/zk_verifier.sol' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'plonk', 'zk proof done'])
return ret
}
} catch (e) {
@ -318,7 +312,7 @@ class FileManager extends Plugin {
await this._handleExists(dest, `Cannot paste content into ${dest}. Path does not exist.`)
await this._handleIsDir(dest, `Cannot paste content into ${dest}. Path is not directory.`)
const content = await this.readFile(src)
let copiedFilePath = dest + (customName ? '/' + customName : '/' + `Copy_${helper.extractNameFromKey(src)}`)
let copiedFilePath = dest + (customName ? '/' + customName : '/' + `${helper.extractNameFromKey(src)}`)
copiedFilePath = await helper.createNonClashingNameAsync(copiedFilePath, this)
await this.writeFile(copiedFilePath, content)

@ -43,6 +43,7 @@ export class RemixdHandle extends WebsocketPlugin {
await this.appManager.deactivatePlugin(plugin)
}
}
//@ts-ignore
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
this.localhostProvider.close((error) => {

@ -151,11 +151,14 @@ export class TemplatesSelectionPlugin extends ViewPlugin {
description="Select a template to create a workspace or to add it to current workspace"
>
{
templates(window._intl).map(template => {
templates(window._intl, this).map(template => {
return <RemixUIGridSection
plugin={this}
key={template.name}
title={template.name}
tooltipTitle={template.tooltip}
onClick={template.onClick}
onClickLabel={template.onClickLabel}
hScrollable={false}
>
{template.items.map(item => {

@ -1,5 +1,4 @@
export const templates = (intl) => {
export const templates = (intl, plugin) => {
return [
{
name: "Generic",
@ -258,6 +257,26 @@ export const templates = (intl) => {
}
]
},
{
name: "Cookbook",
tooltip: "Cookbook is a smart contract search tool. Click here to open cookbook and browse contracts.",
onClick: async () => {
await plugin.call('manager', 'activatePlugin', 'cookbookdev')
plugin.call('menuicons', 'showContent', 'cookbookdev')
},
onClickLabel: 'Open cookbook plugin',
items: [
{ value: "token-sale", displayName: 'Token Sale' },
{ value: "simple-nft-sale", displayName: 'Simple Nft Sale' },
{ value: "Azuki-ERC721A-NFT-Sale-basic", displayName: 'Azuki ERC721A NFT Sale basic' },
{ value: "Azuki-ERC721A-ERC721A", displayName: 'Azuki ERC721A' },
{ value: "token-staking-with-infinite-rewards", displayName: 'Token Staking with infinite rewards' },
{ value: "nft-staking-with-infinite-rewards", displayName: 'Nft Staking with infinite rewards' },
{ value: "basic-dao", displayName: 'Basic DAO' },
{ value: "soulbound-nft", displayName: 'Soulbound Nft' },
{ value: "multi-collection-nft-with-burnable-nfts-and-pausable-transfers", displayName: 'Multi collection nft with burnable nfts and pausable transfers' },
]
},
{
name: "OxProject",
items: [

@ -10,6 +10,17 @@
"circuit.noFileSelected": "no file selected",
"circuit.generateR1cs": "Generate R1CS",
"circuit.computeWitness": "Compute Witness",
"circuit.generateProof": "Generate Proof",
"circuit.signalInput": "Signal Input",
"circuit.compute": "Compute"
"circuit.compute": "Compute",
"circuit.setupExports": "Setup and Exports",
"circuit.provingScheme": "Proving Scheme",
"circuit.ptau": "POWER OF TAU (PTAU)",
"circuit.randomText": "Ceremony: Random Text",
"circuit.randomBeacon": "Ceremony: Random Beacon",
"circuit.exportVerifierContract": "Export verifier contract",
"circuit.exportVerificationKey": "Export verification key",
"circuit.exportVerifierCalldata": "Export verifier calldata",
"circuit.exportWtnsJson": "Export witness as JSON",
"circuit.runSetup": "Run setup"
}

@ -142,5 +142,7 @@
"filePanel.movingFolderFailedMsg": "Unexpected error while moving folder: {src}",
"filePanel.workspaceActions": "Workspace actions",
"filePanel.saveCodeSample": "This code-sample workspace will not be persisted. Click here to save it.",
"filePanel.logInGithub": "Sign in to GitHub.",
"filePanel.gitHubLoggedAs": "Signed in as {githubuser}",
"filePanel.updateSubmodules": "Update all submodules of repository. Click to pull dependencies."
}

@ -16,5 +16,6 @@
"git.unstageall": "unstage all",
"git.stageall": "stage all",
"git.noremote": "this repo has no remotes",
"git.init": "Initialize repository"
"git.init": "Initialize repository",
"git.setup": "Setup git"
}

@ -5,7 +5,7 @@
"home.scamAlertText2": "Beware of online videos promoting \"liquidity front runner bots\"",
"home.scamAlertText3": "Additional safety tips",
"home.learnMore": "Learn more",
"home.here": "here",
"home.here": "more",
"home.featured": "Featured",
"home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3More": "More",

@ -0,0 +1,42 @@
{
"quickDapp.address": "address",
"quickDapp.enterAddress": "Enter address",
"quickDapp.enterAbi": "Enter abi",
"quickDapp.name": "name",
"quickDapp.enterName": "Enter name",
"quickDapp.network": "network",
"quickDapp.enterNetwork": "Enter network",
"quickDapp.submit": "Submit",
"quickDapp.text1": "QuickDapp only work for Injected Provider currently. More providers will be adapted in further iterations.",
"quickDapp.text2": "Click the edit icon in a deployed contract will input the parameters automatically.",
"quickDapp.admin": "Admin",
"quickDapp.resetFunctions": "Reset Functions",
"quickDapp.deleteDapp": "Delete Dapp",
"quickDapp.text3": "QuickDapp deploys to Surge.sh. Surge accounts are free until you reach a level of use. The email & password you input below will register you with a Surge account. The subdomain is your choice but it must be unique. More about <a>surge.sh</a>",
"quickDapp.email": "Email",
"quickDapp.surgeEmail": "Surge email",
"quickDapp.password": "Password",
"quickDapp.surgePassword": "Surge password",
"quickDapp.subdomain": "Subdomain",
"quickDapp.uniqueSubdomain": "Unique subdomain name",
"quickDapp.shareTo": "Share To (Optional)",
"quickDapp.useNatSpec": "Use NatSpec (Optional)",
"quickDapp.useNatSpecTooltip": "Retrieve info from the contract's NatSpec",
"quickDapp.verifiedByEtherscan": "Verified by Etherscan (Optional)",
"quickDapp.verified": "Verified",
"quickDapp.noTerminal": "No Terminal (Optional)",
"quickDapp.no": "No",
"quickDapp.themes": "Themes",
"quickDapp.deploy": "Deploy",
"quickDapp.teardown": "Teardown",
"quickDapp.text4": "Deployed successfully!",
"quickDapp.text5": "Click the link below to view your dapp",
"quickDapp.text6": "Teardown successfully!",
"quickDapp.uploadLogoTooltip": "Click here to change logo",
"quickDapp.dappTitle": "Dapp Title",
"quickDapp.dappInstructions": "Dapp Instructions",
"quickDapp.functionTitle": "Title of function",
"quickDapp.functionInstructions": "Instructions for function",
"quickDapp.addColumn": "Add column",
"quickDapp.column": "Column"
}

@ -39,6 +39,7 @@
"terminal.executionCost": "execution cost",
"terminal.input": "input",
"terminal.decodedInput": "decoded input",
"terminal.output": "output",
"terminal.decodedOutput": "decoded output",
"terminal.rawlogs": "raw logs",
"terminal.logs": "logs"

@ -38,6 +38,7 @@
"terminal.executionCost": "costo de ejecución",
"terminal.input": "entrada",
"terminal.decodedInput": "entrada descodificada",
"terminal.output": "salida",
"terminal.decodedOutput": "salida descodificada",
"terminal.rawlogs": "registros sin procesar",
"terminal.logs": "registros"

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

Loading…
Cancel
Save