From Screen to Walls: Unleashing the True Potential of NFTs

I. Introduction
I recently came across a video of Pierre T Lambert (professional photographer) talking about NFTs in photography (video here). He discusses how NFTs revolutionize artistic traceability, offering more than just prints. As an amateur photographer and Solidity developer, I designed an app for selling NFTs with prints. This article will explore the marriage of blockchain and visual art.
II. Issues
In this project, the major challenge lies in synchronizing NFTs with an online printing service, while guaranteeing the absolute uniqueness of each physical copy. The crucial question is how to ensure that each NFT sold triggers a unique printing process. Each physical print must be associated with a single NFT, eliminating any possibility of duplication. This is vitally important, as the cost of printing must be covered by the sale of the NFT. Any malfunction at this level could potentially lead to unexpected and undesirable printing costs.
What’s more, the certification of each print on the blockchain requires the execution of a transaction, entailing gas charges. A critical aspect of the problem is therefore to prevent any abuse of this system by users, who might seek to deliberately generate excessive transaction costs. Finding effective mechanisms to guarantee the legitimacy of each print while optimizing the costs associated with transactions on the blockchain is a major challenge to be solved in this context.
III. Solution
How NFTs work
NFTs, or non-fungible tokens, serve as a unique, decentralized digital representation of the underlying object — in this case, the photograph and its print. Their format enables free, uncensored exchanges between users while retaining full traceability of the current owner and all previous ones. This characteristic of uniqueness makes it a suitable digital certificate for real objects, such as a physical print.
How smart contracts work
In this section, we take a look at the various smart contracts used in our project. Find the full code here
A. ImageManager
The ImageManager
smart contract plays a crucial role in managing the creation of new NFTs, their purchase, minting, and printing via the Printer smart contract.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import {Image} from "./Image.sol";
import {Certificate} from "./Certificate.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {Printer} from "./Printer.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {OracleLib} from "./libraries/OracleLib.sol";
contract ImageManager is Ownable, ReentrancyGuard {
// ...
}S
1. Creation and initialization
The smart contract constructor handles initialization, including the definition of the initial owner, authorized tokens with their associated Chainlink PriceFeed, and the creation of an instance of the Printer smart contract.
constructor(address initialOwner, address[] memory tokenAddresses, address[] memory priceFeedAddresses) {
// ... (Initialization of owner and authorized tokens with price feeds)
_printer = new Printer(address(this));
}
2. Minting functions
The mint
function manages the purchase and minting of images. It checks image availability and the buyer's balance, and transfers tokens.
function mint(address imageAddress, address to, address token)
external
nonReentrant
onlyRegisteredImage(imageAddress)
onlyAllowedToken(token)
{
// ... (Verifications and token transfers)
image.safeMint(to);
}
3. Locking and unlocking
The lockImage
and unlockImage
functions manage the process of locking an image in preparation for printing and unlocking it in the event of a problem during the printing process or a change of user mind.
function lockImage(address imageAddress, uint256 tokenId) external nonReentrant onlyRegisteredImage(imageAddress) {
// ... (Check image ownership and call up the "Printer" smart contract to lock the image.)
}
function unlockImage() external nonReentrant {
// ... (Call up the "Printer" smart contract to unlock the image.)
}
4. Order confirmation and certificate creation
The confirmOrder
and mintCertificate
functions handle the confirmation of print orders and the creation of associated certificates.
function confirmOrder(string memory cryptedOrderId) external nonReentrant {
// ... (Call up the "Printer" smart contract to confirm the order)
}
function mintCertificate() external nonReentrant {
// ... (Creation of a certificate associated with the locked image via the "Printer" smart contract)
}
5. Creating a new image
The createImage
function allows the owner to create a new image (NFT) by defining its characteristics, including name, symbol, max supply, image URI, price in USD, and associated print ID.
function createImage(
string memory _name,
string memory _symbol,
uint256 _maxSupply,
string memory _baseURIString,
uint256 _priceInUsdWei,
uint256 _printId
) external onlyOwner returns (address) {
// ... (Creating a new image and its associated certificate)
emit ImageCreated(address(image));
return address(image);
}
7. Owner functions
The withdrawToken
, editPrintId
, setAdmin
and updateTokensAllowed
functions, respectively, enable the owner to withdraw tokens used to purchase NFTs, modify the print ID of an NFT, define an admin for the "Printer" contract, and modify the list of tokens authorized to purchase NFTs.
function withdrawToken(address _token, address _to) external onlyOwner {
// token verification and transfer
}
function editPrintId(address imageAddress, uint256 _printId) external onlyOwner onlyRegisteredImage(imageAddress) {
_printIds[imageAddress] = _printId;
}
function setAdmin(address _admin) external onlyOwner {
_printer.setAdmin(_admin);
}
function updateTokensAllowed(address[] memory tokenAddresses, address[] memory priceFeedAddresses)
external
onlyOwner
{
// tokens update
}
B. Printer
The “Printer” smart contract plays a key role in print management. Here’s an analysis of the main features:
1. Lock and Unlock
The lock
and unlock
functions are called via the ImageManager contract.
lock
checks that no NFT is already being prepared for printing, then transfers the NFT and saves the print information and the owner's address.
function lock(address imageAddress, uint256 imageId, address owner) external onlyOwner nonReentrant {
// Verifications
// Saving informations
image.transferFrom(owner, address(this), imageId);
emit ImageLocked(owner, imageAddress, imageId);
}
unlock
checks that the NFT is not printing and that it is the owner who is requesting the unlocking. Then carry out the transfer.
function unlock(address user) external onlyOwner tokenLocked(user) nonReentrant {
// Verifications
_withdraw(user);
emit ImageUnlocked(user, _nftByUser[user].imageAddress, _nftByUser[user].imageId);
}
2. Order confirmation
The confirmOrder
function is called via the ImageManager
contract. It checks that the order has not already been printed or confirmed, then adds to the NFT the pre-order number generated by an external script linked to the printer's API and transmitted to the user.
function confirmOrder(address user, string memory cryptedOrderId) external onlyOwner tokenLocked(user) nonReentrant
// Verifications
// Saving informations
emit ConfirmOrder(user, cryptedOrderId, _nftByUser[user].imageAddress, _nftByUser[user].imageId);
}
3. Resetting the order
The confirmOrder
function is called up via the ImageManager
contract. In the event of an error during the printing process, this function allows the user to reset the status of his NFT to recover it or start a new order process.
function clearOrderId(address user) external onlyOwner tokenLocked(user) nonReentrant {
// Check that printing has not taken place
// Reset print information
}
4. Certificate mint
The mintCertificate
function is called via the ImageManager
contract. It allows the user to mint the certificate after printing the NFT.
function mintCertificate(address user, address certificate) external onlyOwner tokenLocked(user) nonReentrant {
// Check that the NFT is printed
_mintCertificate(user, certificate);
emit CertificateMinted(user, certificate, _nftByUser[user].imageId);
}
5. Print
The setPrinted
function allows the admin address (here managed by an external script) to change the state of an NFT in the smart contract.
function setPrinted(address user) external onlyAdmin tokenLocked(user) nonReentrant {
// check that the NFT is locked
_nftByUser[user].printed = true;
emit ImagePrinted(user, _nftByUser[user].imageAddress,
}
C. Image & Certificate
These smart contracts are based on the ERC-721 standard, to which functions have been added to enable them to be minted only by the owner (here the smart contract).
How utility scripts work
A. “Embryonic Order”
The user interface is managed using NextJS, making it easy to purchase and print NFTs.
Users with an NFT can lock it to generate an “embryonic order” containing their information. (code here)
- Verification of user data
- Verification on the NFT blockchain
- Verification of user signature
- Print ID retrieval from blockchain
- Call the printer’s API to create an “Embryonic Order”
- Sends encrypted data to the user to validate the order
B. “Confirmed Order”
A typescript script that constantly listens to events emitted by our smart contract waits for the user to confirm his order. (code here)
- Listening to the event
- Information retrieval from the blockchain
- Decryption and comparison of information with the Embyonic Order
- NFT changes to “printed” status
- Confirmation of the order via the printer’s API.
Using “The Print Space”
To manage NFT printing, I chose to use The Print Space. A company offering an API to sell its high-quality prints, as well as a “test area”. The code is adapted to the format used by this API, whose documentation can be found here.
IV. Results
The integration of blockchain in the field of visual art has resulted in an innovative platform where digital creativity comes to life. Each digital artwork is now represented by an NFT acting as a digital certificate of authenticity. Thanks to smart contracts and the seamless use of “The Print Space” API, each NFT sold can have a unique print, guaranteeing the uniqueness of the physical copies.
In short, this project demonstrates the transformative potential of NFTs, taking them from screens to walls and offering a new dimension to artistic expression in the digital age.
V. References
- Smart contract implementation: https://nft-prints.ereynier.me
- All code : https://github.com/ereynier/NFT-PhotoPrint
- OpenZeppelin libraries: https://github.com/OpenZeppelin/openzeppelin-contracts
- Chainlink price feeds: https://docs.chain.link/data-feeds/price-feeds
- Foundry solidity: https://book.getfoundry.sh
- Pierre T Lambert’s Youtube channel: https://www.youtube.com/@Pierretlambert