Polywrap is a developer tool that enables easy integration of Web3 protocols into any application. It makes it possible for applications on any platform, written in any language, to read and write data to Web3 protocols.
This repository hosts a Kotlin implementation of the Polywrap Client for Android/JVM.
The readiness of the Kotlin client is tracked at https://github.com/polywrap/client-readiness
Kotlin
plugins {
kotlin("plugin.serialization") version "1.x.x" // Required for serialization
}
dependencies {
implementation("io.polywrap:polywrap-client:0.10.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.x.x") // Required for serialization
}
Groovy
plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version '1.x.x' // Required for serialization
}
dependencies {
implementation "io.polywrap:polywrap-client:0.10.4"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.x.x" // Required for serialization
}
The following examples can be found in SanityClientTest. Many more examples are available in the client section of the tests and the Kotlin client's section of the client readiness test harness.
import io.polywrap.configBuilder.ConfigBuilder
import io.polywrap.configBuilder.polywrapClient
import io.polywrap.core.InvokeResult
import io.polywrap.core.resolution.Uri
import kotlinx.serialization.Serializable
private val sha3Uri = Uri("ipfs/QmThRxFfr7Hj9Mq6WmcGXjkRrgqMG3oD93SLX27tinQWy5")
@Test
fun invokeWithMapStringAnyArgs() {
// instantiate the client with default configuration using the polywrapClient builder DSL
val client = polywrapClient {
addDefaults()
}
// invoke the sha3 wrap with a Map<String, Any> of arguments
val result = client.invoke<String>(
uri = sha3Uri,
method = "keccak_256",
args = mapOf("message" to "Hello World!")
)
assertNull(result.exceptionOrNull())
val hash = result.getOrThrow()
// to prevent a memory leak, close the client when you're done with it
// this typically happens at the end of your application's lifecycle
client.close()
}
@Test
fun invokeWithReifiedTypes() {
@Serializable
data class Keccak256Args(val message: String)
// instantiate the client with default configuration using the builder pattern
val client = ConfigBuilder().addDefaults().build()
// invoke the sha3 wrap with a serializable type
val result: InvokeResult<String> = client.invoke(
uri = sha3Uri,
method = "keccak_256",
args = Keccak256Args("Hello World!")
)
assertNull(result.exceptionOrNull())
val hash = result.getOrThrow()
}
Reference documentation is hosted at https://kotlin.client.polywrap.io/
Example | Platform | Description | Location |
---|---|---|---|
IPFS | Android | Add a file to IPFS and then retrieve it | Link |
Ethereum | JVM | Sign typed data with the Ethers wrap | Link |
ENS | JVM | Get the content hash of an ENS address | examples/ens |
File System | JVM | Write a file to the file system, read its contents, then remove it | examples/fileSystem |
HTTP | JVM | Make an HTTP request | examples/http |
Hello World | JVM | Print a message to the console | Link |
The Kotlin client supports plugins that can be used to extend its functionality.
Some plugins are written in Rust and packaged with the Polywrap Client.
- System Bundle: File-system and HTTP plugins
- Web3 Bundle: Ethereum wallet plugin
The Rust Ethereum Wallet plugin is not configurable from Kotlin. For custom configuration, use the Kotlin Ethereum Wallet plugin instead.
These plugins can be loaded with the addDefaults
method of the ConfigBuilder
, or added using the addBundle
method.
val client = polywrapClient { addDefaults() }
val client = polywrapClient {
addBundle(NativeBundle.System)
addBundle(NativeBundle.Web3)
}
The Rust plugins are located in the Rust client repo.
Other plugins are written in Kotlin. The available Kotlin plugins include:
Plugin | Description | Maven publication | Location |
---|---|---|---|
Ethereum Wallet | Support the Ethers wrap with an configurable Ethereum signer or RPC connection | io.polywrap.plugins:ethereum-wallet:0.10.4 | Link |
Logger | Enable logging in Wasm wraps with SL4J | io.polywrap.plugins:logger:0.10.4 | Link |
File System | Interact with the host file system | io.polywrap.plugins:file-system:0.10.4 | Link |
HTTP | Send HTTP requests | io.polywrap.plugins:http:0.10.4 | Link |
The GenericMap
type is implemented as an extension type for MessagePack serialization.
@Serializable(with = GenericMapExtensionSerializer::class)
data class GenericMap<K, V>(val map: Map<K, V>)
In practice, this means you must wrap a Map<K, V>
in a GenericMap<K, V>
before passing it to the client.
val myMap: Map<String, Int> = mapOf("Hello" to 1, "Heyo" to 50)
val genericMap: GenericMap<String, Int> = GenericMap(myMap)
val alsoGenericMap: GenericMap<String, Int> = myMap.toGenericMap()
val myMapReference: Map<String, Int> = genercMap.map
It also means the client will return a GenericMap<K, V>
, not a Map<K,V>
.
@Serializable
data class ArgsReturnMap(val map: GenericMap<String, Int>)
@Test
fun testReturnMap() = runTest {
val genericMap = mapOf("Hello" to 1, "Heyo" to 50).toGenericMap()
val result: InvokeResult<GenericMap<String, Int>> = client.invoke(
uri = uri,
method = "returnMap",
args = ArgsReturnMap(genericMap)
)
if (result.isFailure) throw result.exceptionOrNull()!!
assertEquals(genericMap, result.getOrThrow())
}
A contextual serializer is provided for GenericMap<K, V>
to help the kotlinx.serialization
framework find the GenericMapExtensionSerializer
serializer when the @Contextual
annotation is used.
@Serializable
data class ArgsReturnMap(
@Contextual
val map: GenericMap<String, Int>
)
It is often preferable to annotate a typealias to help the kotlinx.serialization
framework find the serializer.
typealias GenericMap<K, V> = @Serializable(with = GenericMapExtensionSerializer::class) io.polywrap.core.msgpack.GenericMap<K,V>
The Kotlin client relies on a native library containing the Rust version of the Polywrap Client. Some Kotlin objects contain pointers to natively allocated memory. These objects implement the Autoclosable
interface and should be closed when the memory resources are no longer needed. Once closed, these objects cannot be used again.
The client ConfigBuilder
takes ownership of its configuration and closes those resources for you. In most cases, the client is the only object that needs to be manually closed.
val client = polywrapClient {
addDefaults()
}
// do stuff with the client
// ...
// close the client to avoid a memory leak
client.close()
val client = ConfigBuilder().addDefaults().build()
// the autocloseable interface provides the `use` extension function,
// which closes the resource when the block completes (even if an exception is thrown)
client.use {
// do stuff with the client
}
Run the following to compile the project:
./gradlew assemble
Run the following to run all checks:
./gradlew jvmTest
To lint the project, run the following:
./gradlew ktlintCheck
To auto-fix lint errors:
./gradlew ktlintFormat