NFT collectors enjoy collecting digital assets and sharing them with others online. However, there is currently no such standard for showcasing physical assets as NFTs with verified authenticity and ownership. Existing solutions are fragmented and tend to be susceptible to at least one of the following:
-
The NFT cannot proxy as ownership of the physical item. In most current implementations, the NFT and physical item are functionally two decoupled distinct assets after the NFT mint, in which the NFT can be freely traded independently from the physical item.
-
Verification of authenticity of the physical item requires action from a trusted entity (e.g. StockX).
PBT aims to mitigate these issues in a decentralized way through a new token standard (an extension of EIP-721).
From the Azuki team. Chiru Labs is not liable for any outcomes as a result of using PBT, DYOR. Repo still in beta.
Note: the frontend library for chip signatures can be found here.
npm install @chiru-labs/pbt
forge install https://github.com/chiru-labs/PBT
This approach assumes that the physical item must have a chip attached to it that fulfills the following requirements (Kong is one such vendor for these chips):
- The ability to securely generate and store an ECDSA secp256k1 asymmetric keypair
- The ability to sign messages from the private key of the asymmetric keypair
- The ability for one to retrieve only the public key of the asymmetric keypair (private key non-extractable)
The approach also requires that the contract uses an account-bound implementation of EIP-721 (where all EIP-721 functions that transfer must throw, e.g. the "read only NFT registry" implementation referenced in EIP-721). This ensures that ownership of the physical item is required to initiate transfers and manage ownership of the NFT, through a new function introduced in IPBT.sol
(transferToken
).
On a high level:
- Each NFT is conceptually linked to a physical chip.
- The NFT can only be transferred to a different owner if a signature from the chip is supplied to the contract.
- This guarantees that a token cannot be transferred without consent from the owner of the physical item.
More details available in the EIP and inlined into IPBT.sol
.
v2 is a newer implementation of PBT that uses timestamp as the way to determine if a transfer is eligible. When using this version, it's recommended to have a non-deterministic nonce for signatures. An implementation of this can be seen in /v2/PBTSimple.sol
. You can choose to import PBTSimple directly into your project or build your own.
v1 is considered legacy and uses blockhash instead of timestamp. With the speed of blocks on L2's, moving to timestamp over blockhash makes PBT possible on ever faster chains.
A simple mint for a physical drop could look something like this:
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chiru-labs/pbt/src/PBTSimple.sol";
contract Example is PBTSimple, Ownable {
/// @notice Initialize a mapping from chipAddress to tokenId.
/// @param chipAddresses The addresses derived from the public keys of the chips
/// @param tokenIds The tokenIds to map to the addresses
/// @param maxDurationWindow Maximum duration for a signature to be valid since the timestamp used in the signature.
constructor(
address[] memory chipAddresses,
uint256[] memory tokenIds,
uint256 maxDurationWindow
)
PBTSimple("Example", "EXAMPLE", maxDurationWindow)
{
for (uint256 i = 0; i < chipAddresses.length; i++) {
_setChip(tokenIds[i], chipAddresses[i]);
}
}
/// @param to the address to which the PBT will be minted
/// @param chipId the chip address being minted
/// @param chipSignature the signature generated by the chip
/// @param signatureTimestamp the timestamp used in the signature
/// @param extras misc data, for extensions or custom logic in mint
function mint(
address to,
address chipId,
bytes memory chipSignature,
uint256 signatureTimestamp,
bytes memory extras
) external returns (uint256) {
return _mint(to, chipId, chipSig, sigTimestamp, extras);
}
}
As mentioned above, this repo is still in beta and more documentation is on its way. Feel free to contact @2pmflow if you have any questions.
TODO: flesh this section out more
3 key parts.
- Acquire chips, embed them into the physical items.
- The Azuki hoodies used chips from kongiscash. Docs for chips
- Before you sell/ship the physicals, make sure you save the public keys of the chips first, since the smart contract you deploy will need to know which chips are applicable to it. For kongiscash chips, you can use their bulk scanning tool to do so.
- Note: when you scan a Kong chip, a system notification may popup, even when the scan is prompted from a browser action. To configure this notification's destination url, contact cameron@arx.org. Kong is currently working on (1) making these on-chain registrations decentralized and (2) making the system notification popup optional for future chips.
- Write and deploy a PBT smart contract (use this repo).
- Deployed examples: Azuki Golden Skateboard, Azuki x Ambush Hoodie.
- The chip addresses also need to be seeded into the contract as an allowlist for which chips can mint and transfer
- Set up a simple frontend to support minting/transferring the PBT.
-
Azuki's UX flow for reference.
- The flow is initiated from desktop. The user is beamed to a mobile webapp through a QR code to do the scan. After the scan, the mobile webapp writes the signature to a server, and the desktop webapp polls the server to get the signature. User completes the mint on desktop.
- For now, a working end-to-end flow will also require building out a simple frontend for a mobile browser to grab chip signatures to pass into the smart contract. We have open-sourced a light js lib to help with that piece.
-
Azuki's UX flow for reference.
- [ ] CI pipeline
- [ ] PBT Locking extension (where transfers need to be approved by the current owner first)
- [ ] PBT implementation that doesn't require seeding chip addresses to the contract pre-mint
- how this would work: the mint function takes in a <tokenId, chipAddress> message that's signed by a blessed signer that the contract verifies
Contributions welcome!
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the project
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a pull request
Distributed under the MIT License.