How to sell NFT-based Subscriptions for Fiat?
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:
- NFT Contract
- vechain.energy API-Key as Blockchain-Bridge
- Backend interacting with the NFT Contract
- Payment-Provider (Chargebee) to handle the finances
- 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:
- Create a sha256 hash of the subscription identifier
- 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:
- Sign in to detect the wallet address
- List currently owned NFTs
- 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:
- The Chargebee Id
- An identifier for the subscription + billing cycle
- 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:
- A user signs in and shares the wallet address in the process
- The website links to a payment provider, passing the wallet address
- The payment provider charges the user and redirects back to the website with an id of the subscription.
- At the same time the payment provider sends a notification to the backend about the new subscription
- 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
- Meanwhile the user waits for a NFT with the subscription id issued
- 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