Skip to content

Latest commit

 

History

History
241 lines (185 loc) · 10.5 KB

README.md

File metadata and controls

241 lines (185 loc) · 10.5 KB

Public Release Announcement

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

Installation

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
}

Quick Start

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()
}

Examples & Documentation

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

Plugins

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

Using GenericMap

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>

Memory Management

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
}

Development

Build

Run the following to compile the project:

./gradlew assemble

Test

Run the following to run all checks:

./gradlew jvmTest

Lint

To lint the project, run the following:

./gradlew ktlintCheck

To auto-fix lint errors:

./gradlew ktlintFormat

Resources