diff --git a/processor/build.gradle b/processor/build.gradle index 316ff608..769b9726 100644 --- a/processor/build.gradle +++ b/processor/build.gradle @@ -18,5 +18,7 @@ dependencies { testImplementation "org.jetbrains.kotlin:kotlin-test:${KOTLIN_VERSION}" testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN_VERSION}" + + implementation 'com.github.ligi:ipfs-api-kotlin:0.15' } diff --git a/processor/src/main/kotlin/org/ethereum/lists/chains/Env.kt b/processor/src/main/kotlin/org/ethereum/lists/chains/Env.kt index 418b9972..ad442631 100644 --- a/processor/src/main/kotlin/org/ethereum/lists/chains/Env.kt +++ b/processor/src/main/kotlin/org/ethereum/lists/chains/Env.kt @@ -2,29 +2,43 @@ package org.ethereum.lists.chains import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi +import io.ipfs.kotlin.IPFS +import io.ipfs.kotlin.IPFSConfiguration +import okhttp3.OkHttpClient import org.ethereum.lists.chains.model.Chain +import java.time.Duration val mandatory_fields = listOf( - "name", - "shortName", - "chain", - "chainId", - "networkId", - "rpc", - "faucets", - "infoURL", - "nativeCurrency" + "name", + "shortName", + "chain", + "chainId", + "networkId", + "rpc", + "faucets", + "infoURL", + "nativeCurrency" ) val optionalFields = listOf( - "slip44", - "ens", - "icon", - "explorers", - "title", - "network", - "parent", - "status" + "slip44", + "ens", + "icon", + "explorers", + "title", + "network", + "parent", + "status" ) val moshi: Moshi = Moshi.Builder().build() val chainAdapter: JsonAdapter = moshi.adapter(Chain::class.java) + +val ipfs by lazy { + IPFS( + IPFSConfiguration( + "http://127.0.0.1:5001/api/v0/", + OkHttpClient.Builder().readTimeout(Duration.ofMinutes(1)).build(), + Moshi.Builder().build() + ) + ) +} \ No newline at end of file diff --git a/processor/src/main/kotlin/org/ethereum/lists/chains/Main.kt b/processor/src/main/kotlin/org/ethereum/lists/chains/Main.kt index 2873836a..16ab0c53 100644 --- a/processor/src/main/kotlin/org/ethereum/lists/chains/Main.kt +++ b/processor/src/main/kotlin/org/ethereum/lists/chains/Main.kt @@ -4,10 +4,15 @@ import com.beust.klaxon.JsonArray import java.io.File import com.beust.klaxon.JsonObject import com.beust.klaxon.Klaxon +import com.squareup.moshi.Moshi +import io.ipfs.kotlin.IPFS +import io.ipfs.kotlin.IPFSConfiguration +import okhttp3.OkHttpClient import org.ethereum.lists.chains.model.* import org.kethereum.erc55.isValid import org.kethereum.model.Address import org.kethereum.rpc.HttpEthereumRPC +import java.time.Duration val parsedShortNames = mutableSetOf() val parsedNames = mutableSetOf() @@ -15,6 +20,7 @@ val parsedNames = mutableSetOf() val basePath = File("..") val dataPath = File(basePath, "_data") val iconsPath = File(dataPath, "icons") +val iconsDownloadPath = File(dataPath, "iconsDownload") val chainsPath = File(dataPath, "chains") private val allFiles = chainsPath.listFiles() ?: error("${chainsPath.absolutePath} must contain the chain json files - but it does not") @@ -22,8 +28,7 @@ private val allChainFiles = allFiles.filter { !it.isDirectory } fun main(args: Array) { - doChecks(doRPCConnect = args.contains("rpcConnect")) - + doChecks(doRPCConnect = args.contains("rpcConnect"), doIconDownload = args.contains("iconDownload")) createOutputFiles() } @@ -83,14 +88,14 @@ private fun createOutputFiles() { File(buildPath, "CNAME").writeText("chainid.network") } -private fun doChecks(doRPCConnect: Boolean) { +private fun doChecks(doRPCConnect: Boolean, doIconDownload: Boolean) { allChainFiles.forEach { checkChain(it, doRPCConnect) } val allIcons = iconsPath.listFiles() ?: return allIcons.forEach { - checkIcon(it) + checkIcon(it, doIconDownload) } allFiles.filter { it.isDirectory }.forEach { _ -> @@ -98,7 +103,7 @@ private fun doChecks(doRPCConnect: Boolean) { } } -fun checkIcon(icon: File) { +fun checkIcon(icon: File, withIconDownload: Boolean) { println("checking Icon " + icon.name) val obj: JsonArray<*> = Klaxon().parseJsonArray(icon.reader()) println("found variants " + obj.size) @@ -113,6 +118,25 @@ fun checkIcon(icon: File) { error("url must start with ipfs://") } + if (withIconDownload) { + + + val iconCID = url.removePrefix("ipfs://") + try { + + println("fetching Icon from IPFS $iconCID") + + val iconBytes = ipfs.get.catBytes(iconCID) + println("Icon size" + iconBytes.size) + + val outFile = File(iconsDownloadPath, iconCID) + outFile.createNewFile() + outFile.writeBytes(iconBytes) + } catch (e: Exception) { + println("could not fetch icon from IPFS") + } + } + val width = it["width"] val height = it["height"] if (width != null || height != null) { @@ -143,14 +167,14 @@ fun checkChain(chainFile: File, connectRPC: Boolean) { if (chainFile.nameWithoutExtension.startsWith("eip155-")) { if (chainAsLong != chainFile.nameWithoutExtension.replace("eip155-", "").toLongOrNull()) { - throw(FileNameMustMatchChainId()) + throw (FileNameMustMatchChainId()) } } else { - throw(UnsupportedNamespace()) + throw (UnsupportedNamespace()) } if (chainFile.extension != "json") { - throw(ExtensionMustBeJSON()) + throw (ExtensionMustBeJSON()) } getNumber(jsonObject, "networkId") @@ -183,7 +207,7 @@ fun checkChain(chainFile: File, connectRPC: Boolean) { if (symbol.length >= 7) { throw NativeCurrencySymbolMustHaveLessThan7Chars() } - if (it.keys != setOf("symbol","decimals","name")) { + if (it.keys != setOf("symbol", "decimals", "name")) { throw NativeCurrencyCanOnlyHaveSymbolNameAndDecimals() } if (it["decimals"] !is Int) { @@ -205,20 +229,20 @@ fun checkChain(chainFile: File, connectRPC: Boolean) { } if (explorer["name"] == null) { - throw(ExplorerMustHaveName()) + throw (ExplorerMustHaveName()) } val url = explorer["url"] if (url == null || url !is String || !url.startsWith("https://")) { - throw(ExplorerMustWithHttps()) + throw (ExplorerMustWithHttps()) } if (url.endsWith("/")) { - throw(ExplorerCannotEndInSlash()) + throw (ExplorerCannotEndInSlash()) } if (explorer["standard"] != "EIP3091" && explorer["standard"] != "none") { - throw(ExplorerStandardMustBeEIP3091OrNone()) + throw (ExplorerStandardMustBeEIP3091OrNone()) } } } @@ -239,7 +263,7 @@ fun checkChain(chainFile: File, connectRPC: Boolean) { if (it !is String) { throw StatusMustBeString() } - if (!setOf("incubating","active","deprecated").contains(it)) { + if (!setOf("incubating", "active", "deprecated").contains(it)) { throw StatusMustBeIncubatingActiveOrDeprecated() } } @@ -286,7 +310,7 @@ fun checkChain(chainFile: File, connectRPC: Boolean) { if (jsonObject["rpc"] is List<*>) { (jsonObject["rpc"] as List<*>).forEach { if (it !is String) { - throw(RPCMustBeListOfStrings()) + throw (RPCMustBeListOfStrings()) } else { println("connecting to $it") val ethereumRPC = HttpEthereumRPC(it) @@ -297,7 +321,7 @@ fun checkChain(chainFile: File, connectRPC: Boolean) { } println() } else { - throw(RPCMustBeList()) + throw (RPCMustBeList()) } } } @@ -328,6 +352,6 @@ private fun getNumber(jsonObject: JsonObject, field: String): Long { return when (val chainId = jsonObject[field]) { is Int -> chainId.toLong() is Long -> chainId - else -> throw(Exception("chain_id must be a number")) + else -> throw (Exception("chain_id must be a number")) } } \ No newline at end of file