npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dpa-oss/dpa

v1.0.3

Published

DPA (Digital Public Asset) Smart Contracts

Readme

DPA Contracts

License: MIT Solidity

Digital Public Asset (DPA) – An abstract ERC721A-based smart contract framework for creating immutable, revisable, and composable digital public assets on the blockchain.


Overview

DPA (Digital Public Asset) is an abstract smart contract that extends ERC721A to provide a secure and gas-efficient foundation for creating tokenized public records with built-in revision tracking and cross-contract composition.

Key Features

  • 🔒 Orchestrator-Controlled Minting – Only authorized orchestrators can mint/revise tokens
  • 📝 Protocol-Enforced Revision Tracking – Immutable linked-list structure for full traceability
  • 🔗 Cross-Contract Composition – Link DPA contracts with named, unique references
  • Gas-Efficient Batch Minting – ERC721A-powered batch operations
  • 🛡️ Security Hardened – ReentrancyGuard, Pausable, burn prevention, ERC-165 interface detection
  • 📦 Generic Content Storage – Store arbitrary bytes, decoded by implementers

Installation

npm install

Dependencies


Quick Start

Compile Contracts

npm run compile

Run Tests

npm run test

Clean Build Artifacts

npm run clean

Usage

Extending DPA

DPA is an abstract contract – you must create a concrete implementation:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./DPA.sol";

contract MyAssetDPA is DPA {
    struct MyContent {
        uint256 value;
        string description;
    }

    constructor(
        address orchestrator_
    ) DPA("My Asset DPA", "MY-DPA", orchestrator_) {}

    function _validateContent(bytes calldata content) internal pure override {
        // Validate that content can be decoded
        abi.decode(content, (MyContent));
    }

    function getMyContent(uint256 tokenId) external view returns (MyContent memory) {
        bytes memory raw = this.tokenContent(tokenId);
        return abi.decode(raw, (MyContent));
    }
}

Minting Tokens

// Single mint
uint256 tokenId = dpa.mint(
    recipientAddress,
    "ipfs://metadata-uri",
    encodedContent
);

// Batch mint
uint256 startTokenId = dpa.batchMint(
    recipientAddress,
    ["ipfs://uri1", "ipfs://uri2"],
    [encodedContent1, encodedContent2]
);

Creating Revisions

uint256 newTokenId = dpa.revise(
    parentTokenId,
    "ipfs://new-metadata-uri",
    newEncodedContent,
    "Reason for revision"
);

Linking DPA Contracts

// Link another DPA contract
dpa.linkDPA(tokenId, "expenditures", expenditureContractAddress);

// Query linked contract
address linked = dpa.getLinkedDPA(tokenId, "expenditures");

// Unlink
dpa.unlinkDPA(tokenId, "expenditures");

Architecture

Contract Structure

contracts/
├── DPA.sol                 # Abstract base contract
├── shared/
│   ├── IDPA.sol            # Interface definition (extends IERC165)
│   ├── Types.sol           # Shared data structures
│   └── Errors.sol          # Custom error definitions
└── examples/
    └── AssetDPA.sol        # Example implementation

Inheritance Diagram

ERC721A
   │
   ├── Ownable (OpenZeppelin)
   ├── Pausable (OpenZeppelin)
   ├── ReentrancyGuard (OpenZeppelin)
   │
   └── DPA (Abstract)
          │
          └── Your Implementation (e.g., AssetDPA)

Interface

IDPA Interface

The IDPA interface extends IERC165 and defines the standard functions:

| Function | Description | |----------|-------------| | orchestrator() | Returns the current orchestrator address | | setOrchestrator(address) | Updates the orchestrator (owner only) | | pause() / unpause() | Pause/unpause operations (owner only) | | mint(to, uri, content) | Mints a single token | | mintWithOwner(to, owner_, uri, content) | Mints with explicit owner | | batchMint(to, uris, contents) | Batch mints tokens | | revise(parentTokenId, uri, content, reason) | Creates a revision | | tokenURI(tokenId) | Returns token metadata URI | | tokenContent(tokenId) | Returns raw encoded content | | getRevisionRecord(tokenId) | Returns revision metadata | | getRevisionChain(tokenId) | Returns full revision history | | getLatestVersion(originTokenId) | Returns latest token in chain | | getChildToken(tokenId) | Returns child revision | | getTotalAssets() | Returns unique asset count | | isLatestVersion(tokenId) | Checks if token is latest | | getOriginToken(tokenId) | Returns origin token ID | | getVersion(tokenId) | Returns version number |

RevisionRecord Structure

struct RevisionRecord {
    uint256 previousTokenId;  // Parent revision (0 if origin)
    uint256 originTokenId;    // Root of the revision chain
    uint256 version;          // Version number (1 for origin)
    bytes32 reasonHash;       // Keccak256 hash of revision reason
    uint256 timestamp;        // Block timestamp when created
    address actor;            // Address that created this revision
}

Security

Built-in Protections

| Feature | Description | |---------|-------------| | ReentrancyGuard | Prevents reentrancy attacks on mint/revise/link operations | | Pausable | Owner can pause all minting and revision operations | | Burn Prevention | Tokens cannot be burned – ensures permanent public records | | ERC-165 Interface Detection | Validates linked contracts implement IDPA | | Zero Address Checks | Prevents setting orchestrator or linking to zero address | | Self-Link Prevention | Contracts cannot link to themselves | | Contract Address Validation | Only contracts (not EOAs) can be linked | | Latest Version Enforcement | Only latest version tokens can be revised |

Custom Errors

error NotOrchestrator();      // Caller is not the orchestrator
error InvalidTokenId();       // Token does not exist
error InvalidRevision();      // Revision chain violation
error BatchMintFailed();      // Batch minting error
error ArrayLengthMismatch();  // Arrays length mismatch
error ZeroAddress();          // Zero address provided
error NotLatestVersion();     // Token is not the latest version
error DuplicateLinkName();    // Link name already exists
error LinkNotFound();         // Link name not found
error EmptyLinkName();        // Link name cannot be empty
error NotAContract();         // Address is not a contract
error SelfLink();             // Cannot link to self
error BurnDisabled();         // Token burning is disabled
error NotADPAContract();      // Contract does not implement IDPA

Events

event TokenMinted(uint256 indexed tokenId, address indexed to, string uri);
event BatchMinted(uint256 indexed startTokenId, uint256 quantity, address indexed to);
event TokenRevised(uint256 indexed newTokenId, uint256 indexed parentTokenId, uint256 indexed originTokenId, string reason);
event OrchestratorUpdated(address indexed previousOrchestrator, address indexed newOrchestrator);
event DPALinked(uint256 indexed tokenId, bytes32 indexed nameHash, address indexed dpaContract, string name);
event DPAUnlinked(uint256 indexed tokenId, bytes32 indexed nameHash, address indexed dpaContract);

Gas Optimization

DPA leverages ERC721A for significant gas savings on batch operations:

| Operation | Gas Savings | |-----------|-------------| | Batch Minting | Up to 90% vs ERC721 | | Optimized Storage | Packed revision records | | Hash-Based Reason Storage | Full reason emitted in events, hash stored on-chain |

Generate a gas report:

npm run test

Gas report is saved to gas-report.txt.


Development

Prerequisites

  • Node.js >= 18.x
  • npm >= 9.x

Project Structure

dpa-contracts/
├── contracts/           # Solidity source files
├── test/                # Test files
├── artifacts/           # Compiled contracts (generated)
├── typechain-types/     # TypeScript bindings (generated)
├── hardhat.config.ts    # Hardhat configuration
├── package.json         # Project dependencies
└── tsconfig.json        # TypeScript configuration

Hardhat Configuration

// hardhat.config.ts
const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  gasReporter: {
    enabled: true,
    currency: "USD",
    outputFile: "gas-report.txt",
    noColors: true,
  },
};

Example Implementation

See contracts/examples/AssetDPA.sol for a complete example:

contract AssetDPA is DPA {
    enum AssetType { Enum1, Enum2 }

    struct AssetContent {
        uint256 amount;
        AssetType assetType;
    }

    constructor(address orchestrator_) 
        DPA("Asset DPA", "ASSET-DPA", orchestrator_) {}

    function _validateContent(bytes calldata content) internal pure override {
        abi.decode(content, (AssetContent));
    }

    function getAssetContent(uint256 tokenId) external view returns (AssetContent memory) {
        return abi.decode(this.tokenContent(tokenId), (AssetContent));
    }

    function encodeAssetContent(uint256 amount, AssetType assetType) external pure returns (bytes memory) {
        return abi.encode(AssetContent({amount: amount, assetType: assetType}));
    }
}

License

This project is licensed under the MIT License.


Contributing

Contributions are welcome! Please feel free to submit a Pull Request.


Author

Jason Cruz


Related Projects