✨
Browser SDK
Verify includes a Javascript SDK which handles showing a wallet connection UI, making API requests and returning results. This is the recommended way of interacting with Verify, but we also have a REST API if you want to build your own UI.
Install the SDK via NPM or by importing with a
<script>
tag. NPM
<script>
Install the SDK from NPM
npm install --save @diamondhh/verify
Import the combined bundle for all supported chains
import VerifySdk from '@diamondhh/verify'
Or import a chain specific bundle (supports tree shaking)
import VerifySdk from '@diamondhh/verify/solana' // OR
import VerifySdk from '@diamondhh/verify/ethereum' // Also supports Polygon
The SDK will automatically inject the required stylesheet, no CSS import required.Import the default CSS in your
<head>
Import the Verify SDK before your closing
</body>
tag.<script src="https://unpkg.com/@diamondhh/verify/dist/verify.umd.js"></script>
Before you can use Verify you need to set your API key. You can get an API key for free by registering on Diamond Hands Hotel.
When using the Browser SDK or calling the API from the client side you must use your public key. You must never share your secret key (used for verifying results on the backend) as it could also be used to forge a successful verification.
const verify = new VerifySdk("pk_YOUR_PUBLIC_KEY")
Now the SDK is set up you're ready to start making requests. The quickest way to get started is to read the example below and review the comments for detailed information. This example uses all available options, but only
chain
and contract
are required. const res = await verify.nft({
// Chain symbol: SOL, ETH or MATIC (Polygon)
chain: "SOL",
// Network: options are chain specific, see table below
// Defaults to mainnet if not provided
// network: "testnet",
// Contract address or Candy Machine ID (Solana)
contract: "6p9xpokmE8PcK4VBwBk6nf6PYoBwWUDsYxjMy7erRjpw",
// Provide marketplace information and Verify will automatically fetch
// your avatar and banner images, social links & add a puchase button.
marketplace: {
// Marketplace name: OPENSEA, LOOKS_RARE, MAGIC_EDEN, SOLANART
// Valid marketplace options will depend on your chain
name: "MAGIC_EDEN",
// Slug is only required for Solana NFTs as the collection can't
// be found automatically. The slug is found at the end of your listing
// URL, for example magiceden.io/marketplace/[your slug]
slug: "space_sneks_society"
},
// You can override the collection metadata or provide manually if
// your collection isn't listed yet. Images must be absolute URLs, and
// will be resized automatically. All fields are optional and will be
// merged with marketplace data.
metadata: {
name: "My Awesome NFT",
avatarImage: "https://mynft.com/assets/avatar.png",
bannerImage: "https://mynft.com/assets/banner.png",
socials: [{
type: "TWITTER",
url: "https://twitter.com/MyAwesomeNFT"
}, {
type: "DISCORD",
url: "https://discord.gg/MyAwesomeNFT"
}, {
type: "WEB",
url: "https://myawesomenft.com"
}
},
// Add unlockable content, which only users who pass verification can
// access. Configure unlockables through the web dashboard or via
// the API. Only unlockables for the correct chain/contract will be
// returned (invalid unlockables are silently ignored).
//
// This also works with Webhook unlockables, allowing you to return
// dynamic content based on the results of the user verification.
// Unlike other unlockable types, webhook unlockables are executed
// even if the verification fails allowing you to update a users
// account to revoke access if they previously had it.
//
// You can provide as an array of unlockable names to return or just
// pass 'true' to fetch all available unlockables.
// unlockables: true,
unlockables: ["my-video"],
// Link the request to some data in your system by passing an arbirary
// data string up to 512 bytes. We highly recommend you also provide a HMAC
// signature = SHA256("sk_YOUR_SECRET_KEY" + value) which will
// ensure the data isn't tampered with before the verification request.
// You can also include an object by converting it to a string first.
//
// If a HMAC isn't provided the request will still succeed but
// `result.data.verified` will be false. DO NOT CALCULATE THE HMAC CLIENT
// SIDE. Your secret key must never be transmitted to end users.
//
// This is entirely optional, and has no effect on operation other than
// including the provided data in the verification results. Usually used
// alongside webhook unlockables to perform a side effect based on the
// verification results. Possible uses cases include:
// * passing an email address to subcribe to a holders mailing list
// * passing a user ID and upgrading them to a premium membership
// * passing a user ID and linking to their wallet address for
// wallet-based login
data: {
value: "{\"email\":\"[email protected]\"}",
hmac: "b12f8da26ff8f64f6ae22db5e78d49b8504a4bc446203a4247d5e8990d1fe3e7"
},
})
Parameter | Required | Type | Description |
---|---|---|---|
chain |
| Chain to find the contract on | |
contract | String | Ethereum & Polygon Contract address
Solana Candy Machine program address | |
network | Solana
Ethereum
Polygon
| Network to use, defaults to mainnet | |
marketplace.name |
| Marketplace to fetch collection metadata from | |
marketplace.slug | String | Required if a marketplace name is provided and the chain is SOL | |
metadata.name | String | Collection name | |
metadata.avatarImage | URL | Avatar image (must be an absolute URL) | |
metadata.bannerImage | URL | Banner image (must be an absolute URL) | |
metadata.socials | Object[] | Social links to display. Valid options for type are WEB , TWITTER and DISCORD | |
data.value | String | Arbitrary data that will be included in the signed result | |
data.hmac | String | Recommended with data.value SHA256 HMAC of the data, where the key is your secret key.
If a HMAC is provided but invalid an error will be thrown. |
res
will resolve to an object with the results of the verification request once the user has completed the verification (either passing or failing). If aborted without completing, the call will throw an error.// console.log(res)
{
"result": {
"at": "1643732888",
"ownership": true,
"wallet": {
"address": "sneksprLoHxxRAyLS9W4oZibXRJX9Q69m1Bnv2hRCAE",
"verified": true
},
"data": {
"value": "{\"email\":\"[email protected]\"}",
"verified": true
}
},
"asset": {
"chain": "SOL",
"network": "mainnet",
"contract": "6p9xpokmE8PcK4VBwBk6nf6PYoBwWUDsYxjMy7erRjpw",
"symbol": "SNEK"
},
"tokens": [{
"id": "FuSrkrUnGf2KRHeMvEFh2jpJ55NHwfh3syifebVoPBDS",
"uri": "https://arweave.net/eOGTCQTp1J95o2pzxTt_yinrtuPVVGlgfP4UyE5P2p0",
"metadata": {
"name": "Space Sneks #279",
"description": "An ultra exclusive society of 1,024 highly evolved Sneks, that travelled the cosmos to open the Diamond Hands Hotel.",
"image": "https://www.arweave.net/6YBdvGHAyNc4wLp4hQDDvT-2ytMHaknVTR59cwpw8dI?ext=png",
"collection": {
"name": "Space Sneks",
"family": "Diamond Hands Hotel"
},
"attributes": [
{
"trait_type": "Other",
"value": "Blue Laser"
}
]
}
}],
"unlockables": [
{
"name": "secret-video",
"type": "video",
"content": "https://dmnd.network/verify/v1/get-unlockable?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjcwZTgzMzk5LWVkYTAtNGExNi1hZDhlLWQ5MjVlMDRiNzQzNyIsImlhdCI6MTUxNjIzOTAyMn0.zHXeuqwpyXBwK-27wrGkV_uvrmK_t60uk1ihiGig82w&appId=2da62f65-543b-4338-a68a-9104f6c31b56",
"createdAt": "2022-02-18T14:37:00.387Z",
"updatedAt": null,
}
],
"signed": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJpZmllZCI6eyJvd25lcnNoaXAiOnRydWUsIndhbGxldCI6dHJ1ZSwiYWRkcmVzcyI6InNuZWtzcHJMb0h4eFJBeUxTOVc0b1ppYlhSSlg5UTY5bTFCbnYyaFJDQUUiLCJhdCI6IjE2NDM3MzI4ODgifSwiYXNzZXQiOnsiY2hhaW4iOiJTT0wiLCJjb250cmFjdCI6IjZwOXhwb2ttRThQY0s0VkJ3Qms2bmY2UFlvQndXVURzWXhqTXk3ZXJSanB3Iiwic3ltYm9sIjoiU05FSyJ9LCJvd25lZCI6W3siaWQiOm51bGwsImFkZHJlc3MiOiJGdVNya3JVbkdmMktSSGVNdkVGaDJqcEo1NU5Id2ZoM3N5aWZlYlZvUEJEUyIsIm1ldGFkYXRhIjp7Im5hbWUiOiJTcGFjZSBTbmVrcyAjMjc5IiwiZGVzY3JpcHRpb24iOiJBbiB1bHRyYSBleGNsdXNpdmUgc29jaWV0eSBvZiAxLDAyNCBoaWdobHkgZXZvbHZlZCBTbmVrcywgdGhhdCB0cmF2ZWxsZWQgdGhlIGNvc21vcyB0byBvcGVuIHRoZSBEaWFtb25kIEhhbmRzIEhvdGVsLiIsImltYWdlIjoiaHR0cHM6Ly93d3cuYXJ3ZWF2ZS5uZXQvNllCZHZHSEF5TmM0d0xwNGhRRER2VC0yeXRNSGFrblZUUjU5Y3dwdzhkST9leHQ9cG5nIiwiY29sbGVjdGlvbiI6eyJuYW1lIjoiU3BhY2UgU25la3MiLCJmYW1pbHkiOiJEaWFtb25kIEhhbmRzIEhvdGVsIn0sImF0dHJpYnV0ZXMiOlt7InRyYWl0X3R5cGUiOiJPdGhlciIsInZhbHVlIjoiQmx1ZSBMYXNlciJ9XX19XX0.JnxguNAeT37upXgx9R_8-vGS4WiVQ487F3RttDubJr0"
}
result
holds a summary of the verification result.If using Webhook unlockables you MUST check the value of
ownership
and data.verified
as webhooks will be executed regardless of verification success.Property | Required | Type | Text |
---|---|---|---|
ownership | Boolean | Whether the wallet owns at least one NFT from the provided contract | |
wallet.address | String | The wallet address that was checked | |
wallet.verified | Boolean | Whether wallet ownership was also verified.
false if you provided wallet.address to the verification request without passing a HMAC. | |
data.value | String | Arbitrary data that was passed to the verification request | |
data.verified | Boolean | true if a valid data HMAC was provided.
false if a HMAC was not provided.
Request will throw an error if an invalid HMAC was provided. | |
at | String | Unix millis timestamp of the verification |
asset
has some basic information about the contract verified.Additional fields may be returned depending on the chain and contract. You should expect at least the below, but all available data will be returned (e.g
family
for some Solana NFTs and description
if implemented by contract creator).Property | Required | Type | Description |
---|---|---|---|
chain |
| Contract chain | |
network | Solana
Ethereum
Polygon
| Contract network | |
contract | String | Contract address | |
symbol | String | Symbol declared in the metadata |
tokens
is an array of NFTs from the provided contract that the wallet owns. If the user owns no NFTs from the contract then this array will be empty. NFTs from other contracts are not returned (you should make another request to fetch results from multiple collections).metadata
key includes the raw token metadata, and will vary depending on the contract implementation. Other than checks to ensure reasonable data size, there is no additional processing or normalisation done.Property | Required | Type | Description |
---|---|---|---|
id | String | Ethereum & Polygon
Token ID
Solana
Address of the token | |
uri | URL | Source URL of off-chain metadata | |
metadata | Object |
unlockables
is an array of valid unlockables which were requested. Any unlockables which aren't found or don't match the chain/contract settings will be silently ignored. Property | Required | Type | Description |
---|---|---|---|
name | String | Name of the unlockable | |
type |
| Type of unlockable | |
content |
| Image, video and file unlockables will return a pre-signed URL that can be used to access the file.
JSON unlockables will return an Object.
Text unlockables will return a String.
Webhook unlockables will return an Object if the Content-Type header is application/json , otherwise will return a String. | |
createdAt | Date | Creation date | |
updatedAt | Date | Last modification date | |
error | Object | Webhook unlockables which throw an error will return an object with code and message properties (see Errors) |
Included in the results object is a
signed
property. This is a JSON Web Token signed with your secret key, where the payload is the full results object excluding unlockables
.You can pass this string to your backend and verify it using any JWT library. Below is an example using the
jsonwebtoken
library.import jwt from 'jsonwebtoken'
try {
// Pass the `signed` property to your backend
const signed = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJpZmllZCI6eyJvd25lcnNoaXAiOnRydWUsIndhbGxldCI6dHJ1ZSwiYWRkcmVzcyI6InNuZWtzcHJMb0h4eFJBeUxTOVc0b1ppYlhSSlg5UTY5bTFCbnYyaFJDQUUiLCJhdCI6IjE2NDM3MzI4ODgifSwiYXNzZXQiOnsiY2hhaW4iOiJTT0wiLCJjb250cmFjdCI6IjZwOXhwb2ttRThQY0s0VkJ3Qms2bmY2UFlvQndXVURzWXhqTXk3ZXJSanB3Iiwic3ltYm9sIjoiU05FSyJ9LCJvd25lZCI6W3siaWQiOm51bGwsImFkZHJlc3MiOiJGdVNya3JVbkdmMktSSGVNdkVGaDJqcEo1NU5Id2ZoM3N5aWZlYlZvUEJEUyIsIm1ldGFkYXRhIjp7Im5hbWUiOiJTcGFjZSBTbmVrcyAjMjc5IiwiZGVzY3JpcHRpb24iOiJBbiB1bHRyYSBleGNsdXNpdmUgc29jaWV0eSBvZiAxLDAyNCBoaWdobHkgZXZvbHZlZCBTbmVrcywgdGhhdCB0cmF2ZWxsZWQgdGhlIGNvc21vcyB0byBvcGVuIHRoZSBEaWFtb25kIEhhbmRzIEhvdGVsLiIsImltYWdlIjoiaHR0cHM6Ly93d3cuYXJ3ZWF2ZS5uZXQvNllCZHZHSEF5TmM0d0xwNGhRRER2VC0yeXRNSGFrblZUUjU5Y3dwdzhkST9leHQ9cG5nIiwiY29sbGVjdGlvbiI6eyJuYW1lIjoiU3BhY2UgU25la3MiLCJmYW1pbHkiOiJEaWFtb25kIEhhbmRzIEhvdGVsIn0sImF0dHJpYnV0ZXMiOlt7InRyYWl0X3R5cGUiOiJPdGhlciIsInZhbHVlIjoiQmx1ZSBMYXNlciJ9XX19XX0.JnxguNAeT37upXgx9R_8-vGS4WiVQ487F3RttDubJr0"
const result = jwt.verify(signed, "sk_YOUR_SECRET_KEY")
} catch (err) {
// Signature has been tampered with
}
You can listen for events from Verify to update the state of your application.
verify.on('opened', () => {
// The verify modal was opened
})
verify.on('closed', () => {
// The verify modal was closed
})
verify.on('contract_metadata', (data) => {
// Metadata for the contract has been loaded
})
verify.on('wallet_connected', data => {
// When the users wallet has been connected successfully
})
verify.on('wallet_verified', data => {
// The user has signed the nonce and it was valid
})
verify.on('results', data => {
// IMPORTANT: this event does not confirm the user owns the
// requested NFT. You should check the event data which
// follows the same format as the results object
})
verify.on('error', data => {
// There was an error during verification. These errors are not
// nessecarily fatal as the user can retry within the request.
// The main verify.nft() promise will reject in the event of a
// fatal error.
})
Last modified 1yr ago