Skip to main content

How to use the SDK?

Requirements

Before you start with this demo you require:

  • An extension of Metamask installed in your browser
  • node and npm needs to be installed
  • The Nevermined artifacts, you can find the script here. To use the script run ./download-artifacts.sh [VERSION OF THE CONTRACT] [NETWORK]

Let's start with the app config file

The first file that you need to create is the config.ts file which contains all the options needed to initialize the Nevermined SDK.

import { NeverminedOptions } from '@nevermined-io/sdk'
import { ethers } from 'ethers'

// The web3 endpoint of the blockchain network to connect to, could be an Infura endpoint, Quicknode, or any other web3 provider
export const web3ProviderUri = process.env.REACT_APP_NODE_URI || 'https://sepolia-rollup.arbitrum.io/rpc'

// The url to a Nevermined node. It could be your own if you run a Nevermined Node
export const neverminedNodeUri =
process.env.REACT_APP_GATEWAY_URI || 'https://node.testing.nevermined.app'

// The public address of the above Node
export const neverminedNodeAddress =
process.env.REACT_APP_GATEWAY_ADDRESS || '0x5838B5512cF9f12FE9f2beccB20eb47211F9B0bc'

// The url of the marketplace api if you connect to one. It could be your own service if you run a Marketplace API
export const marketplaceUri = 'https://marketplace-api.testing.nevermined.app'

// The url of the The Graph deployment of Nevermined
const graphHttpUri = process.env.GRAPH_HTTP_URI || 'https://api.thegraph.com/subgraphs/name/nevermined-io/public'

// represent USDC token in arbitrum-sepolia that can be claimed in the faucet https://faucet.circle.com/
export const erc20TokenAddress = process.env.ERC20_TOKEN_ADDRESS || '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'

// The Chain Id of the network where we are connecting
export const acceptedChainId = process.env.REACT_APP_ACCEPTED_CHAIN_ID || '421614'

// The React application URL
export const rootUri = process.env.REACT_APP_ROOT_URI || 'http://localhost:3445'

export const appConfig: NeverminedOptions = {
//@ts-ignore
web3ProviderUri: typeof window !== 'undefined' ? window.ethereum : new ethers.providers.JsonRpcProvider(nodeUri),
neverminedNodeUri,
neverminedNodeAddress,
graphHttpUri,
marketplaceAuthToken: localStorage.getItem('marketplaceApiToken'),
marketplaceUri,
artifactsFolder: `${rootUri}/contracts`,
}

The example file

The example file src/example/index.tsx contains all the basic logic to handle a NFT1155 as a component. It outlines each functionality and component in detail.

SingleAsset

It shows the content of the ddo object published

const SingleAsset = ({ddo}: {ddo: DDO}) => {
return (
<>
<UiLayout>
<UiText className={b('detail')} variants={['bold']}>Asset {ddo.id.slice(0, 10)}...:</UiText>
</UiLayout>
<UiText className={b('ddo')} variants={['detail']}>{JSON.stringify(ddo)}</UiText>
</>
)
}

PublishAsset

It renders a button used to publish a new NFT

const PublishAsset = ({onPublish, }: {onPublish: () => void }) => {
return (
<>
<UiButton className={b('mint')} type='secondary' onClick={onPublish}>
mint
</UiButton>
</>
)
}

loginMetamask

We need a function to login to metamask when it isn't yet

const loginMarketplace = async (sdk: Nevermined, account: Account) => {
const clientAssertion = await sdk.utils.jwt.generateClientAssertion(account)
await sdk.services.marketplace.login(clientAssertion)
}

BuyAsset

The BuyAsset component will display the button buy in order to buy the asset if the wallet account is not a NFT1155 holder. Otherwise, the owner will display a download button to download the NFT asset

const BuyAsset = ({ddo, sdk, account}: {ddo: DDO, sdk: Nevermined, account: Account}) => {
const [ownNFT1155, setOwnNFT1155] = useState(false)
const [isBought, setIsBought] = useState(false)
const [owner, setOwner] = useState('')

useEffect(() => {
(async () => {
const balance = await sdk.nfts1155.balance(ddo.id, account)
const nftBalance = BigNumber.from(balance).toNumber()
setOwnNFT1155(nftBalance > 0)
setOwner(await sdk.assets.owner(ddo.id))
})()
}, [account, isBought])


const onBuy = async () => {
await loginMarketplace(sdk, account)

try {
const agreementId = await sdk.nfts1155.order(ddo.id, BigNumber.from(1), account)
const transferResult = await sdk.nfts1155.transferForDelegate(
agreementId,
owner,
account.getId(),
BigNumber.from(1),
1155,
)

setIsBought(Boolean(transferResult))
} catch (error) {
Logger.error(error)
}
}

const onDownload = async () => {
try {
await sdk.nfts1155.access(ddo.id, account)
} catch (error) {
Logger.error(error)
}
}

return (
<UiLayout className={b('buy')}>
{ownNFT1155 ? (
<UiButton type='secondary' onClick={onDownload}>
Download NFT
</UiButton>
) : (
owner !== account.getId() ?
<UiButton type='secondary' onClick={onBuy}>
buy
</UiButton>
: <span>The owner cannot buy, please change the account to buy the NFT asset</span>
)}
</UiLayout>
)
}

App

The main component of the example, it pulls the rest of the components and also includes the function onPublish with the logic to publish a NFT1155 which is transferred as a parameter to the component PublisAsset

const App = ({config}: {config: Config }) => {
const [sdk, setSdk] = useState<Nevermined>({} as Nevermined)
const [account, setAccount] = useState<Account>(undefined as Account)
const [ddo, setDDO] = useState<DDO>({} as DDO)
const [walletAddress, setWalletAddress] = useState('')

const loginMetamask = async () => {
const response = await (window as any)?.ethereum?.request?.({
method: "eth_requestAccounts",
})

setWalletAddress(ethers.utils.getAddress(response[0]))
}

useEffect(() => {
(window as any)?.ethereum?.on("accountsChanged", (newAccount: string[]) => {
if (newAccount && newAccount.length > 0) {
setWalletAddress(
ethers.utils.getAddress(newAccount[0])
)
} else {
setWalletAddress("")
console.log("No Account found!")
}
})

(async() => {
const provider = new ethers.providers.Web3Provider((window as any).ethereum)
const accounts = await provider.listAccounts()
setWalletAddress(
accounts?.length ? ethers.utils.getAddress(accounts[0]) : ""
)
})()
}, [])

useEffect(() => {
if(walletAddress) {
(async () => {
try {
const nvm = await Nevermined.getInstance(config)
const accounts = await nvm.accounts.list()

setAccount(accounts[0])
setSdk(nvm)
} catch(error) {
console.log(error)
}
})()
}
}, [walletAddress])

const publishNFT1155 = async (nodeAddress: string, accountWallet: Account, metadata: MetaData, royaltyAttributes: RoyaltyAttributes, assetPrice: AssetPrice) => {
const nftAttributes = NFTAttributes.getNFT1155Instance({
metadata,
serviceTypes: ['nft-sales', 'nft-access'],
amount: BigNumber.from(1),
cap: BigNumber.from(100),
royaltyAttributes,
preMint: true,
nftContractAddress: sdk.nfts1155.nftContract.address,
providers: [nodeAddress],
price: assetPrice
})

const ddo = await sdk.nfts1155.create(nftAttributes, accountWallet)

return ddo
}

const onPublish = async () => {
try {
const assetPriceMap = new Map([
[account.getId(), BigNumber.from(1)]
])

const assetPrice = new AssetPrice(assetPriceMap)
const royaltyAttributes = {
royaltyKind: RoyaltyKind.Standard,
scheme: getRoyaltyScheme(sdk, RoyaltyKind.Standard),
amount: 0,
}

const networkFee = await sdk.keeper.nvmConfig.getNetworkFee()
const feeReceiver = await sdk.keeper.nvmConfig.getFeeReceiver()

assetPrice.addNetworkFees(feeReceiver, BigNumber.from(networkFee))
assetPrice.setTokenAddress(ERC_TOKEN)

const metadata: MetaData = {
main: {
name: '',
files: [{
index: 0,
contentType: 'application/json',
url: 'https://uploads5.wikiart.org/00268/images/william-holbrook-beard/the-bear-dance-1870.jpg'
}],
type: 'dataset',
author: '',
license: '',
dateCreated: new Date().toISOString(),
}
}

await loginMarketplace(sdk, account)

const response = await publishNFT1155(config.neverminedNodeAddress, account, metadata, royaltyAttributes, assetPrice)

setDDO(response as DDO)
} catch (error) {
console.log('error', error)
}
}

return (
<div className={b('container')}>
<UiLayout>
{account ?
<>
<UiText variants={['bold']} className={b('detail')}>Wallet address:</UiText>
<UiText>{account.getId()}</UiText>
</> :
<UiButton type='secondary' onClick={loginMetamask}>Connect To MM</UiButton>
}

{walletAddress && !ddo.id && (
<PublishAsset onPublish={onPublish} />
)}

{ddo?.id && (
<>
<SingleAsset ddo={ddo}/>
<BuyAsset ddo={ddo} sdk={sdk} account={account}/>
</>
)}

</UiLayout>
</div>
)
}

export default App

Complete example file

Now let's put everything together.

import React, {useEffect, useState} from 'react'
import { Nevermined, Account, NeverminedOptions, Logger, DDO, MetaData, AssetPrice, RoyaltyKind, getRoyaltyScheme, RoyaltyAttributes, BigNumber, NFTAttributes } from '@nevermined-io/sdk'
import { UiLayout, UiText, UiButton, BEM } from '@nevermined-io/styles'
import { ethers } from 'ethers'
import { appConfig } from './config'
import styles from './styles.module.scss'
const b = BEM('demo', styles)

const ERC_TOKEN = '0xe11a86849d99f524cac3e7a0ec1241828e332c62'

Logger.setLevel(3)

const loginMarketplace = async (sdk: Nevermined, account: Account) => {
const clientAssertion = await sdk.utils.jwt.generateClientAssertion(account)
await sdk.services.marketplace.login(clientAssertion)
}

const PublishAsset = ({onPublish, }: {onPublish: () => void }) => {
return (
<>
<UiButton className={b('mint')} type='secondary' onClick={onPublish}>
mint
</UiButton>
</>
)
}

const SingleAsset = ({ddo}: {ddo: DDO}) => {
return (
<>
<UiLayout>
<UiText className={b('detail')} variants={['bold']}>Asset {ddo.id.slice(0, 10)}...:</UiText>
</UiLayout>
<UiText className={b('ddo')} variants={['detail']}>{JSON.stringify(ddo)}</UiText>
</>
)
}

const BuyAsset = ({ddo, sdk, account}: {ddo: DDO, sdk: Nevermined, account: Account}) => {
const [ownNFT1155, setOwnNFT1155] = useState(false)
const [isBought, setIsBought] = useState(false)
const [owner, setOwner] = useState('')

useEffect(() => {
(async () => {
const balance = await sdk.nfts1155.balance(ddo.id, account)
const nftBalance = BigNumber.from(balance).toNumber()
setOwnNFT1155(nftBalance > 0)
setOwner(await sdk.assets.owner(ddo.id))
})()
}, [account, isBought])


const onBuy = async () => {
await loginMarketplace(sdk, account)

try {
const agreementId = await sdk.nfts1155.order(ddo.id, BigNumber.from(1), account)
const transferResult = await sdk.nfts1155.transferForDelegate(
agreementId,
owner,
account.getId(),
BigNumber.from(1),
1155,
)

setIsBought(Boolean(transferResult))
} catch (error) {
Logger.error(error)
}
}

const onDownload = async () => {
try {
await sdk.nfts1155.access(ddo.id, account)
} catch (error) {
Logger.error(error)
}
}

return (
<UiLayout className={b('buy')}>
{ownNFT1155 ? (
<UiButton type='secondary' onClick={onDownload}>
Download NFT
</UiButton>
) : (
owner !== account.getId() ?
<UiButton type='secondary' onClick={onBuy}>
buy
</UiButton>
: <span>The owner cannot buy, please change the account to buy the NFT asset</span>
)}
</UiLayout>
)
}


const App = ({config}: {config: NeverminedOptions }) => {
const [sdk, setSdk] = useState<Nevermined>({} as Nevermined)
const [account, setAccount] = useState<Account>(undefined as Account)
const [ddo, setDDO] = useState<DDO>({} as DDO)
const [walletAddress, setWalletAddress] = useState('')

const loginMetamask = async () => {
// eslint-disable-next-line
const response = await (window as any)?.ethereum?.request?.({
method: "eth_requestAccounts",
})

setWalletAddress(ethers.utils.getAddress(response[0]))
}

useEffect(() => {
// eslint-disable-next-line
(window as any)?.ethereum?.on("accountsChanged", (newAccount: string[]) => {
if (newAccount && newAccount.length > 0) {
setWalletAddress(
ethers.utils.getAddress(newAccount[0])
)
} else {
setWalletAddress("")
console.log("No Account found!")
}
});

(async() => {
// eslint-disable-next-line
const provider = new ethers.providers.Web3Provider((window as any).ethereum)
const accounts = await provider.listAccounts()
setWalletAddress(
accounts?.length ? ethers.utils.getAddress(accounts[0]) : ""
)
})()
}, [])

useEffect(() => {
if(walletAddress) {
(async () => {
try {
const nvm = await Nevermined.getInstance(config)
const accounts = await nvm.accounts.list()

setAccount(accounts[0])
setSdk(nvm)
} catch(error) {
console.log(error)
}
})()
}
}, [walletAddress])

const publishNFT1155 = async (nodeAddress: string, accountWallet: Account, metadata: MetaData, royaltyAttributes: RoyaltyAttributes, assetPrice: AssetPrice) => {
const nftAttributes = NFTAttributes.getNFT1155Instance({
metadata,
serviceTypes: ['nft-sales', 'nft-access'],
amount: BigNumber.from(1),
cap: BigNumber.from(100),
royaltyAttributes,
preMint: true,
nftContractAddress: sdk.nfts1155.nftContract.address,
providers: [nodeAddress],
price: assetPrice
})

const ddo = await sdk.nfts1155.create(nftAttributes, accountWallet)

return ddo
}

const onPublish = async () => {
try {
const assetPriceMap = new Map([
[account.getId(), BigNumber.from(1)]
])

const assetPrice = new AssetPrice(assetPriceMap)
const royaltyAttributes = {
royaltyKind: RoyaltyKind.Standard,
scheme: getRoyaltyScheme(sdk, RoyaltyKind.Standard),
amount: 0,
}

const networkFee = await sdk.keeper.nvmConfig.getNetworkFee()
const feeReceiver = await sdk.keeper.nvmConfig.getFeeReceiver()

assetPrice.addNetworkFees(feeReceiver, BigNumber.from(networkFee))
assetPrice.setTokenAddress(ERC_TOKEN)

const metadata: MetaData = {
main: {
name: '',
files: [{
index: 0,
contentType: 'application/json',
url: 'https://uploads5.wikiart.org/00268/images/william-holbrook-beard/the-bear-dance-1870.jpg'
}],
type: 'dataset',
author: '',
license: '',
dateCreated: new Date().toISOString(),
}
}

await loginMarketplace(sdk, account)

const response = await publishNFT1155(config.neverminedNodeAddress, account, metadata, royaltyAttributes, assetPrice)

setDDO(response as DDO)
} catch (error) {
console.log('error', error)
}
}

return (
<div className={b('container')}>
<UiLayout>
{account ?
<>
<UiText variants={['bold']} className={b('detail')}>Wallet address:</UiText>
<UiText>{account.getId()}</UiText>
</> :
<UiButton type='secondary' onClick={loginMetamask}>Connect To MM</UiButton>
}

{walletAddress && !ddo.id && (
<PublishAsset onPublish={onPublish} />
)}

{ddo?.id && (
<>
<SingleAsset ddo={ddo}/>
<BuyAsset ddo={ddo} sdk={sdk} account={account}/>
</>
)}

</UiLayout>
</div>
)
}

export default App

Styling

In the path src/examples/example.module.scss you will find some styles to improve the UI of the app.

@import '~@nevermined-io/styles/lib/cjs/styles/index.scss'

.example {
@include component;

&__container {
padding: 25px 0 0 25px;
}

&__mint {
margin-left: 10px;
}

&__detail {
margin-right: 5px;
}

&__ddo {
line-height: 16px;
}

&__buy {
margin-top: 20px;
}
}

The index file

The src/indes.tsx file call the App component with the configurations set

import '@nevermined-io/styles/lib/esm/styles/globals.scss'
import '@nevermined-io/styles/lib/esm/index.css'
import React from 'react'
import ReactDOM from 'react-dom'
import { appConfig } from './config'
import App from './app'

ReactDOM.render(<App config={appConfig}/>, document.getElementById('root') as HTMLElement)