@dpa-oss/dpa 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +378 -0
- package/contracts/DPA.sol +740 -0
- package/contracts/examples/AssetDPA.sol +81 -0
- package/contracts/shared/Errors.sol +50 -0
- package/contracts/shared/IDPA.sol +112 -0
- package/contracts/shared/Types.sol +18 -0
- package/hardhat.config.ts +23 -0
- package/package.json +24 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# DPA Contracts
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://docs.soliditylang.org/)
|
|
5
|
+
|
|
6
|
+
> **Digital Public Asset (DPA)** – An abstract ERC721A-based smart contract framework for creating immutable, revisable, and composable digital public assets on the blockchain.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
DPA (Digital Public Asset) is an abstract smart contract that extends [ERC721A](https://www.erc721a.org/) to provide a secure and gas-efficient foundation for creating tokenized public records with built-in revision tracking and cross-contract composition.
|
|
13
|
+
|
|
14
|
+
### Key Features
|
|
15
|
+
|
|
16
|
+
- 🔒 **Orchestrator-Controlled Minting** – Only authorized orchestrators can mint/revise tokens
|
|
17
|
+
- 📝 **Protocol-Enforced Revision Tracking** – Immutable linked-list structure for full traceability
|
|
18
|
+
- 🔗 **Cross-Contract Composition** – Link DPA contracts with named, unique references
|
|
19
|
+
- ⚡ **Gas-Efficient Batch Minting** – ERC721A-powered batch operations
|
|
20
|
+
- 🛡️ **Security Hardened** – ReentrancyGuard, Pausable, burn prevention, ERC-165 interface detection
|
|
21
|
+
- 📦 **Generic Content Storage** – Store arbitrary bytes, decoded by implementers
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Dependencies
|
|
32
|
+
|
|
33
|
+
- [@openzeppelin/contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) `^5.0.0`
|
|
34
|
+
- [erc721a](https://github.com/chiru-labs/ERC721A) `^4.3.0`
|
|
35
|
+
- [hardhat](https://hardhat.org/) `^2.22.0`
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Compile Contracts
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run compile
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Run Tests
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run test
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Clean Build Artifacts
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run clean
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
### Extending DPA
|
|
64
|
+
|
|
65
|
+
DPA is an **abstract contract** – you must create a concrete implementation:
|
|
66
|
+
|
|
67
|
+
```solidity
|
|
68
|
+
// SPDX-License-Identifier: MIT
|
|
69
|
+
pragma solidity ^0.8.20;
|
|
70
|
+
|
|
71
|
+
import "./DPA.sol";
|
|
72
|
+
|
|
73
|
+
contract MyAssetDPA is DPA {
|
|
74
|
+
struct MyContent {
|
|
75
|
+
uint256 value;
|
|
76
|
+
string description;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
constructor(
|
|
80
|
+
address orchestrator_
|
|
81
|
+
) DPA("My Asset DPA", "MY-DPA", orchestrator_) {}
|
|
82
|
+
|
|
83
|
+
function _validateContent(bytes calldata content) internal pure override {
|
|
84
|
+
// Validate that content can be decoded
|
|
85
|
+
abi.decode(content, (MyContent));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getMyContent(uint256 tokenId) external view returns (MyContent memory) {
|
|
89
|
+
bytes memory raw = this.tokenContent(tokenId);
|
|
90
|
+
return abi.decode(raw, (MyContent));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Minting Tokens
|
|
96
|
+
|
|
97
|
+
```solidity
|
|
98
|
+
// Single mint
|
|
99
|
+
uint256 tokenId = dpa.mint(
|
|
100
|
+
recipientAddress,
|
|
101
|
+
"ipfs://metadata-uri",
|
|
102
|
+
encodedContent
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Batch mint
|
|
106
|
+
uint256 startTokenId = dpa.batchMint(
|
|
107
|
+
recipientAddress,
|
|
108
|
+
["ipfs://uri1", "ipfs://uri2"],
|
|
109
|
+
[encodedContent1, encodedContent2]
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Creating Revisions
|
|
114
|
+
|
|
115
|
+
```solidity
|
|
116
|
+
uint256 newTokenId = dpa.revise(
|
|
117
|
+
parentTokenId,
|
|
118
|
+
"ipfs://new-metadata-uri",
|
|
119
|
+
newEncodedContent,
|
|
120
|
+
"Reason for revision"
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Linking DPA Contracts
|
|
125
|
+
|
|
126
|
+
```solidity
|
|
127
|
+
// Link another DPA contract
|
|
128
|
+
dpa.linkDPA(tokenId, "expenditures", expenditureContractAddress);
|
|
129
|
+
|
|
130
|
+
// Query linked contract
|
|
131
|
+
address linked = dpa.getLinkedDPA(tokenId, "expenditures");
|
|
132
|
+
|
|
133
|
+
// Unlink
|
|
134
|
+
dpa.unlinkDPA(tokenId, "expenditures");
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Architecture
|
|
140
|
+
|
|
141
|
+
### Contract Structure
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
contracts/
|
|
145
|
+
├── DPA.sol # Abstract base contract
|
|
146
|
+
├── shared/
|
|
147
|
+
│ ├── IDPA.sol # Interface definition (extends IERC165)
|
|
148
|
+
│ ├── Types.sol # Shared data structures
|
|
149
|
+
│ └── Errors.sol # Custom error definitions
|
|
150
|
+
└── examples/
|
|
151
|
+
└── AssetDPA.sol # Example implementation
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Inheritance Diagram
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
ERC721A
|
|
158
|
+
│
|
|
159
|
+
├── Ownable (OpenZeppelin)
|
|
160
|
+
├── Pausable (OpenZeppelin)
|
|
161
|
+
├── ReentrancyGuard (OpenZeppelin)
|
|
162
|
+
│
|
|
163
|
+
└── DPA (Abstract)
|
|
164
|
+
│
|
|
165
|
+
└── Your Implementation (e.g., AssetDPA)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Interface
|
|
171
|
+
|
|
172
|
+
### IDPA Interface
|
|
173
|
+
|
|
174
|
+
The `IDPA` interface extends `IERC165` and defines the standard functions:
|
|
175
|
+
|
|
176
|
+
| Function | Description |
|
|
177
|
+
|----------|-------------|
|
|
178
|
+
| `orchestrator()` | Returns the current orchestrator address |
|
|
179
|
+
| `setOrchestrator(address)` | Updates the orchestrator (owner only) |
|
|
180
|
+
| `pause()` / `unpause()` | Pause/unpause operations (owner only) |
|
|
181
|
+
| `mint(to, uri, content)` | Mints a single token |
|
|
182
|
+
| `mintWithOwner(to, owner_, uri, content)` | Mints with explicit owner |
|
|
183
|
+
| `batchMint(to, uris, contents)` | Batch mints tokens |
|
|
184
|
+
| `revise(parentTokenId, uri, content, reason)` | Creates a revision |
|
|
185
|
+
| `tokenURI(tokenId)` | Returns token metadata URI |
|
|
186
|
+
| `tokenContent(tokenId)` | Returns raw encoded content |
|
|
187
|
+
| `getRevisionRecord(tokenId)` | Returns revision metadata |
|
|
188
|
+
| `getRevisionChain(tokenId)` | Returns full revision history |
|
|
189
|
+
| `getLatestVersion(originTokenId)` | Returns latest token in chain |
|
|
190
|
+
| `getChildToken(tokenId)` | Returns child revision |
|
|
191
|
+
| `getTotalAssets()` | Returns unique asset count |
|
|
192
|
+
| `isLatestVersion(tokenId)` | Checks if token is latest |
|
|
193
|
+
| `getOriginToken(tokenId)` | Returns origin token ID |
|
|
194
|
+
| `getVersion(tokenId)` | Returns version number |
|
|
195
|
+
|
|
196
|
+
### RevisionRecord Structure
|
|
197
|
+
|
|
198
|
+
```solidity
|
|
199
|
+
struct RevisionRecord {
|
|
200
|
+
uint256 previousTokenId; // Parent revision (0 if origin)
|
|
201
|
+
uint256 originTokenId; // Root of the revision chain
|
|
202
|
+
uint256 version; // Version number (1 for origin)
|
|
203
|
+
bytes32 reasonHash; // Keccak256 hash of revision reason
|
|
204
|
+
uint256 timestamp; // Block timestamp when created
|
|
205
|
+
address actor; // Address that created this revision
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Security
|
|
212
|
+
|
|
213
|
+
### Built-in Protections
|
|
214
|
+
|
|
215
|
+
| Feature | Description |
|
|
216
|
+
|---------|-------------|
|
|
217
|
+
| **ReentrancyGuard** | Prevents reentrancy attacks on mint/revise/link operations |
|
|
218
|
+
| **Pausable** | Owner can pause all minting and revision operations |
|
|
219
|
+
| **Burn Prevention** | Tokens cannot be burned – ensures permanent public records |
|
|
220
|
+
| **ERC-165 Interface Detection** | Validates linked contracts implement IDPA |
|
|
221
|
+
| **Zero Address Checks** | Prevents setting orchestrator or linking to zero address |
|
|
222
|
+
| **Self-Link Prevention** | Contracts cannot link to themselves |
|
|
223
|
+
| **Contract Address Validation** | Only contracts (not EOAs) can be linked |
|
|
224
|
+
| **Latest Version Enforcement** | Only latest version tokens can be revised |
|
|
225
|
+
|
|
226
|
+
### Custom Errors
|
|
227
|
+
|
|
228
|
+
```solidity
|
|
229
|
+
error NotOrchestrator(); // Caller is not the orchestrator
|
|
230
|
+
error InvalidTokenId(); // Token does not exist
|
|
231
|
+
error InvalidRevision(); // Revision chain violation
|
|
232
|
+
error BatchMintFailed(); // Batch minting error
|
|
233
|
+
error ArrayLengthMismatch(); // Arrays length mismatch
|
|
234
|
+
error ZeroAddress(); // Zero address provided
|
|
235
|
+
error NotLatestVersion(); // Token is not the latest version
|
|
236
|
+
error DuplicateLinkName(); // Link name already exists
|
|
237
|
+
error LinkNotFound(); // Link name not found
|
|
238
|
+
error EmptyLinkName(); // Link name cannot be empty
|
|
239
|
+
error NotAContract(); // Address is not a contract
|
|
240
|
+
error SelfLink(); // Cannot link to self
|
|
241
|
+
error BurnDisabled(); // Token burning is disabled
|
|
242
|
+
error NotADPAContract(); // Contract does not implement IDPA
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Events
|
|
248
|
+
|
|
249
|
+
```solidity
|
|
250
|
+
event TokenMinted(uint256 indexed tokenId, address indexed to, string uri);
|
|
251
|
+
event BatchMinted(uint256 indexed startTokenId, uint256 quantity, address indexed to);
|
|
252
|
+
event TokenRevised(uint256 indexed newTokenId, uint256 indexed parentTokenId, uint256 indexed originTokenId, string reason);
|
|
253
|
+
event OrchestratorUpdated(address indexed previousOrchestrator, address indexed newOrchestrator);
|
|
254
|
+
event DPALinked(uint256 indexed tokenId, bytes32 indexed nameHash, address indexed dpaContract, string name);
|
|
255
|
+
event DPAUnlinked(uint256 indexed tokenId, bytes32 indexed nameHash, address indexed dpaContract);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Gas Optimization
|
|
261
|
+
|
|
262
|
+
DPA leverages ERC721A for significant gas savings on batch operations:
|
|
263
|
+
|
|
264
|
+
| Operation | Gas Savings |
|
|
265
|
+
|-----------|-------------|
|
|
266
|
+
| Batch Minting | Up to 90% vs ERC721 |
|
|
267
|
+
| Optimized Storage | Packed revision records |
|
|
268
|
+
| Hash-Based Reason Storage | Full reason emitted in events, hash stored on-chain |
|
|
269
|
+
|
|
270
|
+
Generate a gas report:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
npm run test
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Gas report is saved to `gas-report.txt`.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Development
|
|
281
|
+
|
|
282
|
+
### Prerequisites
|
|
283
|
+
|
|
284
|
+
- Node.js >= 18.x
|
|
285
|
+
- npm >= 9.x
|
|
286
|
+
|
|
287
|
+
### Project Structure
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
dpa-contracts/
|
|
291
|
+
├── contracts/ # Solidity source files
|
|
292
|
+
├── test/ # Test files
|
|
293
|
+
├── artifacts/ # Compiled contracts (generated)
|
|
294
|
+
├── typechain-types/ # TypeScript bindings (generated)
|
|
295
|
+
├── hardhat.config.ts # Hardhat configuration
|
|
296
|
+
├── package.json # Project dependencies
|
|
297
|
+
└── tsconfig.json # TypeScript configuration
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Hardhat Configuration
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// hardhat.config.ts
|
|
304
|
+
const config: HardhatUserConfig = {
|
|
305
|
+
solidity: {
|
|
306
|
+
version: "0.8.20",
|
|
307
|
+
settings: {
|
|
308
|
+
optimizer: {
|
|
309
|
+
enabled: true,
|
|
310
|
+
runs: 200,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
gasReporter: {
|
|
315
|
+
enabled: true,
|
|
316
|
+
currency: "USD",
|
|
317
|
+
outputFile: "gas-report.txt",
|
|
318
|
+
noColors: true,
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Example Implementation
|
|
326
|
+
|
|
327
|
+
See [`contracts/examples/AssetDPA.sol`](contracts/examples/AssetDPA.sol) for a complete example:
|
|
328
|
+
|
|
329
|
+
```solidity
|
|
330
|
+
contract AssetDPA is DPA {
|
|
331
|
+
enum AssetType { Enum1, Enum2 }
|
|
332
|
+
|
|
333
|
+
struct AssetContent {
|
|
334
|
+
uint256 amount;
|
|
335
|
+
AssetType assetType;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
constructor(address orchestrator_)
|
|
339
|
+
DPA("Asset DPA", "ASSET-DPA", orchestrator_) {}
|
|
340
|
+
|
|
341
|
+
function _validateContent(bytes calldata content) internal pure override {
|
|
342
|
+
abi.decode(content, (AssetContent));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function getAssetContent(uint256 tokenId) external view returns (AssetContent memory) {
|
|
346
|
+
return abi.decode(this.tokenContent(tokenId), (AssetContent));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function encodeAssetContent(uint256 amount, AssetType assetType) external pure returns (bytes memory) {
|
|
350
|
+
return abi.encode(AssetContent({amount: amount, assetType: assetType}));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## License
|
|
358
|
+
|
|
359
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Contributing
|
|
364
|
+
|
|
365
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Author
|
|
370
|
+
|
|
371
|
+
**Jason Cruz**
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Related Projects
|
|
376
|
+
|
|
377
|
+
- [ERC721A](https://github.com/chiru-labs/ERC721A) – Gas-efficient ERC721 implementation
|
|
378
|
+
- [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) – Security-audited smart contract library
|