How to sell NFT-based Subscriptions for Fiat?

favo
vechain.energy
Published in
7 min readMar 8, 2023

--

A common issue with the Blockchain World is the disconnection between the regular economy and the on-boarding of users.

The on-boarding can be simplified with well known Social Logins where the wallet is hidden away. Fee Delegation additionally provides the ability to remove the crypto complexity.

Connecting to the economic part can be solved with a regular Fiat Gateway like PayPal, Stripe, etc. in combination with a backend.

This article covers the basic configuration and an example project NFT that represents a subscription. The NFT is minted instantly after sale and sent to the users wallet. When the subscription ends, it is burned.

The sequence of purchase will look like this:

The following components will be setup:

  1. NFT Contract
  2. vechain.energy API-Key as Blockchain-Bridge
  3. Backend interacting with the NFT Contract
  4. Payment-Provider (Chargebee) to handle the finances
  5. Website to manage the User

NFT Contract

The example contract is generated using the OpenZeppelin Wizard with the options to be Mintable, Burnable and Roles for Access Control.

As modification the burning function will be overridden to be used only by a certain role:

function burn(uint256 tokenId) public onlyRole(MINTER_ROLE) override {
_burn(tokenId);
}

After deployment the first component is already completed.

$ yarn deploy:proxy NFT

Deploying to **TEST** network

ℹ [NFT] Transaction Id: 0xd17702fa02457486b7be3c3a8f02c28014aee508ab77508f43fbcc68763502e1
ℹ [NFT] Contract is now available at 0x8E9A0D678028A5c8fF3B80629c40C308772504C3
ℹ [NFT] Artifact written to src/contracts/test/NFT.json
✔ [NFT] Proxied Contract is now available at 0x9fbcb4312744cf5d36dBa9Fc7B91E9c3D3a2Fa43

✨ Done in 35.58s.

API to Bridge out from the Blockchain

As access point for regular web applications a vechain.energy API is used. After creating a project an API-Key is created and its API-Key-Secret stored at a secret place:

Allow the API-Key to interact with “everyone” and copy the API-Address:

Contract Permission

Every API-Key has its own wallet and address. This address can be granted the “MINTER_ROLE” of the contract, to allow issuing new NFTs.

This is with the grantRole function. A helper script is in the example project:

$ yarn call NFT MINTER_ROLE

Working on **TEST** network

ℹ [NFT] 0x9fbcb4312744cf5d36dBa9Fc7B91E9c3D3a2Fa43 calling MINTER_ROLE()
✔ Results:

0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6
✨ Done in 1.19s.

$ yarn call NFT grantRole 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6 0xc96cb5ba39baeb236773c022fcb974d187430931

Working on **TEST** network

ℹ [NFT] 0x9fbcb4312744cf5d36dBa9Fc7B91E9c3D3a2Fa43 executing grantRole(0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6, 0xc96cb5ba39baeb236773c022fcb974d187430931)
ℹ [NFT] emitted RoleGranted(bytes32,address,address)
ℹ [NFT]
[
"0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6",
"0xC96Cb5ba39BAEB236773C022fcb974d187430931",
"0xa86a839c6023457c52572f438C180049d9A70CbD"
]
ℹ [NFT] Gas costs: 49728 VTHO
✔ [NFT] Completed with Transaction Id 0x9996054595819bb1d5dcc1116bd8c50eb40fe0267041c4def6f495843c5fc7e1
✨ Done in 18.28s.

Backend to issue NFTs

The Backend connects incoming payments and issues commands to create or delete NFTs.

A fetch triggers the functions by sending ABI & arguments.

Issue new NFT

safeMint is called with the recipient address and the to be issued Token Id using a fetch on the vechain.energy API:

fetch(VEN_ENDPOINT_URL, {
method: 'POST',
headers: {
'x-api-key': VEN_API_KEY,
'content-type': 'application/json'
},
body: JSON.stringify({
clauses: [
{
"to": CONTRACT_ADDRESS,
"abi": {
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
"args": [address, tokenId]
}
]
})
})

Delete NFT

Deleting or burning a NFT is similar, calling the burn function on the contract by posting the matching ABI and Token Id to be deleted:

fetch(VEN_ENDPOINT_URL, {
method: 'POST',
headers: {
'x-api-key': VEN_API_KEY,
'content-type': 'application/json'
},
body: JSON.stringify({
clauses: [
{
"to": CONTRACT_ADDRESS,
"abi": {
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
"args": [tokenId]
}
]
})
})

Token Id Calculation

The Token Id represents the subscription which the contracts expects to be a uint256. It will highly likely be a string on the Payment-Providers side and needs to be converted into a number with the help of a sha256 hash and BigNumbers:

  1. Create a sha256 hash of the subscription identifier
  2. Calculate a BigInt from the hexadecimal representation of the hash

Example Function:

export async function idToTokenId(id: string): Promise<string> {
const encodedText = new TextEncoder().encode(id)
const digest = await crypto.subtle.digest({ name: 'SHA-256' }, encodedText)
const hashArray = Array.from(new Uint8Array(digest));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')

return BigInt(`0x${hashHex}`).toString()
}

Website

The website provides basic functionality that accesses the Blockchain directly and links to a Payment-Provider to create the subscription. Responsibilities are:

  1. Sign in to detect the wallet address
  2. List currently owned NFTs
  3. Link to start a new subscription

The details of building a website are not covered in this article.

Backend Included

CloudFlare is used in this example because the Backend can be paired in the same project.

The example website is available on GitHub:
https://github.com/vechain-energy/poc-nft-fiat-subscription

The Website is deployed on CloudFlare Pages with the Backend included as CloudFlare Pages Function as a single file:

https://github.com/vechain-energy/poc-nft-fiat-subscription/blob/master/functions/payment-handler.ts

Payment-Provider

Chargebee provides a platform that manages subscriptions and supports Webhooks to notify backend services.

These configuration values are required to connect the website:

  1. The Chargebee Id
  2. An identifier for the subscription + billing cycle
  3. Wallet-Address of the User

Chargebee Id

The Chargebee Id is the name of the subdomain and can be seen at the top left in the admin panel. In the screenshot it is favo-test:

Subscription Identifier

A new Plan is created with the product description to be sold. To lead the user back to the website the Redirect URL at the bottom links back to the website. Using the Subscription Id the Token Issuance can be verified on the Website:

For different billing cycles like daily, weekly, monthly a different pricing can be set. Each cycle will have its own unique identifier that is required to link to the correct product.

The example plan is a daily subscription for a symbolic cent:

The billing cycle identifier is required to link a user directly to that purchase. It can be obtained from the detail page and in this example is text-nft-subscription-EUR-Daily.

Wallet Address

The address of the users wallet is a custom field that added as attribute to the Plan. It allows to pass the user wallet through the process and is called cf_wallet_address:

Deep-Link to the Payment

The combination allows link users to subscribe a specific subscription and pass the user wallet address:

https://favo-test.chargebee.com/hosted_pages/checkout?subscription_items[item_price_id][0]=test-nft-subscription-EUR-Daily&subscription[cf_wallet_address]=0x9fbcb4312744cf5d36dBa9Fc7B91E9c3D3a2Fa43

Example Configuration

The Chargebee configuration can be adjusted in the example project in the .env file:

https://github.com/vechain-energy/poc-nft-fiat-subscription/blob/master/.env

Webhooks

Chargebee Webhooks close the circuit by informing the Backend of all new or cancelled subscriptions. All subscription events should be send to the Backend:

Summary

The whole purchase process looks like this:

  1. A user signs in and shares the wallet address in the process
  2. The website links to a payment provider, passing the wallet address
  3. The payment provider charges the user and redirects back to the website with an id of the subscription.
  4. At the same time the payment provider sends a notification to the backend about the new subscription
  5. The backend sends a mint command to the vechain.energy-API, which forwards it to the contract and creates a NFT for the subscription id
  6. Meanwhile the user waits for a NFT with the subscription id issued
  7. Once minted, the user receives the NFT in the wallet and a positive confirmation

The working prototype is available on GitHub:
https://github.com/vechain-energy/poc-nft-fiat-subscription

Conclusion

With this Proof of Concept, any kind of Fiat based subscription can be created or any kind of goods be sold on the Blockchain. It shows how to connect the Blockchain with the regular economy.

Using Social Logins like Web3Auth, the blockchain can be removed from the identification process as well. An example blog covering it can be found here:
https://blog.vechain.energy/web2-experience-in-web3-apps-8bb646ae6a4a

--

--