From Screen to Walls: Unleashing the True Potential of NFTs

Esteban Reynier
6 min readDec 4, 2023
User interface of the website — NFT Prints

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)

  1. Verification of user data
  2. Verification on the NFT blockchain
  3. Verification of user signature
  4. Print ID retrieval from blockchain
  5. Call the printer’s API to create an “Embryonic Order”
  6. 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)

  1. Listening to the event
  2. Information retrieval from the blockchain
  3. Decryption and comparison of information with the Embyonic Order
  4. NFT changes to “printed” status
  5. 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

--

--

Esteban Reynier
0 Followers

Solidity | Blockchain | Web3 Freelance Developer. Interested in audiovisual, video games and AI