Example
Before to start
If you project is using React
we recommend to use Catalog to make everything much easier, SDK is the low level of it.
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 { Config } from '@nevermined-io/nevermined-sdk-js'
import { ethers } from 'ethers'
export const web3ProviderUri = process.env.REACT_APP_NODE_URI || 'https://matic-mumbai.chainstacklabs.com'
export const nodeAddress =
process.env.REACT_APP_GATEWAY_ADDRESS || '0x5838B5512cF9f12FE9f2beccB20eb47211F9B0bc'
export const neverminedNodeUri =
process.env.REACT_APP_GATEWAY_URI || 'https://node.mumbai.public.nevermined.network'
export const acceptedChainId = process.env.REACT_APP_ACCEPTED_CHAIN_ID || '80001' // for Mumbai
export const rootUri = process.env.REACT_APP_ROOT_URI || 'http://localhost:3445'
export const marketplaceUri = 'https://marketplace-api.mumbai.public.nevermined.network'
const graphHttpUri = process.env.GRAPH_HTTP_URI || 'https://api.thegraph.com/subgraphs/name/nevermined-io/public'
// represent USDC token in mumbai that can be claimed in the faucet https://calibration-faucet.filswan.com/#/dashboard
export const erc20TokenAddress = process.env.ERC20_TOKEN_ADDRESS || '0xe11a86849d99f524cac3e7a0ec1241828e332c62'
export const appConfig: Config = {
//@ts-ignore
web3Provider: typeof window !== 'undefined' ? window.ethereum : new ethers.providers.JsonRpcProvider(nodeUri),
neverminedNodeUri,
nodeAddress,
graphHttpUri,
marketplaceAuthToken: localStorage.getItem('marketplaceApiToken'),
marketplaceUri,
artifactsFolder: `${rootUri}/contracts`,
newGateway: true,
}
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.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.nfts.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.nfts.order(ddo.id, BigNumber.from(1), account)
const transferResult = await sdk.nfts.transferForDelegate(
agreementId,
owner,
account.getId(),
BigNumber.from(1),
1155,
)
setIsBought(Boolean(transferResult))
} catch (error) {
Logger.error(error)
}
}
const onDownload = async () => {
try {
await sdk.nfts.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, assetRewards: AssetRewards) => {
const transferNftCondition = sdk.keeper.conditions.transferNftCondition
const transferNftConditionContractReceipt = await sdk.nfts.setApprovalForAll(transferNftCondition.address, true, accountWallet)
Logger.log(`Contract Receipt for approved transfer NFT: ${transferNftConditionContractReceipt}`)
const gateawayContractReceipt = await sdk.nfts.setApprovalForAll(nodeAddress, true, accountWallet)
Logger.log(`Contract Receipt for approved gateway: ${gateawayContractReceipt}`)
const ddo = await sdk.nfts.createWithRoyalties(
metadata,
accountWallet,
BigNumber.from(100),
royaltyAttributes,
assetRewards,
BigNumber.from(1),
"0xe11a86849d99f524cac3e7a0ec1241828e332c62",
true,
)
return ddo
}
const onPublish = async () => {
try {
// Here we set the rewards that will receive the publisher
const assetRewardsMap = new Map([
[account.getId(), BigNumber.from(1)]
])
const assetRewards = new AssetRewards(assetRewardsMap)
// This set the royalties that will receive for each sold
const royaltyAttributes = {
royaltyKind: RoyaltyKind.Standard,
scheme: getRoyaltyScheme(sdk, RoyaltyKind.Standard),
amount: 0,
}
// We need to set network fees
const networkFee = await sdk.keeper.nvmConfig.getNetworkFee()
const feeReceiver = await sdk.keeper.nvmConfig.getFeeReceiver()
assetRewards.addNetworkFees(feeReceiver, BigNumber.from(networkFee))
const metadata: MetaData = {
main: {
name: '',
files: [{
index: 0,
contentType: 'application/json',
url: 'https://github.com/nevermined-io/docs/blob/main/docs/architecture/specs/examples/did/v0.4/ddo-example.json'
}],
type: 'dataset',
author: '',
license: '',
dateCreated: new Date().toISOString(),
}
}
await loginMarketplace(sdk, account)
const response = await publishNFT1155(config.nodeAddress, account, metadata, royaltyAttributes, assetRewards)
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, Config, Logger, DDO, MetaData } from '@nevermined-io/nevermined-sdk-js'
import AssetRewards from '@nevermined-io/nevermined-sdk-js/dist/node/models/AssetRewards'
import { RoyaltyKind, getRoyaltyScheme, RoyaltyAttributes } from '@nevermined-io/nevermined-sdk-js/dist/node/nevermined/Assets'
import { UiLayout, UiText, UiButton, BEM } from '@nevermined-io/styles'
import { ethers } from 'ethers'
import { appConfig } from './config'
import styles from './styles.module.scss'
import BigNumber from '@nevermined-io/nevermined-sdk-js/dist/node/utils/BigNumber'
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.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.nfts.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.nfts.order(ddo.id, BigNumber.from(1), account)
const transferResult = await sdk.nfts.transferForDelegate(
agreementId,
owner,
account.getId(),
BigNumber.from(1),
1155,
)
setIsBought(Boolean(transferResult))
} catch (error) {
Logger.error(error)
}
}
const onDownload = async () => {
try {
await sdk.nfts.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: 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, assetRewards: AssetRewards) => {
const transferNftCondition = sdk.keeper.conditions.transferNftCondition
const transferNftConditionContractReceipt = await sdk.nfts.setApprovalForAll(transferNftCondition.address, true, accountWallet)
Logger.log(`Contract Receipt for approved transfer NFT: ${transferNftConditionContractReceipt}`)
const gateawayContractReceipt = await sdk.nfts.setApprovalForAll(nodeAddress, true, accountWallet)
Logger.log(`Contract Receipt for approved gateway: ${gateawayContractReceipt}`)
const ddo = await sdk.nfts.createWithRoyalties(
metadata,
accountWallet,
BigNumber.from(100),
royaltyAttributes,
assetRewards,
BigNumber.from(1),
"0xe11a86849d99f524cac3e7a0ec1241828e332c62",
true,
)
return ddo
}
const onPublish = async () => {
try {
const assetRewardsMap = new Map([
[account.getId(), BigNumber.from(1)]
])
const assetRewards = new AssetRewards(assetRewardsMap)
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()
assetRewards.addNetworkFees(feeReceiver, BigNumber.from(networkFee))
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.nodeAddress, account, metadata, royaltyAttributes, assetRewards)
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)