Skip to content

Commit

Permalink
Merge pull request #139 from walt-id/feat/soulboundNFT
Browse files Browse the repository at this point in the history
Feat/soulbound nft
  • Loading branch information
SuperBatata authored Aug 27, 2023
2 parents 6e10f72 + 5f4a25d commit 7d46c94
Show file tree
Hide file tree
Showing 13 changed files with 726 additions and 9 deletions.
459 changes: 459 additions & 0 deletions src/main/java/smart_contract_wrapper/SoulBoundTest.java

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/main/kotlin/id/walt/nftkit/Values.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ object Values {
const val POLYGON_TESTNET_MUMBAI_CHAIN_ID: Long = 80001
const val MOONBEAM_MAINNET_CHAIN_ID: Long = 1284
const val ASTAR_MAINNET_CHAIN_ID: Long = 592
const val SHIMMEREVM_TESTNET_CHAIN_ID: Long = 1072

const val ETHEREUM_MAINNET_SCAN_API_URL= "api.etherscan.io"
const val ETHEREUM_TESTNET_GOERLI_SCAN_API_URL = "api-goerli.etherscan.io"
Expand All @@ -23,6 +24,7 @@ object Values {
const val ETHEREUM_TESTNET_SEPOLIA_BLOCK_EXPLORER_URL = "https://sepolia.etherscan.io/"
const val POLYGON_MAINNET_BLOCK_EXPLORER_URL = "https://polygonscan.com"
const val POLYGON_TESTNET_MUMBAI_BLOCK_EXPLORER_URL = "https://mumbai.polygonscan.com"
const val SHIMMEREVM_TESTNET_BLOCK_EXPLORER_URL = "https://explorer.evm.testnet.shimmer.network"

const val ETHEREUM_MAINNET_ALCHEMY_URL = "https://eth-mainnet.alchemyapi.io/v2/"
const val ETHEREUM_TESTNET_GOERLI_ALCHEMY_URL = "https://eth-goerli.g.alchemy.com/v2/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ object Erc721TokenStandard : IErc721TokenStandard {
EVMChain.MUMBAI -> Values.POLYGON_TESTNET_MUMBAI_CHAIN_ID
EVMChain.ASTAR -> Values.ASTAR_MAINNET_CHAIN_ID
EVMChain.MOONBEAM -> Values.MOONBEAM_MAINNET_CHAIN_ID
EVMChain.SHIMMEREVM -> Values.SHIMMEREVM_TESTNET_CHAIN_ID
}
val transactionManager: TransactionManager = RawTransactionManager(
web3j, credentials, chainId
Expand All @@ -317,4 +318,4 @@ object Erc721TokenStandard : IErc721TokenStandard {
}


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package id.walt.nftkit.chains.evm.erc721
import id.walt.nftkit.services.Chain
import id.walt.nftkit.services.EVMChain
import org.web3j.abi.datatypes.Address
import org.web3j.abi.datatypes.Bool
import org.web3j.abi.datatypes.DynamicBytes
import org.web3j.abi.datatypes.Utf8String
import org.web3j.abi.datatypes.generated.Uint256
import org.web3j.protocol.core.RemoteFunctionCall
import org.web3j.protocol.core.methods.response.TransactionReceipt
import java.math.BigInteger
interface ISoulBoundTokenStandard {

fun safeMint(chain: EVMChain, contractAddress: String,to: String, uri: String): TransactionReceipt?
fun ownerOf(chain: EVMChain, contractAddress: String, tokenId: Uint256):String?

fun name(chain: EVMChain, contractAddress: String): String?

fun symbol(chain: EVMChain, contractAddress: String): String?

fun tokenURI(chain: EVMChain, contractAddress: String, tokenId: Uint256): String?

fun balanceOf(chain: EVMChain, contractAddress: String, owner: Address): BigInteger?

fun safeTransferFrom(chain: EVMChain, contractAddress: String, from: Address, to: Address, tokenId: Uint256, signedAccount: String?): TransactionReceipt

fun supportsInterface(chain: EVMChain, contractAddress: String): Boolean

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package id.walt.nftkit.chains.evm.erc721

import id.walt.nftkit.Values
import id.walt.nftkit.WaltIdGasProvider
import id.walt.nftkit.services.*
import id.walt.nftkit.utilis.providers.ProviderFactory
import org.web3j.abi.datatypes.Address
import org.web3j.abi.datatypes.generated.Bytes4
import org.web3j.abi.datatypes.generated.Uint256
import org.web3j.crypto.Credentials
import org.web3j.protocol.core.RemoteCall
import org.web3j.protocol.core.methods.response.TransactionReceipt
import org.web3j.tx.RawTransactionManager
import org.web3j.tx.TransactionManager
import org.web3j.tx.gas.ContractGasProvider
import org.web3j.utils.Numeric
import smart_contract_wrapper.SoulBoundTest
import java.math.BigInteger

object SoulBoundTokenStandard : ISoulBoundTokenStandard {






private fun loadContract(chain: EVMChain, address: String, signedAccount: String? ="") : SoulBoundTest {
val web3j = ProviderFactory.getProvider(chain)?.getWeb3j()

val privateKey: String = if(signedAccount == null || "" == (signedAccount)){
WaltIdServices.loadChainConfig().privateKey
}else{
val lowercaseAddress= WaltIdServices.loadAccountKeysConfig().keys.mapKeys { it.key.lowercase() }
lowercaseAddress[signedAccount.lowercase()]!!
}

val credentials: Credentials = Credentials.create(privateKey)

val gasProvider: ContractGasProvider = WaltIdGasProvider
val chainId= when(chain){
EVMChain.ETHEREUM -> Values.ETHEREUM_MAINNET_CHAIN_ID
EVMChain.GOERLI -> Values.ETHEREUM_TESTNET_GOERLI_CHAIN_ID
EVMChain.SEPOLIA -> Values.ETHEREUM_TESTNET_SEPOLIA_CHAIN_ID
EVMChain.POLYGON -> Values.POLYGON_MAINNET_CHAIN_ID
EVMChain.MUMBAI -> Values.POLYGON_TESTNET_MUMBAI_CHAIN_ID
EVMChain.ASTAR -> Values.ASTAR_MAINNET_CHAIN_ID
EVMChain.MOONBEAM -> Values.MOONBEAM_MAINNET_CHAIN_ID
EVMChain.SHIMMEREVM -> Values.SHIMMEREVM_TESTNET_CHAIN_ID
}
val transactionManager: TransactionManager = RawTransactionManager(
web3j, credentials, chainId
)
return SoulBoundTest.load(address, web3j,transactionManager,gasProvider)

}


override fun ownerOf(chain: EVMChain, contractAddress: String, tokenId: Uint256): String? {
return loadContract(chain, contractAddress).ownerOf(tokenId).send().value
}

override fun name(chain: EVMChain, contractAddress: String): String? {
val contract = loadContract(chain, contractAddress)
return contract.name().send().value
}

override fun symbol(chain: EVMChain, contractAddress: String): String? {
return loadContract(chain, contractAddress).symbol().send().value
}

override fun tokenURI(chain: EVMChain, contractAddress: String, tokenId: Uint256): String? {
return loadContract(chain, contractAddress).tokenURI(tokenId).send().value
}

override fun balanceOf(chain: EVMChain, contractAddress: String, owner: Address): BigInteger? {
return loadContract(chain, contractAddress).balanceOf(owner).send().value
}

override fun safeTransferFrom(
chain: EVMChain,
contractAddress: String,
from: Address,
to: Address,
tokenId: Uint256,
signedAccount: String?
): TransactionReceipt {
TODO("Not yet implemented")
}

override fun safeMint(chain: EVMChain, contractAddress: String, to: String, uri: String): TransactionReceipt? {
return loadContract(chain, contractAddress).safeMint(to, uri).send()
}


fun deployContract(chain: EVMChain) : DeploymentResponse {
val chainId= when(chain){
EVMChain.ETHEREUM -> Values.ETHEREUM_MAINNET_CHAIN_ID
EVMChain.GOERLI -> Values.ETHEREUM_TESTNET_GOERLI_CHAIN_ID
EVMChain.SEPOLIA -> Values.ETHEREUM_TESTNET_SEPOLIA_CHAIN_ID
EVMChain.POLYGON -> Values.POLYGON_MAINNET_CHAIN_ID
EVMChain.MUMBAI -> Values.POLYGON_TESTNET_MUMBAI_CHAIN_ID
EVMChain.ASTAR -> Values.ASTAR_MAINNET_CHAIN_ID
EVMChain.MOONBEAM -> Values.MOONBEAM_MAINNET_CHAIN_ID
EVMChain.SHIMMEREVM -> Values.SHIMMEREVM_TESTNET_CHAIN_ID
}

val web3j = ProviderFactory.getProvider(chain)?.getWeb3j()
val credentials: Credentials = Credentials.create(WaltIdServices.loadChainConfig().privateKey)
val gasProvider: ContractGasProvider = WaltIdGasProvider
val remotCall: RemoteCall<SoulBoundTest>
val transactionManager: TransactionManager = RawTransactionManager(
web3j, credentials, chainId
)
remotCall = SoulBoundTest.deploy(web3j, transactionManager, gasProvider)

val contract = remotCall.send()

val url = WaltIdServices.getBlockExplorerUrl(chain)
val ts = TransactionResponse(
contract.transactionReceipt.get().transactionHash,
"$url/tx/${contract.transactionReceipt.get().transactionHash}"
)
return DeploymentResponse(ts, contract.contractAddress, "$url/address/${contract.contractAddress}")
}


override fun supportsInterface(chain: EVMChain, contractAddress: String) : Boolean {
val erc721URIStorageWrapper = loadContract(chain, contractAddress)
val data = Numeric.hexStringToByteArray("0x5b5e139f") // ERC721 interface id
val interfaceId = Bytes4(data)
return erc721URIStorageWrapper.supportsInterface(interfaceId).send().value
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/id/walt/nftkit/rest/NftController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ object NftController {
}.json<DeploymentResponse>("200") { it.description("Transaction ID and smart contract address") }







fun mint(ctx: Context) {
val mintReq = ctx.bodyAsClass(MintRequest::class.java)
val chain = ctx.pathParam("chain")
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/id/walt/nftkit/rest/NftKitApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ object NftKitApi {
"chain/{chain}/contract/deploy",
documented(NftController.deployDocs(), NftController::deploy)
)

post(
"chain/{chain}/contract/{contractAddress}/token/mint",
documented(NftController.mintDocs(), NftController::mint)
Expand Down
36 changes: 32 additions & 4 deletions src/main/kotlin/id/walt/nftkit/services/NftService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package id.walt.nftkit.services

import id.walt.nftkit.Values
import id.walt.nftkit.chains.evm.erc721.Erc721TokenStandard
import id.walt.nftkit.chains.evm.erc721.SoulBoundTokenStandard
import id.walt.nftkit.metadata.IPFSMetadata
import id.walt.nftkit.metadata.MetadataUri
import id.walt.nftkit.metadata.MetadataUriFactory
Expand Down Expand Up @@ -95,7 +96,8 @@ enum class EVMChain {
SEPOLIA,
MUMBAI,
ASTAR,
MOONBEAM
MOONBEAM,
SHIMMEREVM,
}

enum class TokenStandard {
Expand Down Expand Up @@ -281,9 +283,14 @@ object NftService {
}

fun deploySmartContractToken(chain: EVMChain, parameter: DeploymentParameter, options: DeploymentOptions): DeploymentResponse {
return Erc721TokenStandard.deployContract(chain, parameter, options)
return if (parameter.options.transferable){
Erc721TokenStandard.deployContract(chain, parameter, options)
}else{
SoulBoundTokenStandard.deployContract(chain)
}
}


fun mintToken(
chain: EVMChain,
contractAddress: String,
Expand All @@ -302,6 +309,7 @@ object NftService {
return mintNewToken(parameter.recipientAddress, tokenUri, chain, contractAddress)
}


fun getNftMetadata(chain: EVMChain, contractAddress: String, tokenId: BigInteger): NftMetadata {
var uri = getMetadatUri(chain, contractAddress, tokenId)
if(Common.getMetadataType(uri).equals(MetadataStorageType.ON_CHAIN)){
Expand Down Expand Up @@ -336,6 +344,10 @@ object NftService {
return String()
}

fun ownerOfSoulbound( chain: EVMChain , contractAddress: String , tokenId: Uint256) : String{
return SoulBoundTokenStandard.ownerOf(chain, contractAddress, tokenId).toString()
}

fun transferFrom(chain: EVMChain, contractAddress: String, from: String, to: String, tokenId: BigInteger, signedAccount: String?): TransactionResponse {
val transactionReceipt = Erc721TokenStandard.transferFrom(chain, contractAddress, Address(from), Address(to), Uint256(tokenId), signedAccount)
return Common.getTransactionResponse(chain, transactionReceipt)
Expand Down Expand Up @@ -573,7 +585,7 @@ object NftService {
chain: EVMChain,
contractAddress: String
): MintingResponse {
if (isErc721Standard(chain, contractAddress) == true) {
if (isErc721Standard(chain, contractAddress) && !isSoulBoundStandard(chain , contractAddress)) {
//val erc721TokenStandard = Erc721TokenStandard()
val recipient = Address(recipientAddress)
val tokenUri = Utf8String(metadataUri)
Expand All @@ -587,12 +599,25 @@ object NftService {
TransactionResponse(transactionReceipt!!.transactionHash, "$url/tx/${transactionReceipt.transactionHash}")
val mr = MintingResponse(ts, eventValues?.indexedValues?.get(2)?.value as BigInteger)
return mr
} else {
} else if (isSoulBoundStandard(chain , contractAddress) && isErc721Standard(chain, contractAddress)){
val recipient = recipientAddress
val tokenUri = metadataUri
val transactionReceipt: TransactionReceipt? =
SoulBoundTokenStandard.safeMint(chain, contractAddress, recipient, tokenUri)
val eventValues: EventValues? =
staticExtractEventParameters(Erc721OnchainCredentialWrapper.TRANSFER_EVENT, transactionReceipt?.logs?.get(0))

val url = WaltIdServices.getBlockExplorerUrl(chain)
val ts =
TransactionResponse(transactionReceipt!!.transactionHash, "$url/tx/${transactionReceipt.transactionHash}")
val mr = MintingResponse(ts, eventValues?.indexedValues?.get(2)?.value as BigInteger)
return mr
}
return MintingResponse(null, null)
}



private fun getMetadatUri(chain: EVMChain, contractAddress: String, tokenId: BigInteger): String {
if (isErc721Standard(chain, contractAddress) == true) {
return Erc721TokenStandard.tokenURI(chain, contractAddress, Uint256(tokenId))
Expand All @@ -604,6 +629,9 @@ object NftService {
private fun isErc721Standard(chain: EVMChain, contractAddress: String): Boolean {
return Erc721TokenStandard.supportsInterface(chain, contractAddress)
}
private fun isSoulBoundStandard(chain: EVMChain, contractAddress: String): Boolean {
return SoulBoundTokenStandard.supportsInterface(chain, contractAddress)
}

private fun parseNftEvmMetadataResult(nft: JsonObject): NftMetadata{
var attributes: List<NftMetadata.Attributes>?=null
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/id/walt/nftkit/services/WaltIdServices.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import java.io.File
import java.util.*


data class Providers(val ethereum: String, val goerli: String, val sepolia: String, val polygon: String, val mumbai: String, val astar: String, val moonbeam: String, val opal: String, val unique: String)
data class Providers(val ethereum: String, val goerli: String, val sepolia: String, val polygon: String, val mumbai: String, val astar: String, val moonbeam: String, val opal: String, val unique: String , val shimmerevm: String)

data class ChainConfig(val providers: Providers, val privateKey: String)

Expand Down Expand Up @@ -104,6 +104,7 @@ object WaltIdServices {
EVMChain.SEPOLIA -> Values.ETHEREUM_TESTNET_SEPOLIA_BLOCK_EXPLORER_URL
EVMChain.POLYGON -> Values.POLYGON_MAINNET_BLOCK_EXPLORER_URL
EVMChain.MUMBAI -> Values.POLYGON_TESTNET_MUMBAI_BLOCK_EXPLORER_URL
EVMChain.SHIMMEREVM -> Values.SHIMMEREVM_TESTNET_BLOCK_EXPLORER_URL
else -> {throw Exception("${chain.toString()} is not supported")}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/kotlin/id/walt/nftkit/utilis/providers/IotaWeb3.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package id.walt.nftkit.utilis.providers

import id.walt.nftkit.services.WaltIdServices
import org.web3j.protocol.Web3j
import org.web3j.protocol.http.HttpService

class IotaWeb3 : Web3jInstance {
override fun getWeb3j(): Web3j {
return Web3j.build(HttpService(WaltIdServices.loadChainConfig().providers.shimmerevm))
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package id.walt.nftkit.utilis.providers

import id.walt.nftkit.services.Chain
import id.walt.nftkit.services.EVMChain

object ProviderFactory {
Expand All @@ -13,6 +12,7 @@ object ProviderFactory {
EVMChain.MUMBAI -> MumbaiWeb3()
EVMChain.ASTAR -> AstarWeb3()
EVMChain.MOONBEAM -> MoonbeamWeb3()
EVMChain.SHIMMEREVM -> IotaWeb3()
}
}

5 changes: 3 additions & 2 deletions src/main/resources/walt-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ providers:
moonbeam: "https://rpc.api.moonbeam.network"
unique: "https://rpc.unique.network"
opal: "https://rpc-opal.unique.network"
shimmerevm: https://json-rpc.evm.testnet.shimmer.network


privateKey: "bd4cb3e507f342ee3a710370cef39dda48f17b0a158b0b8dd3f000fbd5b2c2d9"

#privateKey: "bd4cb3e507f342ee3a710370cef39dda48f17b0a158b0b8dd3f000fbd5b2c2d9"
privateKey: "fe96ea369abac7818a890da598d40e3e4709f1150e3ff35910f77f63484cffba"
#privateKey: "a1fcab9b58015f452c9a89d4cde4807a80111ab27142730bfb96be936e576be1"

keys:
Expand Down
Loading

0 comments on commit 7d46c94

Please sign in to comment.