@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 ADDED
@@ -0,0 +1,378 @@
1
+ # DPA Contracts
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Solidity](https://img.shields.io/badge/Solidity-^0.8.20-blue.svg)](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