├── .eslintrc.cjs ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.cjs ├── config.json ├── jest-resolver.cjs ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── Add.test.ts ├── Add.ts ├── index.ts └── interact.ts └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | jest: true, 7 | }, 8 | extends: ['eslint:recommended', 'plugin:snarkyjs/recommended'], 9 | parser: '@typescript-eslint/parser', 10 | parserOptions: { 11 | ecmaVersion: 'latest', 12 | }, 13 | plugins: ['@typescript-eslint', 'snarkyjs'], 14 | rules: { 15 | 'no-constant-condition': 'off', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Use line endings appropriate for the system. This prevents Git from 2 | # complaining about project template line endings when committing on Windows. 3 | * text=auto eol=lf 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # 2 | # ci.yml 3 | # 4 | # Run tests for all pushed commits and opened pull requests on Github. 5 | # 6 | 7 | name: CI 8 | on: [push, pull_request] 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | steps: 14 | - name: Set up NodeJS 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: '16' 18 | - name: Git checkout 19 | uses: actions/checkout@v2 20 | - name: NPM ci, build, & test 21 | run: | 22 | npm ci 23 | npm run build --if-present 24 | npm test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS 2 | node_modules 3 | build 4 | coverage 5 | 6 | # Editor 7 | .vscode 8 | 9 | # System 10 | .DS_Store 11 | 12 | # Never commit keys to Git! 13 | keys 14 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # TypeScript files 2 | src 3 | 4 | # Editor 5 | .vscode 6 | 7 | # System 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # NodeJS 2 | node_modules 3 | build 4 | coverage 5 | .husky 6 | 7 | # Editor 8 | .vscode 9 | 10 | # System 11 | .DS_Store 12 | 13 | # Misc 14 | LICENSE 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Licensed under the Apache License, Version 2.0 (the "License"); 190 | you may not use this file except in compliance with the License. 191 | You may obtain a copy of the License at 192 | 193 | http://www.apache.org/licenses/LICENSE-2.0 194 | 195 | Unless required by applicable law or agreed to in writing, software 196 | distributed under the License is distributed on an "AS IS" BASIS, 197 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 198 | See the License for the specific language governing permissions and 199 | limitations under the License. 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mina zkApp: Oracle 2 | 3 | This is tutorial to build Oracle of Mina Protocol Project. 4 | 5 | ## Make New Project 6 | ```sh 7 | zk project oracle 8 | ``` 9 | ```sh 10 | cd oracle 11 | ``` 12 | Delete few files below 13 | ```sh 14 | rm src/Add.ts 15 | rm src/Add.test.ts 16 | rm src/interact.ts 17 | ``` 18 | Edit *index.ts* file in *src* folder 19 | ```sh 20 | import { OracleExample } from './OracleExample.js'; 21 | 22 | export { OracleExample }; 23 | ``` 24 | ### Create new file in *src* folder with name *CreditScoreOracle.ts* and write 25 | ```sh 26 | import { 27 | Field, 28 | SmartContract, 29 | state, 30 | State, 31 | method, 32 | DeployArgs, 33 | Permissions, 34 | PublicKey, 35 | Signature, 36 | PrivateKey, 37 | } from 'snarkyjs'; 38 | 39 | // The public key of our trusted data provider 40 | const ORACLE_PUBLIC_KEY = 41 | 'B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy'; 42 | 43 | export class OracleExample extends SmartContract { 44 | // Define contract state 45 | @state(PublicKey) oraclePublicKey = State(); 46 | 47 | // Define contract events 48 | events = { 49 | verified: Field, 50 | }; 51 | 52 | deploy(args: DeployArgs) { 53 | super.deploy(args); 54 | this.setPermissions({ 55 | ...Permissions.default(), 56 | editState: Permissions.proofOrSignature(), 57 | }); 58 | } 59 | 60 | @method init(zkappKey: PrivateKey) { 61 | super.init(zkappKey); 62 | // Initialize contract state 63 | this.oraclePublicKey.set(PublicKey.fromBase58(ORACLE_PUBLIC_KEY)); 64 | 65 | // Specify that caller should include signature with tx instead of proof 66 | this.requireSignature(); 67 | } 68 | 69 | @method verify(id: Field, creditScore: Field, signature: Signature) { 70 | // Get the oracle public key from the contract state 71 | const oraclePublicKey = this.oraclePublicKey.get(); 72 | this.oraclePublicKey.assertEquals(oraclePublicKey); 73 | 74 | // Evaluate whether the signature is valid for the provided data 75 | const validSignature = signature.verify(oraclePublicKey, [id, creditScore]); 76 | 77 | // Check that the signature is valid 78 | validSignature.assertTrue(); 79 | 80 | // Check that the provided credit score is greater than 700 81 | creditScore.assertGte(Field(700)); 82 | 83 | // Emit an event containing the verified users id 84 | this.emitEvent('verified', id); 85 | 86 | } 87 | } 88 | ``` 89 | ### Create new file in *src* folder with name *OracleExampleScaffold.ts* and write 90 | ```sh 91 | import { 92 | Field, 93 | SmartContract, 94 | state, 95 | State, 96 | method, 97 | DeployArgs, 98 | Permissions, 99 | PublicKey, 100 | Signature, 101 | PrivateKey, 102 | } from 'snarkyjs'; 103 | 104 | // The public key of our trusted data provider 105 | const ORACLE_PUBLIC_KEY = 106 | 'B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy'; 107 | 108 | export class OracleExample extends SmartContract { 109 | // Define contract state 110 | 111 | // Define contract events 112 | 113 | deploy(args: DeployArgs) { 114 | super.deploy(args); 115 | this.setPermissions({ 116 | ...Permissions.default(), 117 | editState: Permissions.proofOrSignature(), 118 | }); 119 | } 120 | 121 | @method init(zkappKey: PrivateKey) { 122 | super.init(zkappKey); 123 | // Initialize contract state 124 | 125 | // Specify that caller should include signature with tx instead of proof 126 | this.requireSignature(); 127 | } 128 | 129 | @method verify(id: Field, creditScore: Field, signature: Signature) { 130 | // Get the oracle public key from the contract state 131 | 132 | // Evaluate whether the signature is valid for the provided data 133 | 134 | // Check that the signature is valid 135 | 136 | // Check that the provided credit score is greater than 700 137 | 138 | // Emit an event containing the verified users id 139 | 140 | } 141 | } 142 | ``` 143 | ### Create new file in *src* folder with name *OracleExample.ts* and write 144 | ```sh 145 | import { 146 | Field, 147 | SmartContract, 148 | state, 149 | State, 150 | method, 151 | DeployArgs, 152 | Permissions, 153 | PublicKey, 154 | Signature, 155 | PrivateKey, 156 | } from 'snarkyjs'; 157 | 158 | // The public key of our trusted data provider 159 | const ORACLE_PUBLIC_KEY = 160 | 'B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy'; 161 | 162 | export class OracleExample extends SmartContract { 163 | // Define contract state 164 | @state(PublicKey) oraclePublicKey = State(); 165 | 166 | // Define contract events 167 | events = { 168 | verified: Field, 169 | }; 170 | 171 | deploy(args: DeployArgs) { 172 | super.deploy(args); 173 | this.setPermissions({ 174 | ...Permissions.default(), 175 | editState: Permissions.proofOrSignature(), 176 | }); 177 | } 178 | 179 | @method init(zkappKey: PrivateKey) { 180 | super.init(zkappKey); 181 | // Initialize contract state 182 | this.oraclePublicKey.set(PublicKey.fromBase58(ORACLE_PUBLIC_KEY)); 183 | // Specify that caller should include signature with tx instead of proof 184 | this.requireSignature(); 185 | } 186 | 187 | @method verify(id: Field, creditScore: Field, signature: Signature) { 188 | // Get the oracle public key from the contract state 189 | const oraclePublicKey = this.oraclePublicKey.get(); 190 | this.oraclePublicKey.assertEquals(oraclePublicKey); 191 | // Evaluate whether the signature is valid for the provided data 192 | const validSignature = signature.verify(oraclePublicKey, [id, creditScore]); 193 | // Check that the signature is valid 194 | validSignature.assertTrue(); 195 | // Check that the provided credit score is greater than 700 196 | creditScore.assertGte(Field(700)); 197 | // Emit an event containing the verified users id 198 | this.emitEvent('verified', id); 199 | } 200 | } 201 | ``` 202 | ### Create new file in *src* folder with name *OracleExample.test.ts* and write 203 | ```sh 204 | import { OracleExample } from './OracleExample'; 205 | import { 206 | isReady, 207 | shutdown, 208 | Field, 209 | Mina, 210 | PrivateKey, 211 | PublicKey, 212 | AccountUpdate, 213 | Signature, 214 | } from 'snarkyjs'; 215 | 216 | // The public key of our trusted data provider 217 | const ORACLE_PUBLIC_KEY = 218 | 'B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy'; 219 | 220 | let proofsEnabled = false; 221 | function createLocalBlockchain() { 222 | const Local = Mina.LocalBlockchain({ proofsEnabled }); 223 | Mina.setActiveInstance(Local); 224 | return Local.testAccounts[0].privateKey; 225 | } 226 | 227 | async function localDeploy( 228 | zkAppInstance: OracleExample, 229 | zkAppPrivatekey: PrivateKey, 230 | deployerAccount: PrivateKey 231 | ) { 232 | const txn = await Mina.transaction(deployerAccount, () => { 233 | AccountUpdate.fundNewAccount(deployerAccount); 234 | zkAppInstance.deploy({ zkappKey: zkAppPrivatekey }); 235 | zkAppInstance.init(zkAppPrivatekey); 236 | }); 237 | await txn.prove(); 238 | txn.sign([zkAppPrivatekey]); 239 | await txn.send(); 240 | } 241 | 242 | describe('CreditScoreOracle', () => { 243 | let deployerAccount: PrivateKey, 244 | zkAppAddress: PublicKey, 245 | zkAppPrivateKey: PrivateKey; 246 | 247 | beforeAll(async () => { 248 | await isReady; 249 | if (proofsEnabled) OracleExample.compile(); 250 | }); 251 | 252 | beforeEach(async () => { 253 | deployerAccount = createLocalBlockchain(); 254 | zkAppPrivateKey = PrivateKey.random(); 255 | zkAppAddress = zkAppPrivateKey.toPublicKey(); 256 | }); 257 | 258 | afterAll(async () => { 259 | // `shutdown()` internally calls `process.exit()` which will exit the running Jest process early. 260 | // Specifying a timeout of 0 is a workaround to defer `shutdown()` until Jest is done running all tests. 261 | // This should be fixed with https://github.com/MinaProtocol/mina/issues/10943 262 | setTimeout(shutdown, 0); 263 | }); 264 | 265 | it('generates and deploys the `CreditScoreOracle` smart contract', async () => { 266 | const zkAppInstance = new OracleExample(zkAppAddress); 267 | await localDeploy(zkAppInstance, zkAppPrivateKey, deployerAccount); 268 | const oraclePublicKey = zkAppInstance.oraclePublicKey.get(); 269 | expect(oraclePublicKey).toEqual(PublicKey.fromBase58(ORACLE_PUBLIC_KEY)); 270 | }); 271 | 272 | describe('actual API requests', () => { 273 | it('emits an `id` event containing the users id if their credit score is above 700 and the provided signature is valid', async () => { 274 | const zkAppInstance = new OracleExample(zkAppAddress); 275 | await localDeploy(zkAppInstance, zkAppPrivateKey, deployerAccount); 276 | 277 | const response = await fetch( 278 | 'https://mina-credit-score-signer-pe3eh.ondigitalocean.app/user/1' 279 | ); 280 | const data = await response.json(); 281 | 282 | const id = Field(data.data.id); 283 | const creditScore = Field(data.data.creditScore); 284 | const signature = Signature.fromJSON(data.signature); 285 | 286 | const txn = await Mina.transaction(deployerAccount, () => { 287 | zkAppInstance.verify( 288 | id, 289 | creditScore, 290 | signature ?? fail('something is wrong with the signature') 291 | ); 292 | }); 293 | await txn.prove(); 294 | await txn.send(); 295 | 296 | const events = await zkAppInstance.fetchEvents(); 297 | const verifiedEventValue = events[0].event.toFields(null)[0]; 298 | expect(verifiedEventValue).toEqual(id); 299 | }); 300 | 301 | it('throws an error if the credit score is below 700 even if the provided signature is valid', async () => { 302 | const zkAppInstance = new OracleExample(zkAppAddress); 303 | await localDeploy(zkAppInstance, zkAppPrivateKey, deployerAccount); 304 | 305 | const response = await fetch( 306 | 'https://mina-credit-score-signer-pe3eh.ondigitalocean.app/user/2' 307 | ); 308 | const data = await response.json(); 309 | 310 | const id = Field(data.data.id); 311 | const creditScore = Field(data.data.creditScore); 312 | const signature = Signature.fromJSON(data.signature); 313 | 314 | expect(async () => { 315 | await Mina.transaction(deployerAccount, () => { 316 | zkAppInstance.verify( 317 | id, 318 | creditScore, 319 | signature ?? fail('something is wrong with the signature') 320 | ); 321 | }); 322 | }).rejects; 323 | }); 324 | }); 325 | 326 | describe('hardcoded values', () => { 327 | it('emits an `id` event containing the users id if their credit score is above 700 and the provided signature is valid', async () => { 328 | const zkAppInstance = new OracleExample(zkAppAddress); 329 | await localDeploy(zkAppInstance, zkAppPrivateKey, deployerAccount); 330 | 331 | const id = Field(1); 332 | const creditScore = Field(787); 333 | const signature = Signature.fromJSON({ 334 | r: '13209474117923890467777795933147746532722569254037337512677934549675287266861', 335 | s: '12079365427851031707052269572324263778234360478121821973603368912000793139475', 336 | }); 337 | 338 | const txn = await Mina.transaction(deployerAccount, () => { 339 | zkAppInstance.verify( 340 | id, 341 | creditScore, 342 | signature ?? fail('something is wrong with the signature') 343 | ); 344 | }); 345 | await txn.prove(); 346 | await txn.send(); 347 | 348 | const events = await zkAppInstance.fetchEvents(); 349 | const verifiedEventValue = events[0].event.toFields(null)[0]; 350 | expect(verifiedEventValue).toEqual(id); 351 | }); 352 | 353 | it('throws an error if the credit score is below 700 even if the provided signature is valid', async () => { 354 | const zkAppInstance = new OracleExample(zkAppAddress); 355 | await localDeploy(zkAppInstance, zkAppPrivateKey, deployerAccount); 356 | 357 | const id = Field(2); 358 | const creditScore = Field(536); 359 | const signature = Signature.fromJSON({ 360 | r: '25163915754510418213153704426580201164374923273432613331381672085201550827220', 361 | s: '20455871399885835832436646442230538178588318835839502912889034210314761124870', 362 | }); 363 | 364 | expect(async () => { 365 | await Mina.transaction(deployerAccount, () => { 366 | zkAppInstance.verify( 367 | id, 368 | creditScore, 369 | signature ?? fail('something is wrong with the signature') 370 | ); 371 | }); 372 | }).rejects; 373 | }); 374 | 375 | it('throws an error if the credit score is above 700 and the provided signature is invalid', async () => { 376 | const zkAppInstance = new OracleExample(zkAppAddress); 377 | await localDeploy(zkAppInstance, zkAppPrivateKey, deployerAccount); 378 | 379 | const id = Field(1); 380 | const creditScore = Field(787); 381 | const signature = Signature.fromJSON({ 382 | r: '26545513748775911233424851469484096799413741017006352456100547880447752952428', 383 | s: '7381406986124079327199694038222605261248869991738054485116460354242251864564', 384 | }); 385 | 386 | expect(async () => { 387 | await Mina.transaction(deployerAccount, () => { 388 | zkAppInstance.verify( 389 | id, 390 | creditScore, 391 | signature ?? fail('something is wrong with the signature') 392 | ); 393 | }); 394 | }).rejects; 395 | }); 396 | }); 397 | }); 398 | ``` 399 | ## Run Build 400 | ```sh 401 | npm run build 402 | ``` 403 | ## Update Browserlist 404 | ```sh 405 | npx browserslist@latest --update-db 406 | ``` 407 | ##Link with Repository Github 408 | Make new repository on Github and run 409 | ```sh 410 | git remote add origin ** 411 | git push -u origin main 412 | ``` 413 | ## Run The Test 414 | Run The Test with Command 415 | ```sh 416 | npm run test 417 | ``` 418 | ## 419 | ## OUTPUT 420 | You might see something like this in output, thet it means you done! 421 | 422 | ![image](https://user-images.githubusercontent.com/82062683/206862707-65791167-a718-4e42-962c-883994db4df9.png) 423 | 424 | ## License 425 | 426 | [Apache-2.0](LICENSE) 427 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }]], 3 | }; 4 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "networks": {} 4 | } 5 | -------------------------------------------------------------------------------- /jest-resolver.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (request, options) => { 2 | return options.defaultResolver(request, { 3 | ...options, 4 | packageFilter: (pkg) => { 5 | // When importing snarkyjs, we specify the Node ESM import as Jest by default imports the web version 6 | if (pkg.name === 'snarkyjs') { 7 | return { 8 | ...pkg, 9 | main: pkg.exports.node.import, 10 | }; 11 | } 12 | if (pkg.name === 'node-fetch') { 13 | return { ...pkg, main: pkg.main }; 14 | } 15 | return { 16 | ...pkg, 17 | main: pkg.module || pkg.main, 18 | }; 19 | }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ 2 | export default { 3 | verbose: true, 4 | preset: 'ts-jest/presets/default-esm', 5 | testEnvironment: 'node', 6 | globals: { 7 | 'ts-jest': { 8 | useESM: true, 9 | }, 10 | }, 11 | transform: { 12 | '^.+\\.(t)s$': 'ts-jest', 13 | '^.+\\.(j)s$': 'babel-jest', 14 | }, 15 | resolver: '/jest-resolver.cjs', 16 | transformIgnorePatterns: [ 17 | '/node_modules/(?!snarkyjs/node_modules/tslib)', 18 | ], 19 | modulePathIgnorePatterns: ['/build/'], 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oracle", 3 | "version": "0.1.0", 4 | "description": "", 5 | "author": "", 6 | "license": "Apache-2.0", 7 | "keywords": [ 8 | "mina-zkapp", 9 | "mina-zk-app", 10 | "mina-dapp", 11 | "zkapp" 12 | ], 13 | "type": "module", 14 | "main": "build/src/index.js", 15 | "types": "build/src/index.d.ts", 16 | "scripts": { 17 | "build": "tsc -p tsconfig.json", 18 | "buildw": "tsc -p tsconfig.json --watch", 19 | "coverage": "node --experimental-vm-modules --experimental-wasm-modules --experimental-wasm-threads node_modules/jest/bin/jest.js --coverage", 20 | "format": "prettier --write --ignore-unknown **/*", 21 | "prepare": "husky install", 22 | "test": "node --experimental-vm-modules --experimental-wasm-modules --experimental-wasm-threads node_modules/jest/bin/jest.js", 23 | "testw": "node --experimental-vm-modules --experimental-wasm-modules --experimental-wasm-threads node_modules/jest/bin/jest.js --watch", 24 | "lint": "npx eslint src/* --fix" 25 | }, 26 | "lint-staged": { 27 | "**/*": [ 28 | "eslint src/* --fix", 29 | "prettier --write --ignore-unknown" 30 | ] 31 | }, 32 | "devDependencies": { 33 | "@babel/preset-env": "^7.16.4", 34 | "@babel/preset-typescript": "^7.16.0", 35 | "@types/jest": "^27.0.3", 36 | "@typescript-eslint/eslint-plugin": "^5.5.0", 37 | "@typescript-eslint/parser": "^5.5.0", 38 | "eslint": "^8.7.0", 39 | "eslint-plugin-snarkyjs": "^0.1.0", 40 | "husky": "^7.0.1", 41 | "jest": "^27.3.1", 42 | "lint-staged": "^11.0.1", 43 | "prettier": "^2.3.2", 44 | "ts-jest": "^27.0.7", 45 | "typescript": "^4.7.2" 46 | }, 47 | "peerDependencies": { 48 | "snarkyjs": "^0.7.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Add.test.ts: -------------------------------------------------------------------------------- 1 | import { Add } from './Add'; 2 | import { 3 | isReady, 4 | shutdown, 5 | Field, 6 | Mina, 7 | PrivateKey, 8 | PublicKey, 9 | AccountUpdate, 10 | } from 'snarkyjs'; 11 | 12 | /* 13 | * This file specifies how to test the `Add` example smart contract. It is safe to delete this file and replace 14 | * with your own tests. 15 | * 16 | * See https://docs.minaprotocol.com/zkapps for more info. 17 | */ 18 | 19 | let proofsEnabled = false; 20 | 21 | describe('Add', () => { 22 | let deployerAccount: PrivateKey, 23 | zkAppAddress: PublicKey, 24 | zkAppPrivateKey: PrivateKey, 25 | zkApp: Add; 26 | 27 | beforeAll(async () => { 28 | await isReady; 29 | if (proofsEnabled) Add.compile(); 30 | }); 31 | 32 | beforeEach(() => { 33 | const Local = Mina.LocalBlockchain({ proofsEnabled }); 34 | Mina.setActiveInstance(Local); 35 | deployerAccount = Local.testAccounts[0].privateKey; 36 | zkAppPrivateKey = PrivateKey.random(); 37 | zkAppAddress = zkAppPrivateKey.toPublicKey(); 38 | zkApp = new Add(zkAppAddress); 39 | }); 40 | 41 | afterAll(() => { 42 | // `shutdown()` internally calls `process.exit()` which will exit the running Jest process early. 43 | // Specifying a timeout of 0 is a workaround to defer `shutdown()` until Jest is done running all tests. 44 | // This should be fixed with https://github.com/MinaProtocol/mina/issues/10943 45 | setTimeout(shutdown, 0); 46 | }); 47 | 48 | async function localDeploy() { 49 | const txn = await Mina.transaction(deployerAccount, () => { 50 | AccountUpdate.fundNewAccount(deployerAccount); 51 | zkApp.deploy(); 52 | }); 53 | await txn.prove(); 54 | // this tx needs .sign(), because `deploy()` adds an account update that requires signature authorization 55 | await txn.sign([zkAppPrivateKey]).send(); 56 | } 57 | 58 | it('generates and deploys the `Add` smart contract', async () => { 59 | await localDeploy(); 60 | const num = zkApp.num.get(); 61 | expect(num).toEqual(Field(1)); 62 | }); 63 | 64 | it('correctly updates the num state on the `Add` smart contract', async () => { 65 | await localDeploy(); 66 | 67 | // update transaction 68 | const txn = await Mina.transaction(deployerAccount, () => { 69 | zkApp.update(); 70 | }); 71 | await txn.prove(); 72 | await txn.send(); 73 | 74 | const updatedNum = zkApp.num.get(); 75 | expect(updatedNum).toEqual(Field(3)); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /src/Add.ts: -------------------------------------------------------------------------------- 1 | import { Field, SmartContract, state, State, method } from 'snarkyjs'; 2 | 3 | /** 4 | * Basic Example 5 | * See https://docs.minaprotocol.com/zkapps for more info. 6 | * 7 | * The Add contract initializes the state variable 'num' to be a Field(1) value by default when deployed. 8 | * When the 'update' method is called, the Add contract adds Field(2) to its 'num' contract state. 9 | * 10 | * This file is safe to delete and replace with your own contract. 11 | */ 12 | export class Add extends SmartContract { 13 | @state(Field) num = State(); 14 | 15 | init() { 16 | super.init(); 17 | this.num.set(Field(1)); 18 | } 19 | 20 | @method update() { 21 | const currentState = this.num.get(); 22 | this.num.assertEquals(currentState); // precondition that links this.num.get() to the actual on-chain state 23 | const newState = currentState.add(2); 24 | this.num.set(newState); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Add } from './Add.js'; 2 | 3 | export { Add }; 4 | -------------------------------------------------------------------------------- /src/interact.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This script can be used to interact with the Add contract, after deploying it. 3 | * 4 | * We call the update() method on the contract, create a proof and send it to the chain. 5 | * The endpoint that we interact with is read from your config.json. 6 | * 7 | * This simulates a user interacting with the zkApp from a browser, except that here, sending the transaction happens 8 | * from the script and we're using your pre-funded zkApp account to pay the transaction fee. In a real web app, the user's wallet 9 | * would send the transaction and pay the fee. 10 | * 11 | * To run locally: 12 | * Build the project: `$ npm run build` 13 | * Run with node: `$ node build/src/interact.js `. 14 | */ 15 | import { Mina, PrivateKey, shutdown } from 'snarkyjs'; 16 | import fs from 'fs/promises'; 17 | import { Add } from './Add.js'; 18 | 19 | // check command line arg 20 | let network = process.argv[2]; 21 | if (!network) 22 | throw Error(`Missing argument. 23 | 24 | Usage: 25 | node build/src/interact.js 26 | 27 | Example: 28 | node build/src/interact.js berkeley 29 | `); 30 | Error.stackTraceLimit = 1000; 31 | 32 | // parse config and private key from file 33 | type Config = { networks: Record }; 34 | let configJson: Config = JSON.parse(await fs.readFile('config.json', 'utf8')); 35 | let config = configJson.networks[network]; 36 | let key: { privateKey: string } = JSON.parse( 37 | await fs.readFile(config.keyPath, 'utf8') 38 | ); 39 | let zkAppKey = PrivateKey.fromBase58(key.privateKey); 40 | 41 | // set up Mina instance and contract we interact with 42 | const Network = Mina.Network(config.url); 43 | Mina.setActiveInstance(Network); 44 | let zkAppAddress = zkAppKey.toPublicKey(); 45 | let zkApp = new Add(zkAppAddress); 46 | 47 | // compile the contract to create prover keys 48 | console.log('compile the contract...'); 49 | await Add.compile(); 50 | 51 | // call update() and send transaction 52 | console.log('build transaction and create proof...'); 53 | let tx = await Mina.transaction({ feePayerKey: zkAppKey, fee: 0.1e9 }, () => { 54 | zkApp.update(); 55 | }); 56 | await tx.prove(); 57 | console.log('send transaction...'); 58 | let sentTx = await tx.send(); 59 | 60 | if (sentTx.hash() !== undefined) { 61 | console.log(` 62 | Success! Update transaction sent. 63 | 64 | Your smart contract state will be updated 65 | as soon as the transaction is included in a block: 66 | https://berkeley.minaexplorer.com/transaction/${sentTx.hash()} 67 | `); 68 | } 69 | shutdown(); 70 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2022", 5 | "lib": ["dom", "esnext"], 6 | "outDir": "./build", 7 | "rootDir": ".", 8 | "strict": true, 9 | "strictPropertyInitialization": false, // to enable generic constructors, e.g. on CircuitValue 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "esModuleInterop": true, 13 | "moduleResolution": "node", 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true, 16 | "allowJs": true, 17 | "declaration": true, 18 | "sourceMap": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "allowSyntheticDefaultImports": true 21 | }, 22 | "include": ["./src"] 23 | } 24 | --------------------------------------------------------------------------------