diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a71fe9d --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +BERKELEY_MINA= +BERKELEY_ARCHIVE= +BERKELEY_COMMITTEE_ADDRESS= +BERKELEY_DKG_ADDRESS= +BERKELEY_ROUND_1_ADDRESS= +BERKELEY_ROUND_2_ADDRESS= +BERKELEY_REQUEST_ADDRESS= +BERKELEY_RESPONSE_ADDRESS= +BERKELEY_PROJECT_ADDRESS= +BERKELEY_CAMPAIGN_ADDRESS= +BERKELEY_PARTICIPATION_ADDRESS= +BERKELEY_FUNDING_ADDRESS= +BERKELEY_TREASURY_ADDRESS= \ No newline at end of file diff --git a/config.json b/config.json index e856985..0c2fac8 100644 --- a/config.json +++ b/config.json @@ -84,6 +84,34 @@ "feepayerKeyPath": "/home/huyminh/.cache/zkapp-cli/keys/myaccount.json", "feepayerAlias": "myaccount", "fee": "0.1" + }, + "acc4": { + "url": "https://proxy.berkeley.minaexplorer.com/graphql", + "keyPath": "keys/acc4.json", + "feepayerKeyPath": "/home/huyminh/.cache/zkapp-cli/keys/myaccount.json", + "feepayerAlias": "myaccount", + "fee": "0.1" + }, + "acc3": { + "url": "https://proxy.berkeley.minaexplorer.com/graphql", + "keyPath": "keys/acc3.json", + "feepayerKeyPath": "/home/huyminh/.cache/zkapp-cli/keys/myaccount.json", + "feepayerAlias": "myaccount", + "fee": "0.1" + }, + "acc2": { + "url": "https://proxy.berkeley.minaexplorer.com/graphql", + "keyPath": "keys/acc2.json", + "feepayerKeyPath": "/home/huyminh/.cache/zkapp-cli/keys/myaccount.json", + "feepayerAlias": "myaccount", + "fee": "0.1" + }, + "acc1": { + "url": "https://proxy.berkeley.minaexplorer.com/graphql", + "keyPath": "keys/acc1.json", + "feepayerKeyPath": "/home/huyminh/.cache/zkapp-cli/keys/myaccount.json", + "feepayerAlias": "myaccount", + "fee": "0.1" } } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ae3f8a8..6bd2d0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "@auxo-dev/platform", - "version": "0.1.3", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@auxo-dev/platform", - "version": "0.1.3", + "version": "0.1.5", "license": "Apache-2.0", "dependencies": { "@auxo-dev/auxo-libs": "0.3.5", "@auxo-dev/dkg": "0.2.12", + "dotenv": "^16.3.1", "o1js": "0.15.1" }, "devDependencies": { @@ -4359,6 +4360,17 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/package.json b/package.json index 0923585..b31c647 100644 --- a/package.json +++ b/package.json @@ -64,8 +64,9 @@ "zkapp-cli": "^0.15.0" }, "dependencies": { - "@auxo-dev/dkg": "0.2.12", "@auxo-dev/auxo-libs": "0.3.5", + "@auxo-dev/dkg": "0.2.12", + "dotenv": "^16.3.1", "o1js": "0.15.1" } -} \ No newline at end of file +} diff --git a/src/scripts/interactions/prepare.ts b/src/scripts/interactions/prepare.ts new file mode 100644 index 0000000..cc17b2d --- /dev/null +++ b/src/scripts/interactions/prepare.ts @@ -0,0 +1,87 @@ +import 'dotenv/config.js'; +import fs from 'fs'; +import { Cache, Mina, PrivateKey, PublicKey, fetchAccount } from 'o1js'; +import { Config, JSONKey, Key } from '../helper/config.js'; +import { wait } from '../helper/deploy.js'; +import { Contract, ZkAppEnum } from '../../constants.js'; +import { + EMPTY_ADDRESS_MT, + AddressStorage, +} from '../../contracts/SharedStorage.js'; + +export async function prepare() { + // Cache folder + const cache = Cache.FileSystem('./caches'); + + // Network configuration + const network = Mina.Network({ + mina: process.env.BERKELEY_MINA as string, + archive: process.env.BERKELEY_ARCHIVE as string, + }); + Mina.setActiveInstance(network); + const FEE = 0.101 * 1e9; + + // Accounts configuration + let configJson: Config = JSON.parse(fs.readFileSync('config.json', 'utf8')); + let acc1: JSONKey = JSON.parse( + fs.readFileSync(configJson.deployAliases['acc1'].keyPath, 'utf8') + ); + let acc2: JSONKey = JSON.parse( + fs.readFileSync(configJson.deployAliases['acc2'].keyPath, 'utf8') + ); + let acc3: JSONKey = JSON.parse( + fs.readFileSync(configJson.deployAliases['acc3'].keyPath, 'utf8') + ); + let acc4: JSONKey = JSON.parse( + fs.readFileSync(configJson.deployAliases['acc4'].keyPath, 'utf8') + ); + + let feePayerKey: Key; + feePayerKey = { + privateKey: PrivateKey.fromBase58(acc1.privateKey), + publicKey: PublicKey.fromBase58(acc1.publicKey), + }; + let sender, feePayerNonce; + do { + console.log('Fetch nonce...'); + sender = await fetchAccount({ publicKey: feePayerKey.publicKey }); + feePayerNonce = Number(sender.account?.nonce); + if (!isNaN(feePayerNonce)) { + console.log('Nonce:', feePayerNonce); + break; + } + await wait(1000); // 1s + } while (true); + + let addressMerkleTree = EMPTY_ADDRESS_MT(); + + // Read contract address from config + await Promise.all( + Object.keys(Contract) + .filter((item) => isNaN(Number(item))) + .map(async (e) => { + let config = configJson.deployAliases[e.toLowerCase()]; + let keyBase58: { privateKey: string; publicKey: string } = JSON.parse( + fs.readFileSync(config.keyPath, 'utf8') + ); + let key = { + privateKey: PrivateKey.fromBase58(keyBase58.privateKey), + publicKey: PublicKey.fromBase58(keyBase58.publicKey), + }; + addressMerkleTree.setLeaf( + AddressStorage.calculateIndex(ZkAppEnum[e]).toBigInt(), + AddressStorage.calculateLeaf(key.publicKey) + ); + }) + ); + + return { + feePayer: { + key: feePayerKey, + nonce: feePayerNonce, + fee: FEE, + }, + cache, + addressMerkleTree, + }; +} diff --git a/src/scripts/interactions/project/createProject.ts b/src/scripts/interactions/project/createProject.ts new file mode 100644 index 0000000..5d7fa8a --- /dev/null +++ b/src/scripts/interactions/project/createProject.ts @@ -0,0 +1,88 @@ +import fs from 'fs'; +import { + Cache, + Field, + Mina, + PrivateKey, + Provable, + PublicKey, + Reducer, + fetchAccount, +} from 'o1js'; +import { Config, JSONKey, Key } from '../../helper/config.js'; +import { + ContractList, + compile, + wait, + proveAndSend, +} from '../../helper/deploy.js'; +import { fetchActions, fetchZkAppState } from '../../helper/deploy.js'; +import { + ProjectContract, + ProjectAction, + CreateProject, + CreateProjectInput, + ProjectProof, +} from '../../../contracts/Project.js'; +import { + MemberStorage, + InfoStorage, + MemberArray, +} from '../../../contracts/ProjectStorage.js'; +import axios from 'axios'; +import { IPFSHash } from '@auxo-dev/auxo-libs'; +import { prepare } from '../prepare.js'; + +async function main() { + const { cache, feePayer } = await prepare(); + + // Compile programs + await compile(CreateProject, cache); + await compile(ProjectContract, cache); + + const projectAddress = + 'B62qoGy5GKNRaa2P8uh4ppdRqnCVjNyHqVSsaD4JMcE6FawLRKmmN4u'; + const projectContract = new ProjectContract( + PublicKey.fromBase58(projectAddress) + ); + + // Do this and state value of contract is fetched in Mina + const rawState = (await fetchZkAppState(projectAddress)) || []; + + let arrayPublicKey = [ + 'B62qjvrida5Kr4rj7f4gDZyG77TdFMp2ntZ9uf5Xzb7iPodykUgwYqm', + 'B62qnhBkHqUeUTmYiAvvGdywce7j5PeTdU6t6mi7UAL8emD3mDPtQW2', + 'B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S', + 'B62qmtfTkHLzmvoKYcTLPeqvuVatnB6wtnXsP6jrEi6i2eUEjcxWauH', + ].map((e) => PublicKey.fromBase58(e)); + let memberArray = new MemberArray(arrayPublicKey); + + let input = new CreateProjectInput({ + members: memberArray, + ipfsHash: IPFSHash.fromString( + 'QmNQLoDczHM3HXKodoYQnRszgd4JR4ZxzEKYe534eEBCc2' + ), + payeeAccount: PublicKey.fromBase58( + 'B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S' + ), + }); + + let tx = await Mina.transaction( + { + sender: feePayer.key.publicKey, + fee: feePayer.fee, + nonce: feePayer.nonce++, + }, + () => { + projectContract.createProject(input); + } + ); + await proveAndSend(tx, feePayer.key, 'projectContract', 'createProject'); +} + +main() + .then() + .catch((err) => { + console.error(err); + process.exit(1); + }); diff --git a/src/scripts/interactions/project/rollup.ts b/src/scripts/interactions/project/rollup.ts new file mode 100644 index 0000000..8ad5a8e --- /dev/null +++ b/src/scripts/interactions/project/rollup.ts @@ -0,0 +1,163 @@ +import fs from 'fs'; +import { + Cache, + Field, + Mina, + PrivateKey, + Provable, + PublicKey, + Reducer, + fetchAccount, +} from 'o1js'; +import { Config, JSONKey, Key } from '../../helper/config.js'; +import { + ContractList, + compile, + wait, + proveAndSend, +} from '../../helper/deploy.js'; +import { fetchActions, fetchZkAppState } from '../../helper/deploy.js'; +import { + ProjectContract, + ProjectAction, + CreateProject, + CreateProjectInput, + ProjectProof, +} from '../../../contracts/Project.js'; +import { + MemberStorage, + InfoStorage, + MemberArray, + InfoStorage as ProjectInfoStorage, + AddressStorage as PayeeStorage, + EMPTY_LEVEL_2_TREE, + Level2Witness, +} from '../../../contracts/ProjectStorage.js'; +import axios from 'axios'; +import { IPFSHash } from '@auxo-dev/auxo-libs'; +import { prepare } from '../prepare.js'; + +// Da test reduce 1 action, 2 action co the sai :v +async function main() { + const { cache, feePayer } = await prepare(); + + // Compile programs + await compile(CreateProject, cache); + await compile(ProjectContract, cache); + + const projectAddress = + 'B62qoGy5GKNRaa2P8uh4ppdRqnCVjNyHqVSsaD4JMcE6FawLRKmmN4u'; + const projectContract = new ProjectContract( + PublicKey.fromBase58(projectAddress) + ); + + // Storage + let memberStorage = new MemberStorage(); + let projectInfoStorage = new ProjectInfoStorage(); + let payeeStorage = new PayeeStorage(); + + // Fetch storage trees + const projects = (await axios.get('https://api.auxo.fund/v0/projects/')).data; + + // Build storage + projects.map((project: any) => { + if (Boolean(project.active)) { + console.log(project); + let level2Tree = EMPTY_LEVEL_2_TREE(); + for (let i = 0; i < project.members.length; i++) { + level2Tree.setLeaf( + BigInt(i), + MemberArray.hash(PublicKey.fromBase58(project.members[i])) + ); + } + memberStorage.updateInternal(Field(project.projectId), level2Tree); + projectInfoStorage.updateLeaf( + projectInfoStorage.calculateLeaf(IPFSHash.fromString(project.ipfsHash)), + Field(project.projectId) + ); + payeeStorage.updateLeaf( + payeeStorage.calculateLeaf(PublicKey.fromBase58(project.payeeAccount)), + Field(project.projectId) + ); + } + }); + + // Do this and state value of contract is fetched in Mina + const rawState = (await fetchZkAppState(projectAddress)) || []; + + const fromState = projectContract.lastRolledUpActionState.get(); + const rawActions = await fetchActions(projectAddress, fromState); + + const actions: ProjectAction[] = rawActions.map((e) => { + let action: Field[] = e.actions[0].map((e) => Field(e)); + return ProjectAction.fromFields(action); + }); + + const reduceActions = actions; + + console.log('CreateProject.firstStep...'); + let proof = await CreateProject.firstStep( + projectContract.nextProjectId.get(), + projectContract.memberTreeRoot.get(), + projectContract.projectInfoTreeRoot.get(), + projectContract.payeeTreeRoot.get(), + projectContract.lastRolledUpActionState.get() + ); + + let nextProjectId = Number(projectContract.nextProjectId.get()); + for (let i = 0; i < reduceActions.length; i++) { + console.log(`${i} - CreateProject.nextStep...`); + console.log('Create projectId: ', nextProjectId + i); + proof = await CreateProject.nextStep( + proof, + reduceActions[i], + memberStorage.getLevel1Witness( + memberStorage.calculateLevel1Index(Field(nextProjectId + i)) + ), + projectInfoStorage.getLevel1Witness( + projectInfoStorage.calculateLevel1Index(Field(nextProjectId + i)) + ), + payeeStorage.getLevel1Witness( + payeeStorage.calculateLevel1Index(Field(nextProjectId + i)) + ) + ); + + let tree1 = EMPTY_LEVEL_2_TREE(); + let memberArray = reduceActions[i].members; + for (let i = 0; i < Number(memberArray.length); i++) { + tree1.setLeaf(BigInt(i), MemberArray.hash(memberArray.get(Field(i)))); + } + + // update storage: + memberStorage.updateInternal(Field(i), tree1); + projectInfoStorage.updateLeaf( + projectInfoStorage.calculateLeaf(reduceActions[i].ipfsHash), + Field(i) + ); + payeeStorage.updateLeaf( + payeeStorage.calculateLeaf(reduceActions[i].payeeAccount), + Field(i) + ); + + console.log('DONE'); + } + + let tx = await Mina.transaction( + { + sender: feePayer.key.publicKey, + fee: feePayer.fee, + nonce: feePayer.nonce++, + }, + () => { + projectContract.rollup(proof); + } + ); + await proveAndSend(tx, feePayer.key, 'ProjectContract', 'rollup'); +} + +main() + .then() + .catch((err) => { + console.error(err); + process.exit(1); + });