Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/seniorjoinu/candid-kt

Lets you write Kotlin apps which interact with the Internet Computer
https://github.com/seniorjoinu/candid-kt

android candid dfinity ic kotlin

Last synced: about 1 month ago
JSON representation

Lets you write Kotlin apps which interact with the Internet Computer

Lists

README

        

[![](https://jitci.com/gh/seniorjoinu/candid-kt/svg?style=flat-square)](https://jitci.com/gh/seniorjoinu/candid-kt)
[![Release](https://jitpack.io/v/seniorjoinu/candid-kt.svg?style=flat-square)](https://jitpack.io/#seniorjoinu/candid-kt)


### Candid-kt
Generates client code for your canisters

### Usage

Use [the gradle plugin](https://github.com/seniorjoinu/candid-kt-gradle-plugin) to generate Kotlin code out of candid code.

For example, this candid code
```
type Phone = nat;
type Name = text;
type Entry =
record {
description: text;
name: Name;
phone: Phone;
};
service : {
insert: (Name, text, Phone) -> ();
lookup: (Name) -> (opt Entry) query;
}
```
would generate this Kotlin code
```kotlin
typealias Phone = BigInteger

val PhoneValueSer: ValueSer = NatValueSer

typealias Name = String

val NameValueSer: ValueSer = TextValueSer

data class Entry(
val name: Name,
val description: String,
val phone: Phone
)

object EntryValueSer : ValueSer {
val nameValueSer: ValueSer = NameValueSer

val descriptionValueSer: ValueSer = TextValueSer

val phoneValueSer: ValueSer = PhoneValueSer

override fun calcSizeBytes(value: Entry): Int = this.nameValueSer.calcSizeBytes(value.name) +
this.descriptionValueSer.calcSizeBytes(value.description) +
this.phoneValueSer.calcSizeBytes(value.phone)

override fun ser(buf: ByteBuffer, value: Entry) {
this.nameValueSer.ser(buf, value.name)
this.descriptionValueSer.ser(buf, value.description)
this.phoneValueSer.ser(buf, value.phone)
}

override fun deser(buf: ByteBuffer): Entry = Entry(this.nameValueSer.deser(buf),
this.descriptionValueSer.deser(buf), this.phoneValueSer.deser(buf))

override fun poetize(): String = Code.of("%T", EntryValueSer::class)
}

typealias PhonebookServiceValueSer = ServiceValueSer

typealias AnonFunc0ValueSer = FuncValueSer

class AnonFunc0(
funcName: String?,
service: SimpleIDLService?
) : SimpleIDLFunc(funcName, service) {
suspend operator fun invoke(
arg0: Name,
arg1: String,
arg2: Phone
) {
val arg0ValueSer = NameValueSer
val arg1ValueSer = senior.joinu.candid.serialize.TextValueSer
val arg2ValueSer = PhoneValueSer
val valueSizeBytes = 0 + arg0ValueSer.calcSizeBytes(arg0) + arg1ValueSer.calcSizeBytes(arg1) +
arg2ValueSer.calcSizeBytes(arg2)
val sendBuf = ByteBuffer.allocate(staticPayload.size + valueSizeBytes)
sendBuf.order(ByteOrder.LITTLE_ENDIAN)
sendBuf.put(staticPayload)
arg0ValueSer.ser(sendBuf, arg0)
arg1ValueSer.ser(sendBuf, arg1)
arg2ValueSer.ser(sendBuf, arg2)
val sendBytes = sendBuf.array()

val receiveBytes = this.service!!.call(this.funcName!!, sendBytes)
val receiveBuf = ByteBuffer.wrap(receiveBytes)
receiveBuf.order(ByteOrder.LITTLE_ENDIAN)
receiveBuf.rewind()
val deserContext = TypeDeser.deserUntilM(receiveBuf)
}

companion object {
val staticPayload: ByteArray = Base64.getDecoder().decode("RElETAADcXF9")
}
}

typealias AnonFunc1ValueSer = FuncValueSer

class AnonFunc1(
funcName: String?,
service: SimpleIDLService?
) : SimpleIDLFunc(funcName, service) {
suspend operator fun invoke(arg0: Name): Entry? {
val arg0ValueSer = NameValueSer
val valueSizeBytes = 0 + arg0ValueSer.calcSizeBytes(arg0)
val sendBuf = ByteBuffer.allocate(staticPayload.size + valueSizeBytes)
sendBuf.order(ByteOrder.LITTLE_ENDIAN)
sendBuf.put(staticPayload)
arg0ValueSer.ser(sendBuf, arg0)
val sendBytes = sendBuf.array()

val receiveBytes = this.service!!.query(this.funcName!!, sendBytes)
val receiveBuf = ByteBuffer.wrap(receiveBytes)
receiveBuf.order(ByteOrder.LITTLE_ENDIAN)
receiveBuf.rewind()
val deserContext = TypeDeser.deserUntilM(receiveBuf)
return senior.joinu.candid.serialize.OptValueSer( EntryValueSer ).deser(receiveBuf) as Entry?
}

companion object {
val staticPayload: ByteArray = Base64.getDecoder().decode("RElETAABcQ==")
}
}

class PhonebookService(
host: String,
canisterId: SimpleIDLPrincipal?,
keyPair: EdDSAKeyPair?,
apiVersion: String = "v1"
) : SimpleIDLService(host, canisterId, keyPair, apiVersion) {
val insert: AnonFunc0 = AnonFunc0("insert", this)

val lookup: AnonFunc1 = AnonFunc1("lookup", this)
}
```
which we then can use to interact with our deployed canister
```kotlin
val host = "http://localhost:8000"
val keys = EdDSAKeyPair.generateInsecure()
val canisterId = "75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q"

val phonebook = PhonebookService(host, SimpleIDLPrincipal.fromText(canisterId), keys)

phonebook.insert("test", "test desc", BigInteger("12345"))
val entry = phonebook.lookup("test")

check(entry != null) { "Entry not found" }
```

### Pros
* Idiomatic Kotlin
* Complete type-safety
* Asynchronous io with coroutines
* Reflectionless single-allocation (de)serialization

### Cons
* Unstable

### Type conversion rules
| IDL | Kotlin |
| --- | --- |
| type T = "existing type" | typealias T = "existing type" |
| int, nat | `BigInteger` |
| int8, nat8 | `Byte` |
| int16, nat16 | `Short` |
| int32, nat32 | `Int` |
| int64, nat64 | `Long` |
| float32 | `Float` |
| float64 | `Double` |
| bool | `Boolean` |
| text | `String` |
| null | `Null` object |
| reserved | `Reserved` object |
| empty | `Empty` object |
| opt T | `T?` |
| vec T | `List` |
| type T = record { a: T1, b: T2 } | `data class T(val a: T1, val b: T2)` |
| type T = variant { A, B: T1 } | `sealed class T { data class A: T(); data class B(val value: T1): T() }` |
| type T = func (T1) -> T2 | `class T { suspend operator fun invoke(arg0: T1): T2 }` |
| type T = service { a: SomeFunc } | `class T { val a: SomeFunc }` |
| principal | `Principal` class |
Unnamed IDL types are transpiled into anonymous Kotlin types.