Compare commits
7 Commits
v7.4.0
...
6cbe82397f
| Author | SHA1 | Date | |
|---|---|---|---|
|
6cbe82397f
|
|||
|
a927d591c6
|
|||
|
0f872c8f1e
|
|||
| 484a9ffcaf | |||
|
6e9f565587
|
|||
|
f4634fe692
|
|||
|
68968e1fbd
|
@@ -0,0 +1,112 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
Guidance for AI agents working on this repository.
|
||||||
|
|
||||||
|
## Project overview
|
||||||
|
|
||||||
|
`@kredits/contracts` — Solidity smart contracts and a JavaScript (ethers v5)
|
||||||
|
wrapper for [Kosmos Kredits](https://wiki.kosmos.org/Kredits), a contribution
|
||||||
|
tracking DAO running on EVM chains (primarily RSK). Contributors earn kredits
|
||||||
|
for contributions; kredits are withdrawable as ERC20 tokens. All contracts are
|
||||||
|
upgradeable via OpenZeppelin hardhat-upgrades proxies.
|
||||||
|
|
||||||
|
## Tech stack
|
||||||
|
|
||||||
|
- Solidity ^0.8.0 (compiler 0.8.2), OpenZeppelin upgradeable contracts
|
||||||
|
- Hardhat (with @openzeppelin/hardhat-upgrades, hardhat-deploy, hardhat-waffle)
|
||||||
|
- JavaScript (CommonJS), ethers v5, ipfs-http-client
|
||||||
|
- Tests: mocha + chai + @nomicfoundation/hardhat-chai-matchers
|
||||||
|
- Lint: solhint (contracts), eslint (JS)
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
- `contracts/` — Solidity smart contracts (see "Contracts" below)
|
||||||
|
- `lib/` — published JavaScript API wrapper (entry: `lib/kredits.js`)
|
||||||
|
- `lib/contracts/` — one wrapper class per contract, extending `Base`/`Record`
|
||||||
|
- `lib/serializers/` — IPFS JSON serializers (contributor, contribution)
|
||||||
|
- `lib/utils/` — ipfs, pagination, validator, preflight, deprecate, format-kredits
|
||||||
|
- `lib/abis/` — committed ABI JSON (regenerated by `npm run build:json`)
|
||||||
|
- `lib/addresses.json` — chainId → deployed contract addresses
|
||||||
|
- `scripts/` — hardhat CLI helpers (create-proxy, seeds, add-/list-*, build-json,
|
||||||
|
upgrade-example, import/, export/)
|
||||||
|
- `test/contracts/` — mocha tests (Contributor.js, Contribution.js)
|
||||||
|
- `config/seeds.js` — demo seed data
|
||||||
|
- `hardhat.config.js` — networks: hardhat (1337), rinkeby, rsk (RSK testnet);
|
||||||
|
injects `hre.kredits` (an initialized `Kredits` instance)
|
||||||
|
|
||||||
|
## Contracts
|
||||||
|
|
||||||
|
All in `contracts/`, all `Initializable` (OpenZeppelin upgradeable):
|
||||||
|
|
||||||
|
- `Contributor.sol` — contributor registry (account + IPFS profile hash).
|
||||||
|
Core team = contributor IDs 1–6 (hardcoded). `withdraw()` mints tokens for
|
||||||
|
confirmed, not-yet-withdrawn kredits.
|
||||||
|
- `Contribution.sol` — contribution records (ERC721-like ownership). Veto
|
||||||
|
period ~40320 blocks (~7 days @ 15s blocks). `add()` → veto window → confirmed.
|
||||||
|
- `Reimbursement.sol` — expense reimbursements, same veto pattern.
|
||||||
|
- `Token.sol` — ERC20 "Kredits" (symbol KS, 0 decimals). `mintFor` callable only
|
||||||
|
by the Contributor contract.
|
||||||
|
|
||||||
|
Contracts are independent and reference each other by address (set via
|
||||||
|
`setContributorContract`, `setContributionContract`, `setTokenContract`).
|
||||||
|
Wiring happens in `scripts/create-proxy.js`.
|
||||||
|
|
||||||
|
## Common commands
|
||||||
|
|
||||||
|
Bootstrap a local dev environment:
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run devchain # hardhat node --network hardhat (separate terminal)
|
||||||
|
npm run bootstrap # build + deploy proxies + seeds (uses localhost)
|
||||||
|
```
|
||||||
|
|
||||||
|
Other:
|
||||||
|
```
|
||||||
|
npm run build # compile contracts + regenerate lib/abis
|
||||||
|
npm run deploy:dao # deploy upgradeable proxies (scripts/create-proxy.js)
|
||||||
|
npm run seeds # seed demo data
|
||||||
|
npm run fund # send devchain ETH to an address
|
||||||
|
npm test # hardhat test (requires devchain running)
|
||||||
|
npm run lint:contracts # solhint
|
||||||
|
npm run lint:wrapper # eslint lib/
|
||||||
|
```
|
||||||
|
|
||||||
|
Run a hardhat script: `hardhat run scripts/<name>.js --network localhost`
|
||||||
|
(or `--network rsk` for RSK testnet). Use `DEPLOY_KEY=<hex>` to override the
|
||||||
|
deploy account.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
### JavaScript (eslint: `lib/`, scripts/, test/)
|
||||||
|
- CommonJS (`require`/`module.exports`)
|
||||||
|
- 2-space indentation; semicolons required
|
||||||
|
- Trailing commas in multiline arrays/objects (not in imports/exports)
|
||||||
|
- Space before named function parens: `function foo () {}`, `async () => {}`
|
||||||
|
- Async wrappers use `.then()` chains in older code; new code may use async/await
|
||||||
|
|
||||||
|
### Solidity (solhint: default + recommended)
|
||||||
|
- `pragma solidity ^0.8.0;`
|
||||||
|
- Upgradeable pattern: `import Initializable`, use `initialize()` (and
|
||||||
|
`reinitializer(n)` for upgrades) — never use constructors for state init
|
||||||
|
- Interfaces declared inline (e.g. `ContributorInterface`) for cross-contract calls
|
||||||
|
- Core-only / deployer-only / contributors-only modifiers guard privileged calls
|
||||||
|
|
||||||
|
### Upgradeable contract changes
|
||||||
|
- Do not use constructors to initialize state (use `initialize`/`reinitializer`)
|
||||||
|
- Do not change the order/types of existing storage variables (storage layout!)
|
||||||
|
- New storage vars go at the end of the contract
|
||||||
|
- See `scripts/upgrade-example.js` and `scripts/create-proxy.js`
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Mocha + hardhat-waffle + chai-matchers. `test/contracts/` only covers
|
||||||
|
Contributor and Contribution today. When changing contract code, add/adapt
|
||||||
|
tests there. Requires a running devchain (`npm run devchain`) for `npm test`.
|
||||||
|
|
||||||
|
## Networks & deployment
|
||||||
|
|
||||||
|
- `hardhat` (chainId 1337) — local devchain
|
||||||
|
- `rsk` (RSK testnet, https://rsk-testnet.kosmos.org) — primary test deployment
|
||||||
|
- `rinkeby` — legacy, configured but deprecated
|
||||||
|
- Deployed addresses are tracked per chainId in `lib/addresses.json` and must be
|
||||||
|
committed when deployment changes (see `npm run version` script)
|
||||||
+4
-4
@@ -70,16 +70,16 @@ const contractCalls = [
|
|||||||
}, { gasLimit: 350000 }]],
|
}, { gasLimit: 350000 }]],
|
||||||
|
|
||||||
['Reimbursement', 'add', [{ amount: 346800, recipientId: 2, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
['Reimbursement', 'add', [{ amount: 346800, recipientId: 2, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||||
{ title: 'Domain kosmos.social', description: 'Yearly registration fee for domain kosmos.social', amount: 69.00, currency: 'EUR', date: '2020-04-30' },
|
{ title: 'Domain kosmos.social', description: 'Yearly registration fee for domain kosmos.social', amount: 69.00, currency: 'EUR', amountSats: 69216, date: '2020-04-30' },
|
||||||
], confirmedAtBlock: 1 }, { gasLimit: 300000 }]],
|
], confirmedAtBlock: 1 }, { gasLimit: 300000 }]],
|
||||||
|
|
||||||
['Reimbursement', 'add', [{ amount: 1116000, recipientId: 1, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
['Reimbursement', 'add', [{ amount: 1116000, recipientId: 1, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||||
{ title: 'Server rent', description: 'Dedicated server: andromeda.kosmos.org, April 2020', amount: 61, currency: 'EUR', date: '2020-05-28' },
|
{ title: 'Server rent', description: 'Dedicated server: andromeda.kosmos.org, April 2020', amount: 61, currency: 'EUR', amountSats: 61191, date: '2020-05-28' },
|
||||||
{ title: 'Server rent', description: 'Dedicated server: centaurus.kosmos.org, April 2020', amount: 32, currency: 'EUR', date: '2020-05-28' },
|
{ title: 'Server rent', description: 'Dedicated server: centaurus.kosmos.org, April 2020', amount: 32, currency: 'EUR', amountSats: 32201, date: '2020-05-28' },
|
||||||
], confirmedAtBlock: 1 }, { gasLimit: 300000 }]],
|
], confirmedAtBlock: 1 }, { gasLimit: 300000 }]],
|
||||||
|
|
||||||
['Reimbursement', 'add', [{ amount: 166800, recipientId: 2, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
['Reimbursement', 'add', [{ amount: 166800, recipientId: 2, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||||
{ title: 'Domain kosmos.chat', description: 'Yearly registration fee for domain kosmos.chat', amount: 13.90, currency: 'EUR', date: '2020-05-30' },
|
{ title: 'Domain kosmos.chat', description: 'Yearly registration fee for domain kosmos.chat', amount: 13.90, currency: 'EUR', amountSats: 13944, date: '2020-05-30' },
|
||||||
]}, { gasLimit: 300000 }]],
|
]}, { gasLimit: 300000 }]],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Contributor extends Record {
|
|||||||
|
|
||||||
const jsonStr = contributor.serialize();
|
const jsonStr = contributor.serialize();
|
||||||
|
|
||||||
|
// console.log('Adding IPFS doc for', contributorAttr.account);
|
||||||
return this.ipfs
|
return this.ipfs
|
||||||
.add(jsonStr)
|
.add(jsonStr)
|
||||||
.then((ipfsHashAttr) => {
|
.then((ipfsHashAttr) => {
|
||||||
@@ -62,7 +63,11 @@ class Contributor extends Record {
|
|||||||
ipfsHashAttr.hashSize,
|
ipfsHashAttr.hashSize,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// console.log('Adding onchain record for', contributorAttr.account);
|
||||||
return this.contract.addContributor(...contributor, callOptions);
|
return this.contract.addContributor(...contributor, callOptions);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('Failed to add IPFS document:', err.message);
|
||||||
|
throw(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class ExpenseSerializer {
|
|||||||
description,
|
description,
|
||||||
currency,
|
currency,
|
||||||
amount,
|
amount,
|
||||||
|
amountSats,
|
||||||
date,
|
date,
|
||||||
url,
|
url,
|
||||||
tags,
|
tags,
|
||||||
@@ -42,6 +43,7 @@ class ExpenseSerializer {
|
|||||||
description,
|
description,
|
||||||
currency,
|
currency,
|
||||||
amount,
|
amount,
|
||||||
|
amountSats,
|
||||||
date,
|
date,
|
||||||
'tags': tags || [],
|
'tags': tags || [],
|
||||||
'details': details || {},
|
'details': details || {},
|
||||||
@@ -76,6 +78,7 @@ class ExpenseSerializer {
|
|||||||
description,
|
description,
|
||||||
currency,
|
currency,
|
||||||
amount,
|
amount,
|
||||||
|
amountSats,
|
||||||
date,
|
date,
|
||||||
url,
|
url,
|
||||||
tags,
|
tags,
|
||||||
@@ -87,6 +90,7 @@ class ExpenseSerializer {
|
|||||||
description,
|
description,
|
||||||
currency,
|
currency,
|
||||||
amount,
|
amount,
|
||||||
|
amountSats,
|
||||||
date,
|
date,
|
||||||
url,
|
url,
|
||||||
tags,
|
tags,
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ class IPFS {
|
|||||||
}
|
}
|
||||||
this._config = config;
|
this._config = config;
|
||||||
this._ipfsAPI = ipfsClient.create(config);
|
this._ipfsAPI = ipfsClient.create(config);
|
||||||
|
|
||||||
|
this._ipfsAPI.id().then(res => {
|
||||||
|
console.debug('IPFS ID:', res.id);
|
||||||
|
}).catch(e => {
|
||||||
|
console.debug('IPFS config:', config);
|
||||||
|
console.warn('Failed to initialize IPFS:', e.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async catAndMerge (contractData, deserialize) {
|
async catAndMerge (contractData, deserialize) {
|
||||||
|
|||||||
Generated
+9
-9
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "@kredits/contracts",
|
"name": "@kredits/contracts",
|
||||||
"version": "7.4.0",
|
"version": "7.5.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@kredits/contracts",
|
"name": "@kredits/contracts",
|
||||||
"version": "7.4.0",
|
"version": "7.5.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kosmos/schemas": "^3.1.0",
|
"@kosmos/schemas": "^3.2.0",
|
||||||
"ethers": "^5.4.7",
|
"ethers": "^5.4.7",
|
||||||
"ipfs-http-client": "^56.0.3",
|
"ipfs-http-client": "^56.0.3",
|
||||||
"multihashes": "^4.0.3",
|
"multihashes": "^4.0.3",
|
||||||
@@ -1022,9 +1022,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@kosmos/schemas": {
|
"node_modules/@kosmos/schemas": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@kosmos/schemas/-/schemas-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@kosmos/schemas/-/schemas-3.2.0.tgz",
|
||||||
"integrity": "sha512-yOTK5WiXFDNAitPByMabE365aEEzFHgSUSgAssbJWt7BZ80HQSVu8XWrQiTbFbCkoIBmXwPP/RoxgXJQVgZTFQ=="
|
"integrity": "sha512-GyWXmWcATcakmEdPOpafdckBgWFqcpSh+tB8nm11VGgtgA94pfoFhKZ3t88WELItKfuP/GFHgMiZmdMB3bSkdQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@metamask/eth-sig-util": {
|
"node_modules/@metamask/eth-sig-util": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@@ -20786,9 +20786,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@kosmos/schemas": {
|
"@kosmos/schemas": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@kosmos/schemas/-/schemas-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@kosmos/schemas/-/schemas-3.2.0.tgz",
|
||||||
"integrity": "sha512-yOTK5WiXFDNAitPByMabE365aEEzFHgSUSgAssbJWt7BZ80HQSVu8XWrQiTbFbCkoIBmXwPP/RoxgXJQVgZTFQ=="
|
"integrity": "sha512-GyWXmWcATcakmEdPOpafdckBgWFqcpSh+tB8nm11VGgtgA94pfoFhKZ3t88WELItKfuP/GFHgMiZmdMB3bSkdQ=="
|
||||||
},
|
},
|
||||||
"@metamask/eth-sig-util": {
|
"@metamask/eth-sig-util": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kredits/contracts",
|
"name": "@kredits/contracts",
|
||||||
"version": "7.4.0",
|
"version": "7.5.0",
|
||||||
"description": "Smart contracts and JavaScript API for Kredits",
|
"description": "Smart contracts and JavaScript API for Kredits",
|
||||||
"main": "./lib/kredits.js",
|
"main": "./lib/kredits.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"yargs": "^15.0.0"
|
"yargs": "^15.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kosmos/schemas": "^3.1.0",
|
"@kosmos/schemas": "^3.2.0",
|
||||||
"ethers": "^5.4.7",
|
"ethers": "^5.4.7",
|
||||||
"ipfs-http-client": "^56.0.3",
|
"ipfs-http-client": "^56.0.3",
|
||||||
"multihashes": "^4.0.3",
|
"multihashes": "^4.0.3",
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ async function main() {
|
|||||||
contractWrapper.contract[method];
|
contractWrapper.contract[method];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// console.log('trying', func);
|
// console.log('trying', contractName, method, args);
|
||||||
const result = await func.apply(contractWrapper, args);
|
const result = await func.apply(contractWrapper, args);
|
||||||
// console.log('result:', result);
|
// console.log('result:', result);
|
||||||
await result.wait();
|
await result.wait();
|
||||||
|
|||||||
Reference in New Issue
Block a user