Jump to content

NFTs on XRPL... How?


KarmaCoverage

Recommended Posts

23 minutes ago, KarmaCoverage said:

Before I put too much thought into this, does anyone know exactly How an NFT is issued onto XRPL?

What are the desired set of characteristics that define an "NFT"?

TBH, I didn't know that it could be done either. I only found out via a Tweet from XRPL Labs:

They actually have an intern will be presenting his graduate-level research/dicertatcion on the issuance of NFTs on the XRPL. I'm actually looking forward to hearing his findings on the subject matter:

 

 

Edited by King34Maine
Link to comment
Share on other sites

5 minutes ago, KarmaCoverage said:

I'm assuming it involves blackholeing the originating wallet's secret key?

That would at least permanently limit the IOU supply, because nothing more can be issued by that wallet. Then other parties can trade in the IOU.blackholeNFTWallet

Ahh yeah, that makes sense. Kind of an odd way to go about it, but I think that actually would work.

Link to comment
Share on other sites

An NFT is unique and indivisible. That's all you really need. I would assume that they are usually used in sets. There's not a lot you can do with a totally one-off token, but a group of related tokens all representing individual things could get interesting. It appears that they usually use a website or something to describe everything that the token represents. Apparently there are also cases for semi fungible tokens. Think of like representing pokemon cards or something. You still want to represent all of the different cards, but you may have more than one of a particular card in a deck. Copies of the same card are fungible with each other (at least for gameplay purposes), but not all the other cards in the game.

Link to comment
Share on other sites

1 hour ago, King34Maine said:

Ahh yeah, that makes sense. Kind of an odd way to go about it, but I think that actually would work.

I'm just wondering if there is a different method for creating an NFT on XRPL?

Something tells me there may be, especially if one starts to consider discrete segments of time.

Maybe there are some desirable characteristics that can be achieved via the addition of a Turning Complete SC?

Edited by KarmaCoverage
Link to comment
Share on other sites

https://xrpl.org/currency-formats.html

Quote

Note:The XRP Ledger does not support issued currencies that are not fungible . It also does not support limiting an issued currency to whole number amounts only. All issued currencies in the XRP Ledger are always divisible down to the minimum amount.

I think XRPL Labs need change the rippled code.

Link to comment
Share on other sites

16 hours ago, brianwalden said:

An NFT is unique and indivisible.

A divisible NFT might open up some interesting possibilities for trading shares (fractions) of an NFT. This might also open up some issues with ownership (especially with a single physical object). But imagine some possibilities for time or revenue sharing with NFT -- sharing a unique game character with a group of people or buying partial access or ownership in an NFT artwork. Certainly possible with smart contracts holding an NFT, but having NFTs with this native capacity could be interesting.

Link to comment
Share on other sites

16 minutes ago, RambeauTeasebox said:

I remember discussions about positive integer-only (non-divisible) IOUs several years ago, but can't find them.

I'd be very interested in reading that thread, must have missed it.

Couldn't you just issue 0.00001 NFT token? Or can you not issue a partial token? I think you can.

Link to comment
Share on other sites

I've been interested in NFTs since first hearing about CryptoKitties a few years ago and I starting thinking about how to issue NFTs on XRPL after @KarmaCoverage posed the question last week. I worked out a scheme to do so this past Friday. Unfortunately, it looks like I got scooped by the above group, though I don't know where their methods have been published (if they have been). Based on their short video introduction, it also appears that they're using some kind of private database for handling asset storage.(?) So I'm going to post how I created an NFT with the XRPL Testnet and IPFS. I've done this twice now and here I'll use my latest attempt (from earlier today).

As a disclaimer, despite using the XRPL for a long time, I don't consider myself an expert. There may be better or just different ways of issuing NFTs. There also may be mistakes or wrong assumptions here. Corrections, criticism and alternatives will be much appreciated.

Also, while issuing NFTs on the XRPL is definitely possible, the ledger is inflexible compared to Ethereum or another platform that directly interfaces with smart contracts. Perhaps future smart contract platforms that can hook into the XRPL for consensus (coming soon!?) will be better suited to the purposes of issuing NFTs. And this NFT, in particular, will disappear whenever the XRPL Testnet is reset, despite the IPFS object persisting.

OK, on to my NFT guide. First, I'll talk about accounts, assets and the structure of the NFT issuance. Second, I'll show the JSON data payloads and walk through the transactions for issuing and purchasing the NFT.

Assets:

To store the assets that are represented by the NFT, I'm using IPFS (https://ipfs.io/ & https://en.wikipedia.org/wiki/InterPlanetary_File_System). This (hopefully) assures that the assets will be accessible by anyone and never disappear. To link to the asset on IPFS, I'll use the IPFS hash digest of the asset, which is a verifiable representation of the asset and points to it on the IPFS network.

First, the object that we want to be represented by the NFT on XRPL:

QmU7jY9pnxfmzLRjhS87WzVxJeLUz2RhM43Jmt9sSo1jSL (https://ipfs.io/ipfs/QmU7jY9pnxfmzLRjhS87WzVxJeLUz2RhM43Jmt9sSo1jSL)

I think this "trading card" may look familiar to some of you... :):rolleyes:

Second, another IPFS object that is metadata for the NFT object:

Qmcc6igFwkSs4jKyGBNEX9VTqU3Sy7X6bpC3BDWCue1ejD (https://ipfs.io/ipfs/Qmcc6igFwkSs4jKyGBNEX9VTqU3Sy7X6bpC3BDWCue1ejD)

The NFT will reference these IPFS hash digests. (Note: I could just have the NFT point to the metadata, which itself points to the object of interest.)

Now, in another example, this asset could be another image/"trading card" as above, but could also be a video, a written work, a snippet of code that is used as an in-game asset, a real estate deed... Anything, really. Of course, how a given asset is used, managed and verified outside of the ledger is another thing.

XRPL Accounts:

There are three accounts that I'll use on the XRPL Testnet for this example:
Issuer: rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb (https://test.bithomp.com/explorer/rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb)
Distributor: rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa (https://test.bithomp.com/explorer/rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa)
Purchaser: rfWgknJfidjEmeAvJMZpTnj6WtVjDQjNhf (https://test.bithomp.com/explorer/rfWgknJfidjEmeAvJMZpTnj6WtVjDQjNhf)

The Issuer will create and send the NFT to the Distributor via the Distributor's trustline. The Issuer will create a single NFT referencing a single asset*. When the Issuer is finished creating the NFT, it will then "blackhole" itself to make further NFT issuance impossible. The Distributor now holds the NFT and will create an offer to sell it on the XRPL DEX. The Purchaser will then offer to buy it, and when the offers match, the Purchaser will exchange XRP for the NFT**.

* Here, the NFT will actually reference two IPFS assets: The image asset and its metadata asset. As noted above, maybe it's better to just point to the metadata, which points to the image asset.

** Of course, once created, the Purchaser could book an offer first. In an auction, multiple potential purchasers of an NFT could post offers, and at a certain time, the Distributor could decide to take the best offer.

The NFT:

How does one issue an NFT on the XRPL? Well, I just issued the smallest possible amount of a currency, giving it a unique name and linking it to the IPFS asset hash digests using a data storage field in the ledger object. This single currency unit that references a verifiable representation of the asset is our NFT.

To create an indivisible NFT unit, I issued 1e-15 unit of currency (0.000 000 000 000 001), which is the smallest amount that can be issued***. XRPL precision is maintained with integers, so this is a single integer unit of issued currency.  (https://xrpl.org/currency-formats.html#issued-currency-precision)

*** I'm a bit confused about the precision versus the minimum nonzero absolute value but haven't looked into it yet. I think 1e-15 is the correct amount to use and don't believe I could issue less, though I haven't tried. (https://xrpl.org/currency-formats.html#comparison)

Issued currencies can be named with standard ISO-4217 currency codes or non-standard 160-bit (40 HEX characters) codes. To give the NFT a unique name, I used the SHA1 digest of the image's IPFS pointer****, which is conveniently 160 bits:

$ echo "QmU7jY9pnxfmzLRjhS87WzVxJeLUz2RhM43Jmt9sSo1jSL" | sha1sum
212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3  -

So, I'll use "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3" as a unique currency code for the NFT.

**** You could use something more readable, such as the HEX value of a 20-character ASCII descriptor, especially when issuing multiple NFTs for a single asset. For example, if we wanted to issue 5 NFTs for this "trading card" and this was card #2 of 5, this could be the HEX value of "JoelKatzIcon.jSL#2/5" or a digest of it or something similar.

Finally, to link our NFT to a verifiable IPFS asset, I used the Memo field (https://xrpl.org/transaction-common-fields.html#memos-field) to store the IPFS pointers of our asset and its metadata. Translating the ASCII to HEX for a MemoData string:

"
NFT object IPFS hash:
QmU7jY9pnxfmzLRjhS87WzVxJeLUz2RhM43Jmt9sSo1jSL
NFT metadata IPFS hash:
Qmcc6igFwkSs4jKyGBNEX9VTqU3Sy7X6bpC3BDWCue1ejD
"
>>> "4e4654206f626a656374204950465320686173683a0a516d55376a5939706e78666d7a4c526a68533837577a56784a654c557a3252684d34334a6d743973536f316a534c0a4e4654206d65746164617461204950465320686173683a0a516d636336696746776b5373346a4b7947424e455839565471553353793758366270433342445743756531656a44"

There are some other fields that could be used to store metadata with XRPL objects, such as the Domain, Email and MessageKey fields for the Issuer and Distributor accounts. But for now I'll just use the Currency Code and Memos fields to reference the asset.

Before moving on to the JSON payloads and XRPL transactions, a final note about our NFT asset. While the NFT will reference the image and a separate metadata text file, metadata could be included in the original file. For example, for this image, one might want to include certain data in its Exif metadata. Also, a message with the asset's metadata, singed with a known public key of the image's creator or agent that shows pre-NFT provenance or confirms and licenses NFT creation could be useful.

Transactions:

OK, now that the data and structure of the NFT have been established, I'll move on to the actual NFT creation on XRPL. Remember, I'm just issuing one indivisible currency unit with some data attached to its issuing transaction that references a verifiable IPFS object. For another example of XRPL issuance, see the overview here: https://www.jonhq.com/how-to-run-an-ico-on-the-ripple-consensus-ledger/ 

NOTE: I'm going to use an online rippled instance to sign the transactions so that I don't have to add Fee or Sequence fields to the transactions, and will then submit the transaction blobs to verify the transactions. Doing the transactions in a secure, offline manner will require modified JSON payloads and rippled commands.

Before the NFT is issued, the Distributor account must trust the Issuer account so that the Issuer can send its issued currency to the Distributor. This trust is for the smallest currency unit:

{
    "TransactionType": "TrustSet",
    "Account": "rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa", // Distributor account
    "LimitAmount": {
      "currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3", // The non-standard currency code -- the SHA1 digest of the IPFS pointer
      "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", // Issuer account
      "value": "1e-15" // Minimum unit for issued currency precision
    }
}
$ rippled sign sSECRETXXXXXXXXXXXXXXXXXXXXXX '{"TransactionType": "TrustSet", "Account": "rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa", "LimitAmount": {"currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3", "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "value": "1e-15"} }'

Tx hash: 0FA103CC0C2C2149521C3435D25BB7268D0DE872F92BD262B9554FB36E0F585F (https://test.bithomp.com/explorer/0FA103CC0C2C2149521C3435D25BB7268D0DE872F92BD262B9554FB36E0F585F)

Next, the Issuer is going to actually create a unit of currency by sending it to the Distributor account:

{
    "TransactionType": "Payment",
    "Account": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", // Issuer account
    "Destination": "rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa", // Distributor account
    "Memos": [
        {
            "Memo": {
                "MemoData": "4e4654206f626a656374204950465320686173683a0a516d55376a5939706e78666d7a4c526a68533837577a56784a654c557a3252684d34334a6d743973536f316a534c0a4e4654206d65746164617461204950465320686173683a0a516d636336696746776b5373346a4b7947424e455839565471553353793758366270433342445743756531656a44" // IPFS asset pointers in HEX
            }
        }
    ],
    "Amount": { "currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3", "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "value": "1e-15" }
}
$ rippled sign sSECRETXXXXXXXXXXXXXXXXXXXXXX '{"TransactionType": "Payment", "Account": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "Destination": "rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa", "Memos": [ { "Memo": { "MemoData": "4e4654206f626a656374204950465320686173683a0a516d55376a5939706e78666d7a4c526a68533837577a56784a654c557a3252684d34334a6d743973536f316a534c0a4e4654206d65746164617461204950465320686173683a0a516d636336696746776b5373346a4b7947424e455839565471553353793758366270433342445743756531656a44"}}], "Amount": { "currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3", "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "value": "1e-15" } }'

Tx hash: 9CCCC8A04BCCA16264AE0E32BA2636B62BD723B54C39C9D4AE18286A69FA9564 (https://test.bithomp.com/explorer/9CCCC8A04BCCA16264AE0E32BA2636B62BD723B54C39C9D4AE18286A69FA9564)

The NFT has been created! Importantly, the MemoData field refers to the IPFS asset -- the link between our NFT token and the valued object. Referring to the NFT issuance shows our asset pointer data. The Distributor account can now do with it what it wants. First, though, let's make sure the NFT can be more easily sent by accounts without trustlines and make sure the Issuer cannot create more units of currency:

Set DefaultRipple on issuing account (P2P sending without Trustlines):

$ rippled sign sSECRETXXXXXXXXXXXXXXXXXXXXXX '{"TransactionType": "AccountSet","Account" : "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb","SetFlag": 8}'

Tx hash: 6A0FAC075F80AF1356C5CE82E35D30840ADAAA2E1D5CCC0CC2771E79B8251BCF (https://test.bithomp.com/explorer/6A0FAC075F80AF1356C5CE82E35D30840ADAAA2E1D5CCC0CC2771E79B8251BCF)

See: https://xrpl.org/rippling.html#the-default-ripple-flag

Remove ability to issue more of the NFT:

Set RegularKey to Blackhole account:

$ rippled sign sSECRETXXXXXXXXXXXXXXXXXXXXXX '{"Flags": 0, "TransactionType": "SetRegularKey", "Account": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "RegularKey": "rrrrrrrrrrrrrrrrrrrrBZbvji"}'

Tx hash: 01DADA8FFACDCB898743A0276FD91AC55D344599D480C0401A9B94B92780B66B (https://test.bithomp.com/explorer/01DADA8FFACDCB898743A0276FD91AC55D344599D480C0401A9B94B92780B66B)

See: https://xrpl.org/accounts.html#special-addresses

Remove use of MasterKey for the issuing account:

$ rippled sign sSECRETXXXXXXXXXXXXXXXXXXXXXX '{"TransactionType": "AccountSet","Account" : "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "SetFlag": 4}'

Tx hash: 9B4275B3E1B3641255CA73E41FF3DCB20A552A1989400444865D31E85E84AC15 (https://test.bithomp.com/explorer/9B4275B3E1B3641255CA73E41FF3DCB20A552A1989400444865D31E85E84AC15)

See: https://xrpl.org/disable-master-key-pair.html#disable-master-key-pair

Our Issuing account is now toast -- along with any XRP stuck in it. If you'd do this for real, you'd want to remove all but the minimum reserve of XRP and other assets before blackholing it. To not waste more than the necessary XRP, you'd probably want to issue a batch of NFTs from one account before going through this process. But we've destroyed the ability to issue more units of any NFT this account has already issued and preserved their scarcity.

Finally, I had the Distributor account sell the NFT to the Purchaser account:

Distributor account offers NFT for sale (against XRP):

{
    "TransactionType": "OfferCreate",
    "Account": "rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa",
    "TakerPays": "100000000",
    "TakerGets": {
      "currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3",
      "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb",
      "value": "1e-15"
    }
}
$ rippled sign sSECRETXXXXXXXXXXXXXXXXXXXXXX '{"TransactionType": "OfferCreate", "Account": "rEfeRQwjJgEAgQpHtKCRG7tsdSmv4yjqJa", "TakerPays": "100000000", "TakerGets": {"currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3", "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "value": "1e-15"} }'

Tx hash: 40A236443B4C62B83273E65F6D367E66DAC8B480B2B86A994D898C8F879D3BB7 (https://test.bithomp.com/explorer/40A236443B4C62B83273E65F6D367E66DAC8B480B2B86A994D898C8F879D3BB7)

Purchaser account buys NFT for 100 XRP:

{
    "TransactionType": "OfferCreate",
    "Account": "rfWgknJfidjEmeAvJMZpTnj6WtVjDQjNhf",
    "TakerGets": "100000000",
    "TakerPays": {
      "currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3",
      "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb",
      "value": "1e-15"
    }
}
$ rippled sign sSECRETXXXXXXXXXXXXXXXXXXXXXX '{"TransactionType": "OfferCreate", "Account": "rfWgknJfidjEmeAvJMZpTnj6WtVjDQjNhf", "TakerGets": "100000000", "TakerPays": {"currency": "212e5fc6a2bbc0df22df1f1a83dfb6c6af5599a3", "issuer": "rPErcAVSGVXxX7iyCsp483tmG7CVzr7sxb", "value": "1e-15"} }'

Tx hash: B97D6975B0FBF77FE49C3D795605B4475A5B2480E752E5048F958C3F33B527F4 (https://test.bithomp.com/explorer/B97D6975B0FBF77FE49C3D795605B4475A5B2480E752E5048F958C3F33B527F4)

That's it! rfWgknJfidjEmeAvJMZpTnj6WtVjDQjNhf is the proud owner of my test NFT and the Distributor account has 100 more testnet XRP. When we look back at the issuing account tied to the NFT, we can see the following identity in the NFT issuing transaction (https://test.bithomp.com/explorer/9CCCC8A04BCCA16264AE0E32BA2636B62BD723B54C39C9D4AE18286A69FA9564) and go on to verify it with an IPFS node and see our "trading card":

NFT object IPFS hash:
QmU7jY9pnxfmzLRjhS87WzVxJeLUz2RhM43Jmt9sSo1jSL
NFT metadata IPFS hash:
Qmcc6igFwkSs4jKyGBNEX9VTqU3Sy7X6bpC3BDWCue1ejD

As a final note, I learned that https://xumm.community/tokens offers a currency issuing tool. I haven't walked through the whole thing myself, but this may allow for issuance of NFTs (# tokens = 0.000 000 000 000 001) for the community without needing more technical tools if it lets you add Memo data to the issuing transaction.

Looking forward to your comments and, hopefully, some NFTs!
 

Edited by RambeauTeasebox
corrections and removed obfuscated links
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...